From 556f24991e5330d04157631513c3b65ef25bf0d8 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Sun, 29 Dec 2024 21:22:53 +0100 Subject: [PATCH] fixed saved peripherals for a new project --- app.go | 28 ++++- frontend/src/App.svelte | 7 +- .../Settings/InputsOutputsContent.svelte | 37 +++--- .../src/components/Settings/Settings.svelte | 56 ++++++--- hardware/FTDIDriver.go | 7 +- hardware/FTDIPeripheral.go | 111 ++++++++++-------- hardware/MIDIPeripheral.go | 16 ++- hardware/OS2LPeripheral.go | 12 +- hardware/hardware.go | 84 ++++++------- hardware/interfaces.go | 10 +- main.go | 4 +- peripherals.go | 61 +++++++--- 12 files changed, 264 insertions(+), 169 deletions(-) diff --git a/app.go b/app.go index 8532918..719657d 100644 --- a/app.go +++ b/app.go @@ -5,7 +5,9 @@ import ( "context" "fmt" "io" - "log" + + "github.com/rs/zerolog/log" + "os" "strings" "sync" @@ -18,8 +20,6 @@ type App struct { wmiMutex sync.Mutex // Avoid some WMI operations at the same time projectInfo ProjectInfo // The project information structure projectSave string // The file name of the project - // FOR TESTING PURPOSE ONLY - ftdi *hardware.FTDIPeripheral } // NewApp creates a new App application struct @@ -40,15 +40,33 @@ func NewApp() *App { // startup is called when the app starts. The context is saved // so we can call the runtime methods -func (a *App) startup(ctx context.Context) { +func (a *App) onStartup(ctx context.Context) { a.ctx = ctx err := a.hardwareManager.Start(ctx) if err != nil { - log.Fatalf("Unable to start the device manager: %s", err) + log.Err(err).Str("file", "app").Msg("unable to start the hardware manager") return } } +// onReady is called when the DOM is ready +// We get the current peripherals connected +func (a *App) onReady(ctx context.Context) { + log.Debug().Str("file", "peripherals").Msg("getting peripherals...") + err := a.hardwareManager.Scan(a.ctx) + if err != nil { + log.Err(err).Str("file", "app").Msg("unable to get the peripherals") + } + return +} + +// onShutdown is called when the app is closing +// We stop all the pending processes +func (a *App) onShutdown(ctx context.Context) { + log.Warn().Str("file", "app").Msg("app is closing") + return +} + func formatString(input string) string { // Convertir en minuscules lowerCaseString := strings.ToLower(input) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 6f1d10d..14f79f3 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -13,7 +13,7 @@ import { SaveProject } from '../wailsjs/go/main/App.js'; import { construct_svelte_component } from 'svelte/internal'; import { EventsOn } from '../wailsjs/runtime' - import { CreateProject, GetPeripherals } from "../wailsjs/go/main/App"; + import { CreateProject } from "../wailsjs/go/main/App"; import { WindowSetTitle } from "../wailsjs/runtime/runtime" import { get } from "svelte/store" import ToastNotification from './components/General/ToastNotification.svelte'; @@ -83,11 +83,6 @@ $needProjectSave = true }) - // Request the list of peripherals - GetPeripherals().catch((error) => { - generateToast('danger', 'bx-error', 'Unable to get the list of peripherals: ' + error) - }) - // Handle window shortcuts document.addEventListener('keydown', function(event) { // Check the CTRL+S keys diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index dec8ca7..b183ab3 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -106,6 +106,9 @@ generateToast('danger', 'bx-error', 'Unable to create the OS2L peripheral') }) } + + // Get the number of saved peripherals + $: savedPeripheralNumber = Object.values($peripherals).filter(peripheral => peripheral.isSaved).length;
@@ -113,15 +116,15 @@

Available peripherals

Detected

- {#each Object.entries($peripherals) as [serialNumber, peripheral]} - {#if peripheral.isDetected} - addPeripheral(peripheral)} on:dblclick={() => { - if(!peripheral.isSaved) - addPeripheral(peripheral) - }} - title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/> - {/if} - {/each} + {#each Object.entries($peripherals) as [serialNumber, peripheral]} + {#if peripheral.isDetected} + addPeripheral(peripheral)} on:dblclick={() => { + if(!peripheral.isSaved) + addPeripheral(peripheral) + }} + title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/> + {/if} + {/each}

Others

@@ -130,12 +133,16 @@

Project peripherals

- {#each Object.entries($peripherals) as [serialNumber, peripheral]} - {#if peripheral.isSaved} - removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} - disconnected={!peripheral.isDetected} title={peripheral.Name == "" ? "Please wait..." : peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} removable signalizable/> - {/if} - {/each} + {#if savedPeripheralNumber > 0} + {#each Object.entries($peripherals) as [serialNumber, peripheral]} + {#if peripheral.isSaved} + removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} + disconnected={!peripheral.isDetected} title={peripheral.Name == "" ? "Please wait..." : peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} removable signalizable/> + {/if} + {/each} + {:else} + No hardware saved for this project. + {/if}

Peripheral settings

diff --git a/frontend/src/components/Settings/Settings.svelte b/frontend/src/components/Settings/Settings.svelte index 8404cd0..167be7b 100644 --- a/frontend/src/components/Settings/Settings.svelte +++ b/frontend/src/components/Settings/Settings.svelte @@ -27,30 +27,46 @@ }) } + // Unsave peripherals from the store and remove the disconnected peripherals + function unsavePeripherals(){ + peripherals.update((storedPeripherals) => { + // Set all the isSaved keys to false and delete the disconnected peripherals + for (let peripheralID in storedPeripherals) { + storedPeripherals[peripheralID].isSaved = false + if (!storedPeripherals[peripheralID].isDetected) { + delete storedPeripherals[peripheralID] + } + } + return {...storedPeripherals} + }) + } + + // Load the saved peripherals into the store + function loadPeripherals(peripheralsInfo){ + peripherals.update((storedPeripherals) => { + // Add the saved peripherals of the project + // If already exists pass the isSaved key to true, if not create the peripheral and set it to disconnected + for (let peripheralID in peripheralsInfo){ + // Add the peripheral to the list of peripherals, with the last isDetected key and the isSaved key to true + let lastDetectedKey = storedPeripherals[peripheralID]?.isDetected + storedPeripherals[peripheralID] = peripheralsInfo[peripheralID] + storedPeripherals[peripheralID].isDetected = (lastDetectedKey === true) ? true : false + storedPeripherals[peripheralID].isSaved = true + } + return {...storedPeripherals} + }) + } + + // Open the selected project function openSelectedProject(event){ let selectedOption = event.detail.key // Open the selected project GetProjectInfo(selectedOption).then((projectInfo) => { $showInformation = projectInfo.ShowInfo - peripherals.update((storedPeripherals) => { - // Set all the isSaved keys to false and delete the disconnected peripherals - for (let peripheralID in storedPeripherals) { - storedPeripherals[peripheralID].isSaved = false - if (!storedPeripherals[peripheralID].isDetected) { - delete storedPeripherals[peripheralID] - } - } - // Add the saved peripherals of the project - // If already exists pass the isSaved key to true, if not create the peripheral and set it to disconnected - for (let peripheralID in projectInfo.PeripheralsInfo){ - // Add the peripheral to the list of peripherals, with the last isDetected key and the isSaved key to true - let lastDetectedKey = storedPeripherals[peripheralID]?.isDetected - storedPeripherals[peripheralID] = projectInfo.PeripheralsInfo[peripheralID] - storedPeripherals[peripheralID].isDetected = (lastDetectedKey === true) ? true : false - storedPeripherals[peripheralID].isSaved = true - } - return {...storedPeripherals} - }) + // Remove the saved peripherals ofthe current project + unsavePeripherals() + // Load the new project peripherals + loadPeripherals(projectInfo.PeripheralsInfo) needProjectSave.set(false) generateToast('info', 'bx-folder-open', 'The project ' + projectInfo.ShowInfo.Name + ' was opened') }).catch((error) => { @@ -63,6 +79,8 @@ // Instanciate a new project CreateProject().then((showInfo) => { $showInformation = showInfo + // Remove the saved peripherals ofthe current project + unsavePeripherals() $needProjectSave = true generateToast('info', 'bxs-folder-plus', 'The project was created') }) diff --git a/hardware/FTDIDriver.go b/hardware/FTDIDriver.go index ec84f82..bfa4244 100644 --- a/hardware/FTDIDriver.go +++ b/hardware/FTDIDriver.go @@ -32,8 +32,12 @@ func NewFTDIDriver() *FTDIDriver { } } +//go:embed third-party/ftdi/detectFTDI.exe +var findFTDI []byte + // Initialize initializes the FTDI driver func (d *FTDIDriver) Initialize() error { + // Check platform if goRuntime.GOOS != "windows" { log.Error().Str("file", "FTDIDriver").Str("platform", goRuntime.GOOS).Msg("FTDI driver not compatible with your platform") return fmt.Errorf(" The FTDI driver is not compatible with your platform yet (%s)", goRuntime.GOOS) @@ -59,9 +63,6 @@ func (d *FTDIDriver) GetPeripheral(peripheralID string) (Peripheral, bool) { return peripheral, true } -//go:embed third-party/ftdi/detectFTDI.exe -var findFTDI []byte - // Scan scans the FTDI peripherals func (d *FTDIDriver) Scan(ctx context.Context) error { log.Trace().Str("file", "FTDIDriver").Msg("FTDI scan triggered") diff --git a/hardware/FTDIPeripheral.go b/hardware/FTDIPeripheral.go index 5649bc1..c4455b0 100644 --- a/hardware/FTDIPeripheral.go +++ b/hardware/FTDIPeripheral.go @@ -2,6 +2,7 @@ package hardware import ( "bufio" + "context" _ "embed" "fmt" "io" @@ -10,7 +11,6 @@ import ( "os" "os/exec" - "sync" ) const ( @@ -19,9 +19,6 @@ const ( setCommandString = 0x03 ) -//go:embed third-party/ftdi/dmxSender.exe -var dmxSender []byte - // FTDIPeripheral contains the data of an FTDI peripheral type FTDIPeripheral struct { name string // The name of the peripheral @@ -35,14 +32,16 @@ type FTDIPeripheral struct { stderr io.ReadCloser // For reading the errors disconnectChan chan struct{} // Channel to cancel the connection errorsChan chan error // Channel to get the errors - wg sync.WaitGroup // Tasks management } +//go:embed third-party/ftdi/dmxSender.exe +var dmxSender []byte + // NewFTDIPeripheral creates a new FTDI peripheral func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPeripheral, error) { log.Info().Str("file", "FTDIPeripheral").Str("name", name).Str("s/n", serialNumber).Int("location", location).Msg("FTDI peripheral created") // Create a temporary file - tempFile, err := os.CreateTemp("", "dmxSender*.exe") + tempFile, err := os.Create(fmt.Sprintf("dmxSender-%s.exe", serialNumber)) if err != nil { return nil, err } @@ -58,6 +57,7 @@ func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPer return &FTDIPeripheral{ name: name, + dmxSender: nil, programName: tempFile.Name(), serialNumber: serialNumber, location: location, @@ -68,64 +68,75 @@ func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPer } // Connect connects the FTDI peripheral -func (p *FTDIPeripheral) Connect() error { +func (p *FTDIPeripheral) Connect(ctx context.Context) error { // Connect if no connection is already running log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("connecting FTDI peripheral...") - if p.dmxSender == nil { - log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("no instance of dmxSender for this FTDI") - // Executing the command - p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location)) - log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("no instance of dmxSender for this FTDI") + // Check if the connection has already been established + if p.dmxSender != nil { + log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender already initialized") + return nil + } - var err error - p.stdout, err = p.dmxSender.StdoutPipe() - if err != nil { - log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stdout pipe") - return fmt.Errorf("unable to create the stdout pipe: %v", err) + // Initialize the exec.Command for running the process + p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location)) + log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender instance created") + + // Create the pipes for stdin, stdout, and stderr asynchronously without blocking + var err error + if p.stdout, err = p.dmxSender.StdoutPipe(); err != nil { + log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stdout pipe") + return fmt.Errorf("unable to create stdout pipe: %v", err) + } + if p.stdin, err = p.dmxSender.StdinPipe(); err != nil { + log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stdin pipe") + return fmt.Errorf("unable to create stdin pipe: %v", err) + } + if p.stderr, err = p.dmxSender.StderrPipe(); err != nil { + log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stderr pipe") + return fmt.Errorf("unable to create stderr pipe: %v", err) + } + + // Launch a goroutine to read stderr asynchronously + go func() { + scanner := bufio.NewScanner(p.stderr) + for scanner.Scan() { + // Process each line read from stderr + log.Err(fmt.Errorf(scanner.Text())).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error detected in dmx sender") } - p.stdin, err = p.dmxSender.StdinPipe() - if err != nil { - log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stdin pipe") - return fmt.Errorf("unable to create the stdin pipe: %v", err) + if err := scanner.Err(); err != nil { + log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error reading from stderr") } + }() - p.stderr, err = p.dmxSender.StderrPipe() - if err != nil { - log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stderr pipe") - return fmt.Errorf("unable to create the stderr pipe: %v", err) - } - - go func() { - scanner := bufio.NewScanner(p.stderr) - for scanner.Scan() { - // Traitez chaque ligne lue depuis stderr - log.Err(fmt.Errorf(scanner.Text())).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error detected in dmx sender") - } - if err := scanner.Err(); err != nil { - log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error reading from stderr") - } - }() - - p.wg.Add(1) - go func() { - defer p.wg.Done() - err = p.dmxSender.Run() + // Launch the command asynchronously in another goroutine + go func() { + // Run the command, respecting the context cancellation + err := p.dmxSender.Run() + select { + case <-ctx.Done(): + // If the context is canceled, handle it gracefully + log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender was canceled by context") + return + default: + // Handle command exit normally if err != nil { - log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while execution of dmw sender") + log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while execution of dmx sender") if exitError, ok := err.(*exec.ExitError); ok { log.Warn().Str("file", "FTDIPeripheral").Int("exitCode", exitError.ExitCode()).Str("s/n", p.serialNumber).Msg("dmx sender exited with code") } } else { log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmx sender exited successfully") } - }() - } + } + }() + + log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender process started successfully") return nil } // Disconnect disconnects the FTDI peripheral -func (p *FTDIPeripheral) Disconnect() error { +func (p *FTDIPeripheral) Disconnect(ctx context.Context) error { log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("disconnecting FTDI peripheral...") if p.dmxSender != nil { log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI") @@ -139,7 +150,7 @@ func (p *FTDIPeripheral) Disconnect() error { p.dmxSender = nil err = os.Remove(p.programName) if err != nil { - log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to delete the dmx sender temporary file") + log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Str("senderPath", p.programName).Msg("unable to delete the dmx sender temporary file") return fmt.Errorf("unable to delete the temporary file: %v", err) } return nil @@ -149,7 +160,7 @@ func (p *FTDIPeripheral) Disconnect() error { } // Activate activates the FTDI peripheral -func (p *FTDIPeripheral) Activate() error { +func (p *FTDIPeripheral) Activate(ctx context.Context) error { if p.dmxSender != nil { log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI") _, err := io.WriteString(p.stdin, string([]byte{0x01, 0x00, 0x00, 0x00})) @@ -164,7 +175,7 @@ func (p *FTDIPeripheral) Activate() error { } // Deactivate deactivates the FTDI peripheral -func (p *FTDIPeripheral) Deactivate() error { +func (p *FTDIPeripheral) Deactivate(ctx context.Context) error { if p.dmxSender != nil { log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI") _, err := io.WriteString(p.stdin, string([]byte{0x02, 0x00, 0x00, 0x00})) @@ -179,7 +190,7 @@ func (p *FTDIPeripheral) Deactivate() error { } // SetDeviceProperty sends a command to the specified device -func (p *FTDIPeripheral) SetDeviceProperty(uint32, channelNumber uint32, channelValue byte) error { +func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, uint32, channelNumber uint32, channelValue byte) error { if p.dmxSender != nil { log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI") commandString := []byte{0x03, 0x01, 0x00, 0xff, 0x03, 0x02, 0x00, channelValue} diff --git a/hardware/MIDIPeripheral.go b/hardware/MIDIPeripheral.go index 92f95d5..7d434e7 100644 --- a/hardware/MIDIPeripheral.go +++ b/hardware/MIDIPeripheral.go @@ -1,6 +1,10 @@ package hardware -import "github.com/rs/zerolog/log" +import ( + "context" + + "github.com/rs/zerolog/log" +) // MIDIPeripheral contains the data of a MIDI peripheral type MIDIPeripheral struct { @@ -20,27 +24,27 @@ func NewMIDIPeripheral(name string, location int, serialNumber string) *MIDIPeri } // Connect connects the MIDI peripheral -func (p *MIDIPeripheral) Connect() error { +func (p *MIDIPeripheral) Connect(ctx context.Context) error { return nil } // Disconnect disconnects the MIDI peripheral -func (p *MIDIPeripheral) Disconnect() error { +func (p *MIDIPeripheral) Disconnect(ctx context.Context) error { return nil } // Activate activates the MIDI peripheral -func (p *MIDIPeripheral) Activate() error { +func (p *MIDIPeripheral) Activate(ctx context.Context) error { return nil } // Deactivate deactivates the MIDI peripheral -func (p *MIDIPeripheral) Deactivate() error { +func (p *MIDIPeripheral) Deactivate(ctx context.Context) error { return nil } // SetDeviceProperty - not implemented for this kind of peripheral -func (p *MIDIPeripheral) SetDeviceProperty(uint32, uint32, byte) error { +func (p *MIDIPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error { return nil } diff --git a/hardware/OS2LPeripheral.go b/hardware/OS2LPeripheral.go index 222b7e4..5cb7eb0 100644 --- a/hardware/OS2LPeripheral.go +++ b/hardware/OS2LPeripheral.go @@ -1,6 +1,8 @@ package hardware import ( + "context" + "github.com/rs/zerolog/log" ) @@ -20,31 +22,31 @@ func NewOS2LPeripheral(name string, serialNumber string) *OS2LPeripheral { } // Connect connects the MIDI peripheral -func (p *OS2LPeripheral) Connect() error { +func (p *OS2LPeripheral) Connect(ctx context.Context) error { log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected") return nil } // Disconnect disconnects the MIDI peripheral -func (p *OS2LPeripheral) Disconnect() error { +func (p *OS2LPeripheral) Disconnect(ctx context.Context) error { log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected") return nil } // Activate activates the MIDI peripheral -func (p *OS2LPeripheral) Activate() error { +func (p *OS2LPeripheral) Activate(ctx context.Context) error { log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated") return nil } // Deactivate deactivates the MIDI peripheral -func (p *OS2LPeripheral) Deactivate() error { +func (p *OS2LPeripheral) Deactivate(ctx context.Context) error { log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated") return nil } // SetDeviceProperty - not implemented for this kind of peripheral -func (p *OS2LPeripheral) SetDeviceProperty(uint32, uint32, byte) error { +func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error { return nil } diff --git a/hardware/hardware.go b/hardware/hardware.go index a540d56..6f343ff 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -49,41 +49,35 @@ func NewHardwareManager() *HardwareManager { // Start starts to find new peripheral events func (h *HardwareManager) Start(ctx context.Context) error { + // Configure wndProc callback cb := windows.NewCallback(h.wndProc) - log.Trace().Str("file", "hardware").Msg("wndProc callback set") - inst := win.GetModuleHandle(nil) - log.Trace().Str("file", "hardware").Msg("got windows API instance") - cn, err := syscall.UTF16PtrFromString("DMXConnect peripheral watcher") + // Register window class + className, err := syscall.UTF16PtrFromString("DMXConnectPeripheralWatcher") if err != nil { - log.Err(err).Str("file", "hardware").Msg("failed to convert window class name to UTF16") return fmt.Errorf("failed to convert window class name to UTF16: %w", err) } wc := win.WNDCLASSEX{ + CbSize: uint32(unsafe.Sizeof(win.WNDCLASSEX{})), HInstance: inst, LpfnWndProc: cb, - LpszClassName: cn, + LpszClassName: className, } - log.Trace().Str("file", "hardware").Msg("windows API class created") - - wc.CbSize = uint32(unsafe.Sizeof(wc)) if win.RegisterClassEx(&wc) == 0 { - log.Err(syscall.GetLastError()).Str("file", "hardware").Msg("failed to register window class") return fmt.Errorf("failed to register window class: %w", syscall.GetLastError()) } - log.Trace().Str("file", "hardware").Msg("window class registered") - wName, err := syscall.UTF16PtrFromString("usbevent.exe") + // Create hidden window + windowName, err := syscall.UTF16PtrFromString("usbevent.exe") if err != nil { - log.Err(err).Str("file", "hardware").Msg("failed to convert window class name to UTF16") return fmt.Errorf("failed to convert window name to UTF16: %w", err) } - wdw := win.CreateWindowEx( + hwnd := win.CreateWindowEx( 0, wc.LpszClassName, - wName, - win.WS_MINIMIZE|win.WS_OVERLAPPEDWINDOW, + windowName, + win.WS_OVERLAPPEDWINDOW, win.CW_USEDEFAULT, win.CW_USEDEFAULT, 100, @@ -91,34 +85,18 @@ func (h *HardwareManager) Start(ctx context.Context) error { 0, 0, wc.HInstance, - nil) - if wdw == 0 { - log.Err(syscall.GetLastError()).Str("file", "hardware").Msg("failed to create window") + nil, + ) + if hwnd == 0 { return fmt.Errorf("failed to create window: %w", syscall.GetLastError()) } - log.Trace().Str("file", "hardware").Msg("window created successfully") - _ = win.ShowWindow(wdw, win.SW_HIDE) - win.UpdateWindow(wdw) - log.Trace().Str("file", "hardware").Msg("window shown and updated") + // Hide and update window + win.ShowWindow(hwnd, win.SW_HIDE) + win.UpdateWindow(hwnd) - // To continuously get the devices events from Windows - go func() { - defer log.Debug().Str("file", "hardware").Msg("peripheral watcher goroutine exited") - for { - select { - case <-ctx.Done(): - return - default: - var msg win.MSG - got := win.GetMessage(&msg, win.HWND(windows.HWND(wdw)), 0, 0) - if got == 0 { - win.TranslateMessage(&msg) - win.DispatchMessage(&msg) - } - } - } - }() + // Start message loop in a goroutine + go messageLoop(ctx, hwnd) // To handle the peripheral changed go func() { @@ -139,6 +117,31 @@ func (h *HardwareManager) Start(ctx context.Context) error { return nil } +func messageLoop(ctx context.Context, hwnd win.HWND) { + defer log.Debug().Str("file", "hardware").Msg("Peripheral watcher goroutine exited") + + for { + select { + case <-ctx.Done(): + win.PostQuitMessage(0) // Gracefully terminate message loop + return + default: + var msg win.MSG + result := win.GetMessage(&msg, hwnd, 0, 0) + if result > 0 { + win.TranslateMessage(&msg) + win.DispatchMessage(&msg) + } else if result == 0 { + log.Warn().Str("file", "hardware").Msg("WM_QUIT message received") + return + } else { + log.Error().Str("file", "hardware").Msg("GetMessage returned an error") + return + } + } + } +} + // GetDriver returns a register driver func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error) { driver, exists := h.drivers[driverName] @@ -190,6 +193,7 @@ func (h *HardwareManager) Scan(ctx context.Context) error { } func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr { + log.Trace().Str("file", "hardware").Msg("wndProc triggered") switch msg { case win.WM_DEVICECHANGE: // Trigger the devices scan when the last DEVICE_CHANGE event is received diff --git a/hardware/interfaces.go b/hardware/interfaces.go index 2c1fb15..aa1e86a 100644 --- a/hardware/interfaces.go +++ b/hardware/interfaces.go @@ -4,11 +4,11 @@ import "context" // Peripheral represents the methods used to manage a peripheral (input or output hardware) type Peripheral interface { - Connect() error // Connect the peripheral - Disconnect() error // Disconnect the peripheral - Activate() error // Activate the peripheral - Deactivate() error // Deactivate the peripheral - SetDeviceProperty(uint32, uint32, byte) error // Update a device property + Connect(context.Context) error // Connect the peripheral + Disconnect(context.Context) error // Disconnect the peripheral + Activate(context.Context) error // Activate the peripheral + Deactivate(context.Context) error // Deactivate the peripheral + SetDeviceProperty(context.Context, uint32, uint32, byte) error // Update a device property GetInfo() PeripheralInfo // Get the peripheral information } diff --git a/main.go b/main.go index d5783a3..458eb54 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,9 @@ func main() { AssetServer: &assetserver.Options{ Assets: assets, }, - OnStartup: app.startup, + OnStartup: app.onStartup, + OnDomReady: app.onReady, + OnShutdown: app.onShutdown, Bind: []interface{}{ app, }, diff --git a/peripherals.go b/peripherals.go index a5ffce3..e5578ff 100644 --- a/peripherals.go +++ b/peripherals.go @@ -7,12 +7,6 @@ import ( "github.com/rs/zerolog/log" ) -// GetPeripherals gets all the peripherals connected -func (a *App) GetPeripherals() error { - log.Debug().Str("file", "peripherals").Msg("getting peripherals...") - return a.hardwareManager.Scan(a.ctx) -} - // AddPeripheral adds a peripheral to the project func (a *App) AddPeripheral(protocolName string, peripheralID string) error { // Get the device from its finder @@ -64,27 +58,66 @@ func (a *App) AddOS2LPeripheral() (hardware.PeripheralInfo, error) { // FOR TESTING PURPOSE ONLY func (a *App) ConnectFTDI() error { - // Create a new FTDI object - var err error - a.ftdi, err = hardware.NewFTDIPeripheral("FTDI TEST INTERFACE", "A50825I", 0) + // Connect the FTDI + driver, err := a.hardwareManager.GetDriver("FTDI") if err != nil { return err } - return a.ftdi.Connect() + periph, found := driver.GetPeripheral("A50285BI") + if !found { + return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") + } + return periph.Connect(a.ctx) } func (a *App) ActivateFTDI() error { - return a.ftdi.Activate() + // Connect the FTDI + driver, err := a.hardwareManager.GetDriver("FTDI") + if err != nil { + return err + } + periph, found := driver.GetPeripheral("A50285BI") + if !found { + return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") + } + return periph.Activate(a.ctx) } func (a *App) SetDeviceFTDI(channelValue byte) error { - return a.ftdi.SetDeviceProperty(0, 0, channelValue) + // Connect the FTDI + driver, err := a.hardwareManager.GetDriver("FTDI") + if err != nil { + return err + } + periph, found := driver.GetPeripheral("A50285BI") + if !found { + return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") + } + return periph.SetDeviceProperty(a.ctx, 0, 0, channelValue) } func (a *App) DeactivateFTDI() error { - return a.ftdi.Deactivate() + // Connect the FTDI + driver, err := a.hardwareManager.GetDriver("FTDI") + if err != nil { + return err + } + periph, found := driver.GetPeripheral("A50285BI") + if !found { + return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") + } + return periph.Deactivate(a.ctx) } func (a *App) DisconnectFTDI() error { - return a.ftdi.Disconnect() + // Connect the FTDI + driver, err := a.hardwareManager.GetDriver("FTDI") + if err != nil { + return err + } + periph, found := driver.GetPeripheral("A50285BI") + if !found { + return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") + } + return periph.Disconnect(a.ctx) }