From f512936d976e438afa5127ecee4cebcd5c2eaa25 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 29 May 2026 20:57:54 +0200 Subject: [PATCH] Add CACHE_STATUS UART poll and dashboard live stream. Combine cached accel and tap in one low-overhead master command for ~16 ms host polling. The dashboard uses a single live-stream toggle plus per-slave accel-stream controls; fix live_stream state so polling is not cleared every slow client refresh. Co-authored-by: Cursor --- goTool/README.md | 13 +- goTool/api_live_stream.go | 52 ++++++ goTool/api_serve.go | 1 + goTool/api_stream.go | 122 ++++++------ goTool/api_tap.go | 34 ---- goTool/client_api.go | 38 ++++ goTool/cmd_cache_status.go | 35 ++++ goTool/cmd_serve.go | 5 +- goTool/dashboard.go | 206 +++++++++----------- goTool/main.go | 5 +- goTool/pb/uart_messages.pb.go | 332 +++++++++++++++++++++++---------- goTool/webui/index.html | 113 +++++------ main/CMakeLists.txt | 1 + main/README.md | 13 ++ main/cmd/cmd_cache_status.c | 83 +++++++++ main/cmd/cmd_cache_status.h | 3 + main/cmd/cmd_handler.c | 2 + main/powerpod.c | 2 + main/proto/uart_messages.pb.c | 6 + main/proto/uart_messages.pb.h | 61 +++++- main/proto/uart_messages.proto | 14 ++ 21 files changed, 764 insertions(+), 377 deletions(-) create mode 100644 goTool/api_live_stream.go create mode 100644 goTool/cmd_cache_status.go create mode 100644 main/cmd/cmd_cache_status.c create mode 100644 main/cmd/cmd_cache_status.h diff --git a/goTool/README.md b/goTool/README.md index 7c4f29c..2b2f128 100644 --- a/goTool/README.md +++ b/goTool/README.md @@ -28,6 +28,7 @@ go run . -port /dev/ttyUSB0 clients | `accel` | `0x18` | Cached slave accel snapshot from master (`ACCEL_SNAPSHOT`); alias `accel-read` | | `tap-notify` | `0x1b` | Get/set which tap kinds (single/double/triple) notify via ESP-NOW (`-set`, `-client`, `-all`, `-single`, `-double`, `-triple`) | | `tap` | `0x1c` | Cached tap snapshot from master (`TAP_SNAPSHOT`); events ≤16 ms old | +| `cache-status` | `0x1d` | Combined 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`) | | `test` | — | Run an automated scenario (JSON configs under `testdata/`) | | `serve` | — | Web dashboard at `http://localhost:8080` (WebSocket live updates) | @@ -98,7 +99,7 @@ Accel polling runs only when at least one connection has `receive_accel: true` * **Tap** — also two layers (notify alone does **not** poll UART): 1. **`set_tap_notify`** — firmware: which tap kinds (single/double/triple) the slave sends to the master over ESP-NOW. -2. **`set_tap_stream`** — this WebSocket connection: poll `TAP_SNAPSHOT` and push `tap` JSON. Events stay visible for **`tap_display_min_ms`** (2000 ms) after first sight. +2. **`set_tap_stream`** — this WebSocket connection: poll `CACHE_STATUS` (tap slice) and push `tap` JSON. Events stay visible for **`tap_display_min_ms`** (2000 ms) after first sight. Tap polling runs only when at least one connection has `receive_tap: true` (via `set_tap_stream`). @@ -239,7 +240,7 @@ The dashboard can configure nodes using the same UART commands as the CLI: | Alle Slaves | per-slave ESP-NOW (Master bleibt unverändert; CLI `-all` setzt auch den Master) | | Unicast test | `unicast-test -client ID` | -HTTP API (used by the web UI): `GET/POST /api/deadzone`, `GET/PUT /api/clients/{id}/accel-stream`, `POST /api/accel-stream` (legacy / `all_clients`), `GET/PUT /api/clients/{id}/tap-notify`, `PUT /api/clients/{id}/tap-receive`, `GET/POST /api/tap-notify`, `GET /api/tap-snapshot`, `GET/POST /api/battery`, `POST /api/led-ring`, `POST /api/unicast-test`, `POST /api/find-me`, `POST /api/restart`, `POST /api/ota` (multipart field `firmware`, max 2 MiB). +HTTP API (used by the web UI): `GET/PUT /api/live-stream`, `GET/POST /api/deadzone`, `GET/PUT /api/clients/{id}/accel-stream`, `POST /api/accel-stream` (legacy / `all_clients`), `GET/PUT /api/clients/{id}/tap-notify`, `GET/POST /api/tap-notify`, `GET /api/tap-snapshot`, `GET/POST /api/battery`, `POST /api/led-ring`, `POST /api/unicast-test`, `POST /api/find-me`, `POST /api/restart`, `POST /api/ota` (multipart field `firmware`, max 2 MiB). **LED ring** (`POST /api/led-ring` and WebSocket `set_led_ring` on `:8081`): @@ -289,16 +290,16 @@ Content-Type: application/json All slaves: `POST /api/tap-notify` with `{"single":true,"double_tap":false,"triple":false,"all_clients":true}`. -**Tap receive** (host-side; dashboard polls `TAP_SNAPSHOT` while enabled): +**Live stream** (dashboard: host-side `CACHE_STATUS` poll ~16 ms; per-slave accel via `accel-stream`): ```http -PUT /api/clients/16/tap-receive +PUT /api/live-stream Content-Type: application/json {"enable": true} -→ {"client_id":16,"enabled":true,"success":true} +→ {"enabled":true,"success":true} ``` -One-shot read (no receive flag): `GET /api/tap-snapshot?client_id=16` → `{"events":[{"client_id":16,"kind":"single","age_ms":4}]}`. +One-shot read: `GET /api/tap-snapshot?client_id=16` → `{"events":[{"client_id":16,"kind":"single","age_ms":4}]}`. CLI: diff --git a/goTool/api_live_stream.go b/goTool/api_live_stream.go new file mode 100644 index 0000000..6adb3d3 --- /dev/null +++ b/goTool/api_live_stream.go @@ -0,0 +1,52 @@ +package main + +import ( + "encoding/json" + "net/http" +) + +type liveStreamAPIResponse struct { + Enabled bool `json:"enabled"` + Success bool `json:"success"` + Error string `json:"error,omitempty"` +} + +func mountLiveStreamAPI(mux *http.ServeMux, hub *wsHub) { + mux.HandleFunc("/api/live-stream", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + serveLiveStreamGet(w, hub) + case http.MethodPut: + serveLiveStreamPut(w, r, hub) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } + }) +} + +func serveLiveStreamGet(w http.ResponseWriter, hub *wsHub) { + enabled := false + if hub != nil { + enabled = hub.liveStreamEnabled() + } + writeJSON(w, http.StatusOK, liveStreamAPIResponse{Enabled: enabled, Success: true}) +} + +func serveLiveStreamPut(w http.ResponseWriter, r *http.Request, hub *wsHub) { + var body struct { + Enable bool `json:"enable"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeJSON(w, http.StatusBadRequest, liveStreamAPIResponse{Error: "invalid JSON"}) + return + } + + if hub != nil { + hub.patchLiveStream(body.Enable) + } + + writeJSON(w, http.StatusOK, liveStreamAPIResponse{ + Enabled: body.Enable, + Success: true, + }) +} diff --git a/goTool/api_serve.go b/goTool/api_serve.go index aab0876..052b2fe 100644 --- a/goTool/api_serve.go +++ b/goTool/api_serve.go @@ -68,6 +68,7 @@ type otaAPIResponse struct { } func mountServeAPI(mux *http.ServeMux, link *managedSerial, hub *wsHub, streamCtl *accelStreamCtl, tapCtl *tapNotifyCtl) { + mountLiveStreamAPI(mux, hub) mountAccelStreamAPI(mux, link, hub, streamCtl) mountTapAPI(mux, link, hub, tapCtl) mountLedRingAPI(mux, link) diff --git a/goTool/api_stream.go b/goTool/api_stream.go index 285d220..9362eb8 100644 --- a/goTool/api_stream.go +++ b/goTool/api_stream.go @@ -434,80 +434,90 @@ func runAccelStreamer(link *managedSerial, hub *accelStreamHub, dash *wsHub, ctl case <-hub.configChanged: resetTicker() case <-tick: + wantAccel := hub.anyWantsAccel() && accelStreamPollingActive(dash, ctl) + wantTap := hub.anyWantsTap() + if !wantAccel && !wantTap { + continue + } + now := time.Now().UnixNano() - if hub.anyWantsAccel() && accelStreamPollingActive(dash, ctl) { - resp, err := link.readAccelSnapshotPoll(0) - if errors.Is(err, errUARTBusy) { + cache, err := link.readCacheStatusPoll() + if errors.Is(err, errUARTBusy) { + if wantAccel { hub.deliver(AccelStreamMessage{ Type: "accel", T: now, Success: false, Error: "uart busy", }) - } else if err != nil { - hub.deliver(AccelStreamMessage{ - Type: "accel", - T: now, - Success: false, - Error: err.Error(), - }) - } else { - clients := make([]AccelClientSample, 0, len(resp.GetSamples())) - for _, s := range resp.GetSamples() { - clients = append(clients, AccelClientSample{ - ClientID: s.GetClientId(), - Valid: s.GetValid(), - X: s.GetX(), - Y: s.GetY(), - Z: s.GetZ(), - AgeMs: s.GetAgeMs(), - }) - } - hub.deliver(AccelStreamMessage{ - Type: "accel", - T: now, - Success: true, - Clients: clients, - }) } - } - if hub.anyWantsTap() { - nowMs := time.Now().UnixNano() - resp, err := link.readTapSnapshotPoll(0) - if errors.Is(err, errUARTBusy) { + if wantTap { hub.deliverTap(TapStreamMessage{ Type: "tap", - T: nowMs, + T: now, Success: false, Error: "uart busy", }) - } else if err != nil { - hub.deliverTap(TapStreamMessage{ - Type: "tap", - T: nowMs, + } + continue + } + if err != nil { + if wantAccel { + hub.deliver(AccelStreamMessage{ + Type: "accel", + T: now, Success: false, Error: err.Error(), }) - } else { - fresh := make([]TapClientEvent, 0, len(resp.GetEvents())) - for _, e := range resp.GetEvents() { - if !e.GetValid() { - continue - } - fresh = append(fresh, TapClientEvent{ - ClientID: e.GetClientId(), - Valid: true, - Kind: tapKindLabelPB(e.GetKind()), - AgeMs: e.GetAgeMs(), - }) - } - events := hub.ingestTapEvents(fresh) - if len(events) == 0 { - continue - } + } + if wantTap { hub.deliverTap(TapStreamMessage{ Type: "tap", - T: nowMs, + T: now, + Success: false, + Error: err.Error(), + }) + } + continue + } + + if wantAccel { + clients := make([]AccelClientSample, 0, len(cache.GetAccel())) + for _, s := range cache.GetAccel() { + clients = append(clients, AccelClientSample{ + ClientID: s.GetClientId(), + Valid: s.GetValid(), + X: s.GetX(), + Y: s.GetY(), + Z: s.GetZ(), + AgeMs: s.GetAgeMs(), + }) + } + hub.deliver(AccelStreamMessage{ + Type: "accel", + T: now, + Success: true, + Clients: clients, + }) + } + if wantTap { + fresh := make([]TapClientEvent, 0, len(cache.GetTaps())) + for _, e := range cache.GetTaps() { + if !e.GetValid() { + continue + } + fresh = append(fresh, TapClientEvent{ + ClientID: e.GetClientId(), + Valid: true, + Kind: tapKindLabelPB(e.GetKind()), + AgeMs: e.GetAgeMs(), + }) + } + events := hub.ingestTapEvents(fresh) + if len(events) > 0 { + hub.deliverTap(TapStreamMessage{ + Type: "tap", + T: now, Success: true, Events: events, }) diff --git a/goTool/api_tap.go b/goTool/api_tap.go index 25576d3..35e8091 100644 --- a/goTool/api_tap.go +++ b/goTool/api_tap.go @@ -38,13 +38,6 @@ type tapEventView struct { AgeMs uint32 `json:"age_ms"` } -type tapReceiveAPIResponse struct { - ClientID uint32 `json:"client_id"` - Enabled bool `json:"enabled"` - Success bool `json:"success"` - Error string `json:"error,omitempty"` -} - func mountTapAPI(mux *http.ServeMux, link *managedSerial, hub *wsHub, tapCtl *tapNotifyCtl) { mux.HandleFunc("GET /api/clients/{clientID}/tap-notify", func(w http.ResponseWriter, r *http.Request) { clientID, err := parsePathClientID(r) @@ -62,15 +55,6 @@ func mountTapAPI(mux *http.ServeMux, link *managedSerial, hub *wsHub, tapCtl *ta } serveClientTapNotifyPut(w, r, clientID, link, hub, tapCtl) }) - mux.HandleFunc("PUT /api/clients/{clientID}/tap-receive", func(w http.ResponseWriter, r *http.Request) { - clientID, err := parsePathClientID(r) - if err != nil { - writeJSON(w, http.StatusBadRequest, tapReceiveAPIResponse{Error: err.Error()}) - return - } - serveClientTapReceivePut(w, r, clientID, hub) - }) - mux.HandleFunc("/api/tap-notify", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: @@ -181,24 +165,6 @@ func serveTapNotifyPost(w http.ResponseWriter, r *http.Request, link *managedSer writeJSON(w, status, out) } -func serveClientTapReceivePut(w http.ResponseWriter, r *http.Request, clientID uint32, hub *wsHub) { - var body struct { - Enable bool `json:"enable"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - writeJSON(w, http.StatusBadRequest, tapReceiveAPIResponse{Error: "invalid JSON"}) - return - } - if hub != nil { - hub.patchClientTapReceive(clientID, body.Enable) - } - writeJSON(w, http.StatusOK, tapReceiveAPIResponse{ - ClientID: clientID, - Enabled: body.Enable, - Success: true, - }) -} - func applyTapNotifyAll(link *managedSerial, hub *wsHub, tapCtl *tapNotifyCtl, single, doubleTap, triple bool) (uint32, error) { resp, err := link.TapNotify(&pb.TapNotifyRequest{ Write: true, diff --git a/goTool/client_api.go b/goTool/client_api.go index d092acc..0a3aac8 100644 --- a/goTool/client_api.go +++ b/goTool/client_api.go @@ -192,6 +192,36 @@ func (m *managedSerial) tapNotifyVia( return resp, err } +func (m *managedSerial) readCacheStatusPoll() (*pb.CacheStatusResponse, error) { + payload, err := m.exchangePoll(byte(pb.MessageType_CACHE_STATUS), "CACHE_STATUS") + if err != nil { + return nil, err + } + return decodeCacheStatusPayload(payload) +} + +func decodeCacheStatusPayload(payload []byte) (*pb.CacheStatusResponse, error) { + if len(payload) < 1 { + return nil, fmt.Errorf("empty response payload") + } + if payload[0] != byte(pb.MessageType_CACHE_STATUS) { + return nil, fmt.Errorf("unexpected command id 0x%02x (want 0x%02x)", + payload[0], byte(pb.MessageType_CACHE_STATUS)) + } + var msg pb.UartMessage + if err := proto.Unmarshal(payload[1:], &msg); err != nil { + return nil, fmt.Errorf("decode: %w", err) + } + if msg.GetType() != pb.MessageType_CACHE_STATUS { + return nil, fmt.Errorf("unexpected type %v", msg.GetType()) + } + r := msg.GetCacheStatusResponse() + if r == nil { + return nil, fmt.Errorf("missing cache_status_response") + } + return r, nil +} + func (m *managedSerial) readTapSnapshotPoll(clientID uint32) (*pb.TapSnapshotResponse, error) { msg := &pb.UartMessage{ Type: pb.MessageType_TAP_SNAPSHOT, @@ -410,6 +440,14 @@ func (s *serialPort) TapNotify(req *pb.TapNotifyRequest) (*pb.TapNotifyResponse, return r, nil } +func (s *serialPort) readCacheStatus() (*pb.CacheStatusResponse, error) { + payload, err := s.exchange(byte(pb.MessageType_CACHE_STATUS), "CACHE_STATUS") + if err != nil { + return nil, err + } + return decodeCacheStatusPayload(payload) +} + func (s *serialPort) readTapSnapshot(clientID uint32) (*pb.TapSnapshotResponse, error) { msg := &pb.UartMessage{ Type: pb.MessageType_TAP_SNAPSHOT, diff --git a/goTool/cmd_cache_status.go b/goTool/cmd_cache_status.go new file mode 100644 index 0000000..b7c3bb4 --- /dev/null +++ b/goTool/cmd_cache_status.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" +) + +func runCacheStatus(sp *serialPort) error { + r, err := sp.readCacheStatus() + if err != nil { + return err + } + accel := r.GetAccel() + if len(accel) == 0 { + fmt.Println("accel: (none — no slaves with accel stream enabled)") + } else { + for _, s := range accel { + if !s.GetValid() { + fmt.Printf("accel client %d: no sample yet\n", s.GetClientId()) + continue + } + fmt.Printf("accel client %d: x=%d y=%d z=%d (age %d ms)\n", + s.GetClientId(), s.GetX(), s.GetY(), s.GetZ(), s.GetAgeMs()) + } + } + taps := r.GetTaps() + if len(taps) == 0 { + fmt.Println("tap: (none pending)") + } else { + for _, e := range taps { + fmt.Printf("tap client %d: %s (age %d ms)\n", + e.GetClientId(), tapKindLabel(e.GetKind()), e.GetAgeMs()) + } + } + return nil +} diff --git a/goTool/cmd_serve.go b/goTool/cmd_serve.go index 8a1df12..1bd4450 100644 --- a/goTool/cmd_serve.go +++ b/goTool/cmd_serve.go @@ -43,8 +43,7 @@ func runServe(portName string, baud int, args []string) error { defer close(stop) go runPoller(link, portName, hub, streamCtl, tapCtl, *interval, stop) go runBatteryPoller(link, hub, 5*time.Second, stop) - go runAccelDashboardPoller(link, hub, *accelInterval, stop) - go runTapDashboardPoller(link, hub, *accelInterval, stop) + go runCacheStatusDashboardPoller(link, hub, *accelInterval, stop) var apiSrv *http.Server if *apiAddr != "" { @@ -77,7 +76,7 @@ func runServe(portName string, baud int, args []string) error { } mux.Handle("/", http.FileServer(http.FS(ui))) - log.Printf("dashboard http://localhost%s (UART %s @ %d baud, poll %s, accel %s, auto-reconnect)", + log.Printf("dashboard http://localhost%s (UART %s @ %d baud, poll %s, live-stream %s, auto-reconnect)", *addr, portName, baud, interval.String(), accelInterval.String()) if *apiAddr == "" { log.Printf("external API disabled (-api-addr \"\")") diff --git a/goTool/dashboard.go b/goTool/dashboard.go index d1e8d15..0045989 100644 --- a/goTool/dashboard.go +++ b/goTool/dashboard.go @@ -44,8 +44,6 @@ type ClientView struct { TapNotifySingle bool `json:"tap_notify_single"` TapNotifyDouble bool `json:"tap_notify_double"` TapNotifyTriple bool `json:"tap_notify_triple"` - /** Host-side: poll master tap cache for this slave (~16 ms). */ - TapReceive bool `json:"tap_receive"` LastTap string `json:"last_tap,omitempty"` LastTapAt int64 `json:"last_tap_at,omitempty"` Lipo1 lipoReadingJSON `json:"lipo1"` @@ -59,14 +57,17 @@ type DashboardState struct { UARTConnected bool `json:"uart_connected"` SerialOK bool `json:"serial_ok"` SerialError string `json:"serial_error,omitempty"` + /** Host: fast CACHE_STATUS poll (~16 ms) for accel + tap. */ + LiveStream bool `json:"live_stream"` Master MasterView `json:"master"` Clients []ClientView `json:"clients"` } type wsHub struct { - mu sync.RWMutex - clients map[*websocket.Conn]struct{} - state DashboardState + mu sync.RWMutex + clients map[*websocket.Conn]struct{} + state DashboardState + liveStream bool } func newWSHub() *wsHub { @@ -76,9 +77,9 @@ func newWSHub() *wsHub { func (h *wsHub) setState(st DashboardState) { h.mu.Lock() prev := h.state - st.Clients = preserveClientAccel(st.Clients, prev.Clients) + st.LiveStream = prev.LiveStream + st.Clients = preserveClientAccel(st.Clients, prev.Clients, st.LiveStream) st.Clients = preserveClientBattery(st.Clients, prev.Clients) - st.Clients = preserveClientTapReceive(st.Clients, prev.Clients) st.Clients = preserveClientTap(st.Clients, prev.Clients) if !st.Master.Lipo1.Valid && !st.Master.Lipo2.Valid { if prev.Master.Lipo1.Valid || prev.Master.Lipo2.Valid { @@ -87,6 +88,7 @@ func (h *wsHub) setState(st DashboardState) { st.Master.BatteryAgeMs = prev.Master.BatteryAgeMs } } + h.liveStream = st.LiveStream h.state = st conns := make([]*websocket.Conn, 0, len(h.clients)) for c := range h.clients { @@ -150,7 +152,7 @@ func applyAccelSamples(clients []ClientView, samples []*pb.AccelSample) []Client return out } -func preserveClientAccel(newClients, oldClients []ClientView) []ClientView { +func preserveClientAccel(newClients, oldClients []ClientView, liveStream bool) []ClientView { if len(oldClients) == 0 { return newClients } @@ -161,7 +163,15 @@ func preserveClientAccel(newClients, oldClients []ClientView) []ClientView { out := make([]ClientView, len(newClients)) for i, c := range newClients { out[i] = c - if !c.AccelStream { + if !liveStream && !c.AccelStream { + continue + } + if liveStream && !c.AccelStream { + out[i].AccelValid = false + out[i].AccelX = 0 + out[i].AccelY = 0 + out[i].AccelZ = 0 + out[i].AccelAgeMs = 0 continue } prev, ok := oldByID[c.ID] @@ -224,45 +234,6 @@ func anyClientTapNotify(clients []ClientView) bool { return false } -func anyClientTapReceive(clients []ClientView) bool { - for _, c := range clients { - if c.TapReceive { - return true - } - } - return false -} - -func preserveClientTapReceive(newClients, oldClients []ClientView) []ClientView { - if len(oldClients) == 0 { - return newClients - } - oldByID := make(map[uint32]ClientView, len(oldClients)) - for _, c := range oldClients { - oldByID[c.ID] = c - } - out := make([]ClientView, len(newClients)) - for i, c := range newClients { - out[i] = c - prev, ok := oldByID[c.ID] - if !ok { - continue - } - out[i].TapReceive = prev.TapReceive - if !prev.TapReceive { - continue - } - if c.LastTap == "" && prev.LastTap != "" { - cutoff := time.Now().Add(-clientTapDisplayMinMs * time.Millisecond).UnixMilli() - if prev.LastTapAt >= cutoff { - out[i].LastTap = prev.LastTap - out[i].LastTapAt = prev.LastTapAt - } - } - } - return out -} - func tapKindLabelPB(k pb.TapKind) string { switch k { case pb.TapKind_TAP_SINGLE: @@ -293,7 +264,7 @@ func applyTapEvents(clients []ClientView, events []*pb.TapEvent) []ClientView { out := make([]ClientView, len(clients)) for i, c := range clients { out[i] = c - if !c.TapReceive { + if !clientTapNotifyAny(c) { continue } e, ok := byID[c.ID] @@ -308,6 +279,10 @@ func applyTapEvents(clients []ClientView, events []*pb.TapEvent) []ClientView { const clientTapDisplayMinMs = 2000 +func clientTapNotifyAny(c ClientView) bool { + return c.TapNotifySingle || c.TapNotifyDouble || c.TapNotifyTriple +} + func preserveClientTap(newClients, oldClients []ClientView) []ClientView { if len(oldClients) == 0 { return newClients @@ -379,10 +354,51 @@ func (h *wsHub) anyTapNotifyEnabled() bool { return anyClientTapNotify(h.state.Clients) } -func (h *wsHub) anyTapReceiveEnabled() bool { +func (h *wsHub) liveStreamEnabled() bool { h.mu.RLock() defer h.mu.RUnlock() - return anyClientTapReceive(h.state.Clients) + return h.liveStream +} + +func (h *wsHub) snapshotClients() []ClientView { + h.mu.RLock() + defer h.mu.RUnlock() + out := make([]ClientView, len(h.state.Clients)) + copy(out, h.state.Clients) + return out +} + +// patchLiveStream toggles host CACHE_STATUS polling (~16 ms). +func (h *wsHub) patchLiveStream(enabled bool) { + h.mu.Lock() + h.liveStream = enabled + st := h.state + st.LiveStream = enabled + if !enabled { + for i := range st.Clients { + st.Clients[i].AccelValid = false + st.Clients[i].AccelX = 0 + st.Clients[i].AccelY = 0 + st.Clients[i].AccelZ = 0 + st.Clients[i].AccelAgeMs = 0 + st.Clients[i].LastTap = "" + st.Clients[i].LastTapAt = 0 + } + } + h.state = st + conns := make([]*websocket.Conn, 0, len(h.clients)) + for c := range h.clients { + conns = append(conns, c) + } + h.mu.Unlock() + + data, err := json.Marshal(st) + if err != nil { + return + } + for _, c := range conns { + _ = c.WriteMessage(websocket.TextMessage, data) + } } // patchClientTapNotify updates tap notify flags immediately (e.g. after REST) and pushes WS. @@ -420,6 +436,9 @@ func (h *wsHub) patchClientTapNotify(clientID uint32, single, doubleTap, triple // mergeAccel updates cached accel on clients and pushes state to dashboard WebSockets. func (h *wsHub) mergeAccel(samples []*pb.AccelSample) { + if !h.liveStreamEnabled() { + return + } h.mu.Lock() st := h.state st.Clients = applyAccelSamples(st.Clients, samples) @@ -440,38 +459,8 @@ func (h *wsHub) mergeAccel(samples []*pb.AccelSample) { } } -func (h *wsHub) patchClientTapReceive(clientID uint32, enabled bool) { - h.mu.Lock() - for i := range h.state.Clients { - if h.state.Clients[i].ID != clientID { - continue - } - h.state.Clients[i].TapReceive = enabled - if !enabled { - h.state.Clients[i].LastTap = "" - h.state.Clients[i].LastTapAt = 0 - } - break - } - st := h.state - st.UpdatedAt = time.Now().Format(time.RFC3339) - conns := make([]*websocket.Conn, 0, len(h.clients)) - for c := range h.clients { - conns = append(conns, c) - } - h.mu.Unlock() - - data, err := json.Marshal(st) - if err != nil { - return - } - for _, c := range conns { - _ = c.WriteMessage(websocket.TextMessage, data) - } -} - func (h *wsHub) mergeTap(events []*pb.TapEvent) { - if len(events) == 0 { + if len(events) == 0 || !h.liveStreamEnabled() { return } h.mu.Lock() @@ -565,25 +554,16 @@ func pollDashboard(link *managedSerial, portName string, last *DashboardState, s st.Clients = append(st.Clients, cv) } applyBatteryToState(link, &st) - if anyClientAccelStream(st.Clients) { - for i := range st.Clients { - if !st.Clients[i].AccelStream { - continue - } - if dz, err := readDeadzonePoll(link, st.Clients[i].ID); err == nil { - st.Clients[i].Deadzone = dz - } - } - if snap, err := link.readAccelSnapshotPoll(0); err == nil { - st.Clients = applyAccelSamples(st.Clients, snap.GetSamples()) - } - } else { + if last == nil || !last.LiveStream { for i, c := range clients { if dz, err := readDeadzonePoll(link, c.GetId()); err == nil { st.Clients[i].Deadzone = dz } } } + if last != nil { + st.LiveStream = last.LiveStream + } if streamCtl != nil { streamCtl.SyncFromClients(st.Clients) } @@ -647,7 +627,7 @@ func runBatteryPoller(link *managedSerial, hub *wsHub, interval time.Duration, s } } -func runAccelDashboardPoller(link *managedSerial, hub *wsHub, interval time.Duration, stop <-chan struct{}) { +func runCacheStatusDashboardPoller(link *managedSerial, hub *wsHub, interval time.Duration, stop <-chan struct{}) { ticker := time.NewTicker(interval) defer ticker.Stop() @@ -656,35 +636,15 @@ func runAccelDashboardPoller(link *managedSerial, hub *wsHub, interval time.Dura case <-stop: return case <-ticker.C: - if hub.clientCount() == 0 || !hub.anyAccelStreamEnabled() { + if !hub.liveStreamEnabled() { continue } - snap, err := link.readAccelSnapshotPoll(0) + cache, err := link.readCacheStatusPoll() if err != nil { continue } - hub.mergeAccel(snap.GetSamples()) - } - } -} - -func runTapDashboardPoller(link *managedSerial, hub *wsHub, interval time.Duration, stop <-chan struct{}) { - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - select { - case <-stop: - return - case <-ticker.C: - if hub.clientCount() == 0 || !hub.anyTapReceiveEnabled() { - continue - } - snap, err := link.readTapSnapshotPoll(0) - if err != nil { - continue - } - hub.mergeTap(snap.GetEvents()) + hub.mergeAccel(cache.GetAccel()) + hub.mergeTap(cache.GetTaps()) } } } @@ -752,14 +712,16 @@ func runPoller(link *managedSerial, portName string, hub *wsHub, streamCtl *acce var lastGood DashboardState publish := func() { st := pollDashboard(link, portName, &lastGood, streamCtl, tapCtl) + hub.setState(st) if st.UARTConnected && st.SerialOK { - lastGood = st + hub.mu.RLock() + lastGood = hub.state + hub.mu.RUnlock() } if st.UARTConnected && !uartUp { log.Printf("UART %s connected", portName) } uartUp = st.UARTConnected - hub.setState(st) } publish() diff --git a/goTool/main.go b/goTool/main.go index af0771f..c9834c7 100644 --- a/goTool/main.go +++ b/goTool/main.go @@ -19,6 +19,7 @@ func usage() { fmt.Fprintf(os.Stderr, " tap-notify get/set which tap kinds notify via ESP-NOW\n") fmt.Fprintf(os.Stderr, " tap read cached tap events from master\n") fmt.Fprintf(os.Stderr, " accel read cached slave accel snapshot from master\n") + fmt.Fprintf(os.Stderr, " cache-status combined 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, " test run automated scenario (see testdata/)\n") fmt.Fprintf(os.Stderr, " serve web dashboard (Bootstrap + WebSocket)\n") @@ -53,7 +54,7 @@ func main() { os.Exit(2) } runErr = runServe(*portName, *baud, flag.Args()[1:]) - case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-notify", "tap_notify", "tap", "accel", "accel-read", "accel_read", "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", "tap", "accel", "accel-read", "accel_read", "cache-status", "cache_status", "unicast-test", "unicast_test", "led-ring", "led_ring", "find-me", "find_me", "restart", "ota", "ota-progress", "ota_progress": if *portName == "" { fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd) usage() @@ -77,6 +78,8 @@ func main() { runErr = runTapSnapshot(sp, flag.Args()[1:]) case "accel", "accel-read", "accel_read": runErr = runAccel(sp) + case "cache-status", "cache_status": + runErr = runCacheStatus(sp) case "unicast-test", "unicast_test": runErr = runUnicastTest(sp, flag.Args()[1:]) case "led-ring", "led_ring": diff --git a/goTool/pb/uart_messages.pb.go b/goTool/pb/uart_messages.pb.go index 6d67952..9aeb138 100644 --- a/goTool/pb/uart_messages.pb.go +++ b/goTool/pb/uart_messages.pb.go @@ -46,6 +46,8 @@ const ( MessageType_BATTERY_STATUS MessageType = 26 MessageType_TAP_NOTIFY MessageType = 27 MessageType_TAP_SNAPSHOT MessageType = 28 + // * Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). + MessageType_CACHE_STATUS MessageType = 29 ) // Enum value maps for MessageType. @@ -73,6 +75,7 @@ var ( 26: "BATTERY_STATUS", 27: "TAP_NOTIFY", 28: "TAP_SNAPSHOT", + 29: "CACHE_STATUS", } MessageType_value = map[string]int32{ "UNKNOWN": 0, @@ -97,6 +100,7 @@ var ( "BATTERY_STATUS": 26, "TAP_NOTIFY": 27, "TAP_SNAPSHOT": 28, + "CACHE_STATUS": 29, } ) @@ -215,6 +219,8 @@ type UartMessage struct { // *UartMessage_TapNotifyResponse // *UartMessage_TapSnapshotRequest // *UartMessage_TapSnapshotResponse + // *UartMessage_CacheStatusRequest + // *UartMessage_CacheStatusResponse Payload isUartMessage_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -543,6 +549,24 @@ func (x *UartMessage) GetTapSnapshotResponse() *TapSnapshotResponse { return nil } +func (x *UartMessage) GetCacheStatusRequest() *CacheStatusRequest { + if x != nil { + if x, ok := x.Payload.(*UartMessage_CacheStatusRequest); ok { + return x.CacheStatusRequest + } + } + return nil +} + +func (x *UartMessage) GetCacheStatusResponse() *CacheStatusResponse { + if x != nil { + if x, ok := x.Payload.(*UartMessage_CacheStatusResponse); ok { + return x.CacheStatusResponse + } + } + return nil +} + type isUartMessage_Payload interface { isUartMessage_Payload() } @@ -671,6 +695,14 @@ type UartMessage_TapSnapshotResponse struct { TapSnapshotResponse *TapSnapshotResponse `protobuf:"bytes,32,opt,name=tap_snapshot_response,json=tapSnapshotResponse,proto3,oneof"` } +type UartMessage_CacheStatusRequest struct { + CacheStatusRequest *CacheStatusRequest `protobuf:"bytes,33,opt,name=cache_status_request,json=cacheStatusRequest,proto3,oneof"` +} + +type UartMessage_CacheStatusResponse struct { + CacheStatusResponse *CacheStatusResponse `protobuf:"bytes,34,opt,name=cache_status_response,json=cacheStatusResponse,proto3,oneof"` +} + func (*UartMessage_AckPayload) isUartMessage_Payload() {} func (*UartMessage_EchoPayload) isUartMessage_Payload() {} @@ -733,6 +765,10 @@ func (*UartMessage_TapSnapshotRequest) isUartMessage_Payload() {} func (*UartMessage_TapSnapshotResponse) isUartMessage_Payload() {} +func (*UartMessage_CacheStatusRequest) isUartMessage_Payload() {} + +func (*UartMessage_CacheStatusResponse) isUartMessage_Payload() {} + type Ack struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -2163,6 +2199,97 @@ func (x *TapSnapshotResponse) GetEvents() []*TapEvent { return nil } +// * Host → master: one-shot read of subscribed cached slave data (no request body). +type CacheStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CacheStatusRequest) Reset() { + *x = CacheStatusRequest{} + mi := &file_uart_messages_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CacheStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CacheStatusRequest) ProtoMessage() {} + +func (x *CacheStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[24] + 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 CacheStatusRequest.ProtoReflect.Descriptor instead. +func (*CacheStatusRequest) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{24} +} + +type CacheStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // * Slaves with accel_stream_enabled. + Accel []*AccelSample `protobuf:"bytes,1,rep,name=accel,proto3" json:"accel,omitempty"` + // * Slaves with any tap notify flag; pending taps are consumed (like TAP_SNAPSHOT). + Taps []*TapEvent `protobuf:"bytes,2,rep,name=taps,proto3" json:"taps,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CacheStatusResponse) Reset() { + *x = CacheStatusResponse{} + mi := &file_uart_messages_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CacheStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CacheStatusResponse) ProtoMessage() {} + +func (x *CacheStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[25] + 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 CacheStatusResponse.ProtoReflect.Descriptor instead. +func (*CacheStatusResponse) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{25} +} + +func (x *CacheStatusResponse) GetAccel() []*AccelSample { + if x != nil { + return x.Accel + } + return nil +} + +func (x *CacheStatusResponse) GetTaps() []*TapEvent { + if x != nil { + return x.Taps + } + return nil +} + type EspNowUnicastTestRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ClientId uint32 `protobuf:"varint,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` @@ -2173,7 +2300,7 @@ type EspNowUnicastTestRequest struct { func (x *EspNowUnicastTestRequest) Reset() { *x = EspNowUnicastTestRequest{} - mi := &file_uart_messages_proto_msgTypes[24] + mi := &file_uart_messages_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2185,7 +2312,7 @@ func (x *EspNowUnicastTestRequest) String() string { func (*EspNowUnicastTestRequest) ProtoMessage() {} func (x *EspNowUnicastTestRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[24] + mi := &file_uart_messages_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2198,7 +2325,7 @@ func (x *EspNowUnicastTestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowUnicastTestRequest.ProtoReflect.Descriptor instead. func (*EspNowUnicastTestRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{24} + return file_uart_messages_proto_rawDescGZIP(), []int{26} } func (x *EspNowUnicastTestRequest) GetClientId() uint32 { @@ -2225,7 +2352,7 @@ type EspNowUnicastTestResponse struct { func (x *EspNowUnicastTestResponse) Reset() { *x = EspNowUnicastTestResponse{} - mi := &file_uart_messages_proto_msgTypes[25] + mi := &file_uart_messages_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2237,7 +2364,7 @@ func (x *EspNowUnicastTestResponse) String() string { func (*EspNowUnicastTestResponse) ProtoMessage() {} func (x *EspNowUnicastTestResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[25] + mi := &file_uart_messages_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2250,7 +2377,7 @@ func (x *EspNowUnicastTestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowUnicastTestResponse.ProtoReflect.Descriptor instead. func (*EspNowUnicastTestResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{25} + return file_uart_messages_proto_rawDescGZIP(), []int{27} } func (x *EspNowUnicastTestResponse) GetSuccess() bool { @@ -2297,7 +2424,7 @@ type LedRingProgressRequest struct { func (x *LedRingProgressRequest) Reset() { *x = LedRingProgressRequest{} - mi := &file_uart_messages_proto_msgTypes[26] + mi := &file_uart_messages_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2309,7 +2436,7 @@ func (x *LedRingProgressRequest) String() string { func (*LedRingProgressRequest) ProtoMessage() {} func (x *LedRingProgressRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[26] + mi := &file_uart_messages_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2322,7 +2449,7 @@ func (x *LedRingProgressRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LedRingProgressRequest.ProtoReflect.Descriptor instead. func (*LedRingProgressRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{26} + return file_uart_messages_proto_rawDescGZIP(), []int{28} } func (x *LedRingProgressRequest) GetMode() uint32 { @@ -2423,7 +2550,7 @@ type LedRingProgressResponse struct { func (x *LedRingProgressResponse) Reset() { *x = LedRingProgressResponse{} - mi := &file_uart_messages_proto_msgTypes[27] + mi := &file_uart_messages_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2435,7 +2562,7 @@ func (x *LedRingProgressResponse) String() string { func (*LedRingProgressResponse) ProtoMessage() {} func (x *LedRingProgressResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[27] + mi := &file_uart_messages_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2448,7 +2575,7 @@ func (x *LedRingProgressResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LedRingProgressResponse.ProtoReflect.Descriptor instead. func (*LedRingProgressResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{27} + return file_uart_messages_proto_rawDescGZIP(), []int{29} } func (x *LedRingProgressResponse) GetSuccess() bool { @@ -2503,7 +2630,7 @@ type EspNowFindMeRequest struct { func (x *EspNowFindMeRequest) Reset() { *x = EspNowFindMeRequest{} - mi := &file_uart_messages_proto_msgTypes[28] + mi := &file_uart_messages_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2515,7 +2642,7 @@ func (x *EspNowFindMeRequest) String() string { func (*EspNowFindMeRequest) ProtoMessage() {} func (x *EspNowFindMeRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[28] + mi := &file_uart_messages_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2528,7 +2655,7 @@ func (x *EspNowFindMeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowFindMeRequest.ProtoReflect.Descriptor instead. func (*EspNowFindMeRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{28} + return file_uart_messages_proto_rawDescGZIP(), []int{30} } func (x *EspNowFindMeRequest) GetClientId() uint32 { @@ -2548,7 +2675,7 @@ type EspNowFindMeResponse struct { func (x *EspNowFindMeResponse) Reset() { *x = EspNowFindMeResponse{} - mi := &file_uart_messages_proto_msgTypes[29] + mi := &file_uart_messages_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2560,7 +2687,7 @@ func (x *EspNowFindMeResponse) String() string { func (*EspNowFindMeResponse) ProtoMessage() {} func (x *EspNowFindMeResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[29] + mi := &file_uart_messages_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2573,7 +2700,7 @@ func (x *EspNowFindMeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowFindMeResponse.ProtoReflect.Descriptor instead. func (*EspNowFindMeResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{29} + return file_uart_messages_proto_rawDescGZIP(), []int{31} } func (x *EspNowFindMeResponse) GetSuccess() bool { @@ -2600,7 +2727,7 @@ type RestartRequest struct { func (x *RestartRequest) Reset() { *x = RestartRequest{} - mi := &file_uart_messages_proto_msgTypes[30] + mi := &file_uart_messages_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2612,7 +2739,7 @@ func (x *RestartRequest) String() string { func (*RestartRequest) ProtoMessage() {} func (x *RestartRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[30] + mi := &file_uart_messages_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2625,7 +2752,7 @@ func (x *RestartRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartRequest.ProtoReflect.Descriptor instead. func (*RestartRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{30} + return file_uart_messages_proto_rawDescGZIP(), []int{32} } func (x *RestartRequest) GetClientId() uint32 { @@ -2645,7 +2772,7 @@ type RestartResponse struct { func (x *RestartResponse) Reset() { *x = RestartResponse{} - mi := &file_uart_messages_proto_msgTypes[31] + mi := &file_uart_messages_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2657,7 +2784,7 @@ func (x *RestartResponse) String() string { func (*RestartResponse) ProtoMessage() {} func (x *RestartResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[31] + mi := &file_uart_messages_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2670,7 +2797,7 @@ func (x *RestartResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartResponse.ProtoReflect.Descriptor instead. func (*RestartResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{31} + return file_uart_messages_proto_rawDescGZIP(), []int{33} } func (x *RestartResponse) GetSuccess() bool { @@ -2697,7 +2824,7 @@ type OtaStartPayload struct { func (x *OtaStartPayload) Reset() { *x = OtaStartPayload{} - mi := &file_uart_messages_proto_msgTypes[32] + mi := &file_uart_messages_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2709,7 +2836,7 @@ func (x *OtaStartPayload) String() string { func (*OtaStartPayload) ProtoMessage() {} func (x *OtaStartPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[32] + mi := &file_uart_messages_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2722,7 +2849,7 @@ func (x *OtaStartPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaStartPayload.ProtoReflect.Descriptor instead. func (*OtaStartPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{32} + return file_uart_messages_proto_rawDescGZIP(), []int{34} } func (x *OtaStartPayload) GetTotalSize() uint32 { @@ -2743,7 +2870,7 @@ type OtaPayload struct { func (x *OtaPayload) Reset() { *x = OtaPayload{} - mi := &file_uart_messages_proto_msgTypes[33] + mi := &file_uart_messages_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2755,7 +2882,7 @@ func (x *OtaPayload) String() string { func (*OtaPayload) ProtoMessage() {} func (x *OtaPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[33] + mi := &file_uart_messages_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2768,7 +2895,7 @@ func (x *OtaPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaPayload.ProtoReflect.Descriptor instead. func (*OtaPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{33} + return file_uart_messages_proto_rawDescGZIP(), []int{35} } func (x *OtaPayload) GetSeq() uint32 { @@ -2794,7 +2921,7 @@ type OtaEndPayload struct { func (x *OtaEndPayload) Reset() { *x = OtaEndPayload{} - mi := &file_uart_messages_proto_msgTypes[34] + mi := &file_uart_messages_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2806,7 +2933,7 @@ func (x *OtaEndPayload) String() string { func (*OtaEndPayload) ProtoMessage() {} func (x *OtaEndPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[34] + mi := &file_uart_messages_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2819,7 +2946,7 @@ func (x *OtaEndPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaEndPayload.ProtoReflect.Descriptor instead. func (*OtaEndPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{34} + return file_uart_messages_proto_rawDescGZIP(), []int{36} } // Device → host status (also used as ACK after each 4 KiB written). @@ -2836,7 +2963,7 @@ type OtaStatusPayload struct { func (x *OtaStatusPayload) Reset() { *x = OtaStatusPayload{} - mi := &file_uart_messages_proto_msgTypes[35] + mi := &file_uart_messages_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2848,7 +2975,7 @@ func (x *OtaStatusPayload) String() string { func (*OtaStatusPayload) ProtoMessage() {} func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[35] + mi := &file_uart_messages_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2861,7 +2988,7 @@ func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaStatusPayload.ProtoReflect.Descriptor instead. func (*OtaStatusPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{35} + return file_uart_messages_proto_rawDescGZIP(), []int{37} } func (x *OtaStatusPayload) GetStatus() uint32 { @@ -2902,7 +3029,7 @@ type OtaSlaveProgressRequest struct { func (x *OtaSlaveProgressRequest) Reset() { *x = OtaSlaveProgressRequest{} - mi := &file_uart_messages_proto_msgTypes[36] + mi := &file_uart_messages_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2914,7 +3041,7 @@ func (x *OtaSlaveProgressRequest) String() string { func (*OtaSlaveProgressRequest) ProtoMessage() {} func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[36] + mi := &file_uart_messages_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2927,7 +3054,7 @@ func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaSlaveProgressRequest.ProtoReflect.Descriptor instead. func (*OtaSlaveProgressRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{36} + return file_uart_messages_proto_rawDescGZIP(), []int{38} } func (x *OtaSlaveProgressRequest) GetClientId() uint32 { @@ -2951,7 +3078,7 @@ type OtaSlaveProgressEntry struct { func (x *OtaSlaveProgressEntry) Reset() { *x = OtaSlaveProgressEntry{} - mi := &file_uart_messages_proto_msgTypes[37] + mi := &file_uart_messages_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2963,7 +3090,7 @@ func (x *OtaSlaveProgressEntry) String() string { func (*OtaSlaveProgressEntry) ProtoMessage() {} func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[37] + mi := &file_uart_messages_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2976,7 +3103,7 @@ func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaSlaveProgressEntry.ProtoReflect.Descriptor instead. func (*OtaSlaveProgressEntry) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{37} + return file_uart_messages_proto_rawDescGZIP(), []int{39} } func (x *OtaSlaveProgressEntry) GetClientId() uint32 { @@ -3027,7 +3154,7 @@ type OtaSlaveProgressResponse struct { func (x *OtaSlaveProgressResponse) Reset() { *x = OtaSlaveProgressResponse{} - mi := &file_uart_messages_proto_msgTypes[38] + mi := &file_uart_messages_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3039,7 +3166,7 @@ func (x *OtaSlaveProgressResponse) String() string { func (*OtaSlaveProgressResponse) ProtoMessage() {} func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[38] + mi := &file_uart_messages_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3052,7 +3179,7 @@ func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaSlaveProgressResponse.ProtoReflect.Descriptor instead. func (*OtaSlaveProgressResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{38} + return file_uart_messages_proto_rawDescGZIP(), []int{40} } func (x *OtaSlaveProgressResponse) GetActive() bool { @@ -3094,7 +3221,7 @@ var File_uart_messages_proto protoreflect.FileDescriptor const file_uart_messages_proto_rawDesc = "" + "\n" + - "\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\x97\x13\n" + + "\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\xb6\x14\n" + "\vUartMessage\x12%\n" + "\x04type\x18\x01 \x01(\x0e2\x11.alox.MessageTypeR\x04type\x12,\n" + "\vack_payload\x18\x02 \x01(\v2\t.alox.AckH\x00R\n" + @@ -3131,7 +3258,9 @@ const file_uart_messages_proto_rawDesc = "" + "\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" + "\x14tap_snapshot_request\x18\x1f \x01(\v2\x18.alox.TapSnapshotRequestH\x00R\x12tapSnapshotRequest\x12O\n" + - "\x15tap_snapshot_response\x18 \x01(\v2\x19.alox.TapSnapshotResponseH\x00R\x13tapSnapshotResponseB\t\n" + + "\x15tap_snapshot_response\x18 \x01(\v2\x19.alox.TapSnapshotResponseH\x00R\x13tapSnapshotResponse\x12L\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" + "\apayload\"\x05\n" + "\x03Ack\"!\n" + "\vEchoPayload\x12\x12\n" + @@ -3237,7 +3366,11 @@ const file_uart_messages_proto_rawDesc = "" + "\x04kind\x18\x03 \x01(\x0e2\r.alox.TapKindR\x04kind\x12\x15\n" + "\x06age_ms\x18\x04 \x01(\rR\x05ageMs\"D\n" + "\x13TapSnapshotResponse\x12-\n" + - "\x06events\x18\x01 \x03(\v2\x0e.alox.TapEventB\x05\x92?\x02\x10\x10R\x06events\"I\n" + + "\x06events\x18\x01 \x03(\v2\x0e.alox.TapEventB\x05\x92?\x02\x10\x10R\x06events\"\x14\n" + + "\x12CacheStatusRequest\"p\n" + + "\x13CacheStatusResponse\x12.\n" + + "\x05accel\x18\x01 \x03(\v2\x11.alox.AccelSampleB\x05\x92?\x02\x10\x10R\x05accel\x12)\n" + + "\x04taps\x18\x02 \x03(\v2\x0e.alox.TapEventB\x05\x92?\x02\x10\x10R\x04taps\"I\n" + "\x18EspNowUnicastTestRequest\x12\x1b\n" + "\tclient_id\x18\x01 \x01(\rR\bclientId\x12\x10\n" + "\x03seq\x18\x02 \x01(\rR\x03seq\"G\n" + @@ -3308,7 +3441,7 @@ const file_uart_messages_proto_rawDesc = "" + "\x0faggregate_bytes\x18\x03 \x01(\rR\x0eaggregateBytes\x12\x1f\n" + "\vslave_count\x18\x04 \x01(\rR\n" + "slaveCount\x12:\n" + - "\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\xf9\x02\n" + + "\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\x8b\x03\n" + "\vMessageType\x12\v\n" + "\aUNKNOWN\x10\x00\x12\a\n" + "\x03ACK\x10\x01\x12\b\n" + @@ -3333,7 +3466,8 @@ const file_uart_messages_proto_rawDesc = "" + "\x0eBATTERY_STATUS\x10\x1a\x12\x0e\n" + "\n" + "TAP_NOTIFY\x10\x1b\x12\x10\n" + - "\fTAP_SNAPSHOT\x10\x1c*G\n" + + "\fTAP_SNAPSHOT\x10\x1c\x12\x10\n" + + "\fCACHE_STATUS\x10\x1d*G\n" + "\aTapKind\x12\f\n" + "\bTAP_NONE\x10\x00\x12\x0e\n" + "\n" + @@ -3356,7 +3490,7 @@ func file_uart_messages_proto_rawDescGZIP() []byte { } var file_uart_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 39) +var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 41) var file_uart_messages_proto_goTypes = []any{ (MessageType)(0), // 0: alox.MessageType (TapKind)(0), // 1: alox.TapKind @@ -3384,21 +3518,23 @@ var file_uart_messages_proto_goTypes = []any{ (*TapSnapshotRequest)(nil), // 23: alox.TapSnapshotRequest (*TapEvent)(nil), // 24: alox.TapEvent (*TapSnapshotResponse)(nil), // 25: alox.TapSnapshotResponse - (*EspNowUnicastTestRequest)(nil), // 26: alox.EspNowUnicastTestRequest - (*EspNowUnicastTestResponse)(nil), // 27: alox.EspNowUnicastTestResponse - (*LedRingProgressRequest)(nil), // 28: alox.LedRingProgressRequest - (*LedRingProgressResponse)(nil), // 29: alox.LedRingProgressResponse - (*EspNowFindMeRequest)(nil), // 30: alox.EspNowFindMeRequest - (*EspNowFindMeResponse)(nil), // 31: alox.EspNowFindMeResponse - (*RestartRequest)(nil), // 32: alox.RestartRequest - (*RestartResponse)(nil), // 33: alox.RestartResponse - (*OtaStartPayload)(nil), // 34: alox.OtaStartPayload - (*OtaPayload)(nil), // 35: alox.OtaPayload - (*OtaEndPayload)(nil), // 36: alox.OtaEndPayload - (*OtaStatusPayload)(nil), // 37: alox.OtaStatusPayload - (*OtaSlaveProgressRequest)(nil), // 38: alox.OtaSlaveProgressRequest - (*OtaSlaveProgressEntry)(nil), // 39: alox.OtaSlaveProgressEntry - (*OtaSlaveProgressResponse)(nil), // 40: alox.OtaSlaveProgressResponse + (*CacheStatusRequest)(nil), // 26: alox.CacheStatusRequest + (*CacheStatusResponse)(nil), // 27: alox.CacheStatusResponse + (*EspNowUnicastTestRequest)(nil), // 28: alox.EspNowUnicastTestRequest + (*EspNowUnicastTestResponse)(nil), // 29: alox.EspNowUnicastTestResponse + (*LedRingProgressRequest)(nil), // 30: alox.LedRingProgressRequest + (*LedRingProgressResponse)(nil), // 31: alox.LedRingProgressResponse + (*EspNowFindMeRequest)(nil), // 32: alox.EspNowFindMeRequest + (*EspNowFindMeResponse)(nil), // 33: alox.EspNowFindMeResponse + (*RestartRequest)(nil), // 34: alox.RestartRequest + (*RestartResponse)(nil), // 35: alox.RestartResponse + (*OtaStartPayload)(nil), // 36: alox.OtaStartPayload + (*OtaPayload)(nil), // 37: alox.OtaPayload + (*OtaEndPayload)(nil), // 38: alox.OtaEndPayload + (*OtaStatusPayload)(nil), // 39: alox.OtaStatusPayload + (*OtaSlaveProgressRequest)(nil), // 40: alox.OtaSlaveProgressRequest + (*OtaSlaveProgressEntry)(nil), // 41: alox.OtaSlaveProgressEntry + (*OtaSlaveProgressResponse)(nil), // 42: alox.OtaSlaveProgressResponse } var file_uart_messages_proto_depIdxs = []int32{ 0, // 0: alox.UartMessage.type:type_name -> alox.MessageType @@ -3407,22 +3543,22 @@ var file_uart_messages_proto_depIdxs = []int32{ 5, // 3: alox.UartMessage.version_response:type_name -> alox.VersionResponse 7, // 4: alox.UartMessage.client_info_response:type_name -> alox.ClientInfoResponse 9, // 5: alox.UartMessage.client_input_response:type_name -> alox.ClientInputResponse - 34, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload - 35, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload - 36, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload - 37, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload + 36, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload + 37, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload + 38, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload + 39, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload 10, // 10: alox.UartMessage.accel_deadzone_request:type_name -> alox.AccelDeadzoneRequest 11, // 11: alox.UartMessage.accel_deadzone_response:type_name -> alox.AccelDeadzoneResponse - 26, // 12: alox.UartMessage.espnow_unicast_test_request:type_name -> alox.EspNowUnicastTestRequest - 27, // 13: alox.UartMessage.espnow_unicast_test_response:type_name -> alox.EspNowUnicastTestResponse - 38, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest - 40, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse - 28, // 16: alox.UartMessage.led_ring_progress_request:type_name -> alox.LedRingProgressRequest - 29, // 17: alox.UartMessage.led_ring_progress_response:type_name -> alox.LedRingProgressResponse - 30, // 18: alox.UartMessage.espnow_find_me_request:type_name -> alox.EspNowFindMeRequest - 31, // 19: alox.UartMessage.espnow_find_me_response:type_name -> alox.EspNowFindMeResponse - 32, // 20: alox.UartMessage.restart_request:type_name -> alox.RestartRequest - 33, // 21: alox.UartMessage.restart_response:type_name -> alox.RestartResponse + 28, // 12: alox.UartMessage.espnow_unicast_test_request:type_name -> alox.EspNowUnicastTestRequest + 29, // 13: alox.UartMessage.espnow_unicast_test_response:type_name -> alox.EspNowUnicastTestResponse + 40, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest + 42, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse + 30, // 16: alox.UartMessage.led_ring_progress_request:type_name -> alox.LedRingProgressRequest + 31, // 17: alox.UartMessage.led_ring_progress_response:type_name -> alox.LedRingProgressResponse + 32, // 18: alox.UartMessage.espnow_find_me_request:type_name -> alox.EspNowFindMeRequest + 33, // 19: alox.UartMessage.espnow_find_me_response:type_name -> alox.EspNowFindMeResponse + 34, // 20: alox.UartMessage.restart_request:type_name -> alox.RestartRequest + 35, // 21: alox.UartMessage.restart_response:type_name -> alox.RestartResponse 18, // 22: alox.UartMessage.accel_snapshot_request:type_name -> alox.AccelSnapshotRequest 20, // 23: alox.UartMessage.accel_snapshot_response:type_name -> alox.AccelSnapshotResponse 12, // 24: alox.UartMessage.accel_stream_request:type_name -> alox.AccelStreamRequest @@ -3433,20 +3569,24 @@ var file_uart_messages_proto_depIdxs = []int32{ 22, // 29: alox.UartMessage.tap_notify_response:type_name -> alox.TapNotifyResponse 23, // 30: alox.UartMessage.tap_snapshot_request:type_name -> alox.TapSnapshotRequest 25, // 31: alox.UartMessage.tap_snapshot_response:type_name -> alox.TapSnapshotResponse - 6, // 32: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo - 8, // 33: alox.ClientInputResponse.clients:type_name -> alox.ClientInput - 15, // 34: alox.BatterySample.lipo1:type_name -> alox.LipoReading - 15, // 35: alox.BatterySample.lipo2:type_name -> alox.LipoReading - 16, // 36: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample - 19, // 37: alox.AccelSnapshotResponse.samples:type_name -> alox.AccelSample - 1, // 38: alox.TapEvent.kind:type_name -> alox.TapKind - 24, // 39: alox.TapSnapshotResponse.events:type_name -> alox.TapEvent - 39, // 40: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry - 41, // [41:41] is the sub-list for method output_type - 41, // [41:41] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 26, // 32: alox.UartMessage.cache_status_request:type_name -> alox.CacheStatusRequest + 27, // 33: alox.UartMessage.cache_status_response:type_name -> alox.CacheStatusResponse + 6, // 34: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo + 8, // 35: alox.ClientInputResponse.clients:type_name -> alox.ClientInput + 15, // 36: alox.BatterySample.lipo1:type_name -> alox.LipoReading + 15, // 37: alox.BatterySample.lipo2:type_name -> alox.LipoReading + 16, // 38: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample + 19, // 39: alox.AccelSnapshotResponse.samples:type_name -> alox.AccelSample + 1, // 40: alox.TapEvent.kind:type_name -> alox.TapKind + 24, // 41: alox.TapSnapshotResponse.events:type_name -> alox.TapEvent + 19, // 42: alox.CacheStatusResponse.accel:type_name -> alox.AccelSample + 24, // 43: alox.CacheStatusResponse.taps:type_name -> alox.TapEvent + 41, // 44: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry + 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() } @@ -3486,6 +3626,8 @@ func file_uart_messages_proto_init() { (*UartMessage_TapNotifyResponse)(nil), (*UartMessage_TapSnapshotRequest)(nil), (*UartMessage_TapSnapshotResponse)(nil), + (*UartMessage_CacheStatusRequest)(nil), + (*UartMessage_CacheStatusResponse)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -3493,7 +3635,7 @@ func file_uart_messages_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_uart_messages_proto_rawDesc), len(file_uart_messages_proto_rawDesc)), NumEnums: 2, - NumMessages: 39, + NumMessages: 41, NumExtensions: 0, NumServices: 0, }, diff --git a/goTool/webui/index.html b/goTool/webui/index.html index 4d5e3c2..278495e 100644 --- a/goTool/webui/index.html +++ b/goTool/webui/index.html @@ -290,10 +290,17 @@

- Accel-Stream pro Slave per „Stream an“ aktivieren (~16 ms ESP-NOW). Tap-Notify (S/D/T) - konfiguriert den Slave; „Empfang an“ startet das Abfragen von Tap-Events (~16 ms). + Live-Stream startet die schnelle CACHE_STATUS-Abfrage (~16 ms). + Pro Slave Accel aktiviert den ESP-NOW-Accel-Stream; Tap-Notify (S/D/T) steuert + die Tap-Arten auf dem Slave.

+ Tap alle Slaves: @@ -316,7 +323,7 @@ Deadzone Accel (LSB) Akku - Stream + Accel Tap-Notify Tap Aktion @@ -350,7 +357,8 @@ :class="c.accel_stream ? 'btn-warning' : 'btn-outline-success'" @click="setAccelStream(c.id, !c.accel_stream)" :disabled="busy || !state.uart_connected || !c.available" - x-text="c.accel_stream ? 'Aus' : 'An'"> + x-text="c.accel_stream ? 'Aus' : 'An'" + title="ESP-NOW Accel-Stream auf Slave">
@@ -375,16 +383,7 @@
-
- - -
+
@@ -779,12 +778,14 @@ return t; }, formatAccel(c) { + if (!this.state?.live_stream) return '—'; if (!c?.accel_stream) return '—'; if (!c?.accel_valid) return '…'; return `${c.accel_x} / ${c.accel_y} / ${c.accel_z}`; }, accelTitle(c) { - if (!c?.accel_stream) return 'Accel-Stream nicht aktiviert'; + if (!this.state?.live_stream) return 'Live-Stream aus — oben einschalten'; + if (!c?.accel_stream) return 'Accel-Stream für diesen Slave nicht aktiv'; if (!c?.accel_valid) return 'Warte auf erste ESP-NOW Samples…'; const age = c.accel_age_ms != null ? `${c.accel_age_ms} ms alt` : ''; return `x=${c.accel_x} y=${c.accel_y} z=${c.accel_z} (raw LSB, ±2g)${age ? ' · ' + age : ''}`; @@ -820,7 +821,7 @@ return d; }, formatLastTap(c) { - if (!c?.tap_receive) return '—'; + if (!this.state?.live_stream) return '—'; if (!this.tapNotifyAny(c)) return '—'; const labels = { single: 'Single', double: 'Double', triple: 'Triple' }; const d = this.activeTapDisplay(c); @@ -829,7 +830,7 @@ return labels[c.last_tap] || c.last_tap; }, tapTitle(c) { - if (!c?.tap_receive) return 'Tap-Empfang aus — „An“ klicken'; + if (!this.state?.live_stream) return 'Live-Stream aus'; if (!this.tapNotifyAny(c)) return 'Tap-Notify nicht konfiguriert (S/D/T)'; const d = this.activeTapDisplay(c); if (!d && !c?.last_tap) return 'Warte auf Tap-Event…'; @@ -1080,11 +1081,47 @@ async setMasterDeadzone() { await this.setDeadzone(0, this.masterDz); }, + patchLiveStream(enabled) { + let clients = this.state.clients || []; + if (!enabled) { + clients = clients.map((c) => ({ + ...c, + accel_valid: false, + accel_x: 0, + accel_y: 0, + accel_z: 0, + accel_age_ms: 0, + last_tap: '', + last_tap_at: 0 + })); + this.tapDisplay = {}; + } + this.state = { ...this.state, live_stream: enabled, clients }; + }, + async setLiveStream(enable) { + this.busy = true; + try { + const r = await fetch('/api/live-stream', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enable }) + }); + const data = await r.json(); + if (!r.ok || !data.success) { + this.flash(data.error || 'Live-Stream fehlgeschlagen', false); + return; + } + this.patchLiveStream(!!data.enabled); + this.flash(`Live-Stream ${data.enabled ? 'an' : 'aus'}`, true); + } catch (e) { + this.flash(String(e), false); + } finally { + this.busy = false; + } + }, patchClientAccelStream(clientId, enabled) { const clients = (this.state.clients || []).map((c) => { - if (c.id !== clientId) { - return c; - } + if (c.id !== clientId) return c; const next = { ...c, accel_stream: enabled }; if (!enabled) { next.accel_valid = false; @@ -1103,7 +1140,7 @@ const r = await fetch(`/api/clients/${clientId}/accel-stream`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ enable: enable }) + body: JSON.stringify({ enable }) }); const data = await r.json(); if (!r.ok || !data.success) { @@ -1182,40 +1219,6 @@ this.busy = false; } }, - patchClientTapReceive(clientId, enabled) { - const clients = (this.state.clients || []).map((c) => { - if (c.id !== clientId) return c; - const next = { ...c, tap_receive: enabled }; - if (!enabled) { - next.last_tap = ''; - next.last_tap_at = 0; - delete this.tapDisplay[clientId]; - } - return next; - }); - this.state = { ...this.state, clients }; - }, - async setTapReceive(clientId, enable) { - this.busy = true; - try { - const r = await fetch(`/api/clients/${clientId}/tap-receive`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ enable }) - }); - const data = await r.json(); - if (!r.ok || !data.success) { - this.flash(data.error || `Tap-Empfang Slave ${clientId} fehlgeschlagen`, false); - return; - } - this.patchClientTapReceive(clientId, !!data.enabled); - this.flash(`Slave ${clientId}: Tap-Empfang ${data.enabled ? 'an' : 'aus'}`, true); - } catch (e) { - this.flash(String(e), false); - } finally { - this.busy = false; - } - }, async setDeadzoneAll(deadzone) { if (deadzone == null || deadzone < 0) { this.flash('Ungültiger Deadzone-Wert', false); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index a7c17da..f693e08 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -22,6 +22,7 @@ idf_component_register( "cmd/cmd_accel_stream.c" "cmd/cmd_tap_notify.c" "cmd/cmd_tap_snapshot.c" + "cmd/cmd_cache_status.c" "cmd/cmd_espnow_unicast_test.c" "cmd/cmd_espnow_find_me.c" "cmd/cmd_restart.c" diff --git a/main/README.md b/main/README.md index 97544b3..dd6f1d2 100644 --- a/main/README.md +++ b/main/README.md @@ -225,6 +225,7 @@ Host and master speak nanopb-encoded `UartMessage` inside UART frames (byte 0 = | 24 | `ACCEL_SNAPSHOT` | Implemented (`cmd/cmd_accel_snapshot.c`) — cached slave accel from ESP-NOW stream | | 27 | `TAP_NOTIFY` | Implemented (`cmd/cmd_tap_notify.c`) — get/set which tap kinds notify via ESP-NOW | | 28 | `TAP_SNAPSHOT` | Implemented (`cmd/cmd_tap_snapshot.c`) — consume cached tap events from master registry | +| 29 | `CACHE_STATUS` | Implemented (`cmd/cmd_cache_status.c`) — combined accel + tap cache in one UART round-trip | Regenerate C code: @@ -367,6 +368,17 @@ go run . -port /dev/ttyUSB0 tap-notify -client 16 Read **cached** tap events on the **master** (one pending event per slave). Slaves send `ESPNOW_TAP_EVENT` on tap; the master stores the latest value per client in `client_registry.c` for up to **16 ms** (`CLIENT_REGISTRY_TAP_MAX_AGE_MS`). Each snapshot **consumes** fresh events (cleared after read). +### CACHE_STATUS command + +Fast combined poll for host tools at **16 ms** or faster: one UART frame, no request body (command id `0x1d` only). + +**Response:** `cache_status_response` with: + +- `accel[]` — same as `ACCEL_SNAPSHOT`, only clients with `accel_stream_enabled` +- `taps[]` — same as `TAP_SNAPSHOT`, only clients with any tap notify flag; pending taps are consumed + +The master walks `client_registry` once per request (`cmd/cmd_cache_status.c`). Prefer this over separate `ACCEL_SNAPSHOT` + `TAP_SNAPSHOT` when polling both streams. + Only slaves with at least one tap-notify flag enabled are included. **Request:** framed `1c` (`0x1c`) + optional `tap_snapshot_request` (`client_id`: `0` = all, `>0` = one id). @@ -555,6 +567,7 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running ` | `cmd/cmd_accel_snapshot.c` | UART `ACCEL_SNAPSHOT` — cached slave accel | | `cmd/cmd_tap_notify.c` | UART `TAP_NOTIFY` — ESP-NOW tap notify config | | `cmd/cmd_tap_snapshot.c` | UART `TAP_SNAPSHOT` — consume cached tap events | +| `cmd/cmd_cache_status.c` | UART `CACHE_STATUS` — combined accel + tap cache poll | | `board_input.c/h` | Taster GPIO12, LiPo ADC on GPIO1 / GPIO12 | | `pod_settings.c/h` | NVS persistence (accel deadzone, …) | | `led_ring.c/h` | LED ring (digit display, progress bar) | diff --git a/main/cmd/cmd_cache_status.c b/main/cmd/cmd_cache_status.c new file mode 100644 index 0000000..0c26589 --- /dev/null +++ b/main/cmd/cmd_cache_status.c @@ -0,0 +1,83 @@ +#include "client_registry.h" +#include "cmd_cache_status.h" +#include "uart_cmd.h" + +static const char *TAG = "[CACHE_STAT]"; + +static bool tap_notify_any(const client_info_t *client) { + return client != NULL && + (client->tap_notify_single || client->tap_notify_double || + client->tap_notify_triple); +} + +static alox_TapKind tap_kind_from_registry(uint32_t kind) { + switch (kind) { + case 1: + return alox_TapKind_TAP_SINGLE; + case 2: + return alox_TapKind_TAP_DOUBLE; + case 3: + return alox_TapKind_TAP_TRIPLE; + default: + return alox_TapKind_TAP_NONE; + } +} + +static void fill_cache_status(alox_CacheStatusResponse *out) { + if (out == NULL) { + return; + } + + out->accel_count = 0; + out->taps_count = 0; + + size_t count = client_registry_count(); + for (size_t i = 0; i < count; i++) { + const client_info_t *client = client_registry_at(i); + if (client == NULL) { + continue; + } + + if (client->accel_stream_enabled && + out->accel_count < sizeof(out->accel) / sizeof(out->accel[0])) { + alox_AccelSample *sample = &out->accel[out->accel_count++]; + sample->client_id = client->id; + sample->valid = client->accel_valid; + sample->x = client->accel_x; + sample->y = client->accel_y; + sample->z = client->accel_z; + if (client->accel_valid) { + sample->age_ms = client_registry_ms_since(client->accel_updated_at); + } + } + + if (tap_notify_any(client) && + out->taps_count < sizeof(out->taps) / sizeof(out->taps[0])) { + uint32_t kind = 0; + uint32_t age_ms = 0; + if (!client_registry_take_tap(client->id, &kind, &age_ms)) { + continue; + } + alox_TapEvent *event = &out->taps[out->taps_count++]; + event->client_id = client->id; + event->valid = true; + event->kind = tap_kind_from_registry(kind); + event->age_ms = age_ms; + } + } +} + +static void handle_cache_status(const uint8_t *data, size_t len) { + (void)data; + (void)len; + + alox_UartMessage response; + uart_cmd_init_response(&response, alox_MessageType_CACHE_STATUS, + alox_UartMessage_cache_status_response_tag); + fill_cache_status(&response.payload.cache_status_response); + uart_cmd_send(&response, TAG); +} + +void cmd_cache_status_register(void) { + uart_cmd_register(alox_MessageType_CACHE_STATUS, handle_cache_status); +} diff --git a/main/cmd/cmd_cache_status.h b/main/cmd/cmd_cache_status.h new file mode 100644 index 0000000..e2cc89b --- /dev/null +++ b/main/cmd/cmd_cache_status.h @@ -0,0 +1,3 @@ +#pragma once + +void cmd_cache_status_register(void); diff --git a/main/cmd/cmd_handler.c b/main/cmd/cmd_handler.c index 93c123c..10d8434 100644 --- a/main/cmd/cmd_handler.c +++ b/main/cmd/cmd_handler.c @@ -58,6 +58,8 @@ static const char *message_type_name(uint16_t id) { return "TAP_NOTIFY"; case alox_MessageType_TAP_SNAPSHOT: return "TAP_SNAPSHOT"; + case alox_MessageType_CACHE_STATUS: + return "CACHE_STATUS"; default: return "UNKNOWN"; } diff --git a/main/powerpod.c b/main/powerpod.c index 8607037..9d666a7 100644 --- a/main/powerpod.c +++ b/main/powerpod.c @@ -5,6 +5,7 @@ #include "cmd_accel_stream.h" #include "cmd_tap_notify.h" #include "cmd_tap_snapshot.h" +#include "cmd_cache_status.h" #include "cmd_espnow_unicast_test.h" #include "cmd_espnow_find_me.h" #include "cmd_restart.h" @@ -186,6 +187,7 @@ void app_main(void) { cmd_accel_stream_register(); cmd_tap_notify_register(); cmd_tap_snapshot_register(); + cmd_cache_status_register(); cmd_espnow_unicast_test_register(); cmd_espnow_find_me_register(); cmd_restart_register(); diff --git a/main/proto/uart_messages.pb.c b/main/proto/uart_messages.pb.c index 918ea7b..25e9f94 100644 --- a/main/proto/uart_messages.pb.c +++ b/main/proto/uart_messages.pb.c @@ -78,6 +78,12 @@ PB_BIND(alox_TapEvent, alox_TapEvent, AUTO) PB_BIND(alox_TapSnapshotResponse, alox_TapSnapshotResponse, 2) +PB_BIND(alox_CacheStatusRequest, alox_CacheStatusRequest, AUTO) + + +PB_BIND(alox_CacheStatusResponse, alox_CacheStatusResponse, 2) + + PB_BIND(alox_EspNowUnicastTestRequest, alox_EspNowUnicastTestRequest, AUTO) diff --git a/main/proto/uart_messages.pb.h b/main/proto/uart_messages.pb.h index 8a66d1d..1bc69ca 100644 --- a/main/proto/uart_messages.pb.h +++ b/main/proto/uart_messages.pb.h @@ -32,7 +32,9 @@ typedef enum _alox_MessageType { alox_MessageType_ACCEL_STREAM = 25, alox_MessageType_BATTERY_STATUS = 26, alox_MessageType_TAP_NOTIFY = 27, - alox_MessageType_TAP_SNAPSHOT = 28 + alox_MessageType_TAP_SNAPSHOT = 28, + /* * Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */ + alox_MessageType_CACHE_STATUS = 29 } alox_MessageType; typedef enum _alox_TapKind { @@ -209,6 +211,20 @@ typedef struct _alox_TapSnapshotResponse { alox_TapEvent events[16]; } alox_TapSnapshotResponse; +/* * Host → master: one-shot read of subscribed cached slave data (no request body). */ +typedef struct _alox_CacheStatusRequest { + char dummy_field; +} alox_CacheStatusRequest; + +typedef struct _alox_CacheStatusResponse { + /* * Slaves with accel_stream_enabled. */ + pb_size_t accel_count; + alox_AccelSample accel[16]; + /* * Slaves with any tap notify flag; pending taps are consumed (like TAP_SNAPSHOT). */ + pb_size_t taps_count; + alox_TapEvent taps[16]; +} alox_CacheStatusResponse; + typedef struct _alox_EspNowUnicastTestRequest { uint32_t client_id; uint32_t seq; @@ -357,6 +373,8 @@ typedef struct _alox_UartMessage { alox_TapNotifyResponse tap_notify_response; alox_TapSnapshotRequest tap_snapshot_request; alox_TapSnapshotResponse tap_snapshot_response; + alox_CacheStatusRequest cache_status_request; + alox_CacheStatusResponse cache_status_response; } payload; } alox_UartMessage; @@ -367,8 +385,8 @@ extern "C" { /* Helper constants for enums */ #define _alox_MessageType_MIN alox_MessageType_UNKNOWN -#define _alox_MessageType_MAX alox_MessageType_TAP_SNAPSHOT -#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_TAP_SNAPSHOT+1)) +#define _alox_MessageType_MAX alox_MessageType_CACHE_STATUS +#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_CACHE_STATUS+1)) #define _alox_TapKind_MIN alox_TapKind_TAP_NONE #define _alox_TapKind_MAX alox_TapKind_TAP_TRIPLE @@ -416,6 +434,8 @@ extern "C" { + + /* Initializer values for message structs */ #define alox_UartMessage_init_default {_alox_MessageType_MIN, 0, {alox_Ack_init_default}} #define alox_Ack_init_default {0} @@ -441,6 +461,8 @@ extern "C" { #define alox_TapSnapshotRequest_init_default {0} #define alox_TapEvent_init_default {0, 0, _alox_TapKind_MIN, 0} #define alox_TapSnapshotResponse_init_default {0, {alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default}} +#define alox_CacheStatusRequest_init_default {0} +#define alox_CacheStatusResponse_init_default {0, {alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default}, 0, {alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default, alox_TapEvent_init_default}} #define alox_EspNowUnicastTestRequest_init_default {0, 0} #define alox_EspNowUnicastTestResponse_init_default {0, 0} #define alox_LedRingProgressRequest_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -480,6 +502,8 @@ extern "C" { #define alox_TapSnapshotRequest_init_zero {0} #define alox_TapEvent_init_zero {0, 0, _alox_TapKind_MIN, 0} #define alox_TapSnapshotResponse_init_zero {0, {alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero}} +#define alox_CacheStatusRequest_init_zero {0} +#define alox_CacheStatusResponse_init_zero {0, {alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero}, 0, {alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero, alox_TapEvent_init_zero}} #define alox_EspNowUnicastTestRequest_init_zero {0, 0} #define alox_EspNowUnicastTestResponse_init_zero {0, 0} #define alox_LedRingProgressRequest_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -570,6 +594,8 @@ extern "C" { #define alox_TapEvent_kind_tag 3 #define alox_TapEvent_age_ms_tag 4 #define alox_TapSnapshotResponse_events_tag 1 +#define alox_CacheStatusResponse_accel_tag 1 +#define alox_CacheStatusResponse_taps_tag 2 #define alox_EspNowUnicastTestRequest_client_id_tag 1 #define alox_EspNowUnicastTestRequest_seq_tag 2 #define alox_EspNowUnicastTestResponse_success_tag 1 @@ -648,6 +674,8 @@ extern "C" { #define alox_UartMessage_tap_notify_response_tag 30 #define alox_UartMessage_tap_snapshot_request_tag 31 #define alox_UartMessage_tap_snapshot_response_tag 32 +#define alox_UartMessage_cache_status_request_tag 33 +#define alox_UartMessage_cache_status_response_tag 34 /* Struct field encoding specification for nanopb */ #define alox_UartMessage_FIELDLIST(X, a) \ @@ -682,7 +710,9 @@ 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_response,payload.tap_notify_response), 30) \ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_snapshot_request,payload.tap_snapshot_request), 31) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,tap_snapshot_response,payload.tap_snapshot_response), 32) +X(a, STATIC, ONEOF, MESSAGE, (payload,tap_snapshot_response,payload.tap_snapshot_response), 32) \ +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) #define alox_UartMessage_CALLBACK NULL #define alox_UartMessage_DEFAULT NULL #define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack @@ -716,6 +746,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_snapshot_response,payload.tap_sn #define alox_UartMessage_payload_tap_notify_response_MSGTYPE alox_TapNotifyResponse #define alox_UartMessage_payload_tap_snapshot_request_MSGTYPE alox_TapSnapshotRequest #define alox_UartMessage_payload_tap_snapshot_response_MSGTYPE alox_TapSnapshotResponse +#define alox_UartMessage_payload_cache_status_request_MSGTYPE alox_CacheStatusRequest +#define alox_UartMessage_payload_cache_status_response_MSGTYPE alox_CacheStatusResponse #define alox_Ack_FIELDLIST(X, a) \ @@ -890,6 +922,19 @@ X(a, STATIC, REPEATED, MESSAGE, events, 1) #define alox_TapSnapshotResponse_DEFAULT NULL #define alox_TapSnapshotResponse_events_MSGTYPE alox_TapEvent +#define alox_CacheStatusRequest_FIELDLIST(X, a) \ + +#define alox_CacheStatusRequest_CALLBACK NULL +#define alox_CacheStatusRequest_DEFAULT NULL + +#define alox_CacheStatusResponse_FIELDLIST(X, a) \ +X(a, STATIC, REPEATED, MESSAGE, accel, 1) \ +X(a, STATIC, REPEATED, MESSAGE, taps, 2) +#define alox_CacheStatusResponse_CALLBACK NULL +#define alox_CacheStatusResponse_DEFAULT NULL +#define alox_CacheStatusResponse_accel_MSGTYPE alox_AccelSample +#define alox_CacheStatusResponse_taps_MSGTYPE alox_TapEvent + #define alox_EspNowUnicastTestRequest_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, client_id, 1) \ X(a, STATIC, SINGULAR, UINT32, seq, 2) @@ -1022,6 +1067,8 @@ extern const pb_msgdesc_t alox_TapNotifyResponse_msg; extern const pb_msgdesc_t alox_TapSnapshotRequest_msg; extern const pb_msgdesc_t alox_TapEvent_msg; extern const pb_msgdesc_t alox_TapSnapshotResponse_msg; +extern const pb_msgdesc_t alox_CacheStatusRequest_msg; +extern const pb_msgdesc_t alox_CacheStatusResponse_msg; extern const pb_msgdesc_t alox_EspNowUnicastTestRequest_msg; extern const pb_msgdesc_t alox_EspNowUnicastTestResponse_msg; extern const pb_msgdesc_t alox_LedRingProgressRequest_msg; @@ -1063,6 +1110,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg; #define alox_TapSnapshotRequest_fields &alox_TapSnapshotRequest_msg #define alox_TapEvent_fields &alox_TapEvent_msg #define alox_TapSnapshotResponse_fields &alox_TapSnapshotResponse_msg +#define alox_CacheStatusRequest_fields &alox_CacheStatusRequest_msg +#define alox_CacheStatusResponse_fields &alox_CacheStatusResponse_msg #define alox_EspNowUnicastTestRequest_fields &alox_EspNowUnicastTestRequest_msg #define alox_EspNowUnicastTestResponse_fields &alox_EspNowUnicastTestResponse_msg #define alox_LedRingProgressRequest_fields &alox_LedRingProgressRequest_msg @@ -1086,7 +1135,7 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg; /* alox_ClientInfo_size depends on runtime parameters */ /* alox_ClientInfoResponse_size depends on runtime parameters */ /* alox_ClientInputResponse_size depends on runtime parameters */ -#define ALOX_UART_MESSAGES_PB_H_MAX_SIZE alox_BatteryStatusResponse_size +#define ALOX_UART_MESSAGES_PB_H_MAX_SIZE alox_CacheStatusResponse_size #define alox_AccelDeadzoneRequest_size 16 #define alox_AccelDeadzoneResponse_size 20 #define alox_AccelSample_size 32 @@ -1098,6 +1147,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg; #define alox_BatterySample_size 32 #define alox_BatteryStatusRequest_size 8 #define alox_BatteryStatusResponse_size 580 +#define alox_CacheStatusRequest_size 0 +#define alox_CacheStatusResponse_size 832 #define alox_ClientInput_size 22 #define alox_EspNowFindMeRequest_size 6 #define alox_EspNowFindMeResponse_size 8 diff --git a/main/proto/uart_messages.proto b/main/proto/uart_messages.proto index 68355ed..29b459f 100644 --- a/main/proto/uart_messages.proto +++ b/main/proto/uart_messages.proto @@ -27,6 +27,8 @@ enum MessageType { BATTERY_STATUS = 26; TAP_NOTIFY = 27; TAP_SNAPSHOT = 28; + /** Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */ + CACHE_STATUS = 29; } message UartMessage { @@ -63,6 +65,8 @@ message UartMessage { TapNotifyResponse tap_notify_response = 30; TapSnapshotRequest tap_snapshot_request = 31; TapSnapshotResponse tap_snapshot_response = 32; + CacheStatusRequest cache_status_request = 33; + CacheStatusResponse cache_status_response = 34; } } @@ -232,6 +236,16 @@ message TapSnapshotResponse { repeated TapEvent events = 1 [(nanopb).max_count = 16]; } +/** Host → master: one-shot read of subscribed cached slave data (no request body). */ +message CacheStatusRequest {} + +message CacheStatusResponse { + /** Slaves with accel_stream_enabled. */ + repeated AccelSample accel = 1 [(nanopb).max_count = 16]; + /** Slaves with any tap notify flag; pending taps are consumed (like TAP_SNAPSHOT). */ + repeated TapEvent taps = 2 [(nanopb).max_count = 16]; +} + message EspNowUnicastTestRequest { uint32 client_id = 1; uint32 seq = 2;