Wireless field gateway protocol V1

I’m going to build a number of nRF2L01P field gateways (Netduino Ethernet & Wifi running .NetMF, Raspberry PI running Windows 10 IoT Core, RedBearLab 3200  etc.), clients which run on a variety of hardware (Arduino, devDuino, Netduino, Seeeduino etc.) which, then upload data to a selection of IoT Cloud services (AdaFruit.IO, ThingSpeak, Microsoft IoT Central etc.)

The nRF24L01P is widely supported with messages up to 32 bytes long, low power consumption and 250kbps, 1Mbps and 2Mbps data rates.

The aim is to keep the protocol simple (telemetry only initially) to implement and debug as the client side code will be utilised by high school student projects.

The first byte of the message specifies the message type

0 = Echo

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

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

[0] – Set to 1

[1] – Device identifier length

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

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

Overtime I will support more message types and wireless protocols.

 

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.