powerpods/goTool/led_ring_api.go
simon eb67a46158 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>
2026-05-29 19:24:55 +02:00

107 lines
2.8 KiB
Go

package main
import (
"fmt"
"strings"
"powerpod/gotool/pb"
)
const (
ledRingModeClear = 0
ledRingModeProgress = 1
ledRingModeDigit = 2
ledRingModeBlink = 3
ledRingModeFindMe = 4
ledRingModeColor = 5
)
type ledRingAPIRequest struct {
Mode string `json:"mode"`
ClientID uint32 `json:"client_id"`
AllClients bool `json:"all_clients"`
SlavesOnly bool `json:"slaves_only"`
Progress uint32 `json:"progress"`
Digit uint32 `json:"digit"`
R uint32 `json:"r"`
G uint32 `json:"g"`
B uint32 `json:"b"`
Intensity uint32 `json:"intensity"`
BlinkMs uint32 `json:"blink_ms"`
BlinkCount uint32 `json:"blink_count"`
}
type ledRingAPIResponse struct {
Type string `json:"type,omitempty"` // led_ring_status (WebSocket)
Success bool `json:"success"`
Mode uint32 `json:"mode,omitempty"`
Progress uint32 `json:"progress,omitempty"`
Digit uint32 `json:"digit,omitempty"`
ClientID uint32 `json:"client_id,omitempty"`
SlavesUpdated uint32 `json:"slaves_updated,omitempty"`
Error string `json:"error,omitempty"`
}
func ledRingModeFromString(s string) (uint32, error) {
switch strings.ToLower(strings.TrimSpace(s)) {
case "clear", "":
return ledRingModeClear, nil
case "color", "solid", "fill":
return ledRingModeColor, nil
case "progress":
return ledRingModeProgress, nil
case "digit":
return ledRingModeDigit, nil
case "blink":
return ledRingModeBlink, nil
case "find-me", "find_me", "findme":
return ledRingModeFindMe, nil
default:
return 0, fmt.Errorf("unknown mode %q (clear, color, progress, digit, blink, find-me)", s)
}
}
func ledRingPBFromAPI(in ledRingAPIRequest) (*pb.LedRingProgressRequest, error) {
mode, err := ledRingModeFromString(in.Mode)
if err != nil {
return nil, err
}
return &pb.LedRingProgressRequest{
Mode: mode,
Progress: in.Progress,
Digit: in.Digit,
R: in.R,
G: in.G,
B: in.B,
Intensity: in.Intensity,
BlinkMs: in.BlinkMs,
BlinkCount: in.BlinkCount,
ClientId: in.ClientID,
AllClients: in.AllClients,
SlavesOnly: in.SlavesOnly,
}, nil
}
func applyLedRing(link *managedSerial, in ledRingAPIRequest) ledRingAPIResponse {
req, err := ledRingPBFromAPI(in)
if err != nil {
return ledRingAPIResponse{Error: err.Error()}
}
resp, err := link.LedRing(req)
if err != nil {
return ledRingAPIResponse{Error: err.Error()}
}
out := ledRingAPIResponse{
Success: resp.GetSuccess(),
Mode: resp.GetMode(),
Progress: resp.GetProgress(),
Digit: resp.GetDigit(),
ClientID: resp.GetClientId(),
SlavesUpdated: resp.GetSlavesUpdated(),
}
if !out.Success && out.Error == "" {
out.Error = "led ring command rejected"
}
return out
}