From 29402a5e7ac349e55a018a05e4c3f25b5b04e032 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 30 May 2025 20:50:24 -0500 Subject: [PATCH] Notification and time --- src/graphics/Screen.cpp | 214 +-------------------- src/graphics/draw/NotificationRenderer.cpp | 167 ++++++++++++++++ src/graphics/draw/NotificationRenderer.h | 14 ++ src/graphics/draw/UIRenderer.cpp | 56 +++++- src/graphics/draw/UIRenderer.h | 2 +- 5 files changed, 244 insertions(+), 209 deletions(-) create mode 100644 src/graphics/draw/NotificationRenderer.cpp create mode 100644 src/graphics/draw/NotificationRenderer.h diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 976625594..753283e34 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -31,6 +31,7 @@ along with this program. If not, see . #include "draw/DebugRenderer.h" #include "draw/MessageRenderer.h" #include "draw/NodeListRenderer.h" +#include "draw/NotificationRenderer.h" #include "draw/UIRenderer.h" #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" @@ -76,8 +77,6 @@ using graphics::NodeListRenderer::drawScaledXBitmap16x16; #endif #ifdef ARCH_ESP32 -#include "esp_task_wdt.h" -#include "modules/StoreForwardModule.h" #endif #if ARCH_PORTDUINO @@ -141,61 +140,6 @@ static bool heartbeat = false; #include "graphics/ScreenFonts.h" #include -// Start Functions to write date/time to the screen -#include // Only needed if you're using std::string elsewhere - -bool isLeapYear(int year) -{ - return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); -} - -const int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - -// Fills the buffer with a formatted date/time string and returns pixel width -int formatDateTime(char *buf, size_t bufSize, uint32_t rtc_sec, OLEDDisplay *display, bool includeTime) -{ - int sec = rtc_sec % 60; - rtc_sec /= 60; - int min = rtc_sec % 60; - rtc_sec /= 60; - int hour = rtc_sec % 24; - rtc_sec /= 24; - - int year = 1970; - while (true) { - int daysInYear = isLeapYear(year) ? 366 : 365; - if (rtc_sec >= (uint32_t)daysInYear) { - rtc_sec -= daysInYear; - year++; - } else { - break; - } - } - - int month = 0; - while (month < 12) { - int dim = daysInMonth[month]; - if (month == 1 && isLeapYear(year)) - dim++; - if (rtc_sec >= (uint32_t)dim) { - rtc_sec -= dim; - month++; - } else { - break; - } - } - - int day = rtc_sec + 1; - - if (includeTime) { - snprintf(buf, bufSize, "%04d-%02d-%02d %02d:%02d:%02d", year, month + 1, day, hour, min, sec); - } else { - snprintf(buf, bufSize, "%04d-%02d-%02d", year, month + 1, day); - } - - return display->getStringWidth(buf); -} - // Usage: int stringWidth = formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display); // End Functions to write date/time to the screen @@ -367,51 +311,6 @@ void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int1 display->setFont(FONT_MEDIUM); display->drawString(x_offset + x, 26 + y, message); } - -// Used on boot when a certificate is being created -static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_SMALL); - display->drawString(64 + x, y, "Creating SSL certificate"); - -#ifdef ARCH_ESP32 - yield(); - esp_task_wdt_reset(); -#endif - - display->setFont(FONT_SMALL); - if ((millis() / 1000) % 2) { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); - } else { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); - } -} - -// Used when booting without a region set -static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C"); - display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName()); - display->setTextAlignment(TEXT_ALIGN_LEFT); - - if ((millis() / 10000) % 2) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); - } else { - display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, ""); - } - -#ifdef ARCH_ESP32 - yield(); - esp_task_wdt_reset(); -#endif -} // ============================== // Overlay Alert Banner Renderer // ============================== @@ -426,79 +325,6 @@ void Screen::showOverlayBanner(const String &message, uint32_t durationMs) alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs; } -// Draws the overlay banner on screen, if still within display duration -static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) -{ - // Exit if no message is active or duration has passed - if (alertBannerMessage.length() == 0 || (alertBannerUntil != 0 && millis() > alertBannerUntil)) - return; - - // === Layout Configuration === - constexpr uint16_t padding = 5; // Padding around text inside the box - constexpr uint8_t lineSpacing = 1; // Extra space between lines - - // Search the mesage to determine if we need the bell added - bool needs_bell = (alertBannerMessage.indexOf("Alert Received") != -1); - - // Setup font and alignment - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line - - // === Split the message into lines (supports multi-line banners) === - std::vector lines; - int start = 0, newlineIdx; - while ((newlineIdx = alertBannerMessage.indexOf('\n', start)) != -1) { - lines.push_back(alertBannerMessage.substring(start, newlineIdx)); - start = newlineIdx + 1; - } - lines.push_back(alertBannerMessage.substring(start)); - - // === Measure text dimensions === - uint16_t minWidth = (SCREEN_WIDTH > 128) ? 106 : 78; - uint16_t maxWidth = 0; - std::vector lineWidths; - for (const auto &line : lines) { - uint16_t w = display->getStringWidth(line.c_str(), line.length(), true); - lineWidths.push_back(w); - if (w > maxWidth) - maxWidth = w; - } - - uint16_t boxWidth = padding * 2 + maxWidth; - if (needs_bell && boxWidth < minWidth) - boxWidth += (SCREEN_WIDTH > 128) ? 26 : 20; - - uint16_t boxHeight = padding * 2 + lines.size() * FONT_HEIGHT_SMALL + (lines.size() - 1) * lineSpacing; - - int16_t boxLeft = (display->width() / 2) - (boxWidth / 2); - int16_t boxTop = (display->height() / 2) - (boxHeight / 2); - - // === Draw background box === - display->setColor(BLACK); - display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Slightly oversized box - display->setColor(WHITE); - display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); // Border - - // === Draw each line centered in the box === - int16_t lineY = boxTop + padding; - for (size_t i = 0; i < lines.size(); ++i) { - int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2; - uint16_t line_width = display->getStringWidth(lines[i].c_str(), lines[i].length(), true); - - if (needs_bell && i == 0) { - int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2; - display->drawXbm(textX - 10, bellY, 8, 8, bell_alert); - display->drawXbm(textX + line_width + 2, bellY, 8, 8, bell_alert); - } - - display->drawString(textX, lineY, lines[i]); - if (SCREEN_WIDTH > 128) - display->drawString(textX + 1, lineY, lines[i]); // Faux bold - - lineY += FONT_HEIGHT_SMALL + lineSpacing; - } -} - // draw overlay in bottom right corner of screen to show when notifications are muted or modifier key is active static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) { @@ -602,32 +428,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int pi.drawFrame(display, state, x, y); } -static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(64 + x, y, "Updating"); - - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), - "Please be patient and do not power off."); -} - -/// Draw the last text message we received -static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - - char tempBuf[24]; - snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code); - display->drawString(0 + x, 0 + y, tempBuf); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org"); -} - // Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) { @@ -2107,7 +1907,7 @@ static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiStat uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); char datetimeStr[25]; bool showTime = false; // set to true for full datetime - formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime); + graphics::UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime); char fullLine[40]; snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr); display->drawString(0, ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine), fullLine); @@ -2926,7 +2726,7 @@ void Screen::setSSLFrames() { if (address_found.address) { // LOG_DEBUG("Show SSL frames"); - static FrameCallback sslFrames[] = {drawSSLScreen}; + static FrameCallback sslFrames[] = {NotificationRenderer::drawSSLScreen}; ui->setFrames(sslFrames, 1); ui->update(); } @@ -2938,7 +2738,7 @@ void Screen::setWelcomeFrames() { if (address_found.address) { // LOG_DEBUG("Show Welcome frames"); - static FrameCallback frames[] = {drawWelcomeScreen}; + static FrameCallback frames[] = {NotificationRenderer::drawWelcomeScreen}; setFrameImmediateDraw(frames); } } @@ -3057,7 +2857,7 @@ void Screen::setFrames(FrameFocus focus) // If we have a critical fault, show it first fsi.positions.fault = numframes; if (error_code) { - normalFrames[numframes++] = drawCriticalFaultFrame; + normalFrames[numframes++] = NotificationRenderer::drawCriticalFaultFrame; indicatorIcons.push_back(icon_error); focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame } @@ -3145,7 +2945,7 @@ void Screen::setFrames(FrameFocus focus) ui->disableAllIndicators(); // Add overlays: frame icons and alert banner) - static OverlayCallback overlays[] = {NavigationBar, drawAlertBannerOverlay}; + static OverlayCallback overlays[] = {NavigationBar, NotificationRenderer::drawAlertBannerOverlay}; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list @@ -3231,7 +3031,7 @@ void Screen::handleStartFirmwareUpdateScreen() showingNormalScreen = false; EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - static FrameCallback frames[] = {drawFrameFirmware}; + static FrameCallback frames[] = {NotificationRenderer::drawFrameFirmware}; setFrameImmediateDraw(frames); } diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp new file mode 100644 index 000000000..d72d6c907 --- /dev/null +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -0,0 +1,167 @@ +#include "NotificationRenderer.h" +#include "DisplayFormatters.h" +#include "NodeDB.h" +#include "configuration.h" +#include "graphics/ScreenFonts.h" +#include "graphics/SharedUIDisplay.h" +#include "graphics/images.h" +#include "main.h" +#include +#include +#include + +#ifdef ARCH_ESP32 +#include "esp_task_wdt.h" +#endif + +using namespace meshtastic; + +// External references to global variables from Screen.cpp +extern String alertBannerMessage; +extern uint32_t alertBannerUntil; +extern std::vector functionSymbol; +extern std::string functionSymbolString; +extern bool hasUnreadMessage; + +// Used on boot when a certificate is being created +void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_SMALL); + display->drawString(64 + x, y, "Creating SSL certificate"); + +#ifdef ARCH_ESP32 + yield(); + esp_task_wdt_reset(); +#endif + + display->setFont(FONT_SMALL); + if ((millis() / 1000) % 2) { + display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); + } else { + display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); + } +} + +// Used when booting without a region set +void NotificationRenderer::drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C"); + display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName()); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + if ((millis() / 10000) % 2) { + display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); + } else { + display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, ""); + } + +#ifdef ARCH_ESP32 + yield(); + esp_task_wdt_reset(); +#endif +} + +void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) +{ + // Exit if no message is active or duration has passed + if (alertBannerMessage.length() == 0 || (alertBannerUntil != 0 && millis() > alertBannerUntil)) + return; + + // === Layout Configuration === + constexpr uint16_t padding = 5; // Padding around text inside the box + constexpr uint8_t lineSpacing = 1; // Extra space between lines + + // Search the mesage to determine if we need the bell added + bool needs_bell = (alertBannerMessage.indexOf("Alert Received") != -1); + + // Setup font and alignment + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line + + // === Split the message into lines (supports multi-line banners) === + std::vector lines; + int start = 0, newlineIdx; + while ((newlineIdx = alertBannerMessage.indexOf('\n', start)) != -1) { + lines.push_back(alertBannerMessage.substring(start, newlineIdx)); + start = newlineIdx + 1; + } + lines.push_back(alertBannerMessage.substring(start)); + + // === Measure text dimensions === + uint16_t minWidth = (SCREEN_WIDTH > 128) ? 106 : 78; + uint16_t maxWidth = 0; + std::vector lineWidths; + for (const auto &line : lines) { + uint16_t w = display->getStringWidth(line.c_str(), line.length(), true); + lineWidths.push_back(w); + if (w > maxWidth) + maxWidth = w; + } + + uint16_t boxWidth = padding * 2 + maxWidth; + if (needs_bell && boxWidth < minWidth) + boxWidth += (SCREEN_WIDTH > 128) ? 26 : 20; + + uint16_t boxHeight = padding * 2 + lines.size() * FONT_HEIGHT_SMALL + (lines.size() - 1) * lineSpacing; + + int16_t boxLeft = (display->width() / 2) - (boxWidth / 2); + int16_t boxTop = (display->height() / 2) - (boxHeight / 2); + + // === Draw background box === + display->setColor(BLACK); + display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Slightly oversized box + display->setColor(WHITE); + display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); // Border + + // === Draw each line centered in the box === + int16_t lineY = boxTop + padding; + for (size_t i = 0; i < lines.size(); ++i) { + int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2; + uint16_t line_width = display->getStringWidth(lines[i].c_str(), lines[i].length(), true); + + if (needs_bell && i == 0) { + int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2; + display->drawXbm(textX - 10, bellY, 8, 8, bell_alert); + display->drawXbm(textX + line_width + 2, bellY, 8, 8, bell_alert); + } + + display->drawString(textX, lineY, lines[i]); + if (SCREEN_WIDTH > 128) + display->drawString(textX + 1, lineY, lines[i]); // Faux bold + + lineY += FONT_HEIGHT_SMALL + lineSpacing; + } +} + +/// Draw the last text message we received +void NotificationRenderer::drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + + char tempBuf[24]; + snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code); + display->drawString(0 + x, 0 + y, tempBuf); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org"); +} + +void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(64 + x, y, "Updating"); + + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), + "Please be patient and do not power off."); +} diff --git a/src/graphics/draw/NotificationRenderer.h b/src/graphics/draw/NotificationRenderer.h new file mode 100644 index 000000000..0b90979e2 --- /dev/null +++ b/src/graphics/draw/NotificationRenderer.h @@ -0,0 +1,14 @@ +#pragma once + +#include "OLEDDisplay.h" +#include "OLEDDisplayUi.h" + +class NotificationRenderer +{ + public: + static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state); + static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +}; diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 5210b53b9..4789bd92c 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -183,8 +183,62 @@ void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshta } } -} // namespace UIRenderer +// Start Functions to write date/time to the screen +// Helper function to check if a year is a leap year +bool isLeapYear(int year) +{ + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); +} +// Array of days in each month (non-leap year) +const int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +// Fills the buffer with a formatted date/time string and returns pixel width +int formatDateTime(char *buf, size_t bufSize, uint32_t rtc_sec, OLEDDisplay *display, bool includeTime) +{ + int sec = rtc_sec % 60; + rtc_sec /= 60; + int min = rtc_sec % 60; + rtc_sec /= 60; + int hour = rtc_sec % 24; + rtc_sec /= 24; + + int year = 1970; + while (true) { + int daysInYear = isLeapYear(year) ? 366 : 365; + if (rtc_sec >= (uint32_t)daysInYear) { + rtc_sec -= daysInYear; + year++; + } else { + break; + } + } + + int month = 0; + while (month < 12) { + int dim = daysInMonth[month]; + if (month == 1 && isLeapYear(year)) + dim++; + if (rtc_sec >= (uint32_t)dim) { + rtc_sec -= dim; + month++; + } else { + break; + } + } + + int day = rtc_sec + 1; + + if (includeTime) { + snprintf(buf, bufSize, "%04d-%02d-%02d %02d:%02d:%02d", year, month + 1, day, hour, min, sec); + } else { + snprintf(buf, bufSize, "%04d-%02d-%02d", year, month + 1, day); + } + + return display->getStringWidth(buf); +} + +} // namespace UIRenderer } // namespace graphics #endif // !MESHTASTIC_EXCLUDE_GPS diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h index 770e5010d..e1675bc71 100644 --- a/src/graphics/draw/UIRenderer.h +++ b/src/graphics/draw/UIRenderer.h @@ -55,7 +55,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string // Time and date utilities void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength); std::string drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds); -void formatDateTime(char *buffer, size_t bufferSize, uint32_t rtc_sec, OLEDDisplay *display, bool showTime); +int formatDateTime(char *buffer, size_t bufferSize, uint32_t rtc_sec, OLEDDisplay *display, bool showTime); // Message filtering bool shouldDrawMessage(const meshtastic_MeshPacket *packet);