Band 2 SDK Update–Tile notifications in the background

Following on from my first post on Band 2 development, I noticed that the Band 2 SDK had been updated.

The new capability that interested me is one that’s been added whereby a UWP app can receive tile events from a Band 2 even if the foreground app isn’t running.

To experiment, I took my project from that previous post and I updated the Microsoft.Band NuGet package to be at version 1.3.20217 and then I went off to read the SDK docs to see what had changed.

Section 9.3.2 of those docs say that there’s a new capability whereby a UWP app can expose an App Service which can then be invoked by the Health App in order that tile notifications for that specific app can be routed to its service. It’s an interesting architecture and one of the first places where I’ve been offered a true ‘App Service’ powered extensibility point so that’s nice to see.

So, largely just following those docs I changed the manifest for my project such that it included;

image

where the name com.microsoft.band.observer is pre-defined to link up with something that must be built into the Microsoft Health app.

From there, I changed the code that I’d previously written which created a square tile on my band and I added just the one line of code advised by the SDK docs which I’ve tried to highlight clearly below – this code lives behind 2 buttons on ‘a UI’ which are labelled ‘Create’ and ‘Remove’ respectively;

namespace App320
{
  using Microsoft.Band;
  using Microsoft.Band.Tiles;
  using System;
  using System.Linq;
  using Windows.Storage;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Media.Imaging;
  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
    }
    async void OnCreateTile(object sender, RoutedEventArgs args)
    {
      var bands = await BandClientManager.Instance.GetBandsAsync();

      if (bands?.Count() > 0)
      {
        this.client = await BandClientManager.Instance.ConnectAsync(bands.First());

        var tileSpace = await this.client.TileManager.GetRemainingTileCapacityAsync();

        if (tileSpace > 0)
        {
          var iconFile = await StorageFile.GetFileFromApplicationUriAsync(
            new Uri("ms-appx:///Assets/tileicon.png"));

          var smallIconFile = await StorageFile.GetFileFromApplicationUriAsync(
            new Uri("ms-appx:///Assets/smalltileicon.png"));

          using (var stream = await iconFile.OpenReadAsync())
          {
            using (var smallStream = await smallIconFile.OpenReadAsync())
            {
              var largeBitmap = new WriteableBitmap(48, 48);
              largeBitmap.SetSource(stream);
              var largeIcon = largeBitmap.ToBandIcon();

              var smallBitmap = new WriteableBitmap(24, 24);
              smallBitmap.SetSource(smallStream);
              var smallIcon = smallBitmap.ToBandIcon();

              this.tileGuid = Guid.NewGuid();

              var bandTile = new BandTile(this.tileGuid)
              {
                Name = "Test",
                TileIcon = largeIcon,
                SmallIcon = smallIcon
              };
              var added = await this.client.TileManager.AddTileAsync(bandTile);

              // NEW NEW NEW.
              // This is new.
              // NEW NEW NEW.
              // Is that clearly labelled enough? 🙂
              await this.client.SubscribeToBackgroundTileEventsAsync(this.tileGuid);
            }
          }
        }
      }
    }
    async void OnRemove(object sender, RoutedEventArgs e)
    {
      await this.client.TileManager.RemoveTileAsync(this.tileGuid);
    }
    IBandClient client;
    Guid tileGuid;
  }
}

With that set up, it’s my responsibility to build a background task (i.e. implementation of IBackgroundTask) that offers up an App Service which the Microsoft Health app can invoke.

So, I made a new “Windows Runtime Component” project in Visual Studio, added it to my solution, referenced it from my original project so that it would be packaged and deployed with the rest of my bits. Here’s the first version of the code;

namespace TileBackgroundComponent
{
  using System.Diagnostics;
  using Windows.ApplicationModel.AppService;
  using Windows.ApplicationModel.Background;
  public sealed class TheTask : IBackgroundTask
  {
    static readonly string BAND_OBSERVER_SERVICE_NAME = "com.microsoft.band.observer";

    public async void Run(IBackgroundTaskInstance taskInstance)
    {
      this.deferral = taskInstance.GetDeferral();
      taskInstance.Canceled += OnCancelled;

      var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails;

      if (triggerDetails.Name == BAND_OBSERVER_SERVICE_NAME)
      {
        triggerDetails.AppServiceConnection.RequestReceived += this.OnRequestReceived;
      }
    }
    void OnRequestReceived(AppServiceConnection sender,
      AppServiceRequestReceivedEventArgs args)
    {
      foreach (var key in args.Request.Message.Keys)
      {
        Debug.WriteLine($"{key} has value {args.Request.Message[key]}");
      }
    }
    void OnCancelled(IBackgroundTaskInstance sender,
      BackgroundTaskCancellationReason reason)
    {
      this.deferral?.Complete();
      this.deferral = null;
    }
    BackgroundTaskDeferral deferral;  
  }
}

and I then used the UI to;

    • Create the tile on the band.
    • Press the tile on the band to open it up.
    • Remove the tile on the band.

and, interestingly, the debug output from this is;

Timestamp has value 03/03/2016 08:59:45 +00:00
Sequence# has value 4
TileId has value ed0ed23b-f431-4c38-84e2-3cba98f2c4bb
Type has value TileOpenedEvent
Timestamp has value 03/03/2016 09:00:05 +00:00
Sequence# has value 5
TileId has value ed0ed23b-f431-4c38-84e2-3cba98f2c4bb
Type has value TileClosedEvent

and so, clearly, the application service call is sending over a ValueSet (dictionary) that has all the information within it to determine which tile is being referred to (I only have one) and what the event is – i.e. this one is a TileOpenedEvent.

So, it’s all there. I could, for instance, use the event here to pop up a toast notification on the phone. Here’s that example;

namespace TileBackgroundComponent
{
  using NotificationsExtensions.ToastContent;
  using Windows.ApplicationModel.AppService;
  using Windows.ApplicationModel.Background;
  using Windows.UI.Notifications;
  public sealed class TheTask : IBackgroundTask
  {
    static readonly string BAND_OBSERVER_SERVICE_NAME = "com.microsoft.band.observer";
    static readonly string EVENT_TYPE_KEY = "Type";
    static readonly string SEQUENCE_NUMBER_KEY = "Sequence#";
    const string EVENT_TYPE_OPENED = "TileOpenedEvent";

    public async void Run(IBackgroundTaskInstance taskInstance)
    {
      this.deferral = taskInstance.GetDeferral();
      taskInstance.Canceled += OnCancelled;
      
      var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails;

      if (triggerDetails.Name == BAND_OBSERVER_SERVICE_NAME)
      {
        triggerDetails.AppServiceConnection.RequestReceived += this.OnRequestReceived;
      }
    }
    void OnRequestReceived(AppServiceConnection sender,
      AppServiceRequestReceivedEventArgs args)
    {
      if (args.Request.Message.ContainsKey(EVENT_TYPE_KEY))
      {
        string eventType = (string)args.Request.Message[EVENT_TYPE_KEY];
        int sequenceNumber = (int)args.Request.Message[SEQUENCE_NUMBER_KEY];

        switch (eventType)
        {
          // NB: there is also a tile closed, and a tile button pressed
          // event.
          case EVENT_TYPE_OPENED:
            this.PopToast(sequenceNumber);
            break;
          default:
            break;
        }
      }
    }
    void OnCancelled(IBackgroundTaskInstance sender,
      BackgroundTaskCancellationReason reason)
    {
      this.deferral?.Complete();
      this.deferral = null;
    }
    void PopToast(int sequenceNumber)
    {

      var notifier = ToastNotificationManager.CreateToastNotifier("App");
      var toast = ToastContentFactory.CreateToastText01();
      toast.TextBodyWrap.Text = $"Sequence Number {sequenceNumber}";
      notifier.Show(toast.CreateNotification());
    }
    BackgroundTaskDeferral deferral;  
  }
}

and that works just fine but it’s a little bit reliant on some hard-coded strings and so on which I expect that the Band SDK team would rather you didn’t get involved in so it looks like they’ve been very kind and added a strongly typed helper to make it easier;

namespace TileBackgroundComponent
{
  using Microsoft.Band;
  using Microsoft.Band.Tiles;
  using NotificationsExtensions.ToastContent;
  using Windows.ApplicationModel.AppService;
  using Windows.ApplicationModel.Background;
  using Windows.UI.Notifications;
  public sealed class TheTask : IBackgroundTask
  {
    static readonly string BAND_OBSERVER_SERVICE_NAME = "com.microsoft.band.observer";

    public async void Run(IBackgroundTaskInstance taskInstance)
    {
      this.deferral = taskInstance.GetDeferral();
      taskInstance.Canceled += OnCancelled;
      
      var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails;

      if (triggerDetails.Name == BAND_OBSERVER_SERVICE_NAME)
      {
        triggerDetails.AppServiceConnection.RequestReceived += this.OnRequestReceived;
      }
    }
    void OnRequestReceived(AppServiceConnection sender,
      AppServiceRequestReceivedEventArgs args)
    {
      if (!this.addedHandlers)
      {
        this.addedHandlers = true;
        BackgroundTileEventHandler.Instance.TileOpened += this.OnTileOpened;
      }
      // Ask this class to figure out the details of the message that's
      // coming in.
      BackgroundTileEventHandler.Instance.HandleTileEvent(args.Request.Message);
    }

    private void OnTileOpened(object sender, BandTileEventArgs<IBandTileOpenedEvent> e)
    {
      // most scenarios would need this, I don't.
      var tileId = e.TileEvent.TileId;

      // NB: sequence number isn't part of this data so I'll pop 0.
      this.PopToast(0);
    }

    void OnCancelled(IBackgroundTaskInstance sender,
      BackgroundTaskCancellationReason reason)
    {
      this.deferral?.Complete();
      this.deferral = null;
    }
    void PopToast(int sequenceNumber)
    {
      var notifier = ToastNotificationManager.CreateToastNotifier("App");
      var toast = ToastContentFactory.CreateToastText01();
      toast.TextBodyWrap.Text = $"Sequence Number {sequenceNumber}";
      notifier.Show(toast.CreateNotification());
    }
    bool addedHandlers;
    BackgroundTaskDeferral deferral;  
  }
}

and that also works quite nicely. I’m not 100% sure when that event handler should be removed (or whether it should be removed).

I’m also not 100% sure about whether a response should be send back to the Microsoft Health app when a request is received – the code in the SDK documentation does send an (empty) response back to the sender so perhaps this code should be amended to do that.

But…the new bits in the SDK definitely work for me here – I can have a tile on my Band 2 talk to a background agent on my phone when the foreground app isn’t running and I think that’s a significant change in that it means that the Band 2 can truly initiate action on its buddy device without first expecting the user to run an app over there.

Updated to add a link to the VS project.

Band 2 Development–A First Try…

For a while, I’ve had it on my list to look at developing on the Microsoft Band 2. I skipped the first generation of the Band but I did jump in with the 2nd generation back in December and I like the device although I don’t use it to its full capacity, mainly using it for;

    • Sleep tracking
    • Workout tracking
    • Calendar, Mails, Text
    • Steps, Calendar, Calories

and it’s been working pretty well for me in that I get decent battery life from the device and it’s proved to be quite wearable once I’ve got used to its position on my wrist.

I’ll admit that I had a few ‘challenges’ with the device when I first got it in that some of the functionality didn’t work with my original Lumia 1020 phone for a reason that I never got to the bottom of and I found that quite frustrating. That said, I’ve since paired it with a Lumia 950XL and it’s worked fine with that device.

One of the things that I think causes some confusion around the Band 2 is the type of device that it is. I’ve talked to Microsoft folks who are convinced that the Band is a Windows 10 device and that it runs the Windows 10 OS and Universal Windows Platform (UWP) apps and, as you probably know if you’re reading this blog site, it isn’t and it doesn’t.

As an aside, it would be quite something to put the “UWP” onto a device like this. As has been said before, the Windows 10 app platform is made up of platforms (UWP, Mobile, Desktop, etc) and those platforms are made up contracts which are sets of APIs. The “UWP” is a contract and it contains most of the available APIs and so to put all of that functionality onto a device like a Band 2 would be “quite a piece of work”. I’m not saying it’s “impossible” but…

For me, I see the Band 2 as more of a ‘smart peripheral’. That is, it’s really a companion device for a piece of software running on another device (generally, a phone) but it does have some level of built-in functionality in that it can do things like;

    1. Guide you through a pre-defined workout.
    2. Run a stopwatch or a timer.
    3. Record details of your sleep or a run.
    4. Keep track of your activity like steps, calories, heart rate.

in isolation which is a good thing otherwise you’d have to take both the phone and the band with you any time you wanted to use the band.

When it comes to developing for the Band 2, there are 3 options outlined on the website.

    1. Use the Band SDK – write code to talk to the Band 2 over bluetooth from a device like a phone or PC (there’s an iOS, Android and Windows SDK).
    2. Make a ‘Web Tile’ – provide a URL that can be polled for data to display on a tile. The resulting tile can be manually installed to a Band 2 or it can be submitted to the gallery for a user to install themselves via the Health app on the Band 2.
    3. Work with the back-end data that’s ultimately gathered from a Band 2 and available over a REST API.

I think all of these have their place but it’s probably the SDK that’s of most interest to me and so I opened it up and tried out a few scenarios.

A big note – I’m not doing anything here that hasn’t been done many times before. I’ve watched developer sessions on the Band/Band 2. I’m mainly working through it here and writing it up in order to add to that weight of material but also so that it sticks in my head – I tend to find that if I haven’t written the code myself then I can’t remember how things work.

Installing the SDK

Installing the Band 2 SDK is as easy as installing the NuGet package Microsoft.Band which I did from the package manager console in Visual Studio and seemed to get ‘success’ !

image

At the time of writing, I always have a tense “Will it install?” moment when grabbing SDKs from Nuget and trying to install them into UWP projects so I was happy that this one seemed to “just work”.

The SDK docs are then a big PDF document that seems to do a pretty good job of explaining things but I’ll add my own little experiments below.

Connecting to a Band 2

The SDK walks you through getting a connection to a Band which I think can be summarised as;

      var bands = await BandClientManager.Instance.GetBandsAsync();
      
      if (bands?.Count() > 0)
      {
        var client = 
          await BandClientManager.Instance.ConnectAsync(bands.First());

        // Do something...
      }

and that was a piece of code that worked the first time I wrote it and ran it on my phone (which is paired with my Band 2). I should say that I’d modified my manifest to include;

image

but I didn’t manually hack the manifest XML file as suggested in the SDK, I just used the manifest designer here.

So, I’m connected to my Band 2. What can I now do?

Reading from Sensors

There are a bunch of sensors on the Band 2 and accessing them seems pretty simple. There’s even a ‘Sensor Manager’ that handles it for you although I’ve often been told off for naming classes “XYZManager” in the past Smile

If you imagine that the code below is from a code-behind class in XAML and that there is a TextBlock on screen called txtStatus;

      var bands = await BandClientManager.Instance.GetBandsAsync();
      
      if (bands?.Count() > 0)
      {
        var client = 
          await BandClientManager.Instance.ConnectAsync(bands.First());

        var contact = client.SensorManager.Contact;

        // Do something...
        DispatchedHandler handler = async () =>
        {
          var status = 
            await contact.GetCurrentStateAsync();

          this.txtStatus.Text = status.State !=
            BandContactState.Worn ? "Put your band back on now!" : "Glad you are wearing Band";
        };
        handler();

        contact.ReadingChanged += (s, e) =>
        {
          this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, handler);
        };
      }

then this code does what you expect – it monitors the user as they put their Band 2 on/off.

I found that code worked pretty well given that I’d spend maybe 1-2 minutes on it and apologies for the slightly obtuse (i.e. lazy) way that I’ve written and invoked the event handler inline inside of this function.

If I wanted to read from another sensor like the heart rate sensor then that’s pretty easy but it requires a couple of extra steps – I set up an interval for the reading to occur and I also have to ask for permission;

      var bands = await BandClientManager.Instance.GetBandsAsync();

      if (bands?.Count() > 0)
      {
        var client =
          await BandClientManager.Instance.ConnectAsync(bands.First());

        var hr = client.SensorManager.HeartRate;

        var allowed = (hr.GetCurrentUserConsent() == UserConsent.Granted);

        if (!allowed)
        {
          allowed = await hr.RequestUserConsentAsync();
        }
        if (allowed)
        {
          // choose the smallest interval
          hr.ReportingInterval = hr.SupportedReportingIntervals.OrderBy(i => i).First();

          await hr.StartReadingsAsync();

          hr.ReadingChanged += (s, e) =>
          {
            this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
              () =>
              {
                this.txtStatus.Text = e.SensorReading.HeartRate.ToString();
              });
          };
        }

but it’s still pretty easy and it looks like the other sensors including Accelerometer, Altimeter, Barometer, Calories, Distance, Gsr, Gyroscope, Pedometer, RRInterval, SkinTemperature and UV all follow a very similar model meaning that it’s a “learn once, repeat many” type process which I like a lot.

For an application that wants to run on my Phone/PC in either the foreground or the background and talk to my Band 2 for a while, gather some data from it and then “do something with it” like sending it to the cloud it doesn’t feel like it should be too difficult to do.

What’s not so clear to me is how the Band 2 might trigger something on the Phone/PC in a scenario where the Phone/PC wasn’t already “listening” for that action short of having the Phone/PC app running all the time which often isn’t a viable option. I’ll need to come back to that.

What else can be done? There are some other ‘manager’ objects hanging off that IBandClient interface that I’ve got hold of…

Notifications (Part 1)

I can send notifications from code running on my device (Phone/PC) to my Band 2. Here’s my first attempt;

      var bands = await BandClientManager.Instance.GetBandsAsync();

      if (bands?.Count() > 0)
      {
        using (var client =
          await BandClientManager.Instance.ConnectAsync(bands.First()))
        {
          await client.NotificationManager.VibrateAsync(VibrationType.OneToneHigh);
        }
      }

and that worked out fine so I thought that I’d be able to follow it up by sending a message or showing a dialog but both of those need a tile ID – where does that come from?

Tiles

The band displays tiles on its Start Strip;

image

and those can be of two different types;

    • Messaging – just have messages (titles/bodies) behind them.
    • Custom – can have more complex content behind them managed as a set of pages.

I figured that I’d create a messaging tile and see how that worked for me. The code’s pretty simple and so I tried to combine it with some code which then displayed a dialog before tidying up the tile by removing it again;

     var bands = await BandClientManager.Instance.GetBandsAsync();

      if (bands?.Count() > 0)
      {
        using (var client =
          await BandClientManager.Instance.ConnectAsync(bands.First()))
        {
          var tileSpace = await client.TileManager.GetRemainingTileCapacityAsync();

          if (tileSpace > 0)
          {
            var iconFile = await StorageFile.GetFileFromApplicationUriAsync(
              new Uri("ms-appx:///Assets/tileicon.png"));

            var smallIconFile = await StorageFile.GetFileFromApplicationUriAsync(
              new Uri("ms-appx:///Assets/smalltileicon.png"));

            using (var stream = await iconFile.OpenReadAsync())
            {
              using (var smallStream = await smallIconFile.OpenReadAsync())
              {
                var largeBitmap = new WriteableBitmap(48, 48);
                largeBitmap.SetSource(stream);
                var largeIcon = largeBitmap.ToBandIcon();

                var smallBitmap = new WriteableBitmap(24, 24);
                smallBitmap.SetSource(smallStream);
                var smallIcon = smallBitmap.ToBandIcon();

                var guid = Guid.NewGuid();

                var added = await client.TileManager.AddTileAsync(
                  new BandTile(guid)
                  {
                    Name = "Test",
                    TileIcon = largeIcon,
                    SmallIcon = smallIcon
                  }
                );
                if (added)
                {
                  // NB: This call will return back to us *before* the
                  // user has acknowledged the dialog on their device -
                  // we don't get to know their answer here.
                  await client.NotificationManager.ShowDialogAsync(
                    guid, "check-in", "are you ok?");

                  await client.TileManager.RemoveTileAsync(guid);
                }
              }
            }
          }
        }
      }
    }

Note that while this code works, I think it’s a little suspect as I actually make a call to RemoveTileAsync which is likely to run before the user has actually seen the dialog associated with that tile on the screen. The Band 2 (or the SDK) seems to forgive me for this but it’s perhaps not the best of ideas.

I can switch the call to ShowDialogAsync to ShowMessageAsync by just changing that piece of code to;

              await client.NotificationManager.SendMessageAsync(
                    guid, "title", "body", DateTimeOffset.Now, MessageFlags.None);

                  await Task.Delay(20000);

                  await client.TileManager.RemoveTileAsync(guid);

and the difference here is in how the Band 2 displays a message versus a dialog.

For the dialog case it pops a dialog for me to dismiss whereas for the message case I end up with a tile with a badge on it that I can then tap to see the details of the message behind the tile.

That’s why I added the 20 second delay to the code for the message example in order to give me enough time to see the message behind the tile before it was removed. Again, the way in which I’m removing the tile here probably isn’t a great thing to be doing!

Once I’ve got a tile, I can know when the user taps on the tile to “enter” my app within the Band 2 and when they “leave” by using the back button but, again, this is only going to be while I have code with an active connection to the Band 2.

It was time to shake up my “UI” a little so I made 3 buttons for Create/Register Handlers/Remove and put this code behind it;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
    }
    async void OnCreateTile(object sender, RoutedEventArgs args)
    {
      var bands = await BandClientManager.Instance.GetBandsAsync();

      if (bands?.Count() > 0)
      {
        this.client = await BandClientManager.Instance.ConnectAsync(bands.First());

        var tileSpace = await this.client.TileManager.GetRemainingTileCapacityAsync();

        if (tileSpace > 0)
        {
          var iconFile = await StorageFile.GetFileFromApplicationUriAsync(
            new Uri("ms-appx:///Assets/tileicon.png"));

          var smallIconFile = await StorageFile.GetFileFromApplicationUriAsync(
            new Uri("ms-appx:///Assets/smalltileicon.png"));

          using (var stream = await iconFile.OpenReadAsync())
          {
            using (var smallStream = await smallIconFile.OpenReadAsync())
            {
              var largeBitmap = new WriteableBitmap(48, 48);
              largeBitmap.SetSource(stream);
              var largeIcon = largeBitmap.ToBandIcon();

              var smallBitmap = new WriteableBitmap(24, 24);
              smallBitmap.SetSource(smallStream);
              var smallIcon = smallBitmap.ToBandIcon();

              this.tileGuid = Guid.NewGuid();

              var added = await this.client.TileManager.AddTileAsync(
                new BandTile(this.tileGuid)
                {
                  Name = "Test",
                  TileIcon = largeIcon,
                  SmallIcon = smallIcon
                }
              );
            }
          }
        }
      }
    }
    async void OnRemove(object sender, RoutedEventArgs e)
    {
      await this.client.TileManager.StopReadingsAsync();
      await this.client.TileManager.RemoveTileAsync(this.tileGuid);
    }

    void OnRegister(object sender, RoutedEventArgs e)
    {
      this.client.TileManager.TileOpened += OnTileOpened;
      this.client.TileManager.TileClosed += OnTileClosed;
      this.client.TileManager.StartReadingsAsync();
    }
    void OnTileClosed(object sender, BandTileEventArgs<IBandTileClosedEvent> e)
    {
      if (e.TileEvent.TileId == this.tileGuid)
      {
        // My tile!
      }
    }
    void OnTileOpened(object sender, BandTileEventArgs<IBandTileOpenedEvent> e)
    {
      if (e.TileEvent.TileId == this.tileGuid)
      {
        // My tile!
      }
    }
    IBandClient client;
    Guid tileGuid;
  }

and, sure enough, the 2 event handlers that I left empty above get called when I tap on the new tile on the band and when I use the “back” button to leave that tile.

From the SDK docs, these events are handled using intents on Android which would mean that the events can be received whether the app on the phone is running or not which is not something that the Windows SDK here surfaces (although, naturally, it’s possible to do that type of thing on Windows UWP).

Pages, UI Elements

Beyond that, tiles can have pages within them which can contain UI pieces like buttons and text blocks, icons and barcodes within layout containers that may scroll etc.

I found this slightly more tricky to get working in the first instance as there’s a definite ‘pattern’ that’s in use here where you need to;

    1. Create your tile that specifies the page layouts that it contains and the UI elements within them which are given identifiers.
    2. Dynamically “talk” to the tile at a later point to set content for the elements that you earlier identified.

and I didn’t get this ‘2 step dance’ quite right for the first few attempts but it makes sense and is fairly easy once you’ve got the idea of it.

I left my UI as being 3 buttons for create/register/remove and I altered my tile such that it had a single page with a single button on it;

  using Microsoft.Band;
  using Microsoft.Band.Tiles;
  using Microsoft.Band.Tiles.Pages;
  using System;
  using System.Linq;
  using Windows.Storage;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Media.Imaging;
  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
      this.buttonElementId = 1;
    }
    async void OnCreateTile(object sender, RoutedEventArgs args)
    {
      var bands = await BandClientManager.Instance.GetBandsAsync();

      if (bands?.Count() > 0)
      {
        this.client = await BandClientManager.Instance.ConnectAsync(bands.First());

        var tileSpace = await this.client.TileManager.GetRemainingTileCapacityAsync();

        if (tileSpace > 0)
        {
          var iconFile = await StorageFile.GetFileFromApplicationUriAsync(
            new Uri("ms-appx:///Assets/tileicon.png"));

          var smallIconFile = await StorageFile.GetFileFromApplicationUriAsync(
            new Uri("ms-appx:///Assets/smalltileicon.png"));

          using (var stream = await iconFile.OpenReadAsync())
          {
            using (var smallStream = await smallIconFile.OpenReadAsync())
            {
              var largeBitmap = new WriteableBitmap(48, 48);
              largeBitmap.SetSource(stream);
              var largeIcon = largeBitmap.ToBandIcon();

              var smallBitmap = new WriteableBitmap(24, 24);
              smallBitmap.SetSource(smallStream);
              var smallIcon = smallBitmap.ToBandIcon();

              this.tileGuid = Guid.NewGuid();
              this.pageGuid = Guid.NewGuid();

              var panel = new FilledPanel()
              {
                BackgroundColor = new BandColor(0xFF, 0x00, 0x00),
                Rect = new PageRect(0, 0, 245, 102),
                BackgroundColorSource = ElementColorSource.BandBase
              };
              panel.Elements.Add(
                new Microsoft.Band.Tiles.Pages.TextButton()
                {
                  ElementId = this.buttonElementId,
                  Rect = new PageRect(0, 0, 245, 102),
                  VerticalAlignment = Microsoft.Band.Tiles.Pages.VerticalAlignment.Bottom,
                  HorizontalAlignment = Microsoft.Band.Tiles.Pages.HorizontalAlignment.Center
                }
              );
              var pageLayout = new PageLayout(panel);

              var bandTile = new BandTile(this.tileGuid)
              {
                Name = "Test",
                TileIcon = largeIcon,
                SmallIcon = smallIcon
              };
              bandTile.PageLayouts.Add(pageLayout);

              var added = await this.client.TileManager.AddTileAsync(bandTile);
            }
          }
        }
      }

      this.pageGuid = Guid.NewGuid();

      // The hard-coded 0 here means 'layout 0'
      await this.client.TileManager.SetPagesAsync(
        this.tileGuid,
        new PageData(
          this.pageGuid,
          0, 
          new TextButtonData(this.buttonElementId, "Click Me")));
    }
    async void OnRemove(object sender, RoutedEventArgs e)
    {
      await this.client.TileManager.StopReadingsAsync();
      this.client.TileManager.TileOpened -= OnTileOpened;
      this.client.TileManager.TileClosed -= OnTileClosed;
      this.client.TileManager.TileButtonPressed -= OnTileButtonPressed;
      await this.client.TileManager.RemoveTileAsync(this.tileGuid);
    }

    void OnRegister(object sender, RoutedEventArgs e)
    {
      this.client.TileManager.TileOpened += OnTileOpened;
      this.client.TileManager.TileClosed += OnTileClosed;
      this.client.TileManager.TileButtonPressed += OnTileButtonPressed;
      this.client.TileManager.StartReadingsAsync();
    }

    void OnTileButtonPressed(object sender,
      BandTileEventArgs<IBandTileButtonPressedEvent> e)
    {
      if ((e.TileEvent.TileId == this.tileGuid) &&
        (e.TileEvent.PageId == this.pageGuid) &&
        (e.TileEvent.ElementId == this.buttonElementId))
      {
        // My button!
      }
    }

    void OnTileClosed(object sender, BandTileEventArgs<IBandTileClosedEvent> e)
    {
      if (e.TileEvent.TileId == this.tileGuid)
      {
        // My tile!
      }
    }
    async void OnTileOpened(object sender, BandTileEventArgs<IBandTileOpenedEvent> e)
    {
      if (e.TileEvent.TileId == this.tileGuid)
      {
        // My Tile
      }
    }
    IBandClient client;
    Guid tileGuid;
    Guid pageGuid;
    short buttonElementId;
    BandTile bandTile;
  }

and the OnCreateTile handler here creates a tile on the band with a single page with a single button on it whose data is dynamically set to say “Click Me”.

The OnRegister handler sets up event handlers for when that tile is entered/left and also for when the button is clicked – the OnTileButtonPressed handler.

That’s all quite nice and not too difficult at all. What else can the Band 2 do for me?

Personalisation

The last bit of obvious functionality in the Band SDK is the personalisation that lets you write code to;

    • Change the ‘Me Tile’
    • Change the theme

and so it feels like this is the set of APIs that underpin some (or all) of the Health app’s capability to play around with the Band 2’s settings which you might want to duplicate in your own app.

What’s Missing?

The one thing I’d really like to see from the Band 2 SDK is a means by which an action initiated on the Band 2 can cause something to happen on a paired phone or PC even if that phone or PC isn’t actively waiting for it.

I’m thinking of some kind of feature whereby an app on the phone or PC can register a background trigger (possibly on a bluetooth socket) so that the system can quietly wait for an action on the Band 2 (like a button press) and run some background code when that occurs.

From the SDK docs, it looks like that’s do-able on Android and it’s probably do-able on Windows too if you go spelunking into the bluetooth protocol that underpins the SDK but, without that, it’s the one most obvious thing that I’d like to see added.

Otherwise, the SDK’s really easy to pick up and code against – I enjoyed having a quick play with the Band 2.