ropg / heltec_esp32_lora_v3

Proper working Arduino library for the Heltec ESP32 LoRa v3 board, as well as for Wireless Stick v3 and Wireless Stick Lite v3. Uses RadioLib
MIT License
269 stars 13 forks source link

DevNonce isnt being saved #20

Closed recklessop closed 2 months ago

recklessop commented 2 months ago

Recently I updated all the libraries to the latest builds. Now on a second packet to Chirpstack the Heltec clearly forgets the data that was supposed to be moved from rtc ram to EEPROM. When I used the library version from several weeks ago, this worked fine. So I'm just looking for help to see if my program might need a tweak, or if something in the library changed.

Here is the output from the code.

First Run:

rst:0x1 (POWERON),boot:0x28 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x4bc
load:0x403c9700,len:0xbd8
load:0x403cc700,len:0x2a0c
entry 0x403c98d0

Soil 0.51v
Battery 4.06v 94.00%
RTC RAM -> flash.
Next TX in 300 s

Second run:

ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x28 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x4bc
load:0x403c9700,len:0xbd8
load:0x403cc700,len:0x2a0c
entry 0x403c98d0
Radio init
[RadioLib] radio.begin() returned 0 (ERR_NONE)
Joining (forced)
[RadioLib] node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, RADIOLIB_LORAWAN_DATA_RATE_SF_12, true) returned -1116 (See https://jgromes.github.io/RadioLib/group__status__codes.html)
RTC RAM -> flash.
Next TX in 300 s

here is the program i am running:

#include <CayenneLPP.h>
#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <ESP32_RTC_EEPROM.h>

/*
  Deep Sleep LoRaWAN TTN example

  This example assumes the ESP32 stays powered, but puts it in a very low-power
  "deep sleep" mode between sends to the network.

  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.

  LoRaWAN v1.1 requires storing parameters persistently. RadioLib does this by
  using the Arduino EEPROM storage. The ESP32 does not have EEPROM memory, so
  the Arduino code from Espressif has code that emulates EEPROM in its "NVS"
  flash key-value storage. This unfortunately means that every change of a
  single but re- writes the entire emulated "EEPROM" to flash again. 

  That would be great for the occasional configuration change, but we need to
  keep state after every single interaction with the LoRaWAN network. Flash
  memory can only be erased and written to a limited number of times. This is
  why this library contains a fork of RadioLib that uses ESP32_RTC_EEPROM, which
  writes to the memory of the ESP32's onboard Real-Time Clock which is kept
  powered during the chip's "deep sleep", which uses almost no power.

  This example then backs up that RTC RAM memory to flash
    a) every time it is woken up with a wiped RTC RAM,
    b) whenever it receives a downlink packet, or 
    c) every so many times as set by BACKUP_EVERY.

  This backup is then automatically loaded when the RTC RAM is empty (after a
  reset or power failure) so that we hang on to enough state information to
  rejoin the network in a secure manner. 

  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
*/

// DevEUI - The device's Extended Unique Identifier
// TTN will generate one for you
uint64_t devEUI =  0x4ebaa92a4dce4de9;

// JoinEUI - previous versions of LoRaWAN called this AppEUI
// for development purposes you can use all zeros - see wiki for details
uint64_t joinEUI = 0xcdddb8a125bce659;

// encryption keys used to secure the communication
// TTN will generate them for you
// see wiki for details on copying & pasting them
uint8_t appKey[] = { 0x3f, 0x52, 0x7c, 0x2b, 0xb9, 0x66, 0x0c, 0x0c, 0x22, 0xe2, 0xc2, 0xe2, 0x59, 0x8c, 0xdf, 0x49 };
uint8_t nwkKey[] = { 0x5e, 0x93, 0xa8, 0x1d, 0xe6, 0x8a, 0xeb, 0x10, 0x78, 0x1d, 0x88, 0xd0, 0x78, 0xdb, 0xcf, 0x3e };

// Frequency in MHz. Keep the decimal point to designate float.
// Check your own rules and regulations to see what is legal where you are.
//#define FREQUENCY           866.3       // for Europe
#define FREQUENCY           905.2       // for US

// LoRa bandwidth. Keep the decimal point to designate float.
// Allowed values are 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0 and 500.0 kHz.
#define BANDWIDTH           250.0

// Number from 5 to 12. Higher means slower but higher "processor gain",
// meaning (in nutshell) longer range and more robust against interference. 
#define SPREADING_FACTOR    9

// Transmit power in dBm. 0 dBm = 1 mW, enough for tabletop-testing. This value can be
// set anywhere between -9 dBm (0.125 mW) to 22 dBm (158 mW). Note that the maximum ERP
// (which is what your antenna maximally radiates) on the EU ISM band is 25 mW, and that
// transmissting without an antenna can damage your hardware.
#define TRANSMIT_POWER      3

// Pause between sends in seconds, so this is every 5 minutes. (Delay will be
// longer if regulatory or TTN Fair Use Policy requires it.)
#define MINIMUM_DELAY 300

// Create a backup copy of the RTC RAM to flash every so many times
#define BACKUP_EVERY 100

// The LoRaWAN specification provides a port field (FPort) to distinguish
// between different types of messages on the receiving end. It's one byte, but
// FPort 0 is reserved for LoRaWAN-internal MAC messages, and values 224 through
// 255 (0xE0…0xFF) are reserved for future standardized application extensions,
// so don't use those.
#define FPORT 10

// create the node instance on the EU-868 band using the radio module and the
// encryption key make sure you are using the correct band based on your
// geographical location!
LoRaWANNode node(&radio, &US915, 1);

// for fixed bands with subband selection such as US915 and AU915, you must
// specify the subband that matches the Frequency Plan that you selected on your
// LoRaWAN console LoRaWANNode node(&radio, &US915, 2);

// Variables that are placed in RTC RAM survive deep sleep. This counter tells
// us how many times we've booted since the last reset or power loss.
RTC_DATA_ATTR int count = 1;

// This flag is set when we receive a downlink packet, so we can save to flash.
bool gotDownlink = false;

CayenneLPP lpp(64);

const int AirValue = 520;   //you need to replace this value with Value_1
const int WaterValue = 260;
int intervals = (AirValue - WaterValue)/3;
float soilMoistureValue = 0.0;

void setup() {
  heltec_setup();

  // Give USB serial some time so we can always see the output
  delay (2000);

  // initialize radio
  both.println("Radio init");
  RADIOLIB(radio.begin());
  if (_radiolib_status != RADIOLIB_ERR_NONE) {
    goToSleep();  // Does not return, program starts over next round
  }

  // Manages uplink intervals to the TTN Fair Use Policy
  node.setDutyCycle(true, 1250);
  node.setTxPower(30); // Set transmit power to 20 dBm

  // Join the network, or resume previous saved session
  if (count == 1) {

    // If this is the first boot, we woke up with a wiped RTC RAM. We assume we
    // were rebooted, have lost RTC RAM (and thus the uplink packet counter
    // FCntUp) and need to join the network again with our keys and nonces saved
    // in flash.   RADIOLIB_LORAWAN_DATA_RATE_UNUSED
    both.printf("Joining (forced)\n");
    RADIOLIB(node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, RADIOLIB_LORAWAN_DATA_RATE_SF_12, true));

  } else {

    // If we woke up with RTC RAM intact, continue with what we have.
    both.printf("Joining\n");
    RADIOLIB(node.beginOTAA(joinEUI, devEUI, nwkKey, appKey));

  }

  // Let's see what happened when we tried to join
  if (_radiolib_status == RADIOLIB_ERR_NONE) {

    both.println("Joined network");

  } else if (_radiolib_status == RADIOLIB_LORAWAN_MODE_OTAA){

    both.println("Was still joined");

  } else {

    // Something went wrong, print error and go to sleep
    display.printf("Join failed: %i\n  (%s)\n",
                   _radiolib_status,
                   radiolib_result_string(_radiolib_status).c_str()
                  );
    goToSleep();   // Does not return, program starts over next round

  }

  // Preparing the CayenneLPP payload
  soilMoistureValue = readSoilMoisture();
  float batteryVoltage = heltec_vbat();
  float batteryPercent = heltec_battery_percent();
  lpp.reset(); // Reset the buffer
  lpp.addAnalogInput(1, batteryVoltage);  // Channel 2, Battery Voltage as Analog Input
  lpp.addAnalogInput(2, soilMoistureValue); //Channel 3, Soil Moisture Sensor

  // Send the data
  String strDown;
  Serial.printf("TX: Sending LPP Data\n");
  RADIOLIB(node.sendReceive(lpp.getBuffer(), lpp.getSize(), FPORT, strDown));

  //RADIOLIB(node.sendReceive(strUp, FPORT, strDown));
  if(_radiolib_status == RADIOLIB_ERR_NONE) {

    // We got a return packet!!
    handleReceived(strDown);

  } else if(_radiolib_status == RADIOLIB_ERR_RX_TIMEOUT) {

    // Don't be deceived by this being called 'ERR_RX_TIMEOUT', It's not an
    // error; not receiving anything in response to sending a packet is what
    // happens most of the time in LoRaWAN. So we do nothing special here.

  } else {

    // Print error 
    display.printf("TX failed: %i\n  (%s)\n",
                   _radiolib_status,
                   radiolib_result_string(_radiolib_status).c_str()
                  );
  }

  both.print("\nSoil ");
  both.print(soilMoistureValue);
  both.println("v");
  both.print("Battery ");
  both.print(batteryVoltage);
  both.print("v ");
  both.print(batteryPercent);
  both.println("%");

  goToSleep();    // Does not return, program starts over next round

}

void loop() {

  // This is never called. There is no repetition: we always go back to deep
  // sleep one way or the other at the end of setup()
}

void handleReceived(String strDown) {

  // Set the flagh so we know to save the state to flash before going to sleep.
  gotDownlink = true;

  both.print("RX: ");
  // print data of the packet (if there are any)
  if(strDown.length() > 0) {
    both.println(strDown);
  } else {
    both.println("<MAC cmds only>");
  }

  // print RSSI (Received Signal Strength Indicator)
  both.printf("  RSSI: %.1f dBm\n", radio.getRSSI());

  // print SNR (Signal-to-Noise Ratio)
  both.printf("  SNR: %.1f dB\n", radio.getSNR());

  // // print frequency error
  // both.printf("  Freq err: %.1f Hz\n", radio.getFrequencyError());
}

void goToSleep() {

  // allows recall of the session after deepsleep
  node.saveSession();

  // If we woke up with wiped RTC RAM, or received a message, or we've
  // reached BACKUP_EVERY, we back it up to flash before going to sleep.
  if (count == 1 || gotDownlink == true || count % BACKUP_EVERY == 0) {
    both.println("RTC RAM -> flash.");
    EEPROM.toNVS();
  }

  // Raise the boot counter, kept in RTC RAM
  count++;

  // Calculate minimum duty cycle delay (per FUP & law!)
  uint32_t interval = node.timeUntilUplink();

  // And then pick it or our MINIMUM_DELAY, whichever is greater
  uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);

  both.printf("Next TX in %i s\n", delayMs/1000);

  // Give the user a chance to see the message
  //both.println("Deep sleep in 5 s");
  delay(5000);

  // And off to bed we go
  heltec_deep_sleep((delayMs / 1000) - 5);

}

float readBatteryVoltage() {
  int raw = analogRead(A0);
  float voltage = raw / 4095.0 * 3.3;  // Convert ADC value to voltage
  return voltage;
}

float readSoilMoisture() {
  int raw = analogRead(A6);
  float voltage = raw / 4095.0 * 3.3;
  return voltage;
}
ropg commented 2 months ago

Forget anything that tries to save to EEPROM, and try (basing off) the example that comes with the LoRaWAN_ESP32 library. Or, if you really want to hardcode credentials, the modified example here.

ropg commented 2 months ago

Did you work it out, or do you need further help?

ropg commented 2 months ago

Closing issue, feel free te reopen if you need further help.

xistoso commented 2 months ago

Sorry to comment in a closed issue. But i'm facing the same issue. Looks like the library is repeating the "devnounce" and chirpstack has the hability to remember a few devnouces. How are you generating those random values? I can only communicate like 2 - 5 times before facing this issue. And needing to flush the nouces in chirpstack side.

ex log in chirpstack: ... level:"ERROR" code:"OTAA" description:"DevNonce has already been used"

ropg commented 1 month ago

Please post the code used and I'll have a look.