oopesp32

ESP32 private members corrupted


I have an ESP32 program that runs on freeRTOS, reads config data from microSD and passes them in as credentials for my API class RemoteLogger:

#ifndef REMOTELOGGER_H
#define REMOTELOGGER_H

#include <PubSubClient.h>

#define API_TIMEOUT       5000
#define TINY_GSM_MODEM_SIM7600

#include <TinyGsmClient.h>
#include "structs.h"

struct MQTT{
  const char* broker;
  const char* topic;
  const char* user;
  const char* pass;
  uint16_t port;
};

struct API{
  const char* host;
  const char* user;
  const char* pass;
  uint16_t port;
};

class RemoteLogger : public PubSubClient{
public:
  RemoteLogger(TinyGsmClient& mqttClient, TinyGsmClient& apiClient)
              : PubSubClient(mqttClient), apiClient(&apiClient){}

  RemoteLogger& setCreds(MQTT& mqtt);
  RemoteLogger& setCreds(API& api);
  PubSubClient& setServer();
  boolean mqttConnect(const char* id);
  boolean mqttConnected();
  boolean publish(const char* payload, boolean retained);
  boolean subscribe();
  
  void retrieveToken();
  bool apiConnect();
  bool apiConnected();
  bool post(const char* request, const char* msg);
  bool authPost(const char* request, const char* msg);
  bool dataToApi(String& jsonPayload);
  bool errorToApi(String& jsonPayload);
private:
  TinyGsmClient* mqttClient;
  TinyGsmClient* apiClient;
  const char* token;
  MQTT mqtt;
  API api;
};

#endif

RemoteLogger& RemoteLogger::setCreds(MQTT& mqtt){
  this->mqtt = mqtt;
  return *this;
}

RemoteLogger& RemoteLogger::setCreds(API& api){
  this->api = api;
  return *this;
}

PubSubClient& RemoteLogger::setServer(){
  return PubSubClient::setServer(this->mqtt.broker, this->mqtt.port);
}

boolean RemoteLogger::mqttConnect(const char* id){
  return PubSubClient::connect(id, this->mqtt.user, this->mqtt.pass);
}

boolean RemoteLogger::mqttConnected(){
  return PubSubClient::connected();
}

boolean RemoteLogger::subscribe(){
  return PubSubClient::subscribe(this->mqtt.topic);
}

boolean RemoteLogger::publish(const char* payload, boolean retained){
  return PubSubClient::publish(this->mqtt.topic, payload, retained);
}

bool RemoteLogger::apiConnect(){
  if(apiClient->connect(this->api.host, this->api.port, 10) != 1){
    return false;
  } else {
    return true;
  }
}

bool RemoteLogger::apiConnected(){
  return apiClient->connected();
}
// POST request without authentication
bool RemoteLogger::post(const char* request, const char* msg){
  char header[1250];
  snprintf(header, sizeof(header),
          "POST %s HTTP/1.1\r\n"
          "Host: %s:%d\r\n"
          "Content-Type: application/json\r\n"
          "tenant: root\r\n"
          "Accept-Language: en-US\r\n"
          "Connection: keep-alive\r\n"
          "Content-Length: %d\r\n\r\n",
          request, this->api.host, this->api.port, strlen(msg));
  if(!apiClient){
    Serial.println("Client not initialized!");
    return false;
  }
  apiClient->print(header);
  Serial.print(header);
  apiClient->print(msg);
  return true;
}
// POST request with authentication
bool RemoteLogger::authPost(const char* request, const char* msg){
  char header[1250];
  snprintf(header, sizeof(header),
          "POST %s HTTP/1.1\r\n"
          "Host: %s:%d\r\n"
          "Content-Type: application/json\r\n"
          "Authorization: Bearer %s\r\n"
          "Accept-Language: en-US\r\n"
          "Connection: keep-alive\r\n"
          "Content-Length: %d\r\n\r\n",
          request, this->api.host, this->api.port, this->token, strlen(msg));
  if(!apiClient){
    Serial.println("Client not initialized!");
    return false;
  }
  apiClient->print(header);
  Serial.print(header);
  apiClient->print(msg);
  return true;
}

void RemoteLogger::retrieveToken(){
  if(!apiClient->connected()){
    Serial.println("Failed to connect to server.");
    return;
  }
  char tokenAuth[128];
  snprintf(tokenAuth, sizeof(tokenAuth),
          "{\"username\":\"%s\",\"password\":\"%s\"}",
          this->api.user, this->api.pass);
  this->post("/api/tokens", tokenAuth);

  Serial.println("Waiting for authentication response...");
  unsigned long startTime = millis();
  String response;
  while((millis() - startTime) < API_TIMEOUT){
    if(apiClient->available()){
      response = apiClient->readString();
      Serial.println(response);
      break;
    }
  }
  if(response.isEmpty()){
    Serial.println("Error: No response from server.");
    return;
  }
  int jsonStart = response.indexOf("{");
  if(jsonStart != -1){
    response = response.substring(jsonStart);
    int jsonEnd = response.lastIndexOf("}");
    if(jsonEnd != -1)
      response = response.substring(0, jsonEnd + 1);
  } else {
    response = "";
  }
  // Parse the token from the response (assumes JSON response format)
  JsonDocument jsonDoc;
  DeserializationError error = deserializeJson(jsonDoc, response);
  if (error){
    Serial.print("Failed to get token: ");
    Serial.println(error.c_str());
    return;
  }
  if(jsonDoc.containsKey("token")){
    this->token = jsonDoc["token"].as<const char*>();
    Serial.print("Extracted Token: ");
    Serial.println(this->token);
  } else {
    Serial.println("Error: 'token' field not found.");
  }
}

bool RemoteLogger::errorToApi(String& jsonPayload){
  if(this->token == NULL){
    Serial.println("Error: Token is NULL");
    return false;
  }
  Serial.println("Sending error chunk...");
  authPost("/api/v1/errorloggers/addlisterrorogger", jsonPayload.c_str());
  
  Serial.println("Waiting for server response...");
  unsigned long startTime = millis();
  String response;
  while((millis() - startTime) < API_TIMEOUT){
    if(apiClient->available()){
      response = apiClient->readString();
      Serial.println(response);
      break;
    }
  }
  if(response.isEmpty()){
    Serial.println("Error: No response from server");
    return false;
  }
  if(response.startsWith("HTTP/1.1 200")){
    Serial.println("Data successfully sent to API");
    return true;
  } else {
    Serial.println("Error: API response indicates failure");
  }
  return false;
}

bool RemoteLogger::dataToApi(String& jsonPayload){
  if(this->token == NULL){
    Serial.println("Error: Token is NULL");
    return false;
  }
  Serial.println("Sending data chunk...");
  authPost("/api/v1/dataloggers/addlistdatalogger", jsonPayload.c_str());
  
  Serial.println("Waiting for server response...");
  unsigned long startTime = millis();
  String response;
  while((millis() - startTime) < API_TIMEOUT){
    if(apiClient->available()){
      response = apiClient->readString();
      Serial.println(response);
      break;
    }
  }
  if(response.isEmpty()){
    Serial.println("Error: No response from server");
    return false;
  }
  if(response.startsWith("HTTP/1.1 200")){
    Serial.println("Data successfully sent to API");
    return true;
  } else {
    Serial.println("Error: API response indicates failure");
  }
  return false;
}

void callBack(char* topic, byte* msg, unsigned int len){
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String tempMsg;
  
  for (int i = 0; i < len; i++) {
    Serial.print((char)msg[i]);
    tempMsg += (char)msg[i];
  }
  Serial.println();
}

Credentials is retrieved using ConfigManager:

#ifndef CONFIGMANAGER_H
#define CONFIGMANAGER_H

#include <vector>
#include "RemoteLogger.h"
#include "Modem.h"
#include "structs.h"

class ConfigManager{
public:
  ConfigManager(RemoteLogger& extRemote, Modem& extModem) : remote(extRemote), modem(extModem) {}

  const char* lastError;

  bool readGprs();
  bool readMqtt();
  bool readApi();
private:
  Modem& modem;
  RemoteLogger& remote;
  friend class Modem;
  friend class RemoteLogger;
};

#endif
#include <SD.h>
#include <ArduinoJson.h>
#include "ConfigManager.h"

bool ConfigManager::readGprs(){
  File file = SD.open("/config.json");
  if (!file) {
    lastError = "Failed to open config file";
    return false;
  }
  // Allocate a JSON document
  JsonDocument config;
  // Parse the JSON from the file
  DeserializationError error = deserializeJson(config, file);
  if (error) {
    lastError = ("Failed to get GPRS config: " + String(error.f_str())).c_str();
    return false;
  }
  file.close();

  GPRS gprs;
  gprs.apn = config["GprsConfiguration"]["Apn"].as<const char*>();
  gprs.simPin = config["GprsConfiguration"]["SimPin"].as<const char*>();
  gprs.user = config["GprsConfiguration"]["User"].as<const char*>();
  gprs.pass = config["GprsConfiguration"]["Password"].as<const char*>();

  modem.setCreds(gprs);
  return true;
}

bool ConfigManager::readMqtt(){
  File file = SD.open("/config.json");
  if (!file) {
    lastError = "Failed to open config file";
    return false;
  }
  JsonDocument config;
  DeserializationError error = deserializeJson(config, file);
  if (error) {
    lastError = ("Failed to get MQTT config: " + String(error.f_str())).c_str();
    return false;
  }
  file.close();

  MQTT mqtt;
  mqtt.topic = config["MqttConfiguration"]["Topic"].as<const char*>();
  mqtt.broker = config["MqttConfiguration"]["Broker"].as<const char*>();
  mqtt.user = config["MqttConfiguration"]["User"].as<const char*>();
  mqtt.pass = config["MqttConfiguration"]["Password"].as<const char*>();
  mqtt.port = config["MqttConfiguration"]["Port"].as<uint16_t>();

  remote.setCreds(mqtt);
  return true;
}

bool ConfigManager::readApi(){
  File file = SD.open("/config.json");
  if (!file) {
    lastError = "Failed to open config file";
    return false;
  }
  JsonDocument config;
  DeserializationError error = deserializeJson(config, file);
  if (error) {
    lastError = ("Failed to get API config: " + String(error.f_str())).c_str();
    return false;
  }
  file.close();

  API api;
  api.host = config["ApiConfiguration"]["Host"].as<const char*>();
  api.user = config["ApiConfiguration"]["Username"].as<const char*>();
  api.pass = config["ApiConfiguration"]["Password"].as<const char*>();
  api.port = config["ApiConfiguration"]["Port"].as<uint16_t>();

  remote.setCreds(api);
  return true;
}

After a few functions running in main(), the credentials are corrupted, either gibberish or changed to other contents read from microSD.

#include <Arduino.h>
#include <SD.h>
#include <Wire.h>
#include <EEPROM.h>
#include <ArduinoJson.h>
#include <esp_task_wdt.h>
#include "ConfigManager.h"
#include "RemoteLogger.h"

#define SIM_RXD           32
#define SIM_TXD           33
#define SIM_BAUD          115200
#define RTU_BAUD          9600
#define TASK_WDT_TIMEOUT  60

HardwareSerial SerialAT(1);
Modem modem(SerialAT, SIM_RXD, SIM_TXD, SIM_BAUD);
TinyGsmClient mqttClient(modem), apiClient(modem);
RemoteLogger remote(mqttClient, apiClient);
ConfigManager config(remote, modem);

uint8_t mac[6];
char macAdr[18];
int rowCount = 0;
int logCount = 0;
const int chunkSize = 5;

bool sendRows(File &data, String &timeStamp, int fileNo);
bool processCsv(fs::FS &fs, const char* path, int fileNo);
bool findTimestamp(File &data, String &timeStamp, size_t &filePtr);

void setup() {
  Serial.begin(115200);
  EEPROM.begin(EEPROM_SIZE);

  esp_efuse_mac_get_default(mac);
  sprintf(macAdr, "%02X:%02X:%02X:%02X:%02X:%02X",
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.printf("ESP32 MAC Address: %s\r\n", macAdr);
  /***************************** Set Up microSD *********************************/
  if(!SD.begin(5)){
    Serial.println("Card Mount Failed");
  }
  uint8_t cardType = SD.cardType();
  if(cardType == CARD_NONE){
    Serial.println("No SD card attached");
  }
  // Check SD card type
  Serial.print("SD Card Type: ");
  if(cardType == CARD_MMC){
    Serial.println("MMC");
  } else if(cardType == CARD_SD){
    Serial.println("SDSC");
  } else if(cardType == CARD_SDHC){
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }
  // Check SD card size
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);
  /************************** Get Config Credentials ****************************/
  if(!config.readGprs()){
    Serial.println(config.lastError);
  }
  if(!config.readMqtt()){
    Serial.println(config.lastError);
  }
  if(!config.readApi()){
    Serial.println(config.lastError);
  }
  /**************************** Initialize 4G Modem *****************************/
  Serial.println("Initializing modem...");
  if(!modem.init()){
    Serial.println("Restarting modem...");
    modem.restart();
  }
  remote.setServer();
  remote.setBufferSize(1024);
  remote.setCallback(callBack);
  /******************* Connect to API & check for unlogged data *****************/
  processCsv(SD, "/error.csv", 0);
  processCsv(SD, "/probe1.csv", 1);
}
/***************************** Support functions ********************************/
// Function to skip rows until the last pushed timestamp is found
bool findTimestamp(File &data, String &timeStamp, size_t &filePtr){
  if(data.seek(filePtr, SeekSet)){
    while(data.available()){
      String line = data.readStringUntil('\n');
      line.trim();
      if(timeStamp == "" || line.startsWith(timeStamp)){
        return true;
      }
    }
  }
  return false;
}

// Read and process rows from the CSV file
bool sendRows(File &data, String &timeStamp, int fileNo){
  Serial.println("Sending rows");
  const int chunkSize = 5;
  const int maxRetries = 5;
  int retries = 0;
  int rowCount = 0;
  bool success = true;
  String jsonPayload;
  String rows[chunkSize];
  // Helper lambda to process and send rows
  auto processAndSend = [&](bool isFinalChunk){
    jsonPayload = (fileNo == 0) ? errorToJson(rows, rowCount) : dataToJson(rows, rowCount);
    Serial.println(jsonPayload);
    retries = 0;
    while(retries < maxRetries){
      //Serial.println(remote.token);
      bool sent = (fileNo == 0) ? remote.errorToApi(jsonPayload) : remote.dataToApi(jsonPayload);
      if(sent){
        timeStamp = readCsv(rows[rowCount - 1]);
        rowCount = 0;
        return true;
      } else {
        Serial.println(isFinalChunk ? "Failed to send final chunk. Retrying..." 
                                    : "Failed to send data chunk. Retrying...");
        retries++;
      }
    }
    Serial.println("Max retries reached. Aborting...");
    return false;
  };
  // Main loop to read and process rows
  while(data.available()){
    String line = data.readStringUntil('\n');
    line.trim();
    rows[rowCount++] = line;
    // Process a chunk when full
    if(rowCount == chunkSize){
      if(!processAndSend(false)){
        success = false;
        rowCount = 0;
        break;
      }
    }
  }
  // Process remaining rows if any
  if(rowCount > 0){
    if(!processAndSend(true)){
      success = false;
    }
  }
  return success;
}

bool processCsv(fs::FS &fs, const char* path, int fileNo){
  File data = openFile(fs, path);
  if (!data){
    return false;
  }
  String timeStamp = readFlash<String>(fileNo * 20 + fileNo * sizeof(size_t));
  size_t filePtr = readFlash<size_t>(fileNo * 20 + fileNo * sizeof(size_t) + 20);
  if(!findTimestamp(data, timeStamp, filePtr)){
    data.close();
    return false;
  }
  if(sendRows(data, timeStamp, fileNo)){
    saveToFlash(fileNo * 20 + fileNo * sizeof(size_t), timeStamp);
    saveToFlash(fileNo * 20 + fileNo * sizeof(size_t) + 20, data.position());
  }
  data.close();
  return true;
}

Why is this happening? How can I resolve this?


Solution

  • The API api object in the ConfigManager::readApi method is a local variable. When the method returns, the memory allocated for api is deallocated. Since api.host, api.user, and api.pass point to memory managed by the JsonDocument, their data is no longer valid after the function exits.

    Solution: You need to ensure that the strings and object persist after the function returns. For example, store the strings in a globally accessible object or dynamically allocate them.