t5s3 ui fixes; t5s3 initial ble and wifi companion build envs

This commit is contained in:
pelgraine
2026-03-12 00:08:55 +11:00
parent 2a0497e5ba
commit 7ae9c47006
6 changed files with 132 additions and 71 deletions
+3 -3
View File
@@ -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
@@ -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++;
@@ -6,6 +6,13 @@
#include <MeshCore.h>
#include "../NodePrefs.h"
// Inline edit hint shown next to values being adjusted
#if defined(LilyGo_T5S3_EPaper_Pro)
#define EDIT_ADJ_HINT "<Swipe>"
#else
#define EDIT_ADJ_HINT "<W/S>"
#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 <W/S>", _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 <W/S>", _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 <W/S>", _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 <W/S>", _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 <W/S>", _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 <W/S>", _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) {
@@ -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;
+27 -7
View File
@@ -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
+63 -49
View File
@@ -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}
; +<helpers/esp32/*.cpp>
; +<helpers/ui/MomentaryButton.cpp>
; +<helpers/ui/FastEPDDisplay.cpp>
; +<../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}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/ui/FastEPDDisplay.cpp>
+<../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}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/ui/FastEPDDisplay.cpp>
+<../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}
; +<helpers/esp32/*.cpp>
; +<helpers/ui/MomentaryButton.cpp>
; +<helpers/ui/FastEPDDisplay.cpp>
; +<../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
; (same as headless but explicitly named for clarity)
; ---------------------------------------------------------------------------
[env:meck_t5s3_standalone]
extends = env:meck_t5s3_headless