Font-switcher is now part of wio-tracker-l1-improvements, so there is
no longer a separate branch to check out. Drop the build-font-switcher
job and its download step in release; a single build produces the
combined dual-BLE/USB + font-switcher firmware.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
- 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>
- 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>
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>
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>
- DualSerialInterface: enable()/disable() control BLE only, USB always on;
USB state machine not read while BLE connected to avoid partial-frame
corruption; reset USB on BLE disconnect for clean reconnect
- Fix -UBLE_DEBUG_LOGGING (no space) to avoid PlatformIO SCons parse error
- build.sh: drop separate USB/BLE builds, dual_settings only
- wio-tracker-l1-eink: enable DUAL_SERIAL in companion_radio_ble build
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
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>
The !isBLEConnected() check was too restrictive — if BLE happened to be
connected (app in background) the standalone discover would silently drop
all responses. Tag matching already provides correct isolation: responses
matching _pending_node_discover_tag are handled by the standalone UI and
not forwarded to the BLE app; responses with a different tag fall through
to the normal forward path.
Also show short type names (Rpt/Snsr/Room) in the discover results list
instead of generic "known"/"NEW", with a '*' prefix for nodes not yet
in contacts — matching the repeater/sensor distinction the app shows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[M] key doesn't exist on the device; context menu is long-press Enter.
Also handle KEY_ENTER directly when the list is empty so the hint
is accurate — pressing Enter with no contacts enters discover mode
without going through the context menu.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the inline advert scan in the nearby list with a proper
CTL_TYPE_NODE_DISCOVER_RESP-based sub-screen accessible via the context menu.
- DiscoveredEntry → DiscoverResult: adds name[32] and is_known flag so
both known contacts and unknown nodes are shown with useful labels
- getDiscoveredNodes → getDiscoverResults, capacity 8 → 16
- onControlDataRecv populates name from contacts for known nodes and
records all respondents (not just unknowns)
- NearbyScreen gains _discover_mode sub-screen: 8-second scan window,
live result polling each render cycle, UP/DOWN scroll, CANCEL to
return, ENTER/M to re-scan; known nodes show actual name + "known",
unknown nodes show "[Type]" + "NEW"
- List view is clean: no discovered nodes mixed in, no scanning banner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NearbyScreen now sends CTL_TYPE_NODE_DISCOVER_REQ (same protocol as the
companion app) instead of a plain self-advert. Nearby repeaters, sensors
and room servers respond with their pub_key and type. Known contacts get
their lastmod refreshed; unknown nodes appear as temporary entries (e.g.
"Repeater") in the list until their advert is received.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BLE takes priority when connected; USB is always ready as fallback.
Both state machines run continuously — no BLE disconnect on USB connect.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Build firmware from the font-switcher branch instead of font-lemon.
The font-switcher build includes both Adafruit and Lemon fonts in one
binary with a runtime toggle in Settings > Display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use inverted PWM polarity (0x8000) with PCT {2,5,12,25,50} giving
~6-8 dB perceptual steps (-24/-16/-9/-3/0 dB) vs the original
uneven steps where levels 4 and 5 were only 1 dB apart
- Guard against cmp=0 truncation in both _nrfStartPwm and applyVolume:
at level 1 with high notes (freq > ~2.5 kHz) integer math gives
cmp=0 which with inverted polarity means 100% HIGH → complete silence
- Change volume preview note from C5 (523 Hz) to C6 (1047 Hz) to better
match the piezo resonant frequency range and actual notification sounds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stores RTC timestamp on each history entry and renders it right-aligned
on the sender row using a compact format (Xs/Xm/Xh/>1d).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Describes the font-lemon branch variant with native Unicode rendering,
pixel-accurate word wrap, and a link to the Lemon font source.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add build-lemon-font job that checks out the font-experiments branch and
builds with FIRMWARE_VERSION suffixed with -lemon-font, producing files
like WioTrackerL1_*-v1.15-plus.1.7-lemon-font-SHA.uf2. A separate
release job collects artifacts from both builds into one draft release.
Tag pattern narrowed from wio-tracker-* to wio-tracker-v* to avoid
triggering on wio-tracker-lemon-* tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reply prefix changed to "@[nick] " format — bracket delimiter handles
nicks with spaces unambiguously
- FullscreenMsgView: parse "@[nick] body" to show "To: nick" header;
fall back gracefully if format not matched
- Reduce FS_CHARS 21→20 to prevent scroll arrows overlapping last char
- Transliterate nick before displaying in "To:" header and "RE:" title
so non-ASCII characters render correctly on the OLED font
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_ctx_menu.render() was only called in CONTACT_PICK and CHANNEL_PICK,
so the reply popup was active but invisible in history views. Added
render calls for list view and fullscreen view in both DM_HIST and
CHANNEL_HIST phases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Long-press Enter (KEY_CONTEXT_MENU) on a selected message shows an
'Options / Reply' popup in both list and fullscreen views. Confirming
opens MSG_PICK with 'RE: nick' title — the reply can be sent via
keyboard or quick message templates, both automatically prefixed with
'@nick '.
- Channel: reply prefix extracted from 'sender: message' format
- DM: reply available only on incoming messages, uses contact name
- FullscreenMsgView: REPLY added to Result enum, KEY_CONTEXT_MENU handled
- MSG_PICK: _reply_mode flag routes prefix through keyboard and templates
- Guard: prevent sending empty reply (prefix-only keyboard input ignored)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a message starts with @nick, the fullscreen view expands the header
to show a second row 'To: nick' below the sender name, and displays the
message body without the @nick prefix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
custom_msgs templates are up to 140 chars; the 80-byte msg[] buffer
silently truncated any message longer than 79 characters before sending.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tryBotReplyChannel checked bot_channel_enabled but not bot_enabled,
so disabling the bot via the main Enable toggle had no effect on
channel replies. Added bot_enabled to the guard condition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
auto_lock was defined in NodePrefs but missing from both loadPrefsInt
and savePrefs in DataStore.cpp. Added at the end of the serialization
chain (inside if(file.available())) for backwards compatibility with
existing saved files — old files default to auto_lock=0 (disabled).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously each LPP sensor field called querySensors() separately.
Now a single querySensors() call fills a shared buffer used by both fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously every key press while locked reset _lock_wake_until to now+5s,
so multiple presses (or spurious button events) kept extending the window.
Now the 5s timer is set only when the display first wakes from dark.
The unlock sequence still extends the window on each Back+Enter step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ð/Ð (eth), þ/Þ (thorn) for Icelandic, and ţ/Ţ (t with cedilla)
for legacy-encoded Romanian text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>