From b48c9064b9eaecc4772b143ca9a4cf52bd9744ac Mon Sep 17 00:00:00 2001 From: "dude.eth" <59237470+DeFiDude@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:38:45 -0700 Subject: [PATCH] updating docs to reflect v1.4.2 --- README.md | 348 +++++++++++------------------------ docs/ARCHITECTURE.md | 242 ++++++++++++++---------- docs/BUILDING.md | 148 ++++++++------- docs/DEVELOPMENT.md | 394 ++++++++++++++-------------------------- docs/HOTKEYS.md | 101 ++++++---- docs/PINMAP.md | 163 ++++++++++------- docs/QUICKSTART.md | 207 +++++++++++---------- docs/TROUBLESHOOTING.md | 316 +++++++++++++++----------------- 8 files changed, 883 insertions(+), 1036 deletions(-) diff --git a/README.md b/README.md index f331010..8079e6f 100644 --- a/README.md +++ b/README.md @@ -1,296 +1,156 @@ # Ratdeck -Standalone [Reticulum](https://reticulum.network/) transport node + [LXMF](https://github.com/markqvist/LXMF) encrypted messenger, built for the [LilyGo T-Deck Plus](https://www.lilygo.cc/products/t-deck-plus) with integrated SX1262 LoRa radio. +**v1.4.2** | [Ratspeak.org](https://ratspeak.org) -Not an RNode. Not a gateway. A fully self-contained mesh node with a keyboard, touchscreen, trackball, and LoRa radio — no host computer required. +Standalone [Reticulum](https://reticulum.network/) mesh node + [LXMF](https://github.com/markqvist/LXMF) encrypted messenger for the [LilyGo T-Deck Plus](https://www.lilygo.cc/products/t-deck-plus). -## What This Does +Not an RNode. Not a gateway. A fully self-contained LoRa mesh communicator with a keyboard, trackball, and LVGL UI — no host computer required. -Ratdeck turns a T-Deck Plus into a Reticulum mesh node that can: - -- **Send and receive encrypted messages** over LoRa (LXMF protocol, Ed25519 signatures) -- **Discover other nodes** automatically via Reticulum announces -- **Bridge LoRa to WiFi** so desktop Reticulum instances can reach the mesh -- **Connect to remote Reticulum nodes** over TCP (e.g., `rns.beleth.net`) -- **Store messages and contacts** on flash and SD card with automatic backup -- **Configure everything on-device** — no config files, no host tools - -The device runs [microReticulum](https://github.com/attermann/microReticulum) (a C++ port of the Reticulum stack) directly on the ESP32-S3, with a register-level SX1262 LoRa driver. +``` ++----------------------------------------------+ +| [|||] Ratspeak [87%] | ++----------------------------------------------+ +| | +| CONTENT AREA | +| 320x240, LVGL v8.4 | ++----------------------------------------------+ +| Home Friends Msgs Peers Setup | ++----------------------------------------------+ +``` ## Features -| Category | Details | -|----------|---------| -| **Networking** | Reticulum transport node, path discovery, announce propagation, auto-announce every 5 min | -| **Messaging** | LXMF encrypted messages, Ed25519 signatures, delivery tracking, per-conversation storage | -| **LoRa Radio** | SX1262 at 915 MHz, configurable SF (5-12), BW (7.8-500 kHz), CR (4/5-4/8), TX power (2-22 dBm) | -| **WiFi** | AP mode (TCP server on :4242) or STA mode (TCP client to remote nodes) — not concurrent | -| **BLE** | NimBLE Sideband interface for Reticulum over Bluetooth | -| **Storage** | Dual-backend: LittleFS (7.8 MB on flash) + FAT32 microSD, atomic writes, identity backup | -| **Display** | 320x240 IPS TFT via LovyanGFX, signal green on black, double-buffered sprite rendering | -| **Input** | Full QWERTY keyboard (ESP32-C3 I2C), GT911 capacitive touchscreen, optical trackball | -| **Audio** | I2S codec, notification sounds for messages, announces, errors, boot chime | -| **GPS** | UBlox MIA-M10Q GNSS (pins defined, v1.1) | -| **Power** | Screen dim/off/wake on input, configurable timeouts, battery % in status bar | -| **Reliability** | Boot loop recovery (NVS counter, forces WiFi OFF after 3 failures) | -| **Diagnostics** | Ctrl+D full dump, Ctrl+T radio test packet, Ctrl+R 5-second RSSI monitor | +- **Encrypted LoRa messaging** — LXMF protocol, Ed25519 signatures, per-conversation storage +- **Mesh networking** — Reticulum endpoint or transport node, automatic path discovery +- **Node discovery** — see who's online, save contacts, manage friends +- **WiFi bridging** — TCP client to remote Reticulum nodes, or AP mode to bridge your desktop to LoRa +- **BLE transport** — NimBLE Sideband interface +- **On-device config** — 7-category settings, radio presets, multi-slot identity management +- **Dual storage** — LittleFS flash + SD card with atomic writes and automatic backup +- **OTA-ready** — check for firmware updates directly from the device + +Built on [microReticulum](https://github.com/attermann/microReticulum) with a register-level SX1262 driver and LVGL v8.4. ## Hardware | Component | Part | Notes | |-----------|------|-------| -| **Board** | LilyGo T-Deck Plus | ESP32-S3, 16MB flash, PSRAM, 320x240 IPS TFT, QWERTY keyboard, trackball, touchscreen | -| **Radio** | Integrated SX1262 | 915 MHz ISM, TCXO 1.8V, DIO2 RF switch, shared SPI bus | +| **Board** | LilyGo T-Deck Plus | ESP32-S3, 16MB flash, 8MB PSRAM, 320x240 IPS, QWERTY keyboard, trackball | +| **Radio** | Integrated SX1262 | 915 MHz ISM, TCXO 1.8V, DIO2 RF switch | | **Storage** | microSD card | Optional but recommended. FAT32, any size | -| **USB** | USB-C | USB-Serial/JTAG. Port: `/dev/cu.usbmodem*` (macOS), `/dev/ttyACM*` (Linux) | -See [docs/PINMAP.md](docs/PINMAP.md) for the full GPIO pin map. - -## Prerequisites - -| Requirement | Version | Install | -|-------------|---------|---------| -| **Python** | 3.12+ | [python.org](https://www.python.org/downloads/) or your package manager | -| **PlatformIO Core** | 6.x | `pip install platformio` | -| **Git** | any | Your package manager | -| **USB driver** | — | None needed on macOS/Linux (ESP32-S3 USB-Serial/JTAG is built-in) | - -PlatformIO automatically downloads the ESP32-S3 toolchain, Arduino framework, and all library dependencies on first build. - -## Build and Flash +## Quick Start ```bash -# Clone git clone https://github.com/defidude/Ratdeck.git cd Ratdeck - -# Build (first build takes ~2 min to download toolchain + deps) -python3 -m platformio run -e ratdeck_915 - -# Flash (plug in T-Deck Plus via USB-C) -python3 -m platformio run -e ratdeck_915 -t upload --upload-port /dev/cu.usbmodem* +python3 -m platformio run # build +python3 -m platformio run --target upload # flash ``` -> If `pio` is not on your PATH after install, use `python3 -m platformio` everywhere. +First build pulls the ESP32-S3 toolchain and all dependencies automatically. -### Alternative: esptool +### Web Flash -PlatformIO's default baud sometimes fails over USB-Serial/JTAG. esptool at 460800 is more reliable: - -```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 -``` - -See [docs/BUILDING.md](docs/BUILDING.md) for merged binaries, build flags, partition table, and CI/CD details. +No build tools? Visit **[ratspeak.org/flash](https://ratspeak.org/flash)** to flash from your browser. ## First Boot -1. Plug in or power on the T-Deck Plus -2. Boot animation with progress bar (~3 seconds) -3. SX1262 radio initializes at 915 MHz -4. SD card checked, `/ratdeck/` directories auto-created -5. Reticulum identity generated (Ed25519 keypair, persisted to flash + SD) -6. WiFi AP starts: `ratdeck-XXXX` (password: `ratspeak`) -7. Initial announce broadcast to the mesh -8. Home screen: identity hash, transport status, radio info, uptime +1. Boot animation with progress bar +2. SX1262 radio initializes (915 MHz, Balanced preset) +3. SD card checked, `/ratputer/` directories auto-created +4. Reticulum identity generated (Ed25519 keypair, triple-redundant backup) +5. Name input screen (optional — auto-generates if skipped) +6. Home screen — you're on the mesh -## UI Layout - -``` -+------------------------------------------+ -| [87%] [Ratspeak.org] [LoRa] | Status bar: battery, transport mode, radio -+------------------------------------------+ -| | -| CONTENT AREA | Screens: Home, Messages, Nodes, Map, Settings -| 320 x 240 | -| | -+------------------------------------------+ -| [Home] [Msgs] [Nodes] [Map] [Setup] | Tab bar with unread badges -+------------------------------------------+ -``` - -**Theme**: Signal green (#00FF41) on black. 320x240 pixels. LovyanGFX double-buffered sprite rendering. - -## Keyboard and Hotkeys - -### 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 network (immediate) | -| Ctrl+D | Dump full diagnostics to serial | -| Ctrl+T | Send radio test packet | -| Ctrl+R | RSSI monitor (5 seconds continuous sampling) | +## Usage ### Navigation -| Key | Action | -|-----|--------| -| Trackball | Scroll / navigate | -| Touch | Tap to select UI elements | -| Enter | Select / confirm / send message | -| Esc | Back / cancel | -| Backspace | Delete character in text input | +| Input | Action | +|-------|--------| +| Trackball up/down | Scroll lists | +| Trackball left/right | Cycle tabs | +| Trackball click | Select / confirm | +| Long-press (1.2s) | Context menu | +| `,` / `/` | Previous / next tab | +| Enter | Select / send | +| Esc / Backspace | Back / cancel | +| Ctrl+H | Hotkey help overlay | -## WiFi and Networking +### Tabs -Three WiFi modes, configured in Settings: +| Tab | What It Shows | +|-----|---------------| +| **Home** | Name, LXMF address, connection status, online nodes | +| **Friends** | Saved contacts with display names | +| **Msgs** | Conversations — sorted by most recent, preview, unread dots | +| **Peers** | All discovered nodes with RSSI/SNR | +| **Setup** | Device, Display, Radio, Network, Audio, Info, System | -### OFF Mode +### Sending a Message -No WiFi. Saves power and heap. LoRa-only operation. - -### AP Mode (default) - -Creates a WiFi hotspot named `ratdeck-XXXX` (password: `ratspeak`). Runs a TCP server on port 4242 with HDLC framing. - -**Bridge to desktop Reticulum**: Connect your laptop to the `ratdeck-XXXX` network, then add to your Reticulum config (`~/.reticulum/config`): - -```ini -[[ratdeck]] - type = TCPClientInterface - target_host = 192.168.4.1 - target_port = 4242 -``` - -Now your desktop `rnsd` can reach the LoRa mesh through Ratdeck. - -### STA Mode - -Connects to an existing WiFi network. Establishes outbound TCP connections to configured Reticulum endpoints. - -**Setup**: -1. Ctrl+S, WiFi, Mode: **STA** -2. Enter your WiFi SSID and password -3. Add TCP endpoints: e.g., `rns.beleth.net` port `4242`, auto-connect enabled -4. Save — Ratdeck connects to your WiFi, then opens TCP links to the configured hosts +1. Another node appears in **Peers** (or connect via WiFi) +2. Select the node, press Enter to open chat +3. Type your message, press Enter to send +4. Status: yellow (sending) → green (delivered) → red (failed) ## LoRa Radio -### Default Configuration +Three presets in Settings → Radio: -| Parameter | Default | Range | -|-----------|---------|-------| -| Frequency | 915 MHz | Hardware-dependent | -| Spreading Factor | SF9 | SF5-SF12 | -| Bandwidth | 125 kHz | 7.8 kHz - 500 kHz | -| Coding Rate | 4/5 | 4/5 - 4/8 | -| TX Power | 22 dBm | 2-22 dBm | -| Preamble | 18 symbols | Configurable | -| Sync Word | 0x1424 | Reticulum standard | -| Max Packet | 255 bytes | SX1262 hardware limit | +| Preset | SF | BW | CR | TX | Use Case | +|--------|----|----|----|----|----------| +| **Balanced** | 9 | 250 kHz | 4/5 | 14 dBm | General use | +| Long Range | 12 | 125 kHz | 4/8 | 17 dBm | Maximum range | +| Fast | 7 | 500 kHz | 4/5 | 10 dBm | Short range, fast | -All parameters configurable via Settings. Changes take effect immediately and persist across reboots. +All parameters individually configurable. Changes apply immediately. -## SD Card +## WiFi & Networking -Optional microSD card (FAT32, any size). Provides backup storage and extended capacity beyond the LittleFS partition. +| Mode | Description | +|------|-------------| +| **STA** (default) | Connect to your WiFi, TCP client to remote Reticulum nodes | +| **AP** | Creates `ratdeck-XXXX` hotspot, TCP server on port 4242 | +| **OFF** | LoRa only, saves power | -### Directory Structure (auto-created on first boot) +**Connect to the mesh over WiFi:** Settings → Network → enter WiFi creds → add TCP endpoint (e.g., `rns.beleth.net:4242`). -``` -/ratdeck/ - config/ - user.json Runtime settings (radio, WiFi, display, audio) - messages/ - / Per-conversation message history (JSON) - contacts/ Discovered Reticulum nodes - identity/ - identity.key Ed25519 keypair backup -``` +**Bridge your desktop to LoRa:** Set AP mode → connect laptop to `ratdeck-XXXX` (password: `ratspeak`) → add `TCPClientInterface` to `~/.reticulum/config` pointing at `192.168.4.1:4242`. -## Dependencies +## Transport Mode -All automatically managed by PlatformIO — no manual installation needed: +Default: **endpoint** (handles own traffic). Enable **Transport Node** in Settings → Network to relay packets and maintain routing tables for the mesh. -| Library | Version | Purpose | -|---------|---------|---------| -| [microReticulum](https://github.com/attermann/microReticulum) | git HEAD | Reticulum protocol stack (C++ port) | -| [Crypto](https://github.com/attermann/Crypto) | git HEAD | Ed25519, X25519, AES-128, SHA-256, HMAC | -| [ArduinoJson](https://github.com/bblanchon/ArduinoJson) | ^7.4.2 | JSON serialization for config and message storage | -| [LovyanGFX](https://github.com/lovyan03/LovyanGFX) | ^1.1.16 | Display driver — SPI, DMA, sprite double-buffering | -| [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino) | ^2.1 | BLE stack for Sideband interface | - -### Build Toolchain - -| Component | Version | Notes | -|-----------|---------|-------| -| PlatformIO | espressif32@6.7.0 | ESP-IDF + Arduino framework | -| Board | esp32-s3-devkitc-1 | Generic ESP32-S3, 16MB flash, PSRAM | -| Arduino Core | ESP32 Arduino 2.x | C++17, exceptions enabled | - -## Flash Memory Layout - -16MB flash, partitioned for OTA support: - -| Partition | Offset | Size | Purpose | -|-----------|--------|------|---------| -| nvs | 0x9000 | 20 KB | Boot counter, WiFi credentials | -| otadata | 0xE000 | 8 KB | OTA boot selection | -| app0 | 0x10000 | 4 MB | Active firmware | -| app1 | 0x410000 | 4 MB | OTA update slot (reserved) | -| littlefs | 0x810000 | 7.8 MB | Identity, config, messages, transport paths | -| coredump | 0xFF0000 | 64 KB | ESP-IDF crash dump | - -## Project Structure - -``` -Ratdeck/ - src/ - main.cpp Entry point: setup() + main loop - config/ BoardConfig.h (pins), Config.h (compile-time), UserConfig.* (runtime JSON) - radio/ SX1262.* (register-level driver), RadioConstants.h - hal/ Display (LovyanGFX), TouchInput (GT911), Trackball, Keyboard, Power, GPS, Audio - input/ InputManager (unified input), HotkeyManager (Ctrl+key dispatch) - ui/ UIManager, StatusBar, TabBar, Theme - screens/ Boot, Home, Messages, MessageView, Nodes, Map, Settings, HelpOverlay - reticulum/ ReticulumManager, AnnounceManager, LXMFManager, LXMFMessage - transport/ LoRaInterface, WiFiInterface, TCPClientInterface, BLEInterface, BLESideband - storage/ FlashStore (LittleFS), SDStore (FAT32), MessageStore (dual) - power/ PowerManager (dim/off/wake) - audio/ AudioNotify (boot, message, announce, error sounds) - docs/ BUILDING, PINMAP, TROUBLESHOOTING, DEVELOPMENT, ARCHITECTURE, QUICKSTART, HOTKEYS - platformio.ini Build configuration (single env: ratdeck_915) - partitions_16MB.csv Flash partition table - .github/workflows/ CI: build on push, release merged binary on tag -``` - -## Documentation - -| Document | Contents | -|----------|----------| -| [Quick Start](docs/QUICKSTART.md) | First build, first boot, navigation, WiFi setup, SD card | -| [Building](docs/BUILDING.md) | Build commands, flashing, merged binaries, CI/CD, build flags | -| [Pin Map](docs/PINMAP.md) | Full GPIO assignments for all peripherals | -| [Hotkeys](docs/HOTKEYS.md) | Complete keyboard reference | -| [Architecture](docs/ARCHITECTURE.md) | Layer diagram, directory tree, design decisions | -| [Development](docs/DEVELOPMENT.md) | How to add screens, hotkeys, settings, transports | -| [Troubleshooting](docs/TROUBLESHOOTING.md) | Radio, build, boot loop, storage, WiFi issues | - -## Current Status - -**v1.0.0** — Working on hardware. +## Status | Subsystem | Status | |-----------|--------| -| LoRa radio | Working — TX/RX verified | -| WiFi AP | Working — TCP server, HDLC framing, bridges to desktop rnsd | -| WiFi STA + TCP | Working — connects to remote Reticulum nodes | -| LXMF messaging | Working — send/receive/store with Ed25519 signatures | -| Node discovery | Working — automatic announce processing | -| SD card storage | Working — dual-backend with atomic writes | -| Settings | Working — full on-device configuration | -| Touchscreen | Working — GT911 capacitive touch | -| Trackball | Working — optical navigation | -| BLE Sideband | Working — NimBLE interface | -| GPS | Pins defined — v1.1 | -| OTA updates | Partition reserved — not implemented | +| LoRa (SX1262) | Working — async TX/RX, configurable presets | +| LXMF messaging | Working — send/receive/store, Ed25519, delivery tracking | +| LVGL UI | Working — 5 tabs, chat bubbles, settings, contacts | +| WiFi STA + TCP | Working — auto-reconnect, live server switching | +| WiFi AP | Working — TCP server, HDLC framing, desktop bridge | +| BLE Sideband | Working — NimBLE transport | +| Node discovery | Working — announces, friend management | +| SD + Flash storage | Working — dual-backend, atomic writes | +| Identity management | Working — multi-slot, triple-redundant | +| Transport mode | Working — endpoint or relay | +| Touchscreen | Disabled — GT911 needs calibration | +| GPS | Pins defined — not yet implemented | + +## Documentation + +| Doc | Contents | +|-----|----------| +| **[Quick Start](docs/QUICKSTART.md)** | Build, flash, first boot, first message | +| [Building](docs/BUILDING.md) | Build flags, flashing, CI/CD, partition table | +| [Architecture](docs/ARCHITECTURE.md) | Layer diagram, data flow, design decisions | +| [Development](docs/DEVELOPMENT.md) | Adding screens, settings, transports, debugging | +| [Pin Map](docs/PINMAP.md) | Full T-Deck Plus GPIO assignments | +| [Hotkeys](docs/HOTKEYS.md) | Keyboard shortcuts and navigation | +| [Troubleshooting](docs/TROUBLESHOOTING.md) | Radio, build, boot, storage issues | ## License diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fe388bb..1bc7379 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,83 +1,99 @@ -# Ratputer — Architecture +# Ratdeck — Architecture ## Overview -Ratputer is a standalone Reticulum transport node with LXMF messaging, built for the M5Stack Cardputer Adv with Cap LoRa-1262 radio module. +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 (M5Canvas) │ -│ Screens: Home, Msgs, Nodes, Setup │ -│ Widgets: ScrollList, TextInput │ -│ StatusBar, TabBar, HelpOverlay │ -├─────────────────────────────────────┤ -│ Application Layer │ -│ LXMFManager AnnounceManager │ -│ UserConfig AudioNotify │ -│ PowerManager MessageStore │ -├─────────────────────────────────────┤ -│ Reticulum Layer │ -│ ReticulumManager (microReticulum) │ -│ Identity, Destination, Transport │ -├─────────────────────────────────────┤ -│ Transport Layer │ -│ LoRaInterface WiFiInterface │ -│ TCPClientInterface BLEStub │ -├─────────────────────────────────────┤ -│ Storage Layer │ -│ FlashStore (LittleFS) │ -│ SDStore (FAT32 microSD) │ -├─────────────────────────────────────┤ -│ Hardware Layer │ -│ SX1262 Radio M5Cardputer │ -│ LittleFS ESP32-S3 │ -└─────────────────────────────────────┘ ++---------------------------------------------+ +| 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 Main entry point (setup + loop) -├── config/ -│ ├── BoardConfig.h Pin definitions, hardware constants -│ ├── Config.h Compile-time settings, feature flags, paths -│ └── UserConfig.* Runtime settings (JSON, dual SD+flash backend) -├── radio/ -│ ├── SX1262.* SX1262 LoRa driver (register-level) -│ └── RadioConstants.h Register definitions -├── input/ -│ ├── Keyboard.* M5Cardputer keyboard wrapper -│ └── HotkeyManager.* Ctrl+key dispatch -├── ui/ -│ ├── Theme.h Color palette, layout metrics -│ ├── UIManager.* Canvas rendering, screen stack -│ ├── Screen.h Abstract base class -│ ├── StatusBar.* Top bar (battery, transport, LoRa) -│ ├── TabBar.* Bottom tab navigation -│ ├── screens/ Per-tab screen implementations -│ ├── widgets/ Reusable UI components -│ └── assets/ Boot logo -├── reticulum/ -│ ├── ReticulumManager.* microReticulum integration -│ ├── AnnounceManager.* Node discovery, contact persistence -│ ├── LXMFManager.* LXMF messaging protocol -│ └── LXMFMessage.* Message format (MsgPack wire, JSON storage) -├── transport/ -│ ├── LoRaInterface.* SX1262 ↔ Reticulum bridge (1-byte header) -│ ├── WiFiInterface.* WiFi AP transport, TCP server on :4242 -│ ├── TCPClientInterface.* WiFi STA transport, TCP client to remote nodes -│ └── BLEStub.* BLE advertising placeholder (disabled) -├── storage/ -│ ├── FlashStore.* LittleFS with atomic writes -│ ├── SDStore.* SD card (FAT32) with atomic writes + wipe -│ └── MessageStore.* Per-conversation storage (dual: flash + SD) -├── power/ -│ └── PowerManager.* Screen dim/off/wake -└── audio/ - └── AudioNotify.* Notification sounds ++-- 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 @@ -85,67 +101,99 @@ src/ ### Incoming LoRa Packet ``` -SX1262 IRQ (DIO1) → 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 +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 → MessageView → LXMFManager::send() - → Pack: source_hash(16) + msgpack([ts, content, title, fields]) + Ed25519 sig(64) - → RNS::Packet → RNS::Transport selects interface - ├── LoRaInterface → prepend 1-byte header → SX1262::beginPacket/endPacket - └── WiFi/TCPClient → HDLC frame (0x7E delimit, 0x7D escape) → TCP socket +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 ``` -SettingsScreen → 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 +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 -Extracted from RNode_Firmware_CE, stripped of multi-interface and CSMA/CA. Custom SPI (HSPI) with TCXO 3.0V configuration. IRQ stale latch fix applied to prevent DCD lockup after first TX. + +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 -Double-buffered M5Canvas sprite (240×135 RGB565). All rendering goes through UIManager which handles status bar, content area clipping, tab bar, and overlay. + +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 a Transport Node with LoRa and WiFi/TCP interfaces registered. -### LXMF Messages -Wire format: `source_hash(16) + msgpack([timestamp, content, title, fields]) + signature(64)`. Direct packet delivery for messages under MDU. Stored as JSON per-conversation in flash and SD. +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 separate modes (not concurrent): -- **OFF**: No WiFi — saves power and ~20KB heap -- **AP**: Creates hotspot, TCP server on port 4242 with HDLC framing (0x7E delimiters, 0x7D escape) + +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 — it consumed too much heap and caused instability. +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 first WiFi STA connection, auto-reconnect on disconnect. Uses same HDLC framing as WiFi AP. + +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) 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. -### Boot Loop Recovery -NVS counter tracks consecutive boot failures. After 3 failures, WiFi is forced OFF on next boot (WiFi init is the most common crash source). Counter resets to 0 at end of successful setup(). +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 (25% brightness) → Screen Off. Wakes on any keypress. Configurable timeouts via Settings. + +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 index 27e2a73..acc2551 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -1,60 +1,74 @@ -# Ratputer — Build & Flash Reference +# Ratdeck — Build & Flash Reference ## Prerequisites -- **Python 3.12+** (for PlatformIO and esptool) -- **PlatformIO Core** (CLI): `pip install platformio` -- **esptool** (usually installed with PlatformIO, or `pip install esptool`) +- **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. -> **Note**: PlatformIO may not be on your PATH after pip install. Use `python3 -m platformio` if `pio` is not found. This applies to all `pio` commands throughout this document. +> If `pio` is not on your PATH after install, use `python3 -m platformio` for all commands in this document. ## Build ```bash -python3 -m platformio run -e ratputer_915 +git clone https://github.com/defidude/Ratdeck.git +cd Ratdeck + +python3 -m platformio run ``` -Output binary: `.pio/build/ratputer_915/firmware.bin` +Output: `.pio/build/ratdeck_915/firmware.bin` -First build downloads all dependencies automatically (M5Unified, M5GFX, M5Cardputer, microReticulum, Crypto, ArduinoJson). +First build downloads all dependencies automatically (microReticulum, Crypto, ArduinoJson, LovyanGFX, NimBLE-Arduino, LVGL). ## Flash -### Via PlatformIO (simple) +### Via PlatformIO ```bash -python3 -m platformio run -e ratputer_915 -t upload --upload-port /dev/cu.usbmodem* +python3 -m platformio run --target upload ``` ### Via esptool (more reliable) -PlatformIO defaults to 921600 baud which sometimes fails. esptool at 460800 is 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/ratputer_915/firmware.bin + 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/flash](https://ratspeak.org/flash) 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 for clean flashing: +A merged binary includes bootloader + partition table + app in one file: ```bash python3 -m esptool --chip esp32s3 merge-bin \ - --output ratputer_merged.bin \ - --flash-mode dio --flash-size 8MB \ + --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/ratputer_915/partitions.bin \ + 0x8000 .pio/build/ratdeck_915/partitions.bin \ 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \ - 0x10000 .pio/build/ratputer_915/firmware.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 ratputer_merged.bin + write-flash 0x0 ratdeck_merged.bin ``` ## Serial Monitor @@ -63,22 +77,18 @@ python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* --baud 460800 \ python3 -m platformio device monitor -b 115200 ``` -Or with any serial terminal at 115200 baud. - -### Serial WIPE Command - -Within the first 500ms of boot, send `WIPE` over serial to wipe the SD card's `/ratputer/` directory. Useful for factory reset of stored messages, contacts, and config. +Or any serial terminal at 115200 baud. ## USB Port Identification -The ESP32-S3 on Cardputer Adv uses USB-Serial/JTAG (not a separate UART chip): +The T-Deck Plus ESP32-S3 uses USB-Serial/JTAG (not a separate UART chip): -| State | Port Name | Notes | -|-------|-----------|-------| -| Firmware mode | `/dev/cu.usbmodem` | Normal operation, serial + flashing | -| Bootloader mode | `/dev/cu.usbmodem5101` | Hold G0 during boot, esptool only | +| OS | Port | Notes | +|----|------|-------| +| macOS | `/dev/cu.usbmodem*` | Use glob to match | +| Linux | `/dev/ttyACM0` | May need `dialout` group | -The firmware-mode port name includes the chip's unique ID (e.g., `/dev/cu.usbmodem3C0F02E81B4C1`). Use `/dev/cu.usbmodem*` glob to match either. +On Linux: `sudo usermod -aG dialout $USER` (log out and back in). ## Build Flags @@ -86,35 +96,55 @@ From `platformio.ini`: | Flag | Purpose | |------|---------| -| `-fexceptions` | Enable C++ exceptions (required by microReticulum) | -| `-DRATPUTER=1` | Main feature flag — guards all Ratputer-specific code | -| `-DARDUINO_USB_CDC_ON_BOOT=1` | USB CDC serial on boot (USBMode=default) | -| `-DARDUINO_USB_MODE=1` | USB mode 1 = USB-Serial/JTAG (not native CDC) | +| `-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 to flash | +| `-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` removes the Arduino default no-exceptions flag. +`build_unflags = -fno-exceptions -std=gnu++11` removes Arduino defaults. ## Partition Table -`partitions_8MB_ota.csv` — 8MB flash layout with OTA support: +`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 | 3 MB | Primary firmware | -| app1 | app/ota_1 | 0x310000 | 3 MB | OTA update slot (reserved) | -| littlefs | data/spiffs | 0x610000 | 1.875 MB | LittleFS — identity, config, messages, paths | -| coredump | data/coredump | 0x7F0000 | 64 KB | ESP-IDF core dump on crash | +| 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 and creates a GitHub Release with the binary attached. +- **Release**: Triggers on `v*` tags. Builds firmware, creates a GitHub Release with the binary ZIP attached. Powers the web flasher at [ratspeak.org/flash](https://ratspeak.org/flash). + +### Release Process + +```bash +# 1. Bump version in src/config/Config.h (all 4 defines) +# 2. Commit and push +git add -A && git commit -m "v1.4.2: description" +git push origin main + +# 3. Tag and push tag +git tag v1.4.2 +git push origin v1.4.2 +# CI creates GitHub release automatically +``` ## Dependencies @@ -122,39 +152,29 @@ All managed by PlatformIO's `lib_deps`: | Library | Source | Purpose | |---------|--------|---------| -| microReticulum | github.com/attermann/microReticulum | Reticulum protocol (C++) | -| Crypto | github.com/attermann/Crypto | Ed25519, X25519, AES, SHA256 | -| ArduinoJson | bblanchon/ArduinoJson ^7.4.2 | Config serialization | -| M5Unified | m5stack/M5Unified | Hardware abstraction | -| M5GFX | m5stack/M5GFX | Display + canvas rendering | -| M5Cardputer | m5stack/M5Cardputer | Keyboard (TCA8418) | +| 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 or you want a clean start): +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, you must reflash the firmware. LittleFS will auto-format on first boot, and a new identity will be generated. +After erasing, reflash the firmware. LittleFS will auto-format on first boot and a new identity will be generated. ## Common Errors | Error | Cause | Fix | |-------|-------|-----| -| `A]Fatal error occurred: 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 for USB-Serial/JTAG | Use `--baud 460800` with esptool | -| `No such option: --upload-port` | Old PlatformIO version | `pip install -U platformio` | -| `ImportError: No module named platformio` | PlatformIO not installed for this Python | `pip install platformio` or use the correct `python3` | -| `pio: command not found` | PlatformIO not on PATH | Use `python3 -m platformio` instead | -| `Error: Bootloader binary size ... exceeds` | Partition mismatch | Ensure `partitions_8MB_ota.csv` is present in repo root | - -## macOS vs Linux Ports - -| OS | Firmware Port | Bootloader Port | -|----|---------------|-----------------| -| macOS | `/dev/cu.usbmodem` | `/dev/cu.usbmodem5101` | -| Linux | `/dev/ttyACM0` (typical) | `/dev/ttyACM0` | - -On Linux, you may need to add your user to the `dialout` group: `sudo usermod -aG dialout $USER` (then log out and back in). +| `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 index 5657849..67990f3 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,105 +1,27 @@ -# Ratputer — Developer Guide +# Ratdeck — Developer Guide -## Project Overview +## Overview -Ratputer is a **standalone Reticulum transport node** with LXMF messaging, built for the M5Stack Cardputer Adv with Cap LoRa-1262 radio. It is **NOT an RNode** — it does not speak KISS protocol. It runs its own Reticulum stack (microReticulum) directly on the device. +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 (RNode-compatible on-air format) +- 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 -- Cyberpunk terminal UI with tabbed navigation +- LVGL v8.4 UI with 5-tab navigation - JSON-based runtime configuration with SD card + flash dual-backend -## Source Tree - -``` -src/ -├── main.cpp Setup (24-step init) + main loop (10 steps, 20 FPS) -├── config/ -│ ├── BoardConfig.h GPIO pins, SPI config, hardware constants -│ ├── Config.h Compile-time: version, feature flags, storage paths, limits -│ └── UserConfig.* Runtime settings: dual-backend JSON (SD primary, flash fallback) -├── radio/ -│ ├── SX1262.* Full SX1262 driver (register-level, extracted from RNode CE) -│ └── RadioConstants.h SX1262 register addresses and command bytes -├── input/ -│ ├── Keyboard.* M5Cardputer TCA8418 keyboard wrapper, key event generation -│ └── HotkeyManager.* Ctrl+key dispatch table, tab cycle callback -├── ui/ -│ ├── Theme.h Signal green (#00FF41) on black, layout metrics -│ ├── UIManager.* Canvas rendering loop, screen stack, boot/normal modes -│ ├── Screen.h Abstract base: handleKey(), render(), update() -│ ├── StatusBar.* Battery %, transport mode, LoRa indicator, announce flash -│ ├── TabBar.* Home/Msgs/Nodes/Setup tabs with unread badges -│ ├── screens/ -│ │ ├── BootScreen.* Animated boot with progress bar -│ │ ├── HomeScreen.* Identity hash, transport status, radio info, uptime -│ │ ├── MessagesScreen.* Conversation list with unread counts -│ │ ├── MessageView.* Single conversation view with text input -│ │ ├── NodesScreen.* Discovered Reticulum nodes with RSSI/SNR -│ │ ├── SettingsScreen.* Radio, WiFi, Display, Audio, About, Factory Reset -│ │ └── HelpOverlay.* Hotkey reference overlay (Ctrl+H toggle) -│ ├── widgets/ -│ │ ├── ScrollList.* Scrollable list with selection highlight -│ │ ├── TextInput.* Single-line text input with cursor -│ │ └── ProgressBar.* Boot progress and general-purpose bars -│ └── assets/ -│ └── BootLogo.h Embedded boot screen graphic -├── reticulum/ -│ ├── ReticulumManager.* microReticulum lifecycle, identity, announce, transport loop -│ ├── AnnounceManager.* Node discovery, contact persistence (SD + flash) -│ ├── LXMFManager.* LXMF send/receive, outgoing queue, delivery tracking -│ └── LXMFMessage.* Wire format: source(16) + msgpack + sig(64) -├── transport/ -│ ├── LoRaInterface.* SX1262 ↔ Reticulum bridge (InterfaceImpl), 1-byte header -│ ├── WiFiInterface.* WiFi AP, TCP server on port 4242, HDLC framing -│ ├── TCPClientInterface.* WiFi STA, TCP client to remote endpoints, HDLC framing -│ └── BLEStub.* BLE advertising placeholder (disabled, v1.1) -├── storage/ -│ ├── FlashStore.* LittleFS wrapper with atomic writes (.tmp→verify→.bak→rename) -│ ├── SDStore.* SD card (FAT32) with atomic writes, wipe, directory management -│ └── MessageStore.* Per-conversation message storage (dual: flash + SD backup) -├── power/ -│ └── PowerManager.* Screen dim/off/wake state machine, brightness control -└── audio/ - └── AudioNotify.* Notification sounds (boot, message, announce, error) -``` - ## Configuration System ### Compile-Time (`Config.h`) -Feature flags (`HAS_LORA`, `HAS_WIFI`, etc.), storage paths, protocol limits, power defaults. Changed only by editing source and recompiling. +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`: - -``` -{ - "loraFrequency": 915000000, - "loraSF": 7, - "loraBW": 500000, - "loraCR": 5, - "loraTxPower": 10, - "wifiMode": 1, // 0=OFF, 1=AP, 2=STA - "wifiAPSSID": "ratputer-XXXX", - "wifiAPPassword": "ratspeak", - "wifiSTASSID": "", - "wifiSTAPassword": "", - "tcpConnections": [{"host": "rns.beleth.net", "port": 4242, "autoConnect": true}], - "screenDimTimeout": 30, - "screenOffTimeout": 60, - "brightness": 255, - "audioEnabled": true, - "audioVolume": 80, - "displayName": "" -} -``` - -**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. +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 @@ -122,109 +44,65 @@ 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 (for future split-packet tracking) +- Upper nibble: random sequence number - Lower nibble: flags (`0x01` = split, not currently implemented) -This matches the RNode on-air format, so Ratputer packets are structurally compatible with RNodes on the same frequency/modulation. +## Screen System (LVGL v8.4) -## Reticulum Integration +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) -### microReticulum Library +### Active Screens -C++ port of the Python Reticulum stack. Provides `Identity`, `Destination`, `Transport`, `Packet`, and `Link` classes. +| 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 | -Key integration points in `ReticulumManager`: -- `RNS::Reticulum::start()` — initialize the stack -- `RNS::Transport::register_interface()` — add LoRa, WiFi, TCP interfaces -- `RNS::Transport::register_announce_handler()` — node discovery callback -- `RNS::Reticulum::loop()` — process incoming/outgoing in main loop +### Theme -### Identity Persistence +Matrix green (#00FF41) on black. Layout: 320x240, status bar 20px top, tab bar 20px bottom, content 200px. -Device identity (Ed25519 keypair) is stored at `/identity/identity.key` in LittleFS with a backup copy on SD at `/ratputer/identity/identity.key`. If flash identity is lost (e.g., LittleFS format), the SD backup is restored automatically. - -### Path Persistence - -Transport paths are serialized to `/transport/paths.msgpack` in LittleFS periodically (every 60 seconds, configurable via `PATH_PERSIST_INTERVAL_MS`). - -## LXMF Protocol - -Wire format for direct LoRa delivery: - -``` -source_hash(16 bytes) + msgpack([timestamp, content, title, fields]) + signature(64 bytes) -``` - -- `source_hash` — 16-byte truncated SHA-256 of sender's public key -- MsgPack array: `[double timestamp, string content, string title, map fields]` -- `signature` — Ed25519 signature over `source_hash + msgpack_content` - -Messages under the MDU (Maximum Data Unit, ~254 bytes for LoRa) are sent as single direct packets. Larger messages would require link-based transfer (not yet implemented). - -Messages are stored as JSON per-conversation in both flash (`/messages//`) and SD (`/ratputer/messages//`). - -## Storage Architecture - -### FlashStore (LittleFS) - -Primary storage for all persistent data. 1.875 MB partition at offset 0x610000. - -**Atomic write pattern**: Write to `.tmp` → verify read-back → rename existing to `.bak` → rename `.tmp` to final path. Prevents corruption on power loss. - -### SDStore (FAT32) - -Secondary/backup storage on microSD card. Shares HSPI bus with LoRa radio. - -Directory structure: -``` -/ratputer/ -├── config/ -│ └── user.json Runtime settings backup -├── messages/ -│ └── / Per-conversation message history -├── contacts/ Discovered node info -└── identity/ - └── identity.key Identity key backup -``` - -### MessageStore (Dual Backend) - -Wraps FlashStore and SDStore to provide unified message access. Writes go to both backends; reads prefer SD (larger capacity), fall back to flash. - -## WiFi State Machine - -Three modes, selected in Settings: - -``` -RAT_WIFI_OFF (0) ──→ No WiFi, saves power + heap -RAT_WIFI_AP (1) ──→ Creates AP "ratputer-XXXX", TCP server on :4242 -RAT_WIFI_STA (2) ──→ Connects to configured network, TCP client connections -``` - -In STA mode, WiFi connection is non-blocking. TCP client interfaces are created on first successful connection and auto-reconnect if WiFi drops. - -Boot loop recovery forces WiFi to OFF if 3 consecutive boots fail. +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/MyScreen.h` and `MyScreen.cpp` -2. Inherit from `Screen` — implement `handleKey()`, `render()`, optionally `update()` -3. In `render()`, use `m5canvas` to draw within the content area (y: 14 to 119) +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: either add to `tabScreens[]` array or navigate to it from a hotkey/callback +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 `HelpOverlay.cpp` +3. Update `docs/HOTKEYS.md` and the help overlay text in `LvHelpOverlay.cpp` -## How To: Add a Settings Submenu +## How To: Add a Settings Category -1. In `SettingsScreen.h`, add an enum value to the menu state -2. In `SettingsScreen.cpp`, add menu item text and handler -3. Add `render*()` and `handleKey*()` methods for the new submenu -4. Use `userConfig->save(sdStore, flash)` to persist changes +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 @@ -232,117 +110,121 @@ Boot loop recovery forces WiFi to OFF if 3 consecutive boots fail. 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 persistent container (e.g., `std::list`) — Transport holds references +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. M5Cardputer.begin() — display, keyboard, battery ADC -2. UI init + boot screen -3. Keyboard init -4. Register hotkeys (Ctrl+H/M/N/S/A/D/T/R) -5. Mount LittleFS (FlashStore) -6. Boot loop detection (NVS counter) -7. Radio init — SX1262 begin, configure modulation, enter RX -8. SD card init (shares HSPI, must be after radio) -9. Serial WIPE window (500ms) -10. Reticulum init — identity load/generate, transport start -11. MessageStore init (dual backend) -12. LXMF init + message callback -13. AnnounceManager init + contact load -14. Register announce handler with Transport -15. Load UserConfig (SD → flash fallback) -16. Boot loop recovery check (force WiFi OFF if triggered) -17. Apply saved radio settings -18. WiFi start (AP, STA, or OFF based on config) -19. BLE skip (disabled) -20. Power manager init + apply saved brightness/timeouts -21. Audio init + apply saved volume -22. Boot complete — switch to Home screen -23. Initial announce broadcast -24. Clear boot loop counter (NVS reset to 0) +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 -Runs at 20 FPS (50ms interval): +Single-threaded on core 1: -1. `M5Cardputer.update()` — poll M5 hardware -2. Keyboard poll → hotkey dispatch → screen key handler → tab cycling -3. `rns.loop()` — Reticulum transport + radio RX processing -4. Auto-announce (every 5 minutes) -5. `lxmf.loop()` — outgoing message queue -6. WiFi STA connection handler + TCP client creation -7. `wifiImpl->loop()` — WiFi transport (AP server accepts, processes clients) -8. TCP client loops — reconnection, frame processing -9. `power.loop()` — dim/off state machine -10. Canvas render (if screen is on) +``` +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 -The ESP32-S3 has 512 KB SRAM. Typical free heap at runtime: +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 | -50 KB | BLE disabled in v1.0 to save this | +| With BLE enabled | ~120 KB | NimBLE stack | -Key consumers: -- microReticulum transport tables: ~20–40 KB (scales with paths/links) -- M5Canvas sprite buffer: 240×135×2 = 64.8 KB (RGB565 double-buffer) -- ArduinoJson documents: ~4 KB per config parse -- SX1262 TX/RX buffers: 255 bytes each -- TCP RX buffer: 600 bytes per connection - -Monitor with `Ctrl+D` → `Free heap` or `ESP.getFreeHeap()` in code. - -## Debugging Tips - -### Serial output - -All subsystems log with `[TAG]` prefixes. Connect at 115200 baud. Key tags: `[BOOT]`, `[RADIO]`, `[LORA_IF]`, `[WIFI]`, `[LXMF]`, `[SD]`. - -### Radio debugging - -- `Ctrl+D` dumps all SX1262 registers — compare sync word, IQ polarity, LNA, and OCP with a known-working device -- `Ctrl+T` sends a test packet and reads back the FIFO — confirms the TX path end-to-end -- `Ctrl+R` samples RSSI for 5 seconds — if readings stay at -110 to -120 dBm while another device transmits, the RX front-end isn't receiving RF -- `DevErrors: 0x0040` = PLL lock failure → check TCXO voltage (must be 3.0V / 0x06) - -### Crash debugging - -ESP-IDF stores a core dump in the `coredump` partition (64 KB at 0x7F0000). To read it: - -```bash -python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* read-flash 0x7F0000 0x10000 coredump.bin -python3 -m esp_coredump info_corefile -t raw -c coredump.bin .pio/build/ratputer_915/firmware.elf -``` - -### Common crash causes - -| Crash | Cause | Fix | -|-------|-------|-----| -| `LoadProhibited` at transport loop | Dangling `Interface&` reference | Store `RNS::Interface` in `std::list` (not vector, not local scope) | -| `Stack overflow` in task | Deep call chain in ISR or recursive render | Increase stack size or reduce nesting | -| `Guru Meditation` on WiFi init | Heap exhaustion | Disable BLE, reduce TCP connections, check for leaks | -| Boot loop (3+ failures) | WiFi or TCP init crash | Boot loop recovery auto-disables WiFi; fix root cause in Settings | +Monitor with `Ctrl+D` -> `Free heap` in serial output. ## Compile-Time Limits -These are defined in `Config.h` and can be adjusted: +Defined in `Config.h`: | Constant | Default | Purpose | |----------|---------|---------| -| `RATPUTER_MAX_NODES` | 50 | Max discovered nodes in AnnounceManager | -| `RATPUTER_MAX_MESSAGES_PER_CONV` | 100 | Max messages stored per conversation | -| `FLASH_MSG_CACHE_LIMIT` | 20 | Keep only N most recent messages per conv in flash (SD has full history) | -| `RATPUTER_MAX_OUTQUEUE` | 20 | Max pending outgoing LXMF messages | +| `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` | 10000 | Retry interval for dropped TCP connections | -| `TCP_CONNECT_TIMEOUT_MS` | 5000 | Timeout for TCP connect() | -| `PATH_PERSIST_INTERVAL_MS` | 60000 | How often transport paths are saved to flash | +| `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 | -| `ANNOUNCE_INTERVAL_MS` | 300000 | Auto-announce period (5 minutes, defined in main.cpp) | + +## 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 index 9d36063..cf1f647 100644 --- a/docs/HOTKEYS.md +++ b/docs/HOTKEYS.md @@ -1,69 +1,92 @@ -# Ratputer — Hotkey Reference +# Ratdeck — Hotkey Reference -All hotkeys use **Ctrl+key** combinations. +## Hotkeys (Ctrl+key) | Shortcut | Action | |----------|--------| -| Ctrl+H | Toggle help overlay | +| Ctrl+H | Toggle help overlay (shows all hotkeys on screen) | | Ctrl+M | Jump to Messages tab | -| Ctrl+N | New message | +| Ctrl+N | Compose new message | | Ctrl+S | Jump to Settings tab | -| Ctrl+A | Force announce to network | -| Ctrl+D | Dump diagnostics to serial | -| Ctrl+T | Send radio test packet (FIFO verification) | +| 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 -These keys match the physical arrow key positions on the Cardputer Adv keyboard: - -| Key | Action | -|-----|--------| -| `;` (semicolon) | Scroll up / previous item | -| `.` (period) | Scroll down / next item | +| 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 | +| Enter | Select / confirm / send message | | Esc | Back / cancel | +| Backspace | Delete character / go back (if input empty) | ## Text Input -When a text input field is active: +When a text field is active (message compose, WiFi password, etc.): - Type normally to enter characters - **Backspace** to delete -- **Enter** to submit -- **Esc** to cancel -- Double-tap **Aa** for caps lock +- **Enter** to submit / send +- **Esc** to cancel and go back ## Tabs -| Tab | Contents | -|-----|----------| -| Home | Identity, transport status, radio info, uptime | -| Msgs | Conversation list with unread badges | -| Nodes | Discovered Reticulum nodes | -| Setup | Settings, about, factory reset | +| 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, transport status, path/link counts +- 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, flash usage, uptime +- Free heap, PSRAM, flash usage, uptime -**Ctrl+T** sends a test packet with header `0xA0` and payload `RATPUTER_TEST_1234567890`, then reads back the FIFO buffer to verify the TX path. +**Ctrl+T** sends a test packet and verifies FIFO readback. -**Ctrl+R** samples RSSI continuously for 5 seconds, printing each reading. Transmit from another device during sampling to verify the RX front-end is hearing RF. - -## Settings Submenus (Ctrl+S) - -| Menu Item | Contents | -|-----------|----------| -| Radio | Frequency (Hz), spreading factor (5–12), bandwidth (7.8k–500k), coding rate (4/5–4/8), TX power (2–22 dBm) | -| WiFi | Mode (OFF/AP/STA), AP SSID + password, STA SSID + password, TCP endpoints list | -| Display | Brightness (0–255), dim timeout (seconds), off timeout (seconds) | -| Audio | Enable/disable notifications, volume (0–100%) | -| About | Firmware version, Reticulum identity hash, uptime, free heap, flash usage | -| Factory Reset | Clears config from flash + SD, reboots with defaults (identity and messages preserved) | +**Ctrl+R** samples RSSI continuously for 5 seconds. diff --git a/docs/PINMAP.md b/docs/PINMAP.md index dcaf145..57d2ecd 100644 --- a/docs/PINMAP.md +++ b/docs/PINMAP.md @@ -1,100 +1,137 @@ -# Ratputer — Hardware Pin Map +# Ratdeck — Hardware Pin Map -M5Stack Cardputer Adv (ESP32-S3) + Cap LoRa-1262 - -All pin definitions are in `src/config/BoardConfig.h`. +LilyGo T-Deck Plus (ESP32-S3) — all pin definitions in `src/config/BoardConfig.h`. ## Bus Overview ``` ESP32-S3 - ├── HSPI (shared bus) ──┬── SX1262 LoRa (CS=5) - │ SCK=40 └── SD Card (CS=12) - │ MISO=39 - │ MOSI=14 + ├── 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 ── TCA8418 Keyboard (SDA=8, SCL=9, INT=11) + ├── I2C (SDA=18, SCL=8) ────┬── Keyboard ESP32-C3 (0x55) + │ └── GT911 Touch (0x5D) │ - ├── UART (reserved) ── GNSS module (RX=15, TX=13) + ├── GPIO ── Trackball (UP=3, DOWN=2, LEFT=1, RIGHT=15, CLICK=0) │ - ├── USB-Serial/JTAG ── USB-C port (firmware + debug) + ├── UART ── UBlox MIA-M10Q GPS (TX=43, RX=44) │ - └── M5Unified managed ──┬── ST7789V2 Display (SPI) - ├── ES8311 Audio Codec (I2S) - └── Battery ADC + ├── I2S ── ES7210 Audio Codec + │ + └── USB-C ── USB-Serial/JTAG ``` -## SX1262 LoRa Radio - -Uses **HSPI** (custom SPI bus, not the default VSPI): +## Power Control | Signal | GPIO | Notes | |--------|------|-------| -| SCK | 40 | SPI clock | -| MISO | 39 | SPI data out (radio → ESP) | -| MOSI | 14 | SPI data in (ESP → radio) | -| CS | 5 | Chip select (active low) | -| IRQ | 4 | DIO1 interrupt (falling edge) | -| RST | 3 | Reset (active low, 100μs pulse) | -| BUSY | 6 | Poll before SPI transactions | +| 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: 3.0V (`MODE_TCXO_3_0V_6X` = 0x06) +- 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 | 12 | Separate from LoRa CS (5) | +| CS | 39 | Shared SPI bus | -Shares HSPI bus with LoRa radio (SCK=40, MISO=39, MOSI=14). Only one device active at a time — SD must be initialized **after** radio. +FAT32, shares SPI2_HOST with display and radio. -## Keyboard (TCA8418) +## GPS (UBlox MIA-M10Q) | Signal | GPIO | Notes | |--------|------|-------| -| SDA | 8 | I2C data | -| SCL | 9 | I2C clock | -| INT | 11 | Active low, falling edge | +| TX | 43 | ESP TX → GPS RX | +| RX | 44 | GPS TX → ESP RX | -Managed by the M5Cardputer library. The TCA8418 is a dedicated keyboard controller IC with built-in key matrix scanning and FIFO. - -## GNSS (Reserved — v1.1) - -| Signal | GPIO | Notes | -|--------|------|-------| -| RX | 15 | GPS TX → ESP RX | -| TX | 13 | ESP TX → GPS RX | - -UART at 115200 baud. Pins defined but no code path yet. - -## Display - -**ST7789V2** — 240×135 pixels, RGB565, SPI interface. - -Fully managed by M5Unified. No GPIO definitions needed in firmware — the M5Unified library auto-configures display pins based on board detection. - -## Audio - -**ES8311** codec + **NS4150B** amplifier. - -Fully managed by M5Unified. No GPIO definitions needed. +UART at 115200 baud. Pins defined, not yet implemented. ## Battery -ADC via M5Unified. 1750mAh LiPo, TP4057 charger IC. +| Signal | GPIO | Notes | +|--------|------|-------| +| ADC | 4 | Battery voltage measurement | -## SPI Bus Sharing +## Audio (ES7210 I2S) -The HSPI bus is shared between the SX1262 radio (CS=5) and the SD card (CS=12). Key constraints: - -1. **Initialize radio first** — SD card init must come after `radio.begin()` since the SPI bus is configured during radio init -2. **One active at a time** — pull CS high on the inactive device before talking to the other -3. **Radio has priority** — if a packet arrives during SD access, there may be a brief delay before the ISR fires +| Signal | GPIO | +|--------|------| +| WS (LRCK) | 5 | +| DOUT | 6 | +| BCK | 7 | +| DIN | 14 | +| SCK | 47 | +| MCLK | 48 | ## Hardware Constants @@ -102,6 +139,6 @@ The HSPI bus is shared between the SX1262 radio (CS=5) and the SD card (CS=12). |----------|-------|-------| | `MAX_PACKET_SIZE` | 255 | SX1262 maximum single packet | | `SPI_FREQUENCY` | 8 MHz | SPI clock for SX1262 | -| `DISPLAY_WIDTH` | 240 | Pixels | -| `DISPLAY_HEIGHT` | 135 | Pixels | -| `GPS_BAUD` | 115200 | GNSS UART speed | +| `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 index 23af631..5b0b7c5 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,10 +1,10 @@ -# Ratputer — Quick Start +# Ratdeck — Quick Start ## Hardware Required -- M5Stack Cardputer Adv (ESP32-S3, 8MB flash) -- Cap LoRa-1262 module (SX1262, 915 MHz) -- microSD card (optional, recommended) +- **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 @@ -13,140 +13,149 @@ pip install platformio # Clone and build -git clone https://github.com/defidude/Ratputer.git -cd Ratputer -python3 -m platformio run -e ratputer_915 +git clone https://github.com/defidude/Ratdeck.git +cd Ratdeck +python3 -m platformio run -# Flash to device (use glob to match USB port) -python3 -m platformio run -e ratputer_915 -t upload --upload-port /dev/cu.usbmodem* +# Flash +python3 -m platformio run --target upload ``` -> **Note**: If `pio` is not on your PATH, use `python3 -m platformio` instead. See [BUILDING.md](BUILDING.md) for esptool flashing and merged binary instructions. +> 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/flash](https://ratspeak.org/flash) to flash directly from your browser using WebSerial. ### USB Port -The Cardputer Adv uses USB-Serial/JTAG — the port appears as `/dev/cu.usbmodem` in firmware mode. Use the `*` glob to match it. +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 Cardputer Adv +1. Power on the T-Deck Plus 2. Boot animation plays with progress bar -3. Radio initializes at 915 MHz -4. SD card checked (auto-creates `/ratputer/` directories) -5. Reticulum transport node starts, identity generated -6. WiFi AP starts: `ratputer-XXXX` (password: `ratspeak`) -7. Home screen shows identity hash and status +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 -Keys match the physical arrow positions on the Cardputer Adv keyboard: +The T-Deck Plus has a QWERTY keyboard, trackball, and touchscreen (touch currently disabled): -- **`;`** / **`.`**: Scroll up/down in lists -- **`,`** / **`/`**: Cycle between tabs (left/right) -- **Enter**: Select/confirm -- **Esc**: Back/cancel -- **Ctrl+key**: Hotkeys (press Ctrl+H for help) +| 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 | -## Sending a Message +### Tabs -1. Wait for another node to appear in the **Nodes** tab -2. Press **Ctrl+M** to go to Messages -3. Select a conversation or use **Ctrl+N** for new -4. Type your message and press **Enter** +| 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 250 kHz, CR 4/5 (Balanced preset). ## WiFi Setup -Default: AP mode with SSID `ratputer-XXXX`. +Default WiFi mode is **STA** (client). Change in Settings -> Network. -### AP Mode (default) +### STA Mode (Connect to Your WiFi) -Connect a laptop to the `ratputer-XXXX` WiFi network, then configure `rnsd` with a TCPClientInterface pointing at `192.168.4.1:4242`. - -### STA Mode - -To connect Ratputer to your WiFi network: - -1. Press **Ctrl+S** → WiFi → Mode → STA +1. Go to Setup -> Network 2. Enter your WiFi SSID and password -3. Save — device reconnects in STA mode -4. Add TCP endpoints (e.g., `rns.beleth.net:4242`) in WiFi → TCP Connections +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 WiFi settings to disable WiFi entirely (saves power and ~20KB heap). +Select OFF in Settings -> Network to disable WiFi entirely (saves power). ## SD Card -Insert a microSD card before powering on. The firmware auto-creates: +Insert a microSD card (FAT32) before powering on. The firmware auto-creates: ``` -/ratputer/config/ Settings backup -/ratputer/messages/ Message archives -/ratputer/contacts/ Discovered nodes -/ratputer/identity/ Identity key backup +/ratputer/ + config/ Settings backup + messages/ Message archives (up to 100 per conversation) + contacts/ Saved friends + identity/ Identity key backup + transport/ Routing table backup ``` -To wipe SD data: connect serial at 115200 baud, send `WIPE` within 500ms of boot. +## Managing Friends -## Serial Monitor +- **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 | 250 kHz | 4/5 | 14 dBm | General use | +| Long Range | 12 | 125 kHz | 4/8 | 17 dBm | Maximum range, slow | +| Fast | 7 | 500 kHz | 4/5 | 10 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 ``` -Useful serial hotkeys: -- **Ctrl+D** on device: dump full diagnostics +- **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 -## Settings +## Transport Mode -Press **Ctrl+S** to access settings: -- **Radio**: frequency, spreading factor, bandwidth, coding rate, TX power -- **WiFi**: mode (OFF/AP/STA), AP SSID + password, STA SSID + password, TCP endpoints -- **Display**: brightness (0–255), dim timeout, off timeout -- **Audio**: enable/disable, volume (0–100) -- **About**: version, identity hash, uptime, factory reset - -Changes take effect immediately and persist to both flash and SD. - -## Connecting Two Ratputers - -Two Ratputers on the same LoRa settings will discover each other automatically: - -1. Power on both devices -2. Wait ~30 seconds for announces to propagate -3. Check the **Nodes** tab — the other device should appear with its identity hash, RSSI, and SNR -4. Select the node → opens a conversation in Messages -5. Type a message and press Enter - -Both devices must use the same frequency, spreading factor, bandwidth, and coding rate. The defaults (915 MHz, SF7, BW 500kHz, CR 4/5) work out of the box. - -## Connecting to a Desktop Reticulum Instance - -### Option A: AP Mode Bridge (Ratputer as hotspot) - -1. Leave Ratputer in AP mode (default) -2. On your laptop, connect to `ratputer-XXXX` (password: `ratspeak`) -3. Add to `~/.reticulum/config`: - ```ini - [[ratputer]] - type = TCPClientInterface - target_host = 192.168.4.1 - target_port = 4242 - ``` -4. Restart `rnsd` — your desktop is now on the LoRa mesh - -### Option B: STA Mode (Ratputer joins your WiFi) - -1. Switch Ratputer to STA mode (Ctrl+S → WiFi → Mode → STA) -2. Enter your WiFi SSID and password -3. On your laptop (same network), configure Ratputer as a TCP server interface in `~/.reticulum/config`: - ```ini - [[ratputer]] - type = TCPServerInterface - listen_ip = 0.0.0.0 - listen_port = 4242 - ``` -4. On Ratputer, add a TCP endpoint pointing to your laptop's IP and port 4242 -5. Both sides can now exchange Reticulum traffic over WiFi +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/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 97fd520..3515f09 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -1,40 +1,44 @@ -# Ratputer — Troubleshooting +# Ratdeck — Troubleshooting -Collected hardware and software gotchas, organized by category. +Collected hardware and software gotchas for the LilyGo T-Deck Plus with SX1262 LoRa. --- ## Radio Issues -### TCXO voltage must be 3.0V +### TCXO voltage must be 1.8V -The Cap LoRa-1262 uses a TCXO (temperature-compensated crystal oscillator) that requires exactly 3.0V. This is configured as `MODE_TCXO_3_0V_6X` (0x06) in `BoardConfig.h`. +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**: Check `Ctrl+D` diagnostics — if `DevErrors` shows `0x0040`, that's PLL lock failure. +**Diagnosis**: `Ctrl+D` diagnostics — if `DevErrors` shows `0x0040`, that's PLL lock failure. -**Fix**: Verify `LORA_TCXO_VOLTAGE` is `0x06` in `BoardConfig.h`. +**Fix**: Verify `LORA_TCXO_VOLTAGE` is `0x02` in `BoardConfig.h`. -### IRQ stale latch fix +### SetModulationParams must be called from STDBY mode -The SX1262's IRQ flags can become latched from previous operations. If stale flags persist, DCD (detect channel activity) gets stuck in "channel busy" state, CSMA blocks, and TX never fires. +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**: First packet sends fine, then all subsequent TX attempts hang. DCD reports channel busy even with nothing transmitting. +**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** (applied in `SX1262.cpp`): -- 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 +**Fix**: `setModulationParams()` now calls `standby()` internally before issuing the SPI command. -### `_txp` is a base class member +### Calibration must run after TCXO is enabled -The `_txp` (TX power) field is declared in `RadioInterface` base class (inherited from RNode firmware lineage). It cannot be initialized in the `sx126x` constructor initializer list — must set `_txp = 14` in the constructor body. +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 power reads as 0, which may cause silent failures or very weak transmission. +**Symptom**: TX completes successfully on both devices, RSSI shows real signals, but neither device ever decodes the other's packets. -### PA ramp time +**Fix**: Init order must be: `enableTCXO()` -> `delay(5ms)` -> `setRegulatorMode(DC-DC)` -> `loraMode()` -> `standby(XOSC)` -> `calibrate()` -> `calibrate_image()` -Use 40μs PA ramp time for the Cap LoRa-1262. This is set during `setTxParams()` in the SX1262 driver. Faster ramp times may cause spectral splatter; slower wastes time. +### 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. --- @@ -42,45 +46,32 @@ Use 40μs PA ramp time for the Cap LoRa-1262. This is set during `setTxParams()` ### PlatformIO not on PATH -After `pip install platformio`, the `pio` binary may not be in your shell's PATH. - **Fix**: Use `python3 -m platformio` instead of `pio`: ```bash -python3 -m platformio run -e ratputer_915 -python3 -m platformio run -e ratputer_915 -t upload +python3 -m platformio run +python3 -m platformio run --target upload python3 -m platformio device monitor -b 115200 ``` -### esptool baud rate +### Flash fails or disconnects mid-upload -PlatformIO defaults to 921600 baud for flashing, which sometimes fails with USB-Serial/JTAG. +The T-Deck Plus USB-Serial/JTAG can be sensitive to baud rates. -**Fix**: Use 460800 baud with esptool directly: +**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/ratputer_915/firmware.bin + write-flash -z 0x10000 .pio/build/ratdeck_915/firmware.bin ``` -### esptool hyphenated flags - -esptool deprecated underscored command names. Use hyphens: - -| Correct | Deprecated | -|---------|-----------| -| `merge-bin` | `merge_bin` | -| `write-flash` | `write_flash` | -| `--flash-mode` | `--flash_mode` | -| `--flash-size` | `--flash_size` | - ### 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 native USB CDC peripheral doesn't enumerate on macOS in firmware mode. The port never appears. +**Symptom**: With `hwcdc` (USB_MODE=0), the port never appears on macOS. -**Fix**: Keep `ARDUINO_USB_MODE=1` in `platformio.ini`. The port appears as `/dev/cu.usbmodem` (not `usbmodem5101`, which is bootloader-only). +**Fix**: Keep `ARDUINO_USB_MODE=1` in `platformio.ini`. --- @@ -88,24 +79,28 @@ The build flag `ARDUINO_USB_MODE=1` selects USB-Serial/JTAG mode (not native CDC ### Boot loop detection and recovery -Ratputer tracks consecutive boot failures in NVS (non-volatile storage, separate from LittleFS). If 3 consecutive boots fail to reach the end of `setup()`, WiFi is forced OFF on the next boot. +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`, `bootLoopRecovery = true` → WiFi forced to `RAT_WIFI_OFF` -3. At the end of successful `setup()`, counter resets to 0 +2. If `bootc >= 3`, WiFi forced to OFF +3. At end of successful `setup()`, counter resets to 0 -**Manual recovery**: If the device is boot-looping, connect serial at 115200 baud and watch for the `[BOOT] Boot loop detected` message. The device should stabilize with WiFi off, then you can change WiFi settings. +**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. -### Root cause: Transport reference stability +### Transport reference crash -The original boot loop was caused by `RNS::Transport::_interfaces` storing `Interface&` (references, NOT copies). A local `RNS::Interface iface(tcp)` declared inside a loop or function would go out of scope, creating a dangling reference. When Reticulum's transport loop tried to access it: `LoadProhibited` 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 tcpIfaces` at global scope. Must use `std::list`, not `std::vector` — vector reallocation would move objects in memory, invalidating references held by Transport. +**Fix**: Store TCP Interface wrappers in `std::list` at global scope. Must use `std::list`, not `std::vector` — vector reallocation invalidates references. -### "auto detect board:24" in serial output +### GPIO 10 must be HIGH -This is the M5Unified library auto-detecting the board type. It's informational, not an error. The number 24 is M5's internal board ID for Cardputer Adv. +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. --- @@ -113,67 +108,27 @@ This is the M5Unified library auto-detecting the board type. It's informational, ### LittleFS not mounting -**Symptom**: `[E][vfs_api.cpp:24] open(): File system is not mounted` during boot. +**Symptom**: `[E][vfs_api.cpp:24] open(): File system is not mounted` -**Possible causes**: -- First boot after flash erase — LittleFS partition needs formatting -- Partition table mismatch — verify `partitions_8MB_ota.csv` matches flash layout -- `flash.begin()` may fail silently +**Causes**: First boot after flash erase, or partition table mismatch. -**Workaround**: FlashStore attempts `LittleFS.begin(true)` which auto-formats on first use. If it persists, erase the LittleFS partition and reflash. +**Fix**: FlashStore calls `LittleFS.begin(true)` which auto-formats on first use. If it persists, erase flash and reflash: -### SD card directories missing - -On first boot with a new SD card, the `/ratputer/` directory tree doesn't exist. - -**Fix**: `setup()` calls `sdStore.ensureDir()` for all required paths after SD init. If directories are still missing, check that SD CS (GPIO 12) is not conflicting with radio SPI. - ---- - -## Interop & RF Issues - -### Ratputer TX/RX verification (QA Round 9) - -- **Ratputer RX confirmed**: Received Heltec V3 RNode announce at -38 dBm, SNR 13.0 -- **Ratputer TX confirmed**: All SX1262 registers verified correct (SF7, BW 500kHz, CR 4/5, sync 0x1424, CRC on) -- **Heltec V3 RNode receive path**: Has never decoded a single LoRa packet from any source. Shows Ratputer RF as interference (-50 to -81 dBm) but can't decode. This is a Heltec issue, not Ratputer. - -### SetModulationParams must be called from STDBY mode - -The SX1262 silently rejects `SetModulationParams` (0x8B) when issued from RX or TX mode. Only STDBY_RC and STDBY_XOSC are valid. The command appears to succeed (no error, BUSY goes low), but the hardware ignores the new SF/BW/CR values. - -**Symptom**: Radio logs show correct SF/BW/CR (read from software variables), but actual TX airtime is wrong. For example, software says SF9 (expected ~900ms for 168 bytes) but actual TX completes in ~280ms (SF7). Both devices see each other's RF (RSSI visible) but every packet fails CRC. SNR is consistently -17 to -20 dB despite short range. - -**Diagnosis**: Add timing instrumentation to `endPacket()` — measure `millis()` from `OP_TX_6X` to `TX_DONE` IRQ. Compare against expected airtime for the configured SF. If actual << expected, the SF didn't apply. - -**Root cause**: `setSpreadingFactor()` / `setSignalBandwidth()` / `setCodingRate4()` were called from `main.cpp` after `begin()` had already entered RX mode via `receive()`. The SX1262 silently dropped the `SetModulationParams` command. - -**Fix**: `setModulationParams()` now calls `standby()` before issuing the SPI command, ensuring the radio is in a valid mode to accept configuration changes. - -### SX1262 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()` or `calibrate_image()`. Calibration locks to whichever oscillator is active. If TCXO isn't enabled yet, calibration uses the internal RC oscillator (~13MHz, ±3% tolerance). Each chip's RC has a different offset, so two devices end up synthesizing slightly different actual frequencies. The combined error can exceed the LoRa demodulation window. - -**Symptom**: TX completes successfully on both devices (TX_DONE fires, no errors). RSSI shows real signals (not noise floor). But neither device ever decodes the other's packets — no RX_DONE, no CRC errors, just silence. Each device works fine individually (e.g., over TCP/LXMF). - -**Diagnosis**: If two TCXO-equipped SX1262 devices can't hear each other despite confirmed TX and visible RSSI, suspect calibration ordering. - -**Fix**: In `SX1262::begin()`, the init order must be: -``` -enableTCXO() → delay(5ms) → setRegulatorMode(DC-DC) → loraMode() → standby() → calibrate() → calibrate_image() +```bash +python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* erase-flash ``` -Also set: -- **Regulator mode** to DC-DC (0x01) — default is LDO, wastes power on boards with DC-DC inductors -- **RX/TX fallback mode** to STDBY_XOSC (0x30) — default STDBY_RC (0x20) shuts off TCXO between TX/RX transitions +### SD card not detected -### Debugging RF with RSSI Monitor +**Check**: SD card must be FAT32. The SD card shares the SPI bus with the radio and display (CS=GPIO 39). -Press **Ctrl+R** to sample RSSI continuously for 5 seconds. Transmit from another device during sampling. If RSSI stays at the noise floor (around -110 to -120 dBm), the RX front-end isn't hearing RF. +**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. -### Radio test packet +### Messages not persisting -Press **Ctrl+T** to send a test packet with a fixed header (0xA0) and payload `RATPUTER_TEST_1234567890`. Includes FIFO readback verification. Use this to confirm the TX path is working without involving Reticulum. +**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. --- @@ -181,17 +136,94 @@ Press **Ctrl+T** to send a test packet with a fixed header (0xA0) and payload `R ### AP and STA are separate modes -Ratputer uses **three WiFi modes**: OFF, AP, STA. These are NOT concurrent — `WIFI_AP_STA` was removed because it consumed ~20KB extra heap and caused instability. +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 `ratputer-XXXX` hotspot, runs TCP server on port 4242 -- **STA mode**: Connects to an existing network, creates TCP client connections to configured endpoints -- **OFF**: No WiFi (saves power and heap) +- **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 Settings → WiFi. +Switch modes in Setup -> Network. WiFi mode changes require reboot. -### TCP client connection lifecycle +### TCP client won't connect -In STA mode, TCP client connections to configured endpoints are created **once** on first WiFi connection. If WiFi drops and reconnects, existing TCP clients attempt reconnection automatically (every 10 seconds). +**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 | --- @@ -199,71 +231,7 @@ In STA mode, TCP client connections to configured endpoints are created **once** | Feature | Status | Notes | |---------|--------|-------| -| BLE | Disabled (stub) | Saves ~50KB heap. Planned for v1.1 Sideband protocol | -| GNSS | Pins defined, no code | v1.1 — GPS RX=15, TX=13 | -| OTA updates | Partition exists, not implemented | `app1` partition at 0x310000 is reserved | -| LittleFS | Occasional mount failures | Auto-formats on first use; rare failures after that | -| WiFi AP+STA | Removed | Uses too much heap; separate AP/STA modes instead | -| Split packets | Header flag defined, not implemented | Single-frame LoRa only (max 254 bytes payload) | - ---- - -## Factory Reset - -### SD card wipe (preserves flash data) - -Connect serial at 115200 baud. Power cycle the device and send `WIPE` within 500ms of boot. This recursively deletes `/ratputer/*` on the SD card and recreates clean directories. - -### Flash erase (full reset) - -Erase all flash contents including LittleFS, NVS, and firmware: - -```bash -python3 -m esptool --chip esp32s3 --port /dev/cu.usbmodem* erase-flash -``` - -Then reflash the firmware. A new Reticulum identity will be generated on first boot. All settings, messages, and contacts stored in flash are lost. SD card data is preserved. - -### Settings-only reset - -In Settings → About → Factory Reset: clears the user config JSON from flash and SD, then reboots. Radio, WiFi, display, and audio revert to compile-time defaults. Identity and messages are preserved. - ---- - -## Diagnostic Reference - -### Serial log tags - -Every subsystem logs with a tag prefix for easy filtering: - -| 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 | -| `[HOTKEY]` | Keyboard hotkey dispatch | -| `[TEST]` | Radio test packet (Ctrl+T) | -| `[RSSI]` | RSSI monitor (Ctrl+R) | -| `[AUTO]` | Periodic auto-announce | -| `[BLE]` | BLE stub status | - -### Ctrl+D diagnostic fields - -| Field | Meaning | -|-------|---------| -| Identity | 16-byte Reticulum destination hash (hex) | -| Transport | ACTIVE or OFFLINE | -| Paths / Links | Number of known Reticulum paths and active links | -| Freq / SF / BW / CR / TXP | Current radio parameters | -| Preamble | Preamble length in symbols | -| SyncWord regs | Raw 0x0740/0x0741 register values (should be 0x14/0x24 for Reticulum) | -| DevErrors | SX1262 error register (0x0040 = PLL lock failure) | -| Status | SX1262 chip mode and command status | -| Current RSSI | Instantaneous RSSI in dBm (noise floor ~-110 to -120 dBm) | -| Free heap | Available RAM in bytes (typical: ~120–150 KB) | -| Flash | LittleFS used/total bytes | -| Uptime | Seconds since boot | +| 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) |