我有一个在 freeRTOS 上运行的 ESP32 程序,从 microSD 读取配置数据并将它们作为我的 API 类的凭据传递
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();
}
使用
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;
}
在
main()
中运行一些功能后,凭据已损坏,要么是乱码,要么更改为从 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;
}
为什么会出现这种情况?我该如何解决这个问题?
api
方法中的API ConfigManager::readApi
对象是一个局部变量。当该方法返回时,分配给 api
的内存将被释放。由于 api.host
、api.user
和 api.pass
指向 JsonDocument 管理的内存,因此在函数退出后,它们的数据不再有效。
解决方案:需要确保函数返回后字符串和对象仍然存在。例如,将字符串存储在全局可访问的对象中或动态分配它们。