diff --git a/.gitignore b/.gitignore
index a83910e..8195664 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,6 @@ frontend/wailsjs
*.exe
*.o
*.rc
+*.dll
+*.dll.a
frontend/public
\ No newline at end of file
diff --git a/app.go b/app.go
index 8708e62..67b9289 100644
--- a/app.go
+++ b/app.go
@@ -24,7 +24,6 @@ type App struct {
wmiMutex sync.Mutex // Avoid some WMI operations at the same time
projectInfo ProjectInfo // The project information structure
projectSave string // The file name of the project
- projectCancel context.CancelFunc // The project cancel function
}
// NewApp creates a new App application struct
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index b7d7ba7..c17324c 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -7,66 +7,50 @@
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';
- import { generateToast, showInformation, needProjectSave, peripherals } from './stores';
- import { SaveProject } from '../wailsjs/go/main/App.js';
- import { construct_svelte_component } from 'svelte/internal';
- import { EventsOn } from '../wailsjs/runtime'
- import { CreateProject } from "../wailsjs/go/main/App";
+ import { generateToast, showInformation, needProjectSave, projectsList } from './stores.js';
+ import { GetProjects, CreateProject, OpenProjectFromDisk, SaveProject } from '../wailsjs/go/main/App.js';
import { WindowSetTitle } from "../wailsjs/runtime/runtime"
- import { get } from "svelte/store"
import ToastNotification from './components/General/ToastNotification.svelte';
+ import { onMount, onDestroy } from 'svelte'
+ import { destroyRuntimeEvents, initRuntimeEvents } from './runtime-events.js'
- // Handle the event when a new peripheral is detected
- EventsOn('PERIPHERAL_ARRIVAL', function(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}
- })
- console.log("Hardware has been added to the system");
- generateToast('info', 'bxs-hdd', $_("peripheralArrivalToast") + ' ' + peripheralInfo.Name + '')
- })
-
- // Handle the event when a peripheral is removed from the system
- EventsOn('PERIPHERAL_REMOVAL', function(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
- return {...storedPeripherals}
+ function initializeNewProject(){
+ // Instanciate a new project
+ CreateProject().then(() => {
+ // Project created, we set the needSave flag to true (not already saved)
+ needProjectSave.set(true)
+ }).catch((error) => {
+ console.error(`Unable to create the project: ${error}`)
+ generateToast('danger', 'bx-error', $_("projectCreateErrorToast"))
})
- generateToast('warning', 'bxs-hdd', $_("peripheralRemovalToast") + ' ' + peripheralInfo.Name + '')
+ }
+
+ // Initialize runtime events at startup
+ onMount(() => {
+ initRuntimeEvents()
+
+ // Handle window shortcuts
+ document.addEventListener('keydown', function(event) {
+ // Check the CTRL+S keys
+ if ((event.ctrlKey || event.metaKey) && event.key === 's') {
+ // Avoid the natural behaviour
+ event.preventDefault();
+ // Save the current project
+ saveProject()
+ }
+ });
+
+ // Initialize a new project
+ initializeNewProject()
})
- // Handle the event when a peripheral status is updated
- EventsOn('PERIPHERAL_STATUS', function(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,
- },
- }})
+ // Destroy runtime events at shutdown
+ onDestroy(() => {
+ destroyRuntimeEvents()
})
// Set the window title
@@ -92,23 +76,41 @@
})
}
- // Handle window shortcuts
- document.addEventListener('keydown', function(event) {
- // Check the CTRL+S keys
- if ((event.ctrlKey || event.metaKey) && event.key === 's') {
- // Avoid the natural behaviour
- event.preventDefault();
- // Save the current project
- saveProject()
- }
- });
+ // Open the selected project
+ function openSelectedProject(event){
+ let selectedOption = event.detail.key
+ // Open the selected project
+ OpenProjectFromDisk(selectedOption).then(() => {
+ // Project opened, we set the needSave flag to false (already saved)
+ needProjectSave.set(false)
+ }).catch((error) => {
+ console.error(`Unable to open the project: ${error}`)
+ generateToast('danger', 'bx-error', $_("projectOpenErrorToast"))
+ })
+ }
+
+ // Refresh the projects list
+ let choices = new Map()
+ function loadProjectsList(){
+ GetProjects().then((projects) => {
+ choices = new Map(projects.map(item => [item.Save, item.Name]));
+ $projectsList = projects
+ }).catch((error) => {
+ console.error(`Unable to get the projects list: ${error}`)
+ generateToast('danger', 'bx-error', $_("projectsLoadErrorToast"))
+ })
+ }
+
+
+
+
{#if $needProjectSave}
-
+
{/if}
diff --git a/frontend/src/components/General/RoundDropdownList.svelte b/frontend/src/components/General/RoundDropdownList.svelte
new file mode 100644
index 0000000..80f618f
--- /dev/null
+++ b/frontend/src/components/General/RoundDropdownList.svelte
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+ {#if (operationalStatus !== undefined)}
+
+
+ {/if}
+
+
+ {#each Array.from(choices) as [key, value]}
+
handleclick({key})}>{value}
+ {/each}
+
+
+
+
\ No newline at end of file
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/components/Settings/Settings.svelte b/frontend/src/components/Settings/Settings.svelte
index a2e7b48..9625ecb 100644
--- a/frontend/src/components/Settings/Settings.svelte
+++ b/frontend/src/components/Settings/Settings.svelte
@@ -1,109 +1,16 @@
-
-
-
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 034c3f3..70e0647 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -1,8 +1,9 @@
import App from './App.svelte';
import { WindowSetTitle } from "../wailsjs/runtime/runtime"
+import { _ } from 'svelte-i18n'
-import {showInformation, needProjectSave} from './stores.js';
+import {messages, showInformation, needProjectSave} from './stores.js';
// Load dictionaries
import { addMessages, init } from 'svelte-i18n';
diff --git a/frontend/src/runtime-events.js b/frontend/src/runtime-events.js
new file mode 100644
index 0000000..59a290b
--- /dev/null
+++ b/frontend/src/runtime-events.js
@@ -0,0 +1,169 @@
+import { EventsOn, EventsOff } from "../wailsjs/runtime/runtime.js"
+import { peripherals, 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){
+ // 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 + '')
+}
+
+// Peripheral is removed from the system
+function peripheralRemoval (peripheralInfo){
+ // If not exists, add it to the map
+ // isDetected key to false
+
+ peripherals.update((storedPeripherals) => {
+ return {
+ ...storedPeripherals,
+ [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) {
+ // 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){
+ // Store project information
+ showInformation.set(showInfo)
+
+ console.log("Project has been opened");
+ generateToast('info', 'bx-folder-open', get(_)("projectOpenedToast") + ' ' + showInfo.Name + '')
+}
+
+// Unload the hardware from the project
+function unloadPeripheral (peripheralInfo) {
+ // If not exists, add it to the map
+ // isSaved key to false
+
+ 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
+
+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 peripheral is removed from the system
+ EventsOn('PERIPHERAL_REMOVAL', peripheralRemoval)
+
+ // Handle the event when a peripheral status is updated
+ EventsOn('PERIPHERAL_STATUS', peripheralUpdateStatus)
+
+ // Handle the event when a new project need to be loaded
+ EventsOn('LOAD_PROJECT', loadProject)
+
+ // Handle a peripheral loaded in the project
+ EventsOn('LOAD_PERIPHERAL', loadPeripheral)
+
+ // Handle a peripheral unloaded from the project
+ EventsOn('UNLOAD_PERIPHERAL', unloadPeripheral)
+}
+
+export function destroyRuntimeEvents(){
+ if (!initialized) return
+ initialized = false
+
+ // Handle the event when a new peripheral is detected
+ EventsOff('PERIPHERAL_ARRIVAL')
+
+ // Handle the event when a peripheral is removed from the system
+ EventsOff('PERIPHERAL_REMOVAL')
+
+ // Handle the event when a peripheral 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
+ EventsOff('LOAD_PERIPHERAL')
+
+ // Handle a peripheral unloaded from the project
+ EventsOff('UNLOAD_PERIPHERAL')
+}
\ No newline at end of file
diff --git a/frontend/src/stores.js b/frontend/src/stores.js
index a060938..02aeb53 100644
--- a/frontend/src/stores.js
+++ b/frontend/src/stores.js
@@ -10,13 +10,12 @@ export let showInformation = writable({})
// Toasts notifications
export let messages = writable([])
export function generateToast(type, icon, text){
- messages.update((value) => {
- value.push( { id: Date.now(), type: type, icon: icon, text: text } )
- return value.slice(-5)
- })
+ messages.update((value) => {
+ value.push( { id: Date.now(), type: type, icon: icon, text: text } )
+ return value.slice(-5)
+ })
}
-
// Application colors
export const colors = writable({
first: "#1B262C",
@@ -35,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 3a748e2..2fc2e5b 100644
--- a/hardware/FTDIFinder.go
+++ b/hardware/FTDIFinder.go
@@ -2,8 +2,6 @@ package hardware
import (
"context"
- _ "embed"
- "errors"
"fmt"
goRuntime "runtime"
"sync"
@@ -11,6 +9,7 @@ import (
"unsafe"
"github.com/rs/zerolog/log"
+ "github.com/wailsapp/wails/v2/pkg/runtime"
)
/*
@@ -23,65 +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()
- // 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")
+ 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)
}
- // 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")
- }
- // Peripheral activated
+
+ // Emits the event in the hardware
+ runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData)
+
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
}
+ runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
}
- delete(f.registeredPeripherals, peripheralID)
+ delete(f.saved, peripheralData.SerialNumber)
+ runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData)
+
return nil
}
@@ -100,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 {
@@ -123,13 +147,13 @@ func (f *FTDIFinder) Start(ctx context.Context) error {
return nil
}
-// ForceScan explicily asks for scanning peripherals
+// 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
@@ -140,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
@@ -178,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",
}
@@ -198,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
}
@@ -211,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 1c22062..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,12 +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 {
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)
@@ -62,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
}
@@ -78,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
}
@@ -95,13 +92,11 @@ func (p *FTDIPeripheral) Activate(ctx context.Context) error {
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(PeripheralStatus), p.info, "activated")
-
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device activated successfully")
-
return nil
}
@@ -120,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
new file mode 100644
index 0000000..af7a157
--- /dev/null
+++ b/project.go
@@ -0,0 +1,248 @@
+package main
+
+import (
+ "dmxconnect/hardware"
+ "fmt"
+
+ "github.com/rs/zerolog/log"
+
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/wailsapp/wails/v2/pkg/runtime"
+ "gopkg.in/yaml.v2"
+)
+
+const (
+ projectsDirectory = "projects" // The directory were are stored all the projects
+ avatarsDirectory = "frontend/public" // The directory were are stored all the avatars
+ projectExtension = ".dmxproj" // The extension of a DMX Connect project
+)
+
+// GetProjects gets all the projects in the projects directory
+func (a *App) GetProjects() ([]ProjectMetaData, error) {
+ projects := []ProjectMetaData{}
+
+ f, err := os.Open(projectsDirectory)
+ if err != nil {
+ log.Err(err).Str("file", "project").Msg("unable to open the projects directory")
+ return nil, fmt.Errorf("unable to open the projects directory: %v", err)
+ }
+ log.Trace().Str("file", "project").Str("projectsDirectory", projectsDirectory).Msg("projects directory opened")
+
+ files, err := f.Readdir(0)
+ if err != nil {
+ log.Err(err).Str("file", "project").Msg("unable to read the projects directory")
+ return nil, fmt.Errorf("unable to read the projects directory: %v", err)
+ }
+ log.Trace().Str("file", "project").Any("projectsFiles", files).Msg("project files got")
+
+ for _, fileInfo := range files {
+ // Open the file and get the show name
+ fileData, err := os.ReadFile(filepath.Join(projectsDirectory, fileInfo.Name()))
+ if err != nil {
+ log.Warn().Str("file", "project").Str("projectFile", fileInfo.Name()).Msg("unable to open the project file")
+ continue
+ }
+ log.Trace().Str("file", "project").Str("projectFile", fileInfo.Name()).Any("fileData", fileData).Msg("project file read")
+
+ projectObject := ProjectInfo{}
+ err = yaml.Unmarshal(fileData, &projectObject)
+ if err != nil {
+ log.Warn().Str("file", "project").Str("projectFile", fileInfo.Name()).Msg("project has invalid format")
+ continue
+ }
+ log.Trace().Str("file", "project").Str("projectFile", fileInfo.Name()).Msg("project file unmarshalled")
+
+ // Add the SaveFile property
+ projects = append(projects, ProjectMetaData{
+ Name: projectObject.ShowInfo.Name,
+ Save: fileInfo.Name(),
+ })
+ }
+ log.Info().Str("file", "project").Any("projectsList", projects).Msg("got the projects list")
+ return projects, nil
+}
+
+// CreateProject creates a new blank project
+func (a *App) CreateProject() error {
+
+ // Create new project information
+ date := time.Now()
+ projectInfo := ProjectInfo{
+ ShowInfo{
+ Name: "My new show",
+ Date: fmt.Sprintf("%04d-%02d-%02dT%02d:%02d", date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute()),
+ Avatar: "appicon.png",
+ Comments: "Write your comments here",
+ },
+ make(map[string]hardware.PeripheralInfo),
+ }
+
+ // The project isn't saved for now
+ a.projectSave = ""
+
+ return a.OpenProject(projectInfo)
+}
+
+// OpenProjectFromDisk opens a project based on its filename
+func (a *App) OpenProjectFromDisk(projectFile string) error {
+ // Open the project file
+ projectPath := filepath.Join(projectsDirectory, projectFile)
+ log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project path is created")
+ content, err := os.ReadFile(projectPath)
+ if err != nil {
+ log.Err(err).Str("file", "project").Str("projectFile", projectFile).Msg("Unable to read the project file")
+ return fmt.Errorf("unable to read the project file: %v", err)
+ }
+ log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project file read")
+
+ // Import project data structure
+ projectInfo := ProjectInfo{}
+ err = yaml.Unmarshal(content, &projectInfo)
+ if err != nil {
+ log.Err(err).Str("file", "project").Str("projectFile", projectFile).Msg("Unable to get the project information")
+ return fmt.Errorf("unable to get the project information: %v", err)
+ }
+ log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project information got")
+
+ // The project is saved
+ a.projectSave = projectFile
+
+ return a.OpenProject(projectInfo)
+}
+
+// OpenProject opens a project based on its information
+func (a *App) OpenProject(projectInfo ProjectInfo) error {
+ // Close the current project
+ err := a.CloseCurrentProject()
+ if err != nil {
+ return fmt.Errorf("unable to close project: %w", err)
+ }
+
+ // Open the project
+ a.projectInfo = projectInfo
+
+ // 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 {
+ hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName)
+ if err != nil {
+ return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err)
+ }
+ _, err = hostFinder.RegisterPeripheral(a.ctx, value)
+ if err != nil {
+ return fmt.Errorf("unable to register the peripheral S/N '%s'", key)
+ }
+ }
+ return nil
+}
+
+// CloseCurrentProject closes the current project
+func (a *App) CloseCurrentProject() error {
+ // Unregistrer all peripherals of the project
+ projectPeripherals := a.projectInfo.PeripheralsInfo
+ for key, value := range projectPeripherals {
+ hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName)
+ if err != nil {
+ return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err)
+ }
+ err = hostFinder.UnregisterPeripheral(a.ctx, value)
+ if err != nil {
+ return fmt.Errorf("unable to unregister the peripheral S/N '%s': %w", key, err)
+ }
+ }
+
+ // Unload project info in the front
+ return nil
+}
+
+// ChooseAvatarPath opens a filedialog to choose the show avatar
+func (a *App) ChooseAvatarPath() (string, error) {
+ // Open the file dialog box
+ filePath, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
+ Title: "Choose your show avatar",
+ Filters: []runtime.FileFilter{
+ {
+ DisplayName: "Images",
+ Pattern: "*.png;*.jpg;*.jpeg",
+ },
+ },
+ })
+ if err != nil {
+ log.Err(err).Str("file", "project").Msg("unable to open the avatar dialog")
+ return "", err
+ }
+ log.Debug().Str("file", "project").Msg("avatar dialog is opened")
+ // Copy the avatar to the application avatars path
+ avatarPath := filepath.Join(avatarsDirectory, filepath.Base(filePath))
+ log.Trace().Str("file", "project").Str("avatarPath", avatarPath).Msg("avatar path is created")
+ _, err = copy(filePath, avatarPath)
+ if err != nil {
+ log.Err(err).Str("file", "project").Str("avatarsDirectory", avatarsDirectory).Str("fileBase", filepath.Base(filePath)).Msg("unable to copy the avatar file")
+ return "", err
+ }
+ log.Info().Str("file", "project").Str("avatarFileName", filepath.Base(filePath)).Msg("got the new avatar file")
+ return filepath.Base(filePath), nil
+}
+
+// UpdateShowInfo updates the show information
+func (a *App) UpdateShowInfo(showInfo ShowInfo) {
+ a.projectInfo.ShowInfo = showInfo
+ log.Info().Str("file", "project").Any("showInfo", showInfo).Msg("show information was updated")
+}
+
+// SaveProject saves the project
+func (a *App) SaveProject() (string, error) {
+ // If there is no save file, create a new one with the show name
+ if a.projectSave == "" {
+ date := time.Now()
+ a.projectSave = fmt.Sprintf("%04d%02d%02d%02d%02d%02d%s", date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), projectExtension)
+ log.Debug().Str("file", "project").Str("newProjectSave", a.projectSave).Msg("projectSave is null, getting a new one")
+ }
+ data, err := yaml.Marshal(a.projectInfo)
+ if err != nil {
+ log.Err(err).Str("file", "project").Any("projectInfo", a.projectInfo).Msg("unable to format the project information")
+ return "", err
+ }
+ log.Trace().Str("file", "project").Any("projectInfo", a.projectInfo).Msg("projectInfo has been marshalled")
+ // Create the project directory if not exists
+ err = os.MkdirAll(projectsDirectory, os.ModePerm)
+ if err != nil {
+ log.Err(err).Str("file", "project").Str("projectsDirectory", projectsDirectory).Msg("unable to create the projects directory")
+ return "", err
+ }
+ log.Trace().Str("file", "project").Str("projectsDirectory", projectsDirectory).Msg("projects directory has been created")
+
+ err = os.WriteFile(filepath.Join(projectsDirectory, a.projectSave), data, os.ModePerm)
+ if err != nil {
+ log.Err(err).Str("file", "project").Str("projectsDirectory", projectsDirectory).Str("projectSave", a.projectSave).Msg("unable to save the project")
+ return "", err
+ }
+ log.Info().Str("file", "project").Str("projectFileName", a.projectSave).Msg("project has been saved")
+ return a.projectSave, nil
+}
+
+// ShowInfo defines the information of the show
+type ShowInfo struct {
+ Name string `yaml:"name"`
+ Date string `yaml:"date"`
+ Avatar string `yaml:"avatar"`
+ Comments string `yaml:"comments"`
+}
+
+// ProjectMetaData defines all the minimum information for a lighting project
+type ProjectMetaData struct {
+ Name string // Show name
+ Save string // The save file of the project
+}
+
+// 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
+}