mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-03-29 05:39:52 +00:00
updating docs to reflect v1.4.2
This commit is contained in:
348
README.md
348
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/
|
||||
<peer_hex>/ 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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
148
docs/BUILDING.md
148
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<ID>` | 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<chipID>` | `/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 |
|
||||
|
||||
@@ -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/<peer_hex>/`) and SD (`/ratputer/messages/<peer_hex>/`).
|
||||
|
||||
## 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/
|
||||
│ └── <peer_hex>/ 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 |
|
||||
|
||||
101
docs/HOTKEYS.md
101
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.
|
||||
|
||||
163
docs/PINMAP.md
163
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 |
|
||||
|
||||
@@ -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<chipID>` 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.
|
||||
|
||||
@@ -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<chipID>` (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<RNS::Interface> 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<RNS::Interface>` 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) |
|
||||
|
||||
Reference in New Issue
Block a user