Files
dmxconnect/hardware/FTDIFinder.go

248 lines
7.5 KiB
Go
Raw Normal View History

package hardware
import (
"context"
_ "embed"
2025-11-02 10:57:53 +01:00
"errors"
"fmt"
goRuntime "runtime"
"sync"
"time"
"unsafe"
"github.com/rs/zerolog/log"
2025-11-10 14:18:22 +01:00
"github.com/wailsapp/wails/v2/pkg/runtime"
)
/*
#include <stdlib.h>
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldetectFTDI
#include "cpp/include/detectFTDIBridge.h"
*/
import "C"
2025-11-02 10:57:53 +01:00
// FTDIFinder manages all the FTDI peripherals
type FTDIFinder struct {
2025-11-02 10:57:53 +01:00
wg sync.WaitGroup
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
}
// NewFTDIFinder creates a new FTDI finder
func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder {
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created")
return &FTDIFinder{
2025-11-02 10:57:53 +01:00
findTicker: time.NewTicker(findPeriod),
2025-01-26 12:01:31 +01:00
foundPeripherals: make(map[string]PeripheralInfo),
registeredPeripherals: make(map[string]*FTDIPeripheral),
2025-01-26 12:01:31 +01:00
scanChannel: make(chan struct{}),
}
}
2025-01-26 12:01:31 +01:00
// RegisterPeripheral registers a new peripheral
func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
2025-11-02 10:57:53 +01:00
// Create a new FTDI peripheral
2025-01-26 12:01:31 +01:00
ftdiPeripheral, err := NewFTDIPeripheral(peripheralData)
if err != nil {
return "", fmt.Errorf("unable to create the FTDI peripheral: %v", err)
}
2025-11-02 10:57:53 +01:00
// Register it in the finder
f.registeredPeripherals[peripheralData.SerialNumber] = ftdiPeripheral
2025-01-26 12:01:31 +01:00
log.Trace().Any("periph", &ftdiPeripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created")
2025-11-02 10:57:53 +01:00
2025-11-10 14:18:22 +01:00
// Emit the event to the front
runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData)
// 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
2025-01-26 12:01:31 +01:00
return peripheralData.SerialNumber, nil
}
// UnregisterPeripheral unregisters an existing peripheral
func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID string) error {
2025-01-26 12:01:31 +01:00
peripheral, registered := f.registeredPeripherals[peripheralID]
if registered {
// Deactivating peripheral
err := peripheral.Deactivate(ctx)
if err != nil {
return err
}
// Disconnecting peripheral
2025-11-02 10:57:53 +01:00
err = peripheral.Disconnect()
2025-01-26 12:01:31 +01:00
if err != nil {
return err
}
2025-11-10 14:18:22 +01:00
delete(f.registeredPeripherals, peripheralID)
// Emit the event to the front
runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheral.info)
2025-01-26 12:01:31 +01:00
}
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 {
2025-11-02 10:57:53 +01:00
f.wg.Add(1)
go func() {
2025-11-02 10:57:53 +01:00
defer f.wg.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() {
2025-11-02 10:57:53 +01:00
select {
case f.scanChannel <- struct{}{}:
default:
// Ignore if the channel is full or if it is closed
}
}
// GetName returns the name of the driver
func (f *FTDIFinder) GetName() string {
return "FTDI"
}
2025-01-26 12:01:31 +01:00
// GetPeripheralSettings gets the peripheral settings
func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
// Return the specified peripheral
2025-01-26 12:01:31 +01:00
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")
2025-01-26 12:01:31 +01:00
return nil, fmt.Errorf("unable to found the peripheral")
}
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
2025-01-26 12:01:31 +01:00
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")
2025-11-02 10:57:53 +01:00
// Allocating C array
size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIPeripheralC{}))
devicesPtr := C.malloc(size)
defer C.free(devicesPtr)
2025-11-02 10:57:53 +01:00
devices := (*[1 << 20]C.FTDIPeripheralC)(devicesPtr)[:count:count]
C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
2025-01-26 12:01:31 +01:00
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",
2025-01-26 12:01:31 +01:00
}
2025-11-02 10:57:53 +01:00
// Free C memory
C.free_ftdi_device(&d)
}
log.Info().Any("peripherals", temporaryPeripherals).Msg("available FTDI peripherals")
// Emit the peripherals changes to the front
2025-01-26 12:01:31 +01:00
emitPeripheralsChanges(ctx, f.foundPeripherals, temporaryPeripherals)
// Store the new peripherals list
2025-01-26 12:01:31 +01:00
f.foundPeripherals = temporaryPeripherals
return nil
}
2025-11-02 10:57:53 +01:00
// WaitStop stops the finder
func (f *FTDIFinder) WaitStop() error {
log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
// Stop the ticker
f.findTicker.Stop()
// Close the channel
close(f.scanChannel)
// Wait for all the peripherals to close
log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals")
var errs []error
for registeredPeripheralSN, registeredPeripheral := range f.registeredPeripherals {
err := registeredPeripheral.WaitStop()
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
}
}
// Wait for goroutines to stop
f.wg.Wait()
// Returning errors
if len(errs) > 0 {
return errors.Join(errs...)
}
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
return nil
}