Files
ezcoo-usb-control/pkg/ezcoo/device.go
T

163 lines
2.8 KiB
Go

package ezcoo
import (
"bufio"
"fmt"
"log/slog"
"strings"
"time"
"go.bug.st/serial"
)
func (d *Device) GetStatus() State {
d.mu.RLock()
defer d.mu.RUnlock()
return d.current
}
func (d *Device) SetOutput(out, in int) {
cmd := fmt.Sprintf("EZS OUT%d VS IN%d", out, in)
select {
case d.cmds <- cmd:
default:
slog.Warn("command queue full, dropping", "cmd", cmd)
}
d.poll()
time.AfterFunc(time.Second, d.poll)
}
func (d *Device) poll() {
select {
case d.cmds <- pollCmd:
default:
}
}
func (d *Device) Run(onState func(State), onAvailable func(bool), done <-chan struct{}) {
if d.cfg.PollInterval > 0 {
go d.pollLoop(done)
}
for {
port, err := Open(d.cfg)
if err != nil {
slog.Error("open serial", "err", err, "retry_in", "5s")
select {
case <-done:
return
case <-time.After(5 * time.Second):
continue
}
}
slog.Info("serial connected", "port", d.cfg.Port)
onAvailable(true)
d.poll()
d.runPort(port, onState, done)
onAvailable(false)
select {
case <-done:
return
default:
slog.Warn("serial disconnected, reconnecting in 5s")
time.Sleep(5 * time.Second)
}
}
}
func (d *Device) runPort(port serial.Port, onState func(State), done <-chan struct{}) {
defer port.Close()
reader := bufio.NewReader(port)
lineCh := make(chan string, 32)
go func() {
for {
line, err := reader.ReadString('\n')
line = strings.TrimSpace(line)
if err != nil {
slog.Error("serial read", "err", err)
close(lineCh)
return
}
if line == "" {
continue
}
select {
case lineCh <- line:
case <-done:
close(lineCh)
return
}
}
}()
collectUntil := time.Time{}
for {
var collectCh <-chan time.Time
if !collectUntil.IsZero() {
collectCh = time.After(time.Until(collectUntil))
}
select {
case <-done:
return
case cmd, ok := <-d.cmds:
if !ok {
return
}
slog.Debug("serial tx", "cmd", cmd)
if _, err := port.Write([]byte(cmd + "\r\n")); err != nil {
slog.Error("serial write", "err", err)
return
}
if strings.HasPrefix(cmd, "EZG OUT") {
collectUntil = time.Now().Add(500 * time.Millisecond)
}
case line, ok := <-lineCh:
if !ok {
slog.Error("serial reader closed")
return
}
d.applyLine(line)
case <-collectCh:
collectUntil = time.Time{}
onState(d.GetStatus())
}
}
}
func (d *Device) applyLine(line string) {
slog.Debug("serial rx", "line", line)
m := reOutVS.FindStringSubmatch(line)
if m == nil {
return
}
out, _ := parseInt(m[1])
in, _ := parseInt(m[2])
if out >= 1 && out <= 2 && in >= 1 && in <= 4 {
d.mu.Lock()
d.current[out-1] = in
d.mu.Unlock()
}
}
func (d *Device) pollLoop(done <-chan struct{}) {
t := time.NewTicker(d.cfg.PollInterval)
defer t.Stop()
for {
select {
case <-done:
return
case <-t.C:
d.poll()
}
}
}