After a couple of weeks work my The Things Industries(TTI) V3 gateway is in beta testing. For this blog post the client is a GHI Electronics Fezduino with a RAK811 LPWAN Evaluation Board(EVB). My test device was configured in Azure IoT Central by the Device Provisioning Service(DPS) and I then manually migrated the device to each of the four templates used in this post.
The first step was to display the temperature and barometric pressure values from the Seeedstudio Grove BMP180 attached to my sensor node.
The next step was to configure a simple Azure IoT Central command to send to the sensor node. This was a queued request with no payload. An example of this sort of command would be a request for a sensor node to reboot or turn on an actuator.
My integration uses only offline queued commands as often messages won’t be delivered to the sensor node immediately, especially if the sensor node only sends a message every half hour/hour/day. The confirmed flag should be used with care as the Azure IoT Hub messages may expire before a delivery Ack/Nack/Failed is received from the TTI and it consumes downlink bandwidth.
if (message.Properties.ContainsKey("method-name"))
{
}
I determine an Azure IoT Hub message is an Azure IoT Central command by the presence of the “method-name” property. If the Azure IoT Central command does not have a request payload the Azure IoT Hub message payload will contain a single “@” character so the Azure IoT Connector sends a TTI downlink message with an empty raw payload via the TTI Data API(MQTT).
if (payloadText.CompareTo("@") != 0)
{
.
}
else
{
downlink.PayloadRaw = "";
}

To send a downlink message, TTI needs a LoRaWAN port number (plus optional queue, confirmed and priority values) which can’t be provided via the Azure IoT Central command setup so these values are configured in the app.settings file.
Each TTI application has zero or more Azure IoT Central command configurations which supply the port, confirmed, priority and queue settings.
"ProgramSettings": {
"Applications": {
"application1": {
"AzureSettings": {
...
}
},
"MQTTAccessKey": "...",
"DeviceIntegrationDefault": false,
"MethodSettings": {
"Reboot": {
"Port": 21,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
},
}
},
"seeeduinolorawan": {
"AzureSettings": {
}
"MQTTAccessKey": "...",
"DeviceIntegrationDefault": true,
"DevicePageSize": 10
}
},
"TheThingsIndustries": {
...
}
}




The next step was to configure a more complex Azure IoT Central command to send to the sensor node. This was a queued request with a single value payload. An example of this sort of command could be setting the speed of a fan or the maximum temperature of a freezer for an out of band (OOB) notification to be sent.

"ProgramSettings": {
"Applications": {
"application1": {
"AzureSettings": {
...
}
},
"MQTTAccessKey": "...",
"DeviceIntegrationDefault": false,
"MethodSettings": {
"Reboot": {
"Port": 21,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
},
"value_0": {
"Port": 30,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
},
"value_1": {
"Port": 30,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
},
}
},
"seeeduinolorawan": {
"AzureSettings": {
}
"MQTTAccessKey": "...",
"DeviceIntegrationDefault": true,
"DevicePageSize": 10
}
},
"TheThingsIndustries": {
...
}
}
The value_0 settings are for the minimum temperature the value_1 settings are for the maximum temperature value.



The single value command payload contains the textual representation of the value e.g. “true”/”false” or “1.23” which are also valid JSON. This initially caused issues as I was trying to splice a single value into the decoded payload.
I had to add a check that the string started with a “{” and finished with a “}” (a JSON object) or started with a “[” and finished with a “]” (a JSON array) as part of the validation process.
For a single value command the payload decoded has a single property with the method-name value as the name and the payload as the value. For a command with a JSON payload the message payload is copied into the PayloadDecoded.
I normally wouldn’t use exceptions for flow control but I can’t see a better way of doing this.
try
{
// Split over multiple lines to improve readability
if (!(payloadText.StartsWith("{") && payloadText.EndsWith("}"))
&&
(!(payloadText.StartsWith("[") && payloadText.EndsWith("]"))))
{
throw new JsonReaderException();
}
downlink.PayloadDecoded = JToken.Parse(payloadText);
}
catch (JsonReaderException)
{
try
{
JToken value = JToken.Parse(payloadText);
downlink.PayloadDecoded = new JObject(new JProperty(methodName, value));
}
catch (JsonReaderException)
{
downlink.PayloadDecoded = new JObject(new JProperty(methodName, payloadText));
}
}
The final step was to configure an another Azure IoT Central command with a JSON payload to send to the sensor node. A “real-world” example of this sort of command would be setting the minimum and maximum temperatures of a freezer in a single downlink message.

"ProgramSettings": {
"Applications": {
"application1": {
"AzureSettings": {
...
}
},
"MQTTAccessKey": "...",
"DeviceIntegrationDefault": false,
"MethodSettings": {
"Reboot": {
"Port": 21,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
},
"value_0": {
"Port": 30,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
},
"value_1": {
"Port": 30,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
},
"TemperatureOOBAlertMinimumAndMaximum": {
"Port": 30,
"Confirmed": true,
"Priority": "normal",
"Queue": "push"
}
}
},
"seeeduinolorawan": {
"AzureSettings": {
}
"MQTTAccessKey": "...",
"DeviceIntegrationDefault": true,
"DevicePageSize": 10
}
},
"TheThingsIndustries": {
...
}
}

The build in TTI decoder only supports downlink decoded payloads with property names “value_0” through “value_x” which results in some odd command names and JSON payload property names. (Custom encoders may support other property names). Case sensitivity of some configuration values also tripped me up.
Pingback: TTI V3 Gateway Azure IoT Hub Support | devMobile's blog
Pingback: TTI V3 Connector Azure IoT Central Device Provisioning Service(DPS) support | devMobile's blog