mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-07-11 16:48:50 +00:00
rsdeck: prepare dual-mode 2.0 release
This commit is contained in:
+23
-99
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,54 +1,69 @@
|
||||
<div align="center">
|
||||
|
||||
# [Ratdeck](https://ratspeak.org/)
|
||||
# [rsDeck](https://ratspeak.org/)
|
||||
|
||||
**Standalone Reticulum for the LilyGo T-Deck**
|
||||
**Dual-mode Ratspeak firmware for the LilyGo T-Deck Plus.**
|
||||
|
||||
[](#install)
|
||||
[](LICENSE)
|
||||
[](https://github.com/ratspeak/rsDeck/releases)
|
||||
|
||||
[Ratspeak](https://github.com/ratspeak/Ratspeak) |
|
||||
[Docs](https://ratspeak.org/docs.html) |
|
||||
[Downloads](https://ratspeak.org/download.html) |
|
||||
[rsReticulum](https://github.com/ratspeak/rsReticulum)
|
||||
|
||||
</div>
|
||||
|
||||
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.
|
||||
|
||||
<div align="center">
|
||||
|
||||
---
|
||||
[](https://www.youtube.com/watch?v=F6I6fkMPxgI)
|
||||
|
||||
<sub>[▶ YouTube: Reticulum Standalone - T-Deck & Cardputer Adv](https://www.youtube.com/watch?v=F6I6fkMPxgI)</sub>
|
||||
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.
|
||||
|
||||
---
|
||||
</div>
|
||||
## 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`.
|
||||
|
||||
+13
-10
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
||||
|
||||
Vendored
+1
-1
@@ -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(
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// =============================================================================
|
||||
// Ratdeck — LilyGo T-Deck Plus Pin Definitions
|
||||
// rsDeck — LilyGo T-Deck Plus Pin Definitions
|
||||
// =============================================================================
|
||||
|
||||
// --- Power Control ---
|
||||
|
||||
+15
-13
@@ -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 ---
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -111,7 +111,8 @@ public:
|
||||
|
||||
private:
|
||||
bool parseJson(const String& json);
|
||||
String serializeToJson() const;
|
||||
String serializeToJson();
|
||||
void sanitizeSettings();
|
||||
|
||||
UserSettings _settings;
|
||||
};
|
||||
|
||||
+3
-3
@@ -8,6 +8,6 @@
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
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)
|
||||
|
||||
@@ -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 <lvgl.h>
|
||||
#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*/
|
||||
|
||||
@@ -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 <lvgl.h>
|
||||
#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*/
|
||||
|
||||
@@ -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 <lvgl.h>
|
||||
#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*/
|
||||
|
||||
+19
-8
@@ -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",
|
||||
|
||||
+34
-14
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -26,7 +26,7 @@ bool LXMFManager::begin(ReticulumManager* rns, MessageStore* store) {
|
||||
}
|
||||
std::vector<LXMFMessage> 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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -25,8 +25,8 @@ public:
|
||||
uint64_t totalBytes() const;
|
||||
uint64_t usedBytes() const;
|
||||
|
||||
bool formatForRatdeck();
|
||||
bool wipeRatdeck();
|
||||
bool formatForRsDeck();
|
||||
bool wipeRsDeck();
|
||||
bool hasExistingData();
|
||||
|
||||
private:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "LoRaInterface.h"
|
||||
#include "config/BoardConfig.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+2
-2
@@ -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]);
|
||||
}
|
||||
|
||||
+14
-14
@@ -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() {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
// 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);
|
||||
|
||||
+1
-1
@@ -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) \
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
// =============================================================================
|
||||
// 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+4
-4
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Vendored
+51
-7
@@ -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;
|
||||
|
||||
|
||||
Vendored
+5
-1
@@ -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)
|
||||
|
||||
|
||||
+6
@@ -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() {
|
||||
|
||||
Vendored
+378
-21
@@ -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()) {
|
||||
|
||||
Vendored
+5
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+13
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user