Better property roundtrip for Azure IoTEdge Module Twins

The newest version of the Azure IoT Edge is still in public preview but this one makes the Edge truly intelligent!

We are now able to both distribute and manage logic on-premise.

Version one of the IoT Edge was and still is based on running an executable. And each module (ingest, transform, distribute) is a class in a dynamic library. And all modules are connected using a broker serving the role as MessageBus. Messages between modules are exchanged using configurable routes.

This first version is still available for download. But the new version is fundamentally different.

Modules hosted as a container

In the new version, we still see modules connected by and exchanging messages using a local MessageBus. But Microsoft has redefined the modules into separate executables, each running is a different container. The containers are hosted in Docker and can run on Linux or Windows.

Microsoft already provided a lot of modules in the Docker Hub, for OPC UA support, for Modbus support, etc. And you can deploy your own modules too!

There are multiple examples available on the Microsoft website which explain how to get started.

In this blog, I show how tweaking some example code leads to even better management and a better understanding of what’s happening on the Edge using the Module Twins properties.

Twin properties

Next to deploying modules from the cloud to the Edge devices, the second great feature is updating parameters.

The IoT Hub already supports device twins for registered devices and the new IoT Edge solution now supports module twins (seen at the lower right side):

What does this mean?

We are now able to change the behavior of deployed standard modules during deployments and afterward, without the need of redeploying that module.

Standard (insufficient) example

If you want to see how this is working out for you, check out the code in this tutorial from Microsoft.

There you will find an (insufficient) code example like this:

/// <summary> 
/// Copy of code calling orignal handler in Init method 
/// Found in https://docs.microsoft.com/en-us/azure/iot-edge/tutorial-csharp-module 
/// </summary> 
static async Task Init(string connectionString, bool bypassCertVerification = false) 
{ 
  ...
  // Attach callback for Twin desired properties updates 
  await ioTHubModuleClient.SetDesiredPropertyUpdateCallbackAsync(onDesiredPropertiesUpdate, null);
  ...
}
/// <summary>
/// Copy of original handler from https://docs.microsoft.com/en-us/azure/iot-edge/tutorial-csharp-module
/// </summary>
static Task onDesiredPropertiesUpdate(TwinCollection desiredProperties, object userContext)
{
  try
  {
    Console.WriteLine("Desired property change:");
    Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));

    if (desiredProperties["TemperatureThreshold"]!=null)
      temperatureThreshold = desiredProperties["TemperatureThreshold"];
  }
  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;
}

During the initialization of the module on the Edge device, a callback function is connected to the event which is called when one or more module twin properties are changed.

This example has two flaws:

  1. The desired properties are only read after initialization, not during initialization. So property update will be ignored if they are done too early
  2. After the desired properties are handled, the module does not report back the changes. So you are in doubt whether the changes are picked up

Luckily, these issues are not hard to fix.

Solution

Here is an example of how to both handle desired properties during initialization and reporting back the changes. The most interesting part is making use of the Reported Properties in the twin.

In this example, a module executes a job every few seconds. The interval is set to five seconds by default. We can override the interval during initialization or afterward. If a desired ‘interval’ property is passed, the local property is updated and the device twin in the cloud is updated using the reported properties:

using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;

static async Task Init(string connectionString, bool bypassCertVerification = false)
{
  ...
  // Execute callback function during Init for Twin desired properties
  var twin = await ioTHubModuleClient.GetTwinAsync();
  await onDesiredPropertiesUpdate(twin.Properties.Desired, ioTHubModuleClient);

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

  Console.WriteLine("Module client module twin initialized.");
  ...
}

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

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;
}

Note: here, the ‘onDesiredPropertiesUpdate’ method is not  implementing the async-await pattern.

Let’s see this code in action.

Example

Deploy a module containing the code shown above. I have written a Heartbeat Module which sends a heartbeat every [Interval] milliseconds:

And we override the Interval using the desired properties option:

Once deployed, the messages are sent every five two seconds (so the initial value of 5000 is overwritten with 2000):

And if we check the module twin, we see how the new Interval value is reported:

So yes, the overruled Interval is reported back to the IoTHub.

Note: during the tests I executed for this blog, I notice it is wise to give each new version of the same module a unique identifier name. It seems that the internal logic of the module registration in the IoTHub sometimes mixes up the update of device twin properties

Updating the Module Twin

So let’s change the Desired Properties, change Interval into 4000 and save/send the value:

After we have been notified that the change (using pop-up notifications in the portal) is sent, we renew the page:

Again, the updated value is reported and the version number is updated.

We can check the change in de Device Explorer also:

Here we see the proof of the change. The interval is updated.

Conclusion

We have seen how to update devices using the module twin and we have experienced the feedback from the Edge. This is still the Public Preview of the IoTEdge so the experience can vary once it’s generally available. But this Module Twin feature is a great addition for managing devices and modules living on the Edge.

In this example, we have updated one device/module by hand. It beats updating the device using some additional software deployment mechanism or even a USB Hub. But this is a bit cumbersome if you have thousands of devices so you better want this to be automated.

And this brings up the question: who will update all these IoT devices and modules in the future?

Is this a job for the IT guys who have their hands full with enterprise devices and applications? Or is this a job for the OT guys who have their own real-life problems with real-life devices?

I see the rise of a new kind of job. What we need is a ‘digital cowboy’, a ‘device twin shepherd‘. We need people who have knowledge about both IT and OT and who will herd a herd, not pet a pet…

Advertenties