Using Device Twins with Azure IoT Hub

In today’s article I’d like to discuss Device Twins and how you can use them with Azure IoT Hub. I will be discussing what Device Twins are, how they are used and then provide a code example of using them with Azure IoT Hub.

What are Device Twins?

Device Twins are JSON documents that store state information about IoT devices. This includes state information along with metadata about the device. Configurations and conditions on the device are also stored. Azure IoT Hub maintains a device twin for each registered device that is connected to that hub.

You can use Device Twins to store specific metadata about your devices in the cloud. For example, you can keep track of a device location by storing location metadata about that device in its Device Twin.

Device Twins can also be used to report state information about the device. This can be useful to determine if a device is connected to IoT Hub, or if it is connected to a WiFi network.

Additionally, the process of updating the firmware on one or more devices can benefit from Device Twins, as firmware installation progress can be reported to a back-end application.

Device Twin Structure

The JSON document that makes up a Device Twin has a few components that I will describe in this section. Here is an example of the overall JSON structure of a Device Twin:

Figure 1: Device Twin Structure

Tags

The Tags section is used exclusively by a back-end solution. Device applications cannot see or query tags at all, but a back-end solution does have the capability to read/write tag values to this section of the Device Twin. The information in this section can be whatever identifying data you need on the back-end solution side. Here is an example, which shows tags for the device location:

{
…
    "tags": {
        "deviceLocation": {
            "city": "Chicago",
            "building": "Willis Tower",
            "floor": "12",
            "room": "NE Conf 1"
        }
    },
…
}

Reported Properties

This section is used in coordination with the Desired Properties section to synchronize device configurations. The device application has the capability to read and write data to this section while a back-end solution can read information from this section as well as receive change notifications when data changes. Typically, this section is used by the device application to report its current state to the cloud.

For example, let us say that we want to report to a back-end solution the current WiFi information for the device. We would serialize the following JSON string into the proper format for Reported Properties and then report the state to the IoT Hub that the device is registered with.

{
…
    "properties": {
…
        "reported": {
            "wifi.wifiIP": “192.168.3.100”,
            "wifi.wifiMask": “255.255.255.0”
        }
    }
}
Figure 2: Sending Reported Properties (device app)

The IoT Hub that maintains the Device Twin for the registered device would update the information being received from the device. In our example, the WiFi IP and Mask values would be updated accordingly in the device’s device twin. A solution back-end can then query the Reported Properties to retrieve these values.

Figure 3: Reading Reported Properties (back-end solution)

Desired Properties

This section is used in coordination with the Reported Properties section to synchronize device configurations. A back-end solution has the capability to read and write data to this section, and the device application can read and receive change notifications for this section. Typically, this section is used by a back-end solution to communicate to the device of some change requested that the device application should handle.

For example, let us say that we want to change the interval time value on the device that is used for sending data to IoT Hub. A back-end solution could update the desired properties and that change would be communicated to the device. As shown in the JSON snippet below we can set the ‘sendDataInterval’ value in the Desired Properties section to ‘5000’ indicating we want to set the interval value on the device to 5 seconds.

{
…
    "properties": {
        "desired": {
            "sendDataInterval": "5000
        },
…
    }
}
Figure 4: Update Desired Properties (back-end solution)

When a change is made to the Desired Properties for a device, a notification event is sent to that device informing the device of the change. Code on the device would configure a Device Twin callback method that would be fired when the change notification is sent to the device.

Figure 5: Configure Device Twin Callback Method (device app)

The callback method would extract the JSON information which would contain the Desired Properties section of that device’s Device Twin record. The code would then be able to interpret what has changed and perform whatever necessary actions are required.

Figure 6: Device Twin Callback Method (device app)

A device application should perform whatever actions necessary to process the change in the Desired Properties and then send back an update (via the Reported Properties) to the cloud.

Device Identity Properties

This is the root of the JSON document and contains the read-only properties that specify the device identity. This information can be used to retrieve the device identity along with connection state and authentication type.

{
    "deviceId": "devA",
    "etag": "AAAAAAAAAAc=", 
    "status": "enabled",
    "statusReason": "provisioned",
    "statusUpdateTime": "0001-01-01T00:00:00",
    "connectionState": "connected",
    "lastActivityTime": "2015-02-30T16:24:48.789Z",
    "cloudToDeviceMessageCount": 0, 
    "authenticationType": "sas",
    "x509Thumbprint": {     
        "primaryThumbprint": null, 
        "secondaryThumbprint": null 
    }, 
    "version": 2, 
…
}

I hope this article was helpful in explaining the details of Device Twins. For more detailed information please visit https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-device-twins.

If you would like to download the full source code for this example, please visit my github repository here.

Thanks for reading!

Reading Sensors on the MXChip AZ3166 IoT Device

Recently I have been playing around with the MXChip IoT DevKit device (AZ3166, you can purchase one here) and I’ve noticed that while there are great code samples designed to get you started with the MXChip DevKit, there aren’t any basic examples showing you how to use the different sensors built into the device. This article aims to go through each of the available on-board sensors (temperature & humidity, pressure, accelerometer & gyroscope, and magnetometer) and give examples on how to use them.

I am assuming you already went through the steps to update to the latest firmware and connect to your WiFi network (steps are here; ignore the sections regarding connecting to Azure IoT Hub as we do not need that for this article). Also make sure your VSCode environment has the Azure IoT Tools extension installed.

There are 4 on-board sensors that come with the MXChip IoT DevKit device:

  • Temperature & Humidity
  • Pressure
  • Accelerometer & Gyroscope
  • Magnetometer

The MXChip IoT DevKit device is Arduino-compatible, and there is support for this hardware via the Microsoft Azure IoT Developer Kit in VSCode (via Arduino Board Manager). Once you’ve connected your MXChip device to your WiFi you can start creating Arduino sketches to upload the code for.

Setup

First off, you will want to create a new project in VSCode. Open VSCode and press F1 and type in Azure IoT Device Workbench in the textbox that appears.

Figure 1: Creating a New Project

Find the entry labeled Azure IoT Device Workbench: Create Project… and click on it. Enter a descriptive name for your project.

Figure 2: Project Name

Once you press Enter you will see another selection dropdown asking for the platform for this project. Choose Arduino as our project will be based on the Arduino platform.

Figure 3: Platform Selection

Pressing Enter will bring you to the next selection choice: the project template. This is where the different boards and service functionality are listed for the project being created. Since we are using the MXChip we want to select the choices that are available for the MXChip. You will notice that there are a couple of entries that have support for Azure IoT Hub or Azure Functions. For this article, we only need the base device support so select MXChip IoT DevKit selection.

Figure 4: Project Templates

At this point VSCode will create a new workspace with the project name you provided and create a device.ino file for your Arduino sketch.

If you look under the .vscode directory in your project (under Device) you will see 4 JSON files that were automatically created as part of this project. These files define what board the project is using along with other information such as the sketch name and the serial port being used.

Figure 5: Project Configuration

General Information

Since we are coding an Arduino sketch for our device, I thought I’d briefly go through some of the general code and setup that is needed before we delve into the sensor code specifics.

First off, there are the setup() and loop() methods, which need to be coded first. The setup() method is for code that is executed once when the sketch is run, while the loop() method runs on a continuous loop (after setup is complete).

There are a few initializations we need to perform when the code first runs. These are as follows:

  • Initialize the OLED display
  • Start the serial support
  • Initialize WiFi

First thing is to initialize the OLED display on the board so we can display text. Fortunately, the Microsoft Azure IoT Developer Kit library provides a Screen object that we can use (this is defined in the Arduino.cpp file):

Figure 6: Initializing Screen object

Also shown is the Screen.print() method which allows us to display text on the OLED display.

Next is to enable the serial port support for the board. This is done by another class in the Microsoft Azure IoT Developer Kit library called Serial. We want to utilize 115200 baud so we pass that number as the parameter to the Serial.begin() method to start our serial port.

Figure 7: Serial Support

We also want to make sure our WiFi connectivity is working properly. Even though our example in this article doesn’t explicitly use WiFi, it is a good practice to enable this and verify connectivity since we would need this for any communication with the cloud or over-the-air (OTA) updates, such as firmware updates.

Again, the Microsoft Azure IoT Developer Kit library files have support for WiFi for the AZ3166 chipset (see the AZ3166WiFi.cpp file) so all we have to do is utilize the WiFi class in our InitWiFi() method. This method enables WiFi for the board and checks if it connects successfully. If it does, it sets the hasWiFi flag to true and prints the IP address the device is using to the screen. The setup() method will return if the WiFi is not connected.

Figure 8: WiFi Initialization

The final initialization we need to perform is the individual sensors we are utilizing. We need to initialize the temperature & humidity sensor, the pressure sensor and finally the magnetometer sensor. In order to do this, we need to instantiate a helper class of type DevI2C which will handle the details of I2C peripherals. We also need to pass 2 parameters to the instantiation of this helper class which correspond to GPIO pins 18 and 17 respectively (D14=GPIO_18, D15=GPIO_17).

Figure 9: I2C Helper Class Instantiation

There is also the main loop() method which runs on a continuous basis (after setup() finishes executing). This method will repeat indefinitely and here is where we want the code to do our work. Please note that I have a line of code that calls a delay so the loop doesn’t continuously run over and over again without a pause. We use this delay to give some time for the device to perform any background tasks it hasn’t yet finished.

Figure 10: Main Program Loop

Temperature & Humidity Sensor

In order to read from the temperature and humidity sensor we need to initialize this sensor in the setup() method. This is done by instantiating a new class of type HTS221Sensor (which is part of the Microsoft Azure IoT Developer Kit library). This class takes a parameter of type DevI2C so we pass the pointer to the DevI2C variable we created earlier. We then call the init() method to initialize the sensor.

Figure 11: Initializing the Temperature & Humidity Sensor

Once we have initialized this sensor we can now use it in the main loop() method. There are a number of supported methods for this sensor (located here). We are reading the temperature and humidity sensor readings by calling getTemperature() and getHumidity(), respectively. These methods read the sensor and place the results into a float variable which we pass as a pointer to the methods. Please also note that we enable the sensor when using it and disable the sensor when finished.

Figure 12: Reading Temperature, Humidity Sensor

Pressure Sensor

In order to read from the pressure sensor we need to initialize this sensor in the setup() method. This is done by instantiating a new class of type LPS22HBSensor (which is part of the Microsoft Azure IoT Developer Kit library). This class takes a parameter of type DevI2C so we pass the pointer to the DevI2C variable we created earlier. We then call the init() method to initialize the sensor.

Figure 13: Initializing the Pressure Sensor

Once we have initialized this sensor we can now use it in the main loop() method. There are a number of supported methods for this sensor (located here). We are reading the pressure sensor reading by calling getPressure(). This method reads the sensor and places the results into a float variable which we pass as a pointer to the method.

Figure 14: Reading Pressure Sensor

Accelerometer & Gyroscope Sensor

In order to read from this sensor we need to initialize it in the setup() method. This is done by instantiating a new class of type LSM6DSLSensor (which is part of the Microsoft Azure IoT Developer Kit library). This class takes a parameter of type DevI2C so we pass the pointer to the DevI2C variable we created earlier. We then call the init() method to initialize the sensor. Please also note that we can enable the accelerometer, gyroscope, and pedometer individually if we want.

Figure 15: Initializing the Accelerometer & Gyroscope Sensor

Once we have initialized this sensor we can now use it in the main loop() method. There are a number of supported methods for this sensor (located here). Note that we read the positional axes (x, y, and z) for the accelerometer and gyroscope, while we read a step count for the pedometer.

Figure 16: Reading Accelerometer & Gyroscope Sensor

Magnetometer Sensor

In order to read from this sensor we need to initialize it in the setup() method. This is done by instantiating a new class of type LIS2MDLSensor (which is part of the Microsoft Azure IoT Developer Kit library). This class takes a parameter of type DevI2C so we pass the pointer to the DevI2C variable we created earlier. We then call the init() method to initialize the sensor.

Figure 17: Initializing the Magnetometer Sensor

Once we have initialized this sensor we can now use it in the main loop() method. There are a number of supported methods for this sensor (located here). Note that we read the positional axes (x, y, and z) for this sensor.

Figure 18: Reading Magnetometer Sensor

Code Execution

In order to run this code on our MXChip device, we first need to compile the code successfully. In VSCode, press F1 and enter Azure IoT Device Workbench: Compile Device Code and select the entry. This will compile the code for your Arduino sketch file.

Figure 19: Compiling Arduino Sketch File

Once the file compiles successfully, you are now ready to upload the code to the device. Again, press F1 and type Azure IoT Device Workbench: Upload Device Code and select the entry. You can alternatively click the Upload button in the upper right.

Figure 20: Uploading Code to Device

Once the code uploads successfully, it begins executing immediately. If you open the Serial Monitor in VSCode you can see the output, which should look something like this:

Figure 21: Code Execution Output

I hope this article was helpful in explaining the details of utilizing the different on-board sensors in the MXChip. If you would like to download the full source code for this example, please visit my github repository here.

Thanks for reading!