Basic Commands
I have been struggling with making The Things Network(TTN) and The Things Industries(TTI) uplink/downlink messages work well Azure IoT Central. To explore different messaging approaches I have built a proof of Concept(PoC) application which simulates TTN/TTI connectivity to an Azure IoT Hub, or Azure IoT Central.
This blog post is about queued and non queued Cloud to Device(C2D) commands without request or response parameters. I have mostly used non queued commands in other projects (my Azure IoT Hub LoRa and RF24L01 gateways) to “Restart” devices etc..
The first step was to create an Azure IoT Central Device Template with command and telemetry device capabilities.

I then “migrated” my second preconfigured device to the CommandBasic template.

I then went back and created a Template view to visualise the telemetry from my console application and initiate commands.

I modified the PoC application adding handlers for Methods (SetMethodDefaultHandlerAsync) and Messages (SetReceiveMessageHandlerAsync).
private static async Task ApplicationCore(CommandLineOptions options)
{
DeviceClient azureIoTHubClient;
Timer MessageSender;
try
{
// Open up the connection
azureIoTHubClient = DeviceClient.CreateFromConnectionString(options.AzureIoTHubconnectionString, TransportType.Amqp_Tcp_Only);
await azureIoTHubClient.OpenAsync();
await azureIoTHubClient.SetReceiveMessageHandlerAsync(ReceiveMessageHandler, azureIoTHubClient);
await azureIoTHubClient.SetMethodHandlerAsync("Named", MethodCallbackNamedHandler, null);
await azureIoTHubClient.SetMethodDefaultHandlerAsync(MethodCallbackDefaultHandler, null);
MessageSender = new Timer(TimerCallbackAsync, azureIoTHubClient, new TimeSpan(0, 0, 10), new TimeSpan(0, 2, 0));
Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
await Task.Delay(100);
}
}
catch (Exception ex)
{
Console.WriteLine($"Main {ex.Message}");
Console.WriteLine("Press <enter> to exit");
Console.ReadLine();
}
}
The method handler displays the method name and the message payload.
private static async Task<MethodResponse> MethodCallbackDefaultHandler(MethodRequest methodRequest, object userContext)
{
Console.WriteLine($"Default handler method {methodRequest.Name} was called.");
Console.WriteLine($"Payload:{methodRequest.DataAsJson}");
Console.WriteLine();
//return new MethodResponse(400);
//return new MethodResponse(404);
return new MethodResponse(200);
}
The message handler displays a selection the message properties, any attributes and the message payload.
private async static Task ReceiveMessageHandler(Message message, object userContext)
{
DeviceClient azureIoTHubClient = (DeviceClient)userContext;
Console.WriteLine($"ReceiveMessageHandler handler method was called.");
Console.WriteLine($" Message ID:{message.MessageId}");
Console.WriteLine($" Message Schema:{message.MessageSchema}");
Console.WriteLine($" Correlation ID:{message.CorrelationId}");
Console.WriteLine($" Component name:{message.ComponentName}");
Console.WriteLine($" To:{message.To}");
Console.WriteLine($" Module ID:{message.ConnectionModuleId}");
Console.WriteLine($" Device ID:{message.ConnectionDeviceId}");
Console.WriteLine($" CreatedAt:{message.CreationTimeUtc}");
Console.WriteLine($" EnqueuedAt:{message.EnqueuedTimeUtc}");
Console.WriteLine($" ExpiresAt:{message.ExpiryTimeUtc}");
Console.WriteLine($" Delivery count:{message.DeliveryCount}");
Console.WriteLine($" InputName:{message.InputName}");
Console.WriteLine($" SequenceNumber:{message.SequenceNumber}");
foreach (var property in message.Properties)
{
Console.WriteLine($" Key:{property.Key} Value:{property.Value}");
}
Console.WriteLine($" Content encoding:{message.ContentEncoding}");
Console.WriteLine($" Content type:{message.ContentType}");
Console.WriteLine($" Content:{Encoding.UTF8.GetString(message.GetBytes())}");
Console.WriteLine();
//await azureIoTHubClient.AbandonAsync(message); // message retries
await azureIoTHubClient.CompleteAsync(message);
//await azureIoTHubClient.RejectAsync(message); // message gone no retry
}
From the Device Commands tab I can could non queued and a queued commands

When I sent a non-queued command the default method handler was invoked with the name of the command capability (Digital_Output_0) as the method name and an empty payload. In the Azure IoT Central interface I couldn’t see any difference for successful (HTTP 200 OK) or failure (HTTP 400 Bad Request or HTTP 404 Not Found) responses. If the application was not running the command failed immediately.

With Azure IoT Explorer failure responses were visible.

When I sent a queued command the message handler was invoked with the name of the command capability(Digital_Output_1) in a message property called “method-name” and the payload contained only an “@” character.


If the application was not running the command was queued until the Console application was started. When the console application was running and AbandonAsync was called rather than CompleteAsync the message was retried 10 times. If RejectAsync was called rather than CompleteAsync the message was deleted from the queue and not retried. There didn’t appear to be any difference for the displayed Azure IoT Central or Azure IoT Hub explorer results when AbandonAsync or RejectAsync were called.

I also created a personal dashboard to visualise the telemetry data and initiate commands. The way the two commands were presented on the dashboard was quite limited so I will go back to the documentation and see what I missed
Pingback: Azure IoT Central Connectivity Part3 | devMobile's blog
Pingback: Azure IoT Central Connectivity Part4 | devMobile's blog
Pingback: TTI V3 Connector Azure IoT Central Cloud to Device(C2D) | devMobile's blog