From ac98b3d09cadb00b4ac512754d13c401ae1e7b69 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 17 Mar 2024 08:03:03 -0500 Subject: [PATCH 1/2] Remove confusing channel suffix --- src/graphics/Screen.cpp | 3030 +++++++++++++++++++++------------------ src/mesh/Channels.cpp | 34 - src/mesh/Channels.h | 19 - 3 files changed, 1607 insertions(+), 1476 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 7f20b56661..9224072140 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -67,39 +67,39 @@ namespace graphics // DEBUG #define NUM_EXTRA_FRAMES 3 // text message and debug frame -// if defined a pixel will blink to show redraws -// #define SHOW_REDRAWS + // if defined a pixel will blink to show redraws + // #define SHOW_REDRAWS -// A text message frame + debug frame + all the node infos -static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; -static uint32_t targetFramerate = IDLE_FRAMERATE; -static char btPIN[16] = "888888"; + // A text message frame + debug frame + all the node infos + static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; + static uint32_t targetFramerate = IDLE_FRAMERATE; + static char btPIN[16] = "888888"; -uint32_t logo_timeout = 5000; // 4 seconds for EACH logo + uint32_t logo_timeout = 5000; // 4 seconds for EACH logo -uint32_t hours_in_month = 730; + uint32_t hours_in_month = 730; -// This image definition is here instead of images.h because it's modified dynamically by the drawBattery function -uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C}; + // This image definition is here instead of images.h because it's modified dynamically by the drawBattery function + uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C}; -// Threshold values for the GPS lock accuracy bar display -uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100}; + // Threshold values for the GPS lock accuracy bar display + uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100}; -// At some point, we're going to ask all of the modules if they would like to display a screen frame -// we'll need to hold onto pointers for the modules that can draw a frame. -std::vector moduleFrames; + // At some point, we're going to ask all of the modules if they would like to display a screen frame + // we'll need to hold onto pointers for the modules that can draw a frame. + std::vector moduleFrames; -// Stores the last 4 of our hardware ID, to make finding the device for pairing easier -static char ourId[5]; + // Stores the last 4 of our hardware ID, to make finding the device for pairing easier + static char ourId[5]; -// GeoCoord object for the screen -GeoCoord geoCoord; + // GeoCoord object for the screen + GeoCoord geoCoord; #ifdef SHOW_REDRAWS -static bool heartbeat = false; + static bool heartbeat = false; #endif -static uint16_t displayWidth, displayHeight; + static uint16_t displayWidth, displayHeight; #define SCREEN_WIDTH displayWidth #define SCREEN_HEIGHT displayHeight @@ -108,1736 +108,1920 @@ static uint16_t displayWidth, displayHeight; #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) -/** - * Draw the icon with extra info printed around the corners - */ -static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // draw an xbm image. - // Please note that everything that should be transitioned - // needs to be drawn relative to x and y - - // draw centered icon left to right and centered above the one line of app text - display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2, - icon_width, icon_height, icon_bits); - - display->setFont(FONT_MEDIUM); - display->setTextAlignment(TEXT_ALIGN_LEFT); - const char *title = "meshtastic.org"; - display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); - display->setFont(FONT_SMALL); - - // Draw region in upper left - if (upperMsg) - display->drawString(x + 0, y + 0, upperMsg); - - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); - screen->forceDisplay(); - // FIXME - draw serial # somewhere? -} - -static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // draw an xbm image. - // Please note that everything that should be transitioned - // needs to be drawn relative to x and y + /** + * Draw the icon with extra info printed around the corners + */ + static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + // draw an xbm image. + // Please note that everything that should be transitioned + // needs to be drawn relative to x and y - // draw centered icon left to right and centered above the one line of app text - display->drawXbm(x + (SCREEN_WIDTH - oemStore.oem_icon_width) / 2, - y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - oemStore.oem_icon_height) / 2 + 2, oemStore.oem_icon_width, - oemStore.oem_icon_height, (const uint8_t *)oemStore.oem_icon_bits.bytes); + // draw centered icon left to right and centered above the one line of app text + display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2, + icon_width, icon_height, icon_bits); - switch (oemStore.oem_font) { - case 0: - display->setFont(FONT_SMALL); - break; - case 2: - display->setFont(FONT_LARGE); - break; - default: display->setFont(FONT_MEDIUM); - break; - } + display->setTextAlignment(TEXT_ALIGN_LEFT); + const char *title = "meshtastic.org"; + display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); - const char *title = oemStore.oem_text; - display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); - display->setFont(FONT_SMALL); + // Draw region in upper left + if (upperMsg) + display->drawString(x + 0, y + 0, upperMsg); - // Draw region in upper left - if (upperMsg) - display->drawString(x + 0, y + 0, upperMsg); + // Draw version in upper right + char buf[16]; + snprintf(buf, sizeof(buf), "%s", + xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + screen->forceDisplay(); + // FIXME - draw serial # somewhere? + } - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); - screen->forceDisplay(); + static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + // draw an xbm image. + // Please note that everything that should be transitioned + // needs to be drawn relative to x and y + + // draw centered icon left to right and centered above the one line of app text + display->drawXbm(x + (SCREEN_WIDTH - oemStore.oem_icon_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - oemStore.oem_icon_height) / 2 + 2, oemStore.oem_icon_width, + oemStore.oem_icon_height, (const uint8_t *)oemStore.oem_icon_bits.bytes); + + switch (oemStore.oem_font) + { + case 0: + display->setFont(FONT_SMALL); + break; + case 2: + display->setFont(FONT_LARGE); + break; + default: + display->setFont(FONT_MEDIUM); + break; + } - // FIXME - draw serial # somewhere? -} + display->setTextAlignment(TEXT_ALIGN_LEFT); + const char *title = oemStore.oem_text; + display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + display->setFont(FONT_SMALL); -static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // Draw region in upper left - const char *region = myRegion ? myRegion->name : NULL; - drawOEMIconScreen(region, display, state, x, y); -} + // Draw region in upper left + if (upperMsg) + display->drawString(x + 0, y + 0, upperMsg); -static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) -{ - uint16_t x_offset = display->width() / 2; - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(x_offset + x, 26 + y, message); -} + // Draw version in upper right + char buf[16]; + snprintf(buf, sizeof(buf), "%s", + xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + screen->forceDisplay(); -static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ -#ifdef ARCH_ESP32 - if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { - drawFrameText(display, state, x, y, "Resuming..."); - } else -#endif + // FIXME - draw serial # somewhere? + } + + static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // Draw region in upper left const char *region = myRegion ? myRegion->name : NULL; - drawIconScreen(region, display, state, x, y); + drawOEMIconScreen(region, display, state, x, y); } -} -// Used on boot when a certificate is being created -static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_SMALL); - display->drawString(64 + x, y, "Creating SSL certificate"); + static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) + { + uint16_t x_offset = display->width() / 2; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, 26 + y, message); + } + static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { #ifdef ARCH_ESP32 - yield(); - esp_task_wdt_reset(); + if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) + { + drawFrameText(display, state, x, y, "Resuming..."); + } + else #endif - - display->setFont(FONT_SMALL); - if ((millis() / 1000) % 2) { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); - } else { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); + { + // Draw region in upper left + const char *region = myRegion ? myRegion->name : NULL; + drawIconScreen(region, display, state, x, y); + } } -} -// Used when booting without a region set -static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C"); - display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName()); - display->setTextAlignment(TEXT_ALIGN_LEFT); - - if ((millis() / 10000) % 2) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); - } else { - display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, ""); + // Used on boot when a certificate is being created + static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_SMALL); + display->drawString(64 + x, y, "Creating SSL certificate"); + +#ifdef ARCH_ESP32 + yield(); + esp_task_wdt_reset(); +#endif + + display->setFont(FONT_SMALL); + if ((millis() / 1000) % 2) + { + display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); + } + else + { + display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); + } } + // Used when booting without a region set + static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C"); + display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName()); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + if ((millis() / 10000) % 2) + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); + } + else + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, ""); + } + #ifdef ARCH_ESP32 - yield(); - esp_task_wdt_reset(); + yield(); + esp_task_wdt_reset(); #endif -} + } #ifdef USE_EINK -/// Used on eink displays while in deep sleep -static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // Next frame should use full-refresh, and block while running, else device will sleep before async callback - EINK_ADD_FRAMEFLAG(display, COSMETIC); - EINK_ADD_FRAMEFLAG(display, BLOCKING); + /// Used on eink displays while in deep sleep + static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + // Next frame should use full-refresh, and block while running, else device will sleep before async callback + EINK_ADD_FRAMEFLAG(display, COSMETIC); + EINK_ADD_FRAMEFLAG(display, BLOCKING); - drawIconScreen("Sleeping...", display, state, x, y); -} + drawIconScreen("Sleeping...", display, state, x, y); + } #endif -static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - uint8_t module_frame; - // there's a little but in the UI transition code - // where it invokes the function at the correct offset - // in the array of "drawScreen" functions; however, - // the passed-state doesn't quite reflect the "current" - // screen, so we have to detect it. - if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) { - // if we're transitioning from the end of the frame list back around to the first - // frame, then we want this to be `0` - module_frame = state->transitionFrameTarget; - } else { - // otherwise, just display the module frame that's aligned with the current frame - module_frame = state->currentFrame; - // LOG_DEBUG("Screen is not in transition. Frame: %d\n\n", module_frame); + static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + uint8_t module_frame; + // there's a little but in the UI transition code + // where it invokes the function at the correct offset + // in the array of "drawScreen" functions; however, + // the passed-state doesn't quite reflect the "current" + // screen, so we have to detect it. + if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) + { + // if we're transitioning from the end of the frame list back around to the first + // frame, then we want this to be `0` + module_frame = state->transitionFrameTarget; + } + else + { + // otherwise, just display the module frame that's aligned with the current frame + module_frame = state->currentFrame; + // LOG_DEBUG("Screen is not in transition. Frame: %d\n\n", module_frame); + } + // LOG_DEBUG("Drawing Module Frame %d\n\n", module_frame); + MeshModule &pi = *moduleFrames.at(module_frame); + pi.drawFrame(display, state, x, y); } - // LOG_DEBUG("Drawing Module Frame %d\n\n", module_frame); - MeshModule &pi = *moduleFrames.at(module_frame); - pi.drawFrame(display, state, x, y); -} -static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(x_offset + x, y_offset + y, "Bluetooth"); - - display->setFont(FONT_SMALL); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; - display->drawString(x_offset + x, y_offset + y, "Enter this code"); - - display->setFont(FONT_LARGE); - String displayPin(btPIN); - String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; - display->drawString(x_offset + x, y_offset + y, pin); - - display->setFont(FONT_SMALL); - String deviceName = "Name: "; - deviceName.concat(getDeviceName()); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; - display->drawString(x_offset + x, y_offset + y, deviceName); -} - -static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(64 + x, y, "Updating"); - - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), - "Please be patient and do not power off."); -} - -/// Draw the last text message we received -static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - - char tempBuf[24]; - snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code); - display->drawString(0 + x, 0 + y, tempBuf); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org"); -} - -// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled -static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) -{ - return packet->from != 0 && !moduleConfig.store_forward.enabled; -} + static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, y_offset + y, "Bluetooth"); -/// Draw the last text message we received -static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // the max length of this buffer is much longer than we can possibly print - static char tempBuf[237]; - - const meshtastic_MeshPacket &mp = devicestate.rx_text_message; - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); - // LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from, - // mp.decoded.variant.data.decoded.bytes); - - // Demo for drawStringMaxWidth: - // with the third parameter you can define the width after which words will - // be wrapped. Currently only spaces and "-" are allowed for wrapping - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } + display->setFont(FONT_SMALL); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; + display->drawString(x_offset + x, y_offset + y, "Enter this code"); - uint32_t seconds = sinceReceived(&mp); - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; + display->setFont(FONT_LARGE); + String displayPin(btPIN); + String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; + display->drawString(x_offset + x, y_offset + y, pin); - if (config.display.heading_bold) { - display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", - screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); + display->setFont(FONT_SMALL); + String deviceName = "Name: "; + deviceName.concat(getDeviceName()); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; + display->drawString(x_offset + x, y_offset + y, deviceName); } - display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - display->setColor(WHITE); - snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); - display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); -} + static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(64 + x, y, "Updating"); -/// Draw the last waypoint we received -static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - static char tempBuf[237]; + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), + "Please be patient and do not power off."); + } - meshtastic_MeshPacket &mp = devicestate.rx_waypoint; - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + /// Draw the last text message we received + static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + + char tempBuf[24]; + snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code); + display->drawString(0 + x, 0 + y, tempBuf); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org"); + } - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); + // Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled + static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) + { + return packet->from != 0 && !moduleConfig.store_forward.enabled; } - uint32_t seconds = sinceReceived(&mp); - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; + /// Draw the last text message we received + static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + // the max length of this buffer is much longer than we can possibly print + static char tempBuf[237]; + + const meshtastic_MeshPacket &mp = devicestate.rx_text_message; + meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + // LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from, + // mp.decoded.variant.data.decoded.bytes); + + // Demo for drawStringMaxWidth: + // with the third parameter you can define the width after which words will + // be wrapped. Currently only spaces and "-" are allowed for wrapping + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } + + uint32_t seconds = sinceReceived(&mp); + uint32_t minutes = seconds / 60; + uint32_t hours = minutes / 60; + uint32_t days = hours / 24; - if (config.display.heading_bold) { - display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", - screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + if (config.display.heading_bold) + { + display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", + screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); + } + display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), (node && node->has_user) ? node->user.short_name : "???"); - } - display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - - display->setColor(WHITE); - meshtastic_Waypoint scratch; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { - snprintf(tempBuf, sizeof(tempBuf), "Received waypoint: %s", scratch.name); + + display->setColor(WHITE); + snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); } -} -/// Draw a series of fields in a column, wrapping to multiple columns if needed -static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) -{ - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + /// Draw the last waypoint we received + static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + static char tempBuf[237]; + + meshtastic_MeshPacket &mp = devicestate.rx_waypoint; + meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - const char **f = fields; - int xo = x, yo = y; - while (*f) { - display->drawString(xo, yo, *f); - if ((display->getColor() == BLACK) && config.display.heading_bold) - display->drawString(xo + 1, yo, *f); + uint32_t seconds = sinceReceived(&mp); + uint32_t minutes = seconds / 60; + uint32_t hours = minutes / 60; + uint32_t days = hours / 24; + + if (config.display.heading_bold) + { + display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", + screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); + } + display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); display->setColor(WHITE); - yo += FONT_HEIGHT_SMALL; - if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) { - xo += SCREEN_WIDTH / 2; - yo = 0; + meshtastic_Waypoint scratch; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) + { + snprintf(tempBuf, sizeof(tempBuf), "Received waypoint: %s", scratch.name); + display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); } - f++; } -} -// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. -static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) -{ - static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD}; - static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85}; - // Clear the bar area on the battery image - for (int i = 1; i < 14; i++) { - imgBuffer[i] = 0x81; + /// Draw a series of fields in a column, wrapping to multiple columns if needed + static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) + { + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); + + const char **f = fields; + int xo = x, yo = y; + while (*f) + { + display->drawString(xo, yo, *f); + if ((display->getColor() == BLACK) && config.display.heading_bold) + display->drawString(xo + 1, yo, *f); + + display->setColor(WHITE); + yo += FONT_HEIGHT_SMALL; + if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) + { + xo += SCREEN_WIDTH / 2; + yo = 0; + } + f++; + } } - // If charging, draw a charging indicator - if (powerStatus->getIsCharging()) { - memcpy(imgBuffer + 3, lightning, 8); - // If not charging, Draw power bars - } else { - for (int i = 0; i < 4; i++) { - if (powerStatus->getBatteryChargePercent() >= 25 * i) - memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); + + // Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. + static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) + { + static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD}; + static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85}; + // Clear the bar area on the battery image + for (int i = 1; i < 14; i++) + { + imgBuffer[i] = 0x81; + } + // If charging, draw a charging indicator + if (powerStatus->getIsCharging()) + { + memcpy(imgBuffer + 3, lightning, 8); + // If not charging, Draw power bars + } + else + { + for (int i = 0; i < 4; i++) + { + if (powerStatus->getBatteryChargePercent() >= 25 * i) + memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); + } } + display->drawFastImage(x, y, 16, 8, imgBuffer); } - display->drawFastImage(x, y, 16, 8, imgBuffer); -} -// Draw nodes status -static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus) -{ - char usersString[20]; - snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + // Draw nodes status + static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus) + { + char usersString[20]; + snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x, y + 3, 8, 8, imgUser); + display->drawFastImage(x, y + 3, 8, 8, imgUser); #else - display->drawFastImage(x, y, 8, 8, imgUser); + display->drawFastImage(x, y, 8, 8, imgUser); #endif - display->drawString(x + 10, y - 2, usersString); - if (config.display.heading_bold) - display->drawString(x + 11, y - 2, usersString); -} - -// Draw GPS status summary -static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) -{ - if (config.position.fixed_position) { - // GPS coordinates are currently fixed - display->drawString(x - 1, y - 2, "Fixed GPS"); + display->drawString(x + 10, y - 2, usersString); if (config.display.heading_bold) - display->drawString(x, y - 2, "Fixed GPS"); - return; + display->drawString(x + 11, y - 2, usersString); } - if (!gps->getIsConnected()) { - display->drawString(x, y - 2, "No GPS"); - if (config.display.heading_bold) - display->drawString(x + 1, y - 2, "No GPS"); - return; - } - display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty); - if (!gps->getHasLock()) { - display->drawString(x + 8, y - 2, "No sats"); - if (config.display.heading_bold) - display->drawString(x + 9, y - 2, "No sats"); - return; - } else { - char satsString[3]; - uint8_t bar[2] = {0}; - - // Draw DOP signal bars - for (int i = 0; i < 5; i++) { - if (gps->getDOP() <= dopThresholds[i]) - bar[0] = ~((1 << (5 - i)) - 1); - else - bar[0] = 0b10000000; - // bar[1] = bar[0]; - display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar); + + // Draw GPS status summary + static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) + { + if (config.position.fixed_position) + { + // GPS coordinates are currently fixed + display->drawString(x - 1, y - 2, "Fixed GPS"); + if (config.display.heading_bold) + display->drawString(x, y - 2, "Fixed GPS"); + return; + } + if (!gps->getIsConnected()) + { + display->drawString(x, y - 2, "No GPS"); + if (config.display.heading_bold) + display->drawString(x + 1, y - 2, "No GPS"); + return; + } + display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty); + if (!gps->getHasLock()) + { + display->drawString(x + 8, y - 2, "No sats"); + if (config.display.heading_bold) + display->drawString(x + 9, y - 2, "No sats"); + return; } + else + { + char satsString[3]; + uint8_t bar[2] = {0}; + + // Draw DOP signal bars + for (int i = 0; i < 5; i++) + { + if (gps->getDOP() <= dopThresholds[i]) + bar[0] = ~((1 << (5 - i)) - 1); + else + bar[0] = 0b10000000; + // bar[1] = bar[0]; + display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar); + } - // Draw satellite image - display->drawFastImage(x + 24, y, 8, 8, imgSatellite); + // Draw satellite image + display->drawFastImage(x + 24, y, 8, 8, imgSatellite); - // Draw the number of satellites - snprintf(satsString, sizeof(satsString), "%u", gps->getNumSatellites()); - display->drawString(x + 34, y - 2, satsString); - if (config.display.heading_bold) - display->drawString(x + 35, y - 2, satsString); + // Draw the number of satellites + snprintf(satsString, sizeof(satsString), "%u", gps->getNumSatellites()); + display->drawString(x + 34, y - 2, satsString); + if (config.display.heading_bold) + display->drawString(x + 35, y - 2, satsString); + } } -} -// Draw status when GPS is disabled or not present -static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) -{ - String displayLine; - int pos; - if (y < FONT_HEIGHT_SMALL) { // Line 1: use short string - displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off"; - pos = SCREEN_WIDTH - display->getStringWidth(displayLine); - } else { - displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "GPS not present" - : "GPS is disabled"; - pos = (SCREEN_WIDTH - display->getStringWidth(displayLine)) / 2; + // Draw status when GPS is disabled or not present + static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) + { + String displayLine; + int pos; + if (y < FONT_HEIGHT_SMALL) + { // Line 1: use short string + displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off"; + pos = SCREEN_WIDTH - display->getStringWidth(displayLine); + } + else + { + displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "GPS not present" + : "GPS is disabled"; + pos = (SCREEN_WIDTH - display->getStringWidth(displayLine)) / 2; + } + display->drawString(x + pos, y, displayLine); } - display->drawString(x + pos, y, displayLine); -} -static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) -{ - String displayLine = ""; - if (!gps->getIsConnected() && !config.position.fixed_position) { - // displayLine = "No GPS Module"; - // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } else if (!gps->getHasLock() && !config.position.fixed_position) { - // displayLine = "No GPS Lock"; - // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } else { - geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); - displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m"; - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - displayLine = "Altitude: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft"; - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) + { + String displayLine = ""; + if (!gps->getIsConnected() && !config.position.fixed_position) + { + // displayLine = "No GPS Module"; + // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } + else if (!gps->getHasLock() && !config.position.fixed_position) + { + // displayLine = "No GPS Lock"; + // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } + else + { + geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); + displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m"; + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + displayLine = "Altitude: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft"; + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } } -} -// Draw GPS status coordinates -static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) -{ - auto gpsFormat = config.display.gps_format; - String displayLine = ""; - - if (!gps->getIsConnected() && !config.position.fixed_position) { - displayLine = "No GPS present"; - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } else if (!gps->getHasLock() && !config.position.fixed_position) { - displayLine = "No GPS Lock"; - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } else { - - geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); - - if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) { - char coordinateLine[22]; - if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees - snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7, - geoCoord.getLongitude() * 1e-7); - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator - snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(), - geoCoord.getUTMEasting(), geoCoord.getUTMNorthing()); - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System - snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(), - geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(), - geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing()); - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code - geoCoord.getOLCCode(coordinateLine); - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference - if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region - snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary"); - else - snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(), - geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing()); - } + // Draw GPS status coordinates + static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) + { + auto gpsFormat = config.display.gps_format; + String displayLine = ""; - // If fixed position, display text "Fixed GPS" alternating with the coordinates. - if (config.position.fixed_position) { - if ((millis() / 10000) % 2) { + if (!gps->getIsConnected() && !config.position.fixed_position) + { + displayLine = "No GPS present"; + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } + else if (!gps->getHasLock() && !config.position.fixed_position) + { + displayLine = "No GPS Lock"; + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } + else + { + + geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); + + if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) + { + char coordinateLine[22]; + if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) + { // Decimal Degrees + snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7, + geoCoord.getLongitude() * 1e-7); + } + else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) + { // Universal Transverse Mercator + snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(), + geoCoord.getUTMEasting(), geoCoord.getUTMNorthing()); + } + else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) + { // Military Grid Reference System + snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(), + geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(), + geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing()); + } + else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) + { // Open Location Code + geoCoord.getOLCCode(coordinateLine); + } + else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) + { // Ordnance Survey Grid Reference + if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region + snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary"); + else + snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(), + geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing()); + } + + // If fixed position, display text "Fixed GPS" alternating with the coordinates. + if (config.position.fixed_position) + { + if ((millis() / 10000) % 2) + { + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine); + } + else + { + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS"); + } + } + else + { display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine); - } else { - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS"); } - } else { - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine); } - } else { - char latLine[22]; - char lonLine[22]; - snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), - geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP()); - snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), - geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP()); - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine); - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine); + else + { + char latLine[22]; + char lonLine[22]; + snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), + geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP()); + snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), + geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP()); + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine); + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine); + } } } -} -namespace -{ + namespace + { -/// A basic 2D point class for drawing -class Point -{ - public: - float x, y; + /// A basic 2D point class for drawing + class Point + { + public: + float x, y; - Point(float _x, float _y) : x(_x), y(_y) {} + Point(float _x, float _y) : x(_x), y(_y) {} - /// Apply a rotation around zero (standard rotation matrix math) - void rotate(float radian) - { - float cos = cosf(radian), sin = sinf(radian); - float rx = x * cos + y * sin, ry = -x * sin + y * cos; + /// Apply a rotation around zero (standard rotation matrix math) + void rotate(float radian) + { + float cos = cosf(radian), sin = sinf(radian); + float rx = x * cos + y * sin, ry = -x * sin + y * cos; - x = rx; - y = ry; - } + x = rx; + y = ry; + } + + void translate(int16_t dx, int dy) + { + x += dx; + y += dy; + } - void translate(int16_t dx, int dy) + void scale(float f) + { + // We use -f here to counter the flip that happens + // on the y axis when drawing and rotating on screen + x *= f; + y *= -f; + } + }; + + } // namespace + + static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) { - x += dx; - y += dy; + d->drawLine(p1.x, p1.y, p2.x, p2.y); } - void scale(float f) + /** + * Given a recent lat/lon return a guess of the heading the user is walking on. + * + * We keep a series of "after you've gone 10 meters, what is your heading since + * the last reference point?" + */ + static float estimatedHeading(double lat, double lon) { - // We use -f here to counter the flip that happens - // on the y axis when drawing and rotating on screen - x *= f; - y *= -f; - } -}; + static double oldLat, oldLon; + static float b; -} // namespace + if (oldLat == 0) + { + // just prepare for next time + oldLat = lat; + oldLon = lon; -static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) -{ - d->drawLine(p1.x, p1.y, p2.x, p2.y); -} - -/** - * Given a recent lat/lon return a guess of the heading the user is walking on. - * - * We keep a series of "after you've gone 10 meters, what is your heading since - * the last reference point?" - */ -static float estimatedHeading(double lat, double lon) -{ - static double oldLat, oldLon; - static float b; + return b; + } + + float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon); + if (d < 10) // haven't moved enough, just keep current bearing + return b; - if (oldLat == 0) { - // just prepare for next time + b = GeoCoord::bearing(oldLat, oldLon, lat, lon); oldLat = lat; oldLon = lon; return b; } - float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon); - if (d < 10) // haven't moved enough, just keep current bearing - return b; - - b = GeoCoord::bearing(oldLat, oldLon, lat, lon); - oldLat = lat; - oldLon = lon; - - return b; -} - -static uint16_t getCompassDiam(OLEDDisplay *display) -{ - uint16_t diam = 0; - uint16_t offset = 0; - - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - offset = FONT_HEIGHT_SMALL; - - // get the smaller of the 2 dimensions and subtract 20 - if (display->getWidth() > (display->getHeight() - offset)) { - diam = display->getHeight() - offset; - // if 2/3 of the other size would be smaller, use that - if (diam > (display->getWidth() * 2 / 3)) { - diam = display->getWidth() * 2 / 3; + static uint16_t getCompassDiam(OLEDDisplay *display) + { + uint16_t diam = 0; + uint16_t offset = 0; + + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + offset = FONT_HEIGHT_SMALL; + + // get the smaller of the 2 dimensions and subtract 20 + if (display->getWidth() > (display->getHeight() - offset)) + { + diam = display->getHeight() - offset; + // if 2/3 of the other size would be smaller, use that + if (diam > (display->getWidth() * 2 / 3)) + { + diam = display->getWidth() * 2 / 3; + } } - } else { - diam = display->getWidth(); - if (diam > ((display->getHeight() - offset) * 2 / 3)) { - diam = (display->getHeight() - offset) * 2 / 3; + else + { + diam = display->getWidth(); + if (diam > ((display->getHeight() - offset) * 2 / 3)) + { + diam = (display->getHeight() - offset) * 2 / 3; + } } - } - return diam - 20; -}; + return diam - 20; + }; -/// We will skip one node - the one for us, so we just blindly loop over all -/// nodes -static size_t nodeIndex; -static int8_t prevFrame = -1; + /// We will skip one node - the one for us, so we just blindly loop over all + /// nodes + static size_t nodeIndex; + static int8_t prevFrame = -1; -// Draw the arrow pointing to a node's location -static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian) -{ - Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially - float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; - Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY); + // Draw the arrow pointing to a node's location + static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian) + { + Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially + float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; + Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY); - Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow}; + Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow}; - for (int i = 0; i < 4; i++) { - arrowPoints[i]->rotate(headingRadian); - arrowPoints[i]->scale(getCompassDiam(display) * 0.6); - arrowPoints[i]->translate(compassX, compassY); + for (int i = 0; i < 4; i++) + { + arrowPoints[i]->rotate(headingRadian); + arrowPoints[i]->scale(getCompassDiam(display) * 0.6); + arrowPoints[i]->translate(compassX, compassY); + } + drawLine(display, tip, tail); + drawLine(display, leftArrow, tip); + drawLine(display, rightArrow, tip); } - drawLine(display, tip, tail); - drawLine(display, leftArrow, tip); - drawLine(display, rightArrow, tip); -} -// Draw north -static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) -{ - // If north is supposed to be at the top of the compass we want rotation to be +0 - if (config.display.compass_north_top) - myHeading = -0; - - Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); - Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); - Point *rosePoints[] = {&N1, &N2, &N3, &N4}; - - for (int i = 0; i < 4; i++) { - // North on compass will be negative of heading - rosePoints[i]->rotate(-myHeading); - rosePoints[i]->scale(getCompassDiam(display)); - rosePoints[i]->translate(compassX, compassY); + // Draw north + static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) + { + // If north is supposed to be at the top of the compass we want rotation to be +0 + if (config.display.compass_north_top) + myHeading = -0; + + Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); + Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); + Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + + for (int i = 0; i < 4; i++) + { + // North on compass will be negative of heading + rosePoints[i]->rotate(-myHeading); + rosePoints[i]->scale(getCompassDiam(display)); + rosePoints[i]->translate(compassX, compassY); + } + drawLine(display, N1, N3); + drawLine(display, N2, N4); + drawLine(display, N1, N4); } - drawLine(display, N1, N3); - drawLine(display, N2, N4); - drawLine(display, N1, N4); -} /// Convert an integer GPS coords to a floating point #define DegD(i) (i * 1e-7) -static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // We only advance our nodeIndex if the frame # has changed - because - // drawNodeInfo will be called repeatedly while the frame is shown - if (state->currentFrame != prevFrame) { - prevFrame = state->currentFrame; - - nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); - meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex); - if (n->num == nodeDB.getNodeNum()) { - // Don't show our node, just skip to next + static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + // We only advance our nodeIndex if the frame # has changed - because + // drawNodeInfo will be called repeatedly while the frame is shown + if (state->currentFrame != prevFrame) + { + prevFrame = state->currentFrame; + nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); - n = nodeDB.getMeshNodeByIndex(nodeIndex); + meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex); + if (n->num == nodeDB.getNodeNum()) + { + // Don't show our node, just skip to next + nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); + n = nodeDB.getMeshNodeByIndex(nodeIndex); + } } - } - meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex); + meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex); - display->setFont(FONT_SMALL); + display->setFont(FONT_SMALL); - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - } + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + } - const char *username = node->has_user ? node->user.long_name : "Unknown Name"; - - static char signalStr[20]; - snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); - - uint32_t agoSecs = sinceLastSeen(node); - static char lastStr[20]; - if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); - else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); - else { - // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad - // data. - if ((agoSecs / 60 / 60) < (hours_in_month * 6)) { - snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); - } else { - snprintf(lastStr, sizeof(lastStr), "unknown age"); + const char *username = node->has_user ? node->user.long_name : "Unknown Name"; + + static char signalStr[20]; + snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); + + uint32_t agoSecs = sinceLastSeen(node); + static char lastStr[20]; + if (agoSecs < 120) // last 2 mins? + snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); + else if (agoSecs < 120 * 60) // last 2 hrs + snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); + else + { + // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad + // data. + if ((agoSecs / 60 / 60) < (hours_in_month * 6)) + { + snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); + } + else + { + snprintf(lastStr, sizeof(lastStr), "unknown age"); + } } - } - static char distStr[20]; - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { - strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data - } else { - strncpy(distStr, "? km", sizeof(distStr)); - } - meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum()); - const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; - int16_t compassX = 0, compassY = 0; - - // coordinates for the center of the compass/circle - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; - compassY = y + SCREEN_HEIGHT / 2; - } else { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; - compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; - } - bool hasNodeHeading = false; - - if (ourNode && hasValidPosition(ourNode)) { - const meshtastic_PositionLite &op = ourNode->position; - float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - drawCompassNorth(display, compassX, compassY, myHeading); - - if (hasValidPosition(node)) { - // display direction toward node - hasNodeHeading = true; - const meshtastic_PositionLite &p = node->position; - float d = - GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); - - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { - if (d < (2 * MILES_TO_FEET)) - snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); - else - snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); - } else { - if (d < 2000) - snprintf(distStr, sizeof(distStr), "%.0f m", d); + static char distStr[20]; + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + { + strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data + } + else + { + strncpy(distStr, "? km", sizeof(distStr)); + } + meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum()); + const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; + int16_t compassX = 0, compassY = 0; + + // coordinates for the center of the compass/circle + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + { + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassY = y + SCREEN_HEIGHT / 2; + } + else + { + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; + } + bool hasNodeHeading = false; + + if (ourNode && hasValidPosition(ourNode)) + { + const meshtastic_PositionLite &op = ourNode->position; + float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + drawCompassNorth(display, compassX, compassY, myHeading); + + if (hasValidPosition(node)) + { + // display direction toward node + hasNodeHeading = true; + const meshtastic_PositionLite &p = node->position; + float d = + GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + { + if (d < (2 * MILES_TO_FEET)) + snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); + else + snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); + } else - snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); - } + { + if (d < 2000) + snprintf(distStr, sizeof(distStr), "%.0f m", d); + else + snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); + } - float bearingToOther = - GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); - // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly - // If the top of the compass is not a static north we need adjust bearingToOther based on heading - if (!config.display.compass_north_top) - bearingToOther -= myHeading; - drawNodeHeading(display, compassX, compassY, bearingToOther); + float bearingToOther = + GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); + // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly + // If the top of the compass is not a static north we need adjust bearingToOther based on heading + if (!config.display.compass_north_top) + bearingToOther -= myHeading; + drawNodeHeading(display, compassX, compassY, bearingToOther); + } } - } - if (!hasNodeHeading) { - // direction to node is unknown so display question mark - // Debug info for gps lock errors - // LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasValidPosition(ourNode), - // hasValidPosition(node)); - display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); - } - display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + if (!hasNodeHeading) + { + // direction to node is unknown so display question mark + // Debug info for gps lock errors + // LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasValidPosition(ourNode), + // hasValidPosition(node)); + display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); + } + display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->setColor(BLACK); + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + { + display->setColor(BLACK); + } + // Must be after distStr is populated + drawColumns(display, x, y, fields); } - // Must be after distStr is populated - drawColumns(display, x, y, fields); -} -Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) - : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) -{ + Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) + : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) + { #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) - dispdev = new SH1106Wire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new SH1106Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_SSD1306) - dispdev = new SSD1306Wire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new SSD1306Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) - dispdev = new TFTDisplay(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new TFTDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) - dispdev = new EInkDisplay(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new EInkDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) - dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_ST7567) - dispdev = new ST7567Wire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); -#elif ARCH_PORTDUINO - if (settingsMap[displayPanel] != no_screen) { - LOG_DEBUG("Making TFTDisplay!\n"); - dispdev = new TFTDisplay(address.address, -1, -1, geometry, + dispdev = new ST7567Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); - } else { +#elif ARCH_PORTDUINO + if (settingsMap[displayPanel] != no_screen) + { + LOG_DEBUG("Making TFTDisplay!\n"); + dispdev = new TFTDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + } + else + { + dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + isAUTOOled = true; + } +#else dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); isAUTOOled = true; - } -#else - dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); - isAUTOOled = true; #endif - ui = new OLEDDisplayUi(dispdev); - cmdQueue.setReader(this); -} + ui = new OLEDDisplayUi(dispdev); + cmdQueue.setReader(this); + } -/** - * Prepare the display for the unit going to the lowest power mode possible. Most screens will just - * poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code - */ -void Screen::doDeepSleep() -{ + /** + * Prepare the display for the unit going to the lowest power mode possible. Most screens will just + * poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code + */ + void Screen::doDeepSleep() + { #ifdef USE_EINK - static FrameCallback sleepFrames[] = {drawSleepScreen}; - static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]); - ui->setFrames(sleepFrames, sleepFrameCount); - ui->update(); + static FrameCallback sleepFrames[] = {drawSleepScreen}; + static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]); + ui->setFrames(sleepFrames, sleepFrameCount); + ui->update(); #ifdef PIN_EINK_EN - digitalWrite(PIN_EINK_EN, LOW); // power off backlight + digitalWrite(PIN_EINK_EN, LOW); // power off backlight #endif #endif - setOn(false); -} - -void Screen::handleSetOn(bool on) -{ - if (!useDisplay) - return; + setOn(false); + } - if (on != screenOn) { - if (on) { - LOG_INFO("Turning on screen\n"); + void Screen::handleSetOn(bool on) + { + if (!useDisplay) + return; + + if (on != screenOn) + { + if (on) + { + LOG_INFO("Turning on screen\n"); #ifdef T_WATCH_S3 - PMU->enablePowerOutput(XPOWERS_ALDO2); + PMU->enablePowerOutput(XPOWERS_ALDO2); #endif #if !ARCH_PORTDUINO - dispdev->displayOn(); + dispdev->displayOn(); #endif - dispdev->displayOn(); - enabled = true; - setInterval(0); // Draw ASAP - runASAP = true; - } else { - LOG_INFO("Turning off screen\n"); - dispdev->displayOff(); + dispdev->displayOn(); + enabled = true; + setInterval(0); // Draw ASAP + runASAP = true; + } + else + { + LOG_INFO("Turning off screen\n"); + dispdev->displayOff(); #ifdef T_WATCH_S3 - PMU->disablePowerOutput(XPOWERS_ALDO2); + PMU->disablePowerOutput(XPOWERS_ALDO2); #endif - enabled = false; + enabled = false; + } + screenOn = on; } - screenOn = on; } -} -void Screen::setup() -{ - // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device - // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. - useDisplay = true; + void Screen::setup() + { + // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device + // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. + useDisplay = true; #ifdef AutoOLEDWire_h - if (isAUTOOled) - static_cast(dispdev)->setDetected(model); + if (isAUTOOled) + static_cast(dispdev)->setDetected(model); #endif #ifdef USE_SH1107_128_64 - static_cast(dispdev)->setSubtype(7); + static_cast(dispdev)->setSubtype(7); #endif - // Initialising the UI will init the display too. - ui->init(); + // Initialising the UI will init the display too. + ui->init(); - displayWidth = dispdev->width(); - displayHeight = dispdev->height(); + displayWidth = dispdev->width(); + displayHeight = dispdev->height(); - ui->setTimePerTransition(0); + ui->setTimePerTransition(0); - ui->setIndicatorPosition(BOTTOM); - // Defines where the first frame is located in the bar. - ui->setIndicatorDirection(LEFT_RIGHT); - ui->setFrameAnimation(SLIDE_LEFT); - // Don't show the page swipe dots while in boot screen. - ui->disableAllIndicators(); - // Store a pointer to Screen so we can get to it from static functions. - ui->getUiState()->userData = this; + ui->setIndicatorPosition(BOTTOM); + // Defines where the first frame is located in the bar. + ui->setIndicatorDirection(LEFT_RIGHT); + ui->setFrameAnimation(SLIDE_LEFT); + // Don't show the page swipe dots while in boot screen. + ui->disableAllIndicators(); + // Store a pointer to Screen so we can get to it from static functions. + ui->getUiState()->userData = this; - // Set the utf8 conversion function - dispdev->setFontTableLookupFunction(customFontTableLookup); + // Set the utf8 conversion function + dispdev->setFontTableLookupFunction(customFontTableLookup); - if (strlen(oemStore.oem_text) > 0) - logo_timeout *= 2; + if (strlen(oemStore.oem_text) > 0) + logo_timeout *= 2; - // Add frames. - static FrameCallback bootFrames[] = {drawBootScreen}; - static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); - ui->setFrames(bootFrames, bootFrameCount); - // No overlays. - ui->setOverlays(nullptr, 0); + // Add frames. + static FrameCallback bootFrames[] = {drawBootScreen}; + static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); + ui->setFrames(bootFrames, bootFrameCount); + // No overlays. + ui->setOverlays(nullptr, 0); - // Require presses to switch between frames. - ui->disableAutoTransition(); + // Require presses to switch between frames. + ui->disableAutoTransition(); - // Set up a log buffer with 3 lines, 32 chars each. - dispdev->setLogBuffer(3, 32); + // Set up a log buffer with 3 lines, 32 chars each. + dispdev->setLogBuffer(3, 32); #ifdef SCREEN_MIRROR - dispdev->mirrorScreen(); + dispdev->mirrorScreen(); #else - // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically - // flip it. If you have a headache now, you're welcome. - if (!config.display.flip_screen) { + // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically + // flip it. If you have a headache now, you're welcome. + if (!config.display.flip_screen) + { #if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) - static_cast(dispdev)->flipScreenVertically(); + static_cast(dispdev)->flipScreenVertically(); #else - dispdev->flipScreenVertically(); + dispdev->flipScreenVertically(); #endif - } + } #endif - // Get our hardware ID - uint8_t dmac[6]; - getMacAddr(dmac); - snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]); + // Get our hardware ID + uint8_t dmac[6]; + getMacAddr(dmac); + snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]); #if ARCH_PORTDUINO - handleSetOn(false); // force clean init + handleSetOn(false); // force clean init #endif - // Turn on the display. - handleSetOn(true); + // Turn on the display. + handleSetOn(true); - // On some ssd1306 clones, the first draw command is discarded, so draw it - // twice initially. Skip this for EINK Displays to save a few seconds during boot - ui->update(); + // On some ssd1306 clones, the first draw command is discarded, so draw it + // twice initially. Skip this for EINK Displays to save a few seconds during boot + ui->update(); #ifndef USE_EINK - ui->update(); + ui->update(); #endif - serialSinceMsec = millis(); + serialSinceMsec = millis(); #if ARCH_PORTDUINO - if (settingsMap[touchscreenModule]) { + if (settingsMap[touchscreenModule]) + { + touchScreenImpl1 = + new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); + touchScreenImpl1->init(); + } +#elif HAS_TOUCHSCREEN touchScreenImpl1 = new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); - } -#elif HAS_TOUCHSCREEN - touchScreenImpl1 = - new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); - touchScreenImpl1->init(); #endif - // Subscribe to status updates - powerStatusObserver.observe(&powerStatus->onNewStatus); - gpsStatusObserver.observe(&gpsStatus->onNewStatus); - nodeStatusObserver.observe(&nodeStatus->onNewStatus); - if (textMessageModule) - textMessageObserver.observe(textMessageModule); - if (inputBroker) - inputObserver.observe(inputBroker); + // Subscribe to status updates + powerStatusObserver.observe(&powerStatus->onNewStatus); + gpsStatusObserver.observe(&gpsStatus->onNewStatus); + nodeStatusObserver.observe(&nodeStatus->onNewStatus); + if (textMessageModule) + textMessageObserver.observe(textMessageModule); + if (inputBroker) + inputObserver.observe(inputBroker); - // Modules can notify screen about refresh - MeshModule::observeUIEvents(&uiFrameEventObserver); -} + // Modules can notify screen about refresh + MeshModule::observeUIEvents(&uiFrameEventObserver); + } -void Screen::forceDisplay() -{ - // Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup. + void Screen::forceDisplay() + { + // Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup. #ifdef USE_EINK - static_cast(dispdev)->forceDisplay(); + static_cast(dispdev)->forceDisplay(); #endif -} + } -static uint32_t lastScreenTransition; + static uint32_t lastScreenTransition; -int32_t Screen::runOnce() -{ - // If we don't have a screen, don't ever spend any CPU for us. - if (!useDisplay) { - enabled = false; - return RUN_SAME; - } + int32_t Screen::runOnce() + { + // If we don't have a screen, don't ever spend any CPU for us. + if (!useDisplay) + { + enabled = false; + return RUN_SAME; + } - // Show boot screen for first logo_timeout seconds, then switch to normal operation. - // serialSinceMsec adjusts for additional serial wait time during nRF52 bootup - static bool showingBootScreen = true; - if (showingBootScreen && (millis() > (logo_timeout + serialSinceMsec))) { - LOG_INFO("Done with boot screen...\n"); - stopBootScreen(); - showingBootScreen = false; - } + // Show boot screen for first logo_timeout seconds, then switch to normal operation. + // serialSinceMsec adjusts for additional serial wait time during nRF52 bootup + static bool showingBootScreen = true; + if (showingBootScreen && (millis() > (logo_timeout + serialSinceMsec))) + { + LOG_INFO("Done with boot screen...\n"); + stopBootScreen(); + showingBootScreen = false; + } - // If we have an OEM Boot screen, toggle after logo_timeout seconds - if (strlen(oemStore.oem_text) > 0) { - static bool showingOEMBootScreen = true; - if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) { - LOG_INFO("Switch to OEM screen...\n"); - // Change frames. - static FrameCallback bootOEMFrames[] = {drawOEMBootScreen}; - static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]); - ui->setFrames(bootOEMFrames, bootOEMFrameCount); - ui->update(); + // If we have an OEM Boot screen, toggle after logo_timeout seconds + if (strlen(oemStore.oem_text) > 0) + { + static bool showingOEMBootScreen = true; + if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) + { + LOG_INFO("Switch to OEM screen...\n"); + // Change frames. + static FrameCallback bootOEMFrames[] = {drawOEMBootScreen}; + static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]); + ui->setFrames(bootOEMFrames, bootOEMFrameCount); + ui->update(); #ifndef USE_EINK - ui->update(); + ui->update(); #endif - showingOEMBootScreen = false; + showingOEMBootScreen = false; + } } - } #ifndef DISABLE_WELCOME_UNSET - if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { - setWelcomeFrames(); - } + if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) + { + setWelcomeFrames(); + } #endif - // Process incoming commands. - for (;;) { - ScreenCmd cmd; - if (!cmdQueue.dequeue(&cmd, 0)) { - break; - } - switch (cmd.cmd) { - case Cmd::SET_ON: - handleSetOn(true); - break; - case Cmd::SET_OFF: - handleSetOn(false); - break; - case Cmd::ON_PRESS: - // If a nag notification is running, stop it - if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) { - externalNotificationModule->stopNow(); - } else { - // Don't advance the screen if we just wanted to switch off the nag notification - handleOnPress(); + // Process incoming commands. + for (;;) + { + ScreenCmd cmd; + if (!cmdQueue.dequeue(&cmd, 0)) + { + break; + } + switch (cmd.cmd) + { + case Cmd::SET_ON: + handleSetOn(true); + break; + case Cmd::SET_OFF: + handleSetOn(false); + break; + case Cmd::ON_PRESS: + // If a nag notification is running, stop it + if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) + { + externalNotificationModule->stopNow(); + } + else + { + // Don't advance the screen if we just wanted to switch off the nag notification + handleOnPress(); + } + break; + case Cmd::SHOW_PREV_FRAME: + handleShowPrevFrame(); + break; + case Cmd::SHOW_NEXT_FRAME: + handleShowNextFrame(); + break; + case Cmd::START_BLUETOOTH_PIN_SCREEN: + handleStartBluetoothPinScreen(cmd.bluetooth_pin); + break; + case Cmd::START_FIRMWARE_UPDATE_SCREEN: + handleStartFirmwareUpdateScreen(); + break; + case Cmd::STOP_BLUETOOTH_PIN_SCREEN: + case Cmd::STOP_BOOT_SCREEN: + EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame + setFrames(); + break; + case Cmd::PRINT: + handlePrint(cmd.print_text); + free(cmd.print_text); + break; + case Cmd::START_SHUTDOWN_SCREEN: + handleShutdownScreen(); + break; + case Cmd::START_REBOOT_SCREEN: + handleRebootScreen(); + break; + default: + LOG_ERROR("Invalid screen cmd\n"); } - break; - case Cmd::SHOW_PREV_FRAME: - handleShowPrevFrame(); - break; - case Cmd::SHOW_NEXT_FRAME: - handleShowNextFrame(); - break; - case Cmd::START_BLUETOOTH_PIN_SCREEN: - handleStartBluetoothPinScreen(cmd.bluetooth_pin); - break; - case Cmd::START_FIRMWARE_UPDATE_SCREEN: - handleStartFirmwareUpdateScreen(); - break; - case Cmd::STOP_BLUETOOTH_PIN_SCREEN: - case Cmd::STOP_BOOT_SCREEN: - EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame - setFrames(); - break; - case Cmd::PRINT: - handlePrint(cmd.print_text); - free(cmd.print_text); - break; - case Cmd::START_SHUTDOWN_SCREEN: - handleShutdownScreen(); - break; - case Cmd::START_REBOOT_SCREEN: - handleRebootScreen(); - break; - default: - LOG_ERROR("Invalid screen cmd\n"); } - } - if (!screenOn) { // If we didn't just wake and the screen is still off, then - // stop updating until it is on again - enabled = false; - return 0; - } + if (!screenOn) + { // If we didn't just wake and the screen is still off, then + // stop updating until it is on again + enabled = false; + return 0; + } - // this must be before the frameState == FIXED check, because we always - // want to draw at least one FIXED frame before doing forceDisplay - ui->update(); + // this must be before the frameState == FIXED check, because we always + // want to draw at least one FIXED frame before doing forceDisplay + ui->update(); - // Switch to a low framerate (to save CPU) when we are not in transition - // but we should only call setTargetFPS when framestate changes, because - // otherwise that breaks animations. - if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) { - // oldFrameState = ui->getUiState()->frameState; - targetFramerate = IDLE_FRAMERATE; + // Switch to a low framerate (to save CPU) when we are not in transition + // but we should only call setTargetFPS when framestate changes, because + // otherwise that breaks animations. + if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) + { + // oldFrameState = ui->getUiState()->frameState; + targetFramerate = IDLE_FRAMERATE; - ui->setTargetFPS(targetFramerate); - forceDisplay(); - } + ui->setTargetFPS(targetFramerate); + forceDisplay(); + } - // While showing the bootscreen or Bluetooth pair screen all of our - // standard screen switching is stopped. - if (showingNormalScreen) { - // standard screen loop handling here - if (config.display.auto_screen_carousel_secs > 0 && - (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) { - LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition)); - handleOnPress(); + // While showing the bootscreen or Bluetooth pair screen all of our + // standard screen switching is stopped. + if (showingNormalScreen) + { + // standard screen loop handling here + if (config.display.auto_screen_carousel_secs > 0 && + (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) + { + LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition)); + handleOnPress(); + } } - } - // LOG_DEBUG("want fps %d, fixed=%d\n", targetFramerate, - // ui->getUiState()->frameState); If we are scrolling we need to be called - // soon, otherwise just 1 fps (to save CPU) We also ask to be called twice - // as fast as we really need so that any rounding errors still result with - // the correct framerate - return (1000 / targetFramerate); -} + // LOG_DEBUG("want fps %d, fixed=%d\n", targetFramerate, + // ui->getUiState()->frameState); If we are scrolling we need to be called + // soon, otherwise just 1 fps (to save CPU) We also ask to be called twice + // as fast as we really need so that any rounding errors still result with + // the correct framerate + return (1000 / targetFramerate); + } -void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - Screen *screen2 = reinterpret_cast(state->userData); - screen2->debugInfo.drawFrame(display, state, x, y); -} + void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + Screen *screen2 = reinterpret_cast(state->userData); + screen2->debugInfo.drawFrame(display, state, x, y); + } -void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - Screen *screen2 = reinterpret_cast(state->userData); - screen2->debugInfo.drawFrameSettings(display, state, x, y); -} + void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + Screen *screen2 = reinterpret_cast(state->userData); + screen2->debugInfo.drawFrameSettings(display, state, x, y); + } -void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - Screen *screen2 = reinterpret_cast(state->userData); - screen2->debugInfo.drawFrameWiFi(display, state, x, y); -} + void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + Screen *screen2 = reinterpret_cast(state->userData); + screen2->debugInfo.drawFrameWiFi(display, state, x, y); + } -/* show a message that the SSL cert is being built - * it is expected that this will be used during the boot phase */ -void Screen::setSSLFrames() -{ - if (address_found.address) { - // LOG_DEBUG("showing SSL frames\n"); - static FrameCallback sslFrames[] = {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::setSSLFrames() + { + if (address_found.address) + { + // LOG_DEBUG("showing SSL frames\n"); + static FrameCallback sslFrames[] = {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("showing Welcome frames\n"); - static FrameCallback frames[] = {drawWelcomeScreen}; - setFrameImmediateDraw(frames); + /* 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("showing Welcome frames\n"); + static FrameCallback frames[] = {drawWelcomeScreen}; + setFrameImmediateDraw(frames); + } } -} -// restore our regular frame list -void Screen::setFrames() -{ - LOG_DEBUG("showing standard frames\n"); - showingNormalScreen = true; + // restore our regular frame list + void Screen::setFrames() + { + LOG_DEBUG("showing standard frames\n"); + showingNormalScreen = true; - moduleFrames = MeshModule::GetMeshModulesWithUIFrames(); - LOG_DEBUG("Showing %d module frames\n", moduleFrames.size()); + moduleFrames = MeshModule::GetMeshModulesWithUIFrames(); + LOG_DEBUG("Showing %d module frames\n", moduleFrames.size()); #ifdef DEBUG_PORT - int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + moduleFrames.size(); - LOG_DEBUG("Total frame count: %d\n", totalFrameCount); + int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + moduleFrames.size(); + LOG_DEBUG("Total frame count: %d\n", totalFrameCount); #endif - // We don't show the node info our our node (if we have it yet - we should) - size_t numMeshNodes = nodeDB.getNumMeshNodes(); - if (numMeshNodes > 0) - numMeshNodes--; - - size_t numframes = 0; - - // put all of the module frames first. - // this is a little bit of a dirty hack; since we're going to call - // the same drawModuleFrame handler here for all of these module frames - // and then we'll just assume that the state->currentFrame value - // is the same offset into the moduleFrames vector - // so that we can invoke the module's callback - for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) { - normalFrames[numframes++] = drawModuleFrame; - } + // We don't show the node info our our node (if we have it yet - we should) + size_t numMeshNodes = nodeDB.getNumMeshNodes(); + if (numMeshNodes > 0) + numMeshNodes--; + + size_t numframes = 0; + + // put all of the module frames first. + // this is a little bit of a dirty hack; since we're going to call + // the same drawModuleFrame handler here for all of these module frames + // and then we'll just assume that the state->currentFrame value + // is the same offset into the moduleFrames vector + // so that we can invoke the module's callback + for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) + { + normalFrames[numframes++] = drawModuleFrame; + } - LOG_DEBUG("Added modules. numframes: %d\n", numframes); + LOG_DEBUG("Added modules. numframes: %d\n", numframes); - // If we have a critical fault, show it first - if (error_code) - normalFrames[numframes++] = drawCriticalFaultFrame; + // If we have a critical fault, show it first + if (error_code) + normalFrames[numframes++] = drawCriticalFaultFrame; - // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules - if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { - normalFrames[numframes++] = drawTextMessageFrame; - } - // If we have a waypoint - show it next, unless it's a phone message and we aren't using any special modules - if (devicestate.has_rx_waypoint && shouldDrawMessage(&devicestate.rx_waypoint)) { - normalFrames[numframes++] = drawWaypointFrame; - } + // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules + if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) + { + normalFrames[numframes++] = drawTextMessageFrame; + } + // If we have a waypoint - show it next, unless it's a phone message and we aren't using any special modules + if (devicestate.has_rx_waypoint && shouldDrawMessage(&devicestate.rx_waypoint)) + { + normalFrames[numframes++] = drawWaypointFrame; + } - // then all the nodes - // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens - size_t numToShow = min(numMeshNodes, 4U); - for (size_t i = 0; i < numToShow; i++) - normalFrames[numframes++] = drawNodeInfo; + // then all the nodes + // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens + size_t numToShow = min(numMeshNodes, 4U); + for (size_t i = 0; i < numToShow; i++) + normalFrames[numframes++] = drawNodeInfo; - // then the debug info - // - // Since frames are basic function pointers, we have to use a helper to - // call a method on debugInfo object. - normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; + // then the debug info + // + // Since frames are basic function pointers, we have to use a helper to + // call a method on debugInfo object. + normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; - // call a method on debugInfoScreen object (for more details) - normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; + // call a method on debugInfoScreen object (for more details) + normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (isWifiAvailable()) { - // call a method on debugInfoScreen object (for more details) - normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline; - } + if (isWifiAvailable()) + { + // call a method on debugInfoScreen object (for more details) + normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline; + } #endif - LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); + LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); - ui->setFrames(normalFrames, numframes); - ui->enableAllIndicators(); + ui->setFrames(normalFrames, numframes); + ui->enableAllIndicators(); - prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list - // just changed) + prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list + // just changed) - setFastFramerate(); // Draw ASAP -} - -void Screen::handleStartBluetoothPinScreen(uint32_t pin) -{ - LOG_DEBUG("showing bluetooth screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + setFastFramerate(); // Draw ASAP + } - static FrameCallback frames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06u", pin); - setFrameImmediateDraw(frames); -} + void Screen::handleStartBluetoothPinScreen(uint32_t pin) + { + LOG_DEBUG("showing bluetooth screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame -void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) -{ - ui->disableAllIndicators(); - ui->setFrames(drawFrames, 1); - setFastFramerate(); -} + static FrameCallback frames[] = {drawFrameBluetooth}; + snprintf(btPIN, sizeof(btPIN), "%06u", pin); + setFrameImmediateDraw(frames); + } -void Screen::handleShutdownScreen() -{ - LOG_DEBUG("showing shutdown screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) + { + ui->disableAllIndicators(); + ui->setFrames(drawFrames, 1); + setFastFramerate(); + } - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - drawFrameText(display, state, x, y, "Shutting down..."); - }; - static FrameCallback frames[] = {frame}; + void Screen::handleShutdownScreen() + { + LOG_DEBUG("showing shutdown screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - setFrameImmediateDraw(frames); -} + auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void + { + drawFrameText(display, state, x, y, "Shutting down..."); + }; + static FrameCallback frames[] = {frame}; -void Screen::handleRebootScreen() -{ - LOG_DEBUG("showing reboot screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + setFrameImmediateDraw(frames); + } - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - drawFrameText(display, state, x, y, "Rebooting..."); - }; - static FrameCallback frames[] = {frame}; - setFrameImmediateDraw(frames); -} + void Screen::handleRebootScreen() + { + LOG_DEBUG("showing reboot screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + + auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void + { + drawFrameText(display, state, x, y, "Rebooting..."); + }; + static FrameCallback frames[] = {frame}; + setFrameImmediateDraw(frames); + } -void Screen::handleStartFirmwareUpdateScreen() -{ - LOG_DEBUG("showing firmware screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + void Screen::handleStartFirmwareUpdateScreen() + { + LOG_DEBUG("showing firmware screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - static FrameCallback frames[] = {drawFrameFirmware}; - setFrameImmediateDraw(frames); -} + static FrameCallback frames[] = {drawFrameFirmware}; + setFrameImmediateDraw(frames); + } -void Screen::blink() -{ - setFastFramerate(); - uint8_t count = 10; - dispdev->setBrightness(254); - while (count > 0) { - dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - dispdev->display(); - delay(50); - dispdev->clear(); - dispdev->display(); - delay(50); - count = count - 1; + void Screen::blink() + { + setFastFramerate(); + uint8_t count = 10; + dispdev->setBrightness(254); + while (count > 0) + { + dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + dispdev->display(); + delay(50); + dispdev->clear(); + dispdev->display(); + delay(50); + count = count - 1; + } + dispdev->setBrightness(brightness); } - dispdev->setBrightness(brightness); -} -std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds) -{ - std::string uptime; - - if (days > (hours_in_month * 6)) - uptime = "?"; - else if (days >= 2) - uptime = std::to_string(days) + "d"; - else if (hours >= 2) - uptime = std::to_string(hours) + "h"; - else if (minutes >= 1) - uptime = std::to_string(minutes) + "m"; - else - uptime = std::to_string(seconds) + "s"; - return uptime; -} - -void Screen::handlePrint(const char *text) -{ - // the string passed into us probably has a newline, but that would confuse the logging system - // so strip it - LOG_DEBUG("Screen: %.*s\n", strlen(text) - 1, text); - if (!useDisplay || !showingNormalScreen) - return; + std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds) + { + std::string uptime; + + if (days > (hours_in_month * 6)) + uptime = "?"; + else if (days >= 2) + uptime = std::to_string(days) + "d"; + else if (hours >= 2) + uptime = std::to_string(hours) + "h"; + else if (minutes >= 1) + uptime = std::to_string(minutes) + "m"; + else + uptime = std::to_string(seconds) + "s"; + return uptime; + } + + void Screen::handlePrint(const char *text) + { + // the string passed into us probably has a newline, but that would confuse the logging system + // so strip it + LOG_DEBUG("Screen: %.*s\n", strlen(text) - 1, text); + if (!useDisplay || !showingNormalScreen) + return; - dispdev->print(text); -} + dispdev->print(text); + } -void Screen::handleOnPress() -{ - // If screen was off, just wake it, otherwise advance to next frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) { - ui->nextFrame(); - lastScreenTransition = millis(); - setFastFramerate(); + void Screen::handleOnPress() + { + // If screen was off, just wake it, otherwise advance to next frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) + { + ui->nextFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } } -} -void Screen::handleShowPrevFrame() -{ - // If screen was off, just wake it, otherwise go back to previous frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) { - ui->previousFrame(); - lastScreenTransition = millis(); - setFastFramerate(); + void Screen::handleShowPrevFrame() + { + // If screen was off, just wake it, otherwise go back to previous frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) + { + ui->previousFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } } -} -void Screen::handleShowNextFrame() -{ - // If screen was off, just wake it, otherwise advance to next frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) { - ui->nextFrame(); - lastScreenTransition = millis(); - setFastFramerate(); + void Screen::handleShowNextFrame() + { + // If screen was off, just wake it, otherwise advance to next frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) + { + ui->nextFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } } -} #ifndef SCREEN_TRANSITION_FRAMERATE #define SCREEN_TRANSITION_FRAMERATE 30 // fps #endif -void Screen::setFastFramerate() -{ - // We are about to start a transition so speed up fps - targetFramerate = SCREEN_TRANSITION_FRAMERATE; + void Screen::setFastFramerate() + { + // We are about to start a transition so speed up fps + targetFramerate = SCREEN_TRANSITION_FRAMERATE; - ui->setTargetFPS(targetFramerate); - setInterval(0); // redraw ASAP - runASAP = true; -} + ui->setTargetFPS(targetFramerate); + setInterval(0); // redraw ASAP + runASAP = true; + } -void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setFont(FONT_SMALL); + void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + display->setFont(FONT_SMALL); - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - char channelStr[20]; - { - concurrency::LockGuard guard(&lock); - auto chName = channels.getPrimaryName(); - snprintf(channelStr, sizeof(channelStr), "%s", chName); - } + char channelStr[20]; + { + concurrency::LockGuard guard(&lock); + snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex())); + } - // Display power status - if (powerStatus->getHasBattery()) { - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - drawBattery(display, x, y + 2, imgBattery, powerStatus); - } else { - drawBattery(display, x + 1, y + 3, imgBattery, powerStatus); + // Display power status + if (powerStatus->getHasBattery()) + { + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + { + drawBattery(display, x, y + 2, imgBattery, powerStatus); + } + else + { + drawBattery(display, x + 1, y + 3, imgBattery, powerStatus); + } } - } else if (powerStatus->knowsUSB()) { - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); - } else { - display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); + else if (powerStatus->knowsUSB()) + { + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + { + display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); + } + else + { + display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); + } } - } - // Display nodes status - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus); - } else { - drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); - } - // Display GPS status - if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { - drawGPSpowerstat(display, x, y + 2, gpsStatus); - } else { - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); - } else { - drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); + // Display nodes status + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + { + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus); + } + else + { + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); + } + // Display GPS status + if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) + { + drawGPSpowerstat(display, x, y + 2, gpsStatus); + } + else + { + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + { + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); + } + else + { + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); + } } - } - display->setColor(WHITE); - // Draw the channel name - display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); - // Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo - if (moduleConfig.store_forward.enabled) { + display->setColor(WHITE); + // Draw the channel name + display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); + // Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo + if (moduleConfig.store_forward.enabled) + { #ifdef ARCH_ESP32 - if (millis() - storeForwardModule->lastHeartbeat > - (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + if (millis() - storeForwardModule->lastHeartbeat > + (storeForwardModule->heartbeatInterval * 1200)) + { // no heartbeat, overlap a bit +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, - imgQuestionL1); - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, - imgQuestionL2); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, + imgQuestionL1); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, + imgQuestionL2); #else - display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, - imgQuestion); + display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, + imgQuestion); #endif - } else { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + } + else + { +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, - imgSFL1); - display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8, - imgSFL2); + display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, + imgSFL1); + display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8, + imgSFL2); #else - display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8, - imgSF); + display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8, + imgSF); #endif - } + } #endif - } else { - // TODO: Raspberry Pi supports more than just the one screen size -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \ + } + else + { + // TODO: Raspberry Pi supports more than just the one screen size +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, - imgInfoL1); - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, - imgInfoL2); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, + imgInfoL1); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, + imgInfoL2); #else - display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo); + display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo); #endif - } + } - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId); - // Draw any log messages - display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2)); + // Draw any log messages + display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2)); - /* Display a heartbeat pixel that blinks every time the frame is redrawn */ + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS - if (heartbeat) - display->setPixel(0, 0); - heartbeat = !heartbeat; + if (heartbeat) + display->setPixel(0, 0); + heartbeat = !heartbeat; #endif -} + } -// Jm -void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ + // Jm + void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { #if HAS_WIFI && !defined(ARCH_PORTDUINO) - const char *wifiName = config.network.wifi_ssid; + const char *wifiName = config.network.wifi_ssid; - display->setFont(FONT_SMALL); + display->setFont(FONT_SMALL); - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - if (WiFi.status() != WL_CONNECTED) { - display->drawString(x, y, String("WiFi: Not Connected")); - if (config.display.heading_bold) - display->drawString(x + 1, y, String("WiFi: Not Connected")); - } else { - display->drawString(x, y, String("WiFi: Connected")); - if (config.display.heading_bold) - display->drawString(x + 1, y, String("WiFi: Connected")); + if (WiFi.status() != WL_CONNECTED) + { + display->drawString(x, y, String("WiFi: Not Connected")); + if (config.display.heading_bold) + display->drawString(x + 1, y, String("WiFi: Not Connected")); + } + else + { + display->drawString(x, y, String("WiFi: Connected")); + if (config.display.heading_bold) + display->drawString(x + 1, y, String("WiFi: Connected")); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, - "RSSI " + String(WiFi.RSSI())); - if (config.display.heading_bold) { - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, "RSSI " + String(WiFi.RSSI())); + if (config.display.heading_bold) + { + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, + "RSSI " + String(WiFi.RSSI())); + } } - } - display->setColor(WHITE); - - /* - - WL_CONNECTED: assigned when connected to a WiFi network; - - WL_NO_SSID_AVAIL: assigned when no SSID are available; - - WL_CONNECT_FAILED: assigned when the connection fails for all the attempts; - - WL_CONNECTION_LOST: assigned when the connection is lost; - - WL_DISCONNECTED: assigned when disconnected from a network; - - WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of - attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED); - - WL_SCAN_COMPLETED: assigned when the scan networks is completed; - - WL_NO_SHIELD: assigned when no WiFi shield is present; - - */ - if (WiFi.status() == WL_CONNECTED) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str())); - } else if (WiFi.status() == WL_NO_SSID_AVAIL) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found"); - } else if (WiFi.status() == WL_CONNECTION_LOST) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost"); - } else if (WiFi.status() == WL_CONNECT_FAILED) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed"); - } else if (WiFi.status() == WL_IDLE_STATUS) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting"); - } + display->setColor(WHITE); + + /* + - WL_CONNECTED: assigned when connected to a WiFi network; + - WL_NO_SSID_AVAIL: assigned when no SSID are available; + - WL_CONNECT_FAILED: assigned when the connection fails for all the attempts; + - WL_CONNECTION_LOST: assigned when the connection is lost; + - WL_DISCONNECTED: assigned when disconnected from a network; + - WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of + attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED); + - WL_SCAN_COMPLETED: assigned when the scan networks is completed; + - WL_NO_SHIELD: assigned when no WiFi shield is present; + + */ + if (WiFi.status() == WL_CONNECTED) + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str())); + } + else if (WiFi.status() == WL_NO_SSID_AVAIL) + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found"); + } + else if (WiFi.status() == WL_CONNECTION_LOST) + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost"); + } + else if (WiFi.status() == WL_CONNECT_FAILED) + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed"); + } + else if (WiFi.status() == WL_IDLE_STATUS) + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting"); + } #ifdef ARCH_ESP32 - else { - // Codes: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, - WiFi.disconnectReasonName(static_cast(getWifiDisconnectReason()))); - } + else + { + // Codes: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, + WiFi.disconnectReasonName(static_cast(getWifiDisconnectReason()))); + } #else - else { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status())); - } + else + { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status())); + } #endif - display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName)); + display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName)); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local"); - /* Display a heartbeat pixel that blinks every time the frame is redrawn */ + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS - if (heartbeat) - display->setPixel(0, 0); - heartbeat = !heartbeat; + if (heartbeat) + display->setPixel(0, 0); + heartbeat = !heartbeat; #endif #endif -} + } -void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setFont(FONT_SMALL); + void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + display->setFont(FONT_SMALL); - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - char batStr[20]; - if (powerStatus->getHasBattery()) { - int batV = powerStatus->getBatteryVoltageMv() / 1000; - int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10; + char batStr[20]; + if (powerStatus->getHasBattery()) + { + int batV = powerStatus->getBatteryVoltageMv() / 1000; + int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10; - snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(), - powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' '); + snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(), + powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' '); - // Line 1 - display->drawString(x, y, batStr); - if (config.display.heading_bold) - display->drawString(x + 1, y, batStr); - } else { - // Line 1 - display->drawString(x, y, String("USB")); + // Line 1 + display->drawString(x, y, batStr); + if (config.display.heading_bold) + display->drawString(x + 1, y, batStr); + } + else + { + // Line 1 + display->drawString(x, y, String("USB")); + if (config.display.heading_bold) + display->drawString(x + 1, y, String("USB")); + } + + auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true); + + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode); if (config.display.heading_bold) - display->drawString(x + 1, y, String("USB")); - } + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode); + + // Line 2 + uint32_t currentMillis = millis(); + uint32_t seconds = currentMillis / 1000; + uint32_t minutes = seconds / 60; + uint32_t hours = minutes / 60; + uint32_t days = hours / 24; + // currentMillis %= 1000; + // seconds %= 60; + // minutes %= 60; + // hours %= 24; - auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true); - - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode); - if (config.display.heading_bold) - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode); - - // Line 2 - uint32_t currentMillis = millis(); - uint32_t seconds = currentMillis / 1000; - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; - // currentMillis %= 1000; - // seconds %= 60; - // minutes %= 60; - // hours %= 24; - - display->setColor(WHITE); - - // Show uptime as days, hours, minutes OR seconds - std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds); - - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); - if (rtc_sec > 0) { - long hms = rtc_sec % SEC_PER_DAY; - // hms += tz.tz_dsttime * SEC_PER_HOUR; - // hms -= tz.tz_minuteswest * SEC_PER_MIN; - // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) - hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; - - // Tear apart hms into h:m:s - int hour = hms / SEC_PER_HOUR; - int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; - int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN - - char timebuf[10]; - snprintf(timebuf, sizeof(timebuf), " %02d:%02d:%02d", hour, min, sec); - uptime += timebuf; - } + display->setColor(WHITE); - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, uptime.c_str()); - - // Display Channel Utilization - char chUtil[13]; - snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent()); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil); - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { - // Line 3 - if (config.display.gps_format != - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude - drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); - - // Line 4 - drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus); - } else { - drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); - } - /* Display a heartbeat pixel that blinks every time the frame is redrawn */ + // Show uptime as days, hours, minutes OR seconds + std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds); + + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); + if (rtc_sec > 0) + { + long hms = rtc_sec % SEC_PER_DAY; + // hms += tz.tz_dsttime * SEC_PER_HOUR; + // hms -= tz.tz_minuteswest * SEC_PER_MIN; + // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + // Tear apart hms into h:m:s + int hour = hms / SEC_PER_HOUR; + int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN + + char timebuf[10]; + snprintf(timebuf, sizeof(timebuf), " %02d:%02d:%02d", hour, min, sec); + uptime += timebuf; + } + + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, uptime.c_str()); + + // Display Channel Utilization + char chUtil[13]; + snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent()); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil); + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) + { + // Line 3 + if (config.display.gps_format != + meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude + drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); + + // Line 4 + drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus); + } + else + { + drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); + } + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS - if (heartbeat) - display->setPixel(0, 0); - heartbeat = !heartbeat; + if (heartbeat) + display->setPixel(0, 0); + heartbeat = !heartbeat; #endif -} - -int Screen::handleStatusUpdate(const meshtastic::Status *arg) -{ - // LOG_DEBUG("Screen got status update %d\n", arg->getStatusType()); - switch (arg->getStatusType()) { - case STATUS_TYPE_NODE: - if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { - setFrames(); // Regen the list of screens - } - nodeDB.updateGUI = false; - break; } - return 0; -} + int Screen::handleStatusUpdate(const meshtastic::Status *arg) + { + // LOG_DEBUG("Screen got status update %d\n", arg->getStatusType()); + switch (arg->getStatusType()) + { + case STATUS_TYPE_NODE: + if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) + { + setFrames(); // Regen the list of screens + } + nodeDB.updateGUI = false; + break; + } -int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) -{ - if (showingNormalScreen) { - setFrames(); // Regen the list of screens (will show new text message) + return 0; } - return 0; -} - -int Screen::handleUIFrameEvent(const UIFrameEvent *event) -{ - if (showingNormalScreen) { - if (event->frameChanged) { + int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) + { + if (showingNormalScreen) + { setFrames(); // Regen the list of screens (will show new text message) - } else if (event->needRedraw) { - setFastFramerate(); - // TODO: We might also want switch to corresponding frame, - // but we don't know the exact frame number. - // ui->switchToFrame(0); } - } - return 0; -} + return 0; + } -int Screen::handleInputEvent(const InputEvent *event) -{ - if (showingNormalScreen && moduleFrames.size() == 0) { - // LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source); - if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { - showPrevFrame(); - } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { - showNextFrame(); + int Screen::handleUIFrameEvent(const UIFrameEvent *event) + { + if (showingNormalScreen) + { + if (event->frameChanged) + { + setFrames(); // Regen the list of screens (will show new text message) + } + else if (event->needRedraw) + { + setFastFramerate(); + // TODO: We might also want switch to corresponding frame, + // but we don't know the exact frame number. + // ui->switchToFrame(0); + } } + + return 0; } - return 0; -} + int Screen::handleInputEvent(const InputEvent *event) + { + if (showingNormalScreen && moduleFrames.size() == 0) + { + // LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source); + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) + { + showPrevFrame(); + } + else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) + { + showNextFrame(); + } + } + + return 0; + } } // namespace graphics #else diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 93dec7e7d3..840e65bca8 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -290,40 +290,6 @@ bool Channels::hasDefaultChannel() return false; } -/** -* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs. -* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they -their nodes -* aren't talking to each other. -* -* This string is of the form "#name-X". -* -* Where X is either: -* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together, -* -* This function will also need to be implemented in GUI apps that talk to the radio. -* -* https://github.com/meshtastic/firmware/issues/269 -*/ -const char *Channels::getPrimaryName() -{ - static char buf[32]; - - char suffix; - // auto channelSettings = getPrimary(); - // if (channelSettings.psk.size != 1) { - // We have a standard PSK, so generate a letter based hash. - uint8_t code = getHash(primaryIndex); - - suffix = 'A' + (code % 26); - /* } else { - suffix = '0' + channelSettings.psk.bytes[0]; - } */ - - snprintf(buf, sizeof(buf), "#%s-%c", getName(primaryIndex), suffix); - return buf; -} - /** Given a channel hash setup crypto for decoding that channel (or the primary channel if that channel is unsecured) * * This method is called before decoding inbound packets diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index a1c4ba1711..952445a1da 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -61,25 +61,6 @@ class Channels ChannelIndex getNumChannels() { return channelFile.channels_count; } - /** - * Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different - PSKs. - * The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why - they their nodes - * aren't talking to each other. - * - * This string is of the form "#name-X". - * - * Where X is either: - * (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together, - * OR (for the standard minimially secure PSKs) a number from 0 to 9. - * - * This function will also need to be implemented in GUI apps that talk to the radio. - * - * https://github.com/meshtastic/firmware/issues/269 - */ - const char *getPrimaryName(); - /// Called by NodeDB on initial boot when the radio config settings are unset. Set a default single channel config. void initDefaults(); From 2c46e54b659960b83bb75f929b14e15db13243e7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 17 Mar 2024 08:03:14 -0500 Subject: [PATCH 2/2] Missed it --- src/graphics/Screen.cpp | 3029 ++++++++++++++++++--------------------- 1 file changed, 1422 insertions(+), 1607 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 9224072140..cfd8494d2f 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -67,39 +67,39 @@ namespace graphics // DEBUG #define NUM_EXTRA_FRAMES 3 // text message and debug frame - // if defined a pixel will blink to show redraws - // #define SHOW_REDRAWS +// if defined a pixel will blink to show redraws +// #define SHOW_REDRAWS - // A text message frame + debug frame + all the node infos - static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; - static uint32_t targetFramerate = IDLE_FRAMERATE; - static char btPIN[16] = "888888"; +// A text message frame + debug frame + all the node infos +static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; +static uint32_t targetFramerate = IDLE_FRAMERATE; +static char btPIN[16] = "888888"; - uint32_t logo_timeout = 5000; // 4 seconds for EACH logo +uint32_t logo_timeout = 5000; // 4 seconds for EACH logo - uint32_t hours_in_month = 730; +uint32_t hours_in_month = 730; - // This image definition is here instead of images.h because it's modified dynamically by the drawBattery function - uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C}; +// This image definition is here instead of images.h because it's modified dynamically by the drawBattery function +uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C}; - // Threshold values for the GPS lock accuracy bar display - uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100}; +// Threshold values for the GPS lock accuracy bar display +uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100}; - // At some point, we're going to ask all of the modules if they would like to display a screen frame - // we'll need to hold onto pointers for the modules that can draw a frame. - std::vector moduleFrames; +// At some point, we're going to ask all of the modules if they would like to display a screen frame +// we'll need to hold onto pointers for the modules that can draw a frame. +std::vector moduleFrames; - // Stores the last 4 of our hardware ID, to make finding the device for pairing easier - static char ourId[5]; +// Stores the last 4 of our hardware ID, to make finding the device for pairing easier +static char ourId[5]; - // GeoCoord object for the screen - GeoCoord geoCoord; +// GeoCoord object for the screen +GeoCoord geoCoord; #ifdef SHOW_REDRAWS - static bool heartbeat = false; +static bool heartbeat = false; #endif - static uint16_t displayWidth, displayHeight; +static uint16_t displayWidth, displayHeight; #define SCREEN_WIDTH displayWidth #define SCREEN_HEIGHT displayHeight @@ -108,1921 +108,1736 @@ namespace graphics #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) - /** - * Draw the icon with extra info printed around the corners - */ - static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - // draw an xbm image. - // Please note that everything that should be transitioned - // needs to be drawn relative to x and y +/** + * Draw the icon with extra info printed around the corners + */ +static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // draw an xbm image. + // Please note that everything that should be transitioned + // needs to be drawn relative to x and y + + // draw centered icon left to right and centered above the one line of app text + display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2, + icon_width, icon_height, icon_bits); + + display->setFont(FONT_MEDIUM); + display->setTextAlignment(TEXT_ALIGN_LEFT); + const char *title = "meshtastic.org"; + display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + display->setFont(FONT_SMALL); + + // Draw region in upper left + if (upperMsg) + display->drawString(x + 0, y + 0, upperMsg); + + // Draw version in upper right + char buf[16]; + snprintf(buf, sizeof(buf), "%s", + xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + screen->forceDisplay(); + // FIXME - draw serial # somewhere? +} + +static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // draw an xbm image. + // Please note that everything that should be transitioned + // needs to be drawn relative to x and y - // draw centered icon left to right and centered above the one line of app text - display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2, - icon_width, icon_height, icon_bits); + // draw centered icon left to right and centered above the one line of app text + display->drawXbm(x + (SCREEN_WIDTH - oemStore.oem_icon_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - oemStore.oem_icon_height) / 2 + 2, oemStore.oem_icon_width, + oemStore.oem_icon_height, (const uint8_t *)oemStore.oem_icon_bits.bytes); - display->setFont(FONT_MEDIUM); - display->setTextAlignment(TEXT_ALIGN_LEFT); - const char *title = "meshtastic.org"; - display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + switch (oemStore.oem_font) { + case 0: display->setFont(FONT_SMALL); - - // Draw region in upper left - if (upperMsg) - display->drawString(x + 0, y + 0, upperMsg); - - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); - screen->forceDisplay(); - // FIXME - draw serial # somewhere? + break; + case 2: + display->setFont(FONT_LARGE); + break; + default: + display->setFont(FONT_MEDIUM); + break; } - static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - // draw an xbm image. - // Please note that everything that should be transitioned - // needs to be drawn relative to x and y - - // draw centered icon left to right and centered above the one line of app text - display->drawXbm(x + (SCREEN_WIDTH - oemStore.oem_icon_width) / 2, - y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - oemStore.oem_icon_height) / 2 + 2, oemStore.oem_icon_width, - oemStore.oem_icon_height, (const uint8_t *)oemStore.oem_icon_bits.bytes); - - switch (oemStore.oem_font) - { - case 0: - display->setFont(FONT_SMALL); - break; - case 2: - display->setFont(FONT_LARGE); - break; - default: - display->setFont(FONT_MEDIUM); - break; - } + display->setTextAlignment(TEXT_ALIGN_LEFT); + const char *title = oemStore.oem_text; + display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); - const char *title = oemStore.oem_text; - display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); - display->setFont(FONT_SMALL); + // Draw region in upper left + if (upperMsg) + display->drawString(x + 0, y + 0, upperMsg); - // Draw region in upper left - if (upperMsg) - display->drawString(x + 0, y + 0, upperMsg); - - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); - screen->forceDisplay(); + // Draw version in upper right + char buf[16]; + snprintf(buf, sizeof(buf), "%s", + xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + screen->forceDisplay(); - // FIXME - draw serial # somewhere? - } + // FIXME - draw serial # somewhere? +} - static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - // Draw region in upper left - const char *region = myRegion ? myRegion->name : NULL; - drawOEMIconScreen(region, display, state, x, y); - } +static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // Draw region in upper left + const char *region = myRegion ? myRegion->name : NULL; + drawOEMIconScreen(region, display, state, x, y); +} - static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) - { - uint16_t x_offset = display->width() / 2; - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(x_offset + x, 26 + y, message); - } +static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) +{ + uint16_t x_offset = display->width() / 2; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, 26 + y, message); +} - static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { +static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ #ifdef ARCH_ESP32 - if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) - { - drawFrameText(display, state, x, y, "Resuming..."); - } - else + if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { + drawFrameText(display, state, x, y, "Resuming..."); + } else #endif - { - // Draw region in upper left - const char *region = myRegion ? myRegion->name : NULL; - drawIconScreen(region, display, state, x, y); - } + { + // Draw region in upper left + const char *region = myRegion ? myRegion->name : NULL; + drawIconScreen(region, display, state, x, y); } +} - // Used on boot when a certificate is being created - static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_SMALL); - display->drawString(64 + x, y, "Creating SSL certificate"); +// Used on boot when a certificate is being created +static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_SMALL); + display->drawString(64 + x, y, "Creating SSL certificate"); #ifdef ARCH_ESP32 - yield(); - esp_task_wdt_reset(); + yield(); + esp_task_wdt_reset(); #endif - display->setFont(FONT_SMALL); - if ((millis() / 1000) % 2) - { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); - } - else - { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); - } + display->setFont(FONT_SMALL); + if ((millis() / 1000) % 2) { + display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); + } else { + display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); } +} - // Used when booting without a region set - static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C"); - display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName()); - display->setTextAlignment(TEXT_ALIGN_LEFT); - - if ((millis() / 10000) % 2) - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); - } - else - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, ""); - } +// Used when booting without a region set +static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C"); + display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName()); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + if ((millis() / 10000) % 2) { + display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); + } else { + display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, ""); + } #ifdef ARCH_ESP32 - yield(); - esp_task_wdt_reset(); + yield(); + esp_task_wdt_reset(); #endif - } +} #ifdef USE_EINK - /// Used on eink displays while in deep sleep - static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - // Next frame should use full-refresh, and block while running, else device will sleep before async callback - EINK_ADD_FRAMEFLAG(display, COSMETIC); - EINK_ADD_FRAMEFLAG(display, BLOCKING); +/// Used on eink displays while in deep sleep +static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // Next frame should use full-refresh, and block while running, else device will sleep before async callback + EINK_ADD_FRAMEFLAG(display, COSMETIC); + EINK_ADD_FRAMEFLAG(display, BLOCKING); - drawIconScreen("Sleeping...", display, state, x, y); - } + drawIconScreen("Sleeping...", display, state, x, y); +} #endif - static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - uint8_t module_frame; - // there's a little but in the UI transition code - // where it invokes the function at the correct offset - // in the array of "drawScreen" functions; however, - // the passed-state doesn't quite reflect the "current" - // screen, so we have to detect it. - if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) - { - // if we're transitioning from the end of the frame list back around to the first - // frame, then we want this to be `0` - module_frame = state->transitionFrameTarget; - } - else - { - // otherwise, just display the module frame that's aligned with the current frame - module_frame = state->currentFrame; - // LOG_DEBUG("Screen is not in transition. Frame: %d\n\n", module_frame); - } - // LOG_DEBUG("Drawing Module Frame %d\n\n", module_frame); - MeshModule &pi = *moduleFrames.at(module_frame); - pi.drawFrame(display, state, x, y); +static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + uint8_t module_frame; + // there's a little but in the UI transition code + // where it invokes the function at the correct offset + // in the array of "drawScreen" functions; however, + // the passed-state doesn't quite reflect the "current" + // screen, so we have to detect it. + if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) { + // if we're transitioning from the end of the frame list back around to the first + // frame, then we want this to be `0` + module_frame = state->transitionFrameTarget; + } else { + // otherwise, just display the module frame that's aligned with the current frame + module_frame = state->currentFrame; + // LOG_DEBUG("Screen is not in transition. Frame: %d\n\n", module_frame); } + // LOG_DEBUG("Drawing Module Frame %d\n\n", module_frame); + MeshModule &pi = *moduleFrames.at(module_frame); + pi.drawFrame(display, state, x, y); +} - static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(x_offset + x, y_offset + y, "Bluetooth"); - - display->setFont(FONT_SMALL); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; - display->drawString(x_offset + x, y_offset + y, "Enter this code"); - - display->setFont(FONT_LARGE); - String displayPin(btPIN); - String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; - display->drawString(x_offset + x, y_offset + y, pin); +static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, y_offset + y, "Bluetooth"); + + display->setFont(FONT_SMALL); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; + display->drawString(x_offset + x, y_offset + y, "Enter this code"); + + display->setFont(FONT_LARGE); + String displayPin(btPIN); + String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; + display->drawString(x_offset + x, y_offset + y, pin); + + display->setFont(FONT_SMALL); + String deviceName = "Name: "; + deviceName.concat(getDeviceName()); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; + display->drawString(x_offset + x, y_offset + y, deviceName); +} + +static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(64 + x, y, "Updating"); + + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), + "Please be patient and do not power off."); +} + +/// Draw the last text message we received +static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + + char tempBuf[24]; + snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code); + display->drawString(0 + x, 0 + y, tempBuf); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org"); +} + +// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled +static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) +{ + return packet->from != 0 && !moduleConfig.store_forward.enabled; +} - display->setFont(FONT_SMALL); - String deviceName = "Name: "; - deviceName.concat(getDeviceName()); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; - display->drawString(x_offset + x, y_offset + y, deviceName); +/// Draw the last text message we received +static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // the max length of this buffer is much longer than we can possibly print + static char tempBuf[237]; + + const meshtastic_MeshPacket &mp = devicestate.rx_text_message; + meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + // LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from, + // mp.decoded.variant.data.decoded.bytes); + + // Demo for drawStringMaxWidth: + // with the third parameter you can define the width after which words will + // be wrapped. Currently only spaces and "-" are allowed for wrapping + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); } - static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(64 + x, y, "Updating"); + uint32_t seconds = sinceReceived(&mp); + uint32_t minutes = seconds / 60; + uint32_t hours = minutes / 60; + uint32_t days = hours / 24; - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), - "Please be patient and do not power off."); + if (config.display.heading_bold) { + display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", + screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); } + display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); - /// Draw the last text message we received - static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); + display->setColor(WHITE); + snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); + display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); +} - char tempBuf[24]; - snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code); - display->drawString(0 + x, 0 + y, tempBuf); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org"); - } +/// Draw the last waypoint we received +static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + static char tempBuf[237]; - // Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled - static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) - { - return packet->from != 0 && !moduleConfig.store_forward.enabled; - } + meshtastic_MeshPacket &mp = devicestate.rx_waypoint; + meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); - /// Draw the last text message we received - static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - // the max length of this buffer is much longer than we can possibly print - static char tempBuf[237]; - - const meshtastic_MeshPacket &mp = devicestate.rx_text_message; - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); - // LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from, - // mp.decoded.variant.data.decoded.bytes); - - // Demo for drawStringMaxWidth: - // with the third parameter you can define the width after which words will - // be wrapped. Currently only spaces and "-" are allowed for wrapping - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - uint32_t seconds = sinceReceived(&mp); - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; + uint32_t seconds = sinceReceived(&mp); + uint32_t minutes = seconds / 60; + uint32_t hours = minutes / 60; + uint32_t days = hours / 24; - if (config.display.heading_bold) - { - display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", - screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - } - display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + if (config.display.heading_bold) { + display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", + screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), (node && node->has_user) ? node->user.short_name : "???"); - - display->setColor(WHITE); - snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); + } + display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); + + display->setColor(WHITE); + meshtastic_Waypoint scratch; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { + snprintf(tempBuf, sizeof(tempBuf), "Received waypoint: %s", scratch.name); display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); } +} - /// Draw the last waypoint we received - static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - static char tempBuf[237]; - - meshtastic_MeshPacket &mp = devicestate.rx_waypoint; - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); - - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } - - uint32_t seconds = sinceReceived(&mp); - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; +/// Draw a series of fields in a column, wrapping to multiple columns if needed +static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) +{ + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.heading_bold) - { - display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", - screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - } - display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); + const char **f = fields; + int xo = x, yo = y; + while (*f) { + display->drawString(xo, yo, *f); + if ((display->getColor() == BLACK) && config.display.heading_bold) + display->drawString(xo + 1, yo, *f); display->setColor(WHITE); - meshtastic_Waypoint scratch; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) - { - snprintf(tempBuf, sizeof(tempBuf), "Received waypoint: %s", scratch.name); - display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); + yo += FONT_HEIGHT_SMALL; + if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) { + xo += SCREEN_WIDTH / 2; + yo = 0; } + f++; } +} - /// Draw a series of fields in a column, wrapping to multiple columns if needed - static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) - { - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); - - const char **f = fields; - int xo = x, yo = y; - while (*f) - { - display->drawString(xo, yo, *f); - if ((display->getColor() == BLACK) && config.display.heading_bold) - display->drawString(xo + 1, yo, *f); - - display->setColor(WHITE); - yo += FONT_HEIGHT_SMALL; - if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) - { - xo += SCREEN_WIDTH / 2; - yo = 0; - } - f++; - } +// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. +static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) +{ + static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD}; + static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85}; + // Clear the bar area on the battery image + for (int i = 1; i < 14; i++) { + imgBuffer[i] = 0x81; } - - // Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. - static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) - { - static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD}; - static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85}; - // Clear the bar area on the battery image - for (int i = 1; i < 14; i++) - { - imgBuffer[i] = 0x81; - } - // If charging, draw a charging indicator - if (powerStatus->getIsCharging()) - { - memcpy(imgBuffer + 3, lightning, 8); - // If not charging, Draw power bars - } - else - { - for (int i = 0; i < 4; i++) - { - if (powerStatus->getBatteryChargePercent() >= 25 * i) - memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); - } + // If charging, draw a charging indicator + if (powerStatus->getIsCharging()) { + memcpy(imgBuffer + 3, lightning, 8); + // If not charging, Draw power bars + } else { + for (int i = 0; i < 4; i++) { + if (powerStatus->getBatteryChargePercent() >= 25 * i) + memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); } - display->drawFastImage(x, y, 16, 8, imgBuffer); } + display->drawFastImage(x, y, 16, 8, imgBuffer); +} - // Draw nodes status - static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus) - { - char usersString[20]; - snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ +// Draw nodes status +static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus) +{ + char usersString[20]; + snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x, y + 3, 8, 8, imgUser); + display->drawFastImage(x, y + 3, 8, 8, imgUser); #else - display->drawFastImage(x, y, 8, 8, imgUser); + display->drawFastImage(x, y, 8, 8, imgUser); #endif - display->drawString(x + 10, y - 2, usersString); + display->drawString(x + 10, y - 2, usersString); + if (config.display.heading_bold) + display->drawString(x + 11, y - 2, usersString); +} + +// Draw GPS status summary +static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) +{ + if (config.position.fixed_position) { + // GPS coordinates are currently fixed + display->drawString(x - 1, y - 2, "Fixed GPS"); if (config.display.heading_bold) - display->drawString(x + 11, y - 2, usersString); + display->drawString(x, y - 2, "Fixed GPS"); + return; } - - // Draw GPS status summary - static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) - { - if (config.position.fixed_position) - { - // GPS coordinates are currently fixed - display->drawString(x - 1, y - 2, "Fixed GPS"); - if (config.display.heading_bold) - display->drawString(x, y - 2, "Fixed GPS"); - return; - } - if (!gps->getIsConnected()) - { - display->drawString(x, y - 2, "No GPS"); - if (config.display.heading_bold) - display->drawString(x + 1, y - 2, "No GPS"); - return; - } - display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty); - if (!gps->getHasLock()) - { - display->drawString(x + 8, y - 2, "No sats"); - if (config.display.heading_bold) - display->drawString(x + 9, y - 2, "No sats"); - return; + if (!gps->getIsConnected()) { + display->drawString(x, y - 2, "No GPS"); + if (config.display.heading_bold) + display->drawString(x + 1, y - 2, "No GPS"); + return; + } + display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty); + if (!gps->getHasLock()) { + display->drawString(x + 8, y - 2, "No sats"); + if (config.display.heading_bold) + display->drawString(x + 9, y - 2, "No sats"); + return; + } else { + char satsString[3]; + uint8_t bar[2] = {0}; + + // Draw DOP signal bars + for (int i = 0; i < 5; i++) { + if (gps->getDOP() <= dopThresholds[i]) + bar[0] = ~((1 << (5 - i)) - 1); + else + bar[0] = 0b10000000; + // bar[1] = bar[0]; + display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar); } - else - { - char satsString[3]; - uint8_t bar[2] = {0}; - - // Draw DOP signal bars - for (int i = 0; i < 5; i++) - { - if (gps->getDOP() <= dopThresholds[i]) - bar[0] = ~((1 << (5 - i)) - 1); - else - bar[0] = 0b10000000; - // bar[1] = bar[0]; - display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar); - } - // Draw satellite image - display->drawFastImage(x + 24, y, 8, 8, imgSatellite); + // Draw satellite image + display->drawFastImage(x + 24, y, 8, 8, imgSatellite); - // Draw the number of satellites - snprintf(satsString, sizeof(satsString), "%u", gps->getNumSatellites()); - display->drawString(x + 34, y - 2, satsString); - if (config.display.heading_bold) - display->drawString(x + 35, y - 2, satsString); - } + // Draw the number of satellites + snprintf(satsString, sizeof(satsString), "%u", gps->getNumSatellites()); + display->drawString(x + 34, y - 2, satsString); + if (config.display.heading_bold) + display->drawString(x + 35, y - 2, satsString); } +} - // Draw status when GPS is disabled or not present - static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) - { - String displayLine; - int pos; - if (y < FONT_HEIGHT_SMALL) - { // Line 1: use short string - displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off"; - pos = SCREEN_WIDTH - display->getStringWidth(displayLine); - } - else - { - displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "GPS not present" - : "GPS is disabled"; - pos = (SCREEN_WIDTH - display->getStringWidth(displayLine)) / 2; - } - display->drawString(x + pos, y, displayLine); +// Draw status when GPS is disabled or not present +static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) +{ + String displayLine; + int pos; + if (y < FONT_HEIGHT_SMALL) { // Line 1: use short string + displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off"; + pos = SCREEN_WIDTH - display->getStringWidth(displayLine); + } else { + displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "GPS not present" + : "GPS is disabled"; + pos = (SCREEN_WIDTH - display->getStringWidth(displayLine)) / 2; } + display->drawString(x + pos, y, displayLine); +} - static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) - { - String displayLine = ""; - if (!gps->getIsConnected() && !config.position.fixed_position) - { - // displayLine = "No GPS Module"; - // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } - else if (!gps->getHasLock() && !config.position.fixed_position) - { - // displayLine = "No GPS Lock"; - // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } - else - { - geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); - displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m"; - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - displayLine = "Altitude: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft"; - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } +static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) +{ + String displayLine = ""; + if (!gps->getIsConnected() && !config.position.fixed_position) { + // displayLine = "No GPS Module"; + // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } else if (!gps->getHasLock() && !config.position.fixed_position) { + // displayLine = "No GPS Lock"; + // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } else { + geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); + displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m"; + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + displayLine = "Altitude: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft"; + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); } +} - // Draw GPS status coordinates - static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) - { - auto gpsFormat = config.display.gps_format; - String displayLine = ""; - - if (!gps->getIsConnected() && !config.position.fixed_position) - { - displayLine = "No GPS present"; - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } - else if (!gps->getHasLock() && !config.position.fixed_position) - { - displayLine = "No GPS Lock"; - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); - } - else - { - - geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); - - if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) - { - char coordinateLine[22]; - if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) - { // Decimal Degrees - snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7, - geoCoord.getLongitude() * 1e-7); - } - else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) - { // Universal Transverse Mercator - snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(), - geoCoord.getUTMEasting(), geoCoord.getUTMNorthing()); - } - else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) - { // Military Grid Reference System - snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(), - geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(), - geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing()); - } - else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) - { // Open Location Code - geoCoord.getOLCCode(coordinateLine); - } - else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) - { // Ordnance Survey Grid Reference - if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region - snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary"); - else - snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(), - geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing()); - } - - // If fixed position, display text "Fixed GPS" alternating with the coordinates. - if (config.position.fixed_position) - { - if ((millis() / 10000) % 2) - { - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine); - } - else - { - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS"); - } - } +// Draw GPS status coordinates +static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) +{ + auto gpsFormat = config.display.gps_format; + String displayLine = ""; + + if (!gps->getIsConnected() && !config.position.fixed_position) { + displayLine = "No GPS present"; + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } else if (!gps->getHasLock() && !config.position.fixed_position) { + displayLine = "No GPS Lock"; + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); + } else { + + geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); + + if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) { + char coordinateLine[22]; + if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees + snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7, + geoCoord.getLongitude() * 1e-7); + } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator + snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(), + geoCoord.getUTMEasting(), geoCoord.getUTMNorthing()); + } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System + snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(), + geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(), + geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing()); + } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code + geoCoord.getOLCCode(coordinateLine); + } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference + if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region + snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary"); else - { + snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(), + geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing()); + } + + // If fixed position, display text "Fixed GPS" alternating with the coordinates. + if (config.position.fixed_position) { + if ((millis() / 10000) % 2) { display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine); + } else { + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS"); } + } else { + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine); } - else - { - char latLine[22]; - char lonLine[22]; - snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), - geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP()); - snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), - geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP()); - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine); - display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine); - } + } else { + char latLine[22]; + char lonLine[22]; + snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), + geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP()); + snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), + geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP()); + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine); + display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine); } } +} - namespace - { - - /// A basic 2D point class for drawing - class Point - { - public: - float x, y; - - Point(float _x, float _y) : x(_x), y(_y) {} - - /// Apply a rotation around zero (standard rotation matrix math) - void rotate(float radian) - { - float cos = cosf(radian), sin = sinf(radian); - float rx = x * cos + y * sin, ry = -x * sin + y * cos; +namespace +{ - x = rx; - y = ry; - } +/// A basic 2D point class for drawing +class Point +{ + public: + float x, y; - void translate(int16_t dx, int dy) - { - x += dx; - y += dy; - } + Point(float _x, float _y) : x(_x), y(_y) {} - void scale(float f) - { - // We use -f here to counter the flip that happens - // on the y axis when drawing and rotating on screen - x *= f; - y *= -f; - } - }; + /// Apply a rotation around zero (standard rotation matrix math) + void rotate(float radian) + { + float cos = cosf(radian), sin = sinf(radian); + float rx = x * cos + y * sin, ry = -x * sin + y * cos; - } // namespace + x = rx; + y = ry; + } - static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) + void translate(int16_t dx, int dy) { - d->drawLine(p1.x, p1.y, p2.x, p2.y); + x += dx; + y += dy; } - /** - * Given a recent lat/lon return a guess of the heading the user is walking on. - * - * We keep a series of "after you've gone 10 meters, what is your heading since - * the last reference point?" - */ - static float estimatedHeading(double lat, double lon) + void scale(float f) { - static double oldLat, oldLon; - static float b; - - if (oldLat == 0) - { - // just prepare for next time - oldLat = lat; - oldLon = lon; + // We use -f here to counter the flip that happens + // on the y axis when drawing and rotating on screen + x *= f; + y *= -f; + } +}; - return b; - } +} // namespace - float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon); - if (d < 10) // haven't moved enough, just keep current bearing - return b; +static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) +{ + d->drawLine(p1.x, p1.y, p2.x, p2.y); +} + +/** + * Given a recent lat/lon return a guess of the heading the user is walking on. + * + * We keep a series of "after you've gone 10 meters, what is your heading since + * the last reference point?" + */ +static float estimatedHeading(double lat, double lon) +{ + static double oldLat, oldLon; + static float b; - b = GeoCoord::bearing(oldLat, oldLon, lat, lon); + if (oldLat == 0) { + // just prepare for next time oldLat = lat; oldLon = lon; return b; } - static uint16_t getCompassDiam(OLEDDisplay *display) - { - uint16_t diam = 0; - uint16_t offset = 0; - - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - offset = FONT_HEIGHT_SMALL; - - // get the smaller of the 2 dimensions and subtract 20 - if (display->getWidth() > (display->getHeight() - offset)) - { - diam = display->getHeight() - offset; - // if 2/3 of the other size would be smaller, use that - if (diam > (display->getWidth() * 2 / 3)) - { - diam = display->getWidth() * 2 / 3; - } + float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon); + if (d < 10) // haven't moved enough, just keep current bearing + return b; + + b = GeoCoord::bearing(oldLat, oldLon, lat, lon); + oldLat = lat; + oldLon = lon; + + return b; +} + +static uint16_t getCompassDiam(OLEDDisplay *display) +{ + uint16_t diam = 0; + uint16_t offset = 0; + + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + offset = FONT_HEIGHT_SMALL; + + // get the smaller of the 2 dimensions and subtract 20 + if (display->getWidth() > (display->getHeight() - offset)) { + diam = display->getHeight() - offset; + // if 2/3 of the other size would be smaller, use that + if (diam > (display->getWidth() * 2 / 3)) { + diam = display->getWidth() * 2 / 3; } - else - { - diam = display->getWidth(); - if (diam > ((display->getHeight() - offset) * 2 / 3)) - { - diam = (display->getHeight() - offset) * 2 / 3; - } + } else { + diam = display->getWidth(); + if (diam > ((display->getHeight() - offset) * 2 / 3)) { + diam = (display->getHeight() - offset) * 2 / 3; } + } - return diam - 20; - }; + return diam - 20; +}; - /// We will skip one node - the one for us, so we just blindly loop over all - /// nodes - static size_t nodeIndex; - static int8_t prevFrame = -1; +/// We will skip one node - the one for us, so we just blindly loop over all +/// nodes +static size_t nodeIndex; +static int8_t prevFrame = -1; - // Draw the arrow pointing to a node's location - static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian) - { - Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially - float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; - Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY); +// Draw the arrow pointing to a node's location +static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian) +{ + Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially + float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; + Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY); - Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow}; + Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow}; - for (int i = 0; i < 4; i++) - { - arrowPoints[i]->rotate(headingRadian); - arrowPoints[i]->scale(getCompassDiam(display) * 0.6); - arrowPoints[i]->translate(compassX, compassY); - } - drawLine(display, tip, tail); - drawLine(display, leftArrow, tip); - drawLine(display, rightArrow, tip); + for (int i = 0; i < 4; i++) { + arrowPoints[i]->rotate(headingRadian); + arrowPoints[i]->scale(getCompassDiam(display) * 0.6); + arrowPoints[i]->translate(compassX, compassY); } + drawLine(display, tip, tail); + drawLine(display, leftArrow, tip); + drawLine(display, rightArrow, tip); +} - // Draw north - static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) - { - // If north is supposed to be at the top of the compass we want rotation to be +0 - if (config.display.compass_north_top) - myHeading = -0; - - Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); - Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); - Point *rosePoints[] = {&N1, &N2, &N3, &N4}; - - for (int i = 0; i < 4; i++) - { - // North on compass will be negative of heading - rosePoints[i]->rotate(-myHeading); - rosePoints[i]->scale(getCompassDiam(display)); - rosePoints[i]->translate(compassX, compassY); - } - drawLine(display, N1, N3); - drawLine(display, N2, N4); - drawLine(display, N1, N4); +// Draw north +static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) +{ + // If north is supposed to be at the top of the compass we want rotation to be +0 + if (config.display.compass_north_top) + myHeading = -0; + + Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); + Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); + Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + + for (int i = 0; i < 4; i++) { + // North on compass will be negative of heading + rosePoints[i]->rotate(-myHeading); + rosePoints[i]->scale(getCompassDiam(display)); + rosePoints[i]->translate(compassX, compassY); } + drawLine(display, N1, N3); + drawLine(display, N2, N4); + drawLine(display, N1, N4); +} /// Convert an integer GPS coords to a floating point #define DegD(i) (i * 1e-7) - static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - // We only advance our nodeIndex if the frame # has changed - because - // drawNodeInfo will be called repeatedly while the frame is shown - if (state->currentFrame != prevFrame) - { - prevFrame = state->currentFrame; - +static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // We only advance our nodeIndex if the frame # has changed - because + // drawNodeInfo will be called repeatedly while the frame is shown + if (state->currentFrame != prevFrame) { + prevFrame = state->currentFrame; + + nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); + meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex); + if (n->num == nodeDB.getNodeNum()) { + // Don't show our node, just skip to next nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); - meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex); - if (n->num == nodeDB.getNodeNum()) - { - // Don't show our node, just skip to next - nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); - n = nodeDB.getMeshNodeByIndex(nodeIndex); - } + n = nodeDB.getMeshNodeByIndex(nodeIndex); } + } - meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex); + meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex); - display->setFont(FONT_SMALL); + display->setFont(FONT_SMALL); - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - } + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + } - const char *username = node->has_user ? node->user.long_name : "Unknown Name"; - - static char signalStr[20]; - snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); - - uint32_t agoSecs = sinceLastSeen(node); - static char lastStr[20]; - if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); - else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); - else - { - // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad - // data. - if ((agoSecs / 60 / 60) < (hours_in_month * 6)) - { - snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); - } - else - { - snprintf(lastStr, sizeof(lastStr), "unknown age"); - } + const char *username = node->has_user ? node->user.long_name : "Unknown Name"; + + static char signalStr[20]; + snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); + + uint32_t agoSecs = sinceLastSeen(node); + static char lastStr[20]; + if (agoSecs < 120) // last 2 mins? + snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); + else if (agoSecs < 120 * 60) // last 2 hrs + snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); + else { + // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad + // data. + if ((agoSecs / 60 / 60) < (hours_in_month * 6)) { + snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); + } else { + snprintf(lastStr, sizeof(lastStr), "unknown age"); } + } - static char distStr[20]; - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - { - strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data - } - else - { - strncpy(distStr, "? km", sizeof(distStr)); - } - meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum()); - const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; - int16_t compassX = 0, compassY = 0; - - // coordinates for the center of the compass/circle - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; - compassY = y + SCREEN_HEIGHT / 2; - } - else - { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; - compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; - } - bool hasNodeHeading = false; - - if (ourNode && hasValidPosition(ourNode)) - { - const meshtastic_PositionLite &op = ourNode->position; - float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - drawCompassNorth(display, compassX, compassY, myHeading); - - if (hasValidPosition(node)) - { - // display direction toward node - hasNodeHeading = true; - const meshtastic_PositionLite &p = node->position; - float d = - GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); - - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - { - if (d < (2 * MILES_TO_FEET)) - snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); - else - snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); - } + static char distStr[20]; + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data + } else { + strncpy(distStr, "? km", sizeof(distStr)); + } + meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum()); + const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; + int16_t compassX = 0, compassY = 0; + + // coordinates for the center of the compass/circle + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassY = y + SCREEN_HEIGHT / 2; + } else { + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; + } + bool hasNodeHeading = false; + + if (ourNode && hasValidPosition(ourNode)) { + const meshtastic_PositionLite &op = ourNode->position; + float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + drawCompassNorth(display, compassX, compassY, myHeading); + + if (hasValidPosition(node)) { + // display direction toward node + hasNodeHeading = true; + const meshtastic_PositionLite &p = node->position; + float d = + GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + if (d < (2 * MILES_TO_FEET)) + snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); else - { - if (d < 2000) - snprintf(distStr, sizeof(distStr), "%.0f m", d); - else - snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); - } - - float bearingToOther = - GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); - // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly - // If the top of the compass is not a static north we need adjust bearingToOther based on heading - if (!config.display.compass_north_top) - bearingToOther -= myHeading; - drawNodeHeading(display, compassX, compassY, bearingToOther); + snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); + } else { + if (d < 2000) + snprintf(distStr, sizeof(distStr), "%.0f m", d); + else + snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); } - } - if (!hasNodeHeading) - { - // direction to node is unknown so display question mark - // Debug info for gps lock errors - // LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasValidPosition(ourNode), - // hasValidPosition(node)); - display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); - } - display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - { - display->setColor(BLACK); + float bearingToOther = + GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); + // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly + // If the top of the compass is not a static north we need adjust bearingToOther based on heading + if (!config.display.compass_north_top) + bearingToOther -= myHeading; + drawNodeHeading(display, compassX, compassY, bearingToOther); } - // Must be after distStr is populated - drawColumns(display, x, y, fields); } + if (!hasNodeHeading) { + // direction to node is unknown so display question mark + // Debug info for gps lock errors + // LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasValidPosition(ourNode), + // hasValidPosition(node)); + display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); + } + display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); - Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) - : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) - { + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->setColor(BLACK); + } + // Must be after distStr is populated + drawColumns(display, x, y, fields); +} + +Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) + : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) +{ #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) - dispdev = new SH1106Wire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new SH1106Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_SSD1306) - dispdev = new SSD1306Wire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new SSD1306Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) - dispdev = new TFTDisplay(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new TFTDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) - dispdev = new EInkDisplay(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new EInkDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) - dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_ST7567) - dispdev = new ST7567Wire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + dispdev = new ST7567Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif ARCH_PORTDUINO - if (settingsMap[displayPanel] != no_screen) - { - LOG_DEBUG("Making TFTDisplay!\n"); - dispdev = new TFTDisplay(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); - } - else - { - dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, - (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); - isAUTOOled = true; - } -#else + if (settingsMap[displayPanel] != no_screen) { + LOG_DEBUG("Making TFTDisplay!\n"); + dispdev = new TFTDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + } else { dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); isAUTOOled = true; + } +#else + dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + isAUTOOled = true; #endif - ui = new OLEDDisplayUi(dispdev); - cmdQueue.setReader(this); - } + ui = new OLEDDisplayUi(dispdev); + cmdQueue.setReader(this); +} - /** - * Prepare the display for the unit going to the lowest power mode possible. Most screens will just - * poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code - */ - void Screen::doDeepSleep() - { +/** + * Prepare the display for the unit going to the lowest power mode possible. Most screens will just + * poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code + */ +void Screen::doDeepSleep() +{ #ifdef USE_EINK - static FrameCallback sleepFrames[] = {drawSleepScreen}; - static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]); - ui->setFrames(sleepFrames, sleepFrameCount); - ui->update(); + static FrameCallback sleepFrames[] = {drawSleepScreen}; + static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]); + ui->setFrames(sleepFrames, sleepFrameCount); + ui->update(); #ifdef PIN_EINK_EN - digitalWrite(PIN_EINK_EN, LOW); // power off backlight + digitalWrite(PIN_EINK_EN, LOW); // power off backlight #endif #endif - setOn(false); - } + setOn(false); +} - void Screen::handleSetOn(bool on) - { - if (!useDisplay) - return; - - if (on != screenOn) - { - if (on) - { - LOG_INFO("Turning on screen\n"); +void Screen::handleSetOn(bool on) +{ + if (!useDisplay) + return; + + if (on != screenOn) { + if (on) { + LOG_INFO("Turning on screen\n"); #ifdef T_WATCH_S3 - PMU->enablePowerOutput(XPOWERS_ALDO2); + PMU->enablePowerOutput(XPOWERS_ALDO2); #endif #if !ARCH_PORTDUINO - dispdev->displayOn(); + dispdev->displayOn(); #endif - dispdev->displayOn(); - enabled = true; - setInterval(0); // Draw ASAP - runASAP = true; - } - else - { - LOG_INFO("Turning off screen\n"); - dispdev->displayOff(); + dispdev->displayOn(); + enabled = true; + setInterval(0); // Draw ASAP + runASAP = true; + } else { + LOG_INFO("Turning off screen\n"); + dispdev->displayOff(); #ifdef T_WATCH_S3 - PMU->disablePowerOutput(XPOWERS_ALDO2); + PMU->disablePowerOutput(XPOWERS_ALDO2); #endif - enabled = false; - } - screenOn = on; + enabled = false; } + screenOn = on; } +} - void Screen::setup() - { - // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device - // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. - useDisplay = true; +void Screen::setup() +{ + // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device + // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. + useDisplay = true; #ifdef AutoOLEDWire_h - if (isAUTOOled) - static_cast(dispdev)->setDetected(model); + if (isAUTOOled) + static_cast(dispdev)->setDetected(model); #endif #ifdef USE_SH1107_128_64 - static_cast(dispdev)->setSubtype(7); + static_cast(dispdev)->setSubtype(7); #endif - // Initialising the UI will init the display too. - ui->init(); + // Initialising the UI will init the display too. + ui->init(); - displayWidth = dispdev->width(); - displayHeight = dispdev->height(); + displayWidth = dispdev->width(); + displayHeight = dispdev->height(); - ui->setTimePerTransition(0); + ui->setTimePerTransition(0); - ui->setIndicatorPosition(BOTTOM); - // Defines where the first frame is located in the bar. - ui->setIndicatorDirection(LEFT_RIGHT); - ui->setFrameAnimation(SLIDE_LEFT); - // Don't show the page swipe dots while in boot screen. - ui->disableAllIndicators(); - // Store a pointer to Screen so we can get to it from static functions. - ui->getUiState()->userData = this; + ui->setIndicatorPosition(BOTTOM); + // Defines where the first frame is located in the bar. + ui->setIndicatorDirection(LEFT_RIGHT); + ui->setFrameAnimation(SLIDE_LEFT); + // Don't show the page swipe dots while in boot screen. + ui->disableAllIndicators(); + // Store a pointer to Screen so we can get to it from static functions. + ui->getUiState()->userData = this; - // Set the utf8 conversion function - dispdev->setFontTableLookupFunction(customFontTableLookup); + // Set the utf8 conversion function + dispdev->setFontTableLookupFunction(customFontTableLookup); - if (strlen(oemStore.oem_text) > 0) - logo_timeout *= 2; + if (strlen(oemStore.oem_text) > 0) + logo_timeout *= 2; - // Add frames. - static FrameCallback bootFrames[] = {drawBootScreen}; - static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); - ui->setFrames(bootFrames, bootFrameCount); - // No overlays. - ui->setOverlays(nullptr, 0); + // Add frames. + static FrameCallback bootFrames[] = {drawBootScreen}; + static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); + ui->setFrames(bootFrames, bootFrameCount); + // No overlays. + ui->setOverlays(nullptr, 0); - // Require presses to switch between frames. - ui->disableAutoTransition(); + // Require presses to switch between frames. + ui->disableAutoTransition(); - // Set up a log buffer with 3 lines, 32 chars each. - dispdev->setLogBuffer(3, 32); + // Set up a log buffer with 3 lines, 32 chars each. + dispdev->setLogBuffer(3, 32); #ifdef SCREEN_MIRROR - dispdev->mirrorScreen(); + dispdev->mirrorScreen(); #else - // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically - // flip it. If you have a headache now, you're welcome. - if (!config.display.flip_screen) - { + // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically + // flip it. If you have a headache now, you're welcome. + if (!config.display.flip_screen) { #if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) - static_cast(dispdev)->flipScreenVertically(); + static_cast(dispdev)->flipScreenVertically(); #else - dispdev->flipScreenVertically(); + dispdev->flipScreenVertically(); #endif - } + } #endif - // Get our hardware ID - uint8_t dmac[6]; - getMacAddr(dmac); - snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]); + // Get our hardware ID + uint8_t dmac[6]; + getMacAddr(dmac); + snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]); #if ARCH_PORTDUINO - handleSetOn(false); // force clean init + handleSetOn(false); // force clean init #endif - // Turn on the display. - handleSetOn(true); + // Turn on the display. + handleSetOn(true); - // On some ssd1306 clones, the first draw command is discarded, so draw it - // twice initially. Skip this for EINK Displays to save a few seconds during boot - ui->update(); + // On some ssd1306 clones, the first draw command is discarded, so draw it + // twice initially. Skip this for EINK Displays to save a few seconds during boot + ui->update(); #ifndef USE_EINK - ui->update(); + ui->update(); #endif - serialSinceMsec = millis(); + serialSinceMsec = millis(); #if ARCH_PORTDUINO - if (settingsMap[touchscreenModule]) - { - touchScreenImpl1 = - new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); - touchScreenImpl1->init(); - } -#elif HAS_TOUCHSCREEN + if (settingsMap[touchscreenModule]) { touchScreenImpl1 = new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); + } +#elif HAS_TOUCHSCREEN + touchScreenImpl1 = + new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); + touchScreenImpl1->init(); #endif - // Subscribe to status updates - powerStatusObserver.observe(&powerStatus->onNewStatus); - gpsStatusObserver.observe(&gpsStatus->onNewStatus); - nodeStatusObserver.observe(&nodeStatus->onNewStatus); - if (textMessageModule) - textMessageObserver.observe(textMessageModule); - if (inputBroker) - inputObserver.observe(inputBroker); + // Subscribe to status updates + powerStatusObserver.observe(&powerStatus->onNewStatus); + gpsStatusObserver.observe(&gpsStatus->onNewStatus); + nodeStatusObserver.observe(&nodeStatus->onNewStatus); + if (textMessageModule) + textMessageObserver.observe(textMessageModule); + if (inputBroker) + inputObserver.observe(inputBroker); - // Modules can notify screen about refresh - MeshModule::observeUIEvents(&uiFrameEventObserver); - } + // Modules can notify screen about refresh + MeshModule::observeUIEvents(&uiFrameEventObserver); +} - void Screen::forceDisplay() - { - // Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup. +void Screen::forceDisplay() +{ + // Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup. #ifdef USE_EINK - static_cast(dispdev)->forceDisplay(); + static_cast(dispdev)->forceDisplay(); #endif - } +} - static uint32_t lastScreenTransition; +static uint32_t lastScreenTransition; - int32_t Screen::runOnce() - { - // If we don't have a screen, don't ever spend any CPU for us. - if (!useDisplay) - { - enabled = false; - return RUN_SAME; - } +int32_t Screen::runOnce() +{ + // If we don't have a screen, don't ever spend any CPU for us. + if (!useDisplay) { + enabled = false; + return RUN_SAME; + } - // Show boot screen for first logo_timeout seconds, then switch to normal operation. - // serialSinceMsec adjusts for additional serial wait time during nRF52 bootup - static bool showingBootScreen = true; - if (showingBootScreen && (millis() > (logo_timeout + serialSinceMsec))) - { - LOG_INFO("Done with boot screen...\n"); - stopBootScreen(); - showingBootScreen = false; - } + // Show boot screen for first logo_timeout seconds, then switch to normal operation. + // serialSinceMsec adjusts for additional serial wait time during nRF52 bootup + static bool showingBootScreen = true; + if (showingBootScreen && (millis() > (logo_timeout + serialSinceMsec))) { + LOG_INFO("Done with boot screen...\n"); + stopBootScreen(); + showingBootScreen = false; + } - // If we have an OEM Boot screen, toggle after logo_timeout seconds - if (strlen(oemStore.oem_text) > 0) - { - static bool showingOEMBootScreen = true; - if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) - { - LOG_INFO("Switch to OEM screen...\n"); - // Change frames. - static FrameCallback bootOEMFrames[] = {drawOEMBootScreen}; - static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]); - ui->setFrames(bootOEMFrames, bootOEMFrameCount); - ui->update(); + // If we have an OEM Boot screen, toggle after logo_timeout seconds + if (strlen(oemStore.oem_text) > 0) { + static bool showingOEMBootScreen = true; + if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) { + LOG_INFO("Switch to OEM screen...\n"); + // Change frames. + static FrameCallback bootOEMFrames[] = {drawOEMBootScreen}; + static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]); + ui->setFrames(bootOEMFrames, bootOEMFrameCount); + ui->update(); #ifndef USE_EINK - ui->update(); + ui->update(); #endif - showingOEMBootScreen = false; - } + showingOEMBootScreen = false; } + } #ifndef DISABLE_WELCOME_UNSET - if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) - { - setWelcomeFrames(); - } + if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { + setWelcomeFrames(); + } #endif - // Process incoming commands. - for (;;) - { - ScreenCmd cmd; - if (!cmdQueue.dequeue(&cmd, 0)) - { - break; - } - switch (cmd.cmd) - { - case Cmd::SET_ON: - handleSetOn(true); - break; - case Cmd::SET_OFF: - handleSetOn(false); - break; - case Cmd::ON_PRESS: - // If a nag notification is running, stop it - if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) - { - externalNotificationModule->stopNow(); - } - else - { - // Don't advance the screen if we just wanted to switch off the nag notification - handleOnPress(); - } - break; - case Cmd::SHOW_PREV_FRAME: - handleShowPrevFrame(); - break; - case Cmd::SHOW_NEXT_FRAME: - handleShowNextFrame(); - break; - case Cmd::START_BLUETOOTH_PIN_SCREEN: - handleStartBluetoothPinScreen(cmd.bluetooth_pin); - break; - case Cmd::START_FIRMWARE_UPDATE_SCREEN: - handleStartFirmwareUpdateScreen(); - break; - case Cmd::STOP_BLUETOOTH_PIN_SCREEN: - case Cmd::STOP_BOOT_SCREEN: - EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame - setFrames(); - break; - case Cmd::PRINT: - handlePrint(cmd.print_text); - free(cmd.print_text); - break; - case Cmd::START_SHUTDOWN_SCREEN: - handleShutdownScreen(); - break; - case Cmd::START_REBOOT_SCREEN: - handleRebootScreen(); - break; - default: - LOG_ERROR("Invalid screen cmd\n"); + // Process incoming commands. + for (;;) { + ScreenCmd cmd; + if (!cmdQueue.dequeue(&cmd, 0)) { + break; + } + switch (cmd.cmd) { + case Cmd::SET_ON: + handleSetOn(true); + break; + case Cmd::SET_OFF: + handleSetOn(false); + break; + case Cmd::ON_PRESS: + // If a nag notification is running, stop it + if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) { + externalNotificationModule->stopNow(); + } else { + // Don't advance the screen if we just wanted to switch off the nag notification + handleOnPress(); } + break; + case Cmd::SHOW_PREV_FRAME: + handleShowPrevFrame(); + break; + case Cmd::SHOW_NEXT_FRAME: + handleShowNextFrame(); + break; + case Cmd::START_BLUETOOTH_PIN_SCREEN: + handleStartBluetoothPinScreen(cmd.bluetooth_pin); + break; + case Cmd::START_FIRMWARE_UPDATE_SCREEN: + handleStartFirmwareUpdateScreen(); + break; + case Cmd::STOP_BLUETOOTH_PIN_SCREEN: + case Cmd::STOP_BOOT_SCREEN: + EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame + setFrames(); + break; + case Cmd::PRINT: + handlePrint(cmd.print_text); + free(cmd.print_text); + break; + case Cmd::START_SHUTDOWN_SCREEN: + handleShutdownScreen(); + break; + case Cmd::START_REBOOT_SCREEN: + handleRebootScreen(); + break; + default: + LOG_ERROR("Invalid screen cmd\n"); } + } - if (!screenOn) - { // If we didn't just wake and the screen is still off, then - // stop updating until it is on again - enabled = false; - return 0; - } + if (!screenOn) { // If we didn't just wake and the screen is still off, then + // stop updating until it is on again + enabled = false; + return 0; + } - // this must be before the frameState == FIXED check, because we always - // want to draw at least one FIXED frame before doing forceDisplay - ui->update(); + // this must be before the frameState == FIXED check, because we always + // want to draw at least one FIXED frame before doing forceDisplay + ui->update(); - // Switch to a low framerate (to save CPU) when we are not in transition - // but we should only call setTargetFPS when framestate changes, because - // otherwise that breaks animations. - if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) - { - // oldFrameState = ui->getUiState()->frameState; - targetFramerate = IDLE_FRAMERATE; + // Switch to a low framerate (to save CPU) when we are not in transition + // but we should only call setTargetFPS when framestate changes, because + // otherwise that breaks animations. + if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) { + // oldFrameState = ui->getUiState()->frameState; + targetFramerate = IDLE_FRAMERATE; - ui->setTargetFPS(targetFramerate); - forceDisplay(); - } + ui->setTargetFPS(targetFramerate); + forceDisplay(); + } - // While showing the bootscreen or Bluetooth pair screen all of our - // standard screen switching is stopped. - if (showingNormalScreen) - { - // standard screen loop handling here - if (config.display.auto_screen_carousel_secs > 0 && - (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) - { - LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition)); - handleOnPress(); - } + // While showing the bootscreen or Bluetooth pair screen all of our + // standard screen switching is stopped. + if (showingNormalScreen) { + // standard screen loop handling here + if (config.display.auto_screen_carousel_secs > 0 && + (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) { + LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition)); + handleOnPress(); } - - // LOG_DEBUG("want fps %d, fixed=%d\n", targetFramerate, - // ui->getUiState()->frameState); If we are scrolling we need to be called - // soon, otherwise just 1 fps (to save CPU) We also ask to be called twice - // as fast as we really need so that any rounding errors still result with - // the correct framerate - return (1000 / targetFramerate); } - void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - Screen *screen2 = reinterpret_cast(state->userData); - screen2->debugInfo.drawFrame(display, state, x, y); - } + // LOG_DEBUG("want fps %d, fixed=%d\n", targetFramerate, + // ui->getUiState()->frameState); If we are scrolling we need to be called + // soon, otherwise just 1 fps (to save CPU) We also ask to be called twice + // as fast as we really need so that any rounding errors still result with + // the correct framerate + return (1000 / targetFramerate); +} - void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - Screen *screen2 = reinterpret_cast(state->userData); - screen2->debugInfo.drawFrameSettings(display, state, x, y); - } +void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + Screen *screen2 = reinterpret_cast(state->userData); + screen2->debugInfo.drawFrame(display, state, x, y); +} - void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - Screen *screen2 = reinterpret_cast(state->userData); - screen2->debugInfo.drawFrameWiFi(display, state, x, y); - } +void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + Screen *screen2 = reinterpret_cast(state->userData); + screen2->debugInfo.drawFrameSettings(display, state, x, y); +} - /* show a message that the SSL cert is being built - * it is expected that this will be used during the boot phase */ - void Screen::setSSLFrames() - { - if (address_found.address) - { - // LOG_DEBUG("showing SSL frames\n"); - static FrameCallback sslFrames[] = {drawSSLScreen}; - ui->setFrames(sslFrames, 1); - ui->update(); - } +void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + Screen *screen2 = reinterpret_cast(state->userData); + screen2->debugInfo.drawFrameWiFi(display, state, x, y); +} + +/* show a message that the SSL cert is being built + * it is expected that this will be used during the boot phase */ +void Screen::setSSLFrames() +{ + if (address_found.address) { + // LOG_DEBUG("showing SSL frames\n"); + static FrameCallback sslFrames[] = {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("showing Welcome frames\n"); - static FrameCallback frames[] = {drawWelcomeScreen}; - setFrameImmediateDraw(frames); - } +/* 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("showing Welcome frames\n"); + static FrameCallback frames[] = {drawWelcomeScreen}; + setFrameImmediateDraw(frames); } +} - // restore our regular frame list - void Screen::setFrames() - { - LOG_DEBUG("showing standard frames\n"); - showingNormalScreen = true; +// restore our regular frame list +void Screen::setFrames() +{ + LOG_DEBUG("showing standard frames\n"); + showingNormalScreen = true; - moduleFrames = MeshModule::GetMeshModulesWithUIFrames(); - LOG_DEBUG("Showing %d module frames\n", moduleFrames.size()); + moduleFrames = MeshModule::GetMeshModulesWithUIFrames(); + LOG_DEBUG("Showing %d module frames\n", moduleFrames.size()); #ifdef DEBUG_PORT - int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + moduleFrames.size(); - LOG_DEBUG("Total frame count: %d\n", totalFrameCount); + int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + moduleFrames.size(); + LOG_DEBUG("Total frame count: %d\n", totalFrameCount); #endif - // We don't show the node info our our node (if we have it yet - we should) - size_t numMeshNodes = nodeDB.getNumMeshNodes(); - if (numMeshNodes > 0) - numMeshNodes--; - - size_t numframes = 0; - - // put all of the module frames first. - // this is a little bit of a dirty hack; since we're going to call - // the same drawModuleFrame handler here for all of these module frames - // and then we'll just assume that the state->currentFrame value - // is the same offset into the moduleFrames vector - // so that we can invoke the module's callback - for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) - { - normalFrames[numframes++] = drawModuleFrame; - } + // We don't show the node info our our node (if we have it yet - we should) + size_t numMeshNodes = nodeDB.getNumMeshNodes(); + if (numMeshNodes > 0) + numMeshNodes--; + + size_t numframes = 0; + + // put all of the module frames first. + // this is a little bit of a dirty hack; since we're going to call + // the same drawModuleFrame handler here for all of these module frames + // and then we'll just assume that the state->currentFrame value + // is the same offset into the moduleFrames vector + // so that we can invoke the module's callback + for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) { + normalFrames[numframes++] = drawModuleFrame; + } - LOG_DEBUG("Added modules. numframes: %d\n", numframes); + LOG_DEBUG("Added modules. numframes: %d\n", numframes); - // If we have a critical fault, show it first - if (error_code) - normalFrames[numframes++] = drawCriticalFaultFrame; + // If we have a critical fault, show it first + if (error_code) + normalFrames[numframes++] = drawCriticalFaultFrame; - // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules - if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) - { - normalFrames[numframes++] = drawTextMessageFrame; - } - // If we have a waypoint - show it next, unless it's a phone message and we aren't using any special modules - if (devicestate.has_rx_waypoint && shouldDrawMessage(&devicestate.rx_waypoint)) - { - normalFrames[numframes++] = drawWaypointFrame; - } + // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules + if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { + normalFrames[numframes++] = drawTextMessageFrame; + } + // If we have a waypoint - show it next, unless it's a phone message and we aren't using any special modules + if (devicestate.has_rx_waypoint && shouldDrawMessage(&devicestate.rx_waypoint)) { + normalFrames[numframes++] = drawWaypointFrame; + } - // then all the nodes - // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens - size_t numToShow = min(numMeshNodes, 4U); - for (size_t i = 0; i < numToShow; i++) - normalFrames[numframes++] = drawNodeInfo; + // then all the nodes + // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens + size_t numToShow = min(numMeshNodes, 4U); + for (size_t i = 0; i < numToShow; i++) + normalFrames[numframes++] = drawNodeInfo; - // then the debug info - // - // Since frames are basic function pointers, we have to use a helper to - // call a method on debugInfo object. - normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; + // then the debug info + // + // Since frames are basic function pointers, we have to use a helper to + // call a method on debugInfo object. + normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; - // call a method on debugInfoScreen object (for more details) - normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; + // call a method on debugInfoScreen object (for more details) + normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (isWifiAvailable()) - { - // call a method on debugInfoScreen object (for more details) - normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline; - } + if (isWifiAvailable()) { + // call a method on debugInfoScreen object (for more details) + normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline; + } #endif - LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); + LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); - ui->setFrames(normalFrames, numframes); - ui->enableAllIndicators(); + ui->setFrames(normalFrames, numframes); + ui->enableAllIndicators(); - prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list - // just changed) + prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list + // just changed) - setFastFramerate(); // Draw ASAP - } + setFastFramerate(); // Draw ASAP +} - void Screen::handleStartBluetoothPinScreen(uint32_t pin) - { - LOG_DEBUG("showing bluetooth screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame +void Screen::handleStartBluetoothPinScreen(uint32_t pin) +{ + LOG_DEBUG("showing bluetooth screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - static FrameCallback frames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06u", pin); - setFrameImmediateDraw(frames); - } + static FrameCallback frames[] = {drawFrameBluetooth}; + snprintf(btPIN, sizeof(btPIN), "%06u", pin); + setFrameImmediateDraw(frames); +} - void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) - { - ui->disableAllIndicators(); - ui->setFrames(drawFrames, 1); - setFastFramerate(); - } +void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) +{ + ui->disableAllIndicators(); + ui->setFrames(drawFrames, 1); + setFastFramerate(); +} - void Screen::handleShutdownScreen() - { - LOG_DEBUG("showing shutdown screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame +void Screen::handleShutdownScreen() +{ + LOG_DEBUG("showing shutdown screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void - { - drawFrameText(display, state, x, y, "Shutting down..."); - }; - static FrameCallback frames[] = {frame}; + auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + drawFrameText(display, state, x, y, "Shutting down..."); + }; + static FrameCallback frames[] = {frame}; - setFrameImmediateDraw(frames); - } + setFrameImmediateDraw(frames); +} - void Screen::handleRebootScreen() - { - LOG_DEBUG("showing reboot screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void - { - drawFrameText(display, state, x, y, "Rebooting..."); - }; - static FrameCallback frames[] = {frame}; - setFrameImmediateDraw(frames); - } +void Screen::handleRebootScreen() +{ + LOG_DEBUG("showing reboot screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - void Screen::handleStartFirmwareUpdateScreen() - { - LOG_DEBUG("showing firmware screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + drawFrameText(display, state, x, y, "Rebooting..."); + }; + static FrameCallback frames[] = {frame}; + setFrameImmediateDraw(frames); +} - static FrameCallback frames[] = {drawFrameFirmware}; - setFrameImmediateDraw(frames); - } +void Screen::handleStartFirmwareUpdateScreen() +{ + LOG_DEBUG("showing firmware screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - void Screen::blink() - { - setFastFramerate(); - uint8_t count = 10; - dispdev->setBrightness(254); - while (count > 0) - { - dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - dispdev->display(); - delay(50); - dispdev->clear(); - dispdev->display(); - delay(50); - count = count - 1; - } - dispdev->setBrightness(brightness); + static FrameCallback frames[] = {drawFrameFirmware}; + setFrameImmediateDraw(frames); +} + +void Screen::blink() +{ + setFastFramerate(); + uint8_t count = 10; + dispdev->setBrightness(254); + while (count > 0) { + dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + dispdev->display(); + delay(50); + dispdev->clear(); + dispdev->display(); + delay(50); + count = count - 1; } + dispdev->setBrightness(brightness); +} - std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds) - { - std::string uptime; - - if (days > (hours_in_month * 6)) - uptime = "?"; - else if (days >= 2) - uptime = std::to_string(days) + "d"; - else if (hours >= 2) - uptime = std::to_string(hours) + "h"; - else if (minutes >= 1) - uptime = std::to_string(minutes) + "m"; - else - uptime = std::to_string(seconds) + "s"; - return uptime; - } - - void Screen::handlePrint(const char *text) - { - // the string passed into us probably has a newline, but that would confuse the logging system - // so strip it - LOG_DEBUG("Screen: %.*s\n", strlen(text) - 1, text); - if (!useDisplay || !showingNormalScreen) - return; +std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds) +{ + std::string uptime; + + if (days > (hours_in_month * 6)) + uptime = "?"; + else if (days >= 2) + uptime = std::to_string(days) + "d"; + else if (hours >= 2) + uptime = std::to_string(hours) + "h"; + else if (minutes >= 1) + uptime = std::to_string(minutes) + "m"; + else + uptime = std::to_string(seconds) + "s"; + return uptime; +} + +void Screen::handlePrint(const char *text) +{ + // the string passed into us probably has a newline, but that would confuse the logging system + // so strip it + LOG_DEBUG("Screen: %.*s\n", strlen(text) - 1, text); + if (!useDisplay || !showingNormalScreen) + return; - dispdev->print(text); - } + dispdev->print(text); +} - void Screen::handleOnPress() - { - // If screen was off, just wake it, otherwise advance to next frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) - { - ui->nextFrame(); - lastScreenTransition = millis(); - setFastFramerate(); - } +void Screen::handleOnPress() +{ + // If screen was off, just wake it, otherwise advance to next frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) { + ui->nextFrame(); + lastScreenTransition = millis(); + setFastFramerate(); } +} - void Screen::handleShowPrevFrame() - { - // If screen was off, just wake it, otherwise go back to previous frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) - { - ui->previousFrame(); - lastScreenTransition = millis(); - setFastFramerate(); - } +void Screen::handleShowPrevFrame() +{ + // If screen was off, just wake it, otherwise go back to previous frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) { + ui->previousFrame(); + lastScreenTransition = millis(); + setFastFramerate(); } +} - void Screen::handleShowNextFrame() - { - // If screen was off, just wake it, otherwise advance to next frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) - { - ui->nextFrame(); - lastScreenTransition = millis(); - setFastFramerate(); - } +void Screen::handleShowNextFrame() +{ + // If screen was off, just wake it, otherwise advance to next frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) { + ui->nextFrame(); + lastScreenTransition = millis(); + setFastFramerate(); } +} #ifndef SCREEN_TRANSITION_FRAMERATE #define SCREEN_TRANSITION_FRAMERATE 30 // fps #endif - void Screen::setFastFramerate() - { - // We are about to start a transition so speed up fps - targetFramerate = SCREEN_TRANSITION_FRAMERATE; +void Screen::setFastFramerate() +{ + // We are about to start a transition so speed up fps + targetFramerate = SCREEN_TRANSITION_FRAMERATE; - ui->setTargetFPS(targetFramerate); - setInterval(0); // redraw ASAP - runASAP = true; - } + ui->setTargetFPS(targetFramerate); + setInterval(0); // redraw ASAP + runASAP = true; +} - void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - display->setFont(FONT_SMALL); +void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setFont(FONT_SMALL); - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - char channelStr[20]; - { - concurrency::LockGuard guard(&lock); - snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex())); - } + char channelStr[20]; + { + concurrency::LockGuard guard(&lock); + snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex())); + } - // Display power status - if (powerStatus->getHasBattery()) - { - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - { - drawBattery(display, x, y + 2, imgBattery, powerStatus); - } - else - { - drawBattery(display, x + 1, y + 3, imgBattery, powerStatus); - } - } - else if (powerStatus->knowsUSB()) - { - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - { - display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); - } - else - { - display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); - } + // Display power status + if (powerStatus->getHasBattery()) { + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + drawBattery(display, x, y + 2, imgBattery, powerStatus); + } else { + drawBattery(display, x + 1, y + 3, imgBattery, powerStatus); } - // Display nodes status - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - { - drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus); + } else if (powerStatus->knowsUSB()) { + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); + } else { + display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); } - else - { - drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); - } - // Display GPS status - if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) - { - drawGPSpowerstat(display, x, y + 2, gpsStatus); - } - else - { - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - { - drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); - } - else - { - drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); - } + } + // Display nodes status + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus); + } else { + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); + } + // Display GPS status + if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { + drawGPSpowerstat(display, x, y + 2, gpsStatus); + } else { + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); + } else { + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); } + } - display->setColor(WHITE); - // Draw the channel name - display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); - // Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo - if (moduleConfig.store_forward.enabled) - { + display->setColor(WHITE); + // Draw the channel name + display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); + // Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo + if (moduleConfig.store_forward.enabled) { #ifdef ARCH_ESP32 - if (millis() - storeForwardModule->lastHeartbeat > - (storeForwardModule->heartbeatInterval * 1200)) - { // no heartbeat, overlap a bit -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + if (millis() - storeForwardModule->lastHeartbeat > + (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, - imgQuestionL1); - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, - imgQuestionL2); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, + imgQuestionL1); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, + imgQuestionL2); #else - display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, - imgQuestion); + display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, + imgQuestion); #endif - } - else - { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + } else { +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, - imgSFL1); - display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8, - imgSFL2); + display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, + imgSFL1); + display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8, + imgSFL2); #else - display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8, - imgSF); -#endif - } + display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8, + imgSF); #endif } - else - { - // TODO: Raspberry Pi supports more than just the one screen size -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \ +#endif + } else { + // TODO: Raspberry Pi supports more than just the one screen size +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, - imgInfoL1); - display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, - imgInfoL2); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, + imgInfoL1); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, + imgInfoL2); #else - display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo); + display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo); #endif - } + } - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId); - // Draw any log messages - display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2)); + // Draw any log messages + display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2)); - /* Display a heartbeat pixel that blinks every time the frame is redrawn */ + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS - if (heartbeat) - display->setPixel(0, 0); - heartbeat = !heartbeat; + if (heartbeat) + display->setPixel(0, 0); + heartbeat = !heartbeat; #endif - } +} - // Jm - void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { +// Jm +void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ #if HAS_WIFI && !defined(ARCH_PORTDUINO) - const char *wifiName = config.network.wifi_ssid; + const char *wifiName = config.network.wifi_ssid; - display->setFont(FONT_SMALL); + display->setFont(FONT_SMALL); - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - if (WiFi.status() != WL_CONNECTED) - { - display->drawString(x, y, String("WiFi: Not Connected")); - if (config.display.heading_bold) - display->drawString(x + 1, y, String("WiFi: Not Connected")); - } - else - { - display->drawString(x, y, String("WiFi: Connected")); - if (config.display.heading_bold) - display->drawString(x + 1, y, String("WiFi: Connected")); + if (WiFi.status() != WL_CONNECTED) { + display->drawString(x, y, String("WiFi: Not Connected")); + if (config.display.heading_bold) + display->drawString(x + 1, y, String("WiFi: Not Connected")); + } else { + display->drawString(x, y, String("WiFi: Connected")); + if (config.display.heading_bold) + display->drawString(x + 1, y, String("WiFi: Connected")); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, + "RSSI " + String(WiFi.RSSI())); + if (config.display.heading_bold) { + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, "RSSI " + String(WiFi.RSSI())); - if (config.display.heading_bold) - { - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, - "RSSI " + String(WiFi.RSSI())); - } } + } - display->setColor(WHITE); - - /* - - WL_CONNECTED: assigned when connected to a WiFi network; - - WL_NO_SSID_AVAIL: assigned when no SSID are available; - - WL_CONNECT_FAILED: assigned when the connection fails for all the attempts; - - WL_CONNECTION_LOST: assigned when the connection is lost; - - WL_DISCONNECTED: assigned when disconnected from a network; - - WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of - attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED); - - WL_SCAN_COMPLETED: assigned when the scan networks is completed; - - WL_NO_SHIELD: assigned when no WiFi shield is present; - - */ - if (WiFi.status() == WL_CONNECTED) - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str())); - } - else if (WiFi.status() == WL_NO_SSID_AVAIL) - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found"); - } - else if (WiFi.status() == WL_CONNECTION_LOST) - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost"); - } - else if (WiFi.status() == WL_CONNECT_FAILED) - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed"); - } - else if (WiFi.status() == WL_IDLE_STATUS) - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting"); - } + display->setColor(WHITE); + + /* + - WL_CONNECTED: assigned when connected to a WiFi network; + - WL_NO_SSID_AVAIL: assigned when no SSID are available; + - WL_CONNECT_FAILED: assigned when the connection fails for all the attempts; + - WL_CONNECTION_LOST: assigned when the connection is lost; + - WL_DISCONNECTED: assigned when disconnected from a network; + - WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of + attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED); + - WL_SCAN_COMPLETED: assigned when the scan networks is completed; + - WL_NO_SHIELD: assigned when no WiFi shield is present; + + */ + if (WiFi.status() == WL_CONNECTED) { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str())); + } else if (WiFi.status() == WL_NO_SSID_AVAIL) { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found"); + } else if (WiFi.status() == WL_CONNECTION_LOST) { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost"); + } else if (WiFi.status() == WL_CONNECT_FAILED) { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed"); + } else if (WiFi.status() == WL_IDLE_STATUS) { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting"); + } #ifdef ARCH_ESP32 - else - { - // Codes: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, - WiFi.disconnectReasonName(static_cast(getWifiDisconnectReason()))); - } + else { + // Codes: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, + WiFi.disconnectReasonName(static_cast(getWifiDisconnectReason()))); + } #else - else - { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status())); - } + else { + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status())); + } #endif - display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName)); + display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName)); - display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local"); - /* Display a heartbeat pixel that blinks every time the frame is redrawn */ + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS - if (heartbeat) - display->setPixel(0, 0); - heartbeat = !heartbeat; + if (heartbeat) + display->setPixel(0, 0); + heartbeat = !heartbeat; #endif #endif - } - - void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - display->setFont(FONT_SMALL); +} - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); - - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } +void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setFont(FONT_SMALL); - char batStr[20]; - if (powerStatus->getHasBattery()) - { - int batV = powerStatus->getBatteryVoltageMv() / 1000; - int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10; + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(), - powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' '); + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } - // Line 1 - display->drawString(x, y, batStr); - if (config.display.heading_bold) - display->drawString(x + 1, y, batStr); - } - else - { - // Line 1 - display->drawString(x, y, String("USB")); - if (config.display.heading_bold) - display->drawString(x + 1, y, String("USB")); - } + char batStr[20]; + if (powerStatus->getHasBattery()) { + int batV = powerStatus->getBatteryVoltageMv() / 1000; + int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10; - auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true); + snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(), + powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' '); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode); + // Line 1 + display->drawString(x, y, batStr); if (config.display.heading_bold) - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode); - - // Line 2 - uint32_t currentMillis = millis(); - uint32_t seconds = currentMillis / 1000; - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; - // currentMillis %= 1000; - // seconds %= 60; - // minutes %= 60; - // hours %= 24; - - display->setColor(WHITE); + display->drawString(x + 1, y, batStr); + } else { + // Line 1 + display->drawString(x, y, String("USB")); + if (config.display.heading_bold) + display->drawString(x + 1, y, String("USB")); + } - // Show uptime as days, hours, minutes OR seconds - std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds); - - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); - if (rtc_sec > 0) - { - long hms = rtc_sec % SEC_PER_DAY; - // hms += tz.tz_dsttime * SEC_PER_HOUR; - // hms -= tz.tz_minuteswest * SEC_PER_MIN; - // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) - hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; - - // Tear apart hms into h:m:s - int hour = hms / SEC_PER_HOUR; - int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; - int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN - - char timebuf[10]; - snprintf(timebuf, sizeof(timebuf), " %02d:%02d:%02d", hour, min, sec); - uptime += timebuf; - } + auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true); + + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode); + if (config.display.heading_bold) + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode); + + // Line 2 + uint32_t currentMillis = millis(); + uint32_t seconds = currentMillis / 1000; + uint32_t minutes = seconds / 60; + uint32_t hours = minutes / 60; + uint32_t days = hours / 24; + // currentMillis %= 1000; + // seconds %= 60; + // minutes %= 60; + // hours %= 24; + + display->setColor(WHITE); + + // Show uptime as days, hours, minutes OR seconds + std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds); + + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); + if (rtc_sec > 0) { + long hms = rtc_sec % SEC_PER_DAY; + // hms += tz.tz_dsttime * SEC_PER_HOUR; + // hms -= tz.tz_minuteswest * SEC_PER_MIN; + // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + // Tear apart hms into h:m:s + int hour = hms / SEC_PER_HOUR; + int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN + + char timebuf[10]; + snprintf(timebuf, sizeof(timebuf), " %02d:%02d:%02d", hour, min, sec); + uptime += timebuf; + } - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, uptime.c_str()); - - // Display Channel Utilization - char chUtil[13]; - snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent()); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil); - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) - { - // Line 3 - if (config.display.gps_format != - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude - drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); - - // Line 4 - drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus); - } - else - { - drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); - } - /* Display a heartbeat pixel that blinks every time the frame is redrawn */ + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, uptime.c_str()); + + // Display Channel Utilization + char chUtil[13]; + snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent()); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil); + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { + // Line 3 + if (config.display.gps_format != + meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude + drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); + + // Line 4 + drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus); + } else { + drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); + } + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS - if (heartbeat) - display->setPixel(0, 0); - heartbeat = !heartbeat; + if (heartbeat) + display->setPixel(0, 0); + heartbeat = !heartbeat; #endif +} + +int Screen::handleStatusUpdate(const meshtastic::Status *arg) +{ + // LOG_DEBUG("Screen got status update %d\n", arg->getStatusType()); + switch (arg->getStatusType()) { + case STATUS_TYPE_NODE: + if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { + setFrames(); // Regen the list of screens + } + nodeDB.updateGUI = false; + break; } - int Screen::handleStatusUpdate(const meshtastic::Status *arg) - { - // LOG_DEBUG("Screen got status update %d\n", arg->getStatusType()); - switch (arg->getStatusType()) - { - case STATUS_TYPE_NODE: - if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) - { - setFrames(); // Regen the list of screens - } - nodeDB.updateGUI = false; - break; - } + return 0; +} - return 0; +int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) +{ + if (showingNormalScreen) { + setFrames(); // Regen the list of screens (will show new text message) } - int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) - { - if (showingNormalScreen) - { + return 0; +} + +int Screen::handleUIFrameEvent(const UIFrameEvent *event) +{ + if (showingNormalScreen) { + if (event->frameChanged) { setFrames(); // Regen the list of screens (will show new text message) + } else if (event->needRedraw) { + setFastFramerate(); + // TODO: We might also want switch to corresponding frame, + // but we don't know the exact frame number. + // ui->switchToFrame(0); } - - return 0; } - int Screen::handleUIFrameEvent(const UIFrameEvent *event) - { - if (showingNormalScreen) - { - if (event->frameChanged) - { - setFrames(); // Regen the list of screens (will show new text message) - } - else if (event->needRedraw) - { - setFastFramerate(); - // TODO: We might also want switch to corresponding frame, - // but we don't know the exact frame number. - // ui->switchToFrame(0); - } - } - - return 0; - } + return 0; +} - int Screen::handleInputEvent(const InputEvent *event) - { - if (showingNormalScreen && moduleFrames.size() == 0) - { - // LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source); - if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) - { - showPrevFrame(); - } - else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) - { - showNextFrame(); - } +int Screen::handleInputEvent(const InputEvent *event) +{ + if (showingNormalScreen && moduleFrames.size() == 0) { + // LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source); + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { + showPrevFrame(); + } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { + showNextFrame(); } - - return 0; } + return 0; +} + } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}