Implements the OS2L feature (tested with Virtual DJ).
Graphics improvements.

Reviewed-on: #32
This commit was merged in pull request #32.
This commit is contained in:
2025-11-14 10:46:24 +00:00
parent 121a14ac61
commit 932c288a9c
18 changed files with 560 additions and 245 deletions

2
app.go
View File

@@ -32,7 +32,7 @@ func NewApp() *App {
hardwareManager := hardware.NewHardwareManager()
// hardwareManager.RegisterFinder(hardware.NewMIDIFinder(5 * time.Second))
hardwareManager.RegisterFinder(hardware.NewFTDIFinder(5 * time.Second))
// hardwareManager.RegisterFinder(hardware.NewOS2LFinder())
hardwareManager.RegisterFinder(hardware.NewOS2LFinder())
return &App{
hardwareManager: hardwareManager,
projectSave: "",

View File

@@ -7,7 +7,6 @@
import Settings from './components/Settings/Settings.svelte';
import Devices from './components/Devices/Devices.svelte';
import Show from './components/Show/Show.svelte';
import DropdownList from "./components/General/DropdownList.svelte";
import RoundDropdownList from "./components/General/RoundDropdownList.svelte";
import GeneralConsole from './components/Console/GeneralConsole.svelte';
import RoundIconButton from './components/General/RoundIconButton.svelte';
@@ -134,7 +133,6 @@
<style>
main {
text-align: left;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}

View File

@@ -101,9 +101,13 @@
<Tooltip message={tooltipMessage} show={tooltipShowing} position={tooltipPosition}></Tooltip>
<div class="list" style="color: {$colors.white}; display: {listShowing ? "block" : "none"}; border: 2px solid {$colors.second}; background-color: {$colors.first};"
on:mouseleave={hideList}>
{#each Array.from(choices) as [key, value]}
<div class="item" on:click={() => handleclick({key})}>{value}</div>
{/each}
{#if choices.size != 0}
{#each Array.from(choices) as [key, value]}
<div class="item" on:click={() => handleclick({key})}>{value}</div>
{/each}
{:else}
<div class="item"><i>{$_("openProjectEmpty")}</i></div>
{/if}
</div>
</div>

View File

@@ -15,15 +15,14 @@
</script>
<div class="tabContainer" style="color: {$colors.white};">
<div class="headerContainer"
style='background-color: {$colors.third};'>
<div class="headerContainer">
{#each tabs as tab, index}
<RoundedButton text={tab.title} icon={tab.icon} tooltip={tab.tooltip} active={ (activeTab == index) ? true : false } on:click={() => setActiveTab(index)}/>
<RoundedButton style="margin: 0.1em;" text={tab.title} icon={tab.icon} tooltip={tab.tooltip} active={ (activeTab !== index) ? true : false } on:click={() => setActiveTab(index)}/>
{/each}
</div>
<div class="bodyContainer"
style='background-color: {$colors.first}; max-width: {maxWidth}; max-height: {maxHeight};'>
style='background-color: {$colors.first}; max-width: {maxWidth};'>
{#if tabs[activeTab]}
<svelte:component this={tabs[activeTab].component} />
{/if}
@@ -41,13 +40,11 @@
}
.headerContainer{
padding: 0.1em;
margin-bottom: 1em;
border-radius: 0.5em;
}
.bodyContainer{
padding: 0.5em;
background-color: red;
border-radius: 0.5em;
overflow:auto;
}
</style>

View File

@@ -38,7 +38,7 @@
</script>
<div class="card" on:dblclick={dblclick}>
<div class="{selected ? "selected" : "unselected"} {status == "PERIPHERAL_CONNECTING" ? "waiting" : ""}" on:mousedown={click} style="color: {(status == "PERIPHERAL_DISCONNECTED") ? $colors.first : $colors.white};">
<div class="{selected ? "selected" : "unselected"} {status == "PERIPHERAL_CONNECTING" ? "waiting" : ""}" on:mousedown={click} style="color: {$colors.white};">
<div style="z-index: 1;">
<p>{#if status == "PERIPHERAL_DISCONNECTED" }<i class='bx bx-no-signal' style="font-size:100%; color: var(--nok-color);"></i> {/if}{title}</p>
<h6 class="subtitle">{type} {location != '' ? "- " : ""}<i>{location}</i></h6>
@@ -52,8 +52,8 @@
<div class="actions">
<InfoButton on:click={add} color="{(status == "PERIPHERAL_DISCONNECTED") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { addable ? 'flex' : 'none' }" icon='bxs-message-square-add' interactive message={$_("projectHardwareAddTooltip")}/>
<InfoButton on:click={remove} color="{(status == "PERIPHERAL_DISCONNECTED") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { removable ? 'flex' : 'none' }" icon='bx-trash' interactive message={$_("projectHardwareDeleteTooltip")}/>
<InfoButton style="margin: 0.2em; display: { (status == "PERIPHERAL_ACTIVATED" || status == "PERIPHERAL_DEACTIVATED") ? 'flex' : 'none' }" background={ (status == "PERIPHERAL_ACTIVATED") ? $colors.ok : (status == "PERIPHERAL_DEACTIVATED") ? $colors.nok : null} icon='bx-pulse' hide={!signalizable}/>
<InfoButton on:click={remove} color="{$colors.white}" style="margin: 0.2em; display: { removable ? 'flex' : 'none' }" icon='bx-trash' interactive message={$_("projectHardwareDeleteTooltip")}/>
<InfoButton style="transition: background-color 0.3s ease; margin: 0.2em; display: { (status == "PERIPHERAL_ACTIVATED" || status == "PERIPHERAL_DEACTIVATED") ? 'flex' : 'none' }" background={ (signalizable && signalized) ? $colors.orange : (status == "PERIPHERAL_ACTIVATED") ? $colors.ok : (status == "PERIPHERAL_DEACTIVATED") ? $colors.nok : null} icon='bx-pulse' hide={!signalizable}/>
</div>
</div>
</div>
@@ -63,19 +63,33 @@
background: linear-gradient(to bottom right, var(--second-color), var(--third-color));
}
.card{
position: relative;
position: relative;
}
.selected {
background-color: var(--first-color);
background-color: var(--third-color);
position: relative;
margin: 0.2em;
padding-left: 0.3em;
padding-bottom: 0.3em;
border-radius: 0.2em;
padding: 0.2em 0.3em 0.5em 0.5em;
border-radius: 0.5em;
display: flex;
justify-content: space-between;
text-align: left;
cursor: pointer;
overflow: hidden;
}
.unselected{
background-color: var(--second-color);
position: relative;
margin: 0.2em;
padding: 0.2em 0.3em 0.5em 0.5em;
border-radius: 0.5em;
display: flex;
justify-content: space-between;
text-align: left;
cursor: pointer;
overflow: hidden;
}
.subtitle{
margin-bottom: 0.5em;
}
@@ -94,7 +108,7 @@
.waiting::before{
content: '';
position: absolute;
background: linear-gradient(var(--second-color), var(--first-color));
background: linear-gradient(var(--first-color), var(--second-color));
width: 100%;
height: 60%;
animation: rotate 3s linear infinite;
@@ -108,21 +122,6 @@
border-radius: 0.2em;
}
.unselected{
background-color: var(--third-color);
background: fixed;
position: relative;
margin: 0.2em;
padding-left: 0.3em;
padding-bottom: 0.3em;
border-radius: 0.2em;
display: flex;
justify-content: space-between;
text-align: left;
cursor: pointer;
overflow: hidden;
}
/* Définition de l'animation */
@keyframes rotate {
from {

View File

@@ -1,9 +1,8 @@
<script lang=ts>
import DeviceCard from "./DeviceCard.svelte";
import Tab from "../General/Tab.svelte";
import Input from "../General/Input.svelte";
import { t, _ } from 'svelte-i18n'
import { generateToast, needProjectSave, peripherals } from "../../stores";
import { generateToast, needProjectSave, peripherals, colors } from "../../stores";
import { get } from "svelte/store"
import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral } from "../../../wailsjs/go/main/App";
import RoundedButton from "../General/RoundedButton.svelte";
@@ -79,22 +78,25 @@
</script>
<div class="hardware">
<div style="padding: 0.5em;">
<p style="margin-bottom: 1em;">{$_("projectHardwareAvailableLabel")}</p>
<div class="libraryPanel">
<div class="availableHardware">
<p style="color: var(--first-color);"><i class='bx bxs-plug'></i> {$_("projectHardwareDetectedLabel")}</p>
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isDetected}
<DeviceCard on:add={() => addPeripheral(peripheral)} on:dblclick={() => {
if(!peripheral.isSaved)
addPeripheral(peripheral)
}}
status="PERIPHERAL_CONNECTED" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/>
{/if}
{/each}
<p style="color: var(--first-color);"><i class='bx bxs-network-chart' ></i> {$_("projectHardwareOthersLabel")}</p>
<RoundedButton on:click={()=>addPeripheral({Name: "OS2L connection", ProtocolName: "OS2L"})} text="Add an OS2L peripheral" icon="bx-plus-circle" tooltip="Configure an OS2L connection"/>
<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)
}}
status="PERIPHERAL_CONNECTED" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.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={() => addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})}
on:dblclick={() => addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})}
status="PERIPHERAL_CONNECTED" title={"OS2L virtual device"} type={"OS2L"} location={""} addable={true}/>
</div>
<div style="padding: 0.5em; flex:2; width:100%;">
@@ -104,14 +106,13 @@
{#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/>
title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} selected={serialNumber == selectedPeripheralSN} removable signalizable signalized={peripheral.eventEmitted}/>
{/if}
{/each}
{:else}
<i>{$_("projectHardwareEmptyLabel")}</i>
{/if}
</div>
<p style="margin-bottom: 1em;">{$_("projectHardwareSettingsLabel")} (<b>{selectedPeripheralSN == null ? $_("projectHardwareNoSelection") : selectedPeripheralSN}</b>)</p>
<div class='flexSettings'>
{#if Object.keys(selectedPeripheralSettings).length > 0}
{#each Object.entries(selectedPeripheralSettings) as [settingName, settingValue]}
@@ -119,8 +120,6 @@
<Input on:blur={(event) => validate(settingName, event.detail.target.value)} label={$t(settingName)} type="{typeof(settingValue)}" width='100%' value="{settingValue}"/>
</div>
{/each}
{:else}
<i>{$_("projectHardwareNoSettingLabel")}</i>
{/if}
</div>
</div>
@@ -149,19 +148,23 @@
border-radius: 0.5em;
padding: 0.2em;
max-height: calc(100vh - 300px);
overflow-y: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
/* overflow: visible; */
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.availableHardware::-webkit-scrollbar {
display: none;
}
.configuredHardware {
background-color: var(--second-color);
border-radius: 0.5em;
.libraryPanel {
padding: 0.5em;
padding: 0.2em;
/* width: 13em; */
height: calc(100vh - 2*8px - 2*4px - 40px - 1em - 2*0.1em - 2*0.1em - 2*0.4em - 21.6px - 2*0.5em);
overflow: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.libraryPanel::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
.configuredHardware {
display: flex;
flex-wrap: wrap;
}

View File

@@ -12,7 +12,7 @@
</script>
<!-- Project tabcontrol -->
<Tab { tabs } maxHeight='73vh'/>
<Tab { tabs }/>
<style>
</style>

View File

@@ -13,6 +13,7 @@
"newProjectTooltip": "Create a new project",
"openProjectString": "Open",
"openProjectTooltip": "Open an existing project",
"openProjectEmpty": "No project found",
"unsavedProjectFlag": "unsaved",
"projectPropertiesTab": "Project properties",
"projectPropertiesTooltip": "The project properties",
@@ -34,16 +35,13 @@
"projectHardwareShowLabel" : "My Show",
"projectHardwareInputsLabel": "INPUTS",
"projectHardwareOutputsLabel": "OUTPUTS",
"projectHardwareDeleteTooltip": "Delete this peripheral",
"projectHardwareAddTooltip": "Add this peripheral to project",
"projectHardwareDeleteTooltip": "Delete",
"projectHardwareAddTooltip": "Add to project",
"projectHardwareNoSelection": "Empty",
"projectHardwareAvailableLabel": "Available peripherals",
"projectHardwareSavedLabel": "Project peripherals",
"projectHardwareSavedLabel": "Saved in project",
"projectHardwareDetectedLabel": "Detected",
"projectHardwareOthersLabel": "Others",
"projectHardwareEmptyLabel": "No hardware saved for this project",
"projectHardwareSettingsLabel": "Peripheral settings",
"projectHardwareNoSettingLabel": "No setting can be displayed",
"peripheralArrivalToast": "Peripheral inserted:",
"peripheralRemovalToast": "Peripheral removed:",

View File

@@ -120,6 +120,32 @@ function unloadPeripheral (peripheralInfo) {
needProjectSave.set(true)
}
// A peripheral event has been emitted
function onPeripheralEvent(sn, event) {
// If not exists, add it to the map
// eventEmitted key to true for 0.2 sec
peripherals.update((storedPeripherals) => {
return {
...storedPeripherals,
[sn]: {
...storedPeripherals[sn],
eventEmitted: true
},
}})
setTimeout(() => {
peripherals.update((storedPeripherals) => {
return {
...storedPeripherals,
[sn]: {
...storedPeripherals[sn],
eventEmitted: false
},
}})
}, 200);
}
let initialized = false
export function initRuntimeEvents(){
@@ -143,6 +169,9 @@ export function initRuntimeEvents(){
// Handle a peripheral unloaded from the project
EventsOn('UNLOAD_PERIPHERAL', unloadPeripheral)
// Handle a peripheral event
EventsOn('PERIPHERAL_EVENT_EMITTED', onPeripheralEvent)
}
export function destroyRuntimeEvents(){
@@ -166,4 +195,7 @@ export function destroyRuntimeEvents(){
// Handle a peripheral unloaded from the project
EventsOff('UNLOAD_PERIPHERAL')
// Handle a peripheral event
EventsOff('PERIPHERAL_EVENT_EMITTED')
}

View File

@@ -45,4 +45,5 @@ export let peripherals = writable({})
// isSaved // if the peripheral is saved in the project
// isDetected // if the peripheral is detected by the system
// status // the status of connection
// status // the status of connection
// eventEmitted // if an event has been emitted for this peripheral (disappear after a delay)

View File

@@ -2,6 +2,7 @@ package hardware
import (
"context"
"errors"
"fmt"
goRuntime "runtime"
"sync"
@@ -59,24 +60,25 @@ func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData Peri
defer f.mu.Unlock()
f.saved[peripheralData.SerialNumber] = peripheralData
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
// If already detected, connect it
if peripheral, ok := f.detected[peripheralData.SerialNumber]; ok {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusConnecting)
err := peripheral.Connect(ctx)
if err != nil {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral")
return peripheralData.SerialNumber, nil
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated)
// Peripheral connected, activate it
err = peripheral.Activate(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the FTDI peripheral")
return peripheralData.SerialNumber, nil
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusActivated)
f.wg.Add(1)
go func() {
defer f.wg.Done()
err := peripheral.Connect(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral")
return
}
// Peripheral connected, activate it
err = peripheral.Activate(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the FTDI peripheral")
return
}
}()
}
// Emits the event in the hardware
@@ -97,14 +99,12 @@ func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData Pe
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral")
return nil
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated)
// Disconnecting peripheral
err = peripheral.Disconnect()
err = peripheral.Disconnect(ctx)
if err != nil {
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral")
return nil
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
}
delete(f.saved, peripheralData.SerialNumber)
runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData)
@@ -164,27 +164,28 @@ func (f *FTDIFinder) GetName() string {
// GetPeripheralSettings gets the peripheral settings
func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
// Return the specified peripheral
// peripheral, found := f.registeredPeripherals[peripheralID]
// if !found {
// log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
// return nil, fmt.Errorf("unable to found the peripheral")
// }
// log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
// return peripheral.GetSettings(), nil
return make(map[string]interface{}), nil
peripheral, found := f.detected[peripheralID]
if !found {
// FTDI not detected, return the last settings saved
if savedPeripheral, isFound := f.saved[peripheralID]; isFound {
return savedPeripheral.Settings, nil
}
return nil, fmt.Errorf("unable to found the peripheral")
}
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
return peripheral.GetSettings(), nil
}
// SetPeripheralSettings sets the peripheral settings
func (f *FTDIFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error {
func (f *FTDIFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error {
// Return the specified peripheral
// peripheral, found := f.registeredPeripherals[peripheralID]
// if !found {
// log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
// return fmt.Errorf("unable to found the peripheral")
// }
// log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
// return peripheral.SetSettings(settings)
return nil
peripheral, found := f.detected[peripheralID]
if !found {
log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
return fmt.Errorf("unable to found the peripheral")
}
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
return peripheral.SetSettings(settings)
}
// scanPeripherals scans the FTDI peripherals
@@ -233,29 +234,26 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
f.detected[sn] = peripheral
if f.onArrival != nil {
go f.onArrival(peripheralData)
f.onArrival(peripheralData)
}
log.Info().Str("sn", sn).Str("name", peripheralData.Name).Msg("[FTDI] New peripheral detected")
// Disconnected by default
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
// If the peripheral is saved in the project => connect
if _, saved := f.saved[sn]; saved {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusConnecting)
err := peripheral.Connect(ctx)
if err != nil {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
log.Err(err).Str("sn", sn).Msg("unable to connect the FTDI peripheral")
return nil
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated)
err = peripheral.Activate(ctx)
if err != nil {
log.Err(err).Str("sn", sn).Msg("unable to activate the FTDI peripheral")
return nil
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusActivated)
f.wg.Add(1)
go func(p PeripheralInfo) {
defer f.wg.Done()
err := peripheral.Connect(ctx)
if err != nil {
log.Err(err).Str("sn", p.SerialNumber).Msg("unable to connect the FTDI peripheral")
return
}
err = peripheral.Activate(ctx)
if err != nil {
log.Err(err).Str("sn", p.SerialNumber).Msg("unable to activate the FTDI peripheral")
return
}
}(peripheralData)
}
}
}
@@ -265,11 +263,14 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
if _, still := currentMap[sn]; !still {
// Properly clean the DMX device
err := oldPeripheral.Disconnect()
err := oldPeripheral.Deactivate(ctx)
if err != nil {
log.Err(err).Str("sn", sn).Msg("unable to clean the FTDI peripheral after disconnection")
log.Err(err).Str("sn", sn).Msg("unable to deactivate the FTDI peripheral after disconnection")
}
err = oldPeripheral.Disconnect(ctx)
if err != nil {
log.Err(err).Str("sn", sn).Msg("unable to disconnect the FTDI peripheral after disconnection")
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), oldPeripheral.GetInfo(), PeripheralStatusDisconnected)
// Delete it from the detected list
delete(f.detected, sn)
@@ -277,7 +278,7 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
// Execute the removal callback
if f.onRemoval != nil {
go f.onRemoval(oldPeripheral.GetInfo())
f.onRemoval(oldPeripheral.GetInfo())
}
}
}
@@ -292,22 +293,22 @@ func (f *FTDIFinder) WaitStop() error {
// close(f.scanChannel)
// Wait for all the peripherals to close
// log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals")
// var errs []error
// for registeredPeripheralSN, registeredPeripheral := range f.registeredPeripherals {
// err := registeredPeripheral.WaitStop()
// if err != nil {
// errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
// }
// }
log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals")
var errs []error
for registeredPeripheralSN, registeredPeripheral := range f.detected {
err := registeredPeripheral.WaitStop()
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
}
}
// Wait for goroutines to stop
f.wg.Wait()
// Returning errors
// if len(errs) > 0 {
// return errors.Join(errs...)
// }
if len(errs) > 0 {
return errors.Join(errs...)
}
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
/*
@@ -41,6 +42,8 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error {
return errors.Errorf("the DMX device has already been created!")
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting)
// Create the DMX sender
p.dmxSender = C.dmx_create()
@@ -48,6 +51,7 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error {
serialNumber := C.CString(p.info.SerialNumber)
defer C.free(unsafe.Pointer(serialNumber))
if C.dmx_connect(p.dmxSender, serialNumber) != C.DMX_OK {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device")
return errors.Errorf("unable to connect '%s'", p.info.SerialNumber)
}
@@ -56,15 +60,16 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error {
go func() {
defer p.wg.Done()
<-ctx.Done()
_ = p.Disconnect()
_ = p.Disconnect(ctx)
}()
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully")
return nil
}
// Disconnect disconnects the FTDI peripheral
func (p *FTDIPeripheral) Disconnect() error {
func (p *FTDIPeripheral) Disconnect(ctx context.Context) error {
// Check if the device has already been created
if p.dmxSender == nil {
return errors.Errorf("the DMX device has not been connected!")
@@ -75,6 +80,8 @@ func (p *FTDIPeripheral) Disconnect() error {
// Reset the pointer to the peripheral
p.dmxSender = nil
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
return nil
}
@@ -96,6 +103,8 @@ func (p *FTDIPeripheral) Activate(ctx context.Context) error {
C.dmx_setValue(p.dmxSender, C.uint16_t(1), C.uint8_t(255))
C.dmx_setValue(p.dmxSender, C.uint16_t(5), C.uint8_t(255))
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated)
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device activated successfully")
return nil
}
@@ -114,6 +123,7 @@ func (p *FTDIPeripheral) Deactivate(ctx context.Context) error {
return errors.Errorf("unable to deactivate the DMX sender!")
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully")
return nil
}

View File

@@ -2,23 +2,32 @@ package hardware
import (
"context"
"errors"
"fmt"
"math/rand"
"strings"
"sync"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// OS2LFinder represents how the protocol is defined
type OS2LFinder struct {
registeredPeripherals map[string]OS2LPeripheral // The list of found peripherals
wg sync.WaitGroup
mu sync.Mutex
saved map[string]*OS2LPeripheral // The list of saved peripherals
onArrival func(p PeripheralInfo) // When a peripheral arrives
onRemoval func(p PeripheralInfo) // When a peripheral goes away
}
// NewOS2LFinder creates a new OS2L finder
func NewOS2LFinder() *OS2LFinder {
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder created")
return &OS2LFinder{
registeredPeripherals: make(map[string]OS2LPeripheral),
saved: make(map[string]*OS2LPeripheral),
}
}
@@ -28,37 +37,92 @@ func (f *OS2LFinder) Initialize() error {
return nil
}
// OnArrival is the callback function when a new peripheral arrives
func (f *OS2LFinder) OnArrival(cb func(p PeripheralInfo)) {
f.onArrival = cb
}
// OnRemoval i the callback when a peripheral goes away
func (f *OS2LFinder) OnRemoval(cb func(p PeripheralInfo)) {
f.onRemoval = cb
}
// RegisterPeripheral registers a new peripheral
func (f *OS2LFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
// Create a random serial number for this peripheral
peripheralData.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
f.mu.Lock()
defer f.mu.Unlock()
// If the SerialNumber is empty, generate another one
if peripheralData.SerialNumber == "" {
peripheralData.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
}
// Create a new OS2L peripheral
peripheral, err := NewOS2LPeripheral(peripheralData)
if err != nil {
return "", fmt.Errorf("unable to create the OS2L peripheral: %w", err)
}
// Set the event callback
peripheral.SetEventCallback(func(event any) {
runtime.EventsEmit(ctx, string(PeripheralEventEmitted), peripheralData.SerialNumber, event)
})
f.saved[peripheralData.SerialNumber] = peripheral
log.Trace().Str("file", "OS2LFinder").Str("serialNumber", peripheralData.SerialNumber).Msg("OS2L peripheral created")
os2lPeripheral, err := NewOS2LPeripheral(peripheralData)
if err != nil {
return "", fmt.Errorf("unable to create the OS2L peripheral: %v", err)
}
// Connect this peripheral
err = os2lPeripheral.Connect(ctx)
if err != nil {
return "", err
// New OS2L peripheral has arrived
if f.onArrival != nil {
f.onArrival(peripheral.GetInfo())
}
f.registeredPeripherals[peripheralData.SerialNumber] = *os2lPeripheral
log.Trace().Any("periph", &os2lPeripheral).Str("file", "OS2LFinder").Str("peripheralName", peripheralData.Name).Msg("OS2L peripheral has been created")
f.wg.Add(1)
go func() {
defer f.wg.Done()
// Connect the OS2L peripheral
err = peripheral.Connect(ctx)
if err != nil {
log.Err(err).Str("file", "OS2LFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral")
return
}
// Peripheral connected, activate it
err = peripheral.Activate(ctx)
if err != nil {
log.Err(err).Str("file", "OS2LFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the OS2L peripheral")
return
}
}()
// Emits the event in the hardware
runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData)
return peripheralData.SerialNumber, nil
}
// UnregisterPeripheral unregisters an existing peripheral
func (f *OS2LFinder) UnregisterPeripheral(peripheralID string) error {
peripheral, registered := f.registeredPeripherals[peripheralID]
if registered {
err := peripheral.Disconnect()
func (f *OS2LFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error {
f.mu.Lock()
defer f.mu.Unlock()
if peripheral, detected := f.saved[peripheralData.SerialNumber]; detected {
// Deactivating peripheral
err := peripheral.Deactivate(ctx)
if err != nil {
return err
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral")
return nil
}
// Disconnecting peripheral
err = peripheral.Disconnect(ctx)
if err != nil {
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral")
return nil
}
}
delete(f.registeredPeripherals, peripheralID)
// The OS2L peripheral has gone
f.onRemoval(peripheralData)
delete(f.saved, peripheralData.SerialNumber)
runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData)
return nil
}
@@ -68,9 +132,9 @@ func (f *OS2LFinder) GetName() string {
}
// GetPeripheralSettings gets the peripheral settings
func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]any, error) {
// Return the specified peripheral
peripheral, found := f.registeredPeripherals[peripheralID]
peripheral, found := f.saved[peripheralID]
if !found {
log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the OS2L finder")
return nil, fmt.Errorf("unable to found the peripheral")
@@ -80,24 +144,49 @@ func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]inte
}
// SetPeripheralSettings sets the peripheral settings
func (f *OS2LFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error {
func (f *OS2LFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error {
// Return the specified peripheral
peripheral, found := f.registeredPeripherals[peripheralID]
peripheral, found := f.saved[peripheralID]
if !found {
log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
return fmt.Errorf("unable to found the peripheral")
}
log.Debug().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
return peripheral.SetSettings(settings)
// Set the peripheral settings
return peripheral.SetSettings(ctx, settings)
}
// Start starts the finder
func (f *OS2LFinder) Start(ctx context.Context) error {
// No peripherals to scan here
return nil
}
// Stop stops this finder
func (f *OS2LFinder) Stop() error {
// WaitStop stops the finder
func (f *OS2LFinder) WaitStop() error {
log.Trace().Str("file", "OS2LFinder").Msg("stopping the OS2L finder...")
// Close the channel
// close(f.scanChannel)
// Wait for all the peripherals to close
log.Trace().Str("file", "OS2LFinder").Msg("closing all OS2L peripherals")
var errs []error
for registeredPeripheralSN, registeredPeripheral := range f.saved {
err := registeredPeripheral.WaitStop()
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
}
}
// Waiting internal tasks
f.wg.Wait()
// Returning errors
if len(errs) > 0 {
return errors.Join(errs...)
}
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder stopped")
return nil
}

View File

@@ -2,86 +2,260 @@ package hardware
import (
"context"
"encoding/json"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// OS2LMessage represents an OS2L message
type OS2LMessage struct {
Event string `json:"evt"`
Name string `json:"name"`
State string `json:"state"`
ID int64 `json:"id"`
Param float64 `json:"param"`
}
// OS2LPeripheral contains the data of an OS2L peripheral
type OS2LPeripheral struct {
info PeripheralInfo // The basic info for this peripheral
serverIP string // OS2L server IP
serverPort int // OS2L server port
wg sync.WaitGroup
info PeripheralInfo // The basic info for this peripheral
serverIP string // OS2L server IP
serverPort int // OS2L server port
listener net.Listener // Net listener (TCP)
listenerCancel context.CancelFunc // Call this function to cancel the peripheral activation
eventCallback func(any) // This callback is called for returning events
}
// NewOS2LPeripheral creates a new OS2L peripheral
func NewOS2LPeripheral(peripheralData PeripheralInfo) (*OS2LPeripheral, error) {
peripheral := &OS2LPeripheral{
info: peripheralData,
listener: nil,
eventCallback: nil,
}
log.Trace().Str("file", "OS2LPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("OS2L peripheral created")
return &OS2LPeripheral{
info: peripheralData,
serverIP: "127.0.0.1",
serverPort: 9005,
}, nil
return peripheral, peripheral.loadSettings(peripheralData.Settings)
}
// Connect connects the MIDI peripheral
// SetEventCallback sets the callback for returning events
func (p *OS2LPeripheral) SetEventCallback(eventCallback func(any)) {
p.eventCallback = eventCallback
}
// Connect connects the OS2L peripheral
func (p *OS2LPeripheral) Connect(ctx context.Context) error {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting)
var err error
addr := net.TCPAddr{Port: p.serverPort, IP: net.ParseIP(p.serverIP)}
log.Debug().Any("addr", addr).Msg("parametres de connexion à la connexion")
p.listener, err = net.ListenTCP("tcp", &addr)
if err != nil {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
return fmt.Errorf("unable to set the OS2L TCP listener")
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected")
go func() {
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "connecting")
time.Sleep(5 * time.Second)
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "disconnected")
}()
return nil
}
// Disconnect disconnects the MIDI peripheral
func (p *OS2LPeripheral) Disconnect() error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected")
// handleMessage handles an OS2L message
func (p *OS2LPeripheral) handleMessage(raw []byte) error {
message := OS2LMessage{}
err := json.Unmarshal(raw, &message)
if err != nil {
return fmt.Errorf("Unable to parse the OS2L message: %w", err)
}
log.Debug().Str("event", message.Event).Str("name", message.Name).Str("state", message.State).Int("ID", int(message.ID)).Float64("param", message.Param).Msg("OS2L event received")
// Return the event to the finder
if p.eventCallback != nil {
go p.eventCallback(message)
}
return nil
}
// Activate activates the MIDI peripheral
func (p *OS2LPeripheral) Activate(ctx context.Context) error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated")
return nil
}
// Deactivate deactivates the MIDI peripheral
func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated")
return nil
}
// SetSettings sets a specific setting for this peripheral
func (p *OS2LPeripheral) SetSettings(settings map[string]interface{}) error {
// loadSettings check and load the settings in the peripheral
func (p *OS2LPeripheral) loadSettings(settings map[string]any) error {
// Check if the IP exists
serverIP, found := settings["os2lIp"]
if !found {
return fmt.Errorf("Unable to find the OS2L server IP")
// Set default IP address
serverIP = "127.0.0.1"
}
// Check if it is a string
ipSetting, ok := serverIP.(string)
if ok {
p.serverIP = ipSetting
} else {
return fmt.Errorf("The specified IP is not a string")
}
// Check if the port exists
serverPort, found := settings["os2lPort"]
if !found {
return fmt.Errorf("Unable to find the OS2L server port")
// Set default port
serverPort = 9995
}
// Check if it is a float and convert to int
portFloat, ok := serverPort.(float64)
if ok {
p.serverPort = int(portFloat)
} else {
return fmt.Errorf("The specified port is not an int")
switch v := serverPort.(type) {
case int:
p.serverPort = v
case float64:
p.serverPort = int(v) // JSON numbers are float64
default:
return fmt.Errorf("The specified port is not a number, got %T", serverPort)
}
return nil
}
// Disconnect disconnects the MIDI peripheral
func (p *OS2LPeripheral) Disconnect(ctx context.Context) error {
// Close the TCP listener if not null
if p.listener != nil {
p.listener.Close()
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected")
return nil
}
// Activate activates the OS2L peripheral
func (p *OS2LPeripheral) Activate(ctx context.Context) error {
// Create a derived context to handle deactivation
var listenerCtx context.Context
listenerCtx, p.listenerCancel = context.WithCancel(ctx)
if p.listener == nil {
return fmt.Errorf("the listener isn't defined")
}
p.wg.Add(1)
go func() {
defer p.wg.Done()
for {
select {
case <-listenerCtx.Done():
return
default:
p.listener.(*net.TCPListener).SetDeadline(time.Now().Add(1 * time.Second))
conn, err := p.listener.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue
}
if strings.Contains(err.Error(), "use of closed network connection") || strings.Contains(err.Error(), "invalid argument") {
return
}
log.Err(err).Str("file", "OS2LPeripheral").Msg("unable to accept the connection")
continue
}
// Every client is handled in a dedicated goroutine
p.wg.Add(1)
go func(c net.Conn) {
defer p.wg.Done()
defer c.Close()
buffer := make([]byte, 1024)
for {
select {
case <-listenerCtx.Done():
return
default:
c.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
n, err := c.Read(buffer)
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
// Lecture a expiré → vérifier si le contexte est annulé
select {
case <-listenerCtx.Done():
return
default:
continue // pas annulé → relancer Read
}
}
return // autre erreur ou EOF
}
p.handleMessage(buffer[:n])
}
}
}(conn)
}
}
}()
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated)
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated")
return nil
}
// Deactivate deactivates the OS2L peripheral
func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
if p.listener == nil {
return fmt.Errorf("the listener isn't defined")
}
// Cancel listener by context
if p.listenerCancel != nil {
p.listenerCancel()
}
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated")
return nil
}
// SetSettings sets a specific setting for this peripheral
func (p *OS2LPeripheral) SetSettings(ctx context.Context, settings map[string]any) error {
err := p.loadSettings(settings)
if err != nil {
return fmt.Errorf("unable to load settings: %w", err)
}
// Reconnect the peripheral
p.wg.Add(1)
go func() {
defer p.wg.Done()
err := p.Deactivate(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to deactivate")
return
}
err = p.Disconnect(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to disconnect")
return
}
// Add a sleep to view changes
time.Sleep(500 * time.Millisecond)
err = p.Connect(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to connect")
return
}
err = p.Activate(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to activate")
return
}
}()
log.Info().Str("sn", p.GetInfo().SerialNumber).Msg("peripheral settings set")
return nil
}
@@ -91,8 +265,8 @@ func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte
}
// GetSettings gets the peripheral settings
func (p *OS2LPeripheral) GetSettings() map[string]interface{} {
return map[string]interface{}{
func (p *OS2LPeripheral) GetSettings() map[string]any {
return map[string]any{
"os2lIp": p.serverIP,
"os2lPort": p.serverPort,
}
@@ -102,3 +276,11 @@ func (p *OS2LPeripheral) GetSettings() map[string]interface{} {
func (p *OS2LPeripheral) GetInfo() PeripheralInfo {
return p.info
}
// WaitStop stops the peripheral
func (p *OS2LPeripheral) WaitStop() error {
log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for OS2L peripheral to close...")
p.wg.Wait()
log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("OS2L peripheral closed!")
return nil
}

View File

@@ -21,8 +21,10 @@ const (
PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL"
// PeripheralRemoval is triggered when a peripheral has been disconnected from the system
PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL"
// PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - connected)
// PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - deactivated - activated)
PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS"
// PeripheralEventEmitted is triggered when a peripheral event is emitted
PeripheralEventEmitted PeripheralEvent = "PERIPHERAL_EVENT_EMITTED"
// PeripheralStatusDisconnected : peripheral is now disconnected
PeripheralStatusDisconnected PeripheralStatus = "PERIPHERAL_DISCONNECTED"
// PeripheralStatusConnecting : peripheral is now connecting

View File

@@ -5,39 +5,37 @@ import "context"
// Peripheral represents the methods used to manage a peripheral (input or output hardware)
type Peripheral interface {
Connect(context.Context) error // Connect the peripheral
IsConnected() bool // Return if the peripheral is connected or not
Disconnect() error // Disconnect the peripheral
SetEventCallback(func(any)) // Callback is called when an event is emitted from the peripheral
Disconnect(context.Context) error // Disconnect the peripheral
Activate(context.Context) error // Activate the peripheral
Deactivate(context.Context) error // Deactivate the peripheral
SetSettings(map[string]interface{}) error // Set a peripheral setting
SetSettings(context.Context, map[string]any) error // Set a peripheral setting
SetDeviceProperty(context.Context, uint32, byte) error // Update a device property
WaitStop() error // Properly close the peripheral
GetInfo() PeripheralInfo // Get the peripheral information
GetSettings() map[string]interface{} // Get the peripheral settings
GetInfo() PeripheralInfo // Get the peripheral information
GetSettings() map[string]any // Get the peripheral settings
}
// PeripheralInfo represents a peripheral information
type PeripheralInfo struct {
Name string `yaml:"name"` // Name of the peripheral
SerialNumber string `yaml:"sn"` // S/N of the peripheral
ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral
// IsConnected bool // If the peripheral is connected to the system
// IsActivated bool // If the peripheral is activated in the project
// IsDetected bool // If the peripheral is detected by the system
Settings map[string]interface{} `yaml:"settings"` // Peripheral settings
Name string `yaml:"name"` // Name of the peripheral
SerialNumber string `yaml:"sn"` // S/N of the peripheral
ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral
Settings map[string]any `yaml:"settings"` // Peripheral settings
}
// PeripheralFinder represents how compatible peripheral drivers are implemented
type PeripheralFinder interface {
Initialize() error // Initializes the protocol
OnArrival(cb func(p PeripheralInfo)) // Callback function when a peripheral arrives
OnRemoval(cb func(p PeripheralInfo)) // Callback function when a peripheral goes away
Start(context.Context) error // Start the detection
WaitStop() error // Waiting for finder to close
ForceScan() // Explicitly scans for peripherals
RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data
UnregisterPeripheral(context.Context, PeripheralInfo) error // Unregisters an existing peripheral
GetPeripheralSettings(string) (map[string]interface{}, error) // Gets the peripheral settings
SetPeripheralSettings(string, map[string]interface{}) error // Sets the peripheral settings
GetName() string // Get the name of the finder
Initialize() error // Initializes the protocol
OnArrival(cb func(p PeripheralInfo)) // Callback function when a peripheral arrives
OnRemoval(cb func(p PeripheralInfo)) // Callback function when a peripheral goes away
Start(context.Context) error // Start the detection
WaitStop() error // Waiting for finder to close
ForceScan() // Explicitly scans for peripherals
RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data
UnregisterPeripheral(context.Context, PeripheralInfo) error // Unregisters an existing peripheral
GetPeripheralSettings(string) (map[string]any, error) // Gets the peripheral settings
SetPeripheralSettings(context.Context, string, map[string]any) error // Sets the peripheral settings
GetName() string // Get the name of the finder
}

View File

@@ -18,9 +18,10 @@ func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, err
// Register this new peripheral
serialNumber, err := f.RegisterPeripheral(a.ctx, peripheralData)
if err != nil {
log.Trace().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", serialNumber).Msg("device registered to the finder")
return "", fmt.Errorf("unable to register the peripheral '%s'", serialNumber)
return "", fmt.Errorf("unable to register the peripheral '%s': %w", serialNumber, err)
}
log.Trace().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", serialNumber).Msg("device registered to the finder")
// Rewrite the serialnumber for virtual devices
peripheralData.SerialNumber = serialNumber
@@ -35,7 +36,7 @@ func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, err
}
// GetPeripheralSettings gets the peripheral settings
func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[string]interface{}, error) {
func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[string]any, error) {
// Get the peripheral from its finder
f, err := a.hardwareManager.GetFinder(protocolName)
if err != nil {
@@ -46,7 +47,7 @@ func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[stri
}
// UpdatePeripheralSettings updates a specific setting of a peripheral
func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settings map[string]interface{}) error {
func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settings map[string]any) error {
// Sets the settings with the finder
f, err := a.hardwareManager.GetFinder(protocolName)
if err != nil {
@@ -61,7 +62,7 @@ func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settin
pInfo.Settings = settings
a.projectInfo.PeripheralsInfo[peripheralID] = pInfo
// Apply changes in the peripheral
return f.SetPeripheralSettings(peripheralID, pInfo.Settings)
return f.SetPeripheralSettings(a.ctx, peripheralID, pInfo.Settings)
}
// RemovePeripheral removes a peripheral from the project

View File

@@ -136,7 +136,7 @@ func (a *App) OpenProject(projectInfo ProjectInfo) error {
}
_, err = hostFinder.RegisterPeripheral(a.ctx, value)
if err != nil {
return fmt.Errorf("unable to register the peripheral S/N '%s'", key)
return fmt.Errorf("unable to register the peripheral S/N '%s': %w", key, err)
}
}
return nil