Sample hardwareAzure IoT Central data visualisation
The Maduino device in the picture is a custom version with an onboard Microchip ATSHA204 crypto and authentication chip (currently only use for the unique 72 bit serial number) and a voltage divider connected to the analog pin A6 to monitor the battery voltage.
There are compile time options ATSHA204 & BATTERY_VOLTAGE_MONITOR which can be used to selectively enable this functionality.
I use the Arduino lowpower library to aggressively sleep the device between measurements
// Adjust the delay so period is close to desired sec as possible, first do 8sec chunks.
int delayCounter = SensorUploadDelay / 8 ;
for( int i = 0 ; i < delayCounter ; i++ )
{
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
// Then to 4 sec chunk
delayCounter = ( SensorUploadDelay % 8 ) / 4;
for( int i = 0 ; i < delayCounter ; i++ )
{
LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF);
}
// Then to 2 sec chunk
delayCounter = ( SensorUploadDelay % 4 ) / 2 ;
for( int i = 0 ; i < delayCounter ; i++ )
{
LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
}
// Then to 1 sec chunk
delayCounter = ( SensorUploadDelay % 2 ) ;
for( int i = 0 ; i < delayCounter ; i++ )
{
LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
}
}
I use a spare digital PIN for powering the soil moisture probe so it can be powered down when not in use. I have included a short delay after powering up the device to allow the reading to settle.
// Turn on soil mosture sensor, take reading then turn off to save power
digitalWrite(SoilMoistureSensorEnablePin, HIGH);
delay(SoilMoistureSensorEnableDelay);
int soilMoistureADCValue = analogRead(SoilMoistureSensorPin);
digitalWrite(SoilMoistureSensorEnablePin, LOW);
int soilMoisture = map(soilMoistureADCValue,SoilMoistureSensorMinimum,SoilMoistureSensorMaximum, SoilMoistureValueMinimum, SoilMoistureValueMaximum);
PayloadAdd( "s", soilMoisture, false);
// <copyright file="client.cs" company="devMobile Software">
// 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>
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 SensorsNexus Sensor data in Azure IoT Hub Field Gateway ETW LoggingNexus temperature & humidity data displayed in Azure IoT Central
The shield has a small OLED screen and 3 buttons connected to General Purpose Input Output(GPIO) pins.
The first step was to check the pin assignments of the 3 buttons.
/*
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
Adafruit documentation page
https://learn.adafruit.com/adafruit-radio-bonnets/pinouts
Button 1: GPIO 5
Button 2: GPIO 6
Button 3: GPIO 12
*/
namespace devMobile.IoT.Rfm9x.AdafruitButtons
{
using System;
using System.Diagnostics;
using Windows.ApplicationModel.Background;
using Windows.Devices.Gpio;
public sealed class StartupTask : IBackgroundTask
{
private BackgroundTaskDeferral backgroundTaskDeferral = null;
private GpioPin InterruptGpioPin1 = null;
private GpioPin InterruptGpioPin2 = null;
private GpioPin InterruptGpioPin3 = null;
private const int InterruptPinNumber1 = 5;
private const int InterruptPinNumber2 = 6;
private const int InterruptPinNumber3 = 12;
private readonly TimeSpan debounceTimeout = new TimeSpan(0, 0, 15);
public void Run(IBackgroundTaskInstance taskInstance)
{
Debug.WriteLine("Application startup");
try
{
GpioController gpioController = GpioController.GetDefault();
InterruptGpioPin1 = gpioController.OpenPin(InterruptPinNumber1);
InterruptGpioPin1.SetDriveMode(GpioPinDriveMode.InputPullUp);
InterruptGpioPin1.ValueChanged += InterruptGpioPin_ValueChanged; ;
InterruptGpioPin2 = gpioController.OpenPin(InterruptPinNumber2);
InterruptGpioPin2.SetDriveMode(GpioPinDriveMode.InputPullUp);
InterruptGpioPin2.ValueChanged += InterruptGpioPin_ValueChanged; ;
InterruptGpioPin3 = gpioController.OpenPin(InterruptPinNumber3);
InterruptGpioPin3.SetDriveMode(GpioPinDriveMode.InputPullUp);
InterruptGpioPin3.ValueChanged += InterruptGpioPin_ValueChanged; ;
Debug.WriteLine("Digital Input Interrupt configuration success");
}
catch (Exception ex)
{
Debug.WriteLine($"Digital Input Interrupt configuration failed " + ex.Message);
return;
}
//enable task to continue running in background
backgroundTaskDeferral = taskInstance.GetDeferral();
}
private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
{
Debug.WriteLine($"Digital Input Interrupt {sender.PinNumber} triggered {args.Edge}");
}
}
}
When I ran the application it produced the following output when I pressed the three buttons (left->right) which confirmed I had the correct GPIO pins configuration.
Application startup
'backgroundTaskHost.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Data\Programs\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27129.1_arm__8wekyb3d8bbwe\System.Runtime.WindowsRuntime.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Digital Input Interrupt configuration success
Digital Input Interrupt 5 triggered FallingEdge
Digital Input Interrupt 5 triggered RisingEdge
Digital Input Interrupt 6 triggered FallingEdge
Digital Input Interrupt 6 triggered RisingEdge
Digital Input Interrupt 12 triggered FallingEdge
Digital Input Interrupt 12 triggered RisingEdge
The next step was to get the Serial Peripheral Interface (SPI) interface for the module working.
/*
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
Adafruit documentation page
https://learn.adafruit.com/adafruit-radio-bonnets/pinouts
CS : CE1
RST : GPIO25
IRQ : GPIO22 (DIO0)
Unused : GPIO23 (DIO1)
Unused : GPIO24 (DIO2)
*/
namespace devMobile.IoT.Rfm9x.AdafruitSPI
{
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)
{
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 output confirm the code worked
'backgroundTaskHost.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Data\Programs\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27129.1_arm__8wekyb3d8bbwe\System.Threading.Thread.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Register 0x42 - Value 0X12 - Bits 00010010
Register 0x42 - Value 0X12 - Bits 00010010
The next step is to build support for this shield into my RFM9X.IoTCore library and get the OLED working.
In an early version of the software I used to provide a sample configuration JSON file in the associated GitHub repository. Users had to download this file to a computer, update it with their Azure IOT Hub or Azure IoT Central connection string or AdafruitIO APIKey , frequency and device address, then upload to the field gateway.
In a later version of the software I added code which created an empty configuration file with defaults for all settings, many of which were a distraction as the majority of users would never change them.
More settings meant there was more scope for users to change settings which broke the device samples and the gateway.
I have removed the code to generate the full configuration file (starting with Azure IOT Hub field gateway) and included a sample configuration file with the minimum required settings in the GitHub repositories and installers.
I am assuming that if a user wants to change advanced settings they can look at the code and/or documentation and figure out the setting names and valid values.
The new sample configuration file for a Azure IoT Hub telemetry only gateway is
Last week a package arrived from LowPowerLab with some Moteino0 devices and accessories . With this gear I have built yet another client for my Azure IoT Hub and AdaFruit.IOLoRa Field Gateways.
It took me a while longer that usual to get the Motenio working as the sketch setup call appeared to hang in DEBUG builds.
After staring at the code for a while I noticed that I hadn’t changed LoRa.dumpRegisters method parameter from Serial to SerialUSB. A couple of hours lost due to a dumb typo by me.
Now that the device is running well, I’ll look at reducing power consumption and splitting the the payload packing code into a library.
/*
Copyright ® 2018 November 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 <stdlib.h>
#include <avr/dtostrf.h>
#include <LoRa.h>
#include <TH02_dev.h>
//#define DEBUG
//#define DEBUG_TELEMETRY
//#define DEBUG_LORA
// LoRa field gateway configuration (these settings must match your field gateway)
const char FieldGatewayAddress[] = {"LoRaIoT1"};
const char DeviceAddress[] = {"Moteino01"};
const float FieldGatewayFrequency = 915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;
// Payload configuration
const int ChipSelectPin = A2;
const int InterruptPin = 9;
const int ResetPin = -1;
// LoRa radio payload configuration
const byte SensorIdValueSeperator = ' ' ;
const byte SensorReadingSeperator = ',' ;
const int LoopSleepDelaySeconds = 10 ;
const byte PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum];
byte payloadLength = 0 ;
void setup()
{
SerialUSB.begin(9600);
#ifdef DEBUG
while (!SerialUSB);
#endif
SerialUSB.println("Setup called");
SerialUSB.println("LoRa setup start");
// override the default chip select and reset pins
LoRa.setPins( ChipSelectPin, ResetPin, InterruptPin );
if (!LoRa.begin(FieldGatewayFrequency))
{
SerialUSB.println("LoRa begin failed");
while (true); // Drop into endless loop requiring restart
}
// Need to do this so field gateways pays attention to messsages from this device
LoRa.enableCrc();
LoRa.setSyncWord(FieldGatewaySyncWord);
#ifdef DEBUG_LORA
LoRa.dumpRegisters(USBSerial);
#endif
SerialUSB.println("LoRa Setup done.");
// Configure the Seeedstudio TH02 temperature & humidity sensor
SerialUSB.println("TH02 setup start");
TH02.begin();
delay(100);
SerialUSB.println("TH02 setup done");
PayloadHeader((byte*)FieldGatewayAddress,strlen(FieldGatewayAddress), (byte*)DeviceAddress, strlen(DeviceAddress));
SerialUSB.println("Setup done");
SerialUSB.println();
}
void loop()
{
float temperature ;
float humidity ;
SerialUSB.println("Loop called");
PayloadReset();
// Read the temperature & humidity & battery voltage values then display nicely
temperature = TH02.ReadTemperature();
SerialUSB.print("T:");
SerialUSB.print( temperature, 1 ) ;
SerialUSB.println( "C " ) ;
PayloadAdd( "T", temperature, 1);
humidity = TH02.ReadHumidity();
SerialUSB.print("H:");
SerialUSB.print( humidity, 0 ) ;
SerialUSB.println( "% " ) ;
PayloadAdd( "H", humidity, 0) ;
#ifdef DEBUG_TELEMETRY
SerialUSB.println();
SerialUSB.print( "RFM9X/SX127X Payload length:");
SerialUSB.print( payloadLength );
SerialUSB.println( " bytes" );
#endif
LoRa.beginPacket();
LoRa.write( payload, payloadLength );
LoRa.endPacket();
SerialUSB.println("Loop done");
SerialUSB.println();
delay(LoopSleepDelaySeconds * 1000l);
}
void PayloadHeader( byte *to, byte toAddressLength, byte *from, byte fromAddressLength)
{
byte addressesLength = toAddressLength + fromAddressLength ;
#ifdef DEBUG_TELEMETRY
SerialUSB.println("PayloadHeader- ");
SerialUSB.print( "To Address len:");
SerialUSB.print( toAddressLength );
SerialUSB.print( " From Address len:");
SerialUSB.print( fromAddressLength );
SerialUSB.print( " Addresses length:");
SerialUSB.print( addressesLength );
SerialUSB.println( );
#endif
payloadLength = 0 ;
// prepare the payload header with "To" Address length (top nibble) and "From" address length (bottom nibble)
payload[payloadLength] = (toAddressLength << 4) | fromAddressLength ;
payloadLength += 1;
// Copy the "To" address into payload
memcpy(&payload[payloadLength], to, toAddressLength);
payloadLength += toAddressLength ;
// Copy the "From" into payload
memcpy(&payload[payloadLength], from, fromAddressLength);
payloadLength += fromAddressLength ;
}
void PayloadAdd( const char *sensorId, float value, byte decimalPlaces)
{
byte sensorIdLength = strlen( sensorId ) ;
#ifdef DEBUG_TELEMETRY
SerialUSB.println("PayloadAdd-float ");
SerialUSB.print( "SensorId:");
SerialUSB.print( sensorId );
SerialUSB.print( " sensorIdLen:");
SerialUSB.print( sensorIdLength );
SerialUSB.print( " Value:");
SerialUSB.print( value, decimalPlaces );
SerialUSB.print( " payloadLength:");
SerialUSB.print( payloadLength);
#endif
memcpy( &payload[payloadLength], sensorId, sensorIdLength) ;
payloadLength += sensorIdLength ;
payload[ payloadLength] = SensorIdValueSeperator;
payloadLength += 1 ;
payloadLength += strlen( dtostrf(value, -1, decimalPlaces, (char *)&payload[payloadLength]));
payload[ payloadLength] = SensorReadingSeperator;
payloadLength += 1 ;
#ifdef DEBUG_TELEMETRY
SerialUSB.print( " payloadLength:");
SerialUSB.print( payloadLength);
SerialUSB.println( );
#endif
}
void PayloadAdd( const char *sensorId, int value )
{
byte sensorIdLength = strlen( sensorId ) ;
#ifdef DEBUG_TELEMETRY
SerialUSB.println("PayloadAdd-int ");
SerialUSB.print( "SensorId:");
SerialUSB.print( sensorId );
SerialUSB.print( " sensorIdLen:");
SerialUSB.print( sensorIdLength );
SerialUSB.print( " Value:");
SerialUSB.print( value );
SerialUSB.print( " payloadLength:");
SerialUSB.print( payloadLength);
#endif
memcpy( &payload[payloadLength], sensorId, sensorIdLength) ;
payloadLength += sensorIdLength ;
payload[ payloadLength] = SensorIdValueSeperator;
payloadLength += 1 ;
payloadLength += strlen( itoa( value,(char *)&payload[payloadLength],10));
payload[ payloadLength] = SensorReadingSeperator;
payloadLength += 1 ;
#ifdef DEBUG_TELEMETRY
SerialUSB.print( " payloadLength:");
SerialUSB.print( payloadLength);
SerialUSB.println( );
#endif
}
void PayloadAdd( const char *sensorId, unsigned int value )
{
byte sensorIdLength = strlen( sensorId ) ;
#ifdef DEBUG_TELEMETRY
SerialUSB.println("PayloadAdd-unsigned int ");
SerialUSB.print( "SensorId:");
SerialUSB.print( sensorId );
SerialUSB.print( " sensorIdLen:");
SerialUSB.print( sensorIdLength );
SerialUSB.print( " Value:");
SerialUSB.print( value );
SerialUSB.print( " payloadLength:");
SerialUSB.print( payloadLength);
#endif
memcpy( &payload[payloadLength], sensorId, sensorIdLength) ;
payloadLength += sensorIdLength ;
payload[ payloadLength] = SensorIdValueSeperator;
payloadLength += 1 ;
payloadLength += strlen( utoa( value,(char *)&payload[payloadLength],10));
payload[ payloadLength] = SensorReadingSeperator;
payloadLength += 1 ;
#ifdef DEBUG_TELEMETRY
SerialUSB.print( " payloadLength:");
SerialUSB.print( payloadLength);
SerialUSB.println( );
#endif
}
void PayloadReset()
{
byte fromAddressLength = payload[0] & 0xf ;
byte toAddressLength = payload[0] >> 4 ;
byte addressesLength = toAddressLength + fromAddressLength ;
payloadLength = addressesLength + 1;
#ifdef DEBUG_TELEMETRY
SerialUSB.println("PayloadReset- ");
SerialUSB.print( "To Address len:");
SerialUSB.print( toAddressLength );
SerialUSB.print( " From Address len:");
SerialUSB.print( fromAddressLength );
SerialUSB.print( " Addresses length:");
SerialUSB.print( addressesLength );
SerialUSB.println( );
#endif
}
The Wisen Bitbucket repository had sample code based on the RadioHead library which was useful for port numbers. This device family supports 433MHz, 868MHz & 915Mz modules. Wisen has other RFM69 based devices as well.
Bill of materials (Prices Sep 2018)
Wizen Whisper Node LoRa (433, 868 or 900 MHz) AUD27.90
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. It has no power conservation, advanced wireless configuration etc.
I needed to use jumpers to connect my device up
/*
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 libraries
#include
#include
const int csPin = 10; // LoRa radio chip select
const int resetPin = 7; // 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[] = {"Wisen01"};
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 messsages 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 debug output window the messages from the device looked like this
20:38:30-RX From Wisen01 PacketSnr 10.0 Packet RSSI -48dBm RSSI -104dBm = 11 byte message "t 22.2,h 91"
Sensor Wisen01t Value 22.2
Sensor Wisen01h Value 91
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x14c0 has exited with code 0 (0x0).
The thread 0x788 has exited with code 0 (0x0).
20:38:40-RX From Wisen01 PacketSnr 9.8 Packet RSSI -48dBm RSSI -103dBm = 11 byte message "t 22.5,h 91"
Sensor Wisen01t Value 22.5
Sensor Wisen01h Value 91
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x1124 has exited with code 0 (0x0).
The thread 0x129c has exited with code 0 (0x0).
20:38:50-RX From Wisen01 PacketSnr 9.3 Packet RSSI -47dBm RSSI -96dBm = 11 byte message "t 22.7,h 91"
Sensor Wisen01t Value 22.7
Sensor Wisen01h Value 91
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x664 has exited with code 0 (0x0).
Then at my Azure IoT hub the data stream looked like this