Wireless field gateway protocol V2

I have now built a couple of nRF2L01P field gateways (for AdaFriut.IO & Azure IoT Hubs) which run as a background tasks on Windows 10 IoT Core on RaspberyPI). I have also written several clients which run on Arduino, devDuino, Netduino, and Seeeduino devices.

I have tried to keep the protocol simple (telemetry only) to deploy and it will be used in high school student projects in the next couple of weeks.

To make the payload smaller the first byte of the message now specifies the message type in the top nibble and the length of the device unique identifier in the bottom nibble.

0 = Echo

The message is displayed by the field gateway as text & hexadecimal.

1 = Device identifier + Comma separated values (CSV) payload

[0] – Set to 0001, XXXX   Device identifier length

[1]..[1+Device identifier length] – Unique device identifier bytes e.g. Mac address

[1+Device identifier length+1 ]..[31] – CSV payload e.g.  SensorID value, SensorID value

 

Microsoft IoT Central dynamic payload desktop client

Unlike most of the Azure IoT Hub client examples the names and number of sensor values will only be known when messages received over the nRF24L01 wireless link are processed so the JSON message payload has to be constructed on the fly.

Using the Newtonsoft.Json NuGet package and Linq + JObject made this much easier than expected so I have added some code improve robustness.

/*

Copyright ® 2018 Jan devMobile Software, All Rights Reserved

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE.

You can do what you want with this code, acknowledgment would be nice.

http://www.devmobile.co.nz

*/
using System;
using System.Text;
using System.Threading;
using Microsoft.Azure.Devices.Client;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace devMobile.IoT.MicrosoftIoTCentral.Desktop.DynamicPayload
{
   class Program
   {
      const string DeviceConnectionString = "YourDeviceConnectionStringFromIoTCentralGoesHere";
      const string TelemetryDataPointPropertyNameFormat = @"{0}-{1}";
      const double temperatureBase = 20.0;
      const double temperatureRange = 10.0;
      const double humidityBase = 70.0;
      const double humidityRange = 20.0;
      const double batteryVoltageBase = 3.00;
      const double batteryVoltageRange = -1.00;
      static readonly TimeSpan feedUpdateDelay = new TimeSpan(0, 0, 15);

      static void Main(string[] args)
      {
         DeviceClient Client = null;

         try
         {
            Console.WriteLine("Connecting to IoI hub");
            Client = DeviceClient.CreateFromConnectionString(DeviceConnectionString, TransportType.Amqp);
            Console.WriteLine(" Connected");
         }
         catch (Exception ex)
         {
            Console.WriteLine("Error connecting or sending data to IoT Central: {0}", ex.Message);
            return;
         }

         while (true)
         {
            // Then send simulated temperature, humidity & battery voltage data
            Random random = new Random();
            double temperature = temperatureBase + random.NextDouble() * temperatureRange;
            double humidity = humidityBase + random.NextDouble() * humidityRange;
            double batteryVoltage = batteryVoltageBase + random.NextDouble() * batteryVoltageRange;

            Console.WriteLine("Temperature {0}°C  Humidity {1}% Battery Voltage {2}V", temperature.ToString("F1"), humidity.ToString("F0"), batteryVoltage.ToString("F2"));

            // Populate the data point -
            JObject telemetryDataPoint = new JObject(); // This could be simplified but for field gateway will use this style

            string sensorDeviceSerialNumber = "0123456789ABCDEF"; // intentionally created and initialised at this level as sensor device will send over NRF24 link

            telemetryDataPoint.Add(string.Format(TelemetryDataPointPropertyNameFormat, sensorDeviceSerialNumber, "T"), temperature.ToString("F1"));
            telemetryDataPoint.Add(string.Format(TelemetryDataPointPropertyNameFormat, sensorDeviceSerialNumber, "H"), humidity.ToString("F0"));
            telemetryDataPoint.Add(string.Format(TelemetryDataPointPropertyNameFormat, sensorDeviceSerialNumber, "V"), batteryVoltage.ToString("F2"));

            string messageString = JsonConvert.SerializeObject(telemetryDataPoint);

            Console.WriteLine("{0:hh:mm:ss} > Sending telemetry: {1}", DateTime.Now, messageString);

            try
            {
               using (Message message = new Message(Encoding.ASCII.GetBytes(messageString)))
               {
                  Client.SendEventAsync(message).Wait();
                  Console.WriteLine(" Sent");
               }
            }
            catch (Exception ex)
            {
               Console.WriteLine("Error sending data to IoT Central: {0}", ex.Message);
            }

            Thread.Sleep(feedUpdateDelay);
         }
      }
   }
}

The application produces very similar output to the basic desktop client

IoTCentralDashboardDynamicPayloadClient

Microsoft IoT Central basic desktop client

One of the replacement Internet of Things services which looked worth evaluating was Microsoft’s IoT Central. My first project was to build the simplest possible desktop client (.Net Core) which simulates a limited number of sensors (sensor names, value formats etc. configured in code) and only sends data to the cloud (no device management, control or provisioning capabilities).

The only required dependencies are the Newtonsoft.Json &  Microsoft.Azure.DevicesClient NuGet packages

/*

Copyright ® 2018 Jan devMobile Software, All Rights Reserved

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE
You can do what you want with this code, acknowledgement would be nice.
http://www.devmobile.co.nz

*/
using System;
using System.Text;
using System.Threading;
using Microsoft.Azure.Devices.Client;
using Newtonsoft.Json;

namespace devMobile.IoT.MicrosoftIoTCentral.Desktop.Basic
{
 class Program
 {
 private const string DeviceConnectionString = "YourDeviceConnectionStringFromIoTCentralGoesHere";
 const double temperatureBase = 20.0;
 const double temperatureRange = 10.0;
 const double humidityBase = 70.0;
 const double humidityRange = 20.0;
 const double batteryVoltageBase = 3.00;
 const double batteryVoltageRange = -1.00;
 static readonly TimeSpan feedUpdateDelay = new TimeSpan(0, 0, 15);

private class TelemetryDataPoint
 {
 [JsonProperty(PropertyName = "H")]
 public double Humidity { get; set; }
 [JsonProperty(PropertyName = "T")]
 public double Temperature { get; set; }
 [JsonProperty(PropertyName = "B")]
 public double BatteryVoltage { get; set; }
 }

static void Main(string[] args)
 {
 DeviceClient Client ;
 Random random = new Random();

try
 {
 Console.WriteLine("Connecting to IoI hub");
 Client = DeviceClient.CreateFromConnectionString(DeviceConnectionString, TransportType.Mqtt);

while (true)
 {
 double temperature = temperatureBase + random.NextDouble() * temperatureRange;
 double humidity = humidityBase + random.NextDouble() * humidityRange;
 double batteryVoltage = batteryVoltageBase + random.NextDouble() * batteryVoltageRange;

Console.WriteLine("Temperature {0}°C Humidity {1}% Battery Voltage {2}V", temperature.ToString("F1"), humidity.ToString("F0"), batteryVoltage.ToString("F2"));

// Populate the data point - this has a static structure and name which could be a problem for field gateway
 TelemetryDataPoint telemetryDataPoint = new TelemetryDataPoint()
 {
 BatteryVoltage = Math.Round(batteryVoltage, 2),
 Humidity = Math.Round(humidity, 0),
 Temperature = Math.Round( temperature, 1 )
 };

string messageString = JsonConvert.SerializeObject(telemetryDataPoint);
 Message message = new Message(Encoding.ASCII.GetBytes(messageString));

Console.WriteLine("{0:hh:mm:ss} > Sending telemetry: {1}", DateTime.Now, messageString);
 Client.SendEventAsync(message).Wait();
 Console.WriteLine(" Done");

Thread.Sleep(feedUpdateDelay);
 }
 }
 catch (Exception ex)
 {
 Console.WriteLine("Error connecting or sending data to IoT Central: {0}", ex.Message);
 Console.WriteLine("Press <ENTER> to exit");
 Console.ReadLine();
 }
 }
 }
}

I manually provisioned the device by copying the device connection string in the IoT Central dashboard

IoTCentralDashboardBasicClient

DesktopClientSimple

Simple dotNet Core 2 IoTCentral Client

A functional client in less than 100 lines of code with support for individual device configuration. For my FieldGateway I’m going to need more flexibility in the construction of telemetry payloads, device provisioning and configuration support.