From 037af2a6ace2d91e354d4216d9c38fade2a6c403 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:02:40 -0600 Subject: [PATCH] - Brightness adjustments --- docs/ARCHITECTURE.md | 199 -------------------------------- docs/BUILDING.md | 170 --------------------------- docs/DEVELOPMENT.md | 230 ------------------------------------ docs/HOTKEYS.md | 104 ----------------- docs/PINMAP.md | 144 ----------------------- docs/QUICKSTART.md | 161 -------------------------- docs/RELEASING.md | 56 --------- docs/TROUBLESHOOTING.md | 237 -------------------------------------- docs/WIRE-FORMAT.md | 91 --------------- src/config/UserConfig.cpp | 2 +- src/hal/Display.cpp | 2 +- src/hal/Power.cpp | 12 +- src/hal/Power.h | 14 ++- 13 files changed, 24 insertions(+), 1398 deletions(-) delete mode 100644 docs/ARCHITECTURE.md delete mode 100644 docs/BUILDING.md delete mode 100644 docs/DEVELOPMENT.md delete mode 100644 docs/HOTKEYS.md delete mode 100644 docs/PINMAP.md delete mode 100644 docs/QUICKSTART.md delete mode 100644 docs/RELEASING.md delete mode 100644 docs/TROUBLESHOOTING.md delete mode 100644 docs/WIRE-FORMAT.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index 1bc7379..0000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -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. diff --git a/docs/BUILDING.md b/docs/BUILDING.md deleted file mode 100644 index 7fcb602..0000000 --- a/docs/BUILDING.md +++ /dev/null @@ -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 | diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md deleted file mode 100644 index 67990f3..0000000 --- a/docs/DEVELOPMENT.md +++ /dev/null @@ -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 | diff --git a/docs/HOTKEYS.md b/docs/HOTKEYS.md deleted file mode 100644 index 2c01bce..0000000 --- a/docs/HOTKEYS.md +++ /dev/null @@ -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`. diff --git a/docs/PINMAP.md b/docs/PINMAP.md deleted file mode 100644 index 57d2ecd..0000000 --- a/docs/PINMAP.md +++ /dev/null @@ -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 | diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md deleted file mode 100644 index 10f26f1..0000000 --- a/docs/QUICKSTART.md +++ /dev/null @@ -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. diff --git a/docs/RELEASING.md b/docs/RELEASING.md deleted file mode 100644 index 041b294..0000000 --- a/docs/RELEASING.md +++ /dev/null @@ -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`) | diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md deleted file mode 100644 index 3515f09..0000000 --- a/docs/TROUBLESHOOTING.md +++ /dev/null @@ -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` 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) | diff --git a/docs/WIRE-FORMAT.md b/docs/WIRE-FORMAT.md deleted file mode 100644 index 812716d..0000000 --- a/docs/WIRE-FORMAT.md +++ /dev/null @@ -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 | diff --git a/src/config/UserConfig.cpp b/src/config/UserConfig.cpp index e03cc6a..908234c 100644 --- a/src/config/UserConfig.cpp +++ b/src/config/UserConfig.cpp @@ -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; diff --git a/src/hal/Display.cpp b/src/hal/Display.cpp index a1525ba..fff6649 100644 --- a/src/hal/Display.cpp +++ b/src/hal/Display.cpp @@ -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", diff --git a/src/hal/Power.cpp b/src/hal/Power.cpp index a55fdea..043b90b 100644 --- a/src/hal/Power.cpp +++ b/src/hal/Power.cpp @@ -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(); diff --git a/src/hal/Power.h b/src/hal/Power.h index 02e5545..565dbbf 100644 --- a/src/hal/Power.h +++ b/src/hal/Power.h @@ -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);