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);
Enums and Masks – Packet lengths, addressing & CRCs
The RFM69CW/RFM69HCW module (based on the Semtech SX1231/SX1231H) has configurable (RegSyncConfig) synchronisation sequences (the length, tolerance for errors and the individual byte values).
By default synchronisation is enabled and a default sequence of bytes is used, in my library synchronisation is NOT enabled until a SyncValue is provided.
I added some additional constants and enumerations for the other settings configured in RegSyncConfig.
// RegSyncConfig
// This is private because default ignored and flag set based on SyncValues parameter being specified rather than default
private enum RegSyncConfigSyncOn
{
Off = 0b00000000,
On = 0b10000000
}
public enum RegSyncConfigFifoFileCondition
{
SyncAddressInterrupt = 0b00000000,
FifoFillCondition = 0b01000000
}
private const RegSyncConfigFifoFileCondition SyncFifoFileConditionDefault = RegSyncConfigFifoFileCondition.SyncAddressInterrupt;
readonly byte[] SyncValuesDefault = {0x01, 0x01, 0x01, 0x01};
public const byte SyncValuesSizeDefault = 4;
public const byte SyncValuesSizeMinimum = 1;
public const byte SyncValuesSizeMaximum = 8;
private const byte SyncToleranceDefault = 0;
public const byte SyncToleranceMinimum = 0;
public const byte SyncToleranceMaximum = 7;
I also added some guard conditions to the initialise method which validate the syncFifoFileCondition, syncTolerance and syncValues length.
public void Initialise(RegOpModeMode modeAfterInitialise,
BitRate bitRate = BitRateDefault,
ushort frequencyDeviation = frequencyDeviationDefault,
double frequency = FrequencyDefault,
ListenModeIdleResolution listenModeIdleResolution = ListenModeIdleResolutionDefault, ListenModeRXTime listenModeRXTime = ListenModeRXTimeDefault, ListenModeCrieria listenModeCrieria = ListenModeCrieriaDefault, ListenModeEnd listenModeEnd = ListenModeEndDefault,
byte listenCoefficientIdle = ListenCoefficientIdleDefault,
byte listenCoefficientReceive = ListenCoefficientReceiveDefault,
bool pa0On = pa0OnDefault, bool pa1On = pa1OnDefaut, bool pa2On = pa2OnDefault, byte outputpower = OutputpowerDefault,
PaRamp paRamp = PaRampDefault,
bool ocpOn = OcpOnDefault, byte ocpTrim = OcpTrimDefault,
LnaZin lnaZin = LnaZinDefault, LnaCurrentGain lnaCurrentGain = LnaCurrentGainDefault, LnaGainSelect lnaGainSelect = LnaGainSelectDefault,
byte dccFrequency = DccFrequencyDefault, RxBwMant rxBwMant = RxBwMantDefault, byte RxBwExp = RxBwExpDefault,
byte dccFreqAfc = DccFreqAfcDefault, byte rxBwMantAfc = RxBwMantAfcDefault, byte bxBwExpAfc = RxBwExpAfcDefault,
ushort preambleSize = PreambleSizeDefault,
RegSyncConfigFifoFileCondition? syncFifoFileCondition = null, byte? syncTolerance = null, byte[] syncValues = null,
RegPacketConfig1PacketFormat packetFormat = RegPacketConfig1PacketFormat.FixedLength,
RegPacketConfig1DcFree packetDcFree = RegPacketConfig1DcFreeDefault,
bool packetCrc = PacketCrcOnDefault,
bool packetCrcAutoClearOff = PacketCrcAutoClearOffDefault,
RegPacketConfig1CrcAddressFiltering packetAddressFiltering = PacketAddressFilteringDefault,
byte payloadLength = PayloadLengthDefault,
byte addressNode = NodeAddressDefault, byte addressbroadcast = BroadcastAddressDefault,
TxStartCondition txStartCondition = TxStartConditionDefault, byte fifoThreshold = FifoThresholdDefault,
byte interPacketRxDelay = InterPacketRxDelayDefault, bool restartRx = RestartRxDefault, bool autoRestartRx = AutoRestartRxDefault,
byte[] aesKey = null
)
{
RegOpModeModeCurrent = modeAfterInitialise;
PacketFormat = packetFormat;
#region RegSyncConfig + RegSyncValue1 to RegSyncValue8 guard conditions
if (syncValues != null)
{
// If sync enabled (i.e. SyncValues array provided) check that SyncValues not to short/long and SyncTolerance not to small/big
if ((syncValues.Length < SyncValuesSizeMinimum) || (syncValues.Length > SyncValuesSizeMaximum))
{
throw new ArgumentException($"The syncValues array length must be between {SyncValuesSizeMinimum} and {SyncValuesSizeMaximum} bytes", "syncValues");
}
if (syncTolerance.HasValue)
{
if ((syncTolerance < SyncToleranceMinimum) || (syncTolerance > SyncToleranceMaximum))
{
throw new ArgumentException($"The syncTolerance size must be between {SyncToleranceMinimum} and {SyncToleranceMaximum}", "syncTolerance");
}
}
}
else
{
// If sync not enabled (i.e. SyncValues array null) check that no syncFifoFileCondition or syncTolerance configuration specified
if (syncFifoFileCondition.HasValue)
{
throw new ArgumentException($"If Sync not enabled syncFifoFileCondition is not supported", "syncFifoFileCondition");
}
if (syncTolerance.HasValue)
{
throw new ArgumentException($"If Sync not enabled SyncTolerance is not supported", "syncTolerance");
}
}
#endregion
I also ensure that the syncFifoFileCondition and syncTolerance are not specified if synchronisation is not enabled.
The library also supports the built in RFRM69 node and broadcast addressing which is enabled when the AddressNode and/or AddressBroadcast parameters of the Initialise method are set.
RegPacketConfig1 address filtering options
My first attempt at getting encryption and addressing working together failed badly, the Windows 10 IoT Core device didn’t receive any addressed messages when encryption was enabled. So, I went back and re-read the datasheet again and noticed
“If the address filtering is expected then AddressFiltering must be enabled on the transmitter side as well to prevent address byte to be encrypted”(Sic).
The Arduino client code had to be modified so I could set the node + broadcast address registers and AddressFiltering bit flag in RegPacketConfig1
My RMRFM69.h modifications
enum moduleType {RFM65, RFM65C, RFM69, RFM69C, RFM69H, RFM69HC};
#define ADDRESS_NODE_DEFAULT 0x0
#define ADDRESS_BROADCAST_DEFAULT 0x0
#define ADDRESSING_ENABLED_NODE 0x2
#define ADDRESSING_ENABLED_NODE_AND_BROADCAST 0x4
class RMRFM69
{
public:
RMRFM69(SPIClass &spiPort, byte csPin, byte dio0Pin, byte rstPin);
modulationType Modulation; //OOK/FSK/GFSK
moduleType COB; //Chip on board
uint32_t Frequency; //unit: KHz
uint32_t SymbolTime; //unit: ns
uint32_t Devation; //unit: KHz
word BandWidth; //unit: KHz
byte OutputPower; //unit: dBm range: 0-31 [-18dBm~+13dBm] for RFM69/RFM69C
// range: 0-31 [-11dBm~+20dBm] for RFM69H/RFM69HC
word PreambleLength; //unit: byte
bool CrcDisable; //fasle: CRC enable�� & use CCITT 16bit
//true : CRC disable
bool CrcMode; //false: CCITT
bool FixedPktLength; //false: for contain packet length in Tx message, the same mean with variable lenth
//true : for doesn't include packet length in Tx message, the same mean with fixed length
bool AesOn; //false:
//true:
bool AfcOn; //false:
//true:
byte SyncLength; //unit: none, range: 1-8[Byte], value '0' is not allowed!
byte SyncWord[8];
byte PayloadLength; //PayloadLength is need to be set a value, when FixedPktLength is true.
byte AesKey[16]; //AES Key block, note [0]->[15] == MSB->LSB
byte AddressNode = ADDRESS_NODE_DEFAULT;
byte AddressBroadcast = ADDRESS_BROADCAST_DEFAULT;
void vInitialize(void);
void vConfig(void);
void vGoRx(void);
void vGoStandby(void);
void vGoSleep(void);
bool bSendMessage(byte msg[], byte length);
bool bSendMessage(byte Address, byte msg[], byte length);
byte bGetMessage(byte msg[]);
void vRF69SetAesKey(void);
void vTrigAfc(void);
void vDirectRx(void); //go continuous rx mode, with init. inside
void vChangeFreq(uint32_t freq); //change frequency
byte bReadRssi(void); //read rssi value
void dumpRegisters(Stream& out);
My RMRFM69.cpp modifications in vConfig
if(!CrcDisable)
{
i += CrcOn;
if(CrcMode)
i += CrcCalc_IBM;
else
i += CrcCalc_CCITT;
}
if((AddressNode!=ADDRESS_NODE_DEFAULT) || (AddressBroadcast==ADDRESS_BROADCAST_DEFAULT))
{
i += ADDRESSING_ENABLED_NODE;
}
if((AddressNode!=ADDRESS_NODE_DEFAULT) || (AddressBroadcast!=ADDRESS_BROADCAST_DEFAULT))
{
i += ADDRESSING_ENABLED_NODE_AND_BROADCAST;
}
vSpiWrite(((word)RegPacketConfig1<<8)+i);
I also validate the lengths of the messages to be sent taking into account whether encryption is enabled\disabled.
Grove – 4 pin Female Jumper to Grove 4 pin Conversion Cable USD3.90
The two sockets on the main board aren’t Grove compatible so I used the 4 pin female to Grove 4 pin conversion cable to connect the temperature and humidity sensor.
STM32 Blue Pill LoRaWAN node test rig
I used a modified version of my Arduino client code which worked after I got the pin reset pin sorted and the female sockets in the right order.
/*
Copyright ® 2019 July 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.
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 <itoa.h>
#include <SPI.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 DeviceAddress[] = {"BLUEPILL"};
// Azure IoT Hub FieldGateway
const char FieldGatewayAddress[] = {"LoRaIoT1"};
const float FieldGatewayFrequency = 915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;
// Bluepill hardware configuration
const int ChipSelectPin = PA4;
const int InterruptPin = PA0;
const int ResetPin = -1;
// LoRa radio payload configuration
const byte SensorIdValueSeperator = ' ' ;
const byte SensorReadingSeperator = ',' ;
const byte PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum];
byte payloadLength = 0 ;
const int LoopDelaySeconds = 300 ;
// Sensor configuration
const char SensorIdTemperature[] = {"t"};
const char SensorIdHumidity[] = {"h"};
void setup()
{
Serial.begin(9600);
#ifdef DEBUG
while (!Serial);
#endif
Serial.println("Setup called");
Serial.println("LoRa setup start");
// override the default chip select and reset pins
LoRa.setPins(ChipSelectPin, ResetPin, InterruptPin);
if (!LoRa.begin(FieldGatewayFrequency))
{
Serial.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(Serial);
#endif
Serial.println("LoRa setup done.");
PayloadHeader((byte*)FieldGatewayAddress, strlen(FieldGatewayAddress), (byte*)DeviceAddress, strlen(DeviceAddress));
// 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() {
// read the value from the sensor:
double temperature = TH02.ReadTemperature();
double humidity = TH02.ReadHumidity();
Serial.print("Humidity: ");
Serial.print(humidity, 0);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(temperature, 1);
Serial.println(" *C");
PayloadReset();
PayloadAdd(SensorIdHumidity, humidity, 0) ;
PayloadAdd(SensorIdTemperature, temperature, 1) ;
LoRa.beginPacket();
LoRa.write(payload, payloadLength);
LoRa.endPacket();
Serial.println("Loop done");
delay(LoopDelaySeconds * 1000);
}
void PayloadHeader( byte *to, byte toAddressLength, byte *from, byte fromAddressLength)
{
byte addressesLength = toAddressLength + fromAddressLength ;
#ifdef DEBUG_TELEMETRY
Serial.println("PayloadHeader- ");
Serial.print( "To Address len:");
Serial.print( toAddressLength );
Serial.print( " From Address len:");
Serial.print( fromAddressLength );
Serial.print( " Addresses length:");
Serial.print( addressesLength );
Serial.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
Serial.println("PayloadAdd-float ");
Serial.print( "SensorId:");
Serial.print( sensorId );
Serial.print( " sensorIdLen:");
Serial.print( sensorIdLength );
Serial.print( " Value:");
Serial.print( value, decimalPlaces );
Serial.print( " payloadLength:");
Serial.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
Serial.print( " payloadLength:");
Serial.print( payloadLength);
Serial.println( );
#endif
}
void PayloadAdd( const char *sensorId, int value )
{
byte sensorIdLength = strlen( sensorId ) ;
#ifdef DEBUG_TELEMETRY
Serial.println("PayloadAdd-int ");
Serial.print( "SensorId:");
Serial.print( sensorId );
Serial.print( " sensorIdLen:");
Serial.print( sensorIdLength );
Serial.print( " Value:");
Serial.print( value );
Serial.print( " payloadLength:");
Serial.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
Serial.print( " payloadLength:");
Serial.print( payloadLength);
Serial.println( );
#endif
}
void PayloadAdd( const char *sensorId, unsigned int value )
{
byte sensorIdLength = strlen( sensorId ) ;
#ifdef DEBUG_TELEMETRY
Serial.println("PayloadAdd-unsigned int ");
Serial.print( "SensorId:");
Serial.print( sensorId );
Serial.print( " sensorIdLen:");
Serial.print( sensorIdLength );
Serial.print( " Value:");
Serial.print( value );
Serial.print( " payloadLength:");
Serial.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
Serial.print( " payloadLength:");
Serial.print( payloadLength);
Serial.println( );
#endif
}
void PayloadReset()
{
byte fromAddressLength = payload[0] & 0xf ;
byte toAddressLength = payload[0] >> 4 ;
byte addressesLength = toAddressLength + fromAddressLength ;
payloadLength = addressesLength + 1;
#ifdef DEBUG_TELEMETRY
Serial.println("PayloadReset- ");
Serial.print( "To Address len:");
Serial.print( toAddressLength );
Serial.print( " From Address len:");
Serial.print( fromAddressLength );
Serial.print( " Addresses length:");
Serial.print( addressesLength );
Serial.println( );
#endif
}
To get the application to compile I also had to include itoa.h rather than stdlib.h.
maple_loader v0.1
Resetting to bootloader via DTR pulse
[Reset via USB Serial Failed! Did you select the right serial port?]
Searching for DFU device [1EAF:0003]...
Assuming the board is in perpetual bootloader mode and continuing to attempt dfu programming...
dfu-util - (C) 2007-2008 by OpenMoko Inc.
Initially I had some problems deploying my software because I hadn’t followed the instructions and run the installation batch file.
I configured the device to upload to my Azure IoT Hub/Azure IoT Central gateway and after getting the device name configuration right it has been running reliably for a couple of days
Azure IoT Central Temperature and humidity
The device was sitting outside on the deck and rapid increase in temperature is me bringing it inside.
The RFM69CW/RFM69HCW modules (based on the Semtech SX1231/SX1231H) have built in support for addressing individual devices (register RegNodeAdrs 0x39) or broadcasting to groups of devices (register RegBroadcastAdrs 0x3A). In this test harness I’m exploring the RFM69 device support for these two different addressing modes which is configured in RegPacketConfig1 0x37.
RFM69 Address filtering options
The fixed length packet format contains the following fields
Preamble (1010…)
Sync word (Network ID)
Optional Address byte (Node ID)
Message data
Optional 2-bytes CRC checksum
Fixed length packet format
The variable length packet format contains the following fields
Preamble (1010…)
Sync word (Network ID)
Length byte
Optional Address byte (Node ID)
Message data
Optional 2-bytes CRC checksum
Variable length packet format
My first attempt at addressing was by modifying the payload (the extra space at the start of the payload was replaced by the target device address)
Initially it truncated messages because I neglected to include the byte with the length of the message in the length of the message. I also had to extend the timeout for sending a message a bit more than I expected for one extra byte.
bool RMRFM69::bSendMessage(byte address, byte msg[], byte length)
{
byte tmp;
uint32_t overtime;
word bittime;
switch(COB)
{
case RFM65: //only for Rx
case RFM65C:
return(false);
case RFM69H:
case RFM69HC:
vSpiWrite(((word)RegTestPa1<<8)+0x5D); //for HighPower
vSpiWrite(((word)RegTestPa2<<8)+0x7C);
break;
default:
case RFM69:
case RFM69C:
vSpiWrite(((word)RegTestPa1<<8)+0x55); //for NormalMode or RxMode
vSpiWrite(((word)RegTestPa2<<8)+0x70);
break;
}
vSpiWrite(((word)RegDioMapping1<<8)+0x04); //DIO0 PacketSend / DIO1 FiflLevel / DIO2 Data /DIO3 FifoFull
if(!FixedPktLength)
vSpiWrite(((word)RegFifo<<8)+length+1);
vSpiWrite(((word)RegFifo<<8)+address);
vSpiBurstWrite(RegFifo, msg, length);
tmp = bSpiRead(RegOpMode);
tmp&= MODE_MASK;
tmp |= RADIO_TX;
vSpiWrite(((word)RegOpMode<<8)+tmp);
//�ȴ��������
bittime = SymbolTime/1000; //unit: us
overtime = SyncLength+PreambleLength+length+1;
if(!FixedPktLength) //SyncWord & PktLength & 2ByteCRC
overtime += 1;
if(!CrcDisable)
overtime += 2;
overtime<<=3; //8bit == 1byte
overtime*= bittime;
overtime/= 1000; //unit: ms
if(overtime==0)
overtime = 1;
overtime += (overtime>>3); //add 12.5% for ensure
delay(overtime); //
for(tmp=0;tmp<1000;tmp++) //about 50ms for overtime
{
if(digitalRead(_dio0Pin))
break;
delayMicroseconds(500);
}
vGoStandby();
if(tmp>=200)
return(false);
else
return(true);
}
The Windows 10 IoT Core library interrupt handler needed some modification to display message only when the address matched and I also displayed the targeted address so I could check that device and broadcast addressing was working
/*
Copyright ® 2019 July 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
*/
namespace devMobile.IoT.Rfm69Hcw.Addressing
{
using System;
using System.Diagnostics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Devices.Gpio;
using Windows.Devices.Spi;
public sealed class Rfm69HcwDevice
{
private SpiDevice Rfm69Hcw;
private GpioPin InterruptGpioPin = null;
private const byte RegisterAddressReadMask = 0X7f;
private const byte RegisterAddressWriteMask = 0x80;
public Rfm69HcwDevice(int chipSelectPin, int resetPin, int interruptPin)
{
SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
var settings = new SpiConnectionSettings(chipSelectPin)
{
ClockFrequency = 500000,
Mode = SpiMode.Mode0,
};
// Factory reset pin configuration
GpioController gpioController = GpioController.GetDefault();
GpioPin resetGpioPin = gpioController.OpenPin(resetPin);
resetGpioPin.SetDriveMode(GpioPinDriveMode.Output);
resetGpioPin.Write(GpioPinValue.High);
Task.Delay(100);
resetGpioPin.Write(GpioPinValue.Low);
Task.Delay(10);
// Interrupt pin for RX message & TX done notification
InterruptGpioPin = gpioController.OpenPin(interruptPin);
resetGpioPin.SetDriveMode(GpioPinDriveMode.Input);
InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;
Rfm69Hcw = spiController.GetDevice(settings);
}
private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
{
if (args.Edge != GpioPinEdge.RisingEdge)
{
return;
}
byte irqFlags2 = this.RegisterReadByte(0x28); // RegIrqFlags2
Debug.WriteLine("{0:HH:mm:ss.fff} RegIrqFlags2 {1}", DateTime.Now, Convert.ToString((byte)irqFlags2, 2).PadLeft(8, '0'));
if ((irqFlags2 & 0b00000100) == 0b00000100) // PayLoadReady set
{
byte irqFlags1 = this.RegisterReadByte(0x27); // RegIrqFlags1
// Read the length of the buffer
byte numberOfBytes = this.RegisterReadByte(0x0);
Debug.WriteLine("{0:HH:mm:ss.fff} RegIrqFlags1 {1}", DateTime.Now, Convert.ToString((byte)irqFlags1, 2).PadLeft(8, '0'));
if ((irqFlags1 & 0b00000001) == 0b00000001) // SyncAddressMatch
{
byte address = this.RegisterReadByte(0x0);
Debug.WriteLine("{0:HH:mm:ss.fff} Address 0X{1:X2} b{2}", DateTime.Now, address, Convert.ToString((byte)address, 2).PadLeft(8, '0'));
numberOfBytes--;
}
// Allocate buffer for message
byte[] messageBytes = new byte[numberOfBytes];
for (int i = 0; i < numberOfBytes; i++)
{
messageBytes[i] = this.RegisterReadByte(0x00); // RegFifo
}
string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
Debug.WriteLine("{0:HH:mm:ss} Received:{1} byte message({2})", DateTime.Now, messageBytes.Length, messageText);
}
if ((irqFlags2 & 0b00001000) == 0b00001000) // PacketSent set
{
this.RegisterWriteByte(0x01, 0b00010000); // RegOpMode set ReceiveMode
Debug.WriteLine("{0:HH:mm:ss.fff} Transmit-Done", DateTime.Now);
}
}
public Byte RegisterReadByte(byte address)
{
byte[] writeBuffer = new byte[] { address &= RegisterAddressReadMask };
byte[] readBuffer = new byte[1];
Debug.Assert(Rfm69Hcw != null);
Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);
return readBuffer[0];
}
public byte[] RegisterRead(byte address, int length)
{
byte[] writeBuffer = new byte[] { address &= RegisterAddressReadMask };
byte[] readBuffer = new byte[length];
Debug.Assert(Rfm69Hcw != null);
Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);
return readBuffer;
}
public void RegisterWriteByte(byte address, byte value)
{
byte[] writeBuffer = new byte[] { address |= RegisterAddressWriteMask, value };
Debug.Assert(Rfm69Hcw != null);
Rfm69Hcw.Write(writeBuffer);
}
public void RegisterWrite(byte address, [ReadOnlyArray()] byte[] bytes)
{
byte[] writeBuffer = new byte[1 + bytes.Length];
Debug.Assert(Rfm69Hcw != null);
Array.Copy(bytes, 0, writeBuffer, 1, bytes.Length);
writeBuffer[0] = address |= RegisterAddressWriteMask;
Rfm69Hcw.Write(writeBuffer);
}
public void RegisterDump()
{
Debug.WriteLine("Register dump");
for (byte registerIndex = 0; registerIndex <= 0x3D; registerIndex++)
{
byte registerValue = this.RegisterReadByte(registerIndex);
Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
}
}
}
public sealed class StartupTask : IBackgroundTask
{
private const int ChipSelectLine = 1;
private const int ResetPin = 25;
private const int InterruptPin = 22;
private Rfm69HcwDevice rfm69Device = new Rfm69HcwDevice(ChipSelectLine, ResetPin, InterruptPin);
const double RH_RF6M9HCW_FXOSC = 32000000.0;
const double RH_RFM69HCW_FSTEP = RH_RF6M9HCW_FXOSC / 524288.0;
public void Run(IBackgroundTaskInstance taskInstance)
{
//rfm69Device.RegisterDump();
// regOpMode standby
rfm69Device.RegisterWriteByte(0x01, 0b00000100);
// BitRate MSB/LSB
rfm69Device.RegisterWriteByte(0x03, 0x34);
rfm69Device.RegisterWriteByte(0x04, 0x00);
// Frequency deviation
rfm69Device.RegisterWriteByte(0x05, 0x02);
rfm69Device.RegisterWriteByte(0x06, 0x3d);
// Calculate the frequency accoring to the datasheett
byte[] bytes = BitConverter.GetBytes((uint)(915000000.0 / RH_RFM69HCW_FSTEP));
Debug.WriteLine("Byte Hex 0x{0:x2} 0x{1:x2} 0x{2:x2} 0x{3:x2}", bytes[0], bytes[1], bytes[2], bytes[3]);
rfm69Device.RegisterWriteByte(0x07, bytes[2]);
rfm69Device.RegisterWriteByte(0x08, bytes[1]);
rfm69Device.RegisterWriteByte(0x09, bytes[0]);
// RegRxBW
rfm69Device.RegisterWriteByte(0x19, 0x2a);
// RegDioMapping1
rfm69Device.RegisterWriteByte(0x26, 0x01);
// Setup preamble length to 16 (default is 3) RegPreambleMsb RegPreambleLsb
rfm69Device.RegisterWriteByte(0x2C, 0x0);
rfm69Device.RegisterWriteByte(0x2D, 0x10);
// RegSyncConfig Set the Sync length and byte values SyncOn + 3 custom sync bytes
rfm69Device.RegisterWriteByte(0x2e, 0x90);
// RegSyncValues1 thru RegSyncValues3
rfm69Device.RegisterWriteByte(0x2f, 0xAA);
rfm69Device.RegisterWriteByte(0x30, 0x2D);
rfm69Device.RegisterWriteByte(0x31, 0xD4);
// RegPacketConfig1 Variable length with CRC on
//rfm69Device.RegisterWriteByte(0x37, 0x90);
// RegPacketConfig1 Variable length with CRC on + NodeAddress
//rfm69Device.RegisterWriteByte(0x37, 0x92);
// RegPacketConfig1 Variable length with CRC on + NodeAddress & Broadcast Address
rfm69Device.RegisterWriteByte(0x37, 0x94);
// RegNodeAdrs
rfm69Device.RegisterWriteByte(0x39, 0x99);
// RegBroadcastAdrs
rfm69Device.RegisterWriteByte(0x3A, 0x66);
rfm69Device.RegisterDump();
rfm69Device.RegisterWriteByte(0x01, 0b00010000); // RegOpMode set ReceiveMode
while (true)
{
Debug.Write(".");
Task.Delay(1000).Wait();
}
}
}
}
The debug output window shows the flags and messages
The next steps will be getting the RFM69 message encryption going, then building a fully featured library based on the code in each of individual test harnesses.
// <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 unique identifier provided by the SHA204A crypto and authentication chip on the EasySensors shield highlighted this issue. The Binary Coded Decimal(BCD) version of the 72 bit identifier was too long to fit in the from address.
My later Arduino based sample clients have some helper functions to populate the message header, add values, and prepare the message payload for reuse.
On the server side I have added code to log the build version and Raspbery PI shield type
// Log the Application build, shield information etc.
LoggingFields appllicationBuildInformation = new LoggingFields();
#if DRAGINO
appllicationBuildInformation.AddString("Shield", "DraginoLoRaGPSHat");
#endif
…
#if UPUTRONICS_RPIPLUS_CS1
appllicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS1");
#endif
appllicationBuildInformation.AddString("Timezone", TimeZoneSettings.CurrentTimeZoneDisplayName);
appllicationBuildInformation.AddString("OSVersion", Environment.OSVersion.VersionString);
appllicationBuildInformation.AddString("MachineName", Environment.MachineName);
// This is from the application manifest
Package package = Package.Current;
PackageId packageId = package.Id;
PackageVersion version = packageId.Version;
appllicationBuildInformation.AddString("ApplicationVersion", string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"));
this.loggingChannel.LogEvent("Application starting", appllicationBuildInformation, LoggingLevel.Information);
Then when the message payload is populated the from address byte array is converted to BCD
private async void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
{
string addressBcdText;
string messageBcdText;
string messageText = "";
char[] sensorReadingSeparator = new char[] { ',' };
char[] sensorIdAndValueSeparator = new char[] { ' ' };
addressBcdText = BitConverter.ToString(e.Address);
messageBcdText = BitConverter.ToString(e.Data);
try
{
messageText = UTF8Encoding.UTF8.GetString(e.Data);
}
catch (Exception)
{
this.loggingChannel.LogMessage("Failure converting payload to text", LoggingLevel.Error);
return;
}
#if DEBUG
Debug.WriteLine(@"{0:HH:mm:ss}-RX From {1} PacketSnr {2:0.0} Packet RSSI {3}dBm RSSI {4}dBm = {5} byte message ""{6}""", DateTime.Now, addressBcdText, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText);
#endif
LoggingFields messagePayload = new LoggingFields();
messagePayload.AddInt32("AddressLength", e.Address.Length);
messagePayload.AddString("Address-BCD", addressBcdText);
messagePayload.AddInt32("Message-Length", e.Data.Length);
messagePayload.AddString("Message-BCD", messageBcdText);
messagePayload.AddString("Nessage-Unicode", messageText);
messagePayload.AddDouble("Packet SNR", e.PacketSnr);
messagePayload.AddInt32("Packet RSSI", e.PacketRssi);
messagePayload.AddInt32("RSSI", e.Rssi);
this.loggingChannel.LogEvent("Message Data", messagePayload, LoggingLevel.Verbose);
// Check the address is not to short/long
if (e.Address.Length < AddressLengthMinimum)
{
this.loggingChannel.LogMessage("From address too short", LoggingLevel.Warning);
return;
}
if (e.Address.Length > MessageLengthMaximum)
{
this.loggingChannel.LogMessage("From address too long", LoggingLevel.Warning);
return;
}
// Check the payload is not too short/long
if (e.Data.Length < MessageLengthMinimum)
{
this.loggingChannel.LogMessage("Message too short to contain any data", LoggingLevel.Warning);
return;
}
if (e.Data.Length > MessageLengthMaximum)
{
this.loggingChannel.LogMessage("Message too long to contain valid data", LoggingLevel.Warning);
return;
}
// Adafruit IO is case sensitive & only does lower case ?
string deviceId = addressBcdText.ToLower();
// Chop up the CSV text payload
string[] sensorReadings = messageText.Split(sensorReadingSeparator, StringSplitOptions.RemoveEmptyEntries);
if (sensorReadings.Length == 0)
{
this.loggingChannel.LogMessage("Payload contains no sensor readings", LoggingLevel.Warning);
return;
}
Group_feed_data groupFeedData = new Group_feed_data();
LoggingFields sensorData = new LoggingFields();
sensorData.AddString("DeviceID", deviceId);
// Chop up each sensor reading into an ID & value
foreach (string sensorReading in sensorReadings)
{
string[] sensorIdAndValue = sensorReading.Split(sensorIdAndValueSeparator, StringSplitOptions.RemoveEmptyEntries);
// Check that there is an id & value
if (sensorIdAndValue.Length != 2)
{
this.loggingChannel.LogMessage("Sensor reading invalid format", LoggingLevel.Warning);
return;
}
string sensorId = sensorIdAndValue[0].ToLower();
string value = sensorIdAndValue[1];
// Construct the sensor ID from SensordeviceID & Value ID
groupFeedData.Feeds.Add(new Anonymous2() { Key = string.Format("{0}{1}", deviceId, sensorId), Value = value });
sensorData.AddString(sensorId, value);
Debug.WriteLine(" Sensor {0}{1} Value {2}", deviceId, sensorId, value);
}
this.loggingChannel.LogEvent("Sensor readings", sensorData, LoggingLevel.Verbose);
try
{
Debug.WriteLine(" CreateGroupDataAsync start");
await this.adaFruitIOClient.CreateGroupDataAsync(this.applicationSettings.AdaFruitIOUserName,
this.applicationSettings.AdaFruitIOGroupName.ToLower(), groupFeedData);
Debug.WriteLine(" CreateGroupDataAsync finish");
}
catch (Exception ex)
{
Debug.WriteLine(" CreateGroupDataAsync failed {0}", ex.Message);
this.loggingChannel.LogMessage("CreateGroupDataAsync failed " + ex.Message, LoggingLevel.Error);
}
}
AfaFruit.IO Data Display
This does mean longer field names but I usually copy n paste them from the Arduino serial monitor or the Event Tracing For Windows (ETW) logging.
The unique identifier provided by the SHA204A crypto and authentication chip on the EasySensors shield highlighted this issue. The Binary Coded Decimal(BCD) version of the 72 bit identifier was too long to fit in the from address.
My Arduino MKR1300 sample code has some helper functions to populate the message header, add values, and prepare the message payload for reuse.
On the server side I have added code to log the build version and Raspbery PI shield type
// Log the Application build, shield information etc.
LoggingFields appllicationBuildInformation = new LoggingFields();
#if DRAGINO
appllicationBuildInformation.AddString("Shield", "DraginoLoRaGPSHat");
#endif
...
#if UPUTRONICS_RPIPLUS_CS1
appllicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS1");
#endif
appllicationBuildInformation.AddString("Timezone", TimeZoneSettings.CurrentTimeZoneDisplayName);
appllicationBuildInformation.AddString("OSVersion", Environment.OSVersion.VersionString);
appllicationBuildInformation.AddString("MachineName", Environment.MachineName);
// This is from the application manifest
Package package = Package.Current;
PackageId packageId = package.Id;
PackageVersion version = packageId.Version;
appllicationBuildInformation.AddString("ApplicationVersion", string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"));
this.logging.LogEvent("Application starting", appllicationBuildInformation, LoggingLevel.Information);
Then when the message payload is populated the from address byte array is converted to BCD
private async void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
{
string addressBcdText;
string messageBcdText;
string messageText = "";
char[] sensorReadingSeparators = new char[] { ',' };
char[] sensorIdAndValueSeparators = new char[] { ' ' };
addressBcdText = BitConverter.ToString(e.Address);
messageBcdText = BitConverter.ToString(e.Data);
try
{
messageText = UTF8Encoding.UTF8.GetString(e.Data);
}
catch (Exception)
{
this.logging.LogMessage("Failure converting payload to text",
LoggingLevel.Error);
return;
}
#if DEBUG
Debug.WriteLine(@"{0:HH:mm:ss}-RX From {1} PacketSnr {2:0.0} Packet
RSSI {3}dBm RSSI {4}dBm = {5} byte message ""{6}""", DateTime.Now,
messageBcdText, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length,
messageText);
#endif
LoggingFields messagePayload = new LoggingFields();
messagePayload.AddInt32("AddressLength", e.Address.Length);
messagePayload.AddString("Address-BCD", addressBcdText);
messagePayload.AddInt32("Message-Length", e.Data.Length);
messagePayload.AddString("Message-BCD", messageBcdText);
messagePayload.AddString("Message-Unicode", messageText);
messagePayload.AddDouble("Packet SNR", e.PacketSnr);
messagePayload.AddInt32("Packet RSSI", e.PacketRssi);
messagePayload.AddInt32("RSSI", e.Rssi);
this.logging.LogEvent("Message Data", messagePayload, LoggingLevel.Verbose);
//...
JObject telemetryDataPoint = new JObject(); // This could be simplified but for field gateway will use this style
LoggingFields sensorData = new LoggingFields();
telemetryDataPoint.Add("DeviceID", addressBcdText);
sensorData.AddString("DeviceID", addressBcdText);
telemetryDataPoint.Add("PacketSNR", e.PacketSnr.ToString("F1"));
sensorData.AddString("PacketSNR", e.PacketSnr.ToString("F1"));
telemetryDataPoint.Add("PacketRSSI", e.PacketRssi);
sensorData.AddInt32("PacketRSSI", e.PacketRssi);
telemetryDataPoint.Add("RSSI", e.Rssi);
sensorData.AddInt32("RSSI", e.Rssi);
//Chop up each sensor read into an ID & value
foreach (string sensorReading in sensorReadings)
{
string[] sensorIdAndValue = sensorReading.Split(sensorIdAndValueSeparators, StringSplitOptions.RemoveEmptyEntries);
// Check that there is an id & value
if (sensorIdAndValue.Length != 2)
{
this.logging.LogMessage("Sensor reading invalid format", LoggingLevel.Warning);
return;
}
string sensorId = sensorIdAndValue[0];
string value = sensorIdAndValue[1];
try
{
if (this.applicationSettings.SensorIDIsDeviceIDSensorID)
{
// Construct the sensor ID from SensordeviceID & Value ID
telemetryDataPoint.Add(string.Format("{0}{1}", addressBcdText, sensorId), value);
sensorData.AddString(string.Format("{0}{1}", addressBcdText, sensorId), value);
Debug.WriteLine(" Sensor {0}{1} Value {2}", addressBcdText, sensorId, value);
}
else
{
telemetryDataPoint.Add(sensorId, value);
sensorData.AddString(sensorId, value);
Debug.WriteLine(" Device {0} Sensor {1} Value {2}", addressBcdText, sensorId, value);
}
}
catch (Exception ex)
{
this.logging.LogMessage("Sensor reading invalid JSON format " + ex.Message, LoggingLevel.Warning);
return;
}
}
this.logging.LogEvent("Sensor readings", sensorData, LoggingLevel.Information);
try
{
using (Message message = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(telemetryDataPoint))))
{
Debug.WriteLine(" AzureIoTHubClient SendEventAsync start");
await this.azureIoTHubClient.SendEventAsync(message);
Debug.WriteLine(" AzureIoTHubClient SendEventAsync finish");
}
}
catch (Exception ex)
{
this.logging.LogMessage("AzureIoTHubClient SendEventAsync failed " + ex.Message, LoggingLevel.Error);
}
}
This does mean longer field names but I usually copy n paste them from the Arduino serial monitor of the Event Tracing For Windows (ETW) logging.