diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 81ab9258b..609f7a51c 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -84,6 +84,8 @@ namespace graphics // A text message frame + debug frame + all the node infos FrameCallback *normalFrames; static uint32_t targetFramerate = IDLE_FRAMERATE; +static String alertBannerMessage; +static uint32_t alertBannerUntil = 0; uint32_t logo_timeout = 5000; // 4 seconds for EACH logo @@ -296,6 +298,53 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i #endif } +// ============================== +// Overlay Alert Banner Renderer +// ============================== +// Displays a temporary centered banner message (e.g., warning, status, etc.) +// The banner appears in the center of the screen and disappears after the specified duration + +// Called to trigger a banner with custom message and duration +void Screen::showOverlayBanner(const String &message, uint32_t durationMs) +{ + // Store the message and set the expiration timestamp + alertBannerMessage = message; + alertBannerUntil = 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 || millis() > alertBannerUntil) return; + + // === Layout Configuration === + constexpr uint16_t padding = 5; // Padding around the text + constexpr uint8_t imprecision = 3; // Pixel jitter to reduce burn-in on E-Ink + + // Setup font and alignment + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + // === Measure and position the box === + uint16_t textWidth = display->getStringWidth(alertBannerMessage.c_str(), alertBannerMessage.length(), true); + uint16_t boxWidth = padding * 2 + textWidth; + uint16_t boxHeight = FONT_HEIGHT_SMALL + padding * 2; + + int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1); + int16_t boxTop = (display->height() / 2) - (boxHeight / 2) + random(-imprecision, imprecision + 1); + + // === 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 the text (twice for faux bold) === + display->drawString(boxLeft + padding, boxTop + padding, alertBannerMessage); + display->drawString(boxLeft + padding + 1, boxTop + padding, alertBannerMessage); // Faux bold effect +} + // 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) { @@ -3576,7 +3625,7 @@ void Screen::setFrames(FrameFocus focus) ui->disableAllIndicators(); // Add function overlay here. This can show when notifications muted, modifier key is active etc - static OverlayCallback overlays[] = {drawFunctionOverlay, drawCustomFrameIcons}; + static OverlayCallback overlays[] = {drawFunctionOverlay, drawCustomFrameIcons, drawAlertBannerOverlay}; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 2c8eff244..9db389296 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -261,6 +261,8 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } + void showOverlayBanner(const String &message, uint32_t durationMs = 3000); + void startFirmwareUpdateScreen() { ScreenCmd cmd; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 9c0245e34..d159f47be 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -19,6 +19,8 @@ #include #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" +#include "buzz.h" +#include "modules/ExternalNotificationModule.h" #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL // Sensors @@ -398,19 +400,35 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt entries.push_back("Hum: " + String(m.relative_humidity, 0) + "%"); if (m.barometric_pressure != 0) entries.push_back("Prss: " + String(m.barometric_pressure, 0) + " hPa"); - if (m.iaq != 0) { - String aqi = "IAQ: " + String(m.iaq); - - if (m.iaq <= 50) aqi += " (Good)"; - else if (m.iaq <= 100) aqi += " (Moderate)"; - else if (m.iaq <= 150) aqi += " (Poor)"; - else if (m.iaq <= 200) aqi += " (Unhealthy)"; - else if (m.iaq <= 250) aqi += " (Very Unhealthy)"; - else if (m.iaq <= 350) aqi += " (Hazardous)"; - else aqi += " (Extreme)"; - - entries.push_back(aqi); + if (m.iaq != 0) { + String aqi = "IAQ: " + String(m.iaq); + + if (m.iaq <= 50) aqi += " (Good)"; + else if (m.iaq <= 100) aqi += " (Moderate)"; + else if (m.iaq <= 150) aqi += " (Poor)"; + else if (m.iaq <= 200) aqi += " (Unhealthy)"; + else if (m.iaq <= 250) aqi += " (Very Unhealthy)"; + else if (m.iaq <= 350) aqi += " (Hazardous)"; + else aqi += " (Extreme)"; + + entries.push_back(aqi); + + // === IAQ alert logic === + static uint32_t lastAlertTime = 0; + uint32_t now = millis(); + + bool isOwnTelemetry = lastMeasurementPacket->from == nodeDB->getNodeNum(); + bool isIAQAlert = m.iaq > 100 && (now - lastAlertTime > 60000); + + if (isOwnTelemetry && isIAQAlert) { + LOG_INFO("drawFrame: IAQ %d (own) — showing banner", m.iaq); + screen->showOverlayBanner("Unhealthy IAQ Levels", 3000); // Always show banner + if (moduleConfig.external_notification.enabled && !externalNotificationModule->getMute()) { + playLongBeep(); // Only buzz if not muted + } + lastAlertTime = now; } + } if (m.voltage != 0 || m.current != 0) entries.push_back(String(m.voltage, 1) + "V / " + String(m.current, 0) + "mA"); if (m.lux != 0)