package hardware import ( "bufio" _ "embed" "fmt" "io" "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 finderName string // The name of the parent finder 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, finderName string) (*FTDIPeripheral, error) { // Create a temporary file tempFile, err := os.CreateTemp("", "dmxSender*.exe") if err != nil { return nil, err } // Write the embedded executable to the temp file if _, err := tempFile.Write(dmxSender); err != nil { return nil, err } tempFile.Close() return &FTDIPeripheral{ name: name, programName: tempFile.Name(), serialNumber: serialNumber, location: location, finderName: finderName, 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 fmt.Println(p.dmxSender) if p.dmxSender == nil { // Executing the command p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location)) var err error p.stdout, err = p.dmxSender.StdoutPipe() if err != nil { log.Fatalf("Unable to create the stdout pipe: %v", err) } p.stdin, err = p.dmxSender.StdinPipe() if err != nil { log.Fatalf("Unable to create the stdin pipe: %v", err) } p.stderr, err = p.dmxSender.StderrPipe() if err != nil { log.Fatalf("Unable to create the stderr pipe: %v", err) } go func() { scanner := bufio.NewScanner(p.stderr) for scanner.Scan() { // Traitez chaque ligne lue depuis stderr fmt.Printf("Erreur : %s\n", scanner.Text()) } if err := scanner.Err(); err != nil { log.Printf("Error reading from stderr: %v", err) } }() p.wg.Add(1) go func() { defer p.wg.Done() err = p.dmxSender.Run() if err != nil { // Affiche l'erreur (cela inclut également les erreurs de retour du programme) fmt.Printf("Erreur lors de l'exécution : %v\n", err) // Vérifie si l'erreur est liée au code de retour if exitError, ok := err.(*exec.ExitError); ok { // Récupère et affiche le code de retour fmt.Printf("Le programme s'est terminé avec le code : %d\n", exitError.ExitCode()) fmt.Println(p.stderr) } } else { fmt.Println("Le programme s'est terminé avec succès.") } }() } return nil } // Disconnect disconnects the FTDI peripheral func (p *FTDIPeripheral) Disconnect() error { if p.dmxSender != nil { _, err := io.WriteString(p.stdin, string([]byte{0x04, 0x00, 0x00, 0x00})) if err != nil { 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 { return fmt.Errorf("Unable to delete the temporary file: %v", err) } return nil } return fmt.Errorf("Unable to disconnect: not connected") // if p.dmxSender != nil { // err := p.dmxSender.Process.Kill() // if err != nil { // 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 { // return fmt.Errorf("Unable to delete the temporary file: %v", err) // } // return nil // } // return fmt.Errorf("Unable to disconnect: not connected") } // Activate activates the FTDI peripheral func (p *FTDIPeripheral) Activate() error { if p.dmxSender != nil { _, err := io.WriteString(p.stdin, string([]byte{0x01, 0x00, 0x00, 0x00})) if err != nil { return fmt.Errorf("Unable to activate: %v", err) } return nil } return fmt.Errorf("Unable to activate: not connected") } // Deactivate deactivates the FTDI peripheral func (p *FTDIPeripheral) Deactivate() error { if p.dmxSender != nil { _, err := io.WriteString(p.stdin, string([]byte{0x02, 0x00, 0x00, 0x00})) if err != nil { return fmt.Errorf("Unable to deactivate: %v", err) } return nil } 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 { fmt.Println(channelValue) commandString := []byte{0x03, 0x01, 0x00, 0xff, 0x03, 0x02, 0x00, channelValue} _, err := io.WriteString(p.stdin, string(commandString)) if err != nil { return fmt.Errorf("Unable to set device property: %v", err) } return nil } 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", } }