package relay import ( "fmt" "os" "golang.org/x/sys/unix" ) // CH341 serial relay protocol (LCUS-type modules on /dev/ttyUSBx). // // Command format: // ON: 0xA0 0x01 // OFF: 0xA0 0x00 // 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, }