Compare commits

...

5 Commits

41 changed files with 2074 additions and 316 deletions

View File

@ -205,9 +205,9 @@ sequenceDiagram
``` ```
**Beispiel-Implementierung:** `main/cmd/cmd_accel_deadzone.c` **Beispiel-Implementierung:** `main/cmd/cmd_accel_deadzone.c`
**Weitere Bridge-Handler:** `cmd_accel_stream.c`, `cmd_tap_notify.c`, `cmd_led_ring.c`, `cmd_espnow_find_me.c`, `cmd_restart.c`, `cmd_espnow_unicast_test.c`, `cmd/cmd_ota.c` (OTA + `ota_espnow.c`). **Weitere Bridge-Handler:** `cmd_accel_stream.c`, `cmd_tap_notify.c`, `cmd_led_ring.c`, `cmd_espnow_find_me.c`, `cmd_restart.c`, `cmd_espnow_unicast_test.c`, `cmd_espnow_echo_ping.c`, `cmd/cmd_ota.c` (OTA + `ota_espnow.c`).
**Nur Master / nur Cache (kein Slave-Roundtrip):** `cmd_client_info.c`, `cmd_battery.c`, `cmd_cache_status.c`, `cmd_version.c`. **Nur Master / nur Cache (kein Slave-Roundtrip):** `cmd_client_info.c`, `cmd_battery.c`, `cmd_cache_status.c`, `cmd_version.c`, `cmd_set_log_level.c`.
--- ---

View File

@ -54,7 +54,9 @@ Zielbild für den Host: Befehle an den Master senden; der Master steuert Slaves
| LiPo ADC 1 | 1 | `board_input.c` | | LiPo ADC 1 | 1 | `board_input.c` |
| LiPo ADC 2 | 12 | Entfällt wenn = Taster-GPIO | | LiPo ADC 2 | 12 | Entfällt wenn = Taster-GPIO |
**UART:** `UART_NUM_1`, **921600** Baud, 8N1, kein Flow-Control. **UART (Host-Protokoll):** `UART_NUM_1`, **921600** Baud, 8N1, kein Flow-Control.
**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.
**I2C:** 100 kHz, interne Pull-ups, gemeinsamer Bus für Expander und BMA456H. **I2C:** 100 kHz, interne Pull-ups, gemeinsamer Bus für Expander und BMA456H.
@ -181,6 +183,8 @@ UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadz
| `ESPNOW_FIND_ME` | M→S | `find_me` | LED-Locate | | `ESPNOW_FIND_ME` | M→S | `find_me` | LED-Locate |
| `ESPNOW_RESTART` | M→S | `restart` | Reboot Slave | | `ESPNOW_RESTART` | M→S | `restart` | Reboot Slave |
| `ESPNOW_UNICAST_TEST` | M→S | `unicast_test` | Link-Test | | `ESPNOW_UNICAST_TEST` | M→S | `unicast_test` | Link-Test |
| `ESPNOW_ECHO_PING` | M→S | `echo_ping` | Latenztest (Host-Timestamp + `master_time_us`) |
| `ESPNOW_ECHO_PONG` | S→M | `echo_pong` | Echo unverändert zurück zum Master |
| `ESPNOW_OTA_*` | M↔S | `ota_*` | Firmware-Verteilung | | `ESPNOW_OTA_*` | M↔S | `ota_*` | Firmware-Verteilung |
### 6.3 Zeitkonstanten (`esp_now_comm.c`) ### 6.3 Zeitkonstanten (`esp_now_comm.c`)
@ -193,6 +197,7 @@ UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadz
| Master-Verlust (Slave) | 5 s ohne Discover | | Master-Verlust (Slave) | 5 s ohne Discover |
| Accel-Stream | 16 ms | | Accel-Stream | 16 ms |
| Batterie-Report | 30 s (+ einmal 150 ms nach Join) | | Batterie-Report | 30 s (+ einmal 150 ms nach Join) |
| Echo-Ping Timeout (Master) | 500 ms (`ESPNOW_ECHO_PING_TIMEOUT_MS`) |
### 6.4 `EspNowSlavePresence` ### 6.4 `EspNowSlavePresence`
@ -229,6 +234,8 @@ Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uar
| 26 | BATTERY_STATUS | `cmd_battery.c` | Cache LiPo Master + Slaves | | 26 | BATTERY_STATUS | `cmd_battery.c` | Cache LiPo Master + Slaves |
| 27 | TAP_NOTIFY | `cmd_tap_notify.c` | Tap-Weiterleitung konfigurieren | | 27 | TAP_NOTIFY | `cmd_tap_notify.c` | Tap-Weiterleitung konfigurieren |
| 29 | CACHE_STATUS | `cmd_cache_status.c` | Accel + Tap Cache (ein Round-Trip) | | 29 | CACHE_STATUS | `cmd_cache_status.c` | Accel + Tap Cache (ein Round-Trip) |
| 30 | ESPNOW_ECHO_PING | `cmd_espnow_echo_ping.c` | Timestamp-Echo über ESP-NOW (Latenztest) |
| 31 | SET_LOG_LEVEL | `cmd_set_log_level.c` | ESP-IDF-Log-Level global (`"*"`) lesen/setzen |
### 7.1 VERSION (3) ### 7.1 VERSION (3)
@ -287,6 +294,53 @@ Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uar
Minimaler Master→Slave-Ping; Slave loggt `UNICAST TEST OK`. Minimaler Master→Slave-Ping; Slave loggt `UNICAST TEST OK`.
### 7.11 ESPNOW_ECHO_PING (30)
Round-Trip-Latenztest zu einem **Slave** (`client_id` > 0, muss in der Registry sein).
**Ablauf:**
1. Host (goTool) setzt `timestamp_us` (Unix-µs) und sendet `EspNowEchoPingRequest` per UART.
2. Master (`cmd_espnow_echo_ping.c`) löst MAC aus Registry auf, ruft `esp_now_comm_echo_ping()` auf.
3. Master sendet `ESPNOW_ECHO_PING` mit `host_timestamp_us` und `master_time_us` (`esp_timer_get_time()` kurz vor Send).
4. Slave (`handle_echo_ping`) antwortet mit `ESPNOW_ECHO_PONG` (Felder unverändert).
5. Master empfängt Pong, berechnet `esp_rtt_us = esp_timer_get_time() - master_time_us`, antwortet per UART.
**Request:** `client_id`, `timestamp_us` (vom Host gesetzt).
**Response:** `success`, `client_id`, `timestamp_us` (echoed), `esp_rtt_us` (nur bei Erfolg).
| Feld | Quelle | Bedeutung |
|------|--------|-----------|
| `timestamp_us` | Host → Slave → Host | Korrelations-ID; muss mit Request übereinstimmen |
| `esp_rtt_us` | Master | Rohe µs-Differenz von `esp_timer_get_time()` (Ping-Send → Pong-Empfang im `recv_cb`) |
**Implementierung:** `main/cmd/cmd_espnow_echo_ping.c`, `esp_now_comm_echo_ping()` in `esp_now_master.c`, Slave `handle_echo_ping` in `esp_now_slave.c`.
**Host-seitig (goTool):** `rtt_ms` = volle UART-Kette (Send bis Response-Empfang); unabhängig von `esp_rtt_us`.
### 7.12 SET_LOG_LEVEL (31)
Laufzeit-Steuerung des **globalen** ESP-IDF-Log-Levels auf dem Master (`esp_log_level_set("*", …)`). Kein ESP-NOW, kein NVS — nur Master.
**Request:** `write`, `level` (`esp_log_level_t`: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE).
| `write` | Verhalten |
|---------|-----------|
| `false` / leer | Aktuelles Level lesen (`esp_log_level_get("*")`) |
| `true` | Level setzen; ungültige Werte (>5) → `success=false` |
**Response:** `success`, `level` (aktuell bzw. gesetzt).
**Boot-Default:** `CONFIG_LOG_DEFAULT_LEVEL` in `sdkconfig` (aktuell **INFO** = 3). Kann per UART/Web-UI zur Laufzeit geändert werden; nach Reboot gilt wieder der sdkconfig-Wert.
**Ausgabe:** Logs erscheinen auf **UART0** (Debug-USB), nicht auf dem Host-UART1 (goTool).
```bash
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3
```
--- ---
## 8. OTA ## 8. OTA
@ -427,6 +481,8 @@ Includes in generierten `.pb.c` müssen `"uart_messages.pb.h"` heißen (nicht `m
| `cmd_espnow_find_me.c` | FIND_ME | | `cmd_espnow_find_me.c` | FIND_ME |
| `cmd_restart.c` | RESTART | | `cmd_restart.c` | RESTART |
| `cmd_espnow_unicast_test.c` | ESPNOW_UNICAST_TEST | | `cmd_espnow_unicast_test.c` | ESPNOW_UNICAST_TEST |
| `cmd_espnow_echo_ping.c` | ESPNOW_ECHO_PING |
| `cmd_set_log_level.c` | SET_LOG_LEVEL |
| `cmd_ota.c` | OTA_* | | `cmd_ota.c` | OTA_* |
| `cmd_ota_slave_progress.c` | OTA_SLAVE_PROGRESS | | `cmd_ota_slave_progress.c` | OTA_SLAVE_PROGRESS |
@ -455,9 +511,12 @@ Includes in generierten `.pb.c` müssen `"uart_messages.pb.h"` heißen (nicht `m
## 15. Logging-Tags ## 15. Logging-Tags
**Level zur Laufzeit:** UART `SET_LOG_LEVEL` (31) oder Dashboard „ESP Log-Level“ — steuert nur die Konsolen-Ausgabe auf UART0, nicht das Host-Protokoll.
| Tag | Modul | | Tag | Modul |
|-----|--------| |-----|--------|
| `[Main]` | powerpod.c | | `[Main]` | powerpod.c |
| `[LOG_LVL]` | cmd_set_log_level.c |
| `[UART]` | uart.c | | `[UART]` | uart.c |
| `[CMDH]` | cmd_handler.c | | `[CMDH]` | cmd_handler.c |
| `[UART_CMD]` | uart_cmd.c | | `[UART_CMD]` | uart_cmd.c |

View File

@ -345,7 +345,7 @@ idf.py build
Ähnliche Features zum Abgucken: Ähnliche Features zum Abgucken:
- **Nur Master, kein ESP-NOW:** `main/cmd/cmd_version.c`, `main/cmd/cmd_led_ring.c` - **Nur Master, kein ESP-NOW:** `main/cmd/cmd_version.c`, `main/cmd/cmd_led_ring.c`, `main/cmd/cmd_set_log_level.c`
- **Nur Slave per ESP-NOW (Master leitet nur durch):** `main/cmd/cmd_espnow_unicast_test.c` - **Nur Slave per ESP-NOW (Master leitet nur durch):** `main/cmd/cmd_espnow_unicast_test.c`
- **Master + alle Slaves / Filter:** `main/cmd/cmd_accel_deadzone.c` - **Master + alle Slaves / Filter:** `main/cmd/cmd_accel_deadzone.c`
- **Großer ESP-NOW-Fluss mit Status:** `ota_espnow.c`, `main/cmd/cmd_ota.c` - **Großer ESP-NOW-Fluss mit Status:** `ota_espnow.c`, `main/cmd/cmd_ota.c`

View File

@ -28,6 +28,7 @@ go run . -port /dev/ttyUSB0 clients
| `tap-notify` | `0x1b` | Get/set which tap kinds (single/double/triple) notify via ESP-NOW (`-set`, `-client`, `-all`, `-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 16 ms polling |
| `unicast-test` | `0x07` | Sends ESP-NOW unicast test to one slave (`-client`, `-seq`) | | `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/`) | | `test` | — | Run an automated scenario (JSON configs under `testdata/`) |
| `serve` | — | Web dashboard at `http://localhost:8080` (WebSocket live updates) | | `serve` | — | Web dashboard at `http://localhost:8080` (WebSocket live updates) |
| `ota` | 1619 | UART firmware upload to master; firmware then pushes to slaves via ESP-NOW | | `ota` | 1619 | UART firmware upload to master; firmware then pushes to slaves via ESP-NOW |
@ -35,6 +36,7 @@ go run . -port /dev/ttyUSB0 clients
| `led-ring` | 8 | LED ring: `-mode clear\|color\|progress\|digit\|blink\|find-me`, `-client`, `-all` | | `led-ring` | 8 | LED ring: `-mode clear\|color\|progress\|digit\|blink\|find-me`, `-client`, `-all` |
| `find-me` | 22 | Locate pod (`-client 0` master, `>0` slave via ESP-NOW) | | `find-me` | 22 | Locate pod (`-client 0` master, `>0` slave via ESP-NOW) |
| `restart` | 23 | Reboot master or slave (`-client 0` / `>0`) | | `restart` | 23 | Reboot master or slave (`-client 0` / `>0`) |
| `log-level` | `0x1f` | Get/set master ESP-IDF log level for tag `"*"` (`-set`, `-level` 05); output on UART0 debug, not host UART |
`clients` requires slaves to have responded to master discover broadcasts first. `clients` requires slaves to have responded to master discover broadcasts first.
@ -86,7 +88,7 @@ If the UART device is unplugged or the port disappears, `serve` keeps running an
| Doc | Content | | Doc | Content |
|-----|---------| |-----|---------|
| **[docs/API_WEBSOCKET.md](docs/API_WEBSOCKET.md)** | `ws://…:8081/ws` commands, **`accel` / `tap` push stream** format, dashboard `ws://…:8080/ws` | | **[docs/API_WEBSOCKET.md](docs/API_WEBSOCKET.md)** | `ws://…:8081/ws` commands and **`input` push stream** (accel + tap) |
| **[docs/API_REST.md](docs/API_REST.md)** | REST on `:8080` (dashboard) and `:8081` (battery, LED, service info) | | **[docs/API_REST.md](docs/API_REST.md)** | REST on `:8080` (dashboard) and `:8081` (battery, LED, service info) |
CLI: CLI:
@ -113,8 +115,24 @@ go run . -port /dev/ttyUSB0 unicast-test -client 16 -seq 42
On success the slave serial log should show `UNICAST TEST OK from master … seq=42`. On success the slave serial log should show `UNICAST TEST OK from master … seq=42`.
```bash
go run . -port /dev/ttyUSB0 echo-ping -client 16
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3
```
`log-level` controls `esp_log_*` on the master (UART0 USB console). The host protocol UART (GPIO 2/3) is unchanged.
Measures latency to one slave. `rtt_ms` is the full host round-trip (UART + ESP-NOW + UART back). `esp_rtt_us` is the master-side ESP-NOW leg only (`esp_timer_get_time()` delta, raw microseconds from firmware).
Example output: Example output:
```
echo ping: success=true client_id=16 rtt_ms=49.729 esp_rtt_us=18234
```
`clients` example:
``` ```
clients (2): clients (2):
[0] id=42 mac=aabbccddeeff ver=1 available=true used=false last_ping=250 last_success_ping=250 [0] id=42 mac=aabbccddeeff ver=1 available=true used=false last_ping=250 last_success_ping=250

View File

@ -41,6 +41,19 @@ type unicastAPIResponse struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
} }
type echoPingAPIRequest struct {
ClientID uint32 `json:"client_id"`
}
type echoPingAPIResponse struct {
Success bool `json:"success"`
ClientID uint32 `json:"client_id,omitempty"`
TimestampUs uint64 `json:"timestamp_us,omitempty"`
RttMs float64 `json:"rtt_ms"`
EspRttUs uint32 `json:"esp_rtt_us"`
Error string `json:"error,omitempty"`
}
type findMeAPIRequest struct { type findMeAPIRequest struct {
ClientID uint32 `json:"client_id"` ClientID uint32 `json:"client_id"`
} }
@ -61,6 +74,17 @@ type restartAPIResponse struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
} }
type logLevelAPIResponse struct {
Success bool `json:"success"`
Level uint32 `json:"level"`
Error string `json:"error,omitempty"`
}
type logLevelAPIRequest struct {
Write bool `json:"write"`
Level uint32 `json:"level"`
}
type otaAPIResponse struct { type otaAPIResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
BytesWritten uint32 `json:"bytes_written,omitempty"` BytesWritten uint32 `json:"bytes_written,omitempty"`
@ -91,6 +115,13 @@ func mountServeAPI(mux *http.ServeMux, link *managedSerial, hub *wsHub, streamCt
} }
serveUnicastTest(w, r, link) serveUnicastTest(w, r, link)
}) })
mux.HandleFunc("/api/echo-ping", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
serveEchoPing(w, r, link)
})
mux.HandleFunc("/api/find-me", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/api/find-me", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed) http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@ -105,6 +136,16 @@ func mountServeAPI(mux *http.ServeMux, link *managedSerial, hub *wsHub, streamCt
} }
serveRestart(w, r, link) serveRestart(w, r, link)
}) })
mux.HandleFunc("/api/log-level", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
serveLogLevelGet(w, r, link)
case http.MethodPost:
serveLogLevelPost(w, r, link)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
})
mux.HandleFunc("/api/ota", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/api/ota", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed) http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@ -260,6 +301,43 @@ func applyDeadzoneToSlaves(link *managedSerial, deadzone uint32) (uint32, error)
return updated, nil return updated, nil
} }
func serveLogLevelGet(w http.ResponseWriter, r *http.Request, link *managedSerial) {
resp, err := link.SetLogLevel(false, 0)
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, logLevelAPIResponse{Error: err.Error()})
return
}
writeJSON(w, http.StatusOK, logLevelAPIResponse{
Success: resp.GetSuccess(),
Level: resp.GetLevel(),
})
}
func serveLogLevelPost(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body logLevelAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, logLevelAPIResponse{Error: "invalid JSON"})
return
}
if !body.Write {
writeJSON(w, http.StatusBadRequest, logLevelAPIResponse{Error: "write must be true"})
return
}
if body.Level > 5 {
writeJSON(w, http.StatusBadRequest, logLevelAPIResponse{Error: "level must be 05"})
return
}
resp, err := link.SetLogLevel(true, body.Level)
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, logLevelAPIResponse{Error: err.Error()})
return
}
writeJSON(w, http.StatusOK, logLevelAPIResponse{
Success: resp.GetSuccess(),
Level: resp.GetLevel(),
})
}
func serveRestart(w http.ResponseWriter, r *http.Request, link *managedSerial) { func serveRestart(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body restartAPIRequest var body restartAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil { if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
@ -316,6 +394,30 @@ func serveUnicastTest(w http.ResponseWriter, r *http.Request, link *managedSeria
}) })
} }
func serveEchoPing(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body echoPingAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, echoPingAPIResponse{Error: "invalid JSON"})
return
}
if body.ClientID == 0 {
writeJSON(w, http.StatusBadRequest, echoPingAPIResponse{Error: "client_id required"})
return
}
result, err := link.EchoPing(body.ClientID)
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, echoPingAPIResponse{Error: err.Error()})
return
}
writeJSON(w, http.StatusOK, echoPingAPIResponse{
Success: result.Success,
ClientID: result.ClientID,
TimestampUs: result.TimestampUs,
RttMs: result.RttMs,
EspRttUs: result.EspRttUs,
})
}
func parseUintQuery(r *http.Request, key string, def uint32) (uint32, error) { func parseUintQuery(r *http.Request, key string, def uint32) (uint32, error) {
s := r.URL.Query().Get(key) s := r.URL.Query().Get(key)
if s == "" { if s == "" {

View File

@ -31,7 +31,7 @@ type InputClientSample struct {
Y int32 `json:"y,omitempty"` Y int32 `json:"y,omitempty"`
Z int32 `json:"z,omitempty"` Z int32 `json:"z,omitempty"`
AccelAgeMs uint32 `json:"accel_age_ms,omitempty"` AccelAgeMs uint32 `json:"accel_age_ms,omitempty"`
TapKind string `json:"tap_kind"` TapKind string `json:"tap_kind,omitempty"`
TapAgeMs uint32 `json:"tap_age_ms,omitempty"` TapAgeMs uint32 `json:"tap_age_ms,omitempty"`
} }
@ -389,7 +389,6 @@ func (h *accelStreamHub) inputClientsFromCacheLocked(cache *pb.CacheStatusRespon
for _, c := range cache.GetClients() { for _, c := range cache.GetClients() {
sample := InputClientSample{ sample := InputClientSample{
ClientID: c.GetClientId(), ClientID: c.GetClientId(),
TapKind: "none",
} }
if a := c.GetAccel(); a != nil { if a := c.GetAccel(); a != nil {
sample.Valid = a.GetValid() sample.Valid = a.GetValid()

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"time"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@ -410,12 +411,82 @@ func (s *serialPort) espnowUnicastTest(clientID, seq uint32) (*pb.EspNowUnicastT
return r, nil return r, nil
} }
// EchoPingResult is the host-side round-trip for ESP-NOW echo ping.
type EchoPingResult struct {
Success bool `json:"success"`
ClientID uint32 `json:"client_id"`
TimestampUs uint64 `json:"timestamp_us"`
RttMs float64 `json:"rtt_ms"` // goTool: full UART round-trip
EspRttUs uint32 `json:"esp_rtt_us"` // master: µs delta ping send → pong recv
}
func (s *serialPort) echoPing(clientID uint32) (*EchoPingResult, error) {
t0 := time.Now()
timestampUs := uint64(t0.UnixMicro())
req := &pb.EspNowEchoPingRequest{
ClientId: clientID,
TimestampUs: timestampUs,
}
msg := &pb.UartMessage{
Type: pb.MessageType_ESPNOW_ECHO_PING,
Payload: &pb.UartMessage_EspnowEchoPingRequest{
EspnowEchoPingRequest: req,
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_ESPNOW_ECHO_PING)}, body...)
respPayload, err := s.exchangePayload(payload, "ESPNOW_ECHO_PING")
if err != nil {
return nil, err
}
rttMs := float64(time.Since(t0).Microseconds()) / 1000.0
var respMsg pb.UartMessage
if err := proto.Unmarshal(respPayload[1:], &respMsg); err != nil {
return nil, fmt.Errorf("decode: %w", err)
}
r := respMsg.GetEspnowEchoPingResponse()
if r == nil {
return nil, fmt.Errorf("missing espnow_echo_ping_response")
}
if !r.GetSuccess() {
return &EchoPingResult{
Success: false,
ClientID: r.GetClientId(),
RttMs: rttMs,
}, nil
}
if r.GetTimestampUs() != timestampUs {
return nil, fmt.Errorf("timestamp mismatch: sent %d got %d", timestampUs, r.GetTimestampUs())
}
return &EchoPingResult{
Success: true,
ClientID: r.GetClientId(),
TimestampUs: r.GetTimestampUs(),
RttMs: rttMs,
EspRttUs: r.GetEspRttUs(),
}, nil
}
func (m *managedSerial) FindMe(clientID uint32) error { func (m *managedSerial) FindMe(clientID uint32) error {
return m.withPort(func(sp *serialPort) error { return m.withPort(func(sp *serialPort) error {
return runFindMeClient(sp, clientID) return runFindMeClient(sp, clientID)
}) })
} }
func (m *managedSerial) SetLogLevel(write bool, level uint32) (*pb.SetLogLevelResponse, error) {
var resp *pb.SetLogLevelResponse
err := m.withPort(func(sp *serialPort) error {
var e error
resp, e = runSetLogLevelClient(sp, write, level)
return e
})
return resp, err
}
func (m *managedSerial) Restart(clientID uint32) error { func (m *managedSerial) Restart(clientID uint32) error {
err := m.withPort(func(sp *serialPort) error { err := m.withPort(func(sp *serialPort) error {
return runRestartClient(sp, clientID) return runRestartClient(sp, clientID)
@ -490,6 +561,20 @@ func (s *serialPort) EspnowUnicastTest(clientID, seq uint32) (*pb.EspNowUnicastT
return s.espnowUnicastTest(clientID, seq) return s.espnowUnicastTest(clientID, seq)
} }
func (s *serialPort) EchoPing(clientID uint32) (*EchoPingResult, error) {
return s.echoPing(clientID)
}
func (m *managedSerial) EchoPing(clientID uint32) (*EchoPingResult, error) {
var result *EchoPingResult
err := m.withPort(func(sp *serialPort) error {
var e error
result, e = sp.echoPing(clientID)
return e
})
return result, err
}
func (s *serialPort) LedRing(req *pb.LedRingProgressRequest) (*pb.LedRingProgressResponse, error) { func (s *serialPort) LedRing(req *pb.LedRingProgressRequest) (*pb.LedRingProgressResponse, error) {
return s.ledRingProgress(req) return s.ledRingProgress(req)
} }

26
goTool/cmd_echo_ping.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"flag"
"fmt"
)
func runEchoPing(sp *serialPort, args []string) error {
fs := flag.NewFlagSet("echo-ping", flag.ExitOnError)
clientID := fs.Uint("client", 0, "slave client id from `clients`")
if err := fs.Parse(args); err != nil {
return err
}
if *clientID == 0 {
return fmt.Errorf("client id required (see `gotool clients`)")
}
r, err := sp.echoPing(uint32(*clientID))
if err != nil {
return err
}
fmt.Printf("echo ping: success=%v client_id=%d rtt_ms=%.3f esp_rtt_us=%d\n",
r.Success, r.ClientID, r.RttMs, r.EspRttUs)
return nil
}

61
goTool/cmd_log_level.go Normal file
View File

@ -0,0 +1,61 @@
package main
import (
"flag"
"fmt"
"google.golang.org/protobuf/proto"
"powerpod/gotool/pb"
)
func runLogLevel(sp *serialPort, args []string) error {
fs := flag.NewFlagSet("log-level", flag.ExitOnError)
write := fs.Bool("set", false, "write log level (default: read)")
level := fs.Uint("level", 0, "esp_log_level_t 05 (with -set)")
if err := fs.Parse(args); err != nil {
return err
}
resp, err := sp.setLogLevel(*write, uint32(*level))
if err != nil {
return err
}
if !resp.GetSuccess() {
return fmt.Errorf("set_log_level rejected (level=%d)", resp.GetLevel())
}
fmt.Printf("log_level=%d success=%v\n", resp.GetLevel(), resp.GetSuccess())
return nil
}
func runSetLogLevelClient(sp *serialPort, write bool, level uint32) (*pb.SetLogLevelResponse, error) {
return sp.setLogLevel(write, level)
}
func (s *serialPort) setLogLevel(write bool, level uint32) (*pb.SetLogLevelResponse, error) {
msg := &pb.UartMessage{
Type: pb.MessageType_SET_LOG_LEVEL,
Payload: &pb.UartMessage_SetLogLevelRequest{
SetLogLevelRequest: &pb.SetLogLevelRequest{
Write: write,
Level: level,
},
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_SET_LOG_LEVEL)}, body...)
respPayload, err := s.exchangePayload(payload, "SET_LOG_LEVEL")
if err != nil {
return nil, err
}
var respMsg pb.UartMessage
if err := proto.Unmarshal(respPayload[1:], &respMsg); err != nil {
return nil, fmt.Errorf("decode: %w", err)
}
r := respMsg.GetSetLogLevelResponse()
if r == nil {
return nil, fmt.Errorf("missing set_log_level_response")
}
return r, nil
}

View File

@ -216,6 +216,54 @@ Content-Type: application/json
{"client_id": 16, "seq": 42} {"client_id": 16, "seq": 42}
``` ```
### Echo ping (ESP-NOW round-trip latency)
```http
POST /api/echo-ping
Content-Type: application/json
{"client_id": 16}
```
`client_id` must be a registered slave id (`> 0`). The host sends a microsecond timestamp; the master forwards it over ESP-NOW and the slave echoes it back unchanged.
**Flow:** Host → UART → `cmd_espnow_echo_ping``ESPNOW_ECHO_PING` (with `master_time_us` from `esp_timer_get_time()`) → Slave → `ESPNOW_ECHO_PONG` → Master `recv_cb` → UART response.
**Response fields:**
| Field | Unit | Meaning |
|-------|------|---------|
| `success` | — | `true` if pong received within 500 ms |
| `client_id` | — | Echo of request |
| `timestamp_us` | µs (Unix) | Echoed host timestamp; must match request on success |
| `rtt_ms` | ms | **Host-side** round-trip: goTool send → UART response (full chain incl. USB serial) |
| `esp_rtt_us` | µs | **Master-side** ESP-NOW only: `esp_timer_get_time()` delta from ping send to pong recv |
`esp_rtt_us` is the raw firmware value (microseconds, not converted). The web dashboard displays it converted to milliseconds for readability (`esp_rtt_us / 1000`, 3 decimal places). The success banner stays visible for at least 5 seconds.
```json
{
"success": true,
"client_id": 16,
"timestamp_us": 1717654321123456,
"rtt_ms": 49.729,
"esp_rtt_us": 18234
}
```
On failure (`success: false`), `esp_rtt_us` is omitted; `rtt_ms` still reflects the host round-trip. HTTP 503 if UART exchange fails (e.g. timeout, client not in registry).
**CLI:**
```bash
go run . -port /dev/ttyUSB0 echo-ping -client 16
```
Example output:
```
echo ping: success=true client_id=16 rtt_ms=49.729 esp_rtt_us=18234
```
### Find me ### Find me
```http ```http
@ -234,6 +282,39 @@ Content-Type: application/json
{"client_id": 16} {"client_id": 16}
``` ```
### Log level (master ESP-IDF console)
Runtime get/set of the **global** log filter on the master (`esp_log_level_set("*", …)`). Output goes to **UART0** (USB debug, 115200), not the host protocol UART (GPIO 2/3, 921600).
```http
GET /api/log-level
POST /api/log-level
Content-Type: application/json
{"write": true, "level": 3}
```
| `level` | Meaning |
|---------|---------|
| 0 | None (no log output) |
| 1 | Error |
| 2 | Warn |
| 3 | Info |
| 4 | Debug |
| 5 | Verbose |
```json
{"success": true, "level": 3}
```
**CLI:**
```bash
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3
```
Dashboard: Master card → dropdown **ESP Log-Level** with **Lesen** / **Setzen**.
### OTA (master UART upload) ### OTA (master UART upload)
```http ```http
@ -280,5 +361,7 @@ Same as external API:
| Einzelner Slave | `client_id: <id>` | | Einzelner Slave | `client_id: <id>` |
| Alle Slaves deadzone | `all_clients` + `slaves_only` on POST | | Alle Slaves deadzone | `all_clients` + `slaves_only` on POST |
| Unicast test | `POST /api/unicast-test` | | Unicast test | `POST /api/unicast-test` |
| Echo ping | `POST /api/echo-ping` (per-slave latency; UI shows Host ms + ESP ms) |
| Master log level | `GET` / `POST /api/log-level` or CLI `log-level` |
| Tap notify S/D/T | `PUT /api/clients/{id}/tap-notify` | | Tap notify S/D/T | `PUT /api/clients/{id}/tap-notify` |
| Tap receive (UI) | Live stream + tap notify; see WebSocket doc for external API | | Tap receive (UI) | Live stream + tap notify; see WebSocket doc for external API |

View File

@ -1,58 +1,59 @@
# WebSocket API # WebSocket API
`go run . -port /dev/ttyUSB0 serve` exposes the WebSocket enpoint External API: `ws://localhost:8081/ws` (default `-api-addr`, disable with empty string).
| URL | Port (default) | Role | Start with `go run . -port /dev/ttyUSB0 serve`.
|-----|----------------|------|
| `ws://localhost:8081/ws` | External API (`-api-addr`) | Request/response commands + optional **input** push stream |
--- ---
## External API (`:8081/ws`) ## Connection flow
### Connection flow 1. Connect → server sends `hello` (push off; defaults and command list).
2. Send JSON commands → one reply per message (`*_status` or `client_list`).
3. After `set_stream` with `enable: true`, server may push `input` messages without a prior command.
1. Connect → server sends **`hello`** (receive off; lists available commands). Commands and pushes share one socket — always branch on `type`.
2. Send JSON commands → server replies with a matching `*_status` or `client_list` message (one reply per command).
3. After `set_stream` with `enable: true`, the server may send **`input`** messages **without** a prior command (push stream).
Commands and stream pushes are multiplexed on one socket. While streaming, always parse `type` and branch (status vs sample vs error). On disconnect, `set_stream` state for that socket is dropped. Firmware settings (`set_input_stream`, `set_tap_notify`) stay on the master until changed.
---
## Two layers (firmware vs host)
### Two layers (firmware vs host)
| Layer | Commands | Effect | | Layer | Commands | Effect |
|-------|----------|--------| | ------------------ | ------------------------------------ | ------------------------------------------------------------------ |
| **Firmware (ESP-NOW)** | `set_input_stream`, `set_tap_notify` | Per `client_id`: slave sends accel samples and/or tap events to the master | | Firmware (ESP-NOW) | `set_input_stream`, `set_tap_notify` | Per `client_id`: slave sends accel and/or tap events to the master |
| **This connection (host)** | `set_stream` | Whether **you** receive push JSON, at what rate (`interval_ms`, 1 ms … 10 s), and how early the UART read starts (`pre_fetch`) | | This connection | `set_stream` | Whether you receive push JSON on this socket |
- **UART polling** runs only if at least one connection has `receive_input: true` (`set_stream`) **and** at least one slave streams input (`set_input_stream`) or has tap notify enabled (`set_tap_notify`).
- **`set_tap_notify` alone** configures which tap kinds the slave reports; it does **not** enable host push by itself — you still need `set_stream`. UART polling runs only when at least one connection has `receive_input: true` **and** at least one slave streams input or has tap notify enabled. `set_tap_notify` alone does not enable push — you still need `set_stream`.
### Push timing (per connection)
| Field | Where | Meaning |
| ------------- | -------------------------------------- | ------------------------------------------------------------ |
| `interval_ms` | `hello`, `set_stream`, `stream_status` | Minimum ms between `input` pushes on this socket (1 … 10000) |
| `pre_fetch` | `set_stream`, `stream_status` | Ms before each push when the host starts the UART cache read |
Global UART poll interval = minimum `interval_ms` among all connections with push enabled.
Typical sequence: Typical sequence:
1. `list_clients` → slave IDs 1. `list_clients` → slave IDs
2. Per slave: `set_input_stream` and/or `set_tap_notify` as needed 2. Per slave: `set_input_stream` and/or `set_tap_notify`
3. `set_stream` with `"enable": true` 3. `set_stream` with `"enable": true`
4. Read **`input`** messages in a loop 4. Read `input` messages; filter by `client_id` in your app (no per-slave filter on the wire)
There is **no per-slave filter** on push messages: each `input` contains all cached slaves. Filter by `client_id` in your app.
--- ---
## Push stream messages ## Push: `input`
These are the samples you get after enabling receive. Timing is per WebSocket connection: Combines latest accel cache and visible tap state for every slave slot on the master.
- **`interval_ms`** — minimum time between consecutive `input` pushes on this socket. **Success:**
- **`pre_fetch`** — milliseconds **before** each scheduled push when the host sends the UART cache read, so the master has time to collect data from all slaves before the JSON goes out.
The server UART poll uses the **minimum** `interval_ms` among all subscribers with `receive_input: true`.
### `input` (type `"input"`)
Sent when `set_stream` has `enable: true` and the poll tick fires for this connection (after the UART read started `pre_fetch` ms earlier). Each message combines the latest accel cache and visible tap state for every slave slot on the master.
**Success** — all slaves with a cache entry (not only those with `valid: true`):
```json ```json
{ {
@ -72,47 +73,43 @@ Sent when `set_stream` has `enable: true` and the poll tick fires for this conne
}, },
{ {
"client_id": 42, "client_id": 42,
"valid": false, "valid": false
"tap_kind": "none"
} }
] ]
} }
``` ```
| Field | Meaning | | Field | Meaning |
|-------|---------| | -------------- | -------------------------------------------------------------------- |
| `t` | Unix timestamp in **nanoseconds** when the host read the cache | | `t` | Unix timestamp in nanoseconds when the host read the cache |
| `success` | `true` if `CACHE_STATUS` succeeded | | `success` | `true` if `CACHE_STATUS` succeeded |
| `clients[]` | One entry per slave slot in the master cache | | `clients[]` | One entry per slave slot (includes invalid/stale entries) |
| `client_id` | ESP-NOW client id (same as `list_clients`) | | `client_id` | Same id as in `list_clients` |
| `valid` | `false` if no accel sample yet or stale; omit `x`/`y`/`z` when false | | `valid` | `false` if no accel sample yet or stale; omit `x`/`y`/`z` when false |
| `x`, `y`, `z` | Raw accelerometer LSB (BMA456, ±2 g scale on the pod) | | `x`, `y`, `z` | Raw accelerometer LSB (BMA456, ±2 g) |
| `accel_age_ms` | Milliseconds since the master received this accel sample | | `accel_age_ms` | Ms since the master received this accel sample |
| `tap_kind` | `"none"`, `"single"`, `"double"`, or `"triple"` | | `tap_kind` | `"single"`, `"double"`, or `"triple"`; omit when no recent tap |
| `tap_age_ms` | Milliseconds since the tap was seen in the master cache; omit when `tap_kind` is `"none"` | | `tap_age_ms` | Ms since tap in master cache; omit with `tap_kind` |
Tap events stay visible for **`tap_display_min_ms`** (2000 ms, also in `hello`) after the API first saw them, even if the hardware age grows.
**Failure** (e.g. UART busy): Tap events stay visible for `tap_display_min_ms` (2000, in `hello`) after the API first saw them.
**Failure** (no `clients` array):
```json ```json
{ {"type":"input","t":1716900123456789012,"success":false,"error":"uart busy"}
"type": "input",
"t": 1716900123456789012,
"success": false,
"error": "uart busy"
}
``` ```
No `clients` array on failure.
--- ---
## Commands (request → response) ## Commands
Send one JSON object per message. Field `type` selects the command. One JSON object per message; field `type` selects the command.
### `hello` (server → client, on connect) **Errors:** Replies use the matching response `type`. On failure: `success: false` (or omitted) and `"error": "…"`. Malformed JSON or unknown `type``stream_status` with `error`.
### `hello` (server → client)
```json ```json
{ {
@ -121,7 +118,6 @@ Send one JSON object per message. Field `type` selects the command.
"interval_ms": 16, "interval_ms": 16,
"pre_fetch_ms": 2, "pre_fetch_ms": 2,
"tap_display_min_ms": 2000, "tap_display_min_ms": 2000,
"note": "set_tap_notify configures slave S/D/T only; set_stream enables input polling/push on this connection",
"commands": [ "commands": [
"list_clients", "list_clients",
"set_stream", "get_stream", "set_stream", "get_stream",
@ -146,11 +142,7 @@ Response `client_list`:
{ {
"id": 16, "id": 16,
"mac": "aa:bb:cc:dd:ee:10", "mac": "aa:bb:cc:dd:ee:10",
"version": 1,
"available": true, "available": true,
"used": true,
"last_ping": 1234,
"last_success_ping": 1200,
"input_stream": false, "input_stream": false,
"tap_notify_single": false, "tap_notify_single": false,
"tap_notify_double": false, "tap_notify_double": false,
@ -160,28 +152,24 @@ Response `client_list`:
} }
``` ```
### `set_stream` / `get_stream` (receive input on this connection) Also per client: `version`, `used`, `last_ping`, `last_success_ping`.
### `set_stream` / `get_stream`
```json ```json
{"type":"set_stream","enable":true,"interval_ms":32,"pre_fetch":2} {"type":"set_stream","enable":true,"interval_ms":32,"pre_fetch":2}
{"type":"get_stream"} {"type":"get_stream"}
``` ```
| Field | Meaning |
|-------|---------|
| `enable` | Turn push stream on/off for this connection |
| `interval_ms` | Minimum time between `input` pushes (1 … 10000) |
| `pre_fetch` | Milliseconds before each push when the host starts the UART cache read; optional, default in `hello` (`pre_fetch_ms`) |
Response `stream_status`: Response `stream_status`:
```json ```json
{"type":"stream_status","receive_input":true,"interval_ms":32,"pre_fetch":2,"success":true} {"type":"stream_status","receive_input":true,"interval_ms":32,"pre_fetch":2,"success":true}
``` ```
### `set_input_stream` / `get_input_stream` (firmware, per slave) ### `set_input_stream` / `get_input_stream` (firmware)
`client_id` required (> 0). Enables accel streaming from the slave to the master. `client_id` required (> 0).
```json ```json
{"type":"set_input_stream","client_id":16,"enable":true} {"type":"set_input_stream","client_id":16,"enable":true}
@ -194,41 +182,32 @@ Response `input_stream_status`:
{"type":"input_stream_status","client_id":16,"enabled":true,"success":true} {"type":"input_stream_status","client_id":16,"enabled":true,"success":true}
``` ```
### `set_tap_notify` / `get_tap_notify` (firmware, per slave) ### `set_tap_notify` / `get_tap_notify` (firmware)
Per client: `single`, `double_tap`, `triple` required on set. Set requires `single`, `double_tap`, `triple` per client, or `"all_clients": true` for broadcast.
```json ```json
{"type":"set_tap_notify","client_id":16,"single":true,"double_tap":false,"triple":false} {"type":"set_tap_notify","client_id":16,"single":true,"double_tap":false,"triple":false}
{"type":"get_tap_notify","client_id":16}
``` ```
Broadcast: `"all_clients": true` with the three booleans.
Response `tap_notify_status`: Response `tap_notify_status`:
```json ```json
{ {"type":"tap_notify_status","client_id":16,"success":true,"single":true,"double_tap":false,"triple":false}
"type": "tap_notify_status",
"client_id": 16,
"success": true,
"single": true,
"double_tap": false,
"triple": false
}
``` ```
### `set_led_ring` ### `set_led_ring`
Control the LED ring on the master or a slave.
```json ```json
{"type":"set_led_ring","mode":"color","client_id":16,"r":255,"g":0,"b":0,"intensity":128} {"type":"set_led_ring","mode":"color","client_id":16,"r":255,"g":0,"b":0,"intensity":128}
{"type":"set_led_ring","mode":"digit","client_id":0,"digit":3,"r":0,"g":255,"b":0} {"type":"set_led_ring","mode":"digit","client_id":0,"digit":3,"r":0,"g":255,"b":0}
{"type":"set_led_ring","mode":"find-me","all_clients":true,"slaves_only":true} {"type":"set_led_ring","mode":"find-me","all_clients":true,"slaves_only":true}
``` ```
| `mode` | Notes |
|--------|--------| | Request `mode` | Notes |
| -------------- | --------------------------- |
| `clear` | Turn off | | `clear` | Turn off |
| `color` | Full ring RGB + `intensity` | | `color` | Full ring RGB + `intensity` |
| `progress` | `progress` 0100 | | `progress` | `progress` 0100 |
@ -236,9 +215,10 @@ Control the LED ring on the master or a slave.
| `blink` | `blink_ms`, `blink_count` | | `blink` | `blink_ms`, `blink_count` |
| `find-me` | Locate pod | | `find-me` | Locate pod |
Use `client_id` (`0` = master) or `all_clients` (+ optional `slaves_only`) for broadcast.
Response `led_ring_status`: Target: `client_id` (`0` = master) or `all_clients` (+ optional `slaves_only`).
Response `led_ring_status``mode` is numeric: 0=clear, 1=progress, 2=digit, 3=blink, 4=find-me, 5=color.
```json ```json
{"type":"led_ring_status","success":true,"mode":5,"client_id":16,"slaves_updated":1} {"type":"led_ring_status","success":true,"mode":5,"client_id":16,"slaves_updated":1}
@ -246,15 +226,13 @@ Response `led_ring_status`:
### `get_battery` ### `get_battery`
Read cached battery samples from the master. Slaves push battery every **30 s**; this command reads the master cache. Slaves push battery every 30 s; this reads the master cache. Default: all clients.
```json ```json
{"type":"get_battery","all_clients":true} {"type":"get_battery","all_clients":true}
{"type":"get_battery","client_id":16} {"type":"get_battery","client_id":16}
``` ```
Default if omitted: all clients.
Response `battery_status`: Response `battery_status`:
```json ```json
@ -271,3 +249,4 @@ Response `battery_status`:
] ]
} }
``` ```

View File

@ -19,13 +19,15 @@ func usage() {
fmt.Fprintf(os.Stderr, " tap-notify get/set which tap kinds notify via ESP-NOW\n") fmt.Fprintf(os.Stderr, " tap-notify get/set which tap kinds notify via ESP-NOW\n")
fmt.Fprintf(os.Stderr, " cache-status subscribed accel + tap cache (one UART round-trip)\n") fmt.Fprintf(os.Stderr, " cache-status subscribed accel + tap cache (one UART round-trip)\n")
fmt.Fprintf(os.Stderr, " unicast-test send ESP-NOW unicast test to one slave\n") fmt.Fprintf(os.Stderr, " unicast-test send ESP-NOW unicast test to one slave\n")
fmt.Fprintf(os.Stderr, " echo-ping ESP-NOW timestamp echo round-trip to one slave\n")
fmt.Fprintf(os.Stderr, " test run automated scenario (see testdata/)\n") fmt.Fprintf(os.Stderr, " test run automated scenario (see testdata/)\n")
fmt.Fprintf(os.Stderr, " serve web dashboard (Bootstrap + WebSocket)\n") fmt.Fprintf(os.Stderr, " serve web dashboard (Bootstrap + WebSocket)\n")
fmt.Fprintf(os.Stderr, " ota UART OTA upload (A/B partitions)\n") fmt.Fprintf(os.Stderr, " ota UART OTA upload (A/B partitions)\n")
fmt.Fprintf(os.Stderr, " ota-progress query per-slave ESP-NOW OTA progress on master\n") fmt.Fprintf(os.Stderr, " ota-progress query per-slave ESP-NOW OTA progress on master\n")
fmt.Fprintf(os.Stderr, " led-ring set LED ring progress bar (0100%%, rgb, intensity)\n") fmt.Fprintf(os.Stderr, " led-ring set LED ring progress bar (0100%%, rgb, intensity)\n")
fmt.Fprintf(os.Stderr, " find-me blink LED ring red/green/blue (3× each, full brightness)\n") fmt.Fprintf(os.Stderr, " find-me blink LED ring red/green/blue (3× each, full brightness)\n")
fmt.Fprintf(os.Stderr, " restart reboot master or slave (ESP-NOW)\n\n") fmt.Fprintf(os.Stderr, " restart reboot master or slave (ESP-NOW)\n")
fmt.Fprintf(os.Stderr, " log-level get/set master ESP-IDF log level (global)\n\n")
flag.PrintDefaults() flag.PrintDefaults()
} }
@ -52,7 +54,7 @@ func main() {
os.Exit(2) os.Exit(2)
} }
runErr = runServe(*portName, *baud, flag.Args()[1:]) runErr = runServe(*portName, *baud, flag.Args()[1:])
case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-notify", "tap_notify", "cache-status", "cache_status", "unicast-test", "unicast_test", "led-ring", "led_ring", "find-me", "find_me", "restart", "ota", "ota-progress", "ota_progress": case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-notify", "tap_notify", "cache-status", "cache_status", "unicast-test", "unicast_test", "echo-ping", "echo_ping", "led-ring", "led_ring", "find-me", "find_me", "restart", "log-level", "log_level", "ota", "ota-progress", "ota_progress":
if *portName == "" { if *portName == "" {
fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd) fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd)
usage() usage()
@ -78,12 +80,16 @@ func main() {
runErr = runCacheStatus(sp) runErr = runCacheStatus(sp)
case "unicast-test", "unicast_test": case "unicast-test", "unicast_test":
runErr = runUnicastTest(sp, flag.Args()[1:]) runErr = runUnicastTest(sp, flag.Args()[1:])
case "echo-ping", "echo_ping":
runErr = runEchoPing(sp, flag.Args()[1:])
case "led-ring", "led_ring": case "led-ring", "led_ring":
runErr = runLedRing(sp, flag.Args()[1:]) runErr = runLedRing(sp, flag.Args()[1:])
case "find-me", "find_me": case "find-me", "find_me":
runErr = runFindMe(sp, flag.Args()[1:]) runErr = runFindMe(sp, flag.Args()[1:])
case "restart": case "restart":
runErr = runRestart(sp, flag.Args()[1:]) runErr = runRestart(sp, flag.Args()[1:])
case "log-level", "log_level":
runErr = runLogLevel(sp, flag.Args()[1:])
case "ota": case "ota":
runErr = runOTA(sp, flag.Args()[1:]) runErr = runOTA(sp, flag.Args()[1:])
case "ota-progress", "ota_progress": case "ota-progress", "ota_progress":

View File

@ -20,6 +20,9 @@ const (
otaDistQueryInterval = 500 * time.Millisecond otaDistQueryInterval = 500 * time.Millisecond
otaDistQueryTimeout = 2 * time.Second otaDistQueryTimeout = 2 * time.Second
otaDistEmitMinInterval = 150 * time.Millisecond otaDistEmitMinInterval = 150 * time.Millisecond
// Pace host chunks so the master UART RX ring is not overrun (~20 frames/block).
otaHostChunkPace = 3 * time.Millisecond
otaBlockMaxRetries = 3
) )
const ( const (
@ -189,6 +192,10 @@ func runOTAOnPortUnlocked(sp *serialPort, firmware []byte, onProgress otaProgres
var seq uint32 var seq uint32
for offset := 0; offset < imageSize; { for offset := 0; offset < imageSize; {
blockStart := offset
blockStartSeq := seq
sendBlock := func() (fullBlock bool, err error) {
bytesInBlock := 0 bytesInBlock := 0
for bytesInBlock < otaFlashBlockSize && offset < imageSize { for bytesInBlock < otaFlashBlockSize && offset < imageSize {
n := otaHostChunkSize n := otaHostChunkSize
@ -207,9 +214,9 @@ func runOTAOnPortUnlocked(sp *serialPort, firmware []byte, onProgress otaProgres
OtaPayload: &pb.OtaPayload{Seq: seq, Data: chunk}, OtaPayload: &pb.OtaPayload{Seq: seq, Data: chunk},
}, },
}); err != nil { }); err != nil {
notify("error", "", 0, err.Error()) return false, err
return err
} }
time.Sleep(otaHostChunkPace)
seq++ seq++
offset += n offset += n
bytesInBlock += n bytesInBlock += n
@ -218,15 +225,43 @@ func runOTAOnPortUnlocked(sp *serialPort, firmware []byte, onProgress otaProgres
if pct > 99 { if pct > 99 {
pct = 99 pct = 99
} }
notify("uploading", otaStepMaster, pct, fmt.Sprintf("Master: %d / %d bytes", offset, imageSize)) notify("uploading", otaStepMaster, pct,
fmt.Sprintf("Master: %d / %d bytes", offset, imageSize))
}
return bytesInBlock == otaFlashBlockSize, nil
} }
if bytesInBlock == otaFlashBlockSize { fullBlock, err := sendBlock()
st, err := waitOtaStatus(sp, otaStBlockAck, otaDefaultTimeout, nil)
if err != nil { if err != nil {
notify("error", "", 0, err.Error()) notify("error", "", 0, err.Error())
return err return err
} }
if !fullBlock {
continue
}
var st *pb.OtaStatusPayload
var ackErr error
for attempt := 0; attempt < otaBlockMaxRetries; attempt++ {
if attempt > 0 {
offset = blockStart
seq = blockStartSeq
if _, err := sendBlock(); err != nil {
notify("error", "", 0, err.Error())
return err
}
}
st, ackErr = waitOtaStatus(sp, otaStBlockAck, otaDefaultTimeout, nil)
if ackErr == nil {
break
}
}
if ackErr != nil {
notify("error", "", 0, ackErr.Error())
return ackErr
}
pct := offset * 100 / imageSize pct := offset * 100 / imageSize
if pct > 99 { if pct > 99 {
pct = 99 pct = 99
@ -235,7 +270,6 @@ func runOTAOnPortUnlocked(sp *serialPort, firmware []byte, onProgress otaProgres
fmt.Sprintf("Master: Block geschrieben (%d bytes)", st.GetBytesWritten()), fmt.Sprintf("Master: Block geschrieben (%d bytes)", st.GetBytesWritten()),
OTAProgress{Bytes: st.GetBytesWritten()}) OTAProgress{Bytes: st.GetBytesWritten()})
} }
}
masterPct = 100 masterPct = 100
masterMsg = "Master: UART-Upload abgeschlossen" masterMsg = "Master: UART-Upload abgeschlossen"

View File

@ -46,6 +46,10 @@ const (
MessageType_TAP_NOTIFY MessageType = 27 MessageType_TAP_NOTIFY MessageType = 27
// * Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). // * Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence).
MessageType_CACHE_STATUS MessageType = 29 MessageType_CACHE_STATUS MessageType = 29
// * Host → master → slave → master: timestamp echo round-trip (latency test).
MessageType_ESPNOW_ECHO_PING MessageType = 30
// * Host → master: get/set ESP-IDF log level for tag "*" (global).
MessageType_SET_LOG_LEVEL MessageType = 31
) )
// Enum value maps for MessageType. // Enum value maps for MessageType.
@ -72,6 +76,8 @@ var (
26: "BATTERY_STATUS", 26: "BATTERY_STATUS",
27: "TAP_NOTIFY", 27: "TAP_NOTIFY",
29: "CACHE_STATUS", 29: "CACHE_STATUS",
30: "ESPNOW_ECHO_PING",
31: "SET_LOG_LEVEL",
} }
MessageType_value = map[string]int32{ MessageType_value = map[string]int32{
"UNKNOWN": 0, "UNKNOWN": 0,
@ -95,6 +101,8 @@ var (
"BATTERY_STATUS": 26, "BATTERY_STATUS": 26,
"TAP_NOTIFY": 27, "TAP_NOTIFY": 27,
"CACHE_STATUS": 29, "CACHE_STATUS": 29,
"ESPNOW_ECHO_PING": 30,
"SET_LOG_LEVEL": 31,
} }
) )
@ -211,6 +219,10 @@ type UartMessage struct {
// *UartMessage_TapNotifyResponse // *UartMessage_TapNotifyResponse
// *UartMessage_CacheStatusRequest // *UartMessage_CacheStatusRequest
// *UartMessage_CacheStatusResponse // *UartMessage_CacheStatusResponse
// *UartMessage_EspnowEchoPingRequest
// *UartMessage_EspnowEchoPingResponse
// *UartMessage_SetLogLevelRequest
// *UartMessage_SetLogLevelResponse
Payload isUartMessage_Payload `protobuf_oneof:"payload"` Payload isUartMessage_Payload `protobuf_oneof:"payload"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -521,6 +533,42 @@ func (x *UartMessage) GetCacheStatusResponse() *CacheStatusResponse {
return nil return nil
} }
func (x *UartMessage) GetEspnowEchoPingRequest() *EspNowEchoPingRequest {
if x != nil {
if x, ok := x.Payload.(*UartMessage_EspnowEchoPingRequest); ok {
return x.EspnowEchoPingRequest
}
}
return nil
}
func (x *UartMessage) GetEspnowEchoPingResponse() *EspNowEchoPingResponse {
if x != nil {
if x, ok := x.Payload.(*UartMessage_EspnowEchoPingResponse); ok {
return x.EspnowEchoPingResponse
}
}
return nil
}
func (x *UartMessage) GetSetLogLevelRequest() *SetLogLevelRequest {
if x != nil {
if x, ok := x.Payload.(*UartMessage_SetLogLevelRequest); ok {
return x.SetLogLevelRequest
}
}
return nil
}
func (x *UartMessage) GetSetLogLevelResponse() *SetLogLevelResponse {
if x != nil {
if x, ok := x.Payload.(*UartMessage_SetLogLevelResponse); ok {
return x.SetLogLevelResponse
}
}
return nil
}
type isUartMessage_Payload interface { type isUartMessage_Payload interface {
isUartMessage_Payload() isUartMessage_Payload()
} }
@ -641,6 +689,22 @@ type UartMessage_CacheStatusResponse struct {
CacheStatusResponse *CacheStatusResponse `protobuf:"bytes,34,opt,name=cache_status_response,json=cacheStatusResponse,proto3,oneof"` CacheStatusResponse *CacheStatusResponse `protobuf:"bytes,34,opt,name=cache_status_response,json=cacheStatusResponse,proto3,oneof"`
} }
type UartMessage_EspnowEchoPingRequest struct {
EspnowEchoPingRequest *EspNowEchoPingRequest `protobuf:"bytes,35,opt,name=espnow_echo_ping_request,json=espnowEchoPingRequest,proto3,oneof"`
}
type UartMessage_EspnowEchoPingResponse struct {
EspnowEchoPingResponse *EspNowEchoPingResponse `protobuf:"bytes,36,opt,name=espnow_echo_ping_response,json=espnowEchoPingResponse,proto3,oneof"`
}
type UartMessage_SetLogLevelRequest struct {
SetLogLevelRequest *SetLogLevelRequest `protobuf:"bytes,37,opt,name=set_log_level_request,json=setLogLevelRequest,proto3,oneof"`
}
type UartMessage_SetLogLevelResponse struct {
SetLogLevelResponse *SetLogLevelResponse `protobuf:"bytes,38,opt,name=set_log_level_response,json=setLogLevelResponse,proto3,oneof"`
}
func (*UartMessage_AckPayload) isUartMessage_Payload() {} func (*UartMessage_AckPayload) isUartMessage_Payload() {}
func (*UartMessage_EchoPayload) isUartMessage_Payload() {} func (*UartMessage_EchoPayload) isUartMessage_Payload() {}
@ -699,6 +763,14 @@ func (*UartMessage_CacheStatusRequest) isUartMessage_Payload() {}
func (*UartMessage_CacheStatusResponse) isUartMessage_Payload() {} func (*UartMessage_CacheStatusResponse) isUartMessage_Payload() {}
func (*UartMessage_EspnowEchoPingRequest) isUartMessage_Payload() {}
func (*UartMessage_EspnowEchoPingResponse) isUartMessage_Payload() {}
func (*UartMessage_SetLogLevelRequest) isUartMessage_Payload() {}
func (*UartMessage_SetLogLevelResponse) isUartMessage_Payload() {}
type Ack struct { type Ack struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
@ -2329,6 +2401,130 @@ func (x *EspNowUnicastTestResponse) GetSeq() uint32 {
return 0 return 0
} }
// * Host → master: ESP-NOW echo ping to one slave (timestamp echoed back).
type EspNowEchoPingRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
ClientId uint32 `protobuf:"varint,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
// * Microseconds since Unix epoch (host clock).
TimestampUs uint64 `protobuf:"varint,2,opt,name=timestamp_us,json=timestampUs,proto3" json:"timestamp_us,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EspNowEchoPingRequest) Reset() {
*x = EspNowEchoPingRequest{}
mi := &file_uart_messages_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EspNowEchoPingRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EspNowEchoPingRequest) ProtoMessage() {}
func (x *EspNowEchoPingRequest) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[27]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EspNowEchoPingRequest.ProtoReflect.Descriptor instead.
func (*EspNowEchoPingRequest) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{27}
}
func (x *EspNowEchoPingRequest) GetClientId() uint32 {
if x != nil {
return x.ClientId
}
return 0
}
func (x *EspNowEchoPingRequest) GetTimestampUs() uint64 {
if x != nil {
return x.TimestampUs
}
return 0
}
type EspNowEchoPingResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
ClientId uint32 `protobuf:"varint,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
// * Echoed host timestamp from goTool request.
TimestampUs uint64 `protobuf:"varint,3,opt,name=timestamp_us,json=timestampUs,proto3" json:"timestamp_us,omitempty"`
// * esp_timer_get_time() delta from ping send to pong recv (master→slave→master).
EspRttUs uint32 `protobuf:"varint,4,opt,name=esp_rtt_us,json=espRttUs,proto3" json:"esp_rtt_us,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EspNowEchoPingResponse) Reset() {
*x = EspNowEchoPingResponse{}
mi := &file_uart_messages_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EspNowEchoPingResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EspNowEchoPingResponse) ProtoMessage() {}
func (x *EspNowEchoPingResponse) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[28]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EspNowEchoPingResponse.ProtoReflect.Descriptor instead.
func (*EspNowEchoPingResponse) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{28}
}
func (x *EspNowEchoPingResponse) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
func (x *EspNowEchoPingResponse) GetClientId() uint32 {
if x != nil {
return x.ClientId
}
return 0
}
func (x *EspNowEchoPingResponse) GetTimestampUs() uint64 {
if x != nil {
return x.TimestampUs
}
return 0
}
func (x *EspNowEchoPingResponse) GetEspRttUs() uint32 {
if x != nil {
return x.EspRttUs
}
return 0
}
// Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW. // Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
// mode: 0=clear, 1=progress (0100 %), 2=digit (010), 3=blink, 4=find-me, 5=all LEDs solid color. // mode: 0=clear, 1=progress (0100 %), 2=digit (010), 3=blink, 4=find-me, 5=all LEDs solid color.
type LedRingProgressRequest struct { type LedRingProgressRequest struct {
@ -2359,7 +2555,7 @@ type LedRingProgressRequest struct {
func (x *LedRingProgressRequest) Reset() { func (x *LedRingProgressRequest) Reset() {
*x = LedRingProgressRequest{} *x = LedRingProgressRequest{}
mi := &file_uart_messages_proto_msgTypes[27] mi := &file_uart_messages_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2371,7 +2567,7 @@ func (x *LedRingProgressRequest) String() string {
func (*LedRingProgressRequest) ProtoMessage() {} func (*LedRingProgressRequest) ProtoMessage() {}
func (x *LedRingProgressRequest) ProtoReflect() protoreflect.Message { func (x *LedRingProgressRequest) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[27] mi := &file_uart_messages_proto_msgTypes[29]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2384,7 +2580,7 @@ func (x *LedRingProgressRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use LedRingProgressRequest.ProtoReflect.Descriptor instead. // Deprecated: Use LedRingProgressRequest.ProtoReflect.Descriptor instead.
func (*LedRingProgressRequest) Descriptor() ([]byte, []int) { func (*LedRingProgressRequest) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{27} return file_uart_messages_proto_rawDescGZIP(), []int{29}
} }
func (x *LedRingProgressRequest) GetMode() uint32 { func (x *LedRingProgressRequest) GetMode() uint32 {
@ -2485,7 +2681,7 @@ type LedRingProgressResponse struct {
func (x *LedRingProgressResponse) Reset() { func (x *LedRingProgressResponse) Reset() {
*x = LedRingProgressResponse{} *x = LedRingProgressResponse{}
mi := &file_uart_messages_proto_msgTypes[28] mi := &file_uart_messages_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2497,7 +2693,7 @@ func (x *LedRingProgressResponse) String() string {
func (*LedRingProgressResponse) ProtoMessage() {} func (*LedRingProgressResponse) ProtoMessage() {}
func (x *LedRingProgressResponse) ProtoReflect() protoreflect.Message { func (x *LedRingProgressResponse) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[28] mi := &file_uart_messages_proto_msgTypes[30]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2510,7 +2706,7 @@ func (x *LedRingProgressResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use LedRingProgressResponse.ProtoReflect.Descriptor instead. // Deprecated: Use LedRingProgressResponse.ProtoReflect.Descriptor instead.
func (*LedRingProgressResponse) Descriptor() ([]byte, []int) { func (*LedRingProgressResponse) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{28} return file_uart_messages_proto_rawDescGZIP(), []int{30}
} }
func (x *LedRingProgressResponse) GetSuccess() bool { func (x *LedRingProgressResponse) GetSuccess() bool {
@ -2565,7 +2761,7 @@ type EspNowFindMeRequest struct {
func (x *EspNowFindMeRequest) Reset() { func (x *EspNowFindMeRequest) Reset() {
*x = EspNowFindMeRequest{} *x = EspNowFindMeRequest{}
mi := &file_uart_messages_proto_msgTypes[29] mi := &file_uart_messages_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2577,7 +2773,7 @@ func (x *EspNowFindMeRequest) String() string {
func (*EspNowFindMeRequest) ProtoMessage() {} func (*EspNowFindMeRequest) ProtoMessage() {}
func (x *EspNowFindMeRequest) ProtoReflect() protoreflect.Message { func (x *EspNowFindMeRequest) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[29] mi := &file_uart_messages_proto_msgTypes[31]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2590,7 +2786,7 @@ func (x *EspNowFindMeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use EspNowFindMeRequest.ProtoReflect.Descriptor instead. // Deprecated: Use EspNowFindMeRequest.ProtoReflect.Descriptor instead.
func (*EspNowFindMeRequest) Descriptor() ([]byte, []int) { func (*EspNowFindMeRequest) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{29} return file_uart_messages_proto_rawDescGZIP(), []int{31}
} }
func (x *EspNowFindMeRequest) GetClientId() uint32 { func (x *EspNowFindMeRequest) GetClientId() uint32 {
@ -2610,7 +2806,7 @@ type EspNowFindMeResponse struct {
func (x *EspNowFindMeResponse) Reset() { func (x *EspNowFindMeResponse) Reset() {
*x = EspNowFindMeResponse{} *x = EspNowFindMeResponse{}
mi := &file_uart_messages_proto_msgTypes[30] mi := &file_uart_messages_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2622,7 +2818,7 @@ func (x *EspNowFindMeResponse) String() string {
func (*EspNowFindMeResponse) ProtoMessage() {} func (*EspNowFindMeResponse) ProtoMessage() {}
func (x *EspNowFindMeResponse) ProtoReflect() protoreflect.Message { func (x *EspNowFindMeResponse) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[30] mi := &file_uart_messages_proto_msgTypes[32]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2635,7 +2831,7 @@ func (x *EspNowFindMeResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use EspNowFindMeResponse.ProtoReflect.Descriptor instead. // Deprecated: Use EspNowFindMeResponse.ProtoReflect.Descriptor instead.
func (*EspNowFindMeResponse) Descriptor() ([]byte, []int) { func (*EspNowFindMeResponse) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{30} return file_uart_messages_proto_rawDescGZIP(), []int{32}
} }
func (x *EspNowFindMeResponse) GetSuccess() bool { func (x *EspNowFindMeResponse) GetSuccess() bool {
@ -2662,7 +2858,7 @@ type RestartRequest struct {
func (x *RestartRequest) Reset() { func (x *RestartRequest) Reset() {
*x = RestartRequest{} *x = RestartRequest{}
mi := &file_uart_messages_proto_msgTypes[31] mi := &file_uart_messages_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2674,7 +2870,7 @@ func (x *RestartRequest) String() string {
func (*RestartRequest) ProtoMessage() {} func (*RestartRequest) ProtoMessage() {}
func (x *RestartRequest) ProtoReflect() protoreflect.Message { func (x *RestartRequest) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[31] mi := &file_uart_messages_proto_msgTypes[33]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2687,7 +2883,7 @@ func (x *RestartRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RestartRequest.ProtoReflect.Descriptor instead. // Deprecated: Use RestartRequest.ProtoReflect.Descriptor instead.
func (*RestartRequest) Descriptor() ([]byte, []int) { func (*RestartRequest) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{31} return file_uart_messages_proto_rawDescGZIP(), []int{33}
} }
func (x *RestartRequest) GetClientId() uint32 { func (x *RestartRequest) GetClientId() uint32 {
@ -2707,7 +2903,7 @@ type RestartResponse struct {
func (x *RestartResponse) Reset() { func (x *RestartResponse) Reset() {
*x = RestartResponse{} *x = RestartResponse{}
mi := &file_uart_messages_proto_msgTypes[32] mi := &file_uart_messages_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2719,7 +2915,7 @@ func (x *RestartResponse) String() string {
func (*RestartResponse) ProtoMessage() {} func (*RestartResponse) ProtoMessage() {}
func (x *RestartResponse) ProtoReflect() protoreflect.Message { func (x *RestartResponse) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[32] mi := &file_uart_messages_proto_msgTypes[34]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2732,7 +2928,7 @@ func (x *RestartResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RestartResponse.ProtoReflect.Descriptor instead. // Deprecated: Use RestartResponse.ProtoReflect.Descriptor instead.
func (*RestartResponse) Descriptor() ([]byte, []int) { func (*RestartResponse) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{32} return file_uart_messages_proto_rawDescGZIP(), []int{34}
} }
func (x *RestartResponse) GetSuccess() bool { func (x *RestartResponse) GetSuccess() bool {
@ -2749,6 +2945,112 @@ func (x *RestartResponse) GetClientId() uint32 {
return 0 return 0
} }
// * Host → master: read/write global log level (esp_log_level_set("*", …)).
type SetLogLevelRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Write bool `protobuf:"varint,1,opt,name=write,proto3" json:"write,omitempty"`
// * esp_log_level_t: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE
Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetLogLevelRequest) Reset() {
*x = SetLogLevelRequest{}
mi := &file_uart_messages_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetLogLevelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetLogLevelRequest) ProtoMessage() {}
func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetLogLevelRequest.ProtoReflect.Descriptor instead.
func (*SetLogLevelRequest) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{35}
}
func (x *SetLogLevelRequest) GetWrite() bool {
if x != nil {
return x.Write
}
return false
}
func (x *SetLogLevelRequest) GetLevel() uint32 {
if x != nil {
return x.Level
}
return 0
}
type SetLogLevelResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetLogLevelResponse) Reset() {
*x = SetLogLevelResponse{}
mi := &file_uart_messages_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetLogLevelResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetLogLevelResponse) ProtoMessage() {}
func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetLogLevelResponse.ProtoReflect.Descriptor instead.
func (*SetLogLevelResponse) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{36}
}
func (x *SetLogLevelResponse) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
func (x *SetLogLevelResponse) GetLevel() uint32 {
if x != nil {
return x.Level
}
return 0
}
// Host → device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS). // Host → device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS).
type OtaStartPayload struct { type OtaStartPayload struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
@ -2759,7 +3061,7 @@ type OtaStartPayload struct {
func (x *OtaStartPayload) Reset() { func (x *OtaStartPayload) Reset() {
*x = OtaStartPayload{} *x = OtaStartPayload{}
mi := &file_uart_messages_proto_msgTypes[33] mi := &file_uart_messages_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2771,7 +3073,7 @@ func (x *OtaStartPayload) String() string {
func (*OtaStartPayload) ProtoMessage() {} func (*OtaStartPayload) ProtoMessage() {}
func (x *OtaStartPayload) ProtoReflect() protoreflect.Message { func (x *OtaStartPayload) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[33] mi := &file_uart_messages_proto_msgTypes[37]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2784,7 +3086,7 @@ func (x *OtaStartPayload) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaStartPayload.ProtoReflect.Descriptor instead. // Deprecated: Use OtaStartPayload.ProtoReflect.Descriptor instead.
func (*OtaStartPayload) Descriptor() ([]byte, []int) { func (*OtaStartPayload) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{33} return file_uart_messages_proto_rawDescGZIP(), []int{37}
} }
func (x *OtaStartPayload) GetTotalSize() uint32 { func (x *OtaStartPayload) GetTotalSize() uint32 {
@ -2805,7 +3107,7 @@ type OtaPayload struct {
func (x *OtaPayload) Reset() { func (x *OtaPayload) Reset() {
*x = OtaPayload{} *x = OtaPayload{}
mi := &file_uart_messages_proto_msgTypes[34] mi := &file_uart_messages_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2817,7 +3119,7 @@ func (x *OtaPayload) String() string {
func (*OtaPayload) ProtoMessage() {} func (*OtaPayload) ProtoMessage() {}
func (x *OtaPayload) ProtoReflect() protoreflect.Message { func (x *OtaPayload) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[34] mi := &file_uart_messages_proto_msgTypes[38]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2830,7 +3132,7 @@ func (x *OtaPayload) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaPayload.ProtoReflect.Descriptor instead. // Deprecated: Use OtaPayload.ProtoReflect.Descriptor instead.
func (*OtaPayload) Descriptor() ([]byte, []int) { func (*OtaPayload) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{34} return file_uart_messages_proto_rawDescGZIP(), []int{38}
} }
func (x *OtaPayload) GetSeq() uint32 { func (x *OtaPayload) GetSeq() uint32 {
@ -2856,7 +3158,7 @@ type OtaEndPayload struct {
func (x *OtaEndPayload) Reset() { func (x *OtaEndPayload) Reset() {
*x = OtaEndPayload{} *x = OtaEndPayload{}
mi := &file_uart_messages_proto_msgTypes[35] mi := &file_uart_messages_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2868,7 +3170,7 @@ func (x *OtaEndPayload) String() string {
func (*OtaEndPayload) ProtoMessage() {} func (*OtaEndPayload) ProtoMessage() {}
func (x *OtaEndPayload) ProtoReflect() protoreflect.Message { func (x *OtaEndPayload) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[35] mi := &file_uart_messages_proto_msgTypes[39]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2881,7 +3183,7 @@ func (x *OtaEndPayload) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaEndPayload.ProtoReflect.Descriptor instead. // Deprecated: Use OtaEndPayload.ProtoReflect.Descriptor instead.
func (*OtaEndPayload) Descriptor() ([]byte, []int) { func (*OtaEndPayload) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{35} return file_uart_messages_proto_rawDescGZIP(), []int{39}
} }
// Device → host status (also used as ACK after each 4 KiB written). // Device → host status (also used as ACK after each 4 KiB written).
@ -2898,7 +3200,7 @@ type OtaStatusPayload struct {
func (x *OtaStatusPayload) Reset() { func (x *OtaStatusPayload) Reset() {
*x = OtaStatusPayload{} *x = OtaStatusPayload{}
mi := &file_uart_messages_proto_msgTypes[36] mi := &file_uart_messages_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2910,7 +3212,7 @@ func (x *OtaStatusPayload) String() string {
func (*OtaStatusPayload) ProtoMessage() {} func (*OtaStatusPayload) ProtoMessage() {}
func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message { func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[36] mi := &file_uart_messages_proto_msgTypes[40]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2923,7 +3225,7 @@ func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaStatusPayload.ProtoReflect.Descriptor instead. // Deprecated: Use OtaStatusPayload.ProtoReflect.Descriptor instead.
func (*OtaStatusPayload) Descriptor() ([]byte, []int) { func (*OtaStatusPayload) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{36} return file_uart_messages_proto_rawDescGZIP(), []int{40}
} }
func (x *OtaStatusPayload) GetStatus() uint32 { func (x *OtaStatusPayload) GetStatus() uint32 {
@ -2964,7 +3266,7 @@ type OtaSlaveProgressRequest struct {
func (x *OtaSlaveProgressRequest) Reset() { func (x *OtaSlaveProgressRequest) Reset() {
*x = OtaSlaveProgressRequest{} *x = OtaSlaveProgressRequest{}
mi := &file_uart_messages_proto_msgTypes[37] mi := &file_uart_messages_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2976,7 +3278,7 @@ func (x *OtaSlaveProgressRequest) String() string {
func (*OtaSlaveProgressRequest) ProtoMessage() {} func (*OtaSlaveProgressRequest) ProtoMessage() {}
func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message { func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[37] mi := &file_uart_messages_proto_msgTypes[41]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2989,7 +3291,7 @@ func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaSlaveProgressRequest.ProtoReflect.Descriptor instead. // Deprecated: Use OtaSlaveProgressRequest.ProtoReflect.Descriptor instead.
func (*OtaSlaveProgressRequest) Descriptor() ([]byte, []int) { func (*OtaSlaveProgressRequest) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{37} return file_uart_messages_proto_rawDescGZIP(), []int{41}
} }
func (x *OtaSlaveProgressRequest) GetClientId() uint32 { func (x *OtaSlaveProgressRequest) GetClientId() uint32 {
@ -3013,7 +3315,7 @@ type OtaSlaveProgressEntry struct {
func (x *OtaSlaveProgressEntry) Reset() { func (x *OtaSlaveProgressEntry) Reset() {
*x = OtaSlaveProgressEntry{} *x = OtaSlaveProgressEntry{}
mi := &file_uart_messages_proto_msgTypes[38] mi := &file_uart_messages_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3025,7 +3327,7 @@ func (x *OtaSlaveProgressEntry) String() string {
func (*OtaSlaveProgressEntry) ProtoMessage() {} func (*OtaSlaveProgressEntry) ProtoMessage() {}
func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message { func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[38] mi := &file_uart_messages_proto_msgTypes[42]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3038,7 +3340,7 @@ func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaSlaveProgressEntry.ProtoReflect.Descriptor instead. // Deprecated: Use OtaSlaveProgressEntry.ProtoReflect.Descriptor instead.
func (*OtaSlaveProgressEntry) Descriptor() ([]byte, []int) { func (*OtaSlaveProgressEntry) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{38} return file_uart_messages_proto_rawDescGZIP(), []int{42}
} }
func (x *OtaSlaveProgressEntry) GetClientId() uint32 { func (x *OtaSlaveProgressEntry) GetClientId() uint32 {
@ -3089,7 +3391,7 @@ type OtaSlaveProgressResponse struct {
func (x *OtaSlaveProgressResponse) Reset() { func (x *OtaSlaveProgressResponse) Reset() {
*x = OtaSlaveProgressResponse{} *x = OtaSlaveProgressResponse{}
mi := &file_uart_messages_proto_msgTypes[39] mi := &file_uart_messages_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -3101,7 +3403,7 @@ func (x *OtaSlaveProgressResponse) String() string {
func (*OtaSlaveProgressResponse) ProtoMessage() {} func (*OtaSlaveProgressResponse) ProtoMessage() {}
func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message { func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[39] mi := &file_uart_messages_proto_msgTypes[43]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -3114,7 +3416,7 @@ func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaSlaveProgressResponse.ProtoReflect.Descriptor instead. // Deprecated: Use OtaSlaveProgressResponse.ProtoReflect.Descriptor instead.
func (*OtaSlaveProgressResponse) Descriptor() ([]byte, []int) { func (*OtaSlaveProgressResponse) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{39} return file_uart_messages_proto_rawDescGZIP(), []int{43}
} }
func (x *OtaSlaveProgressResponse) GetActive() bool { func (x *OtaSlaveProgressResponse) GetActive() bool {
@ -3156,7 +3458,7 @@ var File_uart_messages_proto protoreflect.FileDescriptor
const file_uart_messages_proto_rawDesc = "" + const file_uart_messages_proto_rawDesc = "" +
"\n" + "\n" +
"\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\xec\x11\n" + "\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\xc0\x14\n" +
"\vUartMessage\x12%\n" + "\vUartMessage\x12%\n" +
"\x04type\x18\x01 \x01(\x0e2\x11.alox.MessageTypeR\x04type\x12,\n" + "\x04type\x18\x01 \x01(\x0e2\x11.alox.MessageTypeR\x04type\x12,\n" +
"\vack_payload\x18\x02 \x01(\v2\t.alox.AckH\x00R\n" + "\vack_payload\x18\x02 \x01(\v2\t.alox.AckH\x00R\n" +
@ -3191,7 +3493,11 @@ const file_uart_messages_proto_rawDesc = "" +
"\x12tap_notify_request\x18\x1d \x01(\v2\x16.alox.TapNotifyRequestH\x00R\x10tapNotifyRequest\x12I\n" + "\x12tap_notify_request\x18\x1d \x01(\v2\x16.alox.TapNotifyRequestH\x00R\x10tapNotifyRequest\x12I\n" +
"\x13tap_notify_response\x18\x1e \x01(\v2\x17.alox.TapNotifyResponseH\x00R\x11tapNotifyResponse\x12L\n" + "\x13tap_notify_response\x18\x1e \x01(\v2\x17.alox.TapNotifyResponseH\x00R\x11tapNotifyResponse\x12L\n" +
"\x14cache_status_request\x18! \x01(\v2\x18.alox.CacheStatusRequestH\x00R\x12cacheStatusRequest\x12O\n" + "\x14cache_status_request\x18! \x01(\v2\x18.alox.CacheStatusRequestH\x00R\x12cacheStatusRequest\x12O\n" +
"\x15cache_status_response\x18\" \x01(\v2\x19.alox.CacheStatusResponseH\x00R\x13cacheStatusResponseB\t\n" + "\x15cache_status_response\x18\" \x01(\v2\x19.alox.CacheStatusResponseH\x00R\x13cacheStatusResponse\x12V\n" +
"\x18espnow_echo_ping_request\x18# \x01(\v2\x1b.alox.EspNowEchoPingRequestH\x00R\x15espnowEchoPingRequest\x12Y\n" +
"\x19espnow_echo_ping_response\x18$ \x01(\v2\x1c.alox.EspNowEchoPingResponseH\x00R\x16espnowEchoPingResponse\x12M\n" +
"\x15set_log_level_request\x18% \x01(\v2\x18.alox.SetLogLevelRequestH\x00R\x12setLogLevelRequest\x12P\n" +
"\x16set_log_level_response\x18& \x01(\v2\x19.alox.SetLogLevelResponseH\x00R\x13setLogLevelResponseB\t\n" +
"\apayload\"\x05\n" + "\apayload\"\x05\n" +
"\x03Ack\"!\n" + "\x03Ack\"!\n" +
"\vEchoPayload\x12\x12\n" + "\vEchoPayload\x12\x12\n" +
@ -3311,7 +3617,16 @@ const file_uart_messages_proto_rawDesc = "" +
"\x03seq\x18\x02 \x01(\rR\x03seq\"G\n" + "\x03seq\x18\x02 \x01(\rR\x03seq\"G\n" +
"\x19EspNowUnicastTestResponse\x12\x18\n" + "\x19EspNowUnicastTestResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x10\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x10\n" +
"\x03seq\x18\x02 \x01(\rR\x03seq\"\xc1\x02\n" + "\x03seq\x18\x02 \x01(\rR\x03seq\"W\n" +
"\x15EspNowEchoPingRequest\x12\x1b\n" +
"\tclient_id\x18\x01 \x01(\rR\bclientId\x12!\n" +
"\ftimestamp_us\x18\x02 \x01(\x04R\vtimestampUs\"\x90\x01\n" +
"\x16EspNowEchoPingResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1b\n" +
"\tclient_id\x18\x02 \x01(\rR\bclientId\x12!\n" +
"\ftimestamp_us\x18\x03 \x01(\x04R\vtimestampUs\x12\x1c\n" +
"\n" +
"esp_rtt_us\x18\x04 \x01(\rR\bespRttUs\"\xc1\x02\n" +
"\x16LedRingProgressRequest\x12\x12\n" + "\x16LedRingProgressRequest\x12\x12\n" +
"\x04mode\x18\x01 \x01(\rR\x04mode\x12\x1a\n" + "\x04mode\x18\x01 \x01(\rR\x04mode\x12\x1a\n" +
"\bprogress\x18\x02 \x01(\rR\bprogress\x12\x14\n" + "\bprogress\x18\x02 \x01(\rR\bprogress\x12\x14\n" +
@ -3345,7 +3660,13 @@ const file_uart_messages_proto_rawDesc = "" +
"\tclient_id\x18\x01 \x01(\rR\bclientId\"H\n" + "\tclient_id\x18\x01 \x01(\rR\bclientId\"H\n" +
"\x0fRestartResponse\x12\x18\n" + "\x0fRestartResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1b\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1b\n" +
"\tclient_id\x18\x02 \x01(\rR\bclientId\"0\n" + "\tclient_id\x18\x02 \x01(\rR\bclientId\"@\n" +
"\x12SetLogLevelRequest\x12\x14\n" +
"\x05write\x18\x01 \x01(\bR\x05write\x12\x14\n" +
"\x05level\x18\x02 \x01(\rR\x05level\"E\n" +
"\x13SetLogLevelResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x14\n" +
"\x05level\x18\x02 \x01(\rR\x05level\"0\n" +
"\x0fOtaStartPayload\x12\x1d\n" + "\x0fOtaStartPayload\x12\x1d\n" +
"\n" + "\n" +
"total_size\x18\x01 \x01(\rR\ttotalSize\":\n" + "total_size\x18\x01 \x01(\rR\ttotalSize\":\n" +
@ -3376,7 +3697,7 @@ const file_uart_messages_proto_rawDesc = "" +
"\x0faggregate_bytes\x18\x03 \x01(\rR\x0eaggregateBytes\x12\x1f\n" + "\x0faggregate_bytes\x18\x03 \x01(\rR\x0eaggregateBytes\x12\x1f\n" +
"\vslave_count\x18\x04 \x01(\rR\n" + "\vslave_count\x18\x04 \x01(\rR\n" +
"slaveCount\x12:\n" + "slaveCount\x12:\n" +
"\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\xf1\x02\n" + "\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\x9a\x03\n" +
"\vMessageType\x12\v\n" + "\vMessageType\x12\v\n" +
"\aUNKNOWN\x10\x00\x12\a\n" + "\aUNKNOWN\x10\x00\x12\a\n" +
"\x03ACK\x10\x01\x12\b\n" + "\x03ACK\x10\x01\x12\b\n" +
@ -3400,7 +3721,9 @@ const file_uart_messages_proto_rawDesc = "" +
"\x0eBATTERY_STATUS\x10\x1a\x12\x0e\n" + "\x0eBATTERY_STATUS\x10\x1a\x12\x0e\n" +
"\n" + "\n" +
"TAP_NOTIFY\x10\x1b\x12\x10\n" + "TAP_NOTIFY\x10\x1b\x12\x10\n" +
"\fCACHE_STATUS\x10\x1d\"\x04\b\x18\x10\x18\"\x04\b\x1c\x10\x1c*G\n" + "\fCACHE_STATUS\x10\x1d\x12\x14\n" +
"\x10ESPNOW_ECHO_PING\x10\x1e\x12\x11\n" +
"\rSET_LOG_LEVEL\x10\x1f\"\x04\b\x18\x10\x18\"\x04\b\x1c\x10\x1c*G\n" +
"\aTapKind\x12\f\n" + "\aTapKind\x12\f\n" +
"\bTAP_NONE\x10\x00\x12\x0e\n" + "\bTAP_NONE\x10\x00\x12\x0e\n" +
"\n" + "\n" +
@ -3423,7 +3746,7 @@ func file_uart_messages_proto_rawDescGZIP() []byte {
} }
var file_uart_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_uart_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 40) var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 44)
var file_uart_messages_proto_goTypes = []any{ var file_uart_messages_proto_goTypes = []any{
(MessageType)(0), // 0: alox.MessageType (MessageType)(0), // 0: alox.MessageType
(TapKind)(0), // 1: alox.TapKind (TapKind)(0), // 1: alox.TapKind
@ -3454,19 +3777,23 @@ var file_uart_messages_proto_goTypes = []any{
(*CacheStatusResponse)(nil), // 26: alox.CacheStatusResponse (*CacheStatusResponse)(nil), // 26: alox.CacheStatusResponse
(*EspNowUnicastTestRequest)(nil), // 27: alox.EspNowUnicastTestRequest (*EspNowUnicastTestRequest)(nil), // 27: alox.EspNowUnicastTestRequest
(*EspNowUnicastTestResponse)(nil), // 28: alox.EspNowUnicastTestResponse (*EspNowUnicastTestResponse)(nil), // 28: alox.EspNowUnicastTestResponse
(*LedRingProgressRequest)(nil), // 29: alox.LedRingProgressRequest (*EspNowEchoPingRequest)(nil), // 29: alox.EspNowEchoPingRequest
(*LedRingProgressResponse)(nil), // 30: alox.LedRingProgressResponse (*EspNowEchoPingResponse)(nil), // 30: alox.EspNowEchoPingResponse
(*EspNowFindMeRequest)(nil), // 31: alox.EspNowFindMeRequest (*LedRingProgressRequest)(nil), // 31: alox.LedRingProgressRequest
(*EspNowFindMeResponse)(nil), // 32: alox.EspNowFindMeResponse (*LedRingProgressResponse)(nil), // 32: alox.LedRingProgressResponse
(*RestartRequest)(nil), // 33: alox.RestartRequest (*EspNowFindMeRequest)(nil), // 33: alox.EspNowFindMeRequest
(*RestartResponse)(nil), // 34: alox.RestartResponse (*EspNowFindMeResponse)(nil), // 34: alox.EspNowFindMeResponse
(*OtaStartPayload)(nil), // 35: alox.OtaStartPayload (*RestartRequest)(nil), // 35: alox.RestartRequest
(*OtaPayload)(nil), // 36: alox.OtaPayload (*RestartResponse)(nil), // 36: alox.RestartResponse
(*OtaEndPayload)(nil), // 37: alox.OtaEndPayload (*SetLogLevelRequest)(nil), // 37: alox.SetLogLevelRequest
(*OtaStatusPayload)(nil), // 38: alox.OtaStatusPayload (*SetLogLevelResponse)(nil), // 38: alox.SetLogLevelResponse
(*OtaSlaveProgressRequest)(nil), // 39: alox.OtaSlaveProgressRequest (*OtaStartPayload)(nil), // 39: alox.OtaStartPayload
(*OtaSlaveProgressEntry)(nil), // 40: alox.OtaSlaveProgressEntry (*OtaPayload)(nil), // 40: alox.OtaPayload
(*OtaSlaveProgressResponse)(nil), // 41: alox.OtaSlaveProgressResponse (*OtaEndPayload)(nil), // 41: alox.OtaEndPayload
(*OtaStatusPayload)(nil), // 42: alox.OtaStatusPayload
(*OtaSlaveProgressRequest)(nil), // 43: alox.OtaSlaveProgressRequest
(*OtaSlaveProgressEntry)(nil), // 44: alox.OtaSlaveProgressEntry
(*OtaSlaveProgressResponse)(nil), // 45: alox.OtaSlaveProgressResponse
} }
var file_uart_messages_proto_depIdxs = []int32{ var file_uart_messages_proto_depIdxs = []int32{
0, // 0: alox.UartMessage.type:type_name -> alox.MessageType 0, // 0: alox.UartMessage.type:type_name -> alox.MessageType
@ -3475,22 +3802,22 @@ var file_uart_messages_proto_depIdxs = []int32{
5, // 3: alox.UartMessage.version_response:type_name -> alox.VersionResponse 5, // 3: alox.UartMessage.version_response:type_name -> alox.VersionResponse
7, // 4: alox.UartMessage.client_info_response:type_name -> alox.ClientInfoResponse 7, // 4: alox.UartMessage.client_info_response:type_name -> alox.ClientInfoResponse
9, // 5: alox.UartMessage.client_input_response:type_name -> alox.ClientInputResponse 9, // 5: alox.UartMessage.client_input_response:type_name -> alox.ClientInputResponse
35, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload 39, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload
36, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload 40, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload
37, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload 41, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload
38, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload 42, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload
10, // 10: alox.UartMessage.accel_deadzone_request:type_name -> alox.AccelDeadzoneRequest 10, // 10: alox.UartMessage.accel_deadzone_request:type_name -> alox.AccelDeadzoneRequest
11, // 11: alox.UartMessage.accel_deadzone_response:type_name -> alox.AccelDeadzoneResponse 11, // 11: alox.UartMessage.accel_deadzone_response:type_name -> alox.AccelDeadzoneResponse
27, // 12: alox.UartMessage.espnow_unicast_test_request:type_name -> alox.EspNowUnicastTestRequest 27, // 12: alox.UartMessage.espnow_unicast_test_request:type_name -> alox.EspNowUnicastTestRequest
28, // 13: alox.UartMessage.espnow_unicast_test_response:type_name -> alox.EspNowUnicastTestResponse 28, // 13: alox.UartMessage.espnow_unicast_test_response:type_name -> alox.EspNowUnicastTestResponse
39, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest 43, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest
41, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse 45, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse
29, // 16: alox.UartMessage.led_ring_progress_request:type_name -> alox.LedRingProgressRequest 31, // 16: alox.UartMessage.led_ring_progress_request:type_name -> alox.LedRingProgressRequest
30, // 17: alox.UartMessage.led_ring_progress_response:type_name -> alox.LedRingProgressResponse 32, // 17: alox.UartMessage.led_ring_progress_response:type_name -> alox.LedRingProgressResponse
31, // 18: alox.UartMessage.espnow_find_me_request:type_name -> alox.EspNowFindMeRequest 33, // 18: alox.UartMessage.espnow_find_me_request:type_name -> alox.EspNowFindMeRequest
32, // 19: alox.UartMessage.espnow_find_me_response:type_name -> alox.EspNowFindMeResponse 34, // 19: alox.UartMessage.espnow_find_me_response:type_name -> alox.EspNowFindMeResponse
33, // 20: alox.UartMessage.restart_request:type_name -> alox.RestartRequest 35, // 20: alox.UartMessage.restart_request:type_name -> alox.RestartRequest
34, // 21: alox.UartMessage.restart_response:type_name -> alox.RestartResponse 36, // 21: alox.UartMessage.restart_response:type_name -> alox.RestartResponse
12, // 22: alox.UartMessage.accel_stream_request:type_name -> alox.AccelStreamRequest 12, // 22: alox.UartMessage.accel_stream_request:type_name -> alox.AccelStreamRequest
13, // 23: alox.UartMessage.accel_stream_response:type_name -> alox.AccelStreamResponse 13, // 23: alox.UartMessage.accel_stream_response:type_name -> alox.AccelStreamResponse
14, // 24: alox.UartMessage.battery_status_request:type_name -> alox.BatteryStatusRequest 14, // 24: alox.UartMessage.battery_status_request:type_name -> alox.BatteryStatusRequest
@ -3499,22 +3826,26 @@ var file_uart_messages_proto_depIdxs = []int32{
20, // 27: alox.UartMessage.tap_notify_response:type_name -> alox.TapNotifyResponse 20, // 27: alox.UartMessage.tap_notify_response:type_name -> alox.TapNotifyResponse
22, // 28: alox.UartMessage.cache_status_request:type_name -> alox.CacheStatusRequest 22, // 28: alox.UartMessage.cache_status_request:type_name -> alox.CacheStatusRequest
26, // 29: alox.UartMessage.cache_status_response:type_name -> alox.CacheStatusResponse 26, // 29: alox.UartMessage.cache_status_response:type_name -> alox.CacheStatusResponse
6, // 30: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo 29, // 30: alox.UartMessage.espnow_echo_ping_request:type_name -> alox.EspNowEchoPingRequest
8, // 31: alox.ClientInputResponse.clients:type_name -> alox.ClientInput 30, // 31: alox.UartMessage.espnow_echo_ping_response:type_name -> alox.EspNowEchoPingResponse
15, // 32: alox.BatterySample.lipo1:type_name -> alox.LipoReading 37, // 32: alox.UartMessage.set_log_level_request:type_name -> alox.SetLogLevelRequest
15, // 33: alox.BatterySample.lipo2:type_name -> alox.LipoReading 38, // 33: alox.UartMessage.set_log_level_response:type_name -> alox.SetLogLevelResponse
16, // 34: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample 6, // 34: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo
1, // 35: alox.TapEvent.kind:type_name -> alox.TapKind 8, // 35: alox.ClientInputResponse.clients:type_name -> alox.ClientInput
1, // 36: alox.CacheClientTap.kind:type_name -> alox.TapKind 15, // 36: alox.BatterySample.lipo1:type_name -> alox.LipoReading
23, // 37: alox.CacheClientStatus.accel:type_name -> alox.CacheClientAccel 15, // 37: alox.BatterySample.lipo2:type_name -> alox.LipoReading
24, // 38: alox.CacheClientStatus.tap:type_name -> alox.CacheClientTap 16, // 38: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample
25, // 39: alox.CacheStatusResponse.clients:type_name -> alox.CacheClientStatus 1, // 39: alox.TapEvent.kind:type_name -> alox.TapKind
40, // 40: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry 1, // 40: alox.CacheClientTap.kind:type_name -> alox.TapKind
41, // [41:41] is the sub-list for method output_type 23, // 41: alox.CacheClientStatus.accel:type_name -> alox.CacheClientAccel
41, // [41:41] is the sub-list for method input_type 24, // 42: alox.CacheClientStatus.tap:type_name -> alox.CacheClientTap
41, // [41:41] is the sub-list for extension type_name 25, // 43: alox.CacheStatusResponse.clients:type_name -> alox.CacheClientStatus
41, // [41:41] is the sub-list for extension extendee 44, // 44: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry
0, // [0:41] is the sub-list for field type_name 45, // [45:45] is the sub-list for method output_type
45, // [45:45] is the sub-list for method input_type
45, // [45:45] is the sub-list for extension type_name
45, // [45:45] is the sub-list for extension extendee
0, // [0:45] is the sub-list for field type_name
} }
func init() { file_uart_messages_proto_init() } func init() { file_uart_messages_proto_init() }
@ -3552,6 +3883,10 @@ func file_uart_messages_proto_init() {
(*UartMessage_TapNotifyResponse)(nil), (*UartMessage_TapNotifyResponse)(nil),
(*UartMessage_CacheStatusRequest)(nil), (*UartMessage_CacheStatusRequest)(nil),
(*UartMessage_CacheStatusResponse)(nil), (*UartMessage_CacheStatusResponse)(nil),
(*UartMessage_EspnowEchoPingRequest)(nil),
(*UartMessage_EspnowEchoPingResponse)(nil),
(*UartMessage_SetLogLevelRequest)(nil),
(*UartMessage_SetLogLevelResponse)(nil),
} }
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
@ -3559,7 +3894,7 @@ func file_uart_messages_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_uart_messages_proto_rawDesc), len(file_uart_messages_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_uart_messages_proto_rawDesc), len(file_uart_messages_proto_rawDesc)),
NumEnums: 2, NumEnums: 2,
NumMessages: 40, NumMessages: 44,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },

View File

@ -219,6 +219,8 @@
<dd class="col-7" x-text="state.master.running_partition || '—'"></dd> <dd class="col-7" x-text="state.master.running_partition || '—'"></dd>
<dt class="col-5 text-muted">Deadzone</dt> <dt class="col-5 text-muted">Deadzone</dt>
<dd class="col-7" x-text="state.master.deadzone != null ? state.master.deadzone + ' LSB' : '—'"></dd> <dd class="col-7" x-text="state.master.deadzone != null ? state.master.deadzone + ' LSB' : '—'"></dd>
<dt class="col-5 text-muted">Log-Level</dt>
<dd class="col-7" x-text="formatLogLevel(masterLogLevel)"></dd>
<dt class="col-5 text-muted">LiPo 1</dt> <dt class="col-5 text-muted">LiPo 1</dt>
<dd class="col-7" x-text="formatLipo(state.master?.lipo1)"></dd> <dd class="col-7" x-text="formatLipo(state.master?.lipo1)"></dd>
<dt class="col-5 text-muted">LiPo 2</dt> <dt class="col-5 text-muted">LiPo 2</dt>
@ -264,6 +266,31 @@
Restart Restart
</button> </button>
</div> </div>
<label class="form-label text-muted small mb-1 mt-3" for="master-log-level">
ESP Log-Level (global)
</label>
<div class="d-flex flex-wrap gap-2 align-items-end">
<select id="master-log-level" class="form-select form-select-sm config-input"
x-model.number="masterLogLevel"
:disabled="busy || !state.uart_connected">
<option value="0">None (aus)</option>
<option value="1">Error</option>
<option value="2">Warn</option>
<option value="3">Info</option>
<option value="4">Debug</option>
<option value="5">Verbose</option>
</select>
<button type="button" class="btn btn-outline-secondary btn-sm"
@click="readMasterLogLevel()"
:disabled="busy || !state.uart_connected">
Lesen
</button>
<button type="button" class="btn btn-primary btn-sm"
@click="setMasterLogLevel()"
:disabled="busy || !state.uart_connected">
Setzen
</button>
</div>
<p class="text-muted small mt-2 mb-0" x-show="!state.uart_connected"> <p class="text-muted small mt-2 mb-0" x-show="!state.uart_connected">
UART nicht verbunden — Eingabe gesperrt. UART nicht verbunden — Eingabe gesperrt.
</p> </p>
@ -403,6 +430,12 @@
title="ESP-NOW Unicast-Test"> title="ESP-NOW Unicast-Test">
Test Test
</button> </button>
<button type="button" class="btn btn-outline-info btn-sm"
@click="echoPing(c.id)"
:disabled="busy || !state.uart_connected || !c.available"
title="ESP-NOW Timestamp-Echo (Round-Trip-Latenz)">
Ping
</button>
<button type="button" class="btn btn-outline-info btn-sm" <button type="button" class="btn btn-outline-info btn-sm"
@click="ledRing({ clientId: c.id })" @click="ledRing({ clientId: c.id })"
:disabled="busy || !state.uart_connected || !c.available" :disabled="busy || !state.uart_connected || !c.available"
@ -621,6 +654,7 @@
ws: null, ws: null,
wsConnected: false, wsConnected: false,
masterDz: 100, masterDz: 100,
masterLogLevel: 0,
allDz: 100, allDz: 100,
allTapSingle: false, allTapSingle: false,
allTapDouble: false, allTapDouble: false,
@ -640,6 +674,7 @@
busy: false, busy: false,
configMsg: '', configMsg: '',
configMsgOk: false, configMsgOk: false,
_flashTimer: null,
led: { led: {
mode: 'color', mode: 'color',
r: 0, r: 0,
@ -759,6 +794,11 @@
if (data.samples?.length) this.applyBatterySamples(data.samples); if (data.samples?.length) this.applyBatterySamples(data.samples);
} catch (_) {} } catch (_) {}
}, },
formatLogLevel(level) {
const labels = ['None', 'Error', 'Warn', 'Info', 'Debug', 'Verbose'];
if (level == null || level < 0 || level > 5) return '—';
return `${labels[level]} (${level})`;
},
formatMac(hex) { formatMac(hex) {
if (!hex || hex.length !== 12) return hex || ''; if (!hex || hex.length !== 12) return hex || '';
return hex.match(/.{2}/g).join(':'); return hex.match(/.{2}/g).join(':');
@ -1031,10 +1071,14 @@
this.busy = false; this.busy = false;
} }
}, },
flash(msg, ok) { flash(msg, ok, durationMs = 5000) {
this.configMsg = msg; this.configMsg = msg;
this.configMsgOk = ok; this.configMsgOk = ok;
setTimeout(() => { this.configMsg = ''; }, 5000); if (this._flashTimer) clearTimeout(this._flashTimer);
this._flashTimer = setTimeout(() => {
this.configMsg = '';
this._flashTimer = null;
}, durationMs);
}, },
async setDeadzone(clientId, deadzone, opts = {}) { async setDeadzone(clientId, deadzone, opts = {}) {
if (deadzone == null || deadzone < 0) { if (deadzone == null || deadzone < 0) {
@ -1093,6 +1137,44 @@
async setMasterDeadzone() { async setMasterDeadzone() {
await this.setDeadzone(0, this.masterDz); await this.setDeadzone(0, this.masterDz);
}, },
async readMasterLogLevel() {
this.busy = true;
try {
const r = await fetch('/api/log-level');
const data = await r.json();
if (!r.ok || !data.success) {
this.flash(data.error || 'Log-Level lesen fehlgeschlagen', false);
return;
}
this.masterLogLevel = data.level;
this.flash(`Master: Log-Level ${this.formatLogLevel(data.level)}`, true);
} catch (e) {
this.flash(String(e), false);
} finally {
this.busy = false;
}
},
async setMasterLogLevel() {
this.busy = true;
try {
const r = await fetch('/api/log-level', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ write: true, level: this.masterLogLevel })
});
const data = await r.json();
if (!r.ok || !data.success) {
this.flash(data.error || 'Log-Level setzen fehlgeschlagen', false);
return;
}
this.masterLogLevel = data.level;
this.flash(`Master: Log-Level ${this.formatLogLevel(data.level)} gesetzt`, true);
} catch (e) {
this.flash(String(e), false);
} finally {
this.busy = false;
}
},
patchLiveStream(enabled) { patchLiveStream(enabled) {
let clients = this.state.clients || []; let clients = this.state.clients || [];
if (!enabled) { if (!enabled) {
@ -1258,6 +1340,43 @@
this.busy = false; this.busy = false;
} }
}, },
formatMs(v) {
return v != null && Number.isFinite(Number(v)) ? Number(v).toFixed(3) : '—';
},
formatUsAsMs(us) {
return us != null && Number.isFinite(Number(us))
? (Number(us) / 1000).toFixed(3)
: '—';
},
async echoPing(clientId) {
this.busy = true;
try {
const r = await fetch('/api/echo-ping', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ client_id: clientId })
});
const data = await r.json();
if (!r.ok) {
this.flash(data.error || `Echo-Ping Slave ${clientId} fehlgeschlagen`, false);
return;
}
if (!data.success) {
this.flash(`Echo-Ping Slave ${clientId} fehlgeschlagen (${this.formatMs(data.rtt_ms)} ms)`, false);
return;
}
this.flash(
`Echo-Ping Slave ${clientId}: Host ${this.formatMs(data.rtt_ms)} ms, ` +
`ESP ${this.formatUsAsMs(data.esp_rtt_us)} ms`,
true,
5000
);
} catch (e) {
this.flash(String(e), false);
} finally {
this.busy = false;
}
},
async restart(clientId = 0) { async restart(clientId = 0) {
this.busy = true; this.busy = true;
try { try {

View File

@ -22,11 +22,13 @@ idf_component_register(
"cmd/cmd_tap_notify.c" "cmd/cmd_tap_notify.c"
"cmd/cmd_cache_status.c" "cmd/cmd_cache_status.c"
"cmd/cmd_espnow_unicast_test.c" "cmd/cmd_espnow_unicast_test.c"
"cmd/cmd_espnow_echo_ping.c"
"cmd/cmd_espnow_find_me.c" "cmd/cmd_espnow_find_me.c"
"cmd/cmd_restart.c" "cmd/cmd_restart.c"
"pod_reboot.c" "pod_reboot.c"
"cmd/cmd_led_ring.c" "cmd/cmd_led_ring.c"
"cmd/cmd_battery.c" "cmd/cmd_battery.c"
"cmd/cmd_set_log_level.c"
"cmd/cmd_ota.c" "cmd/cmd_ota.c"
"cmd/cmd_ota_slave_progress.c" "cmd/cmd_ota_slave_progress.c"
"ota_uart.c" "ota_uart.c"
@ -61,6 +63,7 @@ idf_component_register(
esp_driver_i2c esp_driver_i2c
esp_adc esp_adc
app_update app_update
esp_timer
bma456) bma456)
target_compile_definitions(${COMPONENT_LIB} target_compile_definitions(${COMPONENT_LIB}

View File

@ -228,6 +228,8 @@ Host and master speak nanopb-encoded `UartMessage` inside UART frames (byte 0 =
| 25 | `ACCEL_STREAM` | Implemented — enable/disable slave ESP-NOW accel stream to master | | 25 | `ACCEL_STREAM` | Implemented — enable/disable slave ESP-NOW accel stream to master |
| 27 | `TAP_NOTIFY` | Implemented (`cmd/cmd_tap_notify.c`) — get/set which tap kinds notify via ESP-NOW | | 27 | `TAP_NOTIFY` | Implemented (`cmd/cmd_tap_notify.c`) — get/set which tap kinds notify via ESP-NOW |
| 29 | `CACHE_STATUS` | Implemented (`cmd/cmd_cache_status.c`) — subscribed accel + tap cache (one UART round-trip) | | 29 | `CACHE_STATUS` | Implemented (`cmd/cmd_cache_status.c`) — subscribed accel + tap cache (one UART round-trip) |
| 30 | `ESPNOW_ECHO_PING` | Implemented (`cmd/cmd_espnow_echo_ping.c`) — ESP-NOW timestamp echo (latency test) |
| 31 | `SET_LOG_LEVEL` | Implemented (`cmd/cmd_set_log_level.c`) — get/set global `esp_log_level_set("*", …)` on master |
Regenerate C code: Regenerate C code:
@ -390,6 +392,30 @@ go run . -port /dev/ttyUSB0 find-me
go run . -port /dev/ttyUSB0 find-me -client 16 go run . -port /dev/ttyUSB0 find-me -client 16
``` ```
### SET_LOG_LEVEL command
Read or set the **global** ESP-IDF log filter on the master (`esp_log_level_set("*", level)`). Does not affect the host UART protocol (UART1); `esp_log_*` output goes to the **debug console UART0** (USB, 115200).
**Request:** framed `31` (`0x1f`) + `set_log_level_request` (`write`, `level` 05).
**Response:** `set_log_level_response` (`success`, `level`).
| `level` | `esp_log_level_t` |
|---------|-------------------|
| 0 | NONE |
| 1 | ERROR |
| 2 | WARN |
| 3 | INFO |
| 4 | DEBUG |
| 5 | VERBOSE |
Boot default follows `CONFIG_LOG_DEFAULT_LEVEL` in `sdkconfig` (not persisted across reboot).
```bash
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 0
```
### RESTART command ### RESTART command
Reboot the master (`client_id=0`) or one slave via ESP-NOW (`client_id` = registry id). The device sends the UART response, then restarts after ~150 ms. Reboot the master (`client_id=0`) or one slave via ESP-NOW (`client_id` = registry id). The device sends the UART response, then restarts after ~150 ms.
@ -541,6 +567,7 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
| `pod_settings.c/h` | NVS persistence (accel deadzone, …) | | `pod_settings.c/h` | NVS persistence (accel deadzone, …) |
| `led_ring.c/h` | LED ring (digit display, progress bar) | | `led_ring.c/h` | LED ring (digit display, progress bar) |
| `cmd/cmd_led_ring.c` | UART `LED_RING` progress command | | `cmd/cmd_led_ring.c` | UART `LED_RING` progress command |
| `cmd/cmd_set_log_level.c` | UART `SET_LOG_LEVEL` — runtime ESP-IDF log level |
| `proto/uart_messages.proto` | UART protocol schema | | `proto/uart_messages.proto` | UART protocol schema |
| `proto/esp_now_messages.proto` | ESP-NOW protocol schema | | `proto/esp_now_messages.proto` | ESP-NOW protocol schema |
| `esp_now_proto.c/h` | Encode/decode `EspNowMessage` | | `esp_now_proto.c/h` | Encode/decode `EspNowMessage` |

View File

@ -0,0 +1,70 @@
#include "client_registry.h"
#include "cmd_espnow_echo_ping.h"
#include "esp_log.h"
#include "esp_now_comm.h"
#include "uart_cmd.h"
static const char *TAG = "[ECHO_PING]";
static void reply(bool success, uint32_t client_id, uint64_t timestamp_us,
uint32_t esp_rtt_us) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_ESPNOW_ECHO_PING,
alox_UartMessage_espnow_echo_ping_response_tag);
response.payload.espnow_echo_ping_response.success = success;
response.payload.espnow_echo_ping_response.client_id = client_id;
response.payload.espnow_echo_ping_response.timestamp_us = timestamp_us;
response.payload.espnow_echo_ping_response.esp_rtt_us = esp_rtt_us;
uart_cmd_send(&response, TAG);
}
static void handle_espnow_echo_ping(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "decode failed");
reply(false, 0, 0, 0);
return;
}
const alox_EspNowEchoPingRequest *req = UART_CMD_REQ(
&uart_msg, alox_UartMessage_espnow_echo_ping_request_tag,
espnow_echo_ping_request);
if (req == NULL || req->client_id == 0) {
ESP_LOGW(TAG, "need client_id in request");
reply(false, 0, 0, 0);
return;
}
const client_info_t *client = client_registry_find_by_id(req->client_id);
if (client == NULL) {
ESP_LOGW(TAG, "client id %lu not in registry",
(unsigned long)req->client_id);
reply(false, req->client_id, 0, 0);
return;
}
ESP_LOGI(TAG, "UART request client_id=%lu host_ts=%llu",
(unsigned long)req->client_id,
(unsigned long long)req->timestamp_us);
esp_now_echo_ping_result_t ping_result = {0};
esp_err_t err =
esp_now_comm_echo_ping(client->mac, req->timestamp_us, &ping_result);
if (err != ESP_OK) {
ESP_LOGW(TAG, "echo ping to id=%lu failed: %s", (unsigned long)req->client_id,
esp_err_to_name(err));
reply(false, req->client_id, 0, 0);
return;
}
ESP_LOGI(TAG, "UART reply client_id=%lu host_ts=%llu esp_rtt_us=%lu",
(unsigned long)req->client_id,
(unsigned long long)ping_result.echoed_us,
(unsigned long)ping_result.esp_rtt_us);
reply(true, req->client_id, ping_result.echoed_us, ping_result.esp_rtt_us);
}
void cmd_espnow_echo_ping_register(void) {
uart_cmd_register(alox_MessageType_ESPNOW_ECHO_PING, handle_espnow_echo_ping);
}

View File

@ -0,0 +1,6 @@
#ifndef CMD_ESPNOW_ECHO_PING_H
#define CMD_ESPNOW_ECHO_PING_H
void cmd_espnow_echo_ping_register(void);
#endif

View File

@ -57,6 +57,10 @@ static const char *message_type_name(uint16_t id) {
return "TAP_NOTIFY"; return "TAP_NOTIFY";
case alox_MessageType_CACHE_STATUS: case alox_MessageType_CACHE_STATUS:
return "CACHE_STATUS"; return "CACHE_STATUS";
case alox_MessageType_ESPNOW_ECHO_PING:
return "ESPNOW_ECHO_PING";
case alox_MessageType_SET_LOG_LEVEL:
return "SET_LOG_LEVEL";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }

View File

@ -175,8 +175,18 @@ static void handle_ota_payload(const uint8_t *data, size_t len) {
return; return;
} }
ota_feed_result_t r = ota_feed_result_t r = ota_uart_feed_chunk(req_ptr->seq, req_ptr->data.bytes,
ota_uart_feed(req_ptr->data.bytes, req_ptr->data.size); req_ptr->data.size);
if (r == OTA_FEED_SEQ_GAP) {
send_ota_failed(16);
return;
}
if (r == OTA_FEED_SEQ_DUP) {
if (ota_uart_block_ready_for_reack()) {
send_ota_status(OTA_UART_ST_BLOCK_ACK, 0);
}
return;
}
if (r == OTA_FEED_ERROR) { if (r == OTA_FEED_ERROR) {
send_ota_failed( 13); send_ota_failed( 13);
return; return;
@ -189,15 +199,6 @@ static void handle_ota_payload(const uint8_t *data, size_t len) {
led_ring_show_ota_progress(done, total, OTA_LED_UART_R, OTA_LED_UART_G, led_ring_show_ota_progress(done, total, OTA_LED_UART_R, OTA_LED_UART_G,
OTA_LED_UART_B); OTA_LED_UART_B);
send_ota_status(OTA_UART_ST_BLOCK_ACK, 0); send_ota_status(OTA_UART_ST_BLOCK_ACK, 0);
return;
}
if (r == OTA_FEED_OK) {
uint32_t total = ota_uart_total_size();
if (total > 0) {
led_ring_show_ota_progress(ota_uart_bytes_received(), total,
OTA_LED_UART_R, OTA_LED_UART_G, OTA_LED_UART_B);
}
} }
} }

View File

@ -0,0 +1,51 @@
#include "cmd_set_log_level.h"
#include "esp_log.h"
#include "uart_cmd.h"
static const char *TAG = "[LOG_LVL]";
static bool valid_level(uint32_t level) { return level <= ESP_LOG_VERBOSE; }
static void reply(uint32_t level, bool success) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_SET_LOG_LEVEL,
alox_UartMessage_set_log_level_response_tag);
response.payload.set_log_level_response.success = success;
response.payload.set_log_level_response.level = level;
uart_cmd_send(&response, TAG);
}
static void handle_set_log_level(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
alox_SetLogLevelRequest req = alox_SetLogLevelRequest_init_zero;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "decode failed");
reply((uint32_t)esp_log_level_get("*"), false);
return;
}
const alox_SetLogLevelRequest *req_ptr = UART_CMD_REQ(
&uart_msg, alox_UartMessage_set_log_level_request_tag, set_log_level_request);
if (req_ptr != NULL) {
req = *req_ptr;
}
if (req.write) {
if (!valid_level(req.level)) {
ESP_LOGW(TAG, "invalid level %lu", (unsigned long)req.level);
reply((uint32_t)esp_log_level_get("*"), false);
return;
}
esp_log_level_set("*", (esp_log_level_t)req.level);
ESP_LOGI(TAG, "global log level set to %lu", (unsigned long)req.level);
reply(req.level, true);
return;
}
reply((uint32_t)esp_log_level_get("*"), true);
}
void cmd_set_log_level_register(void) {
uart_cmd_register(alox_MessageType_SET_LOG_LEVEL, handle_set_log_level);
}

View File

@ -0,0 +1,6 @@
#ifndef CMD_SET_LOG_LEVEL_H
#define CMD_SET_LOG_LEVEL_H
void cmd_set_log_level_register(void);
#endif

View File

@ -26,6 +26,17 @@ esp_err_t esp_now_comm_send_accel_deadzone(const uint8_t mac[CLIENT_MAC_LEN],
esp_err_t esp_now_comm_send_unicast_test(const uint8_t mac[CLIENT_MAC_LEN], esp_err_t esp_now_comm_send_unicast_test(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t seq); uint32_t seq);
/** Result of a master-side ESP-NOW echo ping round-trip. */
typedef struct {
uint64_t echoed_us;
uint32_t esp_rtt_us;
} esp_now_echo_ping_result_t;
/** Master: ESP-NOW echo ping round-trip; fills result on success. */
esp_err_t esp_now_comm_echo_ping(const uint8_t mac[CLIENT_MAC_LEN],
uint64_t timestamp_us,
esp_now_echo_ping_result_t *result);
/** Master: trigger find-me LED sequence on one slave. */ /** Master: trigger find-me LED sequence on one slave. */
esp_err_t esp_now_comm_send_find_me(const uint8_t mac[CLIENT_MAC_LEN], esp_err_t esp_now_comm_send_find_me(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t client_id); uint32_t client_id);

View File

@ -21,7 +21,18 @@ static app_config_t s_config;
static uint8_t s_wifi_channel; static uint8_t s_wifi_channel;
static uint8_t s_own_mac[ESP_NOW_ETH_ALEN]; static uint8_t s_own_mac[ESP_NOW_ETH_ALEN];
static SemaphoreHandle_t s_send_done; static SemaphoreHandle_t s_send_done;
static SemaphoreHandle_t s_send_lock;
static bool s_send_cb_ready; static bool s_send_cb_ready;
static volatile esp_now_send_status_t s_last_send_status;
static volatile bool s_last_send_ok;
#define ESPNOW_SEND_DONE_TIMEOUT_MS 500u
#define ESPNOW_SEND_MAX_ATTEMPTS 8u
#define ESPNOW_SEND_RETRY_DELAY_MS 10u
#define ESPNOW_NOMEM_RETRY_DELAY_MS 50u
#define ESPNOW_RELIABLE_MAX_ATTEMPTS 24u
#define ESPNOW_RELIABLE_NOMEM_DELAY_MS 50u
static uint8_t network_to_channel(uint8_t network) { static uint8_t network_to_channel(uint8_t network) {
if (network < 1 || network > 13) { if (network < 1 || network > 13) {
@ -33,7 +44,8 @@ static uint8_t network_to_channel(uint8_t network) {
static void espnow_send_done_cb(const esp_now_send_info_t *tx_info, static void espnow_send_done_cb(const esp_now_send_info_t *tx_info,
esp_now_send_status_t status) { esp_now_send_status_t status) {
(void)tx_info; (void)tx_info;
(void)status; s_last_send_status = status;
s_last_send_ok = (status == ESP_NOW_SEND_SUCCESS);
if (s_send_done != NULL) { if (s_send_done != NULL) {
xSemaphoreGive(s_send_done); xSemaphoreGive(s_send_done);
} }
@ -93,43 +105,140 @@ esp_err_t esp_now_core_ensure_broadcast_peer(void) {
return esp_now_core_ensure_peer(ESPNOW_BCAST); return esp_now_core_ensure_peer(ESPNOW_BCAST);
} }
static esp_err_t send_with_backpressure(const uint8_t *dest_mac,
const alox_EspNowMessage *msg,
uint32_t max_attempts,
uint32_t nomem_delay_ms,
uint32_t retry_delay_ms,
bool quiet_retries) {
if (s_send_lock == NULL ||
xSemaphoreTake(s_send_lock, pdMS_TO_TICKS(5000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
uint8_t buf[ESPNOW_PB_MAX_SIZE];
size_t len = 0;
esp_err_t result = ESP_FAIL;
esp_err_t err = esp_now_proto_encode(msg, buf, sizeof(buf), &len);
if (err != ESP_OK) {
ESP_LOGW(TAG, "encode failed");
result = err;
goto out;
}
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);
result = ESP_ERR_INVALID_SIZE;
goto out;
}
if (esp_now_core_ensure_peer(dest_mac) != ESP_OK) {
result = ESP_FAIL;
goto out;
}
for (uint32_t attempt = 0; attempt < max_attempts; attempt++) {
if (s_send_cb_ready && s_send_done != NULL) {
xSemaphoreTake(s_send_done, 0);
}
s_last_send_ok = false;
err = esp_now_send(dest_mac, buf, len);
if (err != ESP_OK) {
const bool last = (attempt + 1 >= max_attempts);
if (!quiet_retries || last) {
ESP_LOGW(TAG, "send type=%u failed (attempt %lu/%lu): %s",
(unsigned)msg->type, (unsigned long)(attempt + 1),
(unsigned long)max_attempts, esp_err_to_name(err));
}
vTaskDelay(pdMS_TO_TICKS(err == ESP_ERR_ESPNOW_NO_MEM ? nomem_delay_ms
: retry_delay_ms));
continue;
}
if (s_send_cb_ready && s_send_done != NULL) {
if (xSemaphoreTake(s_send_done, pdMS_TO_TICKS(ESPNOW_SEND_DONE_TIMEOUT_MS)) !=
pdTRUE) {
const bool last = (attempt + 1 >= max_attempts);
if (!quiet_retries || last) {
ESP_LOGW(TAG, "send type=%u done timeout (attempt %lu/%lu)",
(unsigned)msg->type, (unsigned long)(attempt + 1),
(unsigned long)max_attempts);
}
vTaskDelay(pdMS_TO_TICKS(retry_delay_ms));
continue;
}
if (!s_last_send_ok) {
const bool last = (attempt + 1 >= max_attempts);
if (!quiet_retries || last) {
ESP_LOGW(TAG, "send type=%u peer status=%d (attempt %lu/%lu)",
(unsigned)msg->type, (int)s_last_send_status,
(unsigned long)(attempt + 1), (unsigned long)max_attempts);
}
vTaskDelay(pdMS_TO_TICKS(retry_delay_ms));
continue;
}
}
result = ESP_OK;
goto out;
}
out:
xSemaphoreGive(s_send_lock);
return result;
}
esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac, esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac,
const alox_EspNowMessage *msg) { const alox_EspNowMessage *msg) {
return send_with_backpressure(dest_mac, msg, ESPNOW_SEND_MAX_ATTEMPTS,
ESPNOW_NOMEM_RETRY_DELAY_MS,
ESPNOW_SEND_RETRY_DELAY_MS, false);
}
esp_err_t esp_now_core_send_reliable(const uint8_t *dest_mac,
const alox_EspNowMessage *msg) {
return send_with_backpressure(dest_mac, msg, ESPNOW_RELIABLE_MAX_ATTEMPTS,
ESPNOW_RELIABLE_NOMEM_DELAY_MS,
ESPNOW_SEND_RETRY_DELAY_MS, true);
}
esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac,
const alox_EspNowMessage *msg) {
if (s_send_lock == NULL ||
xSemaphoreTake(s_send_lock, pdMS_TO_TICKS(5000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
uint8_t buf[ESPNOW_PB_MAX_SIZE]; uint8_t buf[ESPNOW_PB_MAX_SIZE];
size_t len = 0; size_t len = 0;
esp_err_t err = esp_now_proto_encode(msg, buf, sizeof(buf), &len); esp_err_t err = esp_now_proto_encode(msg, buf, sizeof(buf), &len);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "encode failed"); ESP_LOGW(TAG, "encode failed");
xSemaphoreGive(s_send_lock);
return err; return err;
} }
if (len > ESP_NOW_MAX_DATA_LEN) { if (len > ESP_NOW_MAX_DATA_LEN) {
ESP_LOGW(TAG, "encoded len %u > ESP-NOW max %u", (unsigned)len, ESP_LOGW(TAG, "encoded len %u > ESP-NOW max %u", (unsigned)len,
(unsigned)ESP_NOW_MAX_DATA_LEN); (unsigned)ESP_NOW_MAX_DATA_LEN);
xSemaphoreGive(s_send_lock);
return ESP_ERR_INVALID_SIZE; return ESP_ERR_INVALID_SIZE;
} }
if (esp_now_core_ensure_peer(dest_mac) != ESP_OK) { if (esp_now_core_ensure_peer(dest_mac) != ESP_OK) {
xSemaphoreGive(s_send_lock);
return ESP_FAIL; return ESP_FAIL;
} }
if (s_send_cb_ready && s_send_done != NULL) {
xSemaphoreTake(s_send_done, 0);
}
err = esp_now_send(dest_mac, buf, len); err = esp_now_send(dest_mac, buf, len);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "send type=%u failed: %s", (unsigned)msg->type, ESP_LOGW(TAG, "send type=%u failed: %s", (unsigned)msg->type,
esp_err_to_name(err)); esp_err_to_name(err));
return err;
}
if (s_send_cb_ready && s_send_done != NULL) {
if (xSemaphoreTake(s_send_done, pdMS_TO_TICKS(50)) != pdTRUE) {
ESP_LOGW(TAG, "send type=%u done timeout", (unsigned)msg->type);
}
} }
xSemaphoreGive(s_send_lock);
return err; return err;
} }
@ -163,7 +272,8 @@ esp_err_t esp_now_core_init_radio(uint8_t channel) {
void esp_now_core_init_send_done(void) { void esp_now_core_init_send_done(void) {
s_send_done = xSemaphoreCreateBinary(); s_send_done = xSemaphoreCreateBinary();
if (s_send_done != NULL && s_send_lock = xSemaphoreCreateMutex();
if (s_send_done != NULL && s_send_lock != NULL &&
esp_now_register_send_cb(espnow_send_done_cb) == ESP_OK) { esp_now_register_send_cb(espnow_send_done_cb) == ESP_OK) {
s_send_cb_ready = true; s_send_cb_ready = true;
} else { } else {

View File

@ -23,8 +23,14 @@ esp_err_t esp_now_core_ensure_broadcast_peer(void);
esp_err_t esp_now_core_send(const uint8_t *dest_mac, esp_err_t esp_now_core_send(const uint8_t *dest_mac,
const alox_EspNowMessage *msg); const alox_EspNowMessage *msg);
/** 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);
esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac, esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac,
const alox_EspNowMessage *msg); const alox_EspNowMessage *msg);
/** OTA payloads: wait for send-done with extra NO_MEM backoff (queue drain). */
esp_err_t esp_now_core_send_reliable(const uint8_t *dest_mac,
const alox_EspNowMessage *msg);
esp_err_t esp_now_core_init_radio(uint8_t channel); esp_err_t esp_now_core_init_radio(uint8_t channel);
void esp_now_core_init_send_done(void); void esp_now_core_init_send_done(void);

View File

@ -5,8 +5,10 @@
#include "esp_now_proto.h" #include "esp_now_proto.h"
#include "board_input.h" #include "board_input.h"
#include "ota_espnow.h" #include "ota_espnow.h"
#include "ota_session.h"
#include "ota_uart.h" #include "ota_uart.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/idf_additions.h" #include "freertos/idf_additions.h"
#include <string.h> #include <string.h>
@ -51,6 +53,66 @@ static esp_err_t send_unicast_test(const uint8_t *dest_mac, uint32_t seq) {
return esp_now_core_send(dest_mac, &msg); return esp_now_core_send(dest_mac, &msg);
} }
#define ESPNOW_ECHO_PING_TIMEOUT_MS 500
#define ECHO_PING_TAG "[ECHO_PING]"
static SemaphoreHandle_t s_echo_pong_sem;
static bool s_echo_waiting;
static uint64_t s_echo_expect_ts;
static uint32_t s_echo_esp_rtt_us;
static uint8_t s_echo_expect_mac[ESP_NOW_ETH_ALEN];
static esp_err_t echo_ping_init(void) {
if (s_echo_pong_sem != NULL) {
return ESP_OK;
}
s_echo_pong_sem = xSemaphoreCreateBinary();
if (s_echo_pong_sem == NULL) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
static esp_err_t send_echo_ping(const uint8_t *dest_mac, uint64_t host_timestamp_us,
uint64_t master_time_us) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
msg.type = alox_EspNowMessageType_ESPNOW_ECHO_PING;
msg.which_payload = alox_EspNowMessage_echo_ping_tag;
msg.payload.echo_ping.host_timestamp_us = host_timestamp_us;
msg.payload.echo_ping.master_time_us = master_time_us;
char mac_str[18];
esp_now_core_mac_to_str(dest_mac, mac_str, sizeof(mac_str));
ESP_LOGI(ECHO_PING_TAG, "ESP-NOW PING send to %s host_ts=%llu master_time_us=%llu",
mac_str, (unsigned long long)host_timestamp_us,
(unsigned long long)master_time_us);
return esp_now_core_send_fast(dest_mac, &msg);
}
static void echo_ping_on_pong(const uint8_t mac[ESP_NOW_ETH_ALEN],
const alox_EspNowEchoPong *pong) {
if (pong == NULL || !s_echo_waiting) {
return;
}
if (!esp_now_core_mac_equal(mac, s_echo_expect_mac)) {
return;
}
if (pong->host_timestamp_us != s_echo_expect_ts) {
return;
}
int64_t now_us = esp_timer_get_time();
s_echo_esp_rtt_us =
(uint32_t)(now_us - (int64_t)pong->master_time_us);
char mac_str[18];
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
ESP_LOGI(ECHO_PING_TAG,
"ESP-NOW PONG recv from %s host_ts=%llu master_time_us=%llu "
"recv_time_us=%lld esp_rtt_us=%lu",
mac_str, (unsigned long long)pong->host_timestamp_us,
(unsigned long long)pong->master_time_us, (long long)now_us,
(unsigned long)s_echo_esp_rtt_us);
xSemaphoreGive(s_echo_pong_sem);
}
static esp_err_t send_find_me(const uint8_t *dest_mac, uint32_t client_id) { static esp_err_t send_find_me(const uint8_t *dest_mac, uint32_t client_id) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero; alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
msg.type = alox_EspNowMessageType_ESPNOW_FIND_ME; msg.type = alox_EspNowMessageType_ESPNOW_FIND_ME;
@ -119,7 +181,7 @@ static esp_err_t send_ota_payload(const uint8_t *dest_mac, uint32_t seq,
msg.payload.ota_payload.seq = seq; msg.payload.ota_payload.seq = seq;
msg.payload.ota_payload.data.size = len; msg.payload.ota_payload.data.size = len;
memcpy(msg.payload.ota_payload.data.bytes, data, len); memcpy(msg.payload.ota_payload.data.bytes, data, len);
return esp_now_core_send_wait(dest_mac, &msg); return esp_now_core_send_reliable(dest_mac, &msg);
} }
static esp_err_t send_ota_end(const uint8_t *dest_mac) { static esp_err_t send_ota_end(const uint8_t *dest_mac) {
@ -224,6 +286,48 @@ esp_err_t esp_now_comm_send_unicast_test(const uint8_t mac[CLIENT_MAC_LEN],
return err; return err;
} }
esp_err_t esp_now_comm_echo_ping(const uint8_t mac[CLIENT_MAC_LEN],
uint64_t timestamp_us,
esp_now_echo_ping_result_t *result) {
if (mac == NULL || !esp_now_core_is_master()) {
return ESP_ERR_INVALID_STATE;
}
if (echo_ping_init() != ESP_OK) {
return ESP_ERR_NO_MEM;
}
xSemaphoreTake(s_echo_pong_sem, 0);
s_echo_waiting = true;
s_echo_expect_ts = timestamp_us;
s_echo_esp_rtt_us = 0;
memcpy(s_echo_expect_mac, mac, ESP_NOW_ETH_ALEN);
uint64_t master_time_us = (uint64_t)esp_timer_get_time();
esp_err_t err = send_echo_ping(mac, timestamp_us, master_time_us);
if (err != ESP_OK) {
s_echo_waiting = false;
return err;
}
if (xSemaphoreTake(s_echo_pong_sem,
pdMS_TO_TICKS(ESPNOW_ECHO_PING_TIMEOUT_MS)) != pdTRUE) {
s_echo_waiting = false;
char mac_str[18];
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
ESP_LOGW(ECHO_PING_TAG,
"ESP-NOW PONG timeout to %s host_ts=%llu",
mac_str, (unsigned long long)timestamp_us);
return ESP_ERR_TIMEOUT;
}
s_echo_waiting = false;
if (result != NULL) {
result->echoed_us = timestamp_us;
result->esp_rtt_us = s_echo_esp_rtt_us;
}
return ESP_OK;
}
esp_err_t esp_now_comm_send_accel_stream(const uint8_t mac[CLIENT_MAC_LEN], esp_err_t esp_now_comm_send_accel_stream(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t client_id, bool enable) { uint32_t client_id, bool enable) {
if (mac == NULL || !esp_now_core_is_master()) { if (mac == NULL || !esp_now_core_is_master()) {
@ -415,6 +519,12 @@ void esp_now_master_on_recv(const esp_now_recv_info_t *info, const uint8_t *data
return; return;
} }
if (msg.which_payload == alox_EspNowMessage_echo_pong_tag) {
esp_now_core_ensure_peer(info->src_addr);
echo_ping_on_pong(info->src_addr, &msg.payload.echo_pong);
return;
}
if (msg.type == alox_EspNowMessageType_ESPNOW_BATTERY_REPORT && if (msg.type == alox_EspNowMessageType_ESPNOW_BATTERY_REPORT &&
msg.which_payload != alox_EspNowMessage_battery_report_tag) { msg.which_payload != alox_EspNowMessage_battery_report_tag) {
ESP_LOGW(TAG, "BATTERY_REPORT type but which=%u", msg.which_payload); ESP_LOGW(TAG, "BATTERY_REPORT type but which=%u", msg.which_payload);
@ -439,7 +549,10 @@ static void discover_task(void *param) {
(unsigned)esp_now_core_wifi_channel()); (unsigned)esp_now_core_wifi_channel());
while (1) { while (1) {
esp_now_core_send(ESPNOW_BCAST, &msg); msg.payload.discover.master_ota_pending = ota_session_busy();
if (!ota_espnow_distribution_active()) {
esp_now_core_send_fast(ESPNOW_BCAST, &msg);
}
vTaskDelay(pdMS_TO_TICKS(ESPNOW_DISCOVER_INTERVAL_MS)); vTaskDelay(pdMS_TO_TICKS(ESPNOW_DISCOVER_INTERVAL_MS));
} }
} }

View File

@ -21,6 +21,8 @@
#define ESPNOW_HEARTBEAT_INTERVAL_MS 1000 #define ESPNOW_HEARTBEAT_INTERVAL_MS 1000
#define SLAVE_MASTER_LOST_MS (ESPNOW_HEARTBEAT_INTERVAL_MS * 5) #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 #define ESPNOW_ACCEL_INTERVAL_MS 16
#define ESPNOW_BATTERY_INTERVAL_MS 30000 #define ESPNOW_BATTERY_INTERVAL_MS 30000
#define SLAVE_BATTERY_AFTER_JOIN_MS 150 #define SLAVE_BATTERY_AFTER_JOIN_MS 150
@ -28,6 +30,7 @@
static const char *TAG = "[ESPNOW_S]"; static const char *TAG = "[ESPNOW_S]";
static bool s_joined; static bool s_joined;
static bool s_master_ota_grace;
static bool s_accel_stream_enabled; static bool s_accel_stream_enabled;
static bool s_tap_notify_single; static bool s_tap_notify_single;
static bool s_tap_notify_double; static bool s_tap_notify_double;
@ -106,8 +109,18 @@ static esp_err_t send_battery_report(const uint8_t *dest_mac,
return esp_now_core_send(dest_mac, &msg); return esp_now_core_send(dest_mac, &msg);
} }
static uint32_t slave_master_lost_ms(void) {
if (ota_uart_is_active() || s_master_ota_grace) {
return SLAVE_MASTER_OTA_GRACE_MS;
}
return SLAVE_MASTER_LOST_MS;
}
static void touch_master_presence(uint32_t now) { s_last_discover_ms = now; }
static void reset_join(void) { static void reset_join(void) {
s_joined = false; s_joined = false;
s_master_ota_grace = false;
s_accel_stream_enabled = false; s_accel_stream_enabled = false;
memset(s_master_mac, 0, sizeof(s_master_mac)); memset(s_master_mac, 0, sizeof(s_master_mac));
s_last_discover_ms = 0; s_last_discover_ms = 0;
@ -216,6 +229,36 @@ static void handle_unicast_test(const uint8_t *master_mac,
(unsigned long)test->seq, (int)s_joined); (unsigned long)test->seq, (int)s_joined);
} }
static void handle_echo_ping(const uint8_t *master_mac,
const alox_EspNowEchoPing *ping) {
if (ping == NULL || !s_joined ||
!esp_now_core_mac_equal(master_mac, s_master_mac)) {
return;
}
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "ESP-NOW PING recv from %s host_ts=%llu master_time_us=%llu",
mac_str, (unsigned long long)ping->host_timestamp_us,
(unsigned long long)ping->master_time_us);
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
msg.type = alox_EspNowMessageType_ESPNOW_ECHO_PONG;
msg.which_payload = alox_EspNowMessage_echo_pong_tag;
msg.payload.echo_pong.host_timestamp_us = ping->host_timestamp_us;
msg.payload.echo_pong.master_time_us = ping->master_time_us;
esp_err_t err = esp_now_core_send_fast(s_master_mac, &msg);
if (err != ESP_OK) {
ESP_LOGW(TAG, "ECHO PONG send failed: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "ESP-NOW PONG send to %s host_ts=%llu master_time_us=%llu",
mac_str, (unsigned long long)ping->host_timestamp_us,
(unsigned long long)ping->master_time_us);
}
static void handle_restart(const uint8_t *master_mac, static void handle_restart(const uint8_t *master_mac,
const alox_EspNowRestart *req) { const alox_EspNowRestart *req) {
const uint8_t *own = esp_now_core_own_mac(); const uint8_t *own = esp_now_core_own_mac();
@ -358,8 +401,13 @@ static void handle_discover(const uint8_t *sender_mac,
if (!esp_now_core_mac_equal(sender_mac, s_master_mac)) { if (!esp_now_core_mac_equal(sender_mac, s_master_mac)) {
return; return;
} }
if ((now - s_last_discover_ms) <= SLAVE_MASTER_LOST_MS) { s_master_ota_grace = discover->master_ota_pending;
s_last_discover_ms = now; if ((now - s_last_discover_ms) <= slave_master_lost_ms()) {
touch_master_presence(now);
return;
}
if (ota_uart_is_active()) {
touch_master_presence(now);
return; return;
} }
ESP_LOGW(TAG, "master lost, rejoining"); ESP_LOGW(TAG, "master lost, rejoining");
@ -368,7 +416,8 @@ static void handle_discover(const uint8_t *sender_mac,
memcpy(s_master_mac, sender_mac, ESP_NOW_ETH_ALEN); memcpy(s_master_mac, sender_mac, ESP_NOW_ETH_ALEN);
s_joined = true; s_joined = true;
s_last_discover_ms = now; s_master_ota_grace = discover->master_ota_pending;
touch_master_presence(now);
esp_now_core_ensure_peer(sender_mac); esp_now_core_ensure_peer(sender_mac);
char mac_str[18]; char mac_str[18];
@ -384,10 +433,14 @@ static void check_master_timeout(void) {
if (!s_joined || s_last_discover_ms == 0) { if (!s_joined || s_last_discover_ms == 0) {
return; return;
} }
if (ota_uart_is_active()) {
return;
}
uint32_t now = esp_now_core_now_ms(); uint32_t now = esp_now_core_now_ms();
if ((now - s_last_discover_ms) > SLAVE_MASTER_LOST_MS) { uint32_t limit = slave_master_lost_ms();
ESP_LOGW(TAG, "no master discover for %u ms, reconnecting", if ((now - s_last_discover_ms) > limit) {
(unsigned)(now - s_last_discover_ms)); ESP_LOGW(TAG, "no master discover for %u ms (limit %u), reconnecting",
(unsigned)(now - s_last_discover_ms), (unsigned)limit);
reset_join(); reset_join();
} }
} }
@ -481,12 +534,16 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
if (ota_uart_is_active()) { if (ota_uart_is_active()) {
switch (msg.which_payload) { switch (msg.which_payload) {
case alox_EspNowMessage_discover_tag:
handle_discover(info->src_addr, &msg.payload.discover);
break;
case alox_EspNowMessage_ota_start_tag: case alox_EspNowMessage_ota_start_tag:
case alox_EspNowMessage_ota_payload_tag: case alox_EspNowMessage_ota_payload_tag:
case alox_EspNowMessage_ota_end_tag: case alox_EspNowMessage_ota_end_tag:
if (!from_joined_master(info->src_addr)) { if (!from_joined_master(info->src_addr)) {
break; break;
} }
touch_master_presence(esp_now_core_now_ms());
if (msg.which_payload == alox_EspNowMessage_ota_start_tag) { if (msg.which_payload == alox_EspNowMessage_ota_start_tag) {
ota_espnow_slave_on_start(info->src_addr, &msg.payload.ota_start); ota_espnow_slave_on_start(info->src_addr, &msg.payload.ota_start);
} else if (msg.which_payload == alox_EspNowMessage_ota_payload_tag) { } else if (msg.which_payload == alox_EspNowMessage_ota_payload_tag) {
@ -510,6 +567,11 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
handle_unicast_test(info->src_addr, &msg.payload.unicast_test); handle_unicast_test(info->src_addr, &msg.payload.unicast_test);
} }
break; break;
case alox_EspNowMessage_echo_ping_tag:
if (from_joined_master(info->src_addr)) {
handle_echo_ping(info->src_addr, &msg.payload.echo_ping);
}
break;
case alox_EspNowMessage_accel_deadzone_tag: case alox_EspNowMessage_accel_deadzone_tag:
if (from_joined_master(info->src_addr)) { if (from_joined_master(info->src_addr)) {
handle_accel_deadzone(info->src_addr, &msg.payload.accel_deadzone); handle_accel_deadzone(info->src_addr, &msg.payload.accel_deadzone);
@ -545,6 +607,11 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
handle_restart(info->src_addr, &msg.payload.restart); handle_restart(info->src_addr, &msg.payload.restart);
} }
break; break;
case alox_EspNowMessage_ota_start_tag:
if (from_joined_master(info->src_addr)) {
ota_espnow_slave_on_start(info->src_addr, &msg.payload.ota_start);
}
break;
default: default:
ESP_LOGW(TAG, "unhandled which=%u type=%u", msg.which_payload, ESP_LOGW(TAG, "unhandled which=%u type=%u", msg.which_payload,
(unsigned)msg.type); (unsigned)msg.type);

View File

@ -158,7 +158,7 @@ void led_ring_init(void) {
void led_ring_send_command(led_command_t *cmd) { void led_ring_send_command(led_command_t *cmd) {
if (led_queue != NULL) { if (led_queue != NULL) {
xQueueSend(led_queue, cmd, portMAX_DELAY); (void)xQueueSend(led_queue, cmd, 0);
} }
} }

View File

@ -18,9 +18,9 @@ static const char *TAG = "[OTA_ESPNOW]";
#define OTA_ESPNOW_PREPARE_PRIO 5 #define OTA_ESPNOW_PREPARE_PRIO 5
#define OTA_PREPARE_TIMEOUT_MS 120000u #define OTA_PREPARE_TIMEOUT_MS 120000u
#define OTA_BLOCK_TIMEOUT_MS 30000u #define OTA_BLOCK_TIMEOUT_PER_SLAVE_MS 2000u
#define OTA_BLOCK_MAX_RETRIES 2u
#define OTA_END_TIMEOUT_MS 60000u #define OTA_END_TIMEOUT_MS 60000u
#define OTA_PAYLOAD_DELAY_MS 3
#define OTA_ST_PREPARING 1u #define OTA_ST_PREPARING 1u
#define OTA_ST_READY 2u #define OTA_ST_READY 2u
@ -35,7 +35,8 @@ static const char *TAG = "[OTA_ESPNOW]";
#define OTA_MAX_TARGETS CLIENT_REGISTRY_MAX #define OTA_MAX_TARGETS CLIENT_REGISTRY_MAX
#define OTA_SLAVE_WORK_QUEUE_LEN 12 /** ~21 payloads per 4 KiB block; headroom for bursts + status/end. */
#define OTA_SLAVE_WORK_QUEUE_LEN 32
#define OTA_SLAVE_WORK_STACK 8192 #define OTA_SLAVE_WORK_STACK 8192
#define OTA_SLAVE_WORK_PRIO 5 #define OTA_SLAVE_WORK_PRIO 5
@ -173,6 +174,54 @@ static bool wait_target_bits(uint32_t want_bits, uint32_t timeout_ms) {
return (got & want_bits) == want_bits; return (got & want_bits) == want_bits;
} }
static uint32_t block_ack_timeout_ms(void) {
if (s_dist.count == 0) {
return OTA_BLOCK_TIMEOUT_PER_SLAVE_MS;
}
return (uint32_t)s_dist.count * OTA_BLOCK_TIMEOUT_PER_SLAVE_MS;
}
static void log_missing_block_acks(uint32_t expected_bytes) {
if (s_eg == NULL || s_dist.count == 0) {
return;
}
EventBits_t bits = xEventGroupGetBits(s_eg);
for (uint8_t i = 0; i < s_dist.count; i++) {
uint32_t bit = (1u << (unsigned)i);
if (bits & bit) {
continue;
}
const ota_prog_entry_t *e = &s_prog.entries[i];
ESP_LOGE(TAG,
"slave %lu missing block ack @%lu (last status=%lu bytes=%lu err=%lu)",
(unsigned long)s_dist.id[i], (unsigned long)expected_bytes,
(unsigned long)e->status, (unsigned long)e->bytes_written,
(unsigned long)e->error);
}
}
static esp_err_t send_block_payloads(const uint8_t *block_buf, uint32_t block_len,
uint32_t *seq_io) {
uint32_t sent = 0;
while (sent < block_len) {
uint32_t chunk = block_len - sent;
if (chunk > OTA_UART_HOST_CHUNK_SIZE) {
chunk = OTA_UART_HOST_CHUNK_SIZE;
}
for (uint8_t i = 0; i < s_dist.count; i++) {
esp_err_t err = esp_now_comm_send_ota_payload(s_dist.mac[i], *seq_io,
block_buf + sent, chunk);
if (err != ESP_OK) {
return err;
}
}
(*seq_io)++;
sent += chunk;
}
return ESP_OK;
}
bool ota_espnow_distribution_active(void) { return s_distribution_active; } bool ota_espnow_distribution_active(void) { return s_distribution_active; }
static void send_slave_status(const uint8_t master_mac[6], uint32_t status, static void send_slave_status(const uint8_t master_mac[6], uint32_t status,
@ -221,8 +270,20 @@ static void process_slave_payload(const uint8_t master_mac[6],
ESP_LOGI(TAG, "ESP-NOW OTA payloads started"); ESP_LOGI(TAG, "ESP-NOW OTA payloads started");
} }
ota_feed_result_t r = ota_feed_result_t r = ota_uart_feed_chunk(payload->seq, payload->data.bytes,
ota_uart_feed(payload->data.bytes, payload->data.size); payload->data.size);
if (r == OTA_FEED_SEQ_GAP) {
led_ring_ota_failed();
send_slave_status(master_mac, OTA_ST_FAILED, ota_uart_bytes_written(), 16);
return;
}
if (r == OTA_FEED_SEQ_DUP) {
if (ota_uart_block_ready_for_reack()) {
send_slave_status(master_mac, OTA_ST_BLOCK_ACK, ota_uart_bytes_written(),
0);
}
return;
}
if (r == OTA_FEED_ERROR) { if (r == OTA_FEED_ERROR) {
led_ring_ota_failed(); led_ring_ota_failed();
send_slave_status(master_mac, OTA_ST_FAILED, ota_uart_bytes_written(), 13); send_slave_status(master_mac, OTA_ST_FAILED, ota_uart_bytes_written(), 13);
@ -509,35 +570,71 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
return err; return err;
} }
uint32_t sent = 0; const bool full_block = (block_len >= OTA_UART_FLASH_BLOCK_SIZE);
while (sent < block_len) { s_dist.expected_bytes = offset + block_len;
uint32_t chunk = block_len - sent; const uint32_t block_start_seq = seq;
if (chunk > OTA_UART_HOST_CHUNK_SIZE) {
chunk = OTA_UART_HOST_CHUNK_SIZE; if (full_block) {
xEventGroupClearBits(s_eg, target_mask);
} }
for (uint8_t i = 0; i < s_dist.count; i++) { bool block_sent = false;
err = esp_now_comm_send_ota_payload(s_dist.mac[i], seq, for (uint32_t send_attempt = 0; send_attempt <= OTA_BLOCK_MAX_RETRIES;
block_buf + sent, chunk); send_attempt++) {
if (send_attempt > 0) {
seq = block_start_seq;
if (full_block) {
xEventGroupClearBits(s_eg, target_mask);
}
ESP_LOGW(TAG, "block send failed @%lu — resend %lu/%lu",
(unsigned long)s_dist.expected_bytes,
(unsigned long)send_attempt,
(unsigned long)OTA_BLOCK_MAX_RETRIES);
}
err = send_block_payloads(block_buf, block_len, &seq);
if (err == ESP_OK) {
block_sent = true;
break;
}
}
if (!block_sent) {
ESP_LOGE(TAG, "block send failed @%lu after %lu retries",
(unsigned long)s_dist.expected_bytes,
(unsigned long)OTA_BLOCK_MAX_RETRIES);
prog_end();
s_distribution_active = false;
return err;
}
if (full_block) {
const uint32_t ack_timeout = block_ack_timeout_ms();
bool acked = false;
for (uint32_t attempt = 0; attempt <= OTA_BLOCK_MAX_RETRIES; attempt++) {
if (wait_target_bits(target_mask, ack_timeout)) {
acked = true;
break;
}
log_missing_block_acks(s_dist.expected_bytes);
if (attempt >= OTA_BLOCK_MAX_RETRIES) {
break;
}
ESP_LOGW(TAG, "block ack timeout @%lu — resend %lu/%lu",
(unsigned long)s_dist.expected_bytes,
(unsigned long)(attempt + 1),
(unsigned long)OTA_BLOCK_MAX_RETRIES);
xEventGroupClearBits(s_eg, target_mask);
seq = block_start_seq;
err = send_block_payloads(block_buf, block_len, &seq);
if (err != ESP_OK) { if (err != ESP_OK) {
prog_end(); prog_end();
s_distribution_active = false; s_distribution_active = false;
return err; return err;
} }
} }
seq++; if (!acked) {
sent += chunk; ESP_LOGE(TAG, "timeout block ack @%lu bytes after %lu retries",
vTaskDelay(pdMS_TO_TICKS(OTA_PAYLOAD_DELAY_MS)); (unsigned long)s_dist.expected_bytes,
} (unsigned long)OTA_BLOCK_MAX_RETRIES);
const bool full_block = (block_len >= OTA_UART_FLASH_BLOCK_SIZE);
s_dist.expected_bytes = offset + block_len;
if (full_block) {
xEventGroupClearBits(s_eg, target_mask);
if (!wait_target_bits(target_mask, OTA_BLOCK_TIMEOUT_MS)) {
ESP_LOGE(TAG, "timeout block ack @%lu bytes",
(unsigned long)s_dist.expected_bytes);
prog_end(); prog_end();
s_distribution_active = false; s_distribution_active = false;
return ESP_ERR_TIMEOUT; return ESP_ERR_TIMEOUT;

View File

@ -12,6 +12,7 @@ typedef struct {
uint32_t total_size; uint32_t total_size;
uint32_t received; uint32_t received;
uint32_t written; uint32_t written;
uint32_t expected_seq;
int target_slot; int target_slot;
uint8_t block_buf[OTA_UART_FLASH_BLOCK_SIZE]; uint8_t block_buf[OTA_UART_FLASH_BLOCK_SIZE];
size_t block_len; size_t block_len;
@ -112,10 +113,30 @@ int ota_uart_prepare(uint32_t total_size) {
return s_ota.target_slot; return s_ota.target_slot;
} }
ota_feed_result_t ota_uart_feed(const uint8_t *data, size_t len) { bool ota_uart_block_ready_for_reack(void) {
if (!s_ota.active) {
return false;
}
return s_ota.written > 0 &&
(s_ota.written % OTA_UART_FLASH_BLOCK_SIZE) == 0 &&
s_ota.block_len == 0;
}
ota_feed_result_t ota_uart_feed_chunk(uint32_t seq, const uint8_t *data,
size_t len) {
if (!s_ota.active || data == NULL || len == 0) { if (!s_ota.active || data == NULL || len == 0) {
return OTA_FEED_ERROR; return OTA_FEED_ERROR;
} }
if (seq < s_ota.expected_seq) {
return OTA_FEED_SEQ_DUP;
}
if (seq > s_ota.expected_seq) {
ESP_LOGW(TAG, "seq gap: got %lu expected %lu", (unsigned long)seq,
(unsigned long)s_ota.expected_seq);
return OTA_FEED_SEQ_GAP;
}
s_ota.expected_seq++;
if (len > OTA_UART_HOST_CHUNK_SIZE) { if (len > OTA_UART_HOST_CHUNK_SIZE) {
ESP_LOGW(TAG, "chunk %u > %u, truncating", (unsigned)len, ESP_LOGW(TAG, "chunk %u > %u, truncating", (unsigned)len,
OTA_UART_HOST_CHUNK_SIZE); OTA_UART_HOST_CHUNK_SIZE);
@ -200,6 +221,13 @@ esp_err_t ota_uart_finish(bool set_boot, bool *success_out) {
return err; return err;
} }
if (s_ota.total_size > 0 && s_ota.received != s_ota.total_size) {
ESP_LOGE(TAG, "size mismatch: received=%lu expected=%lu",
(unsigned long)s_ota.received, (unsigned long)s_ota.total_size);
ota_uart_abort();
return ESP_ERR_INVALID_SIZE;
}
err = esp_ota_end(s_ota.handle); err = esp_ota_end(s_ota.handle);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));

View File

@ -28,6 +28,8 @@ typedef enum {
typedef enum { typedef enum {
OTA_FEED_OK = 0, OTA_FEED_OK = 0,
OTA_FEED_BLOCK_WRITTEN, OTA_FEED_BLOCK_WRITTEN,
OTA_FEED_SEQ_DUP,
OTA_FEED_SEQ_GAP,
OTA_FEED_ERROR, OTA_FEED_ERROR,
} ota_feed_result_t; } ota_feed_result_t;
@ -41,8 +43,14 @@ int ota_uart_prepare(uint32_t total_size);
void ota_uart_abort(void); void ota_uart_abort(void);
/** Append up to 200 bytes; flushes 4 KiB blocks to flash when full. */ /**
ota_feed_result_t ota_uart_feed(const uint8_t *data, size_t len); * 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.
*/
ota_feed_result_t ota_uart_feed_chunk(uint32_t seq, const uint8_t *data, size_t len);
/** True when a full 4 KiB block is in flash (used to re-ACK host block retries). */
bool ota_uart_block_ready_for_reack(void);
uint32_t ota_uart_bytes_written(void); uint32_t ota_uart_bytes_written(void);

View File

@ -5,6 +5,7 @@
#include "cmd_tap_notify.h" #include "cmd_tap_notify.h"
#include "cmd_cache_status.h" #include "cmd_cache_status.h"
#include "cmd_espnow_unicast_test.h" #include "cmd_espnow_unicast_test.h"
#include "cmd_espnow_echo_ping.h"
#include "cmd_espnow_find_me.h" #include "cmd_espnow_find_me.h"
#include "cmd_restart.h" #include "cmd_restart.h"
#include "cmd_client_info.h" #include "cmd_client_info.h"
@ -13,6 +14,7 @@
#include "cmd_ota_slave_progress.h" #include "cmd_ota_slave_progress.h"
#include "cmd_led_ring.h" #include "cmd_led_ring.h"
#include "cmd_battery.h" #include "cmd_battery.h"
#include "cmd_set_log_level.h"
#include "esp_now_comm.h" #include "esp_now_comm.h"
#include "powerpod.h" #include "powerpod.h"
#include "driver/gpio.h" #include "driver/gpio.h"
@ -185,12 +187,14 @@ void app_main(void) {
cmd_tap_notify_register(); cmd_tap_notify_register();
cmd_cache_status_register(); cmd_cache_status_register();
cmd_espnow_unicast_test_register(); cmd_espnow_unicast_test_register();
cmd_espnow_echo_ping_register();
cmd_espnow_find_me_register(); cmd_espnow_find_me_register();
cmd_restart_register(); cmd_restart_register();
cmd_led_ring_register(); cmd_led_ring_register();
cmd_battery_register(); cmd_battery_register();
cmd_ota_register(); cmd_ota_register();
cmd_ota_slave_progress_register(); cmd_ota_slave_progress_register();
cmd_set_log_level_register();
} }
ESP_LOGI(TAG, "LED ring: UART LED_RING commands only (no local demo loop)"); ESP_LOGI(TAG, "LED ring: UART LED_RING commands only (no local demo loop)");

View File

@ -9,6 +9,12 @@
PB_BIND(alox_EspNowUnicastTest, alox_EspNowUnicastTest, AUTO) PB_BIND(alox_EspNowUnicastTest, alox_EspNowUnicastTest, AUTO)
PB_BIND(alox_EspNowEchoPing, alox_EspNowEchoPing, AUTO)
PB_BIND(alox_EspNowEchoPong, alox_EspNowEchoPong, AUTO)
PB_BIND(alox_EspNowFindMe, alox_EspNowFindMe, AUTO) PB_BIND(alox_EspNowFindMe, alox_EspNowFindMe, AUTO)

View File

@ -29,7 +29,9 @@ typedef enum _alox_EspNowMessageType {
alox_EspNowMessageType_ESPNOW_BATTERY_QUERY = 15, alox_EspNowMessageType_ESPNOW_BATTERY_QUERY = 15,
alox_EspNowMessageType_ESPNOW_BATTERY_REPORT = 16, alox_EspNowMessageType_ESPNOW_BATTERY_REPORT = 16,
alox_EspNowMessageType_ESPNOW_SET_TAP_NOTIFY = 17, alox_EspNowMessageType_ESPNOW_SET_TAP_NOTIFY = 17,
alox_EspNowMessageType_ESPNOW_TAP_EVENT = 18 alox_EspNowMessageType_ESPNOW_TAP_EVENT = 18,
alox_EspNowMessageType_ESPNOW_ECHO_PING = 19,
alox_EspNowMessageType_ESPNOW_ECHO_PONG = 20
} alox_EspNowMessageType; } alox_EspNowMessageType;
/* Struct definitions */ /* Struct definitions */
@ -37,6 +39,19 @@ typedef struct _alox_EspNowUnicastTest {
uint32_t seq; uint32_t seq;
} alox_EspNowUnicastTest; } alox_EspNowUnicastTest;
/* * Master → slave: echo ping (host ts + master monotonic time for RTT). */
typedef struct _alox_EspNowEchoPing {
uint64_t host_timestamp_us;
/* * esp_timer_get_time() (µs since boot) when master forwarded this message. */
uint64_t master_time_us;
} alox_EspNowEchoPing;
/* * Slave → master: echo ping payload unchanged. */
typedef struct _alox_EspNowEchoPong {
uint64_t host_timestamp_us;
uint64_t master_time_us;
} alox_EspNowEchoPong;
/* * Master → slave: locate pod (LED ring R/G/B ×3 @ full brightness). */ /* * Master → slave: locate pod (LED ring R/G/B ×3 @ full brightness). */
typedef struct _alox_EspNowFindMe { typedef struct _alox_EspNowFindMe {
/* * 0 = any slave; otherwise only slave_id must match */ /* * 0 = any slave; otherwise only slave_id must match */
@ -50,6 +65,8 @@ typedef struct _alox_EspNowRestart {
typedef struct _alox_EspNowDiscover { typedef struct _alox_EspNowDiscover {
uint32_t network; uint32_t network;
/* * Master is in an OTA session (UART upload or ESP-NOW distribution). */
bool master_ota_pending;
} alox_EspNowDiscover; } alox_EspNowDiscover;
typedef struct _alox_EspNowSlavePresence { typedef struct _alox_EspNowSlavePresence {
@ -169,6 +186,8 @@ typedef struct _alox_EspNowMessage {
alox_EspNowBatteryReport battery_report; alox_EspNowBatteryReport battery_report;
alox_EspNowTapNotify tap_notify; alox_EspNowTapNotify tap_notify;
alox_EspNowTapEvent tap_event; alox_EspNowTapEvent tap_event;
alox_EspNowEchoPing echo_ping;
alox_EspNowEchoPong echo_pong;
} payload; } payload;
} alox_EspNowMessage; } alox_EspNowMessage;
@ -179,8 +198,10 @@ extern "C" {
/* Helper constants for enums */ /* Helper constants for enums */
#define _alox_EspNowMessageType_MIN alox_EspNowMessageType_ESPNOW_UNKNOWN #define _alox_EspNowMessageType_MIN alox_EspNowMessageType_ESPNOW_UNKNOWN
#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_TAP_EVENT #define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_ECHO_PONG
#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_TAP_EVENT+1)) #define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_ECHO_PONG+1))
@ -204,9 +225,11 @@ extern "C" {
/* Initializer values for message structs */ /* Initializer values for message structs */
#define alox_EspNowUnicastTest_init_default {0} #define alox_EspNowUnicastTest_init_default {0}
#define alox_EspNowEchoPing_init_default {0, 0}
#define alox_EspNowEchoPong_init_default {0, 0}
#define alox_EspNowFindMe_init_default {0} #define alox_EspNowFindMe_init_default {0}
#define alox_EspNowRestart_init_default {0} #define alox_EspNowRestart_init_default {0}
#define alox_EspNowDiscover_init_default {0} #define alox_EspNowDiscover_init_default {0, 0}
#define alox_EspNowSlavePresence_init_default {0, {{NULL}, NULL}, 0, 0, 0, 0} #define alox_EspNowSlavePresence_init_default {0, {{NULL}, NULL}, 0, 0, 0, 0}
#define alox_EspNowAccelDeadzone_init_default {0, 0} #define alox_EspNowAccelDeadzone_init_default {0, 0}
#define alox_EspNowAccelStream_init_default {0, 0} #define alox_EspNowAccelStream_init_default {0, 0}
@ -222,9 +245,11 @@ extern "C" {
#define alox_EspNowOtaStatus_init_default {0, 0, 0} #define alox_EspNowOtaStatus_init_default {0, 0, 0}
#define alox_EspNowMessage_init_default {_alox_EspNowMessageType_MIN, 0, {alox_EspNowDiscover_init_default}} #define alox_EspNowMessage_init_default {_alox_EspNowMessageType_MIN, 0, {alox_EspNowDiscover_init_default}}
#define alox_EspNowUnicastTest_init_zero {0} #define alox_EspNowUnicastTest_init_zero {0}
#define alox_EspNowEchoPing_init_zero {0, 0}
#define alox_EspNowEchoPong_init_zero {0, 0}
#define alox_EspNowFindMe_init_zero {0} #define alox_EspNowFindMe_init_zero {0}
#define alox_EspNowRestart_init_zero {0} #define alox_EspNowRestart_init_zero {0}
#define alox_EspNowDiscover_init_zero {0} #define alox_EspNowDiscover_init_zero {0, 0}
#define alox_EspNowSlavePresence_init_zero {0, {{NULL}, NULL}, 0, 0, 0, 0} #define alox_EspNowSlavePresence_init_zero {0, {{NULL}, NULL}, 0, 0, 0, 0}
#define alox_EspNowAccelDeadzone_init_zero {0, 0} #define alox_EspNowAccelDeadzone_init_zero {0, 0}
#define alox_EspNowAccelStream_init_zero {0, 0} #define alox_EspNowAccelStream_init_zero {0, 0}
@ -242,9 +267,14 @@ extern "C" {
/* Field tags (for use in manual encoding/decoding) */ /* Field tags (for use in manual encoding/decoding) */
#define alox_EspNowUnicastTest_seq_tag 1 #define alox_EspNowUnicastTest_seq_tag 1
#define alox_EspNowEchoPing_host_timestamp_us_tag 1
#define alox_EspNowEchoPing_master_time_us_tag 2
#define alox_EspNowEchoPong_host_timestamp_us_tag 1
#define alox_EspNowEchoPong_master_time_us_tag 2
#define alox_EspNowFindMe_client_id_tag 1 #define alox_EspNowFindMe_client_id_tag 1
#define alox_EspNowRestart_client_id_tag 1 #define alox_EspNowRestart_client_id_tag 1
#define alox_EspNowDiscover_network_tag 1 #define alox_EspNowDiscover_network_tag 1
#define alox_EspNowDiscover_master_ota_pending_tag 2
#define alox_EspNowSlavePresence_network_tag 1 #define alox_EspNowSlavePresence_network_tag 1
#define alox_EspNowSlavePresence_mac_tag 2 #define alox_EspNowSlavePresence_mac_tag 2
#define alox_EspNowSlavePresence_version_tag 3 #define alox_EspNowSlavePresence_version_tag 3
@ -306,6 +336,8 @@ extern "C" {
#define alox_EspNowMessage_battery_report_tag 17 #define alox_EspNowMessage_battery_report_tag 17
#define alox_EspNowMessage_tap_notify_tag 18 #define alox_EspNowMessage_tap_notify_tag 18
#define alox_EspNowMessage_tap_event_tag 19 #define alox_EspNowMessage_tap_event_tag 19
#define alox_EspNowMessage_echo_ping_tag 20
#define alox_EspNowMessage_echo_pong_tag 21
/* Struct field encoding specification for nanopb */ /* Struct field encoding specification for nanopb */
#define alox_EspNowUnicastTest_FIELDLIST(X, a) \ #define alox_EspNowUnicastTest_FIELDLIST(X, a) \
@ -313,6 +345,18 @@ X(a, STATIC, SINGULAR, UINT32, seq, 1)
#define alox_EspNowUnicastTest_CALLBACK NULL #define alox_EspNowUnicastTest_CALLBACK NULL
#define alox_EspNowUnicastTest_DEFAULT NULL #define alox_EspNowUnicastTest_DEFAULT NULL
#define alox_EspNowEchoPing_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT64, host_timestamp_us, 1) \
X(a, STATIC, SINGULAR, UINT64, master_time_us, 2)
#define alox_EspNowEchoPing_CALLBACK NULL
#define alox_EspNowEchoPing_DEFAULT NULL
#define alox_EspNowEchoPong_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT64, host_timestamp_us, 1) \
X(a, STATIC, SINGULAR, UINT64, master_time_us, 2)
#define alox_EspNowEchoPong_CALLBACK NULL
#define alox_EspNowEchoPong_DEFAULT NULL
#define alox_EspNowFindMe_FIELDLIST(X, a) \ #define alox_EspNowFindMe_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, client_id, 1) X(a, STATIC, SINGULAR, UINT32, client_id, 1)
#define alox_EspNowFindMe_CALLBACK NULL #define alox_EspNowFindMe_CALLBACK NULL
@ -324,7 +368,8 @@ X(a, STATIC, SINGULAR, UINT32, client_id, 1)
#define alox_EspNowRestart_DEFAULT NULL #define alox_EspNowRestart_DEFAULT NULL
#define alox_EspNowDiscover_FIELDLIST(X, a) \ #define alox_EspNowDiscover_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, network, 1) X(a, STATIC, SINGULAR, UINT32, network, 1) \
X(a, STATIC, SINGULAR, BOOL, master_ota_pending, 2)
#define alox_EspNowDiscover_CALLBACK NULL #define alox_EspNowDiscover_CALLBACK NULL
#define alox_EspNowDiscover_DEFAULT NULL #define alox_EspNowDiscover_DEFAULT NULL
@ -442,7 +487,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,led_ring,payload.led_ring), 15) \
X(a, STATIC, ONEOF, MESSAGE, (payload,battery_query,payload.battery_query), 16) \ X(a, STATIC, ONEOF, MESSAGE, (payload,battery_query,payload.battery_query), 16) \
X(a, STATIC, ONEOF, MESSAGE, (payload,battery_report,payload.battery_report), 17) \ X(a, STATIC, ONEOF, MESSAGE, (payload,battery_report,payload.battery_report), 17) \
X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify,payload.tap_notify), 18) \ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify,payload.tap_notify), 18) \
X(a, STATIC, ONEOF, MESSAGE, (payload,tap_event,payload.tap_event), 19) X(a, STATIC, ONEOF, MESSAGE, (payload,tap_event,payload.tap_event), 19) \
X(a, STATIC, ONEOF, MESSAGE, (payload,echo_ping,payload.echo_ping), 20) \
X(a, STATIC, ONEOF, MESSAGE, (payload,echo_pong,payload.echo_pong), 21)
#define alox_EspNowMessage_CALLBACK NULL #define alox_EspNowMessage_CALLBACK NULL
#define alox_EspNowMessage_DEFAULT NULL #define alox_EspNowMessage_DEFAULT NULL
#define alox_EspNowMessage_payload_discover_MSGTYPE alox_EspNowDiscover #define alox_EspNowMessage_payload_discover_MSGTYPE alox_EspNowDiscover
@ -463,8 +510,12 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_event,payload.tap_event), 19)
#define alox_EspNowMessage_payload_battery_report_MSGTYPE alox_EspNowBatteryReport #define alox_EspNowMessage_payload_battery_report_MSGTYPE alox_EspNowBatteryReport
#define alox_EspNowMessage_payload_tap_notify_MSGTYPE alox_EspNowTapNotify #define alox_EspNowMessage_payload_tap_notify_MSGTYPE alox_EspNowTapNotify
#define alox_EspNowMessage_payload_tap_event_MSGTYPE alox_EspNowTapEvent #define alox_EspNowMessage_payload_tap_event_MSGTYPE alox_EspNowTapEvent
#define alox_EspNowMessage_payload_echo_ping_MSGTYPE alox_EspNowEchoPing
#define alox_EspNowMessage_payload_echo_pong_MSGTYPE alox_EspNowEchoPong
extern const pb_msgdesc_t alox_EspNowUnicastTest_msg; extern const pb_msgdesc_t alox_EspNowUnicastTest_msg;
extern const pb_msgdesc_t alox_EspNowEchoPing_msg;
extern const pb_msgdesc_t alox_EspNowEchoPong_msg;
extern const pb_msgdesc_t alox_EspNowFindMe_msg; extern const pb_msgdesc_t alox_EspNowFindMe_msg;
extern const pb_msgdesc_t alox_EspNowRestart_msg; extern const pb_msgdesc_t alox_EspNowRestart_msg;
extern const pb_msgdesc_t alox_EspNowDiscover_msg; extern const pb_msgdesc_t alox_EspNowDiscover_msg;
@ -485,6 +536,8 @@ extern const pb_msgdesc_t alox_EspNowMessage_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ /* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define alox_EspNowUnicastTest_fields &alox_EspNowUnicastTest_msg #define alox_EspNowUnicastTest_fields &alox_EspNowUnicastTest_msg
#define alox_EspNowEchoPing_fields &alox_EspNowEchoPing_msg
#define alox_EspNowEchoPong_fields &alox_EspNowEchoPong_msg
#define alox_EspNowFindMe_fields &alox_EspNowFindMe_msg #define alox_EspNowFindMe_fields &alox_EspNowFindMe_msg
#define alox_EspNowRestart_fields &alox_EspNowRestart_msg #define alox_EspNowRestart_fields &alox_EspNowRestart_msg
#define alox_EspNowDiscover_fields &alox_EspNowDiscover_msg #define alox_EspNowDiscover_fields &alox_EspNowDiscover_msg
@ -512,7 +565,9 @@ extern const pb_msgdesc_t alox_EspNowMessage_msg;
#define alox_EspNowAccelStream_size 8 #define alox_EspNowAccelStream_size 8
#define alox_EspNowBatteryQuery_size 6 #define alox_EspNowBatteryQuery_size 6
#define alox_EspNowBatteryReport_size 22 #define alox_EspNowBatteryReport_size 22
#define alox_EspNowDiscover_size 6 #define alox_EspNowDiscover_size 8
#define alox_EspNowEchoPing_size 22
#define alox_EspNowEchoPong_size 22
#define alox_EspNowFindMe_size 6 #define alox_EspNowFindMe_size 6
#define alox_EspNowLedRing_size 60 #define alox_EspNowLedRing_size 60
#define alox_EspNowOtaEnd_size 0 #define alox_EspNowOtaEnd_size 0

View File

@ -24,12 +24,27 @@ enum EspNowMessageType {
ESPNOW_BATTERY_REPORT = 16; ESPNOW_BATTERY_REPORT = 16;
ESPNOW_SET_TAP_NOTIFY = 17; ESPNOW_SET_TAP_NOTIFY = 17;
ESPNOW_TAP_EVENT = 18; ESPNOW_TAP_EVENT = 18;
ESPNOW_ECHO_PING = 19;
ESPNOW_ECHO_PONG = 20;
} }
message EspNowUnicastTest { message EspNowUnicastTest {
uint32 seq = 1; uint32 seq = 1;
} }
/** Master → slave: echo ping (host ts + master monotonic time for RTT). */
message EspNowEchoPing {
uint64 host_timestamp_us = 1;
/** esp_timer_get_time() (µs since boot) when master forwarded this message. */
uint64 master_time_us = 2;
}
/** Slave → master: echo ping payload unchanged. */
message EspNowEchoPong {
uint64 host_timestamp_us = 1;
uint64 master_time_us = 2;
}
/** Master → slave: locate pod (LED ring R/G/B ×3 @ full brightness). */ /** Master → slave: locate pod (LED ring R/G/B ×3 @ full brightness). */
message EspNowFindMe { message EspNowFindMe {
/** 0 = any slave; otherwise only slave_id must match */ /** 0 = any slave; otherwise only slave_id must match */
@ -43,6 +58,8 @@ message EspNowRestart {
message EspNowDiscover { message EspNowDiscover {
uint32 network = 1; uint32 network = 1;
/** Master is in an OTA session (UART upload or ESP-NOW distribution). */
bool master_ota_pending = 2;
} }
message EspNowSlavePresence { message EspNowSlavePresence {
@ -158,5 +175,7 @@ message EspNowMessage {
EspNowBatteryReport battery_report = 17; EspNowBatteryReport battery_report = 17;
EspNowTapNotify tap_notify = 18; EspNowTapNotify tap_notify = 18;
EspNowTapEvent tap_event = 19; EspNowTapEvent tap_event = 19;
EspNowEchoPing echo_ping = 20;
EspNowEchoPong echo_pong = 21;
} }
} }

View File

@ -87,6 +87,12 @@ PB_BIND(alox_EspNowUnicastTestRequest, alox_EspNowUnicastTestRequest, AUTO)
PB_BIND(alox_EspNowUnicastTestResponse, alox_EspNowUnicastTestResponse, AUTO) PB_BIND(alox_EspNowUnicastTestResponse, alox_EspNowUnicastTestResponse, AUTO)
PB_BIND(alox_EspNowEchoPingRequest, alox_EspNowEchoPingRequest, AUTO)
PB_BIND(alox_EspNowEchoPingResponse, alox_EspNowEchoPingResponse, AUTO)
PB_BIND(alox_LedRingProgressRequest, alox_LedRingProgressRequest, AUTO) PB_BIND(alox_LedRingProgressRequest, alox_LedRingProgressRequest, AUTO)
@ -105,6 +111,12 @@ PB_BIND(alox_RestartRequest, alox_RestartRequest, AUTO)
PB_BIND(alox_RestartResponse, alox_RestartResponse, AUTO) PB_BIND(alox_RestartResponse, alox_RestartResponse, AUTO)
PB_BIND(alox_SetLogLevelRequest, alox_SetLogLevelRequest, AUTO)
PB_BIND(alox_SetLogLevelResponse, alox_SetLogLevelResponse, AUTO)
PB_BIND(alox_OtaStartPayload, alox_OtaStartPayload, AUTO) PB_BIND(alox_OtaStartPayload, alox_OtaStartPayload, AUTO)

View File

@ -32,7 +32,11 @@ typedef enum _alox_MessageType {
alox_MessageType_BATTERY_STATUS = 26, alox_MessageType_BATTERY_STATUS = 26,
alox_MessageType_TAP_NOTIFY = 27, 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, ~16 ms cadence). */
alox_MessageType_CACHE_STATUS = 29 alox_MessageType_CACHE_STATUS = 29,
/* * Host → master → slave → master: timestamp echo round-trip (latency test). */
alox_MessageType_ESPNOW_ECHO_PING = 30,
/* * Host → master: get/set ESP-IDF log level for tag "*" (global). */
alox_MessageType_SET_LOG_LEVEL = 31
} alox_MessageType; } alox_MessageType;
typedef enum _alox_TapKind { typedef enum _alox_TapKind {
@ -235,6 +239,22 @@ typedef struct _alox_EspNowUnicastTestResponse {
uint32_t seq; uint32_t seq;
} alox_EspNowUnicastTestResponse; } alox_EspNowUnicastTestResponse;
/* * Host → master: ESP-NOW echo ping to one slave (timestamp echoed back). */
typedef struct _alox_EspNowEchoPingRequest {
uint32_t client_id;
/* * Microseconds since Unix epoch (host clock). */
uint64_t timestamp_us;
} alox_EspNowEchoPingRequest;
typedef struct _alox_EspNowEchoPingResponse {
bool success;
uint32_t client_id;
/* * Echoed host timestamp from goTool request. */
uint64_t timestamp_us;
/* * esp_timer_get_time() delta from ping send to pong recv (master→slave→master). */
uint32_t esp_rtt_us;
} alox_EspNowEchoPingResponse;
/* Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW. /* Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
mode: 0=clear, 1=progress (0100 %), 2=digit (010), 3=blink, 4=find-me, 5=all LEDs solid color. */ mode: 0=clear, 1=progress (0100 %), 2=digit (010), 3=blink, 4=find-me, 5=all LEDs solid color. */
typedef struct _alox_LedRingProgressRequest { typedef struct _alox_LedRingProgressRequest {
@ -289,6 +309,18 @@ typedef struct _alox_RestartResponse {
uint32_t client_id; uint32_t client_id;
} alox_RestartResponse; } alox_RestartResponse;
/* * Host → master: read/write global log level (esp_log_level_set("*", …)). */
typedef struct _alox_SetLogLevelRequest {
bool write;
/* * esp_log_level_t: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE */
uint32_t level;
} alox_SetLogLevelRequest;
typedef struct _alox_SetLogLevelResponse {
bool success;
uint32_t level;
} alox_SetLogLevelResponse;
/* Host → device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS). */ /* Host → device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS). */
typedef struct _alox_OtaStartPayload { typedef struct _alox_OtaStartPayload {
uint32_t total_size; uint32_t total_size;
@ -371,6 +403,10 @@ typedef struct _alox_UartMessage {
alox_TapNotifyResponse tap_notify_response; alox_TapNotifyResponse tap_notify_response;
alox_CacheStatusRequest cache_status_request; alox_CacheStatusRequest cache_status_request;
alox_CacheStatusResponse cache_status_response; alox_CacheStatusResponse cache_status_response;
alox_EspNowEchoPingRequest espnow_echo_ping_request;
alox_EspNowEchoPingResponse espnow_echo_ping_response;
alox_SetLogLevelRequest set_log_level_request;
alox_SetLogLevelResponse set_log_level_response;
} payload; } payload;
} alox_UartMessage; } alox_UartMessage;
@ -381,8 +417,8 @@ extern "C" {
/* Helper constants for enums */ /* Helper constants for enums */
#define _alox_MessageType_MIN alox_MessageType_UNKNOWN #define _alox_MessageType_MIN alox_MessageType_UNKNOWN
#define _alox_MessageType_MAX alox_MessageType_CACHE_STATUS #define _alox_MessageType_MAX alox_MessageType_SET_LOG_LEVEL
#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_CACHE_STATUS+1)) #define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_SET_LOG_LEVEL+1))
#define _alox_TapKind_MIN alox_TapKind_TAP_NONE #define _alox_TapKind_MIN alox_TapKind_TAP_NONE
#define _alox_TapKind_MAX alox_TapKind_TAP_TRIPLE #define _alox_TapKind_MAX alox_TapKind_TAP_TRIPLE
@ -429,6 +465,10 @@ extern "C" {
@ -460,12 +500,16 @@ extern "C" {
#define alox_CacheStatusResponse_init_default {0, {alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default}} #define alox_CacheStatusResponse_init_default {0, {alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default}}
#define alox_EspNowUnicastTestRequest_init_default {0, 0} #define alox_EspNowUnicastTestRequest_init_default {0, 0}
#define alox_EspNowUnicastTestResponse_init_default {0, 0} #define alox_EspNowUnicastTestResponse_init_default {0, 0}
#define alox_EspNowEchoPingRequest_init_default {0, 0}
#define alox_EspNowEchoPingResponse_init_default {0, 0, 0, 0}
#define alox_LedRingProgressRequest_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define alox_LedRingProgressRequest_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define alox_LedRingProgressResponse_init_default {0, 0, 0, 0, 0, 0} #define alox_LedRingProgressResponse_init_default {0, 0, 0, 0, 0, 0}
#define alox_EspNowFindMeRequest_init_default {0} #define alox_EspNowFindMeRequest_init_default {0}
#define alox_EspNowFindMeResponse_init_default {0, 0} #define alox_EspNowFindMeResponse_init_default {0, 0}
#define alox_RestartRequest_init_default {0} #define alox_RestartRequest_init_default {0}
#define alox_RestartResponse_init_default {0, 0} #define alox_RestartResponse_init_default {0, 0}
#define alox_SetLogLevelRequest_init_default {0, 0}
#define alox_SetLogLevelResponse_init_default {0, 0}
#define alox_OtaStartPayload_init_default {0} #define alox_OtaStartPayload_init_default {0}
#define alox_OtaPayload_init_default {0, {0, {0}}} #define alox_OtaPayload_init_default {0, {0, {0}}}
#define alox_OtaEndPayload_init_default {0} #define alox_OtaEndPayload_init_default {0}
@ -500,12 +544,16 @@ extern "C" {
#define alox_CacheStatusResponse_init_zero {0, {alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero}} #define alox_CacheStatusResponse_init_zero {0, {alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero}}
#define alox_EspNowUnicastTestRequest_init_zero {0, 0} #define alox_EspNowUnicastTestRequest_init_zero {0, 0}
#define alox_EspNowUnicastTestResponse_init_zero {0, 0} #define alox_EspNowUnicastTestResponse_init_zero {0, 0}
#define alox_EspNowEchoPingRequest_init_zero {0, 0}
#define alox_EspNowEchoPingResponse_init_zero {0, 0, 0, 0}
#define alox_LedRingProgressRequest_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define alox_LedRingProgressRequest_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define alox_LedRingProgressResponse_init_zero {0, 0, 0, 0, 0, 0} #define alox_LedRingProgressResponse_init_zero {0, 0, 0, 0, 0, 0}
#define alox_EspNowFindMeRequest_init_zero {0} #define alox_EspNowFindMeRequest_init_zero {0}
#define alox_EspNowFindMeResponse_init_zero {0, 0} #define alox_EspNowFindMeResponse_init_zero {0, 0}
#define alox_RestartRequest_init_zero {0} #define alox_RestartRequest_init_zero {0}
#define alox_RestartResponse_init_zero {0, 0} #define alox_RestartResponse_init_zero {0, 0}
#define alox_SetLogLevelRequest_init_zero {0, 0}
#define alox_SetLogLevelResponse_init_zero {0, 0}
#define alox_OtaStartPayload_init_zero {0} #define alox_OtaStartPayload_init_zero {0}
#define alox_OtaPayload_init_zero {0, {0, {0}}} #define alox_OtaPayload_init_zero {0, {0, {0}}}
#define alox_OtaEndPayload_init_zero {0} #define alox_OtaEndPayload_init_zero {0}
@ -599,6 +647,12 @@ extern "C" {
#define alox_EspNowUnicastTestRequest_seq_tag 2 #define alox_EspNowUnicastTestRequest_seq_tag 2
#define alox_EspNowUnicastTestResponse_success_tag 1 #define alox_EspNowUnicastTestResponse_success_tag 1
#define alox_EspNowUnicastTestResponse_seq_tag 2 #define alox_EspNowUnicastTestResponse_seq_tag 2
#define alox_EspNowEchoPingRequest_client_id_tag 1
#define alox_EspNowEchoPingRequest_timestamp_us_tag 2
#define alox_EspNowEchoPingResponse_success_tag 1
#define alox_EspNowEchoPingResponse_client_id_tag 2
#define alox_EspNowEchoPingResponse_timestamp_us_tag 3
#define alox_EspNowEchoPingResponse_esp_rtt_us_tag 4
#define alox_LedRingProgressRequest_mode_tag 1 #define alox_LedRingProgressRequest_mode_tag 1
#define alox_LedRingProgressRequest_progress_tag 2 #define alox_LedRingProgressRequest_progress_tag 2
#define alox_LedRingProgressRequest_digit_tag 3 #define alox_LedRingProgressRequest_digit_tag 3
@ -623,6 +677,10 @@ extern "C" {
#define alox_RestartRequest_client_id_tag 1 #define alox_RestartRequest_client_id_tag 1
#define alox_RestartResponse_success_tag 1 #define alox_RestartResponse_success_tag 1
#define alox_RestartResponse_client_id_tag 2 #define alox_RestartResponse_client_id_tag 2
#define alox_SetLogLevelRequest_write_tag 1
#define alox_SetLogLevelRequest_level_tag 2
#define alox_SetLogLevelResponse_success_tag 1
#define alox_SetLogLevelResponse_level_tag 2
#define alox_OtaStartPayload_total_size_tag 1 #define alox_OtaStartPayload_total_size_tag 1
#define alox_OtaPayload_seq_tag 1 #define alox_OtaPayload_seq_tag 1
#define alox_OtaPayload_data_tag 2 #define alox_OtaPayload_data_tag 2
@ -671,6 +729,10 @@ extern "C" {
#define alox_UartMessage_tap_notify_response_tag 30 #define alox_UartMessage_tap_notify_response_tag 30
#define alox_UartMessage_cache_status_request_tag 33 #define alox_UartMessage_cache_status_request_tag 33
#define alox_UartMessage_cache_status_response_tag 34 #define alox_UartMessage_cache_status_response_tag 34
#define alox_UartMessage_espnow_echo_ping_request_tag 35
#define alox_UartMessage_espnow_echo_ping_response_tag 36
#define alox_UartMessage_set_log_level_request_tag 37
#define alox_UartMessage_set_log_level_response_tag 38
/* Struct field encoding specification for nanopb */ /* Struct field encoding specification for nanopb */
#define alox_UartMessage_FIELDLIST(X, a) \ #define alox_UartMessage_FIELDLIST(X, a) \
@ -703,7 +765,11 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,battery_status_response,payload.batt
X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify_request,payload.tap_notify_request), 29) \ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify_request,payload.tap_notify_request), 29) \
X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify_response,payload.tap_notify_response), 30) \ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify_response,payload.tap_notify_response), 30) \
X(a, STATIC, ONEOF, MESSAGE, (payload,cache_status_request,payload.cache_status_request), 33) \ X(a, STATIC, ONEOF, MESSAGE, (payload,cache_status_request,payload.cache_status_request), 33) \
X(a, STATIC, ONEOF, MESSAGE, (payload,cache_status_response,payload.cache_status_response), 34) X(a, STATIC, ONEOF, MESSAGE, (payload,cache_status_response,payload.cache_status_response), 34) \
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_echo_ping_request,payload.espnow_echo_ping_request), 35) \
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_echo_ping_response,payload.espnow_echo_ping_response), 36) \
X(a, STATIC, ONEOF, MESSAGE, (payload,set_log_level_request,payload.set_log_level_request), 37) \
X(a, STATIC, ONEOF, MESSAGE, (payload,set_log_level_response,payload.set_log_level_response), 38)
#define alox_UartMessage_CALLBACK NULL #define alox_UartMessage_CALLBACK NULL
#define alox_UartMessage_DEFAULT NULL #define alox_UartMessage_DEFAULT NULL
#define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack #define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack
@ -735,6 +801,10 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,cache_status_response,payload.cache_
#define alox_UartMessage_payload_tap_notify_response_MSGTYPE alox_TapNotifyResponse #define alox_UartMessage_payload_tap_notify_response_MSGTYPE alox_TapNotifyResponse
#define alox_UartMessage_payload_cache_status_request_MSGTYPE alox_CacheStatusRequest #define alox_UartMessage_payload_cache_status_request_MSGTYPE alox_CacheStatusRequest
#define alox_UartMessage_payload_cache_status_response_MSGTYPE alox_CacheStatusResponse #define alox_UartMessage_payload_cache_status_response_MSGTYPE alox_CacheStatusResponse
#define alox_UartMessage_payload_espnow_echo_ping_request_MSGTYPE alox_EspNowEchoPingRequest
#define alox_UartMessage_payload_espnow_echo_ping_response_MSGTYPE alox_EspNowEchoPingResponse
#define alox_UartMessage_payload_set_log_level_request_MSGTYPE alox_SetLogLevelRequest
#define alox_UartMessage_payload_set_log_level_response_MSGTYPE alox_SetLogLevelResponse
#define alox_Ack_FIELDLIST(X, a) \ #define alox_Ack_FIELDLIST(X, a) \
@ -934,6 +1004,20 @@ X(a, STATIC, SINGULAR, UINT32, seq, 2)
#define alox_EspNowUnicastTestResponse_CALLBACK NULL #define alox_EspNowUnicastTestResponse_CALLBACK NULL
#define alox_EspNowUnicastTestResponse_DEFAULT NULL #define alox_EspNowUnicastTestResponse_DEFAULT NULL
#define alox_EspNowEchoPingRequest_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, client_id, 1) \
X(a, STATIC, SINGULAR, UINT64, timestamp_us, 2)
#define alox_EspNowEchoPingRequest_CALLBACK NULL
#define alox_EspNowEchoPingRequest_DEFAULT NULL
#define alox_EspNowEchoPingResponse_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, success, 1) \
X(a, STATIC, SINGULAR, UINT32, client_id, 2) \
X(a, STATIC, SINGULAR, UINT64, timestamp_us, 3) \
X(a, STATIC, SINGULAR, UINT32, esp_rtt_us, 4)
#define alox_EspNowEchoPingResponse_CALLBACK NULL
#define alox_EspNowEchoPingResponse_DEFAULT NULL
#define alox_LedRingProgressRequest_FIELDLIST(X, a) \ #define alox_LedRingProgressRequest_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, mode, 1) \ X(a, STATIC, SINGULAR, UINT32, mode, 1) \
X(a, STATIC, SINGULAR, UINT32, progress, 2) \ X(a, STATIC, SINGULAR, UINT32, progress, 2) \
@ -982,6 +1066,18 @@ X(a, STATIC, SINGULAR, UINT32, client_id, 2)
#define alox_RestartResponse_CALLBACK NULL #define alox_RestartResponse_CALLBACK NULL
#define alox_RestartResponse_DEFAULT NULL #define alox_RestartResponse_DEFAULT NULL
#define alox_SetLogLevelRequest_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, write, 1) \
X(a, STATIC, SINGULAR, UINT32, level, 2)
#define alox_SetLogLevelRequest_CALLBACK NULL
#define alox_SetLogLevelRequest_DEFAULT NULL
#define alox_SetLogLevelResponse_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, success, 1) \
X(a, STATIC, SINGULAR, UINT32, level, 2)
#define alox_SetLogLevelResponse_CALLBACK NULL
#define alox_SetLogLevelResponse_DEFAULT NULL
#define alox_OtaStartPayload_FIELDLIST(X, a) \ #define alox_OtaStartPayload_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, total_size, 1) X(a, STATIC, SINGULAR, UINT32, total_size, 1)
#define alox_OtaStartPayload_CALLBACK NULL #define alox_OtaStartPayload_CALLBACK NULL
@ -1057,12 +1153,16 @@ extern const pb_msgdesc_t alox_CacheClientStatus_msg;
extern const pb_msgdesc_t alox_CacheStatusResponse_msg; extern const pb_msgdesc_t alox_CacheStatusResponse_msg;
extern const pb_msgdesc_t alox_EspNowUnicastTestRequest_msg; extern const pb_msgdesc_t alox_EspNowUnicastTestRequest_msg;
extern const pb_msgdesc_t alox_EspNowUnicastTestResponse_msg; extern const pb_msgdesc_t alox_EspNowUnicastTestResponse_msg;
extern const pb_msgdesc_t alox_EspNowEchoPingRequest_msg;
extern const pb_msgdesc_t alox_EspNowEchoPingResponse_msg;
extern const pb_msgdesc_t alox_LedRingProgressRequest_msg; extern const pb_msgdesc_t alox_LedRingProgressRequest_msg;
extern const pb_msgdesc_t alox_LedRingProgressResponse_msg; extern const pb_msgdesc_t alox_LedRingProgressResponse_msg;
extern const pb_msgdesc_t alox_EspNowFindMeRequest_msg; extern const pb_msgdesc_t alox_EspNowFindMeRequest_msg;
extern const pb_msgdesc_t alox_EspNowFindMeResponse_msg; extern const pb_msgdesc_t alox_EspNowFindMeResponse_msg;
extern const pb_msgdesc_t alox_RestartRequest_msg; extern const pb_msgdesc_t alox_RestartRequest_msg;
extern const pb_msgdesc_t alox_RestartResponse_msg; extern const pb_msgdesc_t alox_RestartResponse_msg;
extern const pb_msgdesc_t alox_SetLogLevelRequest_msg;
extern const pb_msgdesc_t alox_SetLogLevelResponse_msg;
extern const pb_msgdesc_t alox_OtaStartPayload_msg; extern const pb_msgdesc_t alox_OtaStartPayload_msg;
extern const pb_msgdesc_t alox_OtaPayload_msg; extern const pb_msgdesc_t alox_OtaPayload_msg;
extern const pb_msgdesc_t alox_OtaEndPayload_msg; extern const pb_msgdesc_t alox_OtaEndPayload_msg;
@ -1099,12 +1199,16 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_CacheStatusResponse_fields &alox_CacheStatusResponse_msg #define alox_CacheStatusResponse_fields &alox_CacheStatusResponse_msg
#define alox_EspNowUnicastTestRequest_fields &alox_EspNowUnicastTestRequest_msg #define alox_EspNowUnicastTestRequest_fields &alox_EspNowUnicastTestRequest_msg
#define alox_EspNowUnicastTestResponse_fields &alox_EspNowUnicastTestResponse_msg #define alox_EspNowUnicastTestResponse_fields &alox_EspNowUnicastTestResponse_msg
#define alox_EspNowEchoPingRequest_fields &alox_EspNowEchoPingRequest_msg
#define alox_EspNowEchoPingResponse_fields &alox_EspNowEchoPingResponse_msg
#define alox_LedRingProgressRequest_fields &alox_LedRingProgressRequest_msg #define alox_LedRingProgressRequest_fields &alox_LedRingProgressRequest_msg
#define alox_LedRingProgressResponse_fields &alox_LedRingProgressResponse_msg #define alox_LedRingProgressResponse_fields &alox_LedRingProgressResponse_msg
#define alox_EspNowFindMeRequest_fields &alox_EspNowFindMeRequest_msg #define alox_EspNowFindMeRequest_fields &alox_EspNowFindMeRequest_msg
#define alox_EspNowFindMeResponse_fields &alox_EspNowFindMeResponse_msg #define alox_EspNowFindMeResponse_fields &alox_EspNowFindMeResponse_msg
#define alox_RestartRequest_fields &alox_RestartRequest_msg #define alox_RestartRequest_fields &alox_RestartRequest_msg
#define alox_RestartResponse_fields &alox_RestartResponse_msg #define alox_RestartResponse_fields &alox_RestartResponse_msg
#define alox_SetLogLevelRequest_fields &alox_SetLogLevelRequest_msg
#define alox_SetLogLevelResponse_fields &alox_SetLogLevelResponse_msg
#define alox_OtaStartPayload_fields &alox_OtaStartPayload_msg #define alox_OtaStartPayload_fields &alox_OtaStartPayload_msg
#define alox_OtaPayload_fields &alox_OtaPayload_msg #define alox_OtaPayload_fields &alox_OtaPayload_msg
#define alox_OtaEndPayload_fields &alox_OtaEndPayload_msg #define alox_OtaEndPayload_fields &alox_OtaEndPayload_msg
@ -1136,6 +1240,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_CacheStatusRequest_size 0 #define alox_CacheStatusRequest_size 0
#define alox_CacheStatusResponse_size 736 #define alox_CacheStatusResponse_size 736
#define alox_ClientInput_size 22 #define alox_ClientInput_size 22
#define alox_EspNowEchoPingRequest_size 17
#define alox_EspNowEchoPingResponse_size 25
#define alox_EspNowFindMeRequest_size 6 #define alox_EspNowFindMeRequest_size 6
#define alox_EspNowFindMeResponse_size 8 #define alox_EspNowFindMeResponse_size 8
#define alox_EspNowUnicastTestRequest_size 12 #define alox_EspNowUnicastTestRequest_size 12
@ -1152,6 +1258,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_OtaStatusPayload_size 24 #define alox_OtaStatusPayload_size 24
#define alox_RestartRequest_size 6 #define alox_RestartRequest_size 6
#define alox_RestartResponse_size 8 #define alox_RestartResponse_size 8
#define alox_SetLogLevelRequest_size 8
#define alox_SetLogLevelResponse_size 8
#define alox_TapEvent_size 16 #define alox_TapEvent_size 16
#define alox_TapNotifyRequest_size 16 #define alox_TapNotifyRequest_size 16
#define alox_TapNotifyResponse_size 20 #define alox_TapNotifyResponse_size 20

View File

@ -29,6 +29,10 @@ enum MessageType {
reserved 28; reserved 28;
/** Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */ /** Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */
CACHE_STATUS = 29; CACHE_STATUS = 29;
/** Host → master → slave → master: timestamp echo round-trip (latency test). */
ESPNOW_ECHO_PING = 30;
/** Host → master: get/set ESP-IDF log level for tag "*" (global). */
SET_LOG_LEVEL = 31;
} }
message UartMessage { message UartMessage {
@ -63,6 +67,10 @@ message UartMessage {
TapNotifyResponse tap_notify_response = 30; TapNotifyResponse tap_notify_response = 30;
CacheStatusRequest cache_status_request = 33; CacheStatusRequest cache_status_request = 33;
CacheStatusResponse cache_status_response = 34; CacheStatusResponse cache_status_response = 34;
EspNowEchoPingRequest espnow_echo_ping_request = 35;
EspNowEchoPingResponse espnow_echo_ping_response = 36;
SetLogLevelRequest set_log_level_request = 37;
SetLogLevelResponse set_log_level_response = 38;
} }
} }
@ -255,6 +263,22 @@ message EspNowUnicastTestResponse {
uint32 seq = 2; uint32 seq = 2;
} }
/** Host → master: ESP-NOW echo ping to one slave (timestamp echoed back). */
message EspNowEchoPingRequest {
uint32 client_id = 1;
/** Microseconds since Unix epoch (host clock). */
uint64 timestamp_us = 2;
}
message EspNowEchoPingResponse {
bool success = 1;
uint32 client_id = 2;
/** Echoed host timestamp from goTool request. */
uint64 timestamp_us = 3;
/** esp_timer_get_time() delta from ping send to pong recv (master→slave→master). */
uint32 esp_rtt_us = 4;
}
// Host master: LED ring on master (client_id=0) and/or slaves via ESP-NOW. // Host master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
// mode: 0=clear, 1=progress (0100 %), 2=digit (010), 3=blink, 4=find-me, 5=all LEDs solid color. // mode: 0=clear, 1=progress (0100 %), 2=digit (010), 3=blink, 4=find-me, 5=all LEDs solid color.
message LedRingProgressRequest { message LedRingProgressRequest {
@ -309,6 +333,18 @@ message RestartResponse {
uint32 client_id = 2; uint32 client_id = 2;
} }
/** Host → master: read/write global log level (esp_log_level_set("*", …)). */
message SetLogLevelRequest {
bool write = 1;
/** esp_log_level_t: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE */
uint32 level = 2;
}
message SetLogLevelResponse {
bool success = 1;
uint32 level = 2;
}
// Host device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS). // Host device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS).
message OtaStartPayload { message OtaStartPayload {
uint32 total_size = 1; uint32 total_size = 1;

View File

@ -52,7 +52,8 @@ void init_uart(QueueHandle_t cmd_queue) {
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
}; };
err = uart_driver_install(UART_NUM, UART_BUF_SIZE * 2, UART_BUF_SIZE, 0, NULL, 0); err = uart_driver_install(UART_NUM, UART_DRIVER_RX_BUF_SIZE,
UART_DRIVER_TX_BUF_SIZE, 0, NULL, 0);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
return; return;

View File

@ -16,7 +16,10 @@
#define UART_RXD_PIN 3 #define UART_RXD_PIN 3
#define UART_BUF_SIZE 2048 #define UART_BUF_SIZE 4096
/** Driver RX ring — must hold a full OTA block burst (~20 × ~215 B frames). */
#define UART_DRIVER_RX_BUF_SIZE 16384
#define UART_DRIVER_TX_BUF_SIZE 4096
#define START_MARKER 0xAA #define START_MARKER 0xAA
#define STOP_MARKER 0xCC #define STOP_MARKER 0xCC
#define MAX_BUF_SIZE 252 #define MAX_BUF_SIZE 252