generated from thinkode/modelRepository
265 lines
9.4 KiB
Go
265 lines
9.4 KiB
Go
package hardware
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
goRuntime "runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
const (
|
|
ftdiFinderExecutableName = "FTDI_finder.exe"
|
|
)
|
|
|
|
// 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")
|
|
return peripheralData.SerialNumber, nil
|
|
}
|
|
|
|
// UnregisterPeripheral unregisters an existing peripheral
|
|
func (f *FTDIFinder) UnregisterPeripheral(peripheralID string) error {
|
|
peripheral, registered := f.registeredPeripherals[peripheralID]
|
|
if registered {
|
|
err := peripheral.Disconnect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
delete(f.registeredPeripherals, peripheralID)
|
|
return nil
|
|
}
|
|
|
|
//go:embed third-party/ftdi/detectFTDI.exe
|
|
var finderExe []byte
|
|
|
|
// 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)
|
|
}
|
|
// 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)
|
|
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()
|
|
// 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
|
|
}
|
|
|
|
// 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 {
|
|
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")
|
|
|
|
stdout, err := finder.StdoutPipe()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create the stdout pipe: %s", err)
|
|
}
|
|
defer stdout.Close()
|
|
|
|
stderr, err := finder.StderrPipe()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create the stderr pipe: %s", err)
|
|
}
|
|
defer stderr.Close()
|
|
|
|
err = finder.Start()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to find FTDI peripherals: %s", err)
|
|
}
|
|
|
|
scannerErr := bufio.NewScanner(stderr)
|
|
for scannerErr.Scan() {
|
|
return fmt.Errorf("unable to find FTDI peripherals: %s", scannerErr.Text())
|
|
}
|
|
|
|
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, ":")
|
|
|
|
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",
|
|
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")
|
|
}
|
|
// Emit the peripherals changes to the front
|
|
emitPeripheralsChanges(ctx, f.foundPeripherals, temporaryPeripherals)
|
|
// Store the new peripherals list
|
|
f.foundPeripherals = temporaryPeripherals
|
|
return nil
|
|
}
|