1290 Commits

Author SHA1 Message Date
Jakub
e2d59b9a3d fix(prefs): use magic sentinel to validate page_order
Previously page_order_set status was inferred from page_order[0] being
in 1..11, so a single junk byte in that range falsely triggered
custom-order mode. Add explicit page_order_set magic (0xA5) written
alongside the array; pre-magic saves are migrated once on load when
their first byte still passes the legacy range check.

Also fix(ui): keep "@[nick] " prefix out of placeholder expansion in
QuickMsgScreen reply path, so a nick containing a token like {loc}
isn't substituted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:53:05 +02:00
Jakub
5fde79fae7 fix(ui): translate keyboard preview buffer through UTF-8 handler
KeyboardWidget printed the text preview directly via display.print()
without calling translateUTF8ToBlocks first. When a reply prefix
contained a UTF-8 sender name (e.g. Cyrillic), raw multi-byte sequences
reached the display driver on non-Lemon renders.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 15:28:10 +02:00
Jakub
a6bf3ee08f feat(eink): full refresh interval setting in Settings › Display
Add "Full rfsh" setting (e-ink only) with options off/5/10/20/30
partial refreshes between full refreshes. Prevents ghosting on panels
that accumulate artifacts after many partial updates.

GxEPDDisplay counts partial refreshes and calls display.display(false)
every N frames when the interval is set. Persisted in DataStore and
applied at startup via applyFullRefreshInterval().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 10:22:03 +02:00
Jakub
8012278483 fix(ui): remove space in hibernate hint formatting 2026-05-24 00:56:43 +02:00
Jakub
34fcab6bd5 feat(ui): show MeshCore version large and plus version in splash bar
Add MESHCORE_VERSION build flag ("1.15") to companion base configs.
Splash screen now shows MESHCORE_VERSION in the large text and the
plus firmware version (stripped of commit hash) in the Plus bar,
e.g. "1.15" large + "Plus v1.11 for Wio" below.

Change CI tag pattern to v* and simplify version extraction so tag
v1.11 maps directly to FIRMWARE_VERSION=v1.11.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:35:07 +02:00
Jakub
31179d2175 fix(ui): repair missing Clock entry in page_order and show positions immediately
ensurePageOrderInit now scans for CLOCK (bit 0, stored as 1) after the
existing page_order[0] validity check — if Clock is absent (partial or
corrupted saved order), it resets the array and re-initialises all pages.

renderItem calls ensurePageOrderInit before displaying home page positions
so that position numbers appear immediately on entering Settings rather
than only after the first reorder action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:50:25 +02:00
Jakub
b279b95b2d refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
Jakub
05335420e3 fix(ui): home page order and visibility bugs
- home_pages_mask XOR toggle initialises to HP_ALL before flipping bit,
  preventing accidental hiding of all other pages when mask starts at 0
- buildVisibleOrder appends any visible page missing from custom page_order,
  so pages absent from migrated/corrupted prefs remain navigable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:11:24 +02:00
Jakub
52faa0e2f6 fix(ui): restrict joystick rotation setting to e-ink builds only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 15:33:03 +02:00
Jakub
fea1be8e81 feat(ui): runtime joystick rotation independent of display rotation
Add NodePrefs::joystick_rotation (0-3 steps CW), persisted in DataStore
alongside display_rotation. rotateJoystickKey() now takes a runtime rot
parameter instead of compile-time JOYSTICK_ROTATION macro. UITask::loop()
reads joystick_rotation from NodePrefs each frame. Settings screen exposes
"Joystick" rotation item on any build with UI_HAS_JOYSTICK. JOYSTICK_ROTATION
macro still works as compile-time default for legacy/non-prefs builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 15:20:40 +02:00
Jakub
85cfb6f1af fix(ui): unified unknown-char block rendering and display layout fixes
- GxEPDDisplay::print() renders '\xDB' as filled rect for Lemon font,
  matching OLED Lemon block behavior
- Remove translateUTF8ToBlocks override (base class passthrough sufficient)
- transliterateCodepoint fallback changed '?' → '\xDB' for consistency
- drawTextCentered/Right/LeftAlign now call translateUTF8ToBlocks
- isLemonFont() added to DisplayDriver, SH1106Display, GxEPDDisplay
- isLandscape() guarded by EINK_DISPLAY_MODEL to distinguish OLED vs e-ink
- ind_h trims indicator box height only for Lemon font
- getTextWidth disables wrap before getTextBounds, restores after
- QuickMsgScreen: tighter hist_item padding; 2 messages visible with Lemon
- Hibernate hint fits on one line when width allows, splits otherwise
- BLE PIN page shows toggle hint on landscape e-ink
- Remove unused muted_icon bitmap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:45:24 +02:00
Jakub
6cdd9d01d4 Merge branch 'wio-tracker-l1-improvements' into wio-eink-unified 2026-05-23 10:22:52 +02:00
Jakub
a0a74fe5d3 fix(eink): replace mute bitmap icon with text letter M
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:14:05 +02:00
Jakub
2b7fc37491 fix(eink): 30s clock/lock refresh, static auto-advert indicator
- Clock page: 60s → 30s refresh (time shown without seconds, 30s is sufficient)
- Lock screen: 60s → 30s refresh
- Auto-advert "A" indicator: remove blink on e-ink (always shown when active);
  blink preserved on OLED via EINK_DISPLAY_MODEL guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:06:22 +02:00
Jakub
93e3a29b0d fix: align ON/OFF/always to fixed column flush with right edge in Home Pages
Column at width()-6*charWidth so "always" touches the right edge;
ON and OFF start at the same x position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:54:31 +02:00
Jakub
4f68e5ffba fix: fixed-width position prefix in Home Pages settings to prevent label shift
Use %2d (right-aligned, always 3 chars) instead of %d so label x-position
stays constant when going from single-digit (1-9) to double-digit (10-11).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:46:28 +02:00
Jakub
d3589e293f feat: sort DM contact list by message count (most messages first)
Contacts with at least one message float to the top, ordered by
total message count descending. Contacts with no messages keep their
original relative order. Sorting is applied each time the message
screen is opened (buildContactList), so the list always reflects the
current session's activity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:19:27 +02:00
ripplebiz
26d0001301 Merge pull request #2608 from recrof/flood-advert-interval-47
Set flood advert interval to 47h for new installs
2026-05-23 15:37:31 +10:00
Jakub
7510878f9e fix: center-crop splash logo in portrait mode via negative x offset
Revert logo width clamp — instead let x go negative so GxEPD's drawPixel
clips symmetrically. In portrait (122px wide) x = (122-128)/2 = -3,
cropping 3px from each side rather than 6px from the right only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:53:16 +02:00
Jakub
4dd36f685d fix: apply rotation before splash screen, clip logo to display width
- Move applyRotation() before setCurrScreen(splash) so e-ink startup
  screen respects the saved display_rotation preference
- Clamp meshcore logo width to display.width() so it doesn't overflow
  in portrait mode (122px screen vs 128px logo)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:50:21 +02:00
Jakub
bad9fae419 merge: bring home page ordering feature and CI dual-build from wio-tracker-l1-improvements
Resolved conflicts to preserve eink-ui layout helpers (valCol, lineStep, sepH)
while integrating the page_order persistence, buildVisibleOrder(), and
Settings/Messages ordering UI from the wio-tracker-l1-improvements branch.
display_rotation is now written before page_order in the save/load sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:46:35 +02:00
Jakub
bb94a6f81f feat: home screen ordering — reorder all screens from Settings
NodePrefs: add page_order[11] (1-indexed bit-index, 0=end/legacy).
DataStore: persist page_order appended after use_lemon_font.

UITask/HomeScreen:
- bitToPage(): maps stable bit indices 0-10 to HomePage enum values
- buildVisibleOrder(): respects page_order when initialised, else
  falls back to default enum sequence
- navPage() and navigation dots use buildVisibleOrder()

SettingsScreen Home Pages section:
- Add Settings (bit 9) and Messages (bit 10) — show as "always"
- LEFT/RIGHT on any home page item moves it earlier/later in order
- ENTER toggles ON/OFF for pages that can be disabled
- Position number shown before label once custom order is active
- ensurePageOrderInit() seeds default order on first reorder action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:33:06 +02:00
Jakub
9c671891d9 fix: e-ink portrait/landscape layout — separators, battery, keyboard, screens
- DisplayDriver: add sepH() (2px landscape, 1px OLED); fix transliterateCodepoint
  fallback '?' (was '\xDB' which broke UTF-8 word-wrap and rendered tiny block char)
- GxEPDDisplay: drawRect draws double border on landscape for visible 2px weight
- UITask: navigation dots scale with lh; content_y saves 16px by using dots_y+6;
  battery iconW=lh*2, fill margin bm scales with orientation; indicator spacing fixed;
  clock separator uses sepH(); alert popup tight and vertically centred
- KeyboardWidget: compact cell height (no stretch), multi-line preview, cursor always
  on last visible line
- BotScreen / NearbyScreen detail: use lineStep() as natural item height, shrink only
  when items don't fit (fixes portrait stretch)
- QuickMsgScreen: send button pinned to display bottom (height-lh-2), not floating
- SettingsScreen: AUTO_OFF guarded with #if AUTO_OFF_MILLIS>0; default sel=AUTO_LOCK
- All screens: separators updated to sepH()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:13:27 +02:00
Jakub
118f29b0a1 fix: landscape e-ink layout corrections — clock overlap, keyboard overflow, bar sizing, nearby detail
- GxEPDDisplay: fontAscender only applies to sz=1 with Lemon; sz=2 (NULL font)
  was incorrectly getting a 16px cursor offset, causing clock text to render
  16px lower than expected and overlap the date line
- KeyboardWidget: compute cell_h from available screen height instead of lh+1;
  tighten sep_y to lh so the grid fits within 122px even with Lemon font (lh=20)
- SettingsScreen renderBar: constrain box size to available width so the 5th
  buzzer-volume square doesn't overflow past the right edge with Lemon font
- NearbyScreen discover detail: manually truncate b64 public key by charWidth
  to guarantee one-line rendering; use dynamic step = (height-hdr)/5 so
  Status line doesn't fall off the bottom of the screen
- NearbyScreen contacts detail: merge dist+az into one line and use dynamic
  step to fit 5 lines within the display; removes the off-screen Seen: row
- AutoAdvertScreen: replace lineStep() gap before hints with 4px fixed gap
  so both hint lines fit within the display with large fonts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 19:11:25 +02:00
Jakub
1d8ca25469 fix: adaptive layout for e-ink landscape across all remaining screens
- GxEPDDisplay: sz2 uses 4× scale on landscape (clock 32px vs body 16px)
- UITask: clock date_y and splash date_y computed from lh2 after setTextSize(2)
- UITask: battery/mute/BT/advert indicators scale with lh; muted uses text 'M'
- UITask: hibernate hint split into two lines to prevent wrapping
- ToolsScreen: full refactor — headerH, listStart, lineStep, getCharWidth
- KeyboardWidget: all cell sizes computed from display metrics (cell_w, cell_h, spec_w)
- RingtoneEditorScreen: cell_w = charWidth×2+6 (was hardcoded 18)
- SettingsScreen: BRIGHTNESS hidden on e-ink; renderBar boxes scale with lh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:34:20 +02:00
Jakub
f8fb1dac99 fix: align SettingsScreen item label to dynamic char width
Replace hardcoded x=8 with getCharWidth()+2 so the label doesn't
overlap the '>' selector on displays with larger fonts (e-ink sz1 scale=2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:13:27 +02:00
Jakub
744f263932 refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.

On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
Rastislav Vysoky
a37078f6c5 Companion repeat: update to more usable default EU frequency
869.495 is in 869.4 to 869.65 range which does have 10% duty cycle and 500mW ERP.
2026-05-22 17:04:19 +02:00
Jakub
33ad70221e feat(eink): suppress clock seconds to prevent per-second panel refreshes
E-ink panels take ~2 s for a full refresh and visible flicker on every update.
Showing seconds caused the clock page to redraw every 1 s, triggering a real
panel refresh each time the CRC changed.

- Default clock_hide_seconds=1 for EINK_DISPLAY_MODEL builds so fresh
  installs don't flicker out of the box.
- Remove the Seconds setting from SettingsScreen on e-ink so users can't
  accidentally re-enable it.
- Clock page now returns 60 s refresh interval on e-ink (content changes at
  most once per minute); other home pages capped at 30 s (new messages still
  force immediate redraw via notify()).
- Lock screen refresh interval raised from 1 s to 60 s on e-ink; button
  presses already reset _next_refresh=0, so unlock feedback is instant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:47:19 +02:00
Jakub
6a3b8e3135 feat: add runtime display rotation setting for e-ink builds
Adds Settings > Display > Rotation (0°/90°/180°/270°) that persists to
NodePrefs and is applied on startup. Falls back to compile-time
DISPLAY_ROTATION when the field is missing from an older prefs file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:58:33 +02:00
Jakub
319428c7c6 chore: remove unused FS_START_Y constant from FullscreenMsgView
startY is computed dynamically from header_h, making the constant redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:34:24 +02:00
Jakub
ca5eb221e7 fix: persist use_lemon_font setting across reboots
The font preference was not saved to or loaded from flash, so it would
reset to default (Adafruit font) on every boot. Added to the versioned
DataStore read/write chain in the same pattern as other recently added
fields (auto_lock, dm_melody etc.).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:34:24 +02:00
Jakub
4161951d19 fix: force immediate redraw after font switch in applyFont()
Without _next_refresh = 0 the Settings screen would continue using its
2-second refresh interval after a font change, showing stale rendering
for up to 2 seconds. Mirrors the pattern used by setBrightnessLevel().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:33:35 +02:00
Jakub
a115916d8f feat: runtime font switcher — Default vs Lemon in Settings > Display
- Add Lemon font (LemonFont.h, LemonIcons.h) to the standard build
  alongside the Adafruit built-in font; ~101 KB added to flash
- SH1106Display: add _use_lemon flag + setLemonFont(bool) to switch
  at runtime; print() and getTextWidth() use Lemon path when active;
  translateUTF8ToBlocks() passes UTF-8 through unchanged (Lemon supports
  full Unicode U+0020-U+04FF) vs transliterating for the default font
- DisplayDriver: add getCharWidth()/getLineHeight() virtuals (defaults 6/8);
  SH1106Display returns 5/9 for Lemon, 6/8 for default; add setLemonFont()
  no-op virtual; fix orphaned UTF-8 leading byte in drawTextEllipsized
- FullscreenMsgView: replace fixed-char-count word-wrap with pixel-accurate
  wrap using display.getTextWidth(); use getLineHeight() for spacing so
  Lemon (9 px) and default (8 px) both render correctly
- NodePrefs: add use_lemon_font field (0=default, 1=Lemon)
- SettingsScreen: add "Font" item in Display section (Default / Lemon),
  calls UITask::applyFont() on change
- UITask: add applyFont() that calls display->setLemonFont(use_lemon_font);
  called at startup (begin()) and when the setting changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:33:35 +02:00
Kevin Le
da7663a6fb Fixed hasPendingWork for BLE companions 2026-05-22 10:58:21 +07:00
Kevin Le
280213d41f Fixed to put hasPendingWork out of ENV_INCLUDE_GPS 2026-05-22 10:58:21 +07:00
Kevin Le
6b1099161c Added Power Saving for NRF52 companions 2026-05-22 10:58:17 +07:00
Jakub
72c07df8b3 fix: code review corrections in DualSerialInterface and NearbyScreen
- DualSerialInterface: guard writeFrame/isWriteBusy/isBLEConnected with
  _ble_enabled to avoid BLE-after-disable race
- NearbyScreen: initialize all fields in constructor; promote D_* layout
  constants to class scope; clamp _dscroll on result count change;
  replace hardcoded 2 with D_VISIBLE in scroll logic; allow re-scan
  from detail view via KEY_CONTEXT_MENU/KEY_ENTER
- variant.cpp: fix inconsistent indentation in initVariant()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:08:28 +02:00
Jakub
38cd0fada3 feat: add 12h/24h clock format setting (default 24h)
Toggle in Settings > Display > Format. In 12h mode the clock shows
h:mm AM/PM (or h:mm:ss AM/PM with seconds, no space before AM/PM).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:04:43 +02:00
Jakub
e0312c2b6b fix: strip @[nick] reply prefix in channel and DM history card previews
Reply prefix was eating ~9 chars of display width, causing the actual
message body to be ellipsized in list cards. Fullscreen view already
showed the recipient correctly via the To: header line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:37:25 +02:00
Rastislav Vysoky
01500e55d8 Change room server flood advert interval to 47 hours 2026-05-21 14:53:49 +02:00
Rastislav Vysoky
40180b8fe6 Update repeater flood advert interval to 47 hours 2026-05-21 14:50:58 +02:00
taco
26c641bc63 tiny ui rendering fixes 2026-05-21 19:58:10 +10:00
taco
9f683975c1 add torch 2026-05-21 19:58:10 +10:00
taco
8540c98947 add tinyui for lilygo t-echo card 2026-05-21 19:58:10 +10:00
Jakub
6b2f2ecf01 fix: replace base64.hpp include with local encoder to fix ODR link error
base64.hpp is header-only; including it in a .h file pulled into multiple
translation units caused duplicate symbol errors at link time.

Replaced with a self-contained static pubKeyToBase64() method inside
NearbyScreen that implements the same standard base64 encoding without
any external include.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:14:16 +02:00
Jakub
52d97ed314 feat: show pub_key as base64 in discover detail screen
Replaces "Type: Repeater" line with the node's full public key encoded
as base64 (44 chars). drawTextEllipsized clips it with ... to fit the
128px wide display. Type is already visible in the inverted header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:11:32 +02:00
Jakub
3b7915d0b7 feat: discover detail screen + card selection + Rem removed from card body
Card body now shows only "RSSI:-87 SNR:7" (Rem was too long to fit).

List view:
- UP/DOWN selects items; selected card fully inverted, unselected keeps header-only highlight
- Enter opens full-screen detail for selected node
- Long-press Enter (context menu) re-scans

Detail screen:
- Inverted title bar with node name (or [Type])
- Type / RSSI / SNR / Rem / Status lines
- Cancel or context menu returns to discover list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:05:15 +02:00
Jakub
2da37b3af5 feat: 2-line boxed discover entries with RSSI, SNR, remote SNR
Each discovered node now renders as a boxed 2-line card (same style as
message history):
  ┌──────────────────────────────┐
  │ NodeName (or [Sensor])  Type │  ← inverted header
  │ RSSI:-87 SNR:7 Rem:5         │  ← signal stats
  └──────────────────────────────┘

Add snr_x4 and remote_snr_x4 to DiscoverResult:
  snr_x4       = _radio->getLastSNR()*4  (how well we heard the response)
  remote_snr_x4 = payload[1]             (how well responder heard our request)

2 items visible at a time; UP/DOWN scroll through results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 08:56:54 +02:00
Jakub
2a9874d05d feat: show RSSI in discover results list
Add rssi field to DiscoverResult, captured from _radio->getLastRSSI()
when the discover response is received.

Display format in the right column:
  known node  → "Rpt-87" / "Snsr-92" (type + dBm)
  new node    → "*-87" (asterisk + dBm; type already visible in "[Rpt]" label)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 08:51:43 +02:00