Windows 10 IoT Core–More Baby Steps with AllJoyn

Following up on my previous post, I thought it would be ‘good’ to experiment with how I could differentiate between multiple running instances of my amazing ‘lightbulb’ service because it doesn’t seem unreasonable that I might have more than one instance of this service on my network or, even, different implementations of the service.

Initially, I wondered whether I could do this by publishing some kind of ‘location name’ via AllJoyn’s metadata and, at the time of writing, I’m not sure. I had a look at the AboutData that I can use to advertise my service;

https://allseenalliance.org/developers/learn/core/about-announcement/interface#about-data-interface-fields

but none of those fields felt like the “right” place to put an instance specific identifier for my service and so I changed my interface such that it could return a “location” property.

I’m not likely to win any awards for this interface but here’s the new version. I should have called the method GetLocation but I didn’t;

<node name="/com/taulty/lightbulb">
  <interface name="com.taulty.lightbulb">
    <method name="switch">
	<arg name="on" type="b" direction="in"/>
    </method>
    <method name="location">
	<arg name="returnvalue" type="s" direction="out"/>
    </method>
  </interface>
</node>

I rebuilt the generated code using the alljoyncodegen.exe tool and then I altered the universal app that I’d written to provide an implementation of this lightbulb interface on Windows IoT Core on Raspberry PI 2.

My intention was to make it such that this app could be run both on the Raspberry PI 2 where it would switch on a real LED but also on my laptop where the implementation would just toggle something visual on the screen in response to switching the lightbulb on/off.

That’s easy enough to do. I altered the XAML for this universal app to just have 2 simple grids;

<UserControl
    x:Class="LightbulbService.MainControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:LightbulbService"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

  <Grid Padding="40">
    <Grid x:Name="iotGrid" Visibility="Collapsed">
      <Viewbox>
        <StackPanel>
          <TextBlock Text="started advertising as lounge location" TextAlignment="Center"/>
          <TextBlock Text="LED will light up when request is received" TextAlignment="Center"/>
        </StackPanel>
      </Viewbox>
    </Grid>
    <Grid x:Name="noniotGrid" Visibility="Collapsed">
      <Viewbox>
        <StackPanel>
          <TextBlock Text="started advertising as kitchen location" TextAlignment="Center"/>
          <TextBlock Text="background will change colour when request is received" TextAlignment="Center"/>
        </StackPanel>
      </Viewbox>
    </Grid>
  </Grid>
</UserControl>

And then I tweaked the code behind to toggle behaviour between “running in IoT mode” and “running in desktop mode” as;

namespace LightbulbService
{
  using Windows.Devices.AllJoyn;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using com.taulty;
  using System;
  using Windows.Foundation;
  using System.Threading.Tasks;
  using Windows.UI.Core;
  using Windows.UI.Xaml.Media;
  using Windows.UI;

  public sealed partial class MainControl : UserControl, IlightbulbService
  {
    public MainControl()
    {
      this.InitializeComponent();
      this.Loaded += OnLoaded;
    }
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
      // NB: I was hoping that the first part of this IF statement would return true on
      // Windows IoT Core but it seems not to and so I've added the OR clause which I
      // didn't really want to.
      Grid grid = this.noniotGrid;
      IlightbulbService service = this;

      if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent(
        "Windows.Devices.DevicesLowLevelContract", 1) ||
        Windows.Foundation.Metadata.ApiInformation.IsTypePresent(
        "Windows.Devices.Gpio.GpioController"))
      {
        grid = this.iotGrid;
        service = new GpioLightbulbService(GPIO_LED_PIN, PI_LOCATION);
      }
      grid.Visibility = Visibility.Visible;

      AllJoynBusAttachment busAttachment = new AllJoynBusAttachment();
      busAttachment.AuthenticationMechanisms.Add(AllJoynAuthenticationMechanism.SrpAnonymous);
      lightbulbProducer producer = new lightbulbProducer(busAttachment);
      producer.Service = service;
      producer.Start();
    }
    public IAsyncOperation<lightbulbLocationResult> LocationAsync(AllJoynMessageInfo info)
    {
      return (LocationAsync().AsAsyncOperation());
    }
    static async Task<lightbulbLocationResult> LocationAsync()
    {
      return (lightbulbLocationResult.CreateSuccessResult(THIS_LOCATION));
    }
    public IAsyncOperation<lightbulbSwitchResult> SwitchAsync(AllJoynMessageInfo info, bool interface_on)
    {
      return (SwitchAsync(interface_on).AsAsyncOperation());
    }
    async Task<lightbulbSwitchResult> SwitchAsync(bool on)
    {
      await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
        () =>
        {
          noniotGrid.Background = new SolidColorBrush(on ? Colors.Green : Colors.Red);
        }
      );
      return (lightbulbSwitchResult.CreateSuccessResult());
    }
    static readonly int GPIO_LED_PIN = 5;
    static readonly string THIS_LOCATION = "kitchen";
    static readonly string PI_LOCATION = "lounge";

  }
}

So, this code either advertises the MainPage class itself as an implementation of a lightbulb service where on/off values simply toggle the background colour between red/green or if it’s running on Windows IoT Core it’ll advertise this GpioLightbulbService as the implementation which toggles values on GPIO pin 5;

namespace LightbulbService
{
  using com.taulty;
  using System;
  using System.Threading.Tasks;
  using Windows.Devices.AllJoyn;
  using Windows.Devices.Gpio;
  using Windows.Foundation;

  class GpioLightbulbService : IlightbulbService
  {
    public GpioLightbulbService(int gpioPin, string locationName)
    {
      this.gpioPin = gpioPin;
      this.locationName = locationName;

      gpioController = new Lazy<GpioController>(() =>
      {
        return (GpioController.GetDefault());
      });
      ledPin = new Lazy<GpioPin>(() =>
      {
        var pin = gpioController.Value.OpenPin(this.gpioPin);
        pin.SetDriveMode(GpioPinDriveMode.Output);
        return (pin);
      });
    }
    public IAsyncOperation<lightbulbSwitchResult> SwitchAsync(AllJoynMessageInfo info, 
      bool switchOn)
    {
      return (this.SwitchAsync(switchOn).AsAsyncOperation());
    }
    async Task<lightbulbSwitchResult> SwitchAsync(bool switchOn)
    {
      ledPin.Value.Write(switchOn ? GpioPinValue.Low : GpioPinValue.High);
      return (lightbulbSwitchResult.CreateSuccessResult());
    }
    public IAsyncOperation<lightbulbLocationResult> LocationAsync(AllJoynMessageInfo info)
    {
      return (this.LocationAsync().AsAsyncOperation());
    }
    async Task<lightbulbLocationResult> LocationAsync()
    {
      return (lightbulbLocationResult.CreateSuccessResult(this.locationName));
    }
    int gpioPin;
    string locationName;
    Lazy<GpioController> gpioController;
    Lazy<GpioPin> ledPin;
  }
}

Which is just a minor re-working of the code from the previous blog post.

With that in play, I can run up both of these implementations at the same time with one running on my local laptop and the other on my Raspberry PI 2 respectively and I can then use the AllJoyn Explorer to see them.

NB: I used the CheckNetIsolation tool to ensure that (even outside of VS) I had enabled loopback networking for the relevant apps here as I mentioned in the previous blog post;

Here’s the AllJoyn Explorer seeing my 2 implementations;

image

Where the rid circle is the implementation coming from the Raspberry PI 2 and the yellow highlight is the local PC and both are running the same app from the code above.

I can then use the explorer to ask for the location of the local service;

clip_image002

And that returns;

image

And then I can invoke the light on method and the background goes green on my ‘fake’ lightbulb;

image

And if I use the Raspberry PI implementation, my LED goes on and off as it did previously.

With that in place, I can try and alter my client such that it picks up this new location value and displays it in the UI which is such a minor change to the previous client code that I won’t paste it here.

With that done though, I find that my client app only displays a single implementation of the ‘lightbulb’ service as in the screenshot;

clip_image002

In digging in to this in the debugger, I find that my code which is running in response to the Added handler on my lightbulbWatcher instance is firing twice;

clip_image004

and the first time that code runs, it makes a call to lightbulbConsumer.JoinSessionAsync and that returns a 0 success value.

However, when the second implementation of the service comes along and announces itself, I find that the call to JoinSessionAsync returns a value of 36928 which doesn’t seem to correspond to any of the error values that I could find.

At the time of writing then I can’t use this mechanism to discover 2 instances of my lightbulb service on the network and yet the AllJoyn Explorer is doing just that which makes me wonder whether there’s some other way that I can discover my service which might work?

Firstly, I discovered at least one way to get to the AboutData that my service is publishing – by making a call to AllJoynAboutDataView.GetDataBySessionPortAsync at the time when my service gets Added to my watcher I can get the about data detail;

image

I also experimented with watching my AllJoynBusAttachment fire StateChanged events to watch it go through its states of Connecting, Connected and so on.

Beyond that, my initial explore around the API gave me the (possibly wrong) impression that it’s specifically set up to work at a higher level while delegating down the harder work to the underlying generated code which I think then sits on top of the C API. That is – it doesn’t feel like I could maybe write the AllJoyn Explorer using this API?

One advantage though is that at least those calls into the C API are in front of me – they’re in the generated code and I could see via native debugging that a call to alljoyn_busattachment_registerbusobject was failing when my code was trying to join a session with the second implementation of the service that it found.

What that didn’t tell me was why it was failing or what the error code might mean but it did lead me to status.h from AllJoyn which told me that 36928 (or 0x9040) meant;

ER_BUS_OBJ_ALREADY_EXISTS = 0x9040

/**< Attempt to add a RemoteObject child that already exists

which was helpful because, previously, I’d been staring at the WinRT projections of these errors and trying to figure out why this one wasn’t listed anywhere.

This made me wonder – maybe I’m only supposed to have a session with one of these objects at a time in this model?

I re-worked the client side code along those lines such that I only attempt to join a session with one lightbulb at a time when I’m actually asking it to do something on my behalf such as tell me its location or switch on/off and that seemed to work quite a bit better.

Consequently, I changed my XAML a little for the light switching app – I won’t paste the whole thing here, I just changed the data template for the list;

<DataTemplate>
          <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding ServiceInfo.UniqueName}"/>
            <TextBlock Margin="2,0,2,0" Text="in"></TextBlock>
            <TextBlock Text="{Binding Location}"/>
            <TextBlock Margin="2,0,2,0" Text="made by"></TextBlock>
            <TextBlock Margin="2,0,2,0" Text="{Binding AboutData.Manufacturer}" />
          </StackPanel>
        </DataTemplate>

And I then changed the code behind a little;

namespace lightswitch
{
  using com.taulty;
  using System;
  using System.Collections.ObjectModel;
  using System.ComponentModel;
  using System.Linq;
  using Windows.Devices.AllJoyn;
  using Windows.UI.Core;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;

  public sealed partial class MainPage : Page, INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

    public class LightbulbEntry
    {
      public AllJoynAboutDataView AboutData { get; set; }
      public AllJoynServiceInfo ServiceInfo { get; set; }
      public string Location { get; set; }
    }
    public MainPage()
    {
      this.InitializeComponent();
      this.Loaded += OnLoaded;
      this.Lightbulbs = new ObservableCollection<LightbulbEntry>();
    }
    void OnLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
    {
      this.DataContext = this;

      this.StartDiscoveringLightbulbs();
    }
    void StartDiscoveringLightbulbs()
    {
      this.busAttachment = new AllJoynBusAttachment();

      this.busAttachment.AuthenticationMechanisms.Add(AllJoynAuthenticationMechanism.SrpAnonymous);
     
      this.lightbulbWatcher = new lightbulbWatcher(this.busAttachment);

      this.lightbulbWatcher.Added += OnAdded;

      this.lightbulbWatcher.Start();
    }
    async void OnAdded(lightbulbWatcher sender, AllJoynServiceInfo args)
    {
      await this.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        async () =>
        {
          var aboutData = await AllJoynAboutDataView.GetDataBySessionPortAsync(
            args.UniqueName, this.busAttachment, args.SessionPort);

          string location = string.Empty;

          var session = await lightbulbConsumer.JoinSessionAsync(args, sender);

          if (session.Status == AllJoynStatus.Ok)
          {
            var locationResult = await session.Consumer.LocationAsync();

            if (locationResult.Status == AllJoynStatus.Ok)
            {
              location = locationResult.Returnvalue;
            }
            session.Consumer.Dispose();
          }

          this.Lightbulbs.Add(
            new LightbulbEntry()
            {
              Location = location,
              AboutData = aboutData,
              ServiceInfo = args
            }
          );
        }
      );
    }
    public LightbulbEntry SelectedLightbulb
    {
      get
      {
        return (this.selectedLightbulb);
      }
      set
      {
        if (this.selectedLightbulb != value)
        {
          this.selectedLightbulb = value;
          this.RaisePropertyChanged("SelectedLightbulb");
        }
      }
    }
    void RaisePropertyChanged(string property)
    {
      var changeWatchers = this.PropertyChanged;
      if (changeWatchers != null)
      {
        changeWatchers(this, new PropertyChangedEventArgs(property));
      }
    }
    async void OnLightswitchToggled(object sender, RoutedEventArgs e)
    {
      if (this.SelectedLightbulb != null)
      {
        var sessionResult = await lightbulbConsumer.JoinSessionAsync(
          this.selectedLightbulb.ServiceInfo, this.lightbulbWatcher);

        if (sessionResult.Status == AllJoynStatus.Ok)
        {
          bool value = ((ToggleSwitch)sender).IsOn;
          await sessionResult.Consumer.SwitchAsync(value);

          sessionResult.Consumer.Dispose();
        }
      }
    }
    public ObservableCollection<LightbulbEntry> Lightbulbs { get; private set; }
    AllJoynBusAttachment busAttachment;
    lightbulbWatcher lightbulbWatcher;
    LightbulbEntry selectedLightbulb;
  }
}

And, sure enough, whether I have things correct or not I can get the UI to show multiple ‘lightbulbs’ and I can switch them on and off in this way. Here’s a static screenshot of the ‘light switch’ app running alongside the local ‘light bulb’ app and switching it on;

image

As I’m sure you can tell from the blog post, this is all very ‘rough and ready’ work on my part but I thought I may as well share experiments in case they help anyone else out.