Sideband/Columba and other LXMF clients commonly include "/" in display
names (e.g. handle/path conventions). The allowlist in sanitizeName
stripped it, mangling "alice/columba" into "alicecolumba". Saved
contacts get re-sanitized on each load (line 393), so existing names
also lose the slash on next boot.
Python LXMF was sending bz2-compressed Resource transfers that
microReticulum cannot decode on ESP32 (bz2's working memory exceeds
available SRAM). The companion microReticulum commit (290a133)
removes bz2 entirely; this release advertises no-compression-support
in our LXMF announces so Python LXMF disables auto_compress when
sending to Ratdeck destinations.
Also pins lvgl and LovyanGFX minor versions in lib_deps to prevent
future fresh-clone builds from pulling incompatible 1.2.x / 8.4.x.
Thanks to @RFNexus for surfacing the ESP32 bz2 constraint.
encodeAnnounceName now emits msgpack fixarray(3):
[display_name(bin), stamp_cost=0, supported_functionality=[]]
Empty supported_functionality list = SF_COMPRESSION (=0x00) absent,
so Python LXMF disables auto_compress for our destinations and stops
shipping bz2-compressed Resources we can't decode. Always emit the
3-element form even when the name is empty — Python defaults to
auto_compress=True for legacy <3-element app_data.
Pairs with microReticulum 290a133 which removed bz2 entirely.
LovyanGFX 1.2.x bundles its own lvgl-compat headers that collide
with lvgl >=8.4 on shared types (lv_area_t, lv_font_t, etc.).
Tilde restricts fresh clones to the 1.1.x and 8.3.x ranges this
build was originally cached against, so `pio run` from a fresh
checkout doesn't randomly break.
CORE_DEBUG_LEVEL=0 drops Arduino core noise on expected-missing files, LittleFSFileSystem::open_file now actually works so path_table persists across reboots.
Link proof signature, TCP proof routing, and split-packet handling
were causing intermittent message failures with Python LXMF clients.
Route >254B messages through link delivery, add proof retry for LoRa.
Regenerated Montserrat 10/12/14 with Cyrillic and Latin Extended
Unicode ranges so messages in Russian, Ukrainian, Bulgarian, and
accented European languages render properly instead of rectangles.
Scroll: scrollToSelected() was defined but never called — LVGL didn't
know which pool row to show after viewport updates. Nodes at the
bottom of long lists were invisible. Now called after every sync.
Modal: Replace lv_obj_center() with explicit pixel positioning so
the action modal renders centered regardless of list scroll state.
Trackball: Click now sets character='\n' matching keyboard Enter for
complete event parity.
Timestamps: Incoming messages with invalid sender timestamps
(< Nov 2023 epoch) are now stamped with the receiver's local time
if available. Previously, messages from devices without NTP/GPS
showed no timestamp at all.
Frequency editor: Replace raw 9-digit Hz accumulator with a
radio-style digit cursor. Display shows NNN.NNN.NNN format with
bracket cursor on active digit (e.g., "920.[6]50.500"). Trackball
left/right moves cursor, digit keys replace at cursor and advance.
Esc restores original value. Matches amateur radio transceiver UX.
Status bar: Cache last displayed hour/minute, skip LVGL label
update when minute hasn't changed (eliminates 59/60 redundant
invalidations per minute).
RNS tables: Increase ANNOUNCE_TABLE_MAX 32→48 (prevents premature
eviction on networks >32 nodes). Increase RATE_TIMESTAMPS_MAX
16→32 (prevents false rate-limit rejects on busy networks).
BLE: Add 5-minute idle timeout. Stale connections now get
disconnected to free the single NimBLE connection slot for new
devices. Activity tracked on connect, RX, and TX events.
Message view: Extract appendMessage() from rebuildMessages(). New
messages are now incrementally appended (O(delta) widget ops) instead
of destroying and recreating all bubbles (O(N)). Full rebuild only
on initial load or message count decrease.
WiFi TX: Buffer HDLC frame in PSRAM then send with single write()
call. Previously sent per-byte (200+ syscalls per packet), now 1
syscall. Reduces CPU overhead and TCP fragmentation.
Font: Disable unused LV_FONT_UNSCII_8 (compiled but never referenced).
TCP: Move 2KB stack-allocated packet rewrite buffers in
send_outgoing() to a PSRAM-allocated member (_wrapBuffer).
The ESP32-S3 loop task has 8KB stack — a single 2KB VLA used
25%, risking overflow under deep call chains during TCP load.
WiFi AP: Cap concurrent client connections at 4. Previously
unbounded — any number of clients could connect and exhaust
memory. New clients beyond the limit get a clean TCP RST.
Previously, "Reboot Required!" fired on every applyAndSave() once
any WiFi setting changed — showing 3 times during a single STA
setup (mode + SSID + password). Now the toast only shows on first
detection with a gentler message: "WiFi changes apply on reboot".
Subsequent saves show "Saved" normally.
Two-layer fix:
- NMEAParser: parse time fields from RMC sentences even when status
is void ('V'). The MIA-M10Q's battery-backed RTC provides time in
every sentence — we were discarding it along with the void location.
- GPSManager: allow first time sync without satellite lock. Subsequent
syncs still require sats>0 for accuracy. Year/epoch validation still
guards against garbage data from uninitialized modules.
Previously, time only appeared after WiFi connection (NTP via
configTzTime). Now the clock shows within seconds of boot using the
GPS module's RTC, then corrects when satellites lock.
refreshUI() detected and displayed new messages but never called
markRead() or updated the tab badge. Now marks conversation as read
and refreshes the badge whenever new messages are loaded while the
user is actively viewing the conversation.
Replace direct-message-on-Enter with a 3-option modal overlay:
- "Add Contact" prompts for a nickname (pre-filled with announce
name, falls back to hex hash if left empty), then saves contact
- "Message" opens conversation (previous Enter behavior)
- "Back" closes modal (also Esc at any point)
Long press on unsaved nodes now also shows the modal instead of
silently adding. Long press on saved contacts keeps the existing
delete confirmation flow.
Backend:
- LoRaInterface captures packet RSSI/SNR immediately after every RX,
removing redundant SPI re-reads from log statements
- AnnounceManager populates DiscoveredNode.rssi/snr from LoRaInterface
on every announce (fields existed but were never wired up)
Frontend:
- Nodes screen shows RSSI appended to hops/age when dev mode is on
- Node name label uses smaller font (montserrat_12) to reduce truncation
- Normal users see no change
Screen idle:
- Remove powerMgr.activity() from radio yield callback — screen no
longer wakes on LoRa RX, auto-announce, or RSSI polls
- Add [POWER] state transition logging for on-device diagnostics
Radio regions:
- Add 4-region system: Americas (915), Europe (868), Australia (915),
Asia (923) with timezone-to-region mapping
- Region picker in Radio settings (always visible)
- Presets now apply region-appropriate frequency + modulation params
- Timezone change warns if it suggests a different radio region
Frequency input (developer mode):
- Hz-precision input (9 digits) displayed as MHz with trimmed decimals
- Left/right arrows step by 125 kHz, digit keys for direct entry
- Inspired by @strijar in https://github.com/ratspeak/ratdeck/pull/12
- GPS time sync via UBlox MIA-M10Q (38400 baud auto-detect)
- Custom zero-dependency NMEA parser with XOR checksum validation
- Only trust GPS time when satellites > 0 (no stale RTC cache)
- DST-aware timezone picker at first boot (21 cities, POSIX TZ strings)
- Timezone also configurable in Settings > GPS/Time
- GPS Time on by default, GPS Location off by default (opt-in)
- Status bar: clock (top-left), Ratspeak.org (center), battery (right)
- 12h time default (no AM/PM), configurable 24h in settings
- NVS persistence of GPS time for approximate timestamps across reboots
- Fix factory reset Enter key bleed-through (600ms input guard)
- Fix hardcoded UTC-5 NTP timezone (now uses DST-aware POSIX TZ)