From 9ddc250deb3c188a7bf5d6102b0d5e380f6d5d27 Mon Sep 17 00:00:00 2001 From: Recelis Date: Tue, 9 Jun 2026 08:32:17 +1000 Subject: [PATCH] Mega polls ESP32 for cached notifications instead of showing idle screen When the 5-minute idle timeout fires, the Mega sends a single 'P' byte to the ESP32 over Serial1 instead of clearing the display. The ESP32 caches the last received MQTT batch and re-sends it on demand, so the display always shows the most recent notifications. Memory cost on ESP32: one String (~6KB max) against 177KB free heap. Co-Authored-By: Claude Sonnet 4.6 --- esp32_microcontroller_code/src/main.cpp | 11 ++++++++ .../lib/Communication/Communication.cpp | 6 +++++ .../lib/Communication/Communication.h | 1 + mega_microcontroller_code/lib/LCD/LCD.cpp | 27 +++++++------------ mega_microcontroller_code/lib/LCD/LCD.h | 7 ++--- mega_microcontroller_code/src/main.cpp | 8 ++++-- 6 files changed, 38 insertions(+), 22 deletions(-) diff --git a/esp32_microcontroller_code/src/main.cpp b/esp32_microcontroller_code/src/main.cpp index e6b6aa0..e791fd1 100644 --- a/esp32_microcontroller_code/src/main.cpp +++ b/esp32_microcontroller_code/src/main.cpp @@ -23,11 +23,15 @@ MegaCommunication megaCommunication; IntervalFetcher heartbeatTimer = IntervalFetcher(HEARTBEAT_INTERVAL_MS); WiFiProvisioner provisioner; +// Last notification batch received — re-sent to Mega on poll request +String lastBatch = ""; + // ── MQTT message handler ────────────────────────────────────────────────────── void messageHandler(String &topic, String &payload) { if (topic != NOTIFICATIONS_SUBSCRIBE_TOPIC) return; + lastBatch = payload; megaCommunication.sendRaw(payload.c_str()); } @@ -139,6 +143,13 @@ void loop() connectAWS(); } + // Respond to poll requests from the Mega ('P' byte sent when its idle timeout fires) + if (Serial1.available() && Serial1.read() == 'P' && lastBatch.length() > 0) + { + Serial.println("Poll request received — re-sending last batch"); + megaCommunication.sendRaw(lastBatch.c_str()); + } + if (heartbeatTimer.shouldFetch()) { client.publish("weatherbox/heartbeat", "{\"status\":\"alive\"}"); diff --git a/mega_microcontroller_code/lib/Communication/Communication.cpp b/mega_microcontroller_code/lib/Communication/Communication.cpp index e715d3d..fd32f7d 100644 --- a/mega_microcontroller_code/lib/Communication/Communication.cpp +++ b/mega_microcontroller_code/lib/Communication/Communication.cpp @@ -53,6 +53,12 @@ void Communication::setNewData(bool newDataFlag){ newData = newDataFlag; } +// Sends a single poll byte to the ESP32, asking it to re-send its last batch. +void Communication::sendPollRequest() +{ + Serial1.write('P'); +} + Communication::~Communication() { } diff --git a/mega_microcontroller_code/lib/Communication/Communication.h b/mega_microcontroller_code/lib/Communication/Communication.h index ed77d2d..38e43ec 100644 --- a/mega_microcontroller_code/lib/Communication/Communication.h +++ b/mega_microcontroller_code/lib/Communication/Communication.h @@ -16,6 +16,7 @@ class Communication bool receiveData(); char * getReceivedChars(); void setNewData(bool newDataFlag); + void sendPollRequest(); }; #endif \ No newline at end of file diff --git a/mega_microcontroller_code/lib/LCD/LCD.cpp b/mega_microcontroller_code/lib/LCD/LCD.cpp index 3edd754..a18ba4f 100644 --- a/mega_microcontroller_code/lib/LCD/LCD.cpp +++ b/mega_microcontroller_code/lib/LCD/LCD.cpp @@ -70,21 +70,22 @@ void LCD::drawScreen(char *receivedChars) row++; } - if (row == 0) drawIdleScreen(); - lastDataMs = millis(); - isIdle = false; + _idle = false; + _pollSent = false; } -// Called from loop() to show idle screen after IDLE_TIMEOUT_MS with no data -void LCD::checkIdle() +// Returns true once when the idle timeout first fires — caller sends a poll request. +// Resets automatically when drawScreen() is called with new data. +bool LCD::checkIdle() { - if (!isIdle && millis() - lastDataMs > IDLE_TIMEOUT_MS) + if (!_pollSent && millis() - lastDataMs > IDLE_TIMEOUT_MS) { - refreshScreen(); - drawIdleScreen(); - isIdle = true; + _idle = true; + _pollSent = true; + return true; } + return false; } // ── Private drawing helpers ─────────────────────────────────────────────────── @@ -162,14 +163,6 @@ void LCD::drawRow(int rowIndex, const char *source, const char *sender, tft.drawFastHLine(0, y + ROW_H - 1, SCREEN_W, DARKGREY); } -void LCD::drawIdleScreen() -{ - tft.setTextColor(DARKGREY, BLACK); - tft.setTextSize(2); - int msgLen = 22 * 12; // "No new notifications" × 12px/char - tft.setCursor((SCREEN_W - msgLen) / 2, SCREEN_H / 2 - 8); - tft.print("No new notifications"); -} uint16_t LCD::sourceColor(const char *source) { diff --git a/mega_microcontroller_code/lib/LCD/LCD.h b/mega_microcontroller_code/lib/LCD/LCD.h index 6b3266c..f59494b 100644 --- a/mega_microcontroller_code/lib/LCD/LCD.h +++ b/mega_microcontroller_code/lib/LCD/LCD.h @@ -35,13 +35,13 @@ class LCD private: MCUFRIEND_kbv tft; unsigned long lastDataMs = 0; - bool isIdle = false; + bool _idle = false; + bool _pollSent = false; void drawHeader(); void drawRow(int rowIndex, const char *source, const char *sender, const char *channel, const char *preview, const char *timeStr, uint8_t priority); - void drawIdleScreen(); uint16_t sourceColor(const char *source); void truncate(const char *src, char *dst, uint8_t maxLen); @@ -51,7 +51,8 @@ class LCD void startScreen(); void refreshScreen(); void drawScreen(char *receivedChars); - void checkIdle(); + // Returns true once when idle timeout first fires — caller should send a poll request. + bool checkIdle(); }; #endif diff --git a/mega_microcontroller_code/src/main.cpp b/mega_microcontroller_code/src/main.cpp index 9e473ad..6fa9e0a 100644 --- a/mega_microcontroller_code/src/main.cpp +++ b/mega_microcontroller_code/src/main.cpp @@ -22,6 +22,10 @@ void loop() myCommunication.setNewData(false); } - // Show idle screen after 5 minutes without new notifications - myLCD.checkIdle(); + // After 5 minutes with no new data, ask the ESP32 to re-send its last batch + if (myLCD.checkIdle()) + { + Serial.println("Idle timeout — polling ESP32 for last batch"); + myCommunication.sendPollRequest(); + } }