The Things Network Client Part2

MQTT connectivity

In a previous post I couldn’t add a TTN V3EndDevice to an application (I’m going try again soon) using the REST API so I figured would try out the MQTT API. My aim was to get notifications of when a Device was created/updated/deleted in an Application.

After some tinkering with the format of MQTT usernames and passwords I can connect to my V3 instance and successfully subscribe to topics. But, currently(Aug 2020) I’m not receiving any messages when I create, update or delete a Device. I have tried different Quality of Service QoS settings etc. and I wonder if my topic names aren’t quite right.

.Net Core MQTT Client

I wanted notifications so I could “automagically” provision a device in an Azure IoT Hub (maybe with a tag indicating it’s an “orphan” so it is discoverable) or in Azure IoT Central when a Device was created in TTN.

This looked like a good approach as my Azure IoT Hub applications have other devices which are not connected via LoRaWAN, and there are many specialised LoRaWAN settings which would need to be validated, stored etc. by my software. (maybe TTN device templates would make this easier). The TTN software is pretty good at managing devices so why would I “re-invent the wheel”.

I built a “nasty” console application using MQTTNet so that I could figure out how to connect to my V3 setup and subscribe to topics.

namespace devMobile.TheThingsNetwork.MqttClient
{
   using System;
   using System.Diagnostics;
   using System.Threading;
   using System.Threading.Tasks;

   using MQTTnet;
   using MQTTnet.Client;
   using MQTTnet.Client.Disconnecting;
   using MQTTnet.Client.Options;
   using MQTTnet.Client.Receiving;
   using MQTTnet.Client.Subscribing;

   class Program
   {
      private static IMqttClient mqttClient = null;
      private static IMqttClientOptions mqttOptions = null;
      private static string server;
      private static string username;
      private static string password;
      private static string clientId;

      static async Task Main(string[] args)
      {
         MqttFactory factory = new MqttFactory();
         mqttClient = factory.CreateMqttClient();

         if (args.Length != 4)
         {
            Console.WriteLine("[MQTT Server] [UserName] [Password] [ClientID]");
            Console.WriteLine("Press <enter> to exit");
            Console.ReadLine();
            return;
         }

         server = args[0];
         username = args[1];
         password = args[2];
         clientId = args[3];

         mqttOptions = new MqttClientOptionsBuilder()
            .WithTcpServer(server)
            .WithCredentials(username, password)
            .WithClientId(clientId)
            .WithTls()
            .Build();

         mqttClient.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(e => MqttClient_Disconnected(e)));
         mqttClient.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(e => MqttClient_ApplicationMessageReceived(e)));
         await mqttClient.ConnectAsync(mqttOptions);

         // Different topics I have tried
         string topic;
         topic = $"v3/{username}/devices/{clientId}/events/update";
         //topic = $"v3/{username}/devices/{clientId}/events/create";
         //topic = $"v3/{username}/devices/{clientId}/events/delete";
         //topic = $"v3/{username}/devices/+/events/+";
         //topic = $"v3/{username}/devices/+/events/create";
         //topic = $"v3/{username}/devices/+/events/update";
         //topic = $"v3/{username}/devices/+/events/delete";
         //topic = $"v3/{username}/devices/+/events/+";

         MqttClientSubscribeResult result;

         // Different QoS I have tried
         //result = await mqttClient.SubscribeAsync(topic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce);
         result = await mqttClient.SubscribeAsync(topic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);
         //result = await mqttClient.SubscribeAsync(topic, MQTTnet.Protocol.MqttQualityOfServiceLevel.ExactlyOnce);

         Console.WriteLine("SubscribeAsync Result");
         foreach ( var resultItem in result.Items)
         {
            Console.WriteLine($"ResultCode:{resultItem.ResultCode} TopicFilter:{resultItem.TopicFilter}");
         }                     

         Console.WriteLine("Press any key to temrminate wait");
         while (!Console.KeyAvailable)
         {
            Console.Write(".");

            Thread.Sleep(30100);
         }

         Console.WriteLine("Press <enter> to exit");
         Console.ReadLine();
         return;
      }

      private static void MqttClient_ApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs e)
      {
         Console.WriteLine($"ClientId:{e.ClientId} Topic:{e.ApplicationMessage.Topic} Payload:{e.ApplicationMessage.ConvertPayloadToString()}");
      }

      private static async void MqttClient_Disconnected(MqttClientDisconnectedEventArgs e)
      {
         Debug.WriteLine("Disconnected");
         await Task.Delay(TimeSpan.FromSeconds(5));

         try
         {
            await mqttClient.ConnectAsync(mqttOptions);
         }
         catch (Exception ex)
         {
            Debug.WriteLine("Reconnect failed {0}", ex.Message);
         }
      }
   }
}

I’m going to post some questions on the TTN forums and Slack community to see if what I’m trying to do is supported/possible.

I got some helpful responses on the TTN forums and it looks like what I want todo is not supported by the V3 stack (Aug2020) and I will have to use gRPC.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.