Dragino LoRa config with TTN

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

do you see the join-request and join-accept-message on the TTN V3 network?
image

Yes, I am able to get the join/accept message on TTN side however the payload is never sent or decoded. I am assuming that the OTAA portion of the arduino sketch for the IoT portion is working correctly and it is just the sensor script which packs the data into the "mydata[9]" that is not functioning

after the join-accept does the application show any uplink data message, e.g.

it may be worth adding tests for other events in case there were problems, e.g.

void onEvent (ev_t ev) {
  char text[100]={0};
    Serial.print(os_getTime());
    Serial.print(": ");
    switch(ev) {
        case EV_SCAN_TIMEOUT:
            Serial.println(F("EV_SCAN_TIMEOUT"));
            break;
        case EV_BEACON_FOUND:
            Serial.println(F("EV_BEACON_FOUND"));
            break;
        case EV_BEACON_MISSED:
            Serial.println(F("EV_BEACON_MISSED"));
            break;
        case EV_BEACON_TRACKED:
            Serial.println(F("EV_BEACON_TRACKED"));
            break;
        case EV_JOINING:
            Serial.println(F("EV_JOINING"));
            break;
        case EV_JOINED:
            Serial.println(F("EV_JOINED"));

            // Disable link check validation (automatically enabled
            // during join, but not supported by TTN at this time).
            LMIC_setLinkCheckMode(0);
            break;
        case EV_RFU1:
            Serial.println(F("EV_RFU1"));
            break;
        case EV_JOIN_FAILED:
            Serial.println(F("EV_JOIN_FAILED"));
            break;
        case EV_REJOIN_FAILED:
            Serial.println(F("EV_REJOIN_FAILED"));
            break;
            break;
        case EV_TXCOMPLETE:
            Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
            if (LMIC.txrxFlags & TXRX_ACK)
              Serial.println(F("Received ack"));
            if (LMIC.dataLen) {
              Serial.println(F("Received "));
              Serial.print(LMIC.dataLen);
              Serial.println(F(" bytes of payload"));
              Serial.print(F("Data Received: "));
              for(int i=0;i< LMIC.dataLen;i++)
                 { Serial.print(" 0x"); Serial.print(text[i]=(LMIC.frame+LMIC.dataBeg)[i],  HEX);}  
              Serial.println();
              Serial.print("in ASCII = ");
              Serial.println(text);
            }
            // Schedule next transmission
            os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
            break;
        case EV_LOST_TSYNC:
            Serial.println(F("EV_LOST_TSYNC"));
            break;
        case EV_RESET:
            Serial.println(F("EV_RESET"));
            break;
        case EV_RXCOMPLETE:
            // data received in ping slot
            Serial.println(F("EV_RXCOMPLETE"));
            break;
        case EV_LINK_DEAD:
            Serial.println(F("EV_LINK_DEAD"));
            break;
        case EV_LINK_ALIVE:
            Serial.println(F("EV_LINK_ALIVE"));
            break;
         default:
            Serial.println(F("Unknown event"));
            break;
    }
}

also add some Serial.println() statements are critical points so you can check the flow of the code
upload any serial monitor output as text (not an image)

Thanks, I had already compiled a version of the script which contained several serial.print's, however the byte size of the code was too large to upload to the Uno or the Whisper and as such were removed to save space.
I successfully get Accept join requests however never see a "Forward uplink data message". I do see this when running on the "whisper node", just not on the "Uno+shield".

So remove the BME sensor stuff and send test data, TTN wont know.

The UNO is fairly marginal for a TTN node, as you have discovered.

Remember the TTN fair use policy limits you to 30 seconds total air time per day, which is very easy to exceed with a 30 second interval.

Yes I am aware, I have set the TX to 30 seconds for testing purposes only. Deployment will be at 5 min intervals.

Sending test data is working. I can send simple messages such as "Hello!" or a numerical value, however the script that I currently have reading sensor information is not packaging the data correctly and as such not sending correctly to TTN.

I can send GPS Lat and Long as a predefined value/variable. However reading from the sensor and storing the value in a variable, then configuring it into a byte payload is not. I can also read from the sensor to the serial window with no issues.

add some Serial.println() statements at key points in the program to show program execution flow
in particular add Serial.println() statements to display variable values and the resultant contents of mydata[9]?
uncomment

     //Serial.println(F("Packet queued"));

in do_send() to check the packet is transmitted
upload the resultant serial monitor output

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.