20 May 2015

Using a Windows 10 UWP app and Signal/R on Azure to display Microsoft Band heart rates–basically everywhere

Intro: sometimes it seems like I’ve been here before

Back in February I got my Raspberry PI2 and wanted to do something with it. So I created a setup that made it display my heart rate as measured by my Microsoft Band, making it blink a colored LED in the same rate as my heartbeat. Windows 10 IoT Core wasn’t available at that time, which made me resort to using Raspbian Linux, installing Mono on that, getting a OWIN ASP.NET server running on that, that listened to postings on a WebAPI endpoint, employing a modified C library to actually drive the pins to make the LED go, editing files, setting permissions, all via command line stuff.

The architecture was, simply put, this:

image

This brings another issue to light that is a problem from both an architectural and a practical viewpoint. The Raspberry PI2 had to run a HTTP server. The phone running the app listening to the Band data needs to know the IP address of the PI2, which implicates they must be more or less in the same network space. In addition, running a server on a small device is not desirable in any way at all as it also implicates security issues. Imagine having to set passwords and/or certificates on all your devices. And it’s hard to demo as well.

Back to the drawing board

What I envisioned when I started out in February, was something like this:

image

A kind of magic black box in the middle that would take care of this problem by receiving data and distributing it to clients. It’s the kind of diagram typically drawn by a branch of people who tends to net big salaries by painting happy stories in the client’s mind - and, with the warm fuzzy feeling of a job well done, drop the actual problem of getting these magic boxes to appear out of thin air on the desk of some hapless unsuspecting developer. These people tend to call themselves architects, but I think marchitects would be a better word. Believe me, I’ve done architecture myself, I know how this goes. But I digress.

Anyway, in this case Microsoft has you (and me) poor developer’s – er – behind already covered, because this magic box already exists. It’s called Signal/R, and it’s been around for a while. A basic setup hardly needs any code, and if you host it on on Azure, you can very simply publish it with a few mouseclicks, debug it while running, and you never need to worry about where your magic box is on the network – it has a global URL. And what you end up then with, is this possibility:image

Make a Windows 10 Universal Windows Platform app, and you get an app that can display a heart rate from your Band on any device – a phone, a Surface (or any other PC), a Raspberry PI 2 or any other device capable of running some form on Windows 10 - anywhere in the world.

And the net result really hammers home the concept of the Universal Windows Platform. I just deployed the app to three totally different types of devices and… it just works. It almost feels like cheating.

The basic setup

I have created a pretty simple solution that contains 4 projects:

image

  • A Windows Phone 8.1 app to receive Microsoft Band heart rate data and send it to the Signal/R hub
  • A Windows 10 UWP app to display the hearth rate as it receives notifications from the Signal/R hub
  • A Portable Class Library that contains some classes that are shared by the various projects.
  • A very simple ASP.NET web app hosting a Signal/R app that can accept heart rate pulse information and notify all clients

I opted for making a Windows Phone 8.1 Universal App to actually drive the code that receives the actual Band data for two reasons: first, my main phone is still Windows Phone 8.1 and I still use my Band every day and second, it is a nice way to demonstrate how similar Windows 10 UWP and the ‘old’ UAP code now is (and share code between it).

There are two caveats:

  • To open the whole solution and deploy the Windows 10 apps you will need VS2015RC (of course), but I can only get the 8.1 app deployed correctly on my phone by opening the same solution with VS2013 and deploying it from there. 
  • There is an issue with Signal/R and Windows 10 UAP – you will need to do some fiddling with referenced assemblies to get it working – that is why there is an Signal/R client assembly in a Binaries solution folder. For details see this post I did earlier.

Signal/R hub setup

Basically everything you have to know you can find here in the “ASP.NET SignalR Hubs API Guide - .NET Client”, and I found this “Tutorial: Getting Started with SignalR 2” very useful to get started (until about point 7, where it goes into JavaScript, which is not used in this sample).

Since I dislike solutions peppered with magic strings (ahem), I tend to define them in some form of helper classes. In the HeartRate.Shared project I have created a simple static class “Settings”:

namespace HeartRate
{
  public static class Settings
  {
   public static readonly string HubUrl = "http://heartratehub.azurewebsites.net";

    public static readonly string HubName = "BlinkHub";
    public static readonly string ServerMethod = "PostNewRate";
    public static readonly string ClientMethod = "NotifyNewRate";
    public static readonly string TestBandName = "Joost's Band";
  }
}

Then I created a simple interface which you don’t even have to use, but that makes the Hub strongly-typed, and I tend to like that:

using HeartRate.Models;

namespace Heartrate.SignalRHub
{
  public interface IBlinkHub
  {
    void NotifyNewRate(PulseData pulse);
  }
}

The Hub itself then is pretty simple:

using HeartRate.Models;
using Microsoft.AspNet.SignalR;

namespace Heartrate.SignalRHub
{
  public class BlinkHub : Hub<IBlinkHub>
  {
    public void PostNewRate(PulseData pulse)
    {
      Clients.All.NotifyNewRate(pulse);
    }
  }
}

This basically says: expose a method “PostNewRate” accepting a parameter of type “PulseData” to a clients, that upon being called notifies all clients (including the caller) by calling a method “NotifyNewRate” with that same PulseData as parameter. Note that NotifyNewRate is not a stronly defined method – it’s dynamic. Signal/R is just going to assume it’s there on the client. If it’s not, it won’t crash or something. It just won’t do anything.

PulseData itself is just a simple DTO-type class that lives in the HeartRate.Shared project

namespace HeartRate.Models
{
  public class PulseData
  {
    public int HeartRate { get; set; }
    public string Name { get; set; }
  }
}

All we need now for the Hub is a startup class, and that is also not very complicated:

using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(Heartrate.SignalRHub.Startup))]
namespace Heartrate.SignalRHub
{
  public class Startup
  {
    public void Configuration(IAppBuilder app)
    {
      var hubConfiguration = new HubConfiguration
      {
        EnableDetailedErrors = true,
        EnableJavaScriptProxies = false
      };
      app.MapSignalR(hubConfiguration);
    }
  }
}

Having used Azure in it’s beta days, this really blew me away – you can just publish this to Azure with a few mouse click at some URL you like (provided it’s not already taken), and you have a fully functional Signal/R hub capable of distributing a heart rate to any Signal/R client on the world. Done. I wish every magic box sold by marchitects was so easy to set up.

The Windows 10 UWP client

Blinking at the right rate

As I explained in February, a Microsoft Band does not give you a pulse every time your heart beats – it rather posts at a quite regular interval what your current heartbeat is. So in order to let something blink or flash at the rate of a heartbeat, we actually have to calculate the blink rate ourselves. As you will see, the blinking does not show your actual heartbeat – blinks will happen at the same rate as your heartbeat. More or less.

Stealing my own code from February, I have created a simple generic Blinker class that raises an event every time a blink should occur based upon the heart rate supplied to it. It’s basically an endless loop, that runs until it’s stopped. If the last update was less than 5 seconds ago, it will raise an event, wait for 60000ms divided by the beats per minute – and then fires the next event. If it does not get updates after 5 seconds, it goes into a 10-seconds polling mode. It’s nearly the same thing as last time, except now it’s only raising an event “DoBlink” in stead of actually flashing a LED.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace HeartRate.Models
{
  public class Blinker
  {
    private DateTime lastReceivedUpdate = DateTime.MinValue;
    private int heartRate;
    private Task task;

    private Blinker()
    {
    }

    public void Start()
    {
      if (task == null)
      {
        cancellationTokenSource = new CancellationTokenSource();
        task = new Task(() => ShowHeartRateBlinking(cancellationTokenSource.Token),
           cancellationTokenSource.Token);
        task.Start();
      }
    }

    public void Stop()
    {
      if (cancellationTokenSource != null)
      {
        cancellationTokenSource.Cancel();
        task = null;
      }
    }

    private CancellationTokenSource cancellationTokenSource;

    private async Task ShowHeartRateBlinking(CancellationToken cancellationToken)
    {
      while (!cancellationToken.IsCancellationRequested)
      {
        if (DateTime.Now - lastReceivedUpdate < TimeSpan.FromSeconds(5))
        {
          if(DoBlink!=null) DoBlink(this, GetRate());
          await Task.Delay(60000 / HeartRate, cancellationToken);
        }
        else
        {
          await Task.Delay(10000, cancellationToken);
        }
      }
    }

    public event EventHandler<BlinkRate> DoBlink;

    private BlinkRate GetRate()
    {
      if (HeartRate < 80) return BlinkRate.Low;
      return HeartRate < 130 ? BlinkRate.Medium : BlinkRate.High;
    }

    public int HeartRate
    {
      get { return heartRate; }
      set
      {
        if (value >= 0 && value <= 200)
        {
          lastReceivedUpdate = DateTime.Now;
          heartRate = value;
        }
      }
    }

    private static Blinker blinker;
    public static Blinker GetBlinker()
    {
      return blinker ?? (blinker = new Blinker());
    }
  }
}

Listening to Signal/R notifications

To this extent I have created a small helper class HearthRateListener that basically converts a Signal/R call to an ordinary event. It looks like this:

using System;
using System.Threading.Tasks;
using HeartRate.Models;
using Microsoft.AspNet.SignalR.Client;

namespace HeartRate.Monitor.Models
{
  public class HearthRateListener
  {
    public async Task Init()
    {
      var hubConnection = new HubConnection(Settings.HubUrl);
      var hubProxy = hubConnection.CreateHubProxy(Settings.HubName);
      hubProxy.On<PulseData>(Settings.ClientMethod, pulse =>
        {
          PulseDataReceived?.Invoke(this, pulse);
        }
      );
      await hubConnection.Start();
    }

    public event EventHandler<PulseData> PulseDataReceived;
  }
}

So I make a connection to the hub’s url as defined in the settings class (“http://heartratehub.azurewebsites.net”), then create a hub proxy with the name of the hub (“BlinkHub”, which also is the class name of the Hub) and then, using the “On” method to fire when the Signal/R hub wants to notify me using the “NotifyNewRate” method. So you have to make sure the class name of hub equals the name of the hub you are subscribing to on the client, and that the client makes a dynamic ‘method’ with the same name as the server tries to call. It’s a bit fiddly and getting all the names right is important. Defining names in constants helps a little. In some of the comments on Signal/R people ask for a more strongly typed method. The answer of the team is along the lines of “You want strongly typed? Feel free to use WCF” ;)

You can see the LED and circles blinking at 74 BPM first (in blue), that for some reason my hear rate goes over 100 according to the band and the color of the flash becomes green - and faster, too. This was not intentional, but a happy coincidence when I was shooting the video.

Blinking the lights

Although the basics of operating a GPIO pin on Windows 10 IoT Core is about the same as on the Linux/Mono/C#/WiringPI, the process is vastly less complicated. There are some easy samples to find how to do that, for instance here on the IoT Github page. I created this helper class to do the work. The basic setup is this:

using System.Threading.Tasks;
using HeartRate.Models;
using Windows.Devices.Gpio;

namespace HeartRate.Monitor.Models
{
  public class LedSoundOperator
  {
    GpioController gpioCtrl;

    private LedSoundOperator()
    {
      InitGPIO();
    }

    private static LedSoundOperator lsOperator;
    public static LedSoundOperator GetBlinker()
    {
      return lsOperator ?? (lsOperator = new LedSoundOperator());
    }

    private GpioPin GetPin(BlinkRate rate)
    {
      switch (rate)
      {
        case BlinkRate.High:
          return RedPin;
        case BlinkRate.Medium:
          return GreenPin;
        default:
          return BluePin;
      }
    }

    private GpioPin RedPin;
    private GpioPin GreenPin;
    private GpioPin BluePin;
    private GpioPin SoundPin;

    private const int RedPinId = 5;
    private const int GreenPidId = 6;
    private const int BluePinId = 13;
    private const int SoundPinId = 16;
  }
}

I define this class to be a Singleton. The Sunfounder’s three color LED I used last time red pin is connected to GPIO5, the green pin to GPIO6, the blue pin to GPIO13 and the buzzer’s pin to GPIO16. Based upon the heart rate it selects a different pin and thus the LED color changes.

Initializing a pin is not exactly rocket science either. First you initialize the Gpio controller, then you open a pin, set it’s drive method to output, and write an initial “Low” value to turn in off.

private void InitGPIO()
{
  if (Windows.Foundation.Metadata.ApiInformation‏.IsTypePresent(
     "Windows.Devices.Gpio.GpioController"))
  {
    gpioCtrl = GpioController.GetDefault();
    if (gpioCtrl != null)
    {
      RedPin = InitPin(RedPinId);
      GreenPin = InitPin(GreenPidId);
      BluePin = InitPin(BluePinId);
      SoundPin = InitPin(SoundPinId);
    }
  }
}

private GpioPin InitPin(int pinId)
{
  var pin = gpioCtrl.OpenPin(pinId);
  pin.SetDriveMode(GpioPinDriveMode.Output);
  pin.Write(GpioPinValue.Low);
  return pin;
}

If you intend to be able to run this app on a device that may not support a Gpio controller, like a phone or a PC, you might want to check the actual presence of the Gpio controller type before actually calling it.You do this using the Windows.Foundation.Metadata.ApiInformation‏.IsTypePresent method as displayed.

Making the LED blink (and the buzzer tick) works like this:

public async Task Blink(BlinkRate rate)
{
  if (gpioCtrl != null)
  {
    var pin = GetPin(rate);
    pin.Write(GpioPinValue.High);
    SoundPin.Write(GpioPinValue.High);
    await Task.Delay(200);
    pin.Write(GpioPinValue.Low);
    SoundPin.Write(GpioPinValue.Low);
  }
}

Notice the check if gpioCtrl is set – the IsTypePresent may have prevented that. But if we are running on a device that actually has a GPIO board, then get the right pins, set their value to high (blink! tick!), wait 200ms, and set them to low. As easy as that.

Oh yeah, and at the end, call

public void Cleanup()
{
  RedPin?.Dispose();
  GreenPin?.Dispose();
  BluePin?.Dispose();
  SoundPin?.Dispose();
}

And then some UI stuff

The fun thing is that, unlike when running Raspbian, one app can actually display a XAML defined UI on the PI2. And having a little UI is useful as well when running, for instance, on a phone. The XAML of the page is not very complicated either

<Page
    x:Class="HeartRate.Monitor.MainPage"
    xmlns="...">
  <Grid Background="{ThemeResource AppBarBackgroundThemeBrush}">
    <Grid Margin="12,0,12,0">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
      </Grid.RowDefinitions>
      <StackPanel Margin="0,0,0,5" Orientation="Horizontal">
        <TextBlock TextWrapping="Wrap" Text="UWT Heart Rate Displayer" 
        FontSize="26.667" HorizontalAlignment="Left"/>
      </StackPanel>
      <Grid Margin="12,0,12,0" Grid.Row="1">
        <Grid>
          <Ellipse x:Name ="BlinkCircle" 
                 Fill="Gray"
                Height="200"
                Width="200"
                StrokeThickness="0" />
        </Grid>
      </Grid>
    </Grid>
  </Grid>
</Page>

imageAnd that is because the net resulting UI isn’t that complex either :)

Wiring it all together

Mind you, in a real app I would do all this using MVVM, but I don’t want to obscure the thing I am actually trying to convey by getting too much architecture in the way. So in the code behind of the XAML page there is some stuff that wires it all together:

namespace HeartRate.Monitor
{
  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
    }

    HearthRateListener listener;
    LedSoundOperator lsOperator;

    protected async override void OnNavigatedTo(NavigationEventArgs e)
    {
      listener = new HearthRateListener();
      lsOperator = LedSoundOperator.GetBlinker();
      var blinker = Blinker.GetBlinker();
      listener.PulseDataReceived += (p, q) =>
      {
        blinker.HeartRate = q.HeartRate;
      };
      blinker.DoBlink += Blinker_DoBlink;
      blinker.Start();
      await listener.Init();
    }

    private void Blinker_DoBlink(object sender, BlinkRate e)
    {
      lsOperator.Blink(e);
      Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
      {
        Color c;
        switch (e)
        {
          case BlinkRate.High:
            c = Colors.Red;
            break;
          case BlinkRate.Medium:
            c = Colors.Green;
            break;
          default:
            c = Colors.Blue;
            break;
        }
        BlinkCircle.Fill = new SolidColorBrush(c);
        await Task.Delay(200);
        BlinkCircle.Fill = new SolidColorBrush(Colors.Gray);
      });
    }
    
    protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
    {
      lsOperator.Cleanup();
      Blinker.GetBlinker().Stop();
    }
  }
}

I create a new Heart rate listener – that is the thing that listens to heart rate data coming from the Azure Signal/R hub – and I initialize the LedSoundOperator (that will drive the pins) and a Blinker (that will raise events at the heart rate received from Azure). When the listener gets new data from Azure, it will turn it to the blinker. When the blinker raises an event, the method DoBlink will actually blink the LED and make the buzzer tick by calling the LedSoundOperator and by flashing the circle on the screen.

Some highlights of the Windows Phone 8.1 Band Client

imageThis is a simple app that listens to the Microsoft Band heart rate data coming in and simply pushes it Azure. To make some monitoring possible, I actually added here as well the circle that flashes at your heart rate, as well as two little circles that flash yellow when Band data is received or data from Azure comes back (to the very phone that sent it).

The HeartRateModel does most of the work: it listens to the Band events, sends that data to Signal/R and checks if data is coming back. Both the receiving of data from the Band and Signal/R is exposed via events (BandDataReceived and PulseDataReceived

The method SendPulse is worth pointing out as it shows how data is actually sent to a Signal/R hub:

public async Task SendPulse(PulseData p)
{
  try
  {
    if (hubConnection.State == ConnectionState.Connected)
    {
      await hubProxy.Invoke(Settings.ServerMethod, p);
    }
  }
  catch (Exception ex)
  {
    Debug.WriteLine(ex);
  }
}

I would also like to draw your attention to the fact the app now needs to ask the user’s consent to be able to connect to the Band’s heart rate sensor. This automatically displays a prompt that the user can accept or cancel, and you need to take care of that in your code:

public async Task<bool> StartListening()
{
  var pairedBands = await BandClientManager.Instance.GetBandsAsync();
  if (pairedBands.Any())
  {
    var band = pairedBands.FirstOrDefault();
    if (band != null)
    {
      bandName = band.Name;
      bandClient = await BandClientManager.Instance.ConnectAsync(band);
      var consent = 
        await bandClient.SensorManager.HeartRate.RequestUserConsentAsync();
      if (consent)
      {
        var sensor = bandClient.SensorManager.HeartRate;
        sensor.ReadingChanged += SensorReadingChanged;
        await sensor.StartReadingsAsync();
      }
      return consent;
    }
  }
  return false;
}

Then there is this little oddity about Windows Phone 8.1 Universal apps and Signal/R – to get data back you actually have to set Long Polling as communication method when starting the hub connection or else the data simply won’t come down

public async Task Init()
{
  hubConnection = new HubConnection(Settings.HubUrl);
  hubProxy = hubConnection.CreateHubProxy(Settings.HubName);

  hubProxy.On<PulseData>(Settings.ClientMethod, pulse =>
  {
    if (PulseDataReceived != null)
    {
      PulseDataReceived(this, pulse);
    }
  });

  await hubConnection.Start(new LongPollingTransport());
}

My sincere thanks to Irmak Tevfik for writing this article  explaining that or I would probably still be scratching my head now.

Conclusion

With some very basic stuff published on Azure and utilizing the power of the Universal Windows Platform apps I was able to write a distributed system for displaying my heart rate – using not only one code base but a single app that can be deployed on a range of devices; devices that could basically be everywhere on the world, as long as they have internet access. This really shows the power of current and near future Microsoft technology, making things that used to be extremely hard just very very simple. If I understand this right, this would even be able to run on XBox One and HoloLens, although I am not quite sure a gamer would like to see a big circle flashing on his console, nor would a HoloLens user like to see that more or less blocking his field of vision. But the point is that using these technologies it becomes very easy to connect a lot of devices very easily using one type of app.

There are some issues still to be addressed in this solution. Basically everyone can post to the hub now and everyone can connect to it, so this opens great possibilities of completely destroying an on-stage demo. You clearly need to be sure that not everyone can post heart rate data to your hub or, if you want to make this available as a cloud service, at least make sure no-one get’s other people’s data pushed to it. There are several ways to achieve that, but that’s beyond the scope of this post.

I actually have to credit my wife for thinking of an actual practical application of this crazy demo, for when I showed this to her in my typically overenthusiastic geek way, her reaction was not the typical geeks’ wife “well that’s nice dear” but “so if I understand you right, I can make a doctor listen to my heart rate even if I am on a holiday somewhere far away?” :).

The demo solution, as always when applicable, can be found here

16 May 2015

Getting Signal/R clients to work on Windows 10 UWP apps

Very recently Windows 10 IoT Core became available – just one of the very exiting technologies Microsoft are cranking out like crazy now. This will enable even the humblest of devices to run Universal Windows Platform apps  Especially in the IoT space technologies like Signal/R are key – to enable devices to quickly exchange and distribute data. Unfortunately, if you add the Microsoft.AspNet.SignalR.Client NuGet package to an UWP and try to connect to a hub, using this code:

var hubConnection = new HubConnection("http://www.microsoft.com");
await hubConnection.Start();

You will run into this error:

An exception of type 'System.IO.FileNotFoundException' occurred in Microsoft.AspNet.SignalR.Client.dll but was not handled in user code

Additional information: Could not load file or assembly 'System.Net, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes' or one of its dependencies. The system cannot find the file specified.

And I was not the first one, as can be seen on the Github issues page and this Stackoverflow page.This has nothing to do with Silverlight 5, as is suggested in the comments. Apparently there is some NuGet target mismatch. I don’t quite understand the finer points, but I have found a workaround.

  • I cloned the SignalR repository from Github.
  • When that was done, I opened the Microsoft.AspNet.SignalR.sln using Visual Studio 2015RC.
  • I found the project “Microsoft.AspNet.SignalR.Client.WinRT”. This needed retargeting to Windows 8.1
  • In the configuration manager, I looked for the same project again, and changed the platform from x86 to AnyCPU. For some reason that takes quite some time to complete.

image

  • Then I built just the Microsoft.AspNet.SignalR.Client.WinRT project – nothing else
  • In the folder <your-root-here>\SignalR\src\Microsoft.AspNet.SignalR.Client.WinRT\bin\Release you will now find a Microsoft.AspNet.SignalR.Client.dll. Copy this file into for instance in a Binaries solution folder in the solution containing the app in which you want to use Signal/R
  • In the app that gave you the “File not found” error, locate the two SignalR references and delete these

image

  • Now make a reference to the one “Microsoft.AspNet.SignalR.Client.dll” you copied into the Binaries folder
  • Start the app again
  • You will still get an error :), but that’s because there’s no actual Signal/R hub running on www.microsoft.com. If you point this code to a real Signal/R hub, it will work. I have tried on a PC, a Windows 10 mobile phone, and a Raspberry PI2.

Demo solution with both the working (‘”Working”) and non-working app (“Kaputt”)  can be found here. If you don’t feel like repeating all my steps and just want to grab the assembly you need, get it directly from github here.

05 May 2015

Fixing Windows 10 emulators not showing up in VS2015RC or not having internet access

On one of my two PCs running Windows 10 10074 the Phone emulators did not have internet access, which is a bit of a hindrance if you want to write a demo that requires internet access, and after upgrading to VS2015RC from CTP6 I had an even bigger problem: the emulators did not even show up anymore. The only thing I saw was “Start”

image

I had inadvertently installed the Android Emulator, and there are apparently some issues with that. No amount of removing selected parts, repairing of modifying could fix it. So I went for the drastic solution: I uninstalled everything that remotely looked like it had something to do with VS2015RC, including the duplicate entry of the Windows Phone 8.1 emulators.

Then I opened the Hyper-V manager, removed all virtual machines there of Windows Phone 8.1 and 10 devicesimageAnd I deleted also all virtual switches. Now at one point the Hyper-V manager did not allow me to delete a virtual switch so I went for the bigger kill and removed all of Hyper-V by going into Programs and Features and uninstalling Hyper-V itself, and all the comes with it.

image
This requires the PC to reboot. After the reboot, I re-enabled Hyper-V again (which requires another reboot) and then I installed Visual Studio 2015RC again, sans the Android emulator and the cross-platform development kit, and then at last my Windows 10 emulators showed up – and they also now have internet access, albeit it takes them a minute to get it.

Your mileage may very, but this worked for me.