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