Previously a single ASCII glyph collapsed SENT and DELIVERED. Wire
PacketReceipt delivery callbacks through to sideband-style single
and double checks.
LXMFManager tracks outstanding receipts (opportunistic + link
single-packet) by hash. On delivery: persist DELIVERED and fire
_statusCb. On 60s timeout: drop the entry, leave at SENT — lack
of proof over LoRa isn't proof of failure. Resource transfers stay
at SENT; microReticulum's Transport skips receipts for resource
packets and Link::start_resource_transfer exposes no concluded
callback to hook.
LvMessageView renders the glyph via applyStatusGlyph (Montserrat 12):
REFRESH for in-flight, single OK for SENT, double OK for DELIVERED,
WARNING for FAILED. Status callback matches by timestamp (~1s
tolerance) so DELIVERED lands on the right bubble after later
messages have already moved past QUEUED.
CORE_DEBUG_LEVEL=0 drops Arduino core noise on expected-missing files, LittleFSFileSystem::open_file now actually works so path_table persists across reboots.
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).
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.
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)
- Enable UBlox MIA-M10Q GPS on T-Deck Plus (HAS_GPS true)
- Custom zero-dependency NMEA parser (NMEAParser.h) with XOR checksum
- GPSManager: baud auto-detect (115200/38400/9600, 10s timeout),
settimeofday() sync every 60s, NVS time/position persistence
- GPS Time enabled by default, GPS Location disabled by default
(user must explicitly opt in via Settings)
- Replace status bar connectivity bars with clock display (top-left)
- Default 12h format without AM/PM, configurable 24h in Settings
- Add GPS/Time settings category: GPS Time, GPS Location,
UTC Offset (-12 to +14), 24h Time toggle
- Fix hardcoded UTC-5 NTP timezone to use configurable utcOffset
- Change status bar brand text to "Ratspeak.org"
PlatformIO Build / release (push) Has been skipped
Apply bin8 fix to the duplicate announce encoding in LvSettingsScreen
(Send Announce action). Replace inline copy with shared function from
main.cpp. Follows up on PR #5.
- Fix TCP port input: allow direct digit entry instead of scroll-only
- Fix flash status always showing "Error" (was checking SD path on flash)
- Flash now shows used/total KB instead of just Mounted/Error
- Audio buffers use PSRAM (ps_malloc) with internal heap fallback
- Bump version to 1.6.1
These standalone devices should not act as transport nodes.
Remove the config toggle, settings UI, and boot-time config peek.
Hardcode transport_enabled(false).
- Tighten transport announce rate limit from 5/sec to 2/sec
- Reduce RNS loop frequency from 200Hz to 100Hz
- Throttle LVGL rendering to 30 FPS (was unthrottled)
- Time-box TCP frame loop to 3 frames / 8ms per client
- Add 12ms global TCP budget across all clients
- Replace blocking WiFi announce delay(1500) with deferred non-blocking check
- Rotate persistData() across 3 cycles to spread file I/O
- Reduce LvNodesScreen rebuild frequency (2s → 5s, skip small transient changes)
- Bump version to 1.5.9
4-layer defense against 100+ node announce storms from rns.ratspeak.org:
- Transport-level rate limiter (5/sec) via filter_packet callback, before Ed25519 verify
- TCP frame processing time-boxed to 15ms per loop iteration
- Global announce rate limit (3/sec) in AnnounceManager
- UI rebuild throttled to once per 2 seconds
Also fixes message status not persisting to disk on queue drain.
- Evict highest-hop unsaved nodes first when peer list is full (nearby peers survive)
- Add first-boot SD data cleanup screen before name input
- Conversation summaries cache for faster message list rendering
- Optimize message view refresh with summary-based change detection
- Streamline unread tracking through MessageStore instead of LXMFManager
- Update clone URLs in BUILDING.md and QUICKSTART.md to ratspeak/ratdeck
- Fix update checker API URL in LvSettingsScreen.cpp to ratspeak/ratdeck
- Bump version examples in BUILDING.md from v1.4.2 to v1.5.2
- Add NTP time sync on WiFi STA connect (fixes --:-- timestamps)
- Throttle LXMF send retries to 2s intervals, increase max to 30 (~60s window)
- Restore message status re-save to disk after send completes (QUEUED→SENT/FAILED)
- Update GitHub org URLs to ratspeak
- Fix LXMF wire format: standardize on opportunistic [src:16][sig:64][content]
- Fix LXMF signature: sign(dest||src||packed) per spec, remove message_hash
- Fix MsgPack interop: handle both str and bin types for title/content
- Radio defaults changed to Balanced preset (SF9/BW250k/CR5/TX14)
- Messages screen: sorted by most recent, preview with You:/Them:, green unread dot, timestamps
- Status bar: replace LoRa/BLE/WiFi text with signal bars (green=connected, red=offline)
- Home screen: remove Unread info (shown in Messages tab)
- Contacts screen added (Friends tab)
- Identity manager: multi-slot identity support with per-slot display names
- Message store: fix peer hash truncation, SD directory creation, .bak file leak
- Settings: check for updates, active identity display, info diagnostics
- Migrate all screens to LVGL v8.4 widget system
- Non-blocking radio TX (async endPacket via LoRaInterface)
- Live TCP server switching with transient node cleanup
- Fix UI freeze during radio transmit
- Trackball long-press delete, deferred click with debounce
- Pin microReticulum to 392363c, fix list_directory API
- Fix CI build: portable include path, remove hardcoded local path
- Rewrite MessageView with word-wrapped chat bubbles, timestamps,
incoming/outgoing alignment, pixel-based scrolling
- Add IdentityManager for multi-identity support (create/switch/delete,
8 slots max, per-identity display names)
- Fix transport mode: default to endpoint (no rebroadcast) — was acting
as transport node, rebroadcasting every TCP announce over LoRa causing
5-11 second UI freezes per TX. Loop time: 11146ms → 6ms
- Add Transport Node toggle in Settings > Network
- TCP interface now drains up to 10 frames per loop (was 1)
- Reduce LoRa debug polling from 5s to 30s
Stability fixes:
- Fix message duplication (8x repeat) via messageId-based filenames
- Fix message ordering with epoch timestamps (NTP-aware fallback)
- Fix device freezes: MessageView caches messages instead of reading
disk every frame at 20fps (now refreshes every 500ms)
- Fix .bak file cascade: filter all file ops to .json only, cleanup junk
- Fix radio presets not persisting across reboots (removed force-override)
Settings redesign:
- Collapsible category navigation (6 categories with summaries)
- Enter to drill into category, Backspace to return
- WiFi network scanner with signal bars and RSSI display
- WiFi SSID/Password fields for STA mode
- TCP server preset (None / Ratspeak Hub / Custom) with host/port
- Frequency selector (868/906/915/923 MHz) and Preamble setting
- Factory reset with confirmation step
- Reboot device action
- Merged small categories (Input into Display, Actions into System)
New features:
- TCP network support for rns.ratspeak.org connectivity
- Preamble stored/applied in config and radio at boot
- Version bumped to 1.3.0
- Fix trackball completely dead: remove gpio_reset_pin() calls that
prevented GPIO interrupts from registering on ESP32-S3
- Add trackball left/right tab cycling when screen doesn't consume event
- Add NameInputScreen: Ratspeak-branded first-boot name input
- Add display name to all announces as MessagePack app_data
- Add TEXT_INPUT setting type; expose Display Name in Settings
- HomeScreen Enter key sends announce with toast notification
- Bump version to 1.2.2