Add BMA456 tap detection with ESP-NOW notify and host snapshot API.

Slaves forward configured tap kinds to the master; goTool exposes CLI, dashboard, REST, and WebSocket with separate notify vs receive and 2s display cache.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 20:42:57 +02:00
co-authored by Cursor
parent 3cb0b5bbe9
commit a8d4d42920
34 changed files with 3138 additions and 241 deletions
+54 -1
View File
@@ -116,6 +116,8 @@ Schema: `proto/esp_now_messages.proto`. Encode/decode: `esp_now_proto.c`. The ES
| `ESPNOW_FIND_ME` | Master → slave | `EspNowFindMe` (`client_id` filter) — LED locate sequence |
| `ESPNOW_RESTART` | Master → slave | `EspNowRestart` (`client_id` filter) — reboot slave |
| `ESPNOW_ACCEL_SAMPLE` | Slave → master | `EspNowAccelSample` (`slave_id`, `x`, `y`, `z` raw LSB) — ~every 16 ms |
| `ESPNOW_SET_TAP_NOTIFY` | Master → slave | `EspNowTapNotify` (`client_id`, `single`, `double_tap`, `triple`) — which tap kinds to forward |
| `ESPNOW_TAP_EVENT` | Slave → master | `EspNowTapEvent` (`client_id`, `kind`) — on BMA456 tap interrupt if notify enabled |
| `ESPNOW_BATTERY_REPORT` | Slave → master | `EspNowBatteryReport` (`client_id`, `lipo1/2` mV) — ~every 30 s; cached in `client_registry` |
| `ESPNOW_OTA_START` | Master → slave (unicast) | `EspNowOtaStart` (`total_size`) |
| `ESPNOW_OTA_PAYLOAD` | Master → slave | `EspNowOtaPayload` (`seq`, up to 200 B `data`) |
@@ -221,6 +223,8 @@ Host and master speak nanopb-encoded `UartMessage` inside UART frames (byte 0 =
| 22 | `FIND_ME` | Implemented (`cmd/cmd_espnow_find_me.c`) — `client_id=0` local ring, `>0` ESP-NOW to slave |
| 23 | `RESTART` | Implemented (`cmd/cmd_restart.c`) — `client_id=0` reboot master, `>0` ESP-NOW reboot slave |
| 24 | `ACCEL_SNAPSHOT` | Implemented (`cmd/cmd_accel_snapshot.c`) — cached slave accel from ESP-NOW stream |
| 27 | `TAP_NOTIFY` | Implemented (`cmd/cmd_tap_notify.c`) — get/set which tap kinds notify via ESP-NOW |
| 28 | `TAP_SNAPSHOT` | Implemented (`cmd/cmd_tap_snapshot.c`) — consume cached tap events from master registry |
Regenerate C code:
@@ -337,6 +341,52 @@ go run . -port /dev/ttyUSB0 accel
External API (`serve -api-addr :8081`) polls this command every 16 ms and streams JSON over WebSocket.
### TAP_NOTIFY command
Configure which BMA456 tap kinds a **slave** forwards to the master over ESP-NOW. The slave only sends `ESPNOW_TAP_EVENT` when the matching notify flag is enabled (set locally on the slave via ESP-NOW).
**Request:** framed `1b` (`0x1b`) + `tap_notify_request`:
| Field | Meaning |
|-------|---------|
| `write` | `false` = read, `true` = write |
| `single`, `double_tap`, `triple` | Which tap kinds to notify (write) |
| `client_id` | Slave id (read/write one slave) |
| `all_clients` | Master: ESP-NOW unicast to every registered slave |
**Response:** `tap_notify_response` (`client_id`, `success`, `slaves_updated`, `single`, `double_tap`, `triple`).
Notify flags are mirrored in `ClientInfo` (`tap_notify_single/double/triple`) for the dashboard.
```bash
go run . -port /dev/ttyUSB0 tap-notify -client 16 -set -single
go run . -port /dev/ttyUSB0 tap-notify -client 16
```
### TAP_SNAPSHOT command
Read **cached** tap events on the **master** (one pending event per slave). Slaves send `ESPNOW_TAP_EVENT` on tap; the master stores the latest value per client in `client_registry.c` for up to **16 ms** (`CLIENT_REGISTRY_TAP_MAX_AGE_MS`). Each snapshot **consumes** fresh events (cleared after read).
Only slaves with at least one tap-notify flag enabled are included.
**Request:** framed `1c` (`0x1c`) + optional `tap_snapshot_request` (`client_id`: `0` = all, `>0` = one id).
**Response:** `tap_snapshot_response.events[]`:
| Field | Meaning |
|-------|---------|
| `client_id` | Slave id (registry) |
| `valid` | Fresh tap available (≤16 ms) |
| `kind` | `TAP_SINGLE`, `TAP_DOUBLE`, or `TAP_TRIPLE` |
| `age_ms` | Ms since tap was received on master |
Host tools poll this only when **receive** is enabled (dashboard tap column, WebSocket `set_tap_stream`). They keep events visible for **2 s** in the UI/API after first sight.
```bash
go run . -port /dev/ttyUSB0 tap
go run . -port /dev/ttyUSB0 tap -client 16
```
### ESPNOW_UNICAST_TEST command
Minimal master→slave ESP-NOW unicast check (no BMA456). Use this before debugging `ACCEL_DEADZONE` unicast.
@@ -424,7 +474,7 @@ go run . -port /dev/ttyUSB0 led-ring -mode digit -digit 5 -all
**Response:** payload `04` + nanopb `UartMessage` with `client_info_response.clients` — one `ClientInfo` per registered slave (from ESP-NOW `SLAVE_INFO`).
Fields per client: `id`, `mac`, `version`, `available`, `used`, `last_ping`, `last_success_ping`**milliseconds since** the last packet / last successful heartbeat (computed when `CLIENT_INFO` is answered; typically 01000 while the slave is heartbeating every 1 s).
Fields per client: `id`, `mac`, `version`, `available`, `used`, `last_ping`, `last_success_ping`, `tap_notify_single`, `tap_notify_double`, `tap_notify_triple`**milliseconds since** the last packet / last successful heartbeat (computed when `CLIENT_INFO` is answered; typically 01000 while the slave is heartbeating every 1 s).
## Client registry
@@ -435,6 +485,7 @@ Fields per client: `id`, `mac`, `version`, `available`, `used`, `last_ping`, `la
| `client_registry_heartbeat(mac, id, version, …)` | Same as upsert for heartbeats; reactivates inactive clients |
| `client_registry_check_timeouts(timeout_ms)` | Mark stale clients inactive (master monitor task) |
| `client_registry_count()` / `client_registry_at(i)` | Iterate for UART encoding |
| `client_registry_set_tap_notify()` / `client_registry_take_tap()` | Tap notify flags + short-lived tap cache (16 ms) |
Slaves register when the master receives `SLAVE_INFO` on the matching network; `HEARTBEAT` keeps them marked available. The registry **MAC is always the ESP-NOW source address** (`recv_info.src_addr`), not the optional `mac` bytes in the protobuf (used only on the wire for debugging).
@@ -502,6 +553,8 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
| `client_registry.c/h` | Registered slave table |
| `bosch456.c/h` | BMA456H I2C driver, accel poll, on-demand read, tap INT, deadzone filter |
| `cmd/cmd_accel_snapshot.c` | UART `ACCEL_SNAPSHOT` — cached slave accel |
| `cmd/cmd_tap_notify.c` | UART `TAP_NOTIFY` — ESP-NOW tap notify config |
| `cmd/cmd_tap_snapshot.c` | UART `TAP_SNAPSHOT` — consume cached tap events |
| `board_input.c/h` | Taster GPIO12, LiPo ADC on GPIO1 / GPIO12 |
| `pod_settings.c/h` | NVS persistence (accel deadzone, …) |
| `led_ring.c/h` | LED ring (digit display, progress bar) |