Compare commits

...
9 Commits
Author SHA1 Message Date
Kaj Schittecat f985458363 chore: refresh Mesh America catalog for beta_12 2026-06-23 08:51:17 +02:00
Kaj SchittecatandClaude Opus 4.8 30083efdd7 touch: beta_12 — GPS hotfix (enlarge GPS UART RX buffer)
The GPS NMEA UART used Arduino's default 256-byte RX ring (~66 ms of slack at
38400 baud). On the busy touch UI a single long LVGL/map frame stalls the loop
past that window, dropping bytes and corrupting NMEA ephemeris subframes — each
loss costs the receiver ~30 s, turning a ~1-minute fix into several minutes or,
with frequent stalls, never acquiring. Confirmed on-device: peak UART backlog
hit 448 B at idle, well past the 256 B default. Bump the ring to 4096 B (~1 s of
slack) before the core opens Serial1 in sensors.begin(). App-side (no core
re-tag); gated to ENV_INCLUDE_GPS so both touch boards get it. A -DGPS_BUF_DEBUG
backlog probe is included (compiled out of releases).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:49:00 +02:00
Kaj SchittecatandClaude Opus 4.8 d22390aeb8 flasher: point manifest at immutable per-tag bin, not latest/
The web-flasher manifest referenced https://firmware.wadamesh.com/latest/<board>-merged.bin
— a stable filename cached 4h (Cache-Control max-age=14400) and overwritten in place each
release. version.json (max-age=300) advertised the new tag within minutes, but the latest/
bin could keep serving the PREVIOUS beta's bytes per-POP for up to 4h, so a user flashing
right after a release could install the old version (reported: flashed beta_11, device showed
beta_10). Point the manifest at the immutable releases/TOUCH/<tag>/ bin instead — that URL is
never overwritten so its cache is always correct, and the 5-min manifest TTL propagates the
new path fast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:10:44 +02:00
Kaj Schittecat 3130a1198e chore: refresh Mesh America catalog for beta_11 2026-06-22 19:17:00 +02:00
Kaj SchittecatandClaude Opus 4.8 1f755ec3b8 touch: beta_11 — bindable keyboard navigation, crash-report prompt, map zoom buttons, app-drawer-as-home; publish Tanmatsu port source
Headline: full keyboard navigation (on by default) — WASDZ spatial focus move, S select,
Q back, per-tab hotkeys (E/R/T/U/I) shown on the icons, plus scroll up/down (F/C). Every
key is remappable in Settings -> Keyboard. Foundation for keyboard-first devices like the
Tanmatsu T-Pager (no touchscreen) and a reliable keyboard workflow on the T-Deck today.
Also: #27 receive/flood reboot fix, on-boot crash-report prompt, map zoom +/- buttons (#26),
route-replay wide overview, 'App drawer as home' option. The in-progress Tanmatsu (ESP32-P4)
port source is now public. Mesh America catalog + release.sh kept from main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:10:40 +02:00
Kaj SchittecatandClaude Opus 4.8 c931df6054 touch: beta_10 — trackball D-pad mode, microSD tile-cache fill, Battery group
- T-Deck: trackball mouse/D-pad navigation toggle (Settings -> Keyboard).
- Map (#20): microSD-tile mode now caches Wi-Fi-fetched tiles onto the SD card so
  the offline library fills in + grows instead of going blank past its edge.
- Power: PR #19 idle power-save (Dan Vybiral / @Vybo) relocated into a new
  'Battery' group under Settings -> System, alongside a button that opens the
  battery / power-history screen.
- TouchCfg v10 (ui_scale, tb_keypad, sleep_idle). Mesh America catalog -> beta_10.

Curated touch overlay onto main (the WIP Tanmatsu ESP32-P4 build system stays on
the tanmatsu-port branch). Co-Authored-By: Dan Vybiral <dan.vybiral@greencode.cz>

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 00:08:13 +02:00
Dan Vybiral 11b31e1cdd touch: idle power-save — loop-throttle + downclock when parked (#19)
Opt-in Idle power-save for the T-Deck: when parked (screen off, on battery, no
companion client, Wi-Fi+BLE off, mesh idle) the main loop is throttled with
vTaskDelay so the FreeRTOS idle task can halt the CPU between ticks, on top of the
existing 80 MHz screen-off downclock — without ever stopping mesh RX. Default OFF
(Settings -> Lock, persisted TouchCfg v8 sleep_idle); status-bar moon indicator +
idle stats. Heltec V4 compiles (gate never passes there). A plain scheduler throttle
rather than esp_light_sleep_start(), which trips INT_WDT in this build.

Co-authored-by: Dan Vybiral <dan.vybiral@greencode.cz>
2026-06-21 22:39:59 +02:00
Kaj SchittecatandClaude Opus 4.8 c16b5230a6 release: auto-refresh + push the Mesh America catalog each beta
release.sh now regenerates deploy/meshamerica-catalog.json for the tag and, when
run on main, commits + pushes that one file so apps.meshamerica.com (the flasher
Cascadia Mesh uses) always points at the newest immutable bins instead of going
stale on the previous beta. Idempotent (no-op when already current); non-fatal if
the push fails; also mirrors the catalog into the VPS publish tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 22:27:28 +02:00
Kaj SchittecatandClaude Opus 4.8 f5362b1cb3 deploy: add Mesh America Configurator provider catalog
Catalog JSON (Mesh America provider schema) + a reusable generator so the two
wadamesh touch boards appear in the Mesh America Device Configurator
(apps.meshamerica.com — the flasher Cascadia Mesh uses). Points at the immutable
per-tag bins on firmware.wadamesh.com (HTTPS + CORS). Served live over
raw.githubusercontent.com.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 22:20:29 +02:00
65 changed files with 5792 additions and 225 deletions

No files matched your search

+235
View File
@@ -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`
+29
View File
@@ -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"
}
+81
View File
@@ -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))
+73
View File
@@ -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_12": {
"notes": "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).\nMore 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.\nI 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!",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-heltec-v4-tft-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_12/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_12/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_12": {
"notes": "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).\nMore 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.\nI 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!",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-tdeck-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_12/wadamesh-tdeck-merged.bin",
"title": "Full install (bootloader + firmware)"
},
{
"type": "flash-update",
"name": "wadamesh-tdeck.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_12/wadamesh-tdeck.bin",
"title": "Update (app only)"
}
]
}
}
}
]
}
]
}
+8
View File
@@ -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
+13
View File
@@ -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).
+22
View File
@@ -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.
+8
View File
@@ -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!
+10 -2
View File
@@ -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:
+22
View File
@@ -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/"
+6 -2
View File
@@ -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>
@@ -555,9 +557,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
+10
View File
@@ -780,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);
+95 -1
View File
@@ -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 = 7; // 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
static const uint8_t TOUCH_CFG_VER = 16; // 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
// Defaults (kept identical to the historical per-key defaults).
static const uint16_t DEFAULT_SCREEN_TIMEOUT_S = 20;
@@ -80,6 +80,14 @@ struct __attribute__((packed)) TouchCfg {
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)
};
static TouchCfg s_cfg;
@@ -136,6 +144,14 @@ static void cfgSetDefaults(TouchCfg& c) {
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
{ 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
c.home_is_drawer = 0; // default: Home = Commander screen
c.nav_scroll_keys[0] = 'f'; c.nav_scroll_keys[1] = 'c'; // default scroll-up F / scroll-down C
}
// Persist the whole blob using the same end()/begin(RW)/put/end()/begin(RO)
@@ -176,6 +192,10 @@ static void cfgLoadOrMigrate() {
// 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
@@ -725,6 +745,15 @@ bool touchPrefsSetMapZoom(uint8_t z) {
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();
@@ -763,6 +792,71 @@ bool touchPrefsSetAppGridLarge(bool on) {
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"
+39
View File
@@ -103,6 +103,45 @@ bool touchPrefsSetMapShowContacts(bool on);
* 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). */
+9
View File
@@ -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
+35
View File
@@ -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!
@@ -472,6 +473,17 @@ 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.
Serial1.setRxBufferSize(4096);
#endif
sensors.begin();
#ifdef DISPLAY_CLASS
@@ -620,6 +632,22 @@ 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
@@ -672,4 +700,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
}
+78
View File
@@ -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
+46
View File
@@ -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
+1988 -220
View File
File diff suppressed because it is too large. Load diff
+148
View File
@@ -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*/
+16
View File
@@ -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/
+3
View File
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.10)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(application)
+264
View File
@@ -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
+42
View File
@@ -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)
+14
View File
@@ -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)
+783
View File
@@ -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
+7
View File
@@ -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": ""
}
}
}
+57
View File
@@ -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."
+54
View File
@@ -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.)
+347
View File
@@ -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;
}
}
}
}
+12
View File
@@ -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"
+456
View File
@@ -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);
}
}
+9
View File
@@ -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,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x19000, 16K,
4 otadata, data, ota, 0x1D000, 8K,
5 phy_init, data, phy, 0x1F000, 4K,
6 ota_0, 0, ota_0, 0x20000, 2048K,
7 ota_1, 0, ota_1, 0x220000, 2048K,
8 appfs, 0x43, 3, 0x420000, 8192K,
9 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,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 16K,
4 otadata, data, ota, 0xD000, 8K,
5 phy_init, data, phy, 0xF000, 4K,
6 ota_0, 0, ota_0, 0x10000, 2048K,
7 ota_1, 0, ota_1, 0x210000, 2048K,
8 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,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 16K,
4 otadata, data, ota, 0xD000, 8K,
5 phy_init, data, phy, 0xF000, 4K,
6 ota_0, 0, ota_0, 0x10000, 2048K,
7 ota_1, 0, ota_1, 0x210000, 2048K,
8 appfs, 0x43, 3, 0x410000, 3008K,
9 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,
1 # ESP-IDF Partition Table
2 # Name Type SubType Offset Size
3 nvs, data, nvs, 0x9000, 16K,
4 otadata, data, ota, 0xD000, 8K,
5 phy_init, data, phy, 0xF000, 4K,
6 ota_0, 0, ota_0, 0x10000, 2048K,
7 ota_1, 0, ota_1, 0x210000, 2048K,
8 appfs, 0x43, 3, 0x410000, 6000K,
9 zb_storage, data, fat, 0x9EC000, 16K,
10 zb_fct, data, fat, 0x9F0000, 4K,
11 locfd, data, fat, 0x9F1000, 6204K,
Binary file not shown.
+9
View File
@@ -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,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 16K,
4 otadata, data, ota, 0xD000, 8K,
5 phy_init, data, phy, 0xF000, 4K,
6 ota_0, 0, ota_0, 0x10000, 1600K,
7 ota_1, 0, ota_1, 0x1A0000, 1600K,
8 appfs, 0x43, 3, 0x330000, 8000K,
9 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
+16
View File
@@ -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
+31
View File
@@ -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
+6
View File
@@ -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
+12
View File
@@ -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
+39
View File
@@ -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
+13
View File
@@ -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
+39
View File
@@ -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
+27
View File
@@ -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
+32
View File
@@ -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";
}
};
+48
View File
@@ -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; }
};
+134
View File
@@ -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);
}
+76
View File
@@ -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;
};
+16
View File
@@ -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.
+30
View File
@@ -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
+8
View File
@@ -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) {}
+29
View File
@@ -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);
}
+23
View File
@@ -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();