Ensuring device-to-cloud communication with both AWS and Azure infrastructures - Embedded.com

Ensuring device-to-cloud communication with both AWS and Azure infrastructures

Embedded software development has been Auriga’s core business for many years. During one of the recent firmware development projects for an industrial automated solutions provider, our team faced the task of ensuring IoT device-to-cloud communication in both the Amazon Web Services infrastructure (AWS) and the Microsoft Azure infrastructure (Azure). Our engineers studied the differences between the IoT interfaces in these infrastructures and figured out how to overcome difficulties with connecting to both infrastructures at the IoT device firmware level.

The purpose of this article is to examine aspects of firmware development for IoT device communication with AWS and Azure cloud infrastructures using the MQTT protocol.

MQTT Protocol Implementation 

The MQTT protocol is a simple application layer protocol used to transport messages between IoT devices and infrastructure. The protocol is based on the publish–subscribe model. Anyone who wants to receive a copy of the message subscribes to a specific “topic.” Subscribers can be both IoT devices belonging to a certain group and executable objects inside the cloud infrastructure (applications, event dispatchers, so-called lambda functions, etc.). The data in the MQTT protocol are transported in a textured, structured JSON format.

In both AWS and Azure, MQTT version 3.1.1 is implemented with certain limitations regarding the standard. The protocol is implemented over the TLS connection between the device and the infrastructure, so the messages are encrypted and transported over the established TCP connection.

The main restrictions imposed by both infrastructures are as follows:

  • Only one connection between the device and the infrastructure is supported.
  • The number of messages sent by the device per unit of time is limited.
  • Messages with QoS = 2 (guaranteed one-time delivery) are not supported.

Device Authentication

When establishing a TLS connection for AWS, device authentication is only possible using an X.509 certificate, while for Azure, it is possible to use either an X.509 certificate or a username/password. We chose an authentication method using a certificate for both infrastructures for consistency.

In AWS, a device certificate is automatically generated when a device is registered with the IoT infrastructure and stored inside it. We only need to download the certificate to the device and specify a path to it in the client library configuration file.

In Azure, a device certificate must be created manually. In this case, the parent certificate must be registered in the cloud. After that, the infrastructure accepts any certificate created based on the registered parent certificate to authenticate a device.

To simplify the process of creating a new certificate and a configuration file for the client library in Azure, we developed a cloud REST API-based application, which immediately generates all related data (certificate, private key, configuration file) for subsequent downloading to the device by calling the REST API. To store this data, the application creates a special container (the so-called blob container) in the Azure file storage.

Connecting to Azure Using AWS Client Library

Both infrastructures provide developers with C/C ++ libraries that allow access to MQTT protocol functions, including establishing a connection between IoT devices and the infrastructure. The functionality of both libraries is approximately the same, but their interfaces significantly differ, so we would like to adapt one of these libraries in such a way, that after the adaptation the IoT device firmware can use a single library to work with both infrastructures.

The AWS client library proved to be more flexible, and we had more experience working with it, so it was easier for us to adapt this library. We only needed to make small changes to fill in the “username” and “password” fields in the MQTT connection setup command. These fields are not used in AWS but are used in Azure even when authenticating a device with a certificate. In addition, we added functions for recognizing and processing a hard disconnect by the Azure infrastructure when protocol errors are detected (to allow the library to automatically re-establish the connection). After that, our IoT device firmware was able to connect to the Azure IoT infrastructure and work with it using the modified AWS client library. And of course, the firmware continued to work with the AWS infrastructure without problems after the AWS client library modification.

MQTT Topic Support

The topic in MQTT corresponds to the topic of the published message and consists of several parts separated by the “/” symbol.

To work with IoT devices, we developed a topic system corresponding to the set of functions supported by the devices:

  • Asynchronous events from the device are sent to the topic “<device-name>/events.”
  • Telemetry data from device sensors are sent to the topic “<device-name>/response/sensor/<sensor-number>/reading.”
  • Actions of the “request-response” type (for example, a sensor list request from the device) are implemented as follows: the device subscribes to the topic “<device-name>/request/#,” where “#” by MQTT conventions denotes an arbitrary suffix, and responds to the topic starting with “<device-name>/response.” For example, to obtain general information about the device, the cloud application sends the MQTT message “<device-name>/request/general_information” and waits for the response with the topic “<device-name>/response/general_information.”

AWS supports the use of almost any topics allowed by the protocol. Therefore, when working with AWS, the topic system described above was used with no changes.

Unlike AWS, Azure only supports topics of several hard-coded formats. When the device uses the topic of some other format, the infrastructure immediately disconnects the MQTT connection without giving any error indication.

For MQTT messages coming from the device (events, telemetry, responses to clients’ requests), the only allowed topic format in Azure is “devices/<device-name>/messages/events/<property-bag>.” Here, <device-name> is the name under which the device is registered in the Azure IoT infrastructure and <property-bag> is the set of pairs “<name> = <value>” separated by the “&” symbol.

For MQTT messages sent to the device, the topic format is “devices/<device-name>/messages/devicebound/#,” where “#” under the MQTT conventions denotes an arbitrary suffix.

In order to adapt the topic system developed for IoT devices for Azure, we decided to use the following translation: asynchronous events from devices are sent to the main topic “devices/<device-name>/messages/event.” For topics used for requests and answers, as well as for telemetry, the part of the topic after the device name is converted to the property value named “subtopic” with the “/” replaced by “.”.

For example, to get a list of sensors on the device, a request is sent to it with the topic “<device-name>/request/sensor/list,” and the device sends a response with the topic “<device-name>/response/sensor/list.” In the case of Azure, the request topic looks like this: “devices/<device-name>/messages/devicebound/subtopic=request.sensor.list.” The device response uses the topic “devices/<device-name>/messages/events/subtopic=response.sensor.list.”

For example, telemetry data from sensor 12 are sent with the topic “<device-name>/response/sensor/12/reading,” which is transmitted to Azure in “devices/<device-name>/messages/events/subtopic = response .sensor.12.reading.”

Shadows and Twins

Both infrastructures have mechanisms for accessing device properties at any time, regardless of device connection status, called device shadow in AWS and device twin in Azure. Both mechanisms are implemented in a similar way, as a document in JSON format, which includes two sets of properties: desired and reported.

The reported properties set stores cached device information. When the device is connected to the infrastructure, it periodically updates information about itself in the shadow/twin. If the device is not currently connected, the client can receive information from the shadow/twin that was saved at the time of the last update (information includes the time of the last update).

The desired set contains client requests for changes to device properties (“desired” property values for the client). When the client changes this set or when the device connects to the infrastructure (if it was disconnected at the time of the change), the device receives a notification in a special topic, and it can read the request for changing properties in the desired set and change the specified properties. After the change is complete, the device updates the information in the shadow/twin by transferring the changes to the reported set.


Figure 1. Device twin/shadow structure. (Source: Auriga)

Since the implementation of shadows and twins is very similar, the functionality of the firmware for working with these mechanisms in AWS and in Azure is similar as well. One of the differences is that property names in Azure twins cannot contain spaces; in AWS shadows, there is no such restriction. Therefore, in our project, when adapting to Azure, we removed the spaces in the property names. To work with shadows/twins, special MQTT topics are used for which there is an approximate correspondence. The main topics are listed in the following table.

Topic Function Requester AWS Shadow Azure Twin
Updating information in the reported section Device $aws/things/<device-name>/shadow/update $iothub/twin/PATCH/properties/
reported/?$rid=<rid>
(where <rid> is a unique numerical request identifier)
Accepting updated information Shadow/twin $aws/things/<device-name>/shadow/update/accepted $iothub/twin/res/204/?$rid=<rid>
(<rid> matches the request <rid>)
Rejecting updated information Shadow/twin $aws/things/<device-name>/shadow/update/rejected $iothub/twin/res/<HTTP-error-code>/?$rid=<rid>
(<rid> matches the request <rid>)
Updating information in the desired section Shadow/twin $aws/things/<device-name>/shadow/delta $iothub/twin/PATCH/properties/
desired/…
Reading information from shadows/twins Device $aws/things/<device-name>/shadow/get $iothub/twin/GET/?$rid=<rid>
(where <rid> is a unique numerical request identifier)
Reading information from shadows/twins
– response with data
Shadow/twin $aws/things/<device-name>/shadow/get/accepted $iothub/twin/res/200/?$rid=<rid>
(<rid> matches the request <rid>)
Reading information from shadows/twins
– the request is rejected
Shadow/twin $aws/things/<device-name>/shadow/get/rejected $iothub/twin/res/<HTTP-error-code>/?$rid=<rid>
(<rid> matches the request <rid>)

In both infrastructures, the size of the stored data is limited (8 Kb in AWS, 32 Kb in Azure), so only the most basic properties of the IoT device are saved in shadows/twins. In our case, they include the following:

  • device identification attributes: name, device type, model and manufacturer name, manufacturing date, hardware version, serial number
  • network addresses: IP (IPv4 or IPv6), MAC, subnet mask, gateway, and DNS server addresses
  • firmware version
  • information about the geographical location of the device.

In both infrastructures, client APIs exist for accessing device shadows/twins. In AWS, a lambda function can access device shadow using “boto3” API client library or it can handle shadow update events and get access to shadow data which are contained in the event parameters. In Azure, a Web application can access a device twin using the IoT REST API.

Direct Methods in Azure

The MQTT protocol supports only one-way messaging and does not support request-response interaction. To support such interaction, which would allow the client to transport a request to the device and simultaneously receive a response from it (that is, remotely call for a method on the device), there is a direct method mechanism implemented in Azure on top of MQTT, which is not available in AWS.

This mechanism is implemented as follows: the client calls a special API function of the infrastructure, passing to it the device name, method name, parameters in JSON format, and timeout for waiting for a response from the device. The infrastructure generates an MQTT message with the topic “$ iothub/methods/POST/<method-name>/? $ Rid = <rid>” and sends it to the device. Here, <method-name> is the name of the method and <rid> is the unique identifier of the request in the form of a hexadecimal number.

The device, having received this message, processes the request, calls the appropriate method, and responds with an MQTT message with the topic “$ iothub/methods/res/<HTTP-result-code>/? $ Rid = <rid>. Here, the <rid> must match the <rid> in the request, and <HTTP-result-code> informs the infrastructure of the success or failure of the method execution. Standard HTTP return codes are used. Therefore, if successful, the code is 200 if the method returns data in JSON format or 204 if the method does not return data. In case of failure, 4xx, 5xx codes are used. If the method returns data, they are sent as part of an MQTT message and returned to the client as a result of the method execution.


Figure 2. Interaction diagram when calling the direct method. (Source: Auriga)

Direct methods make synchronous interaction with an IoT device very convenient; however, in the case of AWS, such a mechanism is absent. The following paradigm is used to call synchronous methods in this infrastructure:

  • At startup, the device subscribes to the topic template “<device-name>/request/#” (where “#” under MQTT conventions denotes an arbitrary topic suffix).
  • The client (cloud application) sends an MQTT message with a topic in the format “<device-name>/request/…” (for example, “<device-name>/request/sensor/list” to get a list of sensors or “<device-name >/request/sensor/12/get” to read the sensor value).
  • The device processes the request and sends a response to the topic “<device-name>/response/…” (for example, “<device-name>/response/sensor/list”).
  • A special rule inside the infrastructure intercepts topics of the “<device-name>/response/#” format and saves the topics and contents in the DynamoDB database with a timestamp.
  • The client checks the database, and when a record appears with the corresponding topic and the timestamp, it uses the record as a request result.

AWS uses the database because AWS cloud applications do not have access to subscriptions to MQTT topics.

In the case of Azure, the implementation of synchronous method calls on the device becomes much easier for cloud applications and comes down to calling the corresponding API, so when interacting with Azure in our project, we used this mechanism.

Azure support of direct methods at the device level was implemented in a general way, and it did not require detailed processing of each method. Similar to the translation of topics, the method name is formed based on the tail of the topic after the “/request/” and “/response/” parts, replacing the “/” symbol with “.”. For example, the method for obtaining the list of sensors is named “sensor.list,” and the method for getting the value from the number 12 sensor is named “sensor.12.get.”

When receiving a message about a method call, the method name is translated into the topic with the addition of “<device-name>/request/” in front. Then, the method call and the regular message are processed by the common code, and the response message, in the case of the method call, is translated into the method call response message with the previously stored <rid>, the unique request identifier.

Conclusion

We looked at some of the differences between the AWS and Azure IoT infrastructures, and they turned out to be quite significant. In some aspects, the advantage remains on the AWS side (flexibility of the topic system), while in others, it remains on the side of Azure (direct methods). Nevertheless, while developing the software of the IoT device supporting both infrastructures, we managed to keep most of the code independent of the infrastructure and localize the dependencies in several small interface modules.


Sergey Zhukov is an expert software developer and a system architect at Auriga, Inc. as well as lead programmer at Moscow State University’s Research Computer Center. He is an outstanding specialist accumulating more than 30 years of experience in the industry. His professional interests include object-oriented software design and development, systems programming, languages and compilers.

Related Contents:

For more Embedded, subscribe to Embedded’s weekly email newsletter.

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.