Program hangs after several hours - MKR WAN 1310 // LoRa // RingBuf

Hi,

Long time lurker of this forum and have had many hours of help off this forum, which I am immensely grateful for.

I'm writing the most complex project I've done so far, and I'm out of ideas how to debug this effectively.

Problem:

  • Program seems initially stable and operates as intended, but freezes up after hours (sometimes one, sometimes 4 or more)
  • This is intended to run out in the garden in a container to monitor a garden setup this summer from a 12v battery, but I can't move it outside until it can run weeks without issue.

Hardware:

Intended Behaviour

  1. Initial setup, initialize sensors, variables, arrays
  2. Sends request for time update from a receiver station using a LoRa message. It receives this back and updates the RTC settings
  3. Begins looping, on interrupt, every x (currently 10) seconds, it takes a sensor reading and stores it in a ring buffer (RingBuffer library), along with a timestamp from the RTC
  4. While looping, checks buffer for stored messages. If a message is stored, it sends this via a LoRa message to a base station, and (should) recieve back a confirmation message with the timestamp. If so, removes entry from buffer. If no confirmation, it'll retry on next loop
  5. In case of interruption (e.g. patchy radio signal), it'll store up to x messages in ring buffer, and try to cycle through them.

I'm operating in a fairly consistent radio environment for LoRa, with few dropped messages when running. As I say above, the code seems to work without issues for multiple hours, but then stops. There are no error messages at this point over the Serial Monitor.

(Issue persists with both IDE 1.8 and 2.0, and also when not connected and running off USB power supply)

I am not experienced, and I'm expecting I've done something wrong to do with memory management but have no idea how to debug it or what to try!

Many thanks in advance for suggestions!

Full Code:

#include <SPI.h>
#include <LoRa.h>
#include <RingBuf.h>
#include "Adafruit_seesaw.h"
#include <RTCZero.h>

Adafruit_seesaw ss;

int incSecs = 10; // message timer increment

int wait = 10;    // wait time for array prints
unsigned long startTime;    // start of wait timer
unsigned long elapsedTime;  // trigger for wait timer

/* Create an rtc object */
RTCZero rtc;

// Container for LoRaData String & outgoing packet
RingBuf<String, 5> myBuffer; // create Ring Buffer for storing sensor values
String LoRaData; // String container for LoRa messages
String data; // String container for message data to be sent

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);

  rtc.begin(); // initialize RTC 24H format
  String initialSet = "xxxx000000000000";
  updateTime(initialSet); // Initialize RTC on boot to 00:00:00. Ensures no odd issues with failiure to update.

  Serial.println("LoRa Startup"); // Start LoRA module
  if (!LoRa.begin(868E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }

  doTimeUpdate(); // Update RTC over radio
  setupSoilSensor(); // Initialize soil sensor

  // Set up desired alarm time for first increment based on current RTC
  packet = "";
  Serial.println("Setting alarm");
  alarmInc(incSecs); // Sets Alarm for x secs incrmment from now

  // Set Alarm time
  rtc.enableAlarm(rtc.MATCH_HHMMSS);
  Serial.println("Alarm set");

  // Attach interrupt to taking sensor reading
  rtc.attachInterrupt(takeAnalogReading); //when interrupt is triggered, take sensor reading
  Serial.flush();
  LoRaData = ""; // make sure stored LoRa mesage is empty
  blink(5, 100); 
  startTime = millis();
  delay(1000); // wait for 1 second to ensure base station is ready
}

void loop() {

  if (!myBuffer.isEmpty()) {
    // Execute necessary function for sending data
    data = myBuffer[0];
    Serial.print("Sending: "); Serial.println(data); 
    sendMessage(data);  // sends radio message to base unit for upload to DB
    LoRa.onReceive(onReceive); //onRecieve interrupt, listens for LoRa packets
    Serial.println("Waiting to recieve");
    LoRa.receive(); // puts radio into recieve mode


    // Delay for confirmation (needs to be about >100ms to be safe)
    delay(100);
    if (recieveConfirmation(data)) { // if correct confirmation is recieved
      if (myBuffer.lockedPop(data)) { // remove message from array using blocking Pop function
        Serial.print("Popped: "); Serial.println(data); // confirm removal 
      }
    }
    else {
      delay(1000); // if no confirmation, wait 1 second before trying again
    }
  }

  //  Check elapsed time since last update, blink and print array
  elapsedTime = millis() - startTime;
  if (elapsedTime > (wait * 1000) ) {
    startTime = millis();
    blink(3, 150);
    if (!myBuffer.isEmpty()) {
      // loop & print buffer contents (if any) at regular interval
      Serial.println("");
      for (uint8_t j = 0; j < myBuffer.size(); j++) {
        Serial.print(myBuffer[j]);
        Serial.print("//");
      }
      Serial.println("");
      Serial.println("--------");
      Serial.flush();
    }
  }
}

bool recieveConfirmation(String data) {
  int recTime = LoRaData.substring(4, 10).toInt(); // cut recieved packet to timestamp only, as Int (first 3 chars are identifier for messsage type)
  int index = data.lastIndexOf('/'); // find index of last delimiter character
  int timestamp = data.substring(index + 1, index + 7).toInt(); // use index to identify timestamp
  //Serial.println(""); Serial.println(LoRaData); Serial.println(recTime); Serial.println(timestamp);
  if (recTime == timestamp) { // compare message timestamp to confirmation timestamp, return true if match
    return true;
  }
  else {
    return false;
  }
}

void sendMessage(String outgoing) {   // sends passed string as LoRa message
  LoRa.beginPacket();                   // start packet
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  Serial.print("Sent: "); Serial.println(outgoing);
}

void updateTime(String message) { // updates unit RTC based on LoRa time handshake being recieved
  //Serial.println(message);
  String data = message.substring(5); 
  //Serial.println(data);
  byte day = data.substring(0, 2).toInt();
  byte month = data.substring(2, 4).toInt();
  byte year = data.substring(4, 6).toInt();
  byte hours = data.substring(6, 8).toInt();
  byte minutes = data.substring(8, 10).toInt();
  byte seconds = data.substring(10, 12).toInt();
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);
  Serial.print("Set date & time to: ");
  Serial.print(rtc.getDay()); Serial.print("/");
  Serial.print(rtc.getMonth()); Serial.print("/");
  Serial.print(rtc.getYear()); Serial.print(" ");
  Serial.print(rtc.getHours()); Serial.print(":");
  Serial.print(rtc.getMinutes()); Serial.print(":");
  Serial.println(rtc.getSeconds());
}

String print2digits(int number) { // Convert single digit numbers to 2 digits if <10, for time formatting
  if (number < 10) {
    String result = "0" + String(number);
    return result;
  }
  else {
    return String(number);
  }
}

void onReceive(int packetSize) { // recieves LoRa packet and stores data in global variable LoRaData

  // received a packet
  Serial.print("Received packet '");

  // read packet
  LoRaData = LoRa.readString();

  // print RSSI of packet
  Serial.print(LoRaData);
  Serial.print("' with RSSI ");
  Serial.print(LoRa.packetRssi());
  Serial.print(" // ");
}

void blink(int count, int pause) {
  for (int i = 0; i < count; i++) {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(pause);                       // wait for a second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    delay(pause);
  }
}

void alarmInc(int inc) { // Function that gets current RTC clock time, and sets alarm for inc seconds in future
  int secs; int mins; int hours;

  secs = rtc.getSeconds();
  mins = rtc.getMinutes();
  hours = rtc.getHours();
  rtc.setAlarmTime(hours, mins, secs);

  if (rtc.getSeconds() + inc >= 60) {
    //Serial.println("increment minute/hr");
    secs = rtc.getSeconds() + (inc - 60);
    if (rtc.getMinutes() + 1 >= 60) {
      mins = rtc.getMinutes() + (1 - 60);
      if (rtc.getHours() + 1 >= 24) {
        hours = rtc.getHours() + (1 - 24);
      }
      else {hours = rtc.getHours() + 1;}
    }
    else {mins = rtc.getMinutes() + 1;}
  }
  else {
    secs = rtc.getSeconds() + inc;
    mins = rtc.getMinutes();
    hours = rtc.getHours();
  }
  rtc.setAlarmTime(hours, mins, secs);
}

void takeAnalogReading() { // takes sensor temp and capacitive moisure values.
  float tempC = ss.getTemp();
  uint16_t capread = ss.touchRead(0);

  //Serial.print("Temperature: "); Serial.print(tempC); Serial.println("*C");
  //Serial.print("Capacitive: "); Serial.println(capread);

  String timestamp = print2digits(rtc.getHours()) + print2digits(rtc.getMinutes()) + print2digits(rtc.getSeconds()); // gets timestamp from rtc
  String header = "p/";
  String packet = header + tempC + "/" + capread + "/" + timestamp; // creates message string

  // adds new message string to buffer
  if (! myBuffer.push(packet)) {
    // oops error, push failed because the buffer is full
    Serial.println("Oops Buffer Full"); // if full, do nothing
  }

  alarmInc(incSecs); // increment next alarm by x seconds
  blink(2, 250); // blink for confirmation
}

void doTimeUpdate() {
  while (1) {
    //Request time packet
    Serial.println("Requesting time update");
    packet = "update";
    sendMessage(packet);

    LoRa.onReceive(onReceive);
    LoRa.receive();
    //Serial.println(LoRaData);

    if (LoRaData != "" ) {
      updateTime(LoRaData);
      blink(5, 50);
      break;
    }

    // Loop if not recieved
    blink(2, 1000);
    delay(500);
  }
}

void setupSoilSensor() {
  //Serial.println("seesaw Soil Sensor example!");
  if (!ss.begin(0x36)) {
    Serial.println("ERROR! seesaw not found");
    while (1) delay(1);
  } else {
    Serial.print("seesaw started! version: ");
    Serial.println(ss.getVersion(), HEX);
  }
}

I hate to say it, but at first glance, the use of Strings and [arrays] in a complex way is screaming out to give you problems.

I’d suspect some overflow, out of bounds or stack corruption to be hiding in there.

Maybe some Serial.prints will help you localise where it crashes, but beyond that it could be really easy, or REALLY hard to fix.

Don't use this as a way to avoid debugging it, but I strongly recommend that you use the watchdog to restart the Arduino after a crash:

https://create.arduino.cc/projecthub/rafitc/what-is-watchdog-timer-fffe20

I feared this might be a response. It's a shame because I send the message as a string over radio as a packet, so storing as a string seems the most straightforward way, but I appreciate Strings have issues. I will need to look into some good alternative there.

It feels like writing an alternative for that is probably the first thing to try, then report back.

This sounds really helpful, if something goes wrong it'll reboot itself? At the very least this will cut down on trips outside (much to be valued!)

I'm definitely going to look into integrating this. Thanks!

So send a character array.

Yes. Watchdogs are used to improve system resilience - crash recovery can play an important part in that. As I say, they aren't supposed to be an alternative to bug hunting. :grinning:

Certainly no substitute for correcting your code.

No one should deploy a watchdog solution and then sleep any better without having read and appreciated this article

Not a panacea, possibly giving one a false sense of security &c.

a7

Totally agree. But still a powerful and useful tool when resilience is important (such as remote or hard-to-access systems).

So a brief update on this, I've updated to almost entirely removed strings (converting data to a single string just to send the radio message) but unfortunately doesn't seem to have solved the problem. My best run is still only 5 hours long.

I haven't implemented a watchdog yet as i wanted to see if I could solve the root cause first. Any other suggestions?

Basic process remains the same as above:

#include <SPI.h>
#include <RTCZero.h>
#include <CircularBuffer.h>
#include <LoRa.h>
#include "Adafruit_seesaw.h"

Adafruit_seesaw ss;

/* Create an rtc object */
RTCZero rtc;

namespace data {
  typedef struct {
    unsigned long timeStamp;
    unsigned int mois;
    float temp;
  } record;

  void print(record r) {
    Serial.print(r.timeStamp);
    Serial.print("  ");
    Serial.print(r.mois);
    Serial.print("  ");
    Serial.print(r.temp);
  }
}

//#define CIRCULAR_BUFFER_INT_SAFE
CircularBuffer<data::record, 10> structs;

#define SAMPLE_PIN A0
struct sensorReading{
  unsigned long timeStamp;
  unsigned int mois;
  float temp;
  } record;
String LoRaData;
int incSecs = 10; // reading timer increment
int wait = 10;    // wait time for array prints
unsigned long startTime;    // start of wait timer
unsigned long elapsedTime;  // trigger for wait timer
sensorReading sensorReading0 = { 0, 0, 0};
sensorReading stored0 = { 0, 0, 0};

void setup()
{
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  //while (!Serial);
  
  rtc.begin(); // initialize RTC 24H format
  byte timeSet[] = { 0 , 0 , 0 , 0 , 0 , 0 };
  updateTime(timeSet[0], timeSet[1], timeSet[2], timeSet[3], timeSet[4], timeSet[5]); // Initialize RTC on boot to 00:00:00. Ensures no odd issues with failiure to update.

  Serial.println("LoRa Startup"); // Start LoRA module
  if (!LoRa.begin(868E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }

  setupSoilSensor();
  
  doTimeUpdate();
  

  // Set up desired alarm time for first increment based on current RTC
  Serial.println("Setting alarm");
  alarmInc(incSecs); // Sets Alarm for x secs incrmment from now
  // Set Alarm time
  rtc.enableAlarm(rtc.MATCH_HHMMSS);
  Serial.println("Alarm set");
  
  pinMode(SAMPLE_PIN, INPUT);
  Serial.println("STARTING UP");

  // Attach interrupt to taking sensor reading
  rtc.attachInterrupt(takeAnalogReading); //when interrupt is triggered, take sensor reading
 
  Serial.flush();
  LoRaData = ""; // make sure stored LoRa mesage is empty
  blink(5, 100); 
  startTime = millis();
  delay(500);
}

void loop()
{
  /*Serial.println(startTime);
  if (millis() > startTime + (incSecs*1000) ){
    takeAnalogReading();
  }*/
  
   // unsigned int sample = analogRead(SAMPLE_PIN);
  if (sensorReading0.timeStamp != 0 ) {
    Serial.print("Sending: "); Serial.println(sensorReading0.timeStamp); 
    sendReading(sensorReading0.timeStamp, sensorReading0.mois, sensorReading0.temp);  // sends radio message to base unit for upload to DB
    LoRa.onReceive(onReceive); //onRecieve interrupt, listens for LoRa packets
    LoRa.receive(); // puts radio into recieve mode
    
    // Delay for confirmation (needs to be about >100ms to be safe)
    delay(250);
    if (recieveConfirmation(sensorReading0.timeStamp)) { // if correct confirmation is recieved
      Serial.print("Popping: "); Serial.println(sensorReading0.timeStamp); // confirm removal 
      sensorReading0 = { 0, 0, 0};
    }
    else {
      structs.push(data::record{sensorReading0.timeStamp, sensorReading0.mois, sensorReading0.temp});
      Serial.println("Saving record in buffer");
      sensorReading0 = {0, 0, 0};
    }
    Serial.println("---");
    delay(50);
  }

  if (!structs.isEmpty() ){
    Serial.print("Sending: "); Serial.println(structs.first().timeStamp); 
    sendReading(structs.first().timeStamp, structs.first().mois, structs.first().temp);  // sends radio message to base unit for upload to DB
    delay(20); // Pause briefly to allow hub unit to respond and not recieve old response
    LoRa.onReceive(onReceive); //onRecieve interrupt, listens for LoRa packets
    LoRa.receive(); // puts radio into recieve mode
    
    // Delay for confirmation (needs to be about >100ms to be safe)
    delay(250);

    if (recieveConfirmation(structs.first().timeStamp)) { // if correct confirmation is recieved
      Serial.print("Confirmed. Shifting: ");
      data::print(structs.shift());
      Serial.println();
      }
    }
 
  
  if (structs.isFull()) {
    Serial.println("Stack is full:");
    while (!structs.isEmpty()) {
      data::print(structs.shift());
      Serial.println();
    }
    Serial.println("START AGAIN");
  }
  
  delay(1000);
}

void takeAnalogReading() { // takes sensor temp and capacitive moisure values.
  
  float tempC = ss.getTemp();
  uint16_t capread = ss.touchRead(0);
  
  //Serial.print("Temperature: "); Serial.print(tempC); Serial.println("*C");
  //Serial.print("Capacitive: "); Serial.println(capread);
  
  //String timestamp = print2digits(rtc.getHours()) + print2digits(rtc.getMinutes()) + print2digits(rtc.getSeconds()); // gets timestamp from rtc
  unsigned long timestamp = (rtc.getHours() * 10000) + (rtc.getMinutes() * 100) + (rtc.getSeconds() );
  //Serial.println(timestamp);
  
  sensorReading0.temp = tempC;
  sensorReading0.mois = capread;
  sensorReading0.timeStamp = timestamp;

  Serial.print("Taken reading at ");
  Serial.println(sensorReading0.timeStamp);
  alarmInc(incSecs); // increment next alarm by x seconds
  blink(2, 250); // blink for confirmation
}

// updates unit RTC based on LoRa time handshake being recieved
void updateTime(byte day, byte month, byte year, byte hours, byte minutes, byte seconds) { 
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);
  Serial.print("Set date & time to: ");
  Serial.print(rtc.getDay()); Serial.print("/");
  Serial.print(rtc.getMonth()); Serial.print("/");
  Serial.print(rtc.getYear()); Serial.print(" ");
  Serial.print(rtc.getHours()); Serial.print(":");
  Serial.print(rtc.getMinutes()); Serial.print(":");
  Serial.println(rtc.getSeconds());
}

void sendUpdate() {   // sends passed string as LoRa message
  String outgoing = "update";
  LoRa.beginPacket();                   // start packet
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  Serial.print("Sent: "); Serial.println(outgoing);
}

void sendReading(unsigned long timeStamp, unsigned int mois, float temp) {   // sends passed string as LoRa message
  String outgoing;
  if (timeStamp < 100000){
    outgoing = "p/" + String(temp) + "/" + String(mois) + "/0" + String(timeStamp); // creates message string
  }
  else {
    outgoing = "p/" + String(temp) + "/" + String(mois) + "/" + String(timeStamp); // creates message string
  }
  LoRa.beginPacket();                   // start packet
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  Serial.print("Sent: "); Serial.println(outgoing);
}

// Function that gets current RTC clock time, and sets alarm for inc seconds in future
void alarmInc(int inc) { 
  int secs; int mins; int hours;

  secs = rtc.getSeconds();
  mins = rtc.getMinutes();
  hours = rtc.getHours();
  rtc.setAlarmTime(hours, mins, secs);

  if (rtc.getSeconds() + inc >= 60) {
    //Serial.println("increment minute/hr");
    secs = rtc.getSeconds() + (inc - 60);
    if (rtc.getMinutes() + 1 >= 60) {
      mins = rtc.getMinutes() + (1 - 60);
      if (rtc.getHours() + 1 >= 24) {
        hours = rtc.getHours() + (1 - 24);
      }
      else {hours = rtc.getHours() + 1;}
    }
    else {mins = rtc.getMinutes() + 1;}
  }
  else {
    secs = rtc.getSeconds() + inc;
    mins = rtc.getMinutes();
    hours = rtc.getHours();
  }
  rtc.setAlarmTime(hours, mins, secs);
}

void doTimeUpdate() {
  while (1) {
    //Request time packet
    Serial.println("Requesting time update");
    sendUpdate();

    LoRa.onReceive(onReceive);
    LoRa.receive();
    Serial.println(LoRaData);

    String data = LoRaData.substring(5); 
    Serial.println(data);
    byte day = data.substring(0, 2).toInt();
    byte month = data.substring(2, 4).toInt();
    byte year = data.substring(4, 6).toInt();
    byte hours = data.substring(6, 8).toInt();
    byte minutes = data.substring(8, 10).toInt();
    byte seconds = data.substring(10, 12).toInt();

    if (LoRaData != "" ) {
      updateTime(day, month, year, hours, minutes, seconds);
      blink(5, 50);
      break;
    }

    // Loop if not recieved
    blink(2, 1000);
    delay(500);
  }
}

bool recieveConfirmation(unsigned long timestamp) {
  int recTime = LoRaData.substring(4, 10).toInt(); // cut recieved packet to timestamp only, as Int (first 3 chars are identifier for messsage type)
  Serial.println(recTime);
  Serial.println(timestamp);
  //int index = data.lastIndexOf('/'); // find index of last delimiter character
  //int timestamp = data.substring(index + 1, index + 7).toInt(); // use index to identify timestamp
  //Serial.println(""); Serial.println(LoRaData); Serial.println(recTime); Serial.println(timestamp);
  if (recTime == timestamp) { // compare message timestamp to confirmation timestamp, return true if match
      return true;
  }
  else {
    return false;
  }
}

void onReceive(int packetSize) { // recieves LoRa packet and stores data in global variable LoRaData

  // received a packet
  Serial.print("Received packet '");

  // read packet
  LoRaData = LoRa.readString();

  // print RSSI of packet
  Serial.print(LoRaData);
  Serial.print("' with RSSI ");
  Serial.println(LoRa.packetRssi());
}

void setupSoilSensor() {
  //Serial.println("seesaw Soil Sensor example!");
  if (!ss.begin(0x36)) {
    Serial.println("ERROR! seesaw not found");
    while (1) delay(1);
  } else {
    Serial.print("seesaw started! version: ");
    Serial.println(ss.getVersion(), HEX);
  }
}

void print2digits(int number) {
  if (number < 10) {
    Serial.print("0"); // print a 0 before if the number is < than 10
  }
  Serial.print(number);
}

void blink(int count, int pause) {
  for (int i = 0; i < count; i++) {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(pause);                       // wait for a second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    delay(pause);
  }
}

What happens when you entirely remove Strings ?

The only string used now is converting to a string for a LoRa packet - the LoRa library sends packets as type String) so I combine everything into a string just for sending. That's the only place.

See if you can go further, there are Strings about. I think I see a few more places where you can use character arrays small s strings.

If it is necessary for reasons to use any Strings, look at

and consider making all your (or the one you claim to be down to) Strings global in scope and "reserve" enough room for them at the outset.

Just an idea.

a7

You need to go further.

Strings, on Arduinos, are a known cause of stability problems.

So to eliminate Strings as a cause of you problems, you need to eliminate them.

I have used LoRa devices for very long periods, a year or more with no 'stability' issues.

Reporting back with an update. I've eliminated (capital 'S') Strings entirely, replacing for char arrays or numbers. This took a bit of practice, but a nice side effect is I've learnt much better how to do without Strings.

I've also cleaned up my code considering the new changes. Fundamentally though, it's intended behaviour is as intended in the original post.

Unfortunately - problem remains. I suspected it was affected by buffer size (now designated 'bufLen') but after a few more tests it doesn't seem related, after trying 5, 20 or 50. The program hangs somewhere after 1-2 hours after boot.

I've not yet implemented a watchdog as I continue to try and find the problem at source, but this

I was spooked by a flashing orange LED on the Arduino MKR (1310) and that there might be a hardware fault, but it appears this is a known issue with the MKR series and is common with non-battery power. A blink sketch runs 8 hours (and counting) without issue.

This has been a good learning exercise, but ultimately fruitless so far. Any suggestions on where to go next?

#include <RTCZero.h>
#include <CircularBuffer.h>
#include <LoRa.h>
//#include <stdlib.h>
#include "Adafruit_seesaw.h"
//#include <SPI.h>

Adafruit_seesaw ss;

/* Create an rtc object */
RTCZero rtc;

namespace data {
  typedef struct {
    int timeStamp;
    int mois;
    float temp;
  } record;

  void print(record r) {
    Serial.print(r.timeStamp);
    Serial.print(" / ");
    Serial.print(r.mois);
    Serial.print(" / ");
    Serial.print(r.temp);
  }
}

//#define CIRCULAR_BUFFER_INT_SAFE
const int bufLen = 5;
CircularBuffer<data::record, bufLen> structs;

struct sensorReading{
  unsigned long timeStamp;
  unsigned int mois;
  float temp;
  } record;
  
sensorReading sensorReading0 = { 0, 0, 0};

int incSecs = 10; // reading timer increment
unsigned long elapsedTime = millis();  // trigger for wait timer

char cUpdate[7] = "update";
const int msgBuf = 50; //size of message buffer
char cMessage[msgBuf];
char LoRaData[20];

void setup()
{
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  if (!Serial){
    delay(2000);
  }
  
  rtc.begin(); // initialize RTC 24H format
  byte timeSet[] = { 0 , 0 , 0 , 0 , 0 , 0 };
  updateTime(timeSet[0], timeSet[1], timeSet[2], timeSet[3], timeSet[4], timeSet[5]); // Initialize RTC on boot to 00:00:00. Ensures no odd issues with failiure to update.

  Serial.println("LoRa Startup"); // Start LoRA module
  if (!LoRa.begin(868E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }

  setupSoilSensor();
  doTimeUpdate();

  // Set up desired alarm time for first increment based on current RTC
  Serial.println("Setting alarm");
  alarmInc(incSecs); // Sets Alarm for x secs incrmment from now
  
  // Set Alarm time
  rtc.enableAlarm(rtc.MATCH_HHMMSS);
  Serial.println("Alarm set");
  
  // Attach interrupt to taking sensor reading
  rtc.attachInterrupt(takeAnalogReading); //when interrupt is triggered, take sensor reading
 
  elapsedTime = millis();
 
  Serial.println("STARTING UP");
  Serial.flush();
  blink(5, 100); 
  delay(500);
}

void loop()
{
  if (sensorReading0.timeStamp != 0 ) {
    sendMessage(true, sensorReading0.timeStamp, sensorReading0.mois, sensorReading0.temp);  // sends radio message to base unit for upload to DB
    LoRa.onReceive(onReceive); //onRecieve interrupt, listens for LoRa packets
    LoRa.receive(); // puts radio into recieve mode
    
    // Delay for confirmation (needs to be about >100ms to be safe)
    delay(250);
    
    if (recieveConfirmation(sensorReading0.timeStamp)) { // if correct confirmation is recieved
      Serial.print("Popping: "); Serial.println(sensorReading0.timeStamp); // confirm removal 
      sensorReading0 = { 0, 0, 0};
    }
    else {
      structs.push(data::record{sensorReading0.timeStamp, sensorReading0.mois, sensorReading0.temp});
      Serial.println("Saving record in buffer");
      sensorReading0 = {0, 0, 0};
    }
    Serial.println("---");
    delay(50);
  }

  // Checks for entries in buffer. If buffer not empty, tries to send messages and clear buffer
  if (!structs.isEmpty() ){
    Serial.print("Sending: "); Serial.println(structs.first().timeStamp); 
    sendMessage(true, structs.first().timeStamp, structs.first().mois, structs.first().temp);  // sends radio message to base unit for upload to DB / true is flag for sensor reading
    delay(20); // Pause briefly to allow hub unit to respond and not recieve old response
    LoRa.onReceive(onReceive); //onRecieve interrupt, listens for LoRa packets for confirmation
    LoRa.receive(); // puts radio into recieve mode
    
    // Delay to allow for confirmation before moving on
    delay(250);

    // If confirmation recieved, remove entry from buffer
    if (recieveConfirmation(structs.first().timeStamp)) { // if correct confirmation is recieved
      Serial.print("Confirmed. Shifting: ");
      data::print(structs.shift());
      Serial.println();
      }
    }

   // If buffer has entries, print out to Serial for monitoring
   if(millis() > elapsedTime + 30000){
      if (!structs.isEmpty() ){
        Serial.print("Records in Buffer. Total records: ");
        Serial.println(structs.size());
        Serial.print("Last Entry:");
        data::print(structs.last());
        Serial.println();
        elapsedTime = millis();
      }
   }

  // Should never be needed, if buffer fills up, remove all entries 
  if (structs.isFull()) {
    Serial.println("BUFFER FULL. CLEARING BUFFER");
    while (!structs.isEmpty()) {
      data::print(structs.shift());
      Serial.println();
    }
  }
  blink(1, 250);
  delay(450);
}

// Interupt routine - takes sensor temp and capacitive moisure values in temporary storage, then increments alarm for next reading
void takeAnalogReading() { 
  
  float tempC = ss.getTemp();
  uint16_t capread = ss.touchRead(0); 
  unsigned long timestamp = (rtc.getHours() * 10000) + (rtc.getMinutes() * 100) + (rtc.getSeconds() );
  
  sensorReading0.temp = tempC;
  sensorReading0.mois = capread;
  sensorReading0.timeStamp = timestamp;

  Serial.print("Taken reading at ");
  Serial.println(sensorReading0.timeStamp);
  alarmInc(incSecs); // increment next alarm by x seconds
  blink(2, 250); // blink for confirmation
}

// updates unit RTC based on LoRa time handshake being recieved
void updateTime(byte day, byte month, byte year, byte hours, byte minutes, byte seconds) { 
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);
  Serial.print("Set date & time to: ");
  Serial.print(rtc.getDay()); Serial.print("/");
  Serial.print(rtc.getMonth()); Serial.print("/");
  Serial.print(rtc.getYear()); Serial.print(" ");
  Serial.print(rtc.getHours()); Serial.print(":");
  Serial.print(rtc.getMinutes()); Serial.print(":");
  Serial.println(rtc.getSeconds());
}

void sendMessage(bool type, int timeStamp, int mois, float temp) {   // sends passed string as LoRa message
  
  if (type == true){
    int tempInt = temp*100; 

    // creates message string, adds leading 0 to timestamp int if needed for sending
    if (timeStamp < 100000){ 
      snprintf(cMessage,msgBuf,"p/%d/%d/0%d", tempInt, mois, timeStamp); 
    }
    else {
      snprintf(cMessage,msgBuf,"p/%d/%d/%d", tempInt, mois, timeStamp); 
    }
    LoRa.beginPacket();                   // start packet
    LoRa.print(cMessage);                 // add payload
    LoRa.endPacket();                     // finish packet and send it
    Serial.print("Sent: "); Serial.println(cMessage);
  }
  else {  // Send message for time update
    LoRa.beginPacket();                   // start packet
    LoRa.print(cUpdate);                 // add payload
    LoRa.endPacket();                     // finish packet and send it
    Serial.print("Sent: "); Serial.println(cUpdate); 
  }
}

// Function that gets current RTC clock time, and sets alarm for inc seconds in future
void alarmInc(int inc) { 
  int secs; int mins; int hours;

  secs = rtc.getSeconds();
  mins = rtc.getMinutes();
  hours = rtc.getHours();
  rtc.setAlarmTime(hours, mins, secs);

  if (rtc.getSeconds() + inc >= 60) {
    //Serial.println("increment minute/hr");
    secs = rtc.getSeconds() + (inc - 60);
    if (rtc.getMinutes() + 1 >= 60) {
      mins = rtc.getMinutes() + (1 - 60);
      if (rtc.getHours() + 1 >= 24) {
        hours = rtc.getHours() + (1 - 24);
      }
      else {hours = rtc.getHours() + 1;}
    }
    else {mins = rtc.getMinutes() + 1;}
  }
  else {
    secs = rtc.getSeconds() + inc;
    mins = rtc.getMinutes();
    hours = rtc.getHours();
  }
  rtc.setAlarmTime(hours, mins, secs);
}

void doTimeUpdate() {
  while (1) {
    //Request time packet
    Serial.println("Requesting time update");
    sendMessage(false,0,0,0); // 'false' is flag to send update message signal

    LoRa.onReceive(onReceive);
    LoRa.receive();
    delay(500);
    Serial.println(LoRaData);
  
    char dateBuf[7];
      for (int i=5;i<11;i++){
        dateBuf[i-5] = LoRaData[i];
      }
    int dateInt = atoi(dateBuf);
    //Serial.println(dateInt);
  
    byte day =      (dateInt/10000) % 100;
    byte month =    (dateInt/100) % 100;
    byte year =     dateInt % 100;
  
    char timeBuf[7];
    for (int i=11;i<17;i++){
      timeBuf[i-11] = LoRaData[i];
    }
    int timeInt = atoi(timeBuf);
    //Serial.println(timeInt);
  
    byte hours =    (timeInt/10000) % 100;
    byte minutes =  (timeInt/100) % 100;
    byte seconds =  timeInt % 100;

    if (year != 0 ) {
      updateTime(day, month, year, hours, minutes, seconds);
      blink(5, 50);
      break;
    }

    // Loop if not recieved
    Serial.println("No response.");
    blink(2, 1000);
    delay(500);
  }
}

bool recieveConfirmation(unsigned long timestamp) {
  char charLora[6];
  for(int i=0;i<6;i++){
    charLora[i] = LoRaData[i+4];
  }
  int recTime = atoi(charLora);
  //Serial.print("recTime = "); Serial.println(recTime); 
  if (recTime == timestamp) { // compare message timestamp to confirmation timestamp, return true if match
      return true;
  }
  else {
    return false;
  }
}

void onReceive(int packetSize) {
  // received a packet
  Serial.print("Received packet '");

  // read packet
  for (int i = 0; i < packetSize; i++) {
    LoRaData[i] = ((char)LoRa.read());
    Serial.print(LoRaData[i]);
  }

  // print RSSI of packet
  Serial.print("' with RSSI ");
  Serial.println(LoRa.packetRssi());
}

void setupSoilSensor() {
  //Serial.println("seesaw Soil Sensor example!");
  if (!ss.begin(0x36)) {
    Serial.println("ERROR! seesaw not found");
    while (1) delay(1);
  } else {
    Serial.print("seesaw started! version: ");
    Serial.println(ss.getVersion(), HEX);
  }
}

void blink(int count, int pause) {
  for (int i = 0; i < count; i++) {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(pause);                       // wait for a second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    delay(pause);
  }
}

So what happens if your LoRa device receives a packet longer than 20 bytes ?

Well certainly the answer would be bad things, when not from my own source. After adjusting that array up for (what I understand to be the largest possible number of characters) I received this packet (with a very weak signal) after several hours, which I assume would have caused a crash previously:

Unfortunately the behaviour did continue past that. I've now traced it to something causing a crash during the interrupts, and it didn't seem that it was best practice that I was doing a lot during the interrupt routine, so I've now adjusted some things there to see if I can resolve the issue.

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