diff --git a/src/MessageStore.cpp b/src/MessageStore.cpp index e3de1c038..871e394b9 100644 --- a/src/MessageStore.cpp +++ b/src/MessageStore.cpp @@ -4,6 +4,7 @@ #include "SPILock.h" #include "SafeFile.h" #include "configuration.h" // for millis() +#include "gps/RTC.h" #include "graphics/draw/MessageRenderer.h" using graphics::MessageRenderer::setThreadMode; @@ -36,7 +37,16 @@ void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet) { StoredMessage sm; - sm.timestamp = packet.rx_time ? packet.rx_time : (millis() / 1000); + // Always use our local time, ignore packet.rx_time + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + if (nowSecs > 0) { + sm.timestamp = nowSecs; + sm.isBootRelative = false; + } else { + sm.timestamp = millis() / 1000; + sm.isBootRelative = true; // mark for later upgrade + } + sm.sender = packet.from; sm.channelIndex = packet.channel; sm.text = std::string(reinterpret_cast(packet.decoded.payload.bytes)); @@ -67,7 +77,17 @@ void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet) void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text) { StoredMessage sm; - sm.timestamp = millis() / 1000; + + // Always use our local time + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + if (nowSecs > 0) { + sm.timestamp = nowSecs; + sm.isBootRelative = false; + } else { + sm.timestamp = millis() / 1000; + sm.isBootRelative = true; // mark for later upgrade + } + sm.sender = sender; sm.channelIndex = channelIndex; sm.text = text; @@ -104,6 +124,8 @@ void MessageStore::saveToFlash() f.write((uint8_t *)&m.dest, sizeof(m.dest)); f.write((uint8_t *)m.text.c_str(), std::min(static_cast(MAX_MESSAGE_SIZE), m.text.size())); f.write('\0'); // null terminator + uint8_t bootFlag = m.isBootRelative ? 1 : 0; + f.write(&bootFlag, 1); // persist boot-relative flag } spiLock->unlock(); @@ -146,6 +168,20 @@ void MessageStore::loadFromFlash() m.text.push_back(c); } + // Try to read boot-relative flag (new format) + uint8_t bootFlag = 0; + if (f.available() > 0) { + if (f.readBytes((char *)&bootFlag, 1) == 1) { + m.isBootRelative = (bootFlag != 0); + } else { + // Old format, fallback heuristic + m.isBootRelative = (m.timestamp < 60u * 60u * 24u * 7u); + } + } else { + // Old format, fallback heuristic + m.isBootRelative = (m.timestamp < 60u * 60u * 24u * 7u); + } + // Recompute type from dest if (m.dest == NODENUM_BROADCAST) { m.type = MessageType::BROADCAST; @@ -221,5 +257,35 @@ std::deque MessageStore::getDirectMessages() const return result; } +// === Upgrade boot-relative timestamps once RTC is valid === +// Only same-boot boot-relative messages are healed. +// Persisted boot-relative messages from old boots stay ??? forever. +void MessageStore::upgradeBootRelativeTimestamps() +{ + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + if (nowSecs == 0) + return; // Still no valid RTC + + uint32_t bootNow = millis() / 1000; + + for (auto &m : liveMessages) { + if (m.isBootRelative && m.timestamp <= bootNow) { + uint32_t bootOffset = nowSecs - bootNow; + m.timestamp += bootOffset; + m.isBootRelative = false; + } + // else: persisted from old boot β†’ stays ??? forever + } + + for (auto &m : messages) { + if (m.isBootRelative && m.timestamp <= bootNow) { + uint32_t bootOffset = nowSecs - bootNow; + m.timestamp += bootOffset; + m.isBootRelative = false; + } + // else: persisted from old boot β†’ stays ??? forever + } +} + // === Global definition === MessageStore messageStore("default"); diff --git a/src/MessageStore.h b/src/MessageStore.h index 0cfb5f980..3c77a1cb9 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -24,6 +24,17 @@ struct StoredMessage { // Explicit classification (derived from dest when loading old messages) MessageType type; + + // Marks whether the timestamp was stored relative to boot time + // (true = millis()/1000 fallback, false = epoch/RTC absolute) + bool isBootRelative; + + // Default constructor to initialize all fields safely + StoredMessage() + : timestamp(0), sender(0), channelIndex(0), text(""), dest(0xffffffff), type(MessageType::BROADCAST), + isBootRelative(false) + { + } }; class MessageStore @@ -61,6 +72,9 @@ class MessageStore std::deque getDirectMessages() const; std::deque getConversationWith(uint32_t peer) const; + // Upgrade boot-relative timestamps once RTC is valid + void upgradeBootRelativeTimestamps(); + private: // RAM buffer (always current, main source for UI) std::deque liveMessages; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index a48ffaf98..0b54abf56 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -338,9 +338,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O uint8_t TFT_MESH_b = rawRGB & 0xFF; LOG_INFO("Values of r,g,b: %d, %d, %d", TFT_MESH_r, TFT_MESH_g, TFT_MESH_b); - if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) { - TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b); - } + // Values are always 0–255, no need to check + TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b); } #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) @@ -801,6 +800,7 @@ int32_t Screen::runOnce() break; case Cmd::STOP_ALERT_FRAME: NotificationRenderer::pauseBanner = false; + break; case Cmd::STOP_BOOT_SCREEN: EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) { @@ -971,9 +971,6 @@ void Screen::setFrames(FrameFocus focus) } #endif - // Declare this early so it’s available in FOCUS_PRESERVE block - bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message); - if (!hiddenFrames.home) { fsi.positions.home = numframes; normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused; @@ -1441,7 +1438,17 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) // === Save our own outgoing message to live RAM === StoredMessage sm; - sm.timestamp = millis() / 1000; + + // Always use our local time + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + if (nowSecs > 0) { + sm.timestamp = nowSecs; + sm.isBootRelative = false; + } else { + sm.timestamp = millis() / 1000; + sm.isBootRelative = true; // mark for later upgrade + } + sm.sender = nodeDB->getNodeNum(); // us sm.channelIndex = packet->channel; sm.text = std::string(reinterpret_cast(packet->decoded.payload.bytes)); @@ -1524,7 +1531,17 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) // === Save this incoming message to live RAM === StoredMessage sm; - sm.timestamp = packet->rx_time ? packet->rx_time : (millis() / 1000); + + // Always use our local time + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + if (nowSecs > 0) { + sm.timestamp = nowSecs; + sm.isBootRelative = false; + } else { + sm.timestamp = millis() / 1000; + sm.isBootRelative = true; // mark for later upgrade + } + sm.sender = packet->from; sm.channelIndex = packet->channel; sm.text = std::string(reinterpret_cast(packet->decoded.payload.bytes)); diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 295199d5a..9bf10095d 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -57,8 +57,6 @@ namespace graphics namespace MessageRenderer { -// Simple cache based on text hash -static size_t cachedKey = 0; static std::vector cachedLines; static std::vector cachedHeights; @@ -249,6 +247,9 @@ const std::vector &getSeenPeers() void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { + // Ensure any boot-relative timestamps are upgraded if RTC is valid + messageStore.upgradeBootRelativeTimestamps(); + if (!didReset) { resetScrollState(); didReset = true; @@ -375,12 +376,33 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 snprintf(chanType, sizeof(chanType), "(DM)"); } } - // else: leave empty for thread views // Calculate how long ago - uint32_t nowSecs = millis() / 1000; - uint32_t seconds = (nowSecs > m.timestamp) ? (nowSecs - m.timestamp) : 0; - bool invalidTime = (m.timestamp == 0 || seconds > 315360000); // >10 years + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + uint32_t seconds = 0; + bool invalidTime = true; + + if (m.timestamp > 0 && nowSecs > 0) { + if (nowSecs >= m.timestamp) { + seconds = nowSecs - m.timestamp; + invalidTime = (seconds > 315360000); // >10 years + } else { + uint32_t ahead = m.timestamp - nowSecs; + if (ahead <= 600) { // allow small skew + seconds = 0; + invalidTime = false; + } + } + } else if (m.timestamp > 0 && nowSecs == 0) { + // RTC not valid: only trust boot-relative if same boot + uint32_t bootNow = millis() / 1000; + if (m.isBootRelative && m.timestamp <= bootNow) { + seconds = bootNow - m.timestamp; + invalidTime = false; + } else { + invalidTime = true; // old persisted boot-relative, ignore until healed + } + } char timeBuf[16]; if (invalidTime) { @@ -492,7 +514,6 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } } - // Draw screen title header last graphics::drawCommonHeader(display, x, y, titleStr); } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index d9cc29d84..092943271 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -12,6 +12,7 @@ #include "SPILock.h" #include "buzz.h" #include "detect/ScanI2C.h" +#include "gps/RTC.h" #include "graphics/Screen.h" #include "graphics/SharedUIDisplay.h" #include "graphics/draw/MessageRenderer.h" @@ -974,7 +975,17 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha // Save outgoing message StoredMessage sm; - sm.timestamp = millis() / 1000; + + // Always use our local time, consistent with other paths + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + if (nowSecs > 0) { + sm.timestamp = nowSecs; + sm.isBootRelative = false; + } else { + sm.timestamp = millis() / 1000; + sm.isBootRelative = true; // mark for later upgrade + } + sm.sender = nodeDB->getNodeNum(); // us sm.channelIndex = channel; sm.text = std::string(message); diff --git a/src/modules/TextMessageModule.h b/src/modules/TextMessageModule.h index cc0b0f9d5..3d833334b 100644 --- a/src/modules/TextMessageModule.h +++ b/src/modules/TextMessageModule.h @@ -3,7 +3,13 @@ #include "SinglePortModule.h" /** - * Text message handling for meshtastic - draws on the OLED display the most recent received message + * Text message handling for Meshtastic. + * + * This module is responsible for receiving and storing incoming text messages + * from the mesh. It updates device state and notifies observers so that other + * components (such as the MessageRenderer) can later display or process them. + * + * Rendering of messages on screen is no longer done here. */ class TextMessageModule : public SinglePortModule, public Observable { @@ -15,12 +21,12 @@ class TextMessageModule : public SinglePortModule, public Observable