Sd card + cayenne and wifi interruption

Dear friends: I’m running my temperature datalogger project on dashboard and trying to save data on sd card. The problem is that when WiFi connection drops for whatever reason, my ESP32 doesn’t write to sd.
It would be valuable to have temperature data saved on sd when conectivity stops, but when it occurs the ESP32 keeps trying to connect (a lot of “Network connect failed” on serial monitor) and nothing else happen.
What can I do to go out that loop?
My code is:

// Libraries to get time from NTP Server
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// Libraries for SD card
#include "FS.h"
#include "SD.h"
#include <SPI.h>


//needed for library
#define CAYENNE_PRINT Serial
#include <CayenneMQTTESP32.h>
#include <DNSServer.h>
#include <WebServer.h>
#include <WiFiManager.h>         //https://github.com/tzapu/WiFiManager
#include <OneWire.h>
#include <DallasTemperature.h>

// Save reading number on RTC memory
RTC_DATA_ATTR int readingID = 0;

String dataMessage;

String dataPath;
String dataPatha;

// Temperature value
float temp;
float temp2;

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUSA 22
#define ONE_WIRE_BUSB 21

#define THRESHOLD1 23 //Threshold for the trigger.

//booleano para definir el trigger
bool sendBelowThreshold = true; //Set to true if the trigger should happen when the data value is below the threshold, 


// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

// Variables to save date and time
String formattedDate;
String dayStamp;
String timeStamp;

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWireA(ONE_WIRE_BUSA);
OneWire oneWireB(ONE_WIRE_BUSB);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensorsA(&oneWireA);
DallasTemperature sensorsB(&oneWireB);

// Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
char username[] = "XXX";
char password[] = "XXX";
char clientID[] = "XXX";

// Function to get temperature
void getReadings(){
  sensorsA.requestTemperatures(); 
  temp = sensorsA.getTempCByIndex(0); // Temperature in Celsius
  //temperature = sensors.getTempFByIndex(0); // Temperature in Fahrenheit
  Serial.print("Temperature 1: ");
  Serial.println(temp);

  sensorsB.requestTemperatures(); 
  temp2 = sensorsB.getTempCByIndex(0); // Temperature in Celsius
  Serial.print("Temperature 2: ");
  Serial.println(temp2);
}


void setup() {
    // put your setup code here, to run once:
    Serial.begin(115200);

    sensorsA.begin();
    sensorsB.begin();

    Serial.println();
    Serial.println();
    
    //WiFiManager
    //Local intialization. Once its business is done, there is no need to keep it around
    WiFiManager wifiManager;
    //reset saved settings
    //wifiManager.resetSettings();
    
    //set custom ip for portal
    //wifiManager.setAPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0));

    //fetches ssid and pass from eeprom and tries to connect
    //if it does not connect it starts an access point with the specified name
    //here  "ARD3"
    //and goes into a blocking loop awaiting configuration
    wifiManager.autoConnect("ARD3");
    //or use this for auto generated name ESP + ChipID
    //wifiManager.autoConnect();

    
    //if you get here you have connected to the WiFi
    Serial.println("connected...yeey :)");

    Cayenne.begin(username, password, clientID);


// Initialize a NTPClient to get time
  timeClient.begin();
  // Set offset time in seconds to adjust for your timezone, for example:
  // GMT +1 = 3600
  // GMT +8 = 28800
  // GMT -1 = -3600
  // GMT 0 = 0
  
  timeClient.setTimeOffset(-10800);

 // Initialize SD card
  SD.begin();  
  if(!SD.begin()) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();
  if(cardType == CARD_NONE) {
    Serial.println("No SD card attached");
    return;
  }
  Serial.println("Initializing SD card...");
  if (!SD.begin()) {
    Serial.println("ERROR - SD card initialization failed!");
    return;    // init failed
  }

  // If the iptp3_t1.txt file doesn't exist
  // Create a file on the SD card and write the data labels
  File file = SD.open(dataPatha);
  if(!file) {
    Serial.println("File doesn't exist");
    Serial.println("Creating file...");
    writeFile(SD, dataPatha.c_str(), "Reading ID, Date, Hour, Temp (ºC) \r\n");
  }
  else {
    Serial.println("File already exists");  
  }
  file.close();

}

//trigger para temperaturas. Definir los valores a los que se dispara la alarma en TRIGGER. Si es menor queda en 0, si es mayor o igual va a 1
void sendTriggerValue(int channel, int value, int threshold, bool sendBelowThreshold) {
  if (((value < threshold) && !sendBelowThreshold) || ((value >= threshold) && sendBelowThreshold)) {
    if (!crossedThreshold) {
      Cayenne.virtualWrite(channel, 1, "digital_sensor", "d"); //set trigger two-state widget to 1 
      crossedThreshold = true;
    }
     }
  else if (value < 0) {                                                                     
  Cayenne.virtualWrite(channel, 1, "digital_sensor", "d"); //set trigger two-state widget to 0 // 
  crossedThreshold = false;
}
  else
  {
    Cayenne.virtualWrite(channel, 0, "digital_sensor", "d"); //set trigger two-state widget to 0
    crossedThreshold = false;
  }

      
}

// Default function for sending sensor data at intervals to Cayenne.
// You can also use functions for specific channels, e.g CAYENNE_OUT(1) for sending channel 1 data.
CAYENNE_OUT_DEFAULT()
{
  // Write data to Cayenne here. This example just sends the current uptime in milliseconds on virtual channel 0.
  Cayenne.virtualWrite(0, millis());
  // Some examples of other functions you can use to send data.
  Serial.print("Sensor 1: ");
  Serial.println(sensorsA.getTempCByIndex(0));
  sensorsA.requestTemperatures();
  if (sensorsA.getTempCByIndex(0) != 85 || sensorsA.getTempCByIndex(0) != (-127.0)){
  Cayenne.celsiusWrite(1, sensorsA.getTempCByIndex(0));// do stuff if the condition is false
  }
  sendTriggerValue(3, sensorsA.getTempCByIndex(0), THRESHOLD1, sendBelowThreshold);
  
  //Cayenne.luxWrite(2, 700);
  //Cayenne.virtualWrite(3, 50, TYPE_PROXIMITY, UNIT_CENTIMETER);
}

CAYENNE_OUT(2)
{
  //Cayenne.celsiusWrite(2, Temperature());
 Serial.print("Sensor 2: ");
 Serial.println(sensorsB.getTempCByIndex(0));
 sensorsB.requestTemperatures();
 Cayenne.celsiusWrite(2, sensorsB.getTempCByIndex(0));
 
}

// Default function for processing actuator commands from the Cayenne Dashboard.
// You can also use functions for specific channels, e.g CAYENNE_IN(1) for channel 1 commands.
CAYENNE_IN_DEFAULT()
{
  CAYENNE_LOG("Channel %u, value %s", request.channel, getValue.asString());
  //Process message here. If there is an error set an error message using getValue.setError(), e.g getValue.setError("Error message");
}

void loop() {
    // put your main code here, to run repeatedly:
    
    const unsigned long TiempoenMinutos = 0.5 * 60 * 1000UL;
    static unsigned long lastSampleTime = 0 - TiempoenMinutos;  // initialize such that a reading is due the first time through loop()

    unsigned long now = millis();
    if (now - lastSampleTime >= TiempoenMinutos)
 {
    lastSampleTime += TiempoenMinutos;
    // add code to take temperature reading here
    Cayenne.loop();

    temp = sensorsA.getTempCByIndex(0);

  getReadings();
  getTimeStamp();
  logSDCard();

  // Increment readingID on every new reading
  readingID++;
 }

 
 
}


 // Function to get date and time from NTPClient
void getTimeStamp() {
  if(!timeClient.update()) {
    timeClient.forceUpdate();
  }
  // The formattedDate comes with the following format:
  // 2018-05-28T16:00:13Z
  // We need to extract date and time
  formattedDate = timeClient.getFormattedDate();
  Serial.println(formattedDate);

  // Extract date
  int splitT = formattedDate.indexOf("T");
  dayStamp = formattedDate.substring(0, splitT);
  Serial.println(dayStamp);
  // Extract time
  timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
  Serial.println(timeStamp);
}

// Write the sensor readings on the SD card
void logSDCard() {
  dataPath = String(dayStamp);
  String dataPatha = "/data/"+ dataPath + "_t1.csv";
  String path = dataPatha;
  dataMessage = String(readingID) + "," + String(dayStamp) + "," + String(timeStamp) + "," + 
                String(temp) + "," + String(temp2) +"\r\n";
  Serial.print("Save data: ");
  Serial.println(dataMessage);
  appendFile(SD, dataPatha.c_str(), dataMessage.c_str());
}

// Write to the SD card (DON'T MODIFY THIS FUNCTION)
void writeFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Writing file: %s\n", path);

  File file = fs.open(path, FILE_WRITE);
  if(!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  if(file.print(message)) {
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
  file.close();
}

// Append data to the SD card (DON'T MODIFY THIS FUNCTION)
void appendFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Appending to file: %s\n", path);

  File file = fs.open(path, FILE_APPEND);
  if(!file) {
    Serial.println("Failed to open file for appending");
    return;
  }
  if(file.print(message)) {
    Serial.println("Message appended");
  } else {
    Serial.println("Append failed");
  }
  file.close();
}

many thanks in advance for your help
regards
DL

The cayenne code is designed in a way that whenever there is a connection lost, it goes into a loop to reconnect again, hence you get the Network Failed issue. You can try solution from here Online/offline code

Hi shramik_salgaokar, thanks a lot for the info!!!
best regards
DL

Hi again. I would like to know if

// This function will run every time the Cayenne connection is established.
CAYENNE_CONNECTED()
{
  CAYENNE_LOG("Connection established");
  Serial.println("\n++ Cayenne Connected ! ++"); // can be deleted
 }
CAYENNE_DISCONNECTED()
{
  CAYENNE_LOG("Connection finished");
  Serial.println("\n--  Cayenne Disconnected ! --");    //  can be deleted
//<==== HERE i do something when Cayenne is disconnected  
}

must go inside or outside the loop.
Thank you!
Regards
DL

outside the main loop.

thank you

Well, I tried modifying the CayenneArduinoMQTTClient.h library but didn’t work

I saved CayenneArduinoMQTTClient.h with notepad and re uploaded my sketch. Maybe is there any other step I bypassed?

Thank you
regards
DL

Another option is :-

CAYENNE_CONNECTED() {
  Serial.println("connected.......");
}

CAYENNE_DISCONNECTED() {
  Serial.println("disconnected......");
  while (true)
  {
    Serial.println("blocking");
    digitalWrite(LED_BUILTIN, LOW);   
    delay(1000);                      
    digitalWrite(LED_BUILTIN, HIGH);  
    delay(2000);
  }
}

you can add a counter in the while loop so that after a certain count the device restart exits the loop for the device to try again to connect to the internet.

thank you. I’ll give a try

regards
DL

Hi again. Unfortunately, no sd write when stops wifi.

Keeps posting “blocking” but don’t write to sd.

regards
DL

You need to add your code for SD write and sensor read after the blocking line.

1 Like

it worked! Thank you!!!

1 Like

and added a break to exit the while (and blocking) to check if connection returned

CAYENNE_DISCONNECTED() {
  Serial.println("disconnected......");
  int i;
  i=0;
  while (true)
  {
    Serial.println("blocking");
    delay(30000);
    getReadings();
    getTimeStamp();
    logSDCard();
    digitalWrite(LED_BUILTIN, LOW);   
    delay(1000);                      
    digitalWrite(LED_BUILTIN, HIGH);  
    delay(2000);
    i++;
    if (i>5)
      break;
  }
}

Worked for me, at least.

Many thanks for your help.
best regards
DL

1 Like

that works too. thanks for sharing.