diff --git a/frontend/src/components/General/RoundDropdownList.svelte b/frontend/src/components/General/RoundDropdownList.svelte index e538148..80f618f 100644 --- a/frontend/src/components/General/RoundDropdownList.svelte +++ b/frontend/src/components/General/RoundDropdownList.svelte @@ -99,7 +99,7 @@ {/if} -
{#each Array.from(choices) as [key, value]}
handleclick({key})}>{value}
@@ -125,7 +125,6 @@ .list { z-index: 200; padding: 0.2em; - backdrop-filter: blur(20px); margin-top: 0.2em; position: absolute; width: auto; diff --git a/frontend/src/components/Settings/DeviceCard.svelte b/frontend/src/components/Settings/DeviceCard.svelte index 8910208..8c72cb1 100644 --- a/frontend/src/components/Settings/DeviceCard.svelte +++ b/frontend/src/components/Settings/DeviceCard.svelte @@ -15,13 +15,14 @@ export let signalizable = false; export let signalized = false; export let selected = false; - export let status = "disconnected"; + export let status = "PERIPHERAL_DISCONNECTED"; // Emit a delete event when the device is being removed const dispatch = createEventDispatcher(); function remove(event){ dispatch('delete') } + function add(event){ dispatch('add') } @@ -37,11 +38,11 @@
-
+
-

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

+

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

{type} {location != '' ? "- " : ""}{location}
- {#if status == "disconnected"} + {#if status == "PERIPHERAL_DISCONNECTED"}
Disconnected
{:else}
{line1}
@@ -50,9 +51,9 @@
- - - + + +
diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index 0f5b2c8..a8373a4 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -11,20 +11,8 @@ // Add the peripheral to the project function addPeripheral(peripheral){ // Add the peripheral to the project (backend) - AddPeripheral(peripheral).then((serialNumber) => { - peripherals.update((storedPeripherals) => { - return { - ...storedPeripherals, - [serialNumber]: { - ...storedPeripherals[serialNumber], - Name: peripheral.Name, - ProtocolName: peripheral.ProtocolName, - SerialNumber: serialNumber, - isSaved: true, - }, - }}) - $needProjectSave = true - }).catch((error) => { + AddPeripheral(peripheral) + .catch((error) => { console.log("Unable to add the peripheral to the project: " + error) generateToast('danger', 'bx-error', $_("addPeripheralErrorToast")) }) @@ -33,27 +21,8 @@ // Remove the peripheral from the project function removePeripheral(peripheral) { // Delete the peripheral from the project (backend) - RemovePeripheral(peripheral.ProtocolName, peripheral.SerialNumber).then(() => { - // If the peripheral is not detected, we can delete it form the store - // If not, we only pass the isSaved key to false - let peripheralsList = get(peripherals) - let lastDetectedProperty = peripheralsList[peripheral.SerialNumber]?.isDetected - let needToDelete = (lastDetectedProperty !== true) ? true : false - peripherals.update((storedPeripherals) => { - if (needToDelete){ - delete storedPeripherals[peripheral.SerialNumber]; - return { ...storedPeripherals }; - } - storedPeripherals[peripheral.SerialNumber].isSaved = false - return { ...storedPeripherals }; - }) - $needProjectSave = true - // If the peripheral is currently selected, unselect it - if (selectedPeripheralSN == peripheral.SerialNumber) { - selectedPeripheralSN = null - selectedPeripheralSettings = {} - } - }).catch((error) => { + RemovePeripheral(peripheral) + .catch((error) => { console.log("Unable to remove the peripheral from the project: " + error) generateToast('danger', 'bx-error', $_("removePeripheralErrorToast")) }) @@ -76,7 +45,7 @@ } } - // Unselect the peripheral if it is disconnect + // Unselect the peripheral if it is disconnected $: { Object.entries($peripherals).filter(([serialNumber, peripheral]) => { if (!peripheral.isDetected && peripheral.isSaved && selectedPeripheralSN == serialNumber) { @@ -120,7 +89,7 @@ if(!peripheral.isSaved) addPeripheral(peripheral) }} - status="connected" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/> + status="PERIPHERAL_CONNECTED" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/> {/if} {/each}

{$_("projectHardwareOthersLabel")}

@@ -134,7 +103,7 @@ {#if savedPeripheralNumber > 0} {#each Object.entries($peripherals) as [serialNumber, peripheral]} {#if peripheral.isSaved} - removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} on:click={() => selectPeripheral(peripheral)} + 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/> {/if} {/each} diff --git a/frontend/src/runtime-events.js b/frontend/src/runtime-events.js index 1902a40..59a290b 100644 --- a/frontend/src/runtime-events.js +++ b/frontend/src/runtime-events.js @@ -3,68 +3,91 @@ import { peripherals, generateToast, needProjectSave, showInformation } from './ import { get } from "svelte/store" import { _ } from 'svelte-i18n' -function addPeripheral (peripheralInfo){ - // When a new peripheral is detected, add it to the map and: - // - Pass the isDetected key to true - // - Set the isSaved key to the last value - let peripheralsList = get(peripherals) - let lastSavedProperty = peripheralsList[peripheralInfo.SerialNumber]?.isSaved - peripheralInfo.isDetected = true - peripheralInfo.isSaved = (lastSavedProperty === true) ? true : false - peripherals.update((peripherals) => { - peripherals[peripheralInfo.SerialNumber] = peripheralInfo - return {...peripherals} - }) +// New peripheral has been added to the system +function peripheralArrival (peripheralInfo){ + // If not exists, add it to the map + // isDetected key to true + + peripherals.update((storedPeripherals) => { + return { + ...storedPeripherals, + [peripheralInfo.SerialNumber]: { + ...storedPeripherals[peripheralInfo.SerialNumber], + Name: peripheralInfo.Name, + ProtocolName: peripheralInfo.ProtocolName, + SerialNumber: peripheralInfo.SerialNumber, + Settings: peripheralInfo.Settings, + isDetected: true, + }, + }}) console.log("Hardware has been added to the system"); generateToast('info', 'bxs-hdd', get(_)("peripheralArrivalToast") + ' ' + peripheralInfo.Name + '') } -function removePeripheral (peripheralInfo){ - console.log("Hardware has been removed from the system"); - // When a peripheral is disconnected, pass its isDetected key to false - // If the isSaved key is set to false, we can completely remove the peripheral from the list - let peripheralsList = get(peripherals) - let lastSavedProperty = peripheralsList[peripheralInfo.SerialNumber]?.isSaved - let needToDelete = (lastSavedProperty !== true) ? true : false - peripherals.update((storedPeripherals) => { - if (needToDelete){ - delete storedPeripherals[peripheralInfo.SerialNumber]; - return { ...storedPeripherals }; - } - storedPeripherals[peripheralInfo.SerialNumber].isDetected = false - storedPeripherals[peripheralInfo.SerialNumber].Status = "disconnected" - return {...storedPeripherals} - }) - generateToast('warning', 'bxs-hdd', get(_)("peripheralRemovalToast") + ' ' + peripheralInfo.Name + '') -} +// Peripheral is removed from the system +function peripheralRemoval (peripheralInfo){ + // If not exists, add it to the map + // isDetected key to false -function updatePeripheral(peripheral, status){ - console.log("Hardware status has been updated to " + status); - // When a peripheral status is updated, update it in the store peripherals.update((storedPeripherals) => { return { ...storedPeripherals, - [peripheral.SerialNumber]: { - ...storedPeripherals[peripheral.SerialNumber], - isSaved: true, - Status: status, + [peripheralInfo.SerialNumber]: { + ...storedPeripherals[peripheralInfo.SerialNumber], + Name: peripheralInfo.Name, + ProtocolName: peripheralInfo.ProtocolName, + SerialNumber: peripheralInfo.SerialNumber, + Settings: peripheralInfo.Settings, + isDetected: false, + status: "PERIPHERAL_DISCONNECTED", }, }}) + console.log("Hardware has been removed from the system"); + generateToast('warning', 'bxs-hdd', get(_)("peripheralRemovalToast") + ' ' + peripheralInfo.Name + '') } +// Update peripheral status +function peripheralUpdateStatus(peripheralInfo, status){ + // If not exists, add it to the map + // change status key + + peripherals.update((storedPeripherals) => { + console.log(status) + return { + ...storedPeripherals, + [peripheralInfo.SerialNumber]: { + ...storedPeripherals[peripheralInfo.SerialNumber], + Name: peripheralInfo.Name, + ProtocolName: peripheralInfo.ProtocolName, + SerialNumber: peripheralInfo.SerialNumber, + Settings: peripheralInfo.Settings, + status: status, + }, + }}) + + console.log("Hardware status has been updated to " + status); +} + +// Load the peripheral in the project function loadPeripheral (peripheralInfo) { - peripherals.update((storedPeripherals) => { - // Add the saved peripherals of the project - // If already exists pass the isSaved key to true, if not create the peripheral and set it to disconnected - // Add the peripheral to the list of peripherals, with the last isDetected key and the isSaved key to true - let lastDetectedKey = storedPeripherals[peripheralInfo.SerialNumber]?.isDetected - storedPeripherals[peripheralInfo.SerialNumber] = peripheralInfo - storedPeripherals[peripheralInfo.SerialNumber].isDetected = (lastDetectedKey === true) ? true : false - storedPeripherals[peripheralInfo.SerialNumber].isSaved = true - return {...storedPeripherals} - }) - //TODO: Lors d'un chargement/déchargement natif au démarrage, il ne doit pas y avoir de nécessité de sauvegarder - needProjectSave.set(true) + // If not exists, add it to the map + // isSaved key to true + + peripherals.update((storedPeripherals) => { + return { + ...storedPeripherals, + [peripheralInfo.SerialNumber]: { + ...storedPeripherals[peripheralInfo.SerialNumber], + Name: peripheralInfo.Name, + ProtocolName: peripheralInfo.ProtocolName, + SerialNumber: peripheralInfo.SerialNumber, + Settings: peripheralInfo.Settings, + isSaved: true, + }, + }}) + console.log("Hardware has been added to the project"); + //TODO: Lors d'un chargement/déchargement natif au démarrage, il ne doit pas y avoir de nécessité de sauvegarder + needProjectSave.set(true) } function loadProject (showInfo){ @@ -75,19 +98,27 @@ function loadProject (showInfo){ generateToast('info', 'bx-folder-open', get(_)("projectOpenedToast") + ' ' + showInfo.Name + '') } +// Unload the hardware from the project function unloadPeripheral (peripheralInfo) { - peripherals.update((storedPeripherals) => { - // Set all the isSaved keys to false and delete the disconnected peripherals - storedPeripherals[peripheralInfo.SerialNumber].isSaved = false - if (!storedPeripherals[peripheralInfo.SerialNumber].isDetected) { - delete storedPeripherals[peripheralInfo.SerialNumber] - } - return {...storedPeripherals} - }) + // If not exists, add it to the map + // isSaved key to false - //TODO: Lors d'un chargement/déchargement natif au démarrage, il ne doit pas y avoir de nécessité de sauvegarder - needProjectSave.set(true) - } + peripherals.update((storedPeripherals) => { + return { + ...storedPeripherals, + [peripheralInfo.SerialNumber]: { + ...storedPeripherals[peripheralInfo.SerialNumber], + Name: peripheralInfo.Name, + ProtocolName: peripheralInfo.ProtocolName, + SerialNumber: peripheralInfo.SerialNumber, + Settings: peripheralInfo.Settings, + isSaved: false, + }, + }}) + console.log("Hardware has been removed from the project"); + //TODO: Lors d'un chargement/déchargement natif au démarrage, il ne doit pas y avoir de nécessité de sauvegarder + needProjectSave.set(true) +} let initialized = false @@ -96,13 +127,13 @@ export function initRuntimeEvents(){ initialized = true // Handle the event when a new peripheral is detected - EventsOn('PERIPHERAL_ARRIVAL', addPeripheral) + EventsOn('PERIPHERAL_ARRIVAL', peripheralArrival) // Handle the event when a peripheral is removed from the system - EventsOn('PERIPHERAL_REMOVAL', removePeripheral) + EventsOn('PERIPHERAL_REMOVAL', peripheralRemoval) // Handle the event when a peripheral status is updated - EventsOn('PERIPHERAL_STATUS', updatePeripheral) + EventsOn('PERIPHERAL_STATUS', peripheralUpdateStatus) // Handle the event when a new project need to be loaded EventsOn('LOAD_PROJECT', loadProject) diff --git a/frontend/src/stores.js b/frontend/src/stores.js index 16d093e..02aeb53 100644 --- a/frontend/src/stores.js +++ b/frontend/src/stores.js @@ -34,4 +34,15 @@ export const secondSize = writable("14px") export const thirdSize = writable("20px") // List of current hardware -export let peripherals = writable({}) \ No newline at end of file +export let peripherals = writable({}) + +// Peripheral 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 + +// isSaved // if the peripheral is saved in the project +// isDetected // if the peripheral is detected by the system +// status // the status of connection \ No newline at end of file diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go index df00560..2fc2e5b 100644 --- a/hardware/FTDIFinder.go +++ b/hardware/FTDIFinder.go @@ -2,8 +2,6 @@ package hardware import ( "context" - _ "embed" - "errors" "fmt" goRuntime "runtime" "sync" @@ -24,74 +22,93 @@ import "C" // FTDIFinder manages all the FTDI peripherals type FTDIFinder struct { wg sync.WaitGroup + mu sync.Mutex - findTicker *time.Ticker // Peripherals find ticker - foundPeripherals map[string]PeripheralInfo // The list of peripherals handled by this finder - registeredPeripherals map[string]*FTDIPeripheral // The list of found peripherals - scanChannel chan struct{} // The channel to trigger a scan event + saved map[string]PeripheralInfo // Peripherals saved in the project + detected map[string]*FTDIPeripheral // Detected peripherals + + scanEvery time.Duration // Scans peripherals periodically + + onArrival func(p PeripheralInfo) // When a peripheral arrives + onRemoval func(p PeripheralInfo) // When a peripheral goes away } // NewFTDIFinder creates a new FTDI finder -func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder { +func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder { log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created") return &FTDIFinder{ - findTicker: time.NewTicker(findPeriod), - foundPeripherals: make(map[string]PeripheralInfo), - registeredPeripherals: make(map[string]*FTDIPeripheral), - scanChannel: make(chan struct{}), + scanEvery: scanEvery, + saved: make(map[string]PeripheralInfo), + detected: make(map[string]*FTDIPeripheral), } } +// OnArrival is the callback function when a new peripheral arrives +func (f *FTDIFinder) OnArrival(cb func(p PeripheralInfo)) { + f.onArrival = cb +} + +// OnRemoval i the callback when a peripheral goes away +func (f *FTDIFinder) OnRemoval(cb func(p PeripheralInfo)) { + f.onRemoval = cb +} + // RegisterPeripheral registers a new peripheral func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { - // Create a new FTDI peripheral - ftdiPeripheral, err := NewFTDIPeripheral(peripheralData) - if err != nil { - return "", fmt.Errorf("unable to create the FTDI peripheral: %v", err) - } - // Register it in the finder - f.registeredPeripherals[peripheralData.SerialNumber] = ftdiPeripheral - log.Trace().Any("periph", &ftdiPeripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created") + f.mu.Lock() + defer f.mu.Unlock() - // Emit the event to the front + f.saved[peripheralData.SerialNumber] = peripheralData + + // 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) + } + + // Emits the event in the hardware runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) - // Peripheral created, connect it - err = ftdiPeripheral.Connect(ctx) - if err != nil { - log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral") - return peripheralData.SerialNumber, nil - } - // Peripheral connected, activate it - err = ftdiPeripheral.Activate(ctx) - if err != nil { - log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the peripheral") - return peripheralData.SerialNumber, nil - } - // Peripheral activated return peripheralData.SerialNumber, nil } // UnregisterPeripheral unregisters an existing peripheral -func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID string) error { - peripheral, registered := f.registeredPeripherals[peripheralID] - if registered { +func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error { + f.mu.Lock() + defer f.mu.Unlock() + + if peripheral, detected := f.detected[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 } + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated) // Disconnecting peripheral err = peripheral.Disconnect() if err != nil { - return err + log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral") + return nil } - - delete(f.registeredPeripherals, peripheralID) - - // Emit the event to the front - runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheral.info) + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) } + delete(f.saved, peripheralData.SerialNumber) + runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) + return nil } @@ -110,18 +127,15 @@ func (f *FTDIFinder) Initialize() error { 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 <-f.findTicker.C: - // Scan the peripherals - err := f.scanPeripherals(ctx) - if err != nil { - log.Err(err).Str("file", "FTDIFinder").Msg("unable to scan FTDI peripherals") - } - case <-f.scanChannel: + case <-ticker.C: // Scan the peripherals err := f.scanPeripherals(ctx) if err != nil { @@ -135,11 +149,11 @@ func (f *FTDIFinder) Start(ctx context.Context) error { // ForceScan explicitly asks for scanning peripherals func (f *FTDIFinder) ForceScan() { - select { - case f.scanChannel <- struct{}{}: - default: - // Ignore if the channel is full or if it is closed - } + // select { + // case f.scanChannel <- struct{}{}: + // default: + // // Ignore if the channel is full or if it is closed + // } } // GetName returns the name of the driver @@ -150,25 +164,27 @@ 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 + // 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 } // SetPeripheralSettings sets the peripheral settings func (f *FTDIFinder) SetPeripheralSettings(peripheralID string, settings 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 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) + // 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 } // scanPeripherals scans the FTDI peripherals @@ -188,19 +204,19 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count)) - temporaryPeripherals := make(map[string]PeripheralInfo) + 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 + // isOpen := d.isOpen != 0 - temporaryPeripherals[sn] = PeripheralInfo{ + currentMap[sn] = PeripheralInfo{ SerialNumber: sn, Name: desc, - IsOpen: isOpen, + // IsOpen: isOpen, ProtocolName: "FTDI", } @@ -208,12 +224,63 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { C.free_ftdi_device(&d) } - log.Info().Any("peripherals", temporaryPeripherals).Msg("available FTDI peripherals") + log.Info().Any("peripherals", currentMap).Msg("available FTDI peripherals") - // Emit the peripherals changes to the front - emitPeripheralsChanges(ctx, f.foundPeripherals, temporaryPeripherals) - // Store the new peripherals list - f.foundPeripherals = temporaryPeripherals + // Detect arrivals + for sn, peripheralData := range currentMap { + if _, known := f.detected[sn]; !known { + peripheral := NewFTDIPeripheral(peripheralData) + f.detected[sn] = peripheral + + if f.onArrival != nil { + go 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) + } + } + } + + // Detect removals + for sn, oldPeripheral := range f.detected { + if _, still := currentMap[sn]; !still { + + // Properly clean the DMX device + err := oldPeripheral.Disconnect() + if err != nil { + log.Err(err).Str("sn", sn).Msg("unable to clean the FTDI peripheral after disconnection") + } + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), oldPeripheral.GetInfo(), PeripheralStatusDisconnected) + + // Delete it from the detected list + delete(f.detected, sn) + log.Info().Str("sn", sn).Str("name", oldPeripheral.GetInfo().Name).Msg("[FTDI] peripheral removed") + + // Execute the removal callback + if f.onRemoval != nil { + go f.onRemoval(oldPeripheral.GetInfo()) + } + } + } return nil } @@ -221,29 +288,26 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { func (f *FTDIFinder) WaitStop() error { log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...") - // Stop the ticker - f.findTicker.Stop() - // Close the channel - close(f.scanChannel) + // 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.registeredPeripherals { + // err := registeredPeripheral.WaitStop() + // if err != nil { + // errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err)) + // } + // } // Wait for goroutines to stop f.wg.Wait() // Returning errors - if len(errs) > 0 { - return errors.Join(errs...) - } + // if len(errs) > 0 { + // return errors.Join(errs...) + // } log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped") return nil } diff --git a/hardware/FTDIPeripheral.go b/hardware/FTDIPeripheral.go index c8ed185..8debace 100644 --- a/hardware/FTDIPeripheral.go +++ b/hardware/FTDIPeripheral.go @@ -2,14 +2,12 @@ package hardware import ( "context" - _ "embed" "sync" "unsafe" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/wailsapp/wails/v2/pkg/runtime" ) /* @@ -28,12 +26,12 @@ type FTDIPeripheral struct { } // NewFTDIPeripheral creates a new FTDI peripheral -func NewFTDIPeripheral(info PeripheralInfo) (*FTDIPeripheral, error) { +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, - }, nil + } } // Connect connects the FTDI peripheral @@ -47,13 +45,11 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error { p.dmxSender = C.dmx_create() // Connect the FTDI - runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "connecting") 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(PeripheralStatus), p.info, "disconnected") 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'") + return errors.Errorf("unable to connect '%s'", p.info.SerialNumber) } p.wg.Add(1) @@ -63,10 +59,7 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error { _ = p.Disconnect() }() - // Send deactivated state (connected but not activated for now) - runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "deactivated") log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully") - return nil } @@ -79,6 +72,9 @@ func (p *FTDIPeripheral) Disconnect() error { // Destroy the dmx sender C.dmx_destroy(p.dmxSender) + + // Reset the pointer to the peripheral + p.dmxSender = nil return nil } @@ -93,7 +89,6 @@ func (p *FTDIPeripheral) Activate(ctx context.Context) error { err := C.dmx_activate(p.dmxSender) if err != C.DMX_OK { - runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "deactivated") return errors.Errorf("unable to activate the DMX sender!") } @@ -101,10 +96,7 @@ 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(PeripheralStatus), p.info, "activated") - log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device activated successfully") - return nil } @@ -123,7 +115,6 @@ func (p *FTDIPeripheral) Deactivate(ctx context.Context) error { } log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully") - return nil } diff --git a/hardware/OS2LPeripheral.go b/hardware/OS2LPeripheral.go index 07e69a0..4bc9703 100644 --- a/hardware/OS2LPeripheral.go +++ b/hardware/OS2LPeripheral.go @@ -30,9 +30,9 @@ func NewOS2LPeripheral(peripheralData PeripheralInfo) (*OS2LPeripheral, error) { func (p *OS2LPeripheral) Connect(ctx context.Context) error { log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected") go func() { - runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "connecting") + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "connecting") time.Sleep(5 * time.Second) - runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "disconnected") + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "disconnected") }() return nil } diff --git a/hardware/hardware.go b/hardware/hardware.go index cbc841d..466f4b4 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -7,21 +7,30 @@ import ( "sync" "github.com/rs/zerolog/log" - "github.com/wailsapp/wails/v2/pkg/runtime" ) // PeripheralEvent is trigger by the finders when the scan is complete type PeripheralEvent string +// PeripheralStatus is the peripheral status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED) +type PeripheralStatus 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" - // PeripheralStatus is triggered when a peripheral status has been updated (disconnected - connecting - connected) - PeripheralStatus PeripheralEvent = "PERIPHERAL_STATUS" - // debounceDuration = 500 * time.Millisecond + // PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - connected) + PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS" + // 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" ) // HardwareManager is the class who manages the hardware @@ -45,13 +54,19 @@ func NewHardwareManager() *HardwareManager { // Start starts to find new peripheral events func (h *HardwareManager) Start(ctx context.Context) error { - // Initialize all the finders + // Initialize all the finders and their callback functions for finderName, finder := range h.finders { err := finder.Initialize() if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder") return err } + finder.OnArrival(func(p PeripheralInfo) { + runtime.EventsEmit(ctx, string(PeripheralArrival), p) + }) + finder.OnRemoval(func(p PeripheralInfo) { + runtime.EventsEmit(ctx, string(PeripheralRemoval), p) + }) err = finder.Start(ctx) if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder") @@ -105,27 +120,6 @@ func (h *HardwareManager) Scan() error { } } -// emitPeripheralsChanges compares the old and new peripherals to determine which ones have been added or removed. -func emitPeripheralsChanges(ctx context.Context, oldPeripherals map[string]PeripheralInfo, newPeripherals map[string]PeripheralInfo) { - log.Trace().Any("oldList", oldPeripherals).Any("newList", newPeripherals).Msg("emitting peripherals changes to the front") - - // Identify removed peripherals: present in the old list but not in the new list - for oldPeriphName := range oldPeripherals { - if _, exists := newPeripherals[oldPeriphName]; !exists { - runtime.EventsEmit(ctx, string(PeripheralRemoval), oldPeripherals[oldPeriphName]) - log.Trace().Str("file", "hardware").Str("event", string(PeripheralRemoval)).Msg("emit peripheral removal event") - } - } - - // Identify added peripherals: present in the new list but not in the old list - for newPeriphName := range newPeripherals { - if _, exists := oldPeripherals[newPeriphName]; !exists { - runtime.EventsEmit(ctx, string(PeripheralArrival), newPeripherals[newPeriphName]) - log.Trace().Str("file", "hardware").Str("event", string(PeripheralArrival)).Msg("emit peripheral arrival event") - } - } -} - // WaitStop stops the hardware manager func (h *HardwareManager) WaitStop() error { log.Trace().Str("file", "hardware").Msg("closing the hardware manager") diff --git a/hardware/interfaces.go b/hardware/interfaces.go index 039cf13..d48b3fe 100644 --- a/hardware/interfaces.go +++ b/hardware/interfaces.go @@ -6,7 +6,7 @@ import "context" 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 + Disconnect() 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 @@ -18,21 +18,25 @@ type Peripheral interface { // 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 - IsOpen bool // Open flag for peripheral connection - 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 + // 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 } // 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, string) error // Unregisters an existing peripheral + 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 diff --git a/peripherals.go b/peripherals.go index e128c1d..91ae570 100644 --- a/peripherals.go +++ b/peripherals.go @@ -65,21 +65,21 @@ func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settin } // RemovePeripheral removes a peripheral from the project -func (a *App) RemovePeripheral(protocolName string, peripheralID string) error { +func (a *App) RemovePeripheral(peripheralData hardware.PeripheralInfo) error { // Unregister the peripheral from the finder - f, err := a.hardwareManager.GetFinder(protocolName) + f, err := a.hardwareManager.GetFinder(peripheralData.ProtocolName) if err != nil { - log.Err(err).Str("file", "peripherals").Str("protocolName", protocolName).Msg("unable to find the finder") + log.Err(err).Str("file", "peripherals").Str("protocolName", peripheralData.ProtocolName).Msg("unable to find the finder") return fmt.Errorf("unable to find the finder") } - err = f.UnregisterPeripheral(a.ctx, peripheralID) + err = f.UnregisterPeripheral(a.ctx, peripheralData) if err != nil { - log.Err(err).Str("file", "peripherals").Str("peripheralID", peripheralID).Msg("unable to unregister this peripheral") + log.Err(err).Str("file", "peripherals").Str("peripheralID", peripheralData.SerialNumber).Msg("unable to unregister this peripheral") return fmt.Errorf("unable to unregister this peripheral") } // Remove the peripheral ID from the project - delete(a.projectInfo.PeripheralsInfo, peripheralID) - log.Info().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("peripheral removed from 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 } diff --git a/project.go b/project.go index 0a249ab..af7a157 100644 --- a/project.go +++ b/project.go @@ -151,7 +151,7 @@ func (a *App) CloseCurrentProject() error { if err != nil { return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err) } - err = hostFinder.UnregisterPeripheral(a.ctx, key) + err = hostFinder.UnregisterPeripheral(a.ctx, value) if err != nil { return fmt.Errorf("unable to unregister the peripheral S/N '%s': %w", key, err) }