Note: This content was updated on June 8th 2021 to reflect changes to the Microsoft AzureIoT example application
Introduction
New Azure Sphere developers are presented with high level example applications that can be used as starting points or references for new projects and products. Understanding how the examples are architected is a key first step to efficient re-use of these examples. This blog will review one of the Microsoft Azure Sphere example applications called AzureIoT. If you understand this application and its architecture, then you will be able to understand any of the Microsoft or Avnet high level Azure Sphere example applications.
Objectives
- Become familiar with the AzureIoT example so it can quickly be extended and customized for new Azure Sphere applications
- Learn about the Microsoft AzureIoT example high level application architecture
- Learn about sending telemetry data an Azure IoT Hub
- Learn about managing Azure IoTHub device twins
- Learn about Azure IoTHub direct methods
- Learn how to interface with hardware devices
- Learn about the Azure Sphere Hardware abstraction layer
- Introduction
- Objectives
- Example Source Code
- High Level Application Architecture
- Send IoT Telemetry to Azure
- Process Device Twin Messages from Azure IoT Hub
- Process Azure IoTHub Direct Methods
- Interfacing with hardware devices
- app_manifest.json
- Hardware Abstraction Layer
- Wrap Up
- Additional Azure Sphere Resources
Example Source Code
This document examines one of the Microsoft Azure Sphere examples called AzureIoT. You can pull this project to follow along with this blog to dig deeper into the implementation details.
- Clone the Microsoft azure-sphere-samples repo
- git clone https://github.com/Azure/azure-sphere-samples.git <YourTargetDirectory>
- Open the /Samples/AzureIoT project
- If this is your first dive into Azure Sphere development, I recommend that you work through the Azure Sphere 3-Part blog series to learn how to setup and use the Azure Sphere development tools.
High Level Application Architecture
Since Azure Sphere is a connected secure IoT solution. I recommend leveraging Microsoft’s AzureIoT Azure Sphere example, or one of the Avnet example projects for new high level applications. The AzureIoT example connects to an Azure IoTHub and implements all the connection, Device to Cloud (D2C) and Cloud to Device (C2D) logic that any connected application requires.
To understand this application let’s examine the four ways the application execution path is driven.
- main() loop
- Event Loop Timers
- Event Loop Events
- Azure IoT Library Callbacks
The main() loop
On start up the operating system passes execution control to the main() function along with any command line arguments defined in the app_manifest.json CmdArgs entry . The main function in the AzureIoT example application can be simplified into three sections.
- Initialization: InitPeripheralsAndHandlers()
- Allocates system resources
- Configures hardware interfaces
- Configures timers and timer handlers
- Main loop
- Checks to see if any other logic in the application has set the global variable exitCode to flag an exit condition.
- If application code in the rest of the high level application encounters an error condition it will set the global variable exitCode to reflect any issue encountered.
- EventLoop_Run() “Runs an EventLoop and dispatches pending events in the caller's thread of execution.”
- Checks to see if any other logic in the application has set the global variable exitCode to flag an exit condition.
- Clean up: ClosePeriheralsAndHandlers()
- Closes hardware interfaces
- Releases system resources
Upon application exit, the value of exitCode is returned to the OS so that the exit reason is visible for application health tracking and troubleshooting. Exit codes are defined in common/exitcodes.h in the ExitCode enumeration definition.
Event Loop Timers
The AzureIoT example uses event loop timers to drive periodic logic. Timers can be periodic (fire over and over again) or one shot (only fire once). Each timer has a user defined handler function that will execute when the timer fires. When a timer expires, its handler will be called the next time EventLoop_Run() is called from the main() loop.
The high level application is single threaded. When a timer expires, the implementation does not guarantee that the handler will execute immediately. Event timers are more memory efficient than using threads, however using timers in this way is a form of cooperative tasking and depend on the application quickly and efficiently processing the events so that they don't back up and cause execution errors.
The timers utilized in the AzureIoT Example are . . .
azureTimer
- Used to establish, monitor and maintain the Azure IoTHub connection
- Execution control passes to AzureTimerEventHandler() when the timer expires
- Calls IoTHubDeviceClient_LL_DoWork()
- IoTHubDeviceClient_LL_DoWork() is the routine that ultimately drives all IoTHub messaging to/from the IoTHub. This routine will send any device twin updates, direct method responses and telemetry data that the application sends. If your application never tries to connect to the IoTHub, make sure this routine is being called, if not it will never connect or process messages to/from the IoTHub, ask me how I know!
- Uses dynamic periods defined by the global variable azureIoTPollPeriodSeconds
- The ability to modify timers at runtime is a powerful concept and is demonstrated by this timers usage.
buttonPollTimer
- Used to poll and de-bounce button presses
- Execution control passes to ButtonPollTimerEventHandler() when the timer expires
- Fires every 1 millisecond
telemetryTimer
- Used to periodically send simulated temperature data to the IoT Hub as telemetry
- Execution control passes to TelemetryTimerCallbackHandler() when the timer expires
- Fires every 5 seconds
provisioningTimer
- Used to drive the DPS provisioning flow
- Execution control passes to ProvisioningTimerHandler() when the timer expires
- This timer is created in a disarmed state, i.e., it won't fire.
- The timer is enabled from the Connection_Start() function and set based on the value of the workDelayMs variable
- The timer is disabled once the DPS registration process has completed
timeoutTimer
- Used to monitor the DPS provisioning flow
- Execution control passes to TimeoutTimerHandler() when the timer expires
- If this timer fires the provisioningTimer (see above) is disarmed, a ERROR message is output to the debug window and the ConnectionCallbackHandler() is called which will modify the azureTimer period (see above) to "back off" the period that's used to re-establish the IoTHub connection.
- This timer is created as a OneShot timer, i.e., it will only fire once
- If the connection to the IoTHub is established before the timer fires, this timer is disarmed
Event Loop Timer functions
The Azure Sphere system supports the following functions to create, modify, use and destroy Event Loop Timers. See eventloop_timer_utilities.h for details on each function.
- CreateEventLoopPeriodicTimer()
- CreateEventLoopDisarmedTimer()
- SetEventLoopTimerPeriod()
- SetEventLoopTimerOneShot()
- ConsumeEventLoopTimerEvent()
- DisarmEventLoopTimer()
- DisposeEventLoopTimer()
Add a timer and handler
To add a new timer and handler to the Azure IoT example
- Define a new global EventLoopTimer* variable in main.c
- Add code in InitPeripheralsAndHandlers()to define the timer period and specify the handler to call when the timer expires
- Add the handler routine that will be called each time the timer expires
Search the AzureIoT Example application for buttonPollTimer to see a simple working example of defining and using an Event Loop Timer.
I/O Events
In addition to timer events, Azure Sphere supports events based on input/output (I/O). These events are tied to file descriptors. The Azure IoT example does not implement any I/O events, so let’s examine using I/O events in other example applications.
When an application uses a UART, it can register an Event Loop I/O event so that when the UART receives data, an event fires and a user defined handler will be called that executes the logic to read and process the UART received data. See the UART_HighLevelApp example to see how this is configured and used.
Another example is when two Azure Sphere applications share an inter-core communication connection. The Event Loop I/O event can be configured so that when the real time application inserts a message into the queue, the high level application is notified and a handler is called to process the incoming data. See the intercoreComms High Level example application to see how this is configured and used.
Azure Sphere Interrupt Processing
The Azure Sphere MT3620 does support interrupts, but only in real time applications running on one of the real time cores. If your application requires responding to events based on interrupts you need to implement that functionality in a real time application or determine if the functionality can be managed by the high level application polling the hardware interface using Event Loop Timer Events. Keep in mind that when using timers in the high level application there is no guarantee that the handler will be called immediately when the timer expires, rather it will be put into a ready state and will execute the next time EventLoop_Run() is called from the main() loop.
Azure IoT Library Callbacks
The last mechanism to discuss are Azure IoT library callbacks. The Azure Sphere IoT Library leverages much of the same functionality of the Azure IoT Hub Device SDKs. These SDKs provide support for connecting to an Azure IoT Hub and all the hooks and functionality required to send and process Device to Cloud (D2C) and Cloud to Device (C2D) messages.
The example application uses callback functions to manage Azure IoT Hub related events. The theme here is to define callback routines for each event and implement the callback handlers to manage the event, whatever it is. The AzureIoT example supports the following Azure IoT Library events . . .
High Level Application Diagram
If we put together a diagram that shows all the elements discussed above it would look like . . .
Send IoT Telemetry to Azure
Sending telemetry is a basic function for any IoT project. Typically, IoT devices collect data, they may or may not pre-process the data, and then they send data in the form of telemetry to the cloud. The AzureIoT example project sends simulated data to Azure. Sending telemetry data to Azure is easy. There are 3 things that need to happen . . .
- Establish and maintain a secure connection to an Azure IoT Hub or IoT Central application
- Construct a JSON object that contains valid JSON that includes your data
- Call the routine to send the data to our IoT Hub or IoT Central application
Establish a connection to Azure
The recommended method to provision IoT devices to Azure IoT Hubs is to use an Azure service called a Device Provisioning Service (DPS). You can read all about DPS here. Using a DPS you can deploy a single software application build onto millions of devices. The first time each device connects to the internet, and then the global DPS server, they will all automatically be provisioned to an IoT Hub and then connect to the IoT Hub that each device was provisioned to. This is a powerful IoT concept and is required to deploy IoT devices at scale. The diagram below shows the process . . .
The AzureIoT example supports multiple ways to connect your device to an IoT Hub or IoT Central. The readme.md file in the gitHub repo contains all the details to connect the application to an IoT Hub.
Construct a JSON object, and send the telemetry
Below you’ll see the AzureIoT example application code to create and send a JSON telemetry message. There are basically five things to do . . .
- Read your sensor or generate the data to send (line 211)
- Allocate memory to store the JSON object (line 100)
- Construct the JSON object (lines 103-104)
- Send the message to the IoTHub (line 106)
- The message will be sent the next time IoTHubDeviceClient_LL_DoWork()is called
- Release the memory resources (lines 109 - 110)
One thing that I think is really cool about telemetry is that you can send any {“key”: value} pair, or any valid JSON object, you want to your Azure IoT Hub. You don’t have to tell Azure anything about your data. As long as the data is valid JSON the IoT Hub will accept the data and store it for you to use. Of course if you want to access that data, some other Azure service will need to know about your data so it can ingest it and do something meaningful with it, but the IoT Hub does not care as long as it’s valid JSON.
Process Device Twin Messages from Azure IoT Hub
Device twins are another powerful IoT concept. You can read the Azure documentation on device twins here.
“Device twins are JSON documents that store device state information including metadata, configurations, and conditions. Azure IoT Hub maintains a device twin for each device that you connect to IoT Hub.”
Using device twins you can make changes in the cloud to a device twin’s desired property object and the IoT device will receive a message containing the new desired property. Your application then uses the information in the desired property to do something in your application. For example, toggle a GPIO signal to control an LED, change a variable/property that defines how your application behaves, or anything that your creative mind can think up.
Device twins can be read only as well. For example, the AzureIoT example sends up a device twin when the application connects to the Azure IoT Hub: {"serialNumber":"TEMPMON-01234"}. This device twin captures the device serial number so it can be viewed or used to drive a custom work flow from the Azure cloud.
To work with Device Twins we need four different things . . .
- Define the JSON {“key”: value} pair that we want to implement for our solution
- Setup a callback routine that will be instantiated when the Azure IoT Hub sends a Device Twin update
- Implement the callback routine that looks for and does something with our specific {“key”: value} pair data.
- Send a Device Twin reported properties message back to Azure with the new reported value of our property.
Define the JSON {“key”: value}
The first thing we need to do is define our {"key": value} pair. The AzureIoT example implements a Device Twin called "thermometerTelemetryUploadEnabled," the JSON is shown below, it's a boolean entry. When this device twin is set to true, then the simulated temperature telemetry data is sent to the IoTHub, and when it's false the telemetry data is NOT sent. Note that there is a global variable is declared in main.c named telemetryUploadEnabled. The device twin logic updates the value of this variable, and code in another areas of the application uses the variable to determine if it needs to send telemetry or not.
{"thermometerTelemetryUploadEnabled": (true|false)}
Setup a Device Twin callback routine
Before we can receive a Device Twin update, we need to tell the Azure IoT Library how to inform the application when a new Device Twin message is received. In azure_iot.c around line #133 the application informs the Azure IoT Library that the routine DeviceTwinCallback() should be used to process incoming Device Twin messages.
Implement the callback routine and send a reported properties message back to the IoTHub
The application code path is shown below. It's a little crazy, but this graphic documents what happens when the device twin desired property update is received from the IotHub. I've identified some of the more interesting code.
- The incoming payload is copied to a memory buffer and null terminated. The JSON parser requires a null terminated string.
- The routine looks for the "thermometerTelemetryUploadEnabled" Key
- The global variable is updated with the new value passed in by the device twin desired property
- The "reported" property JSON is constructed and sent back to the IoTHub
- Notice that there is extra metadata appended to the response. These items are required for Azure Plug and Play and IoTCentral interactions.
Note: Click on graphic below to see full size image
I've added code to the example to capture the device twin JSON as it's received and sent for reference.
Send a read only device twin update
Catch and respond to a desired property twin request
Process Azure IoTHub Direct Methods
Another powerful IoT Hub feature is Direct Methods. You can read the Azure documentation on Direct Methods here. You can also read a very detailed blog all about Azure Sphere and Direct Methods here.
“Direct methods represent a request-reply interaction with a device similar to an HTTP call in that they succeed or fail immediately (after a user-specified timeout). This approach is useful for scenarios where the course of immediate action is different depending on whether the device was able to respond.”
Let’s see how to implement a Direct Method on Azure Sphere and review the AzureIoT example application direct method implementation. There are three things we need to accomplish to implement and use a direct method.
- Define the direct method
- Setup a callback routine that will be instantiated when the Azure IoT Hub sends a direct method call to our device
- Implement the callback function to catch, validate and respond to the direct method. Direct methods return a result code and a return JSON string.
Define the direct method
The AzureIoT example implements a direct method called “displayAlert” and it returns a JSON response string {“Alert message displayed successfully”}. This direct method does not require any arguments or payload with additional information for the direct method.
To call this direct method you would enter the direct method name “displayAlert” with an empty JSON payload {}
The Direct Method call returns
Setup a Direct Method callback routine
Similar to processing Device Twin messages from the IoTHub, we tell the Azure IoT Library that we want to be notified when a direct method is called on our device and provide a callback handler.
Implement the callback routine
The callback implementation is shown below. There are three sections identified in the graphic.
- Check to see if the Direct Method called is the one we expect “displayAlert”
- Construct a response string and a response code
- Allocate memory on the heap and copy the response string to the allocated memory. Note that the Azure IoT Library is responsible for freeing this memory. Return the response code 200 to tell the Azure IoT Library that we successfully processed the Direct Method.
Interfacing with hardware devices
Working with hardware interfaces is common with IoT projects. Whether it’s driving an LED, or reading an I2C sensor the Azure Sphere OS and Azure Sphere SDK APIs help you to interface with your hardware devices.
GPIO
In this section we’ll identify the code required to read the General Purpose I/O (GPIO) signal in the AzureIoT example project that’s connected to User Button A. Working with GPIO interfaces is straight forward, we need to do 5 things . . .
- Include the GPIO libraries in the project
- Add the GPIO reference to the app_manifest.json file
- Declare a file descriptor that we’ll associate to our GPIO signal
- Open the GPIO pin as an input and assign a file descriptor to work with the hardware
- Read the GPIO level using the file descriptor as a reference to the hardware
- Close the file descriptor
Using the AzureIoT example let’s identify the source code for each of these items . . .
Include the GPIO libraries in the project
To add the GPIO libraries to our project just include gpio.h header file
Add the GPIO reference to the app_manifest.json file
We need to explicitly grant permission for the application to use the GPIO signal for Sample Button 1. We add "$SAMPLE_BUTTON_1" to the app_manifest.json file in the Capabilities --> Gpio section. At build time the Hardware Abstraction layer implementation will map "$SAMPLE_BUTTON_1" to the number 12 or GPIO12. If we were to omit this step, when the application attempted to open GPIO 12 the OS services would block the operation and return an error.
The Microsoft documentation on the app_manifest.json file can be reviewed here.
Declare a file descriptor
The system uses file descriptors to reference and operate on hardware interfaces. We declare a file descriptor sendMessageButtonGpioFd to allow the code to operate on the GPIO interface. You’ll notice that all calls to initialize and read the GPIO pin reference this file descriptor.
Open the GPIO as an input
This line of code opens GPIO 12 (SAMPLE_BUTTON_1) as an input. That will allow us to read the GPIO level.
Read the GPIO level
This is the code that actually reads the GPIO hardware.
Close the file descriptor
When the application exits, it calls the ClosePeripheralsAndHandlers() which calls UserInterface_Cleanup() to clean up.
The easiest way I’ve found to look at an existing high level Azure Sphere project to understand how a hardware interface is implemented is to search for the file descriptor. When you find all the code that uses some piece of hardware’s file descriptor, you’ll see all the necessary code for that interface.
You can review the AzureIoT example project to see how to drive a GPIO signal, search for statusLedGpioFd.
app_manifest.json
The Microsoft documentation on this Azure Sphere feature is very well written, and I don’t think I can add anything to this very important discussion. The documentation is here, it’s a short document that should be reviewed by every Azure Sphere developer. The text below is taken from the Microsoft documentation.
“The application manifest describes the resources, also called application capabilities that an application requires. Every application has an application manifest. Applications must opt in to use capabilities by listing each required resource in the Capabilities section of the application manifest; no capabilities are enabled by default. If an application requests a capability that is not listed, the request fails. If the application manifest file contains errors, attempts to sideload the application fail. Each application's manifest must be stored as app_manifest.json in the root directory of the application folder on your PC.”
Hardware Abstraction Layer
Microsoft has implemented a hardware abstraction layer so that the Microsoft Azure Sphere GitHub samples can run on multiple Azure Sphere hardware platforms. All the Microsoft Azure Sphere GitHub examples use this abstraction layer. This is a great feature because there are multiple Azure Sphere platforms and each of these platforms may expose hardware features of the underlying Azure Sphere device in different ways. You can read about the Hardware definitions and how to create custom definitions here.
For example the Seeed development kits and the Avnet Starter Kit all use pushbuttons and user LEDs. These devices use GPIO signals. Microsoft needed an implementation so that a single sample application could correctly reference the GPIO signals for the hardware platform that the application was built for, even if different hardware platforms used different signals for a common function like driving a LED.
To implement the abstraction layer, they have included a “HardwareDefinitions” folder in the azure-sphere-samples directory structure. Under the “HardwareDefinitions” folder, there are folders for each of the currently supported platforms.
Each folder contains a JSON file and a header file that maps the board-specific, or module-specific features to the underlying Azure Sphere MCU hardware signals. The example projects use identifiers when referring to hardware signals which are mapped to the specific peripherals on the target hardware platform.
Here’s a code snippet where the AzureIoT example opens the GPIO for the button. Note that we don’t pass in the GPIO integer, but an identifier that references the actual GPIO signal, an integer. In this way different hardware platforms can use different GPIO signals for the buttons, but the sample application will work for all hardware platforms as long as the hardware definitions are correctly defined and the correct hardware platform is defined for the build.
Modify a project to reference a different hardware solution
To specify the target hardware in an Azure Sphere project open the CMakeLists.txt file and update the azsphere_target_hardware_definitions() statement to specify to the correct HardwareDefinitions directory for your kit or solution.
Or
When you modify and save this file, the CMAKE subsystem will automatically re-generate the build configuration. If there is a problem with the path or filename that you added, you’ll see errors. A successful process will look like the graphic below.
Note that the default platform for all the Microsoft GitHub examples is the Seeed development kit. So if you’re using the Avnet Starter Kit, you’ll need to update this property each time you open a sample project from GitHub for the first time.
Wrap Up
Hopefully you have learned a lot about Azure Sphere High Level applications and specifically all the important details for Microsoft’s Azure Sphere AzureIoT example application. Keep this blog handy when you’re starting your first few Azure Sphere applications as a reference.
Additional Azure Sphere Resources
- Avnet Azure Sphere Starter Kit Out of Box Examples: http://avnet.me/mt3620-kit-OOB-ref-design-blog
- Free Azure Sphere training from Microsoft: http://avnet.me/azureSphereMSFTLearningPath
- Listing of all the Avnet Azure Sphere Blogs on Element14 plus other resources: http://avnet.me/azure-sphere-resources
Top Comments