Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e3c212a89 | ||
|
|
a804d709cf | ||
|
|
7111789fa7 | ||
|
|
fbc015cf5c | ||
|
|
3ae9582431 | ||
|
|
36e5ed4c36 | ||
|
|
f817fca41c | ||
|
|
5fcf6ddac3 | ||
|
|
5e6aaa8852 | ||
|
|
cd2dc8b3f8 | ||
|
|
f4fb7171e5 | ||
|
|
8322b5361d | ||
|
|
266f5c8adc | ||
|
|
069aeb8908 | ||
|
|
61a914240f | ||
|
|
29204dd33f | ||
|
|
bb86a32b04 | ||
|
|
f985458363 | ||
|
|
30083efdd7 | ||
|
|
4a0d7f5247 | ||
|
|
d22390aeb8 | ||
|
|
3130a1198e | ||
|
|
1f755ec3b8 | ||
|
|
c931df6054 | ||
|
|
11b31e1cdd | ||
|
|
c16b5230a6 | ||
|
|
f5362b1cb3 | ||
|
|
7a63ffe86c | ||
|
|
2aa8f59410 | ||
|
|
c8b451f14f | ||
|
|
9e567e1789 | ||
|
|
bbcafa3088 | ||
|
|
b3af35666b | ||
|
|
eca5281f22 | ||
|
|
3a70a8ab0f | ||
|
|
2894776a11 | ||
|
|
88a9d386f1 | ||
|
|
a6647dcf55 | ||
|
|
28879f55c4 | ||
|
|
d16c833171 | ||
|
|
031cb03bd4 | ||
|
|
5ec9176906 | ||
|
|
ed9cf619ec | ||
|
|
460b82f9bc | ||
|
|
0a4d8d5b2f | ||
|
|
e72a1b1bd5 | ||
|
|
30fe6fbd6f | ||
|
|
5e8dd55847 | ||
|
|
26ae08101c | ||
|
|
aa89572631 | ||
|
|
ff5818f7f7 | ||
|
|
be7fb32f23 | ||
|
|
17981029a7 | ||
|
|
9bc9822111 | ||
|
|
799a3754d0 | ||
|
|
c9e3de15d4 | ||
|
|
be1c72fbb1 | ||
|
|
b478851a21 | ||
|
|
8ce6baf786 | ||
|
|
b82f4a2363 | ||
|
|
6e62d35328 | ||
|
|
fe8925f91d | ||
|
|
7463530f63 | ||
|
|
d7151dbbf8 | ||
|
|
dd67336fdb | ||
|
|
13b240062a | ||
|
|
9ddb08ba93 | ||
|
|
373be3e546 | ||
|
|
d2e776afe9 | ||
|
|
a5968afb70 | ||
|
|
7aeee1f456 | ||
|
|
ed72f15a81 | ||
|
|
59896b6bdb | ||
|
|
5ff7590168 | ||
|
|
00f2f70800 | ||
|
|
fdaf34575e | ||
|
|
bb95aed673 | ||
|
|
e9ece7bf6b | ||
|
|
976bf4022c | ||
|
|
b85e0a4849 | ||
|
|
e7d6c73d16 |
No files matched your search
@@ -28,3 +28,9 @@ scripts/build/wifi_secrets.h
|
||||
# Local working notes + agent config (contain ops/infra details — never publish)
|
||||
CLAUDE.md
|
||||
.claude/
|
||||
|
||||
# Custom timestamped build outputs
|
||||
/builds/
|
||||
|
||||
# Local reconstruction notes (scratch, never publish)
|
||||
/.notes/
|
||||
@@ -0,0 +1,235 @@
|
||||
# Tanmatsu port — working tracker
|
||||
|
||||
Goal: run the **full** wadamesh UI + functionality on the **Tanmatsu** (Nicolai
|
||||
Electronics), kept as **one codebase** with the existing S3 boards so a UI change
|
||||
ships everywhere at once. This file is the running plan/status — update it as we go.
|
||||
|
||||
Status: **device IN HAND (2026-06-20); ENTIRE APP COMPILES + LINKS on P4** (`f04dba6`) · branch
|
||||
`tanmatsu-port` on `beta_9` · the **full shared src/ — MyMesh + the 28k-line LVGL UITask + all
|
||||
helpers + variant glue — compiles, and the project builds `application.bin`** · LVGL 8.4 + the
|
||||
core + ~10 Arduino libs all build · **next = the real `app_main` bring-up (the spike main still
|
||||
ships, so the bin isn't the real app yet), storage-on-FAT, the 800×480 layout, the bsp_input→LVGL
|
||||
indev, then on-device install via BadgeLink/AppFS.**
|
||||
|
||||
---
|
||||
|
||||
## Decision (architecture) — UPDATED 2026-06-20: wadamesh is an APP on the launcher
|
||||
|
||||
On the Tanmatsu **everything is an app** (badge.team OS + GUI launcher + **AppFS** app
|
||||
store). So wadamesh ships as a **launcher app, NOT a firmware that replaces the OS.**
|
||||
AppFS apps are **standard ESP-IDF app binaries** installed into an AppFS flash partition
|
||||
(via `appfs_add_file.py` / BadgeLink / the badge.team app store) and started from the
|
||||
launcher; a running app owns the screen + input via `badge-bsp` and returns to the
|
||||
launcher on exit (docs.tanmatsu.cloud → AppFS).
|
||||
|
||||
⇒ The Tanmatsu target is an **ESP-IDF app**, NOT the pioarduino/Arduino *standalone*
|
||||
flash (that would overwrite the launcher). The PlatformIO route in the old plan is
|
||||
**dropped**; the "IDF subproject" that was the fallback is now the **primary path** —
|
||||
because the whole app model is IDF-native.
|
||||
|
||||
We still keep **one repo, one `src/`**: the shared app (UITask, MyMesh, i18n, fonts…)
|
||||
compiles inside the IDF app via **arduino-esp32 as an ESP-IDF component**, alongside
|
||||
`badge-bsp` (display/keyboard/power), `tanmatsu-lora` (LoRa — bridge already written in
|
||||
`variants/tanmatsu/`) and `esp_wifi_remote` (C6 Wi-Fi/BLE). Our **LVGL 8.3** flushes to
|
||||
the badge-bsp `esp_lcd` panel (not PAX; not the LVGL-9.3 `badge-bsp-lvgl`). **Distribution
|
||||
= the badge.team app store** — the Tanmatsu analog of LauncherHub. `boards/tanmatsu.json`
|
||||
+ the pioarduino env in the old plan are no longer the build; the LoRa bridge + variant
|
||||
wiring carry over.
|
||||
|
||||
## Compile spike — RESULTS (2026-06-20) ✅ arduino-as-component PROVEN on P4
|
||||
|
||||
IDF-app build lives in **`tanmatsu/`** (badge.team Makefile + project-local IDF **v5.5.1**
|
||||
in `tanmatsu/esp-idf`, gitignored). Build via **`./tanmatsu/build.sh <target>`** (wraps
|
||||
`idf.py -DDEVICE=tanmatsu -DSDKCONFIG_DEFAULTS="sdkconfigs/general;sdkconfigs/tanmatsu;sdkconfigs/wadamesh" -DIDF_TARGET=esp32p4`).
|
||||
Toolchain: PlatformIO already had the riscv32-esp P4 toolchain; IDF cloned via the Makefile
|
||||
+ `idf_tools.py install cmake ninja` (the targeted `install.sh esp32p4` skips those).
|
||||
|
||||
Proven end-to-end:
|
||||
- ✅ Stock badge.team template builds for P4 (baseline) — toolchain + component manager OK.
|
||||
- ✅ **arduino-esp32 3.3.10** compiles + links **alongside badge-bsp** (241 arduino objs).
|
||||
esp32p4 first-class in arduino since 3.1.3. ⇒ the "one shared `src/` via arduino-on-P4"
|
||||
architecture is **validated** — this was the make-or-break risk.
|
||||
|
||||
**Fixes (all in `tanmatsu/sdkconfigs/wadamesh`, our overlay — badge.team files kept pristine):**
|
||||
1. `CONFIG_FREERTOS_HZ=1000` — arduino-esp32 hard-requires it (badge.team defaults 100).
|
||||
2. `CONFIG_BT_ENABLED=n` (+ `BT_NIMBLE_ENABLED=n`, `ESP_HOSTED_ENABLE_BT_NIMBLE=n`) — arduino's
|
||||
bundled BLE won't compile vs IDF 5.5.1 NimBLE (`ble_gap_read_local_irk` renamed). P4 has no
|
||||
native BLE + we use the separate NimBLE-Arduino lib, so BT-off compiles arduino
|
||||
BLE/SimpleBLE/BluetoothSerial out via their `#if` guards. **Revisit hosted BLE (phone
|
||||
companion over the C6) later.** NB: arduino's `CONFIG_ARDUINO_SELECTIVE_COMPILATION` does
|
||||
NOT filter in this setup (verified) — use feature disables, not the selective knob.
|
||||
|
||||
**Gotchas found (carry into the real src/ wiring):**
|
||||
- **badge-bsp headers have NO `extern "C"` guards** → wrap every `#include "bsp/*.h"` in
|
||||
`extern "C" { }` from C++ or the bsp symbols mangle and fail to link.
|
||||
- **Two esp-hosted stacks collide:** arduino + `espressif/esp_wifi_remote` pull
|
||||
`espressif/esp_hosted`, whose whole-archive C6 SDIO driver duplicates badge.team's
|
||||
`nicolaielectronics/esp-hosted-tanmatsu` → "multiple definition". **Fix:
|
||||
`tanmatsu/components/esp_hosted/` (empty forwarder, `REQUIRES esp-hosted-tanmatsu`) shadows
|
||||
the registry component by name** (local components win) → one esp-hosted, the hardware fork,
|
||||
and arduino's `WiFi.h` rides on it. Runtime TODO: the `ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE`
|
||||
config warning (C6 reset pin) — set it when WiFi is brought up live.
|
||||
|
||||
## Spike C1 — MeshCore core compiles on P4 (2026-06-20) ✅
|
||||
|
||||
The core is vendored by **`tanmatsu/fetch-deps.sh`** from `.pio/libdeps/.../MeshCore` into
|
||||
`components/meshcore/core/` (gitignored) under a committed `components/meshcore/CMakeLists.txt`.
|
||||
The Arduino libs it needs (Crypto, RTClib, Adafruit BusIO, Adafruit Unified Sensor, Melopero
|
||||
RV3028, CayenneLPP, ArduinoJson) all go in ONE committed **`ardlibs`** component (so cross-lib
|
||||
`#include`s resolve). Build: `./build.sh build`. What it took:
|
||||
- vendored lib dir names must be **space-free** (spaces break IDF's component-req matching → `BUG:`).
|
||||
- the core's bundled **C ed25519** (`lib/ed25519/`) must be added explicitly (PlatformIO
|
||||
auto-compiles it as a nested lib; IDF doesn't).
|
||||
- `ardlibs` needs `nvs_flash` (Crypto's RNG seeds from NVS); `meshcore` needs `nvs_flash`+`efuse`.
|
||||
- **strip IDF's `-Werror=all`** from `COMPILE_OPTIONS` — vendored code trips reorder/format/
|
||||
class-memaccess, and a bare `-Wno-error` does NOT cancel `-Werror=all` (done in main/CMakeLists).
|
||||
- exclude over-globbed files vs `build_as_lib.py`: non-ESP32 platform helpers (nrf52/stm32/rp2040),
|
||||
`helpers/radiolib/*` (→ LoRa bridge), the S3 display drivers, `ESPNOWRadio`, `SerialBLEInterface`
|
||||
(BT off), and the `MC_VENDORED_TOUCH_APP` files (our own src/ ships them).
|
||||
|
||||
**Remaining for a running app — ordered worklist (next session):**
|
||||
1. **Tanmatsu define set** (`main/CMakeLists.txt` build flags) — a *curated subset* of the S3 touch
|
||||
`build_flags`: KEEP `ESP32_PLATFORM`, `MC_VENDORED_TOUCH_APP`, `UI_LVGL=1`, `HAS_TOUCH_UI=1`,
|
||||
`MULTI_TRANSPORT_COMPANION`, `LORA_FREQ/BW/SF`, `LV_CONF_PATH=lv_conf.h`+`LV_CONF_INCLUDE_SIMPLE=1`
|
||||
+`LV_INDEV_DEF_SCROLL_THROW=7`, `MAX_CONTACTS`, `ENABLE_PRIVATE_KEY_*`, `ENV_INCLUDE_GPS`,
|
||||
`-I src -I src/ui-touch -I include`. DROP every S3-board define (`HELTEC_LORA_V4*`, `USE_SX1262`,
|
||||
`RADIO_CLASS`/`WRAPPER_CLASS`, all `P_LORA_*`/`PIN_*`/TFT/CHSC6x, `ST7789_*`, `RADIOLIB_*`). Give
|
||||
the Tanmatsu its own board/display identity (badge-bsp, NOT `DISPLAY_CLASS=ST7789`).
|
||||
2. **`wadamesh_app` component** — glob `src/` MINUS `main.cpp` (own app_main) + the S3 input helpers
|
||||
(`HeltecV4CapTouch`, `TDeckKeyboard/Touch/Trackball` → Tanmatsu input is `bsp_input`); add
|
||||
`ui-touch/` once LVGL is in.
|
||||
3. **LVGL component** — vendor lvgl 8.3 + the repo `lv_conf.h`.
|
||||
4. **Transports** — re-add AsyncTCP+ESPAsyncWebServer with a WDT-config fix (`CONFIG_ESP_TASK_WDT_*`)
|
||||
or the ESP32Async fork; OR ship companion over USB-serial first (off the critical path).
|
||||
5. **`variants/tanmatsu/` glue** — `TanmatsuBoard` (board API over bsp/power), the LVGL→badge-bsp
|
||||
display driver (`flush_cb`→`bsp_display_blit`, 800×480 RGB565), a `bsp_input`→`lv_indev`
|
||||
keyboard/pointer, `radio_init`→the `TanmatsuLoraRadio` bridge, storage→the FAT `locfd` partition.
|
||||
6. **`app_main`** — replicate setup()/loop(): board.begin → radio_init → storage → the_mesh.begin →
|
||||
ui_task.begin(disp) → loop{ui_task.loop; the_mesh.loop}.
|
||||
7. **Install** — `make build` → `application.bin` → BadgeLink/AppFS → the badge.team app store.
|
||||
|
||||
Biggest chunks: UITask (28k lines) vs the Tanmatsu defines + LVGL, and the 800×480 display flush
|
||||
(768 KB/frame → PSRAM). All methodical integration on the proven foundation.
|
||||
|
||||
Dep status: vendored + compiling = MeshCore core, ed25519, Crypto, RTClib, Adafruit BusIO, Adafruit
|
||||
Unified Sensor, Melopero RV3028, CayenneLPP, ArduinoJson, MicroNMEA. Still to add = LVGL, AsyncTCP +
|
||||
ESPAsyncWebServer (WDT fix), and (only if telemetry wanted) the Adafruit sensor libs.
|
||||
|
||||
## Why this is feasible (verified 2026-06-18)
|
||||
|
||||
- **Arduino-ESP32 supports the ESP32-P4** — core **3.3.10** (2026-06-05) has P4
|
||||
boards incl. MIPI-display variants (`ESP32P4_4DS_MIPI`).
|
||||
- **PlatformIO can target it** via the **pioarduino** platform (rel `55.03.39`),
|
||||
whose board list already includes **`m5stack-tab5-p4`** — a P4 + MIPI display +
|
||||
C6-coprocessor-radio handheld, i.e. a structural near-twin of the Tanmatsu. Crib
|
||||
display/WiFi bring-up from that board.
|
||||
- **Radio maps cleanly:** `mesh::Radio` (7 pure virtuals) ↔ `tanmatsu-lora`'s
|
||||
`lora.h` (raw 256-byte tx/rx, settable freq/bw/sf/cr/sync/preamble/power,
|
||||
RSSI/SNR). Same SX1262/1268 family the S3 boards use. Bridge written (below).
|
||||
- **No driver writing:** display/keyboard/power/wifi/lora all exist as IDF
|
||||
components (`badge-bsp`, `tanmatsu-wifi`, `tanmatsu-lora`).
|
||||
|
||||
## Hardware / platform facts
|
||||
|
||||
| | | (confirmed from docs.tanmatsu.cloud + hackster/CNX) |
|
||||
|---|---|---|
|
||||
| Main SoC | ESP32-**P4** dual-core RISC-V | **@ 400 MHz, 32 MB PSRAM, 16 MB flash**, microSD (SDIO) |
|
||||
| Display | **3.97" MIPI-DSI, 800×480, RGB565** | `esp_lcd` panel from `badge-bsp`; we flush **LVGL 8.3** to it (not PAX) |
|
||||
| Input | **69-key QWERTY** + 6 fn keys | events via `bsp_input_get_queue()` |
|
||||
| Coprocessors | **WCH CH32V203** (keyboard + power) · **ESP32-C6** (WiFi/BLE/802.15.4 via `tanmatsu-wifi`) · **SX1262** LoRa 433/868-915 (via `tanmatsu-lora`, "remote") | the P4 has no native radio |
|
||||
| Native SDK | ESP-IDF ≥ 5.5, CMake, C, PAX graphics | template: `Nicolai-Electronics/tanmatsu-template` |
|
||||
|
||||
## Research findings (2026-06-18, no hardware)
|
||||
|
||||
**Radio bridge — fully validated against `tanmatsu-lora/lora.c`; the code was already correct.**
|
||||
- `lora_packet_stats_t = {rssi_pkt_raw u8, snr_pkt_raw i8, signal_rssi_pkt_raw u8}`; `rssi_dbm = -rssi_pkt_raw/2`, `snr_db = snr_pkt_raw/4`. ✓ matches the bridge.
|
||||
- `lora_send_packet()` is **blocking** (waits ≤2 s on the transaction semaphore) → `isSendComplete()` latch is correct. ✓
|
||||
- `config.bandwidth` = kHz, valid {7,10,15,20,31,41,62,125,250,500} (62.5→62); `config.coding_rate` = 5..8 → 4/5..4/8. `setParams()` is correct. ✓
|
||||
- Only remaining radio tuning (small, MeshCore-side): `sync_word` / `preamble_length` / `power` to match `RadioLibWrappers.cpp` so it interoperates with the existing mesh.
|
||||
|
||||
**Display/input — DECISION: keep wadamesh's LVGL 8.3 and write our own flush; do NOT adopt `badge-bsp-lvgl`.**
|
||||
- `badge-bsp` exposes the screen as an `esp_lcd` panel (`bsp_display_get_panel` / `_get_panel_io` / `_get_parameters` → h_res/v_res/color_format) and the keyboard as a FreeRTOS event queue (`bsp_input_get_queue`). Reference wiring: `badgeteam/konsool-template-lvgl`.
|
||||
- BUT the badge LVGL component (`badgeteam/esp32-component-badge-bsp-lvgl`) targets **LVGL 9.3**, and wadamesh's UITask is **LVGL 8.3**. Adopting it forces an 8→9 migration of ~30k lines — not worth it. So: bring up our own LVGL 8.3 (esp_lvgl_port supports 8.x, or a manual `flush_cb`) flushing to the badge-bsp `esp_lcd` panel, + an `lv_indev` that reads the `bsp_input` queue.
|
||||
- **UI layout target: 800×480 landscape** — make UITask responsive for it (it already branches on `hor_res`).
|
||||
|
||||
**Repos:** `badgeteam/esp32-component-badge-bsp` (BSP) · `badgeteam/konsool-template-lvgl` (LVGL-on-bsp reference) · `Nicolai-Electronics/esp32-component-tanmatsu-lora` (lora.c).
|
||||
|
||||
## Plan / status
|
||||
|
||||
- [x] Confirm framework gap is bridgeable (Arduino-P4 + pioarduino + Tab5 precedent)
|
||||
- [x] Map `mesh::Radio` ↔ `lora.h`; write `TanmatsuLoraRadio` (full drop-in for `radio_driver`: setParams/setTxPower/setRxBoostedGainMode/idle/getPacketsRecvErrors/RSSI/SNR)
|
||||
- [x] `idf_component.yml` for the BSP/wifi/lora components
|
||||
- [x] `boards/tanmatsu.json` (based on pioarduino `m5stack-tab5-p4`)
|
||||
- [x] `variants/tanmatsu/{target.h,target.cpp,TanmatsuBoard.h}` — wires `radio_driver`=bridge, `board`, `radio_init()`, `radio_new_identity()` (StdRNG)
|
||||
- [ ] **Compile spike** — get `src/` to build for P4 under pioarduino (no HW needed; see below)
|
||||
- [ ] `[env:tanmatsu_companion_radio_touch]` in `platformio.ini` — port the shared touch flags, drop S3 LoRa pins; **resolve the RadioLib build issue (next item)**
|
||||
- [x] RadioLib build question — RESOLVED: keep RadioLib in `lib_deps` + the same `RADIOLIB_*` flags as the S3 envs. `RadioLibWrappers.cpp` still compiles, but since the Tanmatsu `target.cpp` never instantiates `WRAPPER_CLASS`/`RADIO_CLASS`, the unused code is linker-stripped. Net: the env mirrors the T-Deck's RadioLib deps/flags; only the `radio_driver` *type* differs (set in `variants/tanmatsu/target.h`). No `build_src_filter` gymnastics needed.
|
||||
- [ ] Wire the IDF components into the pio build (component manager) ⚠️ main risk
|
||||
- [ ] **Headless bring-up:** call `radio_init()` → `TanmatsuLoraRadio` joins the mesh (serial log)
|
||||
- [ ] WiFi/BLE via `esp_wifi_remote`/`tanmatsu-wifi` (likely close to free in Arduino)
|
||||
- [ ] LVGL display driver: flush → `bsp_display_blit`; LVGL indev ← `bsp/input` (keyboard)
|
||||
- [ ] Make UITask responsive for the larger screen (it already branches on `hor_res`)
|
||||
- [ ] Tune the `TanmatsuLoraRadio` TODO(device) items
|
||||
- [ ] Release path (separate bin; not covered by the `wadamesh-release` skill yet)
|
||||
|
||||
## What's done this session (no hardware)
|
||||
|
||||
A structurally-complete variant skeleton — the radio + board wiring is real (mirrors
|
||||
the T-Deck variant); display/input/wifi glue + the env are the device/spike work left.
|
||||
|
||||
- `variants/tanmatsu/TanmatsuLoraRadio.{h,cpp}` — the LoRa bridge, real code against
|
||||
the actual `lora.h` v0.3.0 API + `mesh::Radio` contract, reusing MeshCore's own
|
||||
airtime/packetScore math. A full drop-in for `radio_driver` (every method the app
|
||||
calls in `MyMesh.cpp`). Compiles-ready *pending* the `TODO(device)` items.
|
||||
- `variants/tanmatsu/target.{h,cpp}` — wires `radio_driver` (the bridge), `board`,
|
||||
`radio_init()` (→ `lora_init_remote`), `radio_new_identity()` (seeded via StdRNG).
|
||||
- `variants/tanmatsu/TanmatsuBoard.h` — `: public ESP32Board`; battery/power via
|
||||
badge-bsp left as TODO(device).
|
||||
- `variants/tanmatsu/idf_component.yml` — the managed-component deps.
|
||||
- `boards/tanmatsu.json` — P4/16MB/PSRAM board def (based on `m5stack-tab5-p4`;
|
||||
confirm flash size + f_cpu on the real unit).
|
||||
- This tracker + the `tanmatsu-port` memory.
|
||||
|
||||
### `TanmatsuLoraRadio` — TODO(device) (confirm/tune on hardware)
|
||||
- `lora_packet_stats_t` field names + raw→real RSSI/SNR conversion (assumed `-raw/2`, `raw/4`).
|
||||
- `bandwidth` units (assumed kHz) and `coding_rate` encoding (assumed 4/x, x=5..8).
|
||||
- `sync_word` / `preamble_length` — match `RadioLibWrappers.cpp` so it interoperates with the existing mesh.
|
||||
- Whether `lora_send_packet()` is blocking (assumed yes → `isSendComplete()` latch is fine).
|
||||
- Where the variant creates the radio object + calls `begin()`/`setParams()` (mirror the S3 radio glue in the variant target).
|
||||
|
||||
## Compile spike (run first, needs only the toolchain)
|
||||
|
||||
```bash
|
||||
# add the env (below) to platformio.ini + a stub boards/tanmatsu.json, then:
|
||||
PIO=/Users/kaj/Library/Python/3.9/bin/pio
|
||||
$PIO run -e tanmatsu_companion_radio_touch # expect: platform/toolchain resolves, src/ starts compiling
|
||||
```
|
||||
Outcome tells us: does pioarduino-P4 + Arduino build the shared `src/`? Compiles →
|
||||
proceed with display/radio glue. Fails on the IDF-component wiring → that's the known
|
||||
risk; fall back to the IDF-subproject layout.
|
||||
|
||||
### env block to add to `platformio.ini` (draft)
|
||||
```ini
|
||||
[env:tanmatsu_companion_radio_touch]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.39/platform-espressif32.zip
|
||||
framework = arduino
|
||||
board = tanmatsu ; boards/tanmatsu.json — model on pioarduino m5stack-tab5-p4
|
||||
build_flags =
|
||||
${env.build_flags} ; reuse the shared touch flags
|
||||
-D HAS_TANMATSU=1
|
||||
-I variants/tanmatsu
|
||||
; board_build.partitions = variants/tanmatsu/partitions_tanmatsu.csv ; TODO
|
||||
; the BSP/wifi/lora IDF components are pulled via variants/tanmatsu/idf_component.yml
|
||||
```
|
||||
|
||||
## Risks / open questions
|
||||
1. **IDF components inside PlatformIO** (`badge-bsp`/`tanmatsu-lora`) — the fiddliest part; pioarduino supports the component manager but expect friction. **Still the #1 risk.**
|
||||
2. **Display = our own LVGL 8.3 flush onto the badge-bsp `esp_lcd` panel** (decided; don't use the LVGL-9.3 `badge-bsp-lvgl`). Work = the flush_cb + the `bsp_input`→`lv_indev` glue + an 800×480 layout pass on UITask. Bounded now that the BSP API + konsool reference are known.
|
||||
3. **WiFi/BLE via the C6** (`esp_wifi_remote` + `esp_hosted` over SDIO). Arduino `WiFi.h` on the P4 *is* backed by `esp_wifi_remote` in arduino-esp32 3.x, so wadamesh's WiFi (tile fetch, setup wizard, network tab) can likely run **mostly unchanged** — but it needs the C6 on the right `esp_hosted` slave firmware + the components wired in, and it's a **known-finicky area** in practice (cf. esphome#10956). The template uses IDF wifi helpers (`wifi_remote.h`), not Arduino `WiFi.h`, so confirm Arduino WiFi coexists with `tanmatsu-wifi`'s C6 bring-up during the spike. A real task + risk, not free — but not a rewrite.
|
||||
4. **Heap/PSRAM** — fine: **32 MB PSRAM**, and an 800×480 RGB565 buffer is only ~768 KB. Lots of headroom vs the S3 boards.
|
||||
|
||||
## References
|
||||
- Template: https://github.com/Nicolai-Electronics/tanmatsu-template (ESP-IDF, P4, PAX, badge-bsp)
|
||||
- LoRa component: https://github.com/Nicolai-Electronics/esp32-component-tanmatsu-lora (`include/lora.h`, v0.3.0)
|
||||
- Precedent board: pioarduino `m5stack-tab5-p4` (P4 + MIPI + C6)
|
||||
- `mesh::Radio` base: monorepo `src/Dispatcher.h`; example backend `src/helpers/esp32/ESPNOWRadio.h`
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1"
|
||||
],
|
||||
"f_cpu": "400000000L",
|
||||
"f_flash": "80000000L",
|
||||
"f_psram": "200000000L",
|
||||
"flash_mode": "qio",
|
||||
"mcu": "esp32p4",
|
||||
"variant": "esp32p4"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth"],
|
||||
"debug": { "openocd_target": "esp32p4.cfg" },
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "Tanmatsu (ESP32-P4 + C6)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 512000,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 1500000
|
||||
},
|
||||
"url": "https://nicolaielectronics.nl/",
|
||||
"vendor": "Nicolai Electronics"
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the Mesh America Configurator catalog JSON for wadamesh.
|
||||
|
||||
Mesh America (apps.meshamerica.com — the flasher Cascadia Mesh uses) merges a
|
||||
provider "catalog" JSON into its device list. Spec: third-party hosts ONE static
|
||||
JSON (this file's output) at a stable HTTPS+CORS URL, plus the firmware bins over
|
||||
HTTPS+CORS; their app reads it live. See provider-api-spec.md from TJ Downes.
|
||||
|
||||
We host:
|
||||
- the bins on firmware.wadamesh.com/releases/TOUCH/<TAG>/ (Cloudflare HTTPS +
|
||||
`Access-Control-Allow-Origin: *`, immutable per-tag dir — see deploy/nginx/
|
||||
firmware.wadamesh.com.conf), and
|
||||
- this catalog at a stable HTTPS+CORS URL (deployed alongside the firmware).
|
||||
|
||||
Usage: python3 deploy/gen-meshamerica-catalog.py <TAG> > meshamerica-catalog.json
|
||||
(TAG defaults to the newest beta_N dir; notes come from release-notes/<TAG>.txt)
|
||||
|
||||
Re-run it each release (or wire it into scripts/release.sh) so the catalog always
|
||||
points at the newest immutable bins — the spec wants firmware URLs treated as
|
||||
immutable, so each release adds a fresh versioned URL.
|
||||
"""
|
||||
import sys, json, os, glob, re
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def newest_tag():
|
||||
tags = []
|
||||
for p in glob.glob(os.path.join(REPO, "release-notes", "beta_*.txt")):
|
||||
m = re.search(r"beta_(\d+)", os.path.basename(p))
|
||||
if m:
|
||||
tags.append(int(m.group(1)))
|
||||
return "beta_%d" % max(tags) if tags else "beta_9"
|
||||
|
||||
TAG = sys.argv[1] if len(sys.argv) > 1 else newest_tag()
|
||||
BASE = "https://firmware.wadamesh.com/releases/TOUCH/%s" % TAG
|
||||
ICON = "https://wadamesh.com/wadamesh-icon-512.png"
|
||||
|
||||
# "What's new" — one user-facing line per non-blank, non-# line of the notes file.
|
||||
notes = ""
|
||||
nf = os.path.join(REPO, "release-notes", "%s.txt" % TAG)
|
||||
if os.path.exists(nf):
|
||||
notes = "\n".join(l.strip() for l in open(nf, encoding="utf-8")
|
||||
if l.strip() and not l.strip().startswith("#"))
|
||||
|
||||
# Both touch boards are ESP32-S3, single touch-UI ("gui") role. ota_0 sits at
|
||||
# 0x10000 on both (partitions_t*_touch.csv), so flash-update@0x10000 is correct.
|
||||
BOARDS = [
|
||||
{"name": "wadamesh — Heltec V4 TFT", "slug": "heltec-v4-tft"},
|
||||
{"name": "wadamesh — LilyGo T-Deck", "slug": "tdeck"},
|
||||
]
|
||||
|
||||
def device(b):
|
||||
merged, appbin = "wadamesh-%s-merged.bin" % b["slug"], "wadamesh-%s.bin" % b["slug"]
|
||||
return {
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": b["name"],
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='%s'>" % ICON,
|
||||
"firmware": [{
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {TAG: {
|
||||
"notes": notes,
|
||||
"files": [
|
||||
{"type": "flash-wipe", "name": merged,
|
||||
"url": "%s/%s" % (BASE, merged),
|
||||
"title": "Full install (bootloader + firmware)"},
|
||||
{"type": "flash-update", "name": appbin,
|
||||
"url": "%s/%s" % (BASE, appbin),
|
||||
"title": "Update (app only)"},
|
||||
],
|
||||
}},
|
||||
}],
|
||||
}
|
||||
|
||||
catalog = {
|
||||
"maker": {"wadamesh": {"name": "wadamesh"}},
|
||||
"device": [device(b) for b in BOARDS],
|
||||
}
|
||||
print(json.dumps(catalog, indent=2, ensure_ascii=False))
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"maker": {
|
||||
"wadamesh": {
|
||||
"name": "wadamesh"
|
||||
}
|
||||
},
|
||||
"device": [
|
||||
{
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": "wadamesh — Heltec V4 TFT",
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='https://wadamesh.com/wadamesh-icon-512.png'>",
|
||||
"firmware": [
|
||||
{
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {
|
||||
"beta_14": {
|
||||
"notes": "Fixed a bug introduced in beta_13: if you had turned keyboard navigation OFF (Settings -> Keyboard), the physical keyboard stopped typing into text fields entirely on the T-Deck and Tanmatsu. Typing now works again with keyboard navigation off; with it on, nothing changed. (The Heltec V4 was never affected.)\nIncludes everything from beta_13: room-server posts show the author's name, the Direct (0-hop) contact + map filter, beer emoji with a status-bar unread tap and a new-contact toast toggle and Czech accents, optional trackball edge-scroll, the dedicated special-characters picker, the 12-hour clock applied everywhere, and the keyboard navigation + typing improvements.",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-heltec-v4-tft-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_14/wadamesh-heltec-v4-tft-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-heltec-v4-tft.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_14/wadamesh-heltec-v4-tft.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": "wadamesh — LilyGo T-Deck",
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='https://wadamesh.com/wadamesh-icon-512.png'>",
|
||||
"firmware": [
|
||||
{
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {
|
||||
"beta_14": {
|
||||
"notes": "Fixed a bug introduced in beta_13: if you had turned keyboard navigation OFF (Settings -> Keyboard), the physical keyboard stopped typing into text fields entirely on the T-Deck and Tanmatsu. Typing now works again with keyboard navigation off; with it on, nothing changed. (The Heltec V4 was never affected.)\nIncludes everything from beta_13: room-server posts show the author's name, the Direct (0-hop) contact + map filter, beer emoji with a status-bar unread tap and a new-contact toast toggle and Czech accents, optional trackball edge-scroll, the dedicated special-characters picker, the 12-hour clock applied everywhere, and the keyboard navigation + typing improvements.",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-tdeck-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_14/wadamesh-tdeck-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-tdeck.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_14/wadamesh-tdeck.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+92
-16
@@ -42,19 +42,20 @@
|
||||
font-family:'JetBrains Mono',monospace; background:#fff; transition:border-color .15s, color .15s}
|
||||
.kicker:hover{border-color:var(--teal); color:var(--ink)}
|
||||
.freeline{margin:0 auto 24px; color:var(--mut); font-size:.92rem}
|
||||
.fab{position:fixed; top:16px; z-index:45; width:44px; height:44px; border-radius:50%; display:grid; place-items:center;
|
||||
.fab-rail{position:fixed; top:16px; z-index:45; display:flex; flex-direction:column; gap:12px}
|
||||
.fab-rail.left{left:16px} .fab-rail.right{right:16px}
|
||||
.fab{width:44px; height:44px; border-radius:50%; display:grid; place-items:center; cursor:pointer;
|
||||
background:#fff; border:1px solid var(--line); color:#46525c; box-shadow:0 10px 26px -14px rgba(21,24,30,.45);
|
||||
transition:border-color .15s, color .15s, transform .15s, box-shadow .15s}
|
||||
.fab:hover{border-color:var(--teal); color:var(--teal-ink); transform:translateY(-2px); box-shadow:0 14px 30px -14px rgba(21,182,166,.5)}
|
||||
.fab svg{width:21px; height:21px}
|
||||
.fab-tg{left:16px}
|
||||
.fab-bug{right:16px}
|
||||
.brand{display:flex; align-items:center; justify-content:center; gap:clamp(12px,2.6vw,20px); flex-wrap:wrap}
|
||||
.brand{display:flex; align-items:center; justify-content:center; gap:clamp(12px,2.6vw,20px); flex-wrap:wrap; position:relative}
|
||||
.brand .mark{width:clamp(60px,13vw,88px); height:auto; flex:none}
|
||||
.brand .word{font-weight:700; font-size:clamp(2rem,7.5vw,3.35rem); letter-spacing:3px; color:var(--ink); line-height:1}
|
||||
.brand .word .t{color:var(--teal)}
|
||||
.brand-text{display:flex; flex-direction:column; align-items:center; gap:5px}
|
||||
.byline{font-family:'JetBrains Mono',monospace; font-size:clamp(.62rem,1.7vw,.8rem); letter-spacing:.2em; text-transform:uppercase; color:var(--mut); font-weight:600}
|
||||
/* byline floats beneath the whole lockup, centred on mark+word combined (mark still centres on the word) */
|
||||
.byline{position:absolute; top:100%; left:0; width:100%; margin-top:7px; text-align:center;
|
||||
font-family:'JetBrains Mono',monospace; font-size:clamp(.62rem,1.7vw,.8rem); letter-spacing:.2em; text-transform:uppercase; color:var(--mut); font-weight:600}
|
||||
/* animated mesh mark — repeaters broadcast pings; signal packets ride the trails */
|
||||
.brand .mark{overflow:visible}
|
||||
.wm-flow{display:none}
|
||||
@@ -149,6 +150,27 @@
|
||||
.noserial{display:none;color:#b4631a;text-align:center;margin-top:14px}
|
||||
body.no-serial .noserial{display:block}
|
||||
|
||||
/* community videos */
|
||||
.vids{display:grid; gap:18px; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); margin-top:34px}
|
||||
.vid{border:1px solid var(--line); border-radius:16px; overflow:hidden; background:var(--card); box-shadow:0 12px 30px -22px rgba(21,24,30,.5)}
|
||||
.vid-thumb{position:relative; aspect-ratio:16/9; width:100%; border:0; padding:0; cursor:pointer; display:block; background-size:cover; background-position:center}
|
||||
.vid-thumb::after{content:""; position:absolute; inset:0; background:rgba(21,24,30,.12); transition:background .15s}
|
||||
.vid-thumb:hover::after{background:rgba(21,24,30,0)}
|
||||
.vid-thumb .play{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); width:62px; height:44px; border-radius:12px;
|
||||
background:rgba(229,57,53,.94); display:grid; place-items:center; z-index:1; transition:transform .15s, background .15s}
|
||||
.vid-thumb:hover .play{transform:translate(-50%,-50%) scale(1.07); background:#e53935}
|
||||
.vid-thumb .play svg{width:24px; height:24px; color:#fff}
|
||||
.vid-embed{aspect-ratio:16/9; width:100%; border:0; display:block; background:#000}
|
||||
.vid-cap{padding:13px 15px; font-size:.92rem; color:var(--ink); font-weight:600}
|
||||
.vid-cap span{display:block; color:var(--mut); font-size:.82rem; font-weight:400; margin-top:2px}
|
||||
|
||||
/* toast (docs placeholder etc.) */
|
||||
.toast{position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(12px); z-index:70;
|
||||
background:var(--ink); color:#fff; padding:11px 20px; border-radius:100px; font-size:.92rem; font-weight:600;
|
||||
box-shadow:0 16px 36px -14px rgba(21,24,30,.55); opacity:0; visibility:hidden; pointer-events:none;
|
||||
transition:opacity .2s ease, transform .2s ease}
|
||||
.toast.show{opacity:1; visibility:visible; transform:translateX(-50%) translateY(0)}
|
||||
|
||||
footer{border-top:1px solid var(--line); margin-top:48px; padding:30px 0 56px; text-align:center; color:var(--mut); font-size:.86rem}
|
||||
footer a{color:var(--teal-ink)}
|
||||
.legal-links{margin-top:12px}
|
||||
@@ -175,12 +197,26 @@
|
||||
<div id="vanta-bg"></div>
|
||||
|
||||
<!-- floating quick links -->
|
||||
<a class="fab fab-tg" href="https://t.me/+fHuhG67-RFJlNmFk" target="_blank" rel="noopener" aria-label="Join us on Telegram" title="Join us on Telegram">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.04 15.47 8.7 19.9c.5 0 .72-.22.98-.47l2.36-2.22 4.89 3.53c.9.5 1.53.24 1.77-.83l3.2-14.5c.3-1.33-.5-1.85-1.36-1.53L1.9 9.46c-1.3.5-1.28 1.22-.22 1.55l5.05 1.57L18.4 5.9c.55-.36 1.05-.16.64.2L9.04 15.47z"/></svg>
|
||||
</a>
|
||||
<a class="fab fab-bug" href="https://github.com/ALLFATHER-BV/wadamesh/issues" target="_blank" rel="noopener" aria-label="Report a bug on GitHub" title="Report a bug">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
|
||||
</a>
|
||||
<div class="fab-rail left">
|
||||
<a class="fab" href="https://t.me/+fHuhG67-RFJlNmFk" target="_blank" rel="noopener" aria-label="Join us on Telegram" title="Join us on Telegram">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.04 15.47 8.7 19.9c.5 0 .72-.22.98-.47l2.36-2.22 4.89 3.53c.9.5 1.53.24 1.77-.83l3.2-14.5c.3-1.33-.5-1.85-1.36-1.53L1.9 9.46c-1.3.5-1.28 1.22-.22 1.55l5.05 1.57L18.4 5.9c.55-.36 1.05-.16.64.2L9.04 15.47z"/></svg>
|
||||
</a>
|
||||
<a class="fab" href="#videos" aria-label="Community videos" title="Community videos">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="fab-rail right">
|
||||
<a class="fab" href="https://github.com/ALLFATHER-BV/wadamesh/issues" target="_blank" rel="noopener" aria-label="Report a bug on GitHub" title="Report a bug">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
|
||||
</a>
|
||||
<button class="fab" id="docsBtn" type="button" aria-label="Documentation" title="Documentation">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v5h5"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
|
||||
</button>
|
||||
<a class="fab" href="https://t413.com/p/projects/MapTileDl/" target="_blank" rel="noopener" aria-label="Map tile downloader" title="Map tile downloader">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3z"/><path d="M9 3v15"/><path d="M15 6v15"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||||
|
||||
<header>
|
||||
<div class="wrap">
|
||||
@@ -216,10 +252,8 @@
|
||||
<!-- repeater nodes -->
|
||||
<g class="wm-nodes" fill="#15B6A6"><circle cx="10" cy="60" r="7"/><circle cx="90" cy="60" r="7"/><circle cx="170" cy="60" r="7"/><circle cx="50" cy="12" r="7"/><circle cx="130" cy="12" r="7"/><circle cx="50" cy="108" r="7"/><circle cx="130" cy="108" r="7"/></g>
|
||||
</svg>
|
||||
<div class="brand-text">
|
||||
<span class="word mono">WADA<span class="t">MESH</span></span>
|
||||
<span class="byline">by meshcomod</span>
|
||||
</div>
|
||||
<span class="word mono">WADA<span class="t">MESH</span></span>
|
||||
<span class="byline">by meshcomod</span>
|
||||
</div>
|
||||
<p class="lede">A real touchscreen UI for your mesh radio.</p>
|
||||
<p class="sub">MeshCore MESHCOMOD firmware for the <b>LilyGo T-Deck</b> and <b>Heltec V4</b> — map, chat, channels and contacts on the device itself, <b>and</b> a companion radio for the <b>MeshCore app</b> at the same time.</p>
|
||||
@@ -305,6 +339,14 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="videos">
|
||||
<div class="wrap">
|
||||
<h2 class="h2">Community videos</h2>
|
||||
<p class="p">Walkthroughs, demos and reviews from the community.</p>
|
||||
<div class="vids" id="vids"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="wrap">
|
||||
Built for the mesh · open source under GPL-3.0 ·
|
||||
@@ -421,6 +463,40 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
|
||||
if (notes.length) document.querySelectorAll('[data-whatsnew]').forEach(b => b.hidden = false);
|
||||
}).catch(()=>{});
|
||||
|
||||
// ---- community videos — add an entry here to add a video ----
|
||||
const VIDEOS = [
|
||||
{ id: 'V19od8jbFRc', title: 'WadaMesh: The Best T-Deck Firmware So Far', by: 'KDHD Stuff' },
|
||||
// { id: '<youtube-id>', title: '<title>', by: '<channel>' },
|
||||
];
|
||||
const vidsEl = document.getElementById('vids');
|
||||
if (vidsEl) VIDEOS.forEach(v => {
|
||||
const card = document.createElement('div'); card.className = 'vid';
|
||||
const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'vid-thumb';
|
||||
btn.setAttribute('aria-label', 'Play: ' + (v.title || 'community video'));
|
||||
btn.style.backgroundImage = 'url(https://i.ytimg.com/vi/' + v.id + '/hqdefault.jpg)';
|
||||
btn.innerHTML = '<span class="play"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg></span>';
|
||||
btn.addEventListener('click', () => {
|
||||
const f = document.createElement('iframe'); f.className = 'vid-embed';
|
||||
f.src = 'https://www.youtube-nocookie.com/embed/' + v.id + '?autoplay=1&rel=0';
|
||||
f.title = v.title || 'Community video';
|
||||
f.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
|
||||
f.allowFullscreen = true; f.loading = 'lazy';
|
||||
card.replaceChild(f, btn);
|
||||
});
|
||||
card.appendChild(btn);
|
||||
if (v.title) {
|
||||
const cap = document.createElement('div'); cap.className = 'vid-cap'; cap.textContent = v.title;
|
||||
if (v.by) { const s = document.createElement('span'); s.textContent = 'by ' + v.by; cap.appendChild(s); }
|
||||
card.appendChild(cap);
|
||||
}
|
||||
vidsEl.appendChild(card);
|
||||
});
|
||||
|
||||
// ---- toast + docs placeholder ----
|
||||
function toast(msg){ const t = document.getElementById('toast'); if (!t) return; t.textContent = msg; t.classList.add('show'); clearTimeout(t._t); t._t = setTimeout(() => t.classList.remove('show'), 2400); }
|
||||
const docsBtn = document.getElementById('docsBtn');
|
||||
if (docsBtn) docsBtn.addEventListener('click', () => toast('Documentation — coming soon!'));
|
||||
|
||||
</script>
|
||||
|
||||
<!-- animated mesh backdrop — Vanta NET (3D dot-and-line network) in wadamesh teal on white.
|
||||
|
||||
@@ -48,6 +48,14 @@
|
||||
#define LV_FONT_MONTSERRAT_12 1
|
||||
#define LV_FONT_MONTSERRAT_14 1
|
||||
#define LV_FONT_MONTSERRAT_16 1
|
||||
/* Larger Montserrat sizes for the Tanmatsu's crisp "UI size" (Large/Huge) — render the UI bigger
|
||||
* at native resolution instead of upscaling a low-res frame. Gated to the Tanmatsu so the
|
||||
* flash-tighter S3 touch builds don't pay for fonts they never use. */
|
||||
#if defined(HAS_TANMATSU)
|
||||
#define LV_FONT_MONTSERRAT_18 1
|
||||
#define LV_FONT_MONTSERRAT_20 1
|
||||
#define LV_FONT_MONTSERRAT_24 1
|
||||
#endif
|
||||
/* 28 px Montserrat for the boot splash title — keeps the rest of the UI on
|
||||
* the smaller fonts so the .data cost stays modest. */
|
||||
#define LV_FONT_MONTSERRAT_28 1
|
||||
|
||||
+21
-5
@@ -17,8 +17,13 @@ board_build.partitions = partitions_tft_touch.csv
|
||||
extra_scripts = pre:scripts/inject_wifi_env.py, merge-bin.py
|
||||
|
||||
build_flags =
|
||||
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-Wno-deprecated-declarations -Wno-unused-parameter -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-D MC_VENDORED_TOUCH_APP
|
||||
; NOTE: tried -D ARDUINO_USB_MODE=1 (HW-CDC, drops unused TinyUSB MSC/DFU ≈ 12 KB internal
|
||||
; DRAM) — but on the real V4 the binary companion link regressed: small per-message frames
|
||||
; arrived yet the larger device-profile frame (node name + keys) dropped bytes over HW-CDC's
|
||||
; smaller TX FIFO. Reverted to the board JSON's TinyUSB CDC. Revisit only with an HW-CDC TX
|
||||
; buffer/DTR-wait fix verified on hardware. (T-Deck ships MODE=1 and is fine — board-specific.)
|
||||
-D LORA_FREQ=869.618
|
||||
-D LORA_BW=62.5
|
||||
-D LORA_SF=8
|
||||
@@ -126,6 +131,9 @@ build_flags =
|
||||
-D ENABLE_ADVERT_ON_BOOT=0
|
||||
-D LV_CONF_PATH=lv_conf.h
|
||||
-D LV_CONF_INCLUDE_SIMPLE=1
|
||||
; Smoother scroll: lower deceleration than LVGL's default (10) so flicks glide
|
||||
; longer instead of stopping abruptly. lv_conf leaves this unset, so this -D wins.
|
||||
-D LV_INDEV_DEF_SCROLL_THROW=7
|
||||
-D USER_SETUP_LOADED=1
|
||||
-D ST7789_DRIVER=1
|
||||
-D TFT_WIDTH=240
|
||||
@@ -146,6 +154,7 @@ build_flags =
|
||||
-D DISPLAY_CLASS=ST7789LCDDisplay
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MULTI_TRANSPORT_COMPANION=1
|
||||
-D WADAMESH_FORK_BUILD=1
|
||||
-D TCP_PORT=5000
|
||||
-D WS_PORT=8765
|
||||
-D BLE_PIN_CODE=123456
|
||||
@@ -166,7 +175,7 @@ lib_deps =
|
||||
; MeshCore core = the meshcomod monorepo built as a lib, pinned by tag.
|
||||
; Touch-app files (TouchPrefsStore, WifiRuntimeStore, transports, ...) are
|
||||
; vendored in this app's src/ and dropped from the lib via -DMC_VENDORED_TOUCH_APP.
|
||||
https://github.com/ALLFATHER-BV/meshcomod.git#core-v1.16.2
|
||||
https://github.com/ALLFATHER-BV/meshcomod.git#core-v1.16.4
|
||||
SPI
|
||||
Wire
|
||||
jgromes/RadioLib @ ^7.6.0
|
||||
@@ -212,7 +221,7 @@ board_build.partitions = variants/lilygo_tdeck/partitions_tdeck_touch.csv
|
||||
extra_scripts = pre:scripts/inject_wifi_env.py, merge-bin.py
|
||||
|
||||
build_flags =
|
||||
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-Wno-deprecated-declarations -Wno-unused-parameter -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-D MC_VENDORED_TOUCH_APP
|
||||
-D LORA_FREQ=869.618
|
||||
-D LORA_BW=62.5
|
||||
@@ -238,7 +247,10 @@ build_flags =
|
||||
-I variants/lilygo_tdeck
|
||||
-D LILYGO_TDECK
|
||||
-D BOARD_HAS_PSRAM=1
|
||||
-D CORE_DEBUG_LEVEL=1
|
||||
; 0 = silence ARDUHAL [E] spam (benign vfs_api cache-miss "no permits for
|
||||
; creation" + Preferences nvs_open NOT_FOUND on first-namespace-access) that
|
||||
; also pollutes the USB-CDC companion stream. Our own Serial.printf logs stay.
|
||||
-D CORE_DEBUG_LEVEL=0
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_PERF_POWERON=10
|
||||
@@ -307,11 +319,15 @@ build_flags =
|
||||
-D PIN_TB_RIGHT=1
|
||||
-D LV_CONF_PATH=lv_conf.h
|
||||
-D LV_CONF_INCLUDE_SIMPLE=1
|
||||
; Smoother scroll: lower deceleration than LVGL's default (10) so flicks glide
|
||||
; longer instead of stopping abruptly. lv_conf leaves this unset, so this -D wins.
|
||||
-D LV_INDEV_DEF_SCROLL_THROW=7
|
||||
-D MAX_CONTACTS=2000
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7789LCDDisplay
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MULTI_TRANSPORT_COMPANION=1
|
||||
-D WADAMESH_FORK_BUILD=1
|
||||
-D TCP_PORT=5000
|
||||
-D WS_PORT=8765
|
||||
-D BLE_PIN_CODE=123456
|
||||
@@ -329,7 +345,7 @@ build_src_filter =
|
||||
+<../variants/lilygo_tdeck/*.cpp>
|
||||
|
||||
lib_deps =
|
||||
https://github.com/ALLFATHER-BV/meshcomod.git#core-v1.16.2
|
||||
https://github.com/ALLFATHER-BV/meshcomod.git#core-v1.16.4
|
||||
SPI
|
||||
Wire
|
||||
jgromes/RadioLib @ ^7.6.0
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# beta_10 — what's new (one user-facing note per non-blank, non-# line)
|
||||
|
||||
# --- T-Deck input ---
|
||||
New trackball mode — switch the T-Deck trackball between a mouse cursor (the default) and D-pad navigation (roll to move the selection, click to choose), under Settings → Keyboard. Handy if you prefer button-style navigation.
|
||||
|
||||
# --- Map ---
|
||||
microSD map tiles now fill in as you explore: with "Use microSD for tiles" on, the device also downloads any missing tiles over Wi-Fi and saves them straight onto the SD card, so your offline library grows instead of showing blank areas past its edge (thanks 1nerdherder, #20)
|
||||
|
||||
# --- Power ---
|
||||
Experimental battery saver (idle power-save): throttles the CPU when the device is parked — screen off, on battery, no app connected and the mesh quiet — to trim idle power use, without ever missing a received packet. Off by default; enable it under Settings → System → Battery. (Dan Vybiral / @Vybo, #19)
|
||||
|
||||
# --- Settings ---
|
||||
New "Battery" section under Settings → System — opens the battery & power-history screen (the same one you get by tapping the battery icon up top) and is where the experimental battery saver now lives (it moved out of the Lock settings).
|
||||
@@ -0,0 +1,22 @@
|
||||
# beta_11 — what's new (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- Keyboard navigation (headline) ---
|
||||
New keyboard navigation — drive the whole UI from the keyboard, no touchscreen needed: the WASDZ cluster moves the on-screen selection in the actual direction you press (W up, A left, Z down, D right), S selects, Q goes back, and each bottom tab has its own hotkey (E Messages, R Contacts, T Home, U Map, I Settings) shown as a small letter on its icon. On by default; toggle it any time in Settings → Keyboard.
|
||||
Every navigation key is hotkey-bindable — the directions, select, back, the per-tab hotkeys, and the new scroll-up / scroll-down keys (default F / C) — remap them all under Settings → Keyboard.
|
||||
This lays the groundwork for upcoming keyboard-first devices like the Tanmatsu T-Pager that have no touchscreen, and it already gives every T-Deck a fast, reliable way to use wadamesh entirely from the keyboard.
|
||||
The selected item now shows as clean reverse-video (a bright outline for switches) instead of a coloured border, and the highlight only appears while you're actively navigating with the keys.
|
||||
The Home hotkey mirrors tapping the Home tab — press it again on Home to flip between the Commander screen and the app drawer.
|
||||
|
||||
# --- Stability ---
|
||||
Fixed a reboot that could occur when receiving a message, especially during advert/message floods — a use-after-free while a list rebuilt mid-scroll (#27).
|
||||
After a crash, the device now detects it on the next boot and offers to save the crash report so you can send it in, instead of you having to dig through Settings.
|
||||
|
||||
# --- Map ---
|
||||
Map options can now switch the zoom control between the slider and a +/- button pair, like older builds (#26).
|
||||
Route replay now opens on a wide overview so you can see where the hops start and the whole path at a glance — and you can pan and zoom the map freely while it plays.
|
||||
|
||||
# --- Home ---
|
||||
New "App drawer as home" option (in the app drawer's cog) makes the launcher your home screen instead of the Commander view, for people who prefer the app grid.
|
||||
|
||||
# --- Open source ---
|
||||
The in-progress Tanmatsu (ESP32-P4) port source is now part of the public tree.
|
||||
@@ -0,0 +1,8 @@
|
||||
# beta_12 — GPS hotfix (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- GPS fix (headline) ---
|
||||
Fixed GPS being slow to get a fix — or sometimes never acquiring at all — on the T-Deck. The GPS receives its data over a small serial buffer, and on the busy touch UI a heavy screen redraw could stall long enough to overflow it and drop the satellite data mid-stream, which forced the receiver to start over. The buffer is now 16x larger, so those redraws no longer cost you the fix — acquisition is back to normal (a fix in about a minute instead of several, or none).
|
||||
|
||||
# --- A note from Kaj ---
|
||||
More and more people are picking up wadamesh lately — which is genuinely awesome — and it's surfacing feature requests and bug reports fast, in big numbers.
|
||||
I can't get back to every post in every channel, but I'm on it: I'm prioritising the fixes and quality-of-life improvements, and I'll be working through them later today. Thanks for all the reports and for the patience!
|
||||
@@ -0,0 +1,29 @@
|
||||
# beta_13 — community drop (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- A note from Kaj (headline) ---
|
||||
This one's a community release — thank you! A bunch of the features below came from contributor pull requests, and the rest are the keyboard, typing and quality-of-life fixes I promised after the last drop. Huge thanks to everyone sending PRs, opening issues and reporting bugs: Yazutsu, tun0, Vybo and samuelcoustet all have their work in this build. Keep it coming.
|
||||
|
||||
# --- Typing & keyboard navigation (T-Deck + Tanmatsu) ---
|
||||
Open a chat or channel and the cursor is ready — you can type straight away. Every other text field now waits for Enter before it captures the keyboard, so the letter-navigation keys keep working until you actually want to type.
|
||||
Pressing Enter (or Esc / the back button) on an empty message box drops the cursor and re-selects the field, so you can navigate away with the buttons again.
|
||||
The menu-bar letter shortcuts no longer jump you to another page while you're typing in a field.
|
||||
New "Show menu-bar letters" setting under Settings -> Keyboard (off by default) — turn it on to see the tab hotkey letters on the bar.
|
||||
The accent pop-up no longer grabs the keyboard when it appears, so you can keep typing right through it.
|
||||
|
||||
# --- Composer ---
|
||||
New dedicated "Special characters" picker for symbols that aren't on the keyboard (% $ @ # & =, currency, maths, accents) — separate from the emoji picker.
|
||||
Long-press one of your sent messages to Resend it.
|
||||
|
||||
# --- Clock ---
|
||||
The 12-hour clock setting now applies everywhere — the top bar, the chat bubbles and the lock screen — not just the chat list.
|
||||
|
||||
# --- From contributors ---
|
||||
Room-server posts now show the actual author's name on each message instead of the room's own name. (Yazutsu, #40)
|
||||
New "Direct (0-hop)" filter for the contact list and the map — show only nodes you're hearing directly, with no repeater hops in between. (tun0, #39)
|
||||
Beer emoji in colour, tap the unread badge in the status bar to jump to your Chats, a new "notify on new contact" toggle, and Czech accents in the accent pop-up. (Vybo, #22)
|
||||
Optional trackball "edge scroll" — push the cursor past a screen edge to scroll the list underneath it (off by default, under Settings -> Keyboard). (tun0, #38)
|
||||
TCP now restarts immediately after you turn it off and on again, the OTA action is hidden when there's no update slot to use, and a TCP-toggle alert glitch is fixed. (samuelcoustet, #29)
|
||||
|
||||
# --- Fixes ---
|
||||
Heltec V4: removed the after-crash report prompt — that board has no SD card or reachable storage to save the report to, so it was a dead end. (The T-Deck still offers it.)
|
||||
When both Wi-Fi and GPS are off, the clock now sets itself from the timestamps on messages received over the mesh.
|
||||
@@ -0,0 +1,7 @@
|
||||
# beta_14 — hotfix (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- Fix (headline) ---
|
||||
Fixed a bug introduced in beta_13: if you had turned keyboard navigation OFF (Settings -> Keyboard), the physical keyboard stopped typing into text fields entirely on the T-Deck and Tanmatsu. Typing now works again with keyboard navigation off; with it on, nothing changed. (The Heltec V4 was never affected.)
|
||||
|
||||
# --- Also in this release ---
|
||||
Includes everything from beta_13: room-server posts show the author's name, the Direct (0-hop) contact + map filter, beer emoji with a status-bar unread tap and a new-contact toast toggle and Czech accents, optional trackball edge-scroll, the dedicated special-characters picker, the 12-hour clock applied everywhere, and the keyboard navigation + typing improvements.
|
||||
@@ -0,0 +1,34 @@
|
||||
# beta_6 — what's new (one user-facing note per non-blank, non-# line)
|
||||
|
||||
# --- New: RF Monitor app ---
|
||||
New Monitor app on the app drawer: a live RF sniffer for your node — see exactly what it is pulling out of the air in real time
|
||||
A "Recently heard" feed lists recent received frames by type (text, advert, ack, route-trace…) with RSSI, SNR and hop count, colour-coded by type
|
||||
A link-margin readout grades how much headroom your last packet had (green / amber / red), alongside a live packet rate and a noise-floor + peak-hold RSSI scope with an on-screen dBm scale
|
||||
The Monitor lays out cleanly in both portrait and landscape, so it is readable on the Heltec V4 and the T-Deck
|
||||
|
||||
# --- Chat & contacts ---
|
||||
Direct-message chats now show a person icon and channels a group icon, instead of the old loop / envelope
|
||||
You can block a non-contact sender by name — handy for silencing an automated room or repeater bot you could not block before
|
||||
Opening a chat or channel no longer jumps you to the newest message; it keeps your scroll position
|
||||
The contacts Sort & Filter popup now closes after you pick a sort option
|
||||
The chat/channel long-press popup is fixed: the close X no longer overlaps the buttons while you scroll it
|
||||
|
||||
# --- Time & timezone ---
|
||||
New time-zone picker with named zones (no longer CET-only) — set yours in Settings → System
|
||||
Fixed the clock getting stuck at 1902 and channels showing 1969–1970 dates: GPS no longer clobbers the clock with an invalid date, and the time now survives a reboot
|
||||
|
||||
# --- Launcher & Wi-Fi ---
|
||||
Map tiles now download when running under Launcher (the tile cache was wrongly being reported as full)
|
||||
GPS now acquires a fix when running under Launcher
|
||||
The setup-wizard Wi-Fi scan no longer comes up empty ("no networks found") — Wi-Fi modem sleep is deferred until after you are connected
|
||||
|
||||
# --- Bluetooth ---
|
||||
Bluetooth now advertises under your node / profile name instead of "NimBLE"
|
||||
The Bluetooth PIN no longer resets on its own — it is saved and reused across reboots
|
||||
|
||||
# --- Power & performance ---
|
||||
Turning the screen off now downclocks the CPU and enables Wi-Fi modem sleep to save power
|
||||
On the map, your position dot is drawn before the tiles so it no longer lags behind when you pan
|
||||
|
||||
# --- Community ---
|
||||
Merged map-server and SD-tile rendering fixes contributed by Vybo (PR #11)
|
||||
@@ -0,0 +1,48 @@
|
||||
# beta_7 — what's new (one user-facing note per non-blank, non-# line)
|
||||
|
||||
# --- Community-built ---
|
||||
Most of beta_7 was contributed by the community — Dan Vybiral (Vybo) and Ruben Laban. Thank you both!
|
||||
|
||||
# --- Chat reliability ---
|
||||
Fixed garbled and duplicated messages — chat text is no longer corrupted in transit, so messages arrive intact and duplicates are filtered out correctly (thanks to marcelverdult for the report, issue #13)
|
||||
Unread badges are accurate again: the phantom and stuck unread counts on chats and channels are fixed (Ruben Laban, PR #15)
|
||||
More of your chat history is kept and reloaded across reboots, and it is saved more reliably than before (Ruben Laban, PR #15)
|
||||
|
||||
# --- New: Snake game ---
|
||||
A Snake game is now in the app drawer — steer by swiping the screen or with the T-Deck trackball, with pause/resume and restart after a game over (Vybo, PR #12)
|
||||
While you play, touch stays inside the game and no longer leaks through to the screen behind it
|
||||
|
||||
# --- Battery & telemetry ---
|
||||
Tap the battery in the status bar for a 24-hour history chart with an estimated time-left readout; battery level is logged every 5 minutes (Vybo, PR #12)
|
||||
New per-node telemetry window: poll a node's battery, temperature and humidity, watch them on a live chart, and keep a per-node log (Vybo, PR #12)
|
||||
|
||||
# --- Map ---
|
||||
New map night mode — the status bar and tab bar invert to match the dark map instead of glaring (Vybo, PR #12)
|
||||
A live zoom-level readout sits above the zoom slider, and the zoom button now shows a proper magnifier icon (Vybo, PR #12)
|
||||
Contacts have a new "Show on map" action that centres the map on that node (Vybo, PR #12)
|
||||
|
||||
# --- Status bar & tools ---
|
||||
A microSD activity LED lights whenever the card is accessed, including file-manager reads and copies (Vybo, PR #12)
|
||||
An activity spinner shows while a telemetry, admin, ping, trace, range or sightline request is in flight, kept clear of the clock (Vybo, PR #12)
|
||||
New option to hide the device name and move the clock to the left (Vybo, PR #12)
|
||||
Press and hold the status bar to save a full-screen screenshot to the SD card (Vybo, PR #12)
|
||||
|
||||
# --- Files & images ---
|
||||
The file browser can preview PNG and BMP images now, not just JPEG, and no longer shows a stale image when you open several in a row (Vybo, PR #12)
|
||||
Lock-screen wallpaper: open Files, view any JPEG on your SD card, and tap "Set as wallpaper" — no more slow scan or "no image found"
|
||||
|
||||
# --- App drawer ---
|
||||
The app drawer fits all the apps again — the bottom row's labels are no longer cut off
|
||||
A scrollbar clearly shows when there are more apps than fit, so it is obvious the page scrolls
|
||||
|
||||
# --- Bluetooth ---
|
||||
You can set your own Bluetooth pairing code in Settings now, instead of being stuck with the default
|
||||
|
||||
# --- Storage & stability ---
|
||||
Fixed map tiles failing to cache (with a false "storage full"), and black or missing tiles now re-download instead of staying blank
|
||||
Replying to a message with @ no longer overwrites text you were already typing
|
||||
Fixed an out-of-memory crash and made notification audio more reliable
|
||||
|
||||
# --- Community ---
|
||||
Huge thanks to Dan Vybiral (Vybo) — most of beta_7 is his work: the Snake game, battery history, per-node telemetry, map night mode, PNG/BMP image viewer and the whole status-bar toolkit (PR #12)
|
||||
Thanks to Ruben Laban for the accurate unread-badge and chat-history-persistence fixes (PR #15)
|
||||
@@ -0,0 +1,16 @@
|
||||
# beta_8 — what's new (one user-facing note per non-blank, non-# line)
|
||||
|
||||
# --- Chat ---
|
||||
Quick replies now insert at the cursor instead of wiping whatever you'd already typed — tap one mid-message and it's added in place
|
||||
Long-press any message for a new one-tap "Ack": it drops a ready-to-send reply into the composer with that message's signal strength (SNR/RSSI) and the route it took, so you can instantly confirm someone's test came through — on a channel it @mentions the sender
|
||||
Fixed the message menu (Ack / Copy / Info / Mention / Block) running off the bottom of the screen — it now fits, and scrolls if it ever needs to
|
||||
|
||||
# --- Map ---
|
||||
The zoom button now shows a clear "+/-" icon
|
||||
Each on-map readout can be shown or hidden independently in Map settings — the coordinates line, the tile x/y/z line, and the contact markers
|
||||
Map settings are reorganised: the microSD-tiles switch sits at the top under its own heading, separated from the new show/hide toggles
|
||||
New auto-refresh button: turn it on and the map re-centres on you automatically whenever your position changes, with an on-screen "Auto-refresh on/off" confirmation
|
||||
Fixed the close (X) in Map settings overlapping the switches when the panel was scrolled
|
||||
|
||||
# --- Performance ---
|
||||
Freed about 19 KB of internal memory, leaving more headroom for Wi-Fi and trimming the idle-memory growth seen in recent betas
|
||||
@@ -0,0 +1,26 @@
|
||||
# beta_9 — what's new (one user-facing note per non-blank, non-# line)
|
||||
|
||||
# --- Stability ---
|
||||
Wi-Fi now reconnects on its own after the link drops — no more rebooting just to get back online
|
||||
Fixed a bug where a corrupted chat-history file could make the device reboot on every received message (and hide your messages) — the bad file is now detected and reset automatically so it recovers on its own
|
||||
Hardened the boot path so a device with quirky memory degrades gracefully instead of getting stuck on the boot logo
|
||||
|
||||
# --- Crash reports ---
|
||||
After a crash, Settings -> About now shows an "Export crash report" button that saves the crash dump to the microSD card, so you can send it to the devs and get the bug fixed
|
||||
|
||||
# --- Map ---
|
||||
The reload button for corrupt tiles now actually clears the cached tiles — including when you're offline — and missing/blank "twilight zone" tiles re-download on their own
|
||||
Fixed tiles not re-fetching at zoom levels 13 to 15
|
||||
|
||||
# --- Contacts ---
|
||||
Favourited contacts now always appear in the contact list, even with hundreds of similarly-named contacts (they used to stay hidden until you searched for them)
|
||||
|
||||
# --- Chat ---
|
||||
Room-server messages now show the time each message was actually sent, instead of when it arrived
|
||||
Room-server messages now show who sent them
|
||||
Sharing your contact QR code now produces a valid code that the MeshCore phone app can scan and import
|
||||
Unread counts now show "99+" instead of an alarming raw number on busy channels
|
||||
|
||||
# --- App drawer ---
|
||||
New icon-size setting: tap the cog at the top-right of the app drawer to switch between Compact and Large — bigger icons and labels, easier to read
|
||||
Smoother scrolling through the app list (and the other scrollable lists)
|
||||
@@ -5,7 +5,9 @@
|
||||
# - manifest-heltec-v4-tft.json install dialog, so it must equal the tag)
|
||||
#
|
||||
# These live next to the rolling bins at firmware.wadamesh.com/latest/, so the
|
||||
# flasher always reflects the current release without a site redeploy.
|
||||
# flasher always reflects the current release without a site redeploy. The
|
||||
# manifests themselves point at the IMMUTABLE per-tag bins (see below) to dodge
|
||||
# the latest/*.bin 4h edge cache.
|
||||
#
|
||||
# Usage: gen-flasher-meta.py <tag> <outdir> [notes_file]
|
||||
# notes_file: one note per non-empty line (plain text); optional.
|
||||
@@ -35,7 +37,13 @@ for fn, (name, binf) in BOARDS.items():
|
||||
"new_install_prompt_erase": True,
|
||||
"builds": [{
|
||||
"chipFamily": "ESP32-S3",
|
||||
"parts": [{"path": f"https://firmware.wadamesh.com/latest/{binf}", "offset": 0}],
|
||||
# Point at the IMMUTABLE per-tag bin, NOT latest/. The latest/*.bin URLs
|
||||
# are stable filenames cached 4h (max-age=14400) and overwritten in place,
|
||||
# so for up to 4h after a release the flasher could hand out the PREVIOUS
|
||||
# beta's bytes while version.json already advertised the new tag. The
|
||||
# per-tag path is never overwritten, so its (24h) cache is always correct.
|
||||
# The manifest itself is max-age=300, so this new path propagates in <=5min.
|
||||
"parts": [{"path": f"https://firmware.wadamesh.com/releases/TOUCH/{tag}/{binf}", "offset": 0}],
|
||||
}],
|
||||
}
|
||||
with open(os.path.join(outdir, fn), "w") as f:
|
||||
|
||||
@@ -54,6 +54,28 @@ echo "listing: $(python3 -c 'import json,sys;print(", ".join(x["name"] for x in
|
||||
# frozen one. Notes come from release-notes/<tag>.txt (one per line; optional).
|
||||
python3 "$ROOT/scripts/build/gen-flasher-meta.py" "$TAG" "$LATEST" "$ROOT/release-notes/$TAG.txt"
|
||||
|
||||
# 3c. Mesh America Configurator provider catalog (apps.meshamerica.com — the web
|
||||
# flasher Cascadia Mesh uses). It is served LIVE from raw.githubusercontent
|
||||
# main, so each release must regenerate it for $TAG and push that ONE file to
|
||||
# main; otherwise the configurator keeps offering the previous beta. It points
|
||||
# at the immutable /releases/TOUCH/$TAG/ bins published below.
|
||||
# (Generator: deploy/gen-meshamerica-catalog.py — reads release-notes/$TAG.txt.)
|
||||
CATALOG="$ROOT/deploy/meshamerica-catalog.json"
|
||||
python3 "$ROOT/deploy/gen-meshamerica-catalog.py" "$TAG" > "$CATALOG.tmp" && mv "$CATALOG.tmp" "$CATALOG"
|
||||
mkdir -p "$OUT/meshamerica" && cp "$CATALOG" "$OUT/meshamerica/catalog.json" # also mirror to the VPS (enables the branded URL once its nginx location is deployed)
|
||||
if git -C "$ROOT" diff --quiet -- "$CATALOG"; then
|
||||
echo "Mesh America catalog already current for $TAG"
|
||||
elif [ "$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null)" = "main" ]; then
|
||||
if git -C "$ROOT" commit -q -m "chore: refresh Mesh America catalog for $TAG" -- "$CATALOG" \
|
||||
&& git -C "$ROOT" push origin HEAD:main; then
|
||||
echo "Mesh America catalog refreshed + pushed to main ($TAG)"
|
||||
else
|
||||
echo "WARN: Mesh America catalog commit/push failed — push deploy/meshamerica-catalog.json to main by hand"
|
||||
fi
|
||||
else
|
||||
echo "NOTE: regenerated $CATALOG for $TAG but not on 'main' — commit + push it to main so apps.meshamerica.com updates."
|
||||
fi
|
||||
|
||||
# 4. Publish to the VPS (Cloudflare caches at the edge).
|
||||
if [ -n "$DEST" ]; then
|
||||
rsync -av "$OUT/" "$DEST:$DEST_PATH/"
|
||||
|
||||
@@ -72,6 +72,20 @@ public:
|
||||
(void)is_flood; (void)snr_q4; (void)rssi;
|
||||
newMsgFromPub(path_len, from_pub, from_name, text, msgcount);
|
||||
}
|
||||
/** Room-server post. Like newMsgFromPubWithMeta, but from_pub/from_name
|
||||
* identify the ROOM (the chat thread) while `author_name` is the resolved
|
||||
* display name of the original poster — a room signed message attributes
|
||||
* the author only by a pubkey prefix, never in the text body. Default
|
||||
* ignores the author and behaves like a normal message, so non-touch UIs
|
||||
* are unaffected. */
|
||||
virtual void newRoomMsgFromPubWithMeta(uint8_t path_len, bool is_flood,
|
||||
const uint8_t* from_pub, const char* from_name,
|
||||
const char* author_name,
|
||||
const char* text, int msgcount,
|
||||
int8_t snr_q4, int8_t rssi) {
|
||||
(void)author_name;
|
||||
newMsgFromPubWithMeta(path_len, is_flood, from_pub, from_name, text, msgcount, snr_q4, rssi);
|
||||
}
|
||||
virtual void notify(UIEventType t = UIEventType::none) = 0;
|
||||
virtual void appendDiag(const char* message) { (void)message; }
|
||||
/** Notify UI of an advert reception. `is_new=true` means the contact is NOT in
|
||||
|
||||
+92
-6
@@ -8,7 +8,9 @@
|
||||
#include <time.h> // gmtime_r for the "clock" CLI command
|
||||
#ifdef ESP32
|
||||
#include <esp_system.h> // esp_restart for the "bootloader" CLI command
|
||||
#include <soc/rtc_cntl_reg.h> // RTC_CNTL_OPTION1_REG / FORCE_DOWNLOAD_BOOT
|
||||
#if !defined(HAS_TANMATSU)
|
||||
#include <soc/rtc_cntl_reg.h> // RTC_CNTL_OPTION1_REG / FORCE_DOWNLOAD_BOOT (S3-only; P4 lacks it)
|
||||
#endif
|
||||
#endif
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/HttpOtaDisplayState.h>
|
||||
@@ -494,6 +496,11 @@ bool MyMesh::handleMeshcomodCommand(const char* text, int text_len) {
|
||||
s_meshcomod_pending_until_ms = 0;
|
||||
pushMeshcomodReply("cancelled");
|
||||
return true;
|
||||
} else {
|
||||
// Any other command while a confirmation is pending: block it so the
|
||||
// new command cannot silently overwrite the pending action.
|
||||
pushMeshcomodReply("pending confirmation — reply 'ok' to confirm or 'cancel' to abort");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,9 +562,11 @@ bool MyMesh::handleMeshcomodCommand(const char* text, int text_len) {
|
||||
// rest of RTC_CNTL_OPTION1 and wedges the RTC so esp_restart() hangs instead
|
||||
// of resetting (that was the earlier "freeze"). board.reboot() == esp_restart,
|
||||
// the proven reset path on this board; the RTC bit survives it.
|
||||
#if !defined(HAS_TANMATSU)
|
||||
uint32_t opt1 = REG_READ(RTC_CNTL_OPTION1_REG);
|
||||
REG_WRITE(RTC_CNTL_OPTION1_REG, opt1 | RTC_CNTL_FORCE_DOWNLOAD_BOOT);
|
||||
board.reboot();
|
||||
#endif
|
||||
board.reboot(); // Tanmatsu/P4: plain reboot (the launcher manages flashing)
|
||||
#else
|
||||
pushMeshcomodReply("bootloader: ESP32-only");
|
||||
#endif
|
||||
@@ -1361,6 +1370,20 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
||||
_ui_sig_snr_q4 = (int8_t)(snr * 4.0f);
|
||||
_ui_sig_rssi = (int8_t)rssi;
|
||||
_ui_sig_ms = millis();
|
||||
// Recent-RX ring for the Monitor app (see UiRxRec): parse the minimal header
|
||||
// (type / route / hops) and push newest-first. Header byte layout is
|
||||
// [version:2][payload_type:4][route_type:2]
|
||||
// with 4 transport-code bytes following iff route is a TRANSPORT_* one; the
|
||||
// next byte's low 6 bits are the path length (hop count).
|
||||
if (len > 0) {
|
||||
const uint8_t rt = raw[0] & 0x03;
|
||||
const bool xp = (rt == ROUTE_TYPE_TRANSPORT_FLOOD || rt == ROUTE_TYPE_TRANSPORT_DIRECT);
|
||||
const int ps = 1 + (xp ? 4 : 0);
|
||||
const uint8_t hops = (ps < len) ? (uint8_t)(raw[ps] & 0x3F) : 0;
|
||||
uiRxLogPush(_ui_sig_ms, (int8_t)rssi, _ui_sig_snr_q4,
|
||||
(uint8_t)((raw[0] >> 2) & 0x0F), rt, hops,
|
||||
(uint8_t)(len > 255 ? 255 : len));
|
||||
}
|
||||
#if defined(DISPLAY_CLASS)
|
||||
// Diagnostic: log EVERY received frame so we can prove what reaches the
|
||||
// radio. Header byte layout is
|
||||
@@ -1815,6 +1838,15 @@ ContactInfo* MyMesh::processAck(const uint8_t *data) {
|
||||
|
||||
void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packet *pkt,
|
||||
uint32_t sender_timestamp, const uint8_t *extra, int extra_len, const char *text) {
|
||||
// Clock bootstrap from a peer's send-time when we have no real clock of our own (Wi-Fi
|
||||
// and GPS off, or not yet synced). Reliable by construction: adopt ONLY a sane epoch
|
||||
// (~2023..2033, so the 1902/0 garbage and absurd-future values are ignored), and ONLY
|
||||
// while our own clock still reads unset — NTP/GPS override it the instant they sync, and
|
||||
// a clock that already reads real is never moved by the mesh.
|
||||
if (sender_timestamp > 1700000000UL && sender_timestamp < 2000000000UL &&
|
||||
getRTCClock()->getCurrentTime() < 1700000000UL) {
|
||||
getRTCClock()->setCurrentTime(sender_timestamp);
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_CONTACT_MSG_RECV_V3;
|
||||
out_frame[i++] = (int8_t)(pkt->getSNR() * 4);
|
||||
@@ -1857,7 +1889,14 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
* serial was disconnected, which meant after a channel message arrived
|
||||
* over TCP/BLE g_last_event stayed at `channelMessage` and the next DM
|
||||
* was routed into the channel thread (or vice versa). */
|
||||
_ui->notify(UIEventType::contactMessage);
|
||||
// A TXT_TYPE_SIGNED_PLAIN with a 4-byte sender_prefix is a room-server
|
||||
// post: `from` is the room (the thread) and the author is identified only
|
||||
// by the prefix — the text is the bare body. Plain DMs/channel msgs are
|
||||
// unchanged.
|
||||
const bool is_room_post =
|
||||
(txt_type == TXT_TYPE_SIGNED_PLAIN) && extra && extra_len >= 4;
|
||||
_ui->notify(is_room_post ? UIEventType::roomMessage
|
||||
: UIEventType::contactMessage);
|
||||
// Pass RX metadata so the touch UI can surface it via the bubble's
|
||||
// long-press Info sheet. SNR comes off the packet itself (most accurate
|
||||
// per-message); RSSI is the radio's last-RSSI, which is current since
|
||||
@@ -1866,8 +1905,30 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
const int8_t rssi = (int8_t)(_radio->getLastRSSI());
|
||||
const bool is_flood = pkt->isRouteFlood();
|
||||
uiStashRxMeta(pkt); // capture route + scope for the per-message Info popup
|
||||
_ui->newMsgFromPubWithMeta(path_len, is_flood, from.id.pub_key, from.name,
|
||||
text, history_count, snr_q4, rssi);
|
||||
_last_sender_ts = sender_timestamp; // embedded send-time -> UI bubble ts (room history replay)
|
||||
if (is_room_post) {
|
||||
// Resolve the post's author from the signed message's sender_prefix.
|
||||
// Prefer a saved contact's name; fall back to our own node name for
|
||||
// posts we authored (replayed during sync), else a short hex of the
|
||||
// prefix so the bubble never just shows the room's own name.
|
||||
char author_buf[16];
|
||||
const char* author_name;
|
||||
ContactInfo* author = lookupContactByPubKey(extra, 4);
|
||||
if (author && author->name[0]) {
|
||||
author_name = author->name;
|
||||
} else if (memcmp(extra, self_id.pub_key, 4) == 0) {
|
||||
author_name = _prefs.node_name;
|
||||
} else {
|
||||
mesh::Utils::toHex(author_buf, (uint8_t*)extra, 4);
|
||||
author_name = author_buf;
|
||||
}
|
||||
_ui->newRoomMsgFromPubWithMeta(path_len, is_flood, from.id.pub_key,
|
||||
from.name, author_name, text,
|
||||
history_count, snr_q4, rssi);
|
||||
} else {
|
||||
_ui->newMsgFromPubWithMeta(path_len, is_flood, from.id.pub_key, from.name,
|
||||
text, history_count, snr_q4, rssi);
|
||||
}
|
||||
}
|
||||
// CLI command replies don't belong in the chat thread but the touch UI
|
||||
// *does* want them — they're the response to whatever was typed into the
|
||||
@@ -1962,6 +2023,12 @@ void MyMesh::onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uin
|
||||
|
||||
void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp,
|
||||
const char *text) {
|
||||
// Clock bootstrap from a channel peer's send-time (same sane-window + unset-only guard
|
||||
// as queueMessage above) so a Wi-Fi/GPS-off node can still get time off public channels.
|
||||
if (timestamp > 1700000000UL && timestamp < 2000000000UL &&
|
||||
getRTCClock()->getCurrentTime() < 1700000000UL) {
|
||||
getRTCClock()->setCurrentTime(timestamp);
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_CHANNEL_MSG_RECV_V3;
|
||||
out_frame[i++] = (int8_t)(pkt->getSNR() * 4);
|
||||
@@ -2478,7 +2545,12 @@ void MyMesh::begin(bool has_display) {
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (has_display && BLE_PIN_CODE == 123456) {
|
||||
StdRNG rng;
|
||||
_active_ble_pin = rng.nextInt(100000, 999999); // random pin each session
|
||||
_active_ble_pin = rng.nextInt(100000, 999999); // random pin, generated ONCE
|
||||
// Persist it so it stays the SAME across reboots — it used to be re-rolled
|
||||
// every boot (ble_pin stayed 0), so a paired phone's saved PIN stopped
|
||||
// matching and pairing broke (user report: "BT pin resets sometimes").
|
||||
_prefs.ble_pin = _active_ble_pin;
|
||||
savePrefs();
|
||||
} else {
|
||||
_active_ble_pin = BLE_PIN_CODE; // otherwise static pin
|
||||
}
|
||||
@@ -2534,6 +2606,20 @@ NodePrefs *MyMesh::getNodePrefs() {
|
||||
uint32_t MyMesh::getBLEPin() {
|
||||
return _active_ble_pin;
|
||||
}
|
||||
// User-chosen pairing code from the touch BLE settings page. Validated to a
|
||||
// 6-digit value, persisted to _prefs.ble_pin, and applied at the next boot
|
||||
// (begin() seeds _active_ble_pin from it) — same contract as the companion
|
||||
// CMD_SET_DEVICE_PIN. Returns false on an out-of-range PIN.
|
||||
bool MyMesh::setBLEPin(uint32_t pin) {
|
||||
// Any 6-digit BLE passkey is valid, INCLUDING ones that start with 0 (e.g.
|
||||
// "012345" == 12345) — the UI validates the 6-digit string and displays it
|
||||
// zero-padded. Only reject 0 (the "use default/random" sentinel) and values
|
||||
// that don't fit in 6 digits.
|
||||
if (pin == 0 || pin > 999999) return false;
|
||||
_prefs.ble_pin = pin;
|
||||
savePrefs();
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FreqRange {
|
||||
uint32_t lower_freq, upper_freq;
|
||||
|
||||
@@ -113,6 +113,7 @@ public:
|
||||
const char *getNodeName();
|
||||
NodePrefs *getNodePrefs();
|
||||
uint32_t getBLEPin();
|
||||
bool setBLEPin(uint32_t pin); // user-chosen 6-digit pairing code (persisted; applies next boot)
|
||||
|
||||
// Live device info accessors (used by the touch Settings → Device modal to
|
||||
// mirror the web client's "Device (live)" panel — public key prefix, channel
|
||||
@@ -392,6 +393,7 @@ public:
|
||||
uint8_t _last_rx_path_n = 0;
|
||||
uint16_t _last_rx_scope = 0; // transport_codes[0] of the last RX flood ("scope")
|
||||
bool _last_rx_has_scope = false; // false if the packet carried no transport codes
|
||||
uint32_t _last_sender_ts = 0; // embedded send-time of the last inbound msg (UI bubble ts; 0 = use now)
|
||||
volatile bool _echo_dirty = false; // a repeat was counted -> UI should refresh
|
||||
|
||||
// ---- Live signal strength (top-bar icon) ----
|
||||
@@ -404,6 +406,41 @@ public:
|
||||
int8_t uiSignalRssi() const { return _ui_sig_rssi; }
|
||||
uint32_t uiSignalMs() const { return _ui_sig_ms; }
|
||||
|
||||
// ---- Recent-RX ring (RF Monitor app) ----
|
||||
// One record per received frame, captured in logRxRaw(): payload type / route
|
||||
// / hop count / length + signal, so the Monitor page can show a live "what am
|
||||
// I hearing" feed without a core hook. Newest-first via uiRxLogGet (i=0 = most
|
||||
// recent). Board-independent — populated on every board, not just touch.
|
||||
struct UiRxRec {
|
||||
uint32_t ms; // millis() at RX
|
||||
int8_t rssi; // dBm
|
||||
int8_t snr_q4; // SNR_dB * 4
|
||||
uint8_t ptype; // payload type (raw[0]>>2)&0x0F
|
||||
uint8_t route; // route type raw[0]&0x03
|
||||
uint8_t hops; // path length carried (0 = heard direct from origin)
|
||||
uint8_t len; // frame length (clamped to 255)
|
||||
};
|
||||
static const int UI_RXLOG_MAX = 16;
|
||||
UiRxRec _ui_rxlog[UI_RXLOG_MAX];
|
||||
uint8_t _ui_rxlog_head = 0; // next write slot
|
||||
uint8_t _ui_rxlog_cnt = 0; // valid entries (<= UI_RXLOG_MAX)
|
||||
uint8_t uiRxLogCount() const { return _ui_rxlog_cnt; }
|
||||
bool uiRxLogGet(uint8_t i, UiRxRec& out) const {
|
||||
if (i >= _ui_rxlog_cnt) return false;
|
||||
uint8_t idx = (uint8_t)((_ui_rxlog_head + UI_RXLOG_MAX - 1 - i) % UI_RXLOG_MAX);
|
||||
out = _ui_rxlog[idx];
|
||||
return true;
|
||||
}
|
||||
// Record a reception into the ring (called from logRxRaw).
|
||||
void uiRxLogPush(uint32_t ms, int8_t rssi, int8_t snr_q4,
|
||||
uint8_t ptype, uint8_t route, uint8_t hops, uint8_t len) {
|
||||
UiRxRec& r = _ui_rxlog[_ui_rxlog_head];
|
||||
r.ms = ms; r.rssi = rssi; r.snr_q4 = snr_q4;
|
||||
r.ptype = ptype; r.route = route; r.hops = hops; r.len = len;
|
||||
_ui_rxlog_head = (uint8_t)((_ui_rxlog_head + 1) % UI_RXLOG_MAX);
|
||||
if (_ui_rxlog_cnt < UI_RXLOG_MAX) _ui_rxlog_cnt++;
|
||||
}
|
||||
|
||||
/** Fingerprint of the most-recently originated flood TXT payload (0 if none). */
|
||||
uint32_t uiLastSentFp() const { return _last_sent_fp; }
|
||||
|
||||
@@ -445,6 +482,10 @@ public:
|
||||
/** Scope (transport_codes[0]) of the last received flood; *has = false when
|
||||
* the packet carried no transport codes. */
|
||||
uint16_t lastRxScope(bool* has) const { if (has) *has = _last_rx_has_scope; return _last_rx_scope; }
|
||||
/** Consume the embedded send-time stashed right before the last UI notify (room
|
||||
* history replay carries old send-times; without this the UI stamps "now").
|
||||
* Returns 0 when nothing was stashed -> the caller keeps the delivery time. */
|
||||
uint32_t uiConsumeLastSenderTs() { uint32_t t = _last_sender_ts; _last_sender_ts = 0; return t; }
|
||||
/** Capture route + scope of a just-received flood for the Info popup. Call
|
||||
* synchronously right before the newMsg* notification. */
|
||||
void uiStashRxMeta(mesh::Packet* pkt) {
|
||||
@@ -739,6 +780,16 @@ public:
|
||||
// To check if there is pending work
|
||||
bool hasPendingWork() const;
|
||||
|
||||
// Number of companion clients currently connected on any transport.
|
||||
// Used by the idle light-sleep gate (TouchSleep) to confirm no one is
|
||||
// actively talking to us before the node parks in light sleep.
|
||||
int getProtoNumClients() const { return proto_num_clients; }
|
||||
|
||||
// True if the radio is currently mid-receive of a packet (preamble→RxDone).
|
||||
// _radio is the protected Dispatcher::Radio pointer; isReceiving() returns false
|
||||
// by default and is overridden by RadioLibWrapper with a real preamble-detect check.
|
||||
bool isRadioReceiving() const { return _radio && _radio->isReceiving(); }
|
||||
|
||||
private:
|
||||
void writeOKFrame();
|
||||
void writeErrFrame(uint8_t err_code);
|
||||
|
||||
@@ -54,6 +54,9 @@ void MultiTransportCompanionInterface::stopTcpServer() {
|
||||
|
||||
void MultiTransportCompanionInterface::enableTcp() {
|
||||
_tcp_enabled = true;
|
||||
// Restart immediately: don't wait for the next main-loop tick, and don't
|
||||
// require wifi_started to be true (TCP itself doesn't need an IP address).
|
||||
startTcpServer(false);
|
||||
}
|
||||
|
||||
void MultiTransportCompanionInterface::disableTcp() {
|
||||
|
||||
@@ -35,7 +35,7 @@ static bool s_begun = false;
|
||||
// short read (→ treat as absent → defaults); `ver` lets later builds add fields.
|
||||
static const char* KEY_CFG = "cfg";
|
||||
static const uint16_t TOUCH_CFG_MAGIC = 0x5743; // 'WC' (WadaCfg)
|
||||
static const uint8_t TOUCH_CFG_VER = 2; // v2 adds sig_probe_en + sig_poll_min
|
||||
static const uint8_t TOUCH_CFG_VER = 17; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom; v6 map text/marker visibility; v7 app_grid_large; v8 ui_scale; v9 tb_keypad; v10 sleep_idle; v11 nav_keys; v12 map_zoom_buttons; v13 nav_dir_keys; v14 home_is_drawer; v15 kbd_nav default ON (one-time migrate); v16 nav_scroll_keys; v17 notify_new_contact
|
||||
|
||||
// Defaults (kept identical to the historical per-key defaults).
|
||||
static const uint16_t DEFAULT_SCREEN_TIMEOUT_S = 20;
|
||||
@@ -72,6 +72,23 @@ struct __attribute__((packed)) TouchCfg {
|
||||
uint32_t gps_baud; // "gps_baud" 0 = unset -> caller fallback
|
||||
uint8_t sig_probe_en; // signal auto-discover probe on/off (bool) — v2
|
||||
uint16_t sig_poll_min; // minutes between signal probes, 1..1440 — v2
|
||||
uint8_t tz_zone; // selected time-zone index (0 = Europe/CET) — v3
|
||||
uint8_t hide_node_name; // hide the device name in the status bar + park clock left (bool) — v4
|
||||
uint8_t map_night; // invert map tile colours at render time (bool) — v5
|
||||
uint8_t map_zoom; // last map zoom level (0 = unset, auto-snap) — v5
|
||||
uint8_t map_show_coords; // show the coords read-out on the map (bool) — v6
|
||||
uint8_t map_show_tilexyz; // show the zoom + tile z/x/y line on the map (bool) — v6
|
||||
uint8_t map_show_contacts;// show contact markers on the map (bool) — v6
|
||||
uint8_t app_grid_large; // app drawer: large grid (one fewer column, bigger icons) — v7
|
||||
uint8_t ui_scale; // UI resolution scale: 0=100% 1=150% 2=200% (Tanmatsu, applied at boot) — v8
|
||||
uint8_t kbd_nav; // T-Deck keyboard ESDFX nav: 0=off (default), 1=on (E/X/S/F move focus, D select, Q back) — v9 (was tb_keypad)
|
||||
uint8_t sleep_idle; // idle light-sleep feature on/off (bool) — v10 (trailing so existing blobs default it OFF)
|
||||
uint8_t nav_keys[5]; // keyboard-nav tab hotkeys (ASCII), one per main tab [chat,contacts,home,map,settings] — v11 (trailing)
|
||||
uint8_t map_zoom_buttons; // map zoom control: 0=slider (default), 1=+/- buttons — v12 (trailing)
|
||||
uint8_t nav_dir_keys[6]; // keyboard-nav control keys (ASCII): up,down,left,right,select,back — v13 (trailing)
|
||||
uint8_t home_is_drawer; // Home tab defaults to the app drawer (1) vs the Commander screen (0, default) — v14 (trailing)
|
||||
uint8_t nav_scroll_keys[2]; // keyboard-nav scroll keys (ASCII): scroll-up, scroll-down — v16 (trailing)
|
||||
uint8_t notify_new_contact;// toast/chip when a contact is auto-discovered (bool) — v17 (trailing)
|
||||
};
|
||||
|
||||
static TouchCfg s_cfg;
|
||||
@@ -120,6 +137,31 @@ static void cfgSetDefaults(TouchCfg& c) {
|
||||
c.gps_baud = 0; // 0 sentinel -> getter returns caller fallback
|
||||
c.sig_probe_en = DEFAULT_SIG_PROBE_EN;
|
||||
c.sig_poll_min = DEFAULT_SIG_POLL_MIN;
|
||||
c.tz_zone = 0; // 0 = Europe (CET/CEST) — preserves prior behaviour
|
||||
c.hide_node_name = 0; // default: show the device name
|
||||
c.map_night = 0; // default: normal (light) tiles
|
||||
c.map_zoom = 0; // 0 = unset -> auto-snap on first map open
|
||||
c.map_show_coords = 1; // default: show coords / tile line / contacts
|
||||
c.map_show_tilexyz = 1;
|
||||
c.map_show_contacts = 1;
|
||||
c.app_grid_large = 0; // default: compact app grid (T-Deck 4 cols / V4 3 cols)
|
||||
c.ui_scale = 0; // default: 100% UI scale (native resolution)
|
||||
c.kbd_nav = 1; // default: keyboard navigation ON (v15)
|
||||
c.sleep_idle = 0; // default: idle light-sleep OFF
|
||||
{ const char* d = "ertui"; for (int i = 0; i < 5; i++) c.nav_keys[i] = (uint8_t)d[i]; } // default tab hotkeys E/R/T/U/I
|
||||
c.map_zoom_buttons = 0; // default: map zoom = slider
|
||||
#if defined(HAS_TANMATSU)
|
||||
{ const char* d = "wxads"; for (int i = 0; i < 6; i++) c.nav_dir_keys[i] = (uint8_t)d[i]; } // Tanmatsu: W up/X down/A left/D right/S select; no Back letter (Esc/F-key), d[5]='\0'
|
||||
#else
|
||||
{ const char* d = "wzadsq"; for (int i = 0; i < 6; i++) c.nav_dir_keys[i] = (uint8_t)d[i]; } // default W/Z/A/D/S/Q
|
||||
#endif
|
||||
c.home_is_drawer = 0; // default: Home = Commander screen
|
||||
#if defined(HAS_TANMATSU)
|
||||
c.nav_scroll_keys[0] = 'f'; c.nav_scroll_keys[1] = 'v'; // Tanmatsu scroll-up F / scroll-down V
|
||||
#else
|
||||
c.nav_scroll_keys[0] = 'f'; c.nav_scroll_keys[1] = 'c'; // default scroll-up F / scroll-down C
|
||||
#endif
|
||||
c.notify_new_contact = 1; // default: show the new-contact toast (preserve prior behaviour)
|
||||
}
|
||||
|
||||
// Persist the whole blob using the same end()/begin(RW)/put/end()/begin(RO)
|
||||
@@ -154,6 +196,16 @@ static void cfgLoadOrMigrate() {
|
||||
// newer trailing fields at their default), then version-upgrade if needed.
|
||||
memcpy(&s_cfg, &tmp, n < sizeof(s_cfg) ? n : sizeof(s_cfg));
|
||||
if (s_cfg.ver < TOUCH_CFG_VER) {
|
||||
// v2->v3: a manual hour offset used to mean "CET base + offset". Preserve
|
||||
// that under the new zone picker by mapping such users onto the Custom
|
||||
// (UTC-offset) zone, so their clock doesn't jump to CET. 0xFE is resolved
|
||||
// to the real Custom index on the first touchPrefsGetTimezone() call (the
|
||||
// zone count isn't known here). offset 0 stays zone 0 (Europe) = unchanged.
|
||||
if (s_cfg.ver < 3 && s_cfg.time_offs != 0) s_cfg.tz_zone = 0xFE;
|
||||
// v15: keyboard navigation is now ON by default. Force it on ONCE for existing
|
||||
// users (pre-v15) so they get the feature; their later explicit on/off then
|
||||
// persists (this only fires for ver < 15, never again).
|
||||
if (s_cfg.ver < 15) s_cfg.kbd_nav = 1;
|
||||
s_cfg.ver = TOUCH_CFG_VER;
|
||||
s_cfg.magic = TOUCH_CFG_MAGIC;
|
||||
cfgFlush(); // rewrite with new fields defaulted-in
|
||||
@@ -489,13 +541,70 @@ bool touchPrefsSetTimeOffsetHours(int hours) {
|
||||
s_cfg.time_offs = (int8_t)hours;
|
||||
return cfgFlush();
|
||||
}
|
||||
// Curated time zones, each with the correct POSIX DST rules for that region, so
|
||||
// non-European users get the right time year-round instead of the CET base + EU
|
||||
// DST dates. Index 0 (Europe/CET) is the default and matches the old behaviour.
|
||||
struct TzZone { const char* label; const char* posix; };
|
||||
static const TzZone TZ_ZONES[] = {
|
||||
{ "Europe (CET/CEST)", "CET-1CEST,M3.5.0,M10.5.0/3" },
|
||||
{ "UK (GMT/BST)", "GMT0BST,M3.5.0/1,M10.5.0" },
|
||||
{ "UTC", "UTC0" },
|
||||
{ "US Eastern", "EST5EDT,M3.2.0,M11.1.0" },
|
||||
{ "US Central", "CST6CDT,M3.2.0,M11.1.0" },
|
||||
{ "US Mountain", "MST7MDT,M3.2.0,M11.1.0" },
|
||||
{ "US Arizona (no DST)", "MST7" },
|
||||
{ "US Pacific", "PST8PDT,M3.2.0,M11.1.0" },
|
||||
{ "US Alaska", "AKST9AKDT,M3.2.0,M11.1.0" },
|
||||
{ "US Hawaii", "HST10" },
|
||||
{ "Canada Atlantic", "AST4ADT,M3.2.0,M11.1.0" },
|
||||
{ "Brazil (Brasilia)", "<-03>3" },
|
||||
{ "India (IST)", "IST-5:30" },
|
||||
{ "China (CST)", "CST-8" },
|
||||
{ "Japan (JST)", "JST-9" },
|
||||
{ "Sydney (AEST/AEDT)", "AEST-10AEDT,M10.1.0,M4.1.0/3" },
|
||||
};
|
||||
static const int TZ_ZONE_N = (int)(sizeof(TZ_ZONES) / sizeof(TZ_ZONES[0]));
|
||||
|
||||
int touchPrefsTimezoneCount() { return TZ_ZONE_N + 1; } // +1 = "Custom (UTC offset)"
|
||||
|
||||
const char* touchPrefsTimezoneLabel(int idx) {
|
||||
if (idx >= 0 && idx < TZ_ZONE_N) return TZ_ZONES[idx].label;
|
||||
if (idx == TZ_ZONE_N) return "Custom (UTC offset)";
|
||||
return "";
|
||||
}
|
||||
|
||||
uint8_t touchPrefsGetTimezone() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
uint8_t z = s_cfg.tz_zone;
|
||||
if (z == 0xFE) { // v2->v3 migration sentinel: had a manual offset -> Custom zone
|
||||
z = (uint8_t)(touchPrefsTimezoneCount() - 1);
|
||||
s_cfg.tz_zone = z;
|
||||
cfgFlush();
|
||||
}
|
||||
if (z >= (uint8_t)touchPrefsTimezoneCount()) z = 0; // stale/garbage -> default
|
||||
return z;
|
||||
}
|
||||
|
||||
void touchPrefsSetTimezone(uint8_t idx) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
if (idx >= (uint8_t)touchPrefsTimezoneCount()) idx = 0;
|
||||
s_cfg.tz_zone = idx;
|
||||
cfgFlush();
|
||||
}
|
||||
|
||||
void touchPrefsBuildLocalTz(char* out, int out_cap) {
|
||||
if (!out || out_cap <= 0) return;
|
||||
// CET base is UTC+1 (POSIX std offset -1). The displayed local time is
|
||||
// UTC + (1 + userOffset), so the POSIX std-offset field is -(1 + userOffset).
|
||||
// CEST (summer) auto-adds 1h via the DST rule. offset 0 == "CET-1CEST,...".
|
||||
int off = touchPrefsGetTimeOffsetHours();
|
||||
snprintf(out, out_cap, "CET%dCEST,M3.5.0,M10.5.0/3", -(1 + off));
|
||||
const uint8_t z = touchPrefsGetTimezone();
|
||||
if (z < (uint8_t)TZ_ZONE_N) {
|
||||
snprintf(out, out_cap, "%s", TZ_ZONES[z].posix);
|
||||
return;
|
||||
}
|
||||
// "Custom (UTC offset)": a fixed offset, no DST. POSIX std-offset is the
|
||||
// negation of the UTC offset (it's the time to ADD to local to reach UTC), so
|
||||
// UTC-7 -> "<-07>7", UTC+2 -> "<+02>-2".
|
||||
const int off = touchPrefsGetTimeOffsetHours();
|
||||
if (off == 0) { snprintf(out, out_cap, "UTC0"); return; }
|
||||
snprintf(out, out_cap, "<%+03d>%d", off, -off);
|
||||
}
|
||||
|
||||
uint32_t touchPrefsGetLockTextColor() {
|
||||
@@ -617,6 +726,158 @@ bool touchPrefsSetTilesFromSd(bool from_sd) {
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetHideNodeName() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.hide_node_name != 0; // default = show the name
|
||||
}
|
||||
|
||||
bool touchPrefsSetHideNodeName(bool hide) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.hide_node_name = hide ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetNewContactToast() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.notify_new_contact != 0; // default = show the toast
|
||||
}
|
||||
|
||||
bool touchPrefsSetNewContactToast(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.notify_new_contact = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetMapNight() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.map_night != 0;
|
||||
}
|
||||
bool touchPrefsSetMapNight(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.map_night = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
uint8_t touchPrefsGetMapZoom() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.map_zoom;
|
||||
}
|
||||
bool touchPrefsSetMapZoom(uint8_t z) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.map_zoom = z;
|
||||
return cfgFlush();
|
||||
}
|
||||
bool touchPrefsGetSleepIdle() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.sleep_idle != 0;
|
||||
}
|
||||
bool touchPrefsSetSleepIdle(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.sleep_idle = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetMapShowCoords() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.map_show_coords != 0;
|
||||
}
|
||||
bool touchPrefsSetMapShowCoords(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.map_show_coords = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
bool touchPrefsGetMapShowTileXYZ() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.map_show_tilexyz != 0;
|
||||
}
|
||||
bool touchPrefsSetMapShowTileXYZ(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.map_show_tilexyz = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
bool touchPrefsGetMapShowContacts() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.map_show_contacts != 0;
|
||||
}
|
||||
bool touchPrefsSetMapShowContacts(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.map_show_contacts = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
bool touchPrefsGetAppGridLarge() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.app_grid_large != 0;
|
||||
}
|
||||
bool touchPrefsSetAppGridLarge(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.app_grid_large = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
uint8_t touchPrefsGetUiScale() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.ui_scale > 2 ? 0 : s_cfg.ui_scale; // 0=100% 1=150% 2=200%
|
||||
}
|
||||
bool touchPrefsSetUiScale(uint8_t scale) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.ui_scale = scale > 2 ? 0 : scale;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetKbdNav() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.kbd_nav != 0;
|
||||
}
|
||||
bool touchPrefsSetKbdNav(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.kbd_nav = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
uint8_t touchPrefsGetNavKey(int tab) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
if (tab < 0 || tab >= 5) return 0;
|
||||
return s_cfg.nav_keys[tab];
|
||||
}
|
||||
bool touchPrefsSetNavKey(int tab, uint8_t ch) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
if (tab < 0 || tab >= 5) return false;
|
||||
s_cfg.nav_keys[tab] = ch;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetMapZoomButtons() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.map_zoom_buttons != 0;
|
||||
}
|
||||
bool touchPrefsSetMapZoomButtons(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.map_zoom_buttons = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
uint8_t touchPrefsGetNavDirKey(int idx) { // idx 0-5 = move/select/back, 6-7 = scroll up/down
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
if (idx < 0 || idx >= 8) return 0;
|
||||
return (idx < 6) ? s_cfg.nav_dir_keys[idx] : s_cfg.nav_scroll_keys[idx - 6];
|
||||
}
|
||||
bool touchPrefsSetNavDirKey(int idx, uint8_t ch) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
if (idx < 0 || idx >= 8) return false;
|
||||
if (idx < 6) s_cfg.nav_dir_keys[idx] = ch;
|
||||
else s_cfg.nav_scroll_keys[idx - 6] = ch;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetHomeIsDrawer() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.home_is_drawer != 0;
|
||||
}
|
||||
bool touchPrefsSetHomeIsDrawer(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.home_is_drawer = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
// Store ALL device data (identity, prefs, contacts, channels) on the SD card
|
||||
// under /meshcomod instead of internal SPIFFS. Read at boot (main.cpp) BEFORE
|
||||
// the data loads, so changing it needs a reboot. Key "use_sd" in the "touch"
|
||||
@@ -883,6 +1144,67 @@ bool touchPrefsSetIgnored(const uint8_t* pub_key6, bool ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ignored / blocked sender NAMES (channel/room senders that aren't contacts) ---
|
||||
// One NVS blob "ign_nm" of up to TOUCH_IGNORED_NAMES_MAX fixed-width,
|
||||
// NUL-padded TOUCH_IGNORED_NAME_LEN slots. Same read/replace/write scheme as
|
||||
// the 6-byte prefix list above.
|
||||
static const char* KEY_IGN_NAMES = "ign_nm";
|
||||
|
||||
static int ignNamesReadAll(char out[TOUCH_IGNORED_NAMES_MAX * TOUCH_IGNORED_NAME_LEN]) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
if (!s_prefs.isKey(KEY_IGN_NAMES)) return 0; // absent on a fresh device — skip [E] NOT_FOUND
|
||||
size_t n = s_prefs.getBytes(KEY_IGN_NAMES, out, TOUCH_IGNORED_NAMES_MAX * TOUCH_IGNORED_NAME_LEN);
|
||||
if (n == 0 || n > (size_t)(TOUCH_IGNORED_NAMES_MAX * TOUCH_IGNORED_NAME_LEN)) return 0;
|
||||
return (int)(n / TOUCH_IGNORED_NAME_LEN);
|
||||
}
|
||||
|
||||
static bool ignNamesWriteAll(const char* buf, int count) {
|
||||
s_prefs.end();
|
||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
||||
bool ok;
|
||||
if (count <= 0) { s_prefs.remove(KEY_IGN_NAMES); ok = true; }
|
||||
else ok = s_prefs.putBytes(KEY_IGN_NAMES, buf, (size_t)(count * TOUCH_IGNORED_NAME_LEN)) > 0;
|
||||
s_prefs.end();
|
||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool touchPrefsIsNameIgnored(const char* name) {
|
||||
if (!name || !name[0]) return false;
|
||||
char buf[TOUCH_IGNORED_NAMES_MAX * TOUCH_IGNORED_NAME_LEN];
|
||||
int n = ignNamesReadAll(buf);
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (strncmp(&buf[i * TOUCH_IGNORED_NAME_LEN], name, TOUCH_IGNORED_NAME_LEN) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int touchPrefsCopyIgnoredNames(char* out_buf) {
|
||||
if (!out_buf) return 0;
|
||||
return ignNamesReadAll(out_buf);
|
||||
}
|
||||
|
||||
bool touchPrefsSetNameIgnored(const char* name, bool ignored) {
|
||||
if (!name || !name[0]) return false;
|
||||
char buf[TOUCH_IGNORED_NAMES_MAX * TOUCH_IGNORED_NAME_LEN];
|
||||
int n = ignNamesReadAll(buf);
|
||||
int found = -1;
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (strncmp(&buf[i * TOUCH_IGNORED_NAME_LEN], name, TOUCH_IGNORED_NAME_LEN) == 0) { found = i; break; }
|
||||
if (ignored) {
|
||||
if (found >= 0) return true;
|
||||
if (n >= TOUCH_IGNORED_NAMES_MAX) return false; // cap reached, silently refuse
|
||||
char* slot = &buf[n * TOUCH_IGNORED_NAME_LEN];
|
||||
memset(slot, 0, TOUCH_IGNORED_NAME_LEN);
|
||||
strncpy(slot, name, TOUCH_IGNORED_NAME_LEN - 1);
|
||||
++n; ignNamesWriteAll(buf, n); return true;
|
||||
} else {
|
||||
if (found < 0) return false;
|
||||
for (int i = found; i < n - 1; ++i)
|
||||
memcpy(&buf[i * TOUCH_IGNORED_NAME_LEN], &buf[(i + 1) * TOUCH_IGNORED_NAME_LEN], TOUCH_IGNORED_NAME_LEN);
|
||||
--n; ignNamesWriteAll(buf, n); return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Notification-sound prefs (individual NVS keys — integer getters don't emit the
|
||||
// [E] NOT_FOUND log that getString/getBytes do on a fresh device) -----------
|
||||
static void prefsPutUChar(const char* key, uint8_t v) {
|
||||
@@ -912,6 +1234,18 @@ uint8_t touchPrefsGetDiscoveredMaxHops() {
|
||||
return s_prefs.getUChar("dsc_hops", 0); // 0 = off
|
||||
}
|
||||
void touchPrefsSetDiscoveredMaxHops(uint8_t hops) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("dsc_hops", hops); }
|
||||
bool touchPrefsGetEnterSends() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("ent_send", 1) != 0; }
|
||||
void touchPrefsSetEnterSends(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("ent_send", on ? 1 : 0); }
|
||||
bool touchPrefsGetClock12h() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("clk_12h", 0) != 0; }
|
||||
void touchPrefsSetClock12h(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("clk_12h", on ? 1 : 0); }
|
||||
bool touchPrefsGetNavMenubarKeys() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("nav_mbk", 0) != 0; }
|
||||
void touchPrefsSetNavMenubarKeys(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("nav_mbk", on ? 1 : 0); }
|
||||
bool touchPrefsGetScrollReverse() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("tb_rev", 0) != 0; }
|
||||
void touchPrefsSetScrollReverse(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("tb_rev", on ? 1 : 0); }
|
||||
bool touchPrefsGetEdgeScroll() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("tb_edgesc", 0) != 0; }
|
||||
void touchPrefsSetEdgeScroll(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("tb_edgesc", on ? 1 : 0); }
|
||||
bool touchPrefsGetLockOnScreenOff() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("lock_off", 0) != 0; }
|
||||
void touchPrefsSetLockOnScreenOff(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("lock_off", on ? 1 : 0); }
|
||||
|
||||
// Generic blob (used to persist the discovered-nodes list across reboots).
|
||||
size_t touchPrefsGetBlob(const char* key, uint8_t* out, size_t maxlen) {
|
||||
|
||||
@@ -80,6 +80,79 @@ bool touchPrefsSetUseMiles(bool use_miles);
|
||||
bool touchPrefsGetTilesFromSd();
|
||||
bool touchPrefsSetTilesFromSd(bool from_sd);
|
||||
|
||||
/** Map night mode: invert tile colours at render time (cache stays untouched).
|
||||
* Default false. */
|
||||
bool touchPrefsGetMapNight();
|
||||
bool touchPrefsSetMapNight(bool on);
|
||||
|
||||
/** Last map zoom level, persisted so the map reopens where the user left it.
|
||||
* 0 = unset (let the auto-snap pick a level for the available tile pack). */
|
||||
uint8_t touchPrefsGetMapZoom();
|
||||
bool touchPrefsSetMapZoom(uint8_t z);
|
||||
|
||||
/** Per-element visibility of the map's on-screen text/markers. All default true
|
||||
* (shown), so existing installs are unchanged. */
|
||||
bool touchPrefsGetMapShowCoords();
|
||||
bool touchPrefsSetMapShowCoords(bool on);
|
||||
bool touchPrefsGetMapShowTileXYZ();
|
||||
bool touchPrefsSetMapShowTileXYZ(bool on);
|
||||
bool touchPrefsGetMapShowContacts();
|
||||
bool touchPrefsSetMapShowContacts(bool on);
|
||||
|
||||
/* App drawer: large grid (one fewer column → bigger icons + labels, for low vision).
|
||||
* Default false = the compact grid (T-Deck 4 cols / Heltec V4 3 cols). */
|
||||
bool touchPrefsGetAppGridLarge();
|
||||
bool touchPrefsSetAppGridLarge(bool on);
|
||||
/** Idle light-sleep feature: when ON, the device enters light sleep while the
|
||||
* screen is off and all activity gates pass. Default OFF. */
|
||||
bool touchPrefsGetSleepIdle();
|
||||
bool touchPrefsSetSleepIdle(bool on);
|
||||
|
||||
/* UI resolution scale (Tanmatsu): 0=100% (native 800x480), 1=150%, 2=200%. Applied at boot —
|
||||
* LVGL renders at a lower resolution and the flush upscales to the panel. Reboot to apply. */
|
||||
uint8_t touchPrefsGetUiScale();
|
||||
bool touchPrefsSetUiScale(uint8_t scale);
|
||||
|
||||
/* T-Deck keyboard navigation: false (default) = keys type / jump tabs as usual;
|
||||
* true = when no text field is focused, the WASDZ cluster moves focus — W up, A
|
||||
* left, Z down, D right, S select, Q back — and the programmable tab hotkeys
|
||||
* (touchPrefsGetNavKey) jump straight to a tab. The trackball always stays a soft
|
||||
* mouse cursor. Applied live (no reboot). */
|
||||
bool touchPrefsGetKbdNav();
|
||||
bool touchPrefsSetKbdNav(bool on);
|
||||
|
||||
/* Keyboard-nav tab hotkeys: the ASCII key that jumps to each main tab while
|
||||
* keyboard navigation is on. `tab` is the tab index 0..4 = chat / contacts / home
|
||||
* / map / settings. Defaults E/R/T/U/I. Programmable in Settings → Keyboard. */
|
||||
uint8_t touchPrefsGetNavKey(int tab);
|
||||
bool touchPrefsSetNavKey(int tab, uint8_t ch);
|
||||
|
||||
/* Map zoom control style: false (default) = a zoom slider (toggled by the on-map
|
||||
* button); true = a +/- button pair like older builds. Set in the Map options popup. */
|
||||
bool touchPrefsGetMapZoomButtons();
|
||||
bool touchPrefsSetMapZoomButtons(bool on);
|
||||
|
||||
/* Keyboard-nav control keys (ASCII), one per action: idx 0..5 = up, down, left,
|
||||
* right, select, back; 6..7 = scroll up, scroll down. Defaults W/Z/A/D/S/Q/F/C.
|
||||
* Programmable in Settings → Keyboard. */
|
||||
uint8_t touchPrefsGetNavDirKey(int idx);
|
||||
bool touchPrefsSetNavDirKey(int idx, uint8_t ch);
|
||||
|
||||
/* Home tab default: false (default) = the Commander screen; true = the app drawer
|
||||
* (for users who prefer the launcher as their home). Toggled in the app drawer's cog. */
|
||||
bool touchPrefsGetHomeIsDrawer();
|
||||
bool touchPrefsSetHomeIsDrawer(bool on);
|
||||
|
||||
/** Hide the device/profile name in the status bar and move the clock to the
|
||||
* left where the name used to be. Default false (name shown). */
|
||||
bool touchPrefsGetHideNodeName();
|
||||
bool touchPrefsSetHideNodeName(bool hide);
|
||||
|
||||
/** Show the low-key toast/chip when a contact is auto-discovered/added.
|
||||
* Default true (toast shown). Off keeps the diag-log line + tab badge. */
|
||||
bool touchPrefsGetNewContactToast();
|
||||
bool touchPrefsSetNewContactToast(bool on);
|
||||
|
||||
/** Store all device data (identity/prefs/contacts/channels) on the SD card under
|
||||
* /meshcomod instead of internal SPIFFS. T-Deck only; read at boot before data
|
||||
* loads, so changing it requires a reboot. Default false (SPIFFS). */
|
||||
@@ -156,6 +229,20 @@ bool touchPrefsIsIgnored(const uint8_t* pub_key6);
|
||||
bool touchPrefsSetIgnored(const uint8_t* pub_key6, bool ignored);
|
||||
int touchPrefsCopyIgnored(uint8_t* out_buf);
|
||||
|
||||
/** Ignored / blocked sender NAMES — for channel/room senders who are NOT saved
|
||||
* contacts, so the 6-byte pubkey scheme above can't target them (e.g. a bot
|
||||
* posting inside a joined room: posts carry only a display name, not a pubkey).
|
||||
* Incoming channel/room messages whose parsed sender name is stored here are
|
||||
* dropped (no bubble, no notification, no sound). Case-sensitive exact match.
|
||||
* • touchPrefsIsNameIgnored / touchPrefsSetNameIgnored: membership + add/remove.
|
||||
* • touchPrefsCopyIgnoredNames: copy every stored name into `out_buf`
|
||||
* (>= TOUCH_IGNORED_NAMES_MAX * TOUCH_IGNORED_NAME_LEN bytes); returns count. */
|
||||
constexpr int TOUCH_IGNORED_NAMES_MAX = 16;
|
||||
constexpr int TOUCH_IGNORED_NAME_LEN = 28; // fixed NUL-padded slot (>= MAX_SENDER_NAME+1)
|
||||
bool touchPrefsIsNameIgnored(const char* name);
|
||||
bool touchPrefsSetNameIgnored(const char* name, bool ignored);
|
||||
int touchPrefsCopyIgnoredNames(char* out_buf);
|
||||
|
||||
/** Notification-sound prefs (the message chime itself stays on the core
|
||||
* buzzer_quiet flag). @-mention chime is a SEPARATE enable + a distinct sound,
|
||||
* so an operator can mute everything but still hear @-mentions. Volume is the
|
||||
@@ -170,6 +257,18 @@ bool touchPrefsGetSoundMentions(); // default true
|
||||
void touchPrefsSetSoundMentions(bool on);
|
||||
uint8_t touchPrefsGetSoundVolume(); // 0..100, default 70
|
||||
void touchPrefsSetSoundVolume(uint8_t vol);
|
||||
bool touchPrefsGetEnterSends(); // Enter key sends a chat message (default true)
|
||||
void touchPrefsSetEnterSends(bool on);
|
||||
bool touchPrefsGetClock12h(); // 12-hour clock (default false = 24h)
|
||||
void touchPrefsSetClock12h(bool on);
|
||||
bool touchPrefsGetNavMenubarKeys(); // show per-tab nav hotkey letters over the menubar (default false = hidden)
|
||||
void touchPrefsSetNavMenubarKeys(bool on);
|
||||
bool touchPrefsGetScrollReverse(); // invert trackball/scrollball direction (default false)
|
||||
void touchPrefsSetScrollReverse(bool on);
|
||||
bool touchPrefsGetEdgeScroll(); // push cursor past edge to scroll content (default false)
|
||||
void touchPrefsSetEdgeScroll(bool on);
|
||||
bool touchPrefsGetLockOnScreenOff(); // idle screen-off auto-locks; only a deliberate hold wakes (default false)
|
||||
void touchPrefsSetLockOnScreenOff(bool on);
|
||||
|
||||
/** Per-channel mute, keyed by channel name. Bit 0 = mute messages, bit 1 =
|
||||
* mute @-mentions. Suppresses the notification SOUND for that channel (the
|
||||
@@ -252,9 +351,20 @@ bool touchPrefsSetAccentColor(uint32_t rgb);
|
||||
int touchPrefsGetTimeOffsetHours();
|
||||
bool touchPrefsSetTimeOffsetHours(int hours);
|
||||
|
||||
/** Build the POSIX TZ string for local-time display: the CET/CEST base plus the
|
||||
* user's hour offset (see touchPrefsGetTimeOffsetHours). Used by both the
|
||||
* boot-time setenv and the Wi-Fi NTP sync so the offset is honoured everywhere.
|
||||
/** Time-zone picker. A curated list of named zones, each carrying the correct
|
||||
* POSIX TZ string WITH that region's DST rules — so non-European users get the
|
||||
* right time year-round instead of being stuck on the CET base + EU DST dates.
|
||||
* Default 0 == "Europe (CET/CEST)" (unchanged behaviour). The LAST index is
|
||||
* "Custom (UTC offset)", which uses touchPrefsGetTimeOffsetHours() as a fixed,
|
||||
* no-DST offset. touchPrefsBuildLocalTz() resolves the selected zone. */
|
||||
int touchPrefsTimezoneCount(); // number of pickable entries
|
||||
const char* touchPrefsTimezoneLabel(int idx); // display label for the picker
|
||||
uint8_t touchPrefsGetTimezone(); // selected index (default 0)
|
||||
void touchPrefsSetTimezone(uint8_t idx);
|
||||
|
||||
/** Build the POSIX TZ string for local-time display from the selected time zone
|
||||
* (touchPrefsGetTimezone), or a fixed UTC offset for the "Custom" entry. Used by
|
||||
* both the boot-time setenv and the Wi-Fi NTP sync so it's honoured everywhere.
|
||||
* `out` is always null-terminated when out_cap > 0. */
|
||||
void touchPrefsBuildLocalTz(char* out, int out_cap);
|
||||
|
||||
|
||||
@@ -177,6 +177,11 @@ bool wifiConfigConsumeApplyRequest() {
|
||||
}
|
||||
|
||||
void wifiConfigApply() {
|
||||
#if defined(HAS_TANMATSU)
|
||||
printf("[WIFI] apply radio_en=%d hasRuntime=%d mode=%d status=%d\n",
|
||||
(int)wifiConfigGetRadioEnabled(), (int)wifiConfigHasRuntime(),
|
||||
(int)WiFi.getMode(), (int)WiFi.status());
|
||||
#endif
|
||||
if (!wifiConfigGetRadioEnabled()) {
|
||||
WiFi.disconnect(true);
|
||||
delay(50);
|
||||
@@ -188,9 +193,13 @@ void wifiConfigApply() {
|
||||
char pwd[WIFI_CONFIG_PWD_MAX];
|
||||
wifiConfigGetSsid(ssid, sizeof(ssid));
|
||||
wifiConfigGetPwd(pwd, sizeof(pwd));
|
||||
WiFi.mode(WIFI_STA); // esp_wifi_remote needs an explicit STA mode before begin()
|
||||
WiFi.disconnect();
|
||||
delay(100);
|
||||
WiFi.begin(ssid, pwd[0] ? pwd : nullptr);
|
||||
#if defined(HAS_TANMATSU)
|
||||
printf("[WIFI] begin ssid='%s' pwlen=%d\n", ssid, (int)strlen(pwd));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
+88
-1
@@ -12,6 +12,7 @@
|
||||
#include "helpers/esp32/SdNvsPrefs.h" // route prefs to file storage (SD/SPIFFS), off NVS
|
||||
// (quoted: use wadamesh's src/ copy, not the lib's stale one)
|
||||
#include "wadamesh_mark_rgb.h" // anti-aliased mesh-mark (RGB565) for the pre-LVGL boot screen
|
||||
#include "ui-touch/TouchSleep.h" // idle light-sleep controller (loopEnd called at end of loop())
|
||||
#endif
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
@@ -399,8 +400,14 @@ void setup() {
|
||||
* happens later in loop(); this just inits the stack.) */
|
||||
if (want_wifi) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.setAutoReconnect(true); // let esp_wifi rejoin on its own after a beacon loss / AP reboot
|
||||
// (was false — the 10 s poll below was the ONLY recovery, and a
|
||||
// bare begin() on a wedged supplicant is a no-op -> never reconnected)
|
||||
WiFi.persistent(false);
|
||||
// NOTE: do NOT enable modem-sleep here. On a fresh, *unassociated* STA (the
|
||||
// setup wizard, no creds yet) DTIM modem-sleep naps the radio through the
|
||||
// scan dwell, so WiFi.scanNetworks() comes back empty ("no networks found").
|
||||
// It's enabled once we actually associate — see the GOT_IP handler below.
|
||||
}
|
||||
#if defined(BLE_PIN_CODE)
|
||||
/* Always stash the BLE params so the toggle can bring BLE up live later, even
|
||||
@@ -466,6 +473,27 @@ void setup() {
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
|
||||
#if defined(ENV_INCLUDE_GPS) && (ENV_INCLUDE_GPS == 1)
|
||||
// GPS UART resilience (slow / never-acquires TTFF fix): the core opens Serial1 for the
|
||||
// NMEA GPS with Arduino's default 256-byte RX ring — only ~66 ms of slack at 38400 baud
|
||||
// (~270 ms at 9600). A single long LVGL/map frame stalls this loop past that, dropping
|
||||
// UART bytes and corrupting NMEA ephemeris subframes. Each corrupted subframe costs the
|
||||
// receiver ~30 s of re-acquisition, so a busy UI turns a ~1-minute fix into several
|
||||
// minutes — or, with frequent stalls, never. A larger ring absorbs the stalls. MUST
|
||||
// precede the core's Serial1.begin() inside sensors.begin(); setRxBufferSize is a no-op
|
||||
// once the UART is already running.
|
||||
//
|
||||
// Gate on gps_enabled: sensors.begin()'s GPS-detect opens Serial1 on EVERY boot —
|
||||
// including the many V4s with no GPS module — and never calls Serial1.end(), so an
|
||||
// unconditional 4 KB ring permanently costs ~3.8 KB of scarce internal DRAM for nothing
|
||||
// on the GPS-off majority (the "RAM is higher now" reports). GPS-on users (who actually
|
||||
// hit the overflow) still get the big ring; default GPS-off keeps the stock 256 B. A user
|
||||
// who enables GPS mid-session picks it up on the next reboot (gps_enabled is persisted).
|
||||
{
|
||||
auto* np = the_mesh.getNodePrefs();
|
||||
if (np && np->gps_enabled) Serial1.setRxBufferSize(4096);
|
||||
}
|
||||
#endif
|
||||
sensors.begin();
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
@@ -553,6 +581,10 @@ void loop() {
|
||||
wifiConfigGetSsid(ssid, sizeof(ssid));
|
||||
wifiConfigGetPwd(pwd, sizeof(pwd));
|
||||
if (strlen(ssid) > 0) {
|
||||
// A bare begin() on a supplicant wedged after a silent drop (AP reboot /
|
||||
// beacon loss) can be a no-op — clear its state first so this forces a
|
||||
// fresh association. Backs up setAutoReconnect(true) for the stuck case.
|
||||
WiFi.disconnect(false, true);
|
||||
WiFi.begin(ssid, pwd[0] ? pwd : nullptr);
|
||||
}
|
||||
}
|
||||
@@ -563,6 +595,12 @@ void loop() {
|
||||
static bool sntp_pushed = false;
|
||||
static uint32_t sntp_kick_ms = 0;
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
// Now that we're associated, enable DTIM modem-sleep (saves power + gives
|
||||
// BLE coexistence airtime). Deferred to here on purpose: enabling it on the
|
||||
// unassociated STA naps the radio through a scan dwell and breaks the setup
|
||||
// wizard's WiFi.scanNetworks() ("no networks found"). One-shot.
|
||||
static bool modem_sleep_set = false;
|
||||
if (!modem_sleep_set) { WiFi.setSleep(true); modem_sleep_set = true; }
|
||||
if (!sntp_kicked) {
|
||||
/* Brussels timezone with DST rules baked in (POSIX "CET-1CEST,...").
|
||||
* On touch builds the base is shifted by the user's manual hour offset
|
||||
@@ -604,7 +642,49 @@ void loop() {
|
||||
}
|
||||
#endif
|
||||
the_mesh.loop();
|
||||
#if defined(GPS_BUF_DEBUG)
|
||||
// Bench diagnostic (build with -DGPS_BUF_DEBUG only; absent in releases): peak GPS UART
|
||||
// backlog accumulated between sensors.loop() drains. A peak above the old 256-byte default
|
||||
// proves loop stalls were overflowing the default ring — i.e. NMEA was being lost, which
|
||||
// is the slow/never-acquires TTFF mechanism. With the 4096 ring above it can climb past
|
||||
// 256 without loss, so a >256 reading is direct proof the fix matters on this unit.
|
||||
{ static size_t s_gps_peak = 0; static uint32_t s_gps_log = 0;
|
||||
size_t bl = Serial1.available();
|
||||
if (bl > s_gps_peak) s_gps_peak = bl;
|
||||
if (millis() - s_gps_log > 5000) {
|
||||
s_gps_log = millis();
|
||||
Serial.printf("[GPSBUF] peak=%u B / 5s (old cap 256, now 4096)\n", (unsigned)s_gps_peak);
|
||||
s_gps_peak = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
sensors.loop();
|
||||
#if defined(ESP32)
|
||||
// GPS time guard (Ricky Leong's "stuck at 1902"): MicroNMEALocationProvider
|
||||
// sets the mesh RTC from a GPS *position* fix even before the date fields are
|
||||
// valid (getYear()==0 -> ~1902-10-11), and re-syncs every 30 min — so it
|
||||
// periodically clobbers a good time. A 1902 clock stamps our adverts as
|
||||
// decades old and every other node rejects them as stale.
|
||||
//
|
||||
// On the T-Deck the mesh RTC, NTP and GPS ALL share the one ESP32 system clock
|
||||
// (ESP32RTCClock == settimeofday/gettimeofday), so we can't recover by
|
||||
// "re-reading NTP" — it was already overwritten. Instead keep a millis()-
|
||||
// anchored copy of the last good time and rebuild from it whenever the clock
|
||||
// reads garbage, undoing the clobber before the next the_mesh.loop() sends an
|
||||
// advert. The anchor refreshes every loop while the clock is sane, so the
|
||||
// rebuilt time is accurate to the second.
|
||||
{
|
||||
static uint32_t good_epoch = 0, good_millis = 0;
|
||||
const uint32_t live = rtc_clock.getCurrentTime();
|
||||
if (live > 1700000000UL) { // clock sane -> remember it (anchor)
|
||||
good_epoch = live;
|
||||
good_millis = millis();
|
||||
} else if (good_epoch != 0) { // clock went bad -> rebuild from anchor
|
||||
const uint32_t rebuilt = good_epoch + (uint32_t)((millis() - good_millis) / 1000UL);
|
||||
rtc_clock.setCurrentTime(rebuilt);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
// (1.16) sleep when there's no pending work — nRF power saving
|
||||
@@ -630,4 +710,11 @@ void loop() {
|
||||
#if defined(ESP32_PLATFORM)
|
||||
board.pollHttpOtaReboot();
|
||||
#endif
|
||||
|
||||
#if defined(HAS_TOUCH_UI)
|
||||
// Idle light-sleep gate: evaluated every loop tick. g_enabled is false by
|
||||
// default (Task 1 is inert); Task 2 sets it from the NVS pref and wires
|
||||
// the real predicates so the gate can actually pass and arm light sleep.
|
||||
touchSleep::loopEnd(millis());
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#include "SnakeGame.h"
|
||||
|
||||
#include <Arduino.h> // random()
|
||||
#include <string.h> // memmove
|
||||
#include <LvglPsramAlloc.h> // PSRAM-preferred buffer for the canvas
|
||||
|
||||
// Mirrors the app's status-bar height; the overlay starts below it so the bar
|
||||
// stays visible. Kept local so this module needs nothing from UITask.
|
||||
static constexpr lv_coord_t kTopBar = 22;
|
||||
static constexpr uint32_t kStepMs = 300; // tick period (a touch slower than before)
|
||||
|
||||
// Palette (hex literals so the module is self-contained).
|
||||
static constexpr uint32_t kColBg = 0x0A0B0C; // playfield background
|
||||
static constexpr uint32_t kColFood = 0xE0584C; // food
|
||||
static constexpr uint32_t kColBody = 0x15B6A6; // snake body (brand teal)
|
||||
static constexpr uint32_t kColText = 0xE6EAEE;
|
||||
|
||||
SnakeGame* SnakeGame::s_active = nullptr;
|
||||
|
||||
bool SnakeGame::isOpen() { return s_active != nullptr && s_active->root_ != nullptr; }
|
||||
|
||||
void SnakeGame::steer(int dx, int dy) {
|
||||
if (!s_active || !s_active->started_ || s_active->over_) return;
|
||||
// Trackball deltas -> one cardinal direction (dominant axis).
|
||||
if (dx == 0 && dy == 0) return;
|
||||
const int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
|
||||
if (adx >= ady) s_active->setDir(dx > 0 ? 1 : -1, 0);
|
||||
else s_active->setDir(0, dy > 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
void SnakeGame::launch() {
|
||||
if (s_active) return;
|
||||
SnakeGame* g = new SnakeGame();
|
||||
s_active = g;
|
||||
if (!g->open()) { s_active = nullptr; delete g; }
|
||||
}
|
||||
|
||||
void SnakeGame::placeFood() {
|
||||
const int cells = cols_ * rows_;
|
||||
for (int tries = 0; tries < 4 * cells + 16; ++tries) {
|
||||
const uint8_t fx = (uint8_t)random(cols_);
|
||||
const uint8_t fy = (uint8_t)random(rows_);
|
||||
bool on = false;
|
||||
for (int i = 0; i < len_; ++i) if (bx_[i] == fx && by_[i] == fy) { on = true; break; }
|
||||
if (!on) { fx_ = fx; fy_ = fy; return; }
|
||||
}
|
||||
}
|
||||
|
||||
void SnakeGame::reset() {
|
||||
len_ = 3;
|
||||
const uint8_t cx = (uint8_t)(cols_ / 2), cy = (uint8_t)(rows_ / 2);
|
||||
for (int i = 0; i < len_; ++i) { bx_[i] = (uint8_t)(cx - i); by_[i] = cy; }
|
||||
dx_ = 1; dy_ = 0; ndx_ = 1; ndy_ = 0;
|
||||
over_ = false; score_val_ = 0;
|
||||
placeFood();
|
||||
}
|
||||
|
||||
void SnakeGame::render() {
|
||||
if (!canvas_) return;
|
||||
const int c = kCellPx;
|
||||
lv_canvas_fill_bg(canvas_, lv_color_hex(kColBg), LV_OPA_COVER);
|
||||
lv_draw_rect_dsc_t d; lv_draw_rect_dsc_init(&d); d.radius = 2;
|
||||
d.bg_color = lv_color_hex(kColFood);
|
||||
lv_canvas_draw_rect(canvas_, fx_ * c + 1, fy_ * c + 1, c - 2, c - 2, &d);
|
||||
for (int i = 0; i < len_; ++i) {
|
||||
d.bg_color = lv_color_hex(kColBody);
|
||||
d.bg_opa = (i == 0) ? LV_OPA_COVER : LV_OPA_80; // head reads brighter
|
||||
lv_canvas_draw_rect(canvas_, bx_[i] * c + 1, by_[i] * c + 1, c - 2, c - 2, &d);
|
||||
}
|
||||
}
|
||||
|
||||
void SnakeGame::updateScoreLabel() {
|
||||
if (!score_) return;
|
||||
if (over_) lv_label_set_text_fmt(score_, "Game over \xe2\x80\x94 score %d (tap to restart)", score_val_);
|
||||
else if (paused_) lv_label_set_text_fmt(score_, "Paused \xe2\x80\x94 score %d", score_val_);
|
||||
else if (started_) lv_label_set_text_fmt(score_, "score %d", score_val_);
|
||||
else lv_label_set_text(score_, "Snake \xe2\x80\x94 swipe or roll the trackball");
|
||||
}
|
||||
|
||||
void SnakeGame::step() {
|
||||
if (over_ || !started_ || paused_) return;
|
||||
if (!(ndx_ == -dx_ && ndy_ == -dy_)) { dx_ = ndx_; dy_ = ndy_; }
|
||||
const int nx = bx_[0] + dx_, ny = by_[0] + dy_;
|
||||
const bool eat = (nx == fx_ && ny == fy_);
|
||||
bool over = (nx < 0 || ny < 0 || nx >= cols_ || ny >= rows_);
|
||||
const int clen = eat ? len_ : len_ - 1; // the tail vacates unless we grow
|
||||
for (int i = 0; i < clen && !over; ++i) if (bx_[i] == nx && by_[i] == ny) over = true;
|
||||
if (over) { over_ = true; updateScoreLabel(); return; }
|
||||
|
||||
int newlen = len_ + (eat ? 1 : 0);
|
||||
if (newlen > cols_ * rows_) newlen = cols_ * rows_;
|
||||
memmove(&bx_[1], &bx_[0], (size_t)(newlen - 1));
|
||||
memmove(&by_[1], &by_[0], (size_t)(newlen - 1));
|
||||
bx_[0] = (uint8_t)nx; by_[0] = (uint8_t)ny;
|
||||
len_ = newlen;
|
||||
if (eat) { score_val_++; placeFood(); updateScoreLabel(); }
|
||||
render();
|
||||
}
|
||||
|
||||
void SnakeGame::setDir(int dx, int dy) { ndx_ = dx; ndy_ = dy; }
|
||||
|
||||
void SnakeGame::startGame() {
|
||||
if (started_) return;
|
||||
started_ = true;
|
||||
paused_ = false;
|
||||
if (start_btn_) { lv_obj_del(start_btn_); start_btn_ = nullptr; }
|
||||
if (pause_btn_) { // reveal the pause toggle now that we're playing
|
||||
lv_obj_clear_flag(pause_btn_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (pause_lbl_) lv_label_set_text(pause_lbl_, LV_SYMBOL_PAUSE);
|
||||
}
|
||||
reset();
|
||||
updateScoreLabel();
|
||||
render();
|
||||
if (!timer_) timer_ = lv_timer_create(timerCb, kStepMs, this);
|
||||
}
|
||||
|
||||
void SnakeGame::togglePause() {
|
||||
if (!started_ || over_) return;
|
||||
paused_ = !paused_;
|
||||
if (pause_lbl_) lv_label_set_text(pause_lbl_, paused_ ? LV_SYMBOL_PLAY : LV_SYMBOL_PAUSE);
|
||||
updateScoreLabel();
|
||||
}
|
||||
|
||||
bool SnakeGame::open() {
|
||||
const lv_coord_t sw = lv_disp_get_hor_res(nullptr);
|
||||
const lv_coord_t sh = lv_disp_get_ver_res(nullptr);
|
||||
root_ = lv_obj_create(lv_layer_top());
|
||||
lv_obj_remove_style_all(root_);
|
||||
lv_obj_set_size(root_, sw, sh - kTopBar);
|
||||
lv_obj_set_pos(root_, 0, kTopBar);
|
||||
lv_obj_set_style_bg_color(root_, lv_color_hex(0x0E1216), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_opa(root_, LV_OPA_COVER, LV_PART_MAIN);
|
||||
lv_obj_clear_flag(root_, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(root_, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(root_, gestureCb, LV_EVENT_GESTURE, this);
|
||||
lv_obj_add_event_cb(root_, tapCb, LV_EVENT_CLICKED, this);
|
||||
|
||||
// Fill the unoccupied screen below a 24-px score/close row. Whole cells only.
|
||||
const int avail_w = (int)sw;
|
||||
const int avail_h = (int)(sh - kTopBar) - 24;
|
||||
cols_ = avail_w / kCellPx; if (cols_ > kMaxCols) cols_ = kMaxCols; if (cols_ < 6) cols_ = 6;
|
||||
rows_ = avail_h / kCellPx; if (rows_ > kMaxRows) rows_ = kMaxRows; if (rows_ < 6) rows_ = 6;
|
||||
const int pw = cols_ * kCellPx, ph = rows_ * kCellPx;
|
||||
buf_ = (lv_color_t*)lvglPsramAlloc((size_t)pw * ph * sizeof(lv_color_t));
|
||||
if (!buf_) return false;
|
||||
canvas_ = lv_canvas_create(root_);
|
||||
lv_canvas_set_buffer(canvas_, buf_, pw, ph, LV_IMG_CF_TRUE_COLOR);
|
||||
lv_obj_align(canvas_, LV_ALIGN_BOTTOM_MID, 0, 0); // playfield fills the lower area
|
||||
|
||||
score_ = lv_label_create(root_);
|
||||
lv_obj_set_style_text_color(score_, lv_color_hex(kColText), LV_PART_MAIN);
|
||||
lv_obj_align(score_, LV_ALIGN_TOP_LEFT, 6, 4);
|
||||
|
||||
lv_obj_t* x = lv_btn_create(root_);
|
||||
lv_obj_set_size(x, 30, 24);
|
||||
lv_obj_align(x, LV_ALIGN_TOP_RIGHT, -4, 0);
|
||||
lv_obj_add_event_cb(x, closeCb, LV_EVENT_CLICKED, this);
|
||||
lv_obj_t* xl = lv_label_create(x); lv_label_set_text(xl, LV_SYMBOL_CLOSE); lv_obj_center(xl);
|
||||
|
||||
// Pause/resume toggle, left of the X. Hidden until the game starts.
|
||||
pause_btn_ = lv_btn_create(root_);
|
||||
lv_obj_set_size(pause_btn_, 30, 24);
|
||||
lv_obj_align(pause_btn_, LV_ALIGN_TOP_RIGHT, -40, 0);
|
||||
lv_obj_add_event_cb(pause_btn_, pauseCb, LV_EVENT_CLICKED, this);
|
||||
pause_lbl_ = lv_label_create(pause_btn_); lv_label_set_text(pause_lbl_, LV_SYMBOL_PAUSE); lv_obj_center(pause_lbl_);
|
||||
lv_obj_add_flag(pause_btn_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
// Paused: draw the initial snake, then wait on a centred "New game" button.
|
||||
reset();
|
||||
updateScoreLabel();
|
||||
render();
|
||||
start_btn_ = lv_btn_create(root_);
|
||||
lv_obj_set_size(start_btn_, 150, 44);
|
||||
lv_obj_align(start_btn_, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_add_event_cb(start_btn_, startCb, LV_EVENT_CLICKED, this);
|
||||
lv_obj_t* sl = lv_label_create(start_btn_);
|
||||
lv_label_set_text(sl, LV_SYMBOL_PLAY " New game");
|
||||
lv_obj_center(sl);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SnakeGame::close() {
|
||||
if (timer_) { lv_timer_del(timer_); timer_ = nullptr; }
|
||||
if (root_) { lv_obj_del(root_); root_ = nullptr; } // deletes canvas + button children
|
||||
if (buf_) { lvglPsramFree(buf_); buf_ = nullptr; } // free AFTER the canvas is gone
|
||||
canvas_ = nullptr; score_ = nullptr; start_btn_ = nullptr;
|
||||
pause_btn_ = nullptr; pause_lbl_ = nullptr;
|
||||
}
|
||||
|
||||
// ---- LVGL C-callback trampolines (user_data = the instance) ----
|
||||
void SnakeGame::timerCb(lv_timer_t* t) {
|
||||
auto* self = static_cast<SnakeGame*>(t->user_data);
|
||||
if (self) self->step();
|
||||
}
|
||||
void SnakeGame::gestureCb(lv_event_t* e) {
|
||||
auto* self = static_cast<SnakeGame*>(lv_event_get_user_data(e));
|
||||
if (!self || !self->started_) return;
|
||||
switch (lv_indev_get_gesture_dir(lv_indev_get_act())) {
|
||||
case LV_DIR_TOP: self->setDir(0, -1); break;
|
||||
case LV_DIR_BOTTOM: self->setDir(0, 1); break;
|
||||
case LV_DIR_LEFT: self->setDir(-1, 0); break;
|
||||
case LV_DIR_RIGHT: self->setDir(1, 0); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
void SnakeGame::tapCb(lv_event_t* e) {
|
||||
auto* self = static_cast<SnakeGame*>(lv_event_get_user_data(e));
|
||||
if (self && self->started_ && self->over_) { self->reset(); self->updateScoreLabel(); self->render(); }
|
||||
}
|
||||
void SnakeGame::startCb(lv_event_t* e) {
|
||||
auto* self = static_cast<SnakeGame*>(lv_event_get_user_data(e));
|
||||
if (self) self->startGame();
|
||||
}
|
||||
void SnakeGame::pauseCb(lv_event_t* e) {
|
||||
auto* self = static_cast<SnakeGame*>(lv_event_get_user_data(e));
|
||||
if (self) self->togglePause();
|
||||
}
|
||||
void SnakeGame::closeCb(lv_event_t* e) {
|
||||
auto* self = static_cast<SnakeGame*>(lv_event_get_user_data(e));
|
||||
if (!self) return;
|
||||
self->close();
|
||||
if (s_active == self) s_active = nullptr;
|
||||
delete self;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
// Self-contained Snake mini-game launched from the Apps drawer.
|
||||
//
|
||||
// Owns a full-screen overlay on lv_layer_top: a canvas playfield filling the
|
||||
// unoccupied screen, a score line, and a close button. The game waits on a
|
||||
// "New game" button (doesn't start moving on open). Steer by swipe OR trackball
|
||||
// (UITask routes the trackball here via isOpen()/steer() and hides the cursor);
|
||||
// tap to restart after a game over; the X closes it. One instance at a time.
|
||||
//
|
||||
// Decoupled from UITask internals — depends only on LVGL and lvglPsramAlloc.
|
||||
class SnakeGame {
|
||||
public:
|
||||
static void launch(); // open (no-op if already open)
|
||||
static bool isOpen(); // UITask: gate the trackball + tab bar
|
||||
static void steer(int dx, int dy); // UITask: trackball motion -> direction
|
||||
|
||||
private:
|
||||
// The playfield grid is sized to the screen at open() (cols_ x rows_), capped
|
||||
// by these maxima so the body arrays are fixed-size.
|
||||
static constexpr int kCellPx = 14; // px per cell
|
||||
static constexpr int kMaxCols = 40;
|
||||
static constexpr int kMaxRows = 30;
|
||||
static constexpr int kMaxCells = kMaxCols * kMaxRows;
|
||||
|
||||
lv_obj_t* root_ = nullptr; // full-screen overlay (owns everything below)
|
||||
lv_obj_t* canvas_ = nullptr;
|
||||
lv_color_t* buf_ = nullptr; // canvas pixel buffer (PSRAM)
|
||||
lv_obj_t* score_ = nullptr;
|
||||
lv_obj_t* start_btn_ = nullptr; // "New game" gate (deleted once tapped)
|
||||
lv_obj_t* pause_btn_ = nullptr; // pause/resume toggle (shown once playing)
|
||||
lv_obj_t* pause_lbl_ = nullptr; // its glyph (play <-> pause)
|
||||
lv_timer_t* timer_ = nullptr;
|
||||
|
||||
int cols_ = 14, rows_ = 14; // grid dimensions (set at open)
|
||||
bool started_ = false; // false until "New game" is tapped
|
||||
bool paused_ = false; // pause toggle (timer keeps ticking; step() no-ops)
|
||||
uint8_t bx_[kMaxCells], by_[kMaxCells]; // snake body, [0] = head
|
||||
int len_ = 0;
|
||||
int dx_ = 1, dy_ = 0; // active direction
|
||||
int ndx_ = 1, ndy_ = 0; // queued direction (applied next step)
|
||||
uint8_t fx_ = 0, fy_ = 0; // food cell
|
||||
bool over_ = false;
|
||||
int score_val_ = 0;
|
||||
|
||||
bool open(); // build UI (paused); false on alloc failure
|
||||
void startGame(); // begin the tick (from the New-game button)
|
||||
void togglePause(); // pause/resume the running game
|
||||
void close();
|
||||
void reset();
|
||||
void placeFood();
|
||||
void render();
|
||||
void step();
|
||||
void setDir(int dx, int dy);
|
||||
void updateScoreLabel();
|
||||
|
||||
static SnakeGame* s_active; // the one live game (or nullptr)
|
||||
static void timerCb(lv_timer_t* t);
|
||||
static void gestureCb(lv_event_t* e);
|
||||
static void tapCb(lv_event_t* e);
|
||||
static void closeCb(lv_event_t* e);
|
||||
static void startCb(lv_event_t* e);
|
||||
static void pauseCb(lv_event_t* e);
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "TouchSleep.h"
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
namespace touchSleep {
|
||||
namespace {
|
||||
// When the gate passes (device parked: screen off, no companion client, WiFi+BLE
|
||||
// off, on battery, mesh idle) we THROTTLE the main loop with vTaskDelay instead of
|
||||
// entering esp_light_sleep.
|
||||
//
|
||||
// Why not real light sleep: manual esp_light_sleep_start() from the Arduino loop
|
||||
// task trips the Interrupt Watchdog in this build (dual-core, no PM framework,
|
||||
// INT_WDT on both cores @ 300 ms, always-on companion/LVGL/USB tasks) — reliably,
|
||||
// regardless of duration or wake-source handling. vTaskDelay is a standard scheduler
|
||||
// yield: the FreeRTOS idle task halts the CPU (WFI) between 1 ms ticks, trimming the
|
||||
// idle busy-spin draw, with zero watchdog/hang risk. The radio stays in RX; an
|
||||
// incoming packet is serviced on the next loop iteration (<= THROTTLE_MS late).
|
||||
constexpr uint32_t THROTTLE_MS = 50;
|
||||
|
||||
uint64_t g_acc_idle_us = 0; // cumulative time spent parked/throttled
|
||||
|
||||
Hooks g_hooks = {};
|
||||
TransitionCb g_transition = nullptr;
|
||||
bool g_enabled = false; // default OFF; driven from NVS via touchPrefsGetSleepIdle() at init
|
||||
bool g_asleep_regime = false;
|
||||
uint32_t g_cycle_count = 0; // throttle cycles while parked (liveness counter)
|
||||
WakeReason g_last_reason = WakeReason::None;
|
||||
|
||||
bool gatePasses() {
|
||||
if (!g_enabled) return false;
|
||||
if (!g_hooks.screenOff || !g_hooks.screenOff()) return false;
|
||||
if (!g_hooks.noClient || !g_hooks.noClient()) return false;
|
||||
if (!g_hooks.wifiOff || !g_hooks.wifiOff()) return false;
|
||||
if (!g_hooks.bleOff || !g_hooks.bleOff()) return false;
|
||||
if (!g_hooks.onBattery || !g_hooks.onBattery()) return false;
|
||||
if (!g_hooks.meshIdle || !g_hooks.meshIdle()) return false;
|
||||
return true;
|
||||
}
|
||||
void emitTransition(bool entering) {
|
||||
if (g_transition && g_hooks.epochNow) g_transition(g_hooks.epochNow(), entering);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void begin(const Hooks& hooks) { g_hooks = hooks; }
|
||||
void onTransition(TransitionCb cb) { g_transition = cb; }
|
||||
void setEnabled(bool on) { g_enabled = on; }
|
||||
bool enabled() { return g_enabled; }
|
||||
|
||||
void loopEnd(uint32_t /*now_ms*/) {
|
||||
const bool pass = gatePasses();
|
||||
if (!pass) {
|
||||
if (g_asleep_regime) { g_asleep_regime = false; emitTransition(false); } // sun — resumed activity
|
||||
return;
|
||||
}
|
||||
if (!g_asleep_regime) { g_asleep_regime = true; emitTransition(true); } // moon — parked
|
||||
|
||||
// Yield the CPU to the idle task for THROTTLE_MS (replaces esp_light_sleep_start —
|
||||
// see the note above). Standard FreeRTOS call: cannot starve a watchdog or hang.
|
||||
const uint64_t t0 = esp_timer_get_time();
|
||||
vTaskDelay(pdMS_TO_TICKS(THROTTLE_MS));
|
||||
g_acc_idle_us += (uint64_t)(esp_timer_get_time() - t0);
|
||||
g_cycle_count++;
|
||||
g_last_reason = WakeReason::Timer; // a throttle is a timed yield
|
||||
}
|
||||
|
||||
bool isSleeping() { return g_asleep_regime; } // true while parked/throttling
|
||||
uint32_t wakeCount() { return g_cycle_count; } // throttle cycles while parked
|
||||
WakeReason lastWakeReason() { return g_last_reason; }
|
||||
|
||||
uint8_t pctAsleep() {
|
||||
const uint64_t up = (uint64_t)esp_timer_get_time();
|
||||
if (up == 0) return 0;
|
||||
uint64_t pct = (g_acc_idle_us * 100ULL) / up;
|
||||
return pct > 100 ? 100 : (uint8_t)pct;
|
||||
}
|
||||
|
||||
} // namespace touchSleep
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// Idle power-save controller for the T-Deck (ESP32-S3). When the device is parked
|
||||
// (screen off, on battery, standalone) it throttles the main loop with vTaskDelay
|
||||
// so the FreeRTOS idle task can halt the CPU between ticks. (Real esp_light_sleep
|
||||
// trips the Interrupt Watchdog in this dual-core / no-PM build — see TouchSleep.cpp
|
||||
// and .notes/2026-06-18-light-sleep-rx-design.md for the full story.)
|
||||
//
|
||||
// Decoupled from UI/mesh by predicate hooks supplied by the integration layer
|
||||
// (UITask.cpp), so this module pulls in no UI/mesh/LVGL headers.
|
||||
namespace touchSleep {
|
||||
|
||||
enum class WakeReason : uint8_t { None, Timer, Packet, Touch, Button, Other };
|
||||
|
||||
struct Hooks {
|
||||
bool (*screenOff)(); // display is off
|
||||
bool (*noClient)(); // no companion client on any transport
|
||||
bool (*wifiOff)(); // WiFi fully off
|
||||
bool (*bleOff)(); // BLE fully off
|
||||
bool (*onBattery)(); // running on battery (no USB / charge source)
|
||||
bool (*meshIdle)(); // radio not mid-RX AND send queue empty
|
||||
// ms until the soonest wake-forcing deadline (advert / clock alarm),
|
||||
// or UINT32_MAX when nothing must wake us:
|
||||
uint32_t (*nextWakeForcingDueMs)(uint32_t now_ms);
|
||||
uint32_t (*epochNow)(); // wall-clock epoch seconds (for the event log)
|
||||
};
|
||||
|
||||
// regime transition: entering==true -> went to sleep (moon)
|
||||
// entering==false -> woke to activity (sun)
|
||||
using TransitionCb = void (*)(uint32_t epoch, bool entering);
|
||||
|
||||
void begin(const Hooks& hooks); // install hooks; arm static GPIO wake config once
|
||||
void onTransition(TransitionCb cb);
|
||||
void setEnabled(bool on); // feature toggle (driven by the NVS pref)
|
||||
bool enabled();
|
||||
|
||||
void loopEnd(uint32_t now_ms); // call at the very END of the main loop()
|
||||
|
||||
// instrumentation (read by the settings / diag UI)
|
||||
bool isSleeping(); // currently in the asleep regime
|
||||
uint32_t wakeCount(); // cumulative meaningful wakes
|
||||
WakeReason lastWakeReason();
|
||||
uint8_t pctAsleep(); // % wall-time asleep since boot (0..100)
|
||||
|
||||
} // namespace touchSleep
|
||||
+6411
-711
File diff suppressed because it is too large.
Load diff
+27
-10
@@ -29,7 +29,7 @@ enum class TouchUiScreen : uint8_t { Home = 0, ChatInbox = 1, Contacts = 2, Sett
|
||||
|
||||
class UITask : public AbstractUITask {
|
||||
public:
|
||||
static const int MAX_UI_MESSAGES = 96;
|
||||
static const int MAX_UI_MESSAGES = 500;
|
||||
static const int MAX_UI_THREADS = 48;
|
||||
static const int MAX_THREAD_NAME = 32;
|
||||
static const int MAX_SENDER_NAME = 24;
|
||||
@@ -160,8 +160,10 @@ private:
|
||||
TouchUiScreen _touch_screen;
|
||||
bool _active_dm_contact_set;
|
||||
uint8_t _active_dm_contact_pub[32];
|
||||
bool _history_dirty;
|
||||
unsigned long _next_history_flush_ms;
|
||||
bool _threads_dirty;
|
||||
unsigned long _next_threads_flush_ms;
|
||||
bool _msgs_dirty;
|
||||
unsigned long _next_msgs_flush_ms;
|
||||
|
||||
void userLedHandler();
|
||||
|
||||
@@ -194,7 +196,8 @@ private:
|
||||
uint16_t in_scope = 0);
|
||||
// Shared core for newMsg / newMsgFromPubWithMeta — see UITask.cpp.
|
||||
void newMsgImpl(uint8_t path_len, const char* from_name, const char* text, int msgcount,
|
||||
uint8_t meta_flags, int8_t snr_q4, int8_t rssi);
|
||||
uint8_t meta_flags, int8_t snr_q4, int8_t rssi,
|
||||
const char* sender_override = nullptr);
|
||||
public:
|
||||
/** Match an arriving ACK (4-byte hash) against the last few outgoing DMs
|
||||
* and flip their delivery state to DELIV_DELIVERED so the chat detail
|
||||
@@ -212,11 +215,16 @@ private:
|
||||
void appendComposerChar(char c);
|
||||
void appendComposerText(const char* text);
|
||||
void backspaceComposerChar();
|
||||
bool sendComposerToActiveThread();
|
||||
void markHistoryDirty(unsigned long delay_ms = 600);
|
||||
bool sendComposerToActiveThread(const char* override_text);
|
||||
void markThreadsDirty(unsigned long delay_ms = 200);
|
||||
void markMsgsDirty(unsigned long delay_ms = 2000);
|
||||
void flushHistoryIfDue(unsigned long now);
|
||||
bool loadHistoryFromStorage();
|
||||
bool saveHistoryToStorage();
|
||||
bool loadThreadsFromStorage();
|
||||
bool loadMsgsFromStorage();
|
||||
bool loadLegacyHistoryFromStorage();
|
||||
bool saveThreadsToStorage();
|
||||
bool saveMsgsToStorage();
|
||||
|
||||
void setCurrScreen(UIScreen* c);
|
||||
|
||||
@@ -230,8 +238,10 @@ public:
|
||||
_next_mesh_thread_refresh = 0;
|
||||
_active_dm_contact_set = false;
|
||||
memset(_active_dm_contact_pub, 0, sizeof(_active_dm_contact_pub));
|
||||
_history_dirty = false;
|
||||
_next_history_flush_ms = 0;
|
||||
_threads_dirty = false;
|
||||
_next_threads_flush_ms = 0;
|
||||
_msgs_dirty = false;
|
||||
_next_msgs_flush_ms = 0;
|
||||
_last_input_ms = 0;
|
||||
_screen_timeout_ms = 20000; // overridden by NVS in begin()
|
||||
_screen_off = false;
|
||||
@@ -255,6 +265,7 @@ public:
|
||||
int getUnreadTotal() const;
|
||||
int getUnreadMentionCount() const; // # of threads with an unread @mention of me
|
||||
void markThreadRead(int idx); // clear one thread's unread count (persisted)
|
||||
void markActiveThreadRead(); // clear the currently-open thread's unread (viewing == read)
|
||||
void markAllThreadsRead(); // clear every thread's unread count
|
||||
bool threadHasMention(int idx) const; // unread @mention of me in this thread
|
||||
int getThreadCount(bool channel_mode, int out_indexes[], int max_out) const;
|
||||
@@ -275,7 +286,8 @@ public:
|
||||
void composerAppendChar(char c) { appendComposerChar(c); }
|
||||
void composerAppendText(const char* text) { appendComposerText(text); }
|
||||
void composerBackspace() { backspaceComposerChar(); }
|
||||
bool composerSend() { return sendComposerToActiveThread(); }
|
||||
// override_text != nullptr resends that exact text (msg action menu "Resend"); nullptr = composer draft.
|
||||
bool composerSend(const char* override_text = nullptr) { return sendComposerToActiveThread(override_text); }
|
||||
void composerTypingMode() { _composer_action_idx = -1; }
|
||||
/** Open DM thread for mesh contact index (e.g. Chats thread list / external hooks). */
|
||||
void openMeshContactDm(uint32_t mesh_contact_index);
|
||||
@@ -392,6 +404,11 @@ public:
|
||||
const uint8_t* from_pub, const char* from_name,
|
||||
const char* text, int msgcount,
|
||||
int8_t snr_q4, int8_t rssi) override;
|
||||
void newRoomMsgFromPubWithMeta(uint8_t path_len, bool is_flood,
|
||||
const uint8_t* from_pub, const char* from_name,
|
||||
const char* author_name,
|
||||
const char* text, int msgcount,
|
||||
int8_t snr_q4, int8_t rssi) override;
|
||||
void notify(UIEventType t = UIEventType::none) override;
|
||||
void logRxFrame(float snr, float rssi, const uint8_t* raw, int len) override;
|
||||
void discoveredContact(const ContactInfo& contact, bool is_new, uint8_t path_len) override;
|
||||
|
||||
@@ -505,6 +505,10 @@ static const lv_img_dsc_t d_1f40c = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16
|
||||
static const uint8_t e_1f41e[] = {0,0,1,0,0,0,31,0,1,0,0,1,0,0,1,44,99,5,0,0,0,110,107,102,110,107,74,110,99,133,0,0,0,170,82,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,107,22,0,0,0,0,0,0,0,0,0,0,0,0,239,115,39,110,107,115,142,107,34,110,107,193,77,107,60,0,0,1,239,123,2,0,0,0,0,0,0,0,0,0,77,107,17,142,107,91,110,107,111,115,140,20,146,124,14,239,115,86,203,82,162,45,99,81,0,0,0,175,115,150,110,107,106,170,170,3,239,123,4,0,0,0,0,0,0,0,0,0,110,107,103,110,99,113,110,107,58,106,74,134,203,82,209,110,107,255,167,49,255,17,173,246,146,189,159,73,66,207,255,47,6,0,0,0,0,248,2,0,0,0,0,0,0,0,0,0,78,107,43,110,99,160,0,0,0,80,148,150,138,82,255,69,41,248,69,41,253,7,66,255,40,74,255,227,184,253,195,248,176,138,186,63,0,0,0,231,121,4,239,123,4,0,0,0,109,107,41,110,107,192,174,83,70,20,190,230,7,50,253,69,25,254,134,33,255,69,33,254,227,192,251,227,248,255,4,177,255,199,169,255,77,123,112,0,0,0,0,0,0,0,0,0,0,0,0,207,115,66,105,122,224,36,137,255,37,129,253,69,121,255,101,73,255,69,161,255,227,248,255,227,248,255,101,57,251,36,81,254,8,194,244,207,91,107,110,107,113,0,0,0,0,248,1,175,91,88,199,209,227,195,208,252,227,248,254,195,248,255,228,248,255,37,241,255,4,193,255,101,73,255,69,129,254,227,216,254,37,233,241,142,99,130,110,107,87,0,0,0,0,0,0,142,115,149,8,122,255,101,9,252,36,185,255,69,105,255,69,89,255,69,225,255,69,129,255,134,1,255,69,105,255,195,248,254,4,241,248,44,123,145,0,0,0,0,0,0,0,0,0,175,99,125,199,161,248,4,145,255,36,177,254,134,17,255,134,17,255,69,177,255,37,225,255,36,153,255,36,177,255,195,240,252,37,249,255,45,115,213,239,83,6,0,0,0,44,99,10,175,91,168,166,209,178,163,248,255,227,248,254,36,153,255,36,145,255,227,248,255,37,249,255,4,209,255,134,17,255,36,129,251,101,249,255,77,107,170,0,0,0,0,0,0,110,107,155,142,91,126,171,146,138,4,249,255,227,248,252,228,224,255,101,81,255,4,169,255,36,249,255,36,193,255,101,49,254,4,161,252,102,233,234,143,91,206,77,107,81,0,0,0,77,107,12,0,0,0,174,91,211,167,201,213,162,248,255,36,169,252,134,1,252,69,105,254,4,249,254,36,241,253,195,248,249,4,249,255,195,248,88,207,83,46,77,107,115,0,0,0,0,0,0,170,82,3,110,99,58,110,99,202,4,241,177,228,248,255,36,193,255,4,233,255,227,248,255,37,241,255,4,249,255,227,248,177,10,248,3,0,0,0,0,0,0,0,0,0,239,123,2,0,0,0,110,107,51,110,99,197,243,44,16,163,248,89,227,248,192,227,248,231,227,248,235,36,241,209,4,241,127,69,249,6,0,248,1,170,170,3,181,82,3,0,0,0,239,123,2,170,82,3,110,107,161,78,107,50,0,0,0,0,0,0,0,0,0,195,224,19,4,249,23,0,248,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0};
|
||||
static const lv_img_dsc_t d_1f41e = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f41e), e_1f41e };
|
||||
|
||||
static const uint8_t e_1f37a[] = {0,0,0,0,0,0,255,255,2,0,0,0,46,254,22,55,255,178,186,255,232,121,255,208,212,254,81,0,0,0,255,255,2,0,248,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,255,147,121,255,223,255,255,255,255,255,255,255,255,255,154,255,251,178,254,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,212,254,98,253,255,255,255,255,255,255,255,253,255,255,255,255,255,253,255,255,255,187,255,255,56,255,199,80,246,19,255,255,1,255,255,1,0,0,0,0,0,0,0,0,0,0,0,0,56,255,241,255,255,252,255,255,252,255,255,255,255,255,255,255,255,255,255,255,253,255,255,255,122,255,255,146,246,34,255,255,3,255,255,3,0,0,0,0,0,0,0,0,0,0,0,0,25,239,167,55,255,255,189,255,253,222,255,255,89,255,255,89,255,255,56,255,253,55,255,254,247,238,216,255,157,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,230,125,89,255,255,56,255,252,23,255,255,81,238,255,82,238,255,49,238,254,172,237,255,84,238,226,255,255,193,223,255,156,28,231,27,0,0,0,0,0,0,0,0,0,0,0,0,176,229,131,89,255,255,43,229,252,166,228,255,134,220,255,69,220,255,68,220,255,192,219,255,111,213,240,25,190,237,218,222,255,93,239,160,0,0,0,0,0,0,0,0,0,0,0,0,241,229,137,122,255,255,101,220,252,160,219,255,2,220,255,164,228,255,226,219,253,34,228,255,110,221,206,206,74,20,23,198,136,60,231,223,0,0,0,0,0,0,0,0,0,0,0,0,17,230,138,154,255,255,134,228,252,2,220,255,100,220,255,100,228,255,2,220,253,97,236,255,206,229,202,0,0,0,92,239,82,60,231,228,0,0,0,0,0,0,0,0,0,0,0,0,49,238,142,153,255,255,197,228,253,98,228,255,196,228,255,33,220,255,98,228,253,193,244,255,238,237,200,0,0,0,92,239,136,219,222,223,0,0,0,0,0,0,0,0,0,0,0,0,146,238,145,219,255,255,36,237,253,35,237,255,69,237,255,161,236,255,194,236,253,33,253,255,13,238,202,255,255,93,125,239,245,150,181,167,0,0,0,0,0,0,0,0,0,0,0,0,113,238,149,50,255,255,131,245,252,33,245,255,99,245,255,132,245,255,33,245,254,96,253,255,79,238,242,94,231,255,150,181,217,239,123,38,0,0,0,0,0,0,0,0,0,0,0,0,218,222,158,197,245,255,128,253,254,161,253,255,228,253,255,161,253,255,128,253,255,162,253,255,86,214,212,212,148,99,140,115,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,239,219,26,231,254,136,229,245,130,245,252,129,245,253,98,245,253,134,229,246,85,214,237,61,231,208,0,0,0,0,0,0,85,173,3,0,0,0,0,0,0,0,0,0,0,0,0,251,222,163,28,223,252,252,222,255,121,214,247,87,214,249,120,214,243,187,214,252,28,223,255,251,222,195,251,222,15,239,255,2,255,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,231,71,251,230,162,252,222,207,252,222,226,252,222,221,251,222,194,27,231,118,125,239,16,0,0,0,255,255,1,0,0,0,0,0,0,0,0,0};
|
||||
static const lv_img_dsc_t d_1f37a = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f37a), e_1f37a };
|
||||
static const uint8_t e_1f37b[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,2,0,0,0,68,249,65,100,249,52,101,249,94,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,239,251,2,115,254,5,197,249,48,100,249,86,36,249,142,64,248,61,114,255,14,89,255,119,154,255,137,186,255,126,56,255,66,0,0,0,85,253,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,32,2,249,106,102,250,63,220,255,165,221,255,233,255,255,255,255,255,255,255,255,255,155,255,237,192,252,5,0,0,0,255,255,2,255,255,2,255,255,2,89,255,39,187,255,166,252,255,158,253,255,193,187,255,219,254,255,255,255,255,255,255,255,252,255,255,249,221,255,251,123,255,215,254,255,24,255,255,19,0,0,0,255,255,5,0,0,0,88,255,115,255,255,255,255,255,255,255,255,255,255,255,255,189,255,252,156,255,254,189,255,255,121,255,254,212,254,255,216,238,224,187,214,200,186,214,215,125,239,50,0,0,0,255,255,42,91,247,117,188,255,250,154,255,249,122,255,253,255,255,253,255,255,255,255,255,255,121,255,255,49,246,254,232,228,254,76,229,255,30,215,94,121,214,105,60,231,158,158,247,100,251,222,234,252,222,221,240,237,252,101,228,255,199,228,255,122,255,255,23,255,255,122,255,253,174,237,254,100,220,255,2,220,253,165,236,255,26,239,140,255,255,10,60,231,189,60,231,185,85,181,43,191,247,165,106,245,255,192,219,252,7,229,255,214,246,255,2,220,255,142,229,239,35,220,253,67,220,255,131,228,254,224,244,255,113,238,212,127,231,143,121,214,209,93,239,174,0,0,0,156,247,209,70,245,255,2,237,253,112,246,255,48,246,255,224,219,252,115,238,237,80,246,255,225,236,255,165,245,254,96,253,254,73,254,245,187,214,211,51,165,67,218,222,202,61,223,142,90,247,238,98,253,255,131,245,254,199,245,255,68,245,255,6,237,247,213,238,228,51,255,255,195,253,255,226,253,255,160,253,255,199,237,251,220,214,183,255,255,5,84,173,70,123,206,189,142,254,250,96,253,255,193,253,255,96,253,255,64,253,255,201,237,231,95,231,136,176,246,246,229,253,251,130,245,249,138,221,246,121,214,255,28,223,203,90,239,13,0,0,0,158,247,142,181,238,250,112,238,241,78,238,245,9,246,246,226,253,254,78,246,189,255,231,54,184,222,255,53,214,255,120,214,255,188,214,251,61,231,186,93,247,36,0,0,0,121,206,5,154,214,157,57,198,240,58,198,251,90,198,251,123,206,247,185,222,255,251,230,188,255,231,10,61,231,119,62,231,158,61,231,127,124,247,62,0,0,0,0,0,0,255,255,1,0,0,0,170,82,6,181,181,79,182,189,140,214,189,176,214,189,195,248,189,197,154,214,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,2,0,0,0};
|
||||
static const lv_img_dsc_t d_1f37b = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f37b), e_1f37b };
|
||||
typedef struct { uint32_t cp; const lv_img_dsc_t* dsc; } EmojiGlyph;
|
||||
static const EmojiGlyph kGlyphs[] = {
|
||||
{ 0x200Du, &d_zero },
|
||||
@@ -554,6 +558,8 @@ static const EmojiGlyph kGlyphs[] = {
|
||||
{ 0x1F340u, &d_1f340 },
|
||||
{ 0x1F341u, &d_1f341 },
|
||||
{ 0x1F342u, &d_1f342 },
|
||||
{ 0x1F37Au, &d_1f37a },
|
||||
{ 0x1F37Bu, &d_1f37b },
|
||||
{ 0x1F381u, &d_1f381 },
|
||||
{ 0x1F388u, &d_1f388 },
|
||||
{ 0x1F389u, &d_1f389 },
|
||||
@@ -761,7 +767,7 @@ static const EmojiGlyph kGlyphs[] = {
|
||||
{ 0x1F9D0u, &d_1f9d0 },
|
||||
{ 0x1F9E1u, &d_1f9e1 },
|
||||
};
|
||||
const uint16_t kEmojiGlyphCount = 250;
|
||||
const uint16_t kEmojiGlyphCount = 252;
|
||||
|
||||
const lv_img_dsc_t* emojiGlyphLookup(uint32_t cp) {
|
||||
int lo = 0, hi = (int)(sizeof(kGlyphs)/sizeof(kGlyphs[0])) - 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// AUTO-GENERATED by scripts/build/gen-emoji-font.py — do not edit.
|
||||
// 250 Noto colour emoji baked to RGB565+alpha at 16px.
|
||||
// 252 Noto colour emoji baked to RGB565+alpha at 16px.
|
||||
#pragma once
|
||||
#include "lvgl.h"
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 4
|
||||
* Opts: --no-compress --bpp 4 --size 16 --font /tmp/fa/fa-solid-900.ttf -r 0xF007 -r 0xF519 --format lvgl -o src/ui-touch/person_font.c
|
||||
* Opts: --no-compress --bpp 4 --size 16 --font /tmp/fa/fa-solid-900.ttf -r 0xF007 -r 0xF519 -r 0xF0C0 --format lvgl -o src/ui-touch/person_font.c
|
||||
******************************************************************************/
|
||||
|
||||
#include "lvgl.h"
|
||||
@@ -34,6 +34,28 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
|
||||
|
||||
/* U+F0C0 "" */
|
||||
0x0, 0x1c, 0xfc, 0x10, 0x0, 0x0, 0x0, 0x7e,
|
||||
0xe7, 0x0, 0x0, 0xbf, 0xff, 0xb0, 0x0, 0x0,
|
||||
0x3, 0xff, 0xff, 0x30, 0x0, 0xff, 0xff, 0xf0,
|
||||
0x0, 0x0, 0x7, 0xff, 0xff, 0x70, 0x0, 0xbf,
|
||||
0xff, 0xb0, 0x0, 0x0, 0x3, 0xff, 0xff, 0x30,
|
||||
0x0, 0x1c, 0xfc, 0x10, 0x8e, 0xe8, 0x0, 0x7e,
|
||||
0xe7, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff,
|
||||
0x80, 0x0, 0x0, 0x0, 0x6, 0xdf, 0xfd, 0xe,
|
||||
0xff, 0xff, 0xe0, 0xdf, 0xfd, 0x60, 0x6f, 0xff,
|
||||
0xff, 0xe, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xf6,
|
||||
0xdf, 0xff, 0xff, 0x58, 0xff, 0xff, 0x85, 0xff,
|
||||
0xff, 0xfd, 0xdf, 0xff, 0xff, 0xd1, 0x8e, 0xe8,
|
||||
0x1d, 0xff, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x1, 0xae, 0xff, 0xff, 0xea, 0x10, 0x0, 0x0,
|
||||
0x0, 0x0, 0x1e, 0xff, 0xff, 0xff, 0xff, 0xe1,
|
||||
0x0, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfa, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0,
|
||||
0xcf, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x0,
|
||||
|
||||
/* U+F519 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x3e, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
@@ -65,7 +87,8 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 112, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}
|
||||
{.bitmap_index = 112, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 272, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@@ -73,7 +96,7 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x512
|
||||
0x0, 0xb9, 0x512
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@@ -81,7 +104,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 61447, .range_length = 1299, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 2, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 3, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/*******************************************************************************
|
||||
* Size: 14 px
|
||||
* Bpp: 4
|
||||
* Opts: --no-compress --bpp 4 --size 14 --font /tmp/fa/fa-solid-900.ttf -r 0xF007 -r 0xF519 -r 0xF0C0 --format lvgl -o src/ui-touch/person_font14.c
|
||||
******************************************************************************/
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#ifndef PERSON_FONT14
|
||||
#define PERSON_FONT14 1
|
||||
#endif
|
||||
|
||||
#if PERSON_FONT14
|
||||
|
||||
/*-----------------
|
||||
* BITMAPS
|
||||
*----------------*/
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+F007 "" */
|
||||
0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0,
|
||||
0x1b, 0xff, 0xd3, 0x0, 0x0, 0x0, 0xc, 0xff,
|
||||
0xff, 0xe1, 0x0, 0x0, 0x3, 0xff, 0xff, 0xff,
|
||||
0x70, 0x0, 0x0, 0x5f, 0xff, 0xff, 0xf9, 0x0,
|
||||
0x0, 0x2, 0xff, 0xff, 0xff, 0x60, 0x0, 0x0,
|
||||
0x9, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x6,
|
||||
0xcc, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x5, 0xcf, 0xff, 0xfd, 0x70,
|
||||
0x0, 0x9, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x5,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x90, 0xcf, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xf, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xf3, 0x8b, 0xbb, 0xbb, 0xbb, 0xbb,
|
||||
0xba, 0x0,
|
||||
|
||||
/* U+F0C0 "" */
|
||||
0x0, 0x2, 0x20, 0x0, 0x0, 0x0, 0x2, 0x20,
|
||||
0x0, 0x0, 0xaf, 0xf8, 0x0, 0x0, 0x0, 0x9f,
|
||||
0xf9, 0x0, 0x2, 0xff, 0xff, 0x0, 0x0, 0x1,
|
||||
0xff, 0xff, 0x10, 0x2, 0xff, 0xff, 0x0, 0x0,
|
||||
0x1, 0xff, 0xff, 0x10, 0x0, 0x8f, 0xf6, 0x6,
|
||||
0xb9, 0x20, 0x7f, 0xf7, 0x0, 0x0, 0x0, 0x0,
|
||||
0x6f, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x9, 0xef,
|
||||
0xf3, 0xdf, 0xff, 0xf5, 0xaf, 0xfc, 0x40, 0x9f,
|
||||
0xff, 0xf5, 0xcf, 0xff, 0xf4, 0xdf, 0xff, 0xf1,
|
||||
0xef, 0xff, 0xfa, 0x3f, 0xff, 0xb3, 0xff, 0xff,
|
||||
0xf6, 0x68, 0x88, 0x87, 0x12, 0x75, 0x5, 0x88,
|
||||
0x88, 0x72, 0x0, 0x0, 0x0, 0x46, 0x66, 0x62,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x4e, 0xff, 0xff,
|
||||
0xff, 0xa0, 0x0, 0x0, 0x0, 0x1, 0xef, 0xff,
|
||||
0xff, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x5, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x5,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x0,
|
||||
|
||||
/* U+F519 "" */
|
||||
0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20,
|
||||
0x7f, 0x23, 0x10, 0x0, 0x0, 0x2, 0x16, 0xf3,
|
||||
0xcf, 0x1f, 0xa0, 0x4, 0x30, 0xe, 0xc4, 0xf8,
|
||||
0xed, 0x4f, 0x70, 0x9f, 0xf5, 0xb, 0xf1, 0xfa,
|
||||
0xfc, 0x5f, 0x60, 0xcf, 0xf8, 0xa, 0xf1, 0xfb,
|
||||
0xee, 0x3f, 0x90, 0x5f, 0xe2, 0xd, 0xf2, 0xf9,
|
||||
0xaf, 0x2a, 0x70, 0xf, 0xc0, 0xa, 0x76, 0xf6,
|
||||
0x2a, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x3, 0xa0,
|
||||
0x0, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xf, 0xb0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xa, 0x60, 0x0, 0x0, 0x0
|
||||
};
|
||||
|
||||
|
||||
/*---------------------
|
||||
* GLYPH DESCRIPTION
|
||||
*--------------------*/
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 98, .adv_w = 280, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 233, .adv_w = 252, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
* CHARACTER MAPPING
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0xb9, 0x512
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 61447, .range_length = 1299, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 3, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*--------------------
|
||||
* ALL CUSTOM DATA
|
||||
*--------------------*/
|
||||
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
/*Store all the custom data of the font*/
|
||||
static lv_font_fmt_txt_glyph_cache_t cache;
|
||||
#endif
|
||||
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
static const lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#else
|
||||
static lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#endif
|
||||
.glyph_bitmap = glyph_bitmap,
|
||||
.glyph_dsc = glyph_dsc,
|
||||
.cmaps = cmaps,
|
||||
.kern_dsc = NULL,
|
||||
.kern_scale = 0,
|
||||
.cmap_num = 1,
|
||||
.bpp = 4,
|
||||
.kern_classes = 0,
|
||||
.bitmap_format = 0,
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
.cache = &cache
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*-----------------
|
||||
* PUBLIC FONT
|
||||
*----------------*/
|
||||
|
||||
/*Initialize a public general font descriptor*/
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
const lv_font_t person_font14 = {
|
||||
#else
|
||||
lv_font_t person_font14 = {
|
||||
#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*/
|
||||
.line_height = 15, /*The maximum line height required by the font*/
|
||||
.base_line = 2, /*Baseline measured from the bottom of the line*/
|
||||
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
|
||||
.subpx = LV_FONT_SUBPX_NONE,
|
||||
#endif
|
||||
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
|
||||
.underline_position = -1,
|
||||
.underline_thickness = 1,
|
||||
#endif
|
||||
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
|
||||
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
|
||||
.fallback = NULL,
|
||||
#endif
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /*#if PERSON_FONT14*/
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 4
|
||||
* Opts: --no-compress --bpp 4 --size 16 --font /Applications/MeshCore.app/Wrapper/Runner.app/Frameworks/App.framework/flutter_assets/packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf -r 0xF185 -r 0xF186 --format lvgl --lv-font-name sleepicons_font -o src/ui-touch/sleepicons_font.c
|
||||
******************************************************************************/
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#ifndef SLEEPICONS_FONT
|
||||
#define SLEEPICONS_FONT 1
|
||||
#endif
|
||||
|
||||
#if SLEEPICONS_FONT
|
||||
|
||||
/*-----------------
|
||||
* BITMAPS
|
||||
*----------------*/
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+F185 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x8, 0xa1, 0x0, 0x1a, 0x80,
|
||||
0x0, 0x0, 0x0, 0x0, 0xc, 0xfe, 0x55, 0xef,
|
||||
0xc0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xff,
|
||||
0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x4f, 0xff,
|
||||
0xff, 0xff, 0xf4, 0x0, 0x0, 0x8, 0xcf, 0xff,
|
||||
0xd5, 0x11, 0x5d, 0xff, 0xfc, 0x80, 0xa, 0xff,
|
||||
0xfd, 0x18, 0xee, 0x81, 0xdf, 0xff, 0xa0, 0x1,
|
||||
0xef, 0xf5, 0x8f, 0xff, 0xf8, 0x5f, 0xfe, 0x10,
|
||||
0x0, 0x5f, 0xf1, 0xef, 0xff, 0xfe, 0x1f, 0xf5,
|
||||
0x0, 0x0, 0x5f, 0xf1, 0xef, 0xff, 0xfe, 0x1f,
|
||||
0xf5, 0x0, 0x1, 0xef, 0xf5, 0x8f, 0xff, 0xf8,
|
||||
0x5f, 0xfe, 0x10, 0xa, 0xff, 0xfd, 0x18, 0xee,
|
||||
0x81, 0xdf, 0xff, 0xa0, 0x8, 0xcf, 0xff, 0xd5,
|
||||
0x11, 0x5d, 0xff, 0xfc, 0x80, 0x0, 0x0, 0x4f,
|
||||
0xff, 0xff, 0xff, 0xf4, 0x0, 0x0, 0x0, 0x0,
|
||||
0xf, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0,
|
||||
0x0, 0xc, 0xfe, 0x55, 0xef, 0xc0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x8, 0xa1, 0x0, 0x1a, 0x80, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0,
|
||||
|
||||
/* U+F186 "" */
|
||||
0x0, 0x1, 0x7c, 0xee, 0x0, 0x0, 0x0, 0x4,
|
||||
0xef, 0xfe, 0x30, 0x0, 0x0, 0x5, 0xff, 0xff,
|
||||
0x20, 0x0, 0x0, 0x1, 0xff, 0xff, 0x80, 0x0,
|
||||
0x0, 0x0, 0x7f, 0xff, 0xf2, 0x0, 0x0, 0x0,
|
||||
0xc, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0xef,
|
||||
0xff, 0xf1, 0x0, 0x0, 0x0, 0xe, 0xff, 0xff,
|
||||
0x40, 0x0, 0x0, 0x0, 0xcf, 0xff, 0xfc, 0x0,
|
||||
0x0, 0x0, 0x7, 0xff, 0xff, 0xf8, 0x0, 0x0,
|
||||
0x0, 0x1e, 0xff, 0xff, 0xfa, 0x20, 0x0, 0x0,
|
||||
0x4f, 0xff, 0xff, 0xff, 0xdc, 0xb0, 0x0, 0x4e,
|
||||
0xff, 0xff, 0xff, 0xf5, 0x0, 0x0, 0x17, 0xce,
|
||||
0xfc, 0x81, 0x0
|
||||
};
|
||||
|
||||
|
||||
/*---------------------
|
||||
* GLYPH DESCRIPTION
|
||||
*--------------------*/
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 256, .box_w = 18, .box_h = 18, .ofs_x = -1, .ofs_y = -3},
|
||||
{.bitmap_index = 162, .adv_w = 192, .box_w = 13, .box_h = 14, .ofs_x = 0, .ofs_y = -1}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
* CHARACTER MAPPING
|
||||
*--------------------*/
|
||||
|
||||
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 61829, .range_length = 2, .glyph_id_start = 1,
|
||||
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*--------------------
|
||||
* ALL CUSTOM DATA
|
||||
*--------------------*/
|
||||
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
/*Store all the custom data of the font*/
|
||||
static lv_font_fmt_txt_glyph_cache_t cache;
|
||||
#endif
|
||||
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
static const lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#else
|
||||
static lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#endif
|
||||
.glyph_bitmap = glyph_bitmap,
|
||||
.glyph_dsc = glyph_dsc,
|
||||
.cmaps = cmaps,
|
||||
.kern_dsc = NULL,
|
||||
.kern_scale = 0,
|
||||
.cmap_num = 1,
|
||||
.bpp = 4,
|
||||
.kern_classes = 0,
|
||||
.bitmap_format = 0,
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
.cache = &cache
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*-----------------
|
||||
* PUBLIC FONT
|
||||
*----------------*/
|
||||
|
||||
/*Initialize a public general font descriptor*/
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
const lv_font_t sleepicons_font = {
|
||||
#else
|
||||
lv_font_t sleepicons_font = {
|
||||
#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*/
|
||||
.line_height = 18, /*The maximum line height required by the font*/
|
||||
.base_line = 3, /*Baseline measured from the bottom of the line*/
|
||||
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
|
||||
.subpx = LV_FONT_SUBPX_NONE,
|
||||
#endif
|
||||
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
|
||||
.underline_position = -1,
|
||||
.underline_thickness = 1,
|
||||
#endif
|
||||
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
|
||||
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
|
||||
.fallback = NULL,
|
||||
#endif
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /*#if SLEEPICONS_FONT*/
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 4
|
||||
* Opts: --no-compress --bpp 4 --size 16 --font /Users/dan.vybiral/.claude/jobs/dadc5d4b/tmp/fa-solid-900.ttf -r 0xF002 --format lvgl --lv-font-name zoom_font -o src/ui-touch/zoom_font.c
|
||||
******************************************************************************/
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#ifndef ZOOM_FONT
|
||||
#define ZOOM_FONT 1
|
||||
#endif
|
||||
|
||||
#if ZOOM_FONT
|
||||
|
||||
/*-----------------
|
||||
* BITMAPS
|
||||
*----------------*/
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+F002 "" */
|
||||
0x0, 0x3, 0xae, 0xfe, 0xa3, 0x0, 0x0, 0x0,
|
||||
0x0, 0x9f, 0xff, 0xff, 0xff, 0x90, 0x0, 0x0,
|
||||
0x9, 0xff, 0x82, 0x2, 0x8f, 0xf9, 0x0, 0x0,
|
||||
0x3f, 0xf3, 0x0, 0x0, 0x3, 0xff, 0x30, 0x0,
|
||||
0xaf, 0x80, 0x0, 0x0, 0x0, 0x8f, 0xa0, 0x0,
|
||||
0xef, 0x20, 0x0, 0x0, 0x0, 0x2f, 0xe0, 0x0,
|
||||
0xff, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0,
|
||||
0xef, 0x20, 0x0, 0x0, 0x0, 0x2f, 0xe0, 0x0,
|
||||
0xaf, 0x80, 0x0, 0x0, 0x0, 0x8f, 0xa0, 0x0,
|
||||
0x3f, 0xf3, 0x0, 0x0, 0x3, 0xff, 0x40, 0x0,
|
||||
0x9, 0xff, 0x82, 0x2, 0x8f, 0xfe, 0x10, 0x0,
|
||||
0x0, 0x9f, 0xff, 0xff, 0xff, 0xef, 0xd1, 0x0,
|
||||
0x0, 0x3, 0xae, 0xfe, 0xa4, 0x1d, 0xfd, 0x10,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xdf, 0xd1,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, 0xfc,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xcc
|
||||
};
|
||||
|
||||
|
||||
/*---------------------
|
||||
* GLYPH DESCRIPTION
|
||||
*--------------------*/
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
* CHARACTER MAPPING
|
||||
*--------------------*/
|
||||
|
||||
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 61442, .range_length = 1, .glyph_id_start = 1,
|
||||
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*--------------------
|
||||
* ALL CUSTOM DATA
|
||||
*--------------------*/
|
||||
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
/*Store all the custom data of the font*/
|
||||
static lv_font_fmt_txt_glyph_cache_t cache;
|
||||
#endif
|
||||
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
static const lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#else
|
||||
static lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#endif
|
||||
.glyph_bitmap = glyph_bitmap,
|
||||
.glyph_dsc = glyph_dsc,
|
||||
.cmaps = cmaps,
|
||||
.kern_dsc = NULL,
|
||||
.kern_scale = 0,
|
||||
.cmap_num = 1,
|
||||
.bpp = 4,
|
||||
.kern_classes = 0,
|
||||
.bitmap_format = 0,
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
.cache = &cache
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*-----------------
|
||||
* PUBLIC FONT
|
||||
*----------------*/
|
||||
|
||||
/*Initialize a public general font descriptor*/
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
const lv_font_t zoom_font = {
|
||||
#else
|
||||
lv_font_t zoom_font = {
|
||||
#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*/
|
||||
.line_height = 16, /*The maximum line height required by the font*/
|
||||
.base_line = 2, /*Baseline measured from the bottom of the line*/
|
||||
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
|
||||
.subpx = LV_FONT_SUBPX_NONE,
|
||||
#endif
|
||||
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
|
||||
.underline_position = -1,
|
||||
.underline_thickness = 1,
|
||||
#endif
|
||||
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
|
||||
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
|
||||
.fallback = NULL,
|
||||
#endif
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /*#if ZOOM_FONT*/
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# project-local ESP-IDF + tools (bootstrapped by `make prepare`)
|
||||
esp-idf/
|
||||
esp-idf-tools/
|
||||
# build output + component manager cache
|
||||
build/
|
||||
managed_components/
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
sdkconfig_tanmatsu
|
||||
.submodules_update_done
|
||||
|
||||
# vendored dependency payloads (regenerate with fetch-deps.sh)
|
||||
components/ardlib_*/
|
||||
components/meshcore/core/
|
||||
components/ardlibs/upstream/
|
||||
components/lvgl/upstream/
|
||||
@@ -0,0 +1,3 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(application)
|
||||
@@ -0,0 +1,264 @@
|
||||
PORT ?= /dev/ttyACM0
|
||||
|
||||
IDF_PATH ?= $(shell cat .IDF_PATH 2>/dev/null || echo `pwd`/esp-idf)
|
||||
IDF_TOOLS_PATH ?= $(shell cat .IDF_TOOLS_PATH 2>/dev/null || echo `pwd`/esp-idf-tools)
|
||||
IDF_BRANCH ?= v5.5.1
|
||||
#IDF_COMMIT ?= aaebc374676621980878789c49d239232ea714c5
|
||||
IDF_EXPORT_QUIET ?= 1
|
||||
IDF_GITHUB_ASSETS ?= dl.espressif.com/github_assets
|
||||
MAKEFLAGS += --silent
|
||||
|
||||
SHELL := /usr/bin/env bash
|
||||
|
||||
DEVICE ?= tanmatsu
|
||||
BUILD ?= build/$(DEVICE)
|
||||
FAT ?= 0
|
||||
SDKCONFIG_DEFAULTS ?= sdkconfigs/general;sdkconfigs/$(DEVICE)
|
||||
SDKCONFIG ?= sdkconfig_$(DEVICE)
|
||||
|
||||
####
|
||||
|
||||
# Set IDF_TARGET based on device name
|
||||
|
||||
ifeq ($(DEVICE), tanmatsu)
|
||||
IDF_TARGET ?= esp32p4
|
||||
else ifeq ($(DEVICE), konsool)
|
||||
IDF_TARGET ?= esp32p4
|
||||
else ifeq ($(DEVICE), esp32-p4-function-ev-board)
|
||||
IDF_TARGET ?= esp32p4
|
||||
else ifeq ($(DEVICE), mch2022)
|
||||
IDF_TARGET ?= esp32
|
||||
else ifeq ($(DEVICE), kami)
|
||||
IDF_TARGET ?= esp32
|
||||
else ifeq ($(DEVICE), hackerhotel-2024)
|
||||
IDF_TARGET ?= esp32c6
|
||||
else ifeq ($(DEVICE), heltecv3)
|
||||
IDF_TARGET ?= esp32s3
|
||||
else
|
||||
$(warning "Unknown device, defaulting to ESP32 $(DEVICE)")
|
||||
IDF_TARGET ?= esp32
|
||||
endif
|
||||
|
||||
IDF_PARAMS := -B $(BUILD) build -DDEVICE=$(DEVICE) -DSDKCONFIG_DEFAULTS="$(SDKCONFIG_DEFAULTS)" -DSDKCONFIG=$(SDKCONFIG) -DIDF_TARGET=$(IDF_TARGET) -DFAT=$(FAT)
|
||||
|
||||
#####
|
||||
|
||||
export IDF_TOOLS_PATH
|
||||
export IDF_GITHUB_ASSETS
|
||||
|
||||
# General targets
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
# Badgelink
|
||||
.PHONY: badgelink
|
||||
badgelink:
|
||||
rm -rf badgelink
|
||||
git clone https://github.com/badgeteam/esp32-component-badgelink.git badgelink
|
||||
cd badgelink/tools; ./install.sh
|
||||
|
||||
.PHONY: install
|
||||
install: build
|
||||
install:
|
||||
cd badgelink/tools; ./badgelink.sh appfs upload application "template application" 0 ../../$(BUILD)/application.bin
|
||||
|
||||
.PHONY: run
|
||||
run:
|
||||
cd badgelink/tools; ./badgelink.sh start application
|
||||
|
||||
# Preparation
|
||||
|
||||
.PHONY: prepare
|
||||
prepare: submodules sdk
|
||||
|
||||
.PHONY: submodules
|
||||
submodules:
|
||||
if [ ! -f .submodules_update_done ]; then \
|
||||
echo "Updating submodules"; \
|
||||
git submodule update --init --recursive; \
|
||||
touch .submodules_update_done; \
|
||||
fi
|
||||
|
||||
.PHONY: sdk
|
||||
sdk:
|
||||
if test -d "$(IDF_PATH)"; then echo -e "ESP-IDF target folder exists!\r\nPlease remove the folder or un-set the environment variable."; exit 1; fi
|
||||
if test -d "$(IDF_TOOLS_PATH)"; then echo -e "ESP-IDF tools target folder exists!\r\nPlease remove the folder or un-set the environment variable."; exit 1; fi
|
||||
git clone --recursive --branch "$(IDF_BRANCH)" https://github.com/espressif/esp-idf.git "$(IDF_PATH)" --depth=1 --shallow-submodules
|
||||
# cd "$(IDF_PATH)"; git fetch origin "$(IDF_COMMIT)" --recurse-submodules || true
|
||||
# cd "$(IDF_PATH)"; git checkout "$(IDF_COMMIT)"
|
||||
cd "$(IDF_PATH)"; git submodule update --init --recursive
|
||||
cd "$(IDF_PATH)"; bash install.sh all
|
||||
|
||||
.PHONY: reinstallsdk
|
||||
reinstallsdk:
|
||||
cd "$(IDF_PATH)"; bash install.sh all
|
||||
|
||||
.PHONY: removesdk
|
||||
removesdk:
|
||||
rm -rf "$(IDF_PATH)"
|
||||
rm -rf "$(IDF_TOOLS_PATH)"
|
||||
|
||||
.PHONY: refreshsdk
|
||||
refreshsdk: removesdk sdk
|
||||
|
||||
.PHONY: menuconfig
|
||||
menuconfig:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py menuconfig -DDEVICE=$(DEVICE) -DSDKCONFIG_DEFAULTS="$(SDKCONFIG_DEFAULTS)" -DSDKCONFIG=$(SDKCONFIG) -DIDF_TARGET=$(IDF_TARGET)
|
||||
|
||||
# Cleaning
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILD)
|
||||
rm -f .submodules_update_done
|
||||
|
||||
.PHONY: fullclean
|
||||
fullclean: clean
|
||||
rm -f sdkconfig
|
||||
rm -f sdkconfig.old
|
||||
rm -f sdkconfig.ci
|
||||
rm -f sdkconfig.defaults
|
||||
|
||||
# Check if build environment is set up correctly
|
||||
.PHONY: checkbuildenv
|
||||
checkbuildenv:
|
||||
if [ -z "$(IDF_PATH)" ]; then echo "IDF_PATH is not set!"; exit 1; fi
|
||||
if [ -z "$(IDF_TOOLS_PATH)" ]; then echo "IDF_TOOLS_PATH is not set!"; exit 1; fi
|
||||
# Check if the IDF commit id the one we need
|
||||
#if [ -d "$(IDF_PATH)" ]; then \
|
||||
# if [ "$(IDF_COMMIT)" != "$(shell cd $(IDF_PATH); git rev-parse HEAD)" ]; then \
|
||||
# echo "ESP-IDF commit id does not match! Expected '$(IDF_COMMIT)' got '$(shell git rev-parse HEAD)'"; \
|
||||
# echo "Run $ make refreshsdk"; \
|
||||
# echo "To update the ESP-IDF to the correct commit id"; \
|
||||
# echo "Or set the IDF_COMMIT variable in the Makefile to the correct commit id"; \
|
||||
# exit 1; \
|
||||
# fi; \
|
||||
#fi
|
||||
|
||||
# Building
|
||||
|
||||
.PHONY: build
|
||||
build: icons checkbuildenv submodules
|
||||
source "$(IDF_PATH)/export.sh" >/dev/null && idf.py $(IDF_PARAMS)
|
||||
|
||||
# Hardware
|
||||
|
||||
.PHONY: flash
|
||||
flash: build
|
||||
source "$(IDF_PATH)/export.sh" && \
|
||||
idf.py $(IDF_PARAMS) flash -p $(PORT)
|
||||
|
||||
.PHONY: flashmonitor
|
||||
flashmonitor: build
|
||||
source "$(IDF_PATH)/export.sh" && \
|
||||
idf.py $(IDF_PARAMS) flash -p $(PORT) monitor
|
||||
|
||||
.PHONY: prepappfs
|
||||
prepappfs:
|
||||
source "$(IDF_PATH)/export.sh" && \
|
||||
python3 managed_components/badgeteam__appfs/tools/appfs_generate.py \
|
||||
8192000 \
|
||||
appfs.bin
|
||||
|
||||
.PHONY: appfs
|
||||
appfs:
|
||||
source "$(IDF_PATH)/export.sh" && \
|
||||
esptool.py \
|
||||
-b 921600 --port $(PORT) \
|
||||
write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB \
|
||||
0x330000 appfs.bin
|
||||
|
||||
.PHONY: erase
|
||||
erase:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) erase-flash -p $(PORT)
|
||||
|
||||
.PHONY: monitor
|
||||
monitor:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) monitor -p $(PORT)
|
||||
|
||||
.PHONY: openocd
|
||||
openocd:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) openocd
|
||||
|
||||
.PHONY: openocdftdi
|
||||
openocdftdi:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) openocd --openocd-commands "-f board/esp32p4-ftdi.cfg"
|
||||
|
||||
.PHONY: gdb
|
||||
gdb:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) gdb
|
||||
|
||||
.PHONY: gdbgui
|
||||
gdbgui:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) gdbgui
|
||||
|
||||
.PHONY: gdbtui
|
||||
gdbtui:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) gdbtui
|
||||
|
||||
# Tools
|
||||
|
||||
.PHONY: size
|
||||
size:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) size
|
||||
|
||||
.PHONY: size-components
|
||||
size-components:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) size-components
|
||||
|
||||
.PHONY: size-files
|
||||
size-files:
|
||||
source "$(IDF_PATH)/export.sh" && idf.py $(IDF_PARAMS) size-files
|
||||
|
||||
.PHONY: efuse
|
||||
efuse:
|
||||
$(IDF_PATH)/components/efuse/efuse_table_gen.py --idf_target esp32p4 $(IDF_PATH)/components/efuse/esp32p4/esp_efuse_table.csv main/esp_efuse_custom_table.csv
|
||||
|
||||
# Formatting
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
find main/ -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' | xargs clang-format -i
|
||||
|
||||
# Re-compile protobuf files
|
||||
# If you are an end user, you do not need to run this;
|
||||
# the output files are already there in the repository.
|
||||
|
||||
.PHONY: compile-protobuf
|
||||
compile-protobuf:
|
||||
protoc --pyi_out=tools --python_out=tools badgelink.proto
|
||||
python3 main/badgelink/nanopb/generator/nanopb_generator.py -D main/badgelink -f badgelink.options badgelink.proto
|
||||
|
||||
# Take all svg files from main/static/icons and put them in main/fat/icons as png using tools/connvert.sh
|
||||
ICONS_SRC := $(wildcard main/static/icons/*.svg)
|
||||
ICONS_DST := $(patsubst main/static/icons/%.svg,main/fat/icons/%.png,$(ICONS_SRC))
|
||||
|
||||
.PHONY: icons
|
||||
icons: $(ICONS_DST)
|
||||
|
||||
main/fat/icons/%.png: main/static/icons/%.svg
|
||||
mkdir -p main/fat/icons
|
||||
tools/convert.sh $< $@
|
||||
|
||||
# Build all targets
|
||||
.PHONY: buildall
|
||||
buildall:
|
||||
$(MAKE) build DEVICE=tanmatsu
|
||||
$(MAKE) build DEVICE=konsool
|
||||
$(MAKE) build DEVICE=hackerhotel-2026
|
||||
$(MAKE) build DEVICE=esp32-p4-function-ev-board
|
||||
$(MAKE) build DEVICE=mch2022
|
||||
$(MAKE) build DEVICE=hackerhotel-2024
|
||||
|
||||
# Flash all: assumes Tanmatsu P4 is /dev/ttyACM0, C6 is /dev/ttyACM1 and MCH2022 badge is /dev/ttyACM2
|
||||
.PHONY: flashall
|
||||
flashall:
|
||||
$(MAKE) flash DEVICE=tanmatsu PORT=/dev/ttyACM0
|
||||
$(MAKE) flash DEVICE=mch2022 PORT=/dev/ttyACM2
|
||||
|
||||
# Vscode
|
||||
.PHONY: vscode
|
||||
vscode:
|
||||
rm -rf .vscode
|
||||
cp -r .vscode.template .vscode
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# Reusable wadamesh/Tanmatsu build wrapper: activates the project-local ESP-IDF 5.5.1
|
||||
# and runs idf.py with our device/target/sdkconfig params.
|
||||
# ./build.sh build # compile
|
||||
# ./build.sh flash -p ... # flash over USB (dev)
|
||||
# ./build.sh menuconfig
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
export IDF_TOOLS_PATH="$PWD/esp-idf-tools"
|
||||
# shellcheck disable=SC1091
|
||||
source esp-idf/export.sh >/dev/null 2>&1
|
||||
|
||||
# --- Build-time patch: arduino-esp32 esp-hosted WiFi init (Tanmatsu) ------------------------------
|
||||
# esp_hosted is force-initialised by an unconditional constructor in the esp_hosted component BEFORE
|
||||
# app_main, so arduino's WiFi.mode -> hostedInit() -> esp_hosted_sdio_set_config() returns
|
||||
# ESP_ERR_NOT_ALLOWED ("already configured"). This arduino build treats that as fatal and never runs
|
||||
# esp_wifi_init(), leaving WiFi at WIFI_NOT_INIT. Apply the fix the arduino source itself flags
|
||||
# ("uncomment when second init is fixed"): also accept ESP_ERR_NOT_ALLOWED. Idempotent + re-applies
|
||||
# after a dependency re-fetch. This patches a BUILD DEPENDENCY on this machine only — it compiles into
|
||||
# our app .bin and never touches the device's own firmware. (On a brand-new checkout managed_components
|
||||
# doesn't exist yet, so the very first build ships unpatched; a second build picks it up.)
|
||||
HOSTED_C="managed_components/espressif__arduino-esp32/cores/esp32/esp32-hal-hosted.c"
|
||||
if [ -f "$HOSTED_C" ] && grep -q 'if (err != ESP_OK) { *//&& err != ESP_ERR_NOT_ALLOWED' "$HOSTED_C"; then
|
||||
sed -i '' 's|if (err != ESP_OK) { *//&& err != ESP_ERR_NOT_ALLOWED.*|if (err != ESP_OK \&\& err != ESP_ERR_NOT_ALLOWED) { // wadamesh: tolerate esp_hosted pre-init|' "$HOSTED_C"
|
||||
echo "[build.sh] patched arduino esp32-hal-hosted.c (esp-hosted NOT_ALLOWED tolerance for WiFi)"
|
||||
fi
|
||||
|
||||
# --- Build-time patch: arduino-esp32 bundled BLE lib vs IDF 5.5 NimBLE ----------------------------
|
||||
# With BLE enabled (NimBLE), arduino-esp32's libraries/BLE compiles its NimBLE path, which calls
|
||||
# ble_gap_read_local_irk() — renamed/removed in IDF 5.5.1's NimBLE. We don't use arduino's BLE (the
|
||||
# companion uses esp-nimble-cpp's SerialBLEInterface), and getLocalIRK() is never called, so stub the
|
||||
# call so the file compiles. Idempotent.
|
||||
BLEDEV_CPP="managed_components/espressif__arduino-esp32/libraries/BLE/src/BLEDevice.cpp"
|
||||
if [ -f "$BLEDEV_CPP" ] && grep -q 'int rc = ble_gap_read_local_irk(irk);' "$BLEDEV_CPP"; then
|
||||
sed -i '' 's|int rc = ble_gap_read_local_irk(irk);|int rc = 0; (void)irk; // wadamesh: arduino BLE unused; symbol renamed in IDF 5.5 NimBLE|' "$BLEDEV_CPP"
|
||||
echo "[build.sh] patched arduino BLEDevice.cpp (stub renamed ble_gap_read_local_irk)"
|
||||
fi
|
||||
|
||||
exec idf.py -B build/tanmatsu \
|
||||
-DDEVICE=tanmatsu \
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfigs/general;sdkconfigs/tanmatsu;sdkconfigs/wadamesh" \
|
||||
-DIDF_TARGET=esp32p4 "$@"
|
||||
@@ -0,0 +1,19 @@
|
||||
# ALL vendored Arduino libraries compiled as ONE IDF component, so their cross-includes
|
||||
# resolve without per-lib REQUIRES (e.g. RTClib -> Adafruit_I2CDevice.h from Adafruit BusIO).
|
||||
# Payload under upstream/<lib>/ is gitignored (populated by fetch-deps.sh); this wrapper is committed.
|
||||
file(GLOB _libs LIST_DIRECTORIES true RELATIVE "${CMAKE_CURRENT_LIST_DIR}" "${CMAKE_CURRENT_LIST_DIR}/upstream/*")
|
||||
set(_inc "")
|
||||
foreach(_l ${_libs})
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/${_l}")
|
||||
list(APPEND _inc "${_l}")
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/${_l}/src")
|
||||
list(APPEND _inc "${_l}/src")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
file(GLOB_RECURSE _srcs RELATIVE "${CMAKE_CURRENT_LIST_DIR}"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/upstream/*.cpp" "${CMAKE_CURRENT_LIST_DIR}/upstream/*.c")
|
||||
list(FILTER _srcs EXCLUDE REGEX "/(examples|test|tests|extras)/|catch|/test[-_]") # drop bundled tests (e.g. base64's Catch)
|
||||
idf_component_register(SRCS ${_srcs} INCLUDE_DIRS ${_inc} REQUIRES espressif__arduino-esp32 nvs_flash)
|
||||
# Pure third-party Arduino libs — silence warnings (and never -Werror-fail) on code we don't own.
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -w)
|
||||
@@ -0,0 +1,12 @@
|
||||
# OVERRIDE: replace the registry espressif/esp_hosted with badge.team's Tanmatsu fork.
|
||||
#
|
||||
# arduino-esp32 and espressif/esp_wifi_remote both pull espressif/esp_hosted, whose C6 SDIO
|
||||
# host driver (port_esp_hosted_host_sdio.c, whole-archive) duplicates the one already provided
|
||||
# by nicolaielectronics/esp-hosted-tanmatsu — and only the fork is wired to the Tanmatsu's real
|
||||
# SDIO pins. Two whole-archive copies => "multiple definition" at link.
|
||||
#
|
||||
# IDF gives a local component precedence over a managed one of the same name, so this empty
|
||||
# forwarder (component name "esp_hosted") shadows espressif__esp_hosted and re-exports the
|
||||
# fork's headers + symbols. Result: a single esp-hosted (the hardware-correct one) that both
|
||||
# arduino's WiFi and badge.team's stack link against. Keeps our shared src/ WiFi.h code intact.
|
||||
idf_component_register(REQUIRES esp-hosted-tanmatsu)
|
||||
@@ -0,0 +1,14 @@
|
||||
# LVGL 8.4 as an IDF component. Payload (upstream/) vendored by fetch-deps.sh (gitignored);
|
||||
# the config is the repo's own include/lv_conf.h (same one the S3 touch build uses).
|
||||
file(GLOB_RECURSE LVGL_SRCS "${CMAKE_CURRENT_LIST_DIR}/upstream/src/*.c")
|
||||
list(FILTER LVGL_SRCS EXCLUDE REGEX "/(examples|demos)/")
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${LVGL_SRCS}
|
||||
INCLUDE_DIRS "upstream" "${CMAKE_CURRENT_LIST_DIR}/../../../include"
|
||||
REQUIRES espressif__arduino-esp32
|
||||
)
|
||||
# LV_CONF_INCLUDE_SIMPLE → lvgl does #include "lv_conf.h" (found via the repo include/ above).
|
||||
# LV_INDEV_DEF_SCROLL_THROW matches the S3 build (-D, since lv_conf leaves it unset).
|
||||
target_compile_definitions(${COMPONENT_LIB} PUBLIC LV_CONF_INCLUDE_SIMPLE=1 LV_INDEV_DEF_SCROLL_THROW=7)
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -w)
|
||||
@@ -0,0 +1,36 @@
|
||||
# MeshCore core as an IDF component. Payload (core/src, core/variants) is vendored by
|
||||
# fetch-deps.sh and gitignored; this wrapper is committed.
|
||||
#
|
||||
# Source selection mirrors the monorepo's build_as_lib.py for an ESP32 + MC_VENDORED_TOUCH_APP
|
||||
# build, MINUS the bits the Tanmatsu replaces:
|
||||
# - helpers/radiolib/* -> we use variants/tanmatsu/TanmatsuLoraRadio (no RadioLib)
|
||||
# - *Board.cpp -> the S3/other board classes; Tanmatsu uses variants/tanmatsu/TanmatsuBoard
|
||||
# - helpers/ui/*Display* -> S3 panel drivers; we render via LVGL -> badge-bsp
|
||||
# - helpers/sensors/* -> telemetry; deferred (re-add with the sensor libs later)
|
||||
# - the MC_VENDORED_TOUCH_APP files -> our own src/ ships them (avoid duplicate symbols)
|
||||
set(CORE "core/src")
|
||||
file(GLOB_RECURSE CORE_SRCS "${CMAKE_CURRENT_LIST_DIR}/${CORE}/*.cpp")
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "/helpers/radiolib/")
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "/helpers/sensors/")
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "(NRF52Board|TBeamBoard)\\.cpp$") # keep ESP32Board (base TanmatsuBoard extends); drop nRF52 + the RadioLib S3 board
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "/helpers/ui/.*(Display|OLED|TFT|Epaper|EInk|GxEPD|U8g2|SH1106|SSD1306|ST77|ILI9|Adafruit).*\\.cpp$")
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "(LvglPsramAlloc|TouchDiagTrace)\\.cpp$")
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "/helpers/esp32/(MultiTransportCompanionInterface|SdNvsPrefs|TCPCompanionServer|TouchPrefsStore|WifiRuntimeStore|WebSocketCompanionServer)\\.cpp$")
|
||||
# other platforms' helpers (glob is recursive; build_as_lib.py only takes the current platform):
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "/helpers/(nrf52|stm32|rp2040)/")
|
||||
list(FILTER CORE_SRCS EXCLUDE REGEX "/ESPNOWRadio\\.cpp$") # ESP-NOW radio — we use the LoRa bridge
|
||||
# SerialBLEInterface.cpp IS compiled now (BLE over the C6, ported to esp-nimble-cpp 2.x).
|
||||
|
||||
# Core's bundled ed25519 C implementation (Identity.cpp does #include <ed_25519.h>);
|
||||
# PlatformIO auto-compiles it as a nested lib, IDF needs it added explicitly.
|
||||
file(GLOB ED25519_SRCS "${CMAKE_CURRENT_LIST_DIR}/core/lib/ed25519/*.c")
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${CORE_SRCS} ${ED25519_SRCS}
|
||||
INCLUDE_DIRS "${CORE}" "core/lib/ed25519"
|
||||
REQUIRES espressif__arduino-esp32 ardlibs nvs_flash efuse h2zero__esp-nimble-cpp bt
|
||||
)
|
||||
# Vendored core — builds clean under PlatformIO's laxer flags; don't let IDF's strict -Werror
|
||||
# (reorder/unused/…) fail the build on code we consume as a library.
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error
|
||||
-include "${CMAKE_CURRENT_LIST_DIR}/../../../variants/tanmatsu/tanmatsu_compat.h") # adcAttachPin() shim
|
||||
@@ -0,0 +1,29 @@
|
||||
# The wadamesh app layer (MyMesh + DataStore + helpers + transports + the LVGL UITask),
|
||||
# compiled directly from the repo's src/ — NOT vendored (it's our own code, in-tree).
|
||||
# Excluded: main.cpp (our app_main lives in tanmatsu/main/), and the S3 input drivers
|
||||
# (HeltecV4CapTouch / TDeck*) — the Tanmatsu's input comes from bsp_input.
|
||||
set(REPO "${CMAKE_CURRENT_LIST_DIR}/../../..")
|
||||
set(APP "${REPO}/src")
|
||||
# app src/ + the Tanmatsu variant glue (target.cpp = board/radio/display globals + radio_init,
|
||||
# TanmatsuLoraRadio.cpp = the LoRa bridge).
|
||||
file(GLOB_RECURSE APP_SRCS "${APP}/*.cpp" "${APP}/*.c" "${REPO}/variants/tanmatsu/*.cpp")
|
||||
list(FILTER APP_SRCS EXCLUDE REGEX "/main\\.cpp$")
|
||||
list(FILTER APP_SRCS EXCLUDE REGEX "/helpers/input/(HeltecV4CapTouch|TDeckKeyboard|TDeckTouch|TDeckTrackball)\\.cpp$")
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${APP_SRCS}
|
||||
INCLUDE_DIRS "${APP}" "${APP}/ui-touch" "${REPO}/include" "${REPO}/variants/tanmatsu"
|
||||
REQUIRES espressif__arduino-esp32 meshcore lvgl ardlibs nvs_flash badge-bsp esp_lcd fatfs
|
||||
tanmatsu-lora tanmatsu-wifi h2zero__esp-nimble-cpp bt
|
||||
soc esp_hw_support app_update driver esp_timer esp_partition spi_flash esp_adc esp_pm espcoredump
|
||||
# IDF links component archives in one groupless ordered pass (with a repeat trick), NOT a
|
||||
# --start-group. arduino's NetworkClientSecure references our ssl_client stubs, but arduino is
|
||||
# re-scanned (pulling NetworkClientSecure.cpp.obj, which makes start_ssl_client undefined) only
|
||||
# AFTER this archive's last occurrence — so the stub object never gets pulled. Whole-archive this
|
||||
# component so the strong stub defs are always present. --gc-sections still drops unreferenced
|
||||
# sections, so the size impact is nil.
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
# our own code, but huge + LVGL-heavy — keep the first port building; don't -Werror on warnings.
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -w
|
||||
-include "${REPO}/variants/tanmatsu/tanmatsu_compat.h") # adcAttachPin() shim (arduino 3.x dropped it)
|
||||
@@ -0,0 +1,783 @@
|
||||
dependencies:
|
||||
badgeteam/badge-bsp:
|
||||
component_hash: 718beba37dc03ffaf3b21f29035a1379f2024927398397a81ce7a05c46ffec90
|
||||
dependencies:
|
||||
- name: badgeteam/mch2022-rp2040
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32
|
||||
version: ^0.0.2
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: espressif/esp_lcd_ili9341
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^2.0.2
|
||||
- name: espressif/led_strip
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^3.0.3
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
- name: nicolaielectronics/bmi270
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.0.1
|
||||
- name: nicolaielectronics/es8156
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^0.0.3
|
||||
- name: nicolaielectronics/mipi_dsi_abstraction
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32p4
|
||||
version: ^0.3.3
|
||||
- name: nicolaielectronics/mpr121
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^0.0.2
|
||||
- name: nicolaielectronics/ssd1619
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^0.0.1
|
||||
- name: nicolaielectronics/tanmatsu_coprocessor
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32p4
|
||||
version: ^0.7.0
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
targets:
|
||||
- esp32
|
||||
- esp32p4
|
||||
- esp32c6
|
||||
- esp32c3
|
||||
- esp32s3
|
||||
version: 0.9.9
|
||||
badgeteam/custom-certificates:
|
||||
component_hash: fe511ea8c8301bfc0a148d4458d5f2f3129ddc0d60c0fb4e8c720e256f7e73ce
|
||||
dependencies: []
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.1.0
|
||||
chmorgan/esp-libhelix-mp3:
|
||||
component_hash: cbb76089dc2c5749f7b470e2e70aedc44c9da519e04eb9a67d4c7ec275229e53
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.1.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.0.3
|
||||
espressif/arduino-esp32:
|
||||
component_hash: e7e5006cc864a1b33907f7d3ec7af5f64acfae9f5a53bd8097cb80f0936c4c7c
|
||||
dependencies:
|
||||
- name: chmorgan/esp-libhelix-mp3
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 1.0.3
|
||||
- name: espressif/cbor
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: 0.6.1~4
|
||||
- name: espressif/cjson
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: idf_version >= 6.0
|
||||
version: ^1.7.19~2
|
||||
- name: espressif/dm9051
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: idf_version >= 6.0 && target != esp32c2
|
||||
version: ^1.1.0
|
||||
- name: espressif/esp-dsp
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32c61]
|
||||
version: ^1.3.4
|
||||
- name: espressif/esp-modbus
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^2.1.2
|
||||
- name: espressif/esp-sr
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target in [esp32s3, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: ^2.1.5
|
||||
- name: espressif/esp-zboss-lib
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32c61, esp32p4]
|
||||
version: ==1.6.4
|
||||
- name: espressif/esp-zigbee-lib
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32c61, esp32p4]
|
||||
version: ==1.6.8
|
||||
- name: espressif/esp_diag_data_store
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: 1.0.2
|
||||
- name: espressif/esp_diagnostics
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: 1.2.1
|
||||
- name: espressif/esp_hosted
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32p4
|
||||
version: ^2.9.2
|
||||
- name: espressif/esp_insights
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: 1.2.2
|
||||
- name: espressif/esp_modem
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^2.0.0
|
||||
- name: espressif/esp_rainmaker
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: 1.5.2
|
||||
- name: espressif/esp_wifi_remote
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32p4
|
||||
version: ^1.2.2
|
||||
- name: espressif/ksz8851snl
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: idf_version >= 6.0 && target != esp32c2
|
||||
version: ^1.0.0
|
||||
- name: espressif/lan867x
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target in [esp32, esp32p4]
|
||||
version: ^2.0.0
|
||||
- name: espressif/libsodium
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: idf_version < 6.0
|
||||
version: ^1.0.21
|
||||
- name: espressif/mdns
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.2.3
|
||||
- matches:
|
||||
- if: idf_version >= 6.0
|
||||
version: 1.2.4
|
||||
- if: idf_version < 6.0
|
||||
version: 1.0.2
|
||||
name: espressif/network_provisioning
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target != esp32c2
|
||||
version: '*'
|
||||
- name: espressif/qrcode
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: 0.1.0~2
|
||||
- name: espressif/rmaker_common
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target not in [esp32c2, esp32p4]
|
||||
- if: idf_version < 6.0
|
||||
version: 1.4.6
|
||||
- name: espressif/w5500
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: idf_version >= 6.0 && target != esp32c2
|
||||
version: ^1.0.1
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3,<6.1'
|
||||
- name: joltwallet/littlefs
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.10.2
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
targets:
|
||||
- esp32
|
||||
- esp32c2
|
||||
- esp32c3
|
||||
- esp32c5
|
||||
- esp32c6
|
||||
- esp32c61
|
||||
- esp32h2
|
||||
- esp32p4
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
version: 3.3.10
|
||||
espressif/cjson:
|
||||
component_hash: e788323270d90738662d66fffa910bfe1fba019bba087f01557e70c40485b469
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.7.19~2
|
||||
espressif/cmake_utilities:
|
||||
component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.1'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.5.3
|
||||
espressif/dl_fft:
|
||||
component_hash: ced3cf28cc70452b7859c06f4e5059215167254a2047e34c893d6f501ccd6ea2
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.4.0
|
||||
espressif/eppp_link:
|
||||
component_hash: 04f7f31868b8ae6014c96132740a192d09611a6e46aaa7038c865ffc7e26b38f
|
||||
dependencies:
|
||||
- name: espressif/esp_serial_slave_link
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.1.0
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.1.5
|
||||
espressif/esp-dsp:
|
||||
component_hash: 12e44db246517a627bc0fe2b255b8335410c0215c98bfba2df5a8e3edea839ef
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.8.0
|
||||
espressif/esp-modbus:
|
||||
component_hash: 56be3df9f436ce30ee513b9c4a77db13b0c3d6c1f52ee48f2198e7aba4319d3c
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 2.1.2
|
||||
espressif/esp-sr:
|
||||
component_hash: aa58d6a13a49600314a50a27240ccbd17e407f608216a3086d4868e0da2e8053
|
||||
dependencies:
|
||||
- name: espressif/cjson
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.7.19
|
||||
- name: espressif/dl_fft
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '>=0.2.0'
|
||||
- name: espressif/esp-dsp
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 1.8.0
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 2.4.6
|
||||
espressif/esp_hosted:
|
||||
component_hash: 59c68fc3a8f25388b9a239ddec5f7bc81e6310d51108db1688e129e791450c31
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 2.12.9
|
||||
espressif/esp_lcd_ek79007:
|
||||
component_hash: 8005700b7f10c7136b6e2a3f19a48f972aa1d13ed107ed298574e8d24d17ea83
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32p4
|
||||
version: 1.0.4
|
||||
espressif/esp_lcd_ili9341:
|
||||
component_hash: 0baab584e0e490511d6bdbae6aa045130424d732ecdaad6cd1d3bd3c0abe2c30
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 2.0.2
|
||||
espressif/esp_lcd_ili9881c:
|
||||
component_hash: 0288df12755c95d406ccea245a427595f314df0b14a61846286d926ea6f3698e
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32p4
|
||||
version: 1.1.0
|
||||
espressif/esp_lcd_lt8912b:
|
||||
component_hash: d78113f9330e8cff5e91bb2faa1bc450a59e7235438b7d5558abe739f7ecda4b
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.1.3
|
||||
espressif/esp_lcd_st7701:
|
||||
component_hash: 0b11e8733e51e12da83003b9d475777eaf47bfbdce01379879bd2504d14f4f83
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>5.0.4,!=5.1.1'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32s3
|
||||
- esp32p4
|
||||
version: 1.1.5
|
||||
espressif/esp_modem:
|
||||
component_hash: 475c2185273ddedff2bb0d21a8ab196a9d0ef60595f73d634edf2d8be03a5f54
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.1'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 2.0.2
|
||||
espressif/esp_serial_slave_link:
|
||||
component_hash: ac1776806de0a6e371c84e87898bb983e19ce62aa7f1e2e5c4a3b0234a575d2c
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.1.2
|
||||
espressif/esp_wifi_remote:
|
||||
component_hash: caa856dac9d28afacafbaefdaaef3fffdf3f9149c6011ba36ac680a13f5fc19f
|
||||
dependencies:
|
||||
- name: espressif/esp_hosted
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target in [esp32h2, esp32p4]
|
||||
version: '>=2.11'
|
||||
- name: espressif/wifi_remote_over_eppp
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '>=0.1'
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.6.1
|
||||
espressif/lan867x:
|
||||
component_hash: 651c9d6e3d96acb08bd5066144b12a12e94df0a6241127a0538d8680689de2c4
|
||||
dependencies:
|
||||
- name: espressif/lan86xx_common
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '*'
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32
|
||||
- esp32p4
|
||||
version: 2.0.0
|
||||
espressif/lan86xx_common:
|
||||
component_hash: 6dcb1be9f43ae940979bd628fa5bee278949b66c545e1fe2321e486102e81026
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.0.0
|
||||
espressif/led_strip:
|
||||
component_hash: 28621486f77229aaf81c71f5e15d6fbf36c2949cf11094e07090593e659e7639
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 3.0.3
|
||||
espressif/libsodium:
|
||||
component_hash: 39c9dc77d81804d54a539c8f076faed165152be7720ddd0e721acb9daf4aa5af
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.0.22
|
||||
espressif/mdns:
|
||||
component_hash: 291732b5df4d181eb75eba9042799a508c4c124503a0c0573132a0a48d2e3fbe
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.11.2
|
||||
espressif/network_provisioning:
|
||||
component_hash: ef2e10182fd1861e68b821491916327c25416ca7ae70e5a6e43313dbc71fe993
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.1'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.0.2
|
||||
espressif/wifi_remote_over_eppp:
|
||||
component_hash: 06d8bc15348bbd21f1390daace36ed5adcf6026c93c5b69decc55fa04df2711a
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
- name: espressif/eppp_link
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '>=0.1'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.3.2
|
||||
h2zero/esp-nimble-cpp:
|
||||
component_hash: 720050476f7fb47bd0aa08a94642fd09a50536391d60081858702a789405d76f
|
||||
dependencies:
|
||||
- name: espressif/esp_hosted
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target in [esp32p4]
|
||||
version: '*'
|
||||
- name: espressif/esp_wifi_remote
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target in [esp32p4]
|
||||
version: '>=0.5.3'
|
||||
- name: idf
|
||||
require: private
|
||||
rules:
|
||||
- if: target in [esp32p4]
|
||||
version: '>=5.3.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 2.5.0
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.5.1
|
||||
joltwallet/littlefs:
|
||||
component_hash: 01faa388c6305fe77fd6962cda6171e753033458f24f8fae9e9d932e3d817a4c
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.22.1
|
||||
nicolaielectronics/bmi270:
|
||||
component_hash: 4eba2761d6fc2ea032c3e5321e594680f0a47a53ae76323a129628e16af38a0c
|
||||
dependencies: []
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.0.1
|
||||
nicolaielectronics/es8156:
|
||||
component_hash: 2cf4431df1d0b0867cd1eefa0a1fbc77dcf5347e9aa81446c95d5e3175c9bd63
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.0.3
|
||||
nicolaielectronics/esp-hosted-tanmatsu:
|
||||
component_hash: 23dddc20e6ef030b811d70423c2cd17c0d29e792464bdc7c40130fca75efb913
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 2.12.3
|
||||
nicolaielectronics/esp-wifi-remote-tanmatsu:
|
||||
component_hash: 727ff301054ea14ce2482ffc12154a64b4ac06d2aadd70621d3bb62ccfdebefa
|
||||
dependencies:
|
||||
- name: espressif/wifi_remote_over_eppp
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '>=0.1'
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
- name: nicolaielectronics/esp-hosted-tanmatsu
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target in [esp32h2, esp32p4]
|
||||
version: '>=2.12.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.4.2
|
||||
nicolaielectronics/mipi_dsi_abstraction:
|
||||
component_hash: 730040c77abc1f6bb5cf117405c9963e60159e807a5eea7d1750a2a48ccaaf89
|
||||
dependencies:
|
||||
- name: espressif/esp_lcd_st7701
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.1.1
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.4'
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: espressif/esp_lcd_ek79007
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.0.0
|
||||
- name: espressif/esp_lcd_ili9881c
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^1.0.0
|
||||
- name: espressif/esp_lcd_lt8912b
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^0.1.1
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32p4
|
||||
version: 0.3.3
|
||||
nicolaielectronics/mpr121:
|
||||
component_hash: 93853032af10158cbb8714c9f487b1afec9f182b3826d95906d79704858f5b1b
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.4'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.0.2
|
||||
nicolaielectronics/ssd1619:
|
||||
component_hash: de439a13c0e052ffe5fb161635bc6b2b6e504145042a4694fec4399462468003
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.4'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.0.1
|
||||
nicolaielectronics/sx126x:
|
||||
component_hash: bcd14ddf5981071ce4734eced3082f425098826bcce514a8f467d41d18f1d291
|
||||
dependencies: []
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.0.3
|
||||
nicolaielectronics/tanmatsu-lora:
|
||||
component_hash: 4b42f50fb1c7ce45ed5b0109346e2bc6d86a494334fcf0a633836d8f2459d977
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.5'
|
||||
- name: nicolaielectronics/esp-hosted-tanmatsu
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32p4
|
||||
version: '>=2.12.3'
|
||||
- name: nicolaielectronics/sx126x
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: ^0.0.3
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 0.3.0
|
||||
nicolaielectronics/tanmatsu-wifi:
|
||||
component_hash: 6abd4560becbe220bd03eb25c33fd877dc36f60c9fc4de619021bbe8807cea8d
|
||||
dependencies:
|
||||
- name: badgeteam/badge-bsp
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '>=0.3.0'
|
||||
- name: badgeteam/custom-certificates
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '>=1.0.0'
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.4.0'
|
||||
- name: nicolaielectronics/esp-hosted-tanmatsu
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32p4
|
||||
version: ^2.12.3
|
||||
- name: nicolaielectronics/esp-wifi-remote-tanmatsu
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
rules:
|
||||
- if: target == esp32p4
|
||||
version: ^1.4.2
|
||||
- name: nicolaielectronics/wifi-manager
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '>=0.1.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.2.0
|
||||
nicolaielectronics/tanmatsu_coprocessor:
|
||||
component_hash: 398edb22bb368517aaa31fa21f14a260c304137703cdd5b16299157e4126e7b3
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32p4
|
||||
version: 0.7.0
|
||||
nicolaielectronics/wifi-manager:
|
||||
component_hash: a5a8fb59d367a5f37ee3cfa6bc2615eafa22a7bf620739cd5bb9b571c2b3c871
|
||||
dependencies: []
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 0.3.0
|
||||
direct_dependencies:
|
||||
- badgeteam/badge-bsp
|
||||
- espressif/arduino-esp32
|
||||
- h2zero/esp-nimble-cpp
|
||||
- idf
|
||||
- nicolaielectronics/tanmatsu-lora
|
||||
- nicolaielectronics/tanmatsu-wifi
|
||||
manifest_hash: 57d90fb7077b5ae9fad34d258407e3084fc2da6ac7e8f583f97c99d0a7e171cb
|
||||
target: esp32p4
|
||||
version: 2.0.0
|
||||
@@ -0,0 +1,7 @@
|
||||
export IDF_PATH=$(pwd)/esp-idf
|
||||
export IDF_TOOLS_PATH=$(pwd)/esp-idf-tools
|
||||
|
||||
echo "ESP-IDF: $IDF_PATH"
|
||||
echo "TOOLS: $IDF_TOOLS_PATH"
|
||||
|
||||
source esp-idf/export.sh
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"Tanmatsu": {
|
||||
"build": {
|
||||
"compileArgs": [
|
||||
"-DDEVICE=tanmatsu",
|
||||
"-DFAT=0",
|
||||
"-GNinja"
|
||||
],
|
||||
"ninjaArgs": [],
|
||||
"buildDirectoryPath": "build/tanmatsu",
|
||||
"sdkconfigDefaults": [
|
||||
"sdkconfigs/general",
|
||||
"sdkconfigs/tanmatsu"
|
||||
],
|
||||
"sdkconfigFilePath": "sdkconfig"
|
||||
},
|
||||
"env": {},
|
||||
"idfTarget": "esp32p4",
|
||||
"flashBaudRate": "",
|
||||
"monitorBaudRate": "",
|
||||
"openOCD": {
|
||||
"debugLevel": 2,
|
||||
"configs": [],
|
||||
"args": []
|
||||
},
|
||||
"tasks": {
|
||||
"preBuild": "",
|
||||
"preFlash": "",
|
||||
"postBuild": "",
|
||||
"postFlash": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Populate tanmatsu/components/ with the MeshCore core + the Arduino libraries wadamesh needs,
|
||||
# sourced from PlatformIO's already-resolved libdeps. Run an S3 build first to populate them:
|
||||
# /Users/kaj/Library/Python/3.9/bin/pio run -e LilyGo_TDeck_companion_radio_touch
|
||||
# Vendored payloads are gitignored (regenerate with this script); the committed component
|
||||
# wrappers (components/meshcore/CMakeLists.txt, components/ardlibs/CMakeLists.txt, the
|
||||
# esp_hosted override) are tracked.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
LIBDEPS="../.pio/libdeps/LilyGo_TDeck_companion_radio_touch"
|
||||
[ -d "$LIBDEPS/MeshCore" ] || { echo "!! $LIBDEPS/MeshCore missing — run an S3 'pio run' first"; exit 1; }
|
||||
|
||||
mkdir -p components
|
||||
|
||||
# --- MeshCore core: copy src/ + variants/ under the committed meshcore wrapper ---
|
||||
rm -rf components/meshcore/core
|
||||
mkdir -p components/meshcore/core
|
||||
cp -R "$LIBDEPS/MeshCore/src" components/meshcore/core/src
|
||||
cp -R "$LIBDEPS/MeshCore/variants" components/meshcore/core/variants 2>/dev/null || true
|
||||
mkdir -p components/meshcore/core/lib
|
||||
cp -R "$LIBDEPS/MeshCore/lib/ed25519" components/meshcore/core/lib/ed25519 # bundled C ed25519 (Identity.cpp)
|
||||
echo "vendored: meshcore core ($(find components/meshcore/core/src -name '*.cpp' | wc -l | tr -d ' ') cpp)"
|
||||
|
||||
# --- LVGL 8.4 (own component; big; uses the repo's include/lv_conf.h) ---
|
||||
rm -rf components/lvgl/upstream
|
||||
mkdir -p components/lvgl
|
||||
cp -R "$LIBDEPS/lvgl" components/lvgl/upstream
|
||||
echo "vendored: lvgl ($(find components/lvgl/upstream/src -name '*.c' | wc -l | tr -d ' ') c)"
|
||||
|
||||
# --- Arduino libraries: ALL go into ONE 'ardlibs' component so inter-library #includes
|
||||
# resolve without per-lib REQUIRES (e.g. RTClib -> Adafruit BusIO). Payload gitignored. ---
|
||||
rm -rf components/ardlibs/upstream
|
||||
mkdir -p components/ardlibs/upstream
|
||||
vend() { # vend <libdeps-subdir>
|
||||
local src="$1"; local dst="${src// /_}" # spaces in dir names break IDF component-req matching
|
||||
[ -d "$LIBDEPS/$src" ] || { echo " skip ($src not in libdeps)"; return; }
|
||||
cp -R "$LIBDEPS/$src" "components/ardlibs/upstream/$dst"
|
||||
echo " + $src -> $dst"
|
||||
}
|
||||
# the core's own lib_deps (minus RadioLib — replaced by the TanmatsuLoraRadio bridge) + their deps:
|
||||
vend "Crypto"
|
||||
vend "RTClib"
|
||||
vend "Adafruit BusIO" # RTClib + RV3028 need Adafruit_I2CDevice.h
|
||||
vend "Adafruit Unified Sensor"
|
||||
vend "Melopero RV3028"
|
||||
vend "CayenneLPP"
|
||||
vend "ArduinoJson" # header-only; some core/app files include it
|
||||
vend "base64" # base64.hpp (companion / QR code paths)
|
||||
# app-layer libs (validate they build on P4; used once wadamesh_app is wired):
|
||||
vend "MicroNMEA" # GPS NMEA parsing
|
||||
vend "AsyncTCP" # the core's ESP32Board HTTP-OTA + companion-over-IP transport
|
||||
vend "ESPAsyncWebServer" # WebSocket/HTTP companion server (needs the task WDT on — sdkconfigs/wadamesh)
|
||||
vend "AsyncElegantOTA" # OTA web UI (the core's ESP32Board HTTP-OTA)
|
||||
# Bump the component CMakeLists so the next build re-globs (IDF component scripts can't use
|
||||
# file(GLOB CONFIGURE_DEPENDS) — they run in script mode during requirement expansion).
|
||||
touch components/meshcore/CMakeLists.txt components/ardlibs/CMakeLists.txt 2>/dev/null || true
|
||||
echo "done."
|
||||
@@ -0,0 +1,54 @@
|
||||
idf_build_get_property(idf_target IDF_TARGET)
|
||||
if("${idf_target}" STREQUAL "esp32p4")
|
||||
list(APPEND extra_requires esp-hosted-tanmatsu)
|
||||
endif()
|
||||
|
||||
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"main.cpp"
|
||||
PRIV_REQUIRES
|
||||
esp_lcd
|
||||
fatfs
|
||||
nvs_flash
|
||||
bt
|
||||
badge-bsp
|
||||
tanmatsu-wifi
|
||||
espressif__arduino-esp32
|
||||
meshcore
|
||||
lvgl
|
||||
wadamesh_app
|
||||
${extra_requires}
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
)
|
||||
|
||||
idf_build_set_property(COMPILE_OPTIONS "-Wno-error=unused-variable" APPEND)
|
||||
idf_build_set_property(COMPILE_OPTIONS "-Wno-error=unused-const-variable" APPEND)
|
||||
# The vendored MeshCore core + Arduino libs build clean under PlatformIO's laxer flags but trip
|
||||
# IDF's blanket -Werror=all (reorder/format/class-memaccess) as ERRORS. A bare -Wno-error does
|
||||
# NOT cancel -Werror=all, so strip it from the global options outright — warnings stay visible,
|
||||
# just non-fatal. (Our own new code stays warning-clean by convention.)
|
||||
idf_build_get_property(_co COMPILE_OPTIONS)
|
||||
list(REMOVE_ITEM _co "-Werror=all")
|
||||
idf_build_set_property(COMPILE_OPTIONS "${_co}")
|
||||
|
||||
# --- Tanmatsu app define set: curated subset of the S3 touch build_flags (see TANMATSU_PORT.md).
|
||||
# Applied globally so the core + app + UITask all see a consistent config. No S3-board defines
|
||||
# (HELTEC_*, ST7789_*, P_LORA_*, RADIOLIB_*) — the Tanmatsu uses badge-bsp + the LoRa bridge.
|
||||
set(WADA_DEFS
|
||||
HAS_TANMATSU=1 ESP32_PLATFORM MC_VENDORED_TOUCH_APP UI_LVGL=1 HAS_TOUCH_UI=1
|
||||
MULTI_TRANSPORT_COMPANION TCP_PORT=5000 WS_PORT=8765
|
||||
BLE_PIN_CODE=123456 # enables the app's NimBLE companion (BLE over the C6)
|
||||
LORA_FREQ=869.618 LORA_BW=62.5 LORA_SF=8
|
||||
MAX_CONTACTS=500 MAX_GROUP_CHANNELS=40 # 2000 was a ~400KB internal-DRAM strand; BT's SDIO RX buffers (MALLOC_CAP_INTERNAL|DMA) need that headroom
|
||||
ENABLE_PRIVATE_KEY_IMPORT=1 ENABLE_PRIVATE_KEY_EXPORT=1
|
||||
DISPLAY_CLASS=TanmatsuDisplay
|
||||
# the core's ESP32Board.h reads these raw pins directly; Tanmatsu uses badge-bsp, so -1 (none):
|
||||
P_LORA_DIO_1=-1 P_LORA_TX_LED=-1 P_LORA_TX_NEOPIXEL_LED=-1
|
||||
PIN_BOARD_SCL=-1 PIN_BOARD_SDA=-1 PIN_VBAT_READ=-1)
|
||||
foreach(_d ${WADA_DEFS})
|
||||
idf_build_set_property(COMPILE_OPTIONS "-D${_d}" APPEND)
|
||||
endforeach()
|
||||
# (the adcAttachPin compat shim is force-included per-component in meshcore + wadamesh_app —
|
||||
# NOT globally, which would feed the C++ header to FreeRTOS's .S assembly files and break them.)
|
||||
@@ -0,0 +1,347 @@
|
||||
#include <stdio.h>
|
||||
#include "bsp/device.h"
|
||||
#include "bsp/display.h"
|
||||
#include "bsp/input.h"
|
||||
#include "bsp/led.h"
|
||||
#include "bsp/power.h"
|
||||
#include "custom_certificates.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_types.h"
|
||||
#include "esp_log.h"
|
||||
#include "hal/lcd_types.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "pax_fonts.h"
|
||||
#include "pax_gfx.h"
|
||||
#include "pax_text.h"
|
||||
#include "pax_types.h"
|
||||
#include "portmacro.h"
|
||||
#include "wifi_connection.h"
|
||||
#include "wifi_remote.h"
|
||||
|
||||
// Constants
|
||||
static char const TAG[] = "main";
|
||||
|
||||
#if defined(CONFIG_BSP_TARGET_KAMI)
|
||||
#define BLACK 0
|
||||
#define WHITE 1
|
||||
#define RED 2
|
||||
#else
|
||||
#define BLACK 0xFF000000
|
||||
#define WHITE 0xFFFFFFFF
|
||||
#define RED 0xFFFF0000
|
||||
#endif
|
||||
|
||||
// Global variables
|
||||
static size_t display_h_res = 0;
|
||||
static size_t display_v_res = 0;
|
||||
static bsp_display_color_format_t display_color_format = 0;
|
||||
static bsp_display_endianness_t display_data_endian = 0;
|
||||
static pax_buf_t fb = {0};
|
||||
static QueueHandle_t input_event_queue = NULL;
|
||||
|
||||
#if defined(CONFIG_BSP_TARGET_KAMI)
|
||||
// Temporary addition for supporting epaper devices (irrelevant for Tanmatsu)
|
||||
static pax_col_t palette[] = {0xffffffff, 0xff000000, 0xffff0000}; // white, black, red
|
||||
#endif
|
||||
|
||||
static void blit(void) {
|
||||
esp_err_t res = bsp_display_blit(0, 0, display_h_res, display_v_res, pax_buf_get_pixels(&fb));
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to blit to display: %d", res);
|
||||
}
|
||||
}
|
||||
|
||||
static void display_message(const char* message) {
|
||||
if (pax_buf_get_width(&fb) > 0) {
|
||||
pax_background(&fb, BLACK);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 0, message);
|
||||
blit();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Message: %s", message);
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
// Start the GPIO interrupt service
|
||||
gpio_install_isr_service(0);
|
||||
|
||||
// Initialize the Non Volatile Storage partition
|
||||
esp_err_t res = nvs_flash_init();
|
||||
if (res == ESP_ERR_NVS_NO_FREE_PAGES || res == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
res = nvs_flash_erase();
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to erase NVS flash: %d", res);
|
||||
return;
|
||||
}
|
||||
res = nvs_flash_init();
|
||||
}
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize NVS flash: %d", res);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the Board Support Package
|
||||
const bsp_configuration_t bsp_configuration = {
|
||||
.display =
|
||||
{
|
||||
.requested_color_format = BSP_DISPLAY_COLOR_FORMAT_24_888RGB,
|
||||
.num_fbs = 1,
|
||||
},
|
||||
};
|
||||
res = bsp_device_initialize(&bsp_configuration);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize BSP: %d", res);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get display parameters and rotation
|
||||
res = bsp_display_get_parameters(&display_h_res, &display_v_res, &display_color_format, &display_data_endian);
|
||||
if (res != ESP_ERR_NOT_SUPPORTED) {
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get display parameters: %d", res);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert ESP-IDF color format into PAX buffer type
|
||||
pax_buf_type_t format = PAX_BUF_24_888RGB;
|
||||
switch (display_color_format) {
|
||||
case BSP_DISPLAY_COLOR_FORMAT_1_PAL:
|
||||
format = PAX_BUF_1_PAL;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_2_PAL:
|
||||
format = PAX_BUF_2_PAL;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_4_PAL:
|
||||
format = PAX_BUF_4_PAL;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_8_PAL:
|
||||
format = PAX_BUF_8_PAL;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_16_PAL:
|
||||
format = PAX_BUF_16_PAL;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_1_GREY:
|
||||
format = PAX_BUF_1_GREY;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_2_GREY:
|
||||
format = PAX_BUF_2_GREY;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_4_GREY:
|
||||
format = PAX_BUF_4_GREY;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_8_GREY:
|
||||
format = PAX_BUF_8_GREY;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_8_332RGB:
|
||||
format = PAX_BUF_8_332RGB;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_16_565RGB:
|
||||
format = PAX_BUF_16_565RGB;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_4_1111ARGB:
|
||||
format = PAX_BUF_4_1111ARGB;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_8_2222ARGB:
|
||||
format = PAX_BUF_8_2222ARGB;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_16_4444ARGB:
|
||||
format = PAX_BUF_16_4444ARGB;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_24_888RGB:
|
||||
format = PAX_BUF_24_888RGB;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_32_8888ARGB:
|
||||
format = PAX_BUF_32_8888ARGB;
|
||||
break;
|
||||
case BSP_DISPLAY_COLOR_FORMAT_18_666RGB:
|
||||
default:
|
||||
ESP_LOGW(TAG, "BSP requests color format not supported by PAX (%u)", format);
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert BSP display rotation format into PAX orientation type
|
||||
bsp_display_rotation_t display_rotation = bsp_display_get_default_rotation();
|
||||
pax_orientation_t orientation = PAX_O_UPRIGHT;
|
||||
switch (display_rotation) {
|
||||
case BSP_DISPLAY_ROTATION_90:
|
||||
orientation = PAX_O_ROT_CCW;
|
||||
break;
|
||||
case BSP_DISPLAY_ROTATION_180:
|
||||
orientation = PAX_O_ROT_HALF;
|
||||
break;
|
||||
case BSP_DISPLAY_ROTATION_270:
|
||||
orientation = PAX_O_ROT_CW;
|
||||
break;
|
||||
case BSP_DISPLAY_ROTATION_0:
|
||||
default:
|
||||
orientation = PAX_O_UPRIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
// Initialize graphics stack
|
||||
printf("Initializing framebuffer with w=%d h=%d format=%d endian=%d orientation=%d\n", display_h_res,
|
||||
display_v_res, format, display_data_endian, orientation);
|
||||
pax_buf_init(&fb, NULL, display_h_res, display_v_res, format);
|
||||
pax_buf_reversed(&fb, display_data_endian == BSP_DISPLAY_ENDIAN_BIG);
|
||||
#if defined(CONFIG_BSP_TARGET_KAMI)
|
||||
// Temporary addition for supporting Kami
|
||||
fb.palette = palette;
|
||||
fb.palette_size = sizeof(palette) / sizeof(pax_col_t);
|
||||
#endif
|
||||
pax_buf_set_orientation(&fb, orientation);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "This board has no display support");
|
||||
}
|
||||
|
||||
// Get input event queue from BSP
|
||||
ESP_ERROR_CHECK(bsp_input_get_queue(&input_event_queue));
|
||||
|
||||
// LEDs
|
||||
bsp_led_set_pixel(0, 0xFF0000); // Red
|
||||
bsp_led_set_pixel(1, 0x00FF00); // Green
|
||||
bsp_led_set_pixel(2, 0x0000FF); // Blue
|
||||
bsp_led_set_pixel(3, 0xFFFF00); // Yellow
|
||||
bsp_led_set_pixel(4, 0x00FFFF); // Magenta
|
||||
bsp_led_set_pixel(5, 0xFF00FF); // Cyan
|
||||
bsp_led_send(); // Send data to the coprocessor
|
||||
bsp_led_set_mode(false); // Take control over all LEDs by disabling automatic mode
|
||||
|
||||
// Start WiFi stack (if your app does not require WiFi or BLE you can remove this section)
|
||||
pax_background(&fb, BLACK);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 0, "Connecting to radio...");
|
||||
blit();
|
||||
|
||||
if (wifi_remote_initialize() == ESP_OK) {
|
||||
display_message("Starting WiFi stack...");
|
||||
wifi_connection_init_stack(); // Start the Espressif WiFi stack
|
||||
|
||||
display_message("Connecting to WiFi network...");
|
||||
|
||||
if (wifi_connect_try_all() == ESP_OK) {
|
||||
display_message("Successfully connected to WiFi network");
|
||||
} else {
|
||||
display_message("Failed to connect to WiFi network");
|
||||
}
|
||||
} else {
|
||||
bsp_power_set_radio_state(BSP_POWER_RADIO_STATE_OFF);
|
||||
ESP_LOGE(TAG, "WiFi radio not responding, WiFi not available");
|
||||
display_message("WiFi radio unavailable");
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
// Main section of the app
|
||||
|
||||
// This example shows how to read from the BSP event queue to read input events
|
||||
|
||||
// If you want to run something at an interval in this same main thread you can replace portMAX_DELAY with an amount
|
||||
// of ticks to wait, for example pdMS_TO_TICKS(1000)
|
||||
|
||||
display_message("Welcome! Press any key to trigger an event.");
|
||||
|
||||
while (1) {
|
||||
bsp_input_event_t event;
|
||||
if (xQueueReceive(input_event_queue, &event, portMAX_DELAY) == pdTRUE) {
|
||||
switch (event.type) {
|
||||
case INPUT_EVENT_TYPE_KEYBOARD: {
|
||||
if (event.args_keyboard.ascii != '\b' ||
|
||||
event.args_keyboard.ascii != '\t') { // Ignore backspace & tab keyboard events
|
||||
if (pax_buf_get_height(&fb) <= 128) {
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "%c %s M=%02" PRIx32, event.args_keyboard.ascii,
|
||||
event.args_keyboard.utf8, event.args_keyboard.modifiers);
|
||||
display_message(text);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Keyboard event %c (%02x) %s", event.args_keyboard.ascii,
|
||||
(uint8_t)event.args_keyboard.ascii, event.args_keyboard.utf8);
|
||||
if (pax_buf_get_width(&fb) > 0) {
|
||||
pax_simple_rect(&fb, BLACK, 0, 0, pax_buf_get_width(&fb), 72);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 0, "Keyboard event");
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "ASCII: %c (0x%02x)", event.args_keyboard.ascii,
|
||||
(uint8_t)event.args_keyboard.ascii);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 18, text);
|
||||
snprintf(text, sizeof(text), "UTF-8: %s", event.args_keyboard.utf8);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 36, text);
|
||||
snprintf(text, sizeof(text), "Modifiers: 0x%0" PRIX32, event.args_keyboard.modifiers);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 54, text);
|
||||
blit();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case INPUT_EVENT_TYPE_NAVIGATION: {
|
||||
ESP_LOGI(TAG, "Navigation event %0" PRIX32 ": %s", (uint32_t)event.args_navigation.key,
|
||||
event.args_navigation.state ? "pressed" : "released");
|
||||
|
||||
if (event.args_navigation.key == BSP_INPUT_NAVIGATION_KEY_F1) {
|
||||
bsp_device_restart_to_launcher();
|
||||
}
|
||||
if (event.args_navigation.key == BSP_INPUT_NAVIGATION_KEY_F2) {
|
||||
bsp_input_set_backlight_brightness(0);
|
||||
}
|
||||
if (event.args_navigation.key == BSP_INPUT_NAVIGATION_KEY_F3) {
|
||||
bsp_input_set_backlight_brightness(100);
|
||||
}
|
||||
|
||||
if (pax_buf_get_height(&fb) <= 128) {
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "%02" PRIx32 " %s M=%02" PRIx32,
|
||||
(uint32_t)event.args_navigation.key, event.args_navigation.state ? "P" : "R",
|
||||
event.args_navigation.modifiers);
|
||||
display_message(text);
|
||||
} else {
|
||||
pax_simple_rect(&fb, BLACK, 0, 100, pax_buf_get_width(&fb), 72);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 100 + 0, "Navigation event");
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "Key: 0x%0" PRIX32, (uint32_t)event.args_navigation.key);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 100 + 18, text);
|
||||
snprintf(text, sizeof(text), "State: %s",
|
||||
event.args_navigation.state ? "pressed" : "released");
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 100 + 36, text);
|
||||
snprintf(text, sizeof(text), "Modifiers: 0x%0" PRIX32, event.args_navigation.modifiers);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 100 + 54, text);
|
||||
blit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case INPUT_EVENT_TYPE_ACTION: {
|
||||
ESP_LOGI(TAG, "Action event 0x%0" PRIX32 ": %s", (uint32_t)event.args_action.type,
|
||||
event.args_action.state ? "yes" : "no");
|
||||
if (pax_buf_get_height(&fb) <= 128) {
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "%02" PRIx32 " %s" PRIx32, (uint32_t)event.args_action.type,
|
||||
event.args_action.state ? "Y" : "N");
|
||||
display_message(text);
|
||||
} else {
|
||||
pax_simple_rect(&fb, BLACK, 0, 200 + 0, pax_buf_get_width(&fb), 72);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 200 + 0, "Action event");
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "Type: 0x%0" PRIX32, (uint32_t)event.args_action.type);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 200 + 36, text);
|
||||
snprintf(text, sizeof(text), "State: %s", event.args_action.state ? "yes" : "no");
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 200 + 54, text);
|
||||
blit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case INPUT_EVENT_TYPE_SCANCODE: {
|
||||
ESP_LOGI(TAG, "Scancode event 0x%0" PRIX32, (uint32_t)event.args_scancode.scancode);
|
||||
if (pax_buf_get_width(&fb) > 0) {
|
||||
pax_simple_rect(&fb, BLACK, 0, 300 + 0, pax_buf_get_width(&fb), 72);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 300 + 0, "Scancode event");
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "Scancode: 0x%0" PRIX32, (uint32_t)event.args_scancode.scancode);
|
||||
pax_draw_text(&fb, WHITE, pax_font_sky_mono, 16, 0, 300 + 36, text);
|
||||
blit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
dependencies:
|
||||
idf: ">=5.5.1"
|
||||
badgeteam/badge-bsp: "^0.9.9"
|
||||
nicolaielectronics/tanmatsu-wifi: "^1.1.2"
|
||||
nicolaielectronics/tanmatsu-lora: "^0.3.0" # SX1262 LoRa over the C6 — TanmatsuLoraRadio bridge
|
||||
# arduino-esp32 as an IDF component so the shared wadamesh src/ (Arduino API) compiles.
|
||||
# esp32p4 is a first-class target from 3.1.3+; 3.3.10 is the current stable.
|
||||
espressif/arduino-esp32: "3.3.10"
|
||||
# BLE companion over the C6: esp-nimble-cpp is the IDF-host NimBLE C++ wrapper (works with esp-hosted's
|
||||
# remote BLE controller, unlike NimBLE-Arduino's bundled host). The Tanmatsu's vendored SerialBLEInterface
|
||||
# copy is ported to this 2.x API. C6 controller comes up via hostedInitBLE().
|
||||
h2zero/esp-nimble-cpp: "^2.3.0"
|
||||
@@ -0,0 +1,456 @@
|
||||
// wadamesh on the Tanmatsu (ESP32-P4) — the app entry point.
|
||||
//
|
||||
// Replaces the S3 boards' src/main.cpp setup()/loop(): it declares the app globals and runs the
|
||||
// bring-up. badge-bsp owns the display/input/power; arduino-esp32 is the runtime; the LoRa is the
|
||||
// TanmatsuLoraRadio bridge. The board/radio/display/sensors globals + radio_init() live in
|
||||
// variants/tanmatsu/target.cpp (pulled via target.h).
|
||||
#include <tanmatsu_compat.h> // adcAttachPin() shim — BEFORE target.h pulls ESP32Board.h
|
||||
#include <Arduino.h>
|
||||
#include <Mesh.h>
|
||||
#include "MyMesh.h"
|
||||
#include "UITask.h"
|
||||
#include "target.h" // board, radio_driver, rtc_clock, display, sensors, radio_init()
|
||||
#include <SPIFFS.h>
|
||||
#include <FFat.h> // DataStore persists on the internal 'locfd' FAT partition
|
||||
#include "esp_partition.h" // enumerate partitions (AppFS apps boot fresh — verify locfd is visible)
|
||||
#include <WiFi.h>
|
||||
#include <helpers/esp32/MultiTransportCompanionInterface.h>
|
||||
#include <helpers/esp32/WifiRuntimeStore.h>
|
||||
#include <helpers/esp32/TouchPrefsStore.h>
|
||||
#include "esp32-hal-hosted.h" // arduino's esp-hosted bring-up (shared by LoRa + WiFi on the C6)
|
||||
#include "esp_heap_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "lvgl.h"
|
||||
extern "C" {
|
||||
#include "bsp/device.h"
|
||||
#include "bsp/input.h"
|
||||
#include "bsp/display.h"
|
||||
#include "esp_hosted.h" // C6 radio co-processor link (WiFi + LoRa ride esp-hosted)
|
||||
#include "esp_netif.h" // lwIP/tcpip bring-up (else WiFi calls assert "Invalid mbox")
|
||||
#include "esp_event.h"
|
||||
}
|
||||
|
||||
#ifndef TCP_PORT
|
||||
#define TCP_PORT 5000
|
||||
#endif
|
||||
#ifndef WS_PORT
|
||||
#define WS_PORT 8765
|
||||
#endif
|
||||
|
||||
// Boot-trace hooks the shared code defines on the S3 boards (in src/main.cpp, which we exclude).
|
||||
volatile int g_boot_phase = 0;
|
||||
extern "C" void set_boot_phase(int phase) { g_boot_phase = phase; }
|
||||
void halt() { while (1) { delay(1000); } }
|
||||
|
||||
// App globals — src/main.cpp declares these; we own them here (that file is excluded from the build).
|
||||
// Persistence: an AppFS app boots FRESH (its own P4 reset) and does NOT inherit the launcher's VFS
|
||||
// mounts, so we mount the internal 'locfd' FAT partition ourselves via FFat (an fs::FS, which is what
|
||||
// DataStore takes — FILESYSTEM == fs::FS on ESP32). See the storage block in setup().
|
||||
DataStore store(FFat, rtc_clock);
|
||||
bool g_fs_ok = false; // true once FFat(locfd) is mounted; UITask's file browser checks this (extern)
|
||||
MultiTransportCompanionInterface serial_interface;
|
||||
StdRNG fast_rng;
|
||||
SimpleMeshTables tables;
|
||||
UITask ui_task(&board, &serial_interface);
|
||||
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store, &ui_task);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Boot splash — a tiny on-screen console so launching the app shows progress
|
||||
// instead of a black screen (the other badge.team apps print boot lines before
|
||||
// their UI comes up; ours stayed black during the long pre-LVGL bring-up:
|
||||
// radio + mesh + the 28k-line UI build). We draw straight to the panel with an
|
||||
// 8x8 font. The panel is native 480x800 PORTRAIT and LVGL software-rotates the
|
||||
// UI to 800x480 landscape (ROT_270); we apply the SAME mapping so the boot text
|
||||
// is upright in landscape, then the first LVGL frame paints over it.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define BOOT_LW 800 // logical (landscape) width
|
||||
#define BOOT_LH 480 // logical (landscape) height
|
||||
#define BOOT_PW 480 // physical panel width (stride)
|
||||
#define BOOT_PH 800 // physical panel height
|
||||
#define BOOT_FG 0x07E0 // terminal green — pure G channel, immune to R/B order; only a full byte-swap limes it
|
||||
|
||||
// font8x8_basic (public domain, Daniel Hepper) — printable ASCII 0x20..0x7E.
|
||||
// One glyph = 8 rows; bit 0 (LSB) is the leftmost pixel, row 0 the top.
|
||||
static const uint8_t kBootFont[95][8] = {
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},{0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00},
|
||||
{0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00},{0x36,0x36,0x7F,0x36,0x7F,0x36,0x36,0x00},
|
||||
{0x0C,0x3E,0x03,0x1E,0x30,0x1F,0x0C,0x00},{0x00,0x63,0x33,0x18,0x0C,0x66,0x63,0x00},
|
||||
{0x1C,0x36,0x1C,0x6E,0x3B,0x33,0x6E,0x00},{0x06,0x06,0x03,0x00,0x00,0x00,0x00,0x00},
|
||||
{0x18,0x0C,0x06,0x06,0x06,0x0C,0x18,0x00},{0x06,0x0C,0x18,0x18,0x18,0x0C,0x06,0x00},
|
||||
{0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00},{0x00,0x0C,0x0C,0x3F,0x0C,0x0C,0x00,0x00},
|
||||
{0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x06},{0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00},
|
||||
{0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00},{0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x00},
|
||||
{0x3E,0x63,0x73,0x7B,0x6F,0x67,0x3E,0x00},{0x0C,0x0E,0x0C,0x0C,0x0C,0x0C,0x3F,0x00},
|
||||
{0x1E,0x33,0x30,0x1C,0x06,0x33,0x3F,0x00},{0x1E,0x33,0x30,0x1C,0x30,0x33,0x1E,0x00},
|
||||
{0x38,0x3C,0x36,0x33,0x7F,0x30,0x78,0x00},{0x3F,0x03,0x1F,0x30,0x30,0x33,0x1E,0x00},
|
||||
{0x1C,0x06,0x03,0x1F,0x33,0x33,0x1E,0x00},{0x3F,0x33,0x30,0x18,0x0C,0x0C,0x0C,0x00},
|
||||
{0x1E,0x33,0x33,0x1E,0x33,0x33,0x1E,0x00},{0x1E,0x33,0x33,0x3E,0x30,0x18,0x0E,0x00},
|
||||
{0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x00},{0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x06},
|
||||
{0x18,0x0C,0x06,0x03,0x06,0x0C,0x18,0x00},{0x00,0x00,0x3F,0x00,0x00,0x3F,0x00,0x00},
|
||||
{0x06,0x0C,0x18,0x30,0x18,0x0C,0x06,0x00},{0x1E,0x33,0x30,0x18,0x0C,0x00,0x0C,0x00},
|
||||
{0x3E,0x63,0x7B,0x7B,0x7B,0x03,0x1E,0x00},{0x0C,0x1E,0x33,0x33,0x3F,0x33,0x33,0x00},
|
||||
{0x3F,0x66,0x66,0x3E,0x66,0x66,0x3F,0x00},{0x3C,0x66,0x03,0x03,0x03,0x66,0x3C,0x00},
|
||||
{0x1F,0x36,0x66,0x66,0x66,0x36,0x1F,0x00},{0x7F,0x46,0x16,0x1E,0x16,0x46,0x7F,0x00},
|
||||
{0x7F,0x46,0x16,0x1E,0x16,0x06,0x0F,0x00},{0x3C,0x66,0x03,0x03,0x73,0x66,0x7C,0x00},
|
||||
{0x33,0x33,0x33,0x3F,0x33,0x33,0x33,0x00},{0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},
|
||||
{0x78,0x30,0x30,0x30,0x33,0x33,0x1E,0x00},{0x67,0x66,0x36,0x1E,0x36,0x66,0x67,0x00},
|
||||
{0x0F,0x06,0x06,0x06,0x46,0x66,0x7F,0x00},{0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x00},
|
||||
{0x63,0x67,0x6F,0x7B,0x73,0x63,0x63,0x00},{0x1C,0x36,0x63,0x63,0x63,0x36,0x1C,0x00},
|
||||
{0x3F,0x66,0x66,0x3E,0x06,0x06,0x0F,0x00},{0x1E,0x33,0x33,0x33,0x3B,0x1E,0x38,0x00},
|
||||
{0x3F,0x66,0x66,0x3E,0x36,0x66,0x67,0x00},{0x1E,0x33,0x07,0x0E,0x38,0x33,0x1E,0x00},
|
||||
{0x3F,0x2D,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},{0x33,0x33,0x33,0x33,0x33,0x33,0x3F,0x00},
|
||||
{0x33,0x33,0x33,0x33,0x33,0x1E,0x0C,0x00},{0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00},
|
||||
{0x63,0x63,0x36,0x1C,0x1C,0x36,0x63,0x00},{0x33,0x33,0x33,0x1E,0x0C,0x0C,0x1E,0x00},
|
||||
{0x7F,0x63,0x31,0x18,0x4C,0x66,0x7F,0x00},{0x1E,0x06,0x06,0x06,0x06,0x06,0x1E,0x00},
|
||||
{0x03,0x06,0x0C,0x18,0x30,0x60,0x40,0x00},{0x1E,0x18,0x18,0x18,0x18,0x18,0x1E,0x00},
|
||||
{0x08,0x1C,0x36,0x63,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF},
|
||||
{0x0C,0x0C,0x18,0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x1E,0x30,0x3E,0x33,0x6E,0x00},
|
||||
{0x07,0x06,0x06,0x3E,0x66,0x66,0x3B,0x00},{0x00,0x00,0x1E,0x33,0x03,0x33,0x1E,0x00},
|
||||
{0x38,0x30,0x30,0x3E,0x33,0x33,0x6E,0x00},{0x00,0x00,0x1E,0x33,0x3F,0x03,0x1E,0x00},
|
||||
{0x1C,0x36,0x06,0x0F,0x06,0x06,0x0F,0x00},{0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x1F},
|
||||
{0x07,0x06,0x36,0x6E,0x66,0x66,0x67,0x00},{0x0C,0x00,0x0E,0x0C,0x0C,0x0C,0x1E,0x00},
|
||||
{0x30,0x00,0x30,0x30,0x30,0x33,0x33,0x1E},{0x07,0x06,0x66,0x36,0x1E,0x36,0x67,0x00},
|
||||
{0x0E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},{0x00,0x00,0x33,0x7F,0x7F,0x6B,0x63,0x00},
|
||||
{0x00,0x00,0x1F,0x33,0x33,0x33,0x33,0x00},{0x00,0x00,0x1E,0x33,0x33,0x33,0x1E,0x00},
|
||||
{0x00,0x00,0x3B,0x66,0x66,0x3E,0x06,0x0F},{0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x78},
|
||||
{0x00,0x00,0x3B,0x6E,0x66,0x06,0x0F,0x00},{0x00,0x00,0x3E,0x03,0x1E,0x30,0x1F,0x00},
|
||||
{0x08,0x0C,0x3E,0x0C,0x0C,0x2C,0x18,0x00},{0x00,0x00,0x33,0x33,0x33,0x33,0x6E,0x00},
|
||||
{0x00,0x00,0x33,0x33,0x33,0x1E,0x0C,0x00},{0x00,0x00,0x63,0x6B,0x7F,0x7F,0x36,0x00},
|
||||
{0x00,0x00,0x63,0x36,0x1C,0x36,0x63,0x00},{0x00,0x00,0x33,0x33,0x33,0x3E,0x30,0x1F},
|
||||
{0x00,0x00,0x3F,0x19,0x0C,0x26,0x3F,0x00},{0x38,0x0C,0x0C,0x07,0x0C,0x0C,0x38,0x00},
|
||||
{0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00},{0x07,0x0C,0x0C,0x38,0x0C,0x0C,0x07,0x00},
|
||||
{0x6E,0x3B,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
};
|
||||
|
||||
static uint16_t* s_boot_fb = nullptr; // 480x800 RGB565 portrait framebuffer (PSRAM)
|
||||
static int s_boot_y = 0; // next text row, in landscape coords
|
||||
static char s_boot_cur[48] = {0}; // the in-progress line, so the animator can append dots to it
|
||||
static int s_boot_cur_y = 0;
|
||||
static volatile bool s_boot_anim_run = false;
|
||||
static SemaphoreHandle_t s_boot_lock = nullptr; // guards s_boot_fb + the panel between threads
|
||||
|
||||
// Map one logical (landscape) pixel into the portrait panel buffer. The on-device
|
||||
// result showed the ROT_270 formula was 180° off, so use its opposite (ROT_90):
|
||||
// phys_x = (PW-1) - ly, phys_y = lx.
|
||||
static inline void bootPx(int lx, int ly, uint16_t c) {
|
||||
if ((unsigned)lx >= BOOT_LW || (unsigned)ly >= BOOT_LH) return;
|
||||
s_boot_fb[lx * BOOT_PW + (BOOT_PW - 1 - ly)] = c;
|
||||
}
|
||||
|
||||
static void bootDrawText(int x, int y, const char* msg, uint16_t col, int scale) {
|
||||
for (const char* p = msg; *p; p++) {
|
||||
char ch = *p;
|
||||
if (ch < 0x20 || ch > 0x7E) ch = ' ';
|
||||
const uint8_t* g = kBootFont[ch - 0x20];
|
||||
for (int gy = 0; gy < 8; gy++)
|
||||
for (int gx = 0; gx < 8; gx++)
|
||||
if (g[gy] & (1 << gx))
|
||||
for (int sy = 0; sy < scale; sy++)
|
||||
for (int sx = 0; sx < scale; sx++)
|
||||
bootPx(x + gx * scale + sx, y + gy * scale + sy, col);
|
||||
x += 8 * scale;
|
||||
if (x + 8 * scale > BOOT_LW) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Push the whole framebuffer to the panel in horizontal bands. A single full-frame
|
||||
// (480x800) blit only showed partially on-device — the proven LVGL flush path always
|
||||
// writes sub-full bands, so do the same here.
|
||||
static void bootBlit() {
|
||||
if (!s_boot_fb) return;
|
||||
const int BAND = 40; // match the proven LVGL flush height (draw buf is 800x24 px -> <=480x40 portrait)
|
||||
for (int y = 0; y < BOOT_PH; y += BAND) {
|
||||
int h = (y + BAND > BOOT_PH) ? (BOOT_PH - y) : BAND;
|
||||
bsp_display_blit(0, y, BOOT_PW, y + h, s_boot_fb + (size_t)y * BOOT_PW);
|
||||
}
|
||||
}
|
||||
|
||||
// Redraw the trailing "..." on the in-progress line with `ndots` dots, then blit only the few
|
||||
// rows those dots occupy. Called from the animator (and to finalize a line at 3 dots).
|
||||
static void bootDrawDots(int ndots) {
|
||||
if (!s_boot_fb || !s_boot_cur[0]) return;
|
||||
const int S = 2;
|
||||
const int dotstart = 16 + (int)strlen(s_boot_cur) * 8 * S; // lx just past the text
|
||||
const int zonew = 4 * 8 * S; // room for up to 4 dots
|
||||
for (int lx = dotstart; lx < dotstart + zonew && lx < BOOT_LW; lx++) // clear the dot zone
|
||||
for (int ly = s_boot_cur_y; ly < s_boot_cur_y + 8 * S; ly++)
|
||||
bootPx(lx, ly, 0x0000);
|
||||
if (ndots > 4) ndots = 4;
|
||||
char d[8]; int j = 0; for (int k = 0; k < ndots; k++) d[j++] = '.'; d[j] = 0;
|
||||
if (ndots > 0) bootDrawText(dotstart, s_boot_cur_y, d, BOOT_FG, S);
|
||||
int r0 = dotstart - (dotstart % 40), r1 = dotstart + zonew; // lx maps to fb rows (py)
|
||||
if (r1 > BOOT_PH) r1 = BOOT_PH;
|
||||
for (int y = r0; y < r1; y += 40) {
|
||||
int h = (y + 40 > BOOT_PH) ? (BOOT_PH - y) : 40;
|
||||
bsp_display_blit(0, y, BOOT_PW, y + h, s_boot_fb + (size_t)y * BOOT_PW);
|
||||
}
|
||||
}
|
||||
|
||||
// Cycles ".", "..", "..." on the current line so a long phase (e.g. the slow C6 radio bring-up)
|
||||
// shows activity instead of looking frozen. Runs on core 1 while the boot thread blocks on core 0.
|
||||
static void bootAnimTask(void*) {
|
||||
int phase = 0;
|
||||
while (s_boot_anim_run) {
|
||||
if (s_boot_lock && xSemaphoreTake(s_boot_lock, pdMS_TO_TICKS(60)) == pdTRUE) {
|
||||
if (s_boot_anim_run && s_boot_fb) bootDrawDots((phase % 3) + 1);
|
||||
xSemaphoreGive(s_boot_lock);
|
||||
}
|
||||
phase++;
|
||||
vTaskDelay(pdMS_TO_TICKS(280));
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void bootSplashBegin() {
|
||||
s_boot_fb = (uint16_t*)heap_caps_malloc(BOOT_PW * BOOT_PH * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (!s_boot_fb) return;
|
||||
size_t hh = 0, vv = 0; bsp_display_color_format_t cf = (bsp_display_color_format_t)0;
|
||||
bsp_display_endianness_t en = (bsp_display_endianness_t)0;
|
||||
bsp_display_get_parameters(&hh, &vv, &cf, &en);
|
||||
printf("[BOOT] panel %ux%u fmt=%d endian=%d\n", (unsigned)hh, (unsigned)vv, (int)cf, (int)en);
|
||||
for (int i = 0; i < BOOT_PW * BOOT_PH; i++) s_boot_fb[i] = 0x0000; // black
|
||||
bootDrawText(16, 28, "WADAMESH", BOOT_FG, 3); // caps wordmark
|
||||
bootDrawText(16, 64, "============", BOOT_FG, 2); // terminal rule under it
|
||||
s_boot_y = 100;
|
||||
bootBlit();
|
||||
s_boot_lock = xSemaphoreCreateMutex();
|
||||
s_boot_anim_run = true;
|
||||
xTaskCreatePinnedToCore(bootAnimTask, "bootanim", 3072, NULL, 1, NULL, 1);
|
||||
}
|
||||
|
||||
// Start a new boot-progress line ("> MSG"), finalizing the previous one at a steady "...". The
|
||||
// animator then cycles dots on this line until the next bootLog().
|
||||
static void bootLog(const char* msg) {
|
||||
Serial.printf("[BOOT] %s\n", msg);
|
||||
printf("[BOOT] %s\n", msg); // Serial is swallowed on the IDF console
|
||||
if (!s_boot_fb) return;
|
||||
if (s_boot_lock) xSemaphoreTake(s_boot_lock, portMAX_DELAY);
|
||||
bootDrawDots(3); // freeze the previous line at "..."
|
||||
char line[48];
|
||||
int j = 0; line[j++] = '>'; line[j++] = ' ';
|
||||
for (const char* p = msg; *p && j < (int)sizeof(line) - 1; p++) {
|
||||
char c = *p; if (c >= 'a' && c <= 'z') c -= 32; // uppercase for the boot-ROM look
|
||||
line[j++] = c;
|
||||
}
|
||||
line[j] = 0;
|
||||
const int S = 2, LH = 8 * S + 6;
|
||||
strncpy(s_boot_cur, line, sizeof(s_boot_cur) - 1); s_boot_cur[sizeof(s_boot_cur) - 1] = 0;
|
||||
s_boot_cur_y = s_boot_y;
|
||||
bootDrawText(16, s_boot_cur_y, line, BOOT_FG, S);
|
||||
s_boot_y += LH;
|
||||
if (s_boot_y + LH > BOOT_LH) s_boot_y = 100;
|
||||
bootBlit();
|
||||
if (s_boot_lock) xSemaphoreGive(s_boot_lock);
|
||||
}
|
||||
|
||||
static void bootSplashEnd() {
|
||||
s_boot_anim_run = false;
|
||||
vTaskDelay(pdMS_TO_TICKS(320)); // let the animator observe the flag + self-delete
|
||||
if (s_boot_lock) xSemaphoreTake(s_boot_lock, portMAX_DELAY);
|
||||
if (s_boot_fb) {
|
||||
// Clean black handoff so no green text lingers; LVGL is then forced to repaint (see app setup).
|
||||
for (int i = 0; i < BOOT_PW * BOOT_PH; i++) s_boot_fb[i] = 0x0000;
|
||||
bootBlit();
|
||||
heap_caps_free(s_boot_fb);
|
||||
s_boot_fb = nullptr;
|
||||
}
|
||||
if (s_boot_lock) xSemaphoreGive(s_boot_lock);
|
||||
}
|
||||
|
||||
// Bring up the esp-hosted SDIO link to the ESP32-C6 radio co-processor that BOTH LoRa and WiFi ride.
|
||||
//
|
||||
// esp_hosted is force-initialized before app_main by an unconditional __attribute__((constructor))
|
||||
// in esp_hosted (port_esp_hosted_host_init.c) — there's no Kconfig to disable it. That pre-init is
|
||||
// what made Arduino WiFi impossible: arduino's WiFi.mode -> hostedInit() calls esp_hosted_sdio_set_config(),
|
||||
// which returns ESP_ERR_NOT_ALLOWED once esp_hosted is already configured, and this arduino build treats
|
||||
// that as fatal -> esp_wifi_init() never runs (WiFi stuck at WIFI_NOT_INIT). And nobody ever called
|
||||
// connect_to_slave(), so LoRa timed out too.
|
||||
//
|
||||
// LoRa fix: call connect_to_slave() (esp_hosted is already inited by the constructor) — that
|
||||
// identifies the C6 slave so the LoRa custom-RPC channel works. WiFi needs more: arduino's WiFi.mode
|
||||
// init bails because esp_hosted is pre-inited (esp_hosted_sdio_set_config -> ESP_ERR_NOT_ALLOWED).
|
||||
// The code-only deinit workaround crashes (esp_hosted_deinit heap bug), so WiFi stays blocked pending
|
||||
// a one-line arduino-core patch (esp32-hal-hosted.c:326 tolerate NOT_ALLOWED).
|
||||
static void hostedConnectC6() {
|
||||
esp_hosted_init(); // no-op (constructor already did it)
|
||||
int rc = -1;
|
||||
for (int i = 0; i < 4 && rc != 0; i++) {
|
||||
rc = esp_hosted_connect_to_slave();
|
||||
if (rc != 0) { Serial.printf("[BOOT] C6 connect try %d -> %d\n", i, rc); delay(250); }
|
||||
}
|
||||
Serial.printf("[BOOT] C6 hosted link: %s\n", rc == 0 ? "UP" : "FAILED");
|
||||
}
|
||||
|
||||
static void wadameshSetup() {
|
||||
Serial.begin(115200);
|
||||
delay(150);
|
||||
Serial.println("[BOOT] wadamesh / tanmatsu");
|
||||
|
||||
// badge-bsp: panel + input + power. The panel must be up before LVGL flushes to it; ask for
|
||||
// RGB565 to match LVGL's 16-bit color depth (lvglFlush -> writePixelsRGB565 -> bsp_display_blit).
|
||||
const bsp_configuration_t bspcfg = {
|
||||
.display = { .requested_color_format = BSP_DISPLAY_COLOR_FORMAT_16_565RGB, .num_fbs = 1 },
|
||||
};
|
||||
if (bsp_device_initialize(&bspcfg) != ESP_OK) Serial.println("[BOOT] bsp_device_initialize FAILED");
|
||||
|
||||
bootSplashBegin(); // panel is up — start the on-screen boot log
|
||||
|
||||
board.begin();
|
||||
// Bring up lwIP/tcpip + the default event loop before any WiFi/netif call. On the S3 boards
|
||||
// Arduino's WiFi path does this implicitly; on the P4/esp_wifi_remote it isn't, so WiFi.mode()
|
||||
// / WiFiServer would hit lwIP with no tcpip mbox and assert. Both are idempotent.
|
||||
esp_netif_init();
|
||||
esp_event_loop_create_default();
|
||||
bootLog("C6 radio coproc");
|
||||
hostedConnectC6(); // identify the C6 slave so WiFi + LoRa work (must precede radio_init)
|
||||
bootLog("Radio link");
|
||||
if (!radio_init()) { bootLog("radio init FAILED"); Serial.println("[BOOT] radio_init FAILED"); }
|
||||
|
||||
DisplayDriver* disp = &display;
|
||||
|
||||
// Persistence: point DataStore at a writable directory the launcher has already mounted (see
|
||||
// discoverPersistMount + the PersistFS globals). DataStore (profile name, NodePrefs, contacts,
|
||||
// channels, chat history) is otherwise lost on reboot; NVS-backed stores (Wi-Fi creds, TouchPrefs)
|
||||
// persist independently. FAT is hierarchical (unlike the flat SPIFFS the other boards use), so the
|
||||
// subdirectories DataStore writes into must exist first — DataStore::begin() makes /bl, but on
|
||||
// ESP32 it never calls identity_store.begin() (RP2040-only), so /identity must be created here or
|
||||
// the node keypair can't be saved and a fresh identity is generated every boot.
|
||||
bootLog("Storage");
|
||||
// An AppFS app boots fresh, so enumerate the partitions we actually see, then mount the internal
|
||||
// 'locfd' FAT partition with FFat. printf (NOT Serial — Arduino Serial is swallowed on this board's
|
||||
// IDF console) so the result lands in the serial monitor.
|
||||
printf("[storage] partitions visible to the app:\n");
|
||||
esp_partition_iterator_t pit = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
|
||||
for (; pit != NULL; pit = esp_partition_next(pit)) {
|
||||
const esp_partition_t* pp = esp_partition_get(pit);
|
||||
printf("[storage] %-10s type=%d sub=%d off=0x%06lx size=0x%06lx\n",
|
||||
pp->label, (int)pp->type, (int)pp->subtype, (unsigned long)pp->address, (unsigned long)pp->size);
|
||||
}
|
||||
if (pit) esp_partition_iterator_release(pit);
|
||||
g_fs_ok = FFat.begin(true, "/ffat", 10, "locfd");
|
||||
printf("[storage] FFat.begin(locfd) = %s\n", g_fs_ok ? "OK" : "FAILED");
|
||||
if (g_fs_ok) {
|
||||
printf("[storage] FFat %u KB used / %u KB total\n",
|
||||
(unsigned)(FFat.usedBytes() / 1024), (unsigned)(FFat.totalBytes() / 1024));
|
||||
// FAT is hierarchical: DataStore::begin() makes /bl, but on ESP32 it never calls
|
||||
// identity_store.begin() (RP2040-only), so /identity is missing — without it the node keypair
|
||||
// can't be saved and a fresh identity is generated every boot.
|
||||
FFat.mkdir("/identity");
|
||||
FFat.mkdir("/bl");
|
||||
} else {
|
||||
printf("[storage] persistence disabled this boot (DataStore opens fail gracefully)\n");
|
||||
}
|
||||
store.begin();
|
||||
|
||||
bootLog("Mesh stack");
|
||||
the_mesh.begin(disp != NULL);
|
||||
|
||||
bootLog("Companion interface");
|
||||
serial_interface.begin(Serial, TCP_PORT, WS_PORT);
|
||||
serial_interface.setBroadcastResponses(true);
|
||||
the_mesh.startInterface(serial_interface);
|
||||
|
||||
#if defined(BLE_PIN_CODE)
|
||||
// BLE companion over the C6: bring up the remote BLE controller (esp-hosted), then the NimBLE host
|
||||
// (esp-nimble-cpp). hostedInitBLE() is arduino's controller bring-up; our build.sh patch lets its
|
||||
// hostedInit() tolerate the already-configured esp_hosted (from hostedConnectC6()).
|
||||
bootLog("Bluetooth");
|
||||
if (hostedInitBLE()) {
|
||||
char* nm = the_mesh.getNodePrefs()->node_name;
|
||||
serial_interface.prepareBle("wadamesh-", nm, the_mesh.getBLEPin());
|
||||
if (wifiConfigGetBleEnabled())
|
||||
serial_interface.beginBle("wadamesh-", nm, the_mesh.getBLEPin());
|
||||
} else {
|
||||
Serial.println("[BOOT] hostedInitBLE FAILED");
|
||||
}
|
||||
#endif
|
||||
|
||||
bootLog("Sensors");
|
||||
sensors.begin();
|
||||
bootLog("Building UI");
|
||||
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs());
|
||||
board.onBootComplete();
|
||||
bootLog("Ready");
|
||||
bootSplashEnd(); // stop the animator + clean black handoff
|
||||
|
||||
// The splash wrote straight to the panel framebuffer behind LVGL's back, so LVGL still believes
|
||||
// the screen is unchanged and would leave our black fill on top of the UI. Invalidate every layer
|
||||
// so the first lv_timer_handler() repaints the whole UI over the handoff.
|
||||
if (lv_disp_t* d = lv_disp_get_default()) {
|
||||
lv_obj_invalidate(lv_disp_get_scr_act(d));
|
||||
lv_obj_invalidate(lv_disp_get_layer_top(d));
|
||||
lv_obj_invalidate(lv_disp_get_layer_sys(d));
|
||||
}
|
||||
Serial.println("[BOOT] setup done");
|
||||
}
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
initArduino();
|
||||
wadameshSetup();
|
||||
|
||||
// WiFi state machine + SNTP + TCP companion server. On the S3 boards this lives in
|
||||
// src/main.cpp's loop() — which the Tanmatsu build excludes — so without it the UI's
|
||||
// wifiConfigRequestApply() is never consumed and WiFi.begin() never runs (radio sits in
|
||||
// "connecting" forever). Ported faithfully here.
|
||||
bool wifi_started = false, wifi_radio_prev = true, wifi_radio_inited = false;
|
||||
bool sntp_kicked = false, sntp_pushed = false, modem_sleep_set = false;
|
||||
uint32_t last_wifi_retry_ms = 0, sntp_kick_ms = 0;
|
||||
const uint32_t WIFI_RETRY_INTERVAL_MS = 10000;
|
||||
|
||||
for (;;) {
|
||||
ui_task.loop(); // UI first (splash/flush)
|
||||
|
||||
bool wifi_radio_en = wifiConfigWantsWifi();
|
||||
if (!wifi_radio_inited) { wifi_radio_inited = true; wifi_radio_prev = wifi_radio_en; }
|
||||
else if (wifi_radio_en != wifi_radio_prev) {
|
||||
wifi_radio_prev = wifi_radio_en;
|
||||
if (!wifi_radio_en) { WiFi.disconnect(true); delay(50); WiFi.mode(WIFI_OFF); }
|
||||
wifi_started = false;
|
||||
}
|
||||
if (wifiConfigConsumeApplyRequest()) { // UI changed SSID/PWD or toggled the radio
|
||||
if (wifi_started) {
|
||||
if (!wifi_radio_en) { WiFi.disconnect(true); delay(50); WiFi.mode(WIFI_OFF); }
|
||||
else { WiFi.disconnect(false, false); delay(50); }
|
||||
}
|
||||
wifi_started = false; last_wifi_retry_ms = 0;
|
||||
}
|
||||
if (wifi_radio_en) {
|
||||
if (!wifi_started) {
|
||||
wifi_started = true;
|
||||
WiFi.mode(WIFI_STA);
|
||||
if (wifiConfigHasRuntime()) {
|
||||
char ssid[WIFI_CONFIG_SSID_MAX], pwd[WIFI_CONFIG_PWD_MAX];
|
||||
wifiConfigGetSsid(ssid, sizeof(ssid)); wifiConfigGetPwd(pwd, sizeof(pwd));
|
||||
if (strlen(ssid) > 0) { WiFi.begin(ssid, pwd[0] ? pwd : nullptr); last_wifi_retry_ms = millis(); }
|
||||
}
|
||||
}
|
||||
if (wifiConfigHasRuntime() && WiFi.status() != WL_CONNECTED) { // periodic reconnect
|
||||
uint32_t now = millis();
|
||||
if ((uint32_t)(now - last_wifi_retry_ms) >= WIFI_RETRY_INTERVAL_MS) {
|
||||
last_wifi_retry_ms = now;
|
||||
char ssid[WIFI_CONFIG_SSID_MAX], pwd[WIFI_CONFIG_PWD_MAX];
|
||||
wifiConfigGetSsid(ssid, sizeof(ssid)); wifiConfigGetPwd(pwd, sizeof(pwd));
|
||||
if (strlen(ssid) > 0) { WiFi.disconnect(false, true); WiFi.begin(ssid, pwd[0] ? pwd : nullptr); }
|
||||
}
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
if (!modem_sleep_set) { WiFi.setSleep(true); modem_sleep_set = true; }
|
||||
if (!sntp_kicked) {
|
||||
char tz[48]; touchPrefsBuildLocalTz(tz, sizeof tz);
|
||||
configTzTime(tz, "pool.ntp.org", "time.google.com");
|
||||
sntp_kicked = true; sntp_kick_ms = millis();
|
||||
} else if (!sntp_pushed && (uint32_t)(millis() - sntp_kick_ms) >= 1500) {
|
||||
time_t t = time(nullptr);
|
||||
if (t > 1700000000) { rtc_clock.setCurrentTime((uint32_t)t); sntp_pushed = true; }
|
||||
}
|
||||
} else if (sntp_kicked && !sntp_pushed) { sntp_kicked = false; }
|
||||
}
|
||||
// (No TCP/WS companion server on the Tanmatsu — the companion app connects over USB serial.
|
||||
// Starting WiFiServer here asserted in lwIP when the STA wasn't associated yet.)
|
||||
|
||||
the_mesh.loop(); // mesh + companion servers
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x19000, 16K,
|
||||
otadata, data, ota, 0x1D000, 8K,
|
||||
phy_init, data, phy, 0x1F000, 4K,
|
||||
ota_0, 0, ota_0, 0x20000, 2048K,
|
||||
ota_1, 0, ota_1, 0x220000, 2048K,
|
||||
appfs, 0x43, 3, 0x420000, 8192K,
|
||||
locfd, data, fat, 0xC20000, 3968K,
|
||||
|
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 16K,
|
||||
otadata, data, ota, 0xD000, 8K,
|
||||
phy_init, data, phy, 0xF000, 4K,
|
||||
ota_0, 0, ota_0, 0x10000, 2048K,
|
||||
ota_1, 0, ota_1, 0x210000, 2048K,
|
||||
locfd, data, fat, 0x410000, 4032K,
|
||||
|
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 16K,
|
||||
otadata, data, ota, 0xD000, 8K,
|
||||
phy_init, data, phy, 0xF000, 4K,
|
||||
ota_0, 0, ota_0, 0x10000, 2048K,
|
||||
ota_1, 0, ota_1, 0x210000, 2048K,
|
||||
appfs, 0x43, 3, 0x410000, 3008K,
|
||||
locfd, data, fat, 0x700000, 1024K,
|
||||
|
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name Type SubType Offset Size
|
||||
nvs, data, nvs, 0x9000, 16K,
|
||||
otadata, data, ota, 0xD000, 8K,
|
||||
phy_init, data, phy, 0xF000, 4K,
|
||||
ota_0, 0, ota_0, 0x10000, 2048K,
|
||||
ota_1, 0, ota_1, 0x210000, 2048K,
|
||||
appfs, 0x43, 3, 0x410000, 6000K,
|
||||
zb_storage, data, fat, 0x9EC000, 16K,
|
||||
zb_fct, data, fat, 0x9F0000, 4K,
|
||||
locfd, data, fat, 0x9F1000, 6204K,
|
||||
|
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 16K,
|
||||
otadata, data, ota, 0xD000, 8K,
|
||||
phy_init, data, phy, 0xF000, 4K,
|
||||
ota_0, 0, ota_0, 0x10000, 1600K,
|
||||
ota_1, 0, ota_1, 0x1A0000, 1600K,
|
||||
appfs, 0x43, 3, 0x330000, 8000K,
|
||||
locfd, data, fat, 0xB00000, 5120K,
|
||||
|
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
CONFIG_BSP_TARGET_ESP32_P4_FUNCTION_EV_BOARD=y
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/16M.csv"
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_UART=n
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=y
|
||||
CONFIG_ESP_WIFI_RX_IRAM_OPT=y
|
||||
CONFIG_ESP_HOSTED_ENABLED=y
|
||||
CONFIG_ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARY=y
|
||||
CONFIG_ESP_HOSTED_HOST_RESTART_NO_COMMUNICATION_WITH_SLAVE=n
|
||||
CONFIG_TINYUSB_RHPORT_FS=y
|
||||
CONFIG_TINYUSB_VENDOR_COUNT=1
|
||||
CONFIG_CUSTOM_CA_TANMATSU_APPS=y
|
||||
CONFIG_CUSTOM_CA_TANMATSU_OTA=y
|
||||
CONFIG_LCD_DSI_ISR_IRAM_SAFE=y
|
||||
CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y
|
||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536
|
||||
CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_UART=n
|
||||
@@ -0,0 +1,16 @@
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_ERROR=y
|
||||
CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC=y
|
||||
CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC_SIZE=0x100
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_FATFS_LFN_HEAP=y
|
||||
CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED=y
|
||||
CONFIG_CUSTOM_CA_LETSENCRYPT_X1=y
|
||||
CONFIG_CUSTOM_CA_LETSENCRYPT_X2=y
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
CONFIG_ESP_PANIC_HANDLER_IRAM=y
|
||||
CONFIG_ESP_DEBUG_STUBS_ENABLE=y
|
||||
CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=n
|
||||
CONFIG_ESP_WIFI_SLP_IRAM_OPT=n
|
||||
CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=n
|
||||
@@ -0,0 +1,31 @@
|
||||
CONFIG_IDF_TARGET="esp32c6"
|
||||
CONFIG_BSP_TARGET_HACKERHOTEL_2024=y
|
||||
CONFIG_BSP_LVGL_DSI_DISPLAY=y
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/hackerhotel2024.csv"
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_LV_CACHE_DEF_SIZE=1280000
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_UART=n
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=y
|
||||
CONFIG_ESP_WIFI_RX_IRAM_OPT=y
|
||||
CONFIG_BSP_LVGL_DSI_DISPLAY=y
|
||||
CONFIG_ESP_SDIO_PIN_CMD=16
|
||||
CONFIG_ESP_SDIO_PIN_CLK=17
|
||||
CONFIG_ESP_SDIO_PIN_D0=18
|
||||
CONFIG_ESP_SDIO_PIN_D1=19
|
||||
CONFIG_ESP_SDIO_PIN_D2=20
|
||||
CONFIG_ESP_SDIO_PIN_D3=21
|
||||
CONFIG_ESP_SDIO_GPIO_RESET_SLAVE=-1
|
||||
CONFIG_ESP_ENABLE_BT=y
|
||||
CONFIG_TINYUSB_RHPORT_FS=y
|
||||
CONFIG_TINYUSB_VENDOR_COUNT=1
|
||||
CONFIG_CUSTOM_CA_TANMATSU_APPS=y
|
||||
CONFIG_CUSTOM_CA_TANMATSU_OTA=y
|
||||
CONFIG_LCD_DSI_ISR_IRAM_SAFE=y
|
||||
@@ -0,0 +1,6 @@
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/esp32s3-8mb-noappfs.csv"
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
||||
CONFIG_BSP_TARGET_HELTECV3=y
|
||||
@@ -0,0 +1,12 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_BSP_TARGET_KAMI=y
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/16M.csv"
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
CONFIG_LV_DPI_DEF=176
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=n
|
||||
CONFIG_CUSTOM_CA_TANMATSU_APPS=y
|
||||
CONFIG_CUSTOM_CA_TANMATSU_OTA=y
|
||||
@@ -0,0 +1,39 @@
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
CONFIG_BSP_TARGET_KONSOOL=y
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/16M.csv"
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_UART=n
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=y
|
||||
CONFIG_ESP_WIFI_RX_IRAM_OPT=y
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=16
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=17
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=18
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=19
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=20
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=21
|
||||
CONFIG_ESP_HOSTED_SDIO_RESET_SLAVE_USING_CALLBACK=y
|
||||
CONFIG_ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARY=y
|
||||
CONFIG_ESP_HOSTED_HOST_RESTART_NO_COMMUNICATION_WITH_SLAVE=n
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
|
||||
CONFIG_CUSTOM_CA_TANMATSU_APPS=y
|
||||
CONFIG_CUSTOM_CA_TANMATSU_OTA=y
|
||||
CONFIG_LCD_DSI_ISR_IRAM_SAFE=y
|
||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_PM_DFS_INIT_AUTO=y
|
||||
CONFIG_FATFS_VOLUME_COUNT=3
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=8192
|
||||
CONFIG_FATFS_USE_FASTSEEK=y
|
||||
CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE=64
|
||||
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=n
|
||||
CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y
|
||||
CONFIG_USB_HOST_HUBS_SUPPORTED=y
|
||||
CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=n
|
||||
@@ -0,0 +1,13 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_BSP_TARGET_MCH2022=y
|
||||
CONFIG_BSP_LVGL_BIG_ENDIAN_COLOUR=y
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/mch2022.csv"
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
CONFIG_LV_DPI_DEF=176
|
||||
CONFIG_CUSTOM_CA_MCH2022_OTA=y
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=n
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
@@ -0,0 +1,39 @@
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
CONFIG_BSP_TARGET_TANMATSU=y
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/16M.csv"
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_UART=n
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=y
|
||||
CONFIG_ESP_WIFI_RX_IRAM_OPT=y
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=16
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=17
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=18
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=19
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=20
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=21
|
||||
CONFIG_ESP_HOSTED_SDIO_RESET_SLAVE_USING_CALLBACK=y
|
||||
CONFIG_ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARY=y
|
||||
CONFIG_ESP_HOSTED_HOST_RESTART_NO_COMMUNICATION_WITH_SLAVE=n
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
|
||||
CONFIG_CUSTOM_CA_TANMATSU_APPS=y
|
||||
CONFIG_CUSTOM_CA_TANMATSU_OTA=y
|
||||
CONFIG_LCD_DSI_ISR_IRAM_SAFE=y
|
||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_PM_DFS_INIT_AUTO=y
|
||||
CONFIG_FATFS_VOLUME_COUNT=3
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=8192
|
||||
CONFIG_FATFS_USE_FASTSEEK=y
|
||||
CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE=64
|
||||
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=n
|
||||
CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y
|
||||
CONFIG_USB_HOST_HUBS_SUPPORTED=y
|
||||
CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=n
|
||||
@@ -0,0 +1,27 @@
|
||||
# wadamesh-specific sdkconfig overlay, layered on top of sdkconfigs/general + sdkconfigs/tanmatsu.
|
||||
# Keep badge.team's files pristine; put all our overrides here.
|
||||
|
||||
# arduino-esp32 (as an IDF component) hard-requires a 1000 Hz FreeRTOS tick.
|
||||
# badge.team defaults the Tanmatsu to 100 Hz; 1000 Hz matches what the S3 wadamesh
|
||||
# boards already run, so this is the correct value for our app.
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
# arduino-esp32's selective-compilation Kconfig doesn't actually filter in this setup, so we
|
||||
# turn off the FEATURES whose arduino libraries don't build against IDF 5.5.1 instead. First: BT.
|
||||
# The Tanmatsu has no native BLE (BT is hosted over the C6); arduino-esp32 3.3.10's bundled BLE
|
||||
# source won't compile against IDF 5.5.1's NimBLE (ble_gap_read_local_irk was renamed), and its
|
||||
# guard is (SOC_BLE_SUPPORTED || ESP_HOSTED_ENABLE_BT_NIMBLE) && (BLUEDROID || NIMBLE_ENABLED).
|
||||
# We don't use BLE yet (companion link = USB/LoRa), so disable the whole BT chain → arduino's
|
||||
# BLE/SimpleBLE/BluetoothSerial compile to nothing. Revisit hosted BLE for the phone app later.
|
||||
# BLE companion over the C6 (re-enabled 2026-06-21): NimBLE host on the P4 + the C6 as the BLE
|
||||
# controller via esp-hosted. Our SerialBLEInterface uses esp-nimble-cpp (IDF-host NimBLE C++). If
|
||||
# arduino-esp32's bundled Bluedroid BLE library breaks the build, it's excluded separately — it's
|
||||
# guarded on CONFIG_BT_BLUEDROID_ENABLED, which we leave OFF (we use NimBLE).
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
# AsyncTCP (pulled by the core's ESP32Board HTTP-OTA) references CONFIG_ESP_TASK_WDT_TIMEOUT_S,
|
||||
# which IDF 5.5 only defines when the task watchdog is initialised. Enable it (5 s timeout).
|
||||
CONFIG_ESP_TASK_WDT_INIT=y
|
||||
CONFIG_ESP_TASK_WDT_TIMEOUT_S=5
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "helpers/ESP32Board.h"
|
||||
extern "C" {
|
||||
#include "bsp/power.h" // badge-bsp battery/charger (no raw ADC pin on the Tanmatsu)
|
||||
}
|
||||
|
||||
// Minimal board for the Tanmatsu. ESP32Board (-> mesh::MainBoard) gives the common
|
||||
// ESP32/Arduino behaviour (millis, deep-sleep scaffolding, etc.). Battery/power and
|
||||
// any board init come from the badge-bsp components, not raw ADC pins like the S3
|
||||
// boards — left as TODO(device) until the hardware is in hand.
|
||||
class TanmatsuBoard : public ESP32Board {
|
||||
public:
|
||||
void begin() {
|
||||
ESP32Board::begin();
|
||||
// TODO(device): bsp_device_initialize() (display/input/power/led) is normally
|
||||
// done once in the variant's startup; decide whether it lives here or in main.
|
||||
}
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
// Battery comes from the CH32 power coprocessor via badge-bsp, not an ADC pin. The UI turns
|
||||
// this mV into a % (and "?" when it's 0), so returning a real reading fixes the "?".
|
||||
uint16_t mv = 0;
|
||||
if (bsp_power_get_battery_voltage(&mv) != ESP_OK) return 0;
|
||||
return mv;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Tanmatsu";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
// badge-bsp headers are plain C — wrap for C++ (no extern "C" guards in them).
|
||||
extern "C" {
|
||||
#include "bsp/display.h"
|
||||
#include "bsp/input.h"
|
||||
}
|
||||
|
||||
// DisplayDriver backed by the Tanmatsu's badge-bsp MIPI panel (800x480 RGB565).
|
||||
//
|
||||
// The touch UI renders with LVGL; UITask's lvglFlush() calls writePixelsRGB565(), which
|
||||
// blits the dirty area straight to the bsp panel — that's the hot path and the only part
|
||||
// that must be correct for the UI to show. The classic DisplayDriver text/shape methods are
|
||||
// used only for the pre-LVGL boot screen + the "Shutting down…" screen; they're minimal
|
||||
// stubs for now (TODO(device): a tiny font renderer if we want those splashes back).
|
||||
class TanmatsuDisplay : public DisplayDriver {
|
||||
bool _on = true;
|
||||
public:
|
||||
TanmatsuDisplay() : DisplayDriver(800, 480) {}
|
||||
|
||||
// Match the ST7789LCDDisplay surface UITask/main.cpp call on the global `display`:
|
||||
void writePixelsRGB565(int x, int y, int w, int h, const uint16_t* pixels) {
|
||||
// NOTE: bsp_display_blit's params are (x_start, y_start, x_end, y_end) despite the public
|
||||
// header naming them width/height — the Tanmatsu impl forwards them straight to
|
||||
// esp_lcd_panel_draw_bitmap. Pass exclusive end coords, NOT w/h, or partial flushes fail
|
||||
// with "start position must be smaller than end position" and the panel shows garbage.
|
||||
bsp_display_blit((size_t)x, (size_t)y, (size_t)(x + w), (size_t)(y + h), pixels);
|
||||
}
|
||||
void setDisplayRotation(int rot) { (void)rot; /* TODO(device): bsp panel rotation */ }
|
||||
void startFrame() { /* boot-screen no-op; LVGL owns the frame */ }
|
||||
void endFrame() {}
|
||||
|
||||
// --- DisplayDriver contract (minimal; LVGL does the real drawing) ---
|
||||
bool isOn() override { return _on; }
|
||||
void turnOn() override { _on = true; bsp_input_set_backlight_brightness(100); }
|
||||
void turnOff() override { _on = false; bsp_input_set_backlight_brightness(0); }
|
||||
void clear() override {}
|
||||
void startFrame(Color) override {}
|
||||
void setTextSize(int) override {}
|
||||
void setColor(Color) override {}
|
||||
void setCursor(int, int) override {}
|
||||
void print(const char*) override {}
|
||||
void fillRect(int, int, int, int) override {}
|
||||
void drawRect(int, int, int, int) override {}
|
||||
void drawXbm(int, int, const uint8_t*, int, int) override {}
|
||||
uint16_t getTextWidth(const char*) override { return 0; }
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// TanmatsuLoraRadio implementation — see TanmatsuLoraRadio.h.
|
||||
// TODO(device) markers = needs confirming/tuning once the Tanmatsu is in hand.
|
||||
//
|
||||
#include "TanmatsuLoraRadio.h"
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <esp_random.h>
|
||||
|
||||
bool TanmatsuLoraRadio::init() {
|
||||
// The radio is on the coprocessor → "remote". 32 = RX packet-queue depth.
|
||||
if (lora_init_remote(&_h, 32) != ESP_OK) return false;
|
||||
_started = true;
|
||||
lora_set_config(&_h, &_cfg); // push whatever setParams()/setTxPower() captured
|
||||
return true;
|
||||
}
|
||||
|
||||
void TanmatsuLoraRadio::setParams(float freq_mhz, float bw_khz, uint8_t sf, uint8_t cr) {
|
||||
_cfg.frequency = (uint32_t)(freq_mhz * 1000000.0f);
|
||||
_cfg.bandwidth = (uint16_t)bw_khz; // confirmed kHz; valid {7,10,15,20,31,41,62,125,250,500} (62.5->62)
|
||||
_cfg.spreading_factor = sf;
|
||||
_cfg.coding_rate = cr; // confirmed: 5..8 -> 4/5..4/8 (matches RadioLib's cr)
|
||||
_cfg.sync_word = 0x12; // CONFIRMED: sx126x_set_sync_word expands 0x12 -> on-air
|
||||
// 0x1424 (ctrl bits 0x44) == RADIOLIB_SX126X_SYNC_WORD_PRIVATE
|
||||
_cfg.preamble_length = (sf <= 8) ? 32 : 16; // match MeshCore RadioLibWrapper::preambleLengthForSF(sf)
|
||||
_cfg.power = 22; // dBm; TODO(device): drive from region/prefs
|
||||
_cfg.crc_enabled = true;
|
||||
_cfg.invert_iq = false;
|
||||
pushConfig();
|
||||
}
|
||||
|
||||
void TanmatsuLoraRadio::setTxPower(int8_t dbm) {
|
||||
_cfg.power = (uint8_t)dbm; // TODO(device): confirm the component clamps to the SX1262 range
|
||||
pushConfig();
|
||||
}
|
||||
|
||||
void TanmatsuLoraRadio::setRxBoostedGainMode(bool on) {
|
||||
_rx_boost = on;
|
||||
_cfg.rx_boost = on;
|
||||
pushConfig();
|
||||
}
|
||||
|
||||
void TanmatsuLoraRadio::idle() {
|
||||
if (_started) lora_set_mode(&_h, LORA_PROTOCOL_MODE_STANDBY_RC);
|
||||
}
|
||||
|
||||
int TanmatsuLoraRadio::recvRaw(uint8_t* bytes, int sz) {
|
||||
if (!_started) return 0;
|
||||
if (!_rx_polling) {
|
||||
// First poll: init() + setParams() are settled now, so ARM continuous RX on the C6. Nothing
|
||||
// else does this — lora_set_config() leaves the modem in STANDBY, and begin()/onSendFinished()
|
||||
// were no-ops built on a false "auto-RX" assumption (that was the "TX works, nothing received"
|
||||
// bug). Once armed, the C6 re-arms after every RX packet and restores RX after every TX, so this
|
||||
// one-shot is enough. It also gates pushConfig from here on (the remote link blocks under RX load).
|
||||
_rx_polling = true;
|
||||
lora_set_mode(&_h, LORA_PROTOCOL_MODE_RX);
|
||||
}
|
||||
lora_protocol_lora_packet_t pkt;
|
||||
// Non-blocking poll of the RX queue (timeout 0).
|
||||
if (lora_receive_packet(&_h, &pkt, 0) != ESP_OK) return 0;
|
||||
int n = pkt.length;
|
||||
if (n > sz) n = sz;
|
||||
memcpy(bytes, pkt.data, n);
|
||||
// Confirmed against lora.c: stats = {rssi_pkt_raw u8, snr_pkt_raw i8, signal_rssi_pkt_raw u8};
|
||||
// rssi_dbm = -rssi_pkt_raw/2, snr_db = snr_pkt_raw/4.
|
||||
_last_rssi = pkt.stats.rssi_pkt_raw / -2.0f;
|
||||
_last_snr = pkt.stats.snr_pkt_raw / 4.0f;
|
||||
_n_recv++;
|
||||
return n;
|
||||
}
|
||||
|
||||
bool TanmatsuLoraRadio::startSendRaw(const uint8_t* bytes, int len) {
|
||||
if (!_started || len < 0 || len > 256) return false;
|
||||
lora_protocol_lora_packet_t pkt = {};
|
||||
pkt.length = (uint8_t)len;
|
||||
memcpy(pkt.data, bytes, len);
|
||||
if (lora_send_packet(&_h, &pkt) != ESP_OK) return false;
|
||||
_sending = true;
|
||||
_n_sent++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TanmatsuLoraRadio::isSendComplete() {
|
||||
// Confirmed against lora.c: lora_send_packet() blocks on the transaction semaphore
|
||||
// (waits up to ~2 s for the coprocessor TX), so TX is done on return — latch is correct.
|
||||
if (_sending) { _sending = false; return true; }
|
||||
return true;
|
||||
}
|
||||
|
||||
void TanmatsuLoraRadio::onSendFinished() { /* radio auto-returns to RX */ }
|
||||
|
||||
bool TanmatsuLoraRadio::isInRecvMode() const { return _started && !_sending; }
|
||||
|
||||
uint32_t TanmatsuLoraRadio::getEstAirtimeFor(int len_bytes) {
|
||||
// Standard Semtech LoRa time-on-air (ms) computed from the active PHY config,
|
||||
// since there's no RadioLib object here to call getTimeOnAir() on.
|
||||
const int sf = _cfg.spreading_factor ? _cfg.spreading_factor : 11;
|
||||
const float bw = (_cfg.bandwidth ? _cfg.bandwidth : 250) * 1000.0f; // Hz (TODO: confirm units)
|
||||
const int crDen = _cfg.coding_rate ? _cfg.coding_rate : 5; // 4/crDen (TODO: confirm 5..8)
|
||||
const int cr = (crDen > 4) ? (crDen - 4) : 1; // 1..4 for the formula
|
||||
const float tSym = (float)(1u << sf) / bw;
|
||||
const int de = (tSym > 0.016f) ? 1 : 0; // low-data-rate optimize
|
||||
const float preamble = _cfg.preamble_length ? _cfg.preamble_length : 16;
|
||||
const float tPreamble = (preamble + 4.25f) * tSym;
|
||||
const int num = 8 * len_bytes - 4 * sf + 28 + (_cfg.crc_enabled ? 16 : 0);
|
||||
const int den = 4 * (sf - 2 * de);
|
||||
int payloadSymb = 8 + (int)fmaxf(ceilf((float)num / (float)den) * (cr + 4), 0.0f);
|
||||
return (uint32_t)((tPreamble + payloadSymb * tSym) * 1000.0f);
|
||||
}
|
||||
|
||||
float TanmatsuLoraRadio::packetScore(float snr, int packet_len) {
|
||||
// Same model as RadioLibWrapper::packetScoreInt (keep scoring consistent across boards).
|
||||
static const float snr_threshold[] = { -7.5f, -10.0f, -12.5f, -15.0f, -17.5f, -20.0f }; // SF7..SF12
|
||||
int sf = _cfg.spreading_factor ? _cfg.spreading_factor : 11;
|
||||
if (sf < 7 || sf > 12) return 0.0f;
|
||||
const float thr = snr_threshold[sf - 7];
|
||||
if (snr < thr) return 0.0f;
|
||||
const float success = (snr - thr) / 10.0f;
|
||||
const float collision = 1.0f - (packet_len / 256.0f);
|
||||
float s = success * collision;
|
||||
if (s < 0.0f) s = 0.0f;
|
||||
if (s > 1.0f) s = 1.0f;
|
||||
return s;
|
||||
}
|
||||
|
||||
void TanmatsuLoraRadio::powerOff() {
|
||||
if (_started) lora_set_mode(&_h, LORA_PROTOCOL_MODE_STANDBY_RC);
|
||||
}
|
||||
|
||||
uint32_t TanmatsuLoraRadio::getRngSeed() {
|
||||
float r = 0.0f;
|
||||
if (_started) lora_get_rssi_inst(&_h, &r);
|
||||
return esp_random() ^ (uint32_t)(r * 1000.0f);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
//
|
||||
// TanmatsuLoraRadio — bridges MeshCore's mesh::Radio onto the Nicolai Electronics
|
||||
// `tanmatsu-lora` component. On the Tanmatsu the LoRa modem (SX1262/SX1268) lives
|
||||
// on the ESP32-C6 radio coprocessor and is reached "remote" over the C6<->P4 link,
|
||||
// so we do NOT use MeshCore's RadioLib-over-SPI wrapper here — this class is the
|
||||
// drop-in replacement. Everything above mesh::Radio (the whole wadamesh app) is
|
||||
// unchanged; the Tanmatsu variant just instantiates this instead of a RadioLibWrapper.
|
||||
//
|
||||
// Written against esp32-component-tanmatsu-lora v0.3.0 (include/lora.h) WITHOUT the
|
||||
// hardware. Anything marked TODO(device) needs confirming/tuning on the real board.
|
||||
//
|
||||
#include <Mesh.h> // mesh::Radio (declared in Dispatcher.h, pulled by Mesh.h)
|
||||
|
||||
extern "C" {
|
||||
#include "lora.h" // nicolaielectronics/tanmatsu-lora
|
||||
}
|
||||
|
||||
class TanmatsuLoraRadio : public mesh::Radio {
|
||||
public:
|
||||
TanmatsuLoraRadio() {}
|
||||
|
||||
// Hardware bring-up: open the remote LoRa link + push the mesh's PHY config.
|
||||
// Returns false if lora_init_remote() fails. The variant's radio_init() calls
|
||||
// this (mirrors RadioLib's radio.std_init()). Call setParams() first, or after —
|
||||
// it re-pushes config when already started.
|
||||
bool init();
|
||||
|
||||
// The S3 boards configure their RadioLibWrapper through this same set of calls
|
||||
// (see MyMesh.cpp); TanmatsuLoraRadio is a drop-in for `radio_driver`, so it
|
||||
// implements them too. None are part of mesh::Radio itself.
|
||||
void setParams(float freq_mhz, float bw_khz, uint8_t sf, uint8_t cr);
|
||||
void setTxPower(int8_t dbm);
|
||||
void setRxBoostedGainMode(bool on);
|
||||
bool getRxBoostedGainMode() const { return _rx_boost; }
|
||||
void idle(); // → standby (RadioLibWrapper::idle)
|
||||
uint32_t getPacketsRecvErrors() const { return _n_recv_errors; }
|
||||
|
||||
// --- mesh::Radio contract (Dispatcher.h) ---
|
||||
void begin() override {} // lifecycle hook; remote radio is RX-ready after init()
|
||||
int recvRaw(uint8_t* bytes, int sz) override; // drain one RX packet, 0 if none
|
||||
bool startSendRaw(const uint8_t* bytes, int len) override;
|
||||
bool isSendComplete() override;
|
||||
void onSendFinished() override;
|
||||
bool isInRecvMode() const override;
|
||||
uint32_t getEstAirtimeFor(int len_bytes) override; // ms
|
||||
float packetScore(float snr, int packet_len) override;
|
||||
float getLastRSSI() const override { return _last_rssi; }
|
||||
float getLastSNR() const override { return _last_snr; }
|
||||
float getCurrentRSSI() { return _last_rssi; } // remote LoRa: no live channel-RSSI probe
|
||||
|
||||
// Convention helpers the companion's radio glue expects (mirror ESPNOWRadio):
|
||||
void powerOff();
|
||||
uint32_t getRngSeed();
|
||||
uint32_t getPacketsRecv() const { return _n_recv; }
|
||||
uint32_t getPacketsSent() const { return _n_sent; }
|
||||
|
||||
private:
|
||||
// Push _cfg to the modem — but ONLY while not yet polling RX. The remote link's
|
||||
// esp_hosted_send_custom_data() blocks indefinitely under continuous-RX load, so a
|
||||
// live reconfig from the UI thread would freeze the app. Boot bring-up (before the
|
||||
// RX loop starts) pushes fine; runtime changes update _cfg and apply on next init.
|
||||
void pushConfig() { if (_started && !_rx_polling) lora_set_config(&_h, &_cfg); }
|
||||
|
||||
lora_handle_t _h = {0};
|
||||
lora_protocol_config_params_t _cfg = {0};
|
||||
bool _started = false;
|
||||
bool _rx_polling = false; // set once recvRaw() starts draining — see pushConfig()
|
||||
bool _sending = false;
|
||||
bool _rx_boost = false;
|
||||
float _last_rssi = 0.0f;
|
||||
float _last_snr = 0.0f;
|
||||
uint32_t _n_recv = 0;
|
||||
uint32_t _n_sent = 0;
|
||||
uint32_t _n_recv_errors = 0;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
# ESP-IDF managed components the Tanmatsu variant needs, on top of the Arduino core.
|
||||
# These come from the badge.team / Nicolai Electronics component registry and provide
|
||||
# the board's drivers so we don't write any: display, keyboard, power, WiFi-over-
|
||||
# coprocessor, and the LoRa modem. Pulling these into the PlatformIO/pioarduino build
|
||||
# (Arduino-as-IDF-component + the component manager) is the main integration unknown —
|
||||
# see TANMATSU_PORT.md.
|
||||
#
|
||||
# Versions track the upstream tanmatsu-template (badge-bsp / tanmatsu-wifi) plus the
|
||||
# LoRa component from the user's instructions (^0.3.0).
|
||||
dependencies:
|
||||
idf: ">=5.5.1"
|
||||
badgeteam/badge-bsp: "^0.9.9" # display (MIPI), input/keyboard, power, led, device
|
||||
nicolaielectronics/tanmatsu-wifi: "^1.1.2" # WiFi/BLE hosted on the C6 coprocessor
|
||||
nicolaielectronics/tanmatsu-lora: "^0.3.0" # SX1262/1268 LoRa (remote, on the coprocessor)
|
||||
# NOTE: pax-gfx is intentionally omitted — wadamesh renders with LVGL, flushed to the
|
||||
# BSP display, not PAX.
|
||||
@@ -0,0 +1,30 @@
|
||||
// Pragmatic stub for arduino-esp32 3.3.10's ssl_client functions.
|
||||
//
|
||||
// On the P4 / IDF 5.5.1, arduino's NetworkClientSecure.cpp call signatures don't match
|
||||
// ssl_client.cpp's definitions (an arduino-internal / toolchain ABI mismatch — e.g. the port
|
||||
// param mangles `unsigned long` at the call site but `uint32_t` at the definition), so they're
|
||||
// undefined at link. wadamesh doesn't use HTTPS yet (the companion link is USB/LoRa and map
|
||||
// tiles are plain HTTP), so stub the referenced symbols to let the app link.
|
||||
//
|
||||
// Symbols + signatures are taken verbatim from the linker's undefined-reference list. These are
|
||||
// STRONG (not weak): a weak def in a .a doesn't get pulled to resolve an undefined ref, and there's
|
||||
// no conflict because arduino's real defs mangle differently (that mismatch is why they're undefined).
|
||||
// TODO: revisit for the HTTPS version-check / OTA-over-Wi-Fi.
|
||||
#if defined(HAS_TANMATSU)
|
||||
#include <IPAddress.h>
|
||||
struct sslclient_context;
|
||||
#define WK
|
||||
|
||||
WK int start_ssl_client(sslclient_context*, const IPAddress&, unsigned long, const char*, int,
|
||||
const char*, bool, const char*, const char*, const char*, const char*,
|
||||
bool, const char**) { return -1; }
|
||||
WK void stop_ssl_socket(sslclient_context*) {}
|
||||
WK int data_to_read(sslclient_context*) { return 0; }
|
||||
WK int send_net_data(sslclient_context*, const unsigned char*, unsigned int) { return -1; }
|
||||
WK int get_net_receive(sslclient_context*, unsigned char*, int) { return -1; }
|
||||
WK int peek_net_receive(sslclient_context*, int) { return -1; }
|
||||
WK int send_ssl_data(sslclient_context*, const unsigned char*, unsigned int) { return -1; }
|
||||
WK int get_ssl_receive(sslclient_context*, unsigned char*, int) { return -1; }
|
||||
WK void ssl_init(sslclient_context*) {}
|
||||
WK int ssl_starttls_handshake(sslclient_context*) { return -1; }
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
// Compatibility shims for Arduino-ESP32 3.x / ESP32-P4 API gaps that the vendored MeshCore core
|
||||
// still expects. Force-included on the Tanmatsu build (see tanmatsu/main/CMakeLists.txt).
|
||||
#include <stdint.h>
|
||||
|
||||
// adcAttachPin() was removed in Arduino-ESP32 3.x. The core's ESP32Board calls it for the VBAT
|
||||
// pin; on the Tanmatsu that pin is -1/none (battery comes from badge-bsp/power), so a no-op is fine.
|
||||
static inline void adcAttachPin(uint8_t) {}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h> // StdRNG lives here (there is no StdRNG.h)
|
||||
#include <TanmatsuDisplay.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
TanmatsuBoard board;
|
||||
TanmatsuLoraRadio radio_driver; // the coprocessor LoRa bridge (no RadioLib Module)
|
||||
DISPLAY_CLASS display; // global LVGL flush target + boot/shutdown DisplayDriver
|
||||
SensorManager sensors; // base (no telemetry yet); EnvironmentSensorManager later
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
// TODO(device): if the Tanmatsu exposes a hardware RTC over I2C, do
|
||||
// rtc_clock.begin(Wire) + Wire.begin(<sda>,<scl>) here like the S3 boards.
|
||||
return radio_driver.init(); // lora_init_remote() + push PHY config
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
// S3 boards seed from RadioLib radio noise (RadioNoiseListener). The remote LoRa
|
||||
// has no equivalent, so seed a fresh identity from the same StdRNG the app uses,
|
||||
// keyed off getRngSeed() (esp_random ^ instantaneous RSSI noise).
|
||||
StdRNG rng;
|
||||
rng.begin(radio_driver.getRngSeed());
|
||||
return mesh::LocalIdentity(&rng);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
// Tanmatsu target wiring. Unlike the S3 boards (RadioLib over SPI), the radio here
|
||||
// is the coprocessor LoRa reached via TanmatsuLoraRadio, so there is no RADIO_CLASS/
|
||||
// Module/SPI — radio_driver IS the bridge. Everything the app pulls off `radio_driver`
|
||||
// (MyMesh.cpp) is implemented by TanmatsuLoraRadio.
|
||||
|
||||
#include <TanmatsuBoard.h>
|
||||
#include "TanmatsuLoraRadio.h"
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
|
||||
extern TanmatsuBoard board;
|
||||
extern TanmatsuLoraRadio radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
|
||||
#include <TanmatsuDisplay.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
extern DISPLAY_CLASS display; // LVGL flush target (badge-bsp)
|
||||
extern SensorManager sensors; // telemetry (base/no-op for now)
|
||||
// TODO(device): wire the keyboard/touch as an lv_indev from bsp/input.h.
|
||||
|
||||
bool radio_init();
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
Reference in new issue
Block a user