8/13/21: Added new DX_DEVICE_TWIN context void* pointer detail and blank architecture diagram and exercise
Introduction
I've been working with the Azure Sphere secure MCUs since July 2019. I develop and deliver Azure Sphere training materials, sales demos and create on-line content/examples to help the engineering community learn about and use the Azure Sphere secure solution. As I gained experience developing these applications, I noted that there are common patterns that I saw over and over again. I implemented some common interfaces that leverage these patterns to easily add device twins, direct methods and interface with real time applications running on Azure Sphere MCUs. At the same time Dave Glover from Microsoft noticed these same patterns and developed a set of libraries he called Azure Sphere DevX, that makes adding these common IoT interfaces easy and repeatable.
Dave's description of DevX: "The library consists of convenience functions and data structures that simplify and reduce the amount of code you write, read, debug, and maintain and allows you to focus on the problem you are trying to solve rather than the underlying infrastructure code. The Azure Sphere DevX convenience functions are callback centric, the library looks after the infrastructure, and you write the code for the callback handlers. You have full access to the source code so you can learn how the library works."
The DevX implementation is more portable and maintainable than my implementation, so I dug in and started using the DevX libraries. I have been using DevX for a few weeks now and have contributed to the project. I wanted to blog about DevX and will be creating new training materials around the libraries. Dave has even taken the concept one step further and created an Azure Sphere code generator based on DevX called Azure Sphere GenX. Expect to see a blog on the Azure Sphere GenX utility in the near future.
This blog will walk through one of the DevX example applications to help the reader understand the library, how to use it and how it can be extended. Before you get started please read Dave's blog on the tools. I won't repeat that content here.
This blog assumes that you are already familiar with Azure Sphere development. If you're just getting started with Azure Sphere I recommend that you work through the 3-part Avnet Azure Sphere getting started blogs.
Clone the AzureSphereDevX repo
The AzureSphereDevX library is on GitHub: gloveboxes/AzureSphereDevX (github.com) The repo leverages git submodules, to clone the repo open the Windows PowerShell and enter the command:
git clone --recurse-submodules https://github.com/gloveboxes/AzureSphereDevX.git
Let's take a look at the directory structure to see what we cloned . . .
- The include and src directories contain the DevX library implementation files. You should not need to modify these files. If you come across a implementation requirement where you find yourself making changes to the DevX libraries, please open a issue on the DevX GitHub page to request the change, or open a pull request to contribute your change. Your feature may already be implemented, just not in an obvious way.
- The examples directory contains small and simple example applications that show how to utilize each of the DevX interfaces.
- If we drill down into one of the example directories you'll notice that there are very few implementation files, usually just a main.c. This is one benefit of using the DevX libraries, all the Azure connection logic and logic to manage IoT features is contained in the DevX code so your code is very specific to just your implementation. It's very clean!
Notes on the graphics in this blog
- All the graphics in the blog can be enlarged by clicking on the image. If the image is blurry or not readable, just click on the image to see a full resolution copy of the image.
- Graphics that show c code all include line numbers. The line numbers in the graphics correspond to the final azure_end_to_end_blog example that was created for this blog.
- Can you view the final main.c file here.
azure_end_to_end
Let's load and run the azure_end_to_end example. This will allow you to try out the implementation and give me talking points.
- Using Visual Studio 2019 or VS Code, open the azure_end_to_end folder
- Open the main.c file
High Level Azure Sphere Architecture
The High Level application is single threaded and follows a design pattern called Event Loop. To understand this application let’s examine the four ways the application execution path is driven.
- main()loop
- Event Loop Timers
- Event Loop Hardware Events
- Azure IoT Library Callbacks
main() loop
The entire main() function is shown below. It's logic is simple, (1) initialize the system, (2) call EventLoop_Run() unless an exit condition was encountered, then (3) cleanup and exit. Every DevX application will have this same, simple main() implementation.
The call to EventLoop_Run() is key! When any events are ready to be processed, this function passes control to the appropriate handler to service the event. Events can be timers expiring, hardware events (UART received data) or a real-time application sent a inter-core message to the high level application.
Event Handlers
The DevX library implements high level handlers that in turn call user defined handlers. For completeness, I've listed the DevX handlers below. However, you don't need to touch these handlers as the DevX library abstracts this functionality; you only need to define and implement the handlers for your applications.
DevX Handlers
- AzureConnectionHandler: This handler is called based on a timer and is responsible for establishing and maintaining the secure connection to the Azure IoT Hub. This handler is also responsible for calling IoTHubDeviceClient_LL_DoWork(). IoTHubDeviceClient_LL_DoWork() drives the logic to/from the IoT Hub. For example, when the application sends a telemetry message, it's queued up until IoTHubDeviceClient_LL_DoWork() is called, then it gets sent.
- deviceTwinCallbackHandler: This handler is called whenever the application receives a device twin update message from the IoT Hub. The handler uses the device_twin_bindings[] array to process device twin updates and pass control to specific user defined twin handlers.
- directMethodCallbackHandler: This handler is called whenever a directMethod message is received by the application. The handler uses the direct_method_bindings[] array to pass control to the correct user defined direct method handler.
- messageReceivedCallback: This handler is called whenever a C2D message is received by the application. Your application can register a single callback to receive these messages.
- connectionStatusChangedCallback: Applications can register up to MAX_CONNECTION_STATUS_CALLBACKS handlers that will be called when the Azure IoT Hub connection status changes.
User Defined Handlers
The callback handlers can all be identified in the bindings section of main.c. I've identified all the handlers in the graphic below, more on the bindings in a minute.
Here's a high level architecture diagram that illustrates the azure_end_to_end application architecture, the application code path and all the handlers. This looks like a pretty busy diagram, however if you take some time to look at each of the handlers, they are simple and straight forward.
Bindings
The DevX library uses the concept of bindings to define all the details for different implementations. Binding are defined, then added to binding arrays. When events/messages come into the application, the binding arrays are traversed, and the binding details are used to execute specific functionality or call specific handlers that know how to manage the event. Let's look at each binding type.
DX_DEVICE_TWIN
This binding defines everything the DevX library needs to process user defined device twins.
The key elements of this binding type are . . .
- propertyName: This is the name of the device twin, or the "key" for the device twin JSON {"key": value} pair
- propertyValue: This is the current value of the device twin for all types except for strings.
- Scalar types: For device twins of scalar types (int, bool, float and double), the propertyValue field contains the most recent value of the device twin.
- String device twins types are handled differently from the other scalar data types. When there is a device twin of type string, the propertyValue does not contain the string since strings can be arbitrary lengths. If the application requires a string device twin, it must implement a custom callback for the device twin to use the string immediately or copy the incoming string to a buffer to reference later. Another potential way to leverage string type device twins is to pass JSON documents to the the application.
- twinType: This is the data type for the device twin: DX_DEVICE_TWIN_BOOL, DX_DEVICE_TWIN_FLOAT, DX_DEVICE_TWIN_DOUBLE, DX_DEVICE_TWIN_INT or DX_DEVICE_TWIN_STRING
- handler: Function pointer to an optional user defined handler to do something specific when the device twin is updated. If the handler function is not defined, then the default behavior is to update the twinState variable.
- context: This is a general purpose pointer that can be used in anyway that the developer chooses. See the Avnet example here for one way to leverage this pointer. That example assigns a DX_GPIO_BINDING pointer for the content pointer so that a common handler can process multiple device twins (all related to turning LEDs on/off).
In the azure_end_to_end example the device twins bindings and binding array are identified below. Note that only the DesiredSampleRate twin has a custom handler. This handler will read the device twin value and modify the PublishMessage timer with the new value. All other bindings just keep the state in the twinState element.
DX_DIRECT_METHOD
This binding defines everything the DevX library needs to process user defined direct methods.
The key elements of this binding type are . . .
- methodName: This is the name of the direct method that can be called from the cloud
- handler: This is a function pointer to the handler that implements the specific direct method
In the azure_end_to_end example the direct method binding and binding array are identified below.
DX_GPIO_BINDING
This binding defines everything the DevX library needs to open, drive and close GPIO devices
The key elements of this binding type are . . .
- fd: This is the file descriptor for the GPIO hardware; the file descriptor is assigned when the hardware is opened
- pin: This is the hardware pin for the GPIO device, for example LED2 as defined in the hardware definitions files
- initialState: The initial state to drive the pin when it's opened, high or low
- direction: Is the GPIO an input or an output?
- invertPin: Is the GPIO active high or active low?
In the azure_end_to_end example the GPIO bindings and binding array are identified below.
DX_TIMER_BINDING
This binding defines everything the DevX library needs to initialize and call timer handlers.
There key elements of this binding type are . . .
- handler: This is a function pointer to the handler that will run when the timer expires
- period: This is a timespec struct that defines the period for the timer handler to run, for example every 5 seconds.
In the azure_end_to_end example the timer bindings and binding array are identified below.
Other Binding Types
There are additional binding types that are not used in this example application.
DX_INTERCORE_BINDING
This binding is used to manage connections to real time applications over the inter-core communication path. Refer to the /examples/intercore_example to see how this binding is used.
DX_UART_BINDING
This binding is used to manage UART serial port communications. Refer to the /examples/uart_example to see how this binding is used.
Test your understanding of the Architecture
Now it's your turn to dig into an Azure Sphere example and create the architecture diagram . . .
Open the Avnet Example
Avnet has created a DevX example that reads all the sensors on the Avnet Azure Sphere Starter Kit. You can open the Avnet main.c file here. That file has all the detail you need to diagram the application architecture with the exception of the AzureConnectionHandler() timer handler, which I added to the diagram. Download and print out the blank diagram here to complete the exercise.
Assignment
The assignment is to fill out the diagram using details from the Avnet example main.c file. Identify all the different *_BINDING_*s from the implementation.
Configure and run the example
Let's get this example up and running . . .
Configure the application
This example connects to the Azure IoT Hub using a device provision service. If you're not familiar with these Azure Services or how to set them up, please visit this blog.
- Open the app_manifest.json file
- Add the ScopeID for your Azure Device Provision Service on line #6
- Add your IoT Hub hostname on line #11
- Add your Azure Sphere Tenant ID on line #13
- Run the "azsphere tenant show-selected" command to see your Azure Sphere Tenant ID
My updated manifest file is shown below . . .
- Open the CMakeList.txt file and select your development hardware from lines #12 - #15
Run the Application
- Connect your Azure Sphere device to your development PC
- Make sure your device is connected to a wifi network "azsphere device wifi show-status"
- Save all your changes and press F5 to build, load and run your application on the target hardware
Your application will start up and you should see output similar to the capture below.
Exercise the Application
We can exercise the device twins and direct method features from the cloud using the Azure IoT Explorer tool.
- Install the Azure IoT Explorer if not already installed
- Follow the instructions on the GitHub page to add your IoT Hub to the tool
- Open the Azure IoT Explorer
- Select your IoT Hub
- Select your device
- Execute the "azsphere device show-attached" command to see your device ID
- Select Device twin, note that the reported properties have been updated from the application
- Add the "DesiredSampleRate" device twin desired property and give it a new rate of 1 second, hit the save button, then the refresh button to see the reported property response
- You can directly edit the device twin JSON
- Note that the application is now sending a new telemetry message every one second
- In the Azure IoT Explorer select Direct Method
- Enter the direct method name: LightControl
- Enter the payload {"State": true} to turn on the dev kit LED
- Click on the "Invoke method" link
- Note the LED on the kit turns on
- Enter the payload {"State": false} to turn off the dev kit LED
Add a new feature to the example
I want to demonstrate how easy it is to add a new feature using the DevX libraries. So, let's add a new feature to the example.
Feature: Monitor application peak memory usage
Implementation: To monitor the applications memory usage we'll use the Applications_GetPeakUserModeMemoryUsageInKB(void) library call. We'll create a periodic timer that runs every 30 seconds where we'll poll the current peak memory used. If the new peak memory used is greater than the last reported value, then we'll send a telemetry message with the new value. This will allow the support team to monitor the application's memory usage. If the application has a memory leak, then this feature will help the support engineers identify memory usage issues in the field. Additionally, we'll add a direct method to allocate memory and not release it. We can call the direct method to simulate a memory leak so we can see the new feature in action. This sounds like a lot of code, but you'll see that when we use the DevX libraries, this is a simple task.
MemoryLeak direct method
To add a memory leak direct method we need to modify main.c . I can add that direct method in 11 lines of code!
- define a new DX_DIRECT_METHOD_BINDIN
- Add the new binding to the direct_method_bindings[] array
- Add the MemoryLeakHandler() signature to the Forward declarations section
- Add the MemoryLeakHandler() implementation
I tested my direct method by calling it multiple times until my application crashed!
Memory Monitor Implementation
To implement the memory monitor we need to . . .
- Define the telemetry we intend to send to the IoT Hub
- Define a new message property for the telemetry message
- Create a periodic timer that will run our handler every 30 seconds
- Implement the handler
Define the telemetry
The DevX library contains all the functions we need to construct and send telemetry messages to the IoT Hub. Before we can construct and send any telemetry we need to define what it will look like. Telemetry can be any valid JSON, but is typically {"key": value} pairs. For this feature the telemetry will be . . .
{"MemoryHighWaterMark": <memory usage in Kb>, "reportUTC": "<UTC when the new memory report was generated>"}"
We'll use the same message properties and content properties that this application is already using.
Define a new message property
Here's a little gem for anyone who's reading all this detail! The DevX library provides an easy way to define and send custom telemetry message properties. Why is this important you may ask? Well, when IoT applications leverage message properties you can setup unique routing rules for telemetry messages when they hit the Azure IoT Hub. So regular telemetry messages with the "type" set to "telemetry" can be routed to hot or cold storage, or both for example. For the memory leak data, we may want to make sure someone puts eyes on the data, after all this could indicate that our application in the field has a critical bug, and maybe we have hundreds of thousands of these devices deployed. So when we send the memory leak telemetry we'll send it with the memoryMessagePropterties. On the Azure side, we can setup a routing rule to send these messages to a custom endpoint where they can be automatically processed. If the memory usage reported is above some predefined level, we can automatically generate a support ticket with all the details from the telemetry message. Below you can see the original messageProperties structure and the new memoryMessagePropterties structure I defined for our new feature.
Implementation
Creating the timer is similar to the way we added a direct method. I can add that feature in 21 lines of code!
- Add the required #include to pull in Applications_GetPeakUserModeMemoryUsageInKB(void)
- Add a new DX_TIMER_BINDING definition
- Add the binding to the timer_bindings[] array
- Add the monitor_memory_handler() signature to the Forward declarations section
- Implement the memory_monitor_handler() function. Note that when we send the telemetry message, we're using the memoryMessageProperties we defined above.
My main.c
If you want to recreate this demo, you can find my modified project here.
Test the new Feature
Here's output from my testing. I set the "DesiredSampleRate" to 15 seconds, and the memory monitor is running every 30 seconds. While the application was running I used the MemoryLeak direct method to leak memory between memory checks.
Debug output
Telemetry capture
Conclusion
The AzureSphereDevX libraries are a great addition to the Azure Sphere enablement suite. DevX helps to consolidate your implementation into the main.c file and keeps your application more readable and easier to maintain. I encourage all Azure Sphere development teams to start using these new libraries, I already have.
Dave Glover has also created an Azure Sphere Learning Path course that leverages the DevX libraries. You can view that material: http://avnet.me/azureSphereMSFTLearningPath
Enjoy,
Brian