From 932c288a9c4f633910d108be1baf4e2a51c48551 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Fri, 14 Nov 2025 10:46:24 +0000 Subject: [PATCH] 23-os2l (#32) Implements the OS2L feature (tested with Virtual DJ). Graphics improvements. Reviewed-on: https://factory.vbprojects.fr/DMXStage/dmxconnect/pulls/32 --- app.go | 2 +- frontend/src/App.svelte | 2 - .../General/RoundDropdownList.svelte | 10 +- frontend/src/components/General/Tab.svelte | 9 +- .../src/components/Settings/DeviceCard.svelte | 47 ++-- .../Settings/InputsOutputsContent.svelte | 65 +++-- .../src/components/Settings/Settings.svelte | 2 +- frontend/src/lang/en.json | 10 +- frontend/src/runtime-events.js | 32 +++ frontend/src/stores.js | 3 +- hardware/FTDIFinder.go | 137 ++++----- hardware/FTDIPeripheral.go | 14 +- hardware/OS2LFinder.go | 145 ++++++++-- hardware/OS2LPeripheral.go | 266 +++++++++++++++--- hardware/hardware.go | 4 +- hardware/interfaces.go | 44 ++- peripherals.go | 11 +- project.go | 2 +- 18 files changed, 560 insertions(+), 245 deletions(-) diff --git a/app.go b/app.go index 67b9289..3b5b12f 100644 --- a/app.go +++ b/app.go @@ -32,7 +32,7 @@ func NewApp() *App { hardwareManager := hardware.NewHardwareManager() // hardwareManager.RegisterFinder(hardware.NewMIDIFinder(5 * time.Second)) hardwareManager.RegisterFinder(hardware.NewFTDIFinder(5 * time.Second)) - // hardwareManager.RegisterFinder(hardware.NewOS2LFinder()) + hardwareManager.RegisterFinder(hardware.NewOS2LFinder()) return &App{ hardwareManager: hardwareManager, projectSave: "", diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index c17324c..31d9e32 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -7,7 +7,6 @@ import Settings from './components/Settings/Settings.svelte'; import Devices from './components/Devices/Devices.svelte'; import Show from './components/Show/Show.svelte'; - import DropdownList from "./components/General/DropdownList.svelte"; import RoundDropdownList from "./components/General/RoundDropdownList.svelte"; import GeneralConsole from './components/Console/GeneralConsole.svelte'; import RoundIconButton from './components/General/RoundIconButton.svelte'; @@ -134,7 +133,6 @@ \ No newline at end of file diff --git a/frontend/src/components/Settings/DeviceCard.svelte b/frontend/src/components/Settings/DeviceCard.svelte index 8c72cb1..47c89e4 100644 --- a/frontend/src/components/Settings/DeviceCard.svelte +++ b/frontend/src/components/Settings/DeviceCard.svelte @@ -38,7 +38,7 @@
-
+

{#if status == "PERIPHERAL_DISCONNECTED" } {/if}{title}

{type} {location != '' ? "- " : ""}{location}
@@ -52,8 +52,8 @@
- - + +
@@ -63,19 +63,33 @@ background: linear-gradient(to bottom right, var(--second-color), var(--third-color)); } .card{ - position: relative; + position: relative; } .selected { - background-color: var(--first-color); + background-color: var(--third-color); + position: relative; margin: 0.2em; - padding-left: 0.3em; - padding-bottom: 0.3em; - border-radius: 0.2em; + padding: 0.2em 0.3em 0.5em 0.5em; + border-radius: 0.5em; display: flex; justify-content: space-between; text-align: left; cursor: pointer; + overflow: hidden; } + .unselected{ + background-color: var(--second-color); + position: relative; + margin: 0.2em; + padding: 0.2em 0.3em 0.5em 0.5em; + border-radius: 0.5em; + display: flex; + justify-content: space-between; + text-align: left; + cursor: pointer; + overflow: hidden; + } + .subtitle{ margin-bottom: 0.5em; } @@ -94,7 +108,7 @@ .waiting::before{ content: ''; position: absolute; - background: linear-gradient(var(--second-color), var(--first-color)); + background: linear-gradient(var(--first-color), var(--second-color)); width: 100%; height: 60%; animation: rotate 3s linear infinite; @@ -108,21 +122,6 @@ border-radius: 0.2em; } - .unselected{ - background-color: var(--third-color); - background: fixed; - position: relative; - margin: 0.2em; - padding-left: 0.3em; - padding-bottom: 0.3em; - border-radius: 0.2em; - display: flex; - justify-content: space-between; - text-align: left; - cursor: pointer; - overflow: hidden; - } - /* Définition de l'animation */ @keyframes rotate { from { diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index a8373a4..f67b102 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -1,9 +1,8 @@
-
-

{$_("projectHardwareAvailableLabel")}

+
-

{$_("projectHardwareDetectedLabel")}

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

{$_("projectHardwareOthersLabel")}

- addPeripheral({Name: "OS2L connection", ProtocolName: "OS2L"})} text="Add an OS2L peripheral" icon="bx-plus-circle" tooltip="Configure an OS2L connection"/> +

{$_("projectHardwareDetectedLabel")}

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

{$_("projectHardwareOthersLabel")}

+
+ addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + on:dblclick={() => addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + status="PERIPHERAL_CONNECTED" title={"OS2L virtual device"} type={"OS2L"} location={""} addable={true}/>
@@ -104,14 +106,13 @@ {#each Object.entries($peripherals) as [serialNumber, peripheral]} {#if peripheral.isSaved} removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} on:click={() => selectPeripheral(peripheral)} - title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} selected={serialNumber == selectedPeripheralSN} removable signalizable/> + title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} selected={serialNumber == selectedPeripheralSN} removable signalizable signalized={peripheral.eventEmitted}/> {/if} {/each} {:else} {$_("projectHardwareEmptyLabel")} {/if}
-

{$_("projectHardwareSettingsLabel")} ({selectedPeripheralSN == null ? $_("projectHardwareNoSelection") : selectedPeripheralSN})

{#if Object.keys(selectedPeripheralSettings).length > 0} {#each Object.entries(selectedPeripheralSettings) as [settingName, settingValue]} @@ -119,8 +120,6 @@ validate(settingName, event.detail.target.value)} label={$t(settingName)} type="{typeof(settingValue)}" width='100%' value="{settingValue}"/>
{/each} - {:else} - {$_("projectHardwareNoSettingLabel")} {/if}
@@ -149,19 +148,23 @@ border-radius: 0.5em; padding: 0.2em; max-height: calc(100vh - 300px); - overflow-y: auto; - scrollbar-width: none; /* Firefox */ - -ms-overflow-style: none; /* IE and Edge */ - /* overflow: visible; */ + margin-top: 0.5em; + margin-bottom: 0.5em; } - .availableHardware::-webkit-scrollbar { - display: none; - } - .configuredHardware { - background-color: var(--second-color); - border-radius: 0.5em; + + .libraryPanel { padding: 0.5em; - padding: 0.2em; + /* width: 13em; */ + height: calc(100vh - 2*8px - 2*4px - 40px - 1em - 2*0.1em - 2*0.1em - 2*0.4em - 21.6px - 2*0.5em); + overflow: auto; + scrollbar-width: none; + -ms-overflow-style: none; + } + .libraryPanel::-webkit-scrollbar { + display: none; /* Chrome, Safari, Edge */ + } + + .configuredHardware { display: flex; flex-wrap: wrap; } diff --git a/frontend/src/components/Settings/Settings.svelte b/frontend/src/components/Settings/Settings.svelte index 9625ecb..a1fb30d 100644 --- a/frontend/src/components/Settings/Settings.svelte +++ b/frontend/src/components/Settings/Settings.svelte @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json index f185eb7..f153022 100644 --- a/frontend/src/lang/en.json +++ b/frontend/src/lang/en.json @@ -13,6 +13,7 @@ "newProjectTooltip": "Create a new project", "openProjectString": "Open", "openProjectTooltip": "Open an existing project", + "openProjectEmpty": "No project found", "unsavedProjectFlag": "unsaved", "projectPropertiesTab": "Project properties", "projectPropertiesTooltip": "The project properties", @@ -34,16 +35,13 @@ "projectHardwareShowLabel" : "My Show", "projectHardwareInputsLabel": "INPUTS", "projectHardwareOutputsLabel": "OUTPUTS", - "projectHardwareDeleteTooltip": "Delete this peripheral", - "projectHardwareAddTooltip": "Add this peripheral to project", + "projectHardwareDeleteTooltip": "Delete", + "projectHardwareAddTooltip": "Add to project", "projectHardwareNoSelection": "Empty", - "projectHardwareAvailableLabel": "Available peripherals", - "projectHardwareSavedLabel": "Project peripherals", + "projectHardwareSavedLabel": "Saved in project", "projectHardwareDetectedLabel": "Detected", "projectHardwareOthersLabel": "Others", "projectHardwareEmptyLabel": "No hardware saved for this project", - "projectHardwareSettingsLabel": "Peripheral settings", - "projectHardwareNoSettingLabel": "No setting can be displayed", "peripheralArrivalToast": "Peripheral inserted:", "peripheralRemovalToast": "Peripheral removed:", diff --git a/frontend/src/runtime-events.js b/frontend/src/runtime-events.js index 59a290b..d509a83 100644 --- a/frontend/src/runtime-events.js +++ b/frontend/src/runtime-events.js @@ -120,6 +120,32 @@ function unloadPeripheral (peripheralInfo) { needProjectSave.set(true) } +// A peripheral event has been emitted +function onPeripheralEvent(sn, event) { + // If not exists, add it to the map + // eventEmitted key to true for 0.2 sec + + peripherals.update((storedPeripherals) => { + return { + ...storedPeripherals, + [sn]: { + ...storedPeripherals[sn], + eventEmitted: true + }, + }}) + + setTimeout(() => { + peripherals.update((storedPeripherals) => { + return { + ...storedPeripherals, + [sn]: { + ...storedPeripherals[sn], + eventEmitted: false + }, + }}) + }, 200); +} + let initialized = false export function initRuntimeEvents(){ @@ -143,6 +169,9 @@ export function initRuntimeEvents(){ // Handle a peripheral unloaded from the project EventsOn('UNLOAD_PERIPHERAL', unloadPeripheral) + + // Handle a peripheral event + EventsOn('PERIPHERAL_EVENT_EMITTED', onPeripheralEvent) } export function destroyRuntimeEvents(){ @@ -166,4 +195,7 @@ export function destroyRuntimeEvents(){ // Handle a peripheral unloaded from the project EventsOff('UNLOAD_PERIPHERAL') + + // Handle a peripheral event + EventsOff('PERIPHERAL_EVENT_EMITTED') } \ No newline at end of file diff --git a/frontend/src/stores.js b/frontend/src/stores.js index 02aeb53..984a346 100644 --- a/frontend/src/stores.js +++ b/frontend/src/stores.js @@ -45,4 +45,5 @@ export let peripherals = writable({}) // isSaved // if the peripheral is saved in the project // isDetected // if the peripheral is detected by the system -// status // the status of connection \ No newline at end of file +// status // the status of connection +// eventEmitted // if an event has been emitted for this peripheral (disappear after a delay) \ No newline at end of file diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go index 2fc2e5b..b620730 100644 --- a/hardware/FTDIFinder.go +++ b/hardware/FTDIFinder.go @@ -2,6 +2,7 @@ package hardware import ( "context" + "errors" "fmt" goRuntime "runtime" "sync" @@ -59,24 +60,25 @@ func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData Peri defer f.mu.Unlock() f.saved[peripheralData.SerialNumber] = peripheralData + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) // If already detected, connect it if peripheral, ok := f.detected[peripheralData.SerialNumber]; ok { - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusConnecting) - err := peripheral.Connect(ctx) - if err != nil { - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) - log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral") - return peripheralData.SerialNumber, nil - } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated) - // Peripheral connected, activate it - err = peripheral.Activate(ctx) - if err != nil { - log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the FTDI peripheral") - return peripheralData.SerialNumber, nil - } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusActivated) + f.wg.Add(1) + go func() { + defer f.wg.Done() + err := peripheral.Connect(ctx) + if err != nil { + log.Err(err).Str("file", "FTDIFinder").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", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the FTDI peripheral") + return + } + }() } // Emits the event in the hardware @@ -97,14 +99,12 @@ func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData Pe log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral") return nil } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated) // Disconnecting peripheral - err = peripheral.Disconnect() + err = peripheral.Disconnect(ctx) if err != nil { log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral") return nil } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) } delete(f.saved, peripheralData.SerialNumber) runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) @@ -164,27 +164,28 @@ func (f *FTDIFinder) GetName() string { // 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 - return make(map[string]interface{}), nil + peripheral, found := f.detected[peripheralID] + if !found { + // FTDI not detected, return the last settings saved + if savedPeripheral, isFound := f.saved[peripheralID]; isFound { + return savedPeripheral.Settings, nil + } + 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 { +func (f *FTDIFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) 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) - return nil + peripheral, found := f.detected[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 @@ -233,29 +234,26 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { f.detected[sn] = peripheral if f.onArrival != nil { - go f.onArrival(peripheralData) + f.onArrival(peripheralData) } log.Info().Str("sn", sn).Str("name", peripheralData.Name).Msg("[FTDI] New peripheral detected") - // Disconnected by default - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) - // If the peripheral is saved in the project => connect if _, saved := f.saved[sn]; saved { - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusConnecting) - err := peripheral.Connect(ctx) - if err != nil { - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) - log.Err(err).Str("sn", sn).Msg("unable to connect the FTDI peripheral") - return nil - } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated) - err = peripheral.Activate(ctx) - if err != nil { - log.Err(err).Str("sn", sn).Msg("unable to activate the FTDI peripheral") - return nil - } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusActivated) + f.wg.Add(1) + go func(p PeripheralInfo) { + defer f.wg.Done() + err := peripheral.Connect(ctx) + if err != nil { + log.Err(err).Str("sn", p.SerialNumber).Msg("unable to connect the FTDI peripheral") + return + } + err = peripheral.Activate(ctx) + if err != nil { + log.Err(err).Str("sn", p.SerialNumber).Msg("unable to activate the FTDI peripheral") + return + } + }(peripheralData) } } } @@ -265,11 +263,14 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { if _, still := currentMap[sn]; !still { // Properly clean the DMX device - err := oldPeripheral.Disconnect() + err := oldPeripheral.Deactivate(ctx) if err != nil { - log.Err(err).Str("sn", sn).Msg("unable to clean the FTDI peripheral after disconnection") + log.Err(err).Str("sn", sn).Msg("unable to deactivate the FTDI peripheral after disconnection") + } + err = oldPeripheral.Disconnect(ctx) + if err != nil { + log.Err(err).Str("sn", sn).Msg("unable to disconnect the FTDI peripheral after disconnection") } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), oldPeripheral.GetInfo(), PeripheralStatusDisconnected) // Delete it from the detected list delete(f.detected, sn) @@ -277,7 +278,7 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { // Execute the removal callback if f.onRemoval != nil { - go f.onRemoval(oldPeripheral.GetInfo()) + f.onRemoval(oldPeripheral.GetInfo()) } } } @@ -292,22 +293,22 @@ func (f *FTDIFinder) WaitStop() error { // 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)) - // } - // } + log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals") + var errs []error + for registeredPeripheralSN, registeredPeripheral := range f.detected { + 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...) - // } + if len(errs) > 0 { + return errors.Join(errs...) + } log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped") return nil } diff --git a/hardware/FTDIPeripheral.go b/hardware/FTDIPeripheral.go index 8debace..ac9a088 100644 --- a/hardware/FTDIPeripheral.go +++ b/hardware/FTDIPeripheral.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/wailsapp/wails/v2/pkg/runtime" ) /* @@ -41,6 +42,8 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error { return errors.Errorf("the DMX device has already been created!") } + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting) + // Create the DMX sender p.dmxSender = C.dmx_create() @@ -48,6 +51,7 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error { serialNumber := C.CString(p.info.SerialNumber) defer C.free(unsafe.Pointer(serialNumber)) if C.dmx_connect(p.dmxSender, serialNumber) != C.DMX_OK { + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected) log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device") return errors.Errorf("unable to connect '%s'", p.info.SerialNumber) } @@ -56,15 +60,16 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error { go func() { defer p.wg.Done() <-ctx.Done() - _ = p.Disconnect() + _ = p.Disconnect(ctx) }() + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated) log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully") return nil } // Disconnect disconnects the FTDI peripheral -func (p *FTDIPeripheral) Disconnect() error { +func (p *FTDIPeripheral) Disconnect(ctx context.Context) error { // Check if the device has already been created if p.dmxSender == nil { return errors.Errorf("the DMX device has not been connected!") @@ -75,6 +80,8 @@ func (p *FTDIPeripheral) Disconnect() error { // Reset the pointer to the peripheral p.dmxSender = nil + + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected) return nil } @@ -96,6 +103,8 @@ func (p *FTDIPeripheral) Activate(ctx context.Context) error { C.dmx_setValue(p.dmxSender, C.uint16_t(1), C.uint8_t(255)) C.dmx_setValue(p.dmxSender, C.uint16_t(5), C.uint8_t(255)) + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated) + log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device activated successfully") return nil } @@ -114,6 +123,7 @@ func (p *FTDIPeripheral) Deactivate(ctx context.Context) error { return errors.Errorf("unable to deactivate the DMX sender!") } + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated) log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully") return nil } diff --git a/hardware/OS2LFinder.go b/hardware/OS2LFinder.go index 24a8d42..26d534d 100644 --- a/hardware/OS2LFinder.go +++ b/hardware/OS2LFinder.go @@ -2,23 +2,32 @@ 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 { - registeredPeripherals map[string]OS2LPeripheral // The list of found peripherals + 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{ - registeredPeripherals: make(map[string]OS2LPeripheral), + saved: make(map[string]*OS2LPeripheral), } } @@ -28,37 +37,92 @@ func (f *OS2LFinder) Initialize() error { 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) { - // Create a random serial number for this peripheral - peripheralData.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32))) + 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") - os2lPeripheral, err := NewOS2LPeripheral(peripheralData) - if err != nil { - return "", fmt.Errorf("unable to create the OS2L peripheral: %v", err) - } - // Connect this peripheral - err = os2lPeripheral.Connect(ctx) - if err != nil { - return "", err + // New OS2L peripheral has arrived + if f.onArrival != nil { + f.onArrival(peripheral.GetInfo()) } - f.registeredPeripherals[peripheralData.SerialNumber] = *os2lPeripheral - log.Trace().Any("periph", &os2lPeripheral).Str("file", "OS2LFinder").Str("peripheralName", peripheralData.Name).Msg("OS2L peripheral has been created") + 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(peripheralID string) error { - peripheral, registered := f.registeredPeripherals[peripheralID] - if registered { - err := peripheral.Disconnect() +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 { - return err + 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 } } - delete(f.registeredPeripherals, peripheralID) + + // The OS2L peripheral has gone + f.onRemoval(peripheralData) + + delete(f.saved, peripheralData.SerialNumber) + runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) return nil } @@ -68,9 +132,9 @@ func (f *OS2LFinder) GetName() string { } // GetPeripheralSettings gets the peripheral settings -func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) { +func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]any, error) { // Return the specified peripheral - peripheral, found := f.registeredPeripherals[peripheralID] + 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") @@ -80,24 +144,49 @@ func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]inte } // SetPeripheralSettings sets the peripheral settings -func (f *OS2LFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error { +func (f *OS2LFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error { // Return the specified peripheral - peripheral, found := f.registeredPeripherals[peripheralID] + 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") } - log.Debug().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") - return peripheral.SetSettings(settings) + + // 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 } -// Stop stops this finder -func (f *OS2LFinder) Stop() error { +// 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 } diff --git a/hardware/OS2LPeripheral.go b/hardware/OS2LPeripheral.go index 4bc9703..ee17343 100644 --- a/hardware/OS2LPeripheral.go +++ b/hardware/OS2LPeripheral.go @@ -2,86 +2,260 @@ package hardware import ( "context" + "encoding/json" "fmt" + "net" + "strings" + "sync" "time" "github.com/rs/zerolog/log" "github.com/wailsapp/wails/v2/pkg/runtime" ) +// OS2LMessage represents an OS2L message +type OS2LMessage struct { + Event string `json:"evt"` + Name string `json:"name"` + State string `json:"state"` + ID int64 `json:"id"` + Param float64 `json:"param"` +} + // OS2LPeripheral contains the data of an OS2L peripheral type OS2LPeripheral struct { - info PeripheralInfo // The basic info for this peripheral - serverIP string // OS2L server IP - serverPort int // OS2L server port + wg sync.WaitGroup + + info PeripheralInfo // The basic info for this peripheral + serverIP string // OS2L server IP + serverPort int // OS2L server port + listener net.Listener // Net listener (TCP) + listenerCancel context.CancelFunc // Call this function to cancel the peripheral activation + + eventCallback func(any) // This callback is called for returning events } // NewOS2LPeripheral creates a new OS2L peripheral func NewOS2LPeripheral(peripheralData PeripheralInfo) (*OS2LPeripheral, error) { + peripheral := &OS2LPeripheral{ + info: peripheralData, + listener: nil, + eventCallback: nil, + } log.Trace().Str("file", "OS2LPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("OS2L peripheral created") - return &OS2LPeripheral{ - info: peripheralData, - serverIP: "127.0.0.1", - serverPort: 9005, - }, nil + return peripheral, peripheral.loadSettings(peripheralData.Settings) } -// Connect connects the MIDI peripheral +// SetEventCallback sets the callback for returning events +func (p *OS2LPeripheral) SetEventCallback(eventCallback func(any)) { + p.eventCallback = eventCallback +} + +// Connect connects the OS2L peripheral func (p *OS2LPeripheral) Connect(ctx context.Context) error { + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting) + + var err error + addr := net.TCPAddr{Port: p.serverPort, IP: net.ParseIP(p.serverIP)} + + log.Debug().Any("addr", addr).Msg("parametres de connexion à la connexion") + p.listener, err = net.ListenTCP("tcp", &addr) + if err != nil { + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected) + return fmt.Errorf("unable to set the OS2L TCP listener") + } + + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated) log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected") - go func() { - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "connecting") - time.Sleep(5 * time.Second) - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "disconnected") - }() return nil } -// Disconnect disconnects the MIDI peripheral -func (p *OS2LPeripheral) Disconnect() error { - log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected") +// handleMessage handles an OS2L message +func (p *OS2LPeripheral) handleMessage(raw []byte) error { + message := OS2LMessage{} + err := json.Unmarshal(raw, &message) + if err != nil { + return fmt.Errorf("Unable to parse the OS2L message: %w", err) + } + log.Debug().Str("event", message.Event).Str("name", message.Name).Str("state", message.State).Int("ID", int(message.ID)).Float64("param", message.Param).Msg("OS2L event received") + // Return the event to the finder + if p.eventCallback != nil { + go p.eventCallback(message) + } return nil } -// Activate activates the MIDI peripheral -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(ctx context.Context) error { - log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated") - return nil -} - -// SetSettings sets a specific setting for this peripheral -func (p *OS2LPeripheral) SetSettings(settings map[string]interface{}) error { +// loadSettings check and load the settings in the peripheral +func (p *OS2LPeripheral) loadSettings(settings map[string]any) error { // Check if the IP exists serverIP, found := settings["os2lIp"] if !found { - return fmt.Errorf("Unable to find the OS2L server IP") + // Set default IP address + serverIP = "127.0.0.1" } // Check if it is a string ipSetting, ok := serverIP.(string) if ok { p.serverIP = ipSetting - } else { return fmt.Errorf("The specified IP is not a string") } // Check if the port exists serverPort, found := settings["os2lPort"] if !found { - return fmt.Errorf("Unable to find the OS2L server port") + // Set default port + serverPort = 9995 } - // Check if it is a float and convert to int - portFloat, ok := serverPort.(float64) - if ok { - p.serverPort = int(portFloat) - } else { - return fmt.Errorf("The specified port is not an int") + switch v := serverPort.(type) { + case int: + p.serverPort = v + case float64: + p.serverPort = int(v) // JSON numbers are float64 + default: + return fmt.Errorf("The specified port is not a number, got %T", serverPort) } + + return nil +} + +// Disconnect disconnects the MIDI peripheral +func (p *OS2LPeripheral) Disconnect(ctx context.Context) error { + + // Close the TCP listener if not null + if p.listener != nil { + p.listener.Close() + } + + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected) + + log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected") + return nil +} + +// Activate activates the OS2L peripheral +func (p *OS2LPeripheral) Activate(ctx context.Context) error { + // Create a derived context to handle deactivation + var listenerCtx context.Context + listenerCtx, p.listenerCancel = context.WithCancel(ctx) + + if p.listener == nil { + return fmt.Errorf("the listener isn't defined") + } + + p.wg.Add(1) + go func() { + defer p.wg.Done() + + for { + select { + case <-listenerCtx.Done(): + return + default: + p.listener.(*net.TCPListener).SetDeadline(time.Now().Add(1 * time.Second)) + conn, err := p.listener.Accept() + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Timeout() { + continue + } + if strings.Contains(err.Error(), "use of closed network connection") || strings.Contains(err.Error(), "invalid argument") { + return + } + log.Err(err).Str("file", "OS2LPeripheral").Msg("unable to accept the connection") + continue + } + + // Every client is handled in a dedicated goroutine + p.wg.Add(1) + go func(c net.Conn) { + defer p.wg.Done() + defer c.Close() + + buffer := make([]byte, 1024) + for { + select { + case <-listenerCtx.Done(): + return + default: + c.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + n, err := c.Read(buffer) + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Timeout() { + // Lecture a expiré → vérifier si le contexte est annulé + select { + case <-listenerCtx.Done(): + return + default: + continue // pas annulé → relancer Read + } + } + return // autre erreur ou EOF + } + + p.handleMessage(buffer[:n]) + } + } + }(conn) + } + } + }() + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated) + + log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated") + + return nil +} + +// Deactivate deactivates the OS2L peripheral +func (p *OS2LPeripheral) Deactivate(ctx context.Context) error { + if p.listener == nil { + return fmt.Errorf("the listener isn't defined") + } + + // Cancel listener by context + if p.listenerCancel != nil { + p.listenerCancel() + } + + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated) + + log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated") + return nil +} + +// SetSettings sets a specific setting for this peripheral +func (p *OS2LPeripheral) SetSettings(ctx context.Context, settings map[string]any) error { + err := p.loadSettings(settings) + if err != nil { + return fmt.Errorf("unable to load settings: %w", err) + } + // Reconnect the peripheral + p.wg.Add(1) + go func() { + defer p.wg.Done() + err := p.Deactivate(ctx) + if err != nil { + log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to deactivate") + return + } + err = p.Disconnect(ctx) + if err != nil { + log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to disconnect") + return + } + // Add a sleep to view changes + time.Sleep(500 * time.Millisecond) + err = p.Connect(ctx) + if err != nil { + log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to connect") + return + } + err = p.Activate(ctx) + if err != nil { + log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to activate") + return + } + }() + log.Info().Str("sn", p.GetInfo().SerialNumber).Msg("peripheral settings set") return nil } @@ -91,8 +265,8 @@ func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte } // GetSettings gets the peripheral settings -func (p *OS2LPeripheral) GetSettings() map[string]interface{} { - return map[string]interface{}{ +func (p *OS2LPeripheral) GetSettings() map[string]any { + return map[string]any{ "os2lIp": p.serverIP, "os2lPort": p.serverPort, } @@ -102,3 +276,11 @@ func (p *OS2LPeripheral) GetSettings() map[string]interface{} { func (p *OS2LPeripheral) GetInfo() PeripheralInfo { return p.info } + +// WaitStop stops the peripheral +func (p *OS2LPeripheral) WaitStop() error { + log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for OS2L peripheral to close...") + p.wg.Wait() + log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("OS2L peripheral closed!") + return nil +} diff --git a/hardware/hardware.go b/hardware/hardware.go index 466f4b4..81ba457 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -21,8 +21,10 @@ const ( PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL" // PeripheralRemoval is triggered when a peripheral has been disconnected from the system PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL" - // PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - connected) + // PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - deactivated - activated) PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS" + // PeripheralEventEmitted is triggered when a peripheral event is emitted + PeripheralEventEmitted PeripheralEvent = "PERIPHERAL_EVENT_EMITTED" // PeripheralStatusDisconnected : peripheral is now disconnected PeripheralStatusDisconnected PeripheralStatus = "PERIPHERAL_DISCONNECTED" // PeripheralStatusConnecting : peripheral is now connecting diff --git a/hardware/interfaces.go b/hardware/interfaces.go index d48b3fe..6c9ea82 100644 --- a/hardware/interfaces.go +++ b/hardware/interfaces.go @@ -5,39 +5,37 @@ import "context" // Peripheral represents the methods used to manage a peripheral (input or output hardware) type Peripheral interface { Connect(context.Context) error // Connect the peripheral - IsConnected() bool // Return if the peripheral is connected or not - Disconnect() error // Disconnect the peripheral + SetEventCallback(func(any)) // Callback is called when an event is emitted from the peripheral + Disconnect(context.Context) error // Disconnect the peripheral Activate(context.Context) error // Activate the peripheral Deactivate(context.Context) error // Deactivate the peripheral - SetSettings(map[string]interface{}) error // Set a peripheral setting + SetSettings(context.Context, map[string]any) error // Set a peripheral setting SetDeviceProperty(context.Context, uint32, byte) error // Update a device property + WaitStop() error // Properly close the peripheral - GetInfo() PeripheralInfo // Get the peripheral information - GetSettings() map[string]interface{} // Get the peripheral settings + GetInfo() PeripheralInfo // Get the peripheral information + GetSettings() map[string]any // Get the peripheral settings } // PeripheralInfo represents a peripheral information type PeripheralInfo struct { - Name string `yaml:"name"` // Name of the peripheral - SerialNumber string `yaml:"sn"` // S/N of the peripheral - ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral - // IsConnected bool // If the peripheral is connected to the system - // IsActivated bool // If the peripheral is activated in the project - // IsDetected bool // If the peripheral is detected by the system - Settings map[string]interface{} `yaml:"settings"` // Peripheral settings + Name string `yaml:"name"` // Name of the peripheral + SerialNumber string `yaml:"sn"` // S/N of the peripheral + ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral + Settings map[string]any `yaml:"settings"` // Peripheral settings } // PeripheralFinder represents how compatible peripheral drivers are implemented type PeripheralFinder interface { - Initialize() error // Initializes the protocol - OnArrival(cb func(p PeripheralInfo)) // Callback function when a peripheral arrives - OnRemoval(cb func(p PeripheralInfo)) // Callback function when a peripheral goes away - Start(context.Context) error // Start the detection - WaitStop() error // Waiting for finder to close - ForceScan() // Explicitly scans for peripherals - RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data - UnregisterPeripheral(context.Context, PeripheralInfo) error // Unregisters an existing peripheral - GetPeripheralSettings(string) (map[string]interface{}, error) // Gets the peripheral settings - SetPeripheralSettings(string, map[string]interface{}) error // Sets the peripheral settings - GetName() string // Get the name of the finder + Initialize() error // Initializes the protocol + OnArrival(cb func(p PeripheralInfo)) // Callback function when a peripheral arrives + OnRemoval(cb func(p PeripheralInfo)) // Callback function when a peripheral goes away + Start(context.Context) error // Start the detection + WaitStop() error // Waiting for finder to close + ForceScan() // Explicitly scans for peripherals + RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data + UnregisterPeripheral(context.Context, PeripheralInfo) error // Unregisters an existing peripheral + GetPeripheralSettings(string) (map[string]any, error) // Gets the peripheral settings + SetPeripheralSettings(context.Context, string, map[string]any) error // Sets the peripheral settings + GetName() string // Get the name of the finder } diff --git a/peripherals.go b/peripherals.go index 91ae570..f9fb409 100644 --- a/peripherals.go +++ b/peripherals.go @@ -18,9 +18,10 @@ func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, err // Register this new peripheral serialNumber, err := f.RegisterPeripheral(a.ctx, peripheralData) if err != nil { - log.Trace().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", serialNumber).Msg("device registered to the finder") - return "", fmt.Errorf("unable to register the peripheral '%s'", serialNumber) + return "", fmt.Errorf("unable to register the peripheral '%s': %w", serialNumber, err) } + log.Trace().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", serialNumber).Msg("device registered to the finder") + // Rewrite the serialnumber for virtual devices peripheralData.SerialNumber = serialNumber @@ -35,7 +36,7 @@ func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, err } // GetPeripheralSettings gets the peripheral settings -func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[string]interface{}, error) { +func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[string]any, error) { // Get the peripheral from its finder f, err := a.hardwareManager.GetFinder(protocolName) if err != nil { @@ -46,7 +47,7 @@ func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[stri } // UpdatePeripheralSettings updates a specific setting of a peripheral -func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settings map[string]interface{}) error { +func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settings map[string]any) error { // Sets the settings with the finder f, err := a.hardwareManager.GetFinder(protocolName) if err != nil { @@ -61,7 +62,7 @@ func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settin pInfo.Settings = settings a.projectInfo.PeripheralsInfo[peripheralID] = pInfo // Apply changes in the peripheral - return f.SetPeripheralSettings(peripheralID, pInfo.Settings) + return f.SetPeripheralSettings(a.ctx, peripheralID, pInfo.Settings) } // RemovePeripheral removes a peripheral from the project diff --git a/project.go b/project.go index af7a157..9b9954d 100644 --- a/project.go +++ b/project.go @@ -136,7 +136,7 @@ func (a *App) OpenProject(projectInfo ProjectInfo) error { } _, err = hostFinder.RegisterPeripheral(a.ctx, value) if err != nil { - return fmt.Errorf("unable to register the peripheral S/N '%s'", key) + return fmt.Errorf("unable to register the peripheral S/N '%s': %w", key, err) } } return nil