2024-12-15 13:45:46 +01:00
|
|
|
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
|
|
|
|
|
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
|
2024-12-23 17:22:37 +01:00
|
|
|
func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPeripheral, error) {
|
2024-12-15 13:45:46 +01:00
|
|
|
// 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,
|
|
|
|
|
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{
|
2024-12-20 17:18:57 +01:00
|
|
|
Name: p.name,
|
|
|
|
|
SerialNumber: p.serialNumber,
|
|
|
|
|
ProtocolName: "FTDI",
|
2024-12-15 13:45:46 +01:00
|
|
|
}
|
|
|
|
|
}
|