diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6250101..3af2c77 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,4 +1,4 @@
-name: PlatformIO Build
+name: Firmware Build
on:
push:
@@ -11,55 +11,6 @@ env:
ARDUINO_CLI_VERSION: 1.4.1
jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v6
-
- - name: Set up Python
- uses: actions/setup-python@v6
- with:
- python-version: '3.12'
-
- - name: Install PlatformIO
- run: pip install platformio esptool
-
- - name: Build firmware
- run: pio run -e ratdeck_915
-
- - name: Merge single .bin for M5Burner
- run: |
- esptool.py --chip esp32s3 merge-bin \
- --flash-mode dio --flash-size 16MB \
- -o ratdeck-merged.bin \
- 0x0000 .pio/build/ratdeck_915/bootloader.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
-
- - name: Package firmware ZIP
- run: |
- mkdir -p ratdeck-firmware
- cp .pio/build/ratdeck_915/bootloader.bin ratdeck-firmware/
- cp .pio/build/ratdeck_915/partitions.bin ratdeck-firmware/
- cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin ratdeck-firmware/
- cp .pio/build/ratdeck_915/firmware.bin ratdeck-firmware/
- cat > ratdeck-firmware/manifest.json << 'MANIFEST'
- {
- "chipFamily": "ESP32-S3",
- "flashSize": "16MB",
- "flashMode": "dio",
- "flashFreq": "80m",
- "parts": [
- { "path": "bootloader.bin", "offset": "0x0000" },
- { "path": "partitions.bin", "offset": "0x8000" },
- { "path": "boot_app0.bin", "offset": "0xe000" },
- { "path": "firmware.bin", "offset": "0x10000" }
- ]
- }
- MANIFEST
- cd ratdeck-firmware && zip -r ../ratdeck-firmware.zip . && cd ..
-
package:
runs-on: ubuntu-latest
steps:
@@ -81,9 +32,9 @@ jobs:
run: pip install platformio esptool
- name: Prepare RNode Arduino dependencies
- run: make prep-tdeck
+ run: make -C vendor/rnode_firmware prep-esp32
- - name: Build dual-boot package
+ - name: Build release package
run: make package
- name: Upload firmware artifacts
@@ -93,7 +44,7 @@ jobs:
path: dist/*
release:
- needs: [build, package]
+ needs: package
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
@@ -106,45 +57,6 @@ jobs:
with:
python-version: '3.12'
- - name: Install PlatformIO
- run: pip install platformio esptool
-
- - name: Build firmware
- run: pio run -e ratdeck_915
-
- - name: Merge single .bin for M5Burner
- run: |
- esptool.py --chip esp32s3 merge-bin \
- --flash-mode dio --flash-size 16MB \
- -o ratdeck-merged.bin \
- 0x0000 .pio/build/ratdeck_915/bootloader.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
-
- - name: Package firmware ZIP
- run: |
- mkdir -p ratdeck-firmware
- cp .pio/build/ratdeck_915/bootloader.bin ratdeck-firmware/
- cp .pio/build/ratdeck_915/partitions.bin ratdeck-firmware/
- cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin ratdeck-firmware/
- cp .pio/build/ratdeck_915/firmware.bin ratdeck-firmware/
- cat > ratdeck-firmware/manifest.json << 'MANIFEST'
- {
- "chipFamily": "ESP32-S3",
- "flashSize": "16MB",
- "flashMode": "dio",
- "flashFreq": "80m",
- "parts": [
- { "path": "bootloader.bin", "offset": "0x0000" },
- { "path": "partitions.bin", "offset": "0x8000" },
- { "path": "boot_app0.bin", "offset": "0xe000" },
- { "path": "firmware.bin", "offset": "0x10000" }
- ]
- }
- MANIFEST
- cd ratdeck-firmware && zip -r ../ratdeck-firmware.zip . && cd ..
-
- name: Install Arduino CLI
run: |
mkdir -p "$RUNNER_TEMP/arduino-cli"
@@ -152,24 +64,36 @@ jobs:
| BINDIR="$RUNNER_TEMP/arduino-cli" sh -s "$ARDUINO_CLI_VERSION"
echo "$RUNNER_TEMP/arduino-cli" >> "$GITHUB_PATH"
- - name: Prepare RNode Arduino dependencies
- run: make prep-tdeck
+ - name: Install Python tools
+ run: pip install platformio esptool
- - name: Build dual-boot package
+ - name: Prepare RNode Arduino dependencies
+ run: make -C vendor/rnode_firmware prep-esp32
+
+ - name: Build release package
run: make package
- name: Create Release
env:
GH_TOKEN: ${{ github.token }}
+ GITHUB_TOKEN: ${{ github.token }}
+ GH_REPO: ${{ github.repository }}
run: |
release_args=(--generate-notes)
if [[ "$GITHUB_REF_NAME" == *-* ]]; then
release_args+=(--prerelease)
fi
- assets=(ratdeck-firmware.zip ratdeck-merged.bin dist/*)
- if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
- gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber
+ artifacts=(
+ dist/rsdeck-full.zip
+ dist/rsdeck-standalone.zip
+ dist/rsdeck-rnode.zip
+ dist/rsdeck-standalone-m5launcher.bin
+ dist/rsdeck-rnode-m5launcher.bin
+ )
+
+ if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
+ gh release upload "$GITHUB_REF_NAME" "${artifacts[@]}" --clobber --repo "$GITHUB_REPOSITORY"
else
- gh release create "$GITHUB_REF_NAME" "${assets[@]}" "${release_args[@]}"
+ gh release create "$GITHUB_REF_NAME" "${artifacts[@]}" "${release_args[@]}" --repo "$GITHUB_REPOSITORY"
fi
diff --git a/Makefile b/Makefile
index 50cb139..0ec19f2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-STANDALONE_ENV ?= ratdeck_915
+STANDALONE_ENV ?= rsdeck_915
RNODE_DIR ?= vendor/rnode_firmware
LAUNCHER_DIR ?= launcher
BUILD_DIR ?= build
@@ -10,8 +10,8 @@ PARTITIONS_BIN := $(BUILD_DIR)/rsdeck_16mb_dual_partitions.bin
FULL_NAME := rsdeck-full
STANDALONE_NAME := rsdeck-standalone
RNODE_ONLY_NAME := rsdeck-rnode
-APP_STANDALONE_NAME := rsdeck-standalone-app
-APP_RNODE_NAME := rsdeck-rnode-app
+M5LAUNCHER_STANDALONE_NAME := rsdeck-standalone-m5launcher
+M5LAUNCHER_RNODE_NAME := rsdeck-rnode-m5launcher
FULL_BIN := $(BUILD_DIR)/$(FULL_NAME).bin
STANDALONE_BIN := $(BUILD_DIR)/$(STANDALONE_NAME).bin
@@ -19,7 +19,7 @@ RNODE_ONLY_BIN := $(BUILD_DIR)/$(RNODE_ONLY_NAME).bin
LAUNCHER_BIN := $(LAUNCHER_DIR)/.pio/build/tdeck_launcher/firmware.bin
STANDALONE_APP_BIN := .pio/build/$(STANDALONE_ENV)/firmware.bin
-STANDALONE_FACTORY_BIN := ratdeck-merged.bin
+STANDALONE_FACTORY_BIN := rsdeck-merged.bin
RNODE_OUTPUT := $(RNODE_DIR)/build/tdeck.esp32.esp32s3
RNODE_BIN := $(RNODE_OUTPUT)/RNode_Firmware.ino.bin
@@ -90,11 +90,18 @@ bundle: full-image
package: full-image standalone-image rnode-only-image
mkdir -p $(DIST_DIR)
+ rm -f $(DIST_DIR)/$(FULL_NAME).zip \
+ $(DIST_DIR)/$(STANDALONE_NAME).zip \
+ $(DIST_DIR)/$(RNODE_ONLY_NAME).zip \
+ $(DIST_DIR)/$(M5LAUNCHER_STANDALONE_NAME).bin \
+ $(DIST_DIR)/$(M5LAUNCHER_RNODE_NAME).bin \
+ $(DIST_DIR)/rsdeck-standalone-app.bin \
+ $(DIST_DIR)/rsdeck-rnode-app.bin
python3 tools/package_merged_zip.py --image $(FULL_BIN) --name $(FULL_NAME) --output $(DIST_DIR)/$(FULL_NAME).zip
python3 tools/package_merged_zip.py --image $(STANDALONE_BIN) --name $(STANDALONE_NAME) --output $(DIST_DIR)/$(STANDALONE_NAME).zip
python3 tools/package_merged_zip.py --image $(RNODE_ONLY_BIN) --name $(RNODE_ONLY_NAME) --output $(DIST_DIR)/$(RNODE_ONLY_NAME).zip
- cp $(STANDALONE_APP_BIN) $(DIST_DIR)/$(APP_STANDALONE_NAME).bin
- cp $(RNODE_BIN) $(DIST_DIR)/$(APP_RNODE_NAME).bin
+ cp $(STANDALONE_APP_BIN) $(DIST_DIR)/$(M5LAUNCHER_STANDALONE_NAME).bin
+ cp $(RNODE_BIN) $(DIST_DIR)/$(M5LAUNCHER_RNODE_NAME).bin
release: package
@@ -102,4 +109,4 @@ flash: bundle
python3 -m esptool --chip esp32s3 --port $(PORT) --baud 460800 --before default_reset --after hard_reset write-flash 0x0 $(FULL_BIN)
clean:
- rm -rf $(BUILD_DIR) $(DIST_DIR)
+ rm -rf $(BUILD_DIR) $(DIST_DIR) $(STANDALONE_FACTORY_BIN)
diff --git a/README.md b/README.md
index 94871a4..a7e5962 100644
--- a/README.md
+++ b/README.md
@@ -1,54 +1,69 @@
-# [Ratdeck](https://ratspeak.org/)
+# [rsDeck](https://ratspeak.org/)
-**Standalone Reticulum for the LilyGo T-Deck**
+**Dual-mode Ratspeak firmware for the LilyGo T-Deck Plus.**
+
+[](#install)
+[](LICENSE)
+[](https://github.com/ratspeak/rsDeck/releases)
+
+[Ratspeak](https://github.com/ratspeak/Ratspeak) |
+[Docs](https://ratspeak.org/docs.html) |
+[Downloads](https://ratspeak.org/download.html) |
+[rsReticulum](https://github.com/ratspeak/rsReticulum)
-Ratdeck turns a [LilyGo T-Deck](https://www.lilygo.cc/products/t-deck-plus) into a full standalone [Reticulum](https://reticulum.network/) node. It's not just an RNode which requires another device — it's the complete setup.
-
-End-to-end encrypted [LXMF](https://github.com/markqvist/LXMF) messaging over LoRa, TCP over WiFi for bridging to the wider Reticulum network, node discovery, identity management, and more.
-
-
-
---
-[](https://www.youtube.com/watch?v=F6I6fkMPxgI)
-[▶ YouTube: Reticulum Standalone - T-Deck & Cardputer Adv](https://www.youtube.com/watch?v=F6I6fkMPxgI)
+rsDeck turns a [LilyGo T-Deck Plus](https://www.lilygo.cc/products/t-deck-plus)
+into a two-mode Reticulum handheld. Standalone mode is an on-device
+Ratspeak/LXMF messenger. RNode mode makes the T-Deck a host-controlled radio
+for Ratspeak, Sideband, or another Reticulum client over BLE or USB serial.
----
-
+## Install
-## Installing
+Use the Ratspeak web flasher:
+[ratspeak.org/download.html](https://ratspeak.org/download.html).
-The easiest way is the **[web flasher](https://ratspeak.org/download.html)** — enable download mode (hold the trackball while powering on), plug in the USB, click flash, done.
+Put the T-Deck Plus in download mode by holding the trackball while powering on,
+connect USB, then flash `rsdeck-full`. The standalone-only and RNode-only
+images are release artifacts for launcher users or focused testing.
-To build from source:
+## Modes
-```bash
-git clone https://github.com/ratspeak/ratdeck
-cd ratdeck
-pip install platformio
-python3 -m platformio run --target upload
-```
+On boot, the launcher lets you choose:
-## Usage
+- **Standalone**: a local Reticulum/LXMF messenger with identity management,
+ contacts, peer discovery, messages, LoRa, and WiFi TCP access.
+- **RNode**: a host-controlled RNode-style radio target for Ratspeak or other
+ Reticulum clients over BLE or USB serial.
-On first boot, Ratdeck generates a Reticulum identity and shows a name input screen. Your LXMF address (32-character hex string) is what you share with contacts.
+RNode mode self-provisions the T-Deck RNode product/model/default config and
+running firmware hash on first boot, so users should not need a separate
+`rnodeconf` setup step for the bundled release images.
-**Tabs:** Home, Friends, Msgs, Peers, Setup — navigate with the trackball.
+## Basic Use
-**Manually announce:** To send an announcement manually, press the trackball or enter on the home tab.
+On first boot, Standalone mode generates a Reticulum identity and asks for a
+display name. Your LXMF address is the 32-character hex string you share with
+contacts.
-**Add/delete contacts/messages:** Hold the trackball down on a chat or a peer to add or delete.
+- Tabs: Home, Friends, Msgs, Peers, Setup.
+- Navigation: trackball movement and click/Enter.
+- Announce: press the trackball or Enter on the Home tab.
+- Add contacts: select a discovered peer, then open or save the chat.
+- Send messages: open a chat, type, and press Enter.
+- Delivery color: yellow while sending, green after delivery confirmation.
-**Sending a message:** Find someone in Peers, select to open chat, type your message, hit Enter. Status goes yellow (sending) → green (delivery confirmed).
+## Radio Presets
-**Radio presets** (Setup → Radio). Format: name — SF, bandwidth, coding rate, TX power. `Long Fast` is the compiled-in default.
+`Long Fast` is the compiled-in default. All radio parameters can also be tuned
+from Setup. Changes apply immediately.
| Preset | SF | BW | CR | TXP | Bitrate | Link budget |
-|---|---|---|---|---|---|---|
+|---|---:|---:|---:|---:|---:|---:|
| Short Turbo | 7 | 500 kHz | 4/5 | 14 dBm | 21.99 kbps | 140 dB |
| Short Fast | 7 | 250 kHz | 4/5 | 14 dBm | 10.84 kbps | 143 dB |
| Short Slow | 8 | 250 kHz | 4/5 | 14 dBm | 6.25 kbps | 145.5 dB |
@@ -58,30 +73,65 @@ On first boot, Ratdeck generates a Reticulum identity and shows a name input scr
| **Long Fast** *(default)* | **11** | **250 kHz** | **4/5** | **22 dBm** | **1.07 kbps** | **153 dB** |
| Long Moderate | 11 | 125 kHz | 4/8 | 22 dBm | 0.34 kbps | 156 dB |
-All radio parameters are also individually tunable; changes apply immediately, no reboot. Please operate in accordance with local laws, as you are solely responsible for knowing which regulations and requirements apply to your jurisdiction.
+You are responsible for operating within local laws and radio regulations.
-### WiFi Bridging (Alpha)
+## WiFi Bridging
-Use **STA mode** to connect to existing WiFi and reach remote nodes like `rns.ratspeak.org:4242`.
+WiFi bridging is experimental. STA mode can connect to existing WiFi and reach
+remote Reticulum nodes such as `rns.ratspeak.org:4242`.
-To bridge LoRa with Reticulum on your computer:
-
-1. Set WiFi to **AP mode** in Setup → Network (creates `ratdeck-XXXX`, password: `ratspeak`)
-2. Connect your computer to that network
-3. Add to your Reticulum config:
+AP mode exposes a local TCP endpoint for a nearby Reticulum host:
```ini
-[[ratdeck]]
+[[rsdeck]]
type = TCPClientInterface
target_host = 192.168.4.1
target_port = 4242
```
-Note: WiFi bridging methods and interfaces will be revamped with Ratspeak's client release, therefore, it's unlikely AP mode works at all currently.
+The bridging UI and interface behavior may change as Ratspeak's client release
+stabilizes.
+
+## Build From Source
+
+```bash
+git clone https://github.com/ratspeak/rsDeck
+cd rsDeck
+python3 -m pip install platformio esptool
+make prep-tdeck
+make package
+make flash port=/dev/cu.usbmodem3101
+```
+
+Useful build targets:
+
+```bash
+make build-launcher # launcher only
+make build-standalone # standalone messenger app
+make build-rnode # host-controlled RNode target
+make full-image # launcher + Standalone + RNode
+make package # release zips and launcher bins
+```
+
+Release artifacts are written to `dist/`:
+
+```text
+dist/rsdeck-full.zip
+dist/rsdeck-standalone.zip
+dist/rsdeck-rnode.zip
+dist/rsdeck-standalone-m5launcher.bin
+dist/rsdeck-rnode-m5launcher.bin
+```
+
+Use the `.zip` files with the Ratspeak web flasher. The `*-m5launcher.bin`
+files are app images for M5Launcher/M5Burner-style launchers that boot
+Standalone or RNode directly from SD.
## License
-GNU Affero General Public License v3.0 or later. See [LICENSE](LICENSE).
+rsDeck standalone firmware, launcher, partition tables, and packaging tools are
+licensed under the GNU Affero General Public License v3.0 or later. See
+[LICENSE](LICENSE).
Vendored third-party code keeps its own license notices, including
-`lib/Crypto` under MIT.
+`vendor/rnode_firmware/` and `lib/Crypto`.
diff --git a/docs/DUAL-BOOT.md b/docs/DUAL-BOOT.md
index 08e29bb..2ab7ac3 100644
--- a/docs/DUAL-BOOT.md
+++ b/docs/DUAL-BOOT.md
@@ -6,7 +6,7 @@ flash, with a boot-time chooser:
| Partition | Slot | Offset | Size | Contents |
|------------|-------|----------|---------|----------|
| launcher | ota_0 | 0x10000 | 1MB | Boot chooser UI |
-| standalone | ota_1 | 0x110000 | 4MB | Full Ratdeck messenger |
+| standalone | ota_1 | 0x110000 | 4MB | Full rsDeck messenger |
| rnode | ota_2 | 0x510000 | 3MB | RNode (KISS over USB CDC + BLE) |
| littlefs | — | 0x810000 | ~7.9MB | User data (unchanged from single-app layout) |
@@ -32,22 +32,25 @@ Trackball up/down + click, or keys: `W`/`S` select, `Enter` boot,
## RNode mode
The vendored RNode firmware (`vendor/rnode_firmware`, `make firmware-tdeck`)
-self-provisions EEPROM on first boot (PRODUCT_TDECK_V1 / MODEL_D9, 915 MHz
-defaults) and enables BLE pairing on first run when no bonds exist. The display
-shows the standard RNode UI (node address, waterfall, status) and the BLE
-pairing PIN during pairing; it blanks after 15s idle. Reset to return to the
-launcher.
+self-provisions EEPROM on first boot (PRODUCT_TDECK_V1 / MODEL_D9, default
+radio config) and adopts the running firmware hash for this app image. BLE stays
+connectable for already-bonded hosts; tap the on-screen Pair via BLE panel to
+open a 30s pairing window for a new host. The display shows the standard RNode
+UI (node address, waterfall, status) and the BLE pairing PIN during explicit
+pairing; it blanks after 60s idle. Reset to return to the launcher.
## Build
```bash
make prep-tdeck # once: arduino-cli esp32 core + libs
make package # dist/: rsdeck-full.zip, rsdeck-standalone.zip,
- # rsdeck-rnode.zip, rsdeck-*-app.bin
+ # rsdeck-rnode.zip, rsdeck-*-m5launcher.bin
make flash port=/dev/cu.usbmodemXXXX # flash full image
```
`rsdeck-full.bin` flashes at offset 0x0 and contains all three apps.
-`rsdeck-standalone.zip` repackages the legacy single-app `ratdeck-merged.bin`
-(old partition table, no launcher). The `*-app.bin` files are bare app images
-for external launchers or manual OTA-slot flashing.
+`rsdeck-standalone.zip` repackages the legacy single-app `rsdeck-merged.bin`
+(old partition table, no launcher). The `*-m5launcher.bin` files are bare app
+images for M5Launcher/M5Burner-style launchers, external launchers, or manual
+OTA-slot flashing. The RNode-only app image is T-Deck-specific and
+self-provisioning; it is not a generic upstream `rnodeconf` image.
diff --git a/launcher/src/main.cpp b/launcher/src/main.cpp
index 6b1f406..182a74f 100644
--- a/launcher/src/main.cpp
+++ b/launcher/src/main.cpp
@@ -187,8 +187,6 @@ void drawScreen() {
display.setTextColor(kMuted, kBg);
display.setCursor(24, 196);
display.print("Trackball or W/S select, click/Enter boot");
- display.setCursor(24, 212);
- display.print("Touch: tap card, tap again to boot R/N quick");
drawCountdown(true);
}
@@ -209,7 +207,7 @@ void pauseAutoBoot() {
drawCountdown(true);
}
-void showBooting(const char *label, bool rnode) {
+void showBooting(const char *label) {
booting = true;
display.fillScreen(kBg);
display.setTextSize(3);
@@ -220,10 +218,6 @@ void showBooting(const char *label, bool rnode) {
display.setTextColor(kMuted, kBg);
display.setCursor(30, 110);
display.print("Starting...");
- if (rnode) {
- display.setCursor(30, 134);
- display.print("Reset the device to return here.");
- }
}
void showError(const char *message) {
@@ -244,7 +238,7 @@ void startChoice(Choice choice) {
FirmwareMode mode = choice == Choice::Standalone ? FirmwareMode::Standalone : FirmwareMode::RNode;
Serial.printf("[LAUNCHER] booting %s\n", mode_name(mode));
- showBooting(mode_name(mode), mode == FirmwareMode::RNode);
+ showBooting(mode_name(mode));
SwitchResult result = set_next_boot(mode);
if (!result.ok) {
Serial.printf("[LAUNCHER] boot switch failed: %s\n", result.message);
diff --git a/lv_conf.h b/lv_conf.h
index 5d4104f..31689bc 100644
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -3,9 +3,9 @@
#include
-// Forward-declare custom ratdeck fonts for LV_FONT_DEFAULT
+// Forward-declare custom rsDeck fonts for LV_FONT_DEFAULT
typedef struct _lv_font_t lv_font_t;
-extern const lv_font_t lv_font_ratdeck_14;
+extern const lv_font_t lv_font_rsdeck_14;
// Color depth: 16-bit RGB565
#define LV_COLOR_DEPTH 16
@@ -41,13 +41,13 @@ extern const lv_font_t lv_font_ratdeck_14;
#define LV_USE_THEME_DEFAULT 0
#define LV_THEME_DEFAULT_DARK 0
-// Fonts - built-in (only 16 still used for titles; 10/12/14 replaced by custom ratdeck fonts)
+// Fonts - built-in (only 16 still used for titles; 10/12/14 replaced by custom rsDeck fonts)
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 1 // Delivery-status check glyphs in LvMessageView
#define LV_FONT_MONTSERRAT_14 0
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_UNSCII_8 0
-#define LV_FONT_DEFAULT &lv_font_ratdeck_14
+#define LV_FONT_DEFAULT &lv_font_rsdeck_14
// Widgets — only enable what we actually use
#define LV_USE_LABEL 1
diff --git a/merge_firmware.py b/merge_firmware.py
index 07d54f8..bd351ea 100644
--- a/merge_firmware.py
+++ b/merge_firmware.py
@@ -15,7 +15,7 @@ def merge_bin(source, target, env):
framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif32")
boot_app0 = os.path.join(framework_dir, "tools", "partitions", "boot_app0.bin")
- output = os.path.join(project_dir, "ratdeck-merged.bin")
+ output = os.path.join(project_dir, "rsdeck-merged.bin")
python = env.subst("$PYTHONEXE")
env.Execute(
diff --git a/platformio.ini b/platformio.ini
index ad4b3b7..4241725 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -1,4 +1,4 @@
-[env:ratdeck_915]
+[env:rsdeck_915]
platform = espressif32@6.7.0
board = esp32-s3-devkitc-1
framework = arduino
@@ -12,7 +12,7 @@ build_flags =
-std=gnu++17
-fexceptions
-O2
- -DRATDECK=1
+ -DRSDECK=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
; Silence vfs_api log_e noise — it fires on every open/remove of a missing
diff --git a/src/audio/AudioNotify.cpp b/src/audio/AudioNotify.cpp
index 8a98399..b3bedcc 100644
--- a/src/audio/AudioNotify.cpp
+++ b/src/audio/AudioNotify.cpp
@@ -168,7 +168,7 @@ void AudioNotify::playError() {
void AudioNotify::playBoot() {
if (!_enabled || !_i2sReady) return;
- // === RATDECK BOOT SEQUENCE ===
+ // === RSDECK BOOT SEQUENCE ===
// Sci-fi computer startup: sweep -> digital arpeggio -> confirmation
// Total ~550ms
diff --git a/src/config/BoardConfig.h b/src/config/BoardConfig.h
index 231c230..f3fd6ea 100644
--- a/src/config/BoardConfig.h
+++ b/src/config/BoardConfig.h
@@ -1,7 +1,7 @@
#pragma once
// =============================================================================
-// Ratdeck — LilyGo T-Deck Plus Pin Definitions
+// rsDeck — LilyGo T-Deck Plus Pin Definitions
// =============================================================================
// --- Power Control ---
diff --git a/src/config/Config.h b/src/config/Config.h
index 2f0d051..c51246a 100644
--- a/src/config/Config.h
+++ b/src/config/Config.h
@@ -1,13 +1,13 @@
#pragma once
// =============================================================================
-// Ratdeck — Compile-Time Configuration
+// rsDeck — Compile-Time Configuration
// =============================================================================
-#define RATDECK_VERSION_MAJOR 1
-#define RATDECK_VERSION_MINOR 9
-#define RATDECK_VERSION_PATCH 3
-#define RATDECK_VERSION_STRING "1.9.3"
+#define RSDECK_VERSION_MAJOR 2
+#define RSDECK_VERSION_MINOR 0
+#define RSDECK_VERSION_PATCH 0
+#define RSDECK_VERSION_STRING "2.0.0"
// --- Feature Flags ---
#define HAS_DISPLAY true
@@ -19,10 +19,10 @@
// TODO(BLE): Revisit as a dedicated Ratspeak BLE Peer transport based on the
// rsReticulum GATT/fragmentation/anti-loop design, after T-Deck Plus memory,
// flash, power, and interop behavior are tested end to end.
-#ifndef RATDECK_EXPERIMENTAL_BLE
-#define RATDECK_EXPERIMENTAL_BLE 0
+#ifndef RSDECK_EXPERIMENTAL_BLE
+#define RSDECK_EXPERIMENTAL_BLE 0
#endif
-#define HAS_BLE RATDECK_EXPERIMENTAL_BLE
+#define HAS_BLE RSDECK_EXPERIMENTAL_BLE
#define HAS_SD true
#define HAS_AUDIO true
#define HAS_GPS true // UBlox MIA-M10Q UART GPS
@@ -43,6 +43,8 @@
#define PATH_MESSAGES "/messages"
// --- SD Card Paths ---
+// Legacy path kept intentionally so existing Standalone users keep their data.
+// TODO: Migrate to /rsdeck only with an explicit data migration plan.
#define SD_PATH_CONFIG_DIR "/ratdeck/config"
#define SD_PATH_USER_CONFIG "/ratdeck/config/user.json"
#define SD_PATH_MESSAGES "/ratdeck/messages"
@@ -56,14 +58,14 @@
#define TCP_CONNECT_TIMEOUT_MS 500
// --- Announce Flood Defense ---
-#define RATDECK_MAX_ANNOUNCES_PER_SEC 5 // Transport-level rate limit (before Ed25519 verify)
+#define RSDECK_MAX_ANNOUNCES_PER_SEC 5 // Transport-level rate limit (before Ed25519 verify)
// --- Limits ---
-#define RATDECK_MAX_NODES 100 // Endpoint device, not transport node
-#define RATDECK_MAX_MESSAGES_PER_CONV 100
+#define RSDECK_MAX_NODES 100 // Endpoint device, not transport node
+#define RSDECK_MAX_MESSAGES_PER_CONV 100
#define FLASH_MSG_CACHE_LIMIT 20
-#define RATDECK_MAX_OUTQUEUE 20
-#define RATDECK_LXMF_SINGLE_FRAME_MAX 254 // T-Deck-safe payload cap until resource transfers are fixed
+#define RSDECK_MAX_OUTQUEUE 20
+#define RSDECK_LXMF_SINGLE_FRAME_MAX 254 // T-Deck-safe payload cap until resource transfers are fixed
#define PATH_PERSIST_INTERVAL_MS 60000
// --- Power Management ---
diff --git a/src/config/UserConfig.cpp b/src/config/UserConfig.cpp
index 685e0e0..8851e56 100644
--- a/src/config/UserConfig.cpp
+++ b/src/config/UserConfig.cpp
@@ -1,6 +1,26 @@
#include "UserConfig.h"
#include "config/BoardConfig.h"
+namespace {
+bool validLoRaFrequency(uint32_t freq) {
+ return (freq >= 863000000UL && freq <= 870000000UL) ||
+ (freq >= 902000000UL && freq <= 928000000UL) ||
+ (freq >= 920000000UL && freq <= 925000000UL);
+}
+}
+
+void UserConfig::sanitizeSettings() {
+ if (_settings.radioRegion >= REGION_COUNT) _settings.radioRegion = REGION_AMERICAS;
+ if (!validLoRaFrequency(_settings.loraFrequency)) {
+ _settings.loraFrequency = REGION_FREQ[constrain((int)_settings.radioRegion, 0, REGION_COUNT - 1)];
+ }
+ _settings.loraSF = constrain(_settings.loraSF, 5, 12);
+ _settings.loraBW = constrain(_settings.loraBW, 7800UL, 500000UL);
+ _settings.loraCR = constrain(_settings.loraCR, 5, 8);
+ _settings.loraTxPower = constrain(_settings.loraTxPower, -9, 22);
+ _settings.loraPreamble = constrain(_settings.loraPreamble, 6L, 65L);
+}
+
bool UserConfig::parseJson(const String& json) {
Serial.printf("[CONFIG] Parsing config (%d bytes)\n", json.length());
@@ -102,11 +122,13 @@ bool UserConfig::parseJson(const String& json) {
if (_settings.announceInterval > 360) _settings.announceInterval = 360;
_settings.devMode = doc["dev_mode"] | false;
+ sanitizeSettings();
Serial.println("[CONFIG] Settings loaded");
return true;
}
-String UserConfig::serializeToJson() const {
+String UserConfig::serializeToJson() {
+ sanitizeSettings();
JsonDocument doc;
doc["radio_region"] = _settings.radioRegion;
diff --git a/src/config/UserConfig.h b/src/config/UserConfig.h
index 38c67cd..264c69f 100644
--- a/src/config/UserConfig.h
+++ b/src/config/UserConfig.h
@@ -111,7 +111,8 @@ public:
private:
bool parseJson(const String& json);
- String serializeToJson() const;
+ String serializeToJson();
+ void sanitizeSettings();
UserSettings _settings;
};
diff --git a/src/fonts/fonts.h b/src/fonts/fonts.h
index e852045..7d44d46 100644
--- a/src/fonts/fonts.h
+++ b/src/fonts/fonts.h
@@ -8,6 +8,6 @@
#include
-LV_FONT_DECLARE(lv_font_ratdeck_10)
-LV_FONT_DECLARE(lv_font_ratdeck_12)
-LV_FONT_DECLARE(lv_font_ratdeck_14)
+LV_FONT_DECLARE(lv_font_rsdeck_10)
+LV_FONT_DECLARE(lv_font_rsdeck_12)
+LV_FONT_DECLARE(lv_font_rsdeck_14)
diff --git a/src/fonts/lv_font_ratdeck_10.c b/src/fonts/lv_font_rsdeck_10.c
similarity index 99%
rename from src/fonts/lv_font_ratdeck_10.c
rename to src/fonts/lv_font_rsdeck_10.c
index bbbdc99..1d04d79 100644
--- a/src/fonts/lv_font_ratdeck_10.c
+++ b/src/fonts/lv_font_rsdeck_10.c
@@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 10 px
* Bpp: 4
- * Opts: --no-compress --no-prefilter --bpp 4 --size 10 --font .pio/libdeps/ratdeck_915/lvgl/scripts/built_in_font/Montserrat-Medium.ttf -r 0x20-0x7F,0xA0-0xFF,0x100-0x17F,0x400-0x4FF --format lvgl -o src/fonts/lv_font_ratdeck_10.c --force-fast-kern-format
+ * Opts: --no-compress --no-prefilter --bpp 4 --size 10 --font .pio/libdeps/rsdeck_915/lvgl/scripts/built_in_font/Montserrat-Medium.ttf -r 0x20-0x7F,0xA0-0xFF,0x100-0x17F,0x400-0x4FF --format lvgl -o src/fonts/lv_font_rsdeck_10.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@@ -10,11 +10,11 @@
#include
#endif
-#ifndef LV_FONT_RATDECK_10
-#define LV_FONT_RATDECK_10 1
+#ifndef LV_FONT_RSDECK_10
+#define LV_FONT_RSDECK_10 1
#endif
-#if LV_FONT_RATDECK_10
+#if LV_FONT_RSDECK_10
/*-----------------
* BITMAPS
@@ -4614,9 +4614,9 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
-const lv_font_t lv_font_ratdeck_10 = {
+const lv_font_t lv_font_rsdeck_10 = {
#else
-lv_font_t lv_font_ratdeck_10 = {
+lv_font_t lv_font_rsdeck_10 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
@@ -4638,5 +4638,5 @@ lv_font_t lv_font_ratdeck_10 = {
-#endif /*#if LV_FONT_RATDECK_10*/
+#endif /*#if LV_FONT_RSDECK_10*/
diff --git a/src/fonts/lv_font_ratdeck_12.c b/src/fonts/lv_font_rsdeck_12.c
similarity index 99%
rename from src/fonts/lv_font_ratdeck_12.c
rename to src/fonts/lv_font_rsdeck_12.c
index aa8c73b..5ffda1a 100644
--- a/src/fonts/lv_font_ratdeck_12.c
+++ b/src/fonts/lv_font_rsdeck_12.c
@@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 12 px
* Bpp: 4
- * Opts: --no-compress --no-prefilter --bpp 4 --size 12 --font .pio/libdeps/ratdeck_915/lvgl/scripts/built_in_font/Montserrat-Medium.ttf -r 0x20-0x7F,0xA0-0xFF,0x100-0x17F,0x400-0x4FF --format lvgl -o src/fonts/lv_font_ratdeck_12.c --force-fast-kern-format
+ * Opts: --no-compress --no-prefilter --bpp 4 --size 12 --font .pio/libdeps/rsdeck_915/lvgl/scripts/built_in_font/Montserrat-Medium.ttf -r 0x20-0x7F,0xA0-0xFF,0x100-0x17F,0x400-0x4FF --format lvgl -o src/fonts/lv_font_rsdeck_12.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@@ -10,11 +10,11 @@
#include
#endif
-#ifndef LV_FONT_RATDECK_12
-#define LV_FONT_RATDECK_12 1
+#ifndef LV_FONT_RSDECK_12
+#define LV_FONT_RSDECK_12 1
#endif
-#if LV_FONT_RATDECK_12
+#if LV_FONT_RSDECK_12
/*-----------------
* BITMAPS
@@ -5565,9 +5565,9 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
-const lv_font_t lv_font_ratdeck_12 = {
+const lv_font_t lv_font_rsdeck_12 = {
#else
-lv_font_t lv_font_ratdeck_12 = {
+lv_font_t lv_font_rsdeck_12 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
@@ -5589,5 +5589,5 @@ lv_font_t lv_font_ratdeck_12 = {
-#endif /*#if LV_FONT_RATDECK_12*/
+#endif /*#if LV_FONT_RSDECK_12*/
diff --git a/src/fonts/lv_font_ratdeck_14.c b/src/fonts/lv_font_rsdeck_14.c
similarity index 99%
rename from src/fonts/lv_font_ratdeck_14.c
rename to src/fonts/lv_font_rsdeck_14.c
index c781312..25eed11 100644
--- a/src/fonts/lv_font_ratdeck_14.c
+++ b/src/fonts/lv_font_rsdeck_14.c
@@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 14 px
* Bpp: 4
- * Opts: --no-compress --no-prefilter --bpp 4 --size 14 --font .pio/libdeps/ratdeck_915/lvgl/scripts/built_in_font/Montserrat-Medium.ttf -r 0x20-0x7F,0xA0-0xFF,0x100-0x17F,0x400-0x4FF --format lvgl -o src/fonts/lv_font_ratdeck_14.c --force-fast-kern-format
+ * Opts: --no-compress --no-prefilter --bpp 4 --size 14 --font .pio/libdeps/rsdeck_915/lvgl/scripts/built_in_font/Montserrat-Medium.ttf -r 0x20-0x7F,0xA0-0xFF,0x100-0x17F,0x400-0x4FF --format lvgl -o src/fonts/lv_font_rsdeck_14.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@@ -10,11 +10,11 @@
#include
#endif
-#ifndef LV_FONT_RATDECK_14
-#define LV_FONT_RATDECK_14 1
+#ifndef LV_FONT_RSDECK_14
+#define LV_FONT_RSDECK_14 1
#endif
-#if LV_FONT_RATDECK_14
+#if LV_FONT_RSDECK_14
/*-----------------
* BITMAPS
@@ -6251,9 +6251,9 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
-const lv_font_t lv_font_ratdeck_14 = {
+const lv_font_t lv_font_rsdeck_14 = {
#else
-lv_font_t lv_font_ratdeck_14 = {
+lv_font_t lv_font_rsdeck_14 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
@@ -6275,5 +6275,5 @@ lv_font_t lv_font_ratdeck_14 = {
-#endif /*#if LV_FONT_RATDECK_14*/
+#endif /*#if LV_FONT_RSDECK_14*/
diff --git a/src/main.cpp b/src/main.cpp
index 454c12a..0303cea 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,5 @@
// =============================================================================
-// Ratdeck — Main Entry Point
+// rsDeck — Main Entry Point
// LilyGo T-Deck Plus: LovyanGFX Direct UI + microReticulum + LXMF Messaging
// =============================================================================
@@ -411,7 +411,7 @@ void onHotkeyAutoIface() {
}
void onHotkeyDiag() {
Serial.println("=== DIAGNOSTIC DUMP ===");
- Serial.printf("Device: Ratdeck T-Deck Plus\n");
+ Serial.printf("Device: rsDeck T-Deck Plus\n");
Serial.printf("Identity: %s\n", rns.identityHash().c_str());
Serial.printf("Transport: %s\n", rns.isTransportActive() ? "ACTIVE" : "OFFLINE");
Serial.printf("Paths: %d Links: %d\n", (int)rns.pathCount(), (int)rns.linkCount());
@@ -425,6 +425,17 @@ void onHotkeyDiag() {
radio.getTxPower());
Serial.printf("Regulator: %s\n", LORA_USE_DCDC_REGULATOR ? "DC-DC" : "LDO");
Serial.printf("Preamble: %ld symbols\n", radio.getPreambleLength());
+ Serial.printf("Bitrate: %lu bps LDRO: %s frame255: %.0f ms\n",
+ (unsigned long)radio.getBitrate(),
+ radio.lowDataRateEnabled() ? "ON" : "off",
+ radio.getAirtime(MAX_PACKET_SIZE));
+ if (auto* loraIf = rns.loraInterface()) {
+ Serial.printf("LoRaIF: bitrate=%lu bps split_timeout=%lu ms frame=%.0f ms airtime=%.2f%%\n",
+ (unsigned long)loraIf->bitrate(),
+ loraIf->splitRxTimeoutMs(),
+ loraIf->singleFrameAirtimeMs(),
+ loraIf->airtimeUtilization() * 100.0f);
+ }
Serial.printf("IQ invert: %s\n", radio.getInvertIQ() ? "ON" : "off");
Serial.printf("SyncWord regs: 0x%02X%02X\n",
radio.readRegister(REG_SYNC_WORD_MSB_6X),
@@ -517,7 +528,7 @@ void onHotkeyRssiMonitor() {
void onHotkeyRadioTest() {
Serial.println("[TEST] Sending raw test packet...");
uint8_t header = 0xA0;
- const char* testPayload = "RATDECK_TEST_1234567890";
+ const char* testPayload = "RSDECK_TEST_1234567890";
radio.beginPacket();
radio.write(header);
radio.write((const uint8_t*)testPayload, strlen(testPayload));
@@ -659,7 +670,7 @@ static bool selectDiagnosticPeer(const char* explicitArg, RNS::Bytes& destHash,
}
static std::string makeDiagnosticLxmfPayload(size_t length) {
- static constexpr char kPrefix[] = "RATDECK-LXMF-TEST:";
+ static constexpr char kPrefix[] = "RSDECK-LXMF-TEST:";
static constexpr char kPattern[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -1055,7 +1066,7 @@ void setup() {
delay(100);
Serial.println();
Serial.println("=================================");
- Serial.printf(" Ratdeck v%s\n", RATDECK_VERSION_STRING);
+ Serial.printf(" rsDeck v%s\n", RSDECK_VERSION_STRING);
Serial.println(" LilyGo T-Deck Plus");
Serial.println("=================================");
@@ -1129,7 +1140,7 @@ void setup() {
delay(10);
if (sdStore.begin(&sharedSPI, SD_CS)) {
sdHadExistingData = sdStore.hasExistingData();
- sdStore.formatForRatdeck();
+ sdStore.formatForRsDeck();
Serial.println("[SD] Card ready");
} else {
Serial.println("[SD] Not detected");
@@ -1715,7 +1726,7 @@ void setup() {
if (wipe) {
Serial.println("[BOOT] User chose to wipe old data");
lvDataCleanScreen.showStatus("Clearing old data...");
- sdStore.wipeRatdeck();
+ sdStore.wipeRsDeck();
if (announceManager) announceManager->clearAll();
Serial.println("[BOOT] Old data cleared");
lvDataCleanScreen.showStatus("Done! Rebooting...");
@@ -1843,7 +1854,7 @@ void setup() {
keyboard.backlightOn();
}
- Serial.println("[BOOT] Ratdeck ready");
+ Serial.println("[BOOT] rsDeck ready");
Serial.printf("[BOOT] Summary: radio=%s flash=%s sd=%s\n",
radioOnline ? "ONLINE" : "OFFLINE",
flash.isReady() ? "OK" : "FAIL",
diff --git a/src/radio/SX1262.cpp b/src/radio/SX1262.cpp
index 9369839..7c78c40 100644
--- a/src/radio/SX1262.cpp
+++ b/src/radio/SX1262.cpp
@@ -770,7 +770,13 @@ void SX1262::implicitHeaderMode() {
}
void SX1262::handleLowDataRate() {
- _ldro = long((1 << _sf) / (getSignalBandwidth() / 1000)) > 16;
+ uint32_t bw = getSignalBandwidth();
+ if (_sf == 0 || bw == 0) {
+ _ldro = false;
+ return;
+ }
+ float symbolTimeMs = 1000.0f * (float)(1UL << _sf) / (float)bw;
+ _ldro = symbolTimeMs > 16.0f;
}
void SX1262::standby() {
@@ -785,23 +791,37 @@ void SX1262::sleep() {
float SX1262::getAirtime(uint16_t written) {
if (!_radioOnline) return 0;
- float symbolRate = (float)getSignalBandwidth() / (float)(1 << _sf);
+ uint32_t bw = getSignalBandwidth();
+ uint8_t crDen = getCodingRate4();
+ if (_sf == 0 || bw == 0 || crDen < 5 || crDen > 8) return 0;
+
+ float symbolRate = (float)bw / (float)(1UL << _sf);
float symbolTimeMs = 1000.0 / symbolRate;
- float loraSymbols;
+ float payloadSymbols;
if (_sf >= 7) {
- loraSymbols = (8.0 * written + PHY_CRC_LORA_BITS - 4.0 * _sf + 8 + PHY_HEADER_LORA_SYMBOLS);
- loraSymbols /= 4.0 * (_sf - 2 * (_ldro ? 1 : 0));
- if (loraSymbols < 0) loraSymbols = 0;
- loraSymbols = ceil(loraSymbols);
- loraSymbols += _preambleLength + 0.25 + 8;
+ payloadSymbols = (8.0 * written + PHY_CRC_LORA_BITS - 4.0 * _sf + 8 + PHY_HEADER_LORA_SYMBOLS);
+ payloadSymbols /= 4.0 * (_sf - 2 * (_ldro ? 1 : 0));
+ if (payloadSymbols < 0) payloadSymbols = 0;
+ payloadSymbols = ceil(payloadSymbols) * crDen;
+ payloadSymbols += _preambleLength + 0.25 + 8;
} else {
- loraSymbols = (8.0 * written + PHY_CRC_LORA_BITS - 4.0 * _sf + PHY_HEADER_LORA_SYMBOLS);
- loraSymbols /= 4.0 * _sf;
- if (loraSymbols < 0) loraSymbols = 0;
- loraSymbols = ceil(loraSymbols);
- loraSymbols += _preambleLength + 2.25 + 8;
+ payloadSymbols = (8.0 * written + PHY_CRC_LORA_BITS - 4.0 * _sf + PHY_HEADER_LORA_SYMBOLS);
+ payloadSymbols /= 4.0 * _sf;
+ if (payloadSymbols < 0) payloadSymbols = 0;
+ payloadSymbols = ceil(payloadSymbols) * crDen;
+ payloadSymbols += _preambleLength + 2.25 + 8;
}
- return loraSymbols * symbolTimeMs;
+ return payloadSymbols * symbolTimeMs;
+}
+
+uint32_t SX1262::getBitrate() {
+ uint32_t bw = getSignalBandwidth();
+ uint8_t crDen = getCodingRate4();
+ if (_sf == 0 || bw == 0 || crDen < 5 || crDen > 8) return 0;
+
+ float bitrate = (float)_sf * (4.0f / (float)crDen) * (float)bw / (float)(1UL << _sf);
+ if (bitrate < 1.0f) return 1;
+ return (uint32_t)bitrate;
}
void IRAM_ATTR SX1262::onDio0Rise() {
diff --git a/src/radio/SX1262.h b/src/radio/SX1262.h
index 9983fcc..5684f27 100644
--- a/src/radio/SX1262.h
+++ b/src/radio/SX1262.h
@@ -65,6 +65,8 @@ public:
uint8_t getPacketType();
uint16_t getIrqFlags();
float getAirtime(uint16_t written);
+ uint32_t getBitrate();
+ bool lowDataRateEnabled() const { return _ldro; }
// --- FIFO access ---
void readBuffer(uint8_t* buffer, size_t size);
diff --git a/src/reticulum/AnnounceManager.cpp b/src/reticulum/AnnounceManager.cpp
index 79204b4..ae88001 100644
--- a/src/reticulum/AnnounceManager.cpp
+++ b/src/reticulum/AnnounceManager.cpp
@@ -152,13 +152,14 @@ void AnnounceManager::received_announce(
auto& node = _nodes[it->second];
if (node.lastSeen != 0 && now >= node.lastSeen &&
now - node.lastSeen < ANNOUNCE_MIN_INTERVAL_MS) return;
- if (!name.empty()) node.name = name;
+ // Saved contacts own their local alias. Incoming announce app_data is
+ // still cached below, but must not reset the user-chosen contact name.
+ if (!name.empty() && !node.saved) node.name = name;
if (!idHex.empty()) node.identityHex = idHex;
node.lastSeen = now;
// hops_to() is expensive (linear routing table scan) — only call for saved contacts
if (node.saved) node.hops = RNS::Transport::hops_to(destination_hash);
if (_loraIf) { node.rssi = _loraIf->lastRxRssi(); node.snr = _loraIf->lastRxSnr(); }
- if (node.saved) _contactsDirty = true;
// Name cache update — skip expensive toHex() for unnamed re-announces
if (!name.empty()) {
std::string destHex = destination_hash.toHex();
@@ -227,7 +228,14 @@ void AnnounceManager::received_announce(
DiscoveredNode node;
node.hash = destination_hash;
- node.name = name.empty() ? destHex.substr(0, 12) : name;
+ if (!name.empty()) {
+ node.name = name;
+ } else {
+ auto cached = _nameCache.find(destHex);
+ node.name = (cached != _nameCache.end() && !cached->second.empty())
+ ? cached->second
+ : destHex.substr(0, 12);
+ }
node.identityHex = idHex;
node.lastSeen = millis();
// Skip hops_to() for new (unsaved) nodes — resolved when added as contact
@@ -296,7 +304,14 @@ void AnnounceManager::addManualContact(const std::string& hexHash, const std::st
DiscoveredNode node;
node.hash = hash;
- node.name = safeName.empty() ? hexHash.substr(0, 12) : safeName;
+ if (!safeName.empty()) {
+ node.name = safeName;
+ } else {
+ auto cached = _nameCache.find(hexHash);
+ node.name = (cached != _nameCache.end() && !cached->second.empty())
+ ? cached->second
+ : hexHash.substr(0, 12);
+ }
node.lastSeen = millis();
node.saved = true;
_hashIndex[key] = (int)_nodes.size();
diff --git a/src/reticulum/LXMFManager.cpp b/src/reticulum/LXMFManager.cpp
index 9d266de..3c5f1ee 100644
--- a/src/reticulum/LXMFManager.cpp
+++ b/src/reticulum/LXMFManager.cpp
@@ -26,7 +26,7 @@ bool LXMFManager::begin(ReticulumManager* rns, MessageStore* store) {
}
std::vector pending = _store->loadPendingOutgoing();
for (auto& msg : pending) {
- if ((int)_outQueue.size() >= RATDECK_MAX_OUTQUEUE) break;
+ if ((int)_outQueue.size() >= RSDECK_MAX_OUTQUEUE) break;
msg.lastRetryMs = 0;
_outQueue.push_back(msg);
}
@@ -121,7 +121,7 @@ bool LXMFManager::sendMessage(const RNS::Bytes& destHash, const std::string& con
msg.incoming = false;
msg.status = LXMFStatus::QUEUED;
- if ((int)_outQueue.size() >= RATDECK_MAX_OUTQUEUE) {
+ if ((int)_outQueue.size() >= RSDECK_MAX_OUTQUEUE) {
Serial.println("[LXMF] Outbound queue full; refusing new message");
return false;
}
@@ -131,11 +131,11 @@ bool LXMFManager::sendMessage(const RNS::Bytes& destHash, const std::string& con
Serial.println("[LXMF] Message pack failed");
return false;
}
- if (payload.size() > RATDECK_LXMF_SINGLE_FRAME_MAX) {
+ if (payload.size() > RSDECK_LXMF_SINGLE_FRAME_MAX) {
msg.status = LXMFStatus::FAILED;
if (_store) _store->saveMessage(msg);
Serial.printf("[LXMF] Message too large for T-Deck safe path (%d > %d); resource transfer disabled\n",
- (int)payload.size(), RATDECK_LXMF_SINGLE_FRAME_MAX);
+ (int)payload.size(), RSDECK_LXMF_SINGLE_FRAME_MAX);
return true;
}
if (preference == DeliveryPreference::Link) {
@@ -267,9 +267,9 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) {
if (payload.empty()) { Serial.println("[LXMF] packFull returned empty!"); msg.status = LXMFStatus::FAILED; return true; }
const std::string msgIdHex = msg.messageId.toHex();
const bool requireLink = _linkRequiredIds.count(msgIdHex) > 0;
- if (payload.size() > RATDECK_LXMF_SINGLE_FRAME_MAX) {
+ if (payload.size() > RSDECK_LXMF_SINGLE_FRAME_MAX) {
Serial.printf("[LXMF] Refusing resource-sized message (%d bytes); T-Deck safe cap is %d\n",
- (int)payload.size(), RATDECK_LXMF_SINGLE_FRAME_MAX);
+ (int)payload.size(), RSDECK_LXMF_SINGLE_FRAME_MAX);
msg.status = LXMFStatus::FAILED;
return true;
}
@@ -338,7 +338,7 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) {
// Larger packets require split-frame over LoRa, which is unreliable — any single
// frame loss (CRC error, collision, half-duplex timing) kills the entire transfer
// with no recovery. Link-based delivery handles retransmission at the protocol level.
- if (payloadBytes.size() <= RATDECK_LXMF_SINGLE_FRAME_MAX) {
+ if (payloadBytes.size() <= RSDECK_LXMF_SINGLE_FRAME_MAX) {
// Fits in single LoRa frame — send opportunistic
Serial.printf("[LXMF] sending opportunistic: %d bytes to %s\n",
(int)payloadBytes.size(), outDest.hash().toHex().substr(0, 12).c_str());
@@ -348,7 +348,7 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) {
} else {
// Too large for single frame — need link + resource transfer
Serial.printf("[LXMF] Message needs link delivery (%d bytes > %d single-frame), retry %d\n",
- (int)payloadBytes.size(), RATDECK_LXMF_SINGLE_FRAME_MAX, msg.retries);
+ (int)payloadBytes.size(), RSDECK_LXMF_SINGLE_FRAME_MAX, msg.retries);
if (msg.retries % 3 == 0 && (!_outLink || _outLinkDestHash != msg.destHash
|| _outLink.status() != RNS::Type::Link::ACTIVE)) {
ensureOutboundLink(outDest, msg.destHash, "resource transfer");
@@ -563,7 +563,7 @@ void LXMFManager::handleProofTimeoutHash(const std::string& receiptHash) {
_pendingProofs.erase(it);
p.proofAttempts++;
- bool retry = p.proofAttempts < 3 && (int)_instance->_outQueue.size() < RATDECK_MAX_OUTQUEUE;
+ bool retry = p.proofAttempts < 3 && (int)_instance->_outQueue.size() < RSDECK_MAX_OUTQUEUE;
LXMFStatus nextStatus = retry ? LXMFStatus::QUEUED : LXMFStatus::FAILED;
if (_instance->_store) {
diff --git a/src/reticulum/ReticulumManager.cpp b/src/reticulum/ReticulumManager.cpp
index 51bb0ed..5c67b1c 100644
--- a/src/reticulum/ReticulumManager.cpp
+++ b/src/reticulum/ReticulumManager.cpp
@@ -244,7 +244,7 @@ bool ReticulumManager::begin(SX1262* radio, FlashStore* flash, bool loraEnabled)
if (packet.context() == RNS::Type::Packet::PATH_RESPONSE) return true;
// Adaptive rate: tighter during first 60s boot flood, then normal
- unsigned int maxRate = (now < 60000) ? 3 : RATDECK_MAX_ANNOUNCES_PER_SEC;
+ unsigned int maxRate = (now < 60000) ? 3 : RSDECK_MAX_ANNOUNCES_PER_SEC;
if (++count > maxRate) { ReticulumManager::_announceFilterCount++; return false; }
return true;
@@ -349,7 +349,7 @@ void ReticulumManager::loop() {
// Synchronous persist — one cycle per call to spread I/O across intervals.
// Runs on core 1 (main loop) to avoid data races with microReticulum's
-// single-threaded transport state. Ratdeck is an endpoint, not a transport
+// single-threaded transport state. rsDeck is an endpoint, not a transport
// node, so path tables are intentionally not persisted across boots.
void ReticulumManager::persistData() {
unsigned long start = millis();
diff --git a/src/storage/FlashStore.cpp b/src/storage/FlashStore.cpp
index da63108..61b70c3 100644
--- a/src/storage/FlashStore.cpp
+++ b/src/storage/FlashStore.cpp
@@ -1,7 +1,7 @@
#include "FlashStore.h"
bool FlashStore::begin() {
- // Standalone ratdeck labels this partition "littlefs"; bmorcelli/Launcher
+ // Legacy standalone builds label this partition "littlefs"; bmorcelli/Launcher
// labels the equivalent partition "spiffs". Try ours first, fall back.
const char* labels[] = { "littlefs", "spiffs" };
bool mounted = false;
diff --git a/src/storage/MessageStore.cpp b/src/storage/MessageStore.cpp
index 3e71b6f..41c2645 100644
--- a/src/storage/MessageStore.cpp
+++ b/src/storage/MessageStore.cpp
@@ -362,8 +362,8 @@ bool MessageStore::saveMessage(LXMFMessage& msg) {
if (isPendingStatus(msg.status) && s.pendingCount < UINT16_MAX) s.pendingCount++;
if (msg.status == LXMFStatus::FAILED && s.failedCount < UINT16_MAX) s.failedCount++;
}
- int limit = sdOk ? RATDECK_MAX_MESSAGES_PER_CONV
- : ((_externalStorageEnabled && _sd && _sd->isReady()) ? FLASH_MSG_CACHE_LIMIT : RATDECK_MAX_MESSAGES_PER_CONV);
+ int limit = sdOk ? RSDECK_MAX_MESSAGES_PER_CONV
+ : ((_externalStorageEnabled && _sd && _sd->isReady()) ? FLASH_MSG_CACHE_LIMIT : RSDECK_MAX_MESSAGES_PER_CONV);
if (s.totalCount > limit) {
rebuildSummary(peerHex);
}
@@ -965,7 +965,7 @@ void MessageStore::enforceFlashLimit(const std::string& peerHex) {
}
entry = d.openNextFile();
}
- int limit = (_externalStorageEnabled && _sd && _sd->isReady()) ? FLASH_MSG_CACHE_LIMIT : RATDECK_MAX_MESSAGES_PER_CONV;
+ int limit = (_externalStorageEnabled && _sd && _sd->isReady()) ? FLASH_MSG_CACHE_LIMIT : RSDECK_MAX_MESSAGES_PER_CONV;
if ((int)files.size() <= limit) return;
std::sort(files.begin(), files.end());
int excess = files.size() - limit;
@@ -993,9 +993,9 @@ void MessageStore::enforceSDLimit(const std::string& peerHex) {
}
entry = d.openNextFile();
}
- if ((int)files.size() <= RATDECK_MAX_MESSAGES_PER_CONV) return;
+ if ((int)files.size() <= RSDECK_MAX_MESSAGES_PER_CONV) return;
std::sort(files.begin(), files.end());
- int excess = files.size() - RATDECK_MAX_MESSAGES_PER_CONV;
+ int excess = files.size() - RSDECK_MAX_MESSAGES_PER_CONV;
for (int i = 0; i < excess; i++) {
_sd->remove(files[i].c_str());
}
diff --git a/src/storage/SDStore.cpp b/src/storage/SDStore.cpp
index eb96432..38af9a8 100644
--- a/src/storage/SDStore.cpp
+++ b/src/storage/SDStore.cpp
@@ -176,9 +176,9 @@ String SDStore::readString(const char* path) {
return result;
}
-bool SDStore::wipeRatdeck() {
+bool SDStore::wipeRsDeck() {
if (!_ready) return false;
- Serial.println("[SD] Wiping /ratdeck/ ...");
+ Serial.println("[SD] Wiping rsDeck legacy /ratdeck/ data ...");
wipeDir("/ratdeck/messages");
wipeDir("/ratdeck/contacts");
wipeDir("/ratdeck/identity");
@@ -186,7 +186,7 @@ bool SDStore::wipeRatdeck() {
wipeDir("/ratdeck/transport");
SD.rmdir("/ratdeck");
Serial.println("[SD] Wipe complete, recreating dirs...");
- return formatForRatdeck();
+ return formatForRsDeck();
}
void SDStore::wipeDir(const char* path) {
@@ -221,9 +221,9 @@ bool SDStore::hasExistingData() {
return false;
}
-bool SDStore::formatForRatdeck() {
+bool SDStore::formatForRsDeck() {
if (!_ready) return false;
- Serial.println("[SD] Creating Ratdeck directory structure...");
+ Serial.println("[SD] Creating rsDeck legacy /ratdeck/ directory structure...");
bool ok = true;
ok &= ensureDir("/ratdeck");
ok &= ensureDir("/ratdeck/config");
diff --git a/src/storage/SDStore.h b/src/storage/SDStore.h
index fd72eb3..dc26f9d 100644
--- a/src/storage/SDStore.h
+++ b/src/storage/SDStore.h
@@ -25,8 +25,8 @@ public:
uint64_t totalBytes() const;
uint64_t usedBytes() const;
- bool formatForRatdeck();
- bool wipeRatdeck();
+ bool formatForRsDeck();
+ bool wipeRsDeck();
bool hasExistingData();
private:
diff --git a/src/transport/BLEInterface.cpp b/src/transport/BLEInterface.cpp
index cababff..c571b0a 100644
--- a/src/transport/BLEInterface.cpp
+++ b/src/transport/BLEInterface.cpp
@@ -21,7 +21,7 @@ BLEInterface::~BLEInterface() {
bool BLEInterface::start() {
_framesMutex = xSemaphoreCreateMutex();
- NimBLEDevice::init("Ratdeck");
+ NimBLEDevice::init("rsDeck");
NimBLEDevice::setMTU(512);
_pServer = NimBLEDevice::createServer();
@@ -29,13 +29,13 @@ bool BLEInterface::start() {
_pService = _pServer->createService(SERVICE_UUID);
- // TX: Ratdeck -> remote (NOTIFY)
+ // TX: rsDeck -> remote (NOTIFY)
_pTxChar = _pService->createCharacteristic(
TX_CHAR_UUID,
NIMBLE_PROPERTY::NOTIFY
);
- // RX: remote -> Ratdeck (WRITE)
+ // RX: remote -> rsDeck (WRITE)
_pRxChar = _pService->createCharacteristic(
RX_CHAR_UUID,
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR
@@ -47,7 +47,7 @@ bool BLEInterface::start() {
NimBLEAdvertising* pAdv = NimBLEDevice::getAdvertising();
pAdv->addServiceUUID(SERVICE_UUID);
- pAdv->setName("Ratdeck");
+ pAdv->setName("rsDeck");
pAdv->start();
_active = true;
diff --git a/src/transport/BLEInterface.h b/src/transport/BLEInterface.h
index ad3c4cc..ab075ff 100644
--- a/src/transport/BLEInterface.h
+++ b/src/transport/BLEInterface.h
@@ -79,7 +79,7 @@ private:
static constexpr uint8_t FRAME_ESC = 0x7D;
static constexpr uint8_t FRAME_XOR = 0x20;
- // Custom UUIDs for Ratdeck BLE transport
+ // Custom UUIDs for rsDeck BLE transport
static constexpr const char* SERVICE_UUID = "e2f0a5b1-c3d4-4e56-8f90-1a2b3c4d5e6f";
static constexpr const char* TX_CHAR_UUID = "e2f0a5b2-c3d4-4e56-8f90-1a2b3c4d5e6f";
static constexpr const char* RX_CHAR_UUID = "e2f0a5b3-c3d4-4e56-8f90-1a2b3c4d5e6f";
diff --git a/src/transport/BLESideband.cpp b/src/transport/BLESideband.cpp
index 121634e..0253cb9 100644
--- a/src/transport/BLESideband.cpp
+++ b/src/transport/BLESideband.cpp
@@ -11,7 +11,7 @@ bool BLESideband::begin(NimBLEServer* existingServer) {
_pServer = existingServer;
_ownServer = false;
} else {
- NimBLEDevice::init("Ratdeck");
+ NimBLEDevice::init("rsDeck");
_pServer = NimBLEDevice::createServer();
_ownServer = true;
}
@@ -19,13 +19,13 @@ bool BLESideband::begin(NimBLEServer* existingServer) {
// Create Nordic UART Service (don't set server callbacks — BLEInterface owns them)
_pService = _pServer->createService(NUS_SERVICE_UUID);
- // TX: Ratdeck -> Sideband (NOTIFY)
+ // TX: rsDeck -> Sideband (NOTIFY)
_pTxChar = _pService->createCharacteristic(
NUS_TX_UUID,
NIMBLE_PROPERTY::NOTIFY
);
- // RX: Sideband -> Ratdeck (WRITE)
+ // RX: Sideband -> rsDeck (WRITE)
_pRxChar = _pService->createCharacteristic(
NUS_RX_UUID,
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR
@@ -39,7 +39,7 @@ bool BLESideband::begin(NimBLEServer* existingServer) {
_pServer->start();
NimBLEAdvertising* pAdv = NimBLEDevice::getAdvertising();
pAdv->addServiceUUID(NUS_SERVICE_UUID);
- pAdv->setName("Ratdeck");
+ pAdv->setName("rsDeck");
pAdv->start();
} else {
// Add NUS UUID to existing advertising
diff --git a/src/transport/LoRaInterface.cpp b/src/transport/LoRaInterface.cpp
index 62817bc..5715ed6 100644
--- a/src/transport/LoRaInterface.cpp
+++ b/src/transport/LoRaInterface.cpp
@@ -1,6 +1,7 @@
#include "LoRaInterface.h"
#include "config/BoardConfig.h"
#include
+#include
// RNode on-air framing constants (from RNode_Firmware Framing.h / Config.h)
// Every LoRa packet has a 1-byte header: upper nibble = random sequence, lower nibble = flags
@@ -14,9 +15,9 @@ LoRaInterface::LoRaInterface(SX1262* radio, const char* name)
{
_IN = true;
_OUT = true;
- _bitrate = 2000;
// Reticulum MTU (500 bytes) — split-packet framing allows up to 2x254 = 508 bytes
_HW_MTU = RNS::Type::Reticulum::MTU;
+ refreshRadioTiming(true);
}
LoRaInterface::~LoRaInterface() {
@@ -30,6 +31,7 @@ bool LoRaInterface::start() {
return false;
}
_online = true;
+ refreshRadioTiming(true);
_radio->receive();
Serial.println("[LORA_IF] Interface started (split-packet enabled, MTU=500)");
return true;
@@ -42,6 +44,7 @@ void LoRaInterface::stop() {
void LoRaInterface::send_outgoing(const RNS::Bytes& data) {
if (!_online || !_radio) return;
+ refreshRadioTiming();
// Reject packets exceeding Reticulum MTU (500 bytes)
if (data.size() > RNS::Type::Reticulum::MTU) {
@@ -73,6 +76,7 @@ void LoRaInterface::send_outgoing(const RNS::Bytes& data) {
}
void LoRaInterface::transmitNow(const RNS::Bytes& data) {
+ refreshRadioTiming();
uint8_t header = (uint8_t)(random(256)) & RNODE_NIBBLE_SEQ;
bool needsSplit = (data.size() > RNODE_SINGLE_MTU);
@@ -129,6 +133,7 @@ void LoRaInterface::transmitNow(const RNS::Bytes& data) {
void LoRaInterface::loop() {
if (!_online || !_radio) return;
+ refreshRadioTiming();
// Handle async TX completion
if (_txPending) {
@@ -170,8 +175,10 @@ void LoRaInterface::loop() {
}
// Split RX timeout: discard stale partial packets and drain deferred TX
- if (_splitRxPending && (millis() - _splitRxTimestamp > SPLIT_RX_TIMEOUT_MS)) {
- Serial.println("[LORA_IF] RX SPLIT timeout, discarding partial");
+ if (_splitRxPending && (millis() - _splitRxTimestamp > _splitRxTimeoutMs)) {
+ unsigned long age = millis() - _splitRxTimestamp;
+ Serial.printf("[LORA_IF] RX SPLIT timeout after %lums (limit=%lums frame=%.0fms), discarding partial\n",
+ age, _splitRxTimeoutMs, _singleFrameAirtimeMs);
_splitRxPending = false;
_splitRxBuffer = RNS::Bytes();
// Drain any TX that was deferred during split RX hold
@@ -226,14 +233,14 @@ void LoRaInterface::loop() {
_splitRxBuffer = RNS::Bytes(raw + RNODE_HEADER_L, payloadSize);
_splitRxTimestamp = millis();
- Serial.printf("[LORA_IF] RX SPLIT frame 1: %d bytes (seq=0x%02X), RSSI=%d, SNR=%.1f\n",
- payloadSize, seq, _lastRxRssi, _lastRxSnr);
+ Serial.printf("[LORA_IF] RX SPLIT frame 1: %d bytes (seq=0x%02X), RSSI=%d, SNR=%.1f, timeout=%lums\n",
+ payloadSize, seq, _lastRxRssi, _lastRxSnr, _splitRxTimeoutMs);
_radio->receive();
return;
} else if (seq == _splitRxSeq) {
// Second frame matches — reassemble
- Serial.printf("[LORA_IF] RX SPLIT frame 2: %d bytes (seq=0x%02X), RSSI=%d, SNR=%.1f\n",
- payloadSize, seq, _lastRxRssi, _lastRxSnr);
+ Serial.printf("[LORA_IF] RX SPLIT frame 2: %d bytes (seq=0x%02X), RSSI=%d, SNR=%.1f, age=%lums\n",
+ payloadSize, seq, _lastRxRssi, _lastRxSnr, millis() - _splitRxTimestamp);
_splitRxBuffer.append(raw + RNODE_HEADER_L, payloadSize);
int totalSize = _splitRxBuffer.size();
@@ -293,3 +300,43 @@ float LoRaInterface::airtimeUtilization() const {
float windowMs = std::min((float)elapsed, (float)AIRTIME_WINDOW_MS);
return _airtimeAccumMs / windowMs;
}
+
+unsigned long LoRaInterface::computeSplitRxTimeoutMs(float frameAirtimeMs) const {
+ if (frameAirtimeMs <= 0) return SPLIT_RX_TIMEOUT_FLOOR_MS;
+ float raw = frameAirtimeMs * SPLIT_RX_TIMEOUT_MULT + SPLIT_RX_TIMEOUT_MARGIN_MS;
+ unsigned long rounded = (unsigned long)(ceil(raw / 500.0f) * 500.0f);
+ return std::min(SPLIT_RX_TIMEOUT_CEIL_MS,
+ std::max(SPLIT_RX_TIMEOUT_FLOOR_MS, rounded));
+}
+
+void LoRaInterface::refreshRadioTiming(bool forceLog) {
+ if (!_radio || !_radio->isRadioOnline()) {
+ if (_bitrate == 0) _bitrate = 1;
+ return;
+ }
+
+ unsigned long now = millis();
+ if (!forceLog && _lastTimingRefreshMs != 0 && (now - _lastTimingRefreshMs) < 1000) return;
+ _lastTimingRefreshMs = now;
+
+ uint32_t newBitrate = _radio->getBitrate();
+ if (newBitrate == 0) newBitrate = (_bitrate > 0) ? _bitrate : 1;
+ float newFrameAirtime = _radio->getAirtime(MAX_PACKET_SIZE);
+ unsigned long newTimeout = computeSplitRxTimeoutMs(newFrameAirtime);
+
+ bool changed = forceLog || newBitrate != _bitrate ||
+ newTimeout != _splitRxTimeoutMs ||
+ fabsf(newFrameAirtime - _singleFrameAirtimeMs) >= 1.0f;
+
+ _bitrate = newBitrate;
+ _singleFrameAirtimeMs = newFrameAirtime;
+ _splitRxTimeoutMs = newTimeout;
+
+ if (changed) {
+ Serial.printf("[LORA_IF] timing: bitrate=%lu bps frame=%.0fms split_timeout=%lums ldro=%s\n",
+ (unsigned long)_bitrate,
+ _singleFrameAirtimeMs,
+ _splitRxTimeoutMs,
+ _radio->lowDataRateEnabled() ? "on" : "off");
+ }
+}
diff --git a/src/transport/LoRaInterface.h b/src/transport/LoRaInterface.h
index 0211962..b992cdf 100644
--- a/src/transport/LoRaInterface.h
+++ b/src/transport/LoRaInterface.h
@@ -23,12 +23,17 @@ public:
int lastRxRssi() const { return _lastRxRssi; }
float lastRxSnr() const { return _lastRxSnr; }
bool isOnline() const { return _online; }
+ unsigned long splitRxTimeoutMs() const { return _splitRxTimeoutMs; }
+ float singleFrameAirtimeMs() const { return _singleFrameAirtimeMs; }
+ uint32_t bitrate() const { return _bitrate; }
protected:
virtual void send_outgoing(const RNS::Bytes& data) override;
private:
void transmitNow(const RNS::Bytes& data);
+ void refreshRadioTiming(bool forceLog = false);
+ unsigned long computeSplitRxTimeoutMs(float frameAirtimeMs) const;
SX1262* _radio;
bool _txPending = false;
@@ -44,11 +49,17 @@ private:
uint8_t _splitTxHeader = 0;
// Split-packet RX state: reassemble two LoRa frames into one Reticulum packet
- static constexpr unsigned long SPLIT_RX_TIMEOUT_MS = 5000;
+ static constexpr unsigned long SPLIT_RX_TIMEOUT_FLOOR_MS = 5000;
+ static constexpr unsigned long SPLIT_RX_TIMEOUT_CEIL_MS = 60000;
+ static constexpr unsigned long SPLIT_RX_TIMEOUT_MARGIN_MS = 2000;
+ static constexpr float SPLIT_RX_TIMEOUT_MULT = 1.5f;
bool _splitRxPending = false;
uint8_t _splitRxSeq = 0;
RNS::Bytes _splitRxBuffer;
unsigned long _splitRxTimestamp = 0;
+ unsigned long _splitRxTimeoutMs = SPLIT_RX_TIMEOUT_FLOOR_MS;
+ float _singleFrameAirtimeMs = 0;
+ unsigned long _lastTimingRefreshMs = 0;
int _lastRxRssi = 0;
float _lastRxSnr = 0;
diff --git a/src/transport/WiFiInterface.cpp b/src/transport/WiFiInterface.cpp
index 0ed153e..c611c6a 100644
--- a/src/transport/WiFiInterface.cpp
+++ b/src/transport/WiFiInterface.cpp
@@ -37,7 +37,7 @@ void WiFiInterface::startAP() {
if (_apSSID.isEmpty()) {
uint32_t chip = ESP.getEfuseMac() & 0xFFFF;
char ssid[32];
- snprintf(ssid, sizeof(ssid), "ratdeck-%04x", chip);
+ snprintf(ssid, sizeof(ssid), "rsdeck-%04x", chip);
_apSSID = ssid;
}
diff --git a/src/ui/LvStatusBar.cpp b/src/ui/LvStatusBar.cpp
index 066ca28..d2bef34 100644
--- a/src/ui/LvStatusBar.cpp
+++ b/src/ui/LvStatusBar.cpp
@@ -32,7 +32,7 @@ void LvStatusBar::create(lv_obj_t* parent) {
_lblTime = lv_label_create(_bar);
lv_obj_set_size(_lblTime, kTimeW, Theme::STATUS_BAR_H);
lv_label_set_long_mode(_lblTime, LV_LABEL_LONG_CLIP);
- lv_obj_set_style_text_font(_lblTime, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(_lblTime, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_align(_lblTime, LV_TEXT_ALIGN_LEFT, 0);
lv_label_set_text(_lblTime, "--:--");
lv_obj_align(_lblTime, LV_ALIGN_LEFT_MID, 4, 0);
@@ -42,14 +42,14 @@ void LvStatusBar::create(lv_obj_t* parent) {
lv_obj_set_size(_lblLinks, kLinksW, Theme::STATUS_BAR_H);
lv_label_set_long_mode(_lblLinks, LV_LABEL_LONG_CLIP);
lv_label_set_recolor(_lblLinks, true);
- lv_obj_set_style_text_font(_lblLinks, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_lblLinks, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_align(_lblLinks, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(_lblLinks, LV_ALIGN_LEFT_MID, kLinksX, 0);
// Right: battery percentage.
_lblBatt = lv_label_create(_bar);
lv_obj_set_size(_lblBatt, kBattW, Theme::STATUS_BAR_H);
- lv_obj_set_style_text_font(_lblBatt, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_lblBatt, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_align(_lblBatt, LV_TEXT_ALIGN_RIGHT, 0);
lv_label_set_long_mode(_lblBatt, LV_LABEL_LONG_CLIP);
lv_obj_align(_lblBatt, LV_ALIGN_RIGHT_MID, -4, 0);
@@ -66,7 +66,7 @@ void LvStatusBar::create(lv_obj_t* parent) {
_lblToast = lv_label_create(_toast);
lv_obj_set_width(_lblToast, Theme::SCREEN_W - 12);
lv_label_set_long_mode(_lblToast, LV_LABEL_LONG_DOT);
- lv_obj_set_style_text_font(_lblToast, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(_lblToast, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(_lblToast, lv_color_hex(Theme::BG), 0);
lv_obj_set_style_text_align(_lblToast, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_center(_lblToast);
diff --git a/src/ui/LvTabBar.cpp b/src/ui/LvTabBar.cpp
index 5f7afd7..b40990f 100644
--- a/src/ui/LvTabBar.cpp
+++ b/src/ui/LvTabBar.cpp
@@ -49,7 +49,7 @@ void LvTabBar::create(lv_obj_t* parent) {
_labels[i] = lv_label_create(_cells[i]);
lv_obj_set_size(_labels[i], Theme::TAB_W - 4, 14);
lv_label_set_long_mode(_labels[i], LV_LABEL_LONG_CLIP);
- lv_obj_set_style_text_font(_labels[i], &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_labels[i], &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_align(_labels[i], LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text(_labels[i], TAB_NAMES[i]);
lv_obj_align(_labels[i], LV_ALIGN_CENTER, 0, 2);
@@ -63,7 +63,7 @@ void LvTabBar::create(lv_obj_t* parent) {
_badgeLabels[i] = lv_label_create(_badges[i]);
lv_label_set_long_mode(_badgeLabels[i], LV_LABEL_LONG_CLIP);
- lv_obj_set_style_text_font(_badgeLabels[i], &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_badgeLabels[i], &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_align(_badgeLabels[i], LV_TEXT_ALIGN_CENTER, 0);
lv_obj_center(_badgeLabels[i]);
}
diff --git a/src/ui/LvTheme.cpp b/src/ui/LvTheme.cpp
index 5df98a9..2082f6c 100644
--- a/src/ui/LvTheme.cpp
+++ b/src/ui/LvTheme.cpp
@@ -120,23 +120,23 @@ void init(lv_disp_t* disp) {
// Screen background (LV_USE_THEME_DEFAULT is disabled in lv_conf.h).
lv_style_init(&s_screen);
lv_style_set_bg_opa(&s_screen, LV_OPA_COVER);
- lv_style_set_text_font(&s_screen, &lv_font_ratdeck_14);
+ lv_style_set_text_font(&s_screen, &lv_font_rsdeck_14);
// Labels
lv_style_init(&s_label);
- lv_style_set_text_font(&s_label, &lv_font_ratdeck_14);
+ lv_style_set_text_font(&s_label, &lv_font_rsdeck_14);
lv_style_init(&s_labelMuted);
- lv_style_set_text_font(&s_labelMuted, &lv_font_ratdeck_12);
+ lv_style_set_text_font(&s_labelMuted, &lv_font_rsdeck_12);
lv_style_init(&s_labelAccent);
- lv_style_set_text_font(&s_labelAccent, &lv_font_ratdeck_14);
+ lv_style_set_text_font(&s_labelAccent, &lv_font_rsdeck_14);
// Buttons
lv_style_init(&s_btn);
lv_style_set_bg_opa(&s_btn, LV_OPA_COVER);
lv_style_set_border_width(&s_btn, 1);
- lv_style_set_text_font(&s_btn, &lv_font_ratdeck_12);
+ lv_style_set_text_font(&s_btn, &lv_font_rsdeck_12);
lv_style_set_radius(&s_btn, 3);
lv_style_set_pad_top(&s_btn, 5);
lv_style_set_pad_bottom(&s_btn, 5);
@@ -171,7 +171,7 @@ void init(lv_disp_t* disp) {
lv_style_init(&s_textarea);
lv_style_set_bg_opa(&s_textarea, LV_OPA_COVER);
lv_style_set_border_width(&s_textarea, 1);
- lv_style_set_text_font(&s_textarea, &lv_font_ratdeck_14);
+ lv_style_set_text_font(&s_textarea, &lv_font_rsdeck_14);
lv_style_set_radius(&s_textarea, 3);
lv_style_set_pad_all(&s_textarea, 6);
@@ -189,7 +189,7 @@ void init(lv_disp_t* disp) {
// List items
lv_style_init(&s_listBtn);
lv_style_set_bg_opa(&s_listBtn, LV_OPA_COVER);
- lv_style_set_text_font(&s_listBtn, &lv_font_ratdeck_12);
+ lv_style_set_text_font(&s_listBtn, &lv_font_rsdeck_12);
lv_style_set_border_width(&s_listBtn, 1);
lv_style_set_border_side(&s_listBtn, LV_BORDER_SIDE_BOTTOM);
lv_style_set_pad_top(&s_listBtn, 5);
@@ -208,7 +208,7 @@ void init(lv_disp_t* disp) {
lv_style_init(&s_dropdown);
lv_style_set_bg_opa(&s_dropdown, LV_OPA_COVER);
lv_style_set_border_width(&s_dropdown, 1);
- lv_style_set_text_font(&s_dropdown, &lv_font_ratdeck_12);
+ lv_style_set_text_font(&s_dropdown, &lv_font_rsdeck_12);
lv_style_set_radius(&s_dropdown, 3);
lv_style_set_pad_all(&s_dropdown, 4);
@@ -220,7 +220,7 @@ void init(lv_disp_t* disp) {
// Section header (for list section dividers like "Contacts (3)")
lv_style_init(&s_sectionHeader);
lv_style_set_bg_opa(&s_sectionHeader, LV_OPA_TRANSP);
- lv_style_set_text_font(&s_sectionHeader, &lv_font_ratdeck_10);
+ lv_style_set_text_font(&s_sectionHeader, &lv_font_rsdeck_10);
lv_style_set_text_letter_space(&s_sectionHeader, 0);
lv_style_set_border_width(&s_sectionHeader, 1);
lv_style_set_border_side(&s_sectionHeader, LV_BORDER_SIDE_BOTTOM);
@@ -249,7 +249,7 @@ void init(lv_disp_t* disp) {
// Roller - no border so selected row highlight extends edge-to-edge
lv_style_init(&s_roller);
lv_style_set_bg_opa(&s_roller, LV_OPA_COVER);
- lv_style_set_text_font(&s_roller, &lv_font_ratdeck_14);
+ lv_style_set_text_font(&s_roller, &lv_font_rsdeck_14);
lv_style_set_border_width(&s_roller, 0);
// Persistent shell bars.
@@ -262,7 +262,7 @@ void init(lv_disp_t* disp) {
lv_style_init(&s_statusToast);
lv_style_set_bg_opa(&s_statusToast, LV_OPA_COVER);
- lv_style_set_text_font(&s_statusToast, &lv_font_ratdeck_12);
+ lv_style_set_text_font(&s_statusToast, &lv_font_rsdeck_12);
lv_style_set_pad_all(&s_statusToast, 0);
lv_style_set_radius(&s_statusToast, 0);
@@ -277,7 +277,7 @@ void init(lv_disp_t* disp) {
lv_style_init(&s_tabCell);
lv_style_set_bg_opa(&s_tabCell, LV_OPA_COVER);
lv_style_set_border_width(&s_tabCell, 0);
- lv_style_set_text_font(&s_tabCell, &lv_font_ratdeck_10);
+ lv_style_set_text_font(&s_tabCell, &lv_font_rsdeck_10);
lv_style_set_pad_all(&s_tabCell, 0);
lv_style_set_radius(&s_tabCell, 0);
@@ -288,7 +288,7 @@ void init(lv_disp_t* disp) {
lv_style_init(&s_badge);
lv_style_set_bg_opa(&s_badge, LV_OPA_COVER);
- lv_style_set_text_font(&s_badge, &lv_font_ratdeck_10);
+ lv_style_set_text_font(&s_badge, &lv_font_rsdeck_10);
lv_style_set_radius(&s_badge, 3);
lv_style_set_pad_all(&s_badge, 0);
@@ -297,7 +297,7 @@ void init(lv_disp_t* disp) {
// Apply screen style to default theme
lv_obj_add_style(lv_scr_act(), &s_screen, 0);
- Serial.println("[LVGL] Ratdeck field-console theme initialized");
+ Serial.println("[LVGL] rsDeck field-console theme initialized");
}
void refresh() {
diff --git a/src/ui/LvTheme.h b/src/ui/LvTheme.h
index 8b0757d..8dd08dd 100644
--- a/src/ui/LvTheme.h
+++ b/src/ui/LvTheme.h
@@ -2,7 +2,7 @@
#include
-// Ratdeck LVGL theme: compact field-console styles for LVGL 8.3.
+// rsDeck LVGL theme: compact field-console styles for LVGL 8.3.
namespace LvTheme {
void init(lv_disp_t* disp);
diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp
index 5b4bd44..319e68f 100644
--- a/src/ui/Theme.cpp
+++ b/src/ui/Theme.cpp
@@ -1,6 +1,6 @@
#include "Theme.h"
-// Dark column is the original Ratdeck palette, verbatim. Light column is
+// Dark column is the original rsDeck palette, verbatim. Light column is
// derived: same hue family, brand greens/cyan darkened for light-bg contrast.
#define THEME_COLORS(X) \
X(BG, 0x05080A, 0xF2F6F4) \
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index 276bb55..61f9fd2 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -3,7 +3,7 @@
#include
// =============================================================================
-// Ratdeck design constants
+// rsDeck design constants
// Field-console palette tuned for the 320x240 LVGL surface.
// Colors are runtime-switchable (Dark = original palette, Light = derived);
// the value tables live in Theme.cpp. Identifiers keep their original
diff --git a/src/ui/screens/LvContactsScreen.cpp b/src/ui/screens/LvContactsScreen.cpp
index ce5ffc0..fa4724c 100644
--- a/src/ui/screens/LvContactsScreen.cpp
+++ b/src/ui/screens/LvContactsScreen.cpp
@@ -84,13 +84,13 @@ lv_obj_t* createEmptyState(lv_obj_t* parent) {
lv_obj_clear_flag(shoulders, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* title = lv_label_create(box);
- lv_obj_set_style_text_font(title, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(title, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(title, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_label_set_text(title, "No trusted contacts");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 49);
lv_obj_t* hint = lv_label_create(box);
- lv_obj_set_style_text_font(hint, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(hint, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(hint, "Saved peers appear here");
lv_obj_align(hint, LV_ALIGN_TOP_MID, 0, 70);
@@ -194,13 +194,13 @@ void LvContactsScreen::rebuildList() {
}, LV_EVENT_FOCUSED, nullptr);
lv_obj_t* qrLbl = lv_label_create(qrRow);
- lv_obj_set_style_text_font(qrLbl, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(qrLbl, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(qrLbl, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_text(qrLbl, "Share My QR");
lv_obj_align(qrLbl, LV_ALIGN_LEFT_MID, 12, 0);
lv_obj_t* hintLbl = lv_label_create(qrRow);
- lv_obj_set_style_text_font(hintLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(hintLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(hintLbl, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(hintLbl, "Enter");
lv_obj_align(hintLbl, LV_ALIGN_RIGHT_MID, -12, 0);
@@ -247,7 +247,7 @@ void LvContactsScreen::rebuildList() {
LxmFaceAvatar::render(avatar.canvas, String(hashHex.c_str()));
lv_obj_t* nameLbl = lv_label_create(row);
- lv_obj_set_style_text_font(nameLbl, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(nameLbl, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(nameLbl, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_long_mode(nameLbl, LV_LABEL_LONG_CLIP);
lv_obj_set_width(nameLbl, Theme::CONTENT_W - kContactTextX - 72);
@@ -256,7 +256,7 @@ void LvContactsScreen::rebuildList() {
lv_obj_set_pos(nameLbl, kContactTextX, 2);
lv_obj_t* metaLbl = lv_label_create(row);
- lv_obj_set_style_text_font(metaLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(metaLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(metaLbl, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_obj_set_style_text_align(metaLbl, LV_TEXT_ALIGN_RIGHT, 0);
lv_label_set_long_mode(metaLbl, LV_LABEL_LONG_CLIP);
@@ -266,7 +266,7 @@ void LvContactsScreen::rebuildList() {
lv_obj_set_pos(metaLbl, Theme::CONTENT_W - 72, 5);
lv_obj_t* idLbl = lv_label_create(row);
- lv_obj_set_style_text_font(idLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(idLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(idLbl, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_long_mode(idLbl, LV_LABEL_LONG_CLIP);
lv_obj_set_width(idLbl, Theme::CONTENT_W - kContactTextX - 8);
diff --git a/src/ui/screens/LvDataCleanScreen.cpp b/src/ui/screens/LvDataCleanScreen.cpp
index 4bae772..b35331e 100644
--- a/src/ui/screens/LvDataCleanScreen.cpp
+++ b/src/ui/screens/LvDataCleanScreen.cpp
@@ -61,7 +61,7 @@ void LvDataCleanScreen::createUI(lv_obj_t* parent) {
_selectedYes = false;
_confirmWipe = false;
- lv_obj_t* eyebrow = makeLabel(parent, "STORAGE CHECK", &lv_font_ratdeck_10,
+ lv_obj_t* eyebrow = makeLabel(parent, "STORAGE CHECK", &lv_font_rsdeck_10,
Theme::TEXT_SECONDARY, 220, LV_TEXT_ALIGN_CENTER,
LV_LABEL_LONG_DOT);
lv_obj_align(eyebrow, LV_ALIGN_TOP_MID, 0, 10);
@@ -72,17 +72,17 @@ void LvDataCleanScreen::createUI(lv_obj_t* parent) {
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 28);
lv_obj_t* msg = makeLabel(parent,
- "Ratdeck found older data on the SD card.",
- &lv_font_ratdeck_12, Theme::TEXT_PRIMARY, 268, LV_TEXT_ALIGN_CENTER);
+ "rsDeck found older data on the SD card.",
+ &lv_font_rsdeck_12, Theme::TEXT_PRIMARY, 268, LV_TEXT_ALIGN_CENTER);
lv_obj_align(msg, LV_ALIGN_TOP_MID, 0, 58);
lv_obj_t* prompt = makeLabel(parent,
"Keeping data is safest. Erase only for a clean setup.",
- &lv_font_ratdeck_12, Theme::TEXT_SECONDARY, 276, LV_TEXT_ALIGN_CENTER);
+ &lv_font_rsdeck_12, Theme::TEXT_SECONDARY, 276, LV_TEXT_ALIGN_CENTER);
lv_obj_align(prompt, LV_ALIGN_TOP_MID, 0, 82);
_noBox = makeChoice(parent, 28, 122);
- _noLabel = makeLabel(_noBox, "KEEP DATA", &lv_font_ratdeck_12,
+ _noLabel = makeLabel(_noBox, "KEEP DATA", &lv_font_rsdeck_12,
Theme::TEXT_PRIMARY, 112, LV_TEXT_ALIGN_CENTER,
LV_LABEL_LONG_DOT);
lv_obj_center(_noLabel);
@@ -96,7 +96,7 @@ void LvDataCleanScreen::createUI(lv_obj_t* parent) {
}, LV_EVENT_CLICKED, this);
_yesBox = makeChoice(parent, 166, 122);
- _yesLabel = makeLabel(_yesBox, "ERASE", &lv_font_ratdeck_12,
+ _yesLabel = makeLabel(_yesBox, "ERASE", &lv_font_rsdeck_12,
Theme::TEXT_PRIMARY, 112, LV_TEXT_ALIGN_CENTER,
LV_LABEL_LONG_DOT);
lv_obj_center(_yesLabel);
@@ -112,26 +112,26 @@ void LvDataCleanScreen::createUI(lv_obj_t* parent) {
if (self->_doneCb) self->_doneCb(true);
}, LV_EVENT_CLICKED, this);
- _confirmLabel = makeLabel(parent, "", &lv_font_ratdeck_12,
+ _confirmLabel = makeLabel(parent, "", &lv_font_rsdeck_12,
Theme::WARNING_CLR, 280, LV_TEXT_ALIGN_CENTER);
lv_obj_align(_confirmLabel, LV_ALIGN_TOP_MID, 0, 164);
_hintLabel = makeLabel(parent, "Left/Right choose Enter continues",
- &lv_font_ratdeck_12, Theme::ACCENT,
+ &lv_font_rsdeck_12, Theme::ACCENT,
286, LV_TEXT_ALIGN_CENTER, LV_LABEL_LONG_DOT);
lv_obj_align(_hintLabel, LV_ALIGN_TOP_MID, 0, 188);
- _statusLabel = makeLabel(parent, "", &lv_font_ratdeck_12,
+ _statusLabel = makeLabel(parent, "", &lv_font_rsdeck_12,
Theme::PRIMARY, 286, LV_TEXT_ALIGN_CENTER);
lv_obj_align(_statusLabel, LV_ALIGN_TOP_MID, 0, 140);
lv_obj_add_flag(_statusLabel, LV_OBJ_FLAG_HIDDEN);
updateSelection();
- lv_obj_t* ver = makeLabel(parent, "", &lv_font_ratdeck_10,
+ lv_obj_t* ver = makeLabel(parent, "", &lv_font_rsdeck_10,
Theme::TEXT_MUTED, 120, LV_TEXT_ALIGN_CENTER,
LV_LABEL_LONG_DOT);
char verBuf[32];
- snprintf(verBuf, sizeof(verBuf), "v%s", RATDECK_VERSION_STRING);
+ snprintf(verBuf, sizeof(verBuf), "v%s", RSDECK_VERSION_STRING);
lv_label_set_text(ver, verBuf);
lv_obj_align(ver, LV_ALIGN_BOTTOM_MID, 0, -10);
}
diff --git a/src/ui/screens/LvHelpOverlay.cpp b/src/ui/screens/LvHelpOverlay.cpp
index 3ab377b..ad5767a 100644
--- a/src/ui/screens/LvHelpOverlay.cpp
+++ b/src/ui/screens/LvHelpOverlay.cpp
@@ -34,8 +34,8 @@ lv_obj_t* makeRow(lv_obj_t* parent) {
void addHelpRow(lv_obj_t* parent, const char* keys, const char* action) {
lv_obj_t* row = makeRow(parent);
- makeLabel(row, keys, &lv_font_ratdeck_10, Theme::ACCENT, 72, LV_TEXT_ALIGN_LEFT);
- makeLabel(row, action, &lv_font_ratdeck_10, Theme::TEXT_PRIMARY, 178, LV_TEXT_ALIGN_LEFT);
+ makeLabel(row, keys, &lv_font_rsdeck_10, Theme::ACCENT, 72, LV_TEXT_ALIGN_LEFT);
+ makeLabel(row, action, &lv_font_rsdeck_10, Theme::TEXT_PRIMARY, 178, LV_TEXT_ALIGN_LEFT);
}
} // namespace
@@ -60,8 +60,8 @@ void LvHelpOverlay::create() {
lv_obj_t* header = makeRow(_overlay);
lv_obj_set_height(header, 16);
- makeLabel(header, "HELP", &lv_font_ratdeck_12, Theme::ACCENT, 70, LV_TEXT_ALIGN_LEFT);
- makeLabel(header, "Ratspeak.org", &lv_font_ratdeck_10,
+ makeLabel(header, "HELP", &lv_font_rsdeck_12, Theme::ACCENT, 70, LV_TEXT_ALIGN_LEFT);
+ makeLabel(header, "Ratspeak.org", &lv_font_rsdeck_10,
Theme::TEXT_SECONDARY, 180, LV_TEXT_ALIGN_RIGHT);
addHelpRow(_overlay, "Trackball", "Move selection");
@@ -75,7 +75,7 @@ void LvHelpOverlay::create() {
addHelpRow(_overlay, "Ctrl+D/I/T/R", "Diagnostics tools");
lv_obj_t* footer = makeLabel(_overlay, "Any key or tap closes",
- &lv_font_ratdeck_10, Theme::TEXT_MUTED,
+ &lv_font_rsdeck_10, Theme::TEXT_MUTED,
260, LV_TEXT_ALIGN_CENTER);
lv_obj_set_style_pad_top(footer, 2, 0);
diff --git a/src/ui/screens/LvHomeScreen.cpp b/src/ui/screens/LvHomeScreen.cpp
index e78eb9f..3442f24 100644
--- a/src/ui/screens/LvHomeScreen.cpp
+++ b/src/ui/screens/LvHomeScreen.cpp
@@ -60,7 +60,7 @@ lv_obj_t* makeLabel(lv_obj_t* parent, lv_coord_t x, lv_coord_t y, lv_coord_t w,
}
lv_obj_t* makeCaption(lv_obj_t* parent, const char* text) {
- lv_obj_t* lbl = makeLabel(parent, 7, 4, 84, &lv_font_ratdeck_10,
+ lv_obj_t* lbl = makeLabel(parent, 7, 4, 84, &lv_font_rsdeck_10,
Theme::TEXT_MUTED, text);
lv_obj_set_style_text_letter_space(lbl, 1, 0);
return lbl;
@@ -71,7 +71,7 @@ lv_obj_t* makeChip(lv_obj_t* parent, lv_coord_t x, const char* title,
lv_obj_t* chip = makePanel(parent, x, kChipY, kCellW, kChipH,
Theme::BG_ELEVATED, Theme::BORDER);
makeCaption(chip, title);
- *valueLabel = makeLabel(chip, 7, 18, 84, &lv_font_ratdeck_12,
+ *valueLabel = makeLabel(chip, 7, 18, 84, &lv_font_rsdeck_12,
Theme::TEXT_SECONDARY, "...");
return chip;
}
@@ -81,7 +81,7 @@ lv_obj_t* makeStat(lv_obj_t* parent, lv_coord_t x, const char* title,
lv_obj_t* stat = makePanel(parent, x, kStatY, kCellW, kStatH,
Theme::BG_ELEVATED, Theme::BORDER);
makeCaption(stat, title);
- *valueLabel = makeLabel(stat, 7, 17, 84, &lv_font_ratdeck_14,
+ *valueLabel = makeLabel(stat, 7, 17, 84, &lv_font_rsdeck_14,
Theme::TEXT_PRIMARY, "0");
return stat;
}
@@ -172,23 +172,23 @@ void LvHomeScreen::createUI(lv_obj_t* parent) {
_avatarSeed = "";
_lblConsoleTitle = makeLabel(_identityPanel, 58, 6, 116,
- &lv_font_ratdeck_10, Theme::ACCENT,
+ &lv_font_rsdeck_10, Theme::ACCENT,
"IDENTITY:");
lv_obj_set_style_text_letter_space(_lblConsoleTitle, 1, 0);
- _lblStatus = makeLabel(_identityPanel, 184, 6, 114, &lv_font_ratdeck_10,
+ _lblStatus = makeLabel(_identityPanel, 184, 6, 114, &lv_font_rsdeck_10,
Theme::TEXT_SECONDARY, "OFFLINE",
LV_TEXT_ALIGN_RIGHT);
lv_obj_set_style_text_letter_space(_lblStatus, 1, 0);
- _lblName = makeLabel(_identityPanel, 58, 21, 240, &lv_font_ratdeck_14,
+ _lblName = makeLabel(_identityPanel, 58, 21, 240, &lv_font_rsdeck_14,
Theme::TEXT_PRIMARY, "...");
lv_label_set_long_mode(_lblName, LV_LABEL_LONG_DOT);
- _lblId = makeLabel(_identityPanel, 58, 41, 136, &lv_font_ratdeck_10,
+ _lblId = makeLabel(_identityPanel, 58, 41, 136, &lv_font_rsdeck_10,
Theme::TEXT_SECONDARY, "LXMF ----");
_lblIdentity = makeLabel(_identityPanel, 198, 41, 100,
- &lv_font_ratdeck_10, Theme::TEXT_MUTED,
+ &lv_font_rsdeck_10, Theme::TEXT_MUTED,
"ID ----", LV_TEXT_ALIGN_RIGHT);
_chipLora = makeChip(parent, kPad, "LORA", &_lblLoraState);
@@ -226,10 +226,10 @@ void LvHomeScreen::createUI(lv_obj_t* parent) {
lv_obj_t* footer = makePanel(parent, kPad, kFooterY, 198, kFooterH,
Theme::BG_ELEVATED, Theme::BORDER);
- _lblSummary = makeLabel(footer, 8, 6, 182, &lv_font_ratdeck_12,
+ _lblSummary = makeLabel(footer, 8, 6, 182, &lv_font_rsdeck_12,
Theme::TEXT_SECONDARY, "Waiting for transport");
lv_label_set_long_mode(_lblSummary, LV_LABEL_LONG_DOT);
- _lblLastAnnounce = makeLabel(footer, 8, 23, 182, &lv_font_ratdeck_10,
+ _lblLastAnnounce = makeLabel(footer, 8, 23, 182, &lv_font_rsdeck_10,
Theme::TEXT_MUTED, "Announced: never");
lv_label_set_long_mode(_lblLastAnnounce, LV_LABEL_LONG_CLIP);
@@ -246,7 +246,7 @@ void LvHomeScreen::createUI(lv_obj_t* parent) {
lv_obj_set_style_pad_all(_btnAnnounce, 0, 0);
_lblAnnounceAction = lv_label_create(_btnAnnounce);
- lv_obj_set_style_text_font(_lblAnnounceAction, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(_lblAnnounceAction, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(_lblAnnounceAction, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_text(_lblAnnounceAction, "ANNOUNCE");
lv_obj_center(_lblAnnounceAction);
@@ -291,7 +291,7 @@ void LvHomeScreen::refreshUI() {
String dh = _rns->destinationHashHex();
displayName = "Ratspeak.org-" + dh.substring(0, 3);
} else {
- displayName = "Ratdeck";
+ displayName = "rsDeck";
}
lv_label_set_text(_lblName, displayName.c_str());
diff --git a/src/ui/screens/LvMapScreen.cpp b/src/ui/screens/LvMapScreen.cpp
index 1bcf6d6..dd70e96 100644
--- a/src/ui/screens/LvMapScreen.cpp
+++ b/src/ui/screens/LvMapScreen.cpp
@@ -7,7 +7,7 @@ void LvMapScreen::createUI(lv_obj_t* parent) {
lv_obj_clear_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* lbl = lv_label_create(parent);
- lv_obj_set_style_text_font(lbl, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(lbl, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(lbl, "Map\n\nComing soon\n\nNode topology view\nwill appear here");
lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, 0);
diff --git a/src/ui/screens/LvMessageView.cpp b/src/ui/screens/LvMessageView.cpp
index 6578741..3914178 100644
--- a/src/ui/screens/LvMessageView.cpp
+++ b/src/ui/screens/LvMessageView.cpp
@@ -155,7 +155,7 @@ void LvMessageView::createUI(lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_row(parent, 0, 0);
- const lv_font_t* font = &lv_font_ratdeck_12;
+ const lv_font_t* font = &lv_font_rsdeck_12;
// Header bar (top)
_header = lv_obj_create(parent);
@@ -170,27 +170,27 @@ void LvMessageView::createUI(lv_obj_t* parent) {
lv_obj_clear_flag(_header, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* backLbl = lv_label_create(_header);
- lv_obj_set_style_text_font(backLbl, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(backLbl, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(backLbl, lv_color_hex(Theme::PRIMARY), 0);
lv_label_set_text(backLbl, "<");
lv_obj_align(backLbl, LV_ALIGN_LEFT_MID, 6, 0);
_lblHeader = lv_label_create(_header);
- lv_obj_set_style_text_font(_lblHeader, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(_lblHeader, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(_lblHeader, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_long_mode(_lblHeader, LV_LABEL_LONG_DOT);
lv_obj_set_width(_lblHeader, 188);
lv_obj_set_pos(_lblHeader, 22, 3);
_lblHeaderMeta = lv_label_create(_header);
- lv_obj_set_style_text_font(_lblHeaderMeta, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_lblHeaderMeta, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(_lblHeaderMeta, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_label_set_long_mode(_lblHeaderMeta, LV_LABEL_LONG_DOT);
lv_obj_set_width(_lblHeaderMeta, Theme::CONTENT_W - 30);
lv_obj_set_pos(_lblHeaderMeta, 22, 21);
_lblHeaderState = lv_label_create(_header);
- lv_obj_set_style_text_font(_lblHeaderState, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_lblHeaderState, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(_lblHeaderState, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(_lblHeaderState, "UNKNOWN");
lv_obj_align(_lblHeaderState, LV_ALIGN_TOP_RIGHT, -8, 8);
@@ -255,7 +255,7 @@ void LvMessageView::createUI(lv_obj_t* parent) {
lv_obj_add_style(_btnSend, LvTheme::styleBtn(), 0);
lv_obj_set_style_pad_all(_btnSend, 0, 0);
lv_obj_t* sendLbl = lv_label_create(_btnSend);
- lv_obj_set_style_text_font(sendLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(sendLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(sendLbl, lv_color_hex(Theme::PRIMARY), 0);
lv_label_set_text(sendLbl, "SEND");
lv_obj_center(sendLbl);
@@ -384,7 +384,7 @@ void LvMessageView::refreshUI() {
void LvMessageView::appendMessage(const LXMFMessage& msg) {
if (!_msgScroll) return;
- const lv_font_t* font = &lv_font_ratdeck_12;
+ const lv_font_t* font = &lv_font_rsdeck_12;
int textW = textWidthForBubble(msg.content);
if (!msg.incoming && textW < 96) textW = 96;
int boxW = textW + 16;
@@ -455,7 +455,7 @@ void LvMessageView::appendMessage(const LXMFMessage& msg) {
if (hasTime) {
lv_obj_t* timeLbl = lv_label_create(meta);
- lv_obj_set_style_text_font(timeLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(timeLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(timeLbl, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(timeLbl, timeBuf);
lv_obj_align(timeLbl, LV_ALIGN_LEFT_MID, 0, 0);
@@ -463,7 +463,7 @@ void LvMessageView::appendMessage(const LXMFMessage& msg) {
if (!msg.incoming) {
statusLbl = lv_label_create(meta);
- lv_obj_set_style_text_font(statusLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(statusLbl, &lv_font_rsdeck_10, 0);
applyStatusGlyph(statusLbl, msg.status);
lv_obj_align(statusLbl, LV_ALIGN_RIGHT_MID, 0, 0);
}
@@ -519,13 +519,13 @@ void LvMessageView::rebuildMessages() {
lv_obj_align(icon, LV_ALIGN_TOP_MID, 0, 13);
lv_obj_t* title = lv_label_create(empty);
- lv_obj_set_style_text_font(title, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(title, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(title, lv_color_hex(Theme::TEXT_PRIMARY), 0);
lv_label_set_text(title, "No messages yet");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 32);
lv_obj_t* sub = lv_label_create(empty);
- lv_obj_set_style_text_font(sub, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(sub, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(sub, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_label_set_text(sub, "Thread is quiet");
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 54);
@@ -724,7 +724,7 @@ void LvMessageView::showSendModeMenu() {
lv_obj_clear_flag(_sendOverlay, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* title = lv_label_create(_sendOverlay);
- lv_obj_set_style_text_font(title, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(title, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(title, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_text(title, "Send mode");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 0);
@@ -748,7 +748,7 @@ void LvMessageView::showSendModeMenu() {
}, LV_EVENT_CLICKED, this);
_sendLabels[i] = lv_label_create(row);
- lv_obj_set_style_text_font(_sendLabels[i], &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(_sendLabels[i], &lv_font_rsdeck_12, 0);
lv_label_set_text(_sendLabels[i], labels[i]);
lv_obj_center(_sendLabels[i]);
_sendRows[i] = row;
diff --git a/src/ui/screens/LvMessagesScreen.cpp b/src/ui/screens/LvMessagesScreen.cpp
index fdbead4..6fd80a6 100644
--- a/src/ui/screens/LvMessagesScreen.cpp
+++ b/src/ui/screens/LvMessagesScreen.cpp
@@ -121,13 +121,13 @@ void LvMessagesScreen::createUI(lv_obj_t* parent) {
lv_obj_align(emptyIcon, LV_ALIGN_TOP_MID, 0, 15);
lv_obj_t* emptyTitle = lv_label_create(_lblEmpty);
- lv_obj_set_style_text_font(emptyTitle, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(emptyTitle, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(emptyTitle, lv_color_hex(Theme::TEXT_PRIMARY), 0);
lv_label_set_text(emptyTitle, "No conversations");
lv_obj_align(emptyTitle, LV_ALIGN_TOP_MID, 0, 34);
lv_obj_t* emptySub = lv_label_create(_lblEmpty);
- lv_obj_set_style_text_font(emptySub, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(emptySub, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(emptySub, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_label_set_text(emptySub, "LXMF inbox is clear");
lv_obj_align(emptySub, LV_ALIGN_TOP_MID, 0, 56);
@@ -241,8 +241,8 @@ void LvMessagesScreen::rebuildList() {
for (auto& ci : _sortedConvs) _sortedPeers.push_back(ci.peerHex);
// Build list items with focus group support
- const lv_font_t* nameFont = &lv_font_ratdeck_14;
- const lv_font_t* smallFont = &lv_font_ratdeck_12;
+ const lv_font_t* nameFont = &lv_font_rsdeck_14;
+ const lv_font_t* smallFont = &lv_font_rsdeck_12;
for (int i = 0; i < count; i++) {
const auto& ci = _sortedConvs[i];
@@ -319,7 +319,7 @@ void LvMessagesScreen::rebuildList() {
char timeBuf[8];
if (formatClock(ci.lastTs, timeBuf, sizeof(timeBuf))) {
lv_obj_t* timeLbl = lv_label_create(row);
- lv_obj_set_style_text_font(timeLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(timeLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(timeLbl, lv_color_hex(ci.hasUnread ? Theme::PRIMARY : Theme::TEXT_MUTED), 0);
lv_label_set_text(timeLbl, timeBuf);
lv_obj_align(timeLbl, LV_ALIGN_TOP_RIGHT, -8, 7);
@@ -370,7 +370,7 @@ void LvMessagesScreen::rebuildList() {
lv_obj_clear_flag(chip, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* chipLbl = lv_label_create(chip);
- lv_obj_set_style_text_font(chipLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(chipLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(chipLbl, lv_color_hex(chipColor), 0);
lv_label_set_text(chipLbl, chipBuf);
lv_obj_center(chipLbl);
@@ -430,7 +430,7 @@ void LvMessagesScreen::rebuildActionOverlay(const char* title, const char* const
lv_obj_clear_flag(_actionOverlay, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* titleLbl = lv_label_create(_actionOverlay);
- lv_obj_set_style_text_font(titleLbl, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(titleLbl, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(titleLbl, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_long_mode(titleLbl, LV_LABEL_LONG_DOT);
lv_obj_set_width(titleLbl, 220);
@@ -458,7 +458,7 @@ void LvMessagesScreen::rebuildActionOverlay(const char* title, const char* const
}, LV_EVENT_CLICKED, this);
lv_obj_t* lbl = lv_label_create(row);
- lv_obj_set_style_text_font(lbl, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(lbl, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(Theme::TEXT_PRIMARY), 0);
lv_label_set_long_mode(lbl, LV_LABEL_LONG_DOT);
lv_obj_set_width(lbl, 200);
diff --git a/src/ui/screens/LvNameInputScreen.cpp b/src/ui/screens/LvNameInputScreen.cpp
index 2de2778..4ff674e 100644
--- a/src/ui/screens/LvNameInputScreen.cpp
+++ b/src/ui/screens/LvNameInputScreen.cpp
@@ -46,7 +46,7 @@ lv_obj_t* makeActionButton(lv_obj_t* parent, const char* text, lv_coord_t y) {
lv_obj_set_style_pad_all(btn, 0, 0);
lv_obj_t* lbl = lv_label_create(btn);
- lv_obj_set_style_text_font(lbl, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(lbl, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_long_mode(lbl, LV_LABEL_LONG_DOT);
lv_obj_set_width(lbl, 150);
@@ -66,7 +66,7 @@ void LvNameInputScreen::createUI(lv_obj_t* parent) {
lv_obj_set_style_bg_color(parent, lv_color_hex(Theme::BG), 0);
lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
- lv_obj_t* eyebrow = makeLabel(parent, "FIRST BOOT", &lv_font_ratdeck_10,
+ lv_obj_t* eyebrow = makeLabel(parent, "FIRST BOOT", &lv_font_rsdeck_10,
Theme::TEXT_SECONDARY, 220, LV_TEXT_ALIGN_CENTER,
LV_LABEL_LONG_DOT);
lv_obj_align(eyebrow, LV_ALIGN_TOP_MID, 0, 13);
@@ -79,7 +79,7 @@ void LvNameInputScreen::createUI(lv_obj_t* parent) {
lv_obj_t* prompt = makeLabel(parent,
"Choose the name other operators see on announces.",
- &lv_font_ratdeck_12, Theme::TEXT_PRIMARY, 250, LV_TEXT_ALIGN_CENTER);
+ &lv_font_rsdeck_12, Theme::TEXT_PRIMARY, 250, LV_TEXT_ALIGN_CENTER);
lv_obj_align(prompt, LV_ALIGN_TOP_MID, 0, 70);
_textarea = lv_textarea_create(parent);
@@ -90,13 +90,13 @@ void LvNameInputScreen::createUI(lv_obj_t* parent) {
lv_textarea_set_placeholder_text(_textarea, "Optional");
lv_obj_add_style(_textarea, LvTheme::styleTextarea(), 0);
lv_obj_add_style(_textarea, LvTheme::styleTextareaFocused(), LV_STATE_FOCUSED);
- lv_obj_set_style_text_font(_textarea, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(_textarea, &lv_font_rsdeck_14, 0);
lv_group_add_obj(LvInput::group(), _textarea);
lv_group_focus_obj(_textarea);
lv_obj_t* note = makeLabel(parent,
- "Leave blank to generate a Ratdeck name from this identity.",
- &lv_font_ratdeck_12, Theme::TEXT_SECONDARY, 260, LV_TEXT_ALIGN_CENTER);
+ "Leave blank to generate a rsDeck name from this identity.",
+ &lv_font_rsdeck_12, Theme::TEXT_SECONDARY, 260, LV_TEXT_ALIGN_CENTER);
lv_obj_align(note, LV_ALIGN_TOP_MID, 0, 154);
_doneButton = makeActionButton(parent, "DONE", 184);
@@ -106,11 +106,11 @@ void LvNameInputScreen::createUI(lv_obj_t* parent) {
self->submit(false);
}, LV_EVENT_CLICKED, this);
- lv_obj_t* ver = makeLabel(parent, "", &lv_font_ratdeck_10,
+ lv_obj_t* ver = makeLabel(parent, "", &lv_font_rsdeck_10,
Theme::TEXT_MUTED, 120, LV_TEXT_ALIGN_CENTER,
LV_LABEL_LONG_DOT);
char verBuf[32];
- snprintf(verBuf, sizeof(verBuf), "v%s", RATDECK_VERSION_STRING);
+ snprintf(verBuf, sizeof(verBuf), "v%s", RSDECK_VERSION_STRING);
lv_label_set_text(ver, verBuf);
lv_obj_align(ver, LV_ALIGN_BOTTOM_MID, 0, -10);
}
diff --git a/src/ui/screens/LvNodesScreen.cpp b/src/ui/screens/LvNodesScreen.cpp
index 327c9c3..4c6b248 100644
--- a/src/ui/screens/LvNodesScreen.cpp
+++ b/src/ui/screens/LvNodesScreen.cpp
@@ -80,13 +80,13 @@ lv_obj_t* createEmptyState(lv_obj_t* parent) {
}
lv_obj_t* title = lv_label_create(box);
- lv_obj_set_style_text_font(title, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(title, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(title, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_label_set_text(title, "No peers heard");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 34);
lv_obj_t* hint = lv_label_create(box);
- lv_obj_set_style_text_font(hint, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(hint, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(hint, "Listening for announces");
lv_obj_align(hint, LV_ALIGN_TOP_MID, 0, 55);
@@ -122,21 +122,21 @@ void LvNodesScreen::createUI(lv_obj_t* parent) {
lv_obj_add_flag(_overlay, LV_OBJ_FLAG_HIDDEN);
_overlayTitle = lv_label_create(_overlay);
- lv_obj_set_style_text_font(_overlayTitle, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(_overlayTitle, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(_overlayTitle, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_long_mode(_overlayTitle, LV_LABEL_LONG_CLIP);
lv_obj_set_width(_overlayTitle, 236);
lv_obj_set_pos(_overlayTitle, 12, 9);
_overlayMeta = lv_label_create(_overlay);
- lv_obj_set_style_text_font(_overlayMeta, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_overlayMeta, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(_overlayMeta, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_long_mode(_overlayMeta, LV_LABEL_LONG_CLIP);
lv_obj_set_width(_overlayMeta, 236);
lv_obj_set_pos(_overlayMeta, 12, 29);
_overlayReach = lv_label_create(_overlay);
- lv_obj_set_style_text_font(_overlayReach, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_overlayReach, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(_overlayReach, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_label_set_long_mode(_overlayReach, LV_LABEL_LONG_CLIP);
lv_obj_set_width(_overlayReach, 236);
@@ -166,7 +166,7 @@ void LvNodesScreen::createUI(lv_obj_t* parent) {
}, LV_EVENT_CLICKED, this);
_menuLabels[i] = lv_label_create(btn);
- lv_obj_set_style_text_font(_menuLabels[i], &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(_menuLabels[i], &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(_menuLabels[i], lv_color_hex(Theme::PRIMARY), 0);
lv_label_set_text(_menuLabels[i], menuText[i]);
lv_obj_center(_menuLabels[i]);
@@ -185,13 +185,13 @@ void LvNodesScreen::createUI(lv_obj_t* parent) {
lv_obj_add_flag(_nicknameBox, LV_OBJ_FLAG_HIDDEN);
lv_obj_t* nickTitle = lv_label_create(_nicknameBox);
- lv_obj_set_style_text_font(nickTitle, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(nickTitle, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(nickTitle, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_label_set_text(nickTitle, "Contact name");
lv_obj_set_pos(nickTitle, 0, 0);
_nicknameLbl = lv_label_create(_nicknameBox);
- lv_obj_set_style_text_font(_nicknameLbl, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(_nicknameLbl, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(_nicknameLbl, lv_color_hex(Theme::PRIMARY), 0);
lv_label_set_long_mode(_nicknameLbl, LV_LABEL_LONG_CLIP);
lv_obj_set_width(_nicknameLbl, 232);
@@ -199,7 +199,7 @@ void LvNodesScreen::createUI(lv_obj_t* parent) {
lv_obj_set_pos(_nicknameLbl, 0, 22);
_nicknameHint = lv_label_create(_nicknameBox);
- lv_obj_set_style_text_font(_nicknameHint, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_nicknameHint, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(_nicknameHint, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(_nicknameHint, "Enter saves / Esc keeps");
lv_obj_set_pos(_nicknameHint, 0, 52);
@@ -291,7 +291,7 @@ void LvNodesScreen::rebuildList() {
lv_obj_set_style_pad_all(hdr, 0, 0);
lv_obj_clear_flag(hdr, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
lv_obj_t* lbl = lv_label_create(hdr);
- lv_obj_set_style_text_font(lbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(lbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_text(lbl, text);
lv_obj_set_pos(lbl, 8, 5);
@@ -327,7 +327,7 @@ void LvNodesScreen::rebuildList() {
}, LV_EVENT_FOCUSED, nullptr);
lv_obj_t* nameLbl = lv_label_create(row);
- lv_obj_set_style_text_font(nameLbl, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(nameLbl, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(nameLbl, lv_color_hex(
node.saved ? Theme::ACCENT : Theme::PRIMARY), 0);
lv_label_set_long_mode(nameLbl, LV_LABEL_LONG_CLIP);
@@ -337,7 +337,7 @@ void LvNodesScreen::rebuildList() {
lv_obj_set_pos(nameLbl, 8, 3);
lv_obj_t* infoLbl = lv_label_create(row);
- lv_obj_set_style_text_font(infoLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(infoLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(infoLbl, lv_color_hex(Theme::TEXT_SECONDARY), 0);
lv_obj_set_style_text_align(infoLbl, LV_TEXT_ALIGN_RIGHT, 0);
lv_label_set_long_mode(infoLbl, LV_LABEL_LONG_CLIP);
@@ -347,7 +347,7 @@ void LvNodesScreen::rebuildList() {
lv_obj_set_pos(infoLbl, Theme::CONTENT_W - 124, 5);
lv_obj_t* idLbl = lv_label_create(row);
- lv_obj_set_style_text_font(idLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(idLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(idLbl, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_long_mode(idLbl, LV_LABEL_LONG_CLIP);
lv_obj_set_width(idLbl, Theme::CONTENT_W - 16);
diff --git a/src/ui/screens/LvQrOverlay.cpp b/src/ui/screens/LvQrOverlay.cpp
index 3c45078..ae3b126 100644
--- a/src/ui/screens/LvQrOverlay.cpp
+++ b/src/ui/screens/LvQrOverlay.cpp
@@ -20,7 +20,7 @@ void LvQrOverlay::create() {
lv_obj_clear_flag(_overlay, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* title = lv_label_create(_overlay);
- lv_obj_set_style_text_font(title, &lv_font_ratdeck_14, 0);
+ lv_obj_set_style_text_font(title, &lv_font_rsdeck_14, 0);
lv_obj_set_style_text_color(title, lv_color_hex(Theme::ACCENT), 0);
lv_label_set_text(title, "Scan to add me");
@@ -31,7 +31,7 @@ void LvQrOverlay::create() {
lv_color_hex(0xFFFFFF));
_lblAddr = lv_label_create(_overlay);
- lv_obj_set_style_text_font(_lblAddr, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(_lblAddr, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(_lblAddr, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_long_mode(_lblAddr, LV_LABEL_LONG_WRAP);
lv_obj_set_width(_lblAddr, Theme::SCREEN_W - 12);
@@ -39,7 +39,7 @@ void LvQrOverlay::create() {
lv_label_set_text(_lblAddr, "");
lv_obj_t* hint = lv_label_create(_overlay);
- lv_obj_set_style_text_font(hint, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(hint, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(hint, "Any key to close");
diff --git a/src/ui/screens/LvSettingsScreen.cpp b/src/ui/screens/LvSettingsScreen.cpp
index b42aadb..dbe2844 100644
--- a/src/ui/screens/LvSettingsScreen.cpp
+++ b/src/ui/screens/LvSettingsScreen.cpp
@@ -203,20 +203,20 @@ bool LvSettingsScreen::categoryNeedsReboot(int catIdx) const {
bool LvSettingsScreen::confirmableAction(const SettingItem& item) const {
return labelEq(item.label, "Developer Radio Controls")
|| labelEq(item.label, "Format SD Card")
- || labelEq(item.label, "Erase Ratdeck SD Data")
+ || labelEq(item.label, "Erase rsDeck SD Data")
|| labelEq(item.label, "Erase Device");
}
bool LvSettingsScreen::armedAction(const SettingItem& item) const {
return (_confirmingInitSD && labelEq(item.label, "Format SD Card")) ||
- (_confirmingWipeSD && labelEq(item.label, "Erase Ratdeck SD Data")) ||
+ (_confirmingWipeSD && labelEq(item.label, "Erase rsDeck SD Data")) ||
(_confirmingReset && labelEq(item.label, "Erase Device")) ||
(_confirmingDevMode && labelEq(item.label, "Developer Radio Controls"));
}
bool LvSettingsScreen::destructiveAction(const SettingItem& item) const {
return labelEq(item.label, "Format SD Card")
- || labelEq(item.label, "Erase Ratdeck SD Data")
+ || labelEq(item.label, "Erase rsDeck SD Data")
|| labelEq(item.label, "Erase Device");
}
@@ -255,7 +255,7 @@ void LvSettingsScreen::runFormatSD() {
return;
}
if (_ui) _ui->lvStatusBar().showToast("Formatting SD card...", 2000);
- bool ok = _sd->formatForRatdeck();
+ bool ok = _sd->formatForRsDeck();
if (_ui) _ui->lvStatusBar().showToast(ok ? "SD card formatted" : "SD format failed", 1500);
rebuildItemList();
}
@@ -267,8 +267,8 @@ void LvSettingsScreen::runWipeSD() {
rebuildItemList();
return;
}
- if (_ui) _ui->lvStatusBar().showToast("Erasing Ratdeck SD data...", 2000);
- bool ok = _sd->wipeRatdeck();
+ if (_ui) _ui->lvStatusBar().showToast("Erasing rsDeck SD data...", 2000);
+ bool ok = _sd->wipeRsDeck();
if (_ui) _ui->lvStatusBar().showToast(ok ? "SD data erased" : "SD erase failed", 1500);
rebuildItemList();
}
@@ -276,7 +276,7 @@ void LvSettingsScreen::runWipeSD() {
void LvSettingsScreen::runFactoryReset() {
_confirmingReset = false;
if (_ui) _ui->lvStatusBar().showToast("Erasing device...", 3000);
- if (_sd && _sd->isReady()) _sd->wipeRatdeck();
+ if (_sd && _sd->isReady()) _sd->wipeRsDeck();
if (_flash) _flash->format();
nvs_flash_erase();
delay(1500); // Long enough for key state to clear before reboot
@@ -336,7 +336,7 @@ void LvSettingsScreen::firmwareCheckTask(void* arg) {
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setTimeout(5000);
- if (http.begin("https://api.github.com/repos/ratspeak/ratdeck/releases/latest")) {
+ if (http.begin("https://api.github.com/repos/ratspeak/rsDeck/releases/latest")) {
http.addHeader("Accept", "application/vnd.github.v3+json");
int httpCode = http.GET();
if (httpCode == 200) {
@@ -355,7 +355,7 @@ void LvSettingsScreen::firmwareCheckTask(void* arg) {
delay(10);
}
if (extractReleaseTag(payload, version, sizeof(version))) {
- result = strcmp(version, RATDECK_VERSION_STRING) > 0
+ result = strcmp(version, RSDECK_VERSION_STRING) > 0
? FirmwareCheckState::AVAILABLE
: FirmwareCheckState::CURRENT;
}
@@ -379,7 +379,7 @@ void LvSettingsScreen::buildItems() {
// Identity & Device
int devStart = idx;
_items.push_back({"Firmware", SettingType::READONLY, nullptr, nullptr,
- [](int) { return String(RATDECK_VERSION_STRING); }});
+ [](int) { return String(RSDECK_VERSION_STRING); }});
idx++;
_items.push_back({"LXMF Address", SettingType::READONLY, nullptr, nullptr,
[this](int) { return _destinationHash.length() > 0 ? _destinationHash : String("unknown"); }});
@@ -1005,7 +1005,7 @@ void LvSettingsScreen::buildItems() {
}
{
SettingItem wipeSD;
- wipeSD.label = "Erase Ratdeck SD Data";
+ wipeSD.label = "Erase rsDeck SD Data";
wipeSD.type = SettingType::ACTION;
wipeSD.formatter = [this](int) {
if (!_sd || !_sd->isReady()) return String("No Card");
@@ -1156,7 +1156,7 @@ void LvSettingsScreen::rebuildCategoryList() {
_rowObjs.clear();
lv_obj_clean(_scrollContainer);
- const lv_font_t* font = &lv_font_ratdeck_12;
+ const lv_font_t* font = &lv_font_rsdeck_12;
// Title
lv_obj_t* titleRow = lv_obj_create(_scrollContainer);
@@ -1177,7 +1177,7 @@ void LvSettingsScreen::rebuildCategoryList() {
if (_rebootNeeded) {
lv_obj_t* pendingLbl = lv_label_create(titleRow);
- lv_obj_set_style_text_font(pendingLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(pendingLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(pendingLbl, lv_color_hex(Theme::WARNING_CLR), 0);
lv_label_set_text(pendingLbl, "REBOOT PENDING");
lv_obj_align(pendingLbl, LV_ALIGN_RIGHT_MID, -8, 0);
@@ -1222,7 +1222,7 @@ void LvSettingsScreen::rebuildCategoryList() {
char countBuf[8];
snprintf(countBuf, sizeof(countBuf), "%d", cat.count);
lv_obj_t* countLbl = lv_label_create(row);
- lv_obj_set_style_text_font(countLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(countLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(countLbl, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(countLbl, countBuf);
lv_obj_align(countLbl, LV_ALIGN_TOP_RIGHT, -24, 5);
@@ -1230,7 +1230,7 @@ void LvSettingsScreen::rebuildCategoryList() {
// Summary
if (cat.summary) {
lv_obj_t* sumLbl = lv_label_create(row);
- lv_obj_set_style_text_font(sumLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(sumLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(sumLbl, lv_color_hex(pending ? Theme::WARNING_CLR : Theme::TEXT_MUTED), 0);
clipLabel(sumLbl, Theme::CONTENT_W - 44);
lv_label_set_text(sumLbl, cat.summary().c_str());
@@ -1259,7 +1259,7 @@ void LvSettingsScreen::rebuildItemList() {
_editValueLbl = nullptr; // Invalidate cached label before destroying widgets
lv_obj_clean(_scrollContainer);
- const lv_font_t* font = &lv_font_ratdeck_12;
+ const lv_font_t* font = &lv_font_rsdeck_12;
// Category header
lv_obj_t* headerRow = lv_obj_create(_scrollContainer);
@@ -1289,7 +1289,7 @@ void LvSettingsScreen::rebuildItemList() {
if (_rebootNeeded) {
lv_obj_t* pendingLbl = lv_label_create(headerRow);
- lv_obj_set_style_text_font(pendingLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(pendingLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(pendingLbl, lv_color_hex(Theme::WARNING_CLR), 0);
lv_label_set_text(pendingLbl, "REBOOT NEEDED");
lv_obj_align(pendingLbl, LV_ALIGN_RIGHT_MID, -8, 0);
@@ -1308,7 +1308,7 @@ void LvSettingsScreen::rebuildItemList() {
lv_obj_clear_flag(notice, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* noticeLbl = lv_label_create(notice);
- lv_obj_set_style_text_font(noticeLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(noticeLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(noticeLbl, lv_color_hex(Theme::WARNING_CLR), 0);
clipLabel(noticeLbl, Theme::CONTENT_W - 16);
lv_label_set_text(noticeLbl, "Saved interface config is pending reboot");
@@ -1331,14 +1331,14 @@ void LvSettingsScreen::rebuildItemList() {
lv_obj_clear_flag(confirm, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t* confirmTitleLbl = lv_label_create(confirm);
- lv_obj_set_style_text_font(confirmTitleLbl, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(confirmTitleLbl, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(confirmTitleLbl, lv_color_hex(confirmColor), 0);
clipLabel(confirmTitleLbl, Theme::CONTENT_W - 16);
lv_label_set_text(confirmTitleLbl, confirmTitle);
lv_obj_align(confirmTitleLbl, LV_ALIGN_TOP_LEFT, 8, 3);
lv_obj_t* confirmDetailLbl = lv_label_create(confirm);
- lv_obj_set_style_text_font(confirmDetailLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(confirmDetailLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(confirmDetailLbl, lv_color_hex(Theme::TEXT_PRIMARY), 0);
clipLabel(confirmDetailLbl, Theme::CONTENT_W - 16);
lv_label_set_text(confirmDetailLbl, confirmDetail);
@@ -1515,7 +1515,7 @@ void LvSettingsScreen::rebuildWifiList() {
_rowObjs.clear();
lv_obj_clean(_scrollContainer);
- const lv_font_t* font = &lv_font_ratdeck_12;
+ const lv_font_t* font = &lv_font_rsdeck_12;
// Header
lv_obj_t* headerRow = lv_obj_create(_scrollContainer);
@@ -1591,7 +1591,7 @@ void LvSettingsScreen::rebuildWifiList() {
char sigBuf[12];
snprintf(sigBuf, sizeof(sigBuf), "%ddBm", net.rssi);
lv_obj_t* sigLbl = lv_label_create(row);
- lv_obj_set_style_text_font(sigLbl, &lv_font_ratdeck_10, 0);
+ lv_obj_set_style_text_font(sigLbl, &lv_font_rsdeck_10, 0);
lv_obj_set_style_text_color(sigLbl, lv_color_hex(Theme::TEXT_MUTED), 0);
lv_label_set_text(sigLbl, sigBuf);
lv_obj_align(sigLbl, LV_ALIGN_RIGHT_MID, -4, 0);
@@ -1919,7 +1919,7 @@ bool LvSettingsScreen::handleLongPress() {
runFormatSD();
return true;
}
- if (_confirmingWipeSD && labelEq(item.label, "Erase Ratdeck SD Data")) {
+ if (_confirmingWipeSD && labelEq(item.label, "Erase rsDeck SD Data")) {
runWipeSD();
return true;
}
diff --git a/src/ui/screens/LvTimezoneScreen.cpp b/src/ui/screens/LvTimezoneScreen.cpp
index 546e2b2..039a848 100644
--- a/src/ui/screens/LvTimezoneScreen.cpp
+++ b/src/ui/screens/LvTimezoneScreen.cpp
@@ -54,7 +54,7 @@ lv_obj_t* makeActionButton(lv_obj_t* parent, const char* text, lv_coord_t x,
lv_obj_set_style_pad_all(btn, 0, 0);
lv_obj_t* lbl = lv_label_create(btn);
- lv_obj_set_style_text_font(lbl, &lv_font_ratdeck_12, 0);
+ lv_obj_set_style_text_font(lbl, &lv_font_rsdeck_12, 0);
lv_obj_set_style_text_color(lbl,
lv_color_hex(primary ? Theme::ACCENT : Theme::TEXT_PRIMARY), 0);
lv_label_set_long_mode(lbl, LV_LABEL_LONG_DOT);
@@ -78,7 +78,7 @@ void LvTimezoneScreen::createUI(lv_obj_t* parent) {
lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
_selectedIdx = clampTimezoneIndex(_selectedIdx);
- lv_obj_t* eyebrow = makeLabel(parent, "REGION SETUP", &lv_font_ratdeck_10,
+ lv_obj_t* eyebrow = makeLabel(parent, "REGION SETUP", &lv_font_rsdeck_10,
Theme::TEXT_SECONDARY, 220, LV_TEXT_ALIGN_CENTER,
LV_LABEL_LONG_DOT);
lv_obj_align(eyebrow, LV_ALIGN_TOP_MID, 0, 8);
@@ -89,7 +89,7 @@ void LvTimezoneScreen::createUI(lv_obj_t* parent) {
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 24);
lv_obj_t* sub = makeLabel(parent, "Pick the nearest city for time and radio-region hints.",
- &lv_font_ratdeck_12, Theme::TEXT_PRIMARY,
+ &lv_font_rsdeck_12, Theme::TEXT_PRIMARY,
280, LV_TEXT_ALIGN_CENTER);
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 48);
@@ -115,12 +115,12 @@ void LvTimezoneScreen::createUI(lv_obj_t* parent) {
lv_roller_set_selected(_roller, _selectedIdx, LV_ANIM_OFF);
lv_obj_add_style(_roller, LvTheme::styleRoller(), LV_PART_MAIN);
- lv_obj_set_style_text_font(_roller, &lv_font_ratdeck_14, LV_PART_MAIN);
+ lv_obj_set_style_text_font(_roller, &lv_font_rsdeck_14, LV_PART_MAIN);
lv_obj_set_style_text_align(_roller, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_set_style_bg_color(_roller, lv_color_hex(Theme::BG_HOVER), LV_PART_SELECTED);
lv_obj_set_style_bg_opa(_roller, LV_OPA_COVER, LV_PART_SELECTED);
lv_obj_set_style_text_color(_roller, lv_color_hex(Theme::ACCENT), LV_PART_SELECTED);
- lv_obj_set_style_text_font(_roller, &lv_font_ratdeck_14, LV_PART_SELECTED);
+ lv_obj_set_style_text_font(_roller, &lv_font_rsdeck_14, LV_PART_SELECTED);
lv_obj_set_style_text_align(_roller, LV_TEXT_ALIGN_CENTER, LV_PART_SELECTED);
// Add to focus group for trackball navigation
diff --git a/vendor/rnode_firmware/Bluetooth.h b/vendor/rnode_firmware/Bluetooth.h
index 4e2f356..7bb0803 100644
--- a/vendor/rnode_firmware/Bluetooth.h
+++ b/vendor/rnode_firmware/Bluetooth.h
@@ -58,10 +58,10 @@ char bt_da[BT_DEV_ADDR_LEN];
char bt_dh[BT_DEV_HASH_LEN];
char bt_devname[11];
-#if BOARD_MODEL == BOARD_CARDPUTER_ADV
+#if BOARD_MODEL == BOARD_CARDPUTER_ADV || BOARD_MODEL == BOARD_TDECK
extern void stopRadio();
- void cardputer_adv_ble_host_detached() {
+ void ble_host_detached_cleanup() {
if (radio_online) stopRadio();
cable_state = CABLE_STATE_DISCONNECTED;
current_rssi = -292;
@@ -363,8 +363,8 @@ char bt_devname[11];
display_unblank();
ble_authenticated = false;
bt_state = BT_STATE_ON;
- #if BOARD_MODEL == BOARD_CARDPUTER_ADV
- cardputer_adv_ble_host_detached();
+ #if BOARD_MODEL == BOARD_CARDPUTER_ADV || BOARD_MODEL == BOARD_TDECK
+ ble_host_detached_cleanup();
#endif
}
diff --git a/vendor/rnode_firmware/Display.h b/vendor/rnode_firmware/Display.h
index ffc77d0..65790a3 100644
--- a/vendor/rnode_firmware/Display.h
+++ b/vendor/rnode_firmware/Display.h
@@ -198,7 +198,10 @@ float epd_update_fps = 0.5;
#define CARDPUTER_ADV_DISPLAY_INTENSITY_DEFAULT 96
#elif BOARD_MODEL == BOARD_TDECK
#define DISPLAY_BLANKING_TIMEOUT 60*1000
- #define TDECK_DISPLAY_INTENSITY_DEFAULT 12
+ #define TDECK_DISPLAY_INTENSITY_DEFAULT 80
+ #define TDECK_DISPLAY_INTENSITY_MIN 5
+ #define TDECK_DISPLAY_INTENSITY_STEP 5
+ #define TDECK_DISPLAY_INTENSITY_TAG 0x80
#else
#define DISPLAY_BLANKING_TIMEOUT 15*1000
#endif
@@ -324,29 +327,72 @@ uint8_t display_contrast = 0x00;
else { analogWrite(pin_backlight, value); }
}
#elif BOARD_MODEL == BOARD_TDECK
+ uint8_t tdeck_display_clamp_percent(uint8_t value) {
+ if (value < TDECK_DISPLAY_INTENSITY_MIN) return TDECK_DISPLAY_INTENSITY_MIN;
+ if (value > 100) return 100;
+ uint8_t snapped = ((value + (TDECK_DISPLAY_INTENSITY_STEP / 2)) / TDECK_DISPLAY_INTENSITY_STEP) * TDECK_DISPLAY_INTENSITY_STEP;
+ if (snapped < TDECK_DISPLAY_INTENSITY_MIN) return TDECK_DISPLAY_INTENSITY_MIN;
+ if (snapped > 100) return 100;
+ return snapped;
+ }
+
+ uint8_t tdeck_display_step_to_percent(uint8_t step) {
+ if (step == 0) return 0;
+ if (step > 15) step = 15;
+ uint8_t pct = (((uint16_t)step * 100) / 15 / TDECK_DISPLAY_INTENSITY_STEP) * TDECK_DISPLAY_INTENSITY_STEP;
+ if (pct < TDECK_DISPLAY_INTENSITY_MIN) return TDECK_DISPLAY_INTENSITY_MIN;
+ if (pct > 100) return 100;
+ return pct;
+ }
+
+ uint8_t tdeck_display_decode_intensity(uint8_t stored) {
+ if (stored == 0 || stored == 0xFF) return TDECK_DISPLAY_INTENSITY_DEFAULT;
+ if (stored >= TDECK_DISPLAY_INTENSITY_TAG) {
+ uint8_t index = stored - TDECK_DISPLAY_INTENSITY_TAG;
+ uint16_t pct = (uint16_t)(index + 1) * TDECK_DISPLAY_INTENSITY_STEP;
+ if (pct > 100) pct = 100;
+ return tdeck_display_clamp_percent((uint8_t)pct);
+ }
+ if (stored <= 15) return tdeck_display_step_to_percent(stored);
+ return tdeck_display_clamp_percent(stored);
+ }
+
+ uint8_t tdeck_display_encode_intensity(uint8_t pct) {
+ pct = tdeck_display_clamp_percent(pct);
+ return TDECK_DISPLAY_INTENSITY_TAG + (pct / TDECK_DISPLAY_INTENSITY_STEP) - 1;
+ }
+
+ uint8_t tdeck_display_percent_to_step(uint8_t pct) {
+ pct = tdeck_display_clamp_percent(pct);
+ uint8_t step = (uint8_t)(((uint16_t)pct * 15 + 50) / 100);
+ if (step < 1) step = 1;
+ if (step > 15) step = 15;
+ return step;
+ }
+
void set_contrast(Adafruit_ST7789 *display, uint8_t value) {
static uint8_t level = 0;
static uint8_t steps = 16;
- if (value > 15) value = 15;
if (value == 0) {
digitalWrite(DISPLAY_BL_PIN, 0);
delay(3);
level = 0;
return;
}
+ uint8_t target = tdeck_display_percent_to_step(value);
if (level == 0) {
digitalWrite(DISPLAY_BL_PIN, 1);
level = steps;
delayMicroseconds(30);
}
int from = steps - level;
- int to = steps - value;
+ int to = steps - target;
int num = (steps + to - from) % steps;
for (int i = 0; i < num; i++) {
digitalWrite(DISPLAY_BL_PIN, 0);
digitalWrite(DISPLAY_BL_PIN, 1);
}
- level = value;
+ level = target;
}
#elif BOARD_MODEL == BOARD_CARDPUTER_ADV
void set_contrast(M5Canvas *display, uint8_t value) {
@@ -608,9 +654,7 @@ bool display_init() {
display_intensity = CARDPUTER_ADV_DISPLAY_INTENSITY_DEFAULT;
}
#elif BOARD_MODEL == BOARD_TDECK
- if (display_intensity == 0xFF) {
- display_intensity = TDECK_DISPLAY_INTENSITY_DEFAULT;
- }
+ display_intensity = tdeck_display_decode_intensity(display_intensity);
#endif
display_unblank_intensity = display_intensity;
diff --git a/vendor/rnode_firmware/Makefile b/vendor/rnode_firmware/Makefile
index 913f1cc..4d3c690 100644
--- a/vendor/rnode_firmware/Makefile
+++ b/vendor/rnode_firmware/Makefile
@@ -40,7 +40,11 @@ TDECK_SKETCH = build/tdeck_sketch/RNode_Firmware
TDECK_BUILD = build/tdeck_build
TDECK_OUTPUT = build/tdeck.esp32.esp32s3
TDECK_BIN = $(TDECK_OUTPUT)/RNode_Firmware.ino.bin
-TDECK_EXTRA_FLAGS = \"-DBOARD_MODEL=0x3B\"
+RSDECK_CONFIG_H ?= ../../src/config/Config.h
+RSDECK_VERSION_MAJOR ?= $(shell awk '/\#define RSDECK_VERSION_MAJOR/ {print $$3; exit}' $(RSDECK_CONFIG_H))
+RSDECK_VERSION_MINOR ?= $(shell awk '/\#define RSDECK_VERSION_MINOR/ {print $$3; exit}' $(RSDECK_CONFIG_H))
+RSDECK_VERSION_PATCH ?= $(shell awk '/\#define RSDECK_VERSION_PATCH/ {print $$3; exit}' $(RSDECK_CONFIG_H))
+TDECK_EXTRA_FLAGS = \"-DBOARD_MODEL=0x3B\" \"-DRSDECK_VERSION_MAJOR=$(RSDECK_VERSION_MAJOR)\" \"-DRSDECK_VERSION_MINOR=$(RSDECK_VERSION_MINOR)\" \"-DRSDECK_VERSION_PATCH=$(RSDECK_VERSION_PATCH)\"
ARDUINO_DATA_DIR ?= $(shell if [ -d "$(HOME)/Library/Arduino15/packages" ]; then printf "$(HOME)/Library/Arduino15"; else printf "$(HOME)/.arduino15"; fi)
ESP32_CORE_DIR = $(ARDUINO_DATA_DIR)/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)
diff --git a/vendor/rnode_firmware/RNode_Firmware.ino b/vendor/rnode_firmware/RNode_Firmware.ino
index b3a5089..f581ce9 100644
--- a/vendor/rnode_firmware/RNode_Firmware.ino
+++ b/vendor/rnode_firmware/RNode_Firmware.ino
@@ -622,8 +622,14 @@ bool startRadio() {
}
void stopRadio() {
+ if (!radio_online) return;
+ #if BOARD_MODEL == BOARD_TDECK
+ radio_online = false;
+ LoRa->end();
+ #else
LoRa->end();
radio_online = false;
+ #endif
}
void handleModemTimeout() {
diff --git a/vendor/rnode_firmware/TDeckUI.h b/vendor/rnode_firmware/TDeckUI.h
index caa0325..ebc9e0d 100644
--- a/vendor/rnode_firmware/TDeckUI.h
+++ b/vendor/rnode_firmware/TDeckUI.h
@@ -10,7 +10,9 @@
void display_unblank();
void bt_enable_pairing();
void bt_disable_pairing();
+void bt_debond_all();
int bt_bond_count();
+void di_conf_save(uint8_t dint);
extern uint32_t bt_pairing_started;
#define TD_PAIRING_WINDOW_MS 30000
@@ -36,8 +38,61 @@ extern uint32_t bt_pairing_started;
#define TD_WF_X 212
#define TD_WF_W 107
#define TD_WF_Y 30
-#define TD_WF_H 192
+
+#define TD_BTN_X (TD_WF_X + 5)
+#define TD_BTN_W (TD_WF_W - 10)
+#define TD_PAIR_BTN_H 42
+#define TD_SETTINGS_BTN_H TD_PAIR_BTN_H
+#define TD_PANEL_H (TD_FOOT_Y - TD_WF_Y - 4)
+#define TD_ONLINE_SETTINGS_BTN_Y (TD_FOOT_Y - TD_SETTINGS_BTN_H - 4)
+#define TD_WF_H (TD_ONLINE_SETTINGS_BTN_Y - TD_WF_Y - 8)
#define TD_WF_SIZE TD_WF_H
+#define TD_PAIR_BTN_Y (TD_WF_Y + 42)
+#define TD_SETTINGS_BTN_Y (TD_PAIR_BTN_Y + TD_PAIR_BTN_H + 10)
+#define TD_CANCEL_BTN_Y (TD_WF_Y + 130)
+
+#define TD_SETTINGS_BACK_X 252
+#define TD_SETTINGS_BACK_Y 32
+#define TD_SETTINGS_BACK_W 58
+#define TD_SETTINGS_BACK_H 28
+#define TD_SETTINGS_MINUS_X 18
+#define TD_SETTINGS_MINUS_Y 88
+#define TD_SETTINGS_STEP_W 36
+#define TD_SETTINGS_STEP_H 30
+#define TD_SETTINGS_PLUS_X 266
+#define TD_SETTINGS_PLUS_Y 88
+#define TD_SETTINGS_BAR_X 66
+#define TD_SETTINGS_BAR_Y 99
+#define TD_SETTINGS_BAR_W 188
+#define TD_SETTINGS_BAR_H 8
+#define TD_SETTINGS_ABOUT_X 18
+#define TD_SETTINGS_ABOUT_Y 128
+#define TD_SETTINGS_ABOUT_W 132
+#define TD_SETTINGS_ABOUT_H 34
+#define TD_SETTINGS_SLEEP_X 166
+#define TD_SETTINGS_SLEEP_Y 128
+#define TD_SETTINGS_SLEEP_W 136
+#define TD_SETTINGS_SLEEP_H 34
+#define TD_SETTINGS_FORGET_X 18
+#define TD_SETTINGS_FORGET_Y 174
+#define TD_SETTINGS_FORGET_W 284
+#define TD_SETTINGS_FORGET_H 34
+#define TD_ABOUT_CLOSE_X 126
+#define TD_ABOUT_CLOSE_Y 178
+#define TD_ABOUT_CLOSE_W 68
+#define TD_ABOUT_CLOSE_H 32
+#define TD_BRIGHTNESS_MIN_PCT 5
+#define TD_BRIGHTNESS_STEP_PCT 5
+
+#ifndef RSDECK_VERSION_MAJOR
+ #define RSDECK_VERSION_MAJOR 0
+#endif
+#ifndef RSDECK_VERSION_MINOR
+ #define RSDECK_VERSION_MINOR 0
+#endif
+#ifndef RSDECK_VERSION_PATCH
+ #define RSDECK_VERSION_PATCH 0
+#endif
static const uint16_t td_wf_palette[9] = {
0x0000, 0x0011, 0x001F, 0x07FF,
@@ -50,6 +105,18 @@ int td_waterfall[TD_WF_SIZE];
int td_waterfall_head = 0;
uint8_t td_charge_tick = 0;
+enum TdView : uint8_t {
+ TD_VIEW_MAIN = 0,
+ TD_VIEW_SETTINGS = 1,
+ TD_VIEW_ABOUT = 2
+};
+
+TdView td_view = TD_VIEW_MAIN;
+uint8_t td_settings_brightness_pct = 80;
+uint32_t td_settings_forget_until = 0;
+uint32_t td_settings_status_until = 0;
+const char* td_settings_status_msg = nullptr;
+
// --- GT911 touch (poll-only, tap toggles display power) ---
#define TD_TOUCH_SDA 18
#define TD_TOUCH_SCL 8
@@ -119,6 +186,135 @@ bool td_pair_button_visible() {
return !radio_online && bt_state != BT_STATE_CONNECTED && bt_state != BT_STATE_PAIRING;
}
+bool td_settings_button_visible() {
+ return bt_state != BT_STATE_PAIRING;
+}
+
+bool td_hit(int16_t x, int16_t y, int rx, int ry, int rw, int rh) {
+ return x >= rx && x < rx + rw && y >= ry && y < ry + rh;
+}
+
+uint8_t td_clamp_brightness_pct(uint8_t pct) {
+ if (pct < TD_BRIGHTNESS_MIN_PCT) return TD_BRIGHTNESS_MIN_PCT;
+ if (pct > 100) return 100;
+ uint8_t snapped = ((pct + (TD_BRIGHTNESS_STEP_PCT / 2)) / TD_BRIGHTNESS_STEP_PCT) * TD_BRIGHTNESS_STEP_PCT;
+ if (snapped < TD_BRIGHTNESS_MIN_PCT) return TD_BRIGHTNESS_MIN_PCT;
+ if (snapped > 100) return 100;
+ return snapped;
+}
+
+uint8_t td_intensity_to_pct(uint8_t intensity) {
+ if (intensity == 0 || intensity == 0xFF) return TDECK_DISPLAY_INTENSITY_DEFAULT;
+ return td_clamp_brightness_pct(intensity);
+}
+
+uint8_t td_pct_to_intensity(uint8_t pct) {
+ return td_clamp_brightness_pct(pct);
+}
+
+void td_settings_set_status(const char* msg, uint16_t duration_ms = 1800) {
+ td_settings_status_msg = msg;
+ td_settings_status_until = millis() + duration_ms;
+}
+
+void td_settings_load_brightness() {
+ uint8_t intensity = display_intensity;
+ if (intensity == 0 && display_unblank_intensity != 0) intensity = display_unblank_intensity;
+ td_settings_brightness_pct = td_intensity_to_pct(intensity);
+}
+
+void td_settings_preview_brightness(uint8_t pct) {
+ td_settings_brightness_pct = td_clamp_brightness_pct(pct);
+ display_intensity = td_pct_to_intensity(td_settings_brightness_pct);
+ display_unblank_intensity = 0;
+ di_conf_save(display_intensity);
+ display_blank_frame_drawn = false;
+ display_unblank();
+}
+
+void td_open_settings() {
+ td_view = TD_VIEW_SETTINGS;
+ td_settings_forget_until = 0;
+ td_settings_status_msg = nullptr;
+ td_settings_load_brightness();
+ display_unblank();
+ last_disp_update = 0;
+}
+
+void td_sleep_display() {
+ uint8_t current = display_intensity;
+ if (current == 0 || current == 0xFF) current = TDECK_DISPLAY_INTENSITY_DEFAULT;
+ display_unblank_intensity = current;
+ display_intensity = 0;
+ display_blanked = true;
+ display_blank_frame_drawn = false;
+ td_view = TD_VIEW_MAIN;
+ last_unblank_event = millis() - display_blanking_timeout - 1;
+ last_disp_update = 0;
+}
+
+bool td_handle_settings_touch(int16_t x, int16_t y) {
+ if (td_hit(x, y, TD_SETTINGS_BACK_X, TD_SETTINGS_BACK_Y, TD_SETTINGS_BACK_W, TD_SETTINGS_BACK_H)) {
+ td_view = TD_VIEW_MAIN;
+ td_settings_forget_until = 0;
+ display_unblank();
+ return true;
+ }
+
+ if (td_hit(x, y, TD_SETTINGS_MINUS_X, TD_SETTINGS_MINUS_Y, TD_SETTINGS_STEP_W, TD_SETTINGS_STEP_H)) {
+ uint8_t next = td_settings_brightness_pct;
+ if (next > TD_BRIGHTNESS_MIN_PCT + TD_BRIGHTNESS_STEP_PCT) next -= TD_BRIGHTNESS_STEP_PCT;
+ else next = TD_BRIGHTNESS_MIN_PCT;
+ td_settings_preview_brightness(next);
+ return true;
+ }
+
+ if (td_hit(x, y, TD_SETTINGS_PLUS_X, TD_SETTINGS_PLUS_Y, TD_SETTINGS_STEP_W, TD_SETTINGS_STEP_H)) {
+ uint8_t next = td_settings_brightness_pct;
+ if (next < 100 - TD_BRIGHTNESS_STEP_PCT) next += TD_BRIGHTNESS_STEP_PCT;
+ else next = 100;
+ td_settings_preview_brightness(next);
+ return true;
+ }
+
+ if (td_hit(x, y, TD_SETTINGS_ABOUT_X, TD_SETTINGS_ABOUT_Y, TD_SETTINGS_ABOUT_W, TD_SETTINGS_ABOUT_H)) {
+ td_view = TD_VIEW_ABOUT;
+ display_unblank();
+ return true;
+ }
+
+ if (td_hit(x, y, TD_SETTINGS_SLEEP_X, TD_SETTINGS_SLEEP_Y, TD_SETTINGS_SLEEP_W, TD_SETTINGS_SLEEP_H)) {
+ td_sleep_display();
+ return true;
+ }
+
+ if (td_hit(x, y, TD_SETTINGS_FORGET_X, TD_SETTINGS_FORGET_Y, TD_SETTINGS_FORGET_W, TD_SETTINGS_FORGET_H)) {
+ if ((int32_t)(td_settings_forget_until - millis()) > 0) {
+ bt_debond_all();
+ bt_disable_pairing();
+ td_settings_forget_until = 0;
+ td_settings_set_status("BLE bonds removed", 2200);
+ } else {
+ td_settings_forget_until = millis() + 5000;
+ td_settings_set_status("Tap Forget again", 5000);
+ }
+ display_unblank();
+ return true;
+ }
+
+ return false;
+}
+
+bool td_handle_about_touch(int16_t x, int16_t y) {
+ if (td_hit(x, y, TD_ABOUT_CLOSE_X, TD_ABOUT_CLOSE_Y, TD_ABOUT_CLOSE_W, TD_ABOUT_CLOSE_H)) {
+ td_view = TD_VIEW_SETTINGS;
+ display_unblank();
+ return true;
+ }
+
+ return false;
+}
+
void td_poll_touch() {
if (millis() - td_last_touch_poll < 50) return;
td_last_touch_poll = millis();
@@ -138,15 +334,34 @@ void td_poll_touch() {
// inactivity timeout alone, never from a tap.
if (display_blanked) {
display_unblank();
+ td_view = TD_VIEW_MAIN;
last_disp_update = 0;
return;
}
- bool in_panel = td_touch_x >= (TD_WF_X - 6) && td_touch_y >= TD_BAR_H && td_touch_y < TD_FOOT_Y;
- if (in_panel && td_pair_button_visible()) {
- bt_enable_pairing(); // 30s window, auto-disarms via BT_PAIRING_TIMEOUT
- } else if (in_panel && bt_state == BT_STATE_PAIRING) {
+ if (td_view == TD_VIEW_SETTINGS) {
+ if (!td_handle_settings_touch(td_touch_x, td_touch_y)) {
+ display_unblank();
+ }
+ last_disp_update = 0;
+ return;
+ }
+
+ if (td_view == TD_VIEW_ABOUT) {
+ if (!td_handle_about_touch(td_touch_x, td_touch_y)) {
+ display_unblank();
+ }
+ last_disp_update = 0;
+ return;
+ }
+
+ int settings_y = radio_online ? TD_ONLINE_SETTINGS_BTN_Y : TD_SETTINGS_BTN_Y;
+ if (td_pair_button_visible() && td_hit(td_touch_x, td_touch_y, TD_BTN_X, TD_PAIR_BTN_Y, TD_BTN_W, TD_PAIR_BTN_H)) {
+ bt_enable_pairing(); // Auto-disarms via BT_PAIRING_TIMEOUT.
+ } else if (bt_state == BT_STATE_PAIRING && td_hit(td_touch_x, td_touch_y, TD_BTN_X, TD_CANCEL_BTN_Y, TD_BTN_W, TD_SETTINGS_BTN_H)) {
bt_disable_pairing(); // tap again to cancel
+ } else if (td_settings_button_visible() && td_hit(td_touch_x, td_touch_y, TD_BTN_X, settings_y, TD_BTN_W, TD_SETTINGS_BTN_H)) {
+ td_open_settings();
} else {
display_unblank(); // count as activity, extend the timeout
}
@@ -203,6 +418,29 @@ void td_draw_gradient_bar(int x, int y, int w, int h, float percent) {
if (fill_w > 0) td_canvas->fillRect(x + 1, y + 1, fill_w, h - 2, fill_clr);
}
+int td_text_width(const char* label, int text_size = 1) {
+ int len = 0;
+ while (label && label[len] != '\0') len++;
+ return len * 6 * text_size;
+}
+
+void td_draw_centered_text(const char* label, int x, int y, int w, int text_size, uint16_t colour) {
+ td_canvas->setTextSize(text_size);
+ td_canvas->setTextColor(colour);
+ int tw = td_text_width(label, text_size);
+ int tx = x + ((w - tw) / 2);
+ if (tx < x + 2) tx = x + 2;
+ td_canvas->setCursor(tx, y);
+ td_canvas->print(label);
+}
+
+void td_draw_button(int x, int y, int w, int h, const char* label, uint16_t border, uint16_t text, bool armed = false) {
+ uint16_t fill = armed ? 0x3000 : TD_CLR_BG_PANEL;
+ td_canvas->fillRoundRect(x, y, w, h, 6, fill);
+ td_canvas->drawRoundRect(x, y, w, h, 6, border);
+ td_draw_centered_text(label, x, y + ((h - 8) / 2), w, 1, text);
+}
+
void td_draw_usb_icon(int x, int y, uint16_t colour) {
td_canvas->fillRect(x + 3, y, 7, 10, colour);
td_canvas->fillRect(x + 4, y + 10, 5, 4, colour);
@@ -306,10 +544,7 @@ void td_draw_pair_panel() {
td_canvas->setTextColor(TD_CLR_TEXT_PRIMARY);
td_canvas->setCursor(remain_s >= 10 ? cx - 23 : cx - 11, TD_WF_Y + 76);
td_canvas->print(rbuf);
- td_canvas->setTextSize(1);
- td_canvas->setTextColor(TD_CLR_TEXT_DIM);
- td_canvas->setCursor(cx - 39, TD_WF_Y + 118);
- td_canvas->print("tap to cancel");
+ td_draw_button(TD_BTN_X, TD_CANCEL_BTN_Y, TD_BTN_W, TD_SETTINGS_BTN_H, "Cancel", TD_CLR_WARN, TD_CLR_WARN);
return;
}
@@ -321,37 +556,36 @@ void td_draw_pair_panel() {
td_canvas->setTextColor(TD_CLR_TEXT_DIM);
td_canvas->setCursor(cx - 47, TD_WF_Y + 96);
td_canvas->print("waiting for host");
+ td_draw_button(TD_BTN_X, TD_SETTINGS_BTN_Y, TD_BTN_W, TD_SETTINGS_BTN_H, "Settings", TD_CLR_ACCENT, TD_CLR_ACCENT);
return;
}
// Idle: paired devices can connect without opening pairing. The same panel
// remains tappable to pair an additional or re-paired host.
- int bw = 98; int bh = 48;
- int bx = TD_WF_X + (TD_WF_W - bw) / 2;
- int by = TD_WF_Y + 72;
bool has_bond = bt_bond_count() > 0;
- td_canvas->fillRoundRect(bx, by, bw, bh, 8, TD_CLR_BG_PANEL);
- td_canvas->drawRoundRect(bx, by, bw, bh, 8, has_bond ? TD_CLR_GREEN : TD_CLR_ACCENT);
+ td_canvas->fillRoundRect(TD_BTN_X, TD_PAIR_BTN_Y, TD_BTN_W, TD_PAIR_BTN_H, 8, TD_CLR_BG_PANEL);
+ td_canvas->drawRoundRect(TD_BTN_X, TD_PAIR_BTN_Y, TD_BTN_W, TD_PAIR_BTN_H, 8, has_bond ? TD_CLR_GREEN : TD_CLR_ACCENT);
td_canvas->setTextSize(1);
if (has_bond) {
td_canvas->setTextColor(TD_CLR_GREEN);
- td_canvas->setCursor(bx + 23, by + 10);
+ td_canvas->setCursor(TD_BTN_X + 23, TD_PAIR_BTN_Y + 8);
td_canvas->print("BLE ready");
td_canvas->setTextColor(TD_CLR_TEXT_DIM);
- td_canvas->setCursor(bx + 19, by + 25);
+ td_canvas->setCursor(TD_BTN_X + 19, TD_PAIR_BTN_Y + 23);
td_canvas->print("tap to pair");
} else {
td_canvas->setTextColor(TD_CLR_ACCENT);
- td_canvas->setCursor(bx + 25, by + 14);
+ td_canvas->setCursor(TD_BTN_X + 25, TD_PAIR_BTN_Y + 10);
td_canvas->print("Pair via");
- td_canvas->setCursor(bx + 40, by + 28);
+ td_canvas->setCursor(TD_BTN_X + 40, TD_PAIR_BTN_Y + 24);
td_canvas->print("BLE");
}
+ td_draw_button(TD_BTN_X, TD_SETTINGS_BTN_Y, TD_BTN_W, TD_SETTINGS_BTN_H, "Settings", TD_CLR_ACCENT, TD_CLR_ACCENT);
}
void td_draw_waterfall() {
- td_canvas->fillRect(TD_WF_X, TD_WF_Y, TD_WF_W, TD_WF_H + 2, TD_CLR_BG);
- td_canvas->drawFastVLine(TD_WF_X - 2, TD_WF_Y, TD_WF_H, TD_CLR_BORDER);
+ td_canvas->fillRect(TD_WF_X, TD_WF_Y, TD_WF_W, TD_PANEL_H, TD_CLR_BG);
+ td_canvas->drawFastVLine(TD_WF_X - 2, TD_WF_Y, TD_PANEL_H, TD_CLR_BORDER);
if (!radio_online) {
td_draw_pair_panel();
@@ -389,6 +623,10 @@ void td_draw_waterfall() {
}
}
}
+
+ if (td_settings_button_visible()) {
+ td_draw_button(TD_BTN_X, TD_ONLINE_SETTINGS_BTN_Y, TD_BTN_W, TD_SETTINGS_BTN_H, "Settings", TD_CLR_ACCENT, TD_CLR_ACCENT);
+ }
}
void td_draw_idle_content() {
@@ -548,6 +786,114 @@ void td_draw_error_screen() {
}
}
+void td_draw_settings_screen() {
+ td_canvas->fillRect(0, TD_BAR_H + 1, TD_W, TD_FOOT_Y - TD_BAR_H - 1, TD_CLR_BG);
+
+ int x = 12;
+ int y = TD_CONTENT_Y + 6;
+ td_canvas->setTextSize(2);
+ td_canvas->setTextColor(TD_CLR_TEXT_PRIMARY);
+ td_canvas->setCursor(x, y);
+ td_canvas->print("Settings");
+ td_draw_button(TD_SETTINGS_BACK_X, TD_SETTINGS_BACK_Y, TD_SETTINGS_BACK_W, TD_SETTINGS_BACK_H, "Back", TD_CLR_BORDER, TD_CLR_TEXT_SECONDARY);
+
+ y += 34;
+ td_canvas->setTextSize(1);
+ td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY);
+ td_canvas->setCursor(x, y);
+ td_canvas->print("Brightness");
+
+ char pctbuf[8];
+ snprintf(pctbuf, sizeof(pctbuf), "%u%%", (unsigned int)td_settings_brightness_pct);
+ td_canvas->setTextSize(2);
+ td_canvas->setTextColor(TD_CLR_ACCENT);
+ td_canvas->setCursor(214, y - 5);
+ td_canvas->print(pctbuf);
+
+ td_draw_button(TD_SETTINGS_MINUS_X, TD_SETTINGS_MINUS_Y, TD_SETTINGS_STEP_W, TD_SETTINGS_STEP_H, "-", TD_CLR_BORDER, TD_CLR_TEXT_PRIMARY);
+ td_draw_button(TD_SETTINGS_PLUS_X, TD_SETTINGS_PLUS_Y, TD_SETTINGS_STEP_W, TD_SETTINGS_STEP_H, "+", TD_CLR_BORDER, TD_CLR_TEXT_PRIMARY);
+
+ td_canvas->drawRect(TD_SETTINGS_BAR_X, TD_SETTINGS_BAR_Y, TD_SETTINGS_BAR_W, TD_SETTINGS_BAR_H, TD_CLR_BORDER);
+ int usable = TD_SETTINGS_BAR_W - 2;
+ int fill = ((td_settings_brightness_pct - TD_BRIGHTNESS_MIN_PCT) * usable) / (100 - TD_BRIGHTNESS_MIN_PCT);
+ if (fill > 0) td_canvas->fillRect(TD_SETTINGS_BAR_X + 1, TD_SETTINGS_BAR_Y + 1, fill, TD_SETTINGS_BAR_H - 2, TD_CLR_ACCENT);
+ td_canvas->setTextSize(1);
+ td_canvas->setTextColor(TD_CLR_TEXT_DIM);
+ td_canvas->setCursor(TD_SETTINGS_BAR_X, TD_SETTINGS_BAR_Y + 14);
+ td_canvas->print("min 5%");
+
+ td_draw_button(TD_SETTINGS_ABOUT_X, TD_SETTINGS_ABOUT_Y, TD_SETTINGS_ABOUT_W, TD_SETTINGS_ABOUT_H, "About", TD_CLR_GREEN, TD_CLR_GREEN);
+ td_draw_button(TD_SETTINGS_SLEEP_X, TD_SETTINGS_SLEEP_Y, TD_SETTINGS_SLEEP_W, TD_SETTINGS_SLEEP_H, "Sleep Screen", TD_CLR_ACCENT, TD_CLR_ACCENT);
+
+ bool confirm_forget = (int32_t)(td_settings_forget_until - millis()) > 0;
+ td_draw_button(
+ TD_SETTINGS_FORGET_X,
+ TD_SETTINGS_FORGET_Y,
+ TD_SETTINGS_FORGET_W,
+ TD_SETTINGS_FORGET_H,
+ confirm_forget ? "Confirm Forget BLE" : "Forget BLE Bonds",
+ confirm_forget ? TD_CLR_WARN : TD_CLR_BORDER,
+ confirm_forget ? TD_CLR_WARN : TD_CLR_TEXT_SECONDARY,
+ confirm_forget
+ );
+
+ if (td_settings_status_msg && (int32_t)(td_settings_status_until - millis()) > 0) {
+ td_draw_centered_text(td_settings_status_msg, 10, TD_FOOT_Y - 17, TD_W - 20, 1, TD_CLR_TEXT_DIM);
+ } else {
+ td_settings_status_msg = nullptr;
+ }
+}
+
+void td_draw_about_screen() {
+ td_canvas->fillRect(0, TD_BAR_H + 1, TD_W, TD_FOOT_Y - TD_BAR_H - 1, TD_CLR_BG);
+
+ int px = 34;
+ int py = 44;
+ int pw = 252;
+ int ph = 168;
+ td_canvas->fillRoundRect(px, py, pw, ph, 8, TD_CLR_BG_PANEL);
+ td_canvas->drawRoundRect(px, py, pw, ph, 8, TD_CLR_ACCENT);
+
+ td_canvas->setTextSize(2);
+ td_canvas->setTextColor(TD_CLR_TEXT_PRIMARY);
+ td_canvas->setCursor(px + 20, py + 16);
+ td_canvas->print("About RNode");
+
+ char line[36];
+ td_canvas->setTextSize(1);
+ td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY);
+
+ snprintf(line, sizeof(line), "rsDeck v%d.%d.%d", RSDECK_VERSION_MAJOR, RSDECK_VERSION_MINOR, RSDECK_VERSION_PATCH);
+ td_canvas->setCursor(px + 20, py + 54);
+ td_canvas->print(line);
+
+ snprintf(line, sizeof(line), "RNode v%d.%02d", MAJ_VERS, MIN_VERS);
+ td_canvas->setCursor(px + 20, py + 72);
+ td_canvas->print(line);
+
+ uint32_t total_s = millis() / 1000;
+ uint32_t days = total_s / 86400;
+ uint32_t hours = (total_s / 3600) % 24;
+ uint32_t mins = (total_s / 60) % 60;
+ uint32_t secs = total_s % 60;
+ if (days > 0) {
+ snprintf(line, sizeof(line), "Uptime %lud %02lu:%02lu:%02lu",
+ (unsigned long)days, (unsigned long)hours, (unsigned long)mins, (unsigned long)secs);
+ } else {
+ snprintf(line, sizeof(line), "Uptime %02lu:%02lu:%02lu",
+ (unsigned long)hours, (unsigned long)mins, (unsigned long)secs);
+ }
+ td_canvas->setCursor(px + 20, py + 90);
+ td_canvas->print(line);
+
+ int bonds = bt_bond_count();
+ snprintf(line, sizeof(line), "BLE bonds %d", bonds);
+ td_canvas->setCursor(px + 20, py + 108);
+ td_canvas->print(line);
+
+ td_draw_button(TD_ABOUT_CLOSE_X, TD_ABOUT_CLOSE_Y, TD_ABOUT_CLOSE_W, TD_ABOUT_CLOSE_H, "Close", TD_CLR_ACCENT, TD_CLR_ACCENT);
+}
+
void td_draw_external_framebuffer() {
int scale = 3;
int ox = (TD_LEFT_W - 64 * scale) / 2;
@@ -622,9 +968,20 @@ void td_render_frame() {
td_canvas->fillScreen(TD_CLR_BG);
td_draw_statusbar();
- td_draw_waterfall();
td_draw_footer();
+ if (td_view == TD_VIEW_SETTINGS) {
+ td_draw_settings_screen();
+ return;
+ }
+
+ if (td_view == TD_VIEW_ABOUT) {
+ td_draw_about_screen();
+ return;
+ }
+
+ td_draw_waterfall();
+
if (disp_ext_fb && bt_ssp_pin == 0) {
td_draw_external_framebuffer();
} else if (!hw_ready || radio_error || !device_firmware_ok()) {
diff --git a/vendor/rnode_firmware/Utilities.h b/vendor/rnode_firmware/Utilities.h
index 5c9ae11..87fe3ca 100644
--- a/vendor/rnode_firmware/Utilities.h
+++ b/vendor/rnode_firmware/Utilities.h
@@ -1767,6 +1767,11 @@ void bt_conf_save(bool is_enabled) {
}
void di_conf_save(uint8_t dint) {
+ #if BOARD_MODEL == BOARD_TDECK
+ if (dint != 0 && dint != 0xFF) {
+ dint = tdeck_display_encode_intensity(dint);
+ }
+ #endif
eeprom_update(eeprom_addr(ADDR_CONF_DINT), dint);
}
diff --git a/vendor/rnode_firmware/sx126x.cpp b/vendor/rnode_firmware/sx126x.cpp
index 4ed3974..1fcef5f 100644
--- a/vendor/rnode_firmware/sx126x.cpp
+++ b/vendor/rnode_firmware/sx126x.cpp
@@ -529,7 +529,16 @@ int sx126x::begin(long frequency) {
return 1;
}
-void sx126x::end() { sleep(); LORA_SPI.end(); _preinit_done = false; }
+void sx126x::end() {
+ onReceive(NULL);
+ sleep();
+ #if BOARD_MODEL != BOARD_TDECK
+ LORA_SPI.end();
+ #else
+ // T-Deck display and LoRa share Arduino SPI; ending it crashes the next UI refresh.
+ #endif
+ _preinit_done = false;
+}
int sx126x::beginPacket(int implicitHeader) {
#if HAS_LORA_PA
@@ -749,6 +758,9 @@ void sx126x::onReceive(void(*callback)(int)){
#ifdef SPI_HAS_NOTUSINGINTERRUPT
LORA_SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0));
#endif
+ #if BOARD_MODEL == BOARD_TDECK
+ _irq_pending = false;
+ #endif
}
}