From 7ae9c47006c47c21e4815fd86422dd9825c49a9c Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:08:55 +1100 Subject: [PATCH] t5s3 ui fixes; t5s3 initial ble and wifi companion build envs --- examples/companion_radio/main.cpp | 6 +- .../companion_radio/ui-new/ChannelScreen.h | 6 +- .../companion_radio/ui-new/Settingsscreen.h | 35 ++++-- .../companion_radio/ui-new/Textreaderscreen.h | 10 +- examples/companion_radio/ui-new/UITask.cpp | 34 ++++-- .../lilygo_t5s3_epaper_pro/platformio.ini | 112 ++++++++++-------- 6 files changed, 132 insertions(+), 71 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 9b7617b8..99d4429b 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1101,9 +1101,9 @@ void setup() { } #endif - // T-Deck Pro: BLE starts disabled for standalone-first operation - // User can toggle it on from the Bluetooth home page (Enter or long-press) - #if defined(LilyGo_TDeck_Pro) && defined(BLE_PIN_CODE) + // BLE starts disabled for standalone-first operation + // User can toggle it from the Bluetooth home page (Enter or long-press) + #if (defined(LilyGo_TDeck_Pro) || defined(LilyGo_T5S3_EPaper_Pro)) && defined(BLE_PIN_CODE) serial_interface.disable(); MESH_DEBUG_PRINTLN("setup() - BLE disabled at boot (standalone mode)"); #endif diff --git a/examples/companion_radio/ui-new/ChannelScreen.h b/examples/companion_radio/ui-new/ChannelScreen.h index 3de22f87..ffdfd688 100644 --- a/examples/companion_radio/ui-new/ChannelScreen.h +++ b/examples/companion_radio/ui-new/ChannelScreen.h @@ -811,7 +811,9 @@ public: if (wb == ' ' || isEmojiEscape(wb)) break; charStr[0] = (char)wb; dblStr[0] = dblStr[1] = (char)wb; - wordW += display.getTextWidth(dblStr) - display.getTextWidth(charStr); + int charAdv = display.getTextWidth(dblStr) - display.getTextWidth(charStr); + if (charAdv < 1) charAdv = 1; + wordW += charAdv; } if (px + wordW > lineW) { px = 0; @@ -840,6 +842,7 @@ public: charStr[0] = ' '; dblStr[0] = dblStr[1] = ' '; int adv = display.getTextWidth(dblStr) - display.getTextWidth(charStr); + if (adv < 1) adv = 1; // Minimum advance (rounding fix for proportional fonts) if (px + adv > lineW) { px = 0; linesForThisMsg++; @@ -857,6 +860,7 @@ public: charStr[0] = (char)b; dblStr[0] = dblStr[1] = (char)b; int adv = display.getTextWidth(dblStr) - display.getTextWidth(charStr); + if (adv < 1) adv = 1; // Minimum advance (rounding fix for proportional fonts) if (px + adv > lineW) { px = 0; linesForThisMsg++; diff --git a/examples/companion_radio/ui-new/Settingsscreen.h b/examples/companion_radio/ui-new/Settingsscreen.h index 9b4a53d9..8e8e8925 100644 --- a/examples/companion_radio/ui-new/Settingsscreen.h +++ b/examples/companion_radio/ui-new/Settingsscreen.h @@ -6,6 +6,13 @@ #include #include "../NodePrefs.h" +// Inline edit hint shown next to values being adjusted +#if defined(LilyGo_T5S3_EPaper_Pro) + #define EDIT_ADJ_HINT "" +#else + #define EDIT_ADJ_HINT "" +#endif + #ifdef HAS_4G_MODEM #include "ModemManager.h" #endif @@ -640,7 +647,7 @@ public: case ROW_BW: if (editing && _editMode == EDIT_NUMBER) { - snprintf(tmp, sizeof(tmp), "BW: %.1f ", _editFloat); + snprintf(tmp, sizeof(tmp), "BW: %.1f " EDIT_ADJ_HINT, _editFloat); } else { snprintf(tmp, sizeof(tmp), "BW: %.1f kHz", _prefs->bw); } @@ -649,7 +656,7 @@ public: case ROW_SF: if (editing && _editMode == EDIT_NUMBER) { - snprintf(tmp, sizeof(tmp), "SF: %d ", _editInt); + snprintf(tmp, sizeof(tmp), "SF: %d " EDIT_ADJ_HINT, _editInt); } else { snprintf(tmp, sizeof(tmp), "SF: %d", _prefs->sf); } @@ -658,7 +665,7 @@ public: case ROW_CR: if (editing && _editMode == EDIT_NUMBER) { - snprintf(tmp, sizeof(tmp), "CR: %d ", _editInt); + snprintf(tmp, sizeof(tmp), "CR: %d " EDIT_ADJ_HINT, _editInt); } else { snprintf(tmp, sizeof(tmp), "CR: %d", _prefs->cr); } @@ -667,7 +674,7 @@ public: case ROW_TX_POWER: if (editing && _editMode == EDIT_NUMBER) { - snprintf(tmp, sizeof(tmp), "TX: %d dBm ", _editInt); + snprintf(tmp, sizeof(tmp), "TX: %d dBm " EDIT_ADJ_HINT, _editInt); } else { snprintf(tmp, sizeof(tmp), "TX: %d dBm", _prefs->tx_power_dbm); } @@ -676,7 +683,7 @@ public: case ROW_UTC_OFFSET: if (editing && _editMode == EDIT_NUMBER) { - snprintf(tmp, sizeof(tmp), "UTC: %+d ", _editInt); + snprintf(tmp, sizeof(tmp), "UTC: %+d " EDIT_ADJ_HINT, _editInt); } else { snprintf(tmp, sizeof(tmp), "UTC Offset: %+d", _prefs->utc_offset_hours); } @@ -691,7 +698,7 @@ public: case ROW_PATH_HASH_SIZE: if (editing && _editMode == EDIT_NUMBER) { - snprintf(tmp, sizeof(tmp), "Path Hash Size: %d-byte ", _editInt); + snprintf(tmp, sizeof(tmp), "Path Hash Size: %d-byte " EDIT_ADJ_HINT, _editInt); } else { snprintf(tmp, sizeof(tmp), "Path Hash Size: %d-byte", _prefs->path_hash_mode + 1); } @@ -1002,8 +1009,22 @@ public: display.setTextSize(0); if (_editMode == EDIT_NONE) { display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Hold: Edit Boot: Home"); + } else if (_editMode == EDIT_NUMBER) { + display.drawTextCentered(display.width() / 2, footerY, "Swipe Up/Down: Adjust Tap: OK Boot: Cancel"); + } else if (_editMode == EDIT_PICKER) { + display.drawTextCentered(display.width() / 2, footerY, "Swipe Left/Right: Choose Tap: OK"); + } else if (_editMode == EDIT_CONFIRM) { + display.drawTextCentered(display.width() / 2, footerY, "Tap: Confirm Boot: Cancel"); + #ifdef MECK_WIFI_COMPANION + } else if (_editMode == EDIT_WIFI) { + if (_wifiPhase == WIFI_PHASE_SELECT) { + display.drawTextCentered(display.width() / 2, footerY, "Swipe: Pick Tap: Select Boot: Back"); + } else { + display.drawTextCentered(display.width() / 2, footerY, "Please wait..."); + } + #endif } else { - display.print("Editing..."); + display.drawTextCentered(display.width() / 2, footerY, "Editing..."); } #else if (_editMode == EDIT_TEXT) { diff --git a/examples/companion_radio/ui-new/Textreaderscreen.h b/examples/companion_radio/ui-new/Textreaderscreen.h index 656d17e2..68155516 100644 --- a/examples/companion_radio/ui-new/Textreaderscreen.h +++ b/examples/companion_radio/ui-new/Textreaderscreen.h @@ -1058,16 +1058,18 @@ public: } #if defined(LilyGo_T5S3_EPaper_Pro) // FreeSans12pt is proportional — "M" is the widest character. - // Using M-width gives ~56 chars/line but actual text only fills 60% of screen. - // Re-measure with representative lowercase text for realistic average width. + // Re-measure with representative lowercase text, then apply 1.5× multiplier + // because real English text averages much narrower than the sample string. + // With proportional fonts, character-count-based wrapping is inherently + // approximate — some lines will be shorter, but none should overflow. { uint16_t sampleW = display.getTextWidth("abcdefghijklmno"); // 15 chars if (sampleW > 0) { - _charsPerLine = (display.width() * 15) / sampleW; + _charsPerLine = (display.width() * 15 * 3) / (sampleW * 2); // ×1.5 } } if (_charsPerLine < 15) _charsPerLine = 15; - if (_charsPerLine > 120) _charsPerLine = 120; // Proportional fonts can fit many chars + if (_charsPerLine > 120) _charsPerLine = 120; #else if (_charsPerLine < 15) _charsPerLine = 15; if (_charsPerLine > 60) _charsPerLine = 60; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 86cacdc9..99e849c6 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -31,6 +31,8 @@ #if UI_HAS_JOYSTICK #define PRESS_LABEL "press Enter" +#elif defined(LilyGo_T5S3_EPaper_Pro) + #define PRESS_LABEL "hold Boot btn" #else #define PRESS_LABEL "long press" #endif @@ -156,7 +158,11 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts, // layout: [icon 16px][cap 2px][gap 2px][text][margin 2px] int totalWidth = iconWidth + 2 + 2 + textWidth + 2; int iconX = display.width() - totalWidth; +#if defined(LilyGo_T5S3_EPaper_Pro) + int iconY = 4; // Shift down to match header text offset +#else int iconY = 0; // vertically align with node name text +#endif if (outIconX) *outIconX = iconX; @@ -253,7 +259,14 @@ public: display.setColor(DisplayDriver::GREEN); char filtered_name[sizeof(_node_prefs->node_name)]; display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name)); - display.setCursor(0, -3); +#if defined(LilyGo_T5S3_EPaper_Pro) + // T5S3: FreeSans12pt ascenders need more room than built-in font. + // Shift header elements down by 4 virtual units (~17px physical). + #define HOME_HDR_Y 1 +#else + #define HOME_HDR_Y -3 +#endif + display.setCursor(0, HOME_HDR_Y); display.print(filtered_name); // battery voltage @@ -285,13 +298,17 @@ public: display.setColor(DisplayDriver::LIGHT); uint16_t tw = display.getTextWidth(timeBuf); int clockX = (display.width() - tw) / 2; - display.setCursor(clockX, -3); // align with battery text Y + display.setCursor(clockX, HOME_HDR_Y); // align with node name Y display.print(timeBuf); display.setTextSize(1); // restore } } // curr page indicator +#if defined(LilyGo_T5S3_EPaper_Pro) + int y = 18; +#else int y = 14; +#endif int x = display.width() / 2 - 5 * (HomePage::Count-1); for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) { if (i == _page) { @@ -305,7 +322,11 @@ public: #if defined(LilyGo_T5S3_EPaper_Pro) _task->setHomeShowingTiles(true); #endif +#if defined(LilyGo_T5S3_EPaper_Pro) + int y = 24; +#else int y = 20; +#endif display.setColor(DisplayDriver::YELLOW); display.setTextSize(2); sprintf(tmp, "MSG: %d", _task->getUnreadMsgCount()); @@ -1093,10 +1114,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i ((ChannelScreen *) channel_screen)->markChannelRead(channel_idx); } -#if defined(LilyGo_TDeck_Pro) - // T-Deck Pro: Don't interrupt user with popup - just show brief notification - // Messages are stored in channel history, accessible via 'M' key - // Suppress alert entirely on admin screen - it needs focused interaction +#if defined(LilyGo_TDeck_Pro) || defined(LilyGo_T5S3_EPaper_Pro) + // Don't interrupt user with popup - just show brief notification + // Messages are stored in channel history, accessible via tile/key if (!isOnRepeaterAdmin()) { char alertBuf[40]; snprintf(alertBuf, sizeof(alertBuf), "New: %s", from_name); @@ -1470,7 +1490,7 @@ char UITask::handleTripleClick(char c) { if (board.isBacklightOn()) { board.setBacklight(false); // If already on, turn off } else { - board.setBacklightBrightness(4); + board.setBacklightBrightness(40); board.setBacklight(true); } #else diff --git a/variants/lilygo_t5s3_epaper_pro/platformio.ini b/variants/lilygo_t5s3_epaper_pro/platformio.ini index 95e3277e..ece7ff1d 100644 --- a/variants/lilygo_t5s3_epaper_pro/platformio.ini +++ b/variants/lilygo_t5s3_epaper_pro/platformio.ini @@ -94,55 +94,69 @@ lib_deps = https://github.com/lewisxhe/SensorLib/archive/refs/tags/v0.3.4.zip ; --------------------------------------------------------------------------- -; Phase 3+ variants (uncomment when touch input is implemented) -; --------------------------------------------------------------------------- - ; T5S3 BLE companion — touch UI, BLE phone bridging -;[env:meck_t5s3_ble] -;extends = LilyGo_T5S3_EPaper_Pro -;build_flags = -; ${LilyGo_T5S3_EPaper_Pro.build_flags} -; -I examples/companion_radio/ui-new -; -D MAX_CONTACTS=500 -; -D MAX_GROUP_CHANNELS=20 -; -D BLE_PIN_CODE=123456 -; -D OFFLINE_QUEUE_SIZE=256 -; -D DISPLAY_CLASS=FastEPDDisplay -; -D USE_EINK -; -D MECK_WEB_READER=1 -;build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter} -; + -; + -; + -; +<../examples/companion_radio/*.cpp> -; +<../examples/companion_radio/ui-new/*.cpp> -;lib_deps = -; ${LilyGo_T5S3_EPaper_Pro.lib_deps} -; densaugeo/base64 @ ~1.4.0 -; bitbank2/PNGdec@^1.0.1 -; adafruit/Adafruit GFX Library@^1.11.0 -; https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip +; Connect via MeshCore iOS/Android app over Bluetooth +; Flash: pio run -e meck_t5s3_ble -t upload +; --------------------------------------------------------------------------- +[env:meck_t5s3_ble] +extends = LilyGo_T5S3_EPaper_Pro +build_flags = + ${LilyGo_T5S3_EPaper_Pro.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=500 + -D MAX_GROUP_CHANNELS=20 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=FastEPDDisplay + -D USE_EINK + ; -D MECK_SERIF_FONT +build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_T5S3_EPaper_Pro.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit GFX Library@^1.11.0 + https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip + https://github.com/lewisxhe/SensorLib/archive/refs/tags/v0.3.4.zip +; --------------------------------------------------------------------------- +; T5S3 WiFi companion — touch UI, WiFi phone bridging +; Connect via MeshCore web app or meshcore.js over local network (TCP:5000) +; Flash: pio run -e meck_t5s3_wifi -t upload +; --------------------------------------------------------------------------- +[env:meck_t5s3_wifi] +extends = LilyGo_T5S3_EPaper_Pro +build_flags = + ${LilyGo_T5S3_EPaper_Pro.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=1500 + -D MAX_GROUP_CHANNELS=20 + -D MECK_WIFI_COMPANION=1 + -D TCP_PORT=5000 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=FastEPDDisplay + -D USE_EINK + ; -D MECK_SERIF_FONT +build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_T5S3_EPaper_Pro.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit GFX Library@^1.11.0 + https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip + https://github.com/lewisxhe/SensorLib/archive/refs/tags/v0.3.4.zip + +; --------------------------------------------------------------------------- ; T5S3 standalone — touch UI, no BLE/WiFi, maximum battery life -;[env:meck_t5s3_standalone] -;extends = LilyGo_T5S3_EPaper_Pro -;build_flags = -; ${LilyGo_T5S3_EPaper_Pro.build_flags} -; -I examples/companion_radio/ui-new -; -D MAX_CONTACTS=1500 -; -D MAX_GROUP_CHANNELS=20 -; -D OFFLINE_QUEUE_SIZE=256 -; -D DISPLAY_CLASS=FastEPDDisplay -; -D USE_EINK -;build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter} -; + -; + -; + -; +<../examples/companion_radio/*.cpp> -; +<../examples/companion_radio/ui-new/*.cpp> -;lib_deps = -; ${LilyGo_T5S3_EPaper_Pro.lib_deps} -; densaugeo/base64 @ ~1.4.0 -; bitbank2/PNGdec@^1.0.1 -; adafruit/Adafruit GFX Library@^1.11.0 -; https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip \ No newline at end of file +; (same as headless but explicitly named for clarity) +; --------------------------------------------------------------------------- +[env:meck_t5s3_standalone] +extends = env:meck_t5s3_headless \ No newline at end of file