diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 15c4ebf..216e61f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -109,7 +109,7 @@ sequenceDiagram └─────────────────────────────────────────────────────────────┘ ``` -**Host UART vs BLE:** Nur einer aktiv (DIP8). Gleiches Framing (`0xAA`…`0xCC`) und dieselbe `cmd_queue`. BLE: NUS-ähnliche GATT-Char, Write=RX, Notify=TX. `gotool serve` verbindet UART oder BLE über Dashboard/REST/WS (`managedHost`); **OTA** über UART oder BLE (ATT MTU ≥ ~219). Bei BLE-Host und aktivem Accel-Stream pusht `ble_stream` periodisch `BLE_STREAM_TICK` (8–16 ms; pausiert während OTA) mit `esp_timestamp_us` (Live-Pfad bevorzugt Tick statt CACHE_STATUS-Poll). +**Host UART vs BLE:** Nur einer aktiv (DIP8). Gleiches Framing (`0xAA`…`0xCC`) und dieselbe `cmd_queue`. BLE: NUS-ähnliche GATT-Char, Write=RX, Notify=TX. `gotool serve` verbindet UART oder BLE über Dashboard/REST/WS (`managedHost`); **OTA** über UART oder BLE (ATT MTU ≥ ~219). Bei BLE-Host und aktivem Accel-Stream pusht `ble_stream` periodisch `BLE_STREAM_TICK` (10–16 ms, Default 10; pausiert während OTA) mit `esp_timestamp_us` (Live-Pfad bevorzugt Tick statt CACHE_STATUS-Poll). --- @@ -315,7 +315,7 @@ Alle Master-Sends laufen über `send_message()` / `send_message_ex()`: | Nachricht | Task / Trigger | Master-Ziel | |-----------|----------------|-------------| -| `ESPNOW_ACCEL_SAMPLE` | `slave_accel_stream_task` 16 ms | `client_registry_update_accel()` | +| `ESPNOW_ACCEL_SAMPLE` | `slave_accel_stream_task` 10 ms (best-effort) | `client_registry_update_accel()` | | `ESPNOW_TAP_EVENT` | BMA456-IRQ → `on_bma456_tap` | `client_registry_update_tap()` | | `ESPNOW_BATTERY_REPORT` | Heartbeat + nach Join | `client_registry_update_battery()` | | `ESPNOW_SLAVE_INFO` / `HEARTBEAT` | `slave_tx_task` / Heartbeat-Task | `client_registry_heartbeat()` | @@ -325,7 +325,7 @@ Alle Master-Sends laufen über `send_message()` / `send_message_ex()`: ### 6.5 OTA über ESP-NOW -Nach erfolgreichem UART-OTA auf dem Master (oder `OTA_START_ESPNOW`): `ota_espnow.c` liest gestagte Partition, sendet `OTA_START` / `PAYLOAD` (≤200 B, `send_message_ex` mit ACK-Semaphore) / `OTA_END` an alle **available** Slaves. +Nach erfolgreichem UART-OTA auf dem Master (oder `OTA_START_ESPNOW`): `ota_espnow.c` liest gestagte Partition, sendet `OTA_START` / `PAYLOAD` (≤200 B, `send_message_ex` mit ACK-Semaphore) / `OTA_END` an alle **available** Slaves. Anschließend Reboot; Bootloader führt neues Image als `PENDING_VERIFY`, `ota_confirm` bestätigt nach stabilem Boot. Gemeinsamer Flash-Puffer: `ota_uart.c` (4 KiB Blockgröße). @@ -348,7 +348,7 @@ flowchart TB |-------|-------------------|-------------------| | Presence, Version | HEARTBEAT / SLAVE_INFO | `CLIENT_INFO` | | Accel-Stream-Samples | `ESPNOW_ACCEL_SAMPLE` | `CACHE_STATUS` | -| Tap-Events (16 ms Cache) | `ESPNOW_TAP_EVENT` | `CACHE_STATUS` | +| Tap-Events (10 ms Cache) | `ESPNOW_TAP_EVENT` | `CACHE_STATUS` | | Batterie | `ESPNOW_BATTERY_REPORT` + Master-ADC | `BATTERY_STATUS` | | Konfig-Flags | Handler + ESP-NOW | `CLIENT_INFO`, diverse GET | @@ -366,7 +366,7 @@ flowchart TB | `espnow_mon` | Master | 1 s Timeout + Master-Battery | | `espnow_stx` | Slave | Queue: SLAVE_INFO, Battery nach Join | | `espnow_hb` | Slave | 1 s Heartbeat + 30 s Battery | -| `espnow_accel` | Slave | 16 ms Accel wenn Stream an | +| `espnow_accel` | Slave | 10 ms Accel best-effort wenn Stream an | | `bma456_poll` | Beide | 10 Hz (`bosch456.c`) | | `vTaskLedRing` | Beide | LED-Ring-Befehle | @@ -391,6 +391,7 @@ flowchart TB | LED | `main/led_ring.c` | 95 LEDs, Digit-Maps | | OTA UART | `main/ota_uart.c`, `cmd/cmd_ota.c` | A/B-Partition Upload | | OTA ESP-NOW | `main/ota_espnow.c` | Verteilung an Slaves | +| OTA Confirm | `main/ota_confirm.c` | Nach ~8 s `mark_app_valid` (Rollback) | | Einstellungen NVS | `main/pod_settings.c` | Accel-Deadzone persistent | | Board | `main/board_input.c` | Taster, LiPo-ADC | | Protobuf Schemas | `main/proto/*.proto` | Vertrag UART / ESP-NOW | diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 3ace42a..7f5a02b 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -55,7 +55,7 @@ Zielbild für den Host: Befehle an den Master senden; der Master steuert Slaves | LiPo ADC 1 | 1 | `board_input.c` | | LiPo ADC 2 | 11 | `board_input.c` | -**Host-Protokoll (UART oder BLE):** Gleiches Framing (`0xAA`…`0xCC`) + nanopb. **UART:** `UART_NUM_1`, **921600** Baud, 8N1. **BLE:** Gerät `Powerpod`, NUS-UUIDs `6e400001`/`6e400002` (Write=RX, Notify=TX). goTool CLI: `-port …` oder `-ble` / `-ble-name Powerpod`. **`gotool serve`** startet ohne Pflicht-Gerät; Dashboard/REST/WS verbinden UART oder BLE dynamisch (`-port`/`-ble` nur optionale Vorbelegung). **OTA** über UART oder BLE (DIP8); BLE braucht ATT MTU ≥ ~219 für 200-B-Chunks. Bei BLE + Accel-Stream: periodische `BLE_STREAM_TICK`-Notifies (`period_ms` 8–16; während OTA pausiert). +**Host-Protokoll (UART oder BLE):** Gleiches Framing (`0xAA`…`0xCC`) + nanopb. **UART:** `UART_NUM_1`, **921600** Baud, 8N1. **BLE:** Gerät `Powerpod`, NUS-UUIDs `6e400001`/`6e400002` (Write=RX, Notify=TX). goTool CLI: `-port …` oder `-ble` / `-ble-name Powerpod`. **`gotool serve`** startet ohne Pflicht-Gerät; Dashboard/REST/WS verbinden UART oder BLE dynamisch (`-port`/`-ble` nur optionale Vorbelegung). **OTA** über UART oder BLE (DIP8); BLE braucht ATT MTU ≥ ~219 für 200-B-Chunks. Bei BLE + Accel-Stream: periodische `BLE_STREAM_TICK`-Notifies (`period_ms` 10–16; während OTA pausiert). **ESP-IDF-Log (Debug):** `esp_log_*` geht auf **UART0** (Standard-Konsole, typisch USB am Dev-Board, **115200** Baud, `CONFIG_ESP_CONSOLE_UART_NUM=0`). Das ist **getrennt** vom Host-UART1 — goTool liest keine `esp_log`-Ausgabe. Ohne angeschlossenes Debug-Kabel werden aktivierte Logs trotzdem formatiert und an UART0 gesendet (CPU-Overhead bleibt); nur `ESP_LOG_NONE` / Level-Filter vermeiden die Arbeit. @@ -177,7 +177,7 @@ UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadz | `ESPNOW_HEARTBEAT` | S→M | `heartbeat` | Keepalive 1 s | | `ESPNOW_SET_ACCEL_DEADZONE` | M→S | `accel_deadzone` | Deadzone LSB | | `ESPNOW_SET_TAP_CONFIG` | M→S | `tap_config` | Multitap-Parameter + NVS | -| `ESPNOW_SET_ACCEL_STREAM` | M→S | `accel_stream` | Stream ~16 ms | +| `ESPNOW_SET_ACCEL_STREAM` | M→S | `accel_stream` | Stream ~10 ms (best-effort) | | `ESPNOW_ACCEL_SAMPLE` | S→M | `accel_sample` | x/y/z Roh-LSB | | `ESPNOW_SET_TAP_NOTIFY` | M→S | `tap_notify` | Tap-Arten filtern | | `ESPNOW_TAP_EVENT` | S→M | `tap_event` | kind 1/2/3 | @@ -199,7 +199,7 @@ UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadz | Heartbeat | 1000 ms | | Client-Timeout (Master) | 3 × Heartbeat = 3 s → `available=false` | | Master-Verlust (Slave) | 5 s ohne Discover | -| Accel-Stream | 16 ms | +| Accel-Stream | 10 ms (best-effort, kein ACK-Wait; = FreeRTOS-Tick) | | Batterie-Report | 30 s (+ einmal 150 ms nach Join) | | Echo-Ping Timeout (Master) | 500 ms (`ESPNOW_ECHO_PING_TIMEOUT_MS`) | @@ -260,9 +260,9 @@ Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uar ### 7.4 ACCEL_STREAM (25) -- Optional `period_ms` (8–16): BLE-Host-Push-Intervall für `BLE_STREAM_TICK` (UART ignoriert; Default 16). +- Optional `period_ms` (10–16): BLE-Host-Push-Intervall für `BLE_STREAM_TICK` (UART ignoriert; Default 10). - Master setzt `client_registry_set_accel_stream` und sendet `ESPNOW_SET_ACCEL_STREAM`. -- Slave-Task `slave_accel_stream_task` sendet `ESPNOW_ACCEL_SAMPLE` alle 16 ms. +- Slave-Task `slave_accel_stream_task` sendet `ESPNOW_ACCEL_SAMPLE` alle 10 ms (best-effort / `send_best_effort`). ### 7.5 TAP_NOTIFY (27) @@ -371,6 +371,7 @@ Implementierung: `ota_uart.c`, Steuerung `cmd/cmd_ota.c`. Gleiches Protokoll üb - Inaktive Partition: `esp_ota_get_next_update_partition()`. - LED-Ring zeigt Fortschritt (blau Schreiben, grün Verteilung). - BLE: ATT MTU ≥ ~219 (preferred 247); `BLE_STREAM_TICK` pausiert während `ota_session_busy()`. +- **App-Rollback** (`CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE`): neues Image bootet als `PENDING_VERIFY`. `ota_confirm` markiert nach ~8 s stabilem Lauf `mark_app_valid`; Crash/Reset davor → Bootloader rollt auf die vorherige App zurück. Während `PENDING_VERIFY` ist ein neues `OTA_START` blockiert. ### 8.2 ESP-NOW-OTA (Master → Slaves) @@ -378,10 +379,10 @@ Implementierung: `ota_uart.c`, Steuerung `cmd/cmd_ota.c`. Gleiches Protokoll üb 1. `ESPNOW_OTA_START` + `total_size` → Slave `ota_espnow_slave_on_start` 2. `ESPNOW_OTA_PAYLOAD` bis 200 B → 4 KiB Buffer auf Slave -3. `ESPNOW_OTA_END` → Boot-Partition setzen +3. `ESPNOW_OTA_END` → Boot-Partition setzen + automatischer Reboot 4. Slave antwortet `ESPNOW_OTA_STATUS` (mit `send_message_ex` + Semaphore auf Master) -Nach Erfolg: **alle Knoten neu starten**. +Nach Erfolg: Master und Slaves starten automatisch neu (Rollback-Confirm siehe 8.1). ### 8.3 OTA_SLAVE_PROGRESS (21) diff --git a/goTool/README.md b/goTool/README.md index eee3727..806e6f0 100644 --- a/goTool/README.md +++ b/goTool/README.md @@ -27,7 +27,7 @@ go run . -port /dev/ttyUSB0 clients | `deadzone` | `0x06` | Get/set accelerometer deadzone LSB (`-set`, `-value`, `-client`, `-all`) | | `tap-config` | `0x20` | Get/set BMA456 tap detection params (`-set`, `-client`, `-all`, `-sens`, `-max-gest`, `-shock`, `-quiet`, `-wait`, `-axis`, `-single`, `-double`, `-triple`) | | `tap-notify` | `0x1b` | Get/set which tap kinds (single/double/triple) notify via ESP-NOW (`-set`, `-client`, `-all`, `-single`, `-double`, `-triple`) | -| `cache-status` | `0x1d` | Subscribed accel + tap cache (`CACHE_STATUS`); one UART round-trip for 16 ms polling | +| `cache-status` | `0x1d` | Subscribed accel + tap cache (`CACHE_STATUS`); one UART round-trip for ~8 ms polling | | `unicast-test` | `0x07` | Sends ESP-NOW unicast test to one slave (`-client`, `-seq`) | | `echo-ping` | `0x1e` | ESP-NOW echo round-trip to one slave (`-client`); prints `rtt_ms` (host UART chain) and `esp_rtt_us` (master ESP-NOW, raw µs) | | `test` | — | Run an automated scenario (JSON configs under `testdata/`) | @@ -79,7 +79,7 @@ Open [http://localhost:8080](http://localhost:8080) — Host-Verbindung panel (U | Column | Meaning | |--------|---------| | Tap-Notify (S/D/T) | Which tap kinds the **slave** sends to the master over ESP-NOW (UART `TAP_NOTIFY`) — does **not** poll UART | -| Tap (An/Aus) | Host **receive**: poll master tap cache (~16 ms) and show last tap for **≥2 s** | +| Tap (An/Aus) | Host **receive**: poll master tap cache (~8 ms) and show last tap for **≥2 s** | Enable notify first, then turn receive on to see events. Same split as the external WebSocket API (`set_tap_notify` vs `set_tap_stream`). diff --git a/goTool/api_ble_measure.go b/goTool/api_ble_measure.go index 841ef91..dcc187a 100644 --- a/goTool/api_ble_measure.go +++ b/goTool/api_ble_measure.go @@ -94,7 +94,7 @@ func serveBleStreamTest(w http.ResponseWriter, r *http.Request, link *managedHos return } count := 50 - periodMs := uint32(16) + periodMs := uint32(10) tolMs := 2.0 timeoutS := 15.0 var clientID uint32 diff --git a/goTool/api_stream.go b/goTool/api_stream.go index d9d6d6b..c434839 100644 --- a/goTool/api_stream.go +++ b/goTool/api_stream.go @@ -16,7 +16,7 @@ import ( ) const ( - defaultAccelStreamInterval = 16 * time.Millisecond + defaultAccelStreamInterval = 10 * time.Millisecond defaultPreFetchMs = 2 minAPIStreamInterval = 1 * time.Millisecond maxAPIStreamInterval = 10 * time.Second diff --git a/goTool/ble_measure.go b/goTool/ble_measure.go index 4f6627e..819e7f0 100644 --- a/goTool/ble_measure.go +++ b/goTool/ble_measure.go @@ -79,8 +79,8 @@ func measureBleStream(h hostConn, clientID, periodMs uint32, count int, tolMs, t PeriodMs: periodMs, TolMs: tolMs, } - if periodMs < 8 || periodMs > 16 { - out.Error = "period_ms must be 8..16" + if periodMs < 10 || periodMs > 16 { + out.Error = "period_ms must be 10..16" return out } if count < 2 { diff --git a/goTool/cmd_accel_stream.go b/goTool/cmd_accel_stream.go index b24e5ef..3d056ee 100644 --- a/goTool/cmd_accel_stream.go +++ b/goTool/cmd_accel_stream.go @@ -13,7 +13,7 @@ func runAccelStreamCmd(h hostConn, args []string) error { all := fs.Bool("all", false, "all registered clients") enable := fs.Bool("enable", true, "enable stream") disable := fs.Bool("disable", false, "disable stream") - period := fs.Uint("period", 0, "BLE push period_ms 8–16 (0=leave/default)") + period := fs.Uint("period", 0, "BLE push period_ms 10–16 (0=leave/default)") if err := fs.Parse(args); err != nil { return err } diff --git a/goTool/cmd_ble_stream.go b/goTool/cmd_ble_stream.go index 9768301..f85530f 100644 --- a/goTool/cmd_ble_stream.go +++ b/goTool/cmd_ble_stream.go @@ -8,7 +8,7 @@ import ( func runBleStream(h hostConn, args []string) error { fs := flag.NewFlagSet("ble-stream", flag.ExitOnError) clientID := fs.Uint("client", 0, "slave client id (0 = all_clients)") - periodMs := fs.Uint("period", 16, "BLE push period_ms (8–16)") + periodMs := fs.Uint("period", 10, "BLE push period_ms (10–16)") count := fs.Int("count", 50, "number of BleStreamTick samples") tolMs := fs.Float64("tol", 2.0, "tolerance ms around period for window_ok") duration := fs.Float64("timeout", 15.0, "max seconds waiting for ticks") diff --git a/goTool/dashboard.go b/goTool/dashboard.go index 36c2d4d..99b5768 100644 --- a/goTool/dashboard.go +++ b/goTool/dashboard.go @@ -75,7 +75,7 @@ type DashboardState struct { UARTConnected bool `json:"uart_connected"` SerialOK bool `json:"serial_ok"` SerialError string `json:"serial_error,omitempty"` - /** Host: fast CACHE_STATUS poll (~16 ms) for accel + tap. */ + /** Host: fast CACHE_STATUS poll (~8 ms) for accel + tap. */ LiveStream bool `json:"live_stream"` /** BLE live path: inter-tick stats from BleStreamTick.esp_timestamp_us. */ BleTick *BleTickView `json:"ble_tick,omitempty"` @@ -489,7 +489,7 @@ func (h *wsHub) snapshotClients() []ClientView { return out } -// patchLiveStream toggles host CACHE_STATUS polling (~16 ms). +// patchLiveStream toggles host CACHE_STATUS polling (~8 ms). func (h *wsHub) patchLiveStream(enabled bool) { h.mu.Lock() h.liveStream = enabled diff --git a/goTool/docs/API_REST.md b/goTool/docs/API_REST.md index 5933d64..a5ebd28 100644 --- a/goTool/docs/API_REST.md +++ b/goTool/docs/API_REST.md @@ -148,13 +148,13 @@ POST /api/ble-rtt → {"success":true,"count":50,"warmup":5,"stats":{"n":50,"min_ms":…,"median_ms":…,"mean_ms":…,"max_ms":…}} POST /api/ble-stream -{"count":50,"period_ms":16,"tol_ms":2,"timeout_s":15,"client_id":0} +{"count":50,"period_ms":8,"tol_ms":2,"timeout_s":15,"client_id":0} → {"success":true,"received":50,"requested":50,"window_ok":true,"dt_esp_ms":{…},"dr_pc_ms":{…},…} ``` Pauses dashboard/API polling while running. Requires active BLE host (`DIP8=1`). -### Live stream (host `CACHE_STATUS` poll ~16 ms; BLE uses `BLE_STREAM_TICK`) +### Live stream (host `CACHE_STATUS` poll ~8 ms; BLE uses `BLE_STREAM_TICK`) ```http GET /api/live-stream diff --git a/goTool/ota_upload.go b/goTool/ota_upload.go index 306bf59..f1cd95e 100644 --- a/goTool/ota_upload.go +++ b/goTool/ota_upload.go @@ -21,7 +21,7 @@ const ( otaDistQueryTimeout = 2 * time.Second otaDistEmitMinInterval = 150 * time.Millisecond // Pace host chunks so the master UART RX ring is not overrun (~20 frames/block). - otaHostChunkPaceUART = 3 * time.Millisecond + otaHostChunkPaceUART = 5 * time.Millisecond // Slightly slower for BLE WriteWithoutResponse flow control. otaHostChunkPaceBLE = 6 * time.Millisecond otaBlockMaxRetries = 3 @@ -389,7 +389,7 @@ func runOTAUnlocked(t otaTransport, firmware []byte, onProgress otaProgressFn) e mergeSlaveProgressResponse(prog, slaveBytes, slaveDetails) } notify("done", "", 100, - fmt.Sprintf("Fertig — %d bytes, Boot-Slot %d. Master und Slaves neu starten.", + fmt.Sprintf("Fertig — %d bytes, Boot-Slot %d. Geräte starten neu auf die neue Firmware.", st.GetBytesWritten(), st.GetTargetSlot()), OTAProgress{ Bytes: st.GetBytesWritten(), Slot: st.GetTargetSlot(), diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f513ecb..84aa29b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -39,6 +39,7 @@ idf_component_register( "ota_uart.c" "ota_espnow.c" "ota_session.c" + "ota_confirm.c" "client_registry.c" "esp_now_comm.c" "esp_now_core.c" diff --git a/main/README.md b/main/README.md index 2a31f84..4dc8be3 100644 --- a/main/README.md +++ b/main/README.md @@ -124,7 +124,7 @@ Schema: `proto/esp_now_messages.proto`. Encode/decode: `esp_now_proto.c`. The ES | `ESPNOW_UNICAST_TEST` | Master → slave | `EspNowUnicastTest` (`seq`) | | `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_ACCEL_SAMPLE` | Slave → master | `EspNowAccelSample` (`slave_id`, `x`, `y`, `z` raw LSB) — ~every 8 ms, best-effort | | `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` | @@ -376,7 +376,7 @@ go run . -port /dev/ttyUSB0 tap-notify -client 16 ### CACHE_STATUS command -Read **cached** accel and/or tap data on the **master** in one UART round-trip. Slaves send `ESPNOW_ACCEL_SAMPLE` every **16 ms** when streaming; tap events arrive via `ESPNOW_TAP_EVENT` and are held up to **16 ms** (`CLIENT_REGISTRY_TAP_MAX_AGE_MS`). Pending taps are **consumed** on read (like the former `TAP_SNAPSHOT`). +Read **cached** accel and/or tap data on the **master** in one UART round-trip. Slaves send `ESPNOW_ACCEL_SAMPLE` every **8 ms** (best-effort) when streaming; tap events arrive via `ESPNOW_TAP_EVENT` and are held up to **8 ms** (`CLIENT_REGISTRY_TAP_MAX_AGE_MS`). Pending taps are **consumed** on read (like the former `TAP_SNAPSHOT`). **Request:** framed `1d` (`0x1d`) only — no body (`CacheStatusRequest` empty). @@ -390,7 +390,7 @@ Read **cached** accel and/or tap data on the **master** in one UART round-trip. Unsubscribed submessages are omitted on the wire (proto3 defaults). The master walks `client_registry` once per request (`cmd/cmd_cache_status.c`). -Host tools poll this at **16 ms** when live-stream / WebSocket receive is enabled. Tap events stay visible for **2 s** in the UI/API after first sight. +Host tools poll this at **~8 ms** when live-stream / WebSocket receive is enabled. Tap events stay visible for **2 s** in the UI/API after first sight. ```bash go run . -port /dev/ttyUSB0 cache-status @@ -534,7 +534,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) | +| `client_registry_set_tap_notify()` / `client_registry_take_tap()` | Tap notify flags + short-lived tap cache (8 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). diff --git a/main/ble_stream.c b/main/ble_stream.c index 1a8bc1f..10eb8c7 100644 --- a/main/ble_stream.c +++ b/main/ble_stream.c @@ -15,9 +15,10 @@ static const char *TAG = "[BLE_STREAM]"; -#define BLE_STREAM_PERIOD_MIN_MS 8u +/* Min matches FreeRTOS tick (HZ=100 → 10 ms); 8 ms would delay 0 ticks. */ +#define BLE_STREAM_PERIOD_MIN_MS 10u #define BLE_STREAM_PERIOD_MAX_MS 16u -#define BLE_STREAM_PERIOD_DEFAULT_MS 16u +#define BLE_STREAM_PERIOD_DEFAULT_MS 10u static uint32_t s_period_ms = BLE_STREAM_PERIOD_DEFAULT_MS; static TaskHandle_t s_task; @@ -164,7 +165,11 @@ static void ble_stream_task(void *param) { } } - vTaskDelay(pdMS_TO_TICKS(period)); + TickType_t delay_ticks = pdMS_TO_TICKS(period); + if (delay_ticks < 1) { + delay_ticks = 1; + } + vTaskDelay(delay_ticks); } s_task = NULL; diff --git a/main/ble_stream.h b/main/ble_stream.h index 3effab5..a0691c7 100644 --- a/main/ble_stream.h +++ b/main/ble_stream.h @@ -4,7 +4,7 @@ #include #include -/** Clamp and store BLE push period (8–16 ms). */ +/** Clamp and store BLE push period (10–16 ms; min = FreeRTOS tick). */ void ble_stream_set_period_ms(uint32_t period_ms); uint32_t ble_stream_period_ms(void); diff --git a/main/client_registry.h b/main/client_registry.h index 3820353..e07b3eb 100644 --- a/main/client_registry.h +++ b/main/client_registry.h @@ -53,7 +53,7 @@ typedef struct { #define CLIENT_REGISTRY_DEFAULT_ACCEL_DEADZONE 100u /** Tap events older than this are discarded (matches accel stream interval). */ -#define CLIENT_REGISTRY_TAP_MAX_AGE_MS 16u +#define CLIENT_REGISTRY_TAP_MAX_AGE_MS 10u void client_registry_init(void); diff --git a/main/cmd/cmd_handler.c b/main/cmd/cmd_handler.c index 1e9db22..8560997 100644 --- a/main/cmd/cmd_handler.c +++ b/main/cmd/cmd_handler.c @@ -111,8 +111,13 @@ void vCmdDispatcherTask(void *param) { bool handled = false; for (int i = 0; i < handler_count; i++) { if (handlers[i].msg_id == msg.msg_id) { - ESP_LOGI(TAG, "trigger command %s (0x%02x)", message_type_name(msg.msg_id), - (unsigned)msg.msg_id); + if (msg.msg_id == (uint16_t)alox_MessageType_OTA_PAYLOAD) { + ESP_LOGD(TAG, "trigger command %s (0x%02x)", + message_type_name(msg.msg_id), (unsigned)msg.msg_id); + } else { + ESP_LOGI(TAG, "trigger command %s (0x%02x)", + message_type_name(msg.msg_id), (unsigned)msg.msg_id); + } handlers[i].callback(msg.payload, msg.len); handled = true; break; diff --git a/main/cmd/cmd_ota.c b/main/cmd/cmd_ota.c index b18b3f1..f64a634 100644 --- a/main/cmd/cmd_ota.c +++ b/main/cmd/cmd_ota.c @@ -2,6 +2,7 @@ #include "led_ring.h" #include "ota_espnow.h" #include "ota_uart.h" +#include "pod_reboot.h" #include "uart_cmd.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" @@ -179,8 +180,10 @@ static void handle_ota_payload(const uint8_t *data, size_t len) { ota_feed_result_t r = ota_uart_feed_chunk(req_ptr->seq, req_ptr->data.bytes, req_ptr->data.size); + /* Gap: keep session; host block-retries from the last ACK boundary. */ if (r == OTA_FEED_SEQ_GAP) { - send_ota_failed(16); + ESP_LOGW(TAG, "OTA_PAYLOAD seq gap (got %lu) — waiting for host retry", + (unsigned long)req_ptr->seq); return; } if (r == OTA_FEED_SEQ_DUP) { @@ -257,6 +260,8 @@ static void ota_distribute_task(void *param) { led_ring_ota_success(); free(job); + /* Host already got SUCCESS; reboot onto the staged image. */ + pod_schedule_restart(); vTaskDelete(NULL); } @@ -332,6 +337,7 @@ static void ota_start_espnow_task(void *param) { response.payload.ota_status.error = 0; uart_cmd_send(&response, TAG); led_ring_ota_success(); + pod_schedule_restart(); vTaskDelete(NULL); } diff --git a/main/esp_now_core.c b/main/esp_now_core.c index 955a21d..015b782 100644 --- a/main/esp_now_core.c +++ b/main/esp_now_core.c @@ -204,10 +204,11 @@ esp_err_t esp_now_core_send_reliable(const uint8_t *dest_mac, ESPNOW_SEND_RETRY_DELAY_MS, true); } -esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac, - const alox_EspNowMessage *msg) { +static esp_err_t send_fast_internal(const uint8_t *dest_mac, + const alox_EspNowMessage *msg, + bool quiet) { if (s_send_lock == NULL || - xSemaphoreTake(s_send_lock, pdMS_TO_TICKS(5000)) != pdTRUE) { + xSemaphoreTake(s_send_lock, pdMS_TO_TICKS(5)) != pdTRUE) { return ESP_ERR_TIMEOUT; } @@ -216,14 +217,18 @@ esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac, esp_err_t err = esp_now_proto_encode(msg, buf, sizeof(buf), &len); if (err != ESP_OK) { - ESP_LOGW(TAG, "encode failed"); + if (!quiet) { + ESP_LOGW(TAG, "encode failed"); + } xSemaphoreGive(s_send_lock); return err; } if (len > ESP_NOW_MAX_DATA_LEN) { - ESP_LOGW(TAG, "encoded len %u > ESP-NOW max %u", (unsigned)len, - (unsigned)ESP_NOW_MAX_DATA_LEN); + if (!quiet) { + ESP_LOGW(TAG, "encoded len %u > ESP-NOW max %u", (unsigned)len, + (unsigned)ESP_NOW_MAX_DATA_LEN); + } xSemaphoreGive(s_send_lock); return ESP_ERR_INVALID_SIZE; } @@ -234,7 +239,7 @@ esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac, } err = esp_now_send(dest_mac, buf, len); - if (err != ESP_OK) { + if (err != ESP_OK && !quiet) { ESP_LOGW(TAG, "send type=%u failed: %s", (unsigned)msg->type, esp_err_to_name(err)); } @@ -242,6 +247,16 @@ esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac, return err; } +esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac, + const alox_EspNowMessage *msg) { + return send_fast_internal(dest_mac, msg, false); +} + +esp_err_t esp_now_core_send_best_effort(const uint8_t *dest_mac, + const alox_EspNowMessage *msg) { + return send_fast_internal(dest_mac, msg, true); +} + esp_err_t esp_now_core_send(const uint8_t *dest_mac, const alox_EspNowMessage *msg) { return esp_now_core_send_wait(dest_mac, msg); diff --git a/main/esp_now_core.h b/main/esp_now_core.h index f5a00c6..a2987b4 100644 --- a/main/esp_now_core.h +++ b/main/esp_now_core.h @@ -26,6 +26,12 @@ esp_err_t esp_now_core_send(const uint8_t *dest_mac, /** Like send but does not wait for esp_now send-done (lower latency). */ esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac, const alox_EspNowMessage *msg); +/** + * Fire-and-forget without failure logs — for high-rate gaming input + * (accel/tap) where drops are expected under load. + */ +esp_err_t esp_now_core_send_best_effort(const uint8_t *dest_mac, + const alox_EspNowMessage *msg); esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac, const alox_EspNowMessage *msg); /** OTA payloads: wait for send-done with extra NO_MEM backoff (queue drain). */ diff --git a/main/esp_now_slave.c b/main/esp_now_slave.c index d41a123..a0c2e39 100644 --- a/main/esp_now_slave.c +++ b/main/esp_now_slave.c @@ -23,7 +23,12 @@ #define SLAVE_MASTER_LOST_MS (ESPNOW_HEARTBEAT_INTERVAL_MS * 5) /** While master or slave OTA is in progress (discover may be sparse). */ #define SLAVE_MASTER_OTA_GRACE_MS 300000u -#define ESPNOW_ACCEL_INTERVAL_MS 16 +/** + * Best-effort gaming input cadence (loss OK; send_best_effort). + * Must be >= one FreeRTOS tick (CONFIG_FREERTOS_HZ=100 → 10 ms); shorter + * values become 0 ticks and assert in vTaskDelayUntil. + */ +#define ESPNOW_ACCEL_INTERVAL_MS 10 #define ESPNOW_BATTERY_INTERVAL_MS 30000 #define SLAVE_BATTERY_AFTER_JOIN_MS 150 @@ -165,7 +170,8 @@ static esp_err_t send_accel_sample(const uint8_t *dest_mac, uint32_t slave_id, msg.payload.accel_sample.x = x; msg.payload.accel_sample.y = y; msg.payload.accel_sample.z = z; - return esp_now_core_send(dest_mac, &msg); + /* Fire-and-forget: controller input prefers low latency over delivery. */ + return esp_now_core_send_best_effort(dest_mac, &msg); } static esp_err_t send_tap_event(const uint8_t *dest_mac, uint32_t slave_id, @@ -175,7 +181,7 @@ static esp_err_t send_tap_event(const uint8_t *dest_mac, uint32_t slave_id, msg.which_payload = alox_EspNowMessage_tap_event_tag; msg.payload.tap_event.slave_id = slave_id; msg.payload.tap_event.kind = kind; - return esp_now_core_send(dest_mac, &msg); + return esp_now_core_send_best_effort(dest_mac, &msg); } static esp_err_t send_battery_report(const uint8_t *dest_mac, @@ -579,11 +585,15 @@ static void check_master_timeout(void) { static void accel_stream_task(void *param) { (void)param; - ESP_LOGI(TAG, "accel stream task (interval %u ms)", + ESP_LOGI(TAG, "accel stream task (interval %u ms, best-effort)", (unsigned)ESPNOW_ACCEL_INTERVAL_MS); + TickType_t last_wake = xTaskGetTickCount(); + const TickType_t period_ticks = pdMS_TO_TICKS(ESPNOW_ACCEL_INTERVAL_MS); + /* pdMS_TO_TICKS can be 0 when ms < tick period — DelayUntil asserts. */ + const TickType_t delay_ticks = period_ticks > 0 ? period_ticks : 1; while (1) { - vTaskDelay(pdMS_TO_TICKS(ESPNOW_ACCEL_INTERVAL_MS)); + vTaskDelayUntil(&last_wake, delay_ticks); if (!s_joined || s_assigned_id == 0 || !s_accel_stream_enabled || !bma456_is_ready()) { continue; diff --git a/main/host_transport.c b/main/host_transport.c index a0853d9..2a57f27 100644 --- a/main/host_transport.c +++ b/main/host_transport.c @@ -2,6 +2,7 @@ #include "cmd_handler.h" #include "esp_log.h" #include "uart.h" +#include "uart_messages.pb.h" #include #include @@ -87,9 +88,16 @@ void host_rx_bytes(const uint8_t *data, size_t len) { for (size_t i = 0; i < len; i++) { if (parse_uart_byte(data[i], &s_rx_packet)) { - ESP_LOGI(TAG, "received message cmd=0x%02x len=%u", - s_rx_packet.len > 0 ? s_rx_packet.payload[0] : 0, - (unsigned)s_rx_packet.len); + uint8_t cmd = + s_rx_packet.len > 0 ? s_rx_packet.payload[0] : 0; + /* OTA payloads are high-rate; avoid INFO spam starving the dispatcher. */ + if (cmd == (uint8_t)alox_MessageType_OTA_PAYLOAD) { + ESP_LOGD(TAG, "received message cmd=0x%02x len=%u", cmd, + (unsigned)s_rx_packet.len); + } else { + ESP_LOGI(TAG, "received message cmd=0x%02x len=%u", cmd, + (unsigned)s_rx_packet.len); + } host_enqueue_packet(s_rx_packet.payload, s_rx_packet.len); } } diff --git a/main/ota_confirm.c b/main/ota_confirm.c new file mode 100644 index 0000000..4056a89 --- /dev/null +++ b/main/ota_confirm.c @@ -0,0 +1,60 @@ +#include "ota_confirm.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_partition.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "[OTA_CONFIRM]"; + +/** Long enough for early crash loops (e.g. assert in a worker task) to reboot + * before we cancel rollback; short enough that a follow-up OTA is not blocked + * for long (esp_ota_begin fails while PENDING_VERIFY). */ +#define OTA_CONFIRM_DELAY_MS 8000u + +static void ota_confirm_task(void *param) { + (void)param; + + vTaskDelay(pdMS_TO_TICKS(OTA_CONFIRM_DELAY_MS)); + + const esp_partition_t *running = esp_ota_get_running_partition(); + if (running == NULL) { + ESP_LOGW(TAG, "no running partition"); + vTaskDelete(NULL); + return; + } + + esp_ota_img_states_t state = ESP_OTA_IMG_UNDEFINED; + esp_err_t err = esp_ota_get_state_partition(running, &state); + if (err != ESP_OK) { + /* otadata missing / non-OTA slot — nothing to confirm. */ + ESP_LOGI(TAG, "ota state unavailable (%s) — skip confirm", + esp_err_to_name(err)); + vTaskDelete(NULL); + return; + } + + if (state != ESP_OTA_IMG_PENDING_VERIFY) { + ESP_LOGI(TAG, "running %s state=%d — no pending verify", running->label, + (int)state); + vTaskDelete(NULL); + return; + } + + err = esp_ota_mark_app_valid_cancel_rollback(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "mark_app_valid failed: %s", esp_err_to_name(err)); + } else { + ESP_LOGI(TAG, "app confirmed valid (%s) — rollback cancelled", + running->label); + } + vTaskDelete(NULL); +} + +void ota_confirm_schedule(void) { + if (xTaskCreate(ota_confirm_task, "ota_confirm", 3072, NULL, 3, NULL) != + pdPASS) { + ESP_LOGE(TAG, "failed to create confirm task — attempting immediate mark"); + (void)esp_ota_mark_app_valid_cancel_rollback(); + } +} diff --git a/main/ota_confirm.h b/main/ota_confirm.h new file mode 100644 index 0000000..ad5e426 --- /dev/null +++ b/main/ota_confirm.h @@ -0,0 +1,14 @@ +#ifndef OTA_CONFIRM_H +#define OTA_CONFIRM_H + +/** + * After OTA with BOOTLOADER_APP_ROLLBACK_ENABLE, the new image boots as + * ESP_OTA_IMG_PENDING_VERIFY. If we crash/reset before confirming, the + * bootloader rolls back to the previous app on the next boot. + * + * Call once from app_main after init: schedules a delayed confirm so early + * panics (task asserts, etc.) still trigger rollback. + */ +void ota_confirm_schedule(void); + +#endif diff --git a/main/ota_espnow.c b/main/ota_espnow.c index a0bafd3..598c90f 100644 --- a/main/ota_espnow.c +++ b/main/ota_espnow.c @@ -10,6 +10,7 @@ #include "freertos/event_groups.h" #include "freertos/idf_additions.h" #include "ota_uart.h" +#include "pod_reboot.h" #include static const char *TAG = "[OTA_ESPNOW]"; @@ -186,6 +187,10 @@ static bool any_target_failed(void) { /** * Wait until all want_bits are set, or OTA_EG_FAIL_BIT (slave FAILED). * Returns true only when every target bit is set; false on fail or timeout. + * + * Wait only for *still missing* bits. Waiting on the full mask with "any bit" + * busy-spins after the first slave ACK (already-set bits make WaitBits return + * immediately) and starves IDLE → task_wdt. */ static bool wait_target_bits(uint32_t want_bits, uint32_t timeout_ms) { if (s_eg == NULL || want_bits == 0) { @@ -194,7 +199,6 @@ static bool wait_target_bits(uint32_t want_bits, uint32_t timeout_ms) { const TickType_t start = xTaskGetTickCount(); const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); - const EventBits_t wait_mask = want_bits | OTA_EG_FAIL_BIT; while (true) { TickType_t elapsed = xTaskGetTickCount() - start; @@ -203,16 +207,19 @@ static bool wait_target_bits(uint32_t want_bits, uint32_t timeout_ms) { } TickType_t remain = timeout_ticks - elapsed; - EventBits_t got = - xEventGroupWaitBits(s_eg, wait_mask, pdFALSE, pdFALSE, remain); - if (got & OTA_EG_FAIL_BIT) { + EventBits_t have = xEventGroupGetBits(s_eg); + if (have & OTA_EG_FAIL_BIT) { xEventGroupClearBits(s_eg, OTA_EG_FAIL_BIT); return false; } - if ((got & want_bits) == want_bits) { + if ((have & want_bits) == want_bits) { xEventGroupClearBits(s_eg, want_bits); return true; } + + EventBits_t missing = want_bits & ~have; + (void)xEventGroupWaitBits(s_eg, missing | OTA_EG_FAIL_BIT, pdFALSE, + pdFALSE, remain); } } @@ -314,9 +321,10 @@ static void process_slave_payload(const uint8_t master_mac[6], ota_feed_result_t r = ota_uart_feed_chunk(payload->seq, payload->data.bytes, payload->data.size); + /* Gap: keep session; master block-retries from the last ACK boundary. */ if (r == OTA_FEED_SEQ_GAP) { - led_ring_ota_failed(); - send_slave_status(master_mac, OTA_ST_FAILED, ota_uart_bytes_written(), 16); + ESP_LOGW(TAG, "ESP-NOW OTA seq gap (got %lu) — waiting for retry", + (unsigned long)payload->seq); return; } if (r == OTA_FEED_SEQ_DUP) { @@ -371,6 +379,9 @@ static void process_slave_end(const uint8_t master_mac[6]) { led_ring_ota_success(); ESP_LOGI(TAG, "slave OTA success (%lu bytes), reboot to run", (unsigned long)written); + /* Boot partition already set in ota_uart_finish(true); must reboot or we + * keep running the old image while the master expects the new one. */ + pod_schedule_restart(); } static void ota_slave_work_task(void *param) { diff --git a/main/ota_uart.c b/main/ota_uart.c index 965b026..34b3c7f 100644 --- a/main/ota_uart.c +++ b/main/ota_uart.c @@ -97,7 +97,13 @@ int ota_uart_prepare(uint32_t total_size) { esp_ota_handle_t handle; esp_err_t err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &handle); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err)); + if (err == ESP_ERR_OTA_ROLLBACK_INVALID_STATE) { + ESP_LOGE(TAG, + "esp_ota_begin blocked: running app still PENDING_VERIFY " + "(wait for ota_confirm ~8 s after boot, or let rollback finish)"); + } else { + ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err)); + } return -1; } diff --git a/main/ota_uart.h b/main/ota_uart.h index 8a21e70..217f5f8 100644 --- a/main/ota_uart.h +++ b/main/ota_uart.h @@ -45,7 +45,9 @@ void ota_uart_abort(void); /** * Append up to 200 bytes with strict seq checking (0, 1, 2, …). - * Duplicates (seq < expected) return OTA_FEED_SEQ_DUP; gaps return OTA_FEED_SEQ_GAP. + * Duplicates (seq < expected) return OTA_FEED_SEQ_DUP. + * Gaps (seq > expected) return OTA_FEED_SEQ_GAP without consuming — caller + * should keep the session so the host can retry the current flash block. */ ota_feed_result_t ota_uart_feed_chunk(uint32_t seq, const uint8_t *data, size_t len); diff --git a/main/pod_settings.c b/main/pod_settings.c index 2faa7c4..67c9e96 100644 --- a/main/pod_settings.c +++ b/main/pod_settings.c @@ -355,10 +355,6 @@ esp_err_t pod_settings_load_id_map(pod_id_map_t *out) { (unsigned)len); return ESP_OK; } - if (blob.count > POD_ID_MAP_MAX) { - ESP_LOGW(TAG, "id_map count %u invalid", (unsigned)blob.count); - return ESP_OK; - } if (blob.next_id < POD_CLIENT_ID_MIN) { blob.next_id = (uint8_t)POD_CLIENT_ID_MIN; } @@ -375,7 +371,7 @@ esp_err_t pod_settings_load_id_map(pod_id_map_t *out) { } esp_err_t pod_settings_save_id_map(const pod_id_map_t *map) { - if (map == NULL || map->count > POD_ID_MAP_MAX) { + if (map == NULL) { return ESP_ERR_INVALID_ARG; } if (map->next_id < POD_CLIENT_ID_MIN) { diff --git a/main/powerpod.c b/main/powerpod.c index 8c04378..1fb61d2 100644 --- a/main/powerpod.c +++ b/main/powerpod.c @@ -14,6 +14,7 @@ #include "cmd_version.h" #include "cmd_ota.h" #include "cmd_ota_slave_progress.h" +#include "ota_confirm.h" #include "cmd_led_ring.h" #include "cmd_battery.h" #include "cmd_set_log_level.h" @@ -236,6 +237,10 @@ void app_main(void) { } ESP_LOGI(TAG, "LED ring: UART LED_RING commands only (no local demo loop)"); + + /* After OTA: PENDING_VERIFY until confirm; crash before this → rollback. */ + ota_confirm_schedule(); + while (1) { vTaskDelay(portMAX_DELAY); } diff --git a/main/proto/esp_now_messages.pb.h b/main/proto/esp_now_messages.pb.h index 6788646..8bb8553 100644 --- a/main/proto/esp_now_messages.pb.h +++ b/main/proto/esp_now_messages.pb.h @@ -99,13 +99,13 @@ typedef struct _alox_EspNowAccelDeadzone { uint32_t client_id; /* 0 = all slaves; otherwise only matching slave_id applies */ } alox_EspNowAccelDeadzone; -/* * Master → slave: enable/disable periodic accel ESP-NOW stream (~16 ms). */ +/* * Master → slave: enable/disable periodic accel ESP-NOW stream (~10 ms). */ typedef struct _alox_EspNowAccelStream { bool enable; uint32_t client_id; } alox_EspNowAccelStream; -/* * Slave → master: latest BMA456 sample (sent ~every 16 ms). */ +/* * Slave → master: latest BMA456 sample (best-effort, ~every 10 ms). */ typedef struct _alox_EspNowAccelSample { uint32_t slave_id; int32_t x; diff --git a/main/proto/esp_now_messages.proto b/main/proto/esp_now_messages.proto index c70f137..f827432 100644 --- a/main/proto/esp_now_messages.proto +++ b/main/proto/esp_now_messages.proto @@ -90,13 +90,13 @@ message EspNowAccelDeadzone { uint32 client_id = 2; // 0 = all slaves; otherwise only matching slave_id applies } -/** Master → slave: enable/disable periodic accel ESP-NOW stream (~16 ms). */ +/** Master → slave: enable/disable periodic accel ESP-NOW stream (~10 ms). */ message EspNowAccelStream { bool enable = 1; uint32 client_id = 2; } -/** Slave → master: latest BMA456 sample (sent ~every 16 ms). */ +/** Slave → master: latest BMA456 sample (best-effort, ~every 10 ms). */ message EspNowAccelSample { uint32 slave_id = 1; sint32 x = 2; diff --git a/main/proto/uart_messages.pb.h b/main/proto/uart_messages.pb.h index dd328ed..41029d3 100644 --- a/main/proto/uart_messages.pb.h +++ b/main/proto/uart_messages.pb.h @@ -31,7 +31,7 @@ typedef enum _alox_MessageType { alox_MessageType_ACCEL_STREAM = 25, alox_MessageType_BATTERY_STATUS = 26, alox_MessageType_TAP_NOTIFY = 27, - /* * Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */ + /* * Combined cached accel + tap poll (one UART round-trip, ~10 ms cadence). */ alox_MessageType_CACHE_STATUS = 29, /* * Host → master → slave → master: timestamp echo round-trip (latency test). */ alox_MessageType_ESPNOW_ECHO_PING = 30, @@ -118,9 +118,9 @@ typedef struct _alox_AccelDeadzoneResponse { uint32_t slaves_updated; } alox_AccelDeadzoneResponse; -/* Host → master: enable/disable slave accel ESP-NOW stream (~16 ms per slave). +/* Host → master: enable/disable slave accel ESP-NOW stream (~10 ms per slave, best-effort). write=false: read; write=true: apply. client_id 0 invalid for write (use >0 or all_clients). - period_ms (8–16): BLE host push interval for BleStreamTick; ignored on UART (default 16). */ + period_ms (10–16): BLE host push interval for BleStreamTick; ignored on UART (default 10). */ typedef struct _alox_AccelStreamRequest { bool write; bool enable; diff --git a/main/proto/uart_messages.proto b/main/proto/uart_messages.proto index a5102f6..ab1d5c1 100644 --- a/main/proto/uart_messages.proto +++ b/main/proto/uart_messages.proto @@ -27,7 +27,7 @@ enum MessageType { BATTERY_STATUS = 26; TAP_NOTIFY = 27; reserved 28; - /** Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */ + /** Combined cached accel + tap poll (one UART round-trip, ~10 ms cadence). */ CACHE_STATUS = 29; /** Host → master → slave → master: timestamp echo round-trip (latency test). */ ESPNOW_ECHO_PING = 30; @@ -149,9 +149,9 @@ message AccelDeadzoneResponse { uint32 slaves_updated = 4; } -// Host → master: enable/disable slave accel ESP-NOW stream (~16 ms per slave). +// Host → master: enable/disable slave accel ESP-NOW stream (~10 ms per slave, best-effort). // write=false: read; write=true: apply. client_id 0 invalid for write (use >0 or all_clients). -// period_ms (8–16): BLE host push interval for BleStreamTick; ignored on UART (default 16). +// period_ms (10–16): BLE host push interval for BleStreamTick; ignored on UART (default 10). message AccelStreamRequest { bool write = 1; bool enable = 2; diff --git a/sdkconfig b/sdkconfig index f4c0e84..7abc1ac 100644 --- a/sdkconfig +++ b/sdkconfig @@ -421,7 +421,8 @@ CONFIG_BOOTLOADER_PROJECT_VER=1 # # Application Rollback # -# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +# CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK is not set # end of Application Rollback # @@ -2529,7 +2530,8 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # Deprecated options for backward compatibility # CONFIG_APP_BUILD_TYPE_ELF_RAM is not set # CONFIG_NO_BLOBS is not set -# CONFIG_APP_ROLLBACK_ENABLE is not set +CONFIG_APP_ROLLBACK_ENABLE=y +# CONFIG_APP_ANTI_ROLLBACK is not set # CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set # CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set # CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 3875ab9..a7fc0df 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -6,3 +6,6 @@ CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y # Pin stacks to different cores (WiFi stays on core 0) CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y + +# OTA A/B: new image boots PENDING_VERIFY; crash/reset before confirm → rollback +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y