initial commit
This commit is contained in:
82
controller/controller.go
Normal file
82
controller/controller.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"led-controller/config"
|
||||
"led-controller/controller/openrgb"
|
||||
"led-controller/controller/relay"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
cfg *config.Config
|
||||
orgb *openrgb.Client
|
||||
relay *relay.Relay
|
||||
}
|
||||
|
||||
func New(cfg *config.Config) (*Controller, error) {
|
||||
c := &Controller{cfg: cfg}
|
||||
|
||||
if cfg.OpenRGB.Enabled {
|
||||
client, err := openrgb.Connect()
|
||||
if err != nil {
|
||||
log.Printf("warning: openrgb unavailable: %v", err)
|
||||
} else {
|
||||
c.orgb = client
|
||||
log.Println("openrgb: connected via CLI")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Relay.Enabled {
|
||||
c.relay = relay.New(cfg.Relay.Device, cfg.Relay.Channel, cfg.Relay.Baud)
|
||||
log.Printf("relay: configured on %s channel %d", cfg.Relay.Device, cfg.Relay.Channel)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Controller) SetLEDs(on bool) error {
|
||||
var errs []error
|
||||
|
||||
if c.cfg.OpenRGB.Enabled {
|
||||
if err := c.setOpenRGB(on); err != nil {
|
||||
errs = append(errs, fmt.Errorf("openrgb: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if c.cfg.Relay.Enabled && c.relay != nil {
|
||||
if err := c.relay.Set(on); err != nil {
|
||||
errs = append(errs, fmt.Errorf("relay: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("%v", errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) setOpenRGB(on bool) error {
|
||||
if c.orgb == nil {
|
||||
client, err := openrgb.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.orgb = client
|
||||
}
|
||||
|
||||
if on {
|
||||
return c.orgb.SetAllOn(0xFF, 0xFF, 0xFF)
|
||||
}
|
||||
return c.orgb.SetAllOff()
|
||||
}
|
||||
|
||||
func (c *Controller) Close() {
|
||||
if c.orgb != nil {
|
||||
c.orgb.Close()
|
||||
}
|
||||
if c.relay != nil {
|
||||
c.relay.Close()
|
||||
}
|
||||
}
|
||||
49
controller/openrgb/client.go
Normal file
49
controller/openrgb/client.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package openrgb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SetAllOff sets all devices to black (off) using direct mode.
|
||||
func (c *Client) SetAllOff() error {
|
||||
cmd := exec.Command(c.bin, "--mode", "direct", "--color", "000000")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("openrgb set off: %w\n%s", err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
cmd := exec.Command(c.bin, "--mode", "direct", "--color", color)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("openrgb set on: %w\n%s", err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
82
controller/relay/relay.go
Normal file
82
controller/relay/relay.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// CH341 serial relay protocol (LCUS-type modules on /dev/ttyUSBx).
|
||||
//
|
||||
// Command format:
|
||||
// ON: 0xA0 <channel> 0x01 <checksum>
|
||||
// OFF: 0xA0 <channel> 0x00 <checksum>
|
||||
// checksum = (0xA0 + channel + state) & 0xFF
|
||||
|
||||
type Relay struct {
|
||||
devicePath string
|
||||
channel byte
|
||||
baud int
|
||||
}
|
||||
|
||||
func New(devicePath string, channel, baud int) *Relay {
|
||||
return &Relay{
|
||||
devicePath: devicePath,
|
||||
channel: byte(channel),
|
||||
baud: baud,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Relay) Set(on bool) error {
|
||||
f, err := os.OpenFile(r.devicePath, os.O_RDWR|unix.O_NOCTTY, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("relay open %s: %w", r.devicePath, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fd := int(f.Fd())
|
||||
if err := configureSerial(fd, r.baud); err != nil {
|
||||
return fmt.Errorf("relay serial config: %w", err)
|
||||
}
|
||||
|
||||
var state byte
|
||||
if on {
|
||||
state = 0x01
|
||||
}
|
||||
|
||||
checksum := (0xA0 + r.channel + state) & 0xFF
|
||||
cmd := []byte{0xA0, r.channel, state, checksum}
|
||||
|
||||
if _, err := f.Write(cmd); err != nil {
|
||||
return fmt.Errorf("relay write: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Relay) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureSerial(fd, baud int) error {
|
||||
baudRate, ok := baudRates[baud]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported baud rate: %d", baud)
|
||||
}
|
||||
|
||||
var t unix.Termios
|
||||
t.Cflag = unix.CS8 | unix.CLOCAL | unix.CREAD | baudRate
|
||||
t.Cc[unix.VMIN] = 1
|
||||
t.Cc[unix.VTIME] = 0
|
||||
|
||||
return unix.IoctlSetTermios(fd, unix.TCSETS, &t)
|
||||
}
|
||||
|
||||
var baudRates = map[int]uint32{
|
||||
9600: unix.B9600,
|
||||
19200: unix.B19200,
|
||||
38400: unix.B38400,
|
||||
57600: unix.B57600,
|
||||
115200: unix.B115200,
|
||||
}
|
||||
Reference in New Issue
Block a user