Skip to content

Commit 0a6acb1

Browse files
committed
Add remote serial log support for client and server
Introduces serial log buffering and transfer between client and server for remote debugging. The client now buffers serial logs and responds to server requests by sending logs via Notecard. The server tracks serial logs from itself and multiple clients, exposes new API endpoints for log retrieval and requests, and adds a web-based Serial Monitor UI for viewing and requesting logs from clients.
1 parent 8096b87 commit 0a6acb1

File tree

2 files changed

+1597
-21
lines changed

2 files changed

+1597
-21
lines changed

TankAlarm-112025-Client-BluesOpta/TankAlarm-112025-Client-BluesOpta.ino

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ static size_t strlcpy(char *dst, const char *src, size_t size) {
115115
#define RELAY_CONTROL_FILE "relay.qi"
116116
#endif
117117

118+
#ifndef SERIAL_LOG_FILE
119+
#define SERIAL_LOG_FILE "serial_log.qi" // Send serial logs to server
120+
#endif
121+
122+
#ifndef SERIAL_REQUEST_FILE
123+
#define SERIAL_REQUEST_FILE "serial_request.qi" // Receive serial log requests
124+
#endif
125+
126+
#ifndef CLIENT_SERIAL_BUFFER_SIZE
127+
#define CLIENT_SERIAL_BUFFER_SIZE 50 // Buffer up to 50 log messages
128+
#endif
129+
118130
#ifndef NOTE_BUFFER_PATH
119131
#define NOTE_BUFFER_PATH "/pending_notes.log"
120132
#endif
@@ -308,6 +320,21 @@ static int gRpmLastPinState[MAX_TANKS]; // Initialized dynamically in setup()
308320
#define RPM_SAMPLE_DURATION_MS 3000
309321
#endif
310322

323+
// Serial log buffer structure for client
324+
struct SerialLogEntry {
325+
double timestamp;
326+
char message[160];
327+
};
328+
329+
struct ClientSerialLog {
330+
SerialLogEntry entries[CLIENT_SERIAL_BUFFER_SIZE];
331+
uint8_t writeIndex;
332+
uint8_t count;
333+
};
334+
335+
static ClientSerialLog gSerialLog;
336+
static unsigned long gLastSerialRequestCheckMillis = 0;
337+
311338
// Forward declarations
312339
static void initializeStorage();
313340
static void ensureConfigLoaded();
@@ -345,6 +372,9 @@ static int getRelayPin(uint8_t relayIndex);
345372
static float readNotecardVinVoltage();
346373
static void checkRelayMomentaryTimeout(unsigned long now);
347374
static void resetRelayForTank(uint8_t idx);
375+
static void addSerialLog(const char *message);
376+
static void pollForSerialRequests();
377+
static void sendSerialLogs();
348378

349379
void setup() {
350380
Serial.begin(115200);
@@ -355,6 +385,9 @@ void setup() {
355385
Serial.println();
356386
Serial.println(F("Tank Alarm Client 112025 starting"));
357387

388+
// Initialize serial log buffer
389+
memset(&gSerialLog, 0, sizeof(ClientSerialLog));
390+
358391
// Set analog resolution to 12-bit to match the /4095.0f divisor used in readTankSensor
359392
analogReadResolution(12);
360393

@@ -434,6 +467,7 @@ void setup() {
434467
initializeRelays();
435468

436469
Serial.println(F("Client setup complete"));
470+
addSerialLog("Client started successfully");
437471
}
438472

439473
void loop() {
@@ -472,6 +506,11 @@ void loop() {
472506
pollForRelayCommands();
473507
}
474508

509+
if (now - gLastSerialRequestCheckMillis >= 600000UL) { // Check every 10 minutes
510+
gLastSerialRequestCheckMillis = now;
511+
pollForSerialRequests();
512+
}
513+
475514
// Check for momentary relay timeout (30 minutes)
476515
checkRelayMomentaryTimeout(now);
477516

@@ -1205,6 +1244,7 @@ static void applyConfigUpdate(const JsonDocument &doc) {
12051244
printHardwareRequirements(gConfig);
12061245
scheduleNextDailyReport();
12071246
Serial.println(F("Configuration updated from server"));
1247+
addSerialLog("Config updated from server");
12081248
}
12091249

12101250
static void persistConfigIfDirty() {
@@ -1703,6 +1743,10 @@ static void sendAlarm(uint8_t idx, const char *alarmType, float inches) {
17031743
Serial.print(F(" type "));
17041744
Serial.println(alarmType);
17051745

1746+
char logMsg[128];
1747+
snprintf(logMsg, sizeof(logMsg), "Alarm: %s - %s - %.1fin", cfg.name, alarmType, inches);
1748+
addSerialLog(logMsg);
1749+
17061750
// Handle relay control based on mode
17071751
if (cfg.relayTargetClient[0] != '\0' && cfg.relayMask != 0) {
17081752
bool shouldActivateRelay = false;
@@ -2485,6 +2529,129 @@ static void resetRelayForTank(uint8_t idx) {
24852529
gRelayActivationTime[idx] = 0;
24862530
}
24872531

2532+
// ============================================================================
2533+
// Serial Logging for Remote Debugging
2534+
// ============================================================================
2535+
2536+
static void addSerialLog(const char *message) {
2537+
if (!message || strlen(message) == 0) {
2538+
return;
2539+
}
2540+
2541+
SerialLogEntry &entry = gSerialLog.entries[gSerialLog.writeIndex];
2542+
entry.timestamp = currentEpoch();
2543+
2544+
// Truncate if necessary
2545+
size_t len = strlen(message);
2546+
if (len >= sizeof(entry.message)) {
2547+
len = sizeof(entry.message) - 1;
2548+
}
2549+
memcpy(entry.message, message, len);
2550+
entry.message[len] = '\0';
2551+
2552+
gSerialLog.writeIndex = (gSerialLog.writeIndex + 1) % CLIENT_SERIAL_BUFFER_SIZE;
2553+
if (gSerialLog.count < CLIENT_SERIAL_BUFFER_SIZE) {
2554+
gSerialLog.count++;
2555+
}
2556+
}
2557+
2558+
static void pollForSerialRequests() {
2559+
// Check for serial log requests from server
2560+
while (true) {
2561+
J *req = notecard.newRequest("note.get");
2562+
if (!req) {
2563+
return;
2564+
}
2565+
2566+
JAddStringToObject(req, "file", SERIAL_REQUEST_FILE);
2567+
JAddBoolToObject(req, "delete", true);
2568+
2569+
J *rsp = notecard.requestAndResponse(req);
2570+
if (!rsp) {
2571+
return;
2572+
}
2573+
2574+
J *body = JGetObject(rsp, "body");
2575+
if (!body) {
2576+
notecard.deleteResponse(rsp);
2577+
break;
2578+
}
2579+
2580+
const char *request = JGetString(body, "request");
2581+
if (request && strcmp(request, "send_logs") == 0) {
2582+
DEBUG_PRINTLN(F("Serial log request received from server"));
2583+
addSerialLog("Serial log request received");
2584+
sendSerialLogs();
2585+
}
2586+
2587+
notecard.deleteResponse(rsp);
2588+
}
2589+
}
2590+
2591+
static void sendSerialLogs() {
2592+
if (gSerialLog.count == 0) {
2593+
DEBUG_PRINTLN(F("No serial logs to send"));
2594+
return;
2595+
}
2596+
2597+
// Create a note with an array of log entries
2598+
J *req = notecard.newRequest("note.add");
2599+
if (!req) {
2600+
return;
2601+
}
2602+
2603+
JAddStringToObject(req, "file", SERIAL_LOG_FILE);
2604+
JAddBoolToObject(req, "sync", true);
2605+
2606+
J *body = JCreateObject();
2607+
if (!body) {
2608+
return;
2609+
}
2610+
2611+
JAddStringToObject(body, "client", gDeviceUID);
2612+
2613+
J *logsArray = JCreateArray();
2614+
if (!logsArray) {
2615+
JDelete(body);
2616+
return;
2617+
}
2618+
2619+
// Add logs from oldest to newest (circular buffer)
2620+
uint8_t startIdx = (gSerialLog.count < CLIENT_SERIAL_BUFFER_SIZE) ? 0 : gSerialLog.writeIndex;
2621+
uint8_t sentCount = 0;
2622+
2623+
for (uint8_t i = 0; i < gSerialLog.count && sentCount < 20; ++i) { // Limit to 20 most recent logs
2624+
uint8_t idx = (startIdx + i) % CLIENT_SERIAL_BUFFER_SIZE;
2625+
SerialLogEntry &entry = gSerialLog.entries[idx];
2626+
2627+
if (entry.message[0] == '\0') {
2628+
continue;
2629+
}
2630+
2631+
J *logEntry = JCreateObject();
2632+
if (!logEntry) {
2633+
break;
2634+
}
2635+
2636+
JAddNumberToObject(logEntry, "timestamp", entry.timestamp);
2637+
JAddStringToObject(logEntry, "message", entry.message);
2638+
JAddItemToArray(logsArray, logEntry);
2639+
sentCount++;
2640+
}
2641+
2642+
JAddItemToObject(body, "logs", logsArray);
2643+
JAddItemToObject(req, "body", body);
2644+
2645+
bool queued = notecard.sendRequest(req);
2646+
if (queued) {
2647+
DEBUG_PRINT(F("Sent "));
2648+
DEBUG_PRINT(sentCount);
2649+
DEBUG_PRINTLN(F(" serial logs to server"));
2650+
} else {
2651+
DEBUG_PRINTLN(F("Failed to queue serial logs"));
2652+
}
2653+
}
2654+
24882655
// ============================================================================
24892656
// Notecard VIN Voltage Reading
24902657
// ============================================================================

0 commit comments

Comments
 (0)