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.

Wisen Whisper Node – LoRa 915 MHz Payload Addressing Client

This is a demo Wizen Whisper NodeLoRa client (based on one of the examples from Arduino-LoRa) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

The 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

WisenPatch20180924

WisenLoRa20180924

/*
  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

WisenTalkLoRaAzureIoTHub

Adafruit Feather M0 RFM95 LoRa Radio Payload Addressing Client

This is a demo AdaFruit Feather Mo0 Radio with LoRa Radio Module client (based on one of the examples from Arduino-LoRa) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

The Adafruit learn site had sample code based on the RadioHead library which was useful. This device supports 868MHz & 915Mz, there is are other Arduino 32u4 and 433MHz devices available.

Bill of materials (Prices Sep 2018)

  • Adafruit Feather M0 RFM95 LoRa Radio (433 or 900 MHz) USD34.95
  • 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, has no power conservation, advanced wireless configuration etc. I had to add an extra include from the dstrtof function

AdaFruitM0Feather

/*
  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
#include
#include
const int csPin = 8;          // LoRa radio chip select
const int resetPin = 4;       // LoRa radio reset
const int irqPin = 3;         // 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[] = {"AdafruitM0"};

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 messages 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 Arduino debug monitor the messages from the device looked like this

Loop done
Loop called
T:17.5C H:94%
RFM9X/SX127X Payload length:30 bytes
Loop done
Loop called
T:17.5C H:95%
RFM9X/SX127X Payload length:30 bytes
Loop done
Loop called
T:17.6C H:95%
RFM9X/SX127X Payload length:30 bytes
Loop done

In the debug output window the messages from the device looked like this

The thread 0xf9c has exited with code 0 (0x0).
The thread 0x8ac has exited with code 0 (0x0).
09:53:53-RX From AdafruitM0 PacketSnr 10.3 Packet RSSI -51dBm RSSI -103dBm = 11 byte message "t 17.8,h 93"
Sensor AdafruitM0t Value 17.8
Sensor AdafruitM0h Value 93
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x704 has exited with code 0 (0x0).
The thread 0xad4 has exited with code 0 (0x0).
09:54:03-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -52dBm RSSI -101dBm = 11 byte message "t 17.8,h 93"
Sensor AdafruitM0t Value 17.8
Sensor AdafruitM0h Value 93
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x1084 has exited with code 0 (0x0).
The thread 0xa2c has exited with code 0 (0x0).
09:54:14-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -54dBm RSSI -102dBm = 11 byte message "t 17.7,h 93"
Sensor AdafruitM0t Value 17.7
Sensor AdafruitM0h Value 93
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x1720 has exited with code 0 (0x0).

10:00:06-RX From AdafruitM0 PacketSnr 9.3 Packet RSSI -52dBm RSSI -100dBm = 12 byte message "t 184.0,h 91"
Sensor AdafruitM0t Value 184.0
Sensor AdafruitM0h Value 91
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x15d4 has exited with code 0 (0x0).
The thread 0x15f4 has exited with code 0 (0x0).
10:00:19-RX From AdafruitM0 PacketSnr 10.0 Packet RSSI -48dBm RSSI -102dBm = 12 byte message "t 180.9,h 94"
Sensor AdafruitM0t Value 180.9
Sensor AdafruitM0h Value 94
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0xabc has exited with code 0 (0x0).
The thread 0x2e4 has exited with code 0 (0x0).
10:00:29-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -49dBm RSSI -102dBm = 12 byte message "t -50.0,h 94"
Sensor AdafruitM0t Value -50.0
Sensor AdafruitM0h Value 94
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x950 has exited with code 0 (0x0).
The thread 0x145c has exited with code 0 (0x0).
The thread 0x176c has exited with code 0 (0x0).
10:00:39-RX From AdafruitM0 PacketSnr 9.5 Packet RSSI -50dBm RSSI -102dBm = 11 byte message "t 17.5,h 94"
Sensor AdafruitM0t Value 17.5
Sensor AdafruitM0h Value 94
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x165c has exited with code 0 (0x0).
The thread 0x6e8 has exited with code 0 (0x0).
10:00:49-RX From AdafruitM0 PacketSnr 9.8 Packet RSSI -59dBm RSSI -100dBm = 11 byte message "t 17.5,h 95"
Sensor AdafruitM0t Value 17.5
Sensor AdafruitM0h Value 95
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x1334 has exited with code 0 (0x0).
The thread 0x14d0 has exited with code 0 (0x0).
10:00:59-RX From AdafruitM0 PacketSnr 9.5 Packet RSSI -66dBm RSSI -102dBm = 11 byte message "t 17.6,h 95"
Sensor AdafruitM0t Value 17.6
Sensor AdafruitM0h Value 95
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish

Then at my Azure IoT hub the data stream looked like this
AdaFruitM0LoRaFeatherIoTHub

To reduce power consumption I would disconnect/remove the light emitting diode(LED)

Elecrow 32u4 with Lora RFM95 IOT Board Payload Addressing Client

This is a demo Elecrow 32u4 with Lora RFM95 IOT Board-868MHz/915MHz client (based on one of the examples from Arduino-LoRa) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

The elecrow wiki had sample code based on the RadioHead library which was useful.

Bill of materials (Prices Sep 2018)

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

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

Elecrow32u4LoRa

/*
  Adapted from LoRa Duplex communication with Sync Word

  Sends Light data from Seeedstudio 

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

  To my Windows 10 IoT Core RFM 9X library

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

*/
#include               // include libraries
#include
const int csPin = 10;          // LoRa radio chip select
const int resetPin = 9;       // LoRa radio reset
const int irqPin = 2;         // change for your board; must be a hardware interrupt pin

// Field gateway configuration
const char FieldGatewayAddress[] = "LoRaIoT1";
const float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

// Payload configuration
const int PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum] = "";
const byte SensorReadingSeperator = ',' ;

// Manual serial number configuration
const char DeviceId[] = {"Elecrow32u4"};

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

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

  Serial.println("LoRa Setup");

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

  if (!LoRa.begin(FieldGatewayFrequency))
  {
    Serial.println("LoRa init failed. Check your connections.");
    while (true);
  }

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

  LoRa.dumpRegisters(Serial);
  Serial.println("LoRa Setup done.");

  Serial.println("Setup done");
}

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

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

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

  // prepare the payload header with "To" Address length (top nibble) and "From" address length (bottom nibble)
  payload[0] = (strlen(FieldGatewayAddress) << 4) | strlen( DeviceId ) ;
  payloadLength += 1;

  // Copy the "To" address into payload
  memcpy(&payload[payloadLength], FieldGatewayAddress, strlen(FieldGatewayAddress));
  payloadLength += strlen(FieldGatewayAddress) ;

  // Copy the "From" into payload
  memcpy(&payload[payloadLength], DeviceId, strlen(DeviceId));
  payloadLength += strlen(DeviceId) ;

  Serial.println("Loop called 5");

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

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

  // display info about payload then send it (No ACK) with LoRa unlike nRF24L01
  Serial.print( "RFM9X/SX127X Payload length:");
  Serial.print( payloadLength );
  Serial.println( " bytes" );

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

  Serial.println("Loop done");

  delay(LoopSleepDelaySeconds * 1000l);
}

In the debug output window the messages from the device looked like this

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

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

LoRa Radio Node v1.0 868/915MHz Payload Addressing Client

This is a demo IoTMCU LoRa Radio Node V1.0 868/915MHz client (based on one of the examples from Arduino-LoRa) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

The devices arrived promptly and the sample code and schematics made adapting my Arduino code easy.

Bill of materials (Prices Sep 2018)

The code is pretty basic, it shows how to pack the payload and set the necessary RFM9X/SX127X LoRa module configuration, has no power conservation, advanced wireless configuration etc.

IoTMCULoRa915V2

/*
  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 = 9;       // LoRa radio reset
const int irqPin = 2;         // change for your board; must be a hardware interrupt pin

// Field gateway configuration
const char FieldGatewayAddress[] = "LoRaIoT1";
const float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

// Payload configuration
const int PayloadSizeMaximum = 64 ;
byte payload[PayloadSizeMaximum] = "";
const byte SensorReadingSeperator = ',' ;

// Manual serial number configuration
const char DeviceId[] = {"IoTMCU915"};

const int LoopSleepDelaySeconds = 60 ;

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 messages 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, &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, &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

Register 0x40 – Value 0X00 – Bits 00000000
Register 0x41 – Value 0X00 – Bits 00000000
Register 0x42 – Value 0X12 – Bits 00010010

The thread 0x6f8 has exited with code 0 (0x0).
The thread 0x2f0 has exited with code 0 (0x0).
19:35:35-RX From IoTMCU915 PacketSnr 10.0 Packet RSSI -45dBm RSSI -109dBm = 11 byte message "t 19.8,h 88"
Sensor IoTMCU915t Value 19.8
Sensor IoTMCU915h Value 88
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0x9b0 has exited with code 0 (0x0).
The thread 0x9f4 has exited with code 0 (0x0).
The thread 0x9dc has exited with code 0 (0x0).
The thread 0x17fc has exited with code 0 (0x0).
The thread 0x944 has exited with code 0 (0x0).
19:36:35-RX From IoTMCU915 PacketSnr 10.8 Packet RSSI -45dBm RSSI -108dBm = 11 byte message "t 19.7,h 88"
Sensor IoTMCU915t Value 19.7
Sensor IoTMCU915h Value 88
AzureIoTHubClient SendEventAsync start
AzureIoTHubClient SendEventAsync finish
The thread 0xbf0 has exited with code 0 (0x0).

I have some suitable batteries on order from Jaycar a local supplier. There is also a 433MHz version of this device available other regions.

Arduino LoRa Payload Addressing Client

This is a demo Arduino client (based on one of the examples from Arduino-LoRa) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

Bill of materials (Prices Sep 2018)

ArduinoUnoR3DraginoLoRa

The code is pretty basic, it shows how to pack the payload and set the necessary RFM9X/SX127X LoRa module configuration, has no power conservation, advanced wireless configuration etc.

/*
  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
#include
const int csPin = 10;          // LoRa radio chip select
const int resetPin = 9;       // LoRa radio reset
const int irqPin = 2;         // change for your board; must be a hardware interrupt pin

// Field gateway configuration
const byte FieldGatewayAddress[] = "LoRaIoT1";
const float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

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

// Manual serial number configuration
char DeviceId[] = {"Arduino1"};

const int LoopSleepDelaySeconds = 60;

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, &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, &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

Register 0x40 – Value 0X00 – Bits 00000000
Register 0x41 – Value 0X00 – Bits 00000000
Register 0x42 – Value 0X12 – Bits 00010010

19:15:21-RX From Arduino1 PacketSnr 9.3 Packet RSSI -49dBm RSSI -105dBm = 11 byte message "t 18.8,h 91"

19:15:30-TX 25 byte message Hello from LoRaIoT1 ! 255
19:15:30-TX Done
19:15:31-RX From Arduino1 PacketSnr 9.3 Packet RSSI -49dBm RSSI -103dBm = 11 byte message "t 18.8,h 91"

19:15:41-RX From Arduino1 PacketSnr 9.3 Packet RSSI -48dBm RSSI -106dBm = 11 byte message "t 18.8,h 91"

There must be a nicer way of building the payload, a topic for a future post maybe.

Wireless field gateway protocol V1

I’m going to build a number of nRF2L01P field gateways (Netduino Ethernet & Wifi running .NetMF, Raspberry PI running Windows 10 IoT Core, RedBearLab 3200  etc.), clients which run on a variety of hardware (Arduino, devDuino, Netduino, Seeeduino etc.) which, then upload data to a selection of IoT Cloud services (AdaFruit.IO, ThingSpeak, Microsoft IoT Central etc.)

The nRF24L01P is widely supported with messages up to 32 bytes long, low power consumption and 250kbps, 1Mbps and 2Mbps data rates.

The aim is to keep the protocol simple (telemetry only initially) to implement and debug as the client side code will be utilised by high school student projects.

The first byte of the message specifies the message type

0 = Echo

The message is displayed by the field gateway as text & hexadecimal.

1 = Device identifier + Comma separated values (CSV) payload

[0] – Set to 1

[1] – Device identifier length

[2]..[2+Device identifier length] – Unique device identifier bytes e.g. Mac address

[2+Device identifier length+1 ]..[31] – CSV payload e.g.  SensorID value, SensorID value

Overtime I will support more message types and wireless protocols.