Hello! I am currently working on a LoRaWAN project using a SX1276 LoRa transceiver to transmit sensor data (nitrogen, phosphorous, potassium levels) over LoRaWAN using The Things Network (TTN). The code involves communication with a sensor through RS-485, and I have integrated LMIC for LoRaWAN connectivity.
The code seems to be encountering a problem during the LoRaWAN transmission process. I have noticed that the device successfully joins the network, but when it comes to sending the payload, I am experiencing unexpected behavior. The console shows a "forward join accept" loop, and the payload is not being sent. These are the output on the serial monitor and TTN for your reference:
I noticed that it always stops halfway on the nkKey part, so I verified the keys (APPEUI, DEVEUI, APPKEY) and they are correctly configured in the TTN console.
So I think that the device successfully joins the network, but the payload transmission seems to be problematic.
Code:
/*******************************************************************************
* The Things Network - Sensor Data Example
*
* Example of sending a valid LoRaWAN packet with DHT22 temperature and
* humidity data to The Things Networ using a Feather M0 LoRa.
*
* Learn Guide: https://learn.adafruit.com/the-things-network-for-feather
*
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
* Copyright (c) 2018 Brent Rubell, Adafruit Industries
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <AltSoftSerial.h>
// RO to pin 8 & DI to pin 9 when using AltSoftSerial
#define RE 6
#define DE 7
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl 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] = { 0x34, 0x00, 0x75, 0x36, 0x28, 0x40, 0x89, 0x76 };
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] = { 0x6A, 0x22, 0x00, 0xD8, 0x7E, 0xD5, 0xB3, 0x70 };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from the TTN console can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { 0x38, 0x1D, 0xAB, 0x65, 0xB8, 0xAE, 0x48, 0x4F, 0x72, 0x38, 0x0C, 0x2B, 0xDE, 0x9E, 0x38, 0x98 };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 10;
// Pin mapping for Adafruit Feather M0 LoRa
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 9,
.dio = {3,4,5},
.rxtx_rx_active = 0,
.rssi_cal = 0, // LBT cal for the Adafruit Feather M0 LoRa, in dB
.spi_freq = 1000000,
};
void onEvent (ev_t ev) {
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"));
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("netid: ");
Serial.println(netid, DEC);
Serial.print("devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("artKey: ");
for (int i=0; i<sizeof(artKey); ++i) {
if (i != 0)
Serial.print("-");
Serial.print(artKey[i], HEX);
}
Serial.println("");
Serial.print("nwkKey: ");
for (int i=0; i<sizeof(nwkKey); ++i) {
if (i != 0)
Serial.print("-");
Serial.print(nwkKey[i], HEX);
}
Serial.println("");
}
// Disable link check validation (automatically enabled
// during join, but because slow data rates change max TX
// size, we don't use it in this example.
LMIC_setLinkCheckMode(0);
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| 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;
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.println(LMIC.dataLen);
Serial.println(F(" bytes of payload"));
}
// 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;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
const byte nitro[] = {0x01, 0x03, 0x00, 0x1e, 0x00, 0x01, 0xe4, 0x0c};
const byte phos[] = {0x01, 0x03, 0x00, 0x1f, 0x00, 0x01, 0xb5, 0xcc};
const byte pota[] = {0x01, 0x03, 0x00, 0x20, 0x00, 0x01, 0x85, 0xc0};
byte values[11];
AltSoftSerial mod;
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
Serial.print("Nitrogen: ");
uint8_t val1 = nitrogen();
Serial.print(" = ");
Serial.print(val1);
Serial.println(" mg/kg");
delay(250);
Serial.print("Phosphorous: ");
uint8_t val2 = phosphorous();
Serial.print(" = ");
Serial.print(val2);
Serial.println(" mg/kg");
delay(250);
Serial.print("Potassium: ");
uint8_t val3 = potassium();
Serial.print(" = ");
Serial.print(val3);
Serial.println(" mg/kg");
Serial.println();
Serial.println();
byte payload[3];
payload[0] = val1;
payload[1] = val2;
payload[2] = val3;
// prepare upstream data transmission at the next possible time.
// transmit on port 1 (the first parameter); you can use any value from 1 to 223 (others are reserved).
// don't request an ack (the last parameter, if not zero, requests an ack from the network).
// Remember, acks consume a lot of network resources; don't ask for an ack unless you really need it.
LMIC_setTxData2(3, (xref2u1_t)(payload), sizeof(payload), 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
byte nitrogen() {
// clear the receive buffer
mod.flushInput();
// switch RS-485 to transmit mode
digitalWrite(DE, HIGH);
digitalWrite(RE, HIGH);
delay(1);
// write out the message
for (uint8_t i = 0; i < sizeof(nitro); i++ ) mod.write( nitro[i] );
// wait for the transmission to complete
mod.flush();
// switching RS485 to receive mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
// delay to allow response bytes to be received without blocking the loop
uint32_t startMillis = millis();
while (millis() - startMillis < 1000) {
// Wait for 1000 ms without blocking the loop
}
// read in the received bytes
for (byte i = 0; i < 7; i++) {
values[i] = mod.read();
Serial.print(values[i], HEX);
Serial.print(' ');
}
return values[4];
}
byte phosphorous() {
mod.flushInput();
digitalWrite(DE, HIGH);
digitalWrite(RE, HIGH);
delay(1);
for (uint8_t i = 0; i < sizeof(phos); i++ ) mod.write( phos[i] );
mod.flush();
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
uint32_t startMillis = millis();
while (millis() - startMillis < 1000) {
// Wait for 1000 ms without blocking the loop
}
for (byte i = 0; i < 7; i++) {
values[i] = mod.read();
Serial.print(values[i], HEX);
Serial.print(' ');
}
return values[4];
}
byte potassium() {
mod.flushInput();
digitalWrite(DE, HIGH);
digitalWrite(RE, HIGH);
delay(1);
for (uint8_t i = 0; i < sizeof(pota); i++ ) mod.write( pota[i] );
mod.flush();
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
uint32_t startMillis = millis();
while (millis() - startMillis < 1000) {
// Wait for 1000 ms without blocking the loop
}
for (byte i = 0; i < 7; i++) {
values[i] = mod.read();
Serial.print(values[i], HEX);
Serial.print(' ');
}
return values[4];
}
void setup() {
delay(5000);
while (! Serial);
Serial.begin(9600);
mod.begin(9600);
pinMode(RE, OUTPUT);
pinMode(DE, OUTPUT);
// put RS-485 into receive mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
Serial.println(F("Starting"));
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
// Disable link-check mode and ADR, because ADR tends to complicate testing.
LMIC_setLinkCheckMode(0);
// Set the data rate to Spreading Factor 7. This is the fastest supported rate for 125 kHz channels, and it
// minimizes air time and battery power. Set the transmission power to 14 dBi (25 mW).
LMIC_setDrTxpow(DR_SF7,14);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
// we call the LMIC's runloop processor. This will cause things to happen based on events and time. One
// of the things that will happen is callbacks for transmission complete or received messages. We also
// use this loop to queue periodic data transmissions. You can put other things here in the `loop()` routine,
// but beware that LoRaWAN timing is pretty tight, so if you do more than a few milliseconds of work, you
// will want to call `os_runloop_once()` every so often, to keep the radio running.
os_runloop_once();
}
Thank you in advance for your time and assistance!