From 4bbdf4b07104667a0bcb5876303d19af69344faf Mon Sep 17 00:00:00 2001 From: mikecarper Date: Mon, 31 Aug 2026 16:10:03 -0700 Subject: [PATCH] Enlarge Indicator native setup screen --- examples/companion_radio/main.cpp | 98 +++++++++++++++++++- src/helpers/ui/CompanionHomeLayout.h | 5 +- src/helpers/ui/IndicatorRenderProfile.h | 10 ++ test/test_indicator_display_profile.py | 26 ++++++ test/test_indicator_render_profile.py | 24 +++++ variants/sensecap_indicator-espnow/README.md | 5 + 6 files changed, 163 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 6f8491286..71fa13aef 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1182,6 +1182,88 @@ void halt() { && companion_setup_display->height() >= 128; } + static bool hasNativeIndicatorSetupTypography() { +#if defined(INDICATOR_TRANSPORT_RENDER_PROFILE) + return mesh::ui::usesNativeIndicatorTypography( + companion_setup_display->width(), + companion_setup_display->height(), + companion_setup_display->renderWidth(), + companion_setup_display->renderHeight()); +#else + return false; +#endif + } + + static int drawNativeIndicatorSetupValue(int y, const char* value) { + companion_setup_display->setTextSize(2); + // The setup AP has a stable prefix. Split on its semantic boundary so + // the two large centered rows read cleanly instead of wrapping midway + // through "Setup". + if (value != nullptr + && strncmp(value, "MeshCore-Setup-", 15) == 0) { + companion_setup_display->drawTextCentered( + companion_setup_display->width() / 2, y, "MeshCore-"); + companion_setup_display->drawTextCentered( + companion_setup_display->width() / 2, y + 21, value + 9); + return 2; + } + const int max_width = companion_setup_display->width() - 12; + int line_height = 21; + // A configured SSID can contain 32 bytes. Keep ordinary names at the + // large size, but let the longest legal value use two complete size-1 + // rows instead of silently losing its tail. + if (value != nullptr + && companion_setup_display->getTextWidth(value) > max_width * 2) { + companion_setup_display->setTextSize(1); + line_height = 11; + } + return mesh::ui::drawTextWrapped( + *companion_setup_display, 6, y, + max_width, line_height, 2, + value != nullptr && value[0] != 0 ? value : "(not set)"); + } + + static void drawNativeIndicatorSetupAddress(int y, const char* address) { + const char* value = address != nullptr && address[0] != 0 + ? address : "(not set)"; + companion_setup_display->setTextSize(2); + if (companion_setup_display->getTextWidth(value) + > companion_setup_display->width()) { + companion_setup_display->setTextSize(1); + } + companion_setup_display->drawTextCentered( + companion_setup_display->width() / 2, y, value); + } + + static void renderNativeIndicatorSetupDisplay(const char* title, + const char* wifi_name, + const char* address, + bool connecting) { + companion_setup_display->setTextSize(3); + companion_setup_display->drawTextCentered( + companion_setup_display->width() / 2, 2, title); + + companion_setup_display->setTextSize(2); + companion_setup_display->drawTextCentered( + companion_setup_display->width() / 2, 34, + connecting ? "CONNECTING" : "JOIN WIFI"); + drawNativeIndicatorSetupValue(56, wifi_name); + + if (connecting) { + companion_setup_display->setTextSize(2); + companion_setup_display->drawTextCentered( + companion_setup_display->width() / 2, 126, "WAIT..."); + return; + } + + companion_setup_display->setTextSize(2); + companion_setup_display->drawTextCentered( + companion_setup_display->width() / 2, 101, "BROWSE"); + // The scheme is implicit here so the address itself can remain nearly + // panel-wide at size 2. Long DHCP addresses fall back to size 1. + drawNativeIndicatorSetupAddress(124, address); + } + static int drawCompanionSetupValue(int y, int max_lines, const char* value) { static constexpr int margin = 6; @@ -1225,6 +1307,7 @@ void halt() { companion_setup_display->turnOn(); companion_setup_display->startFrame(); + companion_setup_display->setCompactText(false); companion_setup_display->setTextSize(1); companion_setup_display->setColor(UIColor::primary_txt); @@ -1232,7 +1315,10 @@ void halt() { char setup_ip[16] = {0}; if (WebConfigServer::getSetupInfo(setup_ssid, sizeof(setup_ssid), setup_ip, sizeof(setup_ip))) { - if (hasLargeCompanionSetupDisplay()) { + if (hasNativeIndicatorSetupTypography()) { + renderNativeIndicatorSetupDisplay( + "SETUP", setup_ssid, setup_ip, false); + } else if (hasLargeCompanionSetupDisplay()) { renderLargeCompanionSetupDisplay( "WebUI setup", "Join open WiFi:", setup_ssid, setup_ip, false); } else { @@ -1249,7 +1335,10 @@ void halt() { } } else if (WiFi.status() == WL_CONNECTED) { const String ip = WiFi.localIP().toString(); - if (hasLargeCompanionSetupDisplay()) { + if (hasNativeIndicatorSetupTypography()) { + renderNativeIndicatorSetupDisplay( + "READY", configured_wifi_ssid, ip.c_str(), false); + } else if (hasLargeCompanionSetupDisplay()) { renderLargeCompanionSetupDisplay( "WebUI ready", "Join WiFi:", configured_wifi_ssid, ip.c_str(), false); @@ -1266,7 +1355,10 @@ void halt() { companion_setup_display->width() / 2, 51, ip.c_str()); } } else { - if (hasLargeCompanionSetupDisplay()) { + if (hasNativeIndicatorSetupTypography()) { + renderNativeIndicatorSetupDisplay( + "WIFI", configured_wifi_ssid, nullptr, true); + } else if (hasLargeCompanionSetupDisplay()) { renderLargeCompanionSetupDisplay( "WiFi connecting", "WiFi name:", configured_wifi_ssid, nullptr, true); diff --git a/src/helpers/ui/CompanionHomeLayout.h b/src/helpers/ui/CompanionHomeLayout.h index f1a28e979..78cd71f74 100644 --- a/src/helpers/ui/CompanionHomeLayout.h +++ b/src/helpers/ui/CompanionHomeLayout.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace mesh { namespace ui { @@ -37,8 +38,8 @@ struct CompactCompanionPairingLayout { // unrelated displays that happen to have a tall viewport. inline bool usesExpandedCompanionHomeTypography( int width, int height, int render_width, int render_height) { - return width == 160 && height == 160 - && render_width == 480 && render_height == 480; + return usesNativeIndicatorTypography( + width, height, render_width, render_height); } // The common Companion OLED/TFT viewport is 128x64. Its normal instruction diff --git a/src/helpers/ui/IndicatorRenderProfile.h b/src/helpers/ui/IndicatorRenderProfile.h index 5e08fb4fa..21c491703 100644 --- a/src/helpers/ui/IndicatorRenderProfile.h +++ b/src/helpers/ui/IndicatorRenderProfile.h @@ -11,6 +11,16 @@ struct IndicatorRenderProfile { uint16_t canvas_size; }; +// Full-size Indicator builds retain the established 160x160 logical +// coordinate system even when the backing canvas is the panel's native +// 480x480. Screens with enough room to reflow can use this distinction to +// choose substantially larger text without changing the 320px BLE fallback. +inline bool usesNativeIndicatorTypography( + int width, int height, int render_width, int render_height) { + return width == 160 && height == 160 + && render_width == 480 && render_height == 480; +} + // ESP-NOW and BLE are the Indicator's tightest internal-RAM combination. // Every other exclusive Full-Companion combination can use a native 480px // internal canvas while preserving the established 160px UI coordinates. diff --git a/test/test_indicator_display_profile.py b/test/test_indicator_display_profile.py index ffd594928..bf55427e2 100644 --- a/test/test_indicator_display_profile.py +++ b/test/test_indicator_display_profile.py @@ -164,6 +164,32 @@ class IndicatorDisplayProfileTest(unittest.TestCase): self.assertLessEqual(pairing_label_y + 29, pairing_value_y) self.assertLessEqual(pairing_value_y + 29, 160) + # Native setup mode intentionally uses most of the panel: a size-3 + # title and size-2 action/value rows replace the legacy size-1 page. + for text, requested_size in ( + ("SETUP", 3), + ("READY", 3), + ("WIFI", 3), + ("CONNECTING", 2), + ("JOIN WIFI", 2), + ("BROWSE", 2), + ("192.168.4.1", 2), + ): + width, _height = dimensions(text, requested_size, 3, 1.2) + self.assertLessEqual(width, 160) + + title_height = dimensions("SETUP", 3, 3, 1.2)[1] + row_height = dimensions("JOIN WIFI", 2, 3, 1.2)[1] + self.assertLessEqual(2 + title_height, 34) + self.assertLessEqual(34 + row_height, 56) + self.assertLessEqual(56 + row_height, 77) + self.assertLessEqual(77 + row_height, 101) + self.assertLessEqual(101 + row_height, 124) + self.assertLessEqual(124 + row_height, 160) + + long_ssid = dimensions("ABCDEFGHIJKLMNOPQRSTUVWXYZ123456", 1, 3, 1.2) + self.assertLessEqual(long_ssid[0], 2 * 148) + if __name__ == "__main__": unittest.main() diff --git a/test/test_indicator_render_profile.py b/test/test_indicator_render_profile.py index 549cea88f..56cbc56ba 100644 --- a/test/test_indicator_render_profile.py +++ b/test/test_indicator_render_profile.py @@ -36,6 +36,9 @@ int main() { if (mesh::ui::selectIndicatorTextScale(480, false) != 1.2f) return 5; if (mesh::ui::selectIndicatorTextScale(480, true) != 1.0f) return 6; if (mesh::ui::selectIndicatorTextScale(320, false) != 1.0f) return 7; + if (!mesh::ui::usesNativeIndicatorTypography(160, 160, 480, 480)) return 8; + if (mesh::ui::usesNativeIndicatorTypography(160, 160, 320, 320)) return 9; + if (mesh::ui::usesNativeIndicatorTypography(128, 128, 480, 480)) return 10; return 0; } ''' @@ -120,6 +123,27 @@ int main() { self.assertIn("static_cast(renderWidth()), _compactText", display) self.assertIn("* profile_scale;", display) + def test_native_setup_page_uses_large_reflow(self): + main = MAIN.read_text(encoding="utf-8") + start = main.index("static bool hasNativeIndicatorSetupTypography()") + end = main.index("static bool saveCompanionWiFi", start) + setup = main[start:end] + self.assertIn("usesNativeIndicatorTypography", setup) + self.assertIn("renderNativeIndicatorSetupDisplay", setup) + self.assertIn('setTextSize(3);', setup) + self.assertIn('connecting ? "CONNECTING" : "JOIN WIFI"', setup) + self.assertIn('drawNativeIndicatorSetupValue(56, wifi_name);', setup) + self.assertIn('strncmp(value, "MeshCore-Setup-", 15) == 0', setup) + self.assertIn('y, "MeshCore-"', setup) + self.assertIn('y + 21, value + 9', setup) + self.assertIn('getTextWidth(value) > max_width * 2', setup) + self.assertIn('line_height = 11;', setup) + self.assertIn('101, "BROWSE"', setup) + self.assertIn('drawNativeIndicatorSetupAddress(124, address);', setup) + self.assertIn('"SETUP", setup_ssid, setup_ip, false', setup) + self.assertIn('"READY", configured_wifi_ssid, ip.c_str(), false', setup) + self.assertIn('"WIFI", configured_wifi_ssid, nullptr, true', setup) + def test_lora_fresh_install_uses_compiled_cascade_defaults(self): profile = PROFILE.read_text(encoding="utf-8") lora = profile[profile.index("[SenseCapIndicator-LoRa]") :] diff --git a/variants/sensecap_indicator-espnow/README.md b/variants/sensecap_indicator-espnow/README.md index 4d0ad1e77..1ac939542 100644 --- a/variants/sensecap_indicator-espnow/README.md +++ b/variants/sensecap_indicator-espnow/README.md @@ -144,6 +144,11 @@ uses separate full-width size-4 `INBOX` and count rows plus a large lower action or BLE-status block. Long IP addresses retain a smaller bounded row instead of clipping. +The native-480 WiFi setup/status screen is reflowed separately: its title is +size 3, its join/browse instructions and setup SSID are size 2, and the IP +address remains size 2 whenever it fits. The legacy 320 and other display +layouts keep their existing compact setup page. + LoRa remains the primary radio in both LoRa modes. On the ESP-NOW layout, selecting BLE leaves the primary ESP-NOW WiFi radio and its fixed channel running; it disables only infrastructure WiFi. Rendering follows the memory