From a85d48320ee83db8a4edc917ee298b5c89ddc0a8 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 29 May 2026 21:02:41 +0200 Subject: [PATCH] Add list_clients WebSocket command to external API. Lets API clients discover slave IDs and stream/notify flags before configuring per-slave accel or tap. Co-authored-by: Cursor --- goTool/README.md | 21 +++++++++++++- goTool/api_stream.go | 69 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/goTool/README.md b/goTool/README.md index 2b2f128..2a23e7b 100644 --- a/goTool/README.md +++ b/goTool/README.md @@ -106,7 +106,19 @@ Tap polling runs only when at least one connection has `receive_tap: true` (via **Hello** (on connect; accel/tap receive off until `set_stream` / `set_tap_stream`): ```json -{"type":"hello","serial_port":"/dev/ttyUSB0","interval_ms":16,"tap_display_min_ms":2000,"note":"set_tap_notify configures slave S/D/T only; set_tap_stream enables tap polling/push","commands":["set_stream","get_stream","set_accel_stream","get_accel_stream","set_tap_stream","get_tap_stream","set_tap_notify","get_tap_notify","set_led_ring","get_battery"]} +{"type":"hello","serial_port":"/dev/ttyUSB0","interval_ms":16,"tap_display_min_ms":2000,"note":"set_tap_notify configures slave S/D/T only; set_tap_stream enables tap polling/push","commands":["list_clients","set_stream","get_stream","set_accel_stream","get_accel_stream","set_tap_stream","get_tap_stream","set_tap_notify","get_tap_notify","set_led_ring","get_battery"]} +``` + +**List registered slaves** (UART `CLIENT_INFO`; use before per-slave `set_accel_stream` / `set_tap_notify`): + +```json +{"type":"list_clients"} +``` + +Reply: + +```json +{"type":"client_list","success":true,"clients":[{"id":16,"mac":"aa:bb:cc:dd:ee:10","version":1,"available":true,"used":true,"last_ping":1234,"last_success_ping":1200,"accel_stream":false,"tap_notify_single":false,"tap_notify_double":false,"tap_notify_triple":false}]} ``` **Receive accel on this connection** (optional `interval_ms`, default from `-accel-interval`): @@ -192,6 +204,13 @@ import asyncio, json, websockets async def main(): async with websockets.connect("ws://127.0.0.1:8081/ws") as ws: print(await ws.recv()) # hello + await ws.send(json.dumps({"type": "list_clients"})) + clients = json.loads(await ws.recv())["clients"] + for c in clients: + if not c.get("available"): + continue + await ws.send(json.dumps({"type": "set_accel_stream", "client_id": c["id"], "enable": True})) + print(await ws.recv()) # accel_stream_status await ws.send(json.dumps({"type": "set_stream", "enable": True, "interval_ms": 16})) print(await ws.recv()) # stream_status await ws.send(json.dumps({"type": "set_accel_stream", "client_id": 16, "enable": True})) diff --git a/goTool/api_stream.go b/goTool/api_stream.go index 9362eb8..154cd1d 100644 --- a/goTool/api_stream.go +++ b/goTool/api_stream.go @@ -92,6 +92,29 @@ type TapStreamStatusMessage struct { Error string `json:"error,omitempty"` } +// APIClientInfo is one registered slave (or slot) from CLIENT_INFO. +type APIClientInfo struct { + ID uint32 `json:"id"` + MAC string `json:"mac"` + Version uint32 `json:"version"` + Available bool `json:"available"` + Used bool `json:"used"` + LastPing uint32 `json:"last_ping"` + LastSuccessPing uint32 `json:"last_success_ping"` + AccelStream bool `json:"accel_stream"` + TapNotifySingle bool `json:"tap_notify_single"` + TapNotifyDouble bool `json:"tap_notify_double"` + TapNotifyTriple bool `json:"tap_notify_triple"` +} + +// ClientListMessage is the reply to list_clients. +type ClientListMessage struct { + Type string `json:"type"` // "client_list" + Success bool `json:"success"` + Clients []APIClientInfo `json:"clients,omitempty"` + Error string `json:"error,omitempty"` +} + // TapNotifyStatusMessage is the reply to set_tap_notify / get_tap_notify (slave). type TapNotifyStatusMessage struct { Type string `json:"type"` // "tap_notify_status" @@ -191,6 +214,7 @@ func (h *accelStreamHub) register(conn *websocket.Conn, portName string) *wsSubs TapDisplayMinMs: apiTapDisplayMinMs, Note: "set_tap_notify configures slave S/D/T only; set_tap_stream enables tap polling/push", Commands: []string{ + "list_clients", "set_stream", "get_stream", "set_accel_stream", "get_accel_stream", "set_tap_stream", "get_tap_stream", "set_tap_notify", "get_tap_notify", "set_led_ring", "get_battery", @@ -584,6 +608,30 @@ func writeTapStreamStatus(conn *websocket.Conn, msg TapStreamStatusMessage) { _ = conn.WriteMessage(websocket.TextMessage, data) } +func clientInfoToAPI(c *pb.ClientInfo) APIClientInfo { + return APIClientInfo{ + ID: c.GetId(), + MAC: formatMAC(c.GetMac()), + Version: c.GetVersion(), + Available: c.GetAvailable(), + Used: c.GetUsed(), + LastPing: c.GetLastPing(), + LastSuccessPing: c.GetLastSuccessPing(), + AccelStream: c.GetAccelStreamEnabled(), + TapNotifySingle: c.GetTapNotifySingle(), + TapNotifyDouble: c.GetTapNotifyDouble(), + TapNotifyTriple: c.GetTapNotifyTriple(), + } +} + +func writeClientList(conn *websocket.Conn, msg ClientListMessage) { + data, err := json.Marshal(msg) + if err != nil { + return + } + _ = conn.WriteMessage(websocket.TextMessage, data) +} + func writeTapNotifyStatus(conn *websocket.Conn, out tapNotifyAPIResponse) { msg := TapNotifyStatusMessage{ Type: "tap_notify_status", @@ -640,6 +688,25 @@ func handleAccelWSCommand(conn *websocket.Conn, sub *wsSubscriber, data []byte, } switch cmd.Type { + case "list_clients": + clients, err := link.listClientsPoll() + if err != nil { + writeClientList(conn, ClientListMessage{ + Type: "client_list", + Error: err.Error(), + }) + return + } + out := make([]APIClientInfo, 0, len(clients)) + for _, c := range clients { + out = append(out, clientInfoToAPI(c)) + } + writeClientList(conn, ClientListMessage{ + Type: "client_list", + Success: true, + Clients: out, + }) + case "set_stream": if cmd.Enable == nil { writeStreamStatus(conn, StreamStatusMessage{ @@ -791,7 +858,7 @@ func handleAccelWSCommand(conn *websocket.Conn, sub *wsSubscriber, data []byte, default: writeStreamStatus(conn, StreamStatusMessage{ Type: "stream_status", - Error: "unknown type (set_stream, get_stream, set_accel_stream, get_accel_stream, set_tap_stream, get_tap_stream, set_tap_notify, get_tap_notify, set_led_ring, get_battery)", + Error: "unknown type (list_clients, set_stream, get_stream, set_accel_stream, get_accel_stream, set_tap_stream, get_tap_stream, set_tap_notify, get_tap_notify, set_led_ring, get_battery)", }) } }