IoT Plug and Play conventions

IoT Plug and Play devices should follow a set of conventions when they exchange messages with an IoT hub. IoT Plug and Play devices use the MQTT protocol to communicate with IoT Hub. IoT Hub also supports the AMQP protocol which available in some IoT device SDKs.

A device can include modules, or be implemented in an IoT Edge module hosted by the IoT Edge runtime.

You describe the telemetry, properties, and commands that an IoT Plug and Play device implements with a Digital Twins Definition Language (DTDL) model. There are two types of model referred to in this article:

  • No component - A model with no components. The model declares telemetry, properties, and commands as top-level elements in the contents section of the main interface. In the Azure IoT explorer tool, this model appears as a single default component.
  • Multiple components - A model composed of two or more interfaces. A main interface, which appears as the default component, with telemetry, properties, and commands. One or more interfaces declared as components with more telemetry, properties, and commands.

For more information, see IoT Plug and Play modeling guide.

Identify the model

To announce the model it implements, an IoT Plug and Play device or module includes the model ID in the MQTT connection packet by adding model-id to the USERNAME field.

To identify the model that a device or module implements, a service can get the model ID from:

  • The device twin modelId field.
  • The digital twin $metadata.$model field.
  • A digital twin change notification.

Telemetry

  • Telemetry sent from a no component device doesn't require any extra metadata. The system adds the dt-dataschema property.
  • Telemetry sent from a device using components must add the component name to the telemetry message.
  • When using MQTT, add the $.sub property with the component name to the telemetry topic, the system adds the dt-subject property.
  • When using AMQP, add the dt-subject property with the component name as a message annotation.

Note

Telemetry from components requires one message per component.

For more telemetry examples, see Payloads > Telemetry

Read-only properties

A device sets a read-only property which it then reports to the back-end application.

Sample no component read-only property

A device or module can send any valid JSON that follows the DTDL rules.

DTDL that defines a property on an interface:

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:example: Thermostat;1",
  "@type": "Interface",
  "contents": [
    {
      "@type": "Property",
      "name": "temperature",
      "schema": "double"
    }
  ]
}

Sample reported property payload:

"reported" :
{
  "temperature" : 21.3
}

Sample multiple components read-only property

The device or module must add the {"__t": "c"} marker to indicate that the element refers to a component.

DTDL that references a component:

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:com:example:TemperatureController;1",
  "@type": "Interface",
  "displayName": "Temperature Controller",
  "contents": [
    {
      "@type" : "Component",
      "schema": "dtmi:com:example:Thermostat;1",
      "name": "thermostat1"
    }
  ]
}

DTDL that defines the component:

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:com:example:Thermostat;1",
  "@type": "Interface",
  "contents": [
    {
      "@type": "Property",
      "name": "temperature",
      "schema": "double"
    }
  ]
}

Sample reported property payload:

"reported": {
  "thermostat1": {
    "__t": "c",
    "temperature": 21.3
  }
}

For more read-only property examples, see Payloads > Properties.

Writable properties

A back-end application sets a writable property that IoT Hub then sends to the device.

The device or module should confirm that it received the property by sending a reported property. The reported property should include:

  • value - the actual value of the property (typically the received value, but the device may decide to report a different value).
  • ac - an acknowledgment code that uses an HTTP status code.
  • av - an acknowledgment version that refers to the $version of the desired property. You can find this value in the desired property JSON payload.
  • ad - an optional acknowledgment description.

Acknowledgment responses

When reporting writable properties the device should compose the acknowledgment message, by using the four fields in the previous list, to indicate the actual device state, as described in the following table:

Status(ac) Version(av) Value(value) Description(av)
200 Desired version Desired value Desired property value accepted
202 Desired version Value accepted by the device Desired property value accepted, update in progress (should finish with 200)
203 0 Value set by the device Property set from the device, not reflecting any desired
400 Desired version Actual value used by the device Desired property value not accepted
500 Desired version Actual value used by the device Exception when applying the property

When a device starts up, it should request the device twin, and check for any writable property updates. If the version of a writable property increased while the device was offline, the device should send a reported property response to confirm that it received the update.

When a device starts up for the first time, it can send an initial value for a reported property if it doesn't receive an initial desired property from the IoT hub. In this case, the device can send the default value with av to 0 and ac to 203. For example:

"reported": {
  "targetTemperature": {
    "value": 20.0,
    "ac": 203,
    "av": 0,
    "ad": "initialize"
  }
}

A device can use the reported property to provide other information to the hub. For example, the device could respond with a series of in-progress messages such as:

"reported": {
  "targetTemperature": {
    "value": 35.0,
    "ac": 202,
    "av": 3,
    "ad": "In-progress - reporting current temperature"
  }
}

When the device reaches the target temperature, it sends the following message:

"reported": {
  "targetTemperature": {
    "value": 20.0,
    "ac": 200,
    "av": 4,
    "ad": "Reached target temperature"
  }
}

A device could report an error such as:

"reported": {
  "targetTemperature": {
    "value": 120.0,
    "ac": 500,
    "av": 3,
    "ad": "Target temperature out of range. Valid range is 10 to 99."
  }
}

Object type

If a writable property is defined as an object, the service must send a complete object to the device. The device should acknowledge the update by sending sufficient information back to the service for the service to understand how the device has acted on the update. This response could include:

  • The entire object.
  • Just the fields that the device updated.
  • A subset of the fields.

For large objects, consider minimizing the size of the object you include in the acknowledgment.

The following example shows a writable property defined as an Object with four fields:

DTDL:

{
  "@type": "Property",
  "name": "samplingRange",
  "schema": {
    "@type": "Object",
    "fields": [
      {
        "name": "startTime",
        "schema": "dateTime"
      },
      {
        "name": "lastTime",
        "schema": "dateTime"
      },
      {
        "name": "count",
        "schema": "integer"
      },
      {
        "name": "errorCount",
        "schema": "integer"
      }
    ]
  },
  "displayName": "Sampling range"
  "writable": true
}

To update this writable property, send a complete object from the service that looks like the following example:

{
  "samplingRange": {
    "startTime": "2021-08-17T12:53:00.000Z",
    "lastTime": "2021-08-17T14:54:00.000Z",
    "count": 100,
    "errorCount": 5
  }
}

The device responds with an acknowledgment that looks like the following example:

{
  "samplingRange": {
    "ac": 200,
    "av": 5,
    "ad": "Weighing status updated",
    "value": {
      "startTime": "2021-08-17T12:53:00.000Z",
      "lastTime": "2021-08-17T14:54:00.000Z",
      "count": 100,
      "errorCount": 5
    }
  }
}

Sample no component writable property

When a device receives multiple desired properties in a single payload, it can send the reported property responses across multiple payloads or combine the responses into a single payload.

A device or module can send any valid JSON that follows the DTDL rules.

DTDL:

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:example: Thermostat;1",
  "@type": "Interface",
  "contents": [
    {
      "@type": "Property",
      "name": "targetTemperature",
      "schema": "double",
      "writable": true
    },
    {
      "@type": "Property",
      "name": "targetHumidity",
      "schema": "double",
      "writable": true
    }
  ]
}

Sample desired property payload:

"desired" :
{
  "targetTemperature" : 21.3,
  "targetHumidity" : 80,
  "$version" : 3
}

Sample reported property first payload:

"reported": {
  "targetTemperature": {
    "value": 21.3,
    "ac": 200,
    "av": 3,
    "ad": "complete"
  }
}

Sample reported property second payload:

"reported": {
  "targetHumidity": {
    "value": 80,
    "ac": 200,
    "av": 3,
    "ad": "complete"
  }
}

Note

You could choose to combine these two reported property payloads into a single payload.

Sample multiple components writable property

The device or module must add the {"__t": "c"} marker to indicate that the element refers to a component.

The marker is sent only for updates to properties defined in a component. Updates to properties defined in the default component don't include the marker, see Sample no component writable property.

When a device receives multiple reported properties in a single payload, it can send the reported property responses across multiple payloads or combine the responses into a single payload.

The device or module should confirm that it received the properties by sending reported properties:

DTDL that references a component:

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:com:example:TemperatureController;1",
  "@type": "Interface",
  "displayName": "Temperature Controller",
  "contents": [
    {
      "@type" : "Component",
      "schema": "dtmi:com:example:Thermostat;1",
      "name": "thermostat1"
    }
  ]
}

DTDL that defines the component:

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:com:example:Thermostat;1",
  "@type": "Interface",
  "contents": [
    {
      "@type": "Property",
      "name": "targetTemperature",
      "schema": "double",
      "writable": true
    }
  ]
}

Sample desired property payload:

"desired": {
  "thermostat1": {
    "__t": "c",
    "targetTemperature": 21.3,
    "targetHumidity": 80,
    "$version" : 3
  }
}

Sample reported property first payload:

"reported": {
  "thermostat1": {
    "__t": "c",
    "targetTemperature": {
      "value": 23,
      "ac": 200,
      "av": 3,
      "ad": "complete"
    }
  }
}

Sample reported property second payload:

"reported": {
  "thermostat1": {
    "__t": "c",
    "targetHumidity": {
      "value": 80,
      "ac": 200,
      "av": 3,
      "ad": "complete"
    }
  }
}

Note

You could choose to combine these two reported property payloads into a single payload.

For more writable property examples, see Payloads > Properties.

Commands

No component interfaces use the command name without a prefix.

On a device or module, multiple component interfaces use command names with the following format: componentName*commandName.

For more command examples, see Payloads > Commands.

Tip

IoT Central has its own conventions for implementing Long-running commands and Offline commands.

Next steps

Now that you've learned about IoT Plug and Play conventions, here are some other resources: