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.

Windows 10 Devices and ‘The Developer Portal’

I haven’t found official documentation on this anywhere so I thought I’d write up my own quick notes which I can replace when I find the right web pages to link to – I’ve seen quite a few people talk about the portal that’s present on both IoT Core and on Windows Mobile and I’m assuming on Surface Hub but I haven’t checked that last one at the time of writing.

By way of rough notes…

I’m currently on a mixture of wired/WiFi networking where I have my Surface Pro 3 development machine on a wired network along with a Raspberry PI 2 running Windows IoT Core and a Windows Mobile 10 device (my phone) which connects to the same network but over WiFi.

If I use the IoT Dashboard application then it shows me a couple of devices on my network;

image

and so it’s showing both my PI and my phone.

If I use the “Open Device Portal” on the Raspberry PI 2 then a browser opens;

image

with the portal for the IoT core device and I can use this to manage aspects of the device as per the list on the left hand side of the screen – specifically, I can use this to add/remove/stop/start apps and I can use it to set up networking and bluetooth devices.

If you’ve seen me talk about IoT Core in public then you’ll have seen me talk my way through this portal and show a few things.

What I’ve never shown in public (AFAIK) though is what happens if I use the “Open Device Portal” on my phone. In the first instance, I see an error;

image

but I can fix that by losing the :8080 port on the address here although (right now) I still find myself with a warning;

image

but if I go around that I get to;

image

and this leads over to the pages on the phone where I can set all of this up. As you can see, I have the device in developer mode;

wp_ss_20160203_0001

and you’ll notice that I have “Device discovery” switched on here;wp_ss_20160203_0002

and that it says that this is available over both USB and WiFi, that I have no devices paired and that the portal is also switched on and advertising itself at http://192.168.0.11.

wp_ss_20160203_0003

If I then go through that ‘Pair’ process on the phone, I get a code;wp_ss_20160203_0004

which I can then type into the web page and I’m then onto the portal on the device and here’s the phone portal side by side with the IoT Core portal;

image

and from that Phone portal I can then go and add/remove/stop/start apps and I can look at performance, networking and so on for the phone just like I can on IoT Core device.

What I’d really like to do is to also be able to debug here over WiFi without having to connect a USB cable and I don’t know at the time of writing whether I can do that or not.

If I unplug the USB cable then Visual Studio certainly sees the device as a remote target over the network if I use the Universal authentication mode;

image

but attempting to deploy using this mechanism ends up in me being prompted for a PIN;

image

and I don’t seem to be able to provide any type of PIN here that makes the phone play nicely with me so I’m not sure whether this is just because I don’t know how to provide the right PIN or whether there isn’t a PIN that will work here right now.

Let me know if you know and I’ll update the post.

Intel RealSense SR300, Windows 10 and UWP?

I’ve written quite a few posts about RealSense and, specifically, around the F200 front-facing camera which has a tonne of capability.

I’d taken my eye off the ball a little though in that I only recently caught sight of one of the recent updates around the RealSense developer kit;

RealSense Developer Kit

which talks of the current F200 camera being transitioned to the a new SR300 camera which I think was already shown built into the Razer Stargazer camera shown at CES;

image

and it looks like this requires USB 3.0 and is Windows 10 only and will be available in Q2.

Meanwhile, back at Intel, it looks like the developer kit isn’t yet ready but might be ready some time this month.

There’s more info on this camera in Intel’s SDK update notes in this post;

What’s News in R5 of the Intel® RealSense™ SDK R5 (v7)

which says that the SDK supports the SR300 with new, specific features;

    • Cursor Mode – a very responsive tracking of finger/hand movement to screen. Using only half the power while not requiring the identification/calibration of full hand mode with no latency, longer range (90cm) and faster speed detection (2 meters/second). Includes a click gesture that simulates clicking the mouse.
    • Windows Universal support. The SR300 can use the ‘Microsoft 3D camera’ through the WinRT* service via the Intel RealSense plugin. Windows 10 Hello and Universal Apps will use the 3D camera middleware where the Intel Realsense Runtime calls the camera API which can use the DCM.

I’m not sure that I fully understand what the ‘Microsoft 3D camera’ is but I like the idea of Universal Apps getting support for RealSense as, to date, it’s been a desktop app thing.

I’d also missed this post;

Get Ready for Intel RealSense SDK Universal Windows Platform Apps

which says that “R5 delivers … components … for developing UWP apps that use the Intel RealSense camera (SR300)”.

It sounds like the initial support here is going to be for “raw color and depth streaming and the blob tracking algorithm”.

That doesn’t seem out of line with what I looked at in terms of UWP support for the Kinect for Windows V2 camera in this post;

Kinect V2, Windows Hello and Perception APIs

but there’s also a bit of a teaser in that the article states “Other UWP-specific algorithms are in development”.

It all sounds promising and, as usual, I know no more about this than what’s written on the public web here but I’m waiting for the notification to come through that the SR300 is available and then I’ll be getting my order in to see what the SDK enables.

Windows 10, UWP & Twilio for SMS retrieval

I’ve been wanting to try out Twilio for quite a long time. I’ve seen my colleague, Martin, make great use of Twilio in demonstrations at conferences for the longest time and I always fancied trying it out.

I’ve had a Twilio account for quite a while but, the other week, I went through and added a little bit of money to the account and I bought myself a Twilio phone number that supports both voice and SMS messaging.

The first thing that I found kind of ‘fun’ was that as soon as I’d purchased a phone number I could specify URLs to be invoked in order to return XML files containing ‘TwiML’ which controlled what happened when you called my number or sent an SMS to it. Here’s my SMS file;

<?xml version="1.0" encoding="UTF-8"?>
<Response>
	<Message>
		<Body>Thanks for texting Mike's Twilio number.</Body>
	</Message>
</Response>

and I found this to be a very immediate way of linking the world of SMS/voice straight through to the world of web servers and programmability and, clearly, while I’m currently returning a static response to a voice call or text message I could easily be dynamically generating that response.

The next thing that I wanted to do was to see if I could get hold of the text messages that had been sent to my Twilio number inside of a Windows 10, UWP app.

Twilio has a REST API for SMS messages documented and I went about making sure that I could;

  1. Authenticate
  2. Get the list of text messages for my account and for my phone number including chasing down the numerous pages that might involve

That wasn’t particularly difficult.

Twilio has helper libraries for these APIs across technologies like PHP, Ruby, Java and there’s a .NET library on GitHub.

Now, it may be that I didn’t look hard enough at this package but I didn’t seem to find a variant of it which supported the UWP. The Twilio package itself didn’t seem to be right and I couldn’t find the Twilio.WinRT package that was mentioned and so I figured that I’d just write some code myself as I only wanted to support a simple scenario which is;

“Poll for text messages on an interval and return the newly arrived messages”

and I didn’t need any more than that so I wrote a little UWP app to do just that starting from a blank project and adding just a piece of XAML to display a list;

<Page
  x:Class="App307.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:App307"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">

  <Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition
        Height="Auto" />
    </Grid.RowDefinitions>
    <!-- using x:Bind here just to be lazy and avoid setting datacontext -->
    <ListView Margin="40"
      Header="SMS messages arrived since start button was pressed"
              ItemsSource="{x:Bind NewSmsMessages}">
      <ListView.ItemTemplate>
        <DataTemplate>
          <StackPanel Margin="0,8,0,0">
            <TextBlock
              Text="{Binding Sid}" />
            <TextBlock
              Text="{Binding Body}" />
            <TextBlock
              Text="{Binding DateCreated}" />
            <TextBlock
              Text="{Binding From}" />
            <TextBlock
              Text="{Binding To}" />
          </StackPanel>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    <StackPanel
      Grid.Row="1"
      HorizontalAlignment="Center"
      Orientation="Horizontal"
      Margin="20">
      <Button
        Content="Start"
        Click="OnStartAsync"
        Margin="8" />
      <Button
        Content="Stop"
        Click="OnStopAsync"
        Margin="8" />
    </StackPanel>
  </Grid>
</Page>

and some code behind it to provide the data and so on;

namespace App307
{
  using System;
  using System.Collections.ObjectModel;
  using System.Threading;
  using TwilioSmsUtils;
  using Windows.UI.Core;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Navigation;

  public sealed partial class MainPage : Page
  {
    public ObservableCollection<TwilioSmsDetails> NewSmsMessages
    {
      get;
      private set;
    }
    public MainPage()
    {
      this.InitializeComponent();

      this.NewSmsMessages = new ObservableCollection<TwilioSmsDetails>();
    }
    protected async override void OnNavigatedTo(NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);
    }
    private async void OnStartAsync(object sender, RoutedEventArgs e)
    {
      this.NewSmsMessages.Clear();

      this.tokenSource = new CancellationTokenSource();

      this.twilioWatcher = new TwilioSmsWatcher(
        Constants.ACCOUNT_SID,
        Constants.ACCOUNT_TOKEN,
        Constants.PHONE_NUMBER);

      await this.twilioWatcher.InitialiseAsync(this.tokenSource.Token);

      // We don't await this, we let it go.
      try
      {
        await this.twilioWatcher.PollForNewMessagesAsync(
          TimeSpan.FromSeconds(30),
          OnNewTwilioMessage,
          this.tokenSource.Token);
      }
      catch (OperationCanceledException)
      {
        this.twilioWatcher = null;
        this.tokenSource.Dispose();
        this.tokenSource = null;
      }
    }
    void OnNewTwilioMessage(TwilioSmsDetails message)
    {
      this.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        () =>
        {
          this.NewSmsMessages.Add(message);
        }
      );
    }
    void OnStopAsync(object sender, RoutedEventArgs e)
    {
      this.tokenSource.Cancel();
    }
    TwilioSmsWatcher twilioWatcher;
    CancellationTokenSource tokenSource;
  }
}

and that relies on 3 fairly simple (and quickly written) I should stress classes for talking to Twilio – one to represent an SMS;

namespace TwilioSmsUtils
{
  using System;
  using System.Xml.Linq;
  public class TwilioSmsDetails
  {
    public string Sid { get; private set; }
    public DateTimeOffset DateCreated { get; private set; }
    public DateTimeOffset DateUpdated { get; private set; }
    public string Body { get; private set; }
    public string To { get; private set; }
    public string From { get; private set; }
    public string Uri { get; private set; }
    internal static TwilioSmsDetails FromXElement(XElement xElement)
    {
      return (new TwilioSmsDetails()
      {
        Sid = (string)xElement.Element("Sid"),
        DateCreated = DateTimeOffset.Parse(xElement.Element("DateCreated").Value),
        DateUpdated = DateTimeOffset.Parse(xElement.Element("DateUpdated").Value),
        Body = (string)xElement.Element("Body"),
        To = (string)xElement.Element("To"),
        From = (string)xElement.Element("From"),
        Uri = (string)xElement.Element("Uri")
      });
    }
  }
}

and one to represent a page of SMS messages;

namespace TwilioSmsUtils
{
  using System.Collections.Generic;

  internal class TwilioMessageListPage
  {
    public string NextPageUri { get; set; }
    public List<TwilioSmsDetails> SmsList { get; set; }
  }
}

and a watcher class which polls;

namespace TwilioSmsUtils
{
  using System;
  using System.Linq;
  using System.Net;
  using System.Net.Http;
  using System.Threading;
  using System.Threading.Tasks;
  using System.Xml.Linq;

  public class TwilioSmsWatcher
  {
    public TwilioSmsWatcher(
      string accountSid, 
      string accountToken,
      string phoneNumber)
    {
      this.accountSid = accountSid;
      this.accountToken = accountToken;
      this.phoneNumber = phoneNumber;
    }
    public async Task<bool> InitialiseAsync(CancellationToken token)
    {
      var messageListUri = MakeTwilioUri(TWILIO_MESSAGE_LIST);

      var firstPage = await this.ReadSingleTwilioSmsPageAsync(messageListUri,
        token);

      if (firstPage != null)
      {
        if (firstPage.SmsList?.Count > 0)
        {
          this.latestMessageSeenTime = firstPage.SmsList[0].DateCreated;
        }
        this.initialised = true;
      }
      return (this.initialised);
    }
    public async Task PollForNewMessagesAsync(
      TimeSpan pollInterval,
      Action<TwilioSmsDetails> messageHandler,
      CancellationToken cancellationToken)
    {
      this.CheckInitialised();

      while (true)
      {
        await Task.Delay(pollInterval, cancellationToken);

        var uri = MakeTwilioUri(TWILIO_MESSAGE_LIST);

        var updatedLatestMessageSeenTime = this.latestMessageSeenTime;

        while (uri != null)
        {
          var page = await ReadSingleTwilioSmsPageAsync(uri, cancellationToken);

          // clear the URI so that we don't loop unless we find a whole page
          // of new results and a next page to move to.
          uri = null;

          if (page != null)
          {
            // filter to the messages that are newer than our current
            // latest message (if we have one)
            var newMessageList = page.SmsList.Where(
              sms => IsNewerMessage(sms, this.latestMessageSeenTime)).ToList();

            foreach (var newMessage in newMessageList)
            {
              messageHandler(newMessage);

              if (IsNewerMessage(newMessage, updatedLatestMessageSeenTime))
              {
                updatedLatestMessageSeenTime = newMessage.DateCreated;
              }
              cancellationToken.ThrowIfCancellationRequested();
            }
            // if everything on this page was new, we might have more to do
            // so see if there's another page.
            if (newMessageList.Count == page.SmsList.Count)
            {
              uri = new Uri(page.NextPageUri);
            }
          }
          cancellationToken.ThrowIfCancellationRequested();
        }
        cancellationToken.ThrowIfCancellationRequested();

        this.latestMessageSeenTime = updatedLatestMessageSeenTime;
      }
    }
    bool IsNewerMessage(TwilioSmsDetails sms, DateTimeOffset? currentLatest)
    {
      return (
        !currentLatest.HasValue ||
        sms.DateCreated > currentLatest);
    }
    HttpClient HttpClient
    {
      get
      {
        if (this.httpClient == null)
        {
          HttpClientHandler handler = new HttpClientHandler()
          {
            Credentials = new NetworkCredential(this.accountSid, this.accountToken)
          };
          this.httpClient = new HttpClient(handler);
        }
        return (this.httpClient);
      }
    }
    async Task<TwilioMessageListPage> ReadSingleTwilioSmsPageAsync(Uri uri,
      CancellationToken cancellationToken)
    {
      TwilioMessageListPage page = null;

      var response = await this.HttpClient.GetAsync(uri, cancellationToken);

      if (response.IsSuccessStatusCode)
      {
        using (var responseStream = await response.Content.ReadAsStreamAsync())
        {
          var xElement = XElement.Load(responseStream);

          var twilioResponse = xElement.DescendantsAndSelf(XML_TWILIO_RESPONSE_NODE);

          if (twilioResponse != null)
          {
            page = new TwilioMessageListPage();
            var twilioNextPage = 
              twilioResponse.Attributes(XML_TWILIO_NEXT_PAGE_URI_ATTRIBUTE).FirstOrDefault();

            page.NextPageUri = twilioNextPage?.Value;

            page.SmsList =
              twilioResponse.DescendantsAndSelf(XML_TWILIO_SMS_MESSAGE_NODE).Select(
                xml => TwilioSmsDetails.FromXElement(xml)).ToList();
          }
        }
      }
      return (page);
    }
    Uri MakeTwilioUri(string path)
    {
      return (new Uri(
        $"{TWILIO_HOST}{this.accountSid}/{path}?To={this.phoneNumber}"));
    }
    void CheckInitialised()
    {
      if (!this.initialised)
      {
        throw new InvalidOperationException("Not initialised");
      }
    }
    static readonly string TWILIO_HOST = "https://api.twilio.com/2010-04-01/Accounts/";
    static readonly string TWILIO_MESSAGE_LIST = "Messages";
    static readonly string XML_TWILIO_RESPONSE_NODE = "TwilioResponse";
    static readonly string XML_TWILIO_NEXT_PAGE_URI_ATTRIBUTE = "nextpageuri";
    static readonly string XML_TWILIO_SMS_MESSAGE_NODE = "Message";

    HttpClient httpClient;
    DateTimeOffset? latestMessageSeenTime;
    string phoneNumber;
    string accountSid;
    string accountToken;
    bool initialised;
  }
}

and I can run up my little app and watch SMS messages come in;

image

and that all works really nicely for me and I really like the simplicity/immediacy of being able to quickly add SMS message integration into an app and especially when I think of this coupled with Windows IoT Core and how (e.g.) you could build some kind of kiosk that easily accepts text messaging as a form of input.

The code for this post is here for download. You would need to edit the Constants.cs file in order to provide your own Twilio account details and phone number.

Windows 10 & the Windows Store for Business #6

Disclaimer: These posts represent my own small experiments with the Windows Store for Business. I only have visibility of what’s publicly on the internet and I’m learning here so apply a pinch of salt to these posts as I try to figure things out. You should always check with the official documentation here.

Following on from the previous post, I wanted to finish off these few posts about the Windows Store for Business by having a look at the ‘LOB apps’ section of the Store.

I logged back in to the admin console as my admin user, Jane, and went to that section of the Store;

image

and, having no publishers, I figured that I’d see if I could set one up which involves sending out an invitation by email – I think I may be taking this ‘role play’ a little far here but I filled in the form regardless;

image

and I sent the invite;

image

before I went to check my email, I wondered whether the Windows Developer Centre would surface this new invitation and, sure enough, under my “Account Settings” there’s the “Enterprise Associations” area;

image

and checking my mail revealed that I’d got a mail inviting me to publish apps for Backstreets Software (from Jane);

image

It’d be churlish not to accept an invitation like this so I went ahead and accepted it and the status moved to ‘Active’ both on my side;

image

and on the Backstreets Software side of things;

image

With all of that set up, what does it enable? Well, I don’t think that it enables anything on the organisational side – i.e. it’s not that the organisation ‘pulls’ apps from the developer. It’s more that the developer can now ‘push’ apps to the organisation.

With that in mind, I thought that I’d take this as a real scenario and build a ‘custom’ version of one of my old apps which is already in store and ship it to Backstreets Software as though it was a custom build for that company alone.

I have my QR code scanning app which gets bad reviews in the Store Confused smile and which I’ve been migrating to Windows 10 but which I haven’t yet shipped to the public store.

Maybe Jane from Backstreets Software comes along and has an urgent need for the Windows 10 version of this app and is happy to take it in its current form regardless of me not yet being ready to put it into the public Store.

Is that do-able? I thought I’d see how it worked out. I didn’t want to mess around too much with my existing app in the Store so I made a new app;

image

and I went into the submission process;

image

and within the ‘Pricing and Availability’ section, I switched the ‘Distribution and Visibility’ option to ‘Line of Business’;

image

and that reveals my LOB partners;

image

Great – so I’m now submitting an app purely for the guys at Backstreets Software. As a ‘sanity check’ I also unchecked all the 242 markets that the app would usually be visible in because I really don’t want it showing up in the public Windows Store;

image

and this app is only a PC app at this point so I unchecked the option to allow people to install on Mobile.

When it came time to build packages for my app, I got a bit scared about submitting packages for a new app when the same code has already kind of submitted for an existing (public Store) app but I changed the Store association in the Visual Studio project such that the project was associated with the new app in the Store.

I then filled in the very minimal information for a Store submission and pressed the submit button to see what would happen.

It took about 90 minutes for this bar to go green from left to right and for the app to be ‘published in the Store’;

image

although I must admit that the terminology here seems a bit scary because I’m really hoping that my app isn’t “in the Store” as the label below might suggest that it is;

image

when I only want it to be in the Private Store of Backstreets Software.

With this new, amazing app available, what does it look like to Jane over at Backstreets software? I went over to that section of the admin portal and it hadn’t shown up;

image

and it wasn’t clear to me whether I’d messed up or whether this was just a timing thing so I thought I’d give it a few hours and see if it came through.

2.5 days later and it still hasn’t shown up in the Backstreets Software store so I’m going to have to go off and ask somebody and return to this post where I understand what I did wrong in the submission – expect an update to this post! Smile

Update 1: and here is that update! I asked the folks from Windows Store for Business and they kindly explained that I hadn’t understood things correctly when I had made my Store submission.

My misunderstanding was in the relationship between Markets and the Windows Store for Business. As I said earlier, when I made my submission I;

“”Unchecked the 242 markets that the app would usually be visible in”

and that wasn’t the right thing to do – markets still apply even though the app is being published only to Backstreets Software.

I made a new submission for the app back in the Dev Center, making sure that my app was available in the UK market;

image

and that it was still published directly to Backstreets Software;

image

and, sure enough, a few hours later when Jane at Backstreets Software visits the Windows Store for Business she sees my app in here ‘New LOB apps’ section;

image

Fortunately, it’s not automatically added to her inventory but she can choose to accept/reject it;

image

and if accepted then it gets added to inventory and, naturally, falls out of the ‘New LOB apps’ list and onto the inventory list;

image

From there, like the other apps on that list, it can be added to the Private Store, assigned to specific users and so on as we’ve seen previously for apps on the inventory.

I added it to the private store and waited around 12 hours for it to show up which it did;

image

and then I logged back in to Simon’s machine as Simon to see whether this now shows up in his Store app and it does!

image

and he can start to install it;

2

and I was so close but, unfortunately, at the time of writing that then gives me an error on the installation;

image

and so I’m nearly there but I’ll come back and update the post when I figure out what’s going on at this last step.

Update 2: that error above seemed to be a temporary glitch as I went back later in the day and tried to install the app again and, this time around, it worked fine;

image

and the app’s installed for Simon;

image

and so the process has gone all the way from the ISV (me) through to the organisation (Backstreets Software) and the administrator (Jane) and on to the end user (Simon) and it all works out fine (once I figured out what I was doing).

I think that’s really cool and I’m really looking forward to when the Windows Store for Business can also do this for paid for applications as I think that can open a lot of extra doors for developers of LOB apps.

Windows 10 & the Windows Store for Business #5

Disclaimer: These posts represent my own small experiments with the Windows Store for Business. I only have visibility of what’s publicly on the internet and I’m learning here so apply a pinch of salt to these posts as I try to figure things out. You should always check with the official documentation here.

Following up on the previous post, I wanted to see what it was like to make use of the Private Store which describes itself as;

  1. Only being for online licensing, not offline.
  2. Showing all apps to all members of the organisation.
  3. Showing up on the organisation’s own tab within the Windows Store app.

The docs also very clearly state that it might take 12 hours for an app to show up in the Private Store so I was prepared for a wait but I logged into the admin portal as Jane the admin and added a couple of apps into the Private Store;

image

image

and, sure enough, they then showed up in the inventory as progressing into the Private Store;

image

I’m not sure what happens during the ‘12 hours’ here but it does seem that there’s definitely a period of time involved so I broke off from this post while I waited for this to happen and, sure enough, around 10-12 hours later they showed up;

image

Once there, it looks like I can take similar steps with these ‘Private Store’ apps that I took with my ‘Not in private store’ app in that it looks like I can assign them to specific users as per the page below;

image

but I didn’t do that assignment.

Instead, I wondered what this looked like from the perspective of a user like Simon so I logged in to his PC to have a look and, sure enough, there’s the private store apps showing up in the ‘Backstreets Software’ section of the Store;

image

and if I click on Word (e.g.) then the description that I get here is slightly different from the description I got when I did this in the ‘Non private store’ case as you’ll see below;

image

The description says that ‘Backstreets Software owns this product’ so it’s been flagged that the organisation has ‘purchased’ the app and as a member of that organisation I can go ahead and install it.

It’d be interesting to see how this will look in the scenario where the company has paid for the app and the number of licenses is restricted and whether it’s a first-come, first-served model when specific user assignments have not been made. I assume so.

As an aside, Word Mobile uses in-app-purchases and, at the time of writing, the docs clearly say that in-app purchases don’t quite fit into the Windows Store for Business model. This document;

Apps in the Windows Store for Business

says that;

In-app purchases are not currently supported for apps that are acquired through Store for Business and distributed to employees.

If an employee makes an in-app purchase, they’ll make it with their personal Microsoft account and pay for it with a personal payment method. The employee will own the item purchased, and it cannot be transferred to your organization’s inventory.”

so that would definitely be one to watch out for right now.

I downloaded and installed Word Mobile as Simon and ran it on his PC from the Private Store and it all worked out fine, no problems. It did leave me wondering a couple of things;

  1. How would Jane as the admin know that Simon had installed this app in a situation where she had limited licenses for it?
  2. Could Jane stop Simon using the app?
  3. Could Jane remove the app from the Private Store and stop all users using the app?

The first question was easy enough to answer, if I go back to the admin portal once Simon’s installed the app then Jane can see that he’s done so;

image

Even though Simon was never assigned the app by Jane, he just took it from the Private Store, Jane can still see that he’s taken it and she can reclaim that license from him.

The second question then is also easy to answer – Jane can reclaim that license from Simon to stop him using Word Mobile at a point in the future.

The third question – there’s a button there to Remove from private store so I figured I’d click it knowing that there was a possibility that it might take another 10-12 hours to remove.

After the confirmation dialog;

image

we moved into a ‘Remove in progress’ state;

image

and then I waited to see what would happen on Simon’s machine when this app was ultimately removed from the Private Store.

I checked back about a day later and Word Mobile was gone from my Private Store;

image

Back on Simon’s PC, he can see that Word Mobile is no longer offered via the Private Store as he’s only offered Excel;

image

But, he’s still able to run his installed Word Mobile;

image

and the Windows Store for Business still shows Simon having an assigned license for Word Mobile;

image

So I’d have to assume that taking an app from the Private Store does not reclaim all the licenses from the people using the app.

Instead, what it seems to do is put the app back on the inventory (as though it had never been in the Private Store in the first place) and maintains the list of users who have licensed (whether they had them assigned or whether they simply claimed them like Simon did here).

Don’t take that as definitive, it’s just me experimenting, but that’s how it looks.

So, to reclaim Simon’s license for Word Mobile I would take the same steps as I took in the previous post to reclaim Ivan’s license for PowerPoint Mobile.

Windows 10 UWP–Migrating a Windows 8.1 App (Part 12 of 12)

Following up on that last post, I wanted to finish up this series posts on moving a Windows 8.1 app to Windows 10 UWP.

Since the application is now showing up on Windows Mobile devices, I needed to do a little more work on the UI and, even with this small app, I find it to be quite a challenge to size things such that they look reasonable across a variety of devices. No-one should be telling anyone that this is ‘easy’ or ‘free’ but it’s certainly within the realm of the ‘possible’ Smile

For my own work here, I’m largely testing on a Surface Pro 3 with/without external monitors that are lower DPI than the tablet itself and then also on a Dell Venue Pro 8 and on a Lumia 920 running Windows 10 Mobile Preview.

Where I ended up with the UI was that I maintained a narrow/wide set of visual states rather than thinking in terms of the different mobile/desktop device families.

So, on the desktop, the app in its wide mode has a look;

image

and in its narrow mode it drops down to;

image

and that seems to work reasonably well across desktop mode, tablet mode and then down onto a phone device (screen-shotted here via the ‘project my phone’ app);

image

Beyond that, I had a few more areas that were buggy and that I needed to tidy up;

  1. My camera preview handling had problems with front facing cameras and flipping/mirroring the video. I found the Camera Starter Kit sample to be really useful here.
  2. I hit a problem with calling the GetNativeSystemInfo API on Mobile devices. I fixed that in this post.
  3. I hit a bug in sharing QR code images into the app that only showed up on Windows Mobile but had been there since day 1 and so that was nice to find. I also reworked the share target UI as part of fixing that as I was a bit horrified by the XAML I’d made there.

and I’m also finding that my ‘rotary control’ for controlling the focus of the camera is a bit problematic on Windows Mobile right now;

image

in that its performance is pretty horrible and I haven’t yet come up with a solution to making that better but I’m going to revisit it.

Having got the app into a ‘reasonable’ state, I revisited the manifest because I knew that there would be a bunch of icon work to do and, sure enough, I spent a good hour or two changing images that I had previously drawn at scale factors like 140, 180 into images drawn at scale factors 125, 150, 200, 400 etc.

I’m not sure that everyone knows how much effort it is to complete a full application manifest but I find producing sheet after sheet of these sorts of images;

SNAGHTML391642f

from their original vectors to be quite time consuming.

With that done, it was time to go and visit the Windows Store and set about an app submission. Here, the work goes into the highlighted sections;

image

and, specifically, in the Descriptions section I chose the option which let me create a ‘platform specific description’, I based it off my Windows 8.1 description and so I end up with 3 description areas here in the Store;

image

and then I went to work producing screenshots for the Windows 10 version of the app. Once again, if you filled in everything here then you’re looking at I think 26 separate images so it takes quite a bit of time (and imagination) to populate;

SNAGHTML395ff33

At the time of writing, I’ve still got some of that work to do but the app is heading back into Store (ready to get some more brutal feedback from its users Winking smile) and I’ll then be back into bug-fix mode on it.

As I said at the start of this process, this was a simple app and it’s taken me 11 stages of blog posts to get from Windows 8.1 to Windows 10.0 albeit working on it only from time to time in some stolen moments but I’m glad that I worked through it as it gave me quite a bit of insight into what the process looks like and I hope there were a few things contained in these posts that helped others along the same journey.