# Sending Device Messages

With trusted and connected Ability native devices you can begin sending and receiving messages to and from the cloud. Following is an overview of the most commonly used types of messages you can send and receive through a directly connected device on the Ability Platform. Example types and Device API information can be found elsewhere in the Ability Developer Portal. In the examples below we will be using only the Device API V2 since this is the recommneded communication interface for your devices.

Reference implementation

For an applied example of these concepts check out the reference device project on Codebits.

# Messages Schema

Every directly connected device needs to adhere to the required schema of messaging. Device to cloud (D2C) message interpretation depends on the data that you include in your messages. Every IoT Hub message consists of:

  • system properties
  • application properties
  • message body

The properties act similarly to HTTP headers and they contain metadata about your message and they are in the form of key-value pairs. Some of them are added automatically by the IoT Hub SDK (system properties), some others have to be set by you (application properties). Simply put, the application properties inform the cloud what kind of message you are sending. Probably the most fundamental property that you will be including is msgType. The supported values of this property are:

  • variable
  • alarm
  • event
  • action

The first three types are used for sending telemetry values as defined in your device's type definition. Examples of those are presented in this article. action messages are used for all the other operations. Examples can be found in the Device API section.

TIP

All keys (e.g., properties, attributes, etc.) and all headers in the platform are case sensitive. The common guideline to all custom naming is to have all such names in camelCase.

The message body contains the actual content of your messages. Depending on the type of your message it could be: some information model, telemetry from your device (variables, alarms, events), information about a reference that you want to create, etc.

Sections below will present some of the types of messages that you can send together with their properties and bodies. The C# code is also attached as an example of how to send such mesages using IoT Hub SDK. However, first of all, we need to connect to IoT Hub.

# Connecting to IoT Hub

Before you are able to send any event you need to connect to IoT Hub, which is the communication channel between devices and the cloud. Your device needs to authenticate using its certificate. Have a look at an example of such connection (mind that the main goal of this code snippet is to present you the steps in the simple way, hence it is not a production quality code):

var securityProvider = new SecurityProviderX509Certificate(certificate); //certificate comes from the secure element of your device
var authentication = new DeviceAuthenticationWithX509Certificate(
    deviceId, //deviceId is same as your device's objectId (and same as you certificate's Common Name)
    securityProvider.GetAuthenticationCertificate());

var deviceClient = DeviceClient.Create(
  iotHubUrl, //the URL that you fetched from the Device Provisioning Service (DPS)
  authentication, 
  TransportType.Amqp); //communication protocol

await deviceClient.OpenAsync(); //established connection

//setting up a listener for Clodu-to-Device (C2D) messages
_ = Task.Run(async () =>
{
  while (true)
  {
    var receivedMessage = await _deviceClient.ReceiveAsync(TimeSpan.FromSeconds(5));
    if (receivedMessage == null) continue;

    var properties = receivedMessage.Properties;
    var body = Encoding.ASCII.GetString(receivedMessage.GetBytes());

    await _deviceClient.CompleteAsync(receivedMessage);

    //Do something with the message...
  }
});

The excerpt above goes one more step further after establishing the connection. It sets up a listener for C2D messages. It is necessary for you to be able to receive various information from the cloud, such as: acknowledgements, responses for your queries, configuration updates. Other than that, you can see that it is not only D2C messages that have properties and body. The same applies to the messages from the cloud (C2D). Again, the properties will inform you what kind of message your device received and the body will carry the actual information.

# Model Create Event

First, your device needs to inform the platform of its existence. Until now, you:

  • obtained a certificate
  • registered the device's identity ID in the Global Id Generator
  • obtained the IoT Hub URL via Device Provisioning Service (DPS) registration
  • connected with the IoT Hub

The missing piece is the information model object, as it has not yet been created in the Information Model database. Every device that wants to be connected to the platform needs to have a digital representation in a form of an information model object. In order to create it, your device needs to send a special model.create event. After that your device will become a fully initialized directly connected device and you will be able to do all the other supported operations (sending telemetry, receiving events from the cloud, etc.).

Here's an example of sending such an event:

//message paylaod
var bodyJson = @$" 
{{
  ""objectId"": ""{objectId}"",
  ""model"": ""abb.ability.device"",
  ""type"": ""my.directlyConnectedDevice.typeDefinition@1"",
  ""properties"": [
    ""hubName"": {{
      ""value"": ""{iotHubUrl}""
    }},
    ""deviceId"": {{
      ""value"": ""{deviceId}""
    }}
  ]
}}";

var message = new Message(Encoding.UTF8.GetBytes(bodyJson));

//message properties
message.Properties.Add("msgType", "action");
message.Properties.Add("action", "model.create");
message.Properties.Add("version", "2"); //we are using Device API V2
//optionally
messages.Properties.Add("correlationId", Guid.NewGuid());
messages.Properties.Add("ack", "all");

await deviceClient.SendEventAsync(message);

In the code you can see that the model.create message requires four properties and a body in the form of an information model object of your new device. Optionally, every action message can also contain the correlationId and ack in its properties. When you attach those, your device will be receiving additional acknowledgements with the information if the sent message was successful or not. You can see the format of each acknowledgement message in the Device API section of the Portal.

Regarding the information model, make sure that the type definition you want to use exists in the cloud before sending the event.

After you completed the process of sending the model.query message, you will receive the information model(s) of your device via IoT Hub (remember that listener that we setup right after connecting to IoT Hub?). At this moment, your device is fully initialized. Let's see some more examples of the messages that it can send.

# Send telemetry

Telemetry is sent via device to cloud (D2C) messages with one of the following msgType values:

# variable

Use to send telemetry data associated with one or more variables. Variable with this name must be defined in your device's type definition, and the value must comply with the selected data type.

# Example 1: Single

A single event carries a single piece of data

 {
   "objectId": "2e57962f-2b6c-43c0-8fd8-c0d50d140011",
   "model": "abb.ability.device",
   "timestamp": "2020-07-01T15:12:09.473Z",
   "variable": "brokenRotorIndex",
   "value": 5,
   "quality": 1
}

# Example 2: Batch

A single event carries multiple pieces of data

[
  {
   "objectId": "2e57962f-2b6c-43c0-8fd8-c0d50d140011",
   "model": "abb.ability.device",
   "timestamp": "2020-07-01T15:12:19.474Z",
   "variable": "voltage",
   "value": 5
 },
 {
   "objectId": "c0b4336b-fa23-4a7f-b92b-d971d8b041cd",
   "model": "abb.ability.device",
   "timestamp": "2020-07-01T15:12:19.476Z",
   "variable": "current",
   "value": 0.01,
   "quality": 0
 }
]

# alarm

Use to send telemetry data associated with one or more alarms. Value must conform to a payload definition of the alarm in the corresponding type.

The alarm value must conform to a payload definition of the alarm in the corresponding type.

Example:

{
 "objectId": "c0b4336b-fa23-4a7f-b92b-d971d8b041cd",
 "model": "abb.ability.device",
 "timestamp": "2020-07-09T12:34:23.020Z",
 "alarm" : "motorTempHigh",
 "alarmKey": "alarms.variables.mainComputer.fans.right",
 "value" : {
   "alarmCode": "171901",
   "limit": 10
 }
}

# event

Use to send telemetry data associated with one or more events. Value must conform to a payload definition of the event in the corresponding type.

The event value must conform to a payload definition of the event in the corresponding type definition.

Example:

{
  "objectId": "c0b4336b-fa23-4a7f-b92b-d971d8b041cd",
  "model": "abb.ability.device",
  "timestamp": "2020-07-09T12:34:23.020Z",
  "event" : "motorstarted",
  "value" : {
    "eventCode": "171900",
    "startTime": "2020-07-09T12:30:20.000Z"
  }
}

# Code Example

Below is a simple example of sending a variable message using C# with IoT Hub SDK:

var variableMessage = @" 
{
   ""objectId"": ""2e57962f-2b6c-43c0-8fd8-c0d50d140011"",
   ""model"": ""abb.ability.device"",
   ""timestamp"": ""2020-07-01T15:12:09.473Z"",
   ""variable"": ""brokenRotorIndex"",
   ""value"": 5,
   ""quality"": 0
}"
var iotHubMessage = new Message(Encoding.UTF8.GetBytes(variableMessage));

iotHubMessage.Properties.Add("msgType", "variable"); //put the proper value depending on the type of your telemetry

await deviceClient.SendEventAsync(iotHubMessage);

The same steps would be needed for the alarm and event messages. The things that would change would be the body and the msgType value in the properties.

# Query Models

Your device can request information models from the cloud. These can be the objects that belong to that device or any other objects that the device has permission to access. Let's have a look at a practical example of such an event:

//message paylaod
var dslQuery = "models('abb.ability.configuration').hasObjectId('91a5b789-f733-4809-850d-90f84e1f19ac')";
var bodyJson = $"{{ \"query\": \"{dslQuery}\" }}";

var message = new Message(Encoding.UTF8.GetBytes(bodyJson));

//message properties
message.Properties.Add("iothub-connection-device-id", deviceId);
message.Properties.Add("msgType", "action");
message.Properties.Add("action", "model.query");
message.Properties.Add("version", "2"); //we are using Device API V2
//optionally
messages.Properties.Add("correlationId", Guid.NewGuid());
messages.Properties.Add("ack", "all");

await deviceClient.SendEventAsync(message);

You can see that the procedure is exactly the same as for the model.create request, the only things that obviously differ are the properties and the body. The model.query request uses DSL queries to fetch the models, in the example we requested an object with a specific objectId in the abb.ability.configuration domain.

The response(s) for that request will be delivered to the device in the IoT Hub listener method, same as before. Make sure that you know what is the contact of these messages to treat them properly.

# Invoke Commands

The Ability Platform allows you to invoke commands (methods) on connected devices. It is enabled via IoT Hub Direct Methods. Each method must be defined in the type definition of the device.

Before your device is able to receive the commands and react to them, you need to setup the command handler. Have a look at the code below:

await deviceClient.SetMethodDefaultHandlerAsync(
  (methodRequest, _) =>
    {
      var commandName = methodRequest.Name;
      var commandBody = methodRequest.DataAsJson;

      //Execute the proper logic connected with the invoked command

      return Task.FromResult(new MethodResponse(new byte[0], 200)); //if your command should return some data, you can include it here
    }, 
  null);

If your device serves as a gateway for other (indirectly connected) devices, it can receive method invocations targeted to those devices via a $proxy method, with body consisting of:​

{
   "objectId": "{GUID}", //- target object identifier
   "method": {/*methodName*/},
   "input": {/*method specific input*/}
}

It is your responsibility to relay the command invocation request and response to/from the target device.

# Commands With File Properties

If a command input contains one or more file data type properties, each property will be transformed to include the details of the blob, and the SAS token, so that your device is able to download the file. The token is not long-lived and will expire after a pre-configured timeout (check out further reading from Microsoft). The file that you give as an input to a command, needs to be uploaded to the Global Storage of the Platform first.

Here is a sample body of a message delivered to the device (via IoT Hub) on command with a file invocation:

{
  "file1": {
   "$file": {
     "sasToken": "read-only sas token for the target blob",
     "hostName": "storage host name",
     "container": "storage container name",
     "blob": "blob name"
    }
  }
}

# File Upload

A device may upload files to the cloud via standard Azure IoT Hub SDK methods. If you are not using an SDK, this page lists the steps to upload a file using REST APIs. In order for the files to be processed by the Platform they must contain the objectId and model of the device that the file belongs to: {objectId}/{model}/{file specific path}.

Here is an example of that:

2b5b05d7-7afd-4d11-888c-d8d161d8e962/abb.ability.device/importantFiles/myNewFile.txt

In the given example it is mandatory the 2b5b05d7-7afd-4d11-888c-d8d161d8e962/abb.ability.device/ part is included.

The 2b5b05d7-7afd-4d11-888c-d8d161d8e962 ID is an objectId and abb.ability.device is a model under which the device uploads the file.

importantFiles/myNewFile.txt is a path to the file that will be created in the cloud blob storage for your device.

For a practical example of uploading of a file from a device to the cloud, have a look at the code below:

var targetFile = $"{objectId}/{model}/{fileNameInTheCloud}";

await using var fileStreamSource = File.Open(filePath, FileMode.Open);
await deviceClient.UploadToBlobAsync(targetFile, fileStreamSource);

As you can see the code is rather simple and the only Ability-specific part is the targetFile format. IoT Hub's SDK handles most of the work for you.

If this device acts as a gateway for other devices (like an Edge), then it must put their files in the folder with the objectIds of those devices, i.e. if device 345 is connected to a device 123, all files that belong to a device 345 must be uploaded in 345 folder, i.e. the targetFile from example above would be 345/abb.ability.device/app.log, 345/abb.ability.device/config.bk, etc. (note that 123 and 345 are just examples, in reality all objectIds are in UUID format).

# Compression

All examples presented above may also make use of compression. This feature allows to decrease the amount of bytes sent through the wire, which is mostly useful when:

  • your device operates in area where Internet connection is not "cheap";
  • your messages have a chance to exceed the IoT Hub message limit (256 kB).

# Summary

In this series of articles you learnt how devices can interact with the Abiltiy Plaform. Note that the examples presented on this page do not show you all of the supported message types. To learn about all of them, go to the Device API section. However, now you should be able to use any of them since the general idea and procedures stay the same.

# References

MSDN article about IoT Hub messages

Last updated: 12/3/2021, 1:27:01 PM
Feedback