This article outlines the steps a device developer should follow to convert an existing device to an IoT Plug and Play device. It describes how the developer can create the model that every IoT Plug and Play device requires, and the code changes that are necessary to enable the device to function as an IoT Plug and Play device.
Every IoT Plug and Play device has a model that describes the features and capabilities of the device. The model uses the Digital Twin Definition Language (DTDL) to describe the device capabilities.
IoT Plug and Play use the following DTDL elements to describe devices: Interface, Telemetry, Property, Command, Relationship, and Component. DTDL provides a data description language that's compatible with common serialization formats such as JSON and binary serialization formats.
The core element in an IoT Plug and Play model is the interface. The interface describes the content of any digital twin and contains definitions for properties, telemetry, commands, relationships, and components. Interfaces are reusable as the schema for components in another interface.
{
"@context": "dtmi:dtdl:context;2",
"@id": "dtmi:com:example:Thermostat;1",
"@type": "Interface",
"displayName": "Thermostat",
"contents": [
{
"@type": "Telemetry",
"name": "temp",
"schema": "double"
},
IoT Plug and Play interfaces typically contain definitions for:
To create an IoT Plug and Play model, you need to know for a given device:
To learn more, see IoT Plug and Play conventions.
When the device is created without a IoT Plug and Play model, the properties, telemetry, and commands aren't grouped or organized in any way.
The simplest way to model this device is to use a model with a single interface that contains all the capability definitions. IoT Plug and Play refers to such a model as a _no component_ model.
The following snippet shows an example of an interface that defines a no component model. This model defines the capabilities of a thermostat device:
{
"@context": "dtmi:dtdl:context;2",
"@id": "dtmi:com:example:Thermostat;1",
"@type": "Interface",
"displayName": "Thermostat",
"contents": [
{
"@type": "Telemetry",
"name": "temp",
"schema": "double"
},
{
"@type": "Property",
"name": "setPointTemp",
"writable": true,
"schema": "double"
},
{
"@type": "Command",
"name": "reboot",
"request": {
"name": "rebootTime",
"displayName": "Reboot Time",
"description": "Requested time to reboot the device.",
"schema": "dateTime"
},
"response": {
"name": "scheduledTime",
"schema": "dateTime"
}
}
]
}
In the interface definition:
The device telemetry, properties, and command definitions are contained in the `contents` array.
The following table shows the mapping between the DTDL elements and types in the IoT device SDKs:
IoT device SDK | DTDL |
Direct method | Command |
Device twin properties | Properties (read-only or writable) |
Telemetry | Telemetry |
No component models are useful for simple devices with a single sensor such as a thermostat with a temperature sensor.
No component models are useful for devices that send small quantities of data, or have a few sensors with data that's easy to aggregate.
Components let you compose interfaces from other interfaces. Components describe contents that are directly part of the interface.
In DTDL v2, a component cannot contain another component.
The following example shows a model that uses components. The temperature controller is composed of two thermostat components and a device information component. There are separate interface definitions for the thermostat and device information models:
{
"@context": "dtmi:dtdl:context;2",
"@id": "dtmi:com:example:TemperatureController;1",
"@type": "Interface",
"displayName": "Temperature Controller",
"description": "Device with two thermostats and remote reboot.",
"contents": [
...
{
"@type" : "Component",
"schema": "dtmi:com:example:Thermostat;1",
"name": "thermostat1",
"displayName": "Thermostat One",
"description": "Thermostat One of Two."
},
{
"@type" : "Component",
"schema": "dtmi:com:example:Thermostat;1",
"name": "thermostat2",
"displayName": "Thermostat Two",
"description": "Thermostat Two of Two."
},
{
"@type": "Component",
"schema": "dtmi:azure:DeviceManagement:DeviceInformation;1",
"name": "deviceInformation",
"displayName": "Device Information interface",
"description": "Optional interface with basic device hardware information."
}
...
This component approach is useful for devices with multiple sensors. You can use one component for each sensor or group related sensors that make up a feature into a component. Using components in the model helps to clarify the design of the device to someone using the model.
A component definition referenced by a model could be:
Inheritance
DTDL lets you extend interface capabilities using inheritance. The following example shows how the `ConferenceRoom` interface inherits from the `Room` interface. Because of inheritance, `ConferenceRoom` has both the `occupied` and `capacity` properties:
[
{
"@id": "dtmi:com:example:Room;1",
"@type": "Interface",
"contents": [
{
"@type": "Property",
"name": "occupied",
"schema": "boolean"
}
],
"@context": "dtmi:dtdl:context;2"
},
{
"@id": "dtmi:com:example:ConferenceRoom;1",
"@type": "Interface",
"extends": "dtmi:com:example:Room;1",
"contents": [
{
"@type": "Property",
"name": "capacity",
"schema": "integer"
}
],
"@context": "dtmi:dtdl:context;2"
}
]
Impact on the code
The IoT Plug and Play conventions describe how an IoT Plug and Play device should exchange messages with an IoT hub. The IoT Plug and Play device developer guide provides detailed guidance on how to implement telemetry, properties, and commands in different programming languages.
For example, when you update a property value in a component, it's important to include a marker identifying it as component:
TwinCollection reportedProperties = new TwinCollection();
TwinCollection component = new TwinCollection();
component["maxTemperature"] = 38.7;
component["__t"] = "c"; // marker to identify a component
reportedProperties["thermostat1"] = component;
await client.UpdateReportedPropertiesAsync(reportedProperties);
For writable properties, the convention defines how to acknowledge a property update. The following example shows how to acknowledge a property update in a component:
await client.SetDesiredPropertyUpdateCallbackAsync(async (desired, ctx) =>
{
JObject thermostatComponent = desired["thermostat1"];
JToken targetTempProp = thermostatComponent["targetTemperature"];
double targetTemperature = targetTempProp.Value<double>();
TwinCollection reportedProperties = new TwinCollection();
TwinCollection component = new TwinCollection();
TwinCollection ackProps = new TwinCollection();
component["__t"] = "c"; // marker to identify a component
ackProps["value"] = targetTemperature;
ackProps["ac"] = 200; // using HTTP status codes
ackProps["av"] = desired.Version; // not readed from a desired property
ackProps["ad"] = "desired property received";
component["targetTemperature"] = ackProps;
reportedProperties["thermostat1"] = component;
await client.UpdateReportedPropertiesAsync(reportedProperties);
}, null);
The design of a new model can come from:
"contents": [
{"@type": "Telemetry",
"name": "overallStatus",
"displayName": "Overall Status",
"description": "Overall status",
"schema": {
"@type":"Object",
"@id" :"dtmi:com:example:Telescope:TelescopeStatus;1",
"fields":
[
{
"name": "status",
"schema": {
"@type": "Enum",
"valueSchema": "integer",
"enumValues": [
... ommited for concision ...
]
}
},
{
"name": "pointingAt",
"schema": "dtmi:com:example:Telescope:CelestialCoordinate;1"
},
{
"name": "AtmosphericPressure",
"schema": "double",
"description": "High atmospheric pressure give better quality image"
},
{
"name": "TemperatureDelta",
"schema": "double",
"description": "Exterior temperature compared to interior Temperature, has an impact on the image quality"
}
]
}
Model design and tooling
To build a model, you need to create a valid DTDL file. The following tools can help you:
Hope this post will help you on modeling your PnP models. As always we are interested about your issues, success and questions! Feel free to comment this post or contact us.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.