SparkFun Pro RF – LoRa, 915MHz Payload Addressing Client

Last week a package arrived from NiceGear with a SparkFun Pro RF – LoRa, 915MHz and some cables. With this gear I have built yet another client for my Azure IoT Hub and AdaFruit.IOLoRa Field Gateways.

Now that the device is running well, I’ll look at reducing power consumption and splitting the the payload packing code into a library. Also noticed an extra “,” on the end of a message so need to come up with a better way of doing the payload packing.

/*
  Copyright ® 2018 December devMobile Software, All Rights Reserved

  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  PURPOSE.

  You can do what you want with this code, acknowledgment would be nice.

  http://www.devmobile.co.nz

*/
#include <stdlib.h>

#include <LoRa.h>
#include <avr/dtostrf.h>
#include "DHT.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[] = {"SparkFunX1"};
const float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

// Payload configuration
const int InterruptPin = 12;
const int ChipSelectPin = 6;

// LoRa radio payload configuration
const byte SensorIdValueSeperator = ' ' ;
const byte SensorReadingSeperator = ',' ;
const int LoopSleepDelaySeconds = 10 ;

const byte PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum];
byte payloadLength = 0 ;

#define DHTPIN 4     // what digital pin we're connected to

// Uncomment whatever type you're using!
//#define DHTTYPE DHT11   // DHT 11
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);


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(InterruptPin, ChipSelectPin); 
  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(SerialUSB);
#endif
  SerialUSB.println("LoRa Setup done.");

  // Configure the Seeedstudio TH02 temperature & humidity sensor
  SerialUSB.println("DHT setup start");
  dht.begin();
  delay(100);
  SerialUSB.println("DHT setup done");

  PayloadHeader((byte*)FieldGatewayAddress,strlen(FieldGatewayAddress), (byte*)DeviceAddress, strlen(DeviceAddress));

  SerialUSB.println("Setup done");
  SerialUSB.println();
}


void loop()
{
  float temperature = 17.2;
  float humidity = 75.0;

  SerialUSB.println("Loop called");

  PayloadReset();

  // Read the temperature & humidity & battery voltage values then display nicely
  temperature = dht.readTemperature();
  humidity = dht.readHumidity();
  if (isnan(humidity) || isnan(temperature)) 
  {
    SerialUSB.println("Failed to read from DHT sensor!");
    return;
  } 
   
  SerialUSB.print("T:");
  SerialUSB.print( temperature, 1 ) ;
  SerialUSB.println( "C " ) ;
  PayloadAdd( "T", temperature, 1);

  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( 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( 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( 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
}
  • SparkFun Pro RF – LoRa, 915MHz USD29.95 NZD49
  • Grove – Temperature & Humidity Sensor Pro (AM2302) USD9.90
  • Seeedstudio 4 pin Male Jumper to Grove 4 pin Conversion Cable USD2.90

Moteino M0 Payload Addressing client

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
}
Azure IoT Central

Bill of materials (prices as at November 2018)

  • Moteino M0 USD34.95
  • Seeedstudio Temperature and Humidity Sensor Pro USD11.50
  • Seeedstudio 4 pin Male Jumper to Grove 4 pin Conversion Cable USD2.90

Arduino MKR1300 WAN Payload Addressing client

Last week a package arrived from SeeedStudio with some Arduino devices and Grove shields. With this gear I have built yet another client for my Azure IoT Hub and AdaFruit.IO  LoRa Field Gateways.

For my application I directly access the on-board Semtech SX127X chip by passing the Murata CMWX1ZZABZ functionality. To do this I (November 2018) I had to upgrade the device firmware using the Arduino updater.

Arduino MKR1300 WAN device with Grove Shield & patch antenna

The application is a modified version of my Arduino code with additional debugging support and payload formatting functionality.

/*
  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[] = {"MKR1300LoRa1"};
const float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

// Payload configuration
const int InterruptPin = LORA_IRQ_DUMB;
const int ChipSelectPin = 6;
const int ResetPin = 1;

// LoRa radio payload configuration
const byte SensorIdValueSeperator = ' ' ;
const byte SensorReadingSeperator = ',' ;
const int LoopSleepDelaySeconds = 60 ;

const byte PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum];
byte payloadLength = 0 ;


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(InterruptPin, ChipSelectPin, ResetPin); 
  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.");

  // Configure the Seeedstudio TH02 temperature & humidity sensor
  Serial.println("TH02 setup start");
  TH02.begin();
  delay(100);
  Serial.println("TH02 setup done");

  PayloadHeader((byte*)FieldGatewayAddress,strlen(FieldGatewayAddress), (byte*)DeviceAddress, strlen(DeviceAddress));

  Serial.println("Setup done");
  Serial.println();
}


void loop()
{
  float temperature ;
  float humidity ;

  Serial.println("Loop called");

  PayloadReset();

  // Read the temperature & humidity & battery voltage values then display nicely
  temperature = TH02.ReadTemperature();
  Serial.print("T:");
  Serial.print( temperature, 1 ) ;
  Serial.println( "C " ) ;

  PayloadAdd( "T", temperature, 1);

  humidity = TH02.ReadHumidity();
  Serial.print("H:");
  Serial.print( humidity, 0 ) ;
  Serial.println( "% " ) ;

  PayloadAdd( "H", humidity, 0) ;

#ifdef DEBUG_TELEMETRY
  Serial.println();
  Serial.print( "RFM9X/SX127X Payload length:");
  Serial.print( payloadLength );
  Serial.println( " bytes" );
#endif

  LoRa.beginPacket();
  LoRa.write( payload, payloadLength );
  LoRa.endPacket();

  Serial.println("Loop done");
  Serial.println();
  delay(LoopSleepDelaySeconds * 1000l);
}


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( 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( 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( 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
}

After updating the firmware configuring the data to display in Azure IoT Central (or AdaFruit.IO) took minimal time.

Arduino MKR 1300 Data in Azure IoT Central

Bill of materials (Prices as at Nov 2018)

  • Arduino MKR WAN 1300 USD39.80
  • Arduino MKR Connection Carrier (Grove Compatible) USD22.80
  • Grove Temperature & Humidity Sensor USD11.50

So far the battery life is looking pretty good considering all I have done is used Delay to stop the loop method for 60 seconds.

Next steps are to see if I can retrieve a unique identifier from the Murata firmware and improve battery life by hibernating the processor etc.

Easy Sensors Arduino Nano Radio Shield RFM69/95 Payload Addressing client

After not much project work for a while some new RFM9X devices arrived from EasySensors in Belarus.

This sample client built with an Arduino Nano clone and an Arduino Nano radio shield RFM69/95 or NRF24L01+.

I used the shield’s onboard SHA204A crypto and authentication chip, a Seeedstudio Temperature & Humidity sensor and uploaded the data to Azure IoT Central (Will also work with my AdaFruit.IO LoRa field gateway).

/*
  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 <LoRa.h>
#include <sha204_library.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 float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

// Payload configuration
const int ChipSelectPin = 10;
const int ResetPin = 9;

// LoRa radio payload configuration
const byte SensorIdValueSeperator = ' ' ;
const byte SensorReadingSeperator = ',' ;
const int LoopSleepDelaySeconds = 60 ;

// ATSHA204 secure authentication, validation with crypto and hashing (currently only using for unique serial number)
const byte Atsha204Port = A3;
atsha204Class sha204(Atsha204Port);
const byte DeviceSerialNumberLength = 9 ;
byte deviceSerialNumber[DeviceSerialNumberLength] = {""};

const byte PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum];
byte payloadLength = 0 ;


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

#if DEBUG
  while (!Serial);
#endif
 
  Serial.println("Setup called");

  Serial.print("Field gateway:");
  Serial.print( FieldGatewayAddress ) ;
  Serial.print(" Frequency:");
  Serial.print( FieldGatewayFrequency,0 ) ;
  Serial.print("MHz SyncWord:");
  Serial.print( FieldGatewaySyncWord ) ;
  Serial.println();
  
   // Retrieve the serial number then display it nicely
  if(sha204.getSerialNumber(deviceSerialNumber))
  {
    Serial.println("sha204.getSerialNumber failed");
    while (true); // Drop into endless loop requiring restart
  }

  Serial.print("SNo:");
  for (int i = 0; i < sizeof( deviceSerialNumber) ; i++)
  {
    // Add a leading zero
    if ( deviceSerialNumber[i] < 16)
    {
      Serial.print("0");
    }
    Serial.print(deviceSerialNumber[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  Serial.println("LoRa setup start");

  // override the default chip select and reset pins
  LoRa.setPins(ChipSelectPin, ResetPin);
  if (!LoRa.begin(FieldGatewayFrequency))
  {
    Serial.println("LoRa begin failed");
    while (true); // Drop into endless loop requiring restart
  }

  // Need to do this so field gateway pays attention to messsages from this device
  LoRa.enableCrc();
  LoRa.setSyncWord(FieldGatewaySyncWord);

#ifdef DEBUG_LORA
  LoRa.dumpRegisters(Serial);
#endif
  Serial.println("LoRa Setup done.");

  // Configure the Seeedstudio TH02 temperature & humidity sensor
  Serial.println("TH02 setup start");
  TH02.begin();
  delay(100);
  Serial.println("TH02 setup done");

  PayloadHeader(FieldGatewayAddress,strlen(FieldGatewayAddress), deviceSerialNumber, DeviceSerialNumberLength);

  Serial.println("Setup done");
  Serial.println();
}


void loop()
{
  float temperature ;
  float humidity ;

  Serial.println("Loop called");

  PayloadReset();

  // Read the temperature & humidity values then display nicely
  temperature = TH02.ReadTemperature();
  Serial.print("T:");
  Serial.print(temperature, 1) ;
  Serial.println("C ") ;

  PayloadAdd( "T", temperature, 1);

  humidity = TH02.ReadHumidity();
  Serial.print("H:" );
  Serial.print(humidity, 0) ;
  Serial.println("% ") ;

  PayloadAdd( "H", humidity, 0) ;

#ifdef DEBUG_TELEMETRY
  Serial.println();
  Serial.print("RFM9X/SX127X Payload length:");
  Serial.print(payloadLength);
  Serial.println(" bytes");
#endif

  LoRa.beginPacket();
  LoRa.write(payload, payloadLength);
  LoRa.endPacket();

  Serial.println("Loop done");
  Serial.println();
  delay(LoopSleepDelaySeconds * 1000l);
}


void PayloadHeader(byte *to, byte toAddressLength, byte *from, byte fromAddressLength)
{
  byte addressesLength = toAddressLength + fromAddressLength ;

  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( char *sensorId, float value, byte decimalPlaces)
{
  byte sensorIdLength = strlen( sensorId ) ;

  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("PayloadAdd float-payloadLength:");
  Serial.print( payloadLength);
  Serial.println( );
#endif
}


void PayloadAdd( char *sensorId, int value )
{
  byte sensorIdLength = strlen(sensorId) ;

  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("PayloadAdd int-payloadLength:" );
  Serial.print(payloadLength);
  Serial.println( );
#endif
}


void PayloadAdd( char *sensorId, unsigned int value )
{
  byte sensorIdLength = strlen(sensorId) ;

  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("PayloadAdd uint-payloadLength:");
  Serial.print(payloadLength);
  Serial.println( );
#endif
}


void PayloadReset()
{
  byte fromAddressLength = payload[0] & 0xf ;
  byte toAddressLength = payload[0] >> 4 ;
  
  payloadLength = toAddressLength + fromAddressLength + 1;
}

Arduino monitor output

ArduinoNanoEasySensorsRF95ShieldArduinoLogging

Prototype hardware

ArduinoNanoEasySensorsRF95ShieldHardware

Bill of materials (prices as at November 2018)

  • Arduino Nano clone USD4.70
  • Easy Sensors Arduino Nano Radio Shield for RFM95 USD16
  • Seeedstudio Temperature and Humidity Sensor Pro USD11.50
  • Seeedstudio 4 pin Male Jumper to Grove 4 pin Conversion Cable USD2.90

Azure IoT Central temperature and humidity display.

ArduinoNanoEasySensorsRF95ShieldAzureIoTCentral

Easy Sensors Wireless field gateway Arduino Nano client

After not much development on my nrf24L01 AdaFruit.IO and Azure IOT Hub field gateways for a while some new nRF24L01 devices arrived in the post last week.

This sample client is an Arduino Nano clone with an Arduino Nano radio shield for NRF24L01+.

I use the shield’s onboard SHA204A crypto and authentication chip, and a Seeedstudio Temperature & Humidity sensor with the data uploaded to adafruit.io.

/*
  Copyright ® 2018 September 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 &lt;RF24.h&gt;
#include &lt;sha204_library.h&gt;
#include &lt;TH02_dev.h&gt;

// RF24 radio( ChipeEnable , ChipSelect )
RF24 radio(9, 10);
const byte FieldGatewayChannel = 15 ;
const byte FieldGatewayAddress[] = {"Base1"};
const rf24_datarate_e RadioDataRate = RF24_250KBPS;
const rf24_pa_dbm_e RadioPALevel = RF24_PA_HIGH;

// Payload configuration
const int PayloadSizeMaximum = 32 ;
char payload[PayloadSizeMaximum] = "";
const byte DeviceIdPlusCsvSensorReadings = 1 ;
const byte SensorReadingSeperator = ',' ;

// ATSHA204 secure authentication, validation with crypto and hashing (only using for unique serial number)
atsha204Class sha204(A3);
const int DeviceSerialNumberLength = 9 ;
uint8_t deviceSerialNumber[DeviceSerialNumberLength] = {""};
const int LoopSleepDelaySeconds = 10 ;

void setup()
{
  Serial.begin(9600);
  Serial.println("Setup called");

  // Retrieve the serial number then display it nicely
  sha204.getSerialNumber(deviceSerialNumber);

  Serial.print("SNo:");
  for (int i = 0; i &lt; sizeof( deviceSerialNumber) ; i++)
  {
    // Add a leading zero
    if ( deviceSerialNumber[i] &lt; 16)
    {
      Serial.print("0");
    }
    Serial.print(deviceSerialNumber[i], HEX);
    Serial.print(" ");
  }

  Serial.println();

  // Configure the Seeedstudio TH02 temperature &amp; humidity sensor
  Serial.println("TH02 setup");
  TH02.begin();
  delay(100);

  // Configure the nRF24 module
  Serial.println("nRF24 setup");
  radio.begin();
  radio.setChannel(FieldGatewayChannel);
  radio.openWritingPipe(FieldGatewayAddress);
  radio.setDataRate(RadioDataRate) ;
  radio.setPALevel(RadioPALevel);
  radio.enableDynamicPayloads();

  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 PayloadMessageType (top nibble) and DeviceID length (bottom nibble)
  payload[0] = (DeviceIdPlusCsvSensorReadings &lt;&lt; 4) | sizeof(deviceSerialNumber) ;
  payloadLength += 1;

  // Copy the device serial number into the payload
  memcpy( &amp;payload[payloadLength], deviceSerialNumber, sizeof( deviceSerialNumber));
  payloadLength += sizeof( deviceSerialNumber) ;

  // Read the temperature, humidity &amp; battery voltage values then display nicely
  temperature = TH02.ReadTemperature();
  Serial.print("T:");
  Serial.print( temperature, 1 ) ;
  Serial.print( "C" ) ;

  humidity = TH02.ReadHumidity();
  Serial.print(" H:");
  Serial.print( humidity, 0 ) ;
  Serial.println( "%" ) ;

  // Copy the temperature into the payload
  payload[ payloadLength] = 't';
  payloadLength += 1 ;
  dtostrf(temperature, 6, 1, &amp;payload[payloadLength]);
  payloadLength += 6;

  payload[ payloadLength] = ',';
  payloadLength += 1 ;

  // Copy the humidity into the payload
  payload[ payloadLength] = 'h';
  payloadLength += 1 ;
  dtostrf(humidity, 4, 0, &amp;payload[payloadLength]);
  payloadLength += 4;

  // Powerup the nRF24 chipset then send the payload to base station
  Serial.print( "Payload length:");
  Serial.println( payloadLength );

  Serial.println( "nRF24 write" ) ;
  boolean result = radio.write(payload, payloadLength);
  if (result)
    Serial.println("Write Ok...");
  else
    Serial.println("Write failed.");

  Serial.println("Loop done");
  delay(LoopSleepDelaySeconds * 1000l);
}

Arduino monitor output

NanoArduinoNrf24

Prototype hardware

ArduinoNanonRF24

Bill of materials (prices as at Sep 2018)

  • Arduino Nano clone USD4.70
  • Easy Sensors Arduino Nano Radio Shield for nRF24L01 USD13
  • Seeedstudio Temperature and Humidity Sensor Pro USD11.50
  • Seeedstudio 4 pin Male Jumper to Grove 4 pin Conversion Cable USD2.90

Adafruit.IO temperature display when I moved the sensor outside.

NanoNrf24

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"

 

Re-reading the SX1276 datasheet

I sat down and read the Semtech SX1276 datasheet paying close attention to any references to CRCs and headers. Then to test some ideas I modified my Receive Basic test harness to see if I could reliably reproduce the problem with my stress test harness.LoRaStress2

public sealed class StartupTask : IBackgroundTask
	{
		private const int ChipSelectLine = 25;
		private const int ResetLine = 17;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectLine, ResetLine);

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			// Put device into LoRa + Sleep mode
			rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

			// Set the frequency to 915MHz
			byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
			rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

			rfm9XDevice.RegisterWriteByte(0x0F, 0x0); // RegFifoRxBaseAddress 

			rfm9XDevice.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous

			while (true)
			{
				// Wait until a packet is received, no timeouts in PoC
				Debug.WriteLine("Receive-Wait");
				byte IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
				while ((IrqFlags & 0b01000000) == 0)  // wait until RxDone cleared
				{
					Task.Delay(20).Wait();
					IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
					Debug.Write(".");
				}
				Debug.WriteLine("");

				if ((IrqFlags & 0b00100000) == 0b00100000)
				{
					Debug.WriteLine("Payload CRC error");
				}

				byte regHopChannel = rfm9XDevice.RegisterReadByte(0x1C);
				Debug.WriteLine(string.Format("regHopChannel {0}", Convert.ToString((byte)regHopChannel, 2).PadLeft(8, '0')));

				byte currentFifoAddress = rfm9XDevice.RegisterReadByte(0x10); // RegFifiRxCurrent
				rfm9XDevice.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr*
				byte numberOfBytes = rfm9XDevice.RegisterReadByte(0x13); // RegRxNbBytes

				// Allocate buffer for message
				byte[] messageBytes = new byte[numberOfBytes];

				for (int i = 0; i < numberOfBytes; i++)
				{
					messageBytes[i] = rfm9XDevice.RegisterReadByte(0x00); // RegFifo
				}
				rfm9XDevice.RegisterWriteByte(0x12, 0xff); // RegIrqFlags clear all the bits

				string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
				Debug.WriteLine("Received {0} byte message {1}", messageBytes.Length, messageText);
				Debug.WriteLine(string.Format("RegIrqFlags {0}", Convert.ToString((byte)IrqFlags, 2).PadLeft(8, '0')));
				Debug.WriteLine("Receive-Done");
			}
		}
	}

The RegHopChannel register has a flag indicating whether there was a CRC extracted from the packet header.

regHopChannel 00000000
Received 23 byte message 1 Hello Arduino LoRa! 1
RegIrqFlags 01010000
Receive-Done
Receive-Wait
…………………………..
regHopChannel 00000000
Received 23 byte message 1 Hello Arduino LoRa! 2
RegIrqFlags 01010000
Receive-Done
Receive-Wait
……………………………
regHopChannel 00000000
Received 23 byte message 1 Hello Arduino LoRa! 3
RegIrqFlags 01010000
Receive-Done
Receive-Wait

I then modified my Arduino-LoRa library based client to include a CRC

void setup() {
  Serial.begin(9600);                   // initialize serial
  while (!Serial);

  Serial.println("LoRa Duplex - Set sync word");

  // override the default CS, reset, and IRQ pins (optional)
  LoRa.setPins(csPin, resetPin, irqPin);// set CS, reset, IRQ pin

  if (!LoRa.begin(915E6)) {             // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }

  LoRa.enableCrc();  // BHL This was my change

  LoRa.setSyncWord(0x12);           // ranges from 0-0xFF, default 0x34, see API docs

  LoRa.dumpRegisters(Serial);
  Serial.println("LoRa init succeeded.");
}

void loop() {
  if (millis() - lastSendTime > interval) {
    String message = "5 Hello Arduino LoRa! ";   // send a message
    message += msgCount;
    sendMessage(message);
    Serial.println("Sending " + message);
    lastSendTime = millis();            // timestamp the message
    //interval = random(2000) + 1000;    // 2-3 seconds
    interval = 1000;
  }

  // parse for a packet, and call onReceive with the result:
  onReceive(LoRa.parsePacket());
}

void sendMessage(String outgoing) {
  LoRa.beginPacket();                   // start packet
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  msgCount++;                           // increment message ID
}

void onReceive(int packetSize) {
  if (packetSize == 0) return;          // if there's no packet, return

  // read packet header bytes:
  String incoming = "";

  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }

  Serial.println("Message: " + incoming);
  Serial.println("RSSI: " + String(LoRa.packetRssi()));
  Serial.println("Snr: " + String(LoRa.packetSnr()));
  Serial.println();
}

When I powered up a single client and the payload had a CRC

...............................
regHopChannel 01000000
Received 23 byte message 6 Hello Arduino LoRa! 6
RegIrqFlags 01010000
Receive-Done
Receive-Wait
.................................
regHopChannel 01000000
Received 23 byte message 6 Hello Arduino LoRa! 7
RegIrqFlags 01010000
Receive-Done
Receive-Wait
.................................
regHopChannel 01000000
Received 23 byte message 6 Hello Arduino LoRa! 8
RegIrqFlags 01010000
Receive-Done
Receive-Wait
...............................

Then when I increased the number of clients I started getting corrupted messages with CRC errors.

Received 24 byte message 6 Hello Arduino LoRa! 32
RegIrqFlags 01010000
Receive-Done
Receive-Wait
...............
regHopChannel 01000001
Received 25 byte message 8 Hello Arduino LoRa! 114
RegIrqFlags 01010000
Receive-Done
Receive-Wait
Payload CRC error
regHopChannel 01000000
Received 24 byte message s��=��(��p�^j�\ʏ�����
RegIrqFlags 01100000
Receive-Done
Receive-Wait
.............
regHopChannel 01000000
Received 24 byte message 6 Hello Arduino LoRa! 33
RegIrqFlags 01010000
Receive-Done
Receive-Wait
...............
regHopChannel 01000001
Received 25 byte message 8 Hello Arduino LoRa! 115
RegIrqFlags 01010000
Receive-Done
Receive-Wait

I need to do some more testing but now I think the problem was the RegIrqFlags PayloadCRCError flag was never going to get set because there was no CRC on the payload.