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.

 

Netduino 3 Wifi xively nRF24L01 Gateway introduction

Around home I have a number of Arduino, devDuino and Netduino devices collecting power consumption, temperature & humidity measurements. Previously I had built an Azure event hub gateway which runs on Windows 7(or later) which acts as a gateway forwarding local http requests to an Microsoft Azure event hub.

Not all my embedded devices are capable of making an http request but an nRF24l01 based approach is supported.

For this application I wanted something a bit simpler than an Azure Event hub which could plot basic graphs and as I didn’t require massive scale Xively looked ideal.

Netduino 3 Wifi xively gateway + duino clients

Netduino 3 Wifi xively gateway and *duino clients

Over the next few blog postings I will show how I built the Netduino 3 wifi application and the Arduino based clients.

Bill of materials for the Xively gateway (prices at June 2015)

First step is to configure the network

NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces()[0];

if (networkInterface.IsDhcpEnabled)
{
   Debug.Print(" Waiting for IP address ");

   while (NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress == IPAddress.Any.ToString()) 
   {
      Thread.Sleep(100);
   }
}

// Display network config for debugging
Debug.Print("Network configuration");
Debug.Print(" Network interface type: " + networkInterface.NetworkInterfaceType.ToString());
Debug.Print(" MAC Address: " + BytesToHexString(networkInterface.PhysicalAddress));
Debug.Print(" DHCP enabled: " + networkInterface.IsDhcpEnabled.ToString());
Debug.Print(" Dynamic DNS enabled: " + networkInterface.IsDynamicDnsEnabled.ToString());
Debug.Print(" IP Address: " + networkInterface.IPAddress.ToString());
Debug.Print(" Subnet Mask: " + networkInterface.SubnetMask.ToString());
Debug.Print(" Gateway: " + networkInterface.GatewayAddress.ToString());

foreach (string dnsAddress in networkInterface.DnsAddresses)
{
   Debug.Print(" DNS Server: " + dnsAddress.ToString());
}

_module = new NRF24L01Plus();

Then setup the nRF24l01 driver

_module.OnDataReceived += OnReceive;
_module.OnTransmitFailed += OnSendFailure;
_module.OnTransmitSuccess += OnSendSuccess;

_module.Initialize(SPI.SPI_module.SPI1, Pins.GPIO_PIN_D7, Pins.GPIO_PIN_D3, Pins.GPIO_PIN_D2);
_module.Configure(myAddress, channel, NRFDataRate.DR1Mbps);
_module.Enable();

The setup required for the Xively API and mapping the devices highlighted the need for a means of storing configuration which could be modified using a simple text editor.

Netduino 3 Wifi with nRF24L01 shield

Netduino 3 Wifi + nRF24L01 shield

This software was built using tooling created and shared by others.

Big thanks to

Jakub Bartkowiak – Gralin.NETMF.Nordic.NRF24L01Plus

TechEd 2014 Auckland Presentation online

My TechEd INO204 presentation in now online at on MSDN Channel 9.

INO204  The Things of the Internet of Things
Speaker Rating
3.54 / 4  84.7%

Overall Rating
3.44 / 4 81.29%

The presenters desk had my laptop, document camera, 2 Fez spiders, 9 Netduinos, 2 devDuinos and an Arduino Uno R3 device so it was pretty busy.

TechEdPresentersDesk[1]

While preparing for the presentation I had some problems with the EMG stick on sensors

EMGStickOnPadMarks[1]

Code Camp Christchurch 2014

The Hardware

Flash an LED

OutputPort led = new OutputPort(Pins.ONBOARD_LED, false);
while ( true)
{
   Led.Write(!Led.Read())
   Thread.Sleep(500)
}

Digital Input – Polled

InputPort button = new InputPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled);
OutputPort led = new OutputPort(Pins.ONBOARD_LED, false);
while (true)
{
   led.Write(button.Read());
   Thread.Sleep(1000);
}

Digital Input – Interrupt

static OutputPort interuptled = new OutputPort(Pins.ONBOARD_LED, false);
InterruptPort button = new InterruptPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
button.OnInterrupt += new NativeEventHandler(button_OnInterrupt);

Thread.Sleep(Timeout.Infinite);

static void button_OnInterrupt(uint data1, uint data2, DateTime time)
{
   interuptled.Write(!interuptled.Read());
}

Analog Input

AnalogInput Sensor = new AnalogInput(Cpu.AnalogChannel.ANALOG_0);
while ( true)
{
   Debug.Print( "Value " + Sensor.Read().ToString("F2"));
   Thread.Sleep(500);
}

Pulse Width Modulation Output

AnalogInput brightness = new AnalogInput(AnalogChannels.ANALOG_PIN_A0);
PWM led = new PWM(PWMChannels.PWM_PIN_D5, 1000, 0.0, false);

led.Start();
while (true)
{
   Debug.Print("Brightness " + led.DutyCycle.ToString("F2"));
   led.DutyCycle = brightness.Read();
   Thread.Sleep(500);
}
led.Stop();

Power Consumption Monitor

Developing the software for the Energy Monitor Shield

Robot

Developing the software

  • Determine the distance to objects
  • Control the speed & direction of the motors using a Motor Shield Driver
  • Basic obstacle avoidance
  • Avoid obstacles using a state machine
  • Fine tune the motor speeds using a rotary encoder
  • Connect the GPS
  • Upload the position information to Xively

Heart Rate Monitor

Developing the software

  • Read the buttons using an AnalogInput
  • Count the number of button presses using an InterruptPort and a Timer
  • Determine the pulse rate in BPM by counting
  • Determine the average pulse rate in BPM
  • Display and manage the pulse rate info on the DFRobot 16×2 Lcd Shield
  • Upload the pulse rate information to xively

 

Energy Monitor Shield Nokia 5100 Display

The Energy Monitor Shield is supplied with a Nokia 5110 LCD for displaying instantaneous power consumption etc. There is an excellent Netduino driver for the Nokia 5100 display by omar, and with a few modifications this works with the Energy Monitor Shield. I removed the backlight support and made a few other simple modifications.

Nokia_5110 Lcd = new Nokia_5110(Pins.GPIO_PIN_D3, Pins.GPIO_PIN_D6, Pins.GPIO_PIN_D5);

while (true)
{
   Lcd.Clear();
   Lcd.DrawString(0, 0, DateTime.Now.ToString("HH:mm:ss"), true);
   Lcd.DrawString(0, 1, DateTime.Now.ToString("HH:mm:ss"), true);
   Lcd.DrawString(0, 2, DateTime.Now.ToString("HH:mm:ss"), true);
   Lcd.Refresh();
   Thread.Sleep(500)
}

Netduino Nokia 5110 driver

Energy Monitor Shield Noise Reduction

In a couple of previous posts the noise on the Netduino AnalogInput and the impact of this on the RMS current measurement was discussed. I trialled two versions of the code to see if my approach worked. Both versions used the initial calibration phase to measure the maximum and minimum values of the noise.

int valueSum = 0;
int valueNoiseMinimum = int.MaxValue;
int valueNoiseMaximum = int.MinValue;
int valueSumSqr = 0;
int offset;
AnalogInput x1 = new AnalogInput(Cpu.AnalogChannel.ANALOG_0);

// Calculate the sum for offset for first run
for (int i = 0; i < SampleCount; i++)
{
  int value = x1.ReadRaw();
  valueSum = valueSum + value;

   if (value < valueNoiseMinimum)
   {
      valueNoiseMinimum = value;
   }
   if (value > valueNoiseMaximum)
   {
      valueNoiseMaximum = value;
   }
}

offset = valueSum / SampleCount;
valueNoiseMinimum -= offset;
valueNoiseMaximum -= offset;

The first version used only the initial offset

Stopwatch stopwatch = Stopwatch.StartNew();
stopwatch.Start();

for (int i = 0; i < SampleCount; i++)
{
   int value = x1.ReadRaw();
   value -= offset;

   if ((value &gt; valueNoiseMaximum) || (value &lt; valueNoiseMinimum))
   {
   valueSumSqr += (value * value);
   }
}
stopwatch.Stop();

RMS 42.2729 RMS Current 3.4A RMS Power 775W Duration = 3301 mSec 30293/sec
RMS 42.2137 RMS Current 3.4A RMS Power 774W Duration = 3302 mSec 30284/sec
RMS 42.2374 RMS Current 3.4A RMS Power 775W Duration = 3302 mSec 30284/sec
RMS 42.1307 RMS Current 3.4A RMS Power 773W Duration = 3302 mSec 30284/sec
RMS 42.1307 RMS Current 3.4A RMS Power 773W Duration = 3302 mSec 30284/sec
RMS 42.1189 RMS Current 3.4A RMS Power 773W Duration = 3302 mSec 30284/sec
RMS 42.1307 RMS Current 3.4A RMS Power 773W Duration = 3302 mSec 30284/sec
RMS 42.1070 RMS Current 3.4A RMS Power 772W Duration = 3302 mSec 30284/sec
RMS 42.1189 RMS Current 3.4A RMS Power 773W Duration = 3302 mSec 30284/sec
RMS 42.1426 RMS Current 3.4A RMS Power 773W Duration = 3303 mSec 30275/sec

The second version updated the offset every iteration

Stopwatch stopwatch = Stopwatch.StartNew();
stopwatch.Start();

for (int i = 0; i &lt; SampleCount; i++)
{
   int value = x1.ReadRaw();
   valueSum += value;
   value -= offset;

   if ((value &gt; valueNoiseMaximum) || (value &lt; valueNoiseMinimum))
   {
      valueSumSqr += (value * value);
   }
}
stopwatch.Stop();
offset = valueSum / SampleCount;

This was slightly slower due to the extra addition operation in the sampling loop

RMS 41.5933 RMS Current 3.3A RMS Power 763W Duration = 3537 mSec 28272/sec
RMS 41.6653 RMS Current 3.3A RMS Power 764W Duration = 3541 mSec 28240/sec
RMS 41.6053 RMS Current 3.3A RMS Power 763W Duration = 3538 mSec 28264/sec
RMS 41.5572 RMS Current 3.3A RMS Power 762W Duration = 3537 mSec 28272/sec
RMS 41.5572 RMS Current 3.3A RMS Power 762W Duration = 3537 mSec 28272/sec
RMS 41.5331 RMS Current 3.3A RMS Power 762W Duration = 3537 mSec 28272/sec
RMS 41.4970 RMS Current 3.3A RMS Power 761W Duration = 3540 mSec 28248/sec
RMS 41.4849 RMS Current 3.3A RMS Power 761W Duration = 3538 mSec 28264/sec
RMS 41.4849 RMS Current 3.3A RMS Power 761W Duration = 3538 mSec 28264/sec
RMS 41.4849 RMS Current 3.3A RMS Power 761W Duration = 3516 mSec 28441/sec

At 28K4 samples per second the self adjusting RMS calculation is sampling the 50Hz waveform much more frequently than required.

Energy Monitor Shield RMS Calculation

The voltage output by the current sensor and measured by the Netduino needs to be corrected using the offset value then the RMS value calculated. This RMS value then needs to be adjusted taking into account the voltage range of the Netduino analog input (0V-3V3), the resolution of the analog input (12 bits) and the voltage output by the non-invasive current sensor (0~1V for 0~30A).

My approach appears to produce reasonable values but I will need to compare them with a calibrated reference device to check its accuracy. The 18W measurement with no current flowing is due to the noise on the analog input discussed in an earlier post.

The first version of the software used the initial offset value, the second version updates the offset value at the end of each set of samples.

int valueSum = 0;
int valueSumSqr = 0;
int offset;
AnalogInput x1 = new AnalogInput(Cpu.AnalogChannel.ANALOG_0);

// Calculate the sum for initial offset
for (int i = 0; i < SampleCount; i++)
{
valueSum += x1.ReadRaw();
}
offset = valueSum / SampleCount;

Stopwatch stopwatch = Stopwatch.StartNew();
stopwatch.Start();

for (int i = 0; i < SampleCount; i++)
{
int value = x1.ReadRaw();

value -= offset;

valueSumSqr += (value * value);
}
stopwatch.Stop();

double rms = System.Math.Sqrt((double)(valueSumSqr / SampleCount));
double rmsCurrent = rms * (3.3 / 4096.0) * 3.3 * 30.0;
double rmsWatts = rmsCurrent * 230;

Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2588 mSec 38639/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2587 mSec 38654/sec RMS 1.0 RMS Current 0.1A RMS Power 18W

int valueSum = 0;
int valueSumSqr = 0;
int offset;
AnalogInput x1 = new AnalogInput(Cpu.AnalogChannel.ANALOG_0);
// Calculate the sum for initial offset
for (int i = 0; i < SampleCount; i++)
{
valueSum += x1.ReadRaw();
}
offset = valueSum / SampleCount;

Stopwatch stopwatch = Stopwatch.StartNew();
stopwatch.Start();

for (int i = 0; i < SampleCount; i++)
{
int value = x1.ReadRaw();

valueSum += value;

value -= offset;

valueSumSqr += (value * value);
}
stopwatch.Stop();

offset = valueSum / SampleCount;

double rms = System.Math.Sqrt((double)(valueSumSqr / SampleCount));
double rmsCurrent = rms * (3.3 / 4096.0) * 3.3 * 30.0 ;
double rmsWatts = rmsCurrent * 230;

Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W
Duration = 2816 mSec 35511/sec RMS 1.0 RMS Current 0.1A RMS Power 18W

Both versions appear to sample the output of the non-invasive current sensor at a more than sufficient rate.