diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 1dca83033..3680bb6b8 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -9,10 +9,10 @@ #include "RadioLibInterface.h" #include "buzz.h" #include "main.h" -#include "modules/ExternalNotificationModule.h" #include "modules/CannedMessageModule.h" +#include "modules/ExternalNotificationModule.h" #include "power.h" -#include "sleep.h" +#include "sleep.h" #ifdef ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" #endif @@ -27,7 +27,7 @@ using namespace concurrency; ButtonThread *buttonThread; // Declared extern in header -extern CannedMessageModule* cannedMessageModule; +extern CannedMessageModule *cannedMessageModule; volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE; #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN) @@ -122,7 +122,7 @@ void ButtonThread::switchPage() { // Prevent screen switch if CannedMessageModule is focused and intercepting input #if HAS_SCREEN - extern CannedMessageModule* cannedMessageModule; + extern CannedMessageModule *cannedMessageModule; if (cannedMessageModule && cannedMessageModule->isInterceptingAndFocused()) { LOG_DEBUG("User button ignored during canned message input"); @@ -232,15 +232,15 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_DOUBLE_PRESSED: { LOG_BUTTON("Double press!"); - - #ifdef ELECROW_ThinkNode_M1 + +#ifdef ELECROW_ThinkNode_M1 digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW); break; - #endif - +#endif + // Send GPS position immediately sendAdHocPosition(); - + // Show temporary on-screen confirmation banner for 3 seconds screen->showOverlayBanner("Ad-hoc Ping Sent", 3000); break; @@ -250,21 +250,21 @@ int32_t ButtonThread::runOnce() LOG_BUTTON("Mulitipress! %hux", multipressClickCount); switch (multipressClickCount) { #if HAS_GPS && !defined(ELECROW_ThinkNode_M1) - // 3 clicks: toggle GPS - case 3: - if (!config.device.disable_triple_click && (gps != nullptr)) { - gps->toggleGpsMode(); + // 3 clicks: toggle GPS + case 3: + if (!config.device.disable_triple_click && (gps != nullptr)) { + gps->toggleGpsMode(); - const char* statusMsg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) - ? "GPS Enabled" - : "GPS Disabled"; + const char *statusMsg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) + ? "GPS Enabled" + : "GPS Disabled"; - if (screen) { - screen->forceDisplay(true); // Force a new UI frame, then force an EInk update - screen->showOverlayBanner(statusMsg, 3000); + if (screen) { + screen->forceDisplay(true); // Force a new UI frame, then force an EInk update + screen->showOverlayBanner(statusMsg, 3000); + } } - } - break; + break; #elif defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) case 3: LOG_INFO("3 clicks: toggle buzzer"); @@ -306,12 +306,12 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_LONG_PRESSED: { LOG_BUTTON("Long press!"); powerFSM.trigger(EVENT_PRESS); - + if (screen) { // Show shutdown message as a temporary overlay banner - screen->showOverlayBanner("Shutting Down..."); // Display for 3 seconds + screen->showOverlayBanner("Shutting Down..."); // Display for 3 seconds } - + playBeep(); break; } diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 235398177..9e38f46b1 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -35,19 +35,19 @@ along with this program. If not, see . #include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" +#include "RadioLibInterface.h" #include "error.h" #include "gps/GeoCoord.h" #include "gps/RTC.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" -#include "graphics/images.h" #include "graphics/emotes.h" +#include "graphics/images.h" #include "input/ScanAndSelect.h" #include "input/TouchScreenImpl1.h" #include "main.h" #include "mesh-pb-constants.h" #include "mesh/Channels.h" -#include "RadioLibInterface.h" #include "mesh/generated/meshtastic/deviceonly.pb.h" #include "meshUtils.h" #include "modules/AdminModule.h" @@ -57,7 +57,6 @@ along with this program. If not, see . #include "sleep.h" #include "target_specific.h" - using graphics::Emote; using graphics::emotes; using graphics::numEmotes; @@ -132,18 +131,19 @@ 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 +#include // Only needed if you're using std::string elsewhere -bool isLeapYear(int year) { +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 }; +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 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; @@ -165,7 +165,8 @@ int formatDateTime(char* buf, size_t bufSize, uint32_t rtc_sec, OLEDDisplay* dis int month = 0; while (month < 12) { int dim = daysInMonth[month]; - if (month == 1 && isLeapYear(year)) dim++; + if (month == 1 && isLeapYear(year)) + dim++; if (rtc_sec >= (uint32_t)dim) { rtc_sec -= dim; month++; @@ -188,7 +189,6 @@ int formatDateTime(char* buf, size_t bufSize, uint32_t rtc_sec, OLEDDisplay* dis // Usage: int stringWidth = formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display); // End Functions to write date/time to the screen - void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display) { for (int row = 0; row < height; row++) { @@ -1357,7 +1357,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } for (int i = 0; i < numEmotes; ++i) { const Emote &e = emotes[i]; - if (strcmp(msg, e.label) == 0){ + if (strcmp(msg, e.label) == 0) { // Draw the header if (isInverted) { drawRoundedHighlight(display, x, 0, SCREEN_WIDTH, FONT_HEIGHT_SMALL - 1, cornerRadius); @@ -1881,23 +1881,26 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ for (size_t i = 0; i < total; i++) { meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); // Skip nulls and ourself - if (!n || n->num == nodeDB->getNodeNum()) continue; - if (n->is_favorite) favoritedNodes.push_back(n); + if (!n || n->num == nodeDB->getNodeNum()) + continue; + if (n->is_favorite) + favoritedNodes.push_back(n); } // Keep a stable, consistent display order std::sort(favoritedNodes.begin(), favoritedNodes.end(), - [](meshtastic_NodeInfoLite *a, meshtastic_NodeInfoLite *b) { - return a->num < b->num; - }); + [](meshtastic_NodeInfoLite *a, meshtastic_NodeInfoLite *b) { return a->num < b->num; }); } - if (favoritedNodes.empty()) return; + if (favoritedNodes.empty()) + return; // --- Only display if index is valid --- int nodeIndex = state->currentFrame - (screen->frameCount - favoritedNodes.size()); - if (nodeIndex < 0 || nodeIndex >= (int)favoritedNodes.size()) return; + if (nodeIndex < 0 || nodeIndex >= (int)favoritedNodes.size()) + return; meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex]; - if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite) return; + if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite) + return; display->clear(); @@ -1928,13 +1931,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // 4. The first line is ALWAYS at your macro position; subsequent lines use the next available macro slot. // List of available macro Y positions in order, from top to bottom. - const int yPositions[5] = { - moreCompactFirstLine, - moreCompactSecondLine, - moreCompactThirdLine, - moreCompactFourthLine, - moreCompactFifthLine - }; + const int yPositions[5] = {moreCompactFirstLine, moreCompactSecondLine, moreCompactThirdLine, moreCompactFourthLine, + moreCompactFifthLine}; int line = 0; // which slot to use next // === 1. Long Name (always try to show first) === @@ -1965,7 +1963,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ size_t len = strlen(signalHopsStr); // Decide between "1 Hop" and "N Hops" if (haveSignal) { - snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [%d %s]", node->hops_away, (node->hops_away == 1 ? "Hop" : "Hops")); + snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [%d %s]", node->hops_away, + (node->hops_away == 1 ? "Hop" : "Hops")); } else { snprintf(signalHopsStr, sizeof(signalHopsStr), "[%d %s]", node->hops_away, (node->hops_away == 1 ? "Hop" : "Hops")); } @@ -1980,10 +1979,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ if (seconds != 0 && seconds != UINT32_MAX) { uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24; // Format as "Heard: Xm ago", "Heard: Xh ago", or "Heard: Xd ago" - snprintf(seenStr, sizeof(seenStr), - (days > 365 ? " Heard: ?" : " Heard: %d%c ago"), - (days ? days : hours ? hours : minutes), - (days ? 'd' : hours ? 'h' : 'm')); + snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard: ?" : " Heard: %d%c ago"), + (days ? days + : hours ? hours + : minutes), + (days ? 'd' + : hours ? 'h' + : 'm')); } if (seenStr[0] && line < 5) { display->drawString(x, yPositions[line++], seenStr); @@ -2010,7 +2012,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // === 5. Distance (only if both nodes have GPS position) === meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - char distStr[24] = ""; // Make buffer big enough for any string + char distStr[24] = ""; // Make buffer big enough for any string bool haveDistance = false; if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) { @@ -2022,9 +2024,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ double dLat = (lat2 - lat1) * DEG_TO_RAD; double dLon = (lon2 - lon1) * DEG_TO_RAD; double a = - sin(dLat / 2) * sin(dLat / 2) + - cos(lat1 * DEG_TO_RAD) * cos(lat2 * DEG_TO_RAD) * - sin(dLon / 2) * sin(dLon / 2); + sin(dLat / 2) * sin(dLat / 2) + cos(lat1 * DEG_TO_RAD) * cos(lat2 * DEG_TO_RAD) * sin(dLon / 2) * sin(dLon / 2); double c = 2 * atan2(sqrt(a), sqrt(1 - a)); double distanceKm = earthRadiusKm * c; @@ -2088,19 +2088,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2; const auto &op = ourNode->position; - float myHeading = screen->hasHeading() - ? screen->getHeading() * PI / 180 - : screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180 + : screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); screen->drawCompassNorth(display, compassX, compassY, myHeading); const auto &p = node->position; - float d = GeoCoord::latLongToMeter( - DegD(p.latitude_i), DegD(p.longitude_i), - DegD(op.latitude_i), DegD(op.longitude_i)); - float bearing = GeoCoord::bearing( - DegD(op.latitude_i), DegD(op.longitude_i), - DegD(p.latitude_i), DegD(p.longitude_i)); - if (!config.display.compass_north_top) bearing -= myHeading; + float d = + GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); + if (!config.display.compass_north_top) + bearing -= myHeading; screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearing); display->drawCircle(compassX, compassY, compassRadius); @@ -2115,39 +2112,39 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ if (showCompass) { int yBelowContent = (line > 0 && line <= 5) ? (yPositions[line - 1] + FONT_HEIGHT_SMALL + 2) : moreCompactFirstLine; const int margin = 4; - // --------- PATCH FOR EINK NAV BAR (ONLY CHANGE BELOW) ----------- - #if defined(USE_EINK) - const int iconSize = (SCREEN_WIDTH > 128) ? 16 : 8; - const int navBarHeight = iconSize + 6; - #else - const int navBarHeight = 0; - #endif +// --------- PATCH FOR EINK NAV BAR (ONLY CHANGE BELOW) ----------- +#if defined(USE_EINK) + const int iconSize = (SCREEN_WIDTH > 128) ? 16 : 8; + const int navBarHeight = iconSize + 6; +#else + const int navBarHeight = 0; +#endif int availableHeight = SCREEN_HEIGHT - yBelowContent - navBarHeight - margin; // --------- END PATCH FOR EINK NAV BAR ----------- - if (availableHeight < FONT_HEIGHT_SMALL * 2) return; + if (availableHeight < FONT_HEIGHT_SMALL * 2) + return; int compassRadius = availableHeight / 2; - if (compassRadius < 8) compassRadius = 8; - if (compassRadius * 2 > SCREEN_WIDTH - 16) compassRadius = (SCREEN_WIDTH - 16) / 2; + if (compassRadius < 8) + compassRadius = 8; + if (compassRadius * 2 > SCREEN_WIDTH - 16) + compassRadius = (SCREEN_WIDTH - 16) / 2; int compassX = x + SCREEN_WIDTH / 2; int compassY = yBelowContent + availableHeight / 2; const auto &op = ourNode->position; - float myHeading = screen->hasHeading() - ? screen->getHeading() * PI / 180 - : screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180 + : screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); screen->drawCompassNorth(display, compassX, compassY, myHeading); const auto &p = node->position; - float d = GeoCoord::latLongToMeter( - DegD(p.latitude_i), DegD(p.longitude_i), - DegD(op.latitude_i), DegD(op.longitude_i)); - float bearing = GeoCoord::bearing( - DegD(op.latitude_i), DegD(op.longitude_i), - DegD(p.latitude_i), DegD(p.longitude_i)); - if (!config.display.compass_north_top) bearing -= myHeading; + float d = + GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); + if (!config.display.compass_north_top) + bearing -= myHeading; screen->drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing); display->drawCircle(compassX, compassY, compassRadius); @@ -2346,9 +2343,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int totalEntries = nodeList.size(); int totalRowsAvailable = (display->getHeight() - y) / rowYOffset; - #ifdef USE_EINK - totalRowsAvailable -= 1; - #endif +#ifdef USE_EINK + totalRowsAvailable -= 1; +#endif int visibleNodeRows = totalRowsAvailable; int totalColumns = 2; @@ -2424,8 +2421,8 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); display->drawString(x + ((SCREEN_WIDTH > 128) ? 6 : 2), y, nodeName); - if (node->is_favorite){ - if(SCREEN_WIDTH > 128){ + if (node->is_favorite) { + if (SCREEN_WIDTH > 128) { drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display); } else { display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint); @@ -2455,10 +2452,10 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - + display->drawStringMaxWidth(x + ((SCREEN_WIDTH > 128) ? 6 : 2), y, nameMaxWidth, nodeName); - if (node->is_favorite){ - if(SCREEN_WIDTH > 128){ + if (node->is_favorite) { + if (SCREEN_WIDTH > 128) { drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display); } else { display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint); @@ -2550,8 +2547,8 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); display->drawStringMaxWidth(x + ((SCREEN_WIDTH > 128) ? 6 : 2), y, nameMaxWidth, nodeName); - if (node->is_favorite){ - if(SCREEN_WIDTH > 128){ + if (node->is_favorite) { + if (SCREEN_WIDTH > 128) { drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display); } else { display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint); @@ -2681,8 +2678,8 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); display->drawStringMaxWidth(x + ((SCREEN_WIDTH > 128) ? 6 : 2), y, nameMaxWidth, nodeName); - if (node->is_favorite){ - if(SCREEN_WIDTH > 128){ + if (node->is_favorite) { + if (SCREEN_WIDTH > 128) { drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display); } else { display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint); @@ -2784,13 +2781,14 @@ static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, i rows = 5; } - // === First Row: Region / Channel Utilization and Uptime === bool origBold = config.display.heading_bold; config.display.heading_bold = false; // Display Region and Channel Utilization - drawNodes(display, x + 1, ((rows == 4) ? compactFirstLine : ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine)) + 2, nodeStatus, -1, false, "online"); + drawNodes(display, x + 1, + ((rows == 4) ? compactFirstLine : ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine)) + 2, nodeStatus, + -1, false, "online"); uint32_t uptime = millis() / 1000; char uptimeStr[6]; @@ -2812,7 +2810,9 @@ static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, i char uptimeFullStr[16]; snprintf(uptimeFullStr, sizeof(uptimeFullStr), "Uptime: %s", uptimeStr); - display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeFullStr), ((rows == 4) ? compactFirstLine : ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine)), uptimeFullStr); + display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeFullStr), + ((rows == 4) ? compactFirstLine : ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine)), + uptimeFullStr); config.display.heading_bold = origBold; @@ -2827,9 +2827,13 @@ static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, i } else { displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off"; } - display->drawString(0, ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), displayLine); + display->drawString( + 0, ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), + displayLine); } else { - drawGPS(display, 0, ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)) + 3, gpsStatus); + drawGPS(display, 0, + ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)) + 3, + gpsStatus); } #endif @@ -2838,16 +2842,22 @@ static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, i int batV = powerStatus->getBatteryVoltageMv() / 1000; int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10; snprintf(batStr, sizeof(batStr), "%01d.%02dV", batV, batCv); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(batStr), ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), batStr); + display->drawString( + x + SCREEN_WIDTH - display->getStringWidth(batStr), + ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), batStr); } else { - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("USB"), ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), String("USB")); + display->drawString( + x + SCREEN_WIDTH - display->getStringWidth("USB"), + ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), + String("USB")); } config.display.heading_bold = origBold; // === Third Row: Bluetooth Off (Only If Actually Off) === if (!config.bluetooth.enabled) { - display->drawString(0, ((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactThirdLine : moreCompactThirdLine)), "BT off"); + display->drawString( + 0, ((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactThirdLine : moreCompactThirdLine)), "BT off"); } // === Third & Fourth Rows: Node Identity === @@ -2867,27 +2877,35 @@ static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, i char combinedName[50]; snprintf(combinedName, sizeof(combinedName), "%s (%s)", longName, shortnameble); - if(SCREEN_WIDTH - (display->getStringWidth(longName) + display->getStringWidth(shortnameble)) > 10){ + if (SCREEN_WIDTH - (display->getStringWidth(longName) + display->getStringWidth(shortnameble)) > 10) { size_t len = strlen(combinedName); if (len >= 3 && strcmp(combinedName + len - 3, " ()") == 0) { - combinedName[len - 3] = '\0'; // Remove the last three characters + combinedName[len - 3] = '\0'; // Remove the last three characters } textWidth = display->getStringWidth(combinedName); nameX = (SCREEN_WIDTH - textWidth) / 2; - display->drawString(nameX, ((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactFourthLine : moreCompactFourthLine)) + yOffset, combinedName); + display->drawString( + nameX, + ((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactFourthLine : moreCompactFourthLine)) + yOffset, + combinedName); } else { textWidth = display->getStringWidth(longName); nameX = (SCREEN_WIDTH - textWidth) / 2; yOffset = (strcmp(shortnameble, "") == 0) ? 1 : 0; - if(yOffset == 1){ + if (yOffset == 1) { yOffset = (SCREEN_WIDTH > 128) ? 0 : 7; } - display->drawString(nameX, ((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactFourthLine : moreCompactFourthLine)) + yOffset, longName); + display->drawString( + nameX, + ((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactFourthLine : moreCompactFourthLine)) + yOffset, + longName); // === Fourth Row: ShortName Centered === textWidth = display->getStringWidth(shortnameble); nameX = (SCREEN_WIDTH - textWidth) / 2; - display->drawString(nameX, ((rows == 4) ? compactFourthLine : ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine)), shortnameble); + display->drawString(nameX, + ((rows == 4) ? compactFourthLine : ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine)), + shortnameble); } } @@ -2947,14 +2965,14 @@ static void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int char freqStr[16]; float freq = RadioLibInterface::instance->getFreq(); snprintf(freqStr, sizeof(freqStr), "%.3f", freq); - if(config.lora.channel_num == 0){ + if (config.lora.channel_num == 0) { snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %s", freqStr); } else { snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Chan: %s (%d)", freqStr, config.lora.channel_num); } size_t len = strlen(frequencyslot); if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) { - frequencyslot[len - 4] = '\0'; // Remove the last three characters + frequencyslot[len - 4] = '\0'; // Remove the last three characters } textWidth = display->getStringWidth(frequencyslot); nameX = (SCREEN_WIDTH - textWidth) / 2; @@ -3061,9 +3079,11 @@ static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiStat } else { displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off"; } - display->drawString(display->getStringWidth(Satelite_String) + 3, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), displayLine); + display->drawString(display->getStringWidth(Satelite_String) + 3, + ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), displayLine); } else { - drawGPS(display, display->getStringWidth(Satelite_String) + 3, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine) + 3, gpsStatus); + drawGPS(display, display->getStringWidth(Satelite_String) + 3, + ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine) + 3, gpsStatus); } config.display.heading_bold = origBold; @@ -3107,7 +3127,7 @@ static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiStat // === Fifth Row: Date === uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); char datetimeStr[25]; - bool showTime = false; // set to true for full datetime + bool showTime = false; // set to true for full datetime formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime); char fullLine[40]; snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr); @@ -3160,11 +3180,14 @@ static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiStat SCREEN_HEIGHT - yBelowContent - margin; #endif - if (availableHeight < FONT_HEIGHT_SMALL * 2) return; + if (availableHeight < FONT_HEIGHT_SMALL * 2) + return; int compassRadius = availableHeight / 2; - if (compassRadius < 8) compassRadius = 8; - if (compassRadius * 2 > SCREEN_WIDTH - 16) compassRadius = (SCREEN_WIDTH - 16) / 2; + if (compassRadius < 8) + compassRadius = 8; + if (compassRadius * 2 > SCREEN_WIDTH - 16) + compassRadius = (SCREEN_WIDTH - 16) / 2; int compassX = x + SCREEN_WIDTH / 2; int compassY = yBelowContent + availableHeight / 2; @@ -3532,7 +3555,8 @@ void NavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state) const int bigOffset = useBigIcons ? 1 : 0; const size_t totalIcons = screen->indicatorIcons.size(); - if (totalIcons == 0) return; + if (totalIcons == 0) + return; const size_t iconsPerPage = (SCREEN_WIDTH + spacing) / (iconSize + spacing); const size_t currentPage = currentFrame / iconsPerPage; @@ -3628,7 +3652,7 @@ void Screen::setup() // === Set custom overlay callbacks === static OverlayCallback overlays[] = { drawFunctionOverlay, // For mute/buzzer modifiers etc. - NavigationBar // Custom indicator icons for each frame + NavigationBar // Custom indicator icons for each frame }; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index b92cdbc91..e348e7571 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -275,7 +275,7 @@ class Screen : public concurrency::OSThread } void showOverlayBanner(const String &message, uint32_t durationMs = 3000); - + void startFirmwareUpdateScreen() { ScreenCmd cmd; diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h index 8d85ff764..1a8f5fb23 100644 --- a/src/graphics/SharedUIDisplay.h +++ b/src/graphics/SharedUIDisplay.h @@ -2,7 +2,8 @@ #include -namespace graphics { +namespace graphics +{ // ======================= // Shared UI Helpers @@ -22,11 +23,11 @@ namespace graphics { #define standardFourthLine (FONT_HEIGHT_SMALL + 1) * 4 // More Compact line layout -#define moreCompactFirstLine compactFirstLine -#define moreCompactSecondLine (moreCompactFirstLine + (FONT_HEIGHT_SMALL - 5)) -#define moreCompactThirdLine (moreCompactSecondLine + (FONT_HEIGHT_SMALL - 5)) -#define moreCompactFourthLine (moreCompactThirdLine + (FONT_HEIGHT_SMALL - 5)) -#define moreCompactFifthLine (moreCompactFourthLine + (FONT_HEIGHT_SMALL - 5)) +#define moreCompactFirstLine compactFirstLine +#define moreCompactSecondLine (moreCompactFirstLine + (FONT_HEIGHT_SMALL - 5)) +#define moreCompactThirdLine (moreCompactSecondLine + (FONT_HEIGHT_SMALL - 5)) +#define moreCompactFourthLine (moreCompactThirdLine + (FONT_HEIGHT_SMALL - 5)) +#define moreCompactFifthLine (moreCompactFourthLine + (FONT_HEIGHT_SMALL - 5)) // Quick screen access #define SCREEN_WIDTH display->getWidth() diff --git a/src/graphics/emotes.cpp b/src/graphics/emotes.cpp index 38a9f2915..205d5c660 100644 --- a/src/graphics/emotes.cpp +++ b/src/graphics/emotes.cpp @@ -1,56 +1,57 @@ #include "emotes.h" -namespace graphics { +namespace graphics +{ // Always define Emote list and count const Emote emotes[] = { #ifndef EXCLUDE_EMOJI // --- Thumbs --- - {"\U0001F44D", thumbup, thumbs_width, thumbs_height}, // ๐Ÿ‘ Thumbs Up - {"\U0001F44E", thumbdown, thumbs_width, thumbs_height}, // ๐Ÿ‘Ž Thumbs Down + {"\U0001F44D", thumbup, thumbs_width, thumbs_height}, // ๐Ÿ‘ Thumbs Up + {"\U0001F44E", thumbdown, thumbs_width, thumbs_height}, // ๐Ÿ‘Ž Thumbs Down // --- Smileys (Multiple Unicode Aliases) --- - {"\U0001F60A", smiley, smiley_width, smiley_height}, // ๐Ÿ˜Š Smiling Face with Smiling Eyes - {"\U0001F600", smiley, smiley_width, smiley_height}, // ๐Ÿ˜€ Grinning Face - {"\U0001F642", smiley, smiley_width, smiley_height}, // ๐Ÿ™‚ Slightly Smiling Face - {"\U0001F609", smiley, smiley_width, smiley_height}, // ๐Ÿ˜‰ Winking Face - {"\U0001F601", smiley, smiley_width, smiley_height}, // ๐Ÿ˜ Grinning Face with Smiling Eyes + {"\U0001F60A", smiley, smiley_width, smiley_height}, // ๐Ÿ˜Š Smiling Face with Smiling Eyes + {"\U0001F600", smiley, smiley_width, smiley_height}, // ๐Ÿ˜€ Grinning Face + {"\U0001F642", smiley, smiley_width, smiley_height}, // ๐Ÿ™‚ Slightly Smiling Face + {"\U0001F609", smiley, smiley_width, smiley_height}, // ๐Ÿ˜‰ Winking Face + {"\U0001F601", smiley, smiley_width, smiley_height}, // ๐Ÿ˜ Grinning Face with Smiling Eyes // --- Question/Alert --- - {"\u2753", question, question_width, question_height}, // โ“ Question Mark - {"\u203C\uFE0F", bang, bang_width, bang_height}, // โ€ผ๏ธ Double Exclamation Mark + {"\u2753", question, question_width, question_height}, // โ“ Question Mark + {"\u203C\uFE0F", bang, bang_width, bang_height}, // โ€ผ๏ธ Double Exclamation Mark // --- Laughing Faces --- - {"\U0001F602", haha, haha_width, haha_height}, // ๐Ÿ˜‚ Face with Tears of Joy - {"\U0001F923", haha, haha_width, haha_height}, // ๐Ÿคฃ Rolling on the Floor Laughing - {"\U0001F606", haha, haha_width, haha_height}, // ๐Ÿ˜† Smiling with Open Mouth and Closed Eyes - {"\U0001F605", haha, haha_width, haha_height}, // ๐Ÿ˜… Smiling with Sweat - {"\U0001F604", haha, haha_width, haha_height}, // ๐Ÿ˜„ Grinning Face with Smiling Eyes + {"\U0001F602", haha, haha_width, haha_height}, // ๐Ÿ˜‚ Face with Tears of Joy + {"\U0001F923", haha, haha_width, haha_height}, // ๐Ÿคฃ Rolling on the Floor Laughing + {"\U0001F606", haha, haha_width, haha_height}, // ๐Ÿ˜† Smiling with Open Mouth and Closed Eyes + {"\U0001F605", haha, haha_width, haha_height}, // ๐Ÿ˜… Smiling with Sweat + {"\U0001F604", haha, haha_width, haha_height}, // ๐Ÿ˜„ Grinning Face with Smiling Eyes // --- Gestures and People --- - {"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height},// ๐Ÿ‘‹ Waving Hand - {"\U0001F920", cowboy, cowboy_width, cowboy_height}, // ๐Ÿค  Cowboy Hat Face - {"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // ๐ŸŽง Headphones + {"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // ๐Ÿ‘‹ Waving Hand + {"\U0001F920", cowboy, cowboy_width, cowboy_height}, // ๐Ÿค  Cowboy Hat Face + {"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // ๐ŸŽง Headphones // --- Weather --- - {"\u2600", sun, sun_width, sun_height}, // โ˜€ Sun (without variation selector) - {"\u2600\uFE0F", sun, sun_width, sun_height}, // โ˜€๏ธ Sun (with variation selector) - {"\U0001F327\uFE0F", rain, rain_width, rain_height}, // ๐ŸŒง๏ธ Cloud with Rain - {"\u2601\uFE0F", cloud, cloud_width, cloud_height}, // โ˜๏ธ Cloud - {"\U0001F32B\uFE0F", fog, fog_width, fog_height}, // ๐ŸŒซ๏ธ Fog + {"\u2600", sun, sun_width, sun_height}, // โ˜€ Sun (without variation selector) + {"\u2600\uFE0F", sun, sun_width, sun_height}, // โ˜€๏ธ Sun (with variation selector) + {"\U0001F327\uFE0F", rain, rain_width, rain_height}, // ๐ŸŒง๏ธ Cloud with Rain + {"\u2601\uFE0F", cloud, cloud_width, cloud_height}, // โ˜๏ธ Cloud + {"\U0001F32B\uFE0F", fog, fog_width, fog_height}, // ๐ŸŒซ๏ธ Fog // --- Misc Faces --- - {"\U0001F608", devil, devil_width, devil_height}, // ๐Ÿ˜ˆ Smiling Face with Horns + {"\U0001F608", devil, devil_width, devil_height}, // ๐Ÿ˜ˆ Smiling Face with Horns // --- Hearts (Multiple Unicode Aliases) --- - {"\u2764\uFE0F", heart, heart_width, heart_height}, // โค๏ธ Red Heart - {"\U0001F9E1", heart, heart_width, heart_height}, // ๐Ÿงก Orange Heart - {"\U00002763", heart, heart_width, heart_height}, // โฃ Heart Exclamation - {"\U00002764", heart, heart_width, heart_height}, // โค Red Heart (legacy) - {"\U0001F495", heart, heart_width, heart_height}, // ๐Ÿ’• Two Hearts - {"\U0001F496", heart, heart_width, heart_height}, // ๐Ÿ’– Sparkling Heart - {"\U0001F497", heart, heart_width, heart_height}, // ๐Ÿ’— Growing Heart - {"\U0001F498", heart, heart_width, heart_height}, // ๐Ÿ’˜ Heart with Arrow + {"\u2764\uFE0F", heart, heart_width, heart_height}, // โค๏ธ Red Heart + {"\U0001F9E1", heart, heart_width, heart_height}, // ๐Ÿงก Orange Heart + {"\U00002763", heart, heart_width, heart_height}, // โฃ Heart Exclamation + {"\U00002764", heart, heart_width, heart_height}, // โค Red Heart (legacy) + {"\U0001F495", heart, heart_width, heart_height}, // ๐Ÿ’• Two Hearts + {"\U0001F496", heart, heart_width, heart_height}, // ๐Ÿ’– Sparkling Heart + {"\U0001F497", heart, heart_width, heart_height}, // ๐Ÿ’— Growing Heart + {"\U0001F498", heart, heart_width, heart_height}, // ๐Ÿ’˜ Heart with Arrow // --- Objects --- {"\U0001F4A9", poo, poo_width, poo_height}, // ๐Ÿ’ฉ Pile of Poo @@ -83,8 +84,7 @@ const unsigned char smiley[] PROGMEM = { 0x01, 0x09, 0x12, 0x20, 0x01, 0x0f, 0x1e, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x81, 0x00, 0x20, 0x20, 0x82, 0x00, 0x20, 0x10, 0x02, 0x01, 0x10, 0x10, 0x04, 0x02, 0x08, 0x08, 0x04, 0xfc, 0x07, 0x08, 0x08, 0x00, 0x00, 0x04, - 0x10, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x01, 0x40, 0x00, 0xc0, 0x00, 0x80, 0x01, 0x30, 0x00, 0x00, 0xfe, 0x0f, 0x00 -}; + 0x10, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x01, 0x40, 0x00, 0xc0, 0x00, 0x80, 0x01, 0x30, 0x00, 0x00, 0xfe, 0x0f, 0x00}; const unsigned char question[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00, @@ -219,9 +219,7 @@ const unsigned char bell_icon[] PROGMEM = { 0b00011000, 0b00000000, 0b00000000, 0b00000110, 0b11110000, 0b11111111, 0b11111111, 0b00000011, 0b00000000, 0b00001100, 0b00001100, 0b00000000, 0b00000000, 0b00011000, 0b00000110, 0b00000000, 0b00000000, 0b11111000, 0b00000111, 0b00000000, 0b00000000, 0b11100000, 0b00000001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, - 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 -}; + 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000}; #endif } // namespace graphics - diff --git a/src/graphics/emotes.h b/src/graphics/emotes.h index 5204d870b..5640ac04a 100644 --- a/src/graphics/emotes.h +++ b/src/graphics/emotes.h @@ -1,7 +1,8 @@ #pragma once #include -namespace graphics { +namespace graphics +{ // === Emote List === struct Emote { diff --git a/src/graphics/images.h b/src/graphics/images.h index bd6e19a17..f11acc084 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -241,71 +241,38 @@ const uint8_t mute_symbol_big[] PROGMEM = {0b00000001, 0b00000000, 0b11000010, 0 const unsigned char bell_alert[] PROGMEM = {0b00011000, 0b00100100, 0b00100100, 0b01000010, 0b01000010, 0b01000010, 0b11111111, 0b00011000}; - #define key_symbol_width 8 #define key_symbol_height 8 -const uint8_t key_symbol[] PROGMEM = { - 0b00000000, - 0b00000000, - 0b00000110, - 0b11111001, - 0b10101001, - 0b10000110, - 0b00000000, - 0b00000000 -}; +const uint8_t key_symbol[] PROGMEM = {0b00000000, 0b00000000, 0b00000110, 0b11111001, + 0b10101001, 0b10000110, 0b00000000, 0b00000000}; #define placeholder_width 8 #define placeholder_height 8 -const uint8_t placeholder[] PROGMEM = { - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111 -}; +const uint8_t placeholder[] PROGMEM = {0b11111111, 0b11111111, 0b11111111, 0b11111111, + 0b11111111, 0b11111111, 0b11111111, 0b11111111}; #define icon_node_width 8 #define icon_node_height 8 static const uint8_t icon_node[] PROGMEM = { - 0x10, // # + 0x10, // # 0x10, // # โ† antenna 0x10, // # 0xFE, // ####### โ† device top - 0x82, // # # + 0x82, // # # 0xAA, // # # # # โ† body with pattern - 0x92, // # # # + 0x92, // # # # 0xFE // ####### โ† device base }; #define bluetoothdisabled_width 8 #define bluetoothdisabled_height 8 -const uint8_t bluetoothdisabled[] PROGMEM = { - 0b11101100, - 0b01010100, - 0b01001100, - 0b01010100, - 0b01001100, - 0b00000000, - 0b00000000, - 0b00000000 -}; +const uint8_t bluetoothdisabled[] PROGMEM = {0b11101100, 0b01010100, 0b01001100, 0b01010100, + 0b01001100, 0b00000000, 0b00000000, 0b00000000}; #define smallbulletpoint_width 8 #define smallbulletpoint_height 8 -const uint8_t smallbulletpoint[] PROGMEM = { - 0b00000011, - 0b00000011, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000 -}; +const uint8_t smallbulletpoint[] PROGMEM = {0b00000011, 0b00000011, 0b00000000, 0b00000000, + 0b00000000, 0b00000000, 0b00000000, 0b00000000}; #include "img/icon.xbm" static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning"); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a85853688..7cbf77c83 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1228,7 +1228,7 @@ void setup() LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; nodeDB->saveToDisk(SEGMENT_CONFIG); - + if (!rIf->reconfigure()) { LOG_WARN("Reconfigure failed, rebooting"); screen->startAlert("Rebooting..."); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 5df61386b..d22c648ca 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -299,7 +299,8 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta if (node != NULL) { node->is_favorite = true; saveChanges(SEGMENT_NODEDATABASE, false); - if (screen) screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens + if (screen) + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens } break; } @@ -309,7 +310,8 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta if (node != NULL) { node->is_favorite = false; saveChanges(SEGMENT_NODEDATABASE, false); - if (screen) screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens + if (screen) + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens } break; } @@ -1120,7 +1122,7 @@ void AdminModule::reboot(int32_t seconds) { LOG_INFO("Reboot in %d seconds", seconds); if (screen) - screen->showOverlayBanner("Rebooting...", 0); // stays on screen + screen->showOverlayBanner("Rebooting...", 0); // stays on screen rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000); } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 5f0bb5448..eb3758c30 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -11,12 +11,12 @@ #include "PowerFSM.h" // needed for button bypass #include "SPILock.h" #include "detect/ScanI2C.h" -#include "input/ScanAndSelect.h" -#include "mesh/generated/meshtastic/cannedmessages.pb.h" -#include "graphics/images.h" -#include "modules/AdminModule.h" #include "graphics/SharedUIDisplay.h" -#include "main.h" // for cardkb_found +#include "graphics/images.h" +#include "input/ScanAndSelect.h" +#include "main.h" // for cardkb_found +#include "mesh/generated/meshtastic/cannedmessages.pb.h" +#include "modules/AdminModule.h" #include "modules/ExternalNotificationModule.h" // for buzzer control #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" @@ -70,7 +70,8 @@ CannedMessageModule::CannedMessageModule() } } static bool returnToCannedList = false; -bool hasKeyForNode(const meshtastic_NodeInfoLite* node) { +bool hasKeyForNode(const meshtastic_NodeInfoLite *node) +{ return node && node->has_user && node->user.public_key.size > 0; } /** @@ -96,7 +97,7 @@ int CannedMessageModule::splitConfiguredMessages() strncpy(this->messageStore, canned_messages.c_str(), sizeof(this->messageStore)); // Temporary array to allow for insertion - const char* tempMessages[CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT + 3] = {0}; + const char *tempMessages[CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT + 3] = {0}; int tempCount = 0; // First message always starts at buffer start @@ -117,12 +118,14 @@ int CannedMessageModule::splitConfiguredMessages() // Insert "[Select Destination]" after Free Text if present, otherwise at the top #if defined(USE_VIRTUAL_KEYBOARD) // Insert at position 1 (after Free Text) - for (int j = tempCount; j > 1; j--) tempMessages[j] = tempMessages[j - 1]; + for (int j = tempCount; j > 1; j--) + tempMessages[j] = tempMessages[j - 1]; tempMessages[1] = "[Select Destination]"; tempCount++; #else // Insert at position 0 (top) - for (int j = tempCount; j > 0; j--) tempMessages[j] = tempMessages[j - 1]; + for (int j = tempCount; j > 0; j--) + tempMessages[j] = tempMessages[j - 1]; tempMessages[0] = "[Select Destination]"; tempCount++; #endif @@ -132,13 +135,14 @@ int CannedMessageModule::splitConfiguredMessages() // Copy to the member array for (int k = 0; k < tempCount; ++k) { - this->messages[k] = (char*)tempMessages[k]; + this->messages[k] = (char *)tempMessages[k]; } this->messagesCount = tempCount; return this->messagesCount; } -void CannedMessageModule::drawHeader(OLEDDisplay *display, int16_t x, int16_t y, char* buffer) { +void CannedMessageModule::drawHeader(OLEDDisplay *display, int16_t x, int16_t y, char *buffer) +{ if (display->getWidth() > 128) { if (this->dest == NODENUM_BROADCAST) { display->drawStringf(x, y, buffer, "To: Broadcast@%s", channels.getName(this->channel)); @@ -154,7 +158,8 @@ void CannedMessageModule::drawHeader(OLEDDisplay *display, int16_t x, int16_t y, } } -void CannedMessageModule::resetSearch() { +void CannedMessageModule::resetSearch() +{ LOG_INFO("Resetting search, restoring full destination list"); int previousDestIndex = destIndex; @@ -165,14 +170,16 @@ void CannedMessageModule::resetSearch() { // Adjust scrollIndex so previousDestIndex is still visible int totalEntries = activeChannelIndices.size() + filteredNodes.size(); this->visibleRows = (displayHeight - FONT_HEIGHT_SMALL * 2) / FONT_HEIGHT_SMALL; - if (this->visibleRows < 1) this->visibleRows = 1; + if (this->visibleRows < 1) + this->visibleRows = 1; int maxScrollIndex = std::max(0, totalEntries - visibleRows); scrollIndex = std::min(std::max(previousDestIndex - (visibleRows / 2), 0), maxScrollIndex); lastUpdateMillis = millis(); requestFocus(); } -void CannedMessageModule::updateFilteredNodes() { +void CannedMessageModule::updateFilteredNodes() +{ static size_t lastNumMeshNodes = 0; static String lastSearchQuery = ""; @@ -181,7 +188,8 @@ void CannedMessageModule::updateFilteredNodes() { lastNumMeshNodes = numMeshNodes; // Early exit if nothing changed - if (searchQuery == lastSearchQuery && !nodesChanged) return; + if (searchQuery == lastSearchQuery && !nodesChanged) + return; lastSearchQuery = searchQuery; needsUpdate = false; @@ -196,10 +204,11 @@ void CannedMessageModule::updateFilteredNodes() { this->filteredNodes.reserve(numMeshNodes); for (size_t i = 0; i < numMeshNodes; ++i) { - meshtastic_NodeInfoLite* node = nodeDB->getMeshNodeByIndex(i); - if (!node || node->num == myNodeNum) continue; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); + if (!node || node->num == myNodeNum) + continue; - const String& nodeName = node->user.long_name; + const String &nodeName = node->user.long_name; if (searchQuery.length() == 0) { this->filteredNodes.push_back({node, sinceLastSeen(node)}); @@ -226,13 +235,13 @@ void CannedMessageModule::updateFilteredNodes() { } // Sort by favorite, then last heard - std::sort(this->filteredNodes.begin(), this->filteredNodes.end(), [](const NodeEntry& a, const NodeEntry& b) { + std::sort(this->filteredNodes.begin(), this->filteredNodes.end(), [](const NodeEntry &a, const NodeEntry &b) { if (a.node->is_favorite != b.node->is_favorite) return a.node->is_favorite > b.node->is_favorite; return a.lastHeard < b.lastHeard; }); - scrollIndex = 0; // Show first result at the top - destIndex = 0; // Highlight the first entry + scrollIndex = 0; // Show first result at the top + destIndex = 0; // Highlight the first entry if (nodesChanged) { LOG_INFO("Nodes changed, forcing UI refresh."); screen->forceDisplay(); @@ -240,24 +249,28 @@ void CannedMessageModule::updateFilteredNodes() { } // Returns true if character input is currently allowed (used for search/freetext states) -bool CannedMessageModule::isCharInputAllowed() const { - return runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || - runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; +bool CannedMessageModule::isCharInputAllowed() const +{ + return runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; } /** * Main input event dispatcher for CannedMessageModule. * Routes keyboard/button/touch input to the correct handler based on the current runState. * Only one handler (per state) processes each event, eliminating redundancy. */ -int CannedMessageModule::handleInputEvent(const InputEvent* event) { +int CannedMessageModule::handleInputEvent(const InputEvent *event) +{ // Allow input only from configured source (hardware/software filter) - if (!isInputSourceAllowed(event)) return 0; + if (!isInputSourceAllowed(event)) + return 0; // Global/system commands always processed (brightness, BT, GPS, shutdown, etc.) - if (handleSystemCommandInput(event)) return 1; + if (handleSystemCommandInput(event)) + return 1; // Tab key: Always allow switching between canned/destination screens - if (event->kbchar == INPUT_BROKER_MSG_TAB && handleTabSwitch(event)) return 1; + if (event->kbchar == INPUT_BROKER_MSG_TAB && handleTabSwitch(event)) + return 1; // Matrix keypad: If matrix key, trigger action select for canned message if (event->inputEvent == static_cast(MATRIXKEY)) { @@ -276,106 +289,106 @@ int CannedMessageModule::handleInputEvent(const InputEvent* event) { // Route event to handler for current UI state (no double-handling) switch (runState) { - // Node/Channel destination selection mode: Handles character search, arrows, select, cancel, backspace - case CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION: - return handleDestinationSelectionInput(event, isUp, isDown, isSelect); // All allowed input for this state + // Node/Channel destination selection mode: Handles character search, arrows, select, cancel, backspace + case CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION: + return handleDestinationSelectionInput(event, isUp, isDown, isSelect); // All allowed input for this state - // Free text input mode: Handles character input, cancel, backspace, select, etc. - case CANNED_MESSAGE_RUN_STATE_FREETEXT: - return handleFreeTextInput(event); // All allowed input for this state + // Free text input mode: Handles character input, cancel, backspace, select, etc. + case CANNED_MESSAGE_RUN_STATE_FREETEXT: + return handleFreeTextInput(event); // All allowed input for this state - // If sending, block all input except global/system (handled above) - case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE: - return 1; + // If sending, block all input except global/system (handled above) + case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE: + return 1; - case CANNED_MESSAGE_RUN_STATE_INACTIVE: - if (isSelect) { - // When inactive, call the onebutton shortpress instead. Activate module only on up/down - powerFSM.trigger(EVENT_PRESS); - return 1; // Let caller know we handled it - } - // Let LEFT/RIGHT pass through so frame navigation works - if ( - event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT) || - event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT) - ) { - break; - } - // Handle UP/DOWN: activate canned message list! - if ( - event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP) || - event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN) - ) { - // Always select the first real canned message on activation - int firstRealMsgIdx = 0; - for (int i = 0; i < messagesCount; ++i) { - if (strcmp(messages[i], "[Select Destination]") != 0 && - strcmp(messages[i], "[Exit]") != 0 && - strcmp(messages[i], "[---- Free Text ----]") != 0) { - firstRealMsgIdx = i; - break; - } + case CANNED_MESSAGE_RUN_STATE_INACTIVE: + if (isSelect) { + // When inactive, call the onebutton shortpress instead. Activate module only on up/down + powerFSM.trigger(EVENT_PRESS); + return 1; // Let caller know we handled it + } + // Let LEFT/RIGHT pass through so frame navigation works + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT) || + event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { + break; + } + // Handle UP/DOWN: activate canned message list! + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP) || + event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN)) { + // Always select the first real canned message on activation + int firstRealMsgIdx = 0; + for (int i = 0; i < messagesCount; ++i) { + if (strcmp(messages[i], "[Select Destination]") != 0 && strcmp(messages[i], "[Exit]") != 0 && + strcmp(messages[i], "[---- Free Text ----]") != 0) { + firstRealMsgIdx = i; + break; } - currentMessageIndex = firstRealMsgIdx; - - // This triggers the canned message list - runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; - requestFocus(); - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - notifyObservers(&e); - return 1; } - // Printable char (ASCII) opens free text compose - if (event->kbchar >= 32 && event->kbchar <= 126) { - runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; - requestFocus(); - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - notifyObservers(&e); - // Immediately process the input in the new state (freetext) - return handleFreeTextInput(event); - } - break; + currentMessageIndex = firstRealMsgIdx; - // (Other states can be added here as needed) - default: - break; + // This triggers the canned message list + runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return 1; + } + // Printable char (ASCII) opens free text compose + if (event->kbchar >= 32 && event->kbchar <= 126) { + runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + // Immediately process the input in the new state (freetext) + return handleFreeTextInput(event); + } + break; + + // (Other states can be added here as needed) + default: + break; } // If no state handler above processed the event, let the message selector try to handle it // (Handles up/down/select on canned message list, exit/return) - if (handleMessageSelectorInput(event, isUp, isDown, isSelect)) return 1; + if (handleMessageSelectorInput(event, isUp, isDown, isSelect)) + return 1; // Default: event not handled by canned message system, allow others to process return 0; } -bool CannedMessageModule::isInputSourceAllowed(const InputEvent* event) { +bool CannedMessageModule::isInputSourceAllowed(const InputEvent *event) +{ return strlen(moduleConfig.canned_message.allow_input_source) == 0 || strcasecmp(moduleConfig.canned_message.allow_input_source, event->source) == 0 || strcasecmp(moduleConfig.canned_message.allow_input_source, "_any") == 0; } -bool CannedMessageModule::isUpEvent(const InputEvent* event) { +bool CannedMessageModule::isUpEvent(const InputEvent *event) +{ return event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP); } -bool CannedMessageModule::isDownEvent(const InputEvent* event) { +bool CannedMessageModule::isDownEvent(const InputEvent *event) +{ return event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN); } -bool CannedMessageModule::isSelectEvent(const InputEvent* event) { +bool CannedMessageModule::isSelectEvent(const InputEvent *event) +{ return event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT); } -bool CannedMessageModule::handleTabSwitch(const InputEvent* event) { - if (event->kbchar != 0x09) return false; +bool CannedMessageModule::handleTabSwitch(const InputEvent *event) +{ + if (event->kbchar != 0x09) + return false; - destSelect = (destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) - ? CANNED_MESSAGE_DESTINATION_TYPE_NONE - : CANNED_MESSAGE_DESTINATION_TYPE_NODE; - runState = (destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NONE) - ? CANNED_MESSAGE_RUN_STATE_FREETEXT - : CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; + destSelect = (destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) ? CANNED_MESSAGE_DESTINATION_TYPE_NONE + : CANNED_MESSAGE_DESTINATION_TYPE_NODE; + runState = (destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NONE) ? CANNED_MESSAGE_RUN_STATE_FREETEXT + : CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; destIndex = 0; scrollIndex = 0; @@ -390,11 +403,11 @@ bool CannedMessageModule::handleTabSwitch(const InputEvent* event) { return true; } -int CannedMessageModule::handleDestinationSelectionInput(const InputEvent* event, bool isUp, bool isDown, bool isSelect) { +int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event, bool isUp, bool isDown, bool isSelect) +{ static bool shouldRedraw = false; - if (event->kbchar >= 32 && event->kbchar <= 126 && - !isUp && !isDown && + if (event->kbchar >= 32 && event->kbchar <= 126 && !isUp && !isDown && event->inputEvent != static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT) && event->inputEvent != static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT) && event->inputEvent != static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) { @@ -458,7 +471,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent* event } else { int nodeIndex = destIndex - static_cast(activeChannelIndices.size()); if (nodeIndex >= 0 && nodeIndex < static_cast(filteredNodes.size())) { - meshtastic_NodeInfoLite* selectedNode = filteredNodes[nodeIndex].node; + meshtastic_NodeInfoLite *selectedNode = filteredNodes[nodeIndex].node; if (selectedNode) { dest = selectedNode->num; channel = selectedNode->channel; @@ -489,8 +502,10 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent* event return 0; } -bool CannedMessageModule::handleMessageSelectorInput(const InputEvent* event, bool isUp, bool isDown, bool isSelect) { - if (destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) return false; +bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bool isUp, bool isDown, bool isSelect) +{ + if (destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) + return false; // === Handle Cancel key: go inactive, clear UI state === if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { @@ -518,7 +533,7 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent* event, bo runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN; handled = true; } else if (isSelect) { - const char* current = messages[currentMessageIndex]; + const char *current = messages[currentMessageIndex]; // === [Select Destination] triggers destination selection UI === if (strcmp(current, "[Select Destination]") == 0) { @@ -579,9 +594,11 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent* event, bo return handled; } -bool CannedMessageModule::handleFreeTextInput(const InputEvent* event) { +bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) +{ // Always process only if in FREETEXT mode - if (runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) return false; + if (runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) + return false; #if defined(USE_VIRTUAL_KEYBOARD) // Touch input (virtual keyboard) handling @@ -596,9 +613,9 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent* event) { shift = !shift; valid = true; } else if (keyTapped == "โŒซ") { - #ifndef RAK14014 +#ifndef RAK14014 highlight = keyTapped[0]; - #endif +#endif payload = 0x08; shift = false; valid = true; @@ -608,13 +625,13 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent* event) { charSet = (charSet == 0 ? 1 : 0); valid = true; } else if (keyTapped == " ") { - #ifndef RAK14014 +#ifndef RAK14014 highlight = keyTapped[0]; - #endif +#endif payload = keyTapped[0]; shift = false; valid = true; - } + } // Touch enter/submit else if (keyTapped == "โ†ต") { runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; // Send the message! @@ -623,9 +640,9 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent* event) { shift = false; valid = true; } else if (!keyTapped.isEmpty()) { - #ifndef RAK14014 +#ifndef RAK14014 highlight = keyTapped[0]; - #endif +#endif payload = shift ? keyTapped[0] : std::tolower(keyTapped[0]); shift = false; valid = true; @@ -643,11 +660,13 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent* event) { // Confirm select (Enter) bool isSelect = isSelectEvent(event); if (isSelect) { - LOG_DEBUG("[SELECT] handleFreeTextInput: runState=%d, dest=%u, channel=%d, freetext='%s'", - (int)runState, dest, channel, freetext.c_str()); - if (dest == 0) dest = NODENUM_BROADCAST; + LOG_DEBUG("[SELECT] handleFreeTextInput: runState=%d, dest=%u, channel=%d, freetext='%s'", (int)runState, dest, channel, + freetext.c_str()); + if (dest == 0) + dest = NODENUM_BROADCAST; // Defensive: If channel isn't valid, pick the first available channel - if (channel >= channels.getNumChannels()) channel = 0; + if (channel >= channels.getNumChannels()) + channel = 0; payload = CANNED_MESSAGE_RUN_STATE_FREETEXT; currentMessageIndex = -1; @@ -706,9 +725,11 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent* event) { return false; } -bool CannedMessageModule::handleSystemCommandInput(const InputEvent* event) { +bool CannedMessageModule::handleSystemCommandInput(const InputEvent *event) +{ // Only respond to "ANYKEY" events for system keys - if (event->inputEvent != static_cast(ANYKEY)) return false; + if (event->inputEvent != static_cast(ANYKEY)) + return false; // Block ALL input if an alert banner is active extern String alertBannerMessage; @@ -719,101 +740,114 @@ bool CannedMessageModule::handleSystemCommandInput(const InputEvent* event) { // System commands (all others fall through to return false) switch (event->kbchar) { - // Fn key symbols - case INPUT_BROKER_MSG_FN_SYMBOL_ON: - if (screen) screen->setFunctionSymbol("Fn"); - return true; - case INPUT_BROKER_MSG_FN_SYMBOL_OFF: - if (screen) screen->removeFunctionSymbol("Fn"); - return true; - // Brightness - case INPUT_BROKER_MSG_BRIGHTNESS_UP: - if (screen) screen->increaseBrightness(); - LOG_DEBUG("Increase Screen Brightness"); - return true; - case INPUT_BROKER_MSG_BRIGHTNESS_DOWN: - if (screen) screen->decreaseBrightness(); - LOG_DEBUG("Decrease Screen Brightness"); - return true; - // Mute - case INPUT_BROKER_MSG_MUTE_TOGGLE: - if (moduleConfig.external_notification.enabled && externalNotificationModule) { - bool isMuted = externalNotificationModule->getMute(); - externalNotificationModule->setMute(!isMuted); - graphics::isMuted = !isMuted; - if (!isMuted) - externalNotificationModule->stopNow(); - if (screen) - screen->showOverlayBanner(isMuted ? "Notifications\nEnabled" : "Notifications\nDisabled", 3000); - } - return true; - // Bluetooth - case INPUT_BROKER_MSG_BLUETOOTH_TOGGLE: - config.bluetooth.enabled = !config.bluetooth.enabled; - LOG_INFO("User toggled Bluetooth"); - nodeDB->saveToDisk(); - #if defined(ARDUINO_ARCH_NRF52) - if (!config.bluetooth.enabled) { - disableBluetooth(); - if (screen) screen->showOverlayBanner("Bluetooth OFF\nRebooting", 3000); - rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 2000; - } else { - if (screen) screen->showOverlayBanner("Bluetooth ON\nRebooting", 3000); - rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; - } - #else - if (!config.bluetooth.enabled) { - disableBluetooth(); - if (screen) screen->showOverlayBanner("Bluetooth OFF", 3000); - } else { - if (screen) screen->showOverlayBanner("Bluetooth ON\nRebooting", 3000); - rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; - } - #endif - return true; - // GPS - case INPUT_BROKER_MSG_GPS_TOGGLE: - #if !MESHTASTIC_EXCLUDE_GPS - if (gps) { - gps->toggleGpsMode(); - const char* msg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) - ? "GPS Enabled" : "GPS Disabled"; - if (screen) { - screen->forceDisplay(); - screen->showOverlayBanner(msg, 3000); - } - } - #endif - return true; - // Mesh ping - case INPUT_BROKER_MSG_SEND_PING: - service->refreshLocalMeshNode(); - if (service->trySendPosition(NODENUM_BROADCAST, true)) { - if (screen) screen->showOverlayBanner("Position\nUpdate Sent", 3000); - } else { - if (screen) screen->showOverlayBanner("Node Info\nUpdate Sent", 3000); - } - return true; - // Power control - case INPUT_BROKER_MSG_SHUTDOWN: - if (screen) screen->showOverlayBanner("Shutting down..."); - nodeDB->saveToDisk(); - shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - return true; - case INPUT_BROKER_MSG_REBOOT: - if (screen) screen->showOverlayBanner("Rebooting...", 0); - nodeDB->saveToDisk(); + // Fn key symbols + case INPUT_BROKER_MSG_FN_SYMBOL_ON: + if (screen) + screen->setFunctionSymbol("Fn"); + return true; + case INPUT_BROKER_MSG_FN_SYMBOL_OFF: + if (screen) + screen->removeFunctionSymbol("Fn"); + return true; + // Brightness + case INPUT_BROKER_MSG_BRIGHTNESS_UP: + if (screen) + screen->increaseBrightness(); + LOG_DEBUG("Increase Screen Brightness"); + return true; + case INPUT_BROKER_MSG_BRIGHTNESS_DOWN: + if (screen) + screen->decreaseBrightness(); + LOG_DEBUG("Decrease Screen Brightness"); + return true; + // Mute + case INPUT_BROKER_MSG_MUTE_TOGGLE: + if (moduleConfig.external_notification.enabled && externalNotificationModule) { + bool isMuted = externalNotificationModule->getMute(); + externalNotificationModule->setMute(!isMuted); + graphics::isMuted = !isMuted; + if (!isMuted) + externalNotificationModule->stopNow(); + if (screen) + screen->showOverlayBanner(isMuted ? "Notifications\nEnabled" : "Notifications\nDisabled", 3000); + } + return true; + // Bluetooth + case INPUT_BROKER_MSG_BLUETOOTH_TOGGLE: + config.bluetooth.enabled = !config.bluetooth.enabled; + LOG_INFO("User toggled Bluetooth"); + nodeDB->saveToDisk(); +#if defined(ARDUINO_ARCH_NRF52) + if (!config.bluetooth.enabled) { + disableBluetooth(); + if (screen) + screen->showOverlayBanner("Bluetooth OFF\nRebooting", 3000); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 2000; + } else { + if (screen) + screen->showOverlayBanner("Bluetooth ON\nRebooting", 3000); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - return true; - case INPUT_BROKER_MSG_DISMISS_FRAME: - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - if (screen) screen->dismissCurrentFrame(); - return true; - // Not a system command, let other handlers process it - default: - return false; + } +#else + if (!config.bluetooth.enabled) { + disableBluetooth(); + if (screen) + screen->showOverlayBanner("Bluetooth OFF", 3000); + } else { + if (screen) + screen->showOverlayBanner("Bluetooth ON\nRebooting", 3000); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + } +#endif + return true; + // GPS + case INPUT_BROKER_MSG_GPS_TOGGLE: +#if !MESHTASTIC_EXCLUDE_GPS + if (gps) { + gps->toggleGpsMode(); + const char *msg = + (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) ? "GPS Enabled" : "GPS Disabled"; + if (screen) { + screen->forceDisplay(); + screen->showOverlayBanner(msg, 3000); + } + } +#endif + return true; + // Mesh ping + case INPUT_BROKER_MSG_SEND_PING: + service->refreshLocalMeshNode(); + if (service->trySendPosition(NODENUM_BROADCAST, true)) { + if (screen) + screen->showOverlayBanner("Position\nUpdate Sent", 3000); + } else { + if (screen) + screen->showOverlayBanner("Node Info\nUpdate Sent", 3000); + } + return true; + // Power control + case INPUT_BROKER_MSG_SHUTDOWN: + if (screen) + screen->showOverlayBanner("Shutting down..."); + nodeDB->saveToDisk(); + shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + return true; + case INPUT_BROKER_MSG_REBOOT: + if (screen) + screen->showOverlayBanner("Rebooting...", 0); + nodeDB->saveToDisk(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + return true; + case INPUT_BROKER_MSG_DISMISS_FRAME: + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + if (screen) + screen->dismissCurrentFrame(); + return true; + // Not a system command, let other handlers process it + default: + return false; } } @@ -834,9 +868,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); // Optionally add bell character - if (moduleConfig.canned_message.send_bell && - p->decoded.payload.size < meshtastic_Constants_DATA_PAYLOAD_LEN) - { + if (moduleConfig.canned_message.send_bell && p->decoded.payload.size < meshtastic_Constants_DATA_PAYLOAD_LEN) { p->decoded.payload.bytes[p->decoded.payload.size++] = 7; // Bell p->decoded.payload.bytes[p->decoded.payload.size] = '\0'; // Null-terminate } @@ -845,8 +877,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha this->waitingForAck = true; // Log outgoing message - LOG_INFO("Send message id=%d, dest=%x, msg=%.*s", - p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); + LOG_INFO("Send message id=%d, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); // Send to mesh and phone (even if no phone connected, to track ACKs) service->sendToMesh(p, RX_SRC_LOCAL, true); @@ -862,8 +893,8 @@ bool validEvent = false; unsigned long lastUpdateMillis = 0; int32_t CannedMessageModule::runOnce() { - #define NODE_UPDATE_IDLE_MS 100 - #define NODE_UPDATE_ACTIVE_MS 80 +#define NODE_UPDATE_IDLE_MS 100 +#define NODE_UPDATE_ACTIVE_MS 80 unsigned long updateThreshold = (searchQuery.length() > 0) ? NODE_UPDATE_ACTIVE_MS : NODE_UPDATE_IDLE_MS; if (needsUpdate && millis() - lastUpdateMillis > updateThreshold) { @@ -882,7 +913,8 @@ int32_t CannedMessageModule::runOnce() } UIFrameEvent e; if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || - (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) { + (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || + (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) { this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; temporaryMessage = ""; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; @@ -952,8 +984,7 @@ int32_t CannedMessageModule::runOnce() // Find first actual canned message (not a special action entry) int firstRealMsgIdx = 0; for (int i = 0; i < this->messagesCount; ++i) { - if (strcmp(this->messages[i], "[Select Destination]") != 0 && - strcmp(this->messages[i], "[Exit]") != 0 && + if (strcmp(this->messages[i], "[Select Destination]") != 0 && strcmp(this->messages[i], "[Exit]") != 0 && strcmp(this->messages[i], "[---- Free Text ----]") != 0) { firstRealMsgIdx = i; break; @@ -1021,24 +1052,23 @@ int32_t CannedMessageModule::runOnce() } break; case INPUT_BROKER_MSG_TAB: // Tab key (Switch to Destination Selection Mode) - { - if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NONE) { - // Enter selection screen - this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; - this->destIndex = 0; // Reset to first node/channel - this->scrollIndex = 0; // Reset scrolling - this->runState = CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; - - // Ensure UI updates correctly - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - this->notifyObservers(&e); - } - - // If already inside the selection screen, do nothing (prevent exiting) - return 0; + { + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NONE) { + // Enter selection screen + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; + this->destIndex = 0; // Reset to first node/channel + this->scrollIndex = 0; // Reset scrolling + this->runState = CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; + + // Ensure UI updates correctly + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + this->notifyObservers(&e); } - break; + + // If already inside the selection screen, do nothing (prevent exiting) + return 0; + } break; case INPUT_BROKER_MSG_LEFT: case INPUT_BROKER_MSG_RIGHT: // already handled above @@ -1102,7 +1132,8 @@ const char *CannedMessageModule::getMessageByIndex(int index) const char *CannedMessageModule::getNodeName(NodeNum node) { - if (node == NODENUM_BROADCAST) return "Broadcast"; + if (node == NODENUM_BROADCAST) + return "Broadcast"; meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node); if (info && info->has_user && strlen(info->user.long_name) > 0) { @@ -1378,7 +1409,7 @@ bool CannedMessageModule::interceptingKeyboardInput() void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { - this->displayHeight = display->getHeight(); // Store display height for later use + this->displayHeight = display->getHeight(); // Store display height for later use char buffer[50]; display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); @@ -1394,7 +1425,8 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } // === Destination Selection === - if (this->runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION || this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { + if (this->runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION || + this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { requestFocus(); display->setColor(WHITE); // Always draw cleanly display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -1414,15 +1446,19 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int totalEntries = numActiveChannels + this->filteredNodes.size(); int columns = 1; this->visibleRows = (display->getHeight() - (titleY + FONT_HEIGHT_SMALL)) / (FONT_HEIGHT_SMALL - 4); - if (this->visibleRows < 1) this->visibleRows = 1; + if (this->visibleRows < 1) + this->visibleRows = 1; // === Clamp scrolling === - if (scrollIndex > totalEntries / columns) scrollIndex = totalEntries / columns; - if (scrollIndex < 0) scrollIndex = 0; + if (scrollIndex > totalEntries / columns) + scrollIndex = totalEntries / columns; + if (scrollIndex < 0) + scrollIndex = 0; for (int row = 0; row < visibleRows; row++) { int itemIndex = scrollIndex + row; - if (itemIndex >= totalEntries) break; + if (itemIndex >= totalEntries) + break; int xOffset = 0; int yOffset = row * (FONT_HEIGHT_SMALL - 4) + rowYOffset; @@ -1444,7 +1480,8 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } } - if (entryText.length() == 0 || entryText == "Unknown") entryText = "?"; + if (entryText.length() == 0 || entryText == "Unknown") + entryText = "?"; // === Highlight background (if selected) === if (itemIndex == destIndex) { @@ -1496,22 +1533,21 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st EINK_ADD_FRAMEFLAG(display, COSMETIC); display->setTextAlignment(TEXT_ALIGN_CENTER); - #ifdef USE_EINK +#ifdef USE_EINK display->setFont(FONT_SMALL); int yOffset = y + 10; - #else +#else display->setFont(FONT_MEDIUM); int yOffset = y + 10; - #endif +#endif // --- Delivery Status Message --- if (this->ack) { if (this->lastSentNode == NODENUM_BROADCAST) { snprintf(buffer, sizeof(buffer), "Broadcast Sent to\n%s", channels.getName(this->channel)); } else if (this->lastAckHopLimit > this->lastAckHopStart) { - snprintf(buffer, sizeof(buffer), "Delivered (%d hops)\nto %s", - this->lastAckHopLimit - this->lastAckHopStart, - getNodeName(this->incoming)); + snprintf(buffer, sizeof(buffer), "Delivered (%d hops)\nto %s", this->lastAckHopLimit - this->lastAckHopStart, + getNodeName(this->incoming)); } else { snprintf(buffer, sizeof(buffer), "Delivered\nto %s", getNodeName(this->incoming)); } @@ -1522,20 +1558,21 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st // Draw delivery message and compute y-offset after text height int lineCount = 1; for (const char *ptr = buffer; *ptr; ptr++) { - if (*ptr == '\n') lineCount++; + if (*ptr == '\n') + lineCount++; } display->drawString(display->getWidth() / 2 + x, yOffset, buffer); yOffset += lineCount * FONT_HEIGHT_MEDIUM; // only 1 line gap, no extra padding - #ifndef USE_EINK +#ifndef USE_EINK // --- SNR + RSSI Compact Line --- if (this->ack) { display->setFont(FONT_SMALL); snprintf(buffer, sizeof(buffer), "SNR: %.1f dB RSSI: %d", this->lastRxSnr, this->lastRxRssi); display->drawString(display->getWidth() / 2 + x, yOffset, buffer); } - #endif +#endif return; } @@ -1566,7 +1603,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { requestFocus(); #if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) - EInkDynamicDisplay* einkDisplay = static_cast(display); + EInkDynamicDisplay *einkDisplay = static_cast(display); einkDisplay->enableUnlimitedFastMode(); #endif #if defined(USE_VIRTUAL_KEYBOARD) @@ -1575,25 +1612,26 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - // --- Draw node/channel header at the top --- - drawHeader(display, x, y, buffer); + // --- Draw node/channel header at the top --- + drawHeader(display, x, y, buffer); // --- Char count right-aligned --- if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NONE) { - uint16_t charsLeft = meshtastic_Constants_DATA_PAYLOAD_LEN - this->freetext.length() - (moduleConfig.canned_message.send_bell ? 1 : 0); + uint16_t charsLeft = + meshtastic_Constants_DATA_PAYLOAD_LEN - this->freetext.length() - (moduleConfig.canned_message.send_bell ? 1 : 0); snprintf(buffer, sizeof(buffer), "%d left", charsLeft); display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); } - // --- Draw Free Text input, shifted down --- + // --- Draw Free Text input, shifted down --- display->setColor(WHITE); display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), - drawWithCursor(this->freetext, this->cursor)); + drawWithCursor(this->freetext, this->cursor)); #endif return; } -// === Canned Messages List === + // === Canned Messages List === if (this->messagesCount > 0) { display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); @@ -1607,13 +1645,12 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st const int listYOffset = y + FONT_HEIGHT_SMALL - 3; const int visibleRows = (display->getHeight() - listYOffset) / rowSpacing; - int topMsg = (messagesCount > visibleRows && currentMessageIndex >= visibleRows - 1) - ? currentMessageIndex - visibleRows + 2 - : 0; + int topMsg = + (messagesCount > visibleRows && currentMessageIndex >= visibleRows - 1) ? currentMessageIndex - visibleRows + 2 : 0; for (int i = 0; i < std::min(messagesCount, visibleRows); i++) { int lineY = listYOffset + rowSpacing * i; - const char* msg = getMessageByIndex(topMsg + i); + const char *msg = getMessageByIndex(topMsg + i); if ((topMsg + i) == currentMessageIndex) { #ifdef USE_EINK @@ -1793,6 +1830,7 @@ String CannedMessageModule::drawWithCursor(String text, int cursor) #endif -bool CannedMessageModule::isInterceptingAndFocused() { +bool CannedMessageModule::isInterceptingAndFocused() +{ return this->interceptingKeyboardInput(); } \ No newline at end of file diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 6caf7c9cf..563e8b17c 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -57,10 +57,9 @@ struct NodeEntry { // Main Class // ============================ -class CannedMessageModule : public SinglePortModule, - public Observable, - private concurrency::OSThread { -public: +class CannedMessageModule : public SinglePortModule, public Observable, private concurrency::OSThread +{ + public: CannedMessageModule(); // === Message navigation === @@ -89,19 +88,22 @@ public: #endif // === Packet Interest Filter === - virtual bool wantPacket(const meshtastic_MeshPacket *p) override { - if (p->rx_rssi != 0) lastRxRssi = p->rx_rssi; - if (p->rx_snr > 0) lastRxSnr = p->rx_snr; + virtual bool wantPacket(const meshtastic_MeshPacket *p) override + { + if (p->rx_rssi != 0) + lastRxRssi = p->rx_rssi; + if (p->rx_snr > 0) + lastRxSnr = p->rx_snr; return (p->decoded.portnum == meshtastic_PortNum_ROUTING_APP) ? waitingForAck : false; } -protected: + protected: // === Thread Entry Point === virtual int32_t runOnce() override; // === Transmission === void sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies); - void drawHeader(OLEDDisplay *display, int16_t x, int16_t y, char* buffer); + void drawHeader(OLEDDisplay *display, int16_t x, int16_t y, char *buffer); int splitConfiguredMessages(); int getNextIndex(); int getPrevIndex(); @@ -130,7 +132,7 @@ protected: bool saveProtoForModule(); void installDefaultCannedMessageModuleConfig(); -private: + private: // === Input Observers === CallbackObserver inputObserver = CallbackObserver(this, &CannedMessageModule::handleInputEvent); @@ -155,19 +157,19 @@ private: int currentMessageIndex = -1; // === Routing & Acknowledgment === - NodeNum dest = NODENUM_BROADCAST; // Destination node for outgoing messages (default: broadcast) - NodeNum incoming = NODENUM_BROADCAST; // Source node from which last ACK/NACK was received - NodeNum lastSentNode = 0; // Tracks the most recent node we sent a message to (for UI display) - ChannelIndex channel = 0; // Channel index used when sending a message + NodeNum dest = NODENUM_BROADCAST; // Destination node for outgoing messages (default: broadcast) + NodeNum incoming = NODENUM_BROADCAST; // Source node from which last ACK/NACK was received + NodeNum lastSentNode = 0; // Tracks the most recent node we sent a message to (for UI display) + ChannelIndex channel = 0; // Channel index used when sending a message - bool ack = false; // True = ACK received, False = NACK or failed - bool waitingForAck = false; // True if we're expecting an ACK and should monitor routing packets - bool lastAckWasRelayed = false; // True if the ACK was relayed through intermediate nodes - uint8_t lastAckHopStart = 0; // Hop start value from the received ACK packet - uint8_t lastAckHopLimit = 0; // Hop limit value from the received ACK packet + bool ack = false; // True = ACK received, False = NACK or failed + bool waitingForAck = false; // True if we're expecting an ACK and should monitor routing packets + bool lastAckWasRelayed = false; // True if the ACK was relayed through intermediate nodes + uint8_t lastAckHopStart = 0; // Hop start value from the received ACK packet + uint8_t lastAckHopLimit = 0; // Hop limit value from the received ACK packet - float lastRxSnr = 0; // SNR from last received ACK (used for diagnostics/UI) - int32_t lastRxRssi = 0; // RSSI from last received ACK (used for diagnostics/UI) + float lastRxSnr = 0; // SNR from last received ACK (used for diagnostics/UI) + int32_t lastRxRssi = 0; // RSSI from last received ACK (used for diagnostics/UI) // === State Tracking === cannedMessageModuleRunState runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 74621015a..ea796fca6 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -11,16 +11,16 @@ #include "RTC.h" #include "Router.h" #include "UnitConversions.h" +#include "buzz.h" +#include "graphics/SharedUIDisplay.h" +#include "graphics/images.h" #include "main.h" +#include "modules/ExternalNotificationModule.h" #include "power.h" #include "sleep.h" #include "target_specific.h" #include #include -#include "graphics/SharedUIDisplay.h" -#include "graphics/images.h" -#include "buzz.h" -#include "modules/ExternalNotificationModule.h" #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL @@ -30,8 +30,9 @@ #include "Sensor/RCWL9620Sensor.h" #include "Sensor/nullSensor.h" -namespace graphics { - extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y); +namespace graphics +{ +extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y); } #if __has_include() #include "Sensor/AHT10.h" @@ -358,9 +359,9 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt display->setColor(BLACK); display->setTextAlignment(TEXT_ALIGN_CENTER); - display->drawString(centerX, titleY, titleStr); // Centered title + display->drawString(centerX, titleY, titleStr); // Centered title if (config.display.heading_bold) - display->drawString(centerX + 1, titleY, titleStr); // Bold effect via 1px offset + display->drawString(centerX + 1, titleY, titleStr); // Bold effect via 1px offset // Restore text color & alignment display->setColor(WHITE); @@ -387,9 +388,8 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt const auto &m = telemetry.variant.environment_metrics; // Check if any telemetry field has valid data - bool hasAny = m.has_temperature || m.has_relative_humidity || m.barometric_pressure != 0 || - m.iaq != 0 || m.voltage != 0 || m.current != 0 || m.lux != 0 || - m.white_lux != 0 || m.weight != 0 || m.distance != 0 || m.radiation != 0; + bool hasAny = m.has_temperature || m.has_relative_humidity || m.barometric_pressure != 0 || m.iaq != 0 || m.voltage != 0 || + m.current != 0 || m.lux != 0 || m.white_lux != 0 || m.weight != 0 || m.distance != 0 || m.radiation != 0; if (!hasAny) { display->drawString(x, currentY, "No Telemetry"); @@ -399,21 +399,21 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt // === First line: Show sender name + time since received (left), and first metric (right) === const char *sender = getSenderShortName(*lastMeasurementPacket); uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket); - String agoStr = (agoSecs > 864000) ? "?" : - (agoSecs > 3600) ? String(agoSecs / 3600) + "h" : - (agoSecs > 60) ? String(agoSecs / 60) + "m" : - String(agoSecs) + "s"; + String agoStr = (agoSecs > 864000) ? "?" + : (agoSecs > 3600) ? String(agoSecs / 3600) + "h" + : (agoSecs > 60) ? String(agoSecs / 60) + "m" + : String(agoSecs) + "s"; String leftStr = String(sender) + " (" + agoStr + ")"; - display->drawString(x, currentY, leftStr); // Left side: who and when + display->drawString(x, currentY, leftStr); // Left side: who and when // === Collect sensor readings as label strings (no icons) === std::vector entries; if (m.has_temperature) { String tempStr = moduleConfig.telemetry.environment_display_fahrenheit - ? "Tmp: " + String(UnitConversions::CelsiusToFahrenheit(m.temperature), 1) + "ยฐF" - : "Tmp: " + String(m.temperature, 1) + "ยฐC"; + ? "Tmp: " + String(UnitConversions::CelsiusToFahrenheit(m.temperature), 1) + "ยฐF" + : "Tmp: " + String(m.temperature, 1) + "ยฐC"; entries.push_back(tempStr); } if (m.has_relative_humidity) @@ -422,21 +422,23 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt entries.push_back("Prss: " + String(m.barometric_pressure, 0) + " hPa"); if (m.iaq != 0) { String aqi = "IAQ: " + String(m.iaq); - const char *bannerMsg = nullptr; // Default: no banner + const char *bannerMsg = nullptr; // Default: no banner - if (m.iaq <= 25) aqi += " (Excellent)"; - else if (m.iaq <= 50) aqi += " (Good)"; - else if (m.iaq <= 100) aqi += " (Moderate)"; - else if (m.iaq <= 150) aqi += " (Poor)"; + if (m.iaq <= 25) + aqi += " (Excellent)"; + else 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)"; bannerMsg = "Unhealthy IAQ"; - } - else if (m.iaq <= 300) { + } else if (m.iaq <= 300) { aqi += " (Very Unhealthy)"; bannerMsg = "Very Unhealthy IAQ"; - } - else { + } else { aqi += " (Hazardous)"; bannerMsg = "Hazardous IAQ"; } @@ -480,7 +482,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt String valueStr = entries.front(); int rightX = SCREEN_WIDTH - display->getStringWidth(valueStr); display->drawString(rightX, currentY, valueStr); - entries.erase(entries.begin()); // Remove from queue + entries.erase(entries.begin()); // Remove from queue } // === Advance to next line for remaining telemetry entries === diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index b99e5f91f..e42d718a5 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -10,11 +10,11 @@ #include "PowerTelemetry.h" #include "RTC.h" #include "Router.h" +#include "graphics/SharedUIDisplay.h" #include "main.h" #include "power.h" #include "sleep.h" #include "target_specific.h" -#include "graphics/SharedUIDisplay.h" #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -22,8 +22,9 @@ #include "graphics/ScreenFonts.h" #include -namespace graphics { - extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y); +namespace graphics +{ +extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y); } int32_t PowerTelemetryModule::runOnce() @@ -112,7 +113,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - graphics::drawCommonHeader(display, x, y); // Shared UI header + graphics::drawCommonHeader(display, x, y); // Shared UI header // === Draw title (aligned with header baseline) === const int highlightHeight = FONT_HEIGHT_SMALL - 1; diff --git a/src/shutdown.h b/src/shutdown.h index cc6b0e680..998944677 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -42,7 +42,7 @@ void powerCommandsCheck() #if defined(ARCH_ESP32) || defined(ARCH_NRF52) if (shutdownAtMsec && screen) { - screen->showOverlayBanner("Shutting Down...", 0); // stays on screen + screen->showOverlayBanner("Shutting Down...", 0); // stays on screen } #endif