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.

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.

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.