- Brightness adjustments

This commit is contained in:
DeFiDude
2026-04-06 23:02:40 -06:00
parent 675ae47b4d
commit 037af2a6ac
13 changed files with 24 additions and 1398 deletions
-199
View File
@@ -1,199 +0,0 @@
# Ratdeck — Architecture
## Overview
Ratdeck is a standalone Reticulum mesh node with LXMF encrypted messaging, built for the LilyGo T-Deck Plus (ESP32-S3). It runs the full Reticulum protocol stack on-device — no host computer, no gateway, no KISS protocol. LoRa, WiFi, and BLE transports operate independently or bridged.
## Layer Diagram
```
+---------------------------------------------+
| UI Layer (LVGL v8.4) |
| Screens: Home, Friends, Msgs, Peers, Setup |
| LvStatusBar, LvTabBar, LvInput |
| Theme: Matrix green (#00FF41) on black |
+---------------------------------------------+
| Application Layer |
| LXMFManager AnnounceManager |
| IdentityManager UserConfig |
| MessageStore AudioNotify |
+---------------------------------------------+
| Reticulum Layer |
| ReticulumManager (microReticulum C++) |
| Identity, Destination, Transport |
+---------------------------------------------+
| Transport Layer |
| LoRaInterface WiFiInterface |
| TCPClientInterface BLESideband |
+---------------------------------------------+
| Storage Layer |
| FlashStore (LittleFS, 7.875 MB) |
| SDStore (FAT32 microSD) |
| NVS (ESP32 Preferences) |
+---------------------------------------------+
| Hardware Layer |
| SX1262 Radio ST7789V Display |
| I2C Keyboard GPIO Trackball |
| ESP32-S3 (8MB PSRAM, 16MB Flash) |
+---------------------------------------------+
```
## Directory Structure
```
src/
+-- main.cpp Setup + main loop (single-threaded, core 1)
+-- config/
| +-- BoardConfig.h GPIO pins, SPI config, hardware constants
| +-- Config.h Version, feature flags, storage paths, limits
| +-- UserConfig.* Runtime settings (JSON, dual SD+flash backend)
+-- radio/
| +-- SX1262.* SX1262 LoRa driver (register-level, async TX)
| +-- RadioConstants.h SX1262 register addresses and command bytes
+-- hal/
| +-- Display.* LovyanGFX ST7789V driver + LVGL flush callback
| +-- Power.* Screen dim/off/wake, battery ADC, brightness
| +-- Keyboard.* I2C keyboard (ESP32-C3 at 0x55)
| +-- Trackball.* GPIO trackball (ISR-based, 5 pins)
| +-- TouchInput.* GT911 capacitive touch (disabled)
+-- input/
| +-- InputManager.* Unified keyboard + trackball + touch polling
| +-- HotkeyManager.* Ctrl+key dispatch table
+-- ui/
| +-- Theme.h Color palette, layout constants
| +-- LvTheme.* LVGL theme application
| +-- LvStatusBar.* Signal bars + brand + battery %
| +-- LvTabBar.* 5-tab bar with unread badges
| +-- LvInput.* Key event routing to active screen
| +-- UIManager.* Screen lifecycle, boot/normal modes
| +-- screens/
| +-- LvBootScreen.* Boot animation with progress bar
| +-- LvHomeScreen.* Name, LXMF address, status, online nodes
| +-- LvContactsScreen.* Saved friends (display name only)
| +-- LvMessagesScreen.* Conversation list (sorted, previews, unread dots)
| +-- LvMessageView.* Chat bubbles + status colors + text input
| +-- LvNodesScreen.* Node browser (contacts + online sections)
| +-- LvSettingsScreen.* 7-category settings editor
| +-- LvHelpOverlay.* Hotkey reference modal
| +-- LvNameInputScreen.* First-boot name entry
+-- reticulum/
| +-- ReticulumManager.* microReticulum lifecycle, transport loop
| +-- AnnounceManager.* Node discovery, friend persistence
| +-- LXMFManager.* LXMF send/receive, queue, delivery tracking
| +-- LXMFMessage.* Wire format + JSON storage format
| +-- IdentityManager.* Multi-slot identity management
+-- transport/
| +-- LoRaInterface.* SX1262 <-> Reticulum (1-byte header)
| +-- WiFiInterface.* WiFi AP, TCP server on port 4242
| +-- TCPClientInterface.* WiFi STA, TCP client to remote nodes
| +-- BLEInterface.* BLE transport
| +-- BLESideband.* NimBLE Sideband interface
+-- storage/
| +-- FlashStore.* LittleFS with atomic writes
| +-- SDStore.* SD card (FAT32) with atomic writes
| +-- MessageStore.* Per-conversation dual-backend storage
+-- audio/
+-- AudioNotify.* Notification sounds
```
## Data Flow
### Incoming LoRa Packet
```
SX1262 IRQ (DIO1, GPIO 45) -> SX1262::receive() reads FIFO
-> LoRaInterface::loop() strips 1-byte header
-> RNS::InterfaceImpl::receive_incoming()
-> RNS::Transport processes packet
+-- Announce -> AnnounceManager callback -> UI update
+-- LXMF data -> LXMFManager -> MessageStore (flash + SD) -> UI notification
+-- Path/link -> Transport table update -> persist to flash + SD backup
```
### Outgoing LXMF Message
```
User types message -> LvMessageView -> LXMFManager::send()
-> Save to MessageStore (QUEUED status)
-> Pack: dest_hash(16) + src_hash(16) + signature(64) + msgpack([ts, title, content, fields])
-> RNS::Packet -> RNS::Transport selects interface
+-- LoRaInterface -> prepend 1-byte header -> SX1262::beginPacket/endPacket (async)
+-- TCPClient -> HDLC frame (0x7E delimit, 0x7D escape) -> TCP socket
-> StatusCallback fires -> re-save with SENT/DELIVERED/FAILED status
```
### Config Save
```
LvSettingsScreen -> UserConfig::save(sd, flash)
+-- serialize to JSON string
+-- SDStore::writeAtomic("/ratputer/config/user.json") -> .tmp -> verify -> .bak -> rename
+-- FlashStore::writeAtomic("/config/user.json") -> .tmp -> verify -> .bak -> rename
```
## Key Design Decisions
### Radio Driver
Register-level SX1262 driver with no library abstraction. Async TX mode (`endPacket(true)`) returns immediately — `isTxBusy()` polls completion in `LoRaInterface::loop()`. `waitOnBusy()` calls a yield callback so LVGL stays responsive during SPI waits. TCXO 1.8V, DIO2 as RF switch, 8 MHz SPI clock.
### Display
LovyanGFX drives the ST7789V over SPI at 15 MHz. LVGL v8.4 renders to a flush buffer with DMA transfer. 320x240 landscape, 20px status bar top, 20px tab bar bottom, 200px content area.
### Reticulum Integration
microReticulum C++ library with LittleFS-backed filesystem. Device runs as either an Endpoint (default) or Transport Node (configurable in Settings). All interfaces (LoRa, WiFi, TCP, BLE) register with `RNS::Transport`.
### LXMF Wire Format
```
[dest_hash:16][src_hash:16][signature:64][msgpack_payload]
```
MsgPack payload: `fixarray(4): [timestamp(float64), title(str), content(str), fields(map)]`
Signature covers: `dest_hash || src_hash || msgpack_payload`
Messages under MDU (~254 bytes for LoRa) are sent as single direct packets. Stored as JSON per-conversation in flash and SD.
### Shared SPI Bus
Display, SX1262 radio, and SD card all share SPI2_HOST (FSPI on ESP32-S3). Single-threaded access from `loop()` — no mutex needed. Arduino FSPI=0 maps to SPI2 hardware; do NOT use `SPI2_HOST` IDF constant directly.
### WiFi Transport
Three mutually exclusive modes:
- **OFF**: No WiFi — saves power and ~20 KB heap
- **AP**: Creates `ratdeck-XXXX` hotspot, TCP server on port 4242, HDLC framing
- **STA**: Connects to existing network, TCP client connections to configured endpoints
AP+STA concurrent mode was removed — consumed too much heap and caused instability.
### TCP Client Transport
Outbound TCP connections to remote Reticulum nodes. Created on WiFi STA connection, auto-reconnect on disconnect (15s interval). Settings changes apply live via `_tcpChangeCb` callback — `reloadTCPClients()` stops old clients and creates new ones. Switching servers clears transient nodes.
### Dual-Backend Storage
FlashStore (LittleFS, 7.875 MB partition) is the primary store. SDStore (FAT32 microSD) provides backup and extended capacity. Both use atomic writes (`.tmp` -> verify -> `.bak` -> rename) to prevent corruption on power loss. UserConfig, MessageStore, and AnnounceManager write to both backends.
### Identity System
Triple-redundant storage: Flash (LittleFS), SD card, and NVS (ESP32 Preferences). Load order: Flash -> SD -> NVS -> generate new. On any successful load or creation, saves to all three tiers. Multi-slot identity management with per-slot display names.
### Transport Reference Stability
`RNS::Transport::_interfaces` stores `Interface&` references (not copies). All `RNS::Interface` wrappers must outlive the transport — stored in `std::list` (not vector, which would invalidate references on reallocation).
### Power Management
Three states: ACTIVE -> DIMMED -> SCREEN_OFF. Strong activity (keyboard, trackball nav/click) wakes from any state. Weak activity (trackball raw movement) wakes from DIMMED only. Configurable timeouts via Settings.
### Boot Loop Recovery
NVS counter tracks consecutive boot failures. After 3 failures, WiFi is forced OFF on next boot. Counter resets to 0 at end of successful `setup()`.
### Input System
Trackball click uses deferred release with 80ms GPIO debounce. Long press fires at 1200ms hold, suppresses click on release. Nav events are rate-limited to 200ms with threshold of 3 accumulated deltas. I2C keyboard at address 0x55 with interrupt on GPIO 46.
-170
View File
@@ -1,170 +0,0 @@
# Ratdeck — Build & Flash Reference
## Prerequisites
- **Python 3.x** with PlatformIO: `pip install platformio`
- **Git**
- **USB-C cable** (data-capable)
No USB drivers needed on macOS or Linux — the ESP32-S3's USB-Serial/JTAG interface is natively supported.
> If `pio` is not on your PATH after install, use `python3 -m platformio` for all commands in this document.
## Build
```bash
git clone https://github.com/ratspeak/ratdeck.git
cd Ratdeck
python3 -m platformio run
```
Output: `.pio/build/ratdeck_915/firmware.bin`
First build downloads all dependencies automatically (microReticulum, Crypto, ArduinoJson, LovyanGFX, NimBLE-Arduino, LVGL).
## Flash
### Via PlatformIO
```bash
python3 -m platformio run --target upload
```
### Via esptool (more reliable)
PlatformIO defaults to the upload speed in `platformio.ini` (460800 baud). If flashing fails, use esptool directly:
```bash
python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* --baud 460800 \
--before default-reset --after hard-reset \
write-flash -z 0x10000 .pio/build/ratdeck_915/firmware.bin
```
### Download Mode
If the device doesn't respond to flash commands, enter download mode:
1. Hold the trackball (BOOT/GPIO 0) button
2. While holding, press the reset button
3. Release both — device enters bootloader mode
4. Flash as normal
### Web Flash
Visit [ratspeak.org/download](https://ratspeak.org/download.html) to flash directly from your browser using WebSerial — no build tools required.
### Creating a Merged Binary
A merged binary includes bootloader + partition table + app in one file:
```bash
python3 -m esptool --chip esp32s3 merge-bin \
--output ratdeck_merged.bin \
--flash-mode dio --flash-size 16MB \
0x0 ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32s3/bin/bootloader_dio_80m.bin \
0x8000 .pio/build/ratdeck_915/partitions.bin \
0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \
0x10000 .pio/build/ratdeck_915/firmware.bin
python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* --baud 460800 \
--before default-reset --after hard-reset \
write-flash 0x0 ratdeck_merged.bin
```
## Serial Monitor
```bash
python3 -m platformio device monitor -b 115200
```
Or any serial terminal at 115200 baud.
## USB Port Identification
The T-Deck Plus ESP32-S3 uses USB-Serial/JTAG (not a separate UART chip):
| OS | Port | Notes |
|----|------|-------|
| macOS | `/dev/cu.usbmodem*` | Use glob to match |
| Linux | `/dev/ttyACM0` | May need `dialout` group |
On Linux: `sudo usermod -aG dialout $USER` (log out and back in).
## Build Flags
From `platformio.ini`:
| Flag | Purpose |
|------|---------|
| `-std=gnu++17` | C++17 standard |
| `-fexceptions` | C++ exceptions (required by microReticulum) |
| `-DRATDECK=1` | Main feature flag |
| `-DARDUINO_USB_CDC_ON_BOOT=1` | USB CDC serial on boot |
| `-DARDUINO_USB_MODE=1` | USB-Serial/JTAG mode (not native CDC) |
| `-DRNS_USE_FS` | microReticulum: use filesystem for persistence |
| `-DRNS_PERSIST_PATHS` | microReticulum: persist transport paths |
| `-DMSGPACK_USE_BOOST=OFF` | Disable Boost dependency in MsgPack |
| `-DBOARD_HAS_PSRAM` | Enable 8MB PSRAM |
| `-DDISPLAY_WIDTH=320` | Display width |
| `-DDISPLAY_HEIGHT=240` | Display height |
| `-DLV_CONF_INCLUDE_SIMPLE` | LVGL config via root `lv_conf.h` |
| `"-I${PROJECT_DIR}"` | Required for LVGL to find root `lv_conf.h` |
`build_unflags = -fno-exceptions -std=gnu++11` removes Arduino defaults.
## Partition Table
`partitions_16MB.csv` — 16MB flash layout with OTA support:
| Name | Type | Offset | Size | Purpose |
|------|------|--------|------|---------|
| nvs | data/nvs | 0x9000 | 20 KB | Non-volatile storage (boot counter, WiFi creds) |
| otadata | data/ota | 0xE000 | 8 KB | OTA boot selection |
| app0 | app/ota_0 | 0x10000 | 4 MB | Primary firmware |
| app1 | app/ota_1 | 0x410000 | 4 MB | OTA update slot |
| littlefs | data/spiffs | 0x810000 | 7.875 MB | LittleFS — identity, config, messages, paths |
| coredump | data/coredump | 0xFF0000 | 64 KB | ESP-IDF core dump on crash |
## CI/CD
GitHub Actions workflow (`.github/workflows/build.yml`):
- **Build**: Triggers on push to `main` and PRs. Runs `pio run`, uploads `firmware.bin` as artifact.
- **Release**: Triggers on `v*` tags. Builds firmware, creates a GitHub Release with the binary ZIP attached. Powers the web flasher at [ratspeak.org/download](https://ratspeak.org/download.html).
### Release Process
See [RELEASING.md](RELEASING.md) for the full release protocol (version bump, tagging, post-release verification, hotfix workflow).
## Dependencies
All managed by PlatformIO's `lib_deps`:
| Library | Source | Purpose |
|---------|--------|---------|
| microReticulum | `attermann/microReticulum#392363c` | Reticulum protocol stack (C++) |
| Crypto | `attermann/Crypto` | Ed25519, X25519, AES, SHA-256 |
| ArduinoJson | `bblanchon/ArduinoJson ^7.4.2` | Config and message serialization |
| LovyanGFX | `lovyan03/LovyanGFX ^1.1.16` | SPI display driver |
| NimBLE-Arduino | `h2zero/NimBLE-Arduino ^2.1` | BLE Sideband transport |
| LVGL | `lvgl/lvgl ^8.3.4` | UI framework (v8.4) |
## Erasing Flash
To completely erase the ESP32-S3 flash (useful if LittleFS is corrupted):
```bash
python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* erase-flash
```
After erasing, reflash the firmware. LittleFS will auto-format on first boot and a new identity will be generated.
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| `Could not open port` | Device not connected or wrong port | Check USB cable, try `/dev/cu.usbmodem*` glob |
| `Timed out waiting for packet header` | Baud rate too high | Use `--baud 460800` with esptool |
| Serial disconnects mid-flash | Unstable USB connection | Enter download mode (hold trackball + reset) |
| `No module named platformio` | PlatformIO not installed | `pip install platformio` |
| `pio: command not found` | Not on PATH | Use `python3 -m platformio` instead |
-230
View File
@@ -1,230 +0,0 @@
# Ratdeck — Developer Guide
## Overview
Ratdeck is a standalone Reticulum mesh node with LXMF encrypted messaging for the LilyGo T-Deck Plus (ESP32-S3). It is NOT an RNode — it does not speak KISS protocol. It runs the full Reticulum stack (microReticulum) directly on the device.
Key characteristics:
- Standalone operation — no host computer required
- LoRa transport with 1-byte header framing
- WiFi transport — AP mode (TCP server) or STA mode (TCP client)
- BLE transport — NimBLE Sideband interface
- LXMF encrypted messaging with Ed25519 signatures
- LVGL v8.4 UI with 5-tab navigation
- JSON-based runtime configuration with SD card + flash dual-backend
## Configuration System
### Compile-Time (`Config.h`)
Feature flags (`HAS_LORA`, `HAS_WIFI`, `HAS_BLE`, etc.), storage paths, protocol limits, power defaults. Changed only by editing source and recompiling.
### Runtime (`UserConfig`)
JSON-based settings persisted to storage. Schema defined by `UserSettings` struct in `UserConfig.h`. Dual-backend persistence: `UserConfig::load(SDStore&, FlashStore&)` reads from SD first (`/ratputer/config/user.json`), falls back to flash (`/config/user.json`). `save()` writes to both.
## Transport Architecture
### InterfaceImpl Pattern
All transport interfaces inherit from `RNS::InterfaceImpl`:
- `start()` / `stop()` — lifecycle
- `send_outgoing(const RNS::Bytes& data)` — transmit
- `loop()` — poll for incoming data, call `receive_incoming()` to push up to Reticulum
### HDLC Framing (WiFi + TCP)
TCP connections use HDLC-like byte framing:
- `0x7E` — frame delimiter (start/end)
- `0x7D` — escape byte
- `0x20` — XOR mask for escaped bytes
Any `0x7E` or `0x7D` in payload is escaped as `0x7D (byte ^ 0x20)`.
### LoRa 1-Byte Header
Every LoRa packet has a 1-byte header prepended:
- Upper nibble: random sequence number
- Lower nibble: flags (`0x01` = split, not currently implemented)
## Screen System (LVGL v8.4)
All screens extend `LvScreen` base class:
- `createUI(lv_obj_t* parent)` — build LVGL widgets
- `onEnter()` — called when screen becomes active
- `handleKey(const KeyEvent&)` — keyboard/trackball input
- `handleLongPress()` — trackball long-press (1200ms)
### Active Screens
| Screen | Class | Tab | Purpose |
|--------|-------|-----|---------|
| Boot | LvBootScreen | — | Boot animation with progress bar |
| Home | LvHomeScreen | 1 | Name, LXMF address, status, online nodes |
| Friends | LvContactsScreen | 2 | Saved contacts (display name only) |
| Msgs | LvMessagesScreen | 3 | Conversation list, sorted by recent, previews |
| Peers | LvNodesScreen | 4 | All discovered nodes (contacts + online) |
| Setup | LvSettingsScreen | 5 | 7-category settings editor |
| Help | LvHelpOverlay | — | Hotkey reference modal (Ctrl+H) |
| Name | LvNameInputScreen | — | First-boot name entry |
### Theme
Matrix green (#00FF41) on black. Layout: 320x240, status bar 20px top, tab bar 20px bottom, content 200px.
Key colors:
- `PRIMARY` (0x00FF41) — main text, active elements
- `ACCENT` (0x00FFFF) — cyan highlights, incoming messages
- `MUTED` (0x559955) — secondary text
- `SELECTION_BG` (0x004400) — selected row
- `ERROR_CLR` (0xFF3333) — errors, disconnected indicators
## How To: Add a New Screen
1. Create `src/ui/screens/LvMyScreen.h` and `LvMyScreen.cpp`
2. Inherit from `LvScreen` — implement `createUI()`, `handleKey()`, optionally `onEnter()`
3. In `createUI()`, build LVGL widgets on the provided parent container
4. Add a global instance in `main.cpp`
5. Wire it up: add to `tabScreens[]` array for tab navigation, or navigate to it from a hotkey/callback via `ui.showScreen()`
## How To: Add a New Hotkey
1. In `main.cpp`, create a callback function: `void onHotkeyX() { ... }`
2. Register in `setup()`: `hotkeys.registerHotkey('x', "Description", onHotkeyX);`
3. Update `docs/HOTKEYS.md` and the help overlay text in `LvHelpOverlay.cpp`
## How To: Add a Settings Category
Settings uses a category/item system in `LvSettingsScreen`:
1. Add your category to the categories array
2. Define items with types: `READONLY`, `TOGGLE`, `INTEGER`, `ENUM_CHOICE`, `TEXT_INPUT`, `ACTION`
3. Add value getter/setter logic in the category handler
4. Changes that need reboot: set flag and show toast
5. Live changes: apply immediately in the setter callback
6. Persist with `userConfig->save(sdStore, flash)`
## How To: Add a New Transport Interface
1. Create a class inheriting from `RNS::InterfaceImpl`
2. Implement `start()`, `stop()`, `loop()`, `send_outgoing()`
3. In `loop()`, call `receive_incoming(data)` when data arrives
4. In `main.cpp`, construct the impl, wrap in `RNS::Interface`, register with `RNS::Transport`
5. Store the `RNS::Interface` wrapper in a `std::list` (not vector) — Transport holds references, and vector reallocation invalidates them
## Initialization Sequence
`setup()` runs these steps in order:
1. GPIO 10 HIGH — enables all T-Deck Plus peripherals
2. Serial begin (115200 baud)
3. Shared SPI bus init (SCK=40, MISO=38, MOSI=41)
4. I2C bus init (SDA=18, SCL=8)
5. Display init (LovyanGFX + LVGL)
6. Boot screen shown
7. Keyboard init (I2C 0x55)
8. Trackball init (GPIO ISRs on pins 0/1/2/3/15)
9. Register hotkeys (Ctrl+H/M/N/S/A/D/T/R)
10. Mount LittleFS (FlashStore)
11. Boot loop detection (NVS counter)
12. Radio init — SX1262 TCXO, calibrate, configure, enter RX
13. SD card init (shares SPI, must be after radio)
14. Reticulum init — identity load/generate (triple-redundant), transport start
15. MessageStore init (dual backend)
16. LXMF init + message callback
17. AnnounceManager init + contact load
18. Register announce handler with Transport
19. Load UserConfig (SD -> flash fallback)
20. Boot loop recovery check (force WiFi OFF if triggered)
21. Apply saved radio settings
22. WiFi start (AP, STA, or OFF based on config)
23. BLE init (NimBLE Sideband)
24. Power manager init + apply brightness/timeouts
25. Audio init
26. Name input screen (first boot only)
27. Switch to Home screen
28. Initial announce broadcast
29. Clear boot loop counter (NVS reset to 0)
## Main Loop
Single-threaded on core 1:
```
loop() {
1. inputManager.update() -- keyboard, trackball, touch polling
2. Long-press dispatch -- ui.handleLongPress() at 1200ms
3. Key event dispatch -- hotkeys -> LvInput::feedKey() -> screen handleKey()
4. lv_timer_handler() -- LVGL rendering (skipped when screen off)
5. rns.loop() -- Reticulum + LoRa RX (throttled to 5ms)
6. Auto-announce (5 min interval)
7. lxmf.loop() + announce loop -- message queue + deferred saves
8. WiFi STA handler -- connect/disconnect, TCP client creation
9. WiFi/TCP/BLE loops -- transport processing
10. powerMgr.loop() -- ACTIVE -> DIMMED -> SCREEN_OFF
11. Status bar update (1Hz) -- battery, signal indicators
12. Heartbeat (5s serial)
}
```
## Memory Budget
ESP32-S3 with 8MB PSRAM. PSRAM is used for large allocations; SRAM (512 KB) for stack and DMA:
| State | Free Heap | Notes |
|-------|-----------|-------|
| Boot complete (WiFi OFF) | ~170 KB | Baseline |
| Boot complete (WiFi AP) | ~150 KB | WiFi stack + TCP server |
| Boot complete (WiFi STA) | ~140 KB | WiFi stack + TCP clients |
| With BLE enabled | ~120 KB | NimBLE stack |
Monitor with `Ctrl+D` -> `Free heap` in serial output.
## Compile-Time Limits
Defined in `Config.h`:
| Constant | Default | Purpose |
|----------|---------|---------|
| `RATDECK_MAX_NODES` | 200 | Max discovered nodes (PSRAM allows more) |
| `RATDECK_MAX_MESSAGES_PER_CONV` | 100 | Max messages stored per conversation |
| `FLASH_MSG_CACHE_LIMIT` | 20 | Recent messages per conv in flash (SD has full history) |
| `RATDECK_MAX_OUTQUEUE` | 20 | Max pending outgoing LXMF messages |
| `MAX_TCP_CONNECTIONS` | 4 | Max simultaneous TCP client connections |
| `TCP_RECONNECT_INTERVAL_MS` | 15000 | Retry interval for dropped TCP connections |
| `TCP_CONNECT_TIMEOUT_MS` | 500 | Timeout for TCP connect() |
| `PATH_PERSIST_INTERVAL_MS` | 60000 | Transport path save interval |
| `SCREEN_DIM_TIMEOUT_MS` | 30000 | Default screen dim timeout |
| `SCREEN_OFF_TIMEOUT_MS` | 60000 | Default screen off timeout |
## Debugging
### Serial Output
All subsystems log with `[TAG]` prefixes at 115200 baud. Key tags: `[BOOT]`, `[RADIO]`, `[LORA_IF]`, `[WIFI]`, `[TCP]`, `[LXMF]`, `[SD]`, `[BLE]`.
### Radio Debugging
- `Ctrl+D` — dumps SX1262 registers, identity, transport status, heap, PSRAM, uptime
- `Ctrl+T` — sends test packet with FIFO readback verification
- `Ctrl+R` — 5-second continuous RSSI sampling
### Core Dump
ESP-IDF stores a core dump in the `coredump` partition (64 KB at 0xFF0000):
```bash
python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* \
read-flash 0xFF0000 0x10000 coredump.bin
python3 -m esp_coredump info_corefile -t raw -c coredump.bin \
.pio/build/ratdeck_915/firmware.elf
```
### Common Crashes
| Crash | Cause | Fix |
|-------|-------|-----|
| `LoadProhibited` at transport loop | Dangling `Interface&` reference | Store in `std::list` (not vector, not local scope) |
| `Stack overflow` | Deep call chain or recursive render | Increase stack size or reduce nesting |
| `Guru Meditation` on WiFi init | Heap exhaustion | Reduce TCP connections, check for leaks |
| Boot loop (3+ failures) | WiFi or TCP init crash | Boot loop recovery auto-disables WiFi |
-104
View File
@@ -1,104 +0,0 @@
# Ratdeck — Hotkey Reference
## Hotkeys (Ctrl+key)
| Shortcut | Action |
|----------|--------|
| Ctrl+H | Toggle help overlay (shows all hotkeys on screen) |
| Ctrl+M | Jump to Messages tab |
| Ctrl+N | Compose new message |
| Ctrl+S | Jump to Settings tab |
| Ctrl+A | Force announce to all interfaces (LoRa + TCP) |
| Ctrl+D | Dump full diagnostics to serial |
| Ctrl+T | Send radio test packet |
| Ctrl+R | RSSI monitor (5-second continuous sampling) |
## Navigation
| Input | Action |
|-------|--------|
| Trackball up/down | Scroll / navigate lists |
| Trackball left/right | Cycle tabs |
| Trackball click | Select / confirm |
| Trackball long-press (1.2s) | Context menu |
| `,` (comma) | Previous tab |
| `/` (slash) | Next tab |
| Enter | Select / confirm / send message |
| Esc | Back / cancel |
| Backspace | Delete character / go back (if input empty) |
## Text Input
When a text field is active (message compose, WiFi password, etc.):
- Type normally to enter characters
- **Backspace** to delete
- **Enter** to submit / send
- **Esc** to cancel and go back
## Tabs
| Tab | Name | Contents |
|-----|------|----------|
| 1 | Home | Name, LXMF address, connection status, online node count |
| 2 | Friends | Saved contacts (display name only) |
| 3 | Msgs | Conversation list — sorted by most recent, with preview and unread dots |
| 4 | Peers | All discovered nodes — contacts section + online section |
| 5 | Setup | 7-category settings (Device, Display, Radio, Network, Audio, Info, System) |
## Home Screen
- **Enter / trackball click**: Announce to all connected interfaces (LoRa + TCP)
## Messages Screen
- **Up/Down**: Navigate conversations
- **Enter**: Open conversation
- **Long-press**: Context menu (Add Friend / Delete Chat / Cancel)
- Navigate menu with Up/Down, confirm with Enter
## Message View (Chat)
- **Type**: Compose message
- **Enter**: Send message
- **Up/Down**: Scroll message history
- **Esc / Backspace** (empty input): Go back to conversation list
## Peers Screen
- **Up/Down**: Navigate node list
- **Enter**: Open chat with selected node
- **'s' key**: Toggle saved/friend status
- **Long-press**: Unsaved node → add as friend; saved friend → delete prompt
## Settings Screen
- **Up/Down**: Navigate settings items
- **Enter**: Toggle / edit selected setting
- **Left/Right**: Adjust numeric values, cycle enum choices
- Categories: Device, Display & Input, Radio, Network, Audio, Info, System
## Serial Diagnostics
**Ctrl+D** prints to serial (115200 baud):
- Identity hash, destination hash, transport status
- Path/link counts
- Radio parameters (freq, SF, BW, CR, TX power, preamble)
- SX1262 register dump (sync word, IQ, LNA, OCP, TX clamp)
- Device errors, current RSSI
- Free heap, PSRAM, flash usage, uptime
**Ctrl+T** sends a test packet and verifies FIFO readback.
**Ctrl+R** samples RSSI continuously for 5 seconds.
## Verbose Protocol Logging
microReticulum logging is set to `LOG_WARNING` by default for performance.
To enable full protocol trace logging for debugging, change the log level in
`src/reticulum/ReticulumManager.cpp`:
```cpp
RNS::loglevel(RNS::LOG_TRACE); // Full protocol logging (WARNING: blocks CPU at 115200 baud)
```
Available levels: `LOG_WARNING` (default), `LOG_NOTICE`, `LOG_INFO`, `LOG_VERBOSE`, `LOG_DEBUG`, `LOG_TRACE`.
-144
View File
@@ -1,144 +0,0 @@
# Ratdeck — Hardware Pin Map
LilyGo T-Deck Plus (ESP32-S3) — all pin definitions in `src/config/BoardConfig.h`.
## Bus Overview
```
ESP32-S3
├── SPI2_HOST (shared bus) ──┬── ST7789V Display (CS=12, DC=11)
│ SCK=40 ├── SX1262 LoRa (CS=9)
│ MISO=38 └── SD Card (CS=39)
│ MOSI=41
├── I2C (SDA=18, SCL=8) ────┬── Keyboard ESP32-C3 (0x55)
│ └── GT911 Touch (0x5D)
├── GPIO ── Trackball (UP=3, DOWN=2, LEFT=1, RIGHT=15, CLICK=0)
├── UART ── UBlox MIA-M10Q GPS (TX=43, RX=44)
├── I2S ── ES7210 Audio Codec
└── USB-C ── USB-Serial/JTAG
```
## Power Control
| Signal | GPIO | Notes |
|--------|------|-------|
| BOARD_POWER_PIN | 10 | **Must be set HIGH at boot** to enable all peripherals |
## SX1262 LoRa Radio
Shares SPI2_HOST bus with display and SD card:
| Signal | GPIO | Notes |
|--------|------|-------|
| CS | 9 | Chip select (active low) |
| IRQ | 45 | DIO1 interrupt |
| RST | 17 | Reset (active low) |
| BUSY | 13 | Poll before SPI transactions |
| RXEN | -1 | Not connected (DIO2 used as RF switch) |
| TXEN | -1 | Not connected |
**Radio configuration:**
- TCXO voltage: 1.8V (`MODE_TCXO_1_8V_6X` = 0x02)
- DIO2 as RF switch: enabled
- SPI clock: 8 MHz
## Display (ST7789V)
| Signal | GPIO | Notes |
|--------|------|-------|
| CS | 12 | Chip select |
| DC | 11 | Data/command |
| BL | 42 | Backlight PWM |
320x240 pixels, landscape (rotation=1), SPI clock up to 15 MHz (30 MHz overclockable).
## Shared SPI Bus
| Signal | GPIO |
|--------|------|
| SCK | 40 |
| MISO | 38 |
| MOSI | 41 |
All three SPI devices (display, radio, SD) share SPI2_HOST. Single-threaded access from `loop()`, no mutex needed.
## Keyboard (ESP32-C3)
| Signal | GPIO | Notes |
|--------|------|-------|
| I2C addr | 0x55 | Fixed address |
| INT | 46 | Interrupt pin |
| SDA | 18 | Shared I2C bus |
| SCL | 8 | Shared I2C bus |
## Trackball
| Signal | GPIO | Notes |
|--------|------|-------|
| UP | 3 | ISR-based, GPIO interrupt |
| DOWN | 2 | ISR-based, GPIO interrupt |
| LEFT | 1 | ISR-based, GPIO interrupt |
| RIGHT | 15 | ISR-based, GPIO interrupt |
| CLICK | 0 | Shared with BOOT button |
Click uses deferred release with 80ms debounce. Long press: 1200ms hold threshold.
## Touchscreen (GT911)
| Signal | GPIO | Notes |
|--------|------|-------|
| INT | 16 | Interrupt |
| I2C addr | 0x5D | Depends on INT state at boot |
| SDA | 18 | Shared I2C bus |
| SCL | 8 | Shared I2C bus |
Currently disabled — coordinates uncalibrated.
## SD Card
| Signal | GPIO | Notes |
|--------|------|-------|
| CS | 39 | Shared SPI bus |
FAT32, shares SPI2_HOST with display and radio.
## GPS (UBlox MIA-M10Q)
| Signal | GPIO | Notes |
|--------|------|-------|
| TX | 43 | ESP TX → GPS RX |
| RX | 44 | GPS TX → ESP RX |
UART at 115200 baud. Pins defined, not yet implemented.
## Battery
| Signal | GPIO | Notes |
|--------|------|-------|
| ADC | 4 | Battery voltage measurement |
## Audio (ES7210 I2S)
| Signal | GPIO |
|--------|------|
| WS (LRCK) | 5 |
| DOUT | 6 |
| BCK | 7 |
| DIN | 14 |
| SCK | 47 |
| MCLK | 48 |
## Hardware Constants
| Constant | Value | Notes |
|----------|-------|-------|
| `MAX_PACKET_SIZE` | 255 | SX1262 maximum single packet |
| `SPI_FREQUENCY` | 8 MHz | SPI clock for SX1262 |
| `TFT_WIDTH` | 320 | Display pixels (landscape) |
| `TFT_HEIGHT` | 240 | Display pixels (landscape) |
| `TFT_SPI_FREQ` | 15 MHz | Display SPI clock |
-161
View File
@@ -1,161 +0,0 @@
# Ratdeck — Quick Start
## Hardware Required
- **LilyGo T-Deck Plus** (ESP32-S3, 16MB flash, 8MB PSRAM, integrated SX1262 LoRa)
- **microSD card** (optional, recommended — FAT32, any size)
- **USB-C cable** (data-capable)
## Build & Flash
```bash
# Install PlatformIO
pip install platformio
# Clone and build
git clone https://github.com/ratspeak/ratdeck.git
cd Ratdeck
python3 -m platformio run
# Flash
python3 -m platformio run --target upload
```
> If `pio` is not on your PATH, use `python3 -m platformio` instead. See [BUILDING.md](BUILDING.md) for esptool instructions and troubleshooting.
### Web Flash (No Build Required)
Visit [ratspeak.org/download](https://ratspeak.org/download.html) to flash directly from your browser using WebSerial.
### USB Port
The T-Deck Plus uses USB-Serial/JTAG — port appears as `/dev/cu.usbmodem*` (macOS) or `/dev/ttyACM*` (Linux).
## First Boot
1. Power on the T-Deck Plus
2. Boot animation plays with progress bar
3. SX1262 radio initializes (915 MHz, Balanced preset)
4. SD card checked — auto-creates `/ratputer/` directories
5. Reticulum identity generated (Ed25519 keypair, saved to flash + SD + NVS)
6. Name input screen — type a display name or press Enter to auto-generate one
7. Home screen shows your name, LXMF address, connection status, and online node count
## Navigation
The T-Deck Plus has a QWERTY keyboard, trackball, and touchscreen (touch currently disabled):
| Input | Action |
|-------|--------|
| Trackball up/down | Scroll lists |
| Trackball left/right | Cycle tabs |
| Trackball click | Select / confirm |
| Trackball long-press (1.2s) | Context menu |
| `,` / `/` | Previous / next tab |
| Enter | Select / send message |
| Esc or Backspace | Back / cancel |
| Ctrl+H | Show hotkey help overlay |
### Tabs
| Tab | Name | Contents |
|-----|------|----------|
| 1 | Home | Your name, LXMF address, connection status, online nodes |
| 2 | Friends | Saved contacts with display names |
| 3 | Msgs | Conversations sorted by most recent, with preview and unread dots |
| 4 | Peers | All discovered nodes — contacts section + online section |
| 5 | Setup | Device, Display, Radio, Network, Audio, Info, System |
## Sending Your First Message
1. Wait for another node to appear in the **Peers** tab (or connect via WiFi)
2. Navigate to the node and press **Enter** to open a chat
3. Type your message on the keyboard
4. Press **Enter** to send
5. Message status: yellow (sending) -> green (delivered) or red (failed)
## Connecting Two Ratdecks Over LoRa
1. Power on both devices
2. Ensure both use the same radio settings (defaults work out of the box)
3. Wait ~30 seconds for announces to propagate
4. The other device appears in the **Peers** tab with RSSI and SNR
5. Select the node and press Enter to start chatting
Both devices must use the same frequency, spreading factor, bandwidth, and coding rate. Default: 915 MHz, SF9, BW 125 kHz, CR 4/5 (Balanced preset).
## WiFi Setup
Default WiFi mode is **STA** (client). Change in Settings -> Network.
### STA Mode (Connect to Your WiFi)
1. Go to Setup -> Network
2. Enter your WiFi SSID and password
3. Add a TCP endpoint (e.g., `rns.beleth.net:4242`)
4. Ratdeck connects and bridges LoRa <-> TCP automatically
### AP Mode (Ratdeck as Hotspot)
1. Set WiFi Mode to AP in Settings -> Network
2. Connect your laptop to `ratdeck-XXXX` (password: `ratspeak`)
3. Add to `~/.reticulum/config`:
```ini
[[ratdeck]]
type = TCPClientInterface
target_host = 192.168.4.1
target_port = 4242
```
4. Your desktop is now bridged to the LoRa mesh
### WiFi OFF
Select OFF in Settings -> Network to disable WiFi entirely (saves power).
## SD Card
Insert a microSD card (FAT32) before powering on. The firmware auto-creates:
```
/ratputer/
config/ Settings backup
messages/ Message archives (up to 100 per conversation)
contacts/ Saved friends
identity/ Identity key backup
transport/ Routing table backup
```
## Managing Friends
- **Peers tab**: Navigate to a node, press `s` to toggle friend status, or long-press to add/delete
- **Messages tab**: Long-press a conversation for context menu (Add Friend / Delete Chat / Cancel)
- Friends are saved to flash + SD and persist across reboots
- Friends are never auto-evicted from the node list
## Radio Presets
Three presets available in Setup -> Radio:
| Preset | SF | BW | CR | TX | Use Case |
|--------|----|----|----|----|----------|
| **Balanced** (default) | 9 | 125 kHz | 4/5 | 17 dBm | General use |
| Long Range | 12 | 62.5 kHz | 4/8 | 22 dBm | Maximum range, slow |
| Fast | 7 | 250 kHz | 4/5 | 14 dBm | Short range, fast |
All parameters are individually configurable. Changes apply immediately.
## Serial Diagnostics
Connect at 115200 baud for debug output:
```bash
python3 -m platformio device monitor -b 115200
```
- **Ctrl+D** on device: full diagnostics dump (identity, radio, heap, uptime)
- **Ctrl+T** on device: send radio test packet
- **Ctrl+R** on device: 5-second RSSI sampling
## Transport Mode
By default, Ratdeck runs as an **Endpoint** (handles its own traffic only). Enable **Transport Node** in Setup -> Network to relay packets for other nodes in the mesh. Requires reboot.
-56
View File
@@ -1,56 +0,0 @@
# Ratdeck — Release Protocol
## Pre-Release Checklist
1. All changes committed and pushed to `main`
2. Local build succeeds: `pio run -e ratdeck_915`
3. Flash and test on device — confirm boot completes and basic functionality works
4. Version bumped in `src/config/Config.h` (all 4 defines: `RATDECK_VERSION_MAJOR`, `RATDECK_VERSION_MINOR`, `RATDECK_VERSION_PATCH`, `RATDECK_VERSION_STRING`)
## Release Steps
```bash
# 1. Commit version bump
git add src/config/Config.h
git commit -m "vX.Y.Z: description of changes"
# 2. Push to main
git push origin main
# 3. Create and push tag
git tag vX.Y.Z
git push origin vX.Y.Z
# CI automatically builds and creates GitHub release with ratdeck-firmware.zip
```
## Post-Release Verification
1. Check [GitHub Actions](https://github.com/ratspeak/ratdeck/actions) — both build and release jobs should pass
2. Verify the [release page](https://github.com/ratspeak/ratdeck/releases) has `ratdeck-firmware.zip` attached
3. Download the ZIP and confirm it contains:
- `bootloader.bin`
- `partitions.bin`
- `boot_app0.bin`
- `firmware.bin`
- `manifest.json`
4. Web flasher at [ratspeak.org/download](https://ratspeak.org/download.html) should pick up the new release
## Hotfix Protocol
For critical bugs in a released version:
1. Fix on `main` branch
2. Bump patch version (e.g., 1.5.9 → 1.5.10)
3. Follow normal release steps above
## Build Environment Reference
| Parameter | Value |
|-----------|-------|
| PlatformIO env | `ratdeck_915` |
| Version defines | `RATDECK_VERSION_MAJOR/MINOR/PATCH/STRING` |
| Firmware artifact | `ratdeck-firmware.zip` |
| Flash size | 16MB |
| Flash mode | qio |
| PSRAM | 8MB (enabled via `BOARD_HAS_PSRAM`) |
-237
View File
@@ -1,237 +0,0 @@
# Ratdeck — Troubleshooting
Collected hardware and software gotchas for the LilyGo T-Deck Plus with SX1262 LoRa.
---
## Radio Issues
### TCXO voltage must be 1.8V
The T-Deck Plus SX1262 uses a TCXO that requires 1.8V. This is configured as `MODE_TCXO_1_8V_6X` (0x02) in `BoardConfig.h`.
**Symptom**: Radio reports online but can't synthesize frequencies. PLL lock fails, no TX or RX.
**Diagnosis**: `Ctrl+D` diagnostics — if `DevErrors` shows `0x0040`, that's PLL lock failure.
**Fix**: Verify `LORA_TCXO_VOLTAGE` is `0x02` in `BoardConfig.h`.
### SetModulationParams must be called from STDBY mode
The SX1262 silently rejects `SetModulationParams` (0x8B) when issued from RX or TX mode. The command appears to succeed but the hardware ignores the new SF/BW/CR values.
**Symptom**: Software logs show correct SF/BW/CR but actual TX airtime is wrong. Two devices see each other's RF (RSSI visible) but every packet fails CRC.
**Fix**: `setModulationParams()` now calls `standby()` internally before issuing the SPI command.
### Calibration must run after TCXO is enabled
Per SX1262 datasheet Section 13.1.12, if a TCXO is used, it must be enabled before calling `calibrate()`. Calibration locks to whichever oscillator is active.
**Symptom**: TX completes successfully on both devices, RSSI shows real signals, but neither device ever decodes the other's packets.
**Fix**: Init order must be: `enableTCXO()` -> `delay(5ms)` -> `setRegulatorMode(DC-DC)` -> `loraMode()` -> `standby(XOSC)` -> `calibrate()` -> `calibrate_image()`
### IRQ stale latch
The SX1262's IRQ flags can become latched from previous operations. If stale flags persist, DCD gets stuck in "channel busy" and TX never fires.
**Symptom**: First packet sends fine, then all subsequent TX attempts hang.
**Fix**: Clear all IRQ flags at the top of `receive()` before entering RX mode. In `dcd()`, clear stale header error flags when preamble is not detected.
---
## Build Issues
### PlatformIO not on PATH
**Fix**: Use `python3 -m platformio` instead of `pio`:
```bash
python3 -m platformio run
python3 -m platformio run --target upload
python3 -m platformio device monitor -b 115200
```
### Flash fails or disconnects mid-upload
The T-Deck Plus USB-Serial/JTAG can be sensitive to baud rates.
**Fix**: Enter download mode (hold trackball/BOOT button while pressing reset), then flash:
```bash
python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* --baud 460800 \
write-flash -z 0x10000 .pio/build/ratdeck_915/firmware.bin
```
### USBMode must be `default`
The build flag `ARDUINO_USB_MODE=1` selects USB-Serial/JTAG mode (not native CDC).
**Symptom**: With `hwcdc` (USB_MODE=0), the port never appears on macOS.
**Fix**: Keep `ARDUINO_USB_MODE=1` in `platformio.ini`.
---
## Boot Issues
### Boot loop detection and recovery
Ratdeck tracks consecutive boot failures in NVS. If 3 consecutive boots fail to reach the end of `setup()`, WiFi is forced OFF on the next boot.
**How it works**:
1. On each boot, NVS counter `bootc` increments
2. If `bootc >= 3`, WiFi forced to OFF
3. At end of successful `setup()`, counter resets to 0
**Manual recovery**: Connect serial at 115200 baud and watch for `[BOOT] Boot loop detected`. The device should stabilize with WiFi off, then change WiFi settings in Setup.
### Transport reference crash
`RNS::Transport::_interfaces` stores `Interface&` (references, not copies). If a `RNS::Interface` goes out of scope, it creates a dangling reference — `LoadProhibited` crash.
**Fix**: Store TCP Interface wrappers in `std::list<RNS::Interface>` at global scope. Must use `std::list`, not `std::vector` — vector reallocation invalidates references.
### GPIO 10 must be HIGH
The T-Deck Plus requires GPIO 10 to be set HIGH at boot to enable all peripherals (display, radio, keyboard, etc.).
**Symptom**: Blank screen, no serial output, device appears dead.
**Fix**: `setup()` sets `pinMode(10, OUTPUT); digitalWrite(10, HIGH);` as the very first step.
---
## Storage Issues
### LittleFS not mounting
**Symptom**: `[E][vfs_api.cpp:24] open(): File system is not mounted`
**Causes**: First boot after flash erase, or partition table mismatch.
**Fix**: FlashStore calls `LittleFS.begin(true)` which auto-formats on first use. If it persists, erase flash and reflash:
```bash
python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* erase-flash
```
### SD card not detected
**Check**: SD card must be FAT32. The SD card shares the SPI bus with the radio and display (CS=GPIO 39).
**Fix**: Ensure SD card is inserted before power-on. Check serial output for `[SD]` messages. The device works without an SD card — flash is used as fallback.
### Messages not persisting
**Check**: Verify SD card directories exist. Boot should auto-create `/ratputer/messages/`, `/ratputer/contacts/`, etc.
**Fix**: If directories are missing, reboot with SD card inserted. `SDStore::formatForRatputer()` creates the full directory tree on boot.
---
## WiFi Issues
### AP and STA are separate modes
Ratdeck uses three WiFi modes: OFF, AP, STA. These are NOT concurrent — `WIFI_AP_STA` was removed because it consumed too much heap and caused instability.
- **AP mode**: Creates `ratdeck-XXXX` hotspot, TCP server on port 4242
- **STA mode**: Connects to an existing network, TCP client connections
- **OFF**: No WiFi
Switch modes in Setup -> Network. WiFi mode changes require reboot.
### TCP client won't connect
**Check**: WiFi must be in STA mode and connected to a network first. TCP clients are created after WiFi connection succeeds.
**Fix**: Verify the TCP endpoint address and port. Check serial for `[TCP]` messages. Auto-reconnect runs every 15 seconds.
### WiFi causes crashes
WiFi initialization is the most common crash source. The boot loop recovery system (3 consecutive failures -> WiFi OFF) handles this automatically.
**Manual fix**: If stuck, erase flash to reset NVS, reflash, and reconfigure WiFi settings.
---
## RF Debugging
### RSSI Monitor
Press **Ctrl+R** to sample RSSI continuously for 5 seconds. Transmit from another device during sampling. If RSSI stays at the noise floor (~-110 to -120 dBm), the RX front-end isn't hearing RF.
### Test Packet
Press **Ctrl+T** to send a test packet with FIFO readback verification. Confirms the TX path without involving Reticulum.
### Full Diagnostics
Press **Ctrl+D** for a complete diagnostic dump to serial:
| Field | Meaning |
|-------|---------|
| Identity hash | 16-byte Reticulum identity hash |
| Destination hash | LXMF destination address |
| Transport | ACTIVE or OFFLINE, endpoint or transport node |
| Paths / Links | Known Reticulum paths and active links |
| Freq / SF / BW / CR / TXP | Current radio parameters |
| SyncWord regs | Raw 0x0740/0x0741 values (should be 0x14/0x24) |
| DevErrors | SX1262 error register (0x0040 = PLL lock failure) |
| Current RSSI | Instantaneous RSSI in dBm |
| Free heap / PSRAM | Available memory |
| Flash | LittleFS used/total |
| Uptime | Seconds since boot |
---
## Factory Reset
### Settings-only reset
In Setup -> System -> Factory Reset: clears user config from flash and SD, then reboots. Radio, WiFi, display, and audio revert to defaults. Identity and messages are preserved.
### SD card wipe
Connect serial at 115200 baud. Power cycle the device and send `WIPE` within 500ms of boot. Deletes `/ratputer/*` on the SD card and recreates clean directories.
### Full flash erase
Erases everything — LittleFS, NVS, firmware:
```bash
python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* erase-flash
```
Reflash firmware after erasing. A new identity will be generated. SD card data is preserved.
---
## Serial Log Tags
| Tag | Subsystem |
|-----|-----------|
| `[BOOT]` | Setup sequence |
| `[RADIO]` | SX1262 driver |
| `[LORA_IF]` | LoRa <-> Reticulum interface |
| `[WIFI]` | WiFi AP/STA |
| `[TCP]` | TCP client connections |
| `[LXMF]` | LXMF message protocol |
| `[SD]` | SD card storage |
| `[BLE]` | BLE Sideband transport |
| `[HOTKEY]` | Keyboard hotkey dispatch |
| `[ID]` | Identity management |
---
## Known Limitations
| Feature | Status | Notes |
|---------|--------|-------|
| Touchscreen | Disabled | GT911 needs calibration |
| GPS | Pins defined | Not yet implemented |
| Split packets | Header flag defined | Single-frame LoRa only (max ~254 bytes) |
| TCP client leak | Minor | Stopped clients not freed (Transport holds refs) |
-91
View File
@@ -1,91 +0,0 @@
# LXMF Wire Format Reference
This documents the exact LXMF wire format as implemented by Python Sideband/NomadNet and ratdeck. All fields are big-endian.
## Canonical (Internal) Format
Used for signing, messageId computation, and storage:
```
[dest_hash:16][src_hash:16][signature:64][msgpack_payload]
```
- `dest_hash` — 16-byte truncated hash of the destination identity
- `src_hash` — 16-byte truncated hash of the source identity
- `signature` — 64-byte Ed25519 signature
- `msgpack_payload` — MsgPack array: `[timestamp, title, content, fields]`
## Wire Formats by Delivery Method
### Opportunistic (single-packet, non-link)
The destination hash is **not** included in the LXMF payload — it's carried by the RNS packet header.
```
RNS Packet payload = [src_hash:16][signature:64][msgpack_payload]
```
Python reference (`LXMessage.py:628-631`):
```python
if self.method == LXMessage.OPPORTUNISTIC:
return RNS.Packet(self.__delivery_destination, self.packed[DESTINATION_LENGTH:])
```
### Direct (link-based)
The destination hash IS included in the payload:
```
Link packet payload = [dest_hash:16][src_hash:16][signature:64][msgpack_payload]
```
Python reference:
```python
elif self.method == LXMessage.DIRECT:
return RNS.Packet(self.__delivery_destination, self.packed)
```
## Receiving
On receive, the router reconstructs the canonical format before unpacking:
- **Non-link packets**: Prepend `packet.destination.hash` to the data
- **Link packets**: Data already contains dest_hash
Python reference (`LXMRouter.py:1821-1828`):
```python
if packet.destination_type != RNS.Destination.LINK:
lxmf_data = packet.destination.hash + data # prepend dest_hash
else:
lxmf_data = data # already has dest_hash
```
## Signature Computation
The signature covers the canonical data **plus** a message hash:
```
hashed_part = dest_hash || src_hash || msgpack_payload
msg_hash = SHA256(hashed_part)
signable = hashed_part || msg_hash
signature = Ed25519_sign(signable)
```
## Message ID
```
messageId = SHA256(dest_hash || src_hash || msgpack_payload)
```
This is the same as `msg_hash` above — computed from the hashed_part before appending the hash for signing. It must be identical on sender and receiver for deduplication.
## MsgPack Payload
Fixed 4-element array (`0x94`):
| Index | Field | MsgPack Type | Notes |
|-------|-----------|-------------|-------|
| 0 | timestamp | float64 (0xCB) | Unix epoch seconds |
| 1 | title | bin8/bin16 (0xC4/0xC5) | Python expects bytes, not str |
| 2 | content | bin8/bin16 (0xC4/0xC5) | Python expects bytes, not str |
| 3 | fields | fixmap (0x80) | Empty map for basic messages |
+1 -1
View File
@@ -52,7 +52,7 @@ bool UserConfig::parseJson(const String& json) {
if (rawBri > 100) rawBri = rawBri * 100 / 255; // Migrate from PWM to percentage
_settings.brightness = constrain(rawBri, 1, 100);
_settings.denseFontMode = doc["dense_font"] | false;
_settings.keyboardBrightness = doc["kb_brightness"] | 100;
_settings.keyboardBrightness = constrain(doc["kb_brightness"] | 100, 1, 100);
_settings.keyboardAutoOn = doc["kb_auto_on"] | false;
_settings.keyboardAutoOff = doc["kb_auto_off"] | false;
_settings.trackballSpeed = doc["trackball_speed"] | 3;
+1 -1
View File
@@ -20,7 +20,7 @@ static void lvgl_flush_cb(lv_disp_drv_t* drv, const lv_area_t* area, lv_color_t*
bool Display::begin() {
_gfx.init();
_gfx.setRotation(1); // Landscape: 320x240
_gfx.setBrightness(128);
_gfx.setBrightness(0);
_gfx.fillScreen(TFT_BLACK);
Serial.printf("[DISPLAY] Initialized: %dx%d (rotation=1, LovyanGFX direct)\n",
+10 -2
View File
@@ -108,9 +108,16 @@ void Power::setState(State newState) {
switch (_state) {
case ACTIVE:
if (oldState == SCREEN_OFF) {
// Pre-load correct brightness into LovyanGFX state before wakeup.
// wakeup() sends SLPOUT then restores LGFX's internal _brightness
// to the LEDC — with this ordering, it restores the correct value
// instead of a stale 0, eliminating the rapid 0→0→correct triple-
// write that can cause missed LEDC duty updates on ESP32-S3.
display.setBrightness(percentToPWM(_brightnessPct));
display.wakeup();
} else {
display.setBrightness(percentToPWM(_brightnessPct));
}
display.setBrightness(percentToPWM(_brightnessPct));
if (_kbAutoOn) {
keyboard.backlightOn();
}
@@ -122,7 +129,8 @@ void Power::setState(State newState) {
}
break;
case SCREEN_OFF:
display.setBrightness(0);
// LovyanGFX sleep() sets brightness to 0 internally — no
// need to call setBrightness(0) beforehand.
display.sleep();
if (_kbAutoOff) {
keyboard.backlightOff();
+12 -2
View File
@@ -22,8 +22,18 @@ public:
// Display backlight — accepts percentage 1-100
void setBrightness(uint8_t percent);
void setDimTimeout(uint16_t seconds) { _dimTimeout = seconds * 1000UL; }
void setOffTimeout(uint16_t seconds) { _offTimeout = seconds * 1000UL; }
void setDimTimeout(uint16_t seconds) {
_dimTimeout = seconds * 1000UL;
if (_offTimeout > 0 && _offTimeout <= _dimTimeout) {
_offTimeout = _dimTimeout + 10000UL;
}
}
void setOffTimeout(uint16_t seconds) {
_offTimeout = seconds * 1000UL;
if (_offTimeout > 0 && _offTimeout <= _dimTimeout) {
_offTimeout = _dimTimeout + 10000UL;
}
}
// Keyboard backlight — accepts percentage 1-100
void setKbBrightness(uint8_t percent, bool apply=false);