Nexus Analog, GPIO and PWM testing

Over the weekend I have been testing a beta Ingenuity Micro Nexus device building a series of simple applications to exercise all of the input and output ports.

The device is equipped with 11 x Seeedstudio Grove compatible sockets (2 x UART, 5 x I2C, 3 x ADC, 1 x PWM sockets) which support a wide variety of sensors.

Test cables and devices
Grove Cable Modification with a cross stitch needle

So I could test all the analog port pins I modified a Grove Branch Cable by carefully unplugging the yellow and white branch cables and replacing them with yellow and white (plugged into the yellow connector on both sensor connectors) cables split from a spare Grove Universal Buckled 20cm cable. I used a pair of Grove Rotary Angle Sensors as analog inputs.

public static void Main()
{
	AnalogInput analogSensor1 = new AnalogInput
	(
		Pins.Analog.Socket1Pin1
		//Pins.Analog.Socket2Pin1
		//Pins.Analog.Socket3Pin1
		//Pins.Analog.Socket4Pin1
	);
	AnalogInput analogSensor2 = new AnalogInput
	(
		Pins.Analog.Socket1Pin2
		//Pins.Analog.Socket2Pin2
		//Pins.Analog.Socket3Pin2
		//Pins.Analog.Socket4Pin2
	);

	Debug.Print("Program running");

	while (true)
	{
		double sensorValue1 = analogSensor1.Read();
		double sensorValue2 = analogSensor2.Read();

		Debug.Print("Value 1:" + sensorValue1.ToString("F2") + " Value 2:" + sensorValue2.ToString("F2"));

		Thread.Sleep(500);
	}
}

To speed up testing of the GPIO and PWM ports I modified a Grove Universal Buckled 20cm cable by twisting the white and yellow wires.

I used a pair of Grove illuminated buttons (Red, Yellow or Blue). The button was the digital input, the LED was the digital output. By uncommenting pairs of socket pins I could quickly step through all the ports checking that pressing the button toggled the state of the LED.

public class Program
{
	const Cpu.Pin ButtonLedPin =
		Pins.Gpio.Socket1Pin1;
		//Pins.Gpio.Socket1Pin2;
		//Pins.Gpio.Socket2Pin1;
		//Pins.Gpio.Socket2Pin2;
		//Pins.Gpio.Socket3Pin1;
		//Pins.Gpio.Socket3Pin2;
		//Pins.Gpio.Socket4Pin1;
		//Pins.Gpio.Socket4Pin2;
		//Pins.Gpio.Socket5Pin1;
		//Pins.Gpio.Socket5Pin2;
		//Pins.Gpio.Socket6Pin1;
		//Pins.Gpio.Socket6Pin2;
		//Pins.Gpio.Socket7Pin1;
		//Pins.Gpio.Socket7Pin2;
		//Pins.Gpio.Socket8Pin1;
		//Pins.Gpio.Socket8Pin2;
		//Pins.Gpio.Socket9Pin1;
		//Pins.Gpio.Socket9Pin2;
		//Pins.Gpio.Socket10Pin1;
		//Pins.Gpio.Socket10Pin2;
		//Pins.Gpio.Socket11Pin1;
		//Pins.Gpio.Socket11Pin2;
	const Cpu.Pin ButtonPin =
		//Pins.Gpio.Socket1Pin1;
		Pins.Gpio.Socket1Pin2;
		//Pins.Gpio.Socket2Pin1;
		//Pins.Gpio.Socket2Pin2;
		//Pins.Gpio.Socket3Pin1;
		//Pins.Gpio.Socket3Pin2;
		//Pins.Gpio.Socket4Pin1;
		//Pins.Gpio.Socket4Pin2;
		//Pins.Gpio.Socket5Pin1;
		//Pins.Gpio.Socket5Pin2;
		//Pins.Gpio.Socket6Pin1;
		//Pins.Gpio.Socket6Pin2;
		//Pins.Gpio.Socket7Pin1;
		//Pins.Gpio.Socket7Pin2;
		//Pins.Gpio.Socket8Pin1;
		//Pins.Gpio.Socket8Pin2;
		//Pins.Gpio.Socket9Pin1;
		//Pins.Gpio.Socket9Pin2;
		//Pins.Gpio.Socket10Pin1;
		//Pins.Gpio.Socket10Pin2;
		//Pins.Gpio.Socket11Pin1;
		//Pins.Gpio.Socket11Pin2;
	static OutputPort buttonLed = new OutputPort(ButtonLedPin, false);

	public static void Main()
	{
		InterruptPort button = new InterruptPort(ButtonPin, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
		button.OnInterrupt += Button_OnInterrupt;

		Debug.Print("Program running");

		Thread.Sleep(Timeout.Infinite);
	}

	private static void Button_OnInterrupt(uint data1, uint data2, DateTime time)
	{
		Debug.Print(time.ToString("hh:mm:ss") + " Data1:" + data1 + " Data 2:" + data2);

		buttonLed.Write(!buttonLed.Read());
	}

So I could test the PWM port I used a Grove Rotary Angle Sensor plugged into Socket 4 and a Grove LED (Red, Green or Blue) plugged into Socket 6 with a standard cable for pin 1 or my twisted cable for pin 2.

public class Program
{
	public static void Main()
	{
		AnalogInput analogSensor = new AnalogInput(Pins.Analog.Socket4Pin1);

		//const Cpu.PWMChannel LedPin = Pins.Pwm.Socket6Pin1;
		const Cpu.PWMChannel LedPin = Pins.Pwm.Socket6Pin2;
			
		PWM ledDim = new PWM(LedPin, 1000.0, 0.0, false);

		ledDim.Start();
		Debug.Print("Program running");

		while (true)
		{
			double sensorValue = analogSensor.Read();

			Debug.Print(DateTime.Now.ToString("hh:mm:ss") +" Value:" + sensorValue.ToString("F1"));

			ledDim.DutyCycle = sensorValue;

			Thread.Sleep(500);
		}
	}
}

All of the Analog, GPIO & PWM sockets/pins worked as expected, there maybe a couple of extra PWM outputs available on I2C sockets.

Nexus LoRa Radio 915 MHz Payload Addressing client

This is a demo Ingenuity Micro Nexus client (based on the Netduino example for my RFM9XLoRaNetMF library) that uploads temperature and humidity data to my Azure IoT Hubs/Central or AdaFruit.IO on Raspberry PI field gateways

Bill of materials (Prices June 2019).

// <copyright file="client.cs" company="devMobile Software"&gt;
// Copyright ® 2019 Feb devMobile Software, All Rights Reserved
//
//  MIT License
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE"
//
// </copyright&gt;
namespace devMobile.IoT.Nexus.FieldGateway
{
	using System;
	using System.Text;
	using System.Threading;
	using Microsoft.SPOT;
	using Microsoft.SPOT.Hardware;

	using devMobile.IoT.NetMF.ISM;
	using devMobile.NetMF.Sensor;
	using IngenuityMicro.Nexus;

	class NexusClient
	{
		private Rfm9XDevice rfm9XDevice;
		private readonly TimeSpan dueTime = new TimeSpan(0, 0, 15);
		private readonly TimeSpan periodTime = new TimeSpan(0, 0, 60);
		private readonly SiliconLabsSI7005 sensor = new SiliconLabsSI7005();
		private readonly Led _led = new Led();
		private readonly byte[] fieldGatewayAddress = Encoding.UTF8.GetBytes("LoRaIoT1");
		private readonly byte[] deviceAddress = Encoding.UTF8.GetBytes("Nexus915");

		public NexusClient()
		{
			rfm9XDevice = new Rfm9XDevice(SPI.SPI_module.SPI3, (Cpu.Pin)28, (Cpu.Pin)15, (Cpu.Pin)26);
			_led.Set(0, 0, 0);
		}

		public void Run()
		{

			rfm9XDevice.Initialise(frequency: 915000000, 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.Set(0, 128, 0);

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

		void rfm9XDevice_OnTransmit()
		{
			_led.Set(0, 0, 0);

			Debug.Print("Transmit-Done");
		}

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

Overall the development process was good with no modifications to my RFM9X.NetMF library or SI7005 library (bar removing a Netduino I2C work around) required

Nexus device with Seeedstudio Temperature & Humidity Sensors
Nexus Sensor data in Azure IoT Hub Field Gateway ETW Logging
Nexus temperature & humidity data displayed in Azure IoT Central

IoT.Net LoRa Radio 915 MHz Payload Addressing client

This is a demo ingenuity micro IoT.Net 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. 

Thought the silk screen says RFM69 this is a prototype running an RFM95 module.

iotnetlora.jpg

Bill of materials (Prices Sep 2018)

  • IoT.Net device (Beta tester will add price when available)

The device has an onboard MCP9808 temperature sensor which kept the BoM really short. I have had to make some modifications to my RFM9XLoRaNetMF library as the IoT.Net device uses a different SPI port. The code for this devices and the changes will be uploaded to GitHub in the next couple of days.

//---------------------------------------------------------------------------------
// Copyright (c) Sept 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.
// git remote add origin https://github.com/KiwiBryn/FieldGateway.LoRa.IoTNetClient.git
// git push -u origin master
//---------------------------------------------------------------------------------
namespace devMobile.IoT.IoTNet.FieldGateway
{
	using System;
	using System.Text;
	using System.Threading;
	using Microsoft.SPOT;
	using Microsoft.SPOT.Hardware;
	using devMobile.IoT.NetMF.ISM;
	using IngenuityMicro.Sensors;

	class IoTNetClient
	{
		private readonly Rfm9XDevice rfm9XDevice;
		private readonly TimeSpan dueTime = new TimeSpan(0, 0, 10);
		private readonly TimeSpan periodTime = new TimeSpan(0, 0, 30);
		private readonly MCP9808 mcp9808 = new MCP9808();
		private readonly OutputPort _led = new OutputPort((Cpu.Pin)16 + 8, false);
		private readonly byte[] fieldGatewayAddress = Encoding.UTF8.GetBytes("LoRaIoT1");
		private readonly byte[] deviceAddress = Encoding.UTF8.GetBytes("IoTNet1");

		public IoTNetClient()
		{
			rfm9XDevice = new Rfm9XDevice( SPI.SPI_module.SPI3, (Cpu.Pin)16 + 9, (Cpu.Pin)5, (Cpu.Pin)4);
		}

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

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

			Timer temperatureUpdates = new Timer(TemperatureTimerProc, null, dueTime, periodTime);

			Thread.Sleep(Timeout.Infinite);
		}

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

			double temperature = mcp9808.ReadTempInC();

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

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

			_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);
			}
		}
	}
}
}

.Net Framework debug output Field Gateway

22:55:39-RX From IoTNet1 PacketSnr 9.5 Packet RSSI -50dBm RSSI -110dBm = 6 byte message "t 23.6"
 Sensor IoTNet1t Value 23.6
 AzureIoTHubClient SendEventAsync start
 AzureIoTHubClient SendEventAsync finish
The thread 0xbec has exited with code 0 (0x0).
The thread 0xbb4 has exited with code 0 (0x0).
The thread 0xa0c has exited with code 0 (0x0).
The thread 0x13c has exited with code 0 (0x0).
22:56:09-RX From IoTNet1 PacketSnr 9.3 Packet RSSI -44dBm RSSI -102dBm = 6 byte message "t 23.8"
 Sensor IoTNet1t Value 23.8
 AzureIoTHubClient SendEventAsync start
 AzureIoTHubClient SendEventAsync finish

A small footprint, battery powered .NetMF 4.4 LoRa device designed and made in New Zealand with Visual Studio 2017 support is great.

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 Payload Addressing

I have extended the NetMF sample application and library to show how the conditional compilation directive ADDRESSED_MESSAGES_PAYLOAD controls the configuration.

When the application is started the RFM9X is in sleep mode, then when the Receive method is called the device is set to ReceiveContinuous.

//---------------------------------------------------------------------------------
// 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( frequency:915000000, paBoost: true, rxPayloadCrcOn: true);
#if ADDRESSED_MESSAGES
         rfm9XDevice.Receive(Encoding.UTF8.GetBytes("Netduino"));
#else
         rfm9XDevice.Receive();
#endif
         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);

#if ADDRESSED_MESSAGES
            rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes("LoRaIoT1"), messageBytes);
#else
            rfm9XDevice.Send(messageBytes);
#endif
            Thread.Sleep(10000);
         }
      }

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

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

            Debug.Print(DateTime.UtcNow.ToString("HH:MM:ss") + "-From " + addressText + " PacketSnr " + packetSnr.ToString("F1") + " Packet RSSI " + packetRssi + "dBm RSSI " + rssi + "dBm = " + data.Length + " byte message " + @"""" + messageText + @"""") ;
#else
            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 + @"""") ;
#endif
         }
         catch (Exception ex)
         {
            Debug.Print(ex.Message);
         }
      }
   }
}

namespace System.Diagnostics
{
   public enum DebuggerBrowsableState
   {
      Never = 0,
      Collapsed = 2,
      RootHidden = 3
   }
}

The Netduino client “plays nicely” with my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

The Semech SX127X datasheet describes how addressing can be implemented using interrupts which I will have a look at soon.

Library needs further testing and I’m working on a sample Arduino application.

Rfm9xLoRaDevice NetMF Payload CRCs

To ensure I was only handling messages with valid contents I added code to appended a cyclic redundancy check(CRC) onto outbound messages and validate the CRC on inbound messages.

First step was to update the initialise method parameter list (the parameter list is huge but for most scenarios the defaults are fine)

public void Initialise(RegOpModeMode regOpModeAfterInitialise, // RegOpMode
         double frequency = FrequencyDefault, // RegFrMsb, RegFrMid, RegFrLsb
         bool rxDoneignoreIfCrcMissing = true, bool rxDoneignoreIfCrcInvalid = true,
         bool paBoost = false, byte maxPower = RegPAConfigMaxPowerDefault, byte outputPower = RegPAConfigOutputPowerDefault, // RegPaConfig
         bool ocpOn = true, byte ocpTrim = RegOcpOcpTrimDefault, // RegOcp
         RegLnaLnaGain lnaGain = LnaGainDefault, bool lnaBoost = false, // RegLna
         RegModemConfigBandwidth bandwidth = RegModemConfigBandwidthDefault, RegModemConfigCodingRate codingRate = RegModemConfigCodingRateDefault, RegModemConfigImplicitHeaderModeOn implicitHeaderModeOn = RegModemConfigImplicitHeaderModeOnDefault, //RegModemConfig1
         RegModemConfig2SpreadingFactor spreadingFactor = RegModemConfig2SpreadingFactorDefault, bool txContinuousMode = false, bool rxPayloadCrcOn = false,
         ushort symbolTimeout = SymbolTimeoutDefault,
         ushort preambleLength = PreambleLengthDefault,
         byte payloadLength = PayloadLengthDefault,
         byte payloadMaxLength = PayloadMaxLengthDefault,
         byte freqHoppingPeriod = FreqHoppingPeriodDefault,
         bool lowDataRateOptimize = false, bool agcAutoOn = false,
         byte ppmCorrection = ppmCorrectionDefault,
         RegDetectOptimizeDectionOptimize detectionOptimize = RegDetectOptimizeDectionOptimizeDefault,
         bool invertIQ = false,
         RegisterDetectionThreshold detectionThreshold = RegisterDetectionThresholdDefault,
         byte syncWord = RegSyncWordDefault)

The rxPayloadCrcOn needs to be set to True for outbound messages to have a CRC.

Then in the RxDone interrupt handler the CRC is checked (regHopChannel & regIrqFlagsMask) if this feature is enabled. Any messages with missing\invalid CRCs will currently be silently discarded and I’m not certain this is a good idea.

 // Check to see if payload has CRC
         if (RxDoneIgnoreIfCrcMissing)
         {
            byte regHopChannel = this.Rfm9XLoraModem.ReadByte((byte)Registers.RegHopChannel);
            if ((regHopChannel & (byte)RegHopChannelFlags.CrcOnPayload) != (byte)RegHopChannelFlags.CrcOnPayload)
            {
               return;
            }
         }

         // Check to see if payload CRC is valid
         if (RxDoneIgnoreIfCrcInvalid)
         {
            if (((byte)IrqFlags & (byte)RegIrqFlagsMask.PayLoadCrcErrorMask) == (byte)RegIrqFlagsMask.PayLoadCrcErrorMask)
            {
               return;
            }
         }

The conversion of the payload from an array of bytes to a string for display stopped failing with an exception. When I had a number of clients running up to 10% of the messages were getting corrupted.

Rfm9xLoRaDevice NetMF SNR and RSSI

The signal to noise Ratio (SNR) and Received Signal Strength Indication(RSSI) for inbound messages required reading values from three registers
•RegPktSnrValue
•RegPktRssiValue
•RegRssiValue

I had to modify the OnDataRecievedHandler method signature so the values could be returned

 public delegate void OnDataRecievedHandler(float packetSnr, int packetRssi, int rssi, byte[] data);

I was inspired by the RSSI adjustment approach used in the Arduino-LoRa library

// Get the RSSI HF vs. LF port adjustment section 5.5.5 RSSI and SNR in LoRa Mode
float packetSnr = this.Rfm9XLoraModem.ReadByte((byte)Registers.RegPktSnrValue) * 0.25f;

int rssi = this.Rfm9XLoraModem.ReadByte((byte)Registers.RegRssiValue);
if (Frequency > RFMidBandThreshold)
{
  rssi = RssiAdjustmentHF + rssi;
}
else
{
  rssi = RssiAdjustmentLF + rssi;
}

int packetRssi = this.Rfm9XLoraModem.ReadByte((byte)Registers.RegPktRssiValue);
if (Frequency > RFMidBandThreshold)
{
  packetRssi = RssiAdjustmentHF + packetRssi;
}
else
{
  packetRssi = RssiAdjustmentLF + packetRssi;
}

OnDataReceived?.Invoke( packetSnr, packetRssi, rssi, messageBytes);

The values displayed in the Rfm9xLoRaDeviceClient application looked reasonable, but will need further checking

00:06:14-Rfm9X PacketSnr 9.8 Packet RSSI -47dBm RSSI -111dBm = 28 byte message "Hello W10 IoT Core LoRa! 182"
Sending 20 bytes message Hello NetMF LoRa! 38
Transmit-Done
00:06:24-Rfm9X PacketSnr 9.8 Packet RSSI -48dBm RSSI -111dBm = 28 byte message "Hello W10 IoT Core LoRa! 181"
Sending 20 bytes message Hello NetMF LoRa! 39
Transmit-Done
00:06:34-Rfm9X PacketSnr 9.8 Packet RSSI -47dBm RSSI -112dBm = 28 byte message "Hello W10 IoT Core LoRa! 180"
Sending 20 bytes message Hello NetMF LoRa! 40
Transmit-Done
00:06:44-Rfm9X PacketSnr 10.0 Packet RSSI -48dBm RSSI -111dBm = 28 byte message "Hello W10 IoT Core LoRa! 179"