Azure IoT Device lifecycle events (part 1)

The Azure IoT Hub is the main gateway for Azure IoT-related device connectivity.

It features several useful features to make your IoT developer life easy.

It offers a registry for devices so each device has its own credentials. Each device is capable to ingest device-to-cloud telemetry data into Azure and the IoT Hub also offers several ways for cloud-to-device communication (e.g. desired properties and direct methods).

The IoT Hub is also capable to report any device change event during the lifecycle of that device:

It can generate messages when a device is created or deleted. It also generated messages when the device twin is changed. It even generates a message when the ‘digital twin’ is updated.

Note: The digital twin update is related to Azure IoT Plug and Play. This is out of scope for this post. Though, an example of the digital twin change event is seen here.

In this post, we will look at the format of these messages.

Note: There are many more awesome IoT Hub features. Today, we focus on the non-telemetry message routing.

This is the first part of a two-part post. In the second part, we check out the support for Azure IoT Edge modules.

Why do I care about these messages?

Although these messages are not directly part of the data flow from device to enterprise, there are several reasons to make these part of your IoT solution.

First, these messages are generated when something happens with your device registration or configuration. If you do not get notified of these messages, you are not able to monitor from the IoT Hub perspective. Also, you have no historical overview for audits. New settings overwrite older settings which are then lost in time.

Secondly, using these events you can use the IoT Hub as a single point of truth. If you listen to these changes, you can automatically duplicate them in other solutions. This is especially important if the same device is registered in another ‘cloud’ too. This way, you only have to configure your devices in one place. For example, The Things Network supports a two-way connection where the IoT Hub is leading.

How do I access these messages?

The IoT Hub can route incoming telemetry messages as events to several other Azure Resources.

Originally, the IoT Hub exposed an ‘event-hub-compatible endpoint’. Multiple Azure resources like Azure Functions, Azure Stream Analytics, and Azure TimeSeriesInsights are (still) capable to consume this endpoint.

Later on, a complete routing mechanism was introduced in Azure IoT Hub. Based on rules, the same incoming message could be sent to multiple kinds of resources: Azure Event Hubs, Azure Storage accounts, and Azure Service Bus (both queue and topic). If you activate this, the original endpoint is still active, it’s made available by default as a separate kind of endpoint.

Note: IoT Hub routing is best used as a technical solution to send messages towards other Azure resources based on basic rules. If you need more advanced and flexible rules, the Azure Stream Analytics jobs are far superior to the basic routing in the IoT Hub.

What can be done for telemetry messages, can also be done for ‘non-telemetry event’ messages related to the device lifecycle:

Using this IoT Hub routing mechanism, we are flexible in routing and duplicating incoming (non-) telemetry messages:

Let’s check out how the messages look like.

Anatomy of an IoT Hub message

Messages coming from the IoT Hub are wrapped as ‘EventData’ messages, as described here.

Basically, these messages are built up in three parts:

  1. Event data, the message body inside the class
  2. System properties, a property bag containing key values pairs typically added by the Azure resources (like IoT Hub related values like the partition key)
  3. Properties, aka Application properties. These properties are typically added next to the body to explain the context of the body message.

Later on, we will see that we need to check out these properties too. There is value in these properties!

Routing towards an endpoint

Adding Azure IoT Hub message routes is not that hard. All you need to do is to add:

  • A custom endpoint with a unique name pointing to another Azure resource like storage or message bus or EventHub (or you skip this take the already existing ‘events’ endpoint)
  • A custom route where you specify:
    • Unique name
    • The endpoint of your choice
    • A rule to specify which messages are picked up.

Note: Technically, some messages could be ignored by all custom routes. In that case, the default route could act as a fallback route.

I started with this simple route:

Here, I route ‘Device connection state’ events to the default ‘events’ route.

I accept all messages (the routing query is set to ‘true’):

Note: As you can see, the same route can point to multiple endpoints.

Below, I attached an Azure function to the default endpoint to access the incoming messages.

Let’s take a closer look at that code.

Consuming EventData messages using an Azure Function

Creating an Azure Function and selecting an IoT Hub as trigger is not that hard.

Just start the wizard within the Function App of your choice:

There is a default trigger available.

Just give it a name, add a connection to the IoT Hub and specify a custom consumer group:

Note: This consumer group must be registered already in the IoT Hub. The reason to use a consumer group is to prevent other resources trying to get the same message from the same consumer group (resulting in errors). By specifying extra resource groups, each resource gets its own copy of the messages.

Note: The IoT Hub connection (and related connection string) is stored in the Azure Function app settings.

If the connection of your choice is not added yet, you have to add a new one:

After creation, you see some sample trigger code. We are going to overwrite this because the event is represented by a string. The Azure Function logic is quite smart because it can figure out it needs to represent the event body.

We want to access the event property bags. Just changing the string type into the EventData type is enough to get full access to the underlying event, including the properties.

The Azure Function code we use for this demonstration (I use C# in this example) is not that impressive, we just log the incoming message, including the properties:

using System; 
using System.Text; 
using Microsoft.Azure.EventHubs; 

public static void Run(EventData myIoTHubMessage, ILogger log)
{
  log.LogInformation($"C# IoT Hub trigger function processed a message: {myIoTHubMessage}");
  var bodyText = Encoding.UTF8.GetString(myIoTHubMessage.Body);
    
  log.LogInformation($"body: {bodyText}" );

  log.LogInformation($"App Properties Count: {myIoTHubMessage.Properties.Count}" );

  foreach(var p in myIoTHubMessage.Properties)
  {
    log.LogInformation($"App Property {p.Key} : {p.Value}" );
  }

  log.LogInformation($"System Properties Count: {myIoTHubMessage.SystemProperties.Count}" );

  foreach(var p in myIoTHubMessage.SystemProperties)
  {
    log.LogInformation($"System Property {p.Key} : {p.Value}" );
  }
}

Note: I created this sample function in the Azure portal. It’s recommended to use the Azure Function Extension (e.g. for Visual Studio Code). That extension makes development easier with version control, live debugging, and simplified coding.

We need to add a NuGet package reference because we need access to the library with the EventData class using ‘Microsoft.Azure.EventHubs’.

Alternative: As an alternative to the solution below, you could try to just add the — #r “Microsoft.Azure.EventHubs” — line at the first position of your trigger script. This does only work for packages that the Azure Function team supports by default.

In the Azure portal, this is not that simple thing to add NuGet packages. The project file, by default the location of package references, is not available by default:

Luckily, there is this ‘upload’ button. It supports uploading file to the Function App. We can use this to upload a ‘function.proj’ project file:

Note: this does to work always in just one go. Sometimes just an empty file is added instead of the full file. In that case, just copy/paste the project file content into that empty (project) file and save it. Good luck 🙂

Finally, your project file should look like this and the function compiles successfully:

For convenience, this is the list of packages for my project:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup> 
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.EventHubs" Version="4.3.2" />
  </ItemGroup>
</Project>

We are now ready to ingest events.

Connect/disconnect messages

The IoT device I use today is this device simulation in a browser:

When the device is connected, it can send multiple messages.

I receive this single connection (non-telemetry) message:

We get this message with the message body, (application) properties, and system properties:

body: {"sequenceNumber":"000000000000000001D7F744182052190000000C000000000000000000000001"}

App Properties
hubName : iothub-route-test-weu-ih
deviceId : device5
opType : deviceConnected
iothub-message-schema : deviceConnectionStateNotification
operationTimestamp : 2022-01-16T15:41:57.6869942Z

System Properties
iothub-connection-device-id : device5
iothub-enqueuedtime : 1/16/2022 3:42:27 PM
iothub-message-source : deviceConnectionStateEvents
x-opt-sequence-number : 82
x-opt-offset : 12884901888
x-opt-enqueued-time : 1/16/2022 3:42:27 PM
user-id : System.ArraySegment`1[System.Byte]
correlation-id : f50c0f06-c44a-a6a5-d156-da1b26cd4e5e
content-type : application/json
content-encoding : utf-8

Now it’s clear why we need to access the application properties!

In the application properties, we see the operation type: ‘DeviceConnected’. It also specifies the device identification.

Though, this device identification is also submitted by the IoT Hub in the system properties by default. This is also the case with the operation type. Though, this is called “iothub-message-source”. This message source is called ‘deviceConnectionStateEvents’. In the system properties, it does not explain if the device is connected or disconnected.

The message body also does not tell you why this message is sent.

If I disconnect the device (I reset the simulation in the browser) I get this ‘DeviceDisconnected’ message:

Again, there is no direct relation between the body message and the context:

{"sequenceNumber":"000000000000000001D7F744182052190000000C000000000000000000000002"}

App Properties
hubName : iothub-route-test-weu-ih
deviceId : device5
opType : deviceDisconnected
iothub-message-schema : deviceConnectionStateNotification
operationTimestamp : 2022-01-16T15:49:04.7675206Z

System Properties
iothub-connection-device-id : device5
iothub-enqueuedtime : 1/16/2022 3:49:27 PM
iothub-message-source : deviceConnectionStateEvents
x-opt-sequence-number : 83
-opt-offset : 12884902440
x-opt-enqueued-time : 1/16/2022 3:49:27 PM
user-id : System.ArraySegment`1[System.Byte]
correlation-id : debbadb4-e175-31f8-7b5a-7dc247f3cc8f
content-type : application/json
content-encoding : utf-8

Again, the system properties are not showing if this is a connection change or a disconnection change. That message source is just ‘deviceConnectionStateEvents’.

So, the application properties are clearer.

Disable the fallback route if not done already

If you tried to follow my example, perhaps you encountered the situation that all incoming telemetry messages were sent to the same Azure function too…

In that case, check the ‘Disable fallback route’ button:

Any incoming telemetry message, not touched by any route, is sent to the default ‘event hub-compatible endpoint’ aka ‘events’.

Because I used this same endpoint to check the connect/disconnect messages, I was not interested in the telemetry messages. So, that is why I disabled that fallback option, ignoring all telemetry messages.

Device Lifecycle messages (create/delete)

Now, Let’s check what happens if a device is created or deleted.

First, I change the original route that points towards the Azure Function using the ‘Events’ endpoint.

After the change, it now listens to the creation and deletion of devices:

Then, I create this ‘device6’ and save it:

This will generate this message:

Most importantly, the operation and the device identification are stored in the application properties.

The body is a bit more informative; it contains the initial device twin:

{
    "deviceId": "device6",
    "etag": "AAAAAAAAAAE=",
    "version": 2,
    "properties": {
        "desired": {
            "$metadata": { "$lastUpdated": "2022-01-16T16:16:24.0465798Z" },
            "$version": 1
        },
        "reported": {
            "$metadata": { "$lastUpdated": "2022-01-16T16:16:24.0465798Z" },
            "$version": 1
        }
    }
}

App Properties
hubName : iothub-route-test-weu-ih
deviceId : device6
operationTimestamp : 2022-01-16T16:16:24.0465798Z
opType : createDeviceIdentity
iothub-message-schema : deviceLifecycleNotification

System Properties
iothub-connection-device-id : device6
iothub-enqueuedtime : 1/16/2022 4:16:24 PM
iothub-message-source : deviceLifecycleEvents
x-opt-sequence-number : 84
x-opt-offset : 12884903000
x-opt-enqueued-time : 1/16/2022 4:16:24 PM
user-id : System.ArraySegment`1[System.Byte]
correlation-id : 13b1c90dd99c
content-type : application/json
content-encoding : utf-8

The initial device twin is quite useful.

Yes, it’s quite empty in our case because we just added a simple device.

Though, this device could have been added by an Azure Device Provisioning service. There, device twin settings could be waiting until a device is provisioned and registered in our IoT Hub. In that case, both the device creation and custom device twin change could be done in one go.

Deleting the device gives a similar output:

Again, the message tells us which device is deleted:

{
    "deviceId": "device6",
    "etag": "AAAAAAAAAAI=",
    "version": 3,
    "tags": { "tagTest": "1" },
    "properties": {
        "desired": {
            "desiredTest": "1",
            "$metadata": {
                "$lastUpdated": "2022-01-16T16:24:35.3673226Z",
                "$lastUpdatedVersion": 2,
                "desiredTest": {
                    "$lastUpdated": "2022-01-16T16:24:35.3673226Z",
                    "$lastUpdatedVersion": 2
                }
            },
            "$version": 2
        },
        "reported": {
            "$metadata": { "$lastUpdated": "2022-01-16T16:16:24.0465798Z" },
            "$version": 1
        }
    }
}

App Properties
hubName : iothub-route-test-weu-ih
deviceId : device6
operationTimestamp : 2022-01-16T16:25:02.3796877Z
opType : deleteDeviceIdentity
iothub-message-schema : deviceLifecycleNotification

System Properties
iothub-connection-device-id : device6
iothub-enqueuedtime : 1/16/2022 4:25:02 PM
iothub-message-source : deviceLifecycleEvents
x-opt-sequence-number : 85
x-opt-offset : 12884903680
x-opt-enqueued-time : 1/16/2022 4:25:02 PM
user-id : System.ArraySegment`1[System.Byte]
correlation-id : 13b2fe04a8a6
content-type : application/json
content-encoding : utf-8

And, again we get the (final) device twin representation, showing the device twin while deleting the device registration.

Note: Just for testing purposes, I added that tag and desired property, which is now seen in the message body.

Just like the connection/disconnection of a device, the system properties call this ‘deviceLifecycleEvents’ without specifying what’s actually happening. Again, the application properties are clearer.

Device twin changes – TwinChangeEvents

This time, we update the custom route so we listen to specific Device Twin changes (while the device is connected or disconnected):

Next, I change the device twin of an existing device:

I added both the tag, the desired property, and the reported property.

Note: the reported properties change will be ignored in general by the IoT Hub. Adding reported properties is normally done by devices, on devices.

Once saved, a message is received with operation type “twinChangeNotification”:

We see how the device twin looks now:

{
    "version": 3,
    "tags": { "tagValue": 5 },
    "properties": {
        "desired": {
            "desiredValue": 55,
            "$metadata": {
                "$lastUpdated": "2022-01-16T16:36:53.8146535Z",
                "$lastUpdatedVersion": 2,
                "desiredValue": {
                    "$lastUpdated": "2022-01-16T16:36:53.8146535Z",
                    "$lastUpdatedVersion": 2
                }
            },
            "$version": 2
        }
    }
}

App Properties
hubName : iothub-route-test-weu-ih
deviceId : device5
operationTimestamp : 2022-01-16T16:36:53.8146535Z
iothub-message-schema : twinChangeNotification
opType : updateTwin

System Properties
iothub-connection-device-id : device5
iothub-enqueuedtime : 1/16/2022 4:36:54 PM
iothub-message-source : twinChangeEvents
x-opt-sequence-number : 86
x-opt-offset : 12884904512
x-opt-enqueued-time : 1/16/2022 4:36:54 PM
user-id : System.ArraySegment`1[System.Byte]
correlation-id : 13d32d2158d3
content-type : application/json
content-encoding : utf-8

As expected, the tags change and the desired properties change are shown.

The reported property change is ignored. This is OK. Normally, the reported property change is propagated by the device, not from the server-side (as I did in the portal). Actually, my addition to the reported properties was already ignored in the portal.

What about passing the message to an Event Hub?

We have seen how an Azure Function can pick up these non-telemetry events.

Can we use a regular EventHub for the same solution?

For this to answer, I added several EventHubs in an EventHub namespace:

We can check out the incoming messages using the EventHub capture data functionality:

Here, we see the same messages. Actually, we only see the body of the messages:

Here, we know we are looking at DeviceTwin changes. Though, the deviceId is not shown.

This does not mean the deviceId, thus the property bags, are ignored or even deleted. No, these are just not shown in this Portal interface.

We cannot get them by code either. This is an Azure portal limitation, as seen in the documentation:

Note: Check out the specific GetMetaDataPropertyValue function documentation for more details.

When these EventHub messages end up in e.g. an Azure Function, the application properties are available again.

To prove this, I redirected the EventHub messages to a second Azure Function ingesting the telemetry from that Event Hub (thus not directly from the IoT Hub):

As you can see, the properties are still there, untouched by the EventHub that sits in between the IoT Hub and the Azure Function.

Conclusion

The IoT Hub exposes all state changes of an IoT Device registration during its lifecycle.

Using the non-telemetry events in the custom event routing logic, these state changes can be captured, processed, and stored.

This way, your IoT platform is now ready for audits or multiple IoT solutions can be synchronized so your IoT hub behaves as a single point of truth.

3 gedachten over “Azure IoT Device lifecycle events (part 1)

  1. Thanks for the awesome article 🙂

    Just a quick note in regards to EH Process Data in the Azure Portal. As I’m sure you know it’s actually Azure Stream Analytics (ASA) providing the service. But as you observed, what’s exposed via that query experience is not complete. If you have VS Code, I highly recommend you use the ASA Extension for all Event Hub / IoT Hub discovery: https://docs.microsoft.com/en-us/azure/stream-analytics/visual-studio-code-local-run-live-input

Reacties zijn gesloten.