Wireless field gateway Netduino client V2

This revised client is a Netduino V2Plus/V3 Ethernet/V3 Wifi device with a Silicon Labs SI7005 temperature & humidity sensor. These devices when used as sensor nodes can be battery powered and I use the Mac Address as the unique device identifier.

In this version of the protocol the message type & device identifier are nibbles packed into the first bye of the message. This saved a byte but limits the number of message types and device identifier length

//---------------------------------------------------------------------------------
// Copyright (c) 2018, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//---------------------------------------------------------------------------------
using System;
using System.Net;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Net.NetworkInformation;
using devMobile.NetMF.Sensor;
using Gralin.NETMF.Nordic;
using SecretLabs.NETMF.Hardware.Netduino;

namespace devMobile.IoT.FIeldGateway.Netduino.Client
{
   class Client
   {
      private const byte nRF24Channel = 10;
      private const NRFDataRate nRF24DataRate = NRFDataRate.DR250kbps;
      private readonly byte[] nRF24ClientAddress = Encoding.UTF8.GetBytes("T&H01");
      private readonly byte[] nRF24BaseStationAddress = Encoding.UTF8.GetBytes("Base1");
      private static byte[] deviceIdentifier;
      private readonly OutputPort led = new OutputPort(Pins.ONBOARD_LED, false);
      private readonly NRF24L01Plus radio;
      private readonly SiliconLabsSI7005 sensor = new SiliconLabsSI7005();

      public Client()
      {
         radio = new NRF24L01Plus();
      }

      public void Run()
      {
         // Configure the nRF24 hardware
         radio.OnDataReceived += OnReceive;
         radio.OnTransmitFailed += OnSendFailure;
         radio.OnTransmitSuccess += OnSendSuccess;

         radio.Initialize(SPI.SPI_module.SPI1, Pins.GPIO_PIN_D7, Pins.GPIO_PIN_D3, Pins.GPIO_PIN_D2);
         radio.Configure(nRF24ClientAddress, nRF24Channel, nRF24DataRate);
         radio.Enable();

         // Setup the device unique identifer, in this case the hardware MacAddress
         deviceIdentifier = NetworkInterface.GetAllNetworkInterfaces()[0].PhysicalAddress;
         Debug.Print(" Device Identifier : " + BytesToHexString(deviceIdentifier));

         Timer humidityAndtemperatureUpdates = new Timer(HumidityAndTemperatureTimerProc, null, 15000, 15000);

         Thread.Sleep(Timeout.Infinite);
      }

          private void HumidityAndTemperatureTimerProc(object state)
      {
         led.Write(true);

         double humidity = sensor.Humidity();
         double temperature = sensor.Temperature();

         Debug.Print("H:" + humidity.ToString("F1") + " T:" + temperature.ToString("F1"));
         string values = "T " + temperature.ToString("F1") + ",H " + humidity.ToString("F0");

         // Stuff the single byte header ( payload type nibble & deviceIdentifierLength nibble ) + deviceIdentifier into first byte of payload
         byte[] payload = new byte[ 1 + deviceIdentifier.Length + values.Length];
         payload[0] =  (byte)((1 << 4) | deviceIdentifier.Length );
         Array.Copy(deviceIdentifier, 0, payload, 1, deviceIdentifier.Length);

         Encoding.UTF8.GetBytes(values, 0, values.Length, payload, deviceIdentifier.Length + 1 );

         radio.SendTo(nRF24BaseStationAddress, payload );
      }

      
      private void OnSendSuccess()
      {
         led.Write(false);

         Debug.Print("Send Success!");
      }


      private void OnSendFailure()
      {
         Debug.Print("Send failed!");
      }


      private void OnReceive(byte[] data)
      {
         led.Write(!led.Read());

         string message = new String(Encoding.UTF8.GetChars(data));

         Debug.Print("Receive " + message); ;
      }
      
      
      private static string BytesToHexString(byte[] bytes)
      {
         string hexString = string.Empty;

         // Create a character array for hexidecimal conversion.
         const string hexChars = "0123456789ABCDEF";

         // Loop through the bytes.
         for (byte b = 0; b  0)
               hexString += "-";

            // Grab the top 4 bits and append the hex equivalent to the return string.        
            hexString += hexChars[bytes[b] >> 4];

            // Mask off the upper 4 bits to get the rest of it.
            hexString += hexChars[bytes[b] & 0x0F];
         }

         return hexString;
      }
   }
}

Bill of materials (prices as at March 2018)

Wireless field gateway Netduino client V1

This client is a Netduino V2Plus/V3 Ethernet/V3 Wifi device with a Silicon Labs SI7005 temperature & humidity sensor. These devices when used as sensor nodes can be battery powered and I use the Mac Address as the unique device identifier.

Reducing the power consumption, improving reliability etc. will be covered in future posts

//---------------------------------------------------------------------------------
// Copyright (c) 2017, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//---------------------------------------------------------------------------------
using System;
using System.Net;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Net.NetworkInformation;
using devMobile.NetMF.Sensor;
using Gralin.NETMF.Nordic;
using SecretLabs.NETMF.Hardware.Netduino;

namespace devMobile.IoT.FIeldGateway.Netduino.Client
{
   class Client
   {
      private const byte nRF24Channel = 10;
      private const NRFDataRate nRF24DataRate = NRFDataRate.DR250kbps;
      private readonly byte[] nRF24ClientAddress = Encoding.UTF8.GetBytes("TandH");
      private readonly byte[] nRF24BaseStationAddress = Encoding.UTF8.GetBytes("Base1");
      private static byte[] deviceIdentifier;
      private readonly OutputPort led = new OutputPort(Pins.ONBOARD_LED, false);
      private readonly NRF24L01Plus radio;
      private readonly SiliconLabsSI7005 sensor = new SiliconLabsSI7005();

      public Client()
      {
         radio = new NRF24L01Plus();
      }

      public void Run()
      {
         // Configure the nRF24 hardware
         radio.OnDataReceived += OnReceive;
         radio.OnTransmitFailed += OnSendFailure;
         radio.OnTransmitSuccess += OnSendSuccess;

         radio.Initialize(SPI.SPI_module.SPI1, Pins.GPIO_PIN_D7, Pins.GPIO_PIN_D3, Pins.GPIO_PIN_D2);
         radio.Configure(nRF24ClientAddress, nRF24Channel, nRF24DataRate);
         radio.Enable();

         // Setup the device unique identifer, in this case the hardware MacAddress
         deviceIdentifier = NetworkInterface.GetAllNetworkInterfaces()[0].PhysicalAddress;
         Debug.Print(" Device Identifier : " + BytesToHexString(deviceIdentifier));

         Timer humidityAndtemperatureUpdates = new Timer(HumidityAndTemperatureTimerProc, null, 15000, 15000);

         Thread.Sleep(Timeout.Infinite);
      }

      private void HumidityAndTemperatureTimerProc(object state)
      {
         led.Write(true);

         double humidity = sensor.Humidity();
         double temperature = sensor.Temperature();

         Debug.Print("H:" + humidity.ToString("F1") + " T:" + temperature.ToString("F1"));
         string values = "T " + temperature.ToString("F1") + ",H " + humidity.ToString("F0");

         // Stuff the 2 byte header ( payload type & deviceIdentifierLength ) + deviceIdentifier into payload
         byte[] payload = new byte[1 + 1 + deviceIdentifier.Length + values.Length];
         payload[0] = 1;
         payload[1] = (byte)deviceIdentifier.Length;
         Array.Copy(deviceIdentifier, 0, payload, 2, deviceIdentifier.Length);

         Encoding.UTF8.GetBytes( values, 0, values.Length, payload, 8 ) ;

         radio.SendTo(nRF24BaseStationAddress, payload );
      }

      private void OnSendSuccess()
      {
         led.Write(false);

         Debug.Print("Send Success!");
      }

      private void OnSendFailure()
      {
         Debug.Print("Send failed!");
      }

      private void OnReceive(byte[] data)
      {
         led.Write(!led.Read());

         string message = new String(Encoding.UTF8.GetChars(data));

         Debug.Print("Receive " + message); ;
      }

      private static string BytesToHexString(byte[] bytes)
      {
         string hexString = string.Empty;

         // Create a character array for hexidecimal conversion.
         const string hexChars = "0123456789ABCDEF";

         // Loop through the bytes.
         for (byte b = 0; b < bytes.Length; b++)          {             if (b > 0)
               hexString += "-";

            // Grab the top 4 bits and append the hex equivalent to the return string.
            hexString += hexChars[bytes[b] >> 4];

            // Mask off the upper 4 bits to get the rest of it.
            hexString += hexChars[bytes[b] & 0x0F];
         }

         return hexString;
      }
   }

.Net Micro framework Deployment Tool output

WindowsIoTCentralNetduinoClient

Raspberry PI UWP application output

Interrupt Triggered: FallingEdge
11:40:46 Address 5C-86-4A-00-E4-1D Length 6 Payload T 25.2,H 90 Length 11
 Sensor 5C-86-4A-00-E4-1D-T Value 25.2
 Sensor 5C-86-4A-00-E4-1D-H Value 90
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
11:41:01 Address 5C-86-4A-00-E4-1D Length 6 Payload T 25.3,H 91 Length 11
 Sensor 5C-86-4A-00-E4-1D-T Value 25.3
 Sensor 5C-86-4A-00-E4-1D-H Value 91
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
11:41:16 Address 5C-86-4A-00-E4-1D Length 6 Payload T 25.3,H 90 Length 11
 Sensor 5C-86-4A-00-E4-1D-T Value 25.3
 Sensor 5C-86-4A-00-E4-1D-H Value 90
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
11:41:31 Address 5C-86-4A-00-E4-1D Length 6 Payload T 25.3,H 90 Length 11
 Sensor 5C-86-4A-00-E4-1D-T Value 25.3
 Sensor 5C-86-4A-00-E4-1D-H Value 90
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
11:41:46 Address 5C-86-4A-00-E4-1D Length 6 Payload T 25.3,H 90 Length 11
 Sensor 5C-86-4A-00-E4-1D-T Value 25.3
 Sensor 5C-86-4A-00-E4-1D-H Value 90
Interrupt Triggered: RisingEdge

Bill of materials (prices as at Jan 2018)

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

netduino Plus + nerf supersoaker

A few weeks ago my 6 year old son took his Nerf SuperSoaker to the beach and the salt water damaged the battery pack. Yesterday he pulled it apart to see how it worked and after a couple of quick tests with the multimeter we worked out the switch on the battery pack was the problem.

While it was dismantled we decided to “enhance” it with a Netduino+ and an ultrasonic range finder so it squirted water automatically when a target got with 2.5m. The code for the Ultrasonic range finder was adapted from Dave’s code on Netduino.com

Netduino SuperSoaker++

Bill of Materials