Elecrow 32u4 with Lora RFM95 IOT Board Payload Addressing Client

This is a demo Elecrow 32u4 with Lora RFM95 IOT Board-868MHz/915MHz 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 elecrow wiki had sample code based on the RadioHead library which was useful.

Bill of materials (Prices Sep 2018)

  • 32u4 with Lora RFM95 IOT Board-868MHz/915MHz USD22.50
  • Seeedstudio LightLevel Sensor USD2.90
  • Elecrow Crowtail to Grove 4 pin Conversion Cable USD1.00

The code is pretty basic, it reads a value from the light sensor, scales it, then packs the payload and sets the necessary RFM9X/SX127X LoRa module configuration, has no power conservation, advanced wireless configuration etc.

Elecrow32u4LoRa

/*
  Adapted from LoRa Duplex communication with Sync Word

  Sends Light data from Seeedstudio 

   https://www.seeedstudio.com/Grove-Light-Sensor-v1-2-p-2727.html

  To my Windows 10 IoT Core RFM 9X library

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

*/
#include               // include libraries
#include
const int csPin = 10;          // LoRa radio chip select
const int resetPin = 9;       // LoRa radio reset
const int irqPin = 2;         // 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[] = {"Elecrow32u4"};

const int analogInPin = A0;
const int LoopSleepDelaySeconds = 60 ;

void setup() {
  Serial.begin(9600);

  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.");

  Serial.println("Setup done");
}

void loop()
{
  int payloadLength = 0 ;
  int sensorValue = 0;
  int outputValue = 0; 

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

  // Scale the sensor value to a %
  sensorValue = analogRead(analogInPin);
  outputValue = map(sensorValue, 0, 1023, 0, 100);  

  // 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) ;

  Serial.println("Loop called 5");

  Serial.print("L:");
  Serial.print( outputValue ) ;
  Serial.println( "%" ) ;

  // Copy the temperature into the payload
  payload[ payloadLength] = 'l';
  payloadLength += 1 ;
  payload[ payloadLength] = ' ';
  payloadLength += 1 ;
  payloadLength += strlen( itoa(outputValue, &payload[payloadLength],10 ));  

  // 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 debug output window the messages from the device looked like this

14:06:38-RX From Elecrow32u4 PacketSnr 9.8 Packet RSSI -88dBm RSSI -110dBm = 4 byte message "l 85"
Sensor Elecrow32u4l Value 85
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x930 has exited with code 0 (0x0).
The thread 0xb74 has exited with code 0 (0x0).
The thread 0x3c8 has exited with code 0 (0x0).
The thread 0x984 has exited with code 0 (0x0).
14:07:01-RX From IoTMCU915 PacketSnr 9.3 Packet RSSI -87dBm RSSI -110dBm = 12 byte message "t 13.7,h 113"
Sensor IoTMCU915t Value 13.7
Sensor IoTMCU915h Value 113
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x6e8 has exited with code 0 (0x0).
The thread 0x7b4 has exited with code 0 (0x0).
The thread 0xe9c has exited with code 0 (0x0).

My battery is a bit of an overkill and to reduce power consumption I would disconnect/remove the light emitting diode(LED)

Netduino LoRa Radio 433/868/915 MHz Payload Addressing client

This is a demo Netduino client (based on one of the examples in my RFM9XLoRaNetMF library) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

Bill of materials (Prices Sep 2018)

//---------------------------------------------------------------------------------
// 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.
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Netduino.FieldGateway
{
   using System;
   using System.Text;
   using System.Threading;
   using Microsoft.SPOT;
   using Microsoft.SPOT.Hardware;
   using SecretLabs.NETMF.Hardware.Netduino;
   using devMobile.IoT.NetMF.ISM;
   using devMobile.NetMF.Sensor;

   class NetduinoClient
   {
      Rfm9XDevice rfm9XDevice;
      private readonly TimeSpan dueTime = new TimeSpan(0, 0, 15);
      private readonly TimeSpan periodTime = new TimeSpan(0, 0, 300);
      private readonly SiliconLabsSI7005 sensor = new SiliconLabsSI7005();
      private readonly OutputPort _led = new OutputPort(Pins.ONBOARD_LED, false);
      private readonly byte[] fieldGatewayAddress = Encoding.UTF8.GetBytes("LoRaIoT1");
      private readonly byte[] deviceAddress = Encoding.UTF8.GetBytes("Netduino1");

      public NetduinoClient()
      {
         rfm9XDevice = new Rfm9XDevice(Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D2);
      }

      public void Run()
      {
         //rfm9XDevice.Initialise(frequency: 915000000, paBoost: true, rxPayloadCrcOn: true);
         rfm9XDevice.Initialise(frequency: 433000000, paBoost: true, rxPayloadCrcOn: true);
         rfm9XDevice.Receive(deviceAddress);

         rfm9XDevice.OnDataReceived += rfm9XDevice_OnDataReceived;
         rfm9XDevice.OnTransmit += rfm9XDevice_OnTransmit;

         Timer humidityAndtemperatureUpdates = new Timer(HumidityAndTemperatureTimerProc, null, dueTime, periodTime);

         Thread.Sleep(Timeout.Infinite);
      }

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

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

         Debug.Print(DateTime.UtcNow.ToString("hh:mm:ss") + " H:" + humidity.ToString("F1") + " T:" + temperature.ToString("F1"));

         rfm9XDevice.Send(fieldGatewayAddress, Encoding.UTF8.GetBytes( "t " + temperature.ToString("F1") + ",H " + humidity.ToString("F0")));

         _led.Write(true);
      }

      void rfm9XDevice_OnTransmit()
      {
         Debug.Print("Transmit-Done");
         _led.Write(false);
      }

      void rfm9XDevice_OnDataReceived(byte[] address, float packetSnr, int packetRssi, int rssi, byte[] data)
      {
         try
         {
            string messageText = new string(UTF8Encoding.UTF8.GetChars(data));
            string addressText = new string(UTF8Encoding.UTF8.GetChars(address));

            Debug.Print(DateTime.UtcNow.ToString("HH:MM:ss") + "-Rfm9X PacketSnr " + packetSnr.ToString("F1") + " Packet RSSI " + packetRssi + "dBm RSSI " + rssi + "dBm = " + data.Length + " byte message " + @"""" + messageText + @"""");
         }
         catch (Exception ex)
         {
            Debug.Print(ex.Message);
         }
      }
   }
}

The code is available on GitHub
FieldGatewayNetduinoLoRaElecrow915
Elecrow shield
FieldGatewayNetduinoLoRaDragino915
Dragino shield
FieldGatewayNetduinLoRaMakerFabs433
MakerFabs shield
Net Micro Framework debug output from device

The thread '' (0x2) has exited with code 0 (0x0).
12:00:18 H:96.9 T:19.6
Transmit-Done
12:05:17 H:95.1 T:20.1
Transmit-Done

.Net Framework debug output Field Gateway

The thread 0x1550 has exited with code 0 (0x0).
21:21:49-RX From Netduino1 PacketSnr 9.5 Packet RSSI -40dBm RSSI -107dBm = 11 byte message "t 19.6,H 97"
 Sensor Netduino1t Value 19.6
 Sensor Netduino1H Value 97
 AzureIoTHubClient SendEventAsync start
 AzureIoTHubClient SendEventAsync finish
...
21:26:49-RX From Netduino1 PacketSnr 9.5 Packet RSSI -33dBm RSSI -103dBm = 11 byte message "t 20.1,H 95"
 Sensor Netduino1t Value 20.1
 Sensor Netduino1H Value 95
 AzureIoTHubClient SendEventAsync start
 AzureIoTHubClient SendEventAsync finish
The thread 0xfbc has exited with code 0 (0x0).

Then in my Azure IoT Hub

AzureIOTHubExplorerScreenGrab20180917

RFM9X.NetMF on Github

After a month of posts the source code of V0.9 of my RFM9X/SX127X library is on GitHub. I included all of the source for my test harness and proof of concept(PoC) applications so other people can follow along with “my learning experience”.

I need to trial with some more hardware, frequency bands, variety of clients, initialisation configurations and backport the last round of fixes from my .Net library.

The simplest possible application .NetMF using the new library

/---------------------------------------------------------------------------------
// Copyright (c) August 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.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.NetMF.Rfm9X.Client
{
   using System;
   using System.Text;
   using System.Threading;
   using devMobile.IoT.NetMF.ISM;
   using Microsoft.SPOT;
   using SecretLabs.NETMF.Hardware.Netduino;

   public class Program
   {
      public static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D2);
         byte MessageCount = Byte.MinValue;

         rfm9XDevice.Initialise( Rfm9XDevice.RegOpModeMode.ReceiveContinuous, 915000000, paBoost: true, rxPayloadCrcOn: true);
         rfm9XDevice.OnDataReceived += rfm9XDevice_OnDataReceived;
         rfm9XDevice.OnTransmit += rfm9XDevice_OnTransmit;

         while (true)
         {
            string messageText = "Hello NetMF LoRa! " + MessageCount.ToString();
            MessageCount += 1;
            byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
            Debug.Print("Sending " + messageBytes.Length + " bytes message " + messageText);
            rfm9XDevice.SendMessage(messageBytes);

            Thread.Sleep(10000);
         }
      }

      static void rfm9XDevice_OnTransmit()
      {
         Debug.Print("Transmit-Done");
      }

      static void rfm9XDevice_OnDataReceived(byte[] data)
      {
         try
         {
            string messageText = new string(UTF8Encoding.UTF8.GetChars(data));

            Debug.Print("Received " + data.Length.ToString() + " byte message " + messageText);
         }
         catch (Exception ex)
         {
            Debug.Print(ex.Message);
         }
      }
   }
}

// Dirty hack for Rosyln 
namespace System.Diagnostics
{
   public enum DebuggerBrowsableState
   {
      Never = 0,
      Collapsed = 2,
      RootHidden = 3
   }
}

I need to do more testing (especially of the initialisation options) and will add basic device addressing soon so my field gateway will only see messages which it is interested in.

RFM9X.IoTCore on Github

After a month of posts the source code of V0.9 of my RFM9X/SX127X library is on GitHub. I included all of the source for my test harness and proof of concept(PoC) applications so other people can follow along with “my learning experience”.

I started wanting a library to for a LoRa telemetry field gateway and ended up writing one (which is usually not a good idea). My use case was a device that was configured, then run for long periods of time, was not battery powered, and if settings were changed could be restarted. I need to trial with some more hardware, frequency bands, variety of clients, initialisation configurations and backport the last round of fixes to my .NetMF library.

I am also looking at writing an RFM69 library using a pair of shields (434MHz & 915MHz)  from seegel-systeme.

The simplest possible application using the new library (a fair bit of the code is to support the different supported shields)

//---------------------------------------------------------------------------------
// Copyright (c) August 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.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.LoRaDeviceClient
{
	using System;
	using System.Diagnostics;
	using System.Text;
	using System.Threading.Tasks;

	using devMobile.IoT.Rfm9x;
	using Windows.ApplicationModel.Background;

	public sealed class StartupTask : IBackgroundTask
    {
		private byte NessageCount = Byte.MaxValue;
#if DRAGINO
		private const byte ChipSelectLine = 25;
		private const byte ResetLine = 17;
		private const byte InterruptLine = 4;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, ChipSelectLine, ResetLine, InterruptLine);
#endif
#if M2M
		private const byte ChipSelectLine = 25;
		private const byte ResetLine = 17;
		private const byte InterruptLine = 4;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, ChipSelectLine, ResetLine, InterruptLine);
#endif
#if ELECROW
		private const byte ResetLine = 22;
		private const byte InterruptLine = 25;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS1, ResetLine, InterruptLine);
#endif
#if ELECTRONIC_TRICKS
		private const byte ResetLine = 22;
		private const byte InterruptLine = 25;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, 22, 25);
#endif

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			rfm9XDevice.Initialise(Rfm9XDevice.RegOpModeMode.ReceiveContinuous, 915000000.0, paBoost: true);

#if DEBUG
			rfm9XDevice.RegisterDump();
#endif
			rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
			rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

			Task.Delay(10000).Wait();

			while (true)
			{
				string messageText = string.Format("Hello W10 IoT Core LoRa! {0}", NessageCount);
				NessageCount -= 1;

				byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
				Debug.WriteLine("{0:HH:mm:ss}-TX {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
				this.rfm9XDevice.Send(messageBytes);

				Task.Delay(10000).Wait();
			}
		}

		private void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
		{
			try
			{
				string messageText = UTF8Encoding.UTF8.GetString(e.Data);

				Debug.WriteLine("{0:HH:mm:ss}-RX {1} byte message {2}", DateTime.Now, e.Data.Length, messageText);
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
		}

		private void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
		{
			Debug.WriteLine("{0:HH:mm:ss}-TX Done", DateTime.Now);
		}
	}
}

I have a shield from uputronics on order which should arrive from the UK in roughly a week. This shield has two RFM9X devices onboard (In my case 434MHz & 915MHz) so it will be interesting to see how my library copes with two instances of the stack running together.

I need to do more testing (especially of the initialisation options) and will add basic device addressing soon so my field gateway will only see messages which it is interested in.

Elecrow Lora RFM95 IOT Board for RPI

For the example code so far I had been using the Dragino LoRa GPS HAT for Raspberry PI which after looking at the schematic (to figure out how the chip select line was connected) worked pretty well.

I had also purchased a Lora RFM95 IOT Board for RPI from Elecrow and was keen to get that working for applications which don’t require Global Positioning System (GPS) support.

The RFM95 chip select line is connected to pin 26 (GPIO7), the reset line to pin 15(GPIO22) and the interrupt line (RFM96 DIO0) to pin 22(GPIO25).

Lora RFM95 IOT Board for RPI V1.0

The shield had a Light Emitting Diode (LED) connected to General Purpose Input Output (GPIO) 23 so the first step was to get that to flash.

//---------------------------------------------------------------------------------
// Copyright (c) July 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.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.ElecrowLed
{
	using System;
	using System.Threading;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Gpio;

	public sealed class StartupTask : IBackgroundTask
	{
		public void Run(IBackgroundTaskInstance taskInstance)
		{
			GpioController gpioController = GpioController.GetDefault();
			GpioPin yellowLedPin = gpioController.OpenPin(23);
			yellowLedPin.SetDriveMode(GpioPinDriveMode.Output);

			while (true)
			{
				if (yellowLedPin.Read() == GpioPinValue.High)
				{
					yellowLedPin.Write(GpioPinValue.Low);
				}
				else
				{
					yellowLedPin.Write(GpioPinValue.High);
				}
				Thread.Sleep(500);
			}
		}
	}
}

To confirm I could access the RMF95 registers over the Serial Peripheral Interface (SPI) I read the RegVersion register.

//---------------------------------------------------------------------------------
// Copyright (c) July 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.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.ElecrowSPI
{
	using System;
	using System.Diagnostics;
	using System.Threading;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Spi;

	public sealed class StartupTask : IBackgroundTask
    {
		public void Run(IBackgroundTaskInstance taskInstance)
		{
			SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
			var settings = new SpiConnectionSettings(1)	// GPIO7 Elecrow shield
			{
				ClockFrequency = 500000,
				Mode = SpiMode.Mode0,	// From SemTech docs pg 80 CPOL=0, CPHA=0
			};

			SpiDevice Device = spiController.GetDevice(settings);

			while (true)
			{
				byte[] writeBuffer = new byte[] { 0x42 }; // RegVersion
				byte[] readBuffer = new byte[1];

				Device.TransferSequential(writeBuffer, readBuffer);

				byte registerValue = readBuffer[0];
				Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", 0x42, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));

				Thread.Sleep(10000);
			}
		}
	}
}

The debug output confirmed I was reading the right value from the RegVer register

Register 0x42 - Value 0X12 - Bits 00010010
Register 0x42 - Value 0X12 - Bits 00010010
Register 0x42 - Value 0X12 - Bits 00010010
Register 0x42 - Value 0X12 - Bits 00010010

I’m going to unsolder the coil antenna and replace it with a short SMA Extension drop cable so I can connect an external antenna.

ElecrowLoRaShield

 

nRF24 Windows 10 IoT Core Background Task

First step is to build a basic Windows 10 IoT Core background task which can receive and display messages sent from a variety of devices across an nRF24L01 wireless link.

If you create a new “Windows IoT Core” “Background Application” project then copy this code into StartupTasks.cs the namespace has to be changed in the C# file, project properties\library\Default namespace and “Package.appxmanifest”\declarations\Entry Point.

/*

Copyright ® 2017 December 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.Text;
using Radios.RF24;
using Windows.ApplicationModel.Background;

namespace devmobile.IoTCore.nRF24BackgroundTask
{
    public sealed class StartupTask : IBackgroundTask
    {
      private const byte ChipEnablePin = 25;
      private const byte ChipSelectPin = 0;
      private const byte nRF24InterruptPin = 17;
      private const string BaseStationAddress = "Base1";
      private const byte nRF24Channel = 10;
      private RF24 Radio = new RF24();
      private BackgroundTaskDeferral deferral;

      public void Run(IBackgroundTaskInstance taskInstance)
        {
         Radio.OnDataReceived += Radio_OnDataReceived;
         Radio.OnTransmitFailed += Radio_OnTransmitFailed;
         Radio.OnTransmitSuccess += Radio_OnTransmitSuccess;

         Radio.Initialize(ChipEnablePin, ChipSelectPin, nRF24InterruptPin);
         Radio.Address = Encoding.UTF8.GetBytes(BaseStationAddress);
         Radio.Channel = nRF24Channel;
         Radio.PowerLevel = PowerLevel.High;
         Radio.DataRate = DataRate.DR250Kbps;
         Radio.IsEnabled = true;

         Debug.WriteLine("Address: " + Encoding.UTF8.GetString(Radio.Address));
         Debug.WriteLine("PA: " + Radio.PowerLevel);
         Debug.WriteLine("IsAutoAcknowledge: " + Radio.IsAutoAcknowledge);
         Debug.WriteLine("Channel: " + Radio.Channel);
         Debug.WriteLine("DataRate: " + Radio.DataRate);
         Debug.WriteLine("IsDynamicAcknowledge: " + Radio.IsDyanmicAcknowledge);
         Debug.WriteLine("IsDynamicPayload: " + Radio.IsDynamicPayload);
         Debug.WriteLine("IsEnabled: " + Radio.IsEnabled);
         Debug.WriteLine("Frequency: " + Radio.Frequency);
         Debug.WriteLine("IsInitialized: " + Radio.IsInitialized);
         Debug.WriteLine("IsPowered: " + Radio.IsPowered);

         deferral = taskInstance.GetDeferral();

         Debug.WriteLine("Run completed");
      }

      private void Radio_OnDataReceived(byte[] data)
      {
         // Display as Unicode
         string unicodeText = Encoding.UTF8.GetString(data);
         Debug.WriteLine("Unicode - Payload Length {0} Unicode Length {1} Unicode text {2}", data.Length, unicodeText.Length, unicodeText);

         // display as hex
         Debug.WriteLine("Hex - Length {0} Payload {1}", data.Length, BitConverter.ToString(data));
      }

      private void Radio_OnTransmitSuccess()
      {
         Debug.WriteLine("Transmit Succeeded!");
      }

      private void Radio_OnTransmitFailed()
      {
         Debug.WriteLine("Transmit Failed!");
      }
   }
}

This was displayed in the output window of Visual Studio

Address: Base1
PA: 15
IsAutoAcknowledge: True
Channel: 10
DataRate: DR250Kbps
IsDynamicAcknowledge: False
IsDynamicPayload: True
IsEnabled: True
Frequency: 2410
IsInitialized: True
IsPowered: True
Run completed

Interrupt Triggered: FallingEdge
Unicode – Payload Length 19 Unicode Length 19 Unicode text T  23.8,H  73,V 3.26
Hex – Length 19 Payload 54-20-32-33-2E-38-2C-48-20-20-37-33-2C-56-20-33-2E-32-36
Interrupt Triggered: RisingEdge

Note the odd formatting of the Temperature and humidity values which is due to the way dtostrf function in the Atmel AVR library works.

Also noticed the techfooninja nRF24 library has configurable output power level which I will try to retrofit onto the Gralin NetMF library.

Next, several simple Arduino, devDuino V2.2, Seeeduino V4.2 and Netduino 2/3 clients (plus possibly some others)

nRF24 Windows 10 IoT Core reboot

My first live deployment of the nRF24L01 Windows 10 IoT Core field gateway is now scheduled for mid Q1 2018 so time for a reboot. After digging out my Raspbery PI 2/3 devices and the nRF24L01+ shield (with modifications detailed here) I have a basic plan with some milestones.

My aim is to be able to wirelessly acquire data from several dozen Arduino, devduino, seeeduino, and Netduino devices, Then, using a field gateway on a Raspberry PI running Windows 10 IoT Core upload it to Microsoft IoT Central

First bit of code – Bleepy a simple background application to test the piezo beeper on the RPI NRF24 Shield

namespace devmobile.IoTCore.Bleepy
{
   public sealed class StartupTask : IBackgroundTask
   {
      private BackgroundTaskDeferral deferral;
      private const int ledPinNumber = 4;
      private GpioPin ledGpioPin;
      private ThreadPoolTimer timer;

      public void Run(IBackgroundTaskInstance taskInstance)
      {
         var gpioController = GpioController.GetDefault();
         if (gpioController == null)
         {
            Debug.WriteLine("GpioController.GetDefault failed");
            return;
         }

         ledGpioPin = gpioController.OpenPin(ledPinNumber);
         if (ledGpioPin == null)
         {
            Debug.WriteLine("gpioController.OpenPin failed");
            return;
         }

         ledGpioPin.SetDriveMode(GpioPinDriveMode.Output);

         this.timer = ThreadPoolTimer.CreatePeriodicTimer(Timer_Tick, TimeSpan.FromMilliseconds(500));

         deferral = taskInstance.GetDeferral();

         Debug.WriteLine("Rum completed");
      }

      private void Timer_Tick(ThreadPoolTimer timer)
      {
         GpioPinValue currentPinValue = ledGpioPin.Read();

         if (currentPinValue == GpioPinValue.High)
         {
            ledGpioPin.Write(GpioPinValue.Low);
         }
         else
         {
            ledGpioPin.Write(GpioPinValue.High);
         }
      }
   }
}

Note the blob of blu tack over the piezo beeper to mute noise
nRF24ShieldMuted