package hardware import ( "bufio" "context" _ "embed" "fmt" "os" "os/exec" goRuntime "runtime" "strconv" "strings" "time" "github.com/rs/zerolog/log" ) const ( scanDelay = 4 * time.Second // Waiting delay before scanning the FTDI devices ) // FTDIDriver represents how the protocol is defined type FTDIDriver struct { peripherals map[string]Peripheral } // NewFTDIDriver creates a new FTDI finder func NewFTDIDriver() *FTDIDriver { log.Trace().Str("file", "FTDIDriver").Msg("FTDI driver created") return &FTDIDriver{ peripherals: make(map[string]Peripheral), } } //go:embed third-party/ftdi/detectFTDI.exe var findFTDI []byte // Initialize initializes the FTDI driver func (d *FTDIDriver) Initialize() error { // Check platform if goRuntime.GOOS != "windows" { log.Error().Str("file", "FTDIDriver").Str("platform", goRuntime.GOOS).Msg("FTDI driver not compatible with your platform") return fmt.Errorf(" The FTDI driver is not compatible with your platform yet (%s)", goRuntime.GOOS) } log.Trace().Str("file", "FTDIDriver").Msg("FTDI driver initialized") return nil } // GetName returns the name of the driver func (d *FTDIDriver) GetName() string { return "FTDI" } // GetPeripheral gets the peripheral that correspond to the specified ID func (d *FTDIDriver) GetPeripheral(peripheralID string) (Peripheral, bool) { // Return the specified peripheral peripheral := d.peripherals[peripheralID] if peripheral == nil { log.Error().Str("file", "FTDIDriver").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI driver") return nil, false } log.Debug().Str("file", "FTDIDriver").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI driver") return peripheral, true } // Scan scans the FTDI peripherals func (d *FTDIDriver) Scan(ctx context.Context) error { log.Trace().Str("file", "FTDIDriver").Msg("FTDI scan triggered") time.Sleep(scanDelay) // Create a temporary file tempFile, err := os.CreateTemp("", "findFTDI*.exe") if err != nil { return err } defer os.Remove(tempFile.Name()) log.Trace().Str("file", "FTDIDriver").Msg("has created the FIND executable temp") // Write the embedded executable to the temp file if _, err := tempFile.Write(findFTDI); err != nil { return err } tempFile.Close() log.Trace().Str("file", "FTDIDriver").Msg("has written the FIND executable") ftdiPeripherals := make(map[string]Peripheral) finder := exec.Command(tempFile.Name()) log.Trace().Str("file", "FTDIDriver").Msg("has executed the FIND executable") stdout, err := finder.StdoutPipe() if err != nil { return fmt.Errorf("unable to create the stdout pipe: %s", err) } stderr, err := finder.StderrPipe() if err != nil { return fmt.Errorf("unable to create the stderr pipe: %s", err) } err = finder.Start() if err != nil { return fmt.Errorf("unable to find FTDI devices: %s", err) } scannerErr := bufio.NewScanner(stderr) for scannerErr.Scan() { return fmt.Errorf("unable to find FTDI devices: %s", scannerErr.Text()) } scanner := bufio.NewScanner(stdout) for scanner.Scan() { peripheralString := scanner.Text() // The program output is like '0:1:2' where 0 is the location, 1 is the S/N and 2 is the name peripheralInfo := strings.Split(peripheralString, ":") log.Debug().Str("file", "FTDIDriver").Str("peripheralName", peripheralInfo[2]).Str("peripheralSN", peripheralInfo[1]).Msg("new FTDI peripheral detected") // Convert the location to an integer location, err := strconv.Atoi(peripheralInfo[0]) if err != nil { log.Warn().Str("file", "FTDIDriver").Str("peripheralName", peripheralInfo[2]).Msg("no location provided for this FTDI peripheral") location = -1 } // Add the peripheral to the temporary list peripheral, err := NewFTDIPeripheral(peripheralInfo[2], peripheralInfo[1], location) if err != nil { return fmt.Errorf("Unable to create the FTDI peripheral: %v", err) } ftdiPeripherals[peripheralInfo[1]] = peripheral log.Trace().Str("file", "FTDIDriver").Str("peripheralName", peripheralInfo[2]).Msg("successfully added the FTDI peripheral to the driver") } // Compare with the current peripherals to detect arrivals/removals removedList, addedList := comparePeripherals(d.peripherals, ftdiPeripherals) // Emit the events emitPeripheralsEvents(ctx, removedList, PeripheralRemoval) log.Info().Str("file", "FTDIDriver").Msg("FTDI remove list emitted to the front") emitPeripheralsEvents(ctx, addedList, PeripheralArrival) log.Info().Str("file", "FTDIDriver").Msg("FTDI add list emitted to the front") // Store the new peripherals list d.peripherals = ftdiPeripherals return nil } // CreatePeripheral is not implemented here func (d *FTDIDriver) CreatePeripheral(context.Context) (Peripheral, error) { return nil, nil } // RemovePeripheral is not implemented here func (d *FTDIDriver) RemovePeripheral(serialNumber string) error { return nil }