resolved: activating/deactivating peripherals

This commit is contained in:
2025-11-01 12:23:22 +01:00
parent cb5c5b688e
commit abcc3e0b5e
17 changed files with 559 additions and 1904 deletions

View File

@@ -1,34 +1,31 @@
package hardware
import (
"bufio"
"context"
_ "embed"
"fmt"
"os"
"os/exec"
"path/filepath"
goRuntime "runtime"
"strconv"
"strings"
"sync"
"time"
"unsafe"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
const (
ftdiFinderExecutableName = "FTDI_finder.exe"
)
/*
#include <stdlib.h>
#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
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
@@ -37,7 +34,7 @@ func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder {
return &FTDIFinder{
findTicker: *time.NewTicker(findPeriod),
foundPeripherals: make(map[string]PeripheralInfo),
registeredPeripherals: make(map[string]FTDIPeripheral),
registeredPeripherals: make(map[string]*FTDIPeripheral),
scanChannel: make(chan struct{}),
}
}
@@ -48,16 +45,33 @@ func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData Peri
if err != nil {
return "", fmt.Errorf("unable to create the FTDI peripheral: %v", err)
}
f.registeredPeripherals[peripheralData.SerialNumber] = *ftdiPeripheral
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(peripheralID string) error {
func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID string) error {
peripheral, registered := f.registeredPeripherals[peripheralID]
if registered {
err := peripheral.Disconnect()
// Deactivating peripheral
err := peripheral.Deactivate(ctx)
if err != nil {
return err
}
// Disconnecting peripheral
err = peripheral.Disconnect(ctx)
if err != nil {
return err
}
@@ -66,9 +80,6 @@ func (f *FTDIFinder) UnregisterPeripheral(peripheralID string) error {
return nil
}
//go:embed third-party/ftdi/detectFTDI.exe
var finderExe []byte
// Initialize initializes the FTDI finder
func (f *FTDIFinder) Initialize() error {
// Check platform
@@ -76,34 +87,10 @@ func (f *FTDIFinder) Initialize() error {
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)
}
// Create the FTDI executables
err := createExecutable(ftdiFinderExecutableName, finderExe)
if err != nil {
return err
}
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder initialized")
return nil
}
// createExecutable creates and writes an executable to the temporary directory of the system
func createExecutable(fileName string, storedFile []byte) error {
tempFile, err := os.Create(filepath.Join(os.TempDir(), fileName))
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("fileName", fileName).Msg("unable to create an FTDI executable")
return err
}
log.Trace().Str("file", "FTDIFinder").Str("filePath", tempFile.Name()).Msg("FTDI executable created")
// Write the embedded executable to the temp file
if _, err := tempFile.Write(storedFile); err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("fileName", fileName).Msg("unable to write the content to an FTDI executable")
return err
}
tempFile.Close()
log.Trace().Str("file", "FTDIPeripheral").Str("fileName", fileName).Msg("FTDI executable written")
return nil
}
// Start starts the finder and search for peripherals
func (f *FTDIFinder) Start(ctx context.Context) error {
f.goWait.Add(1)
@@ -143,12 +130,6 @@ func (f *FTDIFinder) Stop() error {
f.goWait.Wait()
// Stop the ticker
f.findTicker.Stop()
// Delete the FTDI executable files
fileToDelete := filepath.Join(os.TempDir(), ftdiFinderExecutableName)
err := os.Remove(fileToDelete)
if err != nil {
log.Warn().Str("file", "FTDIFinder").Str("fileName", fileToDelete).AnErr("error", err).Msg("unable to remove the executable file")
}
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
return nil
}
@@ -184,78 +165,49 @@ func (f *FTDIFinder) SetPeripheralSettings(peripheralID string, settings map[str
// scanPeripherals scans the FTDI peripherals
func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
detectionCtx, cancel := context.WithCancel(ctx)
defer cancel()
log.Trace().Str("file", "FTDIFinder").Msg("FTDI scan triggered")
finder := exec.CommandContext(detectionCtx, filepath.Join(os.TempDir(), ftdiFinderExecutableName))
log.Trace().Str("file", "FTDIFinder").Msg("has executed the FIND executable")
count := int(C.get_peripherals_number())
stdout, err := finder.StdoutPipe()
if err != nil {
return fmt.Errorf("unable to create the stdout pipe: %s", err)
}
defer stdout.Close()
log.Info().Int("number", count).Msg("number of FTDI devices connected")
stderr, err := finder.StderrPipe()
if err != nil {
return fmt.Errorf("unable to create the stderr pipe: %s", err)
}
defer stderr.Close()
// 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)
err = finder.Start()
if err != nil {
return fmt.Errorf("unable to find FTDI peripherals: %s", err)
devices := (*[1 << 30]C.FTDIPeripheralC)(devicesPtr)[:count:count]
type device struct {
SerialNumber string
Description string
IsOpen bool
}
scannerErr := bufio.NewScanner(stderr)
for scannerErr.Scan() {
return fmt.Errorf("unable to find FTDI peripherals: %s", scannerErr.Text())
}
C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
temporaryPeripherals := make(map[string]PeripheralInfo)
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
peripheralString := scanner.Text()
// The program output is like '0:1:2:3' where 0 is the location, 1 is the S/N, 2 is the name and 3 is the open flag [O/C]
peripheralInfo := strings.Split(peripheralString, ":")
for i := 0; i < count; i++ {
d := devices[i]
log.Trace().Str("file", "FTDIFinder").Str("scannedString", peripheralString).Str("peripheralOpenFlag", peripheralInfo[3]).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", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("no location provided for this FTDI peripheral")
location = -1
}
// Add the peripheral info to the found list
temporaryPeripherals[peripheralInfo[1]] = PeripheralInfo{
Name: peripheralInfo[2],
SerialNumber: peripheralInfo[1],
IsOpen: peripheralInfo[3] == "O",
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",
}
// If this peripheral is already registered, connect it and activate it
peripheral, registered := f.registeredPeripherals[peripheralInfo[1]]
if registered {
runtime.EventsEmit(ctx, string(PeripheralStatus), peripheral.info, "connecting")
err := peripheral.Connect(ctx, location)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo[1]).Msg("unable to connect the peripheral")
}
runtime.EventsEmit(ctx, string(PeripheralStatus), peripheral.info, "deactivated")
time.Sleep(2 * time.Second)
err = peripheral.Activate(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo[1]).Msg("unable to activate the peripheral")
}
runtime.EventsEmit(ctx, string(PeripheralStatus), peripheral.info, "activated")
}
log.Trace().Any("periph", temporaryPeripherals).Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("successfully added the FTDI peripheral to the finder")
// 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