package hardware import ( "context" _ "embed" "fmt" goRuntime "runtime" "sync" "time" "unsafe" "github.com/rs/zerolog/log" ) /* #include #cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldetectFTDI #include "cpp/include/detectFTDIBridge.h" */ import "C" // FTDIFinder represents how the protocol is defined type FTDIFinder struct { findTicker time.Ticker // Peripherals find ticker foundPeripherals map[string]PeripheralInfo // The list of peripherals handled by this finder registeredPeripherals map[string]*FTDIPeripheral // The list of found peripherals scanChannel chan struct{} // The channel to trigger a scan event goWait sync.WaitGroup // Check goroutines execution } // NewFTDIFinder creates a new FTDI finder func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder { log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created") return &FTDIFinder{ findTicker: *time.NewTicker(findPeriod), foundPeripherals: make(map[string]PeripheralInfo), registeredPeripherals: make(map[string]*FTDIPeripheral), scanChannel: make(chan struct{}), } } // RegisterPeripheral registers a new peripheral func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { ftdiPeripheral, err := NewFTDIPeripheral(peripheralData) if err != nil { return "", fmt.Errorf("unable to create the FTDI peripheral: %v", err) } f.registeredPeripherals[peripheralData.SerialNumber] = ftdiPeripheral log.Trace().Any("periph", &ftdiPeripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created") // Peripheral created, connect it err = ftdiPeripheral.Connect(ctx) if err != nil { log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral") } // Peripheral connected, activate it err = ftdiPeripheral.Activate(ctx) if err != nil { log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the peripheral") } // Peripheral activated return peripheralData.SerialNumber, nil } // UnregisterPeripheral unregisters an existing peripheral func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID string) error { peripheral, registered := f.registeredPeripherals[peripheralID] if registered { // Deactivating peripheral err := peripheral.Deactivate(ctx) if err != nil { return err } // Disconnecting peripheral err = peripheral.Disconnect(ctx) if err != nil { return err } } delete(f.registeredPeripherals, peripheralID) return nil } // Initialize initializes the FTDI finder func (f *FTDIFinder) Initialize() error { // Check platform if goRuntime.GOOS != "windows" { log.Error().Str("file", "FTDIFinder").Str("platform", goRuntime.GOOS).Msg("FTDI finder not compatible with your platform") return fmt.Errorf("the FTDI finder is not compatible with your platform yet (%s)", goRuntime.GOOS) } log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder initialized") return nil } // Start starts the finder and search for peripherals func (f *FTDIFinder) Start(ctx context.Context) error { f.goWait.Add(1) go func() { defer f.goWait.Done() for { select { case <-ctx.Done(): return case <-f.findTicker.C: // Scan the peripherals err := f.scanPeripherals(ctx) if err != nil { log.Err(err).Str("file", "FTDIFinder").Msg("unable to scan FTDI peripherals") } case <-f.scanChannel: // Scan the peripherals err := f.scanPeripherals(ctx) if err != nil { log.Err(err).Str("file", "FTDIFinder").Msg("unable to scan FTDI peripherals") } } } }() return nil } // ForceScan explicily asks for scanning peripherals func (f *FTDIFinder) ForceScan() { f.scanChannel <- struct{}{} } // Stop stops the finder func (f *FTDIFinder) Stop() error { log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...") // Wait for goroutines to stop f.goWait.Wait() // Stop the ticker f.findTicker.Stop() log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped") return nil } // GetName returns the name of the driver func (f *FTDIFinder) GetName() string { return "FTDI" } // GetPeripheralSettings gets the peripheral settings func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) { // Return the specified peripheral peripheral, found := f.registeredPeripherals[peripheralID] if !found { log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder") return nil, fmt.Errorf("unable to found the peripheral") } log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") return peripheral.GetSettings(), nil } // SetPeripheralSettings sets the peripheral settings func (f *FTDIFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error { // Return the specified peripheral peripheral, found := f.registeredPeripherals[peripheralID] if !found { log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder") return fmt.Errorf("unable to found the peripheral") } log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") return peripheral.SetSettings(settings) } // scanPeripherals scans the FTDI peripherals func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { log.Trace().Str("file", "FTDIFinder").Msg("FTDI scan triggered") count := int(C.get_peripherals_number()) log.Info().Int("number", count).Msg("number of FTDI devices connected") // Alloue un tableau de structures côté C size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIPeripheralC{})) devicesPtr := C.malloc(size) defer C.free(devicesPtr) devices := (*[1 << 30]C.FTDIPeripheralC)(devicesPtr)[:count:count] type device struct { SerialNumber string Description string IsOpen bool } C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count)) temporaryPeripherals := make(map[string]PeripheralInfo) for i := 0; i < count; i++ { d := devices[i] sn := C.GoString(d.serialNumber) desc := C.GoString(d.description) isOpen := d.isOpen != 0 temporaryPeripherals[sn] = PeripheralInfo{ SerialNumber: sn, Name: desc, IsOpen: isOpen, ProtocolName: "FTDI", } // Libération mémoire allouée côté C C.free_ftdi_device(&d) } log.Info().Any("peripherals", temporaryPeripherals).Msg("available FTDI peripherals") // Emit the peripherals changes to the front emitPeripheralsChanges(ctx, f.foundPeripherals, temporaryPeripherals) // Store the new peripherals list f.foundPeripherals = temporaryPeripherals return nil }