package hardware import ( "context" "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 manages all the FTDI peripherals type FTDIFinder struct { wg sync.WaitGroup mu sync.Mutex detected map[string]*FTDIPeripheral // Detected peripherals scanEvery time.Duration // Scans peripherals periodically onArrival func(context.Context, Peripheral) // When a peripheral arrives onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewFTDIFinder creates a new FTDI finder func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder { log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created") return &FTDIFinder{ scanEvery: scanEvery, detected: make(map[string]*FTDIPeripheral), } } // OnArrival is the callback function when a new peripheral arrives func (f *FTDIFinder) OnArrival(cb func(context.Context, Peripheral)) { f.onArrival = cb } // OnRemoval i the callback when a peripheral goes away func (f *FTDIFinder) OnRemoval(cb func(context.Context, Peripheral)) { f.onRemoval = cb } // 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 } // Create creates a new peripheral, based on the peripheral information (manually created) func (f *FTDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { return "", nil } // Remove removes an existing peripheral (manually created) func (f *FTDIFinder) Remove(ctx context.Context, peripheral Peripheral) error { return nil } // Start starts the finder and search for peripherals func (f *FTDIFinder) Start(ctx context.Context) error { f.wg.Add(1) go func() { ticker := time.NewTicker(f.scanEvery) defer ticker.Stop() defer f.wg.Done() for { select { case <-ctx.Done(): return case <-ticker.C: // Scan the peripherals err := f.scanPeripherals(ctx) if err != nil { log.Err(err).Str("file", "FTDIFinder").Msg("unable to scan FTDI peripherals") } } } }() return nil } // GetName returns the name of the driver func (f *FTDIFinder) GetName() string { return "FTDI" } // 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") // Allocating C array size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIPeripheralC{})) devicesPtr := C.malloc(size) defer C.free(devicesPtr) devices := (*[1 << 20]C.FTDIPeripheralC)(devicesPtr)[:count:count] C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count)) currentMap := 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 currentMap[sn] = PeripheralInfo{ SerialNumber: sn, Name: desc, // IsOpen: isOpen, ProtocolName: "FTDI", } // Free C memory C.free_ftdi_device(&d) } log.Info().Any("peripherals", currentMap).Msg("available FTDI peripherals") // Detect arrivals for sn, peripheralData := range currentMap { // If the scanned peripheral isn't in the detected list, create it if _, known := f.detected[sn]; !known { peripheral := NewFTDIPeripheral(peripheralData) if f.onArrival != nil { f.onArrival(ctx, peripheral) } } } // Detect removals for detectedSN, detectedPeripheral := range f.detected { if _, still := currentMap[detectedSN]; !still { // Delete it from the detected list delete(f.detected, detectedSN) // Execute the removal callback if f.onRemoval != nil { f.onRemoval(ctx, detectedPeripheral) } } } return nil } // WaitStop stops the finder func (f *FTDIFinder) WaitStop() error { log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...") // Wait for goroutines to stop f.wg.Wait() log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped") return nil }