Compare commits

...
12 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
Kaj SchittecatandClaude Opus 4.8 7a63ffe86c touch: beta_9 — crash/Wi-Fi reliability, crash-report export, room fixes, app-drawer icon size
- Stability: Wi-Fi auto-reconnect; corrupt chat-history quarantine (fixes reboot-on-
  receive / inaccessible messages); draw-buffer + flush NULL-guards (boot-panic).
- Crash-report export to SD/SPIFFS (Settings -> About).
- Map: reload purges on-disk tiles offline + visible zoom band; corrupt-tile self-heal
  on load; z15 dedup-key overflow fixed.
- Contacts: favourites always listed (#17).
- Chat: room message send-time (#26) + sender labels; QR share uses the official
  meshcore://contact/add format (#16); unread badge capped at 99+.
- App drawer: icon-size setting (Compact/Large) + smoother scrolling.
- ~5 KB internal DRAM reclaimed (backup arrays + msg_idx -> PSRAM).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:16:04 +02:00
Kaj SchittecatandClaude Opus 4.8 2aa8f59410 touch: beta_8 — Ack reply, map visibility toggles + auto-refresh, RAM trim
- Chat: quick replies insert at the cursor (no longer replace typed text);
  long-press 'Ack' drops a signal-strength + route reply into the composer
  (@mentions the sender on channels); fixed the action-menu overflowing the screen.
- Map: +/- zoom glyph; per-line on-map visibility toggles (coords / tile xyz /
  contacts); microSD-tiles regrouped under a heading; auto-refresh follow button
  with on/off toast; close-X overlap fix when scrolled.
- Perf: ~18.9 KB internal DRAM reclaimed (6 scratch buffers -> on-demand PSRAM).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:36:46 +02:00
Kaj SchittecatandClaude Opus 4.8 c8b451f14f release: beta_7 notes — credit contributors prominently (Vybo #12, Ruben Laban #15, marcelverdult #13)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:32:03 +02:00
69 changed files with 6591 additions and 362 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
+11
View File
@@ -19,6 +19,11 @@ extra_scripts = pre:scripts/inject_wifi_env.py, merge-bin.py
build_flags =
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
-D MC_VENDORED_TOUCH_APP
; NOTE: tried -D ARDUINO_USB_MODE=1 (HW-CDC, drops unused TinyUSB MSC/DFU ≈ 12 KB internal
; DRAM) — but on the real V4 the binary companion link regressed: small per-message frames
; arrived yet the larger device-profile frame (node name + keys) dropped bytes over HW-CDC's
; smaller TX FIFO. Reverted to the board JSON's TinyUSB CDC. Revisit only with an HW-CDC TX
; buffer/DTR-wait fix verified on hardware. (T-Deck ships MODE=1 and is fine — board-specific.)
-D LORA_FREQ=869.618
-D LORA_BW=62.5
-D LORA_SF=8
@@ -126,6 +131,9 @@ build_flags =
-D ENABLE_ADVERT_ON_BOOT=0
-D LV_CONF_PATH=lv_conf.h
-D LV_CONF_INCLUDE_SIMPLE=1
; Smoother scroll: lower deceleration than LVGL's default (10) so flicks glide
; longer instead of stopping abruptly. lv_conf leaves this unset, so this -D wins.
-D LV_INDEV_DEF_SCROLL_THROW=7
-D USER_SETUP_LOADED=1
-D ST7789_DRIVER=1
-D TFT_WIDTH=240
@@ -311,6 +319,9 @@ build_flags =
-D PIN_TB_RIGHT=1
-D LV_CONF_PATH=lv_conf.h
-D LV_CONF_INCLUDE_SIMPLE=1
; Smoother scroll: lower deceleration than LVGL's default (10) so flicks glide
; longer instead of stopping abruptly. lv_conf leaves this unset, so this -D wins.
-D LV_INDEV_DEF_SCROLL_THROW=7
-D MAX_CONTACTS=2000
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=ST7789LCDDisplay
+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!
+18 -15
View File
@@ -1,31 +1,34 @@
# beta_7 — what's new (one user-facing note per non-blank, non-# line)
# --- Community-built ---
Most of beta_7 was contributed by the community — Dan Vybiral (Vybo) and Ruben Laban. Thank you both!
# --- Chat reliability ---
Fixed garbled and duplicated messages — chat text is no longer corrupted in transit, so messages arrive intact and duplicates are filtered out correctly (thanks to marcelverdult, issue #13)
Unread badges are accurate again: the phantom and stuck unread counts on chats and channels are fixed
More of your chat history is kept and reloaded across reboots, and it is saved more reliably than before
Fixed garbled and duplicated messages — chat text is no longer corrupted in transit, so messages arrive intact and duplicates are filtered out correctly (thanks to marcelverdult for the report, issue #13)
Unread badges are accurate again: the phantom and stuck unread counts on chats and channels are fixed (Ruben Laban, PR #15)
More of your chat history is kept and reloaded across reboots, and it is saved more reliably than before (Ruben Laban, PR #15)
# --- New: Snake game ---
A Snake game is now in the app drawer — steer by swiping the screen or with the T-Deck trackball, with pause/resume and restart after a game over
A Snake game is now in the app drawer — steer by swiping the screen or with the T-Deck trackball, with pause/resume and restart after a game over (Vybo, PR #12)
While you play, touch stays inside the game and no longer leaks through to the screen behind it
# --- Battery & telemetry ---
Tap the battery in the status bar for a 24-hour history chart with an estimated time-left readout; battery level is logged every 5 minutes
New per-node telemetry window: poll a node's battery, temperature and humidity, watch them on a live chart, and keep a per-node log
Tap the battery in the status bar for a 24-hour history chart with an estimated time-left readout; battery level is logged every 5 minutes (Vybo, PR #12)
New per-node telemetry window: poll a node's battery, temperature and humidity, watch them on a live chart, and keep a per-node log (Vybo, PR #12)
# --- Map ---
New map night mode — the status bar and tab bar invert to match the dark map instead of glaring
A live zoom-level readout sits above the zoom slider, and the zoom button now shows a proper magnifier icon
Contacts have a new "Show on map" action that centres the map on that node
New map night mode — the status bar and tab bar invert to match the dark map instead of glaring (Vybo, PR #12)
A live zoom-level readout sits above the zoom slider, and the zoom button now shows a proper magnifier icon (Vybo, PR #12)
Contacts have a new "Show on map" action that centres the map on that node (Vybo, PR #12)
# --- Status bar & tools ---
A microSD activity LED lights whenever the card is accessed, including file-manager reads and copies
An activity spinner shows while a telemetry, admin, ping, trace, range or sightline request is in flight, kept clear of the clock
New option to hide the device name and move the clock to the left
Press and hold the status bar to save a full-screen screenshot to the SD card
A microSD activity LED lights whenever the card is accessed, including file-manager reads and copies (Vybo, PR #12)
An activity spinner shows while a telemetry, admin, ping, trace, range or sightline request is in flight, kept clear of the clock (Vybo, PR #12)
New option to hide the device name and move the clock to the left (Vybo, PR #12)
Press and hold the status bar to save a full-screen screenshot to the SD card (Vybo, PR #12)
# --- Files & images ---
The file browser can preview PNG and BMP images now, not just JPEG, and no longer shows a stale image when you open several in a row
The file browser can preview PNG and BMP images now, not just JPEG, and no longer shows a stale image when you open several in a row (Vybo, PR #12)
Lock-screen wallpaper: open Files, view any JPEG on your SD card, and tap "Set as wallpaper" — no more slow scan or "no image found"
# --- App drawer ---
@@ -41,5 +44,5 @@ Replying to a message with @ no longer overwrites text you were already typing
Fixed an out-of-memory crash and made notification audio more reliable
# --- Community ---
Huge thanks to Dan Vybíral (Vybo) for the Snake game, battery history, per-node telemetry, map night mode, PNG/BMP image viewer and the status-bar tools (PR #12)
Huge thanks to Dan Vybiral (Vybo) — most of beta_7 is his work: the Snake game, battery history, per-node telemetry, map night mode, PNG/BMP image viewer and the whole status-bar toolkit (PR #12)
Thanks to Ruben Laban for the accurate unread-badge and chat-history-persistence fixes (PR #15)
+16
View File
@@ -0,0 +1,16 @@
# beta_8 — what's new (one user-facing note per non-blank, non-# line)
# --- Chat ---
Quick replies now insert at the cursor instead of wiping whatever you'd already typed — tap one mid-message and it's added in place
Long-press any message for a new one-tap "Ack": it drops a ready-to-send reply into the composer with that message's signal strength (SNR/RSSI) and the route it took, so you can instantly confirm someone's test came through — on a channel it @mentions the sender
Fixed the message menu (Ack / Copy / Info / Mention / Block) running off the bottom of the screen — it now fits, and scrolls if it ever needs to
# --- Map ---
The zoom button now shows a clear "+/-" icon
Each on-map readout can be shown or hidden independently in Map settings — the coordinates line, the tile x/y/z line, and the contact markers
Map settings are reorganised: the microSD-tiles switch sits at the top under its own heading, separated from the new show/hide toggles
New auto-refresh button: turn it on and the map re-centres on you automatically whenever your position changes, with an on-screen "Auto-refresh on/off" confirmation
Fixed the close (X) in Map settings overlapping the switches when the panel was scrolled
# --- Performance ---
Freed about 19 KB of internal memory, leaving more headroom for Wi-Fi and trimming the idle-memory growth seen in recent betas
+26
View File
@@ -0,0 +1,26 @@
# beta_9 — what's new (one user-facing note per non-blank, non-# line)
# --- Stability ---
Wi-Fi now reconnects on its own after the link drops — no more rebooting just to get back online
Fixed a bug where a corrupted chat-history file could make the device reboot on every received message (and hide your messages) — the bad file is now detected and reset automatically so it recovers on its own
Hardened the boot path so a device with quirky memory degrades gracefully instead of getting stuck on the boot logo
# --- Crash reports ---
After a crash, Settings -> About now shows an "Export crash report" button that saves the crash dump to the microSD card, so you can send it to the devs and get the bug fixed
# --- Map ---
The reload button for corrupt tiles now actually clears the cached tiles — including when you're offline — and missing/blank "twilight zone" tiles re-download on their own
Fixed tiles not re-fetching at zoom levels 13 to 15
# --- Contacts ---
Favourited contacts now always appear in the contact list, even with hundreds of similarly-named contacts (they used to stay hidden until you searched for them)
# --- Chat ---
Room-server messages now show the time each message was actually sent, instead of when it arrived
Room-server messages now show who sent them
Sharing your contact QR code now produces a valid code that the MeshCore phone app can scan and import
Unread counts now show "99+" instead of an alarming raw number on busy channels
# --- App drawer ---
New icon-size setting: tap the cog at the top-right of the app drawer to switch between Compact and Large — bigger icons and labels, easier to read
Smoother scrolling through the app list (and the other scrollable lists)
+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/"
+7 -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
@@ -1880,6 +1884,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
const int8_t rssi = (int8_t)(_radio->getLastRSSI());
const bool is_flood = pkt->isRouteFlood();
uiStashRxMeta(pkt); // capture route + scope for the per-message Info popup
_last_sender_ts = sender_timestamp; // embedded send-time -> UI bubble ts (room history replay)
_ui->newMsgFromPubWithMeta(path_len, is_flood, from.id.pub_key, from.name,
text, history_count, snr_q4, rssi);
}
+15
View File
@@ -393,6 +393,7 @@ public:
uint8_t _last_rx_path_n = 0;
uint16_t _last_rx_scope = 0; // transport_codes[0] of the last RX flood ("scope")
bool _last_rx_has_scope = false; // false if the packet carried no transport codes
uint32_t _last_sender_ts = 0; // embedded send-time of the last inbound msg (UI bubble ts; 0 = use now)
volatile bool _echo_dirty = false; // a repeat was counted -> UI should refresh
// ---- Live signal strength (top-bar icon) ----
@@ -481,6 +482,10 @@ public:
/** Scope (transport_codes[0]) of the last received flood; *has = false when
* the packet carried no transport codes. */
uint16_t lastRxScope(bool* has) const { if (has) *has = _last_rx_has_scope; return _last_rx_scope; }
/** Consume the embedded send-time stashed right before the last UI notify (room
* history replay carries old send-times; without this the UI stamps "now").
* Returns 0 when nothing was stashed -> the caller keeps the delivery time. */
uint32_t uiConsumeLastSenderTs() { uint32_t t = _last_sender_ts; _last_sender_ts = 0; return t; }
/** Capture route + scope of a just-received flood for the Info popup. Call
* synchronously right before the newMsg* notification. */
void uiStashRxMeta(mesh::Packet* pkt) {
@@ -775,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);
+140 -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 = 5; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom
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;
@@ -76,6 +76,18 @@ struct __attribute__((packed)) TouchCfg {
uint8_t hide_node_name; // hide the device name in the status bar + park clock left (bool) — v4
uint8_t map_night; // invert map tile colours at render time (bool) — v5
uint8_t map_zoom; // last map zoom level (0 = unset, auto-snap) — v5
uint8_t map_show_coords; // show the coords read-out on the map (bool) — v6
uint8_t map_show_tilexyz; // show the zoom + tile z/x/y line on the map (bool) — v6
uint8_t map_show_contacts;// show contact markers on the map (bool) — v6
uint8_t app_grid_large; // app drawer: large grid (one fewer column, bigger icons) — v7
uint8_t ui_scale; // UI resolution scale: 0=100% 1=150% 2=200% (Tanmatsu, applied at boot) — v8
uint8_t kbd_nav; // T-Deck keyboard ESDFX nav: 0=off (default), 1=on (E/X/S/F move focus, D select, Q back) — v9 (was tb_keypad)
uint8_t sleep_idle; // idle light-sleep feature on/off (bool) — v10 (trailing so existing blobs default it OFF)
uint8_t nav_keys[5]; // keyboard-nav tab hotkeys (ASCII), one per main tab [chat,contacts,home,map,settings] — v11 (trailing)
uint8_t map_zoom_buttons; // map zoom control: 0=slider (default), 1=+/- buttons — v12 (trailing)
uint8_t nav_dir_keys[6]; // keyboard-nav control keys (ASCII): up,down,left,right,select,back — v13 (trailing)
uint8_t home_is_drawer; // Home tab defaults to the app drawer (1) vs the Commander screen (0, default) — v14 (trailing)
uint8_t nav_scroll_keys[2]; // keyboard-nav scroll keys (ASCII): scroll-up, scroll-down — v16 (trailing)
};
static TouchCfg s_cfg;
@@ -128,6 +140,18 @@ static void cfgSetDefaults(TouchCfg& c) {
c.hide_node_name = 0; // default: show the device name
c.map_night = 0; // default: normal (light) tiles
c.map_zoom = 0; // 0 = unset -> auto-snap on first map open
c.map_show_coords = 1; // default: show coords / tile line / contacts
c.map_show_tilexyz = 1;
c.map_show_contacts = 1;
c.app_grid_large = 0; // default: compact app grid (T-Deck 4 cols / V4 3 cols)
c.ui_scale = 0; // default: 100% UI scale (native resolution)
c.kbd_nav = 1; // default: keyboard navigation ON (v15)
c.sleep_idle = 0; // default: idle light-sleep OFF
{ const char* d = "ertui"; for (int i = 0; i < 5; i++) c.nav_keys[i] = (uint8_t)d[i]; } // default tab hotkeys E/R/T/U/I
c.map_zoom_buttons = 0; // default: map zoom = slider
{ 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)
@@ -168,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
@@ -717,6 +745,117 @@ 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();
return s_cfg.map_show_coords != 0;
}
bool touchPrefsSetMapShowCoords(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.map_show_coords = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetMapShowTileXYZ() {
if (!s_begun) touchPrefsBegin();
return s_cfg.map_show_tilexyz != 0;
}
bool touchPrefsSetMapShowTileXYZ(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.map_show_tilexyz = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetMapShowContacts() {
if (!s_begun) touchPrefsBegin();
return s_cfg.map_show_contacts != 0;
}
bool touchPrefsSetMapShowContacts(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.map_show_contacts = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetAppGridLarge() {
if (!s_begun) touchPrefsBegin();
return s_cfg.app_grid_large != 0;
}
bool touchPrefsSetAppGridLarge(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.app_grid_large = on ? 1 : 0;
return cfgFlush();
}
uint8_t touchPrefsGetUiScale() {
if (!s_begun) touchPrefsBegin();
return s_cfg.ui_scale > 2 ? 0 : s_cfg.ui_scale; // 0=100% 1=150% 2=200%
}
bool touchPrefsSetUiScale(uint8_t scale) {
if (!s_begun) touchPrefsBegin();
s_cfg.ui_scale = scale > 2 ? 0 : scale;
return cfgFlush();
}
bool touchPrefsGetKbdNav() {
if (!s_begun) touchPrefsBegin();
return s_cfg.kbd_nav != 0;
}
bool touchPrefsSetKbdNav(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.kbd_nav = on ? 1 : 0;
return cfgFlush();
}
uint8_t touchPrefsGetNavKey(int tab) {
if (!s_begun) touchPrefsBegin();
if (tab < 0 || tab >= 5) return 0;
return s_cfg.nav_keys[tab];
}
bool touchPrefsSetNavKey(int tab, uint8_t ch) {
if (!s_begun) touchPrefsBegin();
if (tab < 0 || tab >= 5) return false;
s_cfg.nav_keys[tab] = ch;
return cfgFlush();
}
bool touchPrefsGetMapZoomButtons() {
if (!s_begun) touchPrefsBegin();
return s_cfg.map_zoom_buttons != 0;
}
bool touchPrefsSetMapZoomButtons(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.map_zoom_buttons = on ? 1 : 0;
return cfgFlush();
}
uint8_t touchPrefsGetNavDirKey(int idx) { // idx 0-5 = move/select/back, 6-7 = scroll up/down
if (!s_begun) touchPrefsBegin();
if (idx < 0 || idx >= 8) return 0;
return (idx < 6) ? s_cfg.nav_dir_keys[idx] : s_cfg.nav_scroll_keys[idx - 6];
}
bool touchPrefsSetNavDirKey(int idx, uint8_t ch) {
if (!s_begun) touchPrefsBegin();
if (idx < 0 || idx >= 8) return false;
if (idx < 6) s_cfg.nav_dir_keys[idx] = ch;
else s_cfg.nav_scroll_keys[idx - 6] = ch;
return cfgFlush();
}
bool touchPrefsGetHomeIsDrawer() {
if (!s_begun) touchPrefsBegin();
return s_cfg.home_is_drawer != 0;
}
bool touchPrefsSetHomeIsDrawer(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.home_is_drawer = on ? 1 : 0;
return cfgFlush();
}
// Store ALL device data (identity, prefs, contacts, channels) on the SD card
// under /meshcomod instead of internal SPIFFS. Read at boot (main.cpp) BEFORE
+53
View File
@@ -90,6 +90,59 @@ bool touchPrefsSetMapNight(bool on);
uint8_t touchPrefsGetMapZoom();
bool touchPrefsSetMapZoom(uint8_t z);
/** Per-element visibility of the map's on-screen text/markers. All default true
* (shown), so existing installs are unchanged. */
bool touchPrefsGetMapShowCoords();
bool touchPrefsSetMapShowCoords(bool on);
bool touchPrefsGetMapShowTileXYZ();
bool touchPrefsSetMapShowTileXYZ(bool on);
bool touchPrefsGetMapShowContacts();
bool touchPrefsSetMapShowContacts(bool on);
/* App drawer: large grid (one fewer column → bigger icons + labels, for low vision).
* Default false = the compact grid (T-Deck 4 cols / Heltec V4 3 cols). */
bool touchPrefsGetAppGridLarge();
bool touchPrefsSetAppGridLarge(bool on);
/** Idle light-sleep feature: when ON, the device enters light sleep while the
* screen is off and all activity gates pass. Default OFF. */
bool touchPrefsGetSleepIdle();
bool touchPrefsSetSleepIdle(bool on);
/* UI resolution scale (Tanmatsu): 0=100% (native 800x480), 1=150%, 2=200%. Applied at boot —
* LVGL renders at a lower resolution and the flush upscales to the panel. Reboot to apply. */
uint8_t touchPrefsGetUiScale();
bool touchPrefsSetUiScale(uint8_t scale);
/* T-Deck keyboard navigation: false (default) = keys type / jump tabs as usual;
* true = when no text field is focused, the WASDZ cluster moves focus — W up, A
* left, Z down, D right, S select, Q back — and the programmable tab hotkeys
* (touchPrefsGetNavKey) jump straight to a tab. The trackball always stays a soft
* mouse cursor. Applied live (no reboot). */
bool touchPrefsGetKbdNav();
bool touchPrefsSetKbdNav(bool on);
/* Keyboard-nav tab hotkeys: the ASCII key that jumps to each main tab while
* keyboard navigation is on. `tab` is the tab index 0..4 = chat / contacts / home
* / map / settings. Defaults E/R/T/U/I. Programmable in Settings → Keyboard. */
uint8_t touchPrefsGetNavKey(int tab);
bool touchPrefsSetNavKey(int tab, uint8_t ch);
/* Map zoom control style: false (default) = a zoom slider (toggled by the on-map
* button); true = a +/- button pair like older builds. Set in the Map options popup. */
bool touchPrefsGetMapZoomButtons();
bool touchPrefsSetMapZoomButtons(bool on);
/* Keyboard-nav control keys (ASCII), one per action: idx 0..5 = up, down, left,
* right, select, back; 6..7 = scroll up, scroll down. Defaults W/Z/A/D/S/Q/F/C.
* Programmable in Settings → Keyboard. */
uint8_t touchPrefsGetNavDirKey(int idx);
bool touchPrefsSetNavDirKey(int idx, uint8_t ch);
/* Home tab default: false (default) = the Commander screen; true = the app drawer
* (for users who prefer the launcher as their home). Toggled in the app drawer's cog. */
bool touchPrefsGetHomeIsDrawer();
bool touchPrefsSetHomeIsDrawer(bool on);
/** Hide the device/profile name in the status bar and move the clock to the
* left where the name used to be. Default false (name shown). */
bool touchPrefsGetHideNodeName();
+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
+42 -1
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!
@@ -399,7 +400,9 @@ void setup() {
* happens later in loop(); this just inits the stack.) */
if (want_wifi) {
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(false);
WiFi.setAutoReconnect(true); // let esp_wifi rejoin on its own after a beacon loss / AP reboot
// (was false — the 10 s poll below was the ONLY recovery, and a
// bare begin() on a wedged supplicant is a no-op -> never reconnected)
WiFi.persistent(false);
// NOTE: do NOT enable modem-sleep here. On a fresh, *unassociated* STA (the
// setup wizard, no creds yet) DTIM modem-sleep naps the radio through the
@@ -470,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
@@ -557,6 +571,10 @@ void loop() {
wifiConfigGetSsid(ssid, sizeof(ssid));
wifiConfigGetPwd(pwd, sizeof(pwd));
if (strlen(ssid) > 0) {
// A bare begin() on a supplicant wedged after a silent drop (AP reboot /
// beacon loss) can be a no-op — clear its state first so this forces a
// fresh association. Backs up setAutoReconnect(true) for the stuck case.
WiFi.disconnect(false, true);
WiFi.begin(ssid, pwd[0] ? pwd : nullptr);
}
}
@@ -614,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
@@ -666,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
+2644 -341
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();