Files
dmxconnect/hardware/FTDIPeripheral.go

205 lines
7.9 KiB
Go

package hardware
import (
"bufio"
_ "embed"
"fmt"
"io"
"github.com/rs/zerolog/log"
"os"
"os/exec"
"sync"
)
const (
activateCommandString = 0x01
deactivateCommandString = 0x02
setCommandString = 0x03
)
//go:embed third-party/ftdi/dmxSender.exe
var dmxSender []byte
// FTDIPeripheral contains the data of an FTDI peripheral
type FTDIPeripheral struct {
name string // The name of the peripheral
serialNumber string // The S/N of the FTDI peripheral
location int // The location of the peripheral
universesNumber int // The number of DMX universes handled by this peripheral
programName string // The temp file name of the executable
dmxSender *exec.Cmd // The command to pilot the DMX sender program
stdin io.WriteCloser // For writing in the DMX sender
stdout io.ReadCloser // For reading from the DMX sender
stderr io.ReadCloser // For reading the errors
disconnectChan chan struct{} // Channel to cancel the connection
errorsChan chan error // Channel to get the errors
wg sync.WaitGroup // Tasks management
}
// NewFTDIPeripheral creates a new FTDI peripheral
func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPeripheral, error) {
log.Info().Str("file", "FTDIPeripheral").Str("name", name).Str("s/n", serialNumber).Int("location", location).Msg("FTDI peripheral created")
// Create a temporary file
tempFile, err := os.CreateTemp("", "dmxSender*.exe")
if err != nil {
return nil, err
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", serialNumber).Msg("FTDI sender temp created")
// Write the embedded executable to the temp file
if _, err := tempFile.Write(dmxSender); err != nil {
return nil, err
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", serialNumber).Msg("FTDI sender written")
tempFile.Close()
return &FTDIPeripheral{
name: name,
programName: tempFile.Name(),
serialNumber: serialNumber,
location: location,
universesNumber: 1,
disconnectChan: make(chan struct{}),
errorsChan: make(chan error, 1),
}, nil
}
// Connect connects the FTDI peripheral
func (p *FTDIPeripheral) Connect() error {
// Connect if no connection is already running
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("connecting FTDI peripheral...")
if p.dmxSender == nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("no instance of dmxSender for this FTDI")
// Executing the command
p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location))
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("no instance of dmxSender for this FTDI")
var err error
p.stdout, err = p.dmxSender.StdoutPipe()
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stdout pipe")
return fmt.Errorf("unable to create the stdout pipe: %v", err)
}
p.stdin, err = p.dmxSender.StdinPipe()
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stdin pipe")
return fmt.Errorf("unable to create the stdin pipe: %v", err)
}
p.stderr, err = p.dmxSender.StderrPipe()
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stderr pipe")
return fmt.Errorf("unable to create the stderr pipe: %v", err)
}
go func() {
scanner := bufio.NewScanner(p.stderr)
for scanner.Scan() {
// Traitez chaque ligne lue depuis stderr
log.Err(fmt.Errorf(scanner.Text())).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error detected in dmx sender")
}
if err := scanner.Err(); err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error reading from stderr")
}
}()
p.wg.Add(1)
go func() {
defer p.wg.Done()
err = p.dmxSender.Run()
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while execution of dmw sender")
if exitError, ok := err.(*exec.ExitError); ok {
log.Warn().Str("file", "FTDIPeripheral").Int("exitCode", exitError.ExitCode()).Str("s/n", p.serialNumber).Msg("dmx sender exited with code")
}
} else {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmx sender exited successfully")
}
}()
}
return nil
}
// Disconnect disconnects the FTDI peripheral
func (p *FTDIPeripheral) Disconnect() error {
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("disconnecting FTDI peripheral...")
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
_, err := io.WriteString(p.stdin, string([]byte{0x04, 0x00, 0x00, 0x00}))
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
return fmt.Errorf("unable to disconnect: %v", err)
}
p.stdin.Close()
p.stdout.Close()
p.dmxSender = nil
err = os.Remove(p.programName)
if err != nil {
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to delete the dmx sender temporary file")
return fmt.Errorf("unable to delete the temporary file: %v", err)
}
return nil
}
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while disconnecting: not connected")
return fmt.Errorf("unable to disconnect: not connected")
}
// Activate activates the FTDI peripheral
func (p *FTDIPeripheral) Activate() error {
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
_, err := io.WriteString(p.stdin, string([]byte{0x01, 0x00, 0x00, 0x00}))
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
return fmt.Errorf("unable to activate: %v", err)
}
return nil
}
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while activating: not connected")
return fmt.Errorf("unable to activate: not connected")
}
// Deactivate deactivates the FTDI peripheral
func (p *FTDIPeripheral) Deactivate() error {
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
_, err := io.WriteString(p.stdin, string([]byte{0x02, 0x00, 0x00, 0x00}))
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
return fmt.Errorf("unable to deactivate: %v", err)
}
return nil
}
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while deactivating: not connected")
return fmt.Errorf("unable to deactivate: not connected")
}
// SetDeviceProperty sends a command to the specified device
func (p *FTDIPeripheral) SetDeviceProperty(uint32, channelNumber uint32, channelValue byte) error {
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
commandString := []byte{0x03, 0x01, 0x00, 0xff, 0x03, 0x02, 0x00, channelValue}
_, err := io.WriteString(p.stdin, string(commandString))
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
return fmt.Errorf("unable to set device property: %v", err)
}
return nil
}
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while setting device property: not connected")
return fmt.Errorf("unable to set device property: not connected")
}
// GetInfo gets all the peripheral information
func (p *FTDIPeripheral) GetInfo() PeripheralInfo {
return PeripheralInfo{
Name: p.name,
SerialNumber: p.serialNumber,
ProtocolName: "FTDI",
}
}