initial commit

This commit is contained in:
2026-04-07 23:46:35 +03:00
commit cdbd8abaa4
13 changed files with 681 additions and 0 deletions

82
controller/controller.go Normal file
View 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()
}
}

View 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
View 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,
}