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.** + +[![Status](https://img.shields.io/badge/status-beta-yellow.svg)](#install) +[![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](LICENSE) +[![Version](https://img.shields.io/badge/version-2.0.0-success.svg)](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. - -
- --- -[![Ratspeak Demo](https://img.youtube.com/vi/F6I6fkMPxgI/maxresdefault.jpg)](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 } }