How to build a simple IoT Edge Version 2 Heartbeat Module

Update March 28, 2018: a new solution structure is introduced. Follow this migration path if needed.

Recently, the Microsoft Azure IoT Edge platform was updated with more features, better documentation and lots of goodies. Version two is brand new and still in Public Preview. The new features and fixes are both very welcome and promising.

Yes, the learning curve of this new version is steep, especially if you are new (like me) to Docker. But once you have started building your own Edge solution, things seem to fall into place quite well.

A logical flow, on learning this new platform, seems to be described in the Microsoft documentation:

  1. Simulate an IoT Edge device in Windows
  2. Simulate an IoT Edge device in Linux
  3. Develop and deploy a C# module
  4. Deploy Azure Stream Analytics as a module
  5. Deploy Azure Machine Learning as a module
  6. Deploy Azure Functions as a module

Looks easy, doesn’t it?

But by reading the comments on the different pages, it seems people are still confused.

The biggest tip is: DO NOT MIX VERSION ONE AND VERSION TWO DOCUMENTATION.

Version one is/was based on one executable (gw.exe) which injects classes from DLL’s (a configuration file has to be supplied). Modules are just classes in the DLL’s.

Version two is based on Docker Containers, each module is a separate container and therefore each module is a separate executable. These modules share the same logic on how to connect to a shared message bus which provides the routing of messages between the modules. This ‘runtime’ has to be installed on the Edge machine, next to/outside the Docker containers.

Note: the good thing is that the architecture still stands, multiple modules on top of a message bus and messages are routed. The best thing is that the new routing solution is far more intelligent, different messages are separated from each other. And Microsoft has provided some guidance for migration for your ‘old’ modules.

So work your way through the documentation provided above. After that, check out my recent blogs:

OK, I know, the learning curve is still steep.

How about if I add an example of a simple but functional module?

In the past, I have written a series of blogs on how to start with version one of the  Azure IoT Edge (it was called the Azure IoT Gateway). I still use that same logic in a few projects (which I want to migrate to version two when I have some time left). But the old version is a great way to start: it runs stable, you can migrate later on and it is a nice alternative if you can not run Version 2 on your hardware (due to the OS limitations).

The most simple Version one module I have written is the Heartbeat module. This was the first version two module I wrote, It’s a good starting point for me and I think also for you.

What does the Heartbeat module do?

What is the purpose of this module? It’s simple, it just sends a timestamp message at a predefined interval. The body of the message itself is not that important (current time), it’s the fact that I keep receiving messages I am interested in!

As long as these messages are received, I know that:

  • The server is up and running
  • The ports are open
  • The IoTHub can be connected
  • The Device registration is ok, the credentials are ok
  • The number of daily messages is not exceeded
  • The routing is ok, both on the Edge and on the IoTHub
  • My Azure Functions are running
  • My Notification platform is running

And in Version one, I changed the body a bit afterward for my customer project. Now, the body of the message contains the size of the executable. So I can see if my app is leaking memory in the IoT Edge modules (which has become very valuable information in that project due to external logic usage :-)).

So I have built the same module again using the guidance shown above, especially building and deploying your own C# module.

The module

If you follow that guide, you will setup your development environment and your laptop will become the Edge device.

Note: VS Code is the primary development environment, not Visual Studio!

The version two module will be written in .Net Core. From the command line, you will generate a c# project, from a template, using this line:

dotnet new aziotedgemodule -n HeartbeatModule

A new directory called HeartbeatModule with the C# project will be generated.

This will provide a console app project which can be altered (you add your own logic to it). Afterward, after compiling, it can be packaged to a Linux or Windows Docker Image and it can be pushed to your own public or private Docker Image repository. This is all done using extensions, extra tooling, in VS Code.

This blog is not a complete tutorial on how to build the complete app. For that, please follow the Microsoft guide and alter that code into this.

Note: The same project is available on Github, for your convenience!

These are the changes I have made:

static async Task Init(string connectionString, bool bypassCertVerification = false)
{
  Console.WriteLine("Connection String {0}", connectionString);

  MqttTransportSettings mqttSetting = new MqttTransportSettings(TransportType.Mqtt_Tcp_Only);
  // During dev you might want to bypass the cert verification. It is highly recommended to verify certs systematically in production
  if (bypassCertVerification)
  {
    mqttSetting.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
  }
  ITransportSettings[] settings = { mqttSetting };

  // Open a connection to the Edge runtime
  DeviceClient ioTHubModuleClient = DeviceClient.CreateFromConnectionString(connectionString, settings);

  // Attach callback for Twin desired properties updates
  await ioTHubModuleClient.SetDesiredPropertyUpdateCallbackAsync(onDesiredPropertiesUpdate, ioTHubModuleClient);

  // Execute callback method for Twin desired properties updates
  var twin = await ioTHubModuleClient.GetTwinAsync();
  await onDesiredPropertiesUpdate(twin.Properties.Desired, ioTHubModuleClient);

  await ioTHubModuleClient.OpenAsync();

  Console.WriteLine("Heartbeat module client initialized.");

  var thread = new Thread(() => ThreadBody(ioTHubModuleClient));
  thread.Start();
}

private static async void ThreadBody(object userContext)
{
  while (true)
  {
    var deviceClient = userContext as DeviceClient;

    if (deviceClient == null)
    {
      throw new InvalidOperationException("UserContext doesn't contain " + "expected values");
    }

    var heartbeatMessageBody = new HeartbeatMessageBody
    {
      timeCreated = DateTime.Now.ToString("hh:mm:ss")
    };

    var jsonMessage = JsonConvert.SerializeObject(heartbeatMessageBody);

    var pipeMessage = new Message(Encoding.UTF8.GetBytes(jsonMessage));

    pipeMessage.Properties.Add("content-type", "application/edge-heartbeat-json");

    await deviceClient.SendEventAsync("output1", pipeMessage);

    Console.WriteLine($"Heartbeat sent {heartbeatMessageBody.timeCreated}");

    Thread.Sleep(Interval);
  }
}

private static int Interval { get; set; } = 5000;

private static Task onDesiredPropertiesUpdate(TwinCollection desiredProperties, object userContext)
{
  if (desiredProperties.Count == 0)
  {
    return Task.CompletedTask;
  }

  try
  {
    Console.WriteLine("Desired property change:");
    Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));

    var deviceClient = userContext as DeviceClient;

    if (deviceClient == null)
    {
      throw new InvalidOperationException("UserContext doesn't contain " + "expected values");
    }

    var reportedProperties = new TwinCollection();

    if (desiredProperties["interval"] != null)
    {
      Interval = desiredProperties["interval"];

      reportedProperties["interval"] = Interval;
    }

    if (reportedProperties.Count > 0)
    {
      deviceClient.UpdateReportedPropertiesAsync(reportedProperties).ConfigureAwait(false);
    }
  }
  catch (AggregateException ex)
  {
    foreach (Exception exception in ex.InnerExceptions)
    {
      Console.WriteLine();
      Console.WriteLine("Error when receiving desired property: {0}", exception);
    }
  }
  catch (Exception ex)
  {
    Console.WriteLine();
    Console.WriteLine("Error when receiving desired property: {0}", ex.Message);
  }

  return Task.CompletedTask;
}

private class HeartbeatMessageBody
{
  public string timeCreated { get; set; }
}

What is exactly altered and added in the template project for my module?

The only thing from the original code altered by me is (the last part of) the Init() method. I leave the certificate stuff for what it is and I only focus on the “ioTHubModuleClient”.

And then I added two things to the project:

  1. a separate thread which generates a message every few seconds (the interval is configurable using the Module Twin)
  2. a better handling of Module Twin events. If a Module Twin update arrives, I check for the desired Interval. Once I have handled it, I report it back to the Azure Portal Module Twin

In this project, there is logic on how to handle message received by an IoTEdge module. Our Heartbeat module only sends messages. For an example on how to react on incoming (routed) messages, follow the FilterModule example provided by Microsoft.

So to sum it up, you only have to care about the members of the class:

  • Init method (remove the message handling line, add a few lines of code)
  • ThreadBody method (add method)
  • onDesiredPropertiesUpdate event handler (add method)
  • Interval property (which just could have been a class member) (add property)

And that’s all there is to do, just compile it, package it, push it to docker and consume it in the Azure Portal.

Interesting facts

There are some interesting facts here, regarding how the module performs:

Module Twin

This module supports one ‘desired’ property in the module twin:

  • “interval”:5000

This alters the interval of the heartbeat in milliseconds. The default value is 5000.

After reading this value, the module will report the value back.

Routing inputs and outputs

This module has no logic attached to the routing input.

The messages created are sent using output output1

Output messages

The output message uses this format:

private class HeartbeatMessageBody 
{ 
  public string timeCreated { get; set; } 
}

the message is sent with the following message property:

  • “content-type”, “application/edge-heartbeat-json”

Docker hub

To make it even more convenient for you, I have made this docker image available publically.

You can skip the code above and pull it with docker pull svelde/iot-edge-heartbeat. But I suggest to use svelde/iot-edge-heartbeat:1.0 when you deploy it using the Azure portal.

Use this already deployed version and compare it’s functionality with the code shown above.

Conclusion

Here you have it, a simple example of a C# module which creates ‘telemetry’ and sends it to the Azure IoTHub.

Again, the learning curve is steep, but once you have deployed your first modules and figured out how the routing works, you will be surprised by the simplicity to roll out new modules.

Advertenties

Reacties zijn gesloten.