Hi all,
I am working on an Arduino UNOr3 project which utilises a Dragino LoRa shiled and the LMIC library to transmit sensor data to TTN. I have used several other devices with TTN before with great success however am having troubles with this project. I have used almost identical code on a Wisen Whisper LoRa node and have had it successfully sending data to TTN, but now I wish to integrate this code onto a UNOr3 with this shield.
I have provided my code below, which is very similar to other use cases I have seen. The arduino LMIC library has had the config changed to use au915, and the TTN console has the arduino device setup with LoRa parameters of:
Frequency: Australia 915-928 MHz, FSB 2 (used by TTN)
LoRa Version: LoRaWAN Specification 1.0.2
Parameters: RP001 Regional Parameters 1.0.2 revision B
The device has been configured to utilise OTAA and is coded to send Temperature/Humidity data from a BME280 sensor along with location data (Lat and Lng) from a NEO-7M GPS module.
Currently The device is accepting/forwarding join requests to TTN but no payload packages are sending or being decoded. The payload parser is also the same as what was utilised on the Wisen Whisper board.
Any guidance would be most appreciated.
// The following is a list of included libraries for the LoRa and sensor packages
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
// The following are defined pins for Dragino shield
#define PIN_RFM95_RST 9
#define PIN_RFM95_NSS 10
#define PIN_RFM95_DIO00 2
#define PIN_RFM95_DIO01 6
#define PIN_RFM95_DIO02 7
// The serial connection to the GPS device pins RXPin = 4, TXPin = 3
SoftwareSerial ss(4, 3);
// The TinyGPS++ object
TinyGPSPlus gps;
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme; // I2C, ensure pinout from sensor is connected to SCL and SDA pins on the arduino board
// For normal use, it is required that you edit the sketch to replace the DEVEUI, APPEUI and APPKEY with values assigned by the TTN console when creating the device on TTN.
// This EUI must be in little-endian format, so least-significant-byte first. When copying an EUI from ttn console output, this means to reverse the bytes.
// For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 0x70.
static const u1_t PROGMEM APPEUI[8]={ 0x45, 0x23, 0x01, 0x89, 0x67, 0x45, 0x23, 0x01 }; // lsb
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ 0x5D, 0x12, 0x00, 0xD8, 0x7E, 0xD5, 0xB3, 0x70 }; // lsb
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (since it is not really a number but a block of memory, endianness does not really apply).
// In practice, a key taken from ttnctl can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { 0x93, 0x24, 0x66, 0x2B, 0x07, 0x81, 0xBD, 0x70, 0x7E, 0x01, 0xB1, 0x69, 0x1A, 0xA1, 0x93, 0x3D };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
static uint8_t mydata[9]; // Set to the size of the payload + 1 for the header byte on uplink
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty cycle limitations).
// NOTE: Be careful with the air time limitations documented by TTN. Ensure no policy breach. We will set the TX interval low for testing purposes
#define TX_INTERVAL 30
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = PIN_RFM95_NSS,
.rxtx = LMIC_UNUSED_PIN,
.rst = PIN_RFM95_RST,
.dio = {PIN_RFM95_DIO00, PIN_RFM95_DIO01, PIN_RFM95_DIO02},
};
void printHex2(unsigned v) {
v &= 0xff;
if (v < 16)
Serial.print('0');
Serial.print(v, HEX);
}
void onEvent (ev_t ev) {
switch(ev) {
case EV_JOINED:
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
for (size_t i=0; i<sizeof(artKey); ++i) {
if (i != 0)
printHex2(artKey[i]);
}
for (size_t i=0; i<sizeof(nwkKey); ++i) {
if (i != 0)
printHex2(nwkKey[i]);
}
}
// Disable link check validation (auto enabled during join, because slow data rates change max TX size, we don't use it in this example)
LMIC_setLinkCheckMode(0);
break;
case EV_TXCOMPLETE:
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
}
}
void do_send(osjob_t* j){
// Run the GPS and BME280 sensor functions
sensor();
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
//Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
//Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void sensor(){
// Now package BME Sensor information and prepare it for transmission and adjust for the f2sflt16 range (-1 to 1)
// float -> int
uint16_t payloadTemp = LMIC_f2sflt16((bme.readTemperature()/100));
// int -> bytes
byte tempLow = lowByte(payloadTemp);
byte tempHigh = highByte(payloadTemp);
// place the bytes into the payload
mydata[0] = tempLow;
mydata[1] = tempHigh;
// repeat for humidity
uint16_t payloadHumid = LMIC_f2sflt16((bme.readHumidity()/100));
// int -> bytes
byte humidLow = lowByte(payloadHumid);
byte humidHigh = highByte(payloadHumid);
mydata[2] = humidLow;
mydata[3] = humidHigh;
// Check for GPS signal and store the lat and lng into mydata
while (ss.available()) {
if (gps.encode(ss.read())) {
// Prepare the lat and lng data for transmission
// float -> int
uint16_t payloadLat = LMIC_f2sflt16(gps.location.lat());
// int -> bytes
byte latLow = lowByte(payloadLat);
byte latHigh = highByte(payloadLat);
// place the bytes into the payload
mydata[4] = latLow;
mydata[5] = latHigh;
// float -> int
uint16_t payloadLng = LMIC_f2sflt16(gps.location.lng());
// int -> bytes
byte lngLow = lowByte(payloadLng);
byte lngHigh = highByte(payloadLng);
// place the bytes into the payload
mydata[6] = lngLow;
mydata[7] = lngHigh;
} else if (millis() > 5000 && gps.charsProcessed() <10){ // if no GPS signal is acquired print an error for debugging
Serial.println(F("No GPS detected: check wiring."));
}
}
}
void setup() {
Serial.begin(9600);
ss.begin(9600);
bool status;
status = bme.begin();
// LMIC initialise
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Let LMIC compensate for +/- 0.5% clock error
LMIC_setClockError(MAX_CLOCK_ERROR * 0.5 / 100);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}
TTN application Parser:
function Decoder(bytes, port) {
// Decode an uplink message from a buffer
// (array) of bytes to an object of fields.
var decoded = {};
// temperature
rawTemp = bytes[0] + bytes[1] * 256;
decoded.degreesC = sflt162f(rawTemp) * 100;
// humidity
rawHumid = bytes[2] + bytes[3] * 256;
decoded.humidity = sflt162f(rawHumid) * 100;
// latitude
rawLat = bytes[4] + bytes[5] * 256;
decoded.latitude = sflt162f(rawLat) * 1000000;
// longitude
rawLng = bytes[6] + bytes[7] * 256;
decoded.longitude = sflt162f(rawLng) * 1000000;
return decoded;
}
function sflt162f(rawSflt16)
{
// rawSflt16 is the 2-byte number decoded from wherever;
// it's in range 0..0xFFFF
// bit 15 is the sign bit
// bits 14..11 are the exponent
// bits 10..0 are the the mantissa. Unlike IEEE format,
// the msb is transmitted; this means that numbers
// might not be normalized, but makes coding for
// underflow easier.
// As with IEEE format, negative zero is possible, so
// we special-case that in hopes that JavaScript will
// also cooperate.
//
// The result is a number in the open interval (-1.0, 1.0);
//
// throw away high bits for repeatability.
rawSflt16 &= 0xFFFF;
// special case minus zero:
if (rawSflt16 == 0x8000)
return -0.0;
// extract the sign.
var sSign = ((rawSflt16 & 0x8000) != 0) ? -1 : 1;
// extract the exponent
var exp1 = (rawSflt16 >> 11) & 0xF;
// extract the "mantissa" (the fractional part)
var mant1 = (rawSflt16 & 0x7FF) / 2048.0;
// convert back to a floating point number. We hope
// that Math.pow(2, k) is handled efficiently by
// the JS interpreter! If this is time critical code,
// you can replace by a suitable shift and divide.
var f_unscaled = sSign * mant1 * Math.pow(2, exp1 - 15);
return f_unscaled;
}