We recently announced IoT Plug and Play (in preview) as part of a broader effort to reduce the time needed to integrate IoT devices into IoT solutions and IoT Central. What Windows Plug and Play did for connecting hardware devices to Windows, IoT Plug and Play aims to do the same for IoT devices connecting to IoT solutions.

There is some excellent Plug and Play documentation available at https://aka.ms/iotpnpdocs/. Take some time to read through this documentation and familiarize yourself with Plug and Play concepts, including DTDL concepts.

What this blog post attempts to do is take the Plug and Play documentation a step further and apply it to a real world device. In this case, a Raspberry Pi 3 Model B+ with a Sense HAT installed. We will configure the Sense HAT LED and sensors for Plug and Play and develop the underlying code to read from these sensors and control the LED display.

This blog is targeted at device builders (interested in device certification and getting their device listed on our Device Catalog) and solution developers. This blog assumes you have a solid understanding of Raspberry Pi + Linux, Azure IoT services such as IoT Hub and the Azure IoT Device SDKs, as well as developing in Python and C. The steps in this blog are based on the Tutorial: Create and test a device capability model using Visual Studio Code.

Reminder, Plug and Play is in Preview meaning no SLA and support is limited to certain regions (Central US, North Europe, or Japan East), SDKs and for devices only (no IoT Edge support yet).

Prerequisites

Install the software prerequisites on your PC that are listed here

Create an IoT Hub in either Central US, North Europe , or Japan East .

, or . Add an IoT Device to your IoT Hub. Make a note of the connection string.

Setup your Azure Cloud Shell as described here

Raspberry Pi 3 Model B+ with Raspbian GNU/Linux 9 (stretch) installed

Sense HAT installed on the Raspberry Pi device

Sense HAT

The Sense HAT device has a LED display and number of sensors built into it including: temperature, humidity and pressure. It also has a well documented Python API. But the biggest reason I like to use it is to avoid fiddling with breadboards, resistors and jumper cables .

Getting Started

Once you have the Sense Hat installed on the Raspberry Pi along with Raspbian 9 (stretch), the next step is to install the Sense HAT Python API.

sudo apt-get update sudo apt-get install sense-hat sudo reboot

Note: I haven't found a decently documented C-based API for the Sense HAT. If you know of one, please send me a link to it!

Let's also ensure we have Python 3 installed.

pi@raspberrypi: python3 --version Python 3.5.3

Now test that the Sense HAT is working with the following Python commands.

pi@raspberrypi:/ $ python3 Python 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from sense_hat import SenseHat >>> sense = SenseHat() >>> sense.show_message("Hello world!")

If all is successful, a "Hello world!" message should scroll across the LED display.

Model your device

If you are familiar with DTDL (which is based on JSON-LD) you'll know we need to first develop an Interface for the Sense HAT and then create a Model file. Together these form a Device Capability Model (DCM).

Creating an Interface

A Interface describes the capabilities that are implemented by a device. Interfaces are reusable and can be used across different capability models.

In our case, the Interface we will develop will describe the Sense HAT's:

temperature sensor

humidity sensor

pressure sensor

LED display

In DTDL, the three (3) sensors are considered a Telemetry data type since they emit data from the sensor. The LED display will be a Command data type, as our IoT solution will send a command to the Sense HAT to display a message on the LED.

Follow the "Create the interface file" instructions, substituting the following:

SenseHat as the name of the interface and press Enter . VS Code creates a sample interface file called SenseHat.interface.json .

Replace the contents of the sample SenseHat.interface.json file with the following JSON:

{ "@id": "urn:sense_hat:SenseHat:1", "@type": "Interface", "displayName": "Sense Hat Interface", "contents": [ { "@type": "Telemetry", "comment": "Sense Hat Temperature", "name": "temperature", "schema": "double", "unit": "Units/Temperature/celsius" }, { "@type": "Telemetry", "comment": "Sense Hat Relative Humidity", "name": "humidity", "schema": "double", "unit": "Units/Humidity/percent" }, { "@type": "Telemetry", "comment": "Sense Hat Pressure", "name": "pressure", "schema": "double", "unit": "Units/Pressure/kiloPascal" }, { "@type": "Command", "description": "This command will display a message on the Sense Hat LED display.", "name": "show_message", "commandType": "synchronous", "request": { "name": "message", "schema": "string" } } ], "@context": "http://azureiot.com/v1/contexts/IoTModel.json" }

As you can see we've modeled the 3 sensors and the LED display in the Interface using DTDL. For each data type there are a number of DTDL properties which can be set. In the above example I have configured a few, but there are more.

IntelliSense is available when editing the DTDL using VS Code. Try changing any of the "unit" values and you'll see what I mean.

Creating a Model file

Next, we will want to create a Model file. The model file specifies the interfaces that your device implements. There are typically at least two interfaces in a model - one or more that define the specific capabilities of your device, and a standard interface that all IoT Plug and Play devices must implement.

Follow the "Create the model file" instructions, substituting the following:

Enter SenseHatModel as the name of the model. VS Code creates a sample interface file called SenseHatModel.capabilitymodel.json .

as the name of the model. VS Code creates a sample interface file called . Replace the contents of the sample SenseHatModel.capabilitymodel.json file (filename must end with .capabilitymodel.json) with the following JSON:

{ "@id": "urn:sense_hat:SenseHatModel:1", "@type": "CapabilityModel", "displayName": "Sense Hat Capability Model", "implements": [ { "schema": "urn:sense_hat:SenseHat:1", "name": "sensehat" }, { "schema": "urn:azureiot:DeviceManagement:DeviceInformation:1", "name": "deviceInfo" } ], "@context": "http://azureiot.com/v1/contexts/IoTModel.json" }

Notice the DeviceInformation interface (known as a standard interface) was automatically added and is required (e.g. for Device Certification).

Lastly download the DeviceInformation interface from the public model repository using the instructions.

Publish the model

As part of the release of Plug and Play, Microsoft is hosting an Azure Certified for IoT repository for you to host your model. Using the model repository is optional; you can store the model on the local device (not covered in this Blog post).

There are two sides to the repository:

Public repository - where publicly published Models and Interfaces are stored.

Company repository - where private Models and Interfaces are stored (e.g. prior to publishing publicly or when you have feature/device that is specific to your organization).

Follow the "Publish your model" instructions to publish the following two files to your Company repository:

SenseHat.interface.json

SenseHatModel.capabilitymodel.json

By default, the model will be stored in the Company repository. You can optionally publish it to the Public repository.

Generate stub code

Using VS Code you can generate stub code for your IoT device. However, there are some limitations with this at the moment:

You will need to use Azure IoT Device SDKs that are Plug and Play-aware. Currently, only the C and Node Device SDKs are Plug and Play-aware. The other SDKs (e.g. Python) will be updated to be Plug and Play-aware in the future.

VS Code only generates stub code in C (sorry Node ).

These limitations pose a problem for us, as you will see later (recall, the Sense HAT only ships with a Python API!).

Follow the "Generate code" instructions to generate the stub C code, substituting the following:

SenseHatModel.capabilitymodel.json as the model file

as the model file sensehat_app as the app name

Once you have finished generating the stub code you will find the following files in a new sensehat_app folder:

CMakeLists.txt main.c pnp_device.c pnp_device.h Readme.md SenseHatModel_impl.c SenseHatModel_impl.h \utilities

The primary file you need to concern yourself with is SenseHatModel_impl.c. This is the file where we will add our implementation code.

Update the stub code

Now let's add our implementation code to SenseHatModel_impl.c.

Using VS Code, open the sensehat_app\SenseHatModel_impl.c file. Paste in the implementation code from https://github.com/khilscher/SenseHATPnP/blob/master/sensehat_app/SenseHatModel_impl.c

Recall that I mentioned earlier that the Sense HAT API is Python-based. To work around this, I am using popen to run the Python 3 process and pass in one of several Python scripts I developed for interacting with the Sense HAT hardware. Popen returns pointer to an open stream, which contains the return value from the Python script. I admit it's a bit of a hack, but it works.

Configure the Raspberry Pi

Now it's time to configure your Raspberry Pi.

SSH into your Raspberry Pi. Note: I am logged in as the user "pi" and I will save everything to the pi user home directory. Run the following commands:

pi@raspberrypi: cd /home/pi pi@raspberrypi: git clone https://github.com/khilscher/SenseHATPnP.git

The last command will copy the Sense HAT Python scripts from GitHub to the /home/pi/SenseHatPnP/python_scripts/ folder.

Note: The Python script paths are hard coded in the SenseHatModel_impl.c file. It expects them to be saved under /home/pi/SenseHATPnP/python_scripts/. If you use another location, please update SenseHatModel_impl.c accordingly.

Build the code

The final step is to build your code on the Raspberry Pi device.

Follow the Ubuntu instructions in the Readme.md file in the sensehat_app folder, substituting the following:

The Readme.md references a azure-iot-sdk-c-pnp folder. This is incorrect. It is azure-iot-sdk-c. Open a cmd prompt in your working folder on your PC. Use scp to copy the sensehat_app folder from your PC to the azure-iot-sdk-c folder on you Pi device.

c:\my_working_folder> scp -r sensehat_app pi@<RPi IP address>:/home/pi/azure-iot-sdk-c

Run the remaining build steps contained in the Readme.md. Ignore warnings but fix any errors encountered during the build process.

Running the code

Running your code involves executing the sensehat_app.

pi@raspberrypi: cd /home/pi/azure-iot-sdk-c/cmake/sensehat_app/ pi@raspberrypi: ./sensehat_app "[IoTHub device connection string]"

After executing the above commands the sensehat_app should be running and sending telemetry to Azure IoT Hub. During startup, watch how it logs the registration of its PnP interfaces with IoT Hub.

... Info: DEVICEINFO_INTERFACE: Interface successfully registered. Info: Interface registration callback invoked, interfaces have been successfully registered Info: DigitalTwin interfaces successfully registered Info: DEVICEINFO_INTERFACE: Queued async report read only property for manufacturer Info: DEVICEINFO_INTERFACE: Queued async report read only property for model Info: DEVICEINFO_INTERFACE: Queued async report read only property for swVersion Info: DEVICEINFO_INTERFACE: Queued async report read only property for osName Info: DEVICEINFO_INTERFACE: Queued async report read only property for processorArchitecture Info: DEVICEINFO_INTERFACE: Queued async report read only property for processorManufacturer Info: DEVICEINFO_INTERFACE: Queued async report read only property for totalStorage Info: DEVICEINFO_INTERFACE: Queued async report read only property for totalMemory Info: DEVICE_INFO: Queuing of all properties to be reported has succeeded ... Info: Send telemetry data to IoT Hub 32.26278305053711 Info: SENSEHAT_INTERFACE:: DigitalTwin_InterfaceClient_SendTelemetryAsync successfully sent temperature 32.638771057128906 Info: SENSEHAT_INTERFACE:: DigitalTwin_InterfaceClient_SendTelemetryAsync successfully sent humidity Info: DigitalTwin Client Core: Processing telemetry callback. confirmationResult=IOTHUB_CLIENT_CONFIRMATION_OK, userContextCallback=0x66c418 Info: DigitalTwin Interface: Invoking telemetry confirmation callback for interface=sensehat, reportedStatus=DIGITALTWIN_CLIENT_RESULT_INVALID, userContextCallback=0xacb4c Info: DigitalTwin successfully delivered telemetry message for Sensehat::temperature Info: SENSEHAT_INTERFACE: DigitalTwin successfully delivered telemetry message for <temperature> Info: DigitalTwin Interface: Invoking telemetry confirmation returned 880.73828125 Info: SENSEHAT_INTERFACE:: DigitalTwin_InterfaceClient_SendTelemetryAsync successfully sent pressure Info: DigitalTwin Client Core: Processing telemetry callback. confirmationResult=IOTHUB_CLIENT_CONFIRMATION_OK, userContextCallback=0x6538e8 Info: DigitalTwin Interface: Invoking telemetry confirmation callback for interface=sensehat, reportedStatus=DIGITALTWIN_CLIENT_RESULT_INVALID, userContextCallback=0xacb58 Info: DigitalTwin successfully delivered telemetry message for Sensehat::humidity Info: SENSEHAT_INTERFACE: DigitalTwin successfully delivered telemetry message for <humidity> Info: DigitalTwin Interface: Invoking telemetry confirmation returned Info: SENSEHAT_INTERFACE: Queuing of all telemetries to be sent has succeeded Info: DigitalTwin Client Core: Processing telemetry callback. confirmationResult=IOTHUB_CLIENT_CONFIRMATION_OK, userContextCallback=0x66c418 Info: DigitalTwin Interface: Invoking telemetry confirmation callback for interface=sensehat, reportedStatus=DIGITALTWIN_CLIENT_RESULT_INVALID, userContextCallback=0xacb64 Info: DigitalTwin successfully delivered telemetry message for Sensehat::pressure Info: SENSEHAT_INTERFACE: DigitalTwin successfully delivered telemetry message for <pressure> ...

Using Device Explorer, you will see messages arriving at your IoT Hub that resemble:

Receiving events... 10/3/2019 10:11:34 AM> Device: [SenseHat], Data:[{"modelInformation":{"capabilityModelId":"urn:sense_hat:SenseHatModel:1","interfaces":{"urn_azureiot_ModelDiscovery_ModelInformation":"urn:azureiot:ModelDiscovery:ModelInformation:1","urn_azureiot_Client_SDKInformation":"urn:azureiot:Client:SDKInformation:1","sensehat":"urn:sense_hat:SenseHat:1","deviceInfo":"urn:azureiot:DeviceManagement:DeviceInformation:1"}}}]Properties: 'iothub-message-schema': 'modelInformation' SYSTEM>iothub-interface-id=urn:azureiot:ModelDiscovery:ModelInformation:1 SYSTEM>iothub-interface-name=urn_azureiot_ModelDiscovery_ModelInformation SYSTEM>iothub-connection-device-id=SenseHat SYSTEM>iothub-connection-auth-method={"scope":"device","type":"sas","issuer":"iothub","acceptingIpFilterRule":null} SYSTEM>iothub-connection-auth-generation-id=637055717398532553 SYSTEM>iothub-enqueuedtime=10/3/2019 4:11:34 PM SYSTEM>iothub-message-source=Telemetry SYSTEM>x-opt-sequence-number=467 SYSTEM>x-opt-offset=222592 SYSTEM>x-opt-enqueued-time=10/3/2019 4:11:34 PM SYSTEM>EnqueuedTimeUtc=10/3/2019 4:11:34 PM SYSTEM>SequenceNumber=467 SYSTEM>Offset=222592 SYSTEM>content-type=application/json 10/3/2019 10:11:46 AM> Device: [SenseHat], Data:[{ "temperature": 32.170456 }]Properties: 'iothub-message-schema': 'temperature' SYSTEM>iothub-interface-name=sensehat SYSTEM>iothub-connection-device-id=SenseHat SYSTEM>iothub-connection-auth-method={"scope":"device","type":"sas","issuer":"iothub","acceptingIpFilterRule":null} SYSTEM>iothub-connection-auth-generation-id=637055717398532553 SYSTEM>iothub-enqueuedtime=10/3/2019 4:11:46 PM SYSTEM>iothub-message-source=Telemetry SYSTEM>x-opt-sequence-number=468 SYSTEM>x-opt-offset=223520 SYSTEM>x-opt-enqueued-time=10/3/2019 4:11:46 PM SYSTEM>EnqueuedTimeUtc=10/3/2019 4:11:46 PM SYSTEM>SequenceNumber=468 SYSTEM>Offset=223520 SYSTEM>content-type=application/json

The above shows that Plug and Play information is passed between the device and IoT Hub using message properties.

Plug and Play testing with IoT explorer

IoT explorer is a new tool that allows you to test Plug and Play interfaces as well as view the telemetry being received by IoT Hub from the device. Think of it as Device Explorer with Plug and Play support.

Download and install IoT explorer on you PC. Launch IoT explorer and connect to your IoT Hub using the connection string. Select Settings in the top right corner. Under "We'll look for your model definition in the following locations (in order)" click New and add the connection string to your Company model repository from the Azure Certified for IoT repository If you've published your device model to the Public repository, you can skip this step. Select Save and close Settings. Select your Plug and Play device and select Device Twin.

Notice how the device information is now available from the IoT Hub device twin!

Under Digital Twin, expand urn:sense_hat:SenseHat:1 and select Commands. Send a message to the LED on the Sense HAT.

To view the telemetry from the Sense HAT, select Telemetry and click Start.

Congratulations! You have successfully Plug and Play-enabled your Sense HAT device. But wait, that's not all!

IoT Central

IoT Central has preview support for Plug and Play as well! In my next blog post I will show you how to connect the Sense HAT to IoT Central using Plug and Play.

Conclusion

Hopefully this blog post has provided you the details needed to get your device working with Plug and Play and helps you avoid some of the pitfalls I ran into. I would also love to hear your feedback on what you like (and don't like) about Plug and Play.

Cheers,

Kevin

Twitter: @khilscher

LinkedIn: https://www.linkedin.com/in/kevinhilscher

References