diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 109ccb518..21cf32283 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -136,12 +136,14 @@ extern bool hasUnreadMessage; // 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 char *message, uint32_t durationMs) +void Screen::showOverlayBanner(const char *message, uint32_t durationMs, uint8_t options, std::function bannerCallback) { // Store the message and set the expiration timestamp - strncpy(alertBannerMessage, message, 255); - alertBannerMessage[255] = '\0'; // Ensure null termination - alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs; + strncpy(NotificationRenderer::alertBannerMessage, message, 255); + NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination + NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs; + NotificationRenderer::alertBannerOptions = options; + NotificationRenderer::alertBannerCallback = bannerCallback; } static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -1116,10 +1118,45 @@ int32_t Screen::runOnce() #endif #ifndef DISABLE_WELCOME_UNSET - if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { - setWelcomeFrames(); + if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { + showOverlayBanner( + "Set the LoRa " + "region\nUS\nEU_433\nEU_868\nCN\nJP\nANZ\nKR\nTW\nRU\nIN\nNZ_865\nTH\nLORA_24\nUA_433\nUA_868\nMY_433\nMY_919\nSG_" + "923\nPH_433\nPH_868\nPH_915", + 0, 21, [](int selected) -> void { + LOG_WARN("Chose %d", selected); + config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected + 1); + if (!owner.is_licensed) { + bool keygenSuccess = false; + if (config.security.private_key.size == 32) { + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { + keygenSuccess = true; + } + } else { + LOG_INFO("Generate new PKI keys"); + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + keygenSuccess = true; + } + if (keygenSuccess) { + config.security.public_key.size = 32; + config.security.private_key.size = 32; + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); + } + } + config.lora.tx_enabled = true; + initRegion(); + if (myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit + } + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + }); } #endif + if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) { + showOverlayBanner("Rebooting...", 0); + } // Process incoming commands. for (;;) { @@ -1226,23 +1263,12 @@ void Screen::setSSLFrames() { if (address_found.address) { // LOG_DEBUG("Show SSL frames"); - static FrameCallback sslFrames[] = {graphics::NotificationRenderer::NotificationRenderer::drawSSLScreen}; + static FrameCallback sslFrames[] = {NotificationRenderer::drawSSLScreen}; ui->setFrames(sslFrames, 1); ui->update(); } } -/* show a message that the SSL cert is being built - * it is expected that this will be used during the boot phase */ -void Screen::setWelcomeFrames() -{ - if (address_found.address) { - // LOG_DEBUG("Show Welcome frames"); - static FrameCallback frames[] = {graphics::NotificationRenderer::NotificationRenderer::drawWelcomeScreen}; - setFrameImmediateDraw(frames); - } -} - #ifdef USE_EINK /// Determine which screensaver frame to use, then set the FrameCallback void Screen::setScreensaverFrames(FrameCallback einkScreensaver) @@ -1357,7 +1383,7 @@ void Screen::setFrames(FrameFocus focus) // If we have a critical fault, show it first fsi.positions.fault = numframes; if (error_code) { - normalFrames[numframes++] = graphics::NotificationRenderer::NotificationRenderer::drawCriticalFaultFrame; + normalFrames[numframes++] = NotificationRenderer::drawCriticalFaultFrame; indicatorIcons.push_back(icon_error); focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame } @@ -1445,8 +1471,7 @@ void Screen::setFrames(FrameFocus focus) ui->disableAllIndicators(); // Add overlays: frame icons and alert banner) - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, - graphics::NotificationRenderer::NotificationRenderer::drawAlertBannerOverlay}; + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawAlertBannerOverlay}; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list @@ -1532,7 +1557,7 @@ void Screen::handleStartFirmwareUpdateScreen() showingNormalScreen = false; EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - static FrameCallback frames[] = {graphics::NotificationRenderer::NotificationRenderer::drawFrameFirmware}; + static FrameCallback frames[] = {graphics::NotificationRenderer::drawFrameFirmware}; setFrameImmediateDraw(frames); } @@ -1770,7 +1795,12 @@ int Screen::handleInputEvent(const InputEvent *event) return 0; } #endif - + if (NotificationRenderer::isOverlayBannerShowing()) { + NotificationRenderer::inEvent = event->inputEvent; + setFrames(); + ui->update(); + return 0; + } // Use left or right input from a keyboard to move between frames, // so long as a mesh module isn't using these events for some other purpose if (showingNormalScreen) { @@ -1809,6 +1839,11 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) return 0; } +bool Screen::isOverlayBannerShowing() +{ + return NotificationRenderer::isOverlayBannerShowing(); +} + } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index fee1dad9f..fc2592c4d 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -220,8 +220,7 @@ class Screen : public concurrency::OSThread meshtastic_Config_DisplayConfig_OledType model; OLEDDISPLAY_GEOMETRY geometry; - char alertBannerMessage[256] = {0}; - uint32_t alertBannerUntil = 0; // 0 is a special case meaning forever + bool isOverlayBannerShowing(); // Stores the last 4 of our hardware ID, to make finding the device for pairing easier // FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class @@ -286,12 +285,8 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } - void showOverlayBanner(const char *message, uint32_t durationMs = 3000); - - bool isOverlayBannerShowing() - { - return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil); - } + void showOverlayBanner(const char *message, uint32_t durationMs = 3000, uint8_t options = 0, + std::function bannerCallback = NULL); void startFirmwareUpdateScreen() { @@ -571,8 +566,6 @@ class Screen : public concurrency::OSThread /// Draws our SSL cert screen during boot (called from WebServer) void setSSLFrames(); - void setWelcomeFrames(); - // Dismiss the currently focussed frame, if possible (e.g. text message, waypoint) void dismissCurrentFrame(); diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index fb55f1ceb..88a14fce0 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -4,7 +4,6 @@ #include "DisplayFormatters.h" #include "NodeDB.h" #include "NotificationRenderer.h" -#include "graphics/Screen.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" @@ -27,8 +26,12 @@ extern bool hasUnreadMessage; namespace graphics { -namespace NotificationRenderer -{ +char NotificationRenderer::inEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE); +int8_t NotificationRenderer::curSelected = 0; +char NotificationRenderer::alertBannerMessage[256] = {0}; +uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever +uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are seelctable options +std::function NotificationRenderer::alertBannerCallback = NULL; // Used on boot when a certificate is being created void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -36,6 +39,7 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_SMALL); display->drawString(64 + x, y, "Creating SSL certificate"); + uint32_t alertBannerUntil = 0; // 0 is a special case meaning forever #ifdef ARCH_ESP32 yield(); @@ -50,48 +54,27 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat } } -// 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 (!screen->isOverlayBannerShowing()) + if (!isOverlayBannerShowing()) return; - + LOG_DEBUG("event: %u, curSelected: %d", inEvent, curSelected); // === Layout Configuration === constexpr uint16_t padding = 5; // Padding around text inside the box + constexpr uint16_t vPadding = 2; // Padding around text inside the box constexpr uint8_t lineSpacing = 1; // Extra space between lines // Search the message to determine if we need the bell added - bool needs_bell = (strstr(screen->alertBannerMessage, "Alert Received") != nullptr); + bool needs_bell = (strstr(alertBannerMessage, "Alert Received") != nullptr); + + uint8_t firstOption = 0; + uint8_t firstOptionToShow = 0; // Setup font and alignment display->setFont(FONT_SMALL); display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line - const int MAX_LINES = 10; + const int MAX_LINES = 23; uint16_t maxWidth = 0; uint16_t lineWidths[MAX_LINES] = {0}; @@ -100,12 +83,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp uint16_t lineCount = 0; char lineBuffer[40] = {0}; // pointer to the terminating null - char *alertEnd = screen->alertBannerMessage + strnlen(screen->alertBannerMessage, sizeof(screen->alertBannerMessage)); - lineStarts[lineCount] = screen->alertBannerMessage; - LOG_WARN(lineStarts[lineCount]); + char *alertEnd = alertBannerMessage + strnlen(alertBannerMessage, sizeof(alertBannerMessage)); + lineStarts[lineCount] = alertBannerMessage; // loop through lines finding \n characters - while ((lineCount < 10) && (lineStarts[lineCount] < alertEnd)) { + while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) { lineStarts[lineCount + 1] = std::find(lineStarts[lineCount], alertEnd, '\n'); lineLengths[lineCount] = lineStarts[lineCount + 1] - lineStarts[lineCount]; if (lineStarts[lineCount + 1][0] == '\n') { @@ -116,6 +98,38 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp maxWidth = lineWidths[lineCount]; } lineCount++; + // if we are doing a selection, add extra width for arrows + } + + if (alertBannerOptions > 0) { + // respond to input + if (inEvent == meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP) { + curSelected--; + } else if (inEvent == meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN) { + curSelected++; + } else if (inEvent == meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT) { + alertBannerCallback(curSelected); + alertBannerMessage[0] = '\0'; + } + if (curSelected == -1) + curSelected = alertBannerOptions - 1; + if (curSelected == alertBannerOptions) + curSelected = 0; + // compare number of options to number of lines + if (lineCount < alertBannerOptions) + return; + firstOption = lineCount - alertBannerOptions; + if (curSelected > 1 && alertBannerOptions > 3) { + firstOptionToShow = curSelected + firstOption - 1; + // put the selected option in the middle + } else { + firstOptionToShow = firstOption; + } + } else { // not in an alert with a callback + // TODO: check that at least a second has passed since the alert started + if (inEvent == meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT) { + alertBannerMessage[0] = '\0'; // end the alert early + } } // set width from longest line @@ -128,9 +142,14 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp boxWidth += 20; } } - + // calculate max lines on screen? for now it's 4 // set height from line count - uint16_t boxHeight = padding * 2 + lineCount * FONT_HEIGHT_SMALL + (lineCount - 1) * lineSpacing; + uint16_t boxHeight; + if (lineCount <= 4) { + boxHeight = vPadding * 2 + lineCount * FONT_HEIGHT_SMALL + (lineCount - 1) * lineSpacing; + } else { + boxHeight = vPadding * 2 + 4 * FONT_HEIGHT_SMALL + 4 * lineSpacing; + } int16_t boxLeft = (display->width() / 2) - (boxWidth / 2); int16_t boxTop = (display->height() / 2) - (boxHeight / 2); @@ -151,13 +170,42 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp display->setColor(WHITE); // === Draw each line centered in the box === - int16_t lineY = boxTop + padding; + int16_t lineY = boxTop + vPadding; + + LOG_DEBUG("firstOptionToShow: %u, firstOption: %u", firstOptionToShow, firstOption); + // for (int i = 0; i < lineCount; i++) { - strncpy(lineBuffer, lineStarts[i], 40); - if (lineLengths[i] > 39) - lineBuffer[39] = '\0'; - else - lineBuffer[lineLengths[i]] = '\0'; + // is this line selected? + // if so, start the buffer with -> and strncpy to the 4th location + if (i == 0 || alertBannerOptions == 0) { + strncpy(lineBuffer, lineStarts[i], 40); + if (lineLengths[i] > 39) + lineBuffer[39] = '\0'; + else + lineBuffer[lineLengths[i]] = '\0'; + } else if (i >= firstOptionToShow && i <= firstOptionToShow + 3) { + if (i == curSelected + firstOption) { + if (lineLengths[i] > 35) + lineLengths[i] = 35; + strncpy(lineBuffer, "->", 3); + strncpy(lineBuffer + 2, lineStarts[i], 36); + strncpy(lineBuffer + lineLengths[i] + 2, "<-", 3); + lineLengths[i] += 4; + lineWidths[i] += display->getStringWidth("-><-", 4, true); + if (lineLengths[i] > 35) + lineBuffer[39] = '\0'; + else + lineBuffer[lineLengths[i]] = '\0'; + } else { + strncpy(lineBuffer, lineStarts[i], 40); + if (lineLengths[i] > 39) + lineBuffer[39] = '\0'; + else + lineBuffer[lineLengths[i]] = '\0'; + } + } else { // add break for the additional lines + continue; + } int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2; @@ -173,6 +221,7 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp lineY += FONT_HEIGHT_SMALL + lineSpacing; } + inEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE); } /// Draw the last text message we received @@ -201,7 +250,10 @@ void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUi "Please be patient and do not power off."); } -} // namespace NotificationRenderer +bool NotificationRenderer::isOverlayBannerShowing() +{ + return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil); +} } // namespace graphics #endif \ No newline at end of file diff --git a/src/graphics/draw/NotificationRenderer.h b/src/graphics/draw/NotificationRenderer.h index 6f07d75c4..2fe758d5f 100644 --- a/src/graphics/draw/NotificationRenderer.h +++ b/src/graphics/draw/NotificationRenderer.h @@ -6,19 +6,21 @@ namespace graphics { -namespace NotificationRenderer -{ - class NotificationRenderer { public: + static char inEvent; + static int8_t curSelected; + static char alertBannerMessage[256]; + static uint32_t alertBannerUntil; // 0 is a special case meaning forever + static uint8_t alertBannerOptions; // last x lines are seelctable options + static std::function alertBannerCallback; + 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); + static bool isOverlayBannerShowing(); }; -} // namespace NotificationRenderer - } // namespace graphics