Reuses dashboard_fields[0] and [1] from NodePrefs — left-aligned and
right-aligned in one row under the clock, no labels to save space.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Long press Back starts lock sequence; 3 Enter presses within 3s toggle lock
- Locked screen wakes for 5 seconds on any key, showing clock and unlock hint
- Incoming messages do not wake the display while locked
- Auto Lock setting in Display section: locks automatically when screen turns off
- Lock state persisted in UITask; NodePrefs gains auto_lock flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static screens (SettingsScreen, BotScreen, FullscreenMsgView, message
list, contact picker) were re-rendering every 300ms with no visible
benefit. New messages still trigger immediate refresh via newMsg() →
_next_refresh=100, so responsiveness is unchanged. Reduces unnecessary
SPI transfers and CPU wakeups ~6x on idle static screens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Text lines fill the full display width, causing the last chars to bleed
into the scroll arrow area. Draw a DARK fillRect behind each arrow before
rendering it, so the arrow appears on a clean black background without
truncating the text width.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single shared _bot_last_reply_ms caused a DM reply to block the
channel bot for 10s and vice versa. Split into _bot_last_dm_reply_ms
and _bot_last_ch_reply_ms so each cooldown is independent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- with_sender[160] could hold only ~160 chars but node_name(32) +
expanded(200) needs up to 234; snprintf would silently truncate
the UI display entry. Increased to 240.
- FS_LINE_H was 9 but Adafruit 5x7 font line height is 8, wasting
1px per line and showing 5 lines instead of 6.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Channel messages have the format "sender_name: body". The trigger was
being searched across the whole string, so a nick containing the trigger
word would fire a reply even with an unrelated message. Now the match
skips past the ": " separator and only checks the message body.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Loading... screen was always shown at full brightness because prefs
are not yet loaded at that point. Apply setBrightness() in the first
common code path after the_mesh.begin() loads prefs, so at least the
tail of the loading screen and the entire splash screen respect the
saved brightness level.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Size 2 rendered text wider than display width. Switched to size 1
and adjusted y-positions to keep both lines visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Raw UTF-8 message body was passed directly to display.print(), rendering
Polish and other accented chars as garbage on the Adafruit font.
Transliterating before wrapLines also fixes byte-count wrapping for
multi-byte UTF-8 characters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Names printed via drawTextEllipsized (contacts, senders) bypassed
translateUTF8ToBlocks, causing raw UTF-8 bytes to render as garbage
on the Adafruit font. Now transliteration happens inside the method.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the block-character fallback with proper transliteration for
Polish (ą→a, ć→c, ę→e, ł→l, ń→n, ó→o, ś→s, ź→z, ż→z), German (ä ö ü ß),
and common French/Spanish/Portuguese accents. Unknown codepoints fall back
to '?' instead of █.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add getCharWidth()/getLineHeight() virtual methods to DisplayDriver so
all layout math adapts to whatever font is active
- Fix Lemon font baseline offset from +8 to +6 (cap height, not FONT_ASCENT)
- Add LemonIcons.h with Private Use Area glyph table; U+E03B mute icon
rendered via drawLemonChar icon lookup
- FullscreenMsgView: compute wrap width and visible lines dynamically from
display font metrics instead of hardcoded constants
- SettingsScreen and MessagesScreen: replace hardcoded ITEM_H/VISIBLE with
members computed at render() time from getLineHeight()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add LemonFont.h: Adafruit GFX bitmap font converted from lemon.bdf,
covering U+0020–U+017F (ASCII + Latin Extended A/B, 319/352 glyphs)
- SH1106Display: custom UTF-8 renderer for size 1 text using Lemon font;
size >1 falls back to default built-in font
- translateUTF8ToBlocks() overridden to pass-through (no block substitution)
- No RAM impact: font bitmap stored in PROGMEM (Flash)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Splash screen bar changes from "Plus for Wio" to "Plus 1.4 for Wio"
by extracting the suffix after "plus." from FIRMWARE_VERSION.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Expand README: ringtone editor now documents two melody slots, full
note spec, and melody assignment to DM/channel notifications; settings
section lists DM Melody and Channel Melody options; context menu
description updated with melody override detail
- Rename quick reply template labels from M1-M10 to Q1-Q10 to avoid
ambiguity with melody slot identifiers M1/M2
- Increase DM message history buffer from 32 to 64 entries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously empty slots used drawRect and filled slots used fillRect,
so at max value (e.g. volume=4) no drawRect was called and no slot
borders were visible when selected (inverted colors). Brightness at
default (2/5) always had visible outlines, making the two bars look
inconsistent.
Now drawRect is called for every slot, with fillRect(+1,+1,w-2,h-2)
on top for filled ones. All 5 slots are always visually distinct.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DataStore: validate ringtone2_len <= 32 after loading from flash
(same guard as ringtone_len on line 252; we introduced this field
but forgot the validation)
- UITask: fix shutdown buzzer timeout to conventional unsigned form
(millis() - start) < timeout to avoid unsigned underflow if called
within first 2.5s of boot
- UITask: guard _last_notif_ch_idx < 64 before 1ULL shift in notify()
(defensive; MAX_GROUP_CHANNELS=40 so currently unreachable, but
prevents UB if the value is ever unexpectedly out of range)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
##*- strips to the last hyphen, so wio-tracker-v1.15-plus.1.3 yielded
"plus.1.3" instead of "v1.15-plus.1.3". Switch to #*-v (shortest prefix
ending with -v) which preserves hyphens inside the version string.
Also remove the -Plus suffix appended in build_wio_tracker_l1_firmwares()
since the version scheme already encodes "plus" in the tag/version string,
making the suffix redundant and producing doubled output like "plus.1.3-Plus".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tone() dynamically computes seq_refresh so the DMA repeats 50% duty for
~13-30ms per note before re-reading its buffer — volume cannot take effect
until that delay passes, producing a staircase artifact.
Replace tone()/NonBlockingRtttl with a custom RTTTL parser that drives
NRF_PWM2 directly using REFRESH=0. DMA now re-reads _duty_buf every
PWM period so:
- Duty is set before SEQSTART fires → no initial full-volume burst
- applyVolume() writes to _duty_buf and takes effect within one period (<2.3ms at A4)
- setVolume() during playback adjusts loudness immediately and smoothly
Non-nRF52 platforms continue to use NonBlockingRtttl + analogWrite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add second ringtone slot (ringtone2_*) to NodePrefs and DataStore.
Add per-channel and per-DM melody assignment (built-in / melody 1 / 2)
stored in NodePrefs bitmasks and DmMelodyEntry table.
Settings screen exposes DM/CH sound items; RingtoneEditorScreen supports
switching between slots. notify() uses buildMelodyFromPrefs() to select
the correct RTTTL string per contact.
Remove broken nRF52 volume control from buzzer.cpp: tone() uses
NRF_PWM2 with DECODER.MODE=Auto so TASKS_NEXTSTEP has no effect and
duty cannot be changed before the first DMA read without patching the
Arduino core. applyVolume() is now a no-op on nRF52.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Swap left/right navigation in fullscreen view: left=newer, right=older
- Update arrow indicators to match new direction
- Remove # prefix from channel name in message and compose view titles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move "A" (auto-advert) indicator left of BT "B" in status bar
- Ellipsize node name to fit available width instead of overflowing
- Shrink A and B background rectangles by 1px right, 2px bottom
- Change A blink to 2s on / 2s off at 1000ms refresh rate
- Auto-advert sends GPS position directly via createSelfAdvert/sendZeroHop
- Align Settings and Tools home page titles to y=22, same as Messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BT indicator and mute icon were at y=1 (1px below text baseline).
Battery icon height reduced 10→8px to match text height.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Changed ~ to " A" (space + capital A, no inverted background)
- Blink: on 500ms / off 1500ms (once per 2s)
- Home screen refresh rate drops to 500ms when auto-advert is active
so the blink is actually visible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blinks at 4Hz (250ms toggle) with inverted background so it's clearly
visible without being distracting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace getAvailableLPPTypes() (hardware-detected) with a live querySensors()
call so only placeholders for sensors currently returning values appear in
the picker.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scan does not discover others — it announces our own presence.
Updated labels: "Scan for nodes"→"Send my advert", "SCANNING..."→"ADVERTISING...", "[M]=Scan"→"[M]=Advert".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Detail view now shows "Seen: Xs/Xm/Xh ago" using ContactInfo.lastmod
- Periodic detail refresh exits back to list view if the contact is no
longer in the filtered list (e.g. removed from favourites)
- Rearranged detail layout to fit 6 rows within 64px display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "Fav" as first filter category (contacts with flags & 1)
- Show distance and bearing on separate lines in detail view
- Add cardinal direction to bearing: "Az: 245d (SW)"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ping sent empty messages appearing in target's chat history, and RTT
measurement never worked for direct sendMessage() calls. Removed all
ping state machinery and registerExpectedAck(); detail view now shows
node info only (lat/lon/dist/bearing/type).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds configurable periodic 0-hop self-advert that includes GPS coordinates,
making the device's position visible in other nodes' Nearby Nodes screen.
Options: off / 1min / 2min / 5min / 10min / 30min / 1h.
Accessible via Tools → Auto-Advert. Timer runs in MyMesh::loop().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Direct sendMessage() calls bypass the BT serial handler that populates
expected_ack_table; add registerExpectedAck() to MyMesh and call it from
NearbyScreen so isAckPending() can actually track the outstanding ping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove gps_lat/lon == 0 filter: screen now shows all contacts
reachable by radio; distance column shows ?GPS when coords unavailable
- Guard haversine: only compute distance when both own AND remote GPS
are valid (prevents bogus distance to 0 N 0 E)
- Fix sort: nodes without GPS (-1) sort to end instead of front
- Rename Chat filter label to Comp, type display to Companion
- Update empty list message to "No contacts found"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stale _ping_ack_seen=true from a previous ping attempt could cause an
immediate PING_OK (with wrong RTT) on the next ping if the new ACK entry
hadn't appeared in the table yet. Reset the flag everywhere _ping_state
is set to PING_IDLE.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Ping: add _ping_ack_seen flag; RTT is measured only after ACK entry
was first confirmed present in the table, preventing false 0ms when
isAckPending() returns false before the entry is registered
- NearbyScreen: replace unsupported degree symbol \xb0 with 'd'
in bearing format ("Dist:1.5km 45d")
- Clock page: hide page indicator dots (already hides node name/battery);
eliminates overlap with size-2 clock text drawn at y=0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BotScreen and DashboardConfigScreen were calling savePrefs() on every
exit regardless of whether any setting changed. Add _dirty flag set only
on actual value changes (toggle, channel select, keyboard confirm,
field cycle), reset on enter().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>