Add LED ring control per client and broadcast over REST and WebSocket.

Solid color mode fills all ring LEDs; master routes UART commands to slaves
via ESPNOW_LED_RING. goTool exposes POST /api/led-ring, WebSocket set_led_ring,
and a dashboard LED panel with master/slave/all targets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 19:24:55 +02:00
co-authored by Cursor
parent 47c75110c9
commit eb67a46158
21 changed files with 759 additions and 133 deletions
+28 -4
View File
@@ -132,7 +132,9 @@ func (h *accelStreamHub) register(conn *websocket.Conn, portName string) *wsSubs
Type: "hello",
Serial: portName,
IntervalMs: int(h.defaultInterval / time.Millisecond),
Commands: []string{"set_stream", "get_stream", "set_accel_stream", "get_accel_stream"},
Commands: []string{
"set_stream", "get_stream", "set_accel_stream", "get_accel_stream", "set_led_ring",
},
}
if data, err := json.Marshal(hello); err == nil {
_ = conn.WriteMessage(websocket.TextMessage, data)
@@ -317,6 +319,15 @@ func writeStreamStatus(conn *websocket.Conn, msg StreamStatusMessage) {
_ = conn.WriteMessage(websocket.TextMessage, data)
}
func writeLedRingStatus(conn *websocket.Conn, out ledRingAPIResponse) {
out.Type = "led_ring_status"
data, err := json.Marshal(out)
if err != nil {
return
}
_ = conn.WriteMessage(websocket.TextMessage, data)
}
func writeAccelStreamStatus(conn *websocket.Conn, out accelStreamAPIResponse) {
msg := AccelStreamStatusMessage{
Type: "accel_stream_status",
@@ -393,10 +404,22 @@ func handleAccelWSCommand(conn *websocket.Conn, sub *wsSubscriber, data []byte,
Success: resp.GetSuccess(),
})
case "set_led_ring":
var body ledRingAPIRequest
if err := json.Unmarshal(data, &body); err != nil {
writeLedRingStatus(conn, ledRingAPIResponse{Error: "invalid JSON"})
return
}
if body.Mode == "" {
writeLedRingStatus(conn, ledRingAPIResponse{Error: "mode required"})
return
}
writeLedRingStatus(conn, applyLedRing(link, body))
default:
writeStreamStatus(conn, StreamStatusMessage{
Type: "stream_status",
Error: "unknown type (set_stream, get_stream, set_accel_stream, get_accel_stream)",
Type: "stream_status",
Error: "unknown type (set_stream, get_stream, set_accel_stream, get_accel_stream, set_led_ring)",
})
}
}
@@ -434,7 +457,7 @@ func mountExternalAPI(mux *http.ServeMux, portName string, defaultInterval time.
DefaultIntervalMs: defMs,
MinIntervalMs: int(minAPIStreamInterval / time.Millisecond),
MaxIntervalMs: int(maxAPIStreamInterval / time.Millisecond),
Description: "WebSocket: per-connection accel receive + interval; slave stream via set_accel_stream",
Description: "WebSocket: accel stream + set_led_ring (modes: clear, color, progress, digit, blink, find-me)",
})
})
@@ -454,6 +477,7 @@ func runAPIServer(portName string, link *managedSerial, addr string, defaultInte
mux := http.NewServeMux()
mountExternalAPI(mux, portName, defaultInterval, hub, link, dash, ctl)
mountLedRingAPI(mux, link)
srv := &http.Server{Addr: addr, Handler: mux}
go func() {