Easy Sensors Wireless field gateway Arduino Nano client

After not much development on my nrf24L01 AdaFruit.IO and Azure IOT Hub field gateways for a while some new nRF24L01 devices arrived in the post last week.

This sample client is an Arduino Nano clone with an Arduino Nano radio shield for NRF24L01+.

I use the shield’s onboard SHA204A crypto and authentication chip, and a Seeedstudio Temperature & Humidity sensor with the data uploaded to adafruit.io.

/*
  Copyright ® 2018 September 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

*/
#include
#include
#include 

// RF24 radio( ChipeEnable , ChipSelect )
RF24 radio(9, 10);
const byte FieldGatewayChannel = 15 ;
const byte FieldGatewayAddress[] = {"Base1"};
const rf24_datarate_e RadioDataRate = RF24_250KBPS;
const rf24_pa_dbm_e RadioPALevel = RF24_PA_HIGH;

// Payload configuration
const int PayloadSizeMaximum = 32 ;
char payload[PayloadSizeMaximum] = "";
const byte DeviceIdPlusCsvSensorReadings = 1 ;
const byte SensorReadingSeperator = ',' ;

// ATSHA204 secure authentication, validation with crypto and hashing (only using for unique serial number)
atsha204Class sha204(A3);
const int DeviceSerialNumberLength = 9 ;
uint8_t deviceSerialNumber[DeviceSerialNumberLength] = {""};
const int LoopSleepDelaySeconds = 10 ;

void setup()
{
  Serial.begin(9600);
  Serial.println("Setup called");

  // Retrieve the serial number then display it nicely
  sha204.getSerialNumber(deviceSerialNumber);

  Serial.print("SNo:");
  for (int i = 0; i < sizeof( deviceSerialNumber) ; i++)
  {
    // Add a leading zero
    if ( deviceSerialNumber[i] < 16)
    {
      Serial.print("0");
    }
    Serial.print(deviceSerialNumber[i], HEX);
    Serial.print(" ");
  }

  Serial.println();

  // Configure the Seeedstudio TH02 temperature & humidity sensor
  Serial.println("TH02 setup");
  TH02.begin();
  delay(100);

  // Configure the nRF24 module
  Serial.println("nRF24 setup");
  radio.begin();
  radio.setChannel(FieldGatewayChannel);
  radio.openWritingPipe(FieldGatewayAddress);
  radio.setDataRate(RadioDataRate) ;
  radio.setPALevel(RadioPALevel);
  radio.enableDynamicPayloads();

  Serial.println("Setup done");
}

void loop()
{
  int payloadLength = 0 ;
  float temperature ;
  float humidity ;

  Serial.println("Loop called");
  memset( payload, 0, sizeof( payload));

  // prepare the payload header with PayloadMessageType (top nibble) and DeviceID length (bottom nibble)
  payload[0] = (DeviceIdPlusCsvSensorReadings &lt;<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>&lt; 4) | sizeof(deviceSerialNumber) ;
  payloadLength += 1;

  // Copy the device serial number into the payload
  memcpy( &amp;payload[payloadLength], deviceSerialNumber, sizeof( deviceSerialNumber));
  payloadLength += sizeof( deviceSerialNumber) ;

  // Read the temperature, humidity &amp; battery voltage values then display nicely
  temperature = TH02.ReadTemperature();
  Serial.print(&quot;T:&quot;);
  Serial.print( temperature, 1 ) ;
  Serial.print( &quot;C&quot; ) ;

  humidity = TH02.ReadHumidity();
  Serial.print(&quot; H:&quot;);
  Serial.print( humidity, 0 ) ;
  Serial.println( &quot;%&quot; ) ;

  // Copy the temperature into the payload
  payload[ payloadLength] = &#039;t&#039;;
  payloadLength += 1 ;
  dtostrf(temperature, 6, 1, &amp;payload[payloadLength]);
  payloadLength += 6;

  payload[ payloadLength] = &#039;,&#039;;
  payloadLength += 1 ;

  // Copy the humidity into the payload
  payload[ payloadLength] = &#039;h&#039;;
  payloadLength += 1 ;
  dtostrf(humidity, 4, 0, &amp;payload[payloadLength]);
  payloadLength += 4;

  // Powerup the nRF24 chipset then send the payload to base station
  Serial.print( &quot;Payload length:&quot;);
  Serial.println( payloadLength );

  Serial.println( &quot;nRF24 write&quot; ) ;
  boolean result = radio.write(payload, payloadLength);
  if (result)
    Serial.println(&quot;Write Ok...&quot;);
  else
    Serial.println(&quot;Write failed.&quot;);

  Serial.println(&quot;Loop done&quot;);
  delay(LoopSleepDelaySeconds * 1000l);
}

Arduino monitor output

NanoArduinoNrf24

Prototype hardware

ArduinoNanonRF24

Bill of materials (prices as at Sep 2018)

  • Arduino Nano clone USD4.70
  • Easy Sensors Arduino Nano Radio Shield for nRF24L01 USD13
  • Seeedstudio Temperature and Humidity Sensor Pro USD11.50
  • Seeedstudio 4 pin Male Jumper to Grove 4 pin Conversion Cable USD2.90

Adafruit.IO temperature display when I moved the sensor outside.

NanoNrf24

Adafruit Feather M0 RFM95 LoRa Radio Payload Addressing Client

This is a demo AdaFruit Feather Mo0 Radio with LoRa Radio Module client (based on one of the examples from Arduino-LoRa) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

The Adafruit learn site had sample code based on the RadioHead library which was useful. This device supports 868MHz & 915Mz, there is are other Arduino 32u4 and 433MHz devices available.

Bill of materials (Prices Sep 2018)

  • Adafruit Feather M0 RFM95 LoRa Radio (433 or 900 MHz) USD34.95
  • Seeedstudio Temperature and Humidity Sensor Pro USD11.50
  • Seeedstudio 4 pin Male Jumper to Grove 4 pin Conversion Cable USD2.90

The code is pretty basic, it reads a value from the Seeedstudio temperature and humidity sensor, then packs the payload and sets the necessary RFM9X/SX127X LoRa module configuration, has no power conservation, advanced wireless configuration etc. I had to add an extra include from the dstrtof function

AdaFruitM0Feather

/*
  Adapted from LoRa Duplex communication with Sync Word

  Sends temperature & humidity data from Seeedstudio 

  https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-High-Accuracy-Min-p-1921.html

  To my Windows 10 IoT Core RFM 9X library

  https://blog.devmobile.co.nz/2018/09/03/rfm9x-iotcore-payload-addressing/

*/
#include
#include
#include
#include
const int csPin = 8;          // LoRa radio chip select
const int resetPin = 4;       // LoRa radio reset
const int irqPin = 3;         // change for your board; must be a hardware interrupt pin

// Field gateway configuration
const char FieldGatewayAddress[] = "LoRaIoT1";
const float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

// Payload configuration
const int PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum] = "";
const byte SensorReadingSeperator = ',' ;

// Manual serial number configuration
const char DeviceId[] = {"AdafruitM0"};

const int LoopSleepDelaySeconds = 10 ;

void setup() {
  Serial.begin(9600);
  //while (!Serial);

  Serial.println("LoRa Setup");

  // override the default CS, reset, and IRQ pins (optional)
  LoRa.setPins(csPin, resetPin, irqPin);// set CS, reset, IRQ pin

  if (!LoRa.begin(FieldGatewayFrequency))
  {
    Serial.println("LoRa init failed. Check your connections.");
    while (true);
  }

  // Need to do this so field gateways pays attention to messages from this device
  LoRa.enableCrc();
  LoRa.setSyncWord(FieldGatewaySyncWord);  

  //LoRa.dumpRegisters(Serial);
  Serial.println("LoRa Setup done.");

  // Configure the Seeedstudio TH02 temperature & humidity sensor
  Serial.println("TH02 setup");
  TH02.begin();
  delay(100);
  Serial.println("TH02 Setup done");  

  Serial.println("Setup done");
}

void loop()
{
  int payloadLength = 0 ;
  float temperature ;
  float humidity ;

  Serial.println("Loop called");
  memset(payload, 0, sizeof(payload));

  // prepare the payload header with "To" Address length (top nibble) and "From" address length (bottom nibble)
  payload[0] = (strlen(FieldGatewayAddress) << 4) | strlen( DeviceId ) ;
  payloadLength += 1;

  // Copy the "To" address into payload
  memcpy(&payload[payloadLength], FieldGatewayAddress, strlen(FieldGatewayAddress));
  payloadLength += strlen(FieldGatewayAddress) ;

  // Copy the "From" into payload
  memcpy(&payload[payloadLength], DeviceId, strlen(DeviceId));
  payloadLength += strlen(DeviceId) ;

  // Read the temperature and humidity values then display nicely
  temperature = TH02.ReadTemperature();
  humidity = TH02.ReadHumidity();

  Serial.print("T:");
  Serial.print( temperature, 1 ) ;
  Serial.print( "C" ) ;

  Serial.print(" H:");
  Serial.print( humidity, 0 ) ;
  Serial.println( "%" ) ;

  // Copy the temperature into the payload
  payload[ payloadLength] = 't';
  payloadLength += 1 ;
  payload[ payloadLength] = ' ';
  payloadLength += 1 ;
  payloadLength += strlen( dtostrf(temperature, -1, 1, (char*)&payload[payloadLength]));
  payload[ payloadLength] = SensorReadingSeperator;
  payloadLength += sizeof(SensorReadingSeperator) ;

  // Copy the humidity into the payload
  payload[ payloadLength] = 'h';
  payloadLength += 1 ;
  payload[ payloadLength] = ' ';
  payloadLength += 1 ;
  payloadLength += strlen( dtostrf(humidity, -1, 0, (char *)&payload[payloadLength]));  

  // display info about payload then send it (No ACK) with LoRa unlike nRF24L01
  Serial.print( "RFM9X/SX127X Payload length:");
  Serial.print( payloadLength );
  Serial.println( " bytes" );

  LoRa.beginPacket();
  LoRa.write( payload, payloadLength );
  LoRa.endPacket();      

  Serial.println("Loop done");

  delay(LoopSleepDelaySeconds * 1000l);
}

In the Arduino debug monitor the messages from the device looked like this

Loop done
Loop called
T:17.5C H:94%
RFM9X/SX127X Payload length:30 bytes
Loop done
Loop called
T:17.5C H:95%
RFM9X/SX127X Payload length:30 bytes
Loop done
Loop called
T:17.6C H:95%
RFM9X/SX127X Payload length:30 bytes
Loop done

In the debug output window the messages from the device looked like this

The thread 0xf9c has exited with code 0 (0x0).
The thread 0x8ac has exited with code 0 (0x0).
09:53:53-RX From AdafruitM0 PacketSnr 10.3 Packet RSSI -51dBm RSSI -103dBm = 11 byte message "t 17.8,h 93"
Sensor AdafruitM0t Value 17.8
Sensor AdafruitM0h Value 93
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x704 has exited with code 0 (0x0).
The thread 0xad4 has exited with code 0 (0x0).
09:54:03-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -52dBm RSSI -101dBm = 11 byte message "t 17.8,h 93"
Sensor AdafruitM0t Value 17.8
Sensor AdafruitM0h Value 93
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x1084 has exited with code 0 (0x0).
The thread 0xa2c has exited with code 0 (0x0).
09:54:14-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -54dBm RSSI -102dBm = 11 byte message "t 17.7,h 93"
Sensor AdafruitM0t Value 17.7
Sensor AdafruitM0h Value 93
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x1720 has exited with code 0 (0x0).

10:00:06-RX From AdafruitM0 PacketSnr 9.3 Packet RSSI -52dBm RSSI -100dBm = 12 byte message "t 184.0,h 91"
Sensor AdafruitM0t Value 184.0
Sensor AdafruitM0h Value 91
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x15d4 has exited with code 0 (0x0).
The thread 0x15f4 has exited with code 0 (0x0).
10:00:19-RX From AdafruitM0 PacketSnr 10.0 Packet RSSI -48dBm RSSI -102dBm = 12 byte message "t 180.9,h 94"
Sensor AdafruitM0t Value 180.9
Sensor AdafruitM0h Value 94
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0xabc has exited with code 0 (0x0).
The thread 0x2e4 has exited with code 0 (0x0).
10:00:29-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -49dBm RSSI -102dBm = 12 byte message "t -50.0,h 94"
Sensor AdafruitM0t Value -50.0
Sensor AdafruitM0h Value 94
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x950 has exited with code 0 (0x0).
The thread 0x145c has exited with code 0 (0x0).
The thread 0x176c has exited with code 0 (0x0).
10:00:39-RX From AdafruitM0 PacketSnr 9.5 Packet RSSI -50dBm RSSI -102dBm = 11 byte message "t 17.5,h 94"
Sensor AdafruitM0t Value 17.5
Sensor AdafruitM0h Value 94
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x165c has exited with code 0 (0x0).
The thread 0x6e8 has exited with code 0 (0x0).
10:00:49-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -59dBm RSSI -100dBm = 11 byte message "t 17.5,h 95"
Sensor AdafruitM0t Value 17.5
Sensor AdafruitM0h Value 95
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x1334 has exited with code 0 (0x0).
The thread 0x14d0 has exited with code 0 (0x0).
10:00:59-RX From AdafruitM0 PacketSnr 9.5 Packet RSSI -66dBm RSSI -102dBm = 11 byte message "t 17.6,h 95"
Sensor AdafruitM0t Value 17.6
Sensor AdafruitM0h Value 95
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish

Then at my Azure IoT hub the data stream looked like this
AdaFruitM0LoRaFeatherIoTHub

To reduce power consumption I would disconnect/remove the light emitting diode(LED)

AdaFruit.IO LoRa Windows 10 IoT Core Field Gateway

This project is now live on github.com, sample Arduino with Dragino LoRa Shield for Arduino, MakerFabs Maduino, Dragino LoRa Mini Dev, M2M Low power Node and Netduino with Elecrow LoRa RFM95 Shield clients uploaded in the next couple of days.

AdaFruit.IO.LoRaScreenShot
While building this AdaFruit.IO LoRa field gateway, and sample clients I revisited my RFM9XLoRa-Net library a couple of times adding functionality and renaming constants to make it more consistent. I made many of the default values public so they could be used in the field gateway config file.
The bare minimum configuration is

{
“AdaFruitIOUserName”: “——“,
“AdaFruitIOApiKey”: “——“,
“AdaFruitIOGroupName”: “——”
“Address”: “——“,
“Frequency”: 915000000.0
}

So far battery life and wireless communications range for the Arduino clients is looking pretty good.

ArduinoUnoR3DraginoLoRa

Wireless field gateway devDuino client V1

This client is a devDuino V2.2 device with an AdaFruit AM2315 temperature & humidity sensor. This sensor is powered by two AAA batteries and has an on-board support for unique device identification and encryption.

In this first iteration the focus was accessing the SHA204A crypto and authentication chip, the AM2315 sensor and message payload assembly. Reducing the power consumption, improving reliability etc. will be covered in future posts.

/*
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

*/
#include <RF24.h>
#include <Adafruit_AM2315.h>
#include <sha204_library.h>

// nRF24L01 ISM wireless module setup
RF24 radio(7,6);
const int nRFPayloadSize = 32 ;
char payload[nRFPayloadSize] = "";
const byte FieldGatewayAddress[5] = "Base1";
const byte FieldGatewayChannel = 10 ;
const rf24_pa_dbm_e RadioPALevel = RF24_PA_MAX;
const rf24_datarate_e RadioDataRate = RF24_250KBPS; 

// ATSHA204 secure authentication, validation with crypto and hashing (initially only used for unique serial number)
atsha204Class sha204(A2);
const int SerialNumberLength = 9 ;
uint8_t serialNumber[SerialNumberLength];

// AM2315 I2C Outdoors temperature and humdity sensor
Adafruit_AM2315 am2315;

const int LoopSleepDelay = 30000 ;

void setup()
{
  Serial.begin(9600);
  Serial.println("Setup called");

  // Retrieve the serial number then display it nicely
  sha204.getSerialNumber(serialNumber);

  Serial.print("SNo:");
  for (int i=0; i<SerialNumberLength; i++)
  {
    // Add a leading zero
    if ( serialNumber[i] < 16)
    {
      Serial.print("0");
    }
    Serial.print(serialNumber[i], HEX);
    Serial.print(" ");
  }
  Serial.println(); 

  // Configure the AM2315 temperature & humidity sensor
  Serial.println("AM2315 setup");
  am2315.begin();

  // Configure the nRF24 module
  Serial.println("nRF24 setup");
  radio.begin();
  radio.setPALevel(RadioPALevel);
  radio.setDataRate(RadioDataRate) ;
  radio.setChannel(FieldGatewayChannel);
  radio.enableDynamicPayloads();
  radio.openWritingPipe(FieldGatewayAddress);

  delay(1000);

  Serial.println("Setup done");
}

void loop()
{
  float temperature ;
  float humidity ;
  float batteryVoltage ;

  Serial.println("Loop called");
  memset( payload, 0, sizeof( payload));

  // prepare the payload header
  int payloadLength = 0 ;
  payload[0] = 1 ; // Sensor device unique ID header with CSV payload
  payloadLength += 1;

  // Copy the ATSHA204 device serial number into the payload
  payload[1] = SerialNumberLength ;
  payloadLength += 1;
  memcpy( &payload[payloadLength], serialNumber, SerialNumberLength);
  payloadLength += SerialNumberLength ;

  // Read the temperature, humidity & battery voltage values then display nicely
  am2315.readTemperatureAndHumidity(temperature, humidity);
  Serial.print("T:");
  Serial.print( temperature, 1 ) ;
  Serial.print( "C" ) ;

  Serial.print(" H:");
  Serial.print( humidity, 0 ) ;
  Serial.print( "%" ) ;

  batteryVoltage = readVcc() / 1000.0 ;
  Serial.print(" B:");
  Serial.print( batteryVoltage, 2 ) ;
  Serial.println( "V" ) ;

  // Copy the temperature into the payload
  payload[ payloadLength] = 'T';
  payloadLength += 1 ;
  dtostrf(temperature, 6, 1, &payload[payloadLength]);
  payloadLength += 6;
  payload[ payloadLength] = ',';
  payloadLength += 1 ;

  // Copy the humidity into the payload
  payload[ payloadLength] = 'H';
  payloadLength += 1 ;
  dtostrf(humidity, 4, 0, &payload[payloadLength]);
  payloadLength += 4;
  payload[ payloadLength] = ',';
  payloadLength += 1 ;

  // Copy the battery voltage into the payload
  payload[ payloadLength] = 'V';
  payloadLength += 1 ;

  dtostrf(batteryVoltage, 5, 2, &payload[payloadLength]);
  payloadLength += 5;

  // Powerup the nRF24 chipset then send the payload to base station
  Serial.print( "Payload length:");
  Serial.println( payloadLength );

  radio.powerUp();
  delay(500);

  Serial.println( "nRF24 write" ) ;
  boolean result = radio.write(payload, payloadLength);
  if (result)
    Serial.println("Write Ok...");
  else
    Serial.println("Write failed.");

 Serial.println( "nRF24 power down" ) ;
 radio.powerDown();

 delay(LoopSleepDelay);
}

Arduino monitor output

devDuinoAM2315V1Output

Prototype hardware

devDuinoAM2315V1Bill of materials (prices as at Jan 2018)

  • devDuino V2.2 USD18
  • AdaFruit AM2315 USD30
  • Grove – 5cm buckled cable USD1.90
  • Grove – Screw Terminal USD2.90
  • 10K resistors x 2

RaspberyPI UWP application diagnostic output

Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
09:39:03 Address 01-23-32-66-C6-FE-0B-8D-EE Length 9 Payload T  25.0,H  48,V 3.31 Length 20
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-T Value 25.0
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-H Value 48
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-V Value 3.31
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
09:39:33 Address 01-23-32-66-C6-FE-0B-8D-EE Length 9 Payload T  24.9,H  48,V 3.30 Length 20
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-T Value 24.9
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-H Value 48
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-V Value 3.30
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
09:40:04 Address 01-23-32-66-C6-FE-0B-8D-EE Length 9 Payload T  24.9,H  48,V 3.31 Length 20
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-T Value 24.9
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-H Value 48
 Sensor 01-23-32-66-C6-FE-0B-8D-EE-V Value 3.31
Interrupt Triggered: RisingEdge

AdaFruit IO Swagger based desktop HTTP client

Manually building clients for complex RESTful APIs (like AdaFruit.IO) can be a bit tedious so I figured I would try generating a C# http client from the Swagger OpenAPI specification(OAS) metadata.

My initial attempts using the Swagger Editor and NSwag on the AdaFruit.IO public API description didn’t go so well. (for more info see this AdaFruit.IO support forum thread) You may need to manually modify the type of the id field in Data & DataResponse, plus possibly other responses.

After figuring out how to set the API key, my code which uploads simulates three individual feeds and one feed group appears to work reliably.

/*

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.

http://www.devmobile.co.nz

 */
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using AdaFruit.IO;

namespace AdaFruit.IO
{
   public partial class Client
   {
      string adaFruitIOApiKey = "yourAPIKey";

      partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url)
      {
         client.DefaultRequestHeaders.Add("X-AIO-Key", adaFruitIOApiKey);
      }
   }
}

namespace devMobile.IoT.Adafruit.IO.Desktop
{
   class Program
   {
      static void Main(string[] args)
      {
         string userName = "YourUserName"; // This is mixed case & case sensitive
         // The feed group and feed key are forced to lower case by UI
         const string feedGroup = "devduinov2-dot-2";
         const string temperatureKey = "t";
         const double temperatureBase = 20.0;
         const double temperatureRange = 10.0;
         const string humidityKey = "h";
         const double humidityBase = 70.0;
         const double humidityRange = 20.0;
         const string batteryVoltageKey = "v";
         const double batteryVoltageBase = 3.00;
         const double batteryVoltageRange = -1.00;
         TimeSpan feedUpdateDelay = new TimeSpan(0, 0, 15);
         TimeSpan groupUpdateDelay = new TimeSpan(0, 0, 30);
         Random random = new Random();

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

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

            // First Update the 3 feeds individually
            // Temperature
            Datum temperatureDatum = new Datum()
            {
               Value = temperature.ToString("F1"),
            };
            client.CreateDataAsync(userName, temperatureKey, temperatureDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Humidity
            Datum humidityDatum = new Datum()
            {
               Value = humidity.ToString("F0"),
            };
            client.CreateDataAsync(userName, humidityKey, humidityDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Battery
            Datum batteryDatum = new Datum()
            {
               Value = batteryVoltage.ToString("F2"),
            };
            client.CreateDataAsync(userName, batteryVoltageKey, batteryDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Then update a feed in a group
            Group_feed_data devDuinoData = new Group_feed_data();

            devDuinoData.Feeds.Add(new Anonymous2() { Key = temperatureKey, Value = temperature.ToString("F1")});
            devDuinoData.Feeds.Add(new Anonymous2() { Key = humidityKey, Value = humidity.ToString("F0")});
            devDuinoData.Feeds.Add(new Anonymous2() { Key = batteryVoltageKey, Value = batteryVoltage.ToString("F2")});

            client.CreateGroupDataAsync(userName, feedGroup, devDuinoData).Wait();
            Task.Delay(groupUpdateDelay).Wait();
         }
      }
   }
}

AdaFruit IO basic desktop HTTP client

AdaFruit IO meets my basic criteria as it has support for HTTP/S clients (it also has an MQTT interface which I will look at in a future post) and the API is well documented.

My first Proof of Concept (PoC) was to build a desktop client which used the HttpWebRequest (for ease of porting to NetMF) classes to upload data.

The program uploaded one of three simulated values to AdaFruit.IO every 10 seconds.

I found the username, group, and feed keys to be case sensitive so pay close attention to the values displayed in the webby UI or copy n paste.

program.cs

 class Program
   {
      static void Main(string[] args)
      {
         string adaFruitIOApiBaseUrl = "https://IO.adafruit.com/api/v2/";
         string adaFruitIOUserName = "YourUserName"; // This is mixed case & case sensitive
         string adaFruitIOApiKey = "YourAPIKey";
         // The feed group and feed key are forced to lower case by UI
         const string feedGroup = "";
         //const string feedGroup = "devduinov2-dot-2";
         const string temperatureKey = "t";
         const double temperatureBase = 20.0;
         const double temperatureRange = 10.0;
         const string humidityKey = "h";
         const double humidityBase = 70.0;
         const double humidityRange = 20.0;
         const string batteryVoltageKey = "v";
         const double batteryVoltageBase = 3.00;
         const double batteryVoltageRange = -1.00;
         TimeSpan dataUpdateDelay = new TimeSpan(0, 0, 10);
         Random random = new Random();

         while (true)
         {
            double temperature = temperatureBase + random.NextDouble() * temperatureRange;
            Console.WriteLine("Temperature {0}°C", temperature.ToString("F1"));
            AdaFruitIoFeedUpdate(adaFruitIOApiBaseUrl, adaFruitIOUserName, adaFruitIOApiKey, feedGroup, temperatureKey, temperature.ToString("F1"));

            Thread.Sleep(dataUpdateDelay);

            double humidity = humidityBase + random.NextDouble() * humidityRange;
            Console.WriteLine("Humidity {0}%", humidity.ToString("F0"));
            AdaFruitIoFeedUpdate(adaFruitIOApiBaseUrl, adaFruitIOUserName, adaFruitIOApiKey, feedGroup, humidityKey, humidity.ToString("F0"));

            Thread.Sleep(dataUpdateDelay);

            double batteryVoltage = batteryVoltageBase + random.NextDouble() * batteryVoltageRange;
            Console.WriteLine("Battery voltage {0}V", batteryVoltage.ToString("F2"));
            AdaFruitIoFeedUpdate(adaFruitIOApiBaseUrl, adaFruitIOUserName, adaFruitIOApiKey, feedGroup, batteryVoltageKey, batteryVoltage.ToString("F2"));

            Thread.Sleep(dataUpdateDelay);
         }
      }

client.cs

      public void AdaFruitIoFeedUpdate(string apiBaseUrl, string userName, string apiKey, string group, string feedKey, string value, int httpRequestTimeoutmSec = 2500, int httpRequestReadWriteTimeoutmSec = 5000)
      {
         string feedUrl;

         if (group.Trim() == string.Empty)
         {
            feedUrl = apiBaseUrl + userName + @"/feeds/" + feedKey + @"/data";
         }
         else
         {
            feedUrl = apiBaseUrl + userName + @"/feeds/" + group.Trim() + "." + feedKey + @"/data";
         }

         Console.WriteLine(" Feed URL :{0}", feedUrl);

         try
         {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(feedUrl);
            {
               string payload = @"{""value"": """ + value + @"""}";

               byte[] buffer = Encoding.UTF8.GetBytes(payload);

               DateTime httpRequestedStartedAtUtc = DateTime.UtcNow;

               request.Method = "POST";
               request.ContentLength = buffer.Length;
               request.ContentType = @"application/json";
               request.Headers.Add("X-AIO-Key", apiKey);
               request.KeepAlive = false;
               request.Timeout = httpRequestTimeoutmSec;
               request.ReadWriteTimeout = httpRequestReadWriteTimeoutmSec;

               using (Stream stream = request.GetRequestStream())
               {
                  stream.Write(buffer, 0, buffer.Length);
               }

               using (var response = (HttpWebResponse)request.GetResponse())
               {
                  Console.WriteLine(" Status: " + response.StatusCode + " : " + response.StatusDescription);
               }

               TimeSpan duration = DateTime.UtcNow - httpRequestedStartedAtUtc;
               Console.WriteLine(" Duration: " + duration.ToString());
            }
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
            throw;
         }
      }
   }

This approach seemed to work pretty reliably

DesktopHTTPRequest

nRF24L01 Raspberry PI Gateway Hardware

For those who came to my MS Ignite AU Intelligent Cloud booth session

Building Wireless Field Gateways

Connecting wireless sensor nodes to the cloud is not the mission it used to be, because the Azure team (and many OS projects) have developed tooling which can help hobbyist and professional developers build solutions. How could you build a home scale robust, reliable and secure solution with off the shelf kit without blowing the budget?

Sparkfun nRF24L01 module &Adafruit perma proto hat

NRF24L01 Raspberry PI DIY Gateway Hardware

BoM (all prices as at Feb 2016)

You will also need some short lengths of wire and a soldering iron.

For those who want an “off the shelf” solution (still requires a minor modification for interrupt support) I have used the Raspberry Pi to NRF24l01+ Shield USD9.90

2015-09-25t072754-447z-20150925_091942-855x570_q85_pad_rcrop

Instructions for modifications and software to follow.