Device on OTAA (TTGO LoRa32 V1) Not Connecting Unless "Flush OTAA device nonces" (Invalid DevNonce)

Hello,

I have a TTGO LoRa32 V1 connecting to ChirpStack via a Laird RG191 with the following code using the RadioLib library:

LoRaWAN_Starter.ino

/*
  RadioLib LoRaWAN Starter Example

  ! Please refer to the included notes to get started !

  This example joins a LoRaWAN network and will send
  uplink packets. Before you start, you will have to
  register your device at https://www.thethingsnetwork.org/
  After your device is registered, you can run this example.
  The device will join the network and start uploading data.

  Running this examples REQUIRES you to check "Resets DevNonces"
  on your LoRaWAN dashboard. Refer to the network's 
  documentation on how to do this.

  For default module settings, see the wiki page
  https://github.com/jgromes/RadioLib/wiki/Default-configuration

  For full API reference, see the GitHub Pages
  https://jgromes.github.io/RadioLib/

  For LoRaWAN details, see the wiki page
  https://github.com/jgromes/RadioLib/wiki/LoRaWAN

*/

#include "config.h"

void setup() {
  Serial.begin(115200);
  while(!Serial);
  delay(5000);  // Give time to switch to the serial monitor
  Serial.println(F("\nSetup ... "));

  Serial.println(F("Initialise the radio"));
  int16_t state = radio.begin();
  debug(state != RADIOLIB_ERR_NONE, F("Initialise radio failed"), state, true);
  
  // Setup the OTAA session information
  state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
  debug(state != RADIOLIB_ERR_NONE, F("Initialise node failed"), state, true);
  Serial.println(F("Join ('login') the LoRaWAN Network"));
  state = node.activateOTAA();
  debug(state != RADIOLIB_LORAWAN_NEW_SESSION, F("Join failed"), state, true);

  Serial.println(F("Ready!\n"));
}

void loop() {
  Serial.println(F("Sending uplink"));

  // // This is the place to gather the sensor inputs
  // // Instead of reading any real sensor, we just generate some random numbers as example
  // uint8_t value1 = 50;
  // uint16_t value2 = 1500;

  // // Build payload byte array
  // uint8_t uplinkPayload[3];
  // uplinkPayload[0] = value1;
  // uplinkPayload[1] = highByte(value2);   // See notes for high/lowByte functions
  // uplinkPayload[2] = lowByte(value2);
  
  // Text string to send
  const char* textValue = "Hello";

  // Calculate the length of the string (including the null terminator)
  uint8_t textLength = strlen(textValue);

  // Create a byte array to hold the string (plus one byte for null terminator)
  uint8_t uplinkPayload[textLength + 1];

  // Copy the string into the payload byte array
  for (uint8_t i = 0; i < textLength; i++) {
    uplinkPayload[i] = textValue[i];  // Copy character by character
  }

  // Add a null terminator if needed (for C-style strings)
  uplinkPayload[textLength] = '\0';  // Optional, if you need null-termination

  // Perform an uplink
  int16_t state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload),true);    
  debug(state < RADIOLIB_ERR_NONE, F("Error in sendReceive"), state, false);

  // Check if a downlink was received 
  // (state 0 = no downlink, state 1/2 = downlink in window Rx1/Rx2)
  if(state > 0) {
    Serial.println(F("Received a downlink"));
  } else {
    Serial.println(F("No downlink received"));
  }

  Serial.print(F("Next uplink in "));
  Serial.print(uplinkIntervalSeconds);
  Serial.println(F(" seconds\n"));
  
  // Wait until next uplink - observing legal & TTN FUP constraints
  delay(uplinkIntervalSeconds * 1000UL);  // delay needs milli-seconds
}

config.h

#ifndef _RADIOLIB_EX_LORAWAN_CONFIG_H
#define _RADIOLIB_EX_LORAWAN_CONFIG_H
#define ARDUINO_TTGO_LORA32_V1

#include <RadioLib.h>

// first you have to set your radio model and pin configuration
// this is provided just as a default example
SX1276 radio = new Module(18, 26, 14, 33);

// if you have RadioBoards (https://github.com/radiolib-org/RadioBoards)
// and are using one of the supported boards, you can do the following:
/*
#define RADIO_BOARD_AUTO
#include <RadioBoards.h>

Radio radio = new RadioModule();
*/

// how often to send an uplink - consider legal & FUP constraints - see notes
const uint32_t uplinkIntervalSeconds = 1UL * 30UL;    // minutes x seconds

// joinEUI - previous versions of LoRaWAN called this AppEUI
// for development purposes you can use all zeros - see wiki for details
#define RADIOLIB_LORAWAN_JOIN_EUI  0x0000000000000000

// the Device EUI & two keys can be generated on the TTN console 
#ifndef RADIOLIB_LORAWAN_DEV_EUI   // Replace with your Device EUI
#define RADIOLIB_LORAWAN_DEV_EUI   0x70B3D57ED006F85A
#endif
#ifndef RADIOLIB_LORAWAN_APP_KEY   // Replace with your App Key 
#define RADIOLIB_LORAWAN_APP_KEY   0x11, 0x25, 0x4E, 0x23, 0x83, 0xD5, 0x85, 0x39, 0x87, 0xC4, 0xDA, 0x5B, 0x68, 0xA6, 0x67, 0xA8
#endif
#ifndef RADIOLIB_LORAWAN_NWK_KEY   // Put your Nwk Key here
#define RADIOLIB_LORAWAN_NWK_KEY   0xDC, 0xEC, 0x04, 0x77, 0xB0, 0x90, 0x2C, 0x60, 0xF2, 0xA3, 0x40, 0x66, 0x44, 0x4A, 0x91, 0xFB
#endif

// for the curious, the #ifndef blocks allow for automated testing &/or you can
// put your EUI & keys in to your platformio.ini - see wiki for more tips

// regional choices: EU868, US915, AU915, AS923, AS923_2, AS923_3, AS923_4, IN865, KR920, CN500
const LoRaWANBand_t Region = AU915;
const uint8_t subBand = 2;  // For US915, change this to 2, otherwise leave on 0

// ============================================================================
// Below is to support the sketch - only make changes if the notes say so ...

// copy over the EUI's & keys in to the something that will not compile if incorrectly formatted
uint64_t joinEUI =   RADIOLIB_LORAWAN_JOIN_EUI;
uint64_t devEUI  =   RADIOLIB_LORAWAN_DEV_EUI;
uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY };
uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY };

// create the LoRaWAN node
LoRaWANNode node(&radio, &Region, subBand);

// result code to text - these are error codes that can be raised when using LoRaWAN
// however, RadioLib has many more - see https://jgromes.github.io/RadioLib/group__status__codes.html for a complete list
String stateDecode(const int16_t result) {
  switch (result) {
  case RADIOLIB_ERR_NONE:
    return "ERR_NONE";
  case RADIOLIB_ERR_CHIP_NOT_FOUND:
    return "ERR_CHIP_NOT_FOUND";
  case RADIOLIB_ERR_PACKET_TOO_LONG:
    return "ERR_PACKET_TOO_LONG";
  case RADIOLIB_ERR_RX_TIMEOUT:
    return "ERR_RX_TIMEOUT";
  case RADIOLIB_ERR_CRC_MISMATCH:
    return "ERR_CRC_MISMATCH";
  case RADIOLIB_ERR_INVALID_BANDWIDTH:
    return "ERR_INVALID_BANDWIDTH";
  case RADIOLIB_ERR_INVALID_SPREADING_FACTOR:
    return "ERR_INVALID_SPREADING_FACTOR";
  case RADIOLIB_ERR_INVALID_CODING_RATE:
    return "ERR_INVALID_CODING_RATE";
  case RADIOLIB_ERR_INVALID_FREQUENCY:
    return "ERR_INVALID_FREQUENCY";
  case RADIOLIB_ERR_INVALID_OUTPUT_POWER:
    return "ERR_INVALID_OUTPUT_POWER";
  case RADIOLIB_ERR_NETWORK_NOT_JOINED:
	  return "RADIOLIB_ERR_NETWORK_NOT_JOINED";
  case RADIOLIB_ERR_DOWNLINK_MALFORMED:
    return "RADIOLIB_ERR_DOWNLINK_MALFORMED";
  case RADIOLIB_ERR_INVALID_REVISION:
    return "RADIOLIB_ERR_INVALID_REVISION";
  case RADIOLIB_ERR_INVALID_PORT:
    return "RADIOLIB_ERR_INVALID_PORT";
  case RADIOLIB_ERR_NO_RX_WINDOW:
    return "RADIOLIB_ERR_NO_RX_WINDOW";
  case RADIOLIB_ERR_INVALID_CID:
    return "RADIOLIB_ERR_INVALID_CID";
  case RADIOLIB_ERR_UPLINK_UNAVAILABLE:
    return "RADIOLIB_ERR_UPLINK_UNAVAILABLE";
  case RADIOLIB_ERR_COMMAND_QUEUE_FULL:
    return "RADIOLIB_ERR_COMMAND_QUEUE_FULL";
  case RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND:
    return "RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND";
  case RADIOLIB_ERR_JOIN_NONCE_INVALID:
    return "RADIOLIB_ERR_JOIN_NONCE_INVALID";
  case RADIOLIB_ERR_N_FCNT_DOWN_INVALID:
    return "RADIOLIB_ERR_N_FCNT_DOWN_INVALID";
  case RADIOLIB_ERR_A_FCNT_DOWN_INVALID:
    return "RADIOLIB_ERR_A_FCNT_DOWN_INVALID";
  case RADIOLIB_ERR_DWELL_TIME_EXCEEDED:
    return "RADIOLIB_ERR_DWELL_TIME_EXCEEDED";
  case RADIOLIB_ERR_CHECKSUM_MISMATCH:
    return "RADIOLIB_ERR_CHECKSUM_MISMATCH";
  case RADIOLIB_ERR_NO_JOIN_ACCEPT:
    return "RADIOLIB_ERR_NO_JOIN_ACCEPT";
  case RADIOLIB_LORAWAN_SESSION_RESTORED:
    return "RADIOLIB_LORAWAN_SESSION_RESTORED";
  case RADIOLIB_LORAWAN_NEW_SESSION:
    return "RADIOLIB_LORAWAN_NEW_SESSION";
  case RADIOLIB_ERR_NONCES_DISCARDED:
    return "RADIOLIB_ERR_NONCES_DISCARDED";
  case RADIOLIB_ERR_SESSION_DISCARDED:
    return "RADIOLIB_ERR_SESSION_DISCARDED";
  }
  return "See https://jgromes.github.io/RadioLib/group__status__codes.html";
}

// helper function to display any issues
void debug(bool failed, const __FlashStringHelper* message, int state, bool halt) {
  if(failed) {
    Serial.print(message);
    Serial.print(" - ");
    Serial.print(stateDecode(state));
    Serial.print(" (");
    Serial.print(state);
    Serial.println(")");
    while(halt) { delay(1); }
  }
}

// helper function to display a byte array
void arrayDump(uint8_t *buffer, uint16_t len) {
  for(uint16_t c = 0; c < len; c++) {
    char b = buffer[c];
    if(b < 0x10) { Serial.print('0'); }
    Serial.print(b, HEX);
  }
  Serial.println();
}

#endif

I have successfully registered the device to my ChirpStack network server (with “Disable frame-counter validation” enabled:


The device connects and transmits successfully only after “Flush OTAA device nonces” is done.

If I reboot the board (regardless of how many times I try), I get the following error:





In the serial monitor of the board, I get:
Join failed - RADIOLIB_ERR_NO_JOIN_ACCEPT (-1116)

As soon as I select “Flush OTAA device nonces”, the device connects without issue.

I am using the latest version of ChirpStack via Docker (6th April 2025) which I downloaded and installed today.

Research and Findings

http://jgromes.github.io/RadioLib/struct_lo_ra_w_a_n_join_event__t.html#abdb57ec1e16ea0d1822ba0a5632c9372

From LoRaWAN.cpp:

int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) {
  // check if there is an active session
  if(this->isActivated()) {
    // already activated, don't do anything
    return(RADIOLIB_ERR_NONE);
  }
  if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) {
    // session restored but not yet activated - do so now
    this->isActive = true;
    return(RADIOLIB_LORAWAN_SESSION_RESTORED);
  }

  int16_t state = RADIOLIB_ERR_UNKNOWN;
  Module* mod = this->phyLayer->getMod();

  // starting a new session, so make sure to update event fields already
  if(joinEvent) {
    joinEvent->newSession = true;
    joinEvent->devNonce = this->devNonce;
    joinEvent->joinNonce = this->joinNonce;
  }

  // setup all MAC properties to default values
  this->createSession(RADIOLIB_LORAWAN_MODE_OTAA, joinDr);

  // build the JoinRequest message
  uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN];
  this->composeJoinRequest(joinRequestMsg);

  // select a random pair of Tx/Rx channels
  state = this->selectChannels();
  RADIOLIB_ASSERT(state);

  // set the physical layer configuration for uplink
  state = this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK],
                                 RADIOLIB_LORAWAN_UPLINK, 
                                 this->txPowerMax - 2*this->txPowerSteps);
  RADIOLIB_ASSERT(state);

  // calculate JoinRequest time-on-air in milliseconds
  if(this->dwellTimeUp) {
    RadioLibTime_t toa = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000;
    if(toa > this->dwellTimeUp) {
      RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Dwell time exceeded: ToA = %lu, max = %d", (unsigned long)toa, this->dwellTimeUp);
      return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED);
    }
  }

  // if requested, delay until transmitting JoinRequest
  RadioLibTime_t tNow = mod->hal->millis();
  if(this->tUplink > tNow) {
    RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow));
    if(this->tUplink > mod->hal->millis()) {
      mod->hal->delay(this->tUplink - mod->hal->millis());
    }
  }

  // send it
  state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN);
  this->rxDelayStart = mod->hal->millis();
  RADIOLIB_ASSERT(state);
  RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinRequest sent (DevNonce = %d) <-- Rx Delay start", this->devNonce);
  RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN);

  // JoinRequest successfully sent, so increase & save devNonce
  this->devNonce += 1;
  LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce);

  // set the Time on Air of the JoinRequest
  this->lastToA = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000;

  // configure Rx1 and Rx2 delay for JoinAccept message - these are re-configured once a valid JoinAccept is received
  this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS;
  this->rxDelays[2] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS;

  // handle Rx1 and Rx2 windows - returns window > 0 if a downlink is received
  state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart);
  if(state < RADIOLIB_ERR_NONE) {
    return(state);
  } else if (state == RADIOLIB_ERR_NONE) {
    return(RADIOLIB_ERR_NO_JOIN_ACCEPT);
  }

  // process JoinAccept message
  state = this->processJoinAccept(joinEvent);
  RADIOLIB_ASSERT(state);

  return(RADIOLIB_LORAWAN_NEW_SESSION);
}

From LoRaWAN.h :

virtual int16_t activateOTAA(uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, LoRaWANJoinEvent_t *joinEvent = NULL);


struct LoRaWANJoinEvent_t {
  /*! \brief Whether a new session was started */
  bool newSession = false;

  /*! \brief The transmitted Join-Request DevNonce value */
  uint16_t devNonce = 0;

  /*! \brief The received Join-Request JoinNonce value */
  uint32_t joinNonce = 0;
};

“devNonce” is always 0 on reboot of the device (I have verified this).

Given that there is no option to disable the “devNonce” check or reset the counter on each join in ChirpStack how can I resolve this automatically? Can I choose an older LoRaWAN version in ChripStack? The only issue with that is RadioLib states:

Choose LoRaWAN 1.1.0 - the last one in the list - the latest specification. RadioLib uses RP001 Regional Parameters 1.1 revision B.

Any help would be much apprecaited.

Well well well… look who we have here :wink: (guess what: the name is Steven!)

RadioLib’s LoRaWAN README points to the solution: use the radiolib-persistence examples! There you will find code that saves the DevNonces on your device so that it adheres to the 1.0.4 / 1.1 specification.

1 Like

Hi @bns (or should I say StevenCellist),

Thank you for your comment. I have given the LoRaWAN_ESP32 sketch a go.

Here is my code:

LoRaWAN_ESP32.ino

/*

This demonstrates how to save the join information in to permanent memory
so that if the power fails, batteries run out or are changed, the rejoin
is more efficient & happens sooner due to the way that LoRaWAN secures
the join process - see the wiki for more details.

This is typically useful for devices that need more power than a battery
driven sensor - something like a air quality monitor or GPS based device that
is likely to use up it's power source resulting in loss of the session.

The relevant code is flagged with a ##### comment

Saving the entire session is possible but not demonstrated here - it has
implications for flash wearing and complications with which parts of the 
session may have changed after an uplink. So it is assumed that the device
is going in to deep-sleep, as below, between normal uplinks.

Once you understand what happens, feel free to delete the comments and 
Serial.prints - we promise the final result isn't that many lines.

*/

#if !defined(ESP32)
  #pragma error ("This is not the example your device is looking for - ESP32 only")
#endif

// ##### load the ESP32 preferences facilites
#include <Preferences.h>
Preferences store;

// LoRaWAN config, credentials & pinmap
#include "config.h" 

#include <RadioLib.h>

// utilities & vars to support ESP32 deep-sleep. The RTC_DATA_ATTR attribute
// puts these in to the RTC memory which is preserved during deep-sleep
RTC_DATA_ATTR uint16_t bootCount = 0;
RTC_DATA_ATTR uint16_t bootCountSinceUnsuccessfulJoin = 0;
RTC_DATA_ATTR uint8_t LWsession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE];

// abbreviated version from the Arduino-ESP32 package, see
// https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/deepsleep.html
// for the complete set of options
void print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) {
    Serial.println(F("Wake from sleep"));
  } else {
    Serial.print(F("Wake not caused by deep sleep: "));
    Serial.println(wakeup_reason);
  }

  Serial.print(F("Boot count: "));
  Serial.println(++bootCount);      // increment before printing
}

// put device in to lowest power deep-sleep mode
void gotoSleep(uint32_t seconds) {
  esp_sleep_enable_timer_wakeup(seconds * 1000UL * 1000UL); // function uses uS
  Serial.println(F("Sleeping\n"));
  Serial.flush();

  esp_deep_sleep_start();

  // if this appears in the serial debug, we didn't go to sleep!
  // so take defensive action so we don't continually uplink
  Serial.println(F("\n\n### Sleep failed, delay of 5 minutes & then restart ###\n"));
  delay(5UL * 60UL * 1000UL);
  ESP.restart();
}

int16_t lwActivate() {
  int16_t state = RADIOLIB_ERR_UNKNOWN;

  // setup the OTAA session information
  node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);

  Serial.println(F("Recalling LoRaWAN nonces & session"));
  // ##### setup the flash storage
  store.begin("radiolib");
  // ##### if we have previously saved nonces, restore them and try to restore session as well
  if (store.isKey("nonces")) {
    uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE];										// create somewhere to store nonces
    store.getBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);	// get them from the store
    state = node.setBufferNonces(buffer); 															// send them to LoRaWAN
    debug(state != RADIOLIB_ERR_NONE, F("Restoring nonces buffer failed"), state, false);

    // recall session from RTC deep-sleep preserved variable
    state = node.setBufferSession(LWsession); // send them to LoRaWAN stack

    // if we have booted more than once we should have a session to restore, so report any failure
    // otherwise no point saying there's been a failure when it was bound to fail with an empty LWsession var.
    debug((state != RADIOLIB_ERR_NONE) && (bootCount > 1), F("Restoring session buffer failed"), state, false);

    // if Nonces and Session restored successfully, activation is just a formality
    // moreover, Nonces didn't change so no need to re-save them
    if (state == RADIOLIB_ERR_NONE) {
      Serial.println(F("Succesfully restored session - now activating"));
      state = node.activateOTAA();
      debug((state != RADIOLIB_LORAWAN_SESSION_RESTORED), F("Failed to activate restored session"), state, true);

      // ##### close the store before returning
      store.end();
      return(state);
    }

  } else {  // store has no key "nonces"
    Serial.println(F("No Nonces saved - starting fresh."));
  }

  // if we got here, there was no session to restore, so start trying to join
  state = RADIOLIB_ERR_NETWORK_NOT_JOINED;
  while (state != RADIOLIB_LORAWAN_NEW_SESSION) {
    Serial.println(F("Join ('login') to the LoRaWAN Network"));
    state = node.activateOTAA();

    // ##### save the join counters (nonces) to permanent store
    Serial.println(F("Saving nonces to flash"));
    uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE];           // create somewhere to store nonces
    uint8_t *persist = node.getBufferNonces();                  // get pointer to nonces
    memcpy(buffer, persist, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);  // copy in to buffer
    store.putBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // send them to the store
    
    // we'll save the session after an uplink

    if (state != RADIOLIB_LORAWAN_NEW_SESSION) {
      Serial.print(F("Join failed: "));
      Serial.println(state);

      // how long to wait before join attempts. This is an interim solution pending 
      // implementation of TS001 LoRaWAN Specification section #7 - this doc applies to v1.0.4 & v1.1
      // it sleeps for longer & longer durations to give time for any gateway issues to resolve
      // or whatever is interfering with the device <-> gateway airwaves.
      uint32_t sleepForSeconds = min((bootCountSinceUnsuccessfulJoin++ + 1UL) * 60UL, 3UL * 60UL);
      Serial.print(F("Boots since unsuccessful join: "));
      Serial.println(bootCountSinceUnsuccessfulJoin);
      Serial.print(F("Retrying join in "));
      Serial.print(sleepForSeconds);
      Serial.println(F(" seconds"));

      gotoSleep(sleepForSeconds);

    } // if activateOTAA state
  } // while join

  Serial.println(F("Joined"));

  // reset the failed join count
  bootCountSinceUnsuccessfulJoin = 0;

  delay(1000);  // hold off off hitting the airwaves again too soon - an issue in the US
  
  // ##### close the store
  store.end();  
  return(state);
}

// setup & execute all device functions ...
void setup() {
  Serial.begin(115200);
  while (!Serial);  							// wait for serial to be initalised
  delay(2000);  									// give time to switch to the serial monitor
  Serial.println(F("\nSetup"));
  print_wakeup_reason();

  int16_t state = 0;  						// return value for calls to RadioLib

  // setup the radio based on the pinmap (connections) in config.h
  Serial.println(F("Initalise the radio"));
  state = radio.begin();
  debug(state != RADIOLIB_ERR_NONE, F("Initalise radio failed"), state, true);

  // activate node by restoring session or otherwise joining the network
  state = lwActivate();
  // state is one of RADIOLIB_LORAWAN_NEW_SESSION or RADIOLIB_LORAWAN_SESSION_RESTORED

  // ----- and now for the main event -----
  Serial.println(F("Sending uplink"));

  // this is the place to gather the sensor inputs
  // instead of reading any real sensor, we just generate some random numbers as example
  uint8_t value1 = radio.random(100);
  uint16_t value2 = radio.random(2000);

  // build payload byte array
  uint8_t uplinkPayload[3];
  uplinkPayload[0] = value1;
  uplinkPayload[1] = highByte(value2);   // See notes for high/lowByte functions
  uplinkPayload[2] = lowByte(value2);
  
  // perform an uplink
  state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload));    
  debug((state < RADIOLIB_ERR_NONE) && (state != RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false);

  Serial.print(F("FCntUp: "));
  Serial.println(node.getFCntUp());

  // now save session to RTC memory
  uint8_t *persist = node.getBufferSession();
  memcpy(LWsession, persist, RADIOLIB_LORAWAN_SESSION_BUF_SIZE);
  
  // wait until next uplink - observing legal & TTN FUP constraints
  gotoSleep(uplinkIntervalSeconds);

}


// The ESP32 wakes from deep-sleep and starts from the very beginning.
// It then goes back to sleep, so loop() is never called and which is
// why it is empty.

void loop() {}

config.h

#ifndef _CONFIG_H
#define _CONFIG_H

#include <RadioLib.h>

// How often to send an uplink - consider legal & FUP constraints - see notes
const uint32_t uplinkIntervalSeconds = 1UL * 10UL;    // minutes x seconds

// JoinEUI - previous versions of LoRaWAN called this AppEUI
// for development purposes you can use all zeros - see wiki for details
#define RADIOLIB_LORAWAN_JOIN_EUI  0x0000000000000000

// The Device EUI & two keys can be generated on the TTN console 
#ifndef RADIOLIB_LORAWAN_DEV_EUI   // Replace with your Device EUI
#define RADIOLIB_LORAWAN_DEV_EUI   0x70B3D57ED006F85A
#endif
#ifndef RADIOLIB_LORAWAN_APP_KEY   // Replace with your App Key 
#define RADIOLIB_LORAWAN_APP_KEY   0x11, 0x25, 0x4E, 0x23, 0x83, 0xD5, 0x85, 0x39, 0x87, 0xC4, 0xDA, 0x5B, 0x68, 0xA6, 0x67, 0xA8
#endif
#ifndef RADIOLIB_LORAWAN_NWK_KEY   // Put your Nwk Key here
#define RADIOLIB_LORAWAN_NWK_KEY   0xDC, 0xEC, 0x04, 0x77, 0xB0, 0x90, 0x2C, 0x60, 0xF2, 0xA3, 0x40, 0x66, 0x44, 0x4A, 0x91, 0xFB
#endif

// For the curious, the #ifndef blocks allow for automated testing &/or you can
// put your EUI & keys in to your platformio.ini - see wiki for more tips



// Regional choices: EU868, US915, AU915, AS923, IN865, KR920, CN780, CN500
const LoRaWANBand_t Region = AU915;
const uint8_t subBand = 2;  // For US915, change this to 2, otherwise leave on 0


// ============================================================================
// Below is to support the sketch - only make changes if the notes say so ...

// Auto select MCU <-> radio connections
// If you get an error message when compiling, it may be that the 
// pinmap could not be determined - see the notes for more info

// Adafruit
#if defined(ARDUINO_SAMD_FEATHER_M0)
    #pragma message ("Adafruit Feather M0 with RFM95")
    #pragma message ("Link required on board")
    SX1276 radio = new Module(8, 3, 4, 6);


// LilyGo 
#elif defined(ARDUINO_TTGO_LORA32_V1)
  #pragma message ("TTGO LoRa32 v1 - no Display")
  SX1276 radio = new Module(18, 26, 14, 33);

#elif defined(ARDUINO_TTGO_LORA32_V2)
   #pragma error ("ARDUINO_TTGO_LORA32_V2 awaiting pin map")

#elif defined(ARDUINO_TTGO_LoRa32_v21new) // T3_V1.6.1
  #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display")
  SX1276 radio = new Module(18, 26, 14, 33);

#elif defined(ARDUINO_TBEAM_USE_RADIO_SX1262)
  #pragma error ("ARDUINO_TBEAM_USE_RADIO_SX1262 awaiting pin map")

#elif defined(ARDUINO_TBEAM_USE_RADIO_SX1276)
  #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display")
  SX1276 radio = new Module(18, 26, 23, 33);


// Heltec
#elif defined(ARDUINO_HELTEC_WIFI_LORA_32)
  #pragma error ("ARDUINO_HELTEC_WIFI_LORA_32 awaiting pin map")

#elif defined(ARDUINO_heltec_wifi_kit_32_V2)
  #pragma message ("ARDUINO_heltec_wifi_kit_32_V2 awaiting pin map")
  SX1276 radio = new Module(18, 26, 14, 35);

#elif defined(ARDUINO_heltec_wifi_kit_32_V3)
  #pragma message ("Using Heltec WiFi LoRa32 v3 - Display + USB-C")
  SX1262 radio = new Module(8, 14, 12, 13);

#elif defined(ARDUINO_CUBECELL_BOARD)
  #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display")
  SX1262 radio = new Module(RADIOLIB_BUILTIN_MODULE);

#elif defined(ARDUINO_CUBECELL_BOARD_V2)
  #pragma error ("ARDUINO_CUBECELL_BOARD_V2 awaiting pin map")


#else
  #pragma message ("Unknown board - no automagic pinmap available")

  // SX1262  pin order: Module(NSS/CS, DIO1, RESET, BUSY);
  // SX1262 radio = new Module(8, 14, 12, 13);

  // SX1278 pin order: Module(NSS/CS, DIO0, RESET, DIO1);
  // SX1278 radio = new Module(10, 2, 9, 3);

  SX1276 radio = new Module(18, 26, 14, 33);

#endif


// Copy over the EUI's & keys in to the something that will not compile if incorrectly formatted
uint64_t joinEUI =   RADIOLIB_LORAWAN_JOIN_EUI;
uint64_t devEUI  =   RADIOLIB_LORAWAN_DEV_EUI;
uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY };
uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY };

// Create the LoRaWAN node
LoRaWANNode node(&radio, &Region, subBand);


// Helper function to display any issues
void debug(bool isFail, const __FlashStringHelper* message, int state, bool Freeze) {
  if (isFail) {
    Serial.print(message);
    Serial.print("(");
    Serial.print(state);
    Serial.println(")");
    while (Freeze);
  }
}

// Helper function to display a byte array
void arrayDump(uint8_t *buffer, uint16_t len) {
  for (uint16_t c = 0; c < len; c++) {
    Serial.printf("%02X", buffer[c]);
  }
  Serial.println();
}


#endif

The code works with the device awaking from sleep, however if I disconnect power to the board and reconnect it, I get a couple of reconnect errors before it finally joins. Is this expected behaviour or something of concern?

Example 1 (Error -1116 and -1101):

20:19:15.321 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
20:19:15.321 -> configsip: 0, SPIWP:0xee
20:19:15.321 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
20:19:15.321 -> mode:DIO, clock div:1
20:19:15.321 -> load:0x3fff0030,len:1184
20:19:15.321 -> load:0x40078000,len:13260
20:19:15.321 -> load:0x40080400,len:3028
20:19:15.321 -> entry 0x400805e4
20:19:17.337 -> 
20:19:17.337 -> Setup
20:19:17.378 -> Wake from sleep
20:19:17.378 -> Boot count: 20
20:19:17.378 -> Initalise the radio
20:19:17.378 -> Recalling LoRaWAN nonces & session
20:19:17.378 -> Succesfully restored session - now activating
20:19:17.378 -> Sending uplink
20:19:19.775 -> FCntUp: 17
20:19:19.775 -> Sleeping
20:19:19.775 -> 

UNPLUGGED THE BOARD AND PLUGGED IT BACK IN HERE

20:19:28.270 -> 
20:19:28.270 -> Setup
20:19:28.270 -> Wake not caused by deep sleep: 0
20:19:28.270 -> Boot count: 1
20:19:28.270 -> Initalise the radio
20:19:28.317 -> Recalling LoRaWAN nonces & session
20:19:28.317 -> Join ('login') to the LoRaWAN Network
20:19:34.966 -> Saving nonces to flash
20:19:34.966 -> Join failed: -1116
20:19:34.966 -> Boots since unsuccessful join: 1
20:19:34.966 -> Retrying join in 60 seconds
20:19:34.966 -> Sleeping
20:19:34.966 -> 
20:20:34.531 -> ets Jun  8 2016 00:22:57
20:20:34.574 -> 
20:20:34.574 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
20:20:34.574 -> configsip: 0, SPIWP:0xee
20:20:34.574 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
20:20:34.574 -> mode:DIO, clock div:1
20:20:34.574 -> load:0x3fff0030,len:1184
20:20:34.574 -> load:0x40078000,len:13260
20:20:34.574 -> load:0x40080400,len:3028
20:20:34.574 -> entry 0x400805e4
20:20:36.596 -> 
20:20:36.596 -> Setup
20:20:36.596 -> Wake from sleep
20:20:36.596 -> Boot count: 2
20:20:36.596 -> Initalise the radio
20:20:36.641 -> Recalling LoRaWAN nonces & session
20:20:36.641 -> Restoring session buffer failed(-1101)
20:20:36.641 -> Join ('login') to the LoRaWAN Network
20:20:42.201 -> Saving nonces to flash
20:20:42.201 -> Joined
20:20:43.199 -> Sending uplink
20:20:45.852 -> FCntUp: 0
20:20:45.852 -> Sleeping
20:20:45.853 -> 
20:20:55.815 -> ets Jun  8 2016 00:22:57
20:20:55.859 -> 
20:20:55.859 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
20:20:55.859 -> configsip: 0, SPIWP:0xee
20:20:55.859 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
20:20:55.859 -> mode:DIO, clock div:1
20:20:55.859 -> load:0x3fff0030,len:1184
20:20:55.859 -> load:0x40078000,len:13260
20:20:55.859 -> load:0x40080400,len:3028
20:20:55.859 -> entry 0x400805e4
20:20:57.888 -> 
20:20:57.888 -> Setup
20:20:57.888 -> Wake from sleep
20:20:57.888 -> Boot count: 3
20:20:57.888 -> Initalise the radio
20:20:57.888 -> Recalling LoRaWAN nonces & session
20:20:57.933 -> Succesfully restored session - now activating
20:20:57.933 -> Sending uplink
20:20:59.474 -> FCntUp: 1
20:20:59.515 -> Sleeping

Example 2 (Error -1116 and -1101, same as Example 1):

20:26:18.892 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
20:26:18.892 -> configsip: 0, SPIWP:0xee
20:26:18.892 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
20:26:18.892 -> mode:DIO, clock div:1
20:26:18.892 -> load:0x3fff0030,len:1184
20:26:18.892 -> load:0x40078000,len:13260
20:26:18.892 -> load:0x40080400,len:3028
20:26:18.938 -> entry 0x400805e4
20:26:20.972 -> 
20:26:20.972 -> Setup
20:26:20.972 -> Wake from sleep
20:26:20.972 -> Boot count: 10
20:26:20.972 -> Initalise the radio
20:26:20.972 -> Recalling LoRaWAN nonces & session
20:26:20.972 -> Succesfully restored session - now activating
20:26:20.972 -> Sending uplink

UNPLUGGED THE BOARD AND PLUGGED IT BACK IN HERE

20:26:29.042 -> 
20:26:29.042 -> Setup
20:26:29.042 -> Wake not caused by deep sleep: 0
20:26:29.081 -> Boot count: 1
20:26:29.081 -> Initalise the radio
20:26:29.081 -> Recalling LoRaWAN nonces & session
20:26:29.081 -> Join ('login') to the LoRaWAN Network
20:26:35.697 -> Saving nonces to flash
20:26:35.731 -> Join failed: -1116
20:26:35.731 -> Boots since unsuccessful join: 1
20:26:35.731 -> Retrying join in 60 seconds
20:26:35.731 -> Sleeping
20:26:35.731 -> 
20:27:35.182 -> ets Jun  8 2016 00:22:57
20:27:35.182 -> 
20:27:35.182 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
20:27:35.182 -> configsip: 0, SPIWP:0xee
20:27:35.182 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
20:27:35.182 -> mode:DIO, clock div:1
20:27:35.182 -> load:0x3fff0030,len:1184
20:27:35.182 -> load:0x40078000,len:13260
20:27:35.182 -> load:0x40080400,len:3028
20:27:35.182 -> entry 0x400805e4
20:27:37.240 -> 
20:27:37.240 -> Setup
20:27:37.240 -> Wake from sleep
20:27:37.240 -> Boot count: 2
20:27:37.240 -> Initalise the radio
20:27:37.240 -> Recalling LoRaWAN nonces & session
20:27:37.240 -> Restoring session buffer failed(-1101)
20:27:37.240 -> Join ('login') to the LoRaWAN Network
20:27:42.796 -> Saving nonces to flash
20:27:42.843 -> Joined
20:27:43.798 -> Sending uplink
20:27:45.393 -> FCntUp: 0
20:27:45.393 -> Sleeping
20:27:45.393 -> 
20:27:55.323 -> ets Jun  8 2016 00:22:57
20:27:55.368 -> 
20:27:55.368 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
20:27:55.368 -> configsip: 0, SPIWP:0xee
20:27:55.368 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
20:27:55.368 -> mode:DIO, clock div:1
20:27:55.368 -> load:0x3fff0030,len:1184
20:27:55.368 -> load:0x40078000,len:13260
20:27:55.368 -> load:0x40080400,len:3028
20:27:55.368 -> entry 0x400805e4
20:27:57.395 -> 
20:27:57.395 -> Setup
20:27:57.395 -> Wake from sleep
20:27:57.395 -> Boot count: 3
20:27:57.395 -> Initalise the radio
20:27:57.395 -> Recalling LoRaWAN nonces & session
20:27:57.439 -> Succesfully restored session - now activating
20:27:57.439 -> Sending uplink
20:27:58.653 -> FCntUp: 1
20:27:58.688 -> Sleeping
20:27:58.688 -> 
20:28:08.601 -> ets Jun  8 2016 00:22:57
20:28:08.648 -> 
20:28:08.648 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
20:28:08.648 -> configsip: 0, SPIWP:0xee
20:28:08.648 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
20:28:08.648 -> mode:DIO, clock div:1
20:28:08.648 -> load:0x3fff0030,len:1184
20:28:08.648 -> load:0x40078000,len:13260
20:28:08.648 -> load:0x40080400,len:3028
20:28:08.648 -> entry 0x400805e4
20:28:10.672 -> 
20:28:10.672 -> Setup
20:28:10.672 -> Wake from sleep
20:28:10.672 -> Boot count: 4
20:28:10.672 -> Initalise the radio
20:28:10.672 -> Recalling LoRaWAN nonces & session
20:28:10.718 -> Succesfully restored session - now activating
20:28:10.718 -> Sending uplink
20:28:11.940 -> FCntUp: 2
20:28:11.940 -> Sleeping
20:28:11.940 -> 

Also, I have noticed that in this sketch’s notes.md, it states that:

RadioLib uses RP001 Regional Parameters 1.1 revision A.

Whereas in the notes.md for LoRaWAN_Starter, it states that:

RadioLib uses RP001 Regional Parameters 1.1 revision B.

Which one should I be using (assuming B)?

The persistence-repository is lagging behind there: you should follow upstream RadioLib (rev B), although there is virtually no difference between the two.

Regarding the logs: after power loss, -1101 is expected as the RTC variables are lost. However, it is supposed to join right away as it does restore the nonces. I haven’t had any issues with that on all of my devices, which is approaching a tally of 100 by now.
But after a -1116, a -1101 is expected again as there is no session to restore if the device isn’t joined. So as long as the device joins after a power loss, you shouldn’t see any more -1101 until you cycle the power.

Thank you @bns.

Given the -1116 is unexpected behaviour (not the -1101 following the -1116), how would you like me to troubleshoot?

I’m happy to share the logs from ChripStack and enable debug in the RadioLib library (Debug mode · jgromes/RadioLib Wiki · GitHub).

It’s current morning in Aus, so I’ll do it this evening.

Unfortunately I am not in a position right now to dig into this - real life is full of activities. Maybe you can find something yourself with debug enabled and a closer look at Chirpstack logs.

1 Like

@bns, I acknowledge that you have seen this already, but for other visitors to this post, I have posted this to the RadioLib GitHub repository. The story continues over there…