@@ -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
312339static void initializeStorage ();
313340static void ensureConfigLoaded ();
@@ -345,6 +372,9 @@ static int getRelayPin(uint8_t relayIndex);
345372static float readNotecardVinVoltage ();
346373static void checkRelayMomentaryTimeout (unsigned long now);
347374static void resetRelayForTank (uint8_t idx);
375+ static void addSerialLog (const char *message);
376+ static void pollForSerialRequests ();
377+ static void sendSerialLogs ();
348378
349379void 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
439473void 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
12101250static 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