package hardware import ( "context" "errors" "fmt" "math/rand" "strings" "sync" "github.com/rs/zerolog/log" "github.com/wailsapp/wails/v2/pkg/runtime" ) // OS2LFinder represents how the protocol is defined type OS2LFinder struct { wg sync.WaitGroup mu sync.Mutex saved map[string]*OS2LPeripheral // The list of saved peripherals onArrival func(p PeripheralInfo) // When a peripheral arrives onRemoval func(p PeripheralInfo) // When a peripheral goes away } // NewOS2LFinder creates a new OS2L finder func NewOS2LFinder() *OS2LFinder { log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder created") return &OS2LFinder{ saved: make(map[string]*OS2LPeripheral), } } // Initialize initializes the finder func (f *OS2LFinder) Initialize() error { log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder initialized") return nil } // OnArrival is the callback function when a new peripheral arrives func (f *OS2LFinder) OnArrival(cb func(p PeripheralInfo)) { f.onArrival = cb } // OnRemoval i the callback when a peripheral goes away func (f *OS2LFinder) OnRemoval(cb func(p PeripheralInfo)) { f.onRemoval = cb } // RegisterPeripheral registers a new peripheral func (f *OS2LFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { f.mu.Lock() defer f.mu.Unlock() // If the SerialNumber is empty, generate another one if peripheralData.SerialNumber == "" { peripheralData.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32))) } // Create a new OS2L peripheral peripheral, err := NewOS2LPeripheral(peripheralData) if err != nil { return "", fmt.Errorf("unable to create the OS2L peripheral: %w", err) } // Set the event callback peripheral.SetEventCallback(func(event any) { runtime.EventsEmit(ctx, string(PeripheralEventEmitted), peripheralData.SerialNumber, event) }) f.saved[peripheralData.SerialNumber] = peripheral log.Trace().Str("file", "OS2LFinder").Str("serialNumber", peripheralData.SerialNumber).Msg("OS2L peripheral created") // New OS2L peripheral has arrived if f.onArrival != nil { f.onArrival(peripheral.GetInfo()) } f.wg.Add(1) go func() { defer f.wg.Done() // Connect the OS2L peripheral err = peripheral.Connect(ctx) if err != nil { log.Err(err).Str("file", "OS2LFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral") return } // Peripheral connected, activate it err = peripheral.Activate(ctx) if err != nil { log.Err(err).Str("file", "OS2LFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the OS2L peripheral") return } }() // Emits the event in the hardware runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) return peripheralData.SerialNumber, nil } // UnregisterPeripheral unregisters an existing peripheral func (f *OS2LFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error { f.mu.Lock() defer f.mu.Unlock() if peripheral, detected := f.saved[peripheralData.SerialNumber]; detected { // Deactivating peripheral err := peripheral.Deactivate(ctx) if err != nil { log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral") return nil } // Disconnecting peripheral err = peripheral.Disconnect(ctx) if err != nil { log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral") return nil } } // The OS2L peripheral has gone f.onRemoval(peripheralData) delete(f.saved, peripheralData.SerialNumber) runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) return nil } // GetName returns the name of the driver func (f *OS2LFinder) GetName() string { return "OS2L" } // GetPeripheralSettings gets the peripheral settings func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]any, error) { // Return the specified peripheral peripheral, found := f.saved[peripheralID] if !found { log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the OS2L finder") return nil, fmt.Errorf("unable to found the peripheral") } log.Debug().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the OS2L finder") return peripheral.GetSettings(), nil } // SetPeripheralSettings sets the peripheral settings func (f *OS2LFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error { // Return the specified peripheral peripheral, found := f.saved[peripheralID] if !found { log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder") return fmt.Errorf("unable to found the peripheral") } // Set the peripheral settings return peripheral.SetSettings(ctx, settings) } // Start starts the finder func (f *OS2LFinder) Start(ctx context.Context) error { // No peripherals to scan here return nil } // WaitStop stops the finder func (f *OS2LFinder) WaitStop() error { log.Trace().Str("file", "OS2LFinder").Msg("stopping the OS2L finder...") // Close the channel // close(f.scanChannel) // Wait for all the peripherals to close log.Trace().Str("file", "OS2LFinder").Msg("closing all OS2L peripherals") var errs []error for registeredPeripheralSN, registeredPeripheral := range f.saved { err := registeredPeripheral.WaitStop() if err != nil { errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err)) } } // Waiting internal tasks f.wg.Wait() // Returning errors if len(errs) > 0 { return errors.Join(errs...) } log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder stopped") return nil } // ForceScan scans the interfaces (not implemented) func (f *OS2LFinder) ForceScan() { }