From 4fbb75ad1944c5d5fcdbb5798a44543db50ad223 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Tue, 2 Dec 2025 18:02:17 +0000 Subject: [PATCH] 34-midi (#35) Closes #34 Implement MIDI peripherals Implement device concept Cleaning project Reviewed-on: https://factory.vbprojects.fr/DMXStage/dmxconnect/pulls/35 --- .gitignore | 1 + app.go | 28 +- build.bat | 4 +- endpoints.go | 61 ++++ frontend/jsconfig.json | 38 --- .../Settings/InputsOutputsContent.svelte | 126 +++---- frontend/src/lang/en.json | 17 +- frontend/src/runtime-events.js | 184 +++++----- frontend/src/stores.js | 18 +- go.mod | 3 +- go.sum | 7 +- hardware/FTDIFinder.go | 314 ------------------ hardware/FTDIPeripheral.go | 171 ---------- hardware/MIDIFinder.go | 210 ------------ hardware/MIDIPeripheral.go | 64 ---- hardware/OS2LFinder.go | 195 ----------- hardware/cpp/generate.bat | 22 -- hardware/cpp/test/detectFTDI_test.cpp | 14 - hardware/devicesHandler.go | 72 ++++ hardware/endpointsHandler.go | 221 ++++++++++++ hardware/genericftdi/FTDIEndpoint.go | 180 ++++++++++ hardware/genericftdi/FTDIProvider.go | 181 ++++++++++ hardware/genericftdi/cpp/generate.bat | 7 + .../cpp/include/detectFTDIBridge.h | 8 +- .../cpp/include/dmxSenderBridge.h | 0 hardware/{ => genericftdi}/cpp/lib/ftd2xx.lib | Bin .../{ => genericftdi}/cpp/src/detectFTDI.cpp | 22 +- .../{ => genericftdi}/cpp/src/detectFTDI.h | 6 +- .../{ => genericftdi}/cpp/src/dmxSender.cpp | 0 .../{ => genericftdi}/cpp/src/dmxSender.h | 2 +- hardware/{ => genericftdi}/cpp/src/ftd2xx.h | 0 .../genericftdi/cpp/test/detectFTDI_test.cpp | 14 + .../cpp/test/dmxSender_test.cpp | 0 hardware/genericmidi/MIDIEndpoint.go | 140 ++++++++ hardware/genericmidi/MIDIProvider.go | 248 ++++++++++++++ hardware/hardware.go | 142 +++----- hardware/interfaces.go | 41 --- .../OS2LEndpoint.go} | 135 ++++---- hardware/os2l/OS2LProvider.go | 106 ++++++ hardware/providersHandler.go | 37 +++ peripherals.go | 126 ------- project.go | 37 +-- 42 files changed, 1638 insertions(+), 1564 deletions(-) create mode 100644 endpoints.go delete mode 100644 frontend/jsconfig.json delete mode 100644 hardware/FTDIFinder.go delete mode 100644 hardware/FTDIPeripheral.go delete mode 100644 hardware/MIDIFinder.go delete mode 100644 hardware/MIDIPeripheral.go delete mode 100644 hardware/OS2LFinder.go delete mode 100644 hardware/cpp/generate.bat delete mode 100644 hardware/cpp/test/detectFTDI_test.cpp create mode 100644 hardware/devicesHandler.go create mode 100644 hardware/endpointsHandler.go create mode 100644 hardware/genericftdi/FTDIEndpoint.go create mode 100644 hardware/genericftdi/FTDIProvider.go create mode 100644 hardware/genericftdi/cpp/generate.bat rename hardware/{ => genericftdi}/cpp/include/detectFTDIBridge.h (50%) rename hardware/{ => genericftdi}/cpp/include/dmxSenderBridge.h (100%) rename hardware/{ => genericftdi}/cpp/lib/ftd2xx.lib (100%) rename hardware/{ => genericftdi}/cpp/src/detectFTDI.cpp (81%) rename hardware/{ => genericftdi}/cpp/src/detectFTDI.h (58%) rename hardware/{ => genericftdi}/cpp/src/dmxSender.cpp (100%) rename hardware/{ => genericftdi}/cpp/src/dmxSender.h (96%) rename hardware/{ => genericftdi}/cpp/src/ftd2xx.h (100%) create mode 100644 hardware/genericftdi/cpp/test/detectFTDI_test.cpp rename hardware/{ => genericftdi}/cpp/test/dmxSender_test.cpp (100%) create mode 100644 hardware/genericmidi/MIDIEndpoint.go create mode 100644 hardware/genericmidi/MIDIProvider.go delete mode 100644 hardware/interfaces.go rename hardware/{OS2LPeripheral.go => os2l/OS2LEndpoint.go} (50%) create mode 100644 hardware/os2l/OS2LProvider.go create mode 100644 hardware/providersHandler.go delete mode 100644 peripherals.go diff --git a/.gitignore b/.gitignore index 8195664..25e933e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build/bin projects +mapping node_modules frontend/.vscode frontend/dist diff --git a/app.go b/app.go index 3b5b12f..a4426ac 100644 --- a/app.go +++ b/app.go @@ -3,6 +3,9 @@ package main import ( "context" "dmxconnect/hardware" + genericmidi "dmxconnect/hardware/genericMIDI" + "dmxconnect/hardware/genericftdi" + "dmxconnect/hardware/os2l" "fmt" "io" "time" @@ -20,24 +23,25 @@ type App struct { cancelFunc context.CancelFunc wait sync.WaitGroup - hardwareManager *hardware.HardwareManager // For managing all the hardware - 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 + hardwareManager *hardware.Manager // For managing all the hardware + 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 } // NewApp creates a new App application struct func NewApp() *App { // Create a new hadware manager - hardwareManager := hardware.NewHardwareManager() - // hardwareManager.RegisterFinder(hardware.NewMIDIFinder(5 * time.Second)) - hardwareManager.RegisterFinder(hardware.NewFTDIFinder(5 * time.Second)) - hardwareManager.RegisterFinder(hardware.NewOS2LFinder()) + hardwareManager := hardware.NewManager() + // Register all the providers to use as hardware scanners + hardwareManager.RegisterProvider(genericftdi.NewProvider(3 * time.Second)) + hardwareManager.RegisterProvider(os2l.NewProvider()) + hardwareManager.RegisterProvider(genericmidi.NewProvider(3 * time.Second)) return &App{ hardwareManager: hardwareManager, projectSave: "", projectInfo: ProjectInfo{ - PeripheralsInfo: make(map[string]hardware.PeripheralInfo), + EndpointsInfo: make(map[string]hardware.EndpointInfo), }, } } @@ -60,12 +64,12 @@ func (a *App) onStartup(ctx context.Context) { } // onReady is called when the DOM is ready -// We get the current peripherals connected +// We get the current endpoints connected func (a *App) onReady(ctx context.Context) { - // log.Debug().Str("file", "peripherals").Msg("getting peripherals...") + // log.Debug().Str("file", "endpoints").Msg("getting endpoints...") // err := a.hardwareManager.Scan() // if err != nil { - // log.Err(err).Str("file", "app").Msg("unable to get the peripherals") + // log.Err(err).Str("file", "app").Msg("unable to get the endpoints") // } return } diff --git a/build.bat b/build.bat index b10b859..bbe008e 100644 --- a/build.bat +++ b/build.bat @@ -26,8 +26,8 @@ echo [INFO] Git version detected: %GIT_TAG% echo [INFO] Mode selectionne : %MODE% echo [INFO] Moving to the C++ folder... -cd /d "%~dp0hardware\cpp" || ( - echo [ERROR] Impossible d'accéder à hardware\cpp +cd /d "%~dp0hardware\genericftdi\cpp" || ( + echo [ERROR] Impossible d'accéder à hardware\genericftdi\cpp exit /b 1 ) diff --git a/endpoints.go b/endpoints.go new file mode 100644 index 0000000..cb71a35 --- /dev/null +++ b/endpoints.go @@ -0,0 +1,61 @@ +package main + +import ( + "dmxconnect/hardware" + "fmt" + + "github.com/rs/zerolog/log" +) + +// AddEndpoint adds a endpoint to the project +func (a *App) AddEndpoint(endpointData hardware.EndpointInfo) (string, error) { + // Register this new endpoint + serialNumber, err := a.hardwareManager.RegisterEndpoint(a.ctx, endpointData) + if err != nil { + return "", fmt.Errorf("unable to register the endpoint '%s': %w", serialNumber, err) + } + log.Trace().Str("file", "endpoint").Str("protocolName", endpointData.ProtocolName).Str("periphID", serialNumber).Msg("device registered to the provider") + + // Rewrite the serialnumber for virtual devices + endpointData.SerialNumber = serialNumber + + // Add the endpoint ID to the project + if a.projectInfo.EndpointsInfo == nil { + a.projectInfo.EndpointsInfo = make(map[string]hardware.EndpointInfo) + } + + a.projectInfo.EndpointsInfo[endpointData.SerialNumber] = endpointData + log.Info().Str("file", "endpoint").Str("protocolName", endpointData.ProtocolName).Str("periphID", endpointData.SerialNumber).Msg("endpoint added to project") + return endpointData.SerialNumber, nil +} + +// GetEndpointSettings gets the endpoint settings +func (a *App) GetEndpointSettings(protocolName, endpointSN string) (map[string]any, error) { + return a.hardwareManager.GetEndpointSettings(endpointSN) +} + +// UpdateEndpointSettings updates a specific setting of a endpoint +func (a *App) UpdateEndpointSettings(protocolName, endpointID string, settings map[string]any) error { + // Save the settings in the application + if a.projectInfo.EndpointsInfo == nil { + a.projectInfo.EndpointsInfo = make(map[string]hardware.EndpointInfo) + } + pInfo := a.projectInfo.EndpointsInfo[endpointID] + pInfo.Settings = settings + a.projectInfo.EndpointsInfo[endpointID] = pInfo + // Apply changes in the endpoint + return a.hardwareManager.SetEndpointSettings(a.ctx, endpointID, pInfo.Settings) +} + +// RemoveEndpoint removes a endpoint from the project +func (a *App) RemoveEndpoint(endpointData hardware.EndpointInfo) error { + // Unregister the endpoint from the provider + err := a.hardwareManager.UnregisterEndpoint(a.ctx, endpointData) + if err != nil { + return fmt.Errorf("unable to unregister this endpoint: %w", err) + } + // Remove the endpoint ID from the project + delete(a.projectInfo.EndpointsInfo, endpointData.SerialNumber) + log.Info().Str("file", "endpoint").Str("protocolName", endpointData.ProtocolName).Str("periphID", endpointData.SerialNumber).Msg("endpoint removed from project") + return nil +} diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json deleted file mode 100644 index 3918b4f..0000000 --- a/frontend/jsconfig.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "compilerOptions": { - "moduleResolution": "Node", - "target": "ESNext", - "module": "ESNext", - /** - * svelte-preprocess cannot figure out whether you have - * a value or a type, so tell TypeScript to enforce using - * `import type` instead of `import` for Types. - */ - "importsNotUsedAsValues": "error", - "isolatedModules": true, - "resolveJsonModule": true, - /** - * To have warnings / errors of the Svelte compiler at the - * correct position, enable source maps by default. - */ - "sourceMap": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "baseUrl": ".", - /** - * Typecheck JS in `.svelte` and `.js` files by default. - * Disable this if you'd like to use dynamic types. - */ - "checkJs": true - }, - /** - * Use global.d.ts instead of compilerOptions.types - * to avoid limiting type declarations. - */ - "include": [ - "src/**/*.d.ts", - "src/**/*.js", - "src/**/*.svelte" - ] -} diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index f67b102..282393f 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -2,77 +2,87 @@ import DeviceCard from "./DeviceCard.svelte"; import Input from "../General/Input.svelte"; import { t, _ } from 'svelte-i18n' - import { generateToast, needProjectSave, peripherals, colors } from "../../stores"; + import { generateToast, needProjectSave, endpoints, colors } from "../../stores"; import { get } from "svelte/store" - import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral } from "../../../wailsjs/go/main/App"; + import { UpdateEndpointSettings, GetEndpointSettings, RemoveEndpoint, AddEndpoint } from "../../../wailsjs/go/main/App"; import RoundedButton from "../General/RoundedButton.svelte"; - // Add the peripheral to the project - function addPeripheral(peripheral){ - // Add the peripheral to the project (backend) - AddPeripheral(peripheral) + // Create the endpoint to the project + function createEndpoint(endpoint){ + // Create the endpoint to the project (backend) + AddEndpoint(endpoint) .catch((error) => { - console.log("Unable to add the peripheral to the project: " + error) - generateToast('danger', 'bx-error', $_("addPeripheralErrorToast")) + console.log("Unable to create the endpoint: " + error) + generateToast('danger', 'bx-error', $_("addEndpointErrorToast")) }) } - // Remove the peripheral from the project - function removePeripheral(peripheral) { - // Delete the peripheral from the project (backend) - RemovePeripheral(peripheral) + // Add the endpoint to the project + function addEndpoint(endpoint){ + // Add the endpoint to the project (backend) + AddEndpoint(endpoint) + .catch((error) => { + console.log("Unable to add the endpoint to the project: " + error) + generateToast('danger', 'bx-error', $_("addEndpointErrorToast")) + }) + } + + // Remove the endpoint from the project + function removeEndpoint(endpoint) { + // Delete the endpoint from the project (backend) + RemoveEndpoint(endpoint) .catch((error) => { - console.log("Unable to remove the peripheral from the project: " + error) - generateToast('danger', 'bx-error', $_("removePeripheralErrorToast")) + console.log("Unable to remove the endpoint from the project: " + error) + generateToast('danger', 'bx-error', $_("removeEndpointErrorToast")) }) } - // Select the peripheral to edit its settings - let selectedPeripheralSN = null - let selectedPeripheralSettings = {} - function selectPeripheral(peripheral){ - // Load the settings array if the peripheral is detected - if (peripheral.isSaved){ - GetPeripheralSettings(peripheral.ProtocolName, peripheral.SerialNumber).then((peripheralSettings) => { - selectedPeripheralSettings = peripheralSettings - // Select the current peripheral - selectedPeripheralSN = peripheral.SerialNumber + // Select the endpoint to edit its settings + let selectedEndpointSN = null + let selectedEndpointSettings = {} + function selectEndpoint(endpoint){ + // Load the settings array if the endpoint is detected + if (endpoint.isSaved){ + GetEndpointSettings(endpoint.ProtocolName, endpoint.SerialNumber).then((endpointSettings) => { + selectedEndpointSettings = endpointSettings + // Select the current endpoint + selectedEndpointSN = endpoint.SerialNumber }).catch((error) => { - console.log("Unable to get the peripheral settings: " + error) - generateToast('danger', 'bx-error', $_("getPeripheralSettingsErrorToast")) + console.log("Unable to get the endpoint settings: " + error) + generateToast('danger', 'bx-error', $_("getEndpointSettingsErrorToast")) }) } } - // Unselect the peripheral if it is disconnected + // Unselect the endpoint if it is disconnected $: { - Object.entries($peripherals).filter(([serialNumber, peripheral]) => { - if (!peripheral.isDetected && peripheral.isSaved && selectedPeripheralSN == serialNumber) { - selectedPeripheralSN = null - selectedPeripheralSettings = {} + Object.entries($endpoints).filter(([serialNumber, endpoint]) => { + if (!endpoint.isDetected && endpoint.isSaved && selectedEndpointSN == serialNumber) { + selectedEndpointSN = null + selectedEndpointSettings = {} } }); } - // Get the number of saved peripherals - $: savedPeripheralNumber = Object.values($peripherals).filter(peripheral => peripheral.isSaved).length; + // Get the number of saved endpoints + $: savedEndpointNumber = Object.values($endpoints).filter(endpoint => endpoint.isSaved).length; - // Validate the peripheral settings + // Validate the endpoint settings function validate(settingName, settingValue){ - console.log("Peripheral setting '" + settingName + "' set to '" + settingValue + "'") + console.log("Endpoint setting '" + settingName + "' set to '" + settingValue + "'") // Get the old setting type and convert the new setting to this type const convert = { number: Number, string: String, boolean: Boolean, - }[typeof(selectedPeripheralSettings[settingName])] || (x => x) - selectedPeripheralSettings[settingName] = convert(settingValue) - let peripheralProtocolName = get(peripherals)[selectedPeripheralSN].ProtocolName - UpdatePeripheralSettings(peripheralProtocolName, selectedPeripheralSN, selectedPeripheralSettings).then(()=> { + }[typeof(selectedEndpointSettings[settingName])] || (x => x) + selectedEndpointSettings[settingName] = convert(settingValue) + let endpointProtocolName = get(endpoints)[selectedEndpointSN].ProtocolName + UpdateEndpointSettings(endpointProtocolName, selectedEndpointSN, selectedEndpointSettings).then(()=> { $needProjectSave = true }).catch((error) => { - console.log("Unable to save the peripheral setting: " + error) - generateToast('danger', 'bx-error', $_("peripheralSettingSaveErrorToast")) + console.log("Unable to save the endpoint setting: " + error) + generateToast('danger', 'bx-error', $_("endpointSettingSaveErrorToast")) }) } @@ -82,31 +92,31 @@

{$_("projectHardwareDetectedLabel")}

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

{$_("projectHardwareOthersLabel")}

- addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} - on:dblclick={() => addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + createEndpoint({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + on:dblclick={() => createEndpoint({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} status="PERIPHERAL_CONNECTED" title={"OS2L virtual device"} type={"OS2L"} location={""} addable={true}/>

{$_("projectHardwareSavedLabel")}

- {#if savedPeripheralNumber > 0} - {#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 signalized={peripheral.eventEmitted}/> + {#if savedEndpointNumber > 0} + {#each Object.entries($endpoints) as [serialNumber, endpoint]} + {#if endpoint.isSaved} + removeEndpoint(endpoint)} on:dblclick={() => removeEndpoint(endpoint)} on:click={() => selectEndpoint(endpoint)} + title={endpoint.Name} type={endpoint.ProtocolName} location={endpoint.Location ? endpoint.Location : ""} line1={endpoint.SerialNumber ? "S/N: " + endpoint.SerialNumber : ""} selected={serialNumber == selectedEndpointSN} removable signalizable signalized={endpoint.eventEmitted}/> {/if} {/each} {:else} @@ -114,9 +124,9 @@ {/if}
- {#if Object.keys(selectedPeripheralSettings).length > 0} - {#each Object.entries(selectedPeripheralSettings) as [settingName, settingValue]} -
+ {#if Object.keys(selectedEndpointSettings).length > 0} + {#each Object.entries(selectedEndpointSettings) as [settingName, settingValue]} +
validate(settingName, event.detail.target.value)} label={$t(settingName)} type="{typeof(settingValue)}" width='100%' value="{settingValue}"/>
{/each} @@ -126,7 +136,7 @@