A very simple complex IoT Hub example

The Microsoft Azure service called IoT Hub is now also available in West-Europe for a couple of weeks. To be precise, it’s available as a service in East Asia, East US, North Europe, Southeast Asia, West Europe and West US.

It is good to know that there are basically three flavours available: Free, S1 standard and S2 standard. The free edition only support one, uniquely registered, device which can send eight thousand messages a day. The other versions support 400.ooo messages to six million messages a day. If you want to support more devices/messages, please contact Microsoft 🙂

Note: You can have only one Free IoT Hub in your Azure subscription. And turning a S1 IoT Hub into the free one is not possible.

Keep in mind that messages have a certain size. If the actual size of a single message is bigger than 0.5KB (free version) or 4KB (S1 or S2) it will be counted as multiple messages.

Getting started with IoT Hub is not that hard. You can start with the fine documentation at the Azure website. But that documentation is still limited. Integrating with StreamAnalytics ea. is not described. So here I present a more elaborate example (but keep in mind the MSFT documentation).

This is part 1 of a series of blogs about IoT Hub communication using UWP:

  • Part 1: A very simple complex IoT Hub example
  • Part 2: Using the Visual Studio 2015 IoT Hub Extension and IoT Hub Device explorer
  • Part 3: Adding the power of BI to your IoT Hub
  • Part 4: Closing the Windows IoT Core feedback loop using Azure Functions
  • Part 5: PowerBI showing telemetry of past ten minutes, always!
  • Part 6: Add more flexibility to StreamAnalytics

What you need for a simple IoT solution are:

  • Windows 10 Device (at least the Nuget packages for consuming the IoT hub must be installed)
  • IoT Hub
  • StreamAnalytics
  • SQL Azure
  • Something to represent the data (out of scope here)

I will show you the steps to build that simple solution.

Azure Portal – IoT Hub

First create an IoT Hub, you can do this in the Azure portal. After construction, take note of the following values, you will need them later on:

  • The connection string of the access policy name ‘iothubowner’of the hub
  • The IoT Hub Uri: eg.  Parking.azure-devices.net

How to register your device at the IoT Hub

Each device connecting to the hub needs a unique registration key and a more descriptive, also unique, name. With this code we can register the device called eg. ‘ParkingLevelOne’:

internal class Program
{
    private static RegistryManager registryManager;
    private static string connectionString = "HostName=...=";
 
    private static void Main(string[] args)
    {
        Console.WriteLine("About to register a device...");
 
        registryManager = RegistryManager.
                 CreateFromConnectionString(connectionString);
        AddDeviceAsync().Wait();
        Console.ReadKey();
    } 
 
    private async static Task AddDeviceAsync()
    {
        string deviceId = "ParkingLevelOne";
        Device device;
        try
        {
            Console.WriteLine("New device:");
            device = await registryManager.
                         AddDeviceAsync(new Device(deviceId));
        }
        catch (DeviceAlreadyExistsException)
        {
            Console.WriteLine("Already existing device:");
            device = await registryManager.
                                 GetDeviceAsync(deviceId);
        }
 
        Console.WriteLine("Generated device key: {0}",
                device.Authentication.SymmetricKey.PrimaryKey);
    }
} 

You will need the Microsoft.Azure.Devices nuget package.

We get a unique code, the device key. Remember it, we need it also later on. Notice that registering your device it typically done by an administrator, not in the field on the actual device. The connection string of the iothubowner is not to be shared inside your client apps!

Send a D2C message from your device

We have a registered device and we have an IoT hub, now let’s send a message to the IotHub. All we need is this simple code example:

private static DeviceClient deviceClient = null;
private static string iotHubUri = "Parking.azure-devices.net";
private static string deviceKey = "...=";
 
private async Task SendSensorData(string deviceId,
                      string parkingLot, string state)
{
  if (deviceClient == null)
  {
    deviceClient = DeviceClient.Create(iotHubUri,
      new DeviceAuthenticationWithRegistrySymmetricKey(
       "ParkingLevelOne", deviceKey), TransportType.Http1);
  }
 
  var telemetryDataPoint = new
  {
    deviceId = deviceId,
    parkingLot = parkingLot,
    parkingState = state,
  };
 
  var messageString = JsonConvert.SerializeObject(
                                     telemetryDataPoint);
  var message = new Message(
                Encoding.ASCII.GetBytes(messageString));
 
  await deviceClient.SendEventAsync(message);
 
  await Dispatcher.RunAsync(
           CoreDispatcherPriority.Normal, () =>
  {
    tbSent.Text = string.Format("{0} > Sending message: {1}",
                             DateTime.Now, messageString);
  });
}

You will need the Microsoft.Azure.Devices.Client nuget package.

Note: As you can see, we are communicating with the Hub using the Http1 protocol. This is because UWP apps are currently not supporting Amqp or Mqtt!

So now, you can see: we can send out telemetry.

Checking the arrival of your telemetry

But is the telemetry arriving at the hub? Use the simple code to check it out:

internal class Program
{
    private static string connectionString = "HostName=...=";
    private static string d2cEndpoint = "messages/events";
    private static EventHubClient eventHubClient;
 
    private static void Main(string[] args)
    {
        Console.WriteLine("Receive messages\n");
        eventHubClient = EventHubClient.
           CreateFromConnectionString(
              connectionString, d2cEndpoint);
 
        var d2cPartitions = eventHubClient.
                GetRuntimeInformation().PartitionIds;
 
        foreach (string partition in d2cPartitions)
        {
            ReceiveMessagesFromDeviceAsync(partition);
        }
        Console.ReadLine();
    }
 
    private async static Task ReceiveMessagesFromDeviceAsync(
                                          string partition)
    {
        var eventHubReceiver = eventHubClient.
              GetDefaultConsumerGroup().
                CreateReceiver(partition, DateTime.UtcNow);
        while (true)
        {
            EventData eventData = await eventHubReceiver.
                                                ReceiveAsync();
            if (eventData == null) continue;
 
            string data = Encoding.UTF8.GetString(
                                      eventData.GetBytes());
            Console.WriteLine(string.Format(
              "Message received. Partition: {0} Data: '{1}'",
              partition, data));
        }
    }
} 

You will need the WindowsAzure.ServiceBus nuget package.

While you are sending messages, these will be shown in the watcher app:

01Watcher

By now it must be clear that the IoT Hub is just a fancy message bus, sort of. The biggest difference is that it can handle a very big load of millions of telemetry messages. And these messages are only accepted from devices with a valid administration key.

Adding a SqlAzure database to persist the telemetry

Before we take a look at the messages in the IoT hub and start consuming them, let’s define a database to put the data in. Please add a SqlAzure database named ParkingMobileService_db and fill it with this table:

create table [dbo].Parking
(
  deviceId nvarchar(50),
  parkingLot nvarchar(50),
  parkingState nvarchar(50),
  EventProcessedUtcTime datetimeoffset
)
 
ALTER TABLE [dbo].AtosParking
  ADD Id INT IDENTITY
 
ALTER TABLE dbo.AtosParking
  ADD CONSTRAINT PK_AtosParking
  PRIMARY KEY(Id) 

This will store our telemetry messages. Do you see that I use the same column names as the fields of the telemetry? But I also add a timestamp…

Handling telemetry using StreamAnalytics

So now we have an IoT Hub full of messages waiting to be handled on the left side. On the right side we can persist the message in a database. And in the middle we put StreamAnalytics.

StreamAnalytics has three main components:

  1. Inputs. Our IotHub
  2. Outputs. Our database table
  3. Query. Logic we have to write
So first add the input and output. After that, add the following (extremely simple) query. We just put the telemetry in the database unfiltered:
SELECT
    deviceId ,
    parkingLot,
    parkingState,
    EventProcessedUtcTime
INTO
    Parking
FROM
    ParkingStream

Start the StreamAnalytics job and send some telemetry. Check the table for persisted data:

02PersistedData

There they are, your data is persisted. Beware that this is a simple example, not a single telemetry message is lost but also not filtered. Check what’s possible with the query, it’s worth it!

The timestamp is an extra telemetry field produced by the IoT Hub.

Representing the data

Now the database is filled, you can start thinking about representing the data in a nice portal. This is out of scope for this example. Notice that it’s not simple to show updates automatically, there is no trigger available for new telemetry. But I use a simple web job to check the database table every X seconds.

Sending a C2D message

There is one extra step that is really interesting. IoT Hub makes it possible to send back commands to specific devices. And is fairly simple!

Let’s pretend you used a website to show the data in a nice portal. In that same portal you can add functionality to select a device. And you send back a message to that device:

public class C2DController : ApiController
{
    private ServiceClient serviceClient;
    private string connectionString = "HostName=...=";
 
    public async Task<string> Get(string id)
    {
        serviceClient = ServiceClient.
               CreateFromConnectionString(connectionString);
        var commandMessage = new Message(
                  Encoding.ASCII.GetBytes(id));
        await serviceClient.SendAsync(
                 "ParkingLevelOne", commandMessage);
 
        return "Sent";
    }
} 

You will need the Microsoft.Azure.Devices nuget package.

The message passed in the id string will be send to the ‘ParkingLevelOne’ device.

Note: this example is a WebApi Rest endpoint. For simplicity this is represented as a GET without any security.

The last part will be handling the message in the device:

private async void ReceiveC2dAsync()
{
    if (deviceClient == null)
    {
        deviceClient = DeviceClient.Create(iotHubUri,
      new DeviceAuthenticationWithRegistrySymmetricKey(
      "ParkingLevelOne", deviceKey), TransportType.Http1);
    }
 
    while (true)
    {
        Message receivedMessage =
                  await deviceClient.ReceiveAsync();
 
        if (receivedMessage == null)
        {
            await Task.Delay(TimeSpan.FromSeconds(2));
 
            continue;
        }
 
        await Dispatcher.RunAsync(
          CoreDispatcherPriority.Normal, () =>
        {
          var command = Encoding.ASCII.GetString(
                             receivedMessage.GetBytes());
 
          tbSent.Text = string.Format(
                          "Received command: {0}", command);
        });
 
        await deviceClient.CompleteAsync(receivedMessage);
 
        await Task.Delay(TimeSpan.FromSeconds(2));
    }
} 

First, again we are using the Http1 protocol. Again, for UWP apps, no other protocol is supported at this moment. So we will receive back an answer without any delay, hence the loop. To prevent Azure throthling I have added a delay of two seconds.

Note: Microsoft recommends an interval of 25 minutes if you are supporting 1 million devices.

And at the end we execute this CompleteAsync(). This is a signal that the message received is handled well and can be removed from the IoT Hub queue.

Conclusion

As you have experienced, it’s not that hard to build a simple but complete IoT Hub solution. We can receive and process telemetry and we can send back commands.

But make sure your solution will be robust and it can handle exceptions well. A real world solution will NOT be handled with care.