WiFi LoRa 32 V3: Send sensor data via LoRa, then go back to deep sleep

Cool :slight_smile: curious to hear if itā€™ll work out of the box!

Well, unfortunately, itā€™s not working out of the box. The code compiles nicely, I have everything changed as needed and described in the notes.md, yet I donā€™t receive any uplink messages. If I use the Heltec LoRaWAN code as is, I do receive packets in the console. Further, the RadioLib code gets stuck after executing the line Serial.println(F("Initalise the radio"));. I get neither the ā€œinitialise radio failedā€ nor the ā€œsending uplinkā€ message. Something very power hungry happens though, probably state = radio.begin(); being stuck.

Iā€™m trying to find out what in the OneWire.h or DallasTemperature.h libraries is causing the clash with the Heltec library, because EEPROM.h works fine with the Heltec library, but so far without success.

Can you share the config.h for the RadioLib setup, as well as the configuration of your Arduino IDE board selection etc? I have a very similar board, @nmcc has an identical one, it is likely a very simple solution to get the radio going.
You can share the DevEUI and JoinEUI without worries - the AppKey is the only sensitive thing (and possibly the NwkKey if you entered LoRaWAN v1.1).

Okay, here is the config.h file:

#ifndef _RADIOLIB_EX_LORAWAN_CONFIG_H
#define _RADIOLIB_EX_LORAWAN_CONFIG_H

#include <RadioLib.h>

// first you have to set your radio model and pin configuration
// this is provided just as a default example
SX1278 radio = new Module(10, 2, 9, 3);

// 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 = 5UL * 60UL;    // 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   0x70B3D57ED0063FB6
#endif
#ifndef RADIOLIB_LORAWAN_APP_KEY   // Replace with your App Key 
  #define RADIOLIB_LORAWAN_APP_KEY   {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set to all zeroes for privacy
#endif
#ifndef RADIOLIB_LORAWAN_NWK_KEY   // Put your Nwk Key here
  #define RADIOLIB_LORAWAN_NWK_KEY   {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set to all zeroes for privacy
#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 = EU868;
const uint8_t subBand = 0;  // 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 =   0x0000000000000000;
uint64_t devEUI  =   0x70B3D57ED0063FB6;
uint8_t appKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // set to all zeroes for privacy
uint8_t nwkKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // set to all zeroes for privacy

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

IDE, libraries, and boards are all up to date to the most recent version. Selected board is Heltec WiFi LoRa 32(V3) and the selected board is /dev/cu.usbserial-001 Serial Port (USB).

Under preferences, I have the following additional boards manager URLs:

https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
https://resource.heltec.cn/download/package_heltec_esp32_index.json

Here is also the .ino code so you can get a better overview over what I changed in the example file:


/*

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>

// SENSOR LIBRARIES
// -- temperature
#include <OneWire.h>
#include <DallasTemperature.h>
// -- electrical conductivity (EC)
#include <EEPROM.h>

// Enable Pin
#define VReg 47

// SENSOR DEFINITIONS
// -- Temperature
#define ONE_WIRE_BUS 7 // pin where the data wire is plugged into
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// -- pH Value
#define T 273.15               // degrees Kelvin ā†’ for temperature correction
#define Alpha 0.05916          // alpha ā†’ for temperature correction
#define Offset 1.37
#define LED 13
const int phPin = 6; // reading the analog value via pin GPIO6
float ph;
float Value = 0;
float pHvoltage, pHcorr; // define params for temperature-based pH correction

// -- total dissolved solids (TDS)
#define tdsPin 5
int tdsSensorValue = 0;
float tdsValue = 0;
float tdsVoltage = 0;

// -- electrical conductivity (EC)
#define EC_PIN 4
#define RES2 820.0
#define ECREF 200.0
float ecVoltage,ecValue;
float kvalue = 0.65; // war 0.996, jetzt nur noch 0.65 bzw. 0.8

// -- dissolved oxygen (DO)
#define DO_PIN 3
#define VREF 3300    //VREF (mv)
#define ADC_RES 4096 //ADC Resolution
#define TWO_POINT_CALIBRATION 1
//Single point calibration needs to be filled CAL1_V and CAL1_T
#define CAL1_V (1400) // mV
#define CAL1_T (27.81)   // Ā°C
//Two-point calibration needs to be filled CAL2_V and CAL2_T
//CAL1 High temperature point, CAL2 Low temperature point
#define CAL2_V (1160) // mV
#define CAL2_T (15.69)   // Ā°C

const uint16_t DO_Table[41] = {
    14460, 14220, 13820, 13440, 13090, 12740, 12420, 12110, 11810, 11530,
    11260, 11010, 10770, 10530, 10300, 10080, 9860, 9660, 9460, 9270,
    9080, 8900, 8730, 8570, 8410, 8250, 8110, 7960, 7820, 7690,
    7560, 7430, 7300, 7180, 7070, 6950, 6840, 6730, 6630, 6530, 6410};

uint8_t Temperaturet;
uint16_t ADC_Raw;
uint16_t ADC_Voltage;
uint16_t DO;

int16_t readDO(uint32_t voltage_mv, uint8_t temperature_c)
{
#if TWO_POINT_CALIBRATION == 0
  uint16_t V_saturation = (uint32_t)CAL1_V + (uint32_t)35 * temperature_c - (uint32_t)CAL1_T * 35;
  return (voltage_mv * DO_Table[temperature_c] / V_saturation);
#else
  uint16_t V_saturation = (int16_t)((int8_t)temperature_c - CAL2_T) * ((uint16_t)CAL1_V - CAL2_V) / ((uint8_t)CAL1_T - CAL2_T) + CAL2_V;
  return (voltage_mv * DO_Table[temperature_c] / V_saturation);
#endif
}

// -- turbidity
#define TU_PIN 2
float ntu;

// 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);

  pinMode(VReg, OUTPUT);
  digitalWrite(VReg, HIGH);   // turn the LED on (HIGH is the voltage level)

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

  // SENSOR INPUTS
  // -- DS18B20 Temperature
  sensors.begin(); // start up the sensor library
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempCByIndex(0); // there's only one temperature sensor so we can index to it
  
  // -- pH
  pinMode(phPin,INPUT); // get the pH output
  static float pHValue,voltage;
  Value = analogRead(phPin);
  voltage = Value * (3.3/4096.0); // ESP32 analogRead output is 0 to 4095, so divide by 4096; 3.3 because the GPIO only received a max of 3.3
  pHValue = 3.5*voltage;
  pHvoltage = voltage / 327.68;
  pHcorr = pHValue - Alpha * (T + tempC) * pHvoltage;
  
  // -- total dissolved solids
  // Steht hier alles drin weil #include "GravityTDS.h" nur mit Arduino UNO nicht aber mit ESP32 funktioniert.
  tdsSensorValue = analogRead(tdsPin);
  tdsVoltage = tdsSensorValue*(3.3/4096.0);
  float compensationCoefficient=1.0+0.02*(tempC-25.0);    //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
  float compVoltage=tdsVoltage/compensationCoefficient;
  tdsValue=(133.42/compVoltage*compVoltage*compVoltage - 255.86*compVoltage*compVoltage + 857.39*compVoltage)*0.5;
  
  // -- electrical conductivity (EC)
  ecVoltage = analogRead(EC_PIN)/4096.0*3300; // 1024.0*5000 fĆ¼r Arduino, 4096*3300 fĆ¼r ESP32
  float rawEC = 1000*ecVoltage/RES2/ECREF;
  float valueTemp = rawEC * kvalue; // 1.0 is the k-value (K = 1)
  if(valueTemp > 2.5){
    kvalue = 0.65; // war 0.996, jetzt nur noch 0.65 bzw. 0.8
  }else if(valueTemp < 2.0){
    kvalue = 0.65; // war 0.996, jetzt nur noch 0.65 bzw. 0.8
  }
  float value = rawEC * kvalue;             //calculate the EC value after automatic shift
  value = value / (1.0+0.0185*(tempC-25.0));  //temperature compensation
  float ecValue = value;

  // -- dissolved oxygen (DO)
  Temperaturet = tempC;
  ADC_Raw = analogRead(DO_PIN);
  ADC_Voltage = uint32_t(VREF) * ADC_Raw / ADC_RES;
  int do_raw = readDO(ADC_Voltage, Temperaturet);
  float DO = do_raw / 1000.0; // convert DO unit to mg/L

  // -- turbidity
  int sensorValue = analogRead(TU_PIN);
  Serial.println(sensorValue);
  float tb_voltage = sensorValue * (3.3 / 4096.0); // convert analog reading
  float multi = 100.0;
  tb_voltage = (tb_voltage * multi) / multi;
  if(tb_voltage < 1.83333333325){ // Wechsel von 2.5 auf 1.83333333325 da wir statt max 4.5V nur noch max 3.3V haben
    ntu = 3000;
  }else{
    //ntu = -1120.4*(tb_voltage*tb_voltage)+ 5742.3*tb_voltage - 4352.9; 
    // alte Gleichung: 4298 + -139x + -260x^2
    // ntu = 4298.0 - 139.0 * tb_voltage - 260.0 * (tb_voltage * tb_voltage);
    // neue Gleichung (01.12.2024): -4.87 * 10^-4 * x + 3.29
    ntu = -0.000487 * tb_voltage + 3.29;
  }

  // INTEGER CONVERSIONS
  int int_temp = tempC * 100; //remove comma
  int int_ph = pHcorr * 100;
  int int_tds = tdsValue * 100;
  int int_ec = ecValue * 100;
  int int_do = DO * 100;
  int int_ntu = ntu * 100;

  // 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);
  uint8_t uplinkPayload[12];
  uplinkPayload[0] = int_temp >> 8;
  uplinkPayload[1] = int_temp;
  uplinkPayload[2] = int_ph >> 8;
  uplinkPayload[3] = int_ph;
  uplinkPayload[4] = int_tds >> 8;
  uplinkPayload[5] = int_tds;
  uplinkPayload[6] = int_ec >> 8;
  uplinkPayload[7] = int_ec;
  uplinkPayload[8] = int_do >> 8;
  uplinkPayload[9] = int_do;
  uplinkPayload[10] = int_ntu >> 8;
  uplinkPayload[11] = int_ntu;

  
  // 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() {}

If there is any more information you need from me, please let me know. Thank you for the help!

I suggest you look at lines 6-8 of the config.h file :wink:
The radio on the V3 is an SX1262 instead of the default SX1278 with the pins being 8, 14, 12, 13 respectively if Iā€™m not mistaken.

1 Like

Ah, Iā€™m a fool :see_no_evil: Thanks for pointing it out ā€” classic case of when you canā€™t read the code from all the lines anymore.

Okay, I now get more output in the serial monitor, namely

17:27:47.099 -> Initalise the radio
17:27:47.131 -> Recalling LoRaWAN nonces & session
17:27:47.131 -> Join ('login') to the LoRaWAN Network
17:27:54.292 -> Saving nonces to flash
17:27:54.292 -> Join failed: -1116
17:27:54.292 -> Boots since unsuccessful join: 1
17:27:54.292 -> Retrying join in 60 seconds
17:27:54.292 -> Sleeping

In the console, I get a MIC mismatch notification. I copied the keys and euis from the device and pasted them in the code again, just to be sure itā€™s not an issue there. The joins are, however, still failing and the MIC mismatch error persists. In the troubleshooting help it says

MIC mismatch error in the device events: Possible mismatch of AppKey in device firmware to the AppKey registered in The Things Stack Console. Update the AppKey in the Console accordingly. Another cause can be using both AppKey and NwkKey for devices configured as LoRaWAN v1.0.x, where the Network Server will consider the device as capable for LoRaWAN v1.1. The configured NwkKey will be used for calculating the MIC and session key derivation in The Things Stack instead of AppKey, while the device will be using AppKey, causing the aforementioned error. You can use the command below to unset the NwkKey for the device:

ttn-lw-cli end-devices set --application-id <app-id> --device-id <dev-id> --unset root-keys.nwk-key

I feel that unsetting the NwkKey might help me because I used OTAA in the past but ā€” and I know this question sounds very dumb ā€” where do I enter that code? I cannot find a command line somewhere in the application overview. :pensive:

Thanks in advance!

No worries! Youā€™re doing great :smiley: and itā€™s quite a bit of code indeed.

RadioLib supports either LoRaWAN v1.0.4 or v1.1.
If you registered your device as v1.0.4, you must set the nwkKey argument as NULL in the line node.beginOTAA(...) in the lwActivate() function. If you registered your device as v1.1, the TTN console should provide you with a NwkKey.

1 Like

From the config.h

// Below is to support the sketch - only make changes if the notes say so ā€¦

ANYTHING you put in the bit you need to change is being overridden by the bits you changed that say only make changes if the notes say so. The notes donā€™t say so ā€¦

I know the notes donā€™t (yet) say use the RADIO_BOARD_AUTO but Iā€™d definitely do that because, well, it makes things work automagically. Previous automagic was removed :sob: :man_shrugging:

Apart from that, Iā€™d very strongly recommend that you roll back to the original example, get that talking to TTN and then add ONE device in at a time. Most of them are just analog reads and I donā€™t think there is a clash with any essential pins for them. But OneWire isnā€™t a trivial bit of code so it may be the culprit. Getting a OneWire temp readout on a Heltec WiFi 8 last week turned in to a game of change the pin until I found one that let it work.

As RadioLib LoRaWAN is like a white knight that can fly fast jets, is a paramedic, can cook to Michelin One Star standard and provide amazing back rubs, perhaps a sub-repro in RadioLib Org should be setup that merges all the ā€œbitsā€ in to place for the Heltec boards, given that it is @bnsā€™s not so secret obsession. And bizarrely with One Channel Hub, Semtechā€™s too!

1 Like

Okay, since I did use the NwkKey from the console, and that one did not work, I will try setting nwkKey to NULL as you said. In the console, under general information, my device has the info LoRaWAN Specification 1.0.2 so I assume that setting it to NULL should do the trick.

@nmcc Yes, I will go back to the basic code for simplicity and check out the RADIO_BOARD_AUTO as well, it sounds interesting.

And about the / Below is to support the sketch - only make changes if the notes say so ā€¦, I checked the notes.md and there it states:

// replace-with-your-device-id
uint64_t joinEUI =   0x0000000000000000;
uint64_t devEUI  =   0x0000000000000000;
uint8_t appKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t nwkKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

So I assumed that this part needs to be changed as well, on top of the part with the definitions.

I will try again tomorrow and let you know if I managed to send data after correctly implementing your valuable suggestions. As always, thank you for the help!

@jsteiwer one thing to pay attention to is the LoRaWAN versions. Heltecā€™s stack is indeed v1.0.2, but RadioLib implements v1.0.4 or v1.1. So you should switch over to v1.0.4 in the Settings page of your device (see below):

And then change this line:

node.beginOTAA(joinEUI, devEUI, NULL, appKey); // set NwkKey to NULL

@nmcc

I know that these all apply to youā€¦

ā€¦ but I havenā€™t experienced these yet. Or do you deploy the cats for those?

In itself a proper idea, but I suggest we first finish up our other projects :wink:

I did change the settings as per your description, then I tried uploading the code on the exact same port Iā€™ve been using the past days and got

esptool.py v4.6
Serial port /dev/cu.usbserial-0001

A fatal error occurred: Could not open /dev/cu.usbserial-0001, the port doesn't exist
Failed uploading: uploading error: exit status 2

after I was previously told the port is busy. :upside_down_face: Very funny (not).

I didnā€™t have the time today to test around some more but I will do that tomorrow, hope my port and the IDE will be friends again, and that the code compiles, uploads, and executes without issues (manifesting this).

UPDATE: After having the error again, I cross-checked if itā€™s really MSB for the AppKey or if itā€™s LSB. I then copied the MSB AppKey again, pasted in the code, and then, after getting ā€œDevNonce too smallā€ for a sec, the payload actually went through. Iā€™m not even sure what happened with the AppKey, it was the right one before, maybe it changed when I switched from 1.0.2 to 1.0.4 but that seems unlikely. Strange but at least I can get payload through now.

The use of edits is slightly confusing, but do I see correctly that itā€™s all working nicely now? :slight_smile:

Apologies for formulating things in a confusing matter, but yes, everything is working smoothly now, thank you so much for your help!

2 Likes