Files
workspace-led-controller/controller/openrgb/client.go
Erki Aas 182aa34008 add retry logic to openrgb commands for boot-time stability
OpenRGB can crash (SIGABRT) during early boot when device enumeration
hasn't completed yet. Retry failed commands up to 4 times with a
3-second delay to let initialization finish.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:22:27 +03:00

69 lines
1.6 KiB
Go

package openrgb
import (
"fmt"
"log"
"os/exec"
"time"
)
const (
maxRetries = 4
retryDelay = 3 * time.Second
)
// Client controls RGB devices via the openrgb CLI.
type Client struct {
bin string
}
func Connect() (*Client, error) {
bin, err := exec.LookPath("openrgb")
if err != nil {
return nil, fmt.Errorf("openrgb not found in PATH: %w", err)
}
// Verify openrgb can list devices (confirms it's running/accessible)
cmd := exec.Command(bin, "--list-devices")
if out, err := cmd.CombinedOutput(); err != nil {
return nil, fmt.Errorf("openrgb list-devices failed: %w\n%s", err, out)
}
return &Client{bin: bin}, nil
}
func (c *Client) Close() error {
return nil
}
// run executes an openrgb command with retries to handle transient failures
// during early boot when device enumeration may not be complete.
func (c *Client) run(args ...string) error {
var lastErr error
for attempt := range maxRetries {
cmd := exec.Command(c.bin, args...)
out, err := cmd.CombinedOutput()
if err == nil {
return nil
}
lastErr = fmt.Errorf("openrgb %v: %w\n%s", args, err, out)
if attempt < maxRetries-1 {
log.Printf("openrgb command failed (attempt %d/%d), retrying in %v: %v",
attempt+1, maxRetries, retryDelay, lastErr)
time.Sleep(retryDelay)
}
}
return lastErr
}
// SetAllOff sets all devices to black (off) using direct mode.
func (c *Client) SetAllOff() error {
return c.run("--mode", "direct", "--color", "000000")
}
// SetAllOn sets all devices to the given color.
func (c *Client) SetAllOn(r, g, b byte) error {
color := fmt.Sprintf("%02X%02X%02X", r, g, b)
return c.run("--mode", "direct", "--color", color)
}