34-midi #35

Merged
thinkode merged 12 commits from 34-midi into develop 2025-12-02 18:02:18 +00:00
25 changed files with 1102 additions and 1102 deletions
Showing only changes of commit 1c8607800a - Show all commits

8
app.go
View File

@@ -33,7 +33,7 @@ func NewApp() *App {
hardwareManager: hardwareManager,
projectSave: "",
projectInfo: ProjectInfo{
PeripheralsInfo: make(map[string]hardware.PeripheralInfo),
EndpointsInfo: make(map[string]hardware.EndpointInfo),
},
}
}
@@ -56,12 +56,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
}

61
endpoints.go Normal file
View File

@@ -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
}

View File

@@ -2,87 +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";
// Create the peripheral to the project
function createPeripheral(peripheral){
// Create 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 create the peripheral: " + error)
generateToast('danger', 'bx-error', $_("addPeripheralErrorToast"))
console.log("Unable to create the endpoint: " + error)
generateToast('danger', 'bx-error', $_("addEndpointErrorToast"))
})
}
// Add the peripheral to the project
function addPeripheral(peripheral){
// Add the peripheral to the project (backend)
AddPeripheral(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 peripheral to the project: " + error)
generateToast('danger', 'bx-error', $_("addPeripheralErrorToast"))
console.log("Unable to add the endpoint to the project: " + error)
generateToast('danger', 'bx-error', $_("addEndpointErrorToast"))
})
}
// Remove the peripheral from the project
function removePeripheral(peripheral) {
// Delete the peripheral from the project (backend)
RemovePeripheral(peripheral)
// 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"))
})
}
</script>
@@ -92,31 +92,31 @@
<div class="availableHardware">
<p style="color: var(--white-color);"><i class='bx bxs-plug'></i> {$_("projectHardwareDetectedLabel")}</p>
</div>
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isDetected}
<DeviceCard on:add={() => addPeripheral(peripheral)} on:dblclick={() => {
if(!peripheral.isSaved)
addPeripheral(peripheral)
{#each Object.entries($endpoints) as [serialNumber, endpoint]}
{#if endpoint.isDetected}
<DeviceCard on:add={() => 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}
<div class="availableHardware">
<p style="color: var(--white-color);"><i class='bx bxs-network-chart' ></i> {$_("projectHardwareOthersLabel")}</p>
</div>
<DeviceCard on:add={() => createPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})}
on:dblclick={() => createPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})}
<DeviceCard on:add={() => 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}/>
</div>
<div style="padding: 0.5em; flex:2; width:100%;">
<p style="margin-bottom: 1em;">{$_("projectHardwareSavedLabel")}</p>
<div class="configuredHardware">
{#if savedPeripheralNumber > 0}
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isSaved}
<DeviceCard status="{peripheral.status}" on:delete={() => 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}
<DeviceCard status="{endpoint.status}" on:delete={() => 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}
@@ -124,9 +124,9 @@
{/if}
</div>
<div class='flexSettings'>
{#if Object.keys(selectedPeripheralSettings).length > 0}
{#each Object.entries(selectedPeripheralSettings) as [settingName, settingValue]}
<div class="peripheralSetting">
{#if Object.keys(selectedEndpointSettings).length > 0}
{#each Object.entries(selectedEndpointSettings) as [settingName, settingValue]}
<div class="endpointSetting">
<Input on:blur={(event) => validate(settingName, event.detail.target.value)} label={$t(settingName)} type="{typeof(settingValue)}" width='100%' value="{settingValue}"/>
</div>
{/each}
@@ -136,7 +136,7 @@
</div>
<style>
.peripheralSetting{
.endpointSetting{
margin: 0.5em;
}
.flexSettings{

View File

@@ -43,22 +43,22 @@
"projectHardwareOthersLabel": "Others",
"projectHardwareEmptyLabel": "No hardware saved for this project",
"peripheralArrivalToast": "Peripheral inserted:",
"peripheralRemovalToast": "Peripheral removed:",
"endpointArrivalToast": "Endpoint inserted:",
"endpointRemovalToast": "Endpoint removed:",
"projectSavedToast": "The project has been saved",
"projectSaveErrorToast": "Unable to save the project:",
"addPeripheralErrorToast": "Unable to add this peripheral to project",
"createPeripheralErrorToast": "Unable to create this peripheral",
"removePeripheralErrorToast": "Unable to remove this peripheral from project",
"os2lPeripheralCreatedToast": "Your OS2L peripheral has been created",
"os2lPeripheralCreateErrorToast": "Unable to create the OS2L peripheral",
"getPeripheralSettingsErrorToast": "Unable to get the peripheral settings",
"addEndpointErrorToast": "Unable to add this endpoint to project",
"createEndpointErrorToast": "Unable to create this endpoint",
"removeEndpointErrorToast": "Unable to remove this endpoint from project",
"os2lEndpointCreatedToast": "Your OS2L endpoint has been created",
"os2lEndpointCreateErrorToast": "Unable to create the OS2L endpoint",
"getEndpointSettingsErrorToast": "Unable to get the endpoint settings",
"projectsLoadErrorToast": "Unable to get the projects list",
"projectOpenedToast": "The project was opened:",
"projectOpenErrorToast": "Unable to open the project",
"projectCreatedToast": "The project was created",
"projectCreateErrorToast": "Unable to create the project",
"peripheralSettingSaveErrorToast": "Unable to save the peripheral settings",
"endpointSettingSaveErrorToast": "Unable to save the endpoint settings",
"os2lIp": "OS2L server IP",
"os2lPort": "OS2L server port"

View File

@@ -1,66 +1,66 @@
import { EventsOn, EventsOff } from "../wailsjs/runtime/runtime.js"
import { peripherals, generateToast, needProjectSave, showInformation } from './stores'
import { endpoints, generateToast, needProjectSave, showInformation } from './stores'
import { get } from "svelte/store"
import { _ } from 'svelte-i18n'
// New peripheral has been added to the system
function peripheralArrival (peripheralInfo){
// New endpoint has been added to the system
function endpointArrival (endpointInfo){
// If not exists, add it to the map
// isDetected key to true
peripherals.update((storedPeripherals) => {
endpoints.update((storedEndpoints) => {
return {
...storedPeripherals,
[peripheralInfo.SerialNumber]: {
...storedPeripherals[peripheralInfo.SerialNumber],
Name: peripheralInfo.Name,
ProtocolName: peripheralInfo.ProtocolName,
SerialNumber: peripheralInfo.SerialNumber,
Settings: peripheralInfo.Settings,
...storedEndpoints,
[endpointInfo.SerialNumber]: {
...storedEndpoints[endpointInfo.SerialNumber],
Name: endpointInfo.Name,
ProtocolName: endpointInfo.ProtocolName,
SerialNumber: endpointInfo.SerialNumber,
Settings: endpointInfo.Settings,
isDetected: true,
},
}})
console.log("Hardware has been added to the system");
generateToast('info', 'bxs-hdd', get(_)("peripheralArrivalToast") + ' <b>' + peripheralInfo.Name + '</b>')
generateToast('info', 'bxs-hdd', get(_)("endpointArrivalToast") + ' <b>' + endpointInfo.Name + '</b>')
}
// Peripheral is removed from the system
function peripheralRemoval (peripheralInfo){
// Endpoint is removed from the system
function endpointRemoval (endpointInfo){
// If not exists, add it to the map
// isDetected key to false
peripherals.update((storedPeripherals) => {
endpoints.update((storedEndpoints) => {
return {
...storedPeripherals,
[peripheralInfo.SerialNumber]: {
...storedPeripherals[peripheralInfo.SerialNumber],
Name: peripheralInfo.Name,
ProtocolName: peripheralInfo.ProtocolName,
SerialNumber: peripheralInfo.SerialNumber,
Settings: peripheralInfo.Settings,
...storedEndpoints,
[endpointInfo.SerialNumber]: {
...storedEndpoints[endpointInfo.SerialNumber],
Name: endpointInfo.Name,
ProtocolName: endpointInfo.ProtocolName,
SerialNumber: endpointInfo.SerialNumber,
Settings: endpointInfo.Settings,
isDetected: false,
status: "PERIPHERAL_DISCONNECTED",
},
}})
console.log("Hardware has been removed from the system");
generateToast('warning', 'bxs-hdd', get(_)("peripheralRemovalToast") + ' <b>' + peripheralInfo.Name + '</b>')
generateToast('warning', 'bxs-hdd', get(_)("endpointRemovalToast") + ' <b>' + endpointInfo.Name + '</b>')
}
// Update peripheral status
function peripheralUpdateStatus(peripheralInfo, status){
// Update endpoint status
function endpointUpdateStatus(endpointInfo, status){
// If not exists, add it to the map
// change status key
peripherals.update((storedPeripherals) => {
endpoints.update((storedEndpoints) => {
console.log(status)
return {
...storedPeripherals,
[peripheralInfo.SerialNumber]: {
...storedPeripherals[peripheralInfo.SerialNumber],
Name: peripheralInfo.Name,
ProtocolName: peripheralInfo.ProtocolName,
SerialNumber: peripheralInfo.SerialNumber,
Settings: peripheralInfo.Settings,
...storedEndpoints,
[endpointInfo.SerialNumber]: {
...storedEndpoints[endpointInfo.SerialNumber],
Name: endpointInfo.Name,
ProtocolName: endpointInfo.ProtocolName,
SerialNumber: endpointInfo.SerialNumber,
Settings: endpointInfo.Settings,
status: status,
},
}})
@@ -68,20 +68,20 @@ function peripheralUpdateStatus(peripheralInfo, status){
console.log("Hardware status has been updated to " + status);
}
// Load the peripheral in the project
function loadPeripheral (peripheralInfo) {
// Load the endpoint in the project
function loadEndpoint (endpointInfo) {
// If not exists, add it to the map
// isSaved key to true
peripherals.update((storedPeripherals) => {
endpoints.update((storedEndpoints) => {
return {
...storedPeripherals,
[peripheralInfo.SerialNumber]: {
...storedPeripherals[peripheralInfo.SerialNumber],
Name: peripheralInfo.Name,
ProtocolName: peripheralInfo.ProtocolName,
SerialNumber: peripheralInfo.SerialNumber,
Settings: peripheralInfo.Settings,
...storedEndpoints,
[endpointInfo.SerialNumber]: {
...storedEndpoints[endpointInfo.SerialNumber],
Name: endpointInfo.Name,
ProtocolName: endpointInfo.ProtocolName,
SerialNumber: endpointInfo.SerialNumber,
Settings: endpointInfo.Settings,
isSaved: true,
},
}})
@@ -99,19 +99,19 @@ function loadProject (showInfo){
}
// Unload the hardware from the project
function unloadPeripheral (peripheralInfo) {
function unloadEndpoint (endpointInfo) {
// If not exists, add it to the map
// isSaved key to false
peripherals.update((storedPeripherals) => {
endpoints.update((storedEndpoints) => {
return {
...storedPeripherals,
[peripheralInfo.SerialNumber]: {
...storedPeripherals[peripheralInfo.SerialNumber],
Name: peripheralInfo.Name,
ProtocolName: peripheralInfo.ProtocolName,
SerialNumber: peripheralInfo.SerialNumber,
Settings: peripheralInfo.Settings,
...storedEndpoints,
[endpointInfo.SerialNumber]: {
...storedEndpoints[endpointInfo.SerialNumber],
Name: endpointInfo.Name,
ProtocolName: endpointInfo.ProtocolName,
SerialNumber: endpointInfo.SerialNumber,
Settings: endpointInfo.Settings,
isSaved: false,
},
}})
@@ -120,26 +120,26 @@ function unloadPeripheral (peripheralInfo) {
needProjectSave.set(true)
}
// A peripheral event has been emitted
function onPeripheralEvent(sn, event) {
// A endpoint event has been emitted
function onEndpointEvent(sn, event) {
// If not exists, add it to the map
// eventEmitted key to true for 0.2 sec
peripherals.update((storedPeripherals) => {
endpoints.update((storedEndpoints) => {
return {
...storedPeripherals,
...storedEndpoints,
[sn]: {
...storedPeripherals[sn],
...storedEndpoints[sn],
eventEmitted: true
},
}})
setTimeout(() => {
peripherals.update((storedPeripherals) => {
endpoints.update((storedEndpoints) => {
return {
...storedPeripherals,
...storedEndpoints,
[sn]: {
...storedPeripherals[sn],
...storedEndpoints[sn],
eventEmitted: false
},
}})
@@ -152,50 +152,50 @@ export function initRuntimeEvents(){
if (initialized) return
initialized = true
// Handle the event when a new peripheral is detected
EventsOn('PERIPHERAL_ARRIVAL', peripheralArrival)
// Handle the event when a new endpoint is detected
EventsOn('PERIPHERAL_ARRIVAL', endpointArrival)
// Handle the event when a peripheral is removed from the system
EventsOn('PERIPHERAL_REMOVAL', peripheralRemoval)
// Handle the event when a endpoint is removed from the system
EventsOn('PERIPHERAL_REMOVAL', endpointRemoval)
// Handle the event when a peripheral status is updated
EventsOn('PERIPHERAL_STATUS', peripheralUpdateStatus)
// Handle the event when a endpoint status is updated
EventsOn('PERIPHERAL_STATUS', endpointUpdateStatus)
// Handle the event when a new project need to be loaded
EventsOn('LOAD_PROJECT', loadProject)
// Handle a peripheral loaded in the project
EventsOn('PERIPHERAL_LOAD', loadPeripheral)
// Handle a endpoint loaded in the project
EventsOn('PERIPHERAL_LOAD', loadEndpoint)
// Handle a peripheral unloaded from the project
EventsOn('PERIPHERAL_UNLOAD', unloadPeripheral)
// Handle a endpoint unloaded from the project
EventsOn('PERIPHERAL_UNLOAD', unloadEndpoint)
// Handle a peripheral event
EventsOn('PERIPHERAL_EVENT_EMITTED', onPeripheralEvent)
// Handle a endpoint event
EventsOn('PERIPHERAL_EVENT_EMITTED', onEndpointEvent)
}
export function destroyRuntimeEvents(){
if (!initialized) return
initialized = false
// Handle the event when a new peripheral is detected
// Handle the event when a new endpoint is detected
EventsOff('PERIPHERAL_ARRIVAL')
// Handle the event when a peripheral is removed from the system
// Handle the event when a endpoint is removed from the system
EventsOff('PERIPHERAL_REMOVAL')
// Handle the event when a peripheral status is updated
// Handle the event when a endpoint status is updated
EventsOff('PERIPHERAL_STATUS')
// Handle the event when a new project need to be loaded
EventsOff('LOAD_PROJECT')
// Handle a peripheral loaded in the project
// Handle a endpoint loaded in the project
EventsOff('PERIPHERAL_LOAD')
// Handle a peripheral unloaded from the project
// Handle a endpoint unloaded from the project
EventsOff('PERIPHERAL_UNLOAD')
// Handle a peripheral event
// Handle a endpoint event
EventsOff('PERIPHERAL_EVENT_EMITTED')
}

View File

@@ -34,16 +34,16 @@ export const secondSize = writable("14px")
export const thirdSize = writable("20px")
// List of current hardware
export let peripherals = writable({})
export let endpoints = writable({})
// Peripheral structure :
// Endpoint structure :
// 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]interface{} `yaml:"settings"` // Peripheral settings
// Name string `yaml:"name"` // Name of the endpoint
// SerialNumber string `yaml:"sn"` // S/N of the endpoint
// ProtocolName string `yaml:"protocol"` // Protocol name of the endpoint
// Settings map[string]interface{} `yaml:"settings"` // Endpoint settings
// isSaved // if the peripheral is saved in the project
// isDetected // if the peripheral is detected by the system
// isSaved // if the endpoint is saved in the project
// isDetected // if the endpoint is detected by the system
// status // the status of connection
// eventEmitted // if an event has been emitted for this peripheral (disappear after a delay)
// eventEmitted // if an event has been emitted for this endpoint (disappear after a delay)

171
hardware/FTDIEndpoint.go Normal file
View File

@@ -0,0 +1,171 @@
package hardware
import (
"context"
"sync"
"unsafe"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
/*
#include <stdlib.h>
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldmxSender
#include "cpp/include/dmxSenderBridge.h"
*/
import "C"
// FTDIEndpoint contains the data of an FTDI endpoint
type FTDIEndpoint struct {
wg sync.WaitGroup
info EndpointInfo // The endpoint basic data
dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
}
// NewFTDIEndpoint creates a new FTDI endpoint
func NewFTDIEndpoint(info EndpointInfo) *FTDIEndpoint {
log.Info().Str("file", "FTDIEndpoint").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI endpoint created")
return &FTDIEndpoint{
info: info,
dmxSender: nil,
}
}
// Connect connects the FTDI endpoint
func (p *FTDIEndpoint) Connect(ctx context.Context) error {
// Check if the device has already been created
if p.dmxSender != nil {
return errors.Errorf("the DMX device has already been created!")
}
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusConnecting)
// Create the DMX sender
p.dmxSender = C.dmx_create()
// Connect the FTDI
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(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDisconnected)
log.Error().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device")
return errors.Errorf("unable to connect '%s'", p.info.SerialNumber)
}
p.wg.Add(1)
go func() {
defer p.wg.Done()
<-ctx.Done()
_ = p.Disconnect(ctx)
}()
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDeactivated)
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully")
return nil
}
// Disconnect disconnects the FTDI endpoint
func (p *FTDIEndpoint) 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!")
}
// Destroy the dmx sender
C.dmx_destroy(p.dmxSender)
// Reset the pointer to the endpoint
p.dmxSender = nil
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDisconnected)
return nil
}
// Activate activates the FTDI endpoint
func (p *FTDIEndpoint) Activate(ctx context.Context) error {
// Check if the device has already been created
if p.dmxSender == nil {
return errors.Errorf("the DMX sender has not been created!")
}
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("activating FTDI endpoint...")
err := C.dmx_activate(p.dmxSender)
if err != C.DMX_OK {
return errors.Errorf("unable to activate the DMX sender!")
}
// Test only
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(EndpointStatusUpdated), p.GetInfo(), EndpointStatusActivated)
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("DMX device activated successfully")
return nil
}
// Deactivate deactivates the FTDI endpoint
func (p *FTDIEndpoint) Deactivate(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 created!")
}
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("deactivating FTDI endpoint...")
err := C.dmx_deactivate(p.dmxSender)
if err != C.DMX_OK {
return errors.Errorf("unable to deactivate the DMX sender!")
}
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDeactivated)
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully")
return nil
}
// SetSettings sets a specific setting for this endpoint
func (p *FTDIEndpoint) SetSettings(ctx context.Context, settings map[string]any) error {
return errors.Errorf("unable to set the settings: not implemented")
}
// SetDeviceProperty sends a command to the specified device
func (p *FTDIEndpoint) SetDeviceProperty(ctx context.Context, channelNumber uint32, channelValue byte) error {
// Check if the device has already been created
if p.dmxSender == nil {
return errors.Errorf("the DMX device has not been created!")
}
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("setting device property on FTDI endpoint...")
err := C.dmx_setValue(p.dmxSender, C.uint16_t(channelNumber), C.uint8_t(channelValue))
if err != C.DMX_OK {
return errors.Errorf("unable to update the channel value!")
}
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("device property set on FTDI endpoint successfully")
return nil
}
// GetSettings gets the endpoint settings
func (p *FTDIEndpoint) GetSettings() map[string]interface{} {
return map[string]interface{}{}
}
// GetInfo gets all the endpoint information
func (p *FTDIEndpoint) GetInfo() EndpointInfo {
return p.info
}
// WaitStop wait about the endpoint to close
func (p *FTDIEndpoint) WaitStop() error {
log.Info().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("waiting for FTDI endpoint to close...")
p.wg.Wait()
log.Info().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("FTDI endpoint closed!")
return nil
}

View File

@@ -1,180 +0,0 @@
package hardware
import (
"context"
"fmt"
goRuntime "runtime"
"sync"
"time"
"unsafe"
"github.com/rs/zerolog/log"
)
/*
#include <stdlib.h>
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldetectFTDI
#include "cpp/include/detectFTDIBridge.h"
*/
import "C"
// FTDIFinder manages all the FTDI peripherals
type FTDIFinder struct {
wg sync.WaitGroup
mu sync.Mutex
detected map[string]*FTDIPeripheral // Detected peripherals
scanEvery time.Duration // Scans peripherals periodically
onArrival func(context.Context, Peripheral) // When a peripheral arrives
onRemoval func(context.Context, Peripheral) // When a peripheral goes away
}
// NewFTDIFinder creates a new FTDI finder
func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder {
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created")
return &FTDIFinder{
scanEvery: scanEvery,
detected: make(map[string]*FTDIPeripheral),
}
}
// OnArrival is the callback function when a new peripheral arrives
func (f *FTDIFinder) OnArrival(cb func(context.Context, Peripheral)) {
f.onArrival = cb
}
// OnRemoval i the callback when a peripheral goes away
func (f *FTDIFinder) OnRemoval(cb func(context.Context, Peripheral)) {
f.onRemoval = cb
}
// Initialize initializes the FTDI finder
func (f *FTDIFinder) Initialize() error {
// Check platform
if goRuntime.GOOS != "windows" {
log.Error().Str("file", "FTDIFinder").Str("platform", goRuntime.GOOS).Msg("FTDI finder not compatible with your platform")
return fmt.Errorf("the FTDI finder is not compatible with your platform yet (%s)", goRuntime.GOOS)
}
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder initialized")
return nil
}
// Create creates a new peripheral, based on the peripheral information (manually created)
func (f *FTDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) {
return PeripheralInfo{}, nil
}
// Remove removes an existing peripheral (manually created)
func (f *FTDIFinder) Remove(ctx context.Context, peripheral Peripheral) error {
return nil
}
// Start starts the finder and search for peripherals
func (f *FTDIFinder) Start(ctx context.Context) error {
f.wg.Add(1)
go func() {
ticker := time.NewTicker(f.scanEvery)
defer ticker.Stop()
defer f.wg.Done()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Scan the peripherals
err := f.scanPeripherals(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Msg("unable to scan FTDI peripherals")
}
}
}
}()
return nil
}
// GetName returns the name of the driver
func (f *FTDIFinder) GetName() string {
return "FTDI"
}
// scanPeripherals scans the FTDI peripherals
func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
log.Trace().Str("file", "FTDIFinder").Msg("FTDI scan triggered")
count := int(C.get_peripherals_number())
log.Info().Int("number", count).Msg("number of FTDI devices connected")
// Allocating C array
size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIPeripheralC{}))
devicesPtr := C.malloc(size)
defer C.free(devicesPtr)
devices := (*[1 << 20]C.FTDIPeripheralC)(devicesPtr)[:count:count]
C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
currentMap := make(map[string]PeripheralInfo)
for i := 0; i < count; i++ {
d := devices[i]
sn := C.GoString(d.serialNumber)
desc := C.GoString(d.description)
// isOpen := d.isOpen != 0
currentMap[sn] = PeripheralInfo{
SerialNumber: sn,
Name: desc,
// IsOpen: isOpen,
ProtocolName: "FTDI",
}
// Free C memory
C.free_ftdi_device(&d)
}
log.Info().Any("peripherals", currentMap).Msg("available FTDI peripherals")
// Detect arrivals
for sn, peripheralData := range currentMap {
// If the scanned peripheral isn't in the detected list, create it
if _, known := f.detected[sn]; !known {
peripheral := NewFTDIPeripheral(peripheralData)
if f.onArrival != nil {
f.onArrival(ctx, peripheral)
}
}
}
// Detect removals
for detectedSN, detectedPeripheral := range f.detected {
if _, still := currentMap[detectedSN]; !still {
// Delete it from the detected list
delete(f.detected, detectedSN)
// Execute the removal callback
if f.onRemoval != nil {
f.onRemoval(ctx, detectedPeripheral)
}
}
}
return nil
}
// WaitStop stops the finder
func (f *FTDIFinder) WaitStop() error {
log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
// Wait for goroutines to stop
f.wg.Wait()
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
return nil
}

View File

@@ -1,171 +0,0 @@
package hardware
import (
"context"
"sync"
"unsafe"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
/*
#include <stdlib.h>
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldmxSender
#include "cpp/include/dmxSenderBridge.h"
*/
import "C"
// FTDIPeripheral contains the data of an FTDI peripheral
type FTDIPeripheral struct {
wg sync.WaitGroup
info PeripheralInfo // The peripheral basic data
dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
}
// NewFTDIPeripheral creates a new FTDI peripheral
func NewFTDIPeripheral(info PeripheralInfo) *FTDIPeripheral {
log.Info().Str("file", "FTDIPeripheral").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI peripheral created")
return &FTDIPeripheral{
info: info,
dmxSender: nil,
}
}
// Connect connects the FTDI peripheral
func (p *FTDIPeripheral) Connect(ctx context.Context) error {
// Check if the device has already been created
if p.dmxSender != nil {
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()
// Connect the FTDI
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)
}
p.wg.Add(1)
go func() {
defer p.wg.Done()
<-ctx.Done()
_ = 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(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!")
}
// Destroy the dmx sender
C.dmx_destroy(p.dmxSender)
// Reset the pointer to the peripheral
p.dmxSender = nil
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
return nil
}
// Activate activates the FTDI peripheral
func (p *FTDIPeripheral) Activate(ctx context.Context) error {
// Check if the device has already been created
if p.dmxSender == nil {
return errors.Errorf("the DMX sender has not been created!")
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("activating FTDI peripheral...")
err := C.dmx_activate(p.dmxSender)
if err != C.DMX_OK {
return errors.Errorf("unable to activate the DMX sender!")
}
// Test only
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
}
// Deactivate deactivates the FTDI peripheral
func (p *FTDIPeripheral) Deactivate(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 created!")
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("deactivating FTDI peripheral...")
err := C.dmx_deactivate(p.dmxSender)
if err != C.DMX_OK {
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
}
// SetSettings sets a specific setting for this peripheral
func (p *FTDIPeripheral) SetSettings(ctx context.Context, settings map[string]any) error {
return errors.Errorf("unable to set the settings: not implemented")
}
// SetDeviceProperty sends a command to the specified device
func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, channelNumber uint32, channelValue byte) error {
// Check if the device has already been created
if p.dmxSender == nil {
return errors.Errorf("the DMX device has not been created!")
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("setting device property on FTDI peripheral...")
err := C.dmx_setValue(p.dmxSender, C.uint16_t(channelNumber), C.uint8_t(channelValue))
if err != C.DMX_OK {
return errors.Errorf("unable to update the channel value!")
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("device property set on FTDI peripheral successfully")
return nil
}
// GetSettings gets the peripheral settings
func (p *FTDIPeripheral) GetSettings() map[string]interface{} {
return map[string]interface{}{}
}
// GetInfo gets all the peripheral information
func (p *FTDIPeripheral) GetInfo() PeripheralInfo {
return p.info
}
// WaitStop wait about the peripheral to close
func (p *FTDIPeripheral) WaitStop() error {
log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for FTDI peripheral to close...")
p.wg.Wait()
log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("FTDI peripheral closed!")
return nil
}

180
hardware/FTDIProvider.go Normal file
View File

@@ -0,0 +1,180 @@
package hardware
import (
"context"
"fmt"
goRuntime "runtime"
"sync"
"time"
"unsafe"
"github.com/rs/zerolog/log"
)
/*
#include <stdlib.h>
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldetectFTDI
#include "cpp/include/detectFTDIBridge.h"
*/
import "C"
// FTDIProvider manages all the FTDI endpoints
type FTDIProvider struct {
wg sync.WaitGroup
mu sync.Mutex
detected map[string]*FTDIEndpoint // Detected endpoints
scanEvery time.Duration // Scans endpoints periodically
onArrival func(context.Context, Endpoint) // When a endpoint arrives
onRemoval func(context.Context, Endpoint) // When a endpoint goes away
}
// NewFTDIProvider creates a new FTDI provider
func NewFTDIProvider(scanEvery time.Duration) *FTDIProvider {
log.Trace().Str("file", "FTDIProvider").Msg("FTDI provider created")
return &FTDIProvider{
scanEvery: scanEvery,
detected: make(map[string]*FTDIEndpoint),
}
}
// OnArrival is the callback function when a new endpoint arrives
func (f *FTDIProvider) OnArrival(cb func(context.Context, Endpoint)) {
f.onArrival = cb
}
// OnRemoval i the callback when a endpoint goes away
func (f *FTDIProvider) OnRemoval(cb func(context.Context, Endpoint)) {
f.onRemoval = cb
}
// Initialize initializes the FTDI provider
func (f *FTDIProvider) Initialize() error {
// Check platform
if goRuntime.GOOS != "windows" {
log.Error().Str("file", "FTDIProvider").Str("platform", goRuntime.GOOS).Msg("FTDI provider not compatible with your platform")
return fmt.Errorf("the FTDI provider is not compatible with your platform yet (%s)", goRuntime.GOOS)
}
log.Trace().Str("file", "FTDIProvider").Msg("FTDI provider initialized")
return nil
}
// Create creates a new endpoint, based on the endpoint information (manually created)
func (f *FTDIProvider) Create(ctx context.Context, endpointInfo EndpointInfo) (EndpointInfo, error) {
return EndpointInfo{}, nil
}
// Remove removes an existing endpoint (manually created)
func (f *FTDIProvider) Remove(ctx context.Context, endpoint Endpoint) error {
return nil
}
// Start starts the provider and search for endpoints
func (f *FTDIProvider) Start(ctx context.Context) error {
f.wg.Add(1)
go func() {
ticker := time.NewTicker(f.scanEvery)
defer ticker.Stop()
defer f.wg.Done()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Scan the endpoints
err := f.scanEndpoints(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIProvider").Msg("unable to scan FTDI endpoints")
}
}
}
}()
return nil
}
// GetName returns the name of the driver
func (f *FTDIProvider) GetName() string {
return "FTDI"
}
// scanEndpoints scans the FTDI endpoints
func (f *FTDIProvider) scanEndpoints(ctx context.Context) error {
log.Trace().Str("file", "FTDIProvider").Msg("FTDI scan triggered")
count := int(C.get_endpoints_number())
log.Info().Int("number", count).Msg("number of FTDI devices connected")
// Allocating C array
size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIEndpointC{}))
devicesPtr := C.malloc(size)
defer C.free(devicesPtr)
devices := (*[1 << 20]C.FTDIEndpointC)(devicesPtr)[:count:count]
C.get_ftdi_devices((*C.FTDIEndpointC)(devicesPtr), C.int(count))
currentMap := make(map[string]EndpointInfo)
for i := 0; i < count; i++ {
d := devices[i]
sn := C.GoString(d.serialNumber)
desc := C.GoString(d.description)
// isOpen := d.isOpen != 0
currentMap[sn] = EndpointInfo{
SerialNumber: sn,
Name: desc,
// IsOpen: isOpen,
ProtocolName: "FTDI",
}
// Free C memory
C.free_ftdi_device(&d)
}
log.Info().Any("endpoints", currentMap).Msg("available FTDI endpoints")
// Detect arrivals
for sn, endpointData := range currentMap {
// If the scanned endpoint isn't in the detected list, create it
if _, known := f.detected[sn]; !known {
endpoint := NewFTDIEndpoint(endpointData)
if f.onArrival != nil {
f.onArrival(ctx, endpoint)
}
}
}
// Detect removals
for detectedSN, detectedEndpoint := range f.detected {
if _, still := currentMap[detectedSN]; !still {
// Delete it from the detected list
delete(f.detected, detectedSN)
// Execute the removal callback
if f.onRemoval != nil {
f.onRemoval(ctx, detectedEndpoint)
}
}
}
return nil
}
// WaitStop stops the provider
func (f *FTDIProvider) WaitStop() error {
log.Trace().Str("file", "FTDIProvider").Msg("stopping the FTDI provider...")
// Wait for goroutines to stop
f.wg.Wait()
log.Trace().Str("file", "FTDIProvider").Msg("FTDI provider stopped")
return nil
}

129
hardware/MIDIEndpoint.go Normal file
View File

@@ -0,0 +1,129 @@
package hardware
import (
"context"
"fmt"
"sync"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
"gitlab.com/gomidi/midi"
)
// MIDIDevice represents the device to control
type MIDIDevice struct {
ID string // Device ID
Mapping MappingInfo // Device mapping configuration
}
// ------------------- //
// MIDIEndpoint contains the data of a MIDI endpoint
type MIDIEndpoint struct {
wg sync.WaitGroup
inputPorts []midi.In
outputsPorts []midi.Out
info EndpointInfo // The endpoint info
settings map[string]interface{} // The settings of the endpoint
devices []MIDIDevice // All the MIDI devices that the endpoint can handle
}
// NewMIDIEndpoint creates a new MIDI endpoint
func NewMIDIEndpoint(endpointData EndpointInfo, inputs []midi.In, outputs []midi.Out) *MIDIEndpoint {
log.Trace().Str("file", "MIDIEndpoint").Str("name", endpointData.Name).Str("s/n", endpointData.SerialNumber).Msg("MIDI endpoint created")
return &MIDIEndpoint{
info: endpointData,
inputPorts: inputs,
outputsPorts: outputs,
settings: endpointData.Settings,
}
}
// Connect connects the MIDI endpoint
func (p *MIDIEndpoint) Connect(ctx context.Context) error {
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusConnecting)
// Open input ports
for _, port := range p.inputPorts {
err := port.Open()
if err != nil {
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDisconnected)
return fmt.Errorf("unable to open the MIDI IN port: %w", err)
}
port.SetListener(func(msg []byte, delta int64) {
// Emit the event to the front
runtime.EventsEmit(ctx, string(EndpointEventEmitted), p.info.SerialNumber, msg)
log.Debug().Str("message", string(msg)).Int64("delta", delta).Msg("message received")
})
log.Info().Str("name", port.String()).Msg("port open successfully")
}
p.wg.Add(1)
go func() {
defer p.wg.Done()
<-ctx.Done()
_ = p.Disconnect(ctx)
}()
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDeactivated)
return nil
}
// Disconnect disconnects the MIDI endpoint
func (p *MIDIEndpoint) Disconnect(ctx context.Context) error {
// Close all inputs ports
for _, port := range p.inputPorts {
err := port.Close()
if err != nil {
return fmt.Errorf("unable to close the MIDI IN port: %w", err)
}
}
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDisconnected)
return nil
}
// Activate activates the MIDI endpoint
func (p *MIDIEndpoint) Activate(ctx context.Context) error {
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusActivated)
return nil
}
// Deactivate deactivates the MIDI endpoint
func (p *MIDIEndpoint) Deactivate(ctx context.Context) error {
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDeactivated)
return nil
}
// SetSettings sets a specific setting for this endpoint
func (p *MIDIEndpoint) SetSettings(ctx context.Context, settings map[string]any) error {
p.settings = settings
return nil
}
// SetDeviceProperty - not implemented for this kind of endpoint
func (p *MIDIEndpoint) SetDeviceProperty(context.Context, uint32, byte) error {
return nil
}
// GetSettings gets the endpoint settings
func (p *MIDIEndpoint) GetSettings() map[string]interface{} {
return p.settings
}
// GetInfo gets the endpoint information
func (p *MIDIEndpoint) GetInfo() EndpointInfo {
return p.info
}
// WaitStop wait about the endpoint to close
func (p *MIDIEndpoint) WaitStop() error {
log.Info().Str("file", "MIDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("waiting for MIDI endpoint to close...")
p.wg.Wait()
log.Info().Str("file", "MIDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("MIDI endpoint closed!")
return nil
}

View File

@@ -1,129 +0,0 @@
package hardware
import (
"context"
"fmt"
"sync"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
"gitlab.com/gomidi/midi"
)
// MIDIDevice represents the device to control
type MIDIDevice struct {
ID string // Device ID
Mapping MappingInfo // Device mapping configuration
}
// ------------------- //
// MIDIPeripheral contains the data of a MIDI peripheral
type MIDIPeripheral struct {
wg sync.WaitGroup
inputPorts []midi.In
outputsPorts []midi.Out
info PeripheralInfo // The peripheral info
settings map[string]interface{} // The settings of the peripheral
devices []MIDIDevice // All the MIDI devices that the peripheral can handle
}
// NewMIDIPeripheral creates a new MIDI peripheral
func NewMIDIPeripheral(peripheralData PeripheralInfo, inputs []midi.In, outputs []midi.Out) *MIDIPeripheral {
log.Trace().Str("file", "MIDIPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("MIDI peripheral created")
return &MIDIPeripheral{
info: peripheralData,
inputPorts: inputs,
outputsPorts: outputs,
settings: peripheralData.Settings,
}
}
// Connect connects the MIDI peripheral
func (p *MIDIPeripheral) Connect(ctx context.Context) error {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting)
// Open input ports
for _, port := range p.inputPorts {
err := port.Open()
if err != nil {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
return fmt.Errorf("unable to open the MIDI IN port: %w", err)
}
port.SetListener(func(msg []byte, delta int64) {
// Emit the event to the front
runtime.EventsEmit(ctx, string(PeripheralEventEmitted), p.info.SerialNumber, msg)
log.Debug().Str("message", string(msg)).Int64("delta", delta).Msg("message received")
})
log.Info().Str("name", port.String()).Msg("port open successfully")
}
p.wg.Add(1)
go func() {
defer p.wg.Done()
<-ctx.Done()
_ = p.Disconnect(ctx)
}()
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
return nil
}
// Disconnect disconnects the MIDI peripheral
func (p *MIDIPeripheral) Disconnect(ctx context.Context) error {
// Close all inputs ports
for _, port := range p.inputPorts {
err := port.Close()
if err != nil {
return fmt.Errorf("unable to close the MIDI IN port: %w", err)
}
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
return nil
}
// Activate activates the MIDI peripheral
func (p *MIDIPeripheral) Activate(ctx context.Context) error {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated)
return nil
}
// Deactivate deactivates the MIDI peripheral
func (p *MIDIPeripheral) Deactivate(ctx context.Context) error {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
return nil
}
// SetSettings sets a specific setting for this peripheral
func (p *MIDIPeripheral) SetSettings(ctx context.Context, settings map[string]any) error {
p.settings = settings
return nil
}
// SetDeviceProperty - not implemented for this kind of peripheral
func (p *MIDIPeripheral) SetDeviceProperty(context.Context, uint32, byte) error {
return nil
}
// GetSettings gets the peripheral settings
func (p *MIDIPeripheral) GetSettings() map[string]interface{} {
return p.settings
}
// GetInfo gets the peripheral information
func (p *MIDIPeripheral) GetInfo() PeripheralInfo {
return p.info
}
// WaitStop wait about the peripheral to close
func (p *MIDIPeripheral) WaitStop() error {
log.Info().Str("file", "MIDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for MIDI peripheral to close...")
p.wg.Wait()
log.Info().Str("file", "MIDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("MIDI peripheral closed!")
return nil
}

View File

@@ -13,56 +13,56 @@ import (
"gitlab.com/gomidi/rtmididrv"
)
// MIDIFinder represents how the protocol is defined
type MIDIFinder struct {
// MIDIProvider represents how the protocol is defined
type MIDIProvider struct {
wg sync.WaitGroup
mu sync.Mutex
detected map[string]*MIDIPeripheral // Detected peripherals
detected map[string]*MIDIEndpoint // Detected endpoints
scanEvery time.Duration // Scans peripherals periodically
scanEvery time.Duration // Scans endpoints periodically
onArrival func(context.Context, Peripheral) // When a peripheral arrives
onRemoval func(context.Context, Peripheral) // When a peripheral goes away
onArrival func(context.Context, Endpoint) // When a endpoint arrives
onRemoval func(context.Context, Endpoint) // When a endpoint goes away
}
// NewMIDIFinder creates a new MIDI finder
func NewMIDIFinder(scanEvery time.Duration) *MIDIFinder {
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder created")
return &MIDIFinder{
// NewMIDIProvider creates a new MIDI provider
func NewMIDIProvider(scanEvery time.Duration) *MIDIProvider {
log.Trace().Str("file", "MIDIProvider").Msg("MIDI provider created")
return &MIDIProvider{
scanEvery: scanEvery,
detected: make(map[string]*MIDIPeripheral),
detected: make(map[string]*MIDIEndpoint),
}
}
// OnArrival is the callback function when a new peripheral arrives
func (f *MIDIFinder) OnArrival(cb func(context.Context, Peripheral)) {
// OnArrival is the callback function when a new endpoint arrives
func (f *MIDIProvider) OnArrival(cb func(context.Context, Endpoint)) {
f.onArrival = cb
}
// OnRemoval i the callback when a peripheral goes away
func (f *MIDIFinder) OnRemoval(cb func(context.Context, Peripheral)) {
// OnRemoval i the callback when a endpoint goes away
func (f *MIDIProvider) OnRemoval(cb func(context.Context, Endpoint)) {
f.onRemoval = cb
}
// Initialize initializes the MIDI driver
func (f *MIDIFinder) Initialize() error {
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder initialized")
func (f *MIDIProvider) Initialize() error {
log.Trace().Str("file", "MIDIProvider").Msg("MIDI provider initialized")
return nil
}
// Create creates a new peripheral, based on the peripheral information (manually created)
func (f *MIDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) {
return PeripheralInfo{}, nil
// Create creates a new endpoint, based on the endpoint information (manually created)
func (f *MIDIProvider) Create(ctx context.Context, endpointInfo EndpointInfo) (EndpointInfo, error) {
return EndpointInfo{}, nil
}
// Remove removes an existing peripheral (manually created)
func (f *MIDIFinder) Remove(ctx context.Context, peripheral Peripheral) error {
// Remove removes an existing endpoint (manually created)
func (f *MIDIProvider) Remove(ctx context.Context, endpoint Endpoint) error {
return nil
}
// Start starts the finder and search for peripherals
func (f *MIDIFinder) Start(ctx context.Context) error {
// Start starts the provider and search for endpoints
func (f *MIDIProvider) Start(ctx context.Context) error {
f.wg.Add(1)
go func() {
ticker := time.NewTicker(f.scanEvery)
@@ -74,10 +74,10 @@ func (f *MIDIFinder) Start(ctx context.Context) error {
case <-ctx.Done():
return
case <-ticker.C:
// Scan the peripherals
err := f.scanPeripherals(ctx)
// Scan the endpoints
err := f.scanEndpoints(ctx)
if err != nil {
log.Err(err).Str("file", "MIDIFinder").Msg("unable to scan MIDI peripherals")
log.Err(err).Str("file", "MIDIProvider").Msg("unable to scan MIDI endpoints")
}
}
}
@@ -85,19 +85,19 @@ func (f *MIDIFinder) Start(ctx context.Context) error {
return nil
}
// WaitStop stops the finder
func (f *MIDIFinder) WaitStop() error {
log.Trace().Str("file", "MIDIFinder").Msg("stopping the MIDI finder...")
// WaitStop stops the provider
func (f *MIDIProvider) WaitStop() error {
log.Trace().Str("file", "MIDIProvider").Msg("stopping the MIDI provider...")
// Wait for goroutines to stop
f.wg.Wait()
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder stopped")
log.Trace().Str("file", "MIDIProvider").Msg("MIDI provider stopped")
return nil
}
// GetName returns the name of the driver
func (f *MIDIFinder) GetName() string {
func (f *MIDIProvider) GetName() string {
return "MIDI"
}
@@ -122,9 +122,9 @@ func splitStringAndNumber(input string) (string, int, error) {
return "", 0, fmt.Errorf("no number found at the end of the string")
}
// scanPeripherals scans the MIDI peripherals
func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
currentMap := make(map[string]*MIDIPeripheral)
// scanEndpoints scans the MIDI endpoints
func (f *MIDIProvider) scanEndpoints(ctx context.Context) error {
currentMap := make(map[string]*MIDIEndpoint)
drv, err := rtmididrv.New()
if err != nil {
@@ -148,8 +148,8 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
sn := strings.ReplaceAll(strings.ToLower(baseName), " ", "_")
if _, ok := currentMap[sn]; !ok {
currentMap[sn] = &MIDIPeripheral{
info: PeripheralInfo{
currentMap[sn] = &MIDIEndpoint{
info: EndpointInfo{
Name: baseName,
SerialNumber: sn,
ProtocolName: "MIDI",
@@ -158,7 +158,7 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
}
currentMap[sn].inputPorts = append(currentMap[sn].inputPorts, port)
log.Info().Any("peripherals", currentMap).Msg("available MIDI IN ports")
log.Info().Any("endpoints", currentMap).Msg("available MIDI IN ports")
}
// Get MIDI OUTPUT ports
@@ -177,8 +177,8 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
sn := strings.ReplaceAll(strings.ToLower(baseName), " ", "_")
if _, ok := currentMap[sn]; !ok {
currentMap[sn] = &MIDIPeripheral{
info: PeripheralInfo{
currentMap[sn] = &MIDIEndpoint{
info: EndpointInfo{
Name: baseName,
SerialNumber: sn,
ProtocolName: "MIDI",
@@ -187,18 +187,18 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
}
currentMap[sn].outputsPorts = append(currentMap[sn].outputsPorts, port)
log.Info().Any("peripherals", currentMap).Msg("available MIDI OUT ports")
log.Info().Any("endpoints", currentMap).Msg("available MIDI OUT ports")
}
log.Debug().Any("value", currentMap).Msg("MIDI peripherals map")
log.Debug().Any("value", currentMap).Msg("MIDI endpoints map")
// Detect arrivals
for sn, discovery := range currentMap {
if _, known := f.detected[sn]; !known {
peripheral := NewMIDIPeripheral(discovery.info, discovery.inputPorts, discovery.outputsPorts)
endpoint := NewMIDIEndpoint(discovery.info, discovery.inputPorts, discovery.outputsPorts)
f.detected[sn] = peripheral
f.detected[sn] = endpoint
if f.onArrival != nil {
f.onArrival(ctx, discovery)
@@ -207,14 +207,14 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
}
// Detect removals
for sn, oldPeripheral := range f.detected {
for sn, oldEndpoint := range f.detected {
if _, still := currentMap[sn]; !still {
log.Info().Str("sn", sn).Str("name", oldPeripheral.GetInfo().Name).Msg("[MIDI] peripheral removed")
log.Info().Str("sn", sn).Str("name", oldEndpoint.GetInfo().Name).Msg("[MIDI] endpoint removed")
// Execute the removal callback
if f.onRemoval != nil {
f.onRemoval(ctx, oldPeripheral)
f.onRemoval(ctx, oldEndpoint)
}
// Delete it from the detected list

View File

@@ -22,38 +22,38 @@ type OS2LMessage struct {
Param float64 `json:"param"`
}
// OS2LPeripheral contains the data of an OS2L peripheral
type OS2LPeripheral struct {
// OS2LEndpoint contains the data of an OS2L endpoint
type OS2LEndpoint struct {
wg sync.WaitGroup
info PeripheralInfo // The basic info for this peripheral
info EndpointInfo // The basic info for this endpoint
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
listenerCancel context.CancelFunc // Call this function to cancel the endpoint 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,
// NewOS2LEndpoint creates a new OS2L endpoint
func NewOS2LEndpoint(endpointData EndpointInfo) (*OS2LEndpoint, error) {
endpoint := &OS2LEndpoint{
info: endpointData,
listener: nil,
eventCallback: nil,
}
log.Trace().Str("file", "OS2LPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("OS2L peripheral created")
return peripheral, peripheral.loadSettings(peripheralData.Settings)
log.Trace().Str("file", "OS2LEndpoint").Str("name", endpointData.Name).Str("s/n", endpointData.SerialNumber).Msg("OS2L endpoint created")
return endpoint, endpoint.loadSettings(endpointData.Settings)
}
// SetEventCallback sets the callback for returning events
func (p *OS2LPeripheral) SetEventCallback(eventCallback func(any)) {
func (p *OS2LEndpoint) 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)
// Connect connects the OS2L endpoint
func (p *OS2LEndpoint) Connect(ctx context.Context) error {
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusConnecting)
var err error
addr := net.TCPAddr{Port: p.serverPort, IP: net.ParseIP(p.serverIP)}
@@ -61,32 +61,32 @@ func (p *OS2LPeripheral) Connect(ctx context.Context) error {
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)
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDisconnected)
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")
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDeactivated)
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint connected")
return nil
}
// handleMessage handles an OS2L message
func (p *OS2LPeripheral) handleMessage(raw []byte) error {
func (p *OS2LEndpoint) 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
// Return the event to the provider
if p.eventCallback != nil {
go p.eventCallback(message)
}
return nil
}
// loadSettings check and load the settings in the peripheral
func (p *OS2LPeripheral) loadSettings(settings map[string]any) error {
// loadSettings check and load the settings in the endpoint
func (p *OS2LEndpoint) loadSettings(settings map[string]any) error {
// Check if the IP exists
serverIP, found := settings["os2lIp"]
if !found {
@@ -118,22 +118,22 @@ func (p *OS2LPeripheral) loadSettings(settings map[string]any) error {
return nil
}
// Disconnect disconnects the MIDI peripheral
func (p *OS2LPeripheral) Disconnect(ctx context.Context) error {
// Disconnect disconnects the MIDI endpoint
func (p *OS2LEndpoint) 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)
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDisconnected)
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected")
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint disconnected")
return nil
}
// Activate activates the OS2L peripheral
func (p *OS2LPeripheral) Activate(ctx context.Context) error {
// Activate activates the OS2L endpoint
func (p *OS2LEndpoint) Activate(ctx context.Context) error {
// Create a derived context to handle deactivation
var listenerCtx context.Context
listenerCtx, p.listenerCancel = context.WithCancel(ctx)
@@ -160,7 +160,7 @@ func (p *OS2LPeripheral) Activate(ctx context.Context) error {
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")
log.Err(err).Str("file", "OS2LEndpoint").Msg("unable to accept the connection")
continue
}
@@ -198,15 +198,15 @@ func (p *OS2LPeripheral) Activate(ctx context.Context) error {
}
}
}()
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated)
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusActivated)
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated")
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint activated")
return nil
}
// Deactivate deactivates the OS2L peripheral
func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
// Deactivate deactivates the OS2L endpoint
func (p *OS2LEndpoint) Deactivate(ctx context.Context) error {
if p.listener == nil {
return fmt.Errorf("the listener isn't defined")
}
@@ -216,19 +216,19 @@ func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
p.listenerCancel()
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), p.GetInfo(), EndpointStatusDeactivated)
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated")
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint deactivated")
return nil
}
// SetSettings sets a specific setting for this peripheral
func (p *OS2LPeripheral) SetSettings(ctx context.Context, settings map[string]any) error {
// SetSettings sets a specific setting for this endpoint
func (p *OS2LEndpoint) 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
// Reconnect the endpoint
p.wg.Add(1)
go func() {
defer p.wg.Done()
@@ -255,32 +255,32 @@ func (p *OS2LPeripheral) SetSettings(ctx context.Context, settings map[string]an
return
}
}()
log.Info().Str("sn", p.GetInfo().SerialNumber).Msg("peripheral settings set")
log.Info().Str("sn", p.GetInfo().SerialNumber).Msg("endpoint settings set")
return nil
}
// SetDeviceProperty - not implemented for this kind of peripheral
func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, byte) error {
// SetDeviceProperty - not implemented for this kind of endpoint
func (p *OS2LEndpoint) SetDeviceProperty(context.Context, uint32, byte) error {
return nil
}
// GetSettings gets the peripheral settings
func (p *OS2LPeripheral) GetSettings() map[string]any {
// GetSettings gets the endpoint settings
func (p *OS2LEndpoint) GetSettings() map[string]any {
return map[string]any{
"os2lIp": p.serverIP,
"os2lPort": p.serverPort,
}
}
// GetInfo gets the peripheral information
func (p *OS2LPeripheral) GetInfo() PeripheralInfo {
// GetInfo gets the endpoint information
func (p *OS2LEndpoint) GetInfo() EndpointInfo {
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...")
// WaitStop stops the endpoint
func (p *OS2LEndpoint) WaitStop() error {
log.Info().Str("file", "OS2LEndpoint").Str("s/n", p.info.SerialNumber).Msg("waiting for OS2L endpoint to close...")
p.wg.Wait()
log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("OS2L peripheral closed!")
log.Info().Str("file", "OS2LEndpoint").Str("s/n", p.info.SerialNumber).Msg("OS2L endpoint closed!")
return nil
}

View File

@@ -1,104 +0,0 @@
package hardware
import (
"context"
"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 {
wg sync.WaitGroup
mu sync.Mutex
detected map[string]*OS2LPeripheral // The list of saved peripherals
onArrival func(context.Context, Peripheral) // When a peripheral arrives
onRemoval func(context.Context, Peripheral) // 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{
detected: make(map[string]*OS2LPeripheral),
}
}
// Initialize initializes the finder
func (f *OS2LFinder) Initialize() error {
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder initialized")
return nil
}
// OnArrival is the callback function when a new peripheral arrives
func (f *OS2LFinder) OnArrival(cb func(context.Context, Peripheral)) {
f.onArrival = cb
}
// OnRemoval if the callback when a peripheral goes away
func (f *OS2LFinder) OnRemoval(cb func(context.Context, Peripheral)) {
f.onRemoval = cb
}
// Create creates a new peripheral, based on the peripheral information (manually created)
func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) {
// If the SerialNumber is empty, generate another one
if peripheralInfo.SerialNumber == "" {
peripheralInfo.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
}
// Create a new OS2L peripheral
peripheral, err := NewOS2LPeripheral(peripheralInfo)
if err != nil {
return PeripheralInfo{}, fmt.Errorf("unable to create the OS2L peripheral: %w", err)
}
// Set the event callback
peripheral.SetEventCallback(func(event any) {
runtime.EventsEmit(ctx, string(PeripheralEventEmitted), peripheralInfo.SerialNumber, event)
})
f.detected[peripheralInfo.SerialNumber] = peripheral
if f.onArrival != nil {
f.onArrival(ctx, peripheral) // Ask to register the peripheral in the project
}
return peripheralInfo, err
}
// Remove removes an existing peripheral (manually created)
func (f *OS2LFinder) Remove(ctx context.Context, peripheral Peripheral) error {
if f.onRemoval != nil {
f.onRemoval(ctx, peripheral)
}
delete(f.detected, peripheral.GetInfo().SerialNumber)
return nil
}
// GetName returns the name of the driver
func (f *OS2LFinder) GetName() string {
return "OS2L"
}
// Start starts the finder
func (f *OS2LFinder) Start(ctx context.Context) error {
// No peripherals to scan here
return nil
}
// WaitStop stops the finder
func (f *OS2LFinder) WaitStop() error {
log.Trace().Str("file", "OS2LFinder").Msg("stopping the OS2L finder...")
// Waiting internal tasks
f.wg.Wait()
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder stopped")
return nil
}

104
hardware/OS2LProvider.go Normal file
View File

@@ -0,0 +1,104 @@
package hardware
import (
"context"
"fmt"
"math/rand"
"strings"
"sync"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// OS2LProvider represents how the protocol is defined
type OS2LProvider struct {
wg sync.WaitGroup
mu sync.Mutex
detected map[string]*OS2LEndpoint // The list of saved endpoints
onArrival func(context.Context, Endpoint) // When a endpoint arrives
onRemoval func(context.Context, Endpoint) // When a endpoint goes away
}
// NewOS2LProvider creates a new OS2L provider
func NewOS2LProvider() *OS2LProvider {
log.Trace().Str("file", "OS2LProvider").Msg("OS2L provider created")
return &OS2LProvider{
detected: make(map[string]*OS2LEndpoint),
}
}
// Initialize initializes the provider
func (f *OS2LProvider) Initialize() error {
log.Trace().Str("file", "OS2LProvider").Msg("OS2L provider initialized")
return nil
}
// OnArrival is the callback function when a new endpoint arrives
func (f *OS2LProvider) OnArrival(cb func(context.Context, Endpoint)) {
f.onArrival = cb
}
// OnRemoval if the callback when a endpoint goes away
func (f *OS2LProvider) OnRemoval(cb func(context.Context, Endpoint)) {
f.onRemoval = cb
}
// Create creates a new endpoint, based on the endpoint information (manually created)
func (f *OS2LProvider) Create(ctx context.Context, endpointInfo EndpointInfo) (EndpointInfo, error) {
// If the SerialNumber is empty, generate another one
if endpointInfo.SerialNumber == "" {
endpointInfo.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
}
// Create a new OS2L endpoint
endpoint, err := NewOS2LEndpoint(endpointInfo)
if err != nil {
return EndpointInfo{}, fmt.Errorf("unable to create the OS2L endpoint: %w", err)
}
// Set the event callback
endpoint.SetEventCallback(func(event any) {
runtime.EventsEmit(ctx, string(EndpointEventEmitted), endpointInfo.SerialNumber, event)
})
f.detected[endpointInfo.SerialNumber] = endpoint
if f.onArrival != nil {
f.onArrival(ctx, endpoint) // Ask to register the endpoint in the project
}
return endpointInfo, err
}
// Remove removes an existing endpoint (manually created)
func (f *OS2LProvider) Remove(ctx context.Context, endpoint Endpoint) error {
if f.onRemoval != nil {
f.onRemoval(ctx, endpoint)
}
delete(f.detected, endpoint.GetInfo().SerialNumber)
return nil
}
// GetName returns the name of the driver
func (f *OS2LProvider) GetName() string {
return "OS2L"
}
// Start starts the provider
func (f *OS2LProvider) Start(ctx context.Context) error {
// No endpoints to scan here
return nil
}
// WaitStop stops the provider
func (f *OS2LProvider) WaitStop() error {
log.Trace().Str("file", "OS2LProvider").Msg("stopping the OS2L provider...")
// Waiting internal tasks
f.wg.Wait()
log.Trace().Str("file", "OS2LProvider").Msg("OS2L provider stopped")
return nil
}

View File

@@ -8,11 +8,11 @@ typedef struct {
char* serialNumber;
char* description;
int isOpen;
} FTDIPeripheralC;
} FTDIEndpointC;
int get_peripherals_number();
void get_ftdi_devices(FTDIPeripheralC* devices, int count);
void free_ftdi_device(FTDIPeripheralC* device);
int get_endpoints_number();
void get_ftdi_devices(FTDIEndpointC* devices, int count);
void free_ftdi_device(FTDIEndpointC* device);
#ifdef __cplusplus
}

View File

@@ -5,7 +5,7 @@
#include <algorithm>
#include <iostream>
int getFTDIPeripheralsNumber() {
int getFTDIEndpointsNumber() {
DWORD numDevs = 0;
if (FT_CreateDeviceInfoList(&numDevs) != FT_OK) {
std::cerr << "Unable to get FTDI devices: create list error\n";
@@ -13,7 +13,7 @@ int getFTDIPeripheralsNumber() {
return numDevs;
}
std::vector<FTDIPeripheral> scanFTDIPeripherals() {
std::vector<FTDIEndpoint> scanFTDIEndpoints() {
DWORD numDevs = 0;
if (FT_CreateDeviceInfoList(&numDevs) != FT_OK) {
std::cerr << "Unable to get FTDI devices: create list error\n";
@@ -30,12 +30,12 @@ std::vector<FTDIPeripheral> scanFTDIPeripherals() {
return {};
}
std::vector<FTDIPeripheral> peripherals;
peripherals.reserve(numDevs);
std::vector<FTDIEndpoint> endpoints;
endpoints.reserve(numDevs);
for (const auto& info : devInfo) {
if (info.SerialNumber[0] != '\0') {
peripherals.push_back({
endpoints.push_back({
info.SerialNumber,
info.Description,
static_cast<bool>(info.Flags & FT_FLAGS_OPENED)
@@ -43,21 +43,21 @@ std::vector<FTDIPeripheral> scanFTDIPeripherals() {
}
}
return peripherals;
return endpoints;
}
extern "C" {
int get_peripherals_number() {
return getFTDIPeripheralsNumber();
int get_endpoints_number() {
return getFTDIEndpointsNumber();
}
void get_ftdi_devices(FTDIPeripheralC* devices, int count) {
void get_ftdi_devices(FTDIEndpointC* devices, int count) {
if (!devices || count <= 0) {
return;
}
auto list = scanFTDIPeripherals();
auto list = scanFTDIEndpoints();
int n = std::min(count, static_cast<int>(list.size()));
for (int i = 0; i < n; ++i) {
@@ -74,7 +74,7 @@ void get_ftdi_devices(FTDIPeripheralC* devices, int count) {
}
}
void free_ftdi_device(FTDIPeripheralC* device) {
void free_ftdi_device(FTDIEndpointC* device) {
if (!device) return;
if (device->serialNumber) {

View File

@@ -4,11 +4,11 @@
#include <string>
#include <vector>
struct FTDIPeripheral {
struct FTDIEndpoint {
std::string serialNumber;
std::string description;
bool isOpen;
};
int getFTDIPeripheralsNumber();
std::vector<FTDIPeripheral> scanFTDIPeripherals();
int getFTDIEndpointsNumber();
std::vector<FTDIEndpoint> scanFTDIEndpoints();

View File

@@ -52,7 +52,7 @@ public:
void resetChannels();
private:
FT_STATUS ftStatus; // FTDI peripheral status
FT_STATUS ftStatus; // FTDI endpoint status
FT_HANDLE ftHandle = nullptr; // FTDI object
std::atomic<uint8_t> dmxData[DMX_CHANNELS + 1]; // For storing dynamically the DMX data
std::atomic<bool> isOutputActivated = false; // Boolean to start/stop the DMX flow

View File

@@ -2,13 +2,13 @@
#include <iostream>
int main(){
int peripheralsNumber = getFTDIPeripheralsNumber();
int endpointsNumber = getFTDIEndpointsNumber();
std::vector<FTDIPeripheral> peripherals = scanFTDIPeripherals();
std::vector<FTDIEndpoint> endpoints = scanFTDIEndpoints();
// for (const auto& peripheral : peripherals) {
// std::cout << peripheral.serialNumber << " (" << peripheral.description << ") -> IS OPEN: " << peripheral.isOpen << std::endl;
// for (const auto& endpoint : endpoints) {
// std::cout << endpoint.serialNumber << " (" << endpoint.description << ") -> IS OPEN: " << endpoint.isOpen << std::endl;
// }
}

View File

@@ -11,33 +11,33 @@ import (
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// PeripheralEvent is trigger by the finders when the scan is complete
type PeripheralEvent string
// EndpointEvent is trigger by the providers when the scan is complete
type EndpointEvent string
// PeripheralStatus is the peripheral status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED)
type PeripheralStatus string
// EndpointStatus is the endpoint status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED)
type EndpointStatus string
const (
// PeripheralArrival is triggerd when a peripheral has been connected to the system
PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL"
// PeripheralRemoval is triggered when a peripheral has been disconnected from the system
PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL"
// PeripheralLoad is triggered when a peripheral is added to the project
PeripheralLoad PeripheralEvent = "PERIPHERAL_LOAD"
// PeripheralUnload is triggered when a peripheral is removed from the project
PeripheralUnload PeripheralEvent = "PERIPHERAL_UNLOAD"
// 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
PeripheralStatusConnecting PeripheralStatus = "PERIPHERAL_CONNECTING"
// PeripheralStatusDeactivated : peripheral is now deactivated
PeripheralStatusDeactivated PeripheralStatus = "PERIPHERAL_DEACTIVATED"
// PeripheralStatusActivated : peripheral is now activated
PeripheralStatusActivated PeripheralStatus = "PERIPHERAL_ACTIVATED"
// EndpointArrival is triggerd when a endpoint has been connected to the system
EndpointArrival EndpointEvent = "PERIPHERAL_ARRIVAL"
// EndpointRemoval is triggered when a endpoint has been disconnected from the system
EndpointRemoval EndpointEvent = "PERIPHERAL_REMOVAL"
// EndpointLoad is triggered when a endpoint is added to the project
EndpointLoad EndpointEvent = "PERIPHERAL_LOAD"
// EndpointUnload is triggered when a endpoint is removed from the project
EndpointUnload EndpointEvent = "PERIPHERAL_UNLOAD"
// EndpointStatusUpdated is triggered when a endpoint status has been updated (disconnected - connecting - deactivated - activated)
EndpointStatusUpdated EndpointEvent = "PERIPHERAL_STATUS"
// EndpointEventEmitted is triggered when a endpoint event is emitted
EndpointEventEmitted EndpointEvent = "PERIPHERAL_EVENT_EMITTED"
// EndpointStatusDisconnected : endpoint is now disconnected
EndpointStatusDisconnected EndpointStatus = "PERIPHERAL_DISCONNECTED"
// EndpointStatusConnecting : endpoint is now connecting
EndpointStatusConnecting EndpointStatus = "PERIPHERAL_CONNECTING"
// EndpointStatusDeactivated : endpoint is now deactivated
EndpointStatusDeactivated EndpointStatus = "PERIPHERAL_DEACTIVATED"
// EndpointStatusActivated : endpoint is now activated
EndpointStatusActivated EndpointStatus = "PERIPHERAL_ACTIVATED"
)
// Manager is the class who manages the hardware
@@ -45,241 +45,241 @@ type Manager struct {
mu sync.Mutex
wg sync.WaitGroup
finders map[string]PeripheralFinder // The map of peripherals finders
DetectedPeripherals map[string]Peripheral // The current list of peripherals
SavedPeripherals map[string]PeripheralInfo // The list of stored peripherals
providers map[string]EndpointProvider // The map of endpoints providers
DetectedEndpoints map[string]Endpoint // The current list of endpoints
SavedEndpoints map[string]EndpointInfo // The list of stored endpoints
}
// NewManager creates a new hardware manager
func NewManager() *Manager {
log.Trace().Str("package", "hardware").Msg("Hardware instance created")
return &Manager{
finders: make(map[string]PeripheralFinder),
DetectedPeripherals: make(map[string]Peripheral, 0),
SavedPeripherals: make(map[string]PeripheralInfo, 0),
providers: make(map[string]EndpointProvider),
DetectedEndpoints: make(map[string]Endpoint, 0),
SavedEndpoints: make(map[string]EndpointInfo, 0),
}
}
// RegisterPeripheral registers a new peripheral
func (h *Manager) RegisterPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) {
// RegisterEndpoint registers a new endpoint
func (h *Manager) RegisterEndpoint(ctx context.Context, endpointInfo EndpointInfo) (string, error) {
h.mu.Lock()
defer h.mu.Unlock()
// Create the peripheral from its finder (if needed)
if finder, found := h.finders[peripheralInfo.ProtocolName]; found {
// Create the endpoint from its provider (if needed)
if provider, found := h.providers[endpointInfo.ProtocolName]; found {
var err error
peripheralInfo, err = finder.Create(ctx, peripheralInfo)
endpointInfo, err = provider.Create(ctx, endpointInfo)
if err != nil {
return "", err
}
}
// Do not save if the peripheral doesn't have a S/N
if peripheralInfo.SerialNumber == "" {
return "", fmt.Errorf("serial number is empty for this peripheral")
// Do not save if the endpoint doesn't have a S/N
if endpointInfo.SerialNumber == "" {
return "", fmt.Errorf("serial number is empty for this endpoint")
}
h.SavedPeripherals[peripheralInfo.SerialNumber] = peripheralInfo
h.SavedEndpoints[endpointInfo.SerialNumber] = endpointInfo
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralInfo, PeripheralStatusDisconnected)
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), endpointInfo, EndpointStatusDisconnected)
// If already detected, connect it
if peripheral, ok := h.DetectedPeripherals[peripheralInfo.SerialNumber]; ok {
if endpoint, ok := h.DetectedEndpoints[endpointInfo.SerialNumber]; ok {
h.wg.Add(1)
go func() {
defer h.wg.Done()
err := peripheral.Connect(ctx)
err := endpoint.Connect(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo.SerialNumber).Msg("unable to connect the peripheral")
log.Err(err).Str("file", "FTDIProvider").Str("endpointSN", endpointInfo.SerialNumber).Msg("unable to connect the endpoint")
return
}
// Peripheral connected, activate it
err = peripheral.Activate(ctx)
// Endpoint connected, activate it
err = endpoint.Activate(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo.SerialNumber).Msg("unable to activate the FTDI peripheral")
log.Err(err).Str("file", "FTDIProvider").Str("endpointSN", endpointInfo.SerialNumber).Msg("unable to activate the FTDI endpoint")
return
}
}()
}
// Emits the event in the hardware
runtime.EventsEmit(ctx, string(PeripheralLoad), peripheralInfo)
runtime.EventsEmit(ctx, string(EndpointLoad), endpointInfo)
return peripheralInfo.SerialNumber, nil
return endpointInfo.SerialNumber, nil
}
// UnregisterPeripheral unregisters an existing peripheral
func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) error {
// UnregisterEndpoint unregisters an existing endpoint
func (h *Manager) UnregisterEndpoint(ctx context.Context, endpointInfo EndpointInfo) error {
h.mu.Lock()
defer h.mu.Unlock()
if peripheral, detected := h.DetectedPeripherals[peripheralInfo.SerialNumber]; detected {
// Deactivating peripheral
err := peripheral.Deactivate(ctx)
if endpoint, detected := h.DetectedEndpoints[endpointInfo.SerialNumber]; detected {
// Deactivating endpoint
err := endpoint.Deactivate(ctx)
if err != nil {
log.Err(err).Str("sn", peripheralInfo.SerialNumber).Msg("unable to deactivate the peripheral")
log.Err(err).Str("sn", endpointInfo.SerialNumber).Msg("unable to deactivate the endpoint")
return nil
}
// Disconnecting peripheral
err = peripheral.Disconnect(ctx)
// Disconnecting endpoint
err = endpoint.Disconnect(ctx)
if err != nil {
log.Err(err).Str("sn", peripheralInfo.SerialNumber).Msg("unable to disconnect the peripheral")
log.Err(err).Str("sn", endpointInfo.SerialNumber).Msg("unable to disconnect the endpoint")
return nil
}
// Remove the peripheral from its finder (if needed)
if finder, found := h.finders[peripheralInfo.ProtocolName]; found {
err = finder.Remove(ctx, peripheral)
// Remove the endpoint from its provider (if needed)
if provider, found := h.providers[endpointInfo.ProtocolName]; found {
err = provider.Remove(ctx, endpoint)
if err != nil {
return err
}
}
}
delete(h.SavedPeripherals, peripheralInfo.SerialNumber)
runtime.EventsEmit(ctx, string(PeripheralUnload), peripheralInfo)
delete(h.SavedEndpoints, endpointInfo.SerialNumber)
runtime.EventsEmit(ctx, string(EndpointUnload), endpointInfo)
return nil
}
// GetPeripheralSettings gets the peripheral settings
func (h *Manager) GetPeripheralSettings(peripheralSN string) (map[string]any, error) {
// Return the specified peripheral
peripheral, found := h.DetectedPeripherals[peripheralSN]
// GetEndpointSettings gets the endpoint settings
func (h *Manager) GetEndpointSettings(endpointSN string) (map[string]any, error) {
// Return the specified endpoint
endpoint, found := h.DetectedEndpoints[endpointSN]
if !found {
// Peripheral not detected, return the last settings saved
if savedPeripheral, isFound := h.SavedPeripherals[peripheralSN]; isFound {
return savedPeripheral.Settings, nil
// Endpoint not detected, return the last settings saved
if savedEndpoint, isFound := h.SavedEndpoints[endpointSN]; isFound {
return savedEndpoint.Settings, nil
}
return nil, fmt.Errorf("unable to found the peripheral")
return nil, fmt.Errorf("unable to found the endpoint")
}
return peripheral.GetSettings(), nil
return endpoint.GetSettings(), nil
}
// SetPeripheralSettings sets the peripheral settings
func (h *Manager) SetPeripheralSettings(ctx context.Context, peripheralSN string, settings map[string]any) error {
peripheral, found := h.DetectedPeripherals[peripheralSN]
// SetEndpointSettings sets the endpoint settings
func (h *Manager) SetEndpointSettings(ctx context.Context, endpointSN string, settings map[string]any) error {
endpoint, found := h.DetectedEndpoints[endpointSN]
if !found {
return fmt.Errorf("unable to found the FTDI peripheral")
return fmt.Errorf("unable to found the FTDI endpoint")
}
return peripheral.SetSettings(ctx, settings)
return endpoint.SetSettings(ctx, settings)
}
// Start starts to find new peripheral events
// Start starts to find new endpoint events
func (h *Manager) Start(ctx context.Context) error {
// Register all the finders to use as hardware scanners
h.RegisterFinder(NewFTDIFinder(3 * time.Second))
h.RegisterFinder(NewOS2LFinder())
h.RegisterFinder(NewMIDIFinder(3 * time.Second))
// Register all the providers to use as hardware scanners
h.RegisterProvider(NewFTDIProvider(3 * time.Second))
h.RegisterProvider(NewOS2LProvider())
h.RegisterProvider(NewMIDIProvider(3 * time.Second))
for finderName, finder := range h.finders {
for providerName, provider := range h.providers {
// Initialize the finder
err := finder.Initialize()
// Initialize the provider
err := provider.Initialize()
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder")
log.Err(err).Str("file", "hardware").Str("providerName", providerName).Msg("unable to initialize provider")
return err
}
// Set callback functions
finder.OnArrival(h.OnPeripheralArrival)
finder.OnRemoval(h.OnPeripheralRemoval)
provider.OnArrival(h.OnEndpointArrival)
provider.OnRemoval(h.OnEndpointRemoval)
// Start the finder
err = finder.Start(ctx)
// Start the provider
err = provider.Start(ctx)
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder")
log.Err(err).Str("file", "hardware").Str("providerName", providerName).Msg("unable to start provider")
return err
}
}
return nil
}
// OnPeripheralArrival is called when a peripheral arrives in the system
func (h *Manager) OnPeripheralArrival(ctx context.Context, peripheral Peripheral) {
// Add the peripheral to the detected hardware
h.DetectedPeripherals[peripheral.GetInfo().SerialNumber] = peripheral
// OnEndpointArrival is called when a endpoint arrives in the system
func (h *Manager) OnEndpointArrival(ctx context.Context, endpoint Endpoint) {
// Add the endpoint to the detected hardware
h.DetectedEndpoints[endpoint.GetInfo().SerialNumber] = endpoint
// If the peripheral is saved in the project, connect it
if _, saved := h.SavedPeripherals[peripheral.GetInfo().SerialNumber]; saved {
// If the endpoint is saved in the project, connect it
if _, saved := h.SavedEndpoints[endpoint.GetInfo().SerialNumber]; saved {
h.wg.Add(1)
go func(p Peripheral) {
go func(p Endpoint) {
defer h.wg.Done()
err := p.Connect(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to connect the FTDI peripheral")
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to connect the FTDI endpoint")
return
}
err = p.Activate(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to activate the FTDI peripheral")
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to activate the FTDI endpoint")
return
}
}(peripheral)
}(endpoint)
}
// TODO: Update the Peripheral reference in the corresponding devices
// TODO: Update the Endpoint reference in the corresponding devices
runtime.EventsEmit(ctx, string(PeripheralArrival), peripheral.GetInfo())
runtime.EventsEmit(ctx, string(EndpointArrival), endpoint.GetInfo())
}
// OnPeripheralRemoval is called when a peripheral exits the system
func (h *Manager) OnPeripheralRemoval(ctx context.Context, peripheral Peripheral) {
// Properly deactivating and disconnecting the peripheral
// OnEndpointRemoval is called when a endpoint exits the system
func (h *Manager) OnEndpointRemoval(ctx context.Context, endpoint Endpoint) {
// Properly deactivating and disconnecting the endpoint
h.wg.Add(1)
go func(p Peripheral) {
go func(p Endpoint) {
defer h.wg.Done()
err := p.Deactivate(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to deactivate peripheral after disconnection")
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to deactivate endpoint after disconnection")
}
err = p.Disconnect(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to disconnect the peripheral after disconnection")
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to disconnect the endpoint after disconnection")
}
}(peripheral)
}(endpoint)
// Remove the peripheral from the hardware
delete(h.DetectedPeripherals, peripheral.GetInfo().SerialNumber)
// Remove the endpoint from the hardware
delete(h.DetectedEndpoints, endpoint.GetInfo().SerialNumber)
// TODO: Update the Peripheral reference in the corresponding devices
runtime.EventsEmit(ctx, string(PeripheralRemoval), peripheral.GetInfo())
// TODO: Update the Endpoint reference in the corresponding devices
runtime.EventsEmit(ctx, string(EndpointRemoval), endpoint.GetInfo())
}
// GetFinder returns a register finder
func (h *Manager) GetFinder(finderName string) (PeripheralFinder, error) {
finder, exists := h.finders[finderName]
// GetProvider returns a register provider
func (h *Manager) GetProvider(providerName string) (EndpointProvider, error) {
provider, exists := h.providers[providerName]
if !exists {
log.Error().Str("file", "hardware").Str("finderName", finderName).Msg("unable to get the finder")
return nil, fmt.Errorf("unable to locate the '%s' finder", finderName)
log.Error().Str("file", "hardware").Str("providerName", providerName).Msg("unable to get the provider")
return nil, fmt.Errorf("unable to locate the '%s' provider", providerName)
}
log.Debug().Str("file", "hardware").Str("finderName", finderName).Msg("got finder")
return finder, nil
log.Debug().Str("file", "hardware").Str("providerName", providerName).Msg("got provider")
return provider, nil
}
// RegisterFinder registers a new peripherals finder
func (h *Manager) RegisterFinder(finder PeripheralFinder) {
h.finders[finder.GetName()] = finder
log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered")
// RegisterProvider registers a new endpoints provider
func (h *Manager) RegisterProvider(provider EndpointProvider) {
h.providers[provider.GetName()] = provider
log.Info().Str("file", "hardware").Str("providerName", provider.GetName()).Msg("provider registered")
}
// WaitStop stops the hardware manager
func (h *Manager) WaitStop() error {
log.Trace().Str("file", "hardware").Msg("closing the hardware manager")
// Stop each finder
// Stop each provider
var errs []error
for name, f := range h.finders {
for name, f := range h.providers {
if err := f.WaitStop(); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", name, err))
}
}
// Wait for all the peripherals to close
log.Trace().Str("file", "MIDIFinder").Msg("closing all MIDI peripherals")
for registeredPeripheralSN, registeredPeripheral := range h.DetectedPeripherals {
err := registeredPeripheral.WaitStop()
// Wait for all the endpoints to close
log.Trace().Str("file", "MIDIProvider").Msg("closing all MIDI endpoints")
for registeredEndpointSN, registeredEndpoint := range h.DetectedEndpoints {
err := registeredEndpoint.WaitStop()
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
errs = append(errs, fmt.Errorf("%s: %w", registeredEndpointSN, err))
}
}

View File

@@ -12,45 +12,45 @@ type MappingInfo struct {
Features map[string]any `yaml:"features"`
}
// Device represents the methods used to manage a device (logic element include in a Peripheral)
// Device represents the methods used to manage a device (logic element include in a Endpoint)
type Device interface {
Configure() error // Load the mapping for the device
}
// Peripheral represents the methods used to manage a peripheral (input or output hardware)
type Peripheral interface {
Connect(context.Context) error // Connect 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
// AddDevice(Device) error // Add a device to the peripheral
// RemoveDevice(Device) error // Remove a device to the peripheral
GetSettings() map[string]any // Get the peripheral settings
SetSettings(context.Context, map[string]any) error // Set a peripheral setting
// Endpoint represents the methods used to manage a endpoint (input or output hardware)
type Endpoint interface {
Connect(context.Context) error // Connect the endpoint
// SetEventCallback(func(any)) // Callback is called when an event is emitted from the endpoint
Disconnect(context.Context) error // Disconnect the endpoint
Activate(context.Context) error // Activate the endpoint
Deactivate(context.Context) error // Deactivate the endpoint
// AddDevice(Device) error // Add a device to the endpoint
// RemoveDevice(Device) error // Remove a device to the endpoint
GetSettings() map[string]any // Get the endpoint settings
SetSettings(context.Context, map[string]any) error // Set a endpoint setting
SetDeviceProperty(context.Context, uint32, byte) error // Update a device property
WaitStop() error // Properly close the peripheral
WaitStop() error // Properly close the endpoint
GetInfo() PeripheralInfo // Get the peripheral information
GetInfo() EndpointInfo // Get the endpoint information
}
// 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
Settings map[string]any `yaml:"settings"` // Peripheral settings
// EndpointInfo represents a endpoint information
type EndpointInfo struct {
Name string `yaml:"name"` // Name of the endpoint
SerialNumber string `yaml:"sn"` // S/N of the endpoint
ProtocolName string `yaml:"protocol"` // Protocol name of the endpoint
Settings map[string]any `yaml:"settings"` // Endpoint settings
}
// PeripheralFinder represents how compatible peripheral drivers are implemented
type PeripheralFinder interface {
Initialize() error // Initializes the protocol
Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) // Manually create a peripheral
Remove(ctx context.Context, peripheral Peripheral) error // Manually remove a peripheral
OnArrival(cb func(context.Context, Peripheral)) // Callback function when a peripheral arrives
OnRemoval(cb func(context.Context, Peripheral)) // Callback function when a peripheral goes away
Start(context.Context) error // Start the detection
WaitStop() error // Waiting for finder to close
GetName() string // Get the name of the finder
// EndpointProvider represents how compatible endpoint drivers are implemented
type EndpointProvider interface {
Initialize() error // Initializes the protocol
Create(ctx context.Context, endpointInfo EndpointInfo) (EndpointInfo, error) // Manually create a endpoint
Remove(ctx context.Context, endpoint Endpoint) error // Manually remove a endpoint
OnArrival(cb func(context.Context, Endpoint)) // Callback function when a endpoint arrives
OnRemoval(cb func(context.Context, Endpoint)) // Callback function when a endpoint goes away
Start(context.Context) error // Start the detection
WaitStop() error // Waiting for provider to close
GetName() string // Get the name of the provider
}

View File

@@ -1,61 +0,0 @@
package main
import (
"dmxconnect/hardware"
"fmt"
"github.com/rs/zerolog/log"
)
// AddPeripheral adds a peripheral to the project
func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, error) {
// Register this new peripheral
serialNumber, err := a.hardwareManager.RegisterPeripheral(a.ctx, peripheralData)
if err != nil {
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
// Add the peripheral ID to the project
if a.projectInfo.PeripheralsInfo == nil {
a.projectInfo.PeripheralsInfo = make(map[string]hardware.PeripheralInfo)
}
a.projectInfo.PeripheralsInfo[peripheralData.SerialNumber] = peripheralData
log.Info().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", peripheralData.SerialNumber).Msg("peripheral added to project")
return peripheralData.SerialNumber, nil
}
// GetPeripheralSettings gets the peripheral settings
func (a *App) GetPeripheralSettings(protocolName, peripheralSN string) (map[string]any, error) {
return a.hardwareManager.GetPeripheralSettings(peripheralSN)
}
// UpdatePeripheralSettings updates a specific setting of a peripheral
func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settings map[string]any) error {
// Save the settings in the application
if a.projectInfo.PeripheralsInfo == nil {
a.projectInfo.PeripheralsInfo = make(map[string]hardware.PeripheralInfo)
}
pInfo := a.projectInfo.PeripheralsInfo[peripheralID]
pInfo.Settings = settings
a.projectInfo.PeripheralsInfo[peripheralID] = pInfo
// Apply changes in the peripheral
return a.hardwareManager.SetPeripheralSettings(a.ctx, peripheralID, pInfo.Settings)
}
// RemovePeripheral removes a peripheral from the project
func (a *App) RemovePeripheral(peripheralData hardware.PeripheralInfo) error {
// Unregister the peripheral from the finder
err := a.hardwareManager.UnregisterPeripheral(a.ctx, peripheralData)
if err != nil {
return fmt.Errorf("unable to unregister this peripheral: %w", err)
}
// Remove the peripheral ID from the project
delete(a.projectInfo.PeripheralsInfo, peripheralData.SerialNumber)
log.Info().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", peripheralData.SerialNumber).Msg("peripheral removed from project")
return nil
}

View File

@@ -77,7 +77,7 @@ func (a *App) CreateProject() error {
Avatar: "appicon.png",
Comments: "Write your comments here",
},
make(map[string]hardware.PeripheralInfo),
make(map[string]hardware.EndpointInfo),
}
// The project isn't saved for now
@@ -127,12 +127,12 @@ func (a *App) OpenProject(projectInfo ProjectInfo) error {
// Send an event with the project data
runtime.EventsEmit(a.ctx, "LOAD_PROJECT", projectInfo.ShowInfo)
// Load all peripherals of the project
projectPeripherals := a.projectInfo.PeripheralsInfo
for key, value := range projectPeripherals {
_, err = a.hardwareManager.RegisterPeripheral(a.ctx, value)
// Load all endpoints of the project
projectEndpoints := a.projectInfo.EndpointsInfo
for key, value := range projectEndpoints {
_, err = a.hardwareManager.RegisterEndpoint(a.ctx, value)
if err != nil {
return fmt.Errorf("unable to register the peripheral S/N '%s': %w", key, err)
return fmt.Errorf("unable to register the endpoint S/N '%s': %w", key, err)
}
}
return nil
@@ -140,12 +140,12 @@ func (a *App) OpenProject(projectInfo ProjectInfo) error {
// CloseCurrentProject closes the current project
func (a *App) CloseCurrentProject() error {
// Unregister all peripherals of the project
projectPeripherals := a.hardwareManager.SavedPeripherals
for key, value := range projectPeripherals {
err := a.hardwareManager.UnregisterPeripheral(a.ctx, value)
// Unregister all endpoints of the project
projectEndpoints := a.hardwareManager.SavedEndpoints
for key, value := range projectEndpoints {
err := a.hardwareManager.UnregisterEndpoint(a.ctx, value)
if err != nil {
return fmt.Errorf("unable to unregister the peripheral S/N '%s': %w", key, err)
return fmt.Errorf("unable to unregister the endpoint S/N '%s': %w", key, err)
}
}
@@ -197,7 +197,7 @@ func (a *App) SaveProject() (string, error) {
log.Debug().Str("file", "project").Str("newProjectSave", a.projectSave).Msg("projectSave is null, getting a new one")
}
// Get hardware info
a.projectInfo.PeripheralsInfo = a.hardwareManager.SavedPeripherals
a.projectInfo.EndpointsInfo = a.hardwareManager.SavedEndpoints
// Marshal the project
data, err := yaml.Marshal(a.projectInfo)
if err != nil {
@@ -238,6 +238,6 @@ type ProjectMetaData struct {
// ProjectInfo defines all the information for a lighting project
type ProjectInfo struct {
ShowInfo ShowInfo `yaml:"show"` // Show information
PeripheralsInfo map[string]hardware.PeripheralInfo `yaml:"peripherals"` // Peripherals information
ShowInfo ShowInfo `yaml:"show"` // Show information
EndpointsInfo map[string]hardware.EndpointInfo `yaml:"endpoints"` // Endpoints information
}