diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4396ea7..3cad51b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -58,6 +58,9 @@
"streambuf": "cpp",
"thread": "cpp",
"typeinfo": "cpp",
- "variant": "cpp"
+ "variant": "cpp",
+ "queue": "cpp",
+ "ranges": "cpp",
+ "text_encoding": "cpp"
}
}
\ No newline at end of file
diff --git a/app.go b/app.go
index 36d1024..8708e62 100644
--- a/app.go
+++ b/app.go
@@ -16,21 +16,24 @@ import (
// App struct
type App struct {
- ctx context.Context
- cancelFunc context.CancelFunc
+ ctx context.Context
+ cancelFunc context.CancelFunc
+ wait sync.WaitGroup
+
hardwareManager *hardware.HardwareManager // For managing all the hardware
wmiMutex sync.Mutex // Avoid some WMI operations at the same time
projectInfo ProjectInfo // The project information structure
projectSave string // The file name of the project
+ projectCancel context.CancelFunc // The project cancel function
}
// NewApp creates a new App application struct
func NewApp() *App {
// Create a new hadware manager
hardwareManager := hardware.NewHardwareManager()
- hardwareManager.RegisterFinder(hardware.NewMIDIFinder(5 * time.Second))
+ // hardwareManager.RegisterFinder(hardware.NewMIDIFinder(5 * time.Second))
hardwareManager.RegisterFinder(hardware.NewFTDIFinder(5 * time.Second))
- hardwareManager.RegisterFinder(hardware.NewOS2LFinder())
+ // hardwareManager.RegisterFinder(hardware.NewOS2LFinder())
return &App{
hardwareManager: hardwareManager,
projectSave: "",
@@ -44,11 +47,17 @@ func NewApp() *App {
// so we can call the runtime methods
func (a *App) onStartup(ctx context.Context) {
a.ctx, a.cancelFunc = context.WithCancel(ctx)
- err := a.hardwareManager.Start(a.ctx)
- if err != nil {
- log.Err(err).Str("file", "app").Msg("unable to start the hardware manager")
- return
- }
+
+ // Starting the hardware manager
+ a.wait.Add(1)
+ go func() {
+ defer a.wait.Done()
+ err := a.hardwareManager.Start(a.ctx)
+ if err != nil {
+ log.Err(err).Str("file", "app").Msg("unable to start the hardware manager")
+ return
+ }
+ }()
}
// onReady is called when the DOM is ready
@@ -69,10 +78,13 @@ func (a *App) onShutdown(ctx context.Context) {
log.Trace().Str("file", "app").Msg("app is closing")
// Explicitly close the context
a.cancelFunc()
- err := a.hardwareManager.Stop()
- if err != nil {
- log.Err(err).Str("file", "app").Msg("unable to stop the hardware manager")
- }
+ // Wait for application to close properly
+ a.hardwareManager.WaitStop()
+ // a.cancelFunc()
+ // err := a.hardwareManager.Stop()
+ // if err != nil {
+ // log.Err(err).Str("file", "app").Msg("unable to stop the hardware manager")
+ // }
return
}
diff --git a/build.bat b/build.bat
new file mode 100644
index 0000000..b10b859
--- /dev/null
+++ b/build.bat
@@ -0,0 +1,55 @@
+@echo off
+setlocal
+
+echo ============================================
+echo [INFO] Starting Wails build script
+echo ============================================
+
+rem Détection du mode (par défaut : build)
+set "MODE=build"
+if /i "%~1"=="-dev" set "MODE=dev"
+
+rem 1️⃣ Essayer de récupérer le dernier tag
+for /f "tokens=*" %%i in ('git describe --tags --abbrev=0 2^>nul') do set "GIT_TAG=%%i"
+
+rem 2️⃣ Si pas de tag, utiliser le hash du commit
+if "%GIT_TAG%"=="" (
+ for /f "tokens=*" %%i in ('git rev-parse --short HEAD 2^>nul') do set "GIT_TAG=%%i"
+)
+
+rem 3️⃣ Si Git n’est pas dispo, mettre "unknown"
+if "%GIT_TAG%"=="" set "GIT_TAG=unknown"
+
+echo [INFO] Git version detected: %GIT_TAG%
+
+
+echo [INFO] Mode selectionne : %MODE%
+
+echo [INFO] Moving to the C++ folder...
+cd /d "%~dp0hardware\cpp" || (
+ echo [ERROR] Impossible d'accéder à hardware\cpp
+ exit /b 1
+)
+
+echo [INFO] Compiling C++ libraries...
+call generate.bat || (
+ echo [ERROR] Échec de la compilation C++
+ exit /b 1
+)
+
+echo [INFO] Returning to project root...
+cd /d "%~dp0" || exit /b 1
+
+if /i "%MODE%"=="dev" (
+ echo [INFO] Launching Wails in DEV mode...
+ wails dev
+) else (
+ echo [INFO] Building Wails application...
+ wails build -o "dmxconnect-%GIT_TAG%.exe"
+)
+
+echo ============================================
+echo [SUCCESS] Done!
+echo ============================================
+
+endlocal
\ No newline at end of file
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index 032ecde..b7d7ba7 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -54,9 +54,24 @@
generateToast('warning', 'bxs-hdd', $_("peripheralRemovalToast") + ' ' + peripheralInfo.Name + '')
})
+ // 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,
+ },
+ }})
+ })
+
// Set the window title
$: {
- WindowSetTitle("DMXConnect - " + $showInformation.Name + ($needProjectSave ? " (unsaved)" : ""))
+ WindowSetTitle("DMXConnect - " + $showInformation.Name + ($needProjectSave ? " (" + $_("unsavedProjectFlag") + ")" : ""))
}
let selectedMenu = "settings"
@@ -77,12 +92,6 @@
})
}
- // Instanciate a new project
- CreateProject().then((showInfo) => {
- showInformation.set(showInfo)
- $needProjectSave = true
- })
-
// Handle window shortcuts
document.addEventListener('keydown', function(event) {
// Check the CTRL+S keys
diff --git a/frontend/src/components/Settings/DeviceCard.svelte b/frontend/src/components/Settings/DeviceCard.svelte
index 85158e0..8910208 100644
--- a/frontend/src/components/Settings/DeviceCard.svelte
+++ b/frontend/src/components/Settings/DeviceCard.svelte
@@ -14,8 +14,8 @@
export let addable = false;
export let signalizable = false;
export let signalized = false;
- export let disconnected = false;
export let selected = false;
+ export let status = "disconnected";
// Emit a delete event when the device is being removed
const dispatch = createEventDispatcher();
@@ -37,11 +37,11 @@
-
-
-
{#if disconnected} {/if}{title}
+
+
+
{#if status == "disconnected" } {/if}{title}
{type} {location != '' ? "- " : ""}{location}
- {#if disconnected}
+ {#if status == "disconnected"}
Disconnected
{:else}
{line1}
@@ -50,15 +50,13 @@
-
-
-
+
+
+
-
-
\ No newline at end of file
diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte
index 415e380..0f5b2c8 100644
--- a/frontend/src/components/Settings/InputsOutputsContent.svelte
+++ b/frontend/src/components/Settings/InputsOutputsContent.svelte
@@ -5,61 +5,24 @@
import { t, _ } from 'svelte-i18n'
import { generateToast, needProjectSave, peripherals } from "../../stores";
import { get } from "svelte/store"
- import { UpdatePeripheralSettings, GetPeripheralSettings, AddOS2LPeripheral, RemovePeripheral, ConnectFTDI, ActivateFTDI, DeactivateFTDI, DisconnectFTDI, SetDeviceFTDI, AddPeripheral } from "../../../wailsjs/go/main/App";
+ import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral } from "../../../wailsjs/go/main/App";
import RoundedButton from "../General/RoundedButton.svelte";
- function ftdiConnect(){
- ConnectFTDI().then(() =>
- console.log("FTDI connected"))
- .catch((error) => {
- console.log("Error when trying to connect: " + error)
- })
- }
-
- function ftdiActivate(){
- ActivateFTDI().then(() =>
- console.log("FTDI activated"))
- .catch((error) => {
- console.log("Error when trying to activate: " + error)
- })
- }
-
- function ftdiDeactivate(){
- DeactivateFTDI().then(() =>
- console.log("FTDI deactivated"))
- .catch((error) => {
- console.log("Error when trying to deactivate: " + error)
- })
- }
-
- let sliderValue = 0
- function ftdiSetDevice(value){
- console.log("value is " + value)
- SetDeviceFTDI(value).then(() =>
- console.log("FTDI device set up"))
- .catch((error) => {
- console.log("Error when trying to set the device: " + error)
- })
- }
-
- function ftdiDisconnect(){
- DisconnectFTDI().then(() =>
- console.log("FTDI disconnected"))
- .catch((error) => {
- console.log("Error when trying to disconnect: " + error)
- })
- }
-
// Add the peripheral to the project
function addPeripheral(peripheral){
// Add the peripheral to the project (backend)
- AddPeripheral(peripheral.ProtocolName, peripheral.SerialNumber).then(() => {
- peripherals.update((value) => {
- if (value[peripheral.SerialNumber]) {
- value[peripheral.SerialNumber].isSaved = true;
- }
- return {...value}
- })
+ 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) => {
console.log("Unable to add the peripheral to the project: " + error)
@@ -96,37 +59,20 @@
})
}
- // Create the OS2L peripheral
- function createOS2L(){
- AddOS2LPeripheral().then(os2lDevice => {
- peripherals.update(currentPeriph => {
- os2lDevice.isSaved = true
- os2lDevice.isDetected = true
- currentPeriph[os2lDevice.SerialNumber] = os2lDevice
- return {...currentPeriph}
- })
- $needProjectSave = true
- generateToast('info', 'bx-signal-5', $_("os2lPeripheralCreatedToast"))
- }).catch(error => {
- console.log("Unable to add the OS2L peripheral: " + error)
- generateToast('danger', 'bx-error', $_("os2lPeripheralCreateErrorToast"))
- })
- }
-
// Select the peripheral to edit its settings
let selectedPeripheralSN = null
let selectedPeripheralSettings = {}
function selectPeripheral(peripheral){
// Load the settings array if the peripheral is detected
- if (peripheral.isDetected){
+ if (peripheral.isSaved){
GetPeripheralSettings(peripheral.ProtocolName, peripheral.SerialNumber).then((peripheralSettings) => {
selectedPeripheralSettings = peripheralSettings
+ // Select the current peripheral
+ selectedPeripheralSN = peripheral.SerialNumber
}).catch((error) => {
console.log("Unable to get the peripheral settings: " + error)
generateToast('danger', 'bx-error', $_("getPeripheralSettingsErrorToast"))
})
- // Select the current peripheral
- selectedPeripheralSN = peripheral.SerialNumber
}
}
@@ -153,7 +99,6 @@
boolean: Boolean,
}[typeof(selectedPeripheralSettings[settingName])] || (x => x)
selectedPeripheralSettings[settingName] = convert(settingValue)
- console.log(typeof(selectedPeripheralSettings[settingName]))
let peripheralProtocolName = get(peripherals)[selectedPeripheralSN].ProtocolName
UpdatePeripheralSettings(peripheralProtocolName, selectedPeripheralSN, selectedPeripheralSettings).then(()=> {
$needProjectSave = true
@@ -175,11 +120,11 @@
if(!peripheral.isSaved)
addPeripheral(peripheral)
}}
- title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/>
+ status="connected" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/>
{/if}
{/each}
{$_("projectHardwareOthersLabel")}
-
+
addPeripheral({Name: "OS2L connection", ProtocolName: "OS2L"})} text="Add an OS2L peripheral" icon="bx-plus-circle" tooltip="Configure an OS2L connection"/>
@@ -189,8 +134,8 @@
{#if savedPeripheralNumber > 0}
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isSaved}
- removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} on:click={() => selectPeripheral(peripheral)}
- disconnected={!peripheral.isDetected} title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} selected={serialNumber == selectedPeripheralSN} removable signalizable/>
+ 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}
{:else}
@@ -208,14 +153,6 @@
{:else}
{$_("projectHardwareNoSettingLabel")}
{/if}
-
-
diff --git a/frontend/src/components/Settings/Settings.svelte b/frontend/src/components/Settings/Settings.svelte
index dfcc601..a2e7b48 100644
--- a/frontend/src/components/Settings/Settings.svelte
+++ b/frontend/src/components/Settings/Settings.svelte
@@ -5,10 +5,11 @@
import DropdownList from "../General/DropdownList.svelte";
import InputsOutputsContent from "./InputsOutputsContent.svelte";
import Tab from "../General/Tab.svelte";
- import { CreateProject, GetProjects, GetProjectInfo } from "../../../wailsjs/go/main/App";
+ import { CreateProject, GetProjects, OpenProjectFromDisk } from "../../../wailsjs/go/main/App";
import { _ } from 'svelte-i18n'
import {colors} from '../../stores.js';
import { get } from "svelte/store"
+ import { EventsOn } from '../../../wailsjs/runtime'
const tabs = [
{ title: $_("projectPropertiesTab"), icon: 'bxs-info-circle', tooltip: $_("projectPropertiesTooltip"), component: ProjectPropertiesContent },
@@ -57,18 +58,28 @@
})
}
+ // Handle the event when a new project need to be loaded
+ EventsOn('LOAD_PROJECT', function(projectInfo){
+ // Store project information
+ showInformation.set(projectInfo.ShowInfo)
+
+ // Remove the saved peripherals of the current project
+ unsavePeripherals()
+
+ // Load new project peripherals
+ loadPeripherals(projectInfo.PeripheralsInfo)
+
+ console.log("Project has been opened");
+ generateToast('info', 'bx-folder-open', $_("projectOpenedToast") + ' ' + projectInfo.ShowInfo.Name + '')
+ })
+
// Open the selected project
function openSelectedProject(event){
let selectedOption = event.detail.key
// Open the selected project
- GetProjectInfo(selectedOption).then((projectInfo) => {
- $showInformation = projectInfo.ShowInfo
- // Remove the saved peripherals ofthe current project
- unsavePeripherals()
- // Load the new project peripherals
- loadPeripherals(projectInfo.PeripheralsInfo)
+ OpenProjectFromDisk(selectedOption).then(() => {
+ // Project opened, we set the needSave flag to false (already saved)
needProjectSave.set(false)
- generateToast('info', 'bx-folder-open', $_("projectOpenedToast") + ' ' + projectInfo.ShowInfo.Name + '')
}).catch((error) => {
console.error(`Unable to open the project: ${error}`)
generateToast('danger', 'bx-error', $_("projectOpenErrorToast"))
@@ -77,14 +88,17 @@
function initializeNewProject(){
// Instanciate a new project
- CreateProject().then((showInfo) => {
- $showInformation = showInfo
- // Remove the saved peripherals ofthe current project
- unsavePeripherals()
- $needProjectSave = true
- generateToast('info', 'bxs-folder-plus', $_("projectCreatedToast"))
+ 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"))
})
}
+
+ // Instantiate a new project
+ initializeNewProject()
diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json
index 7e7de71..f185eb7 100644
--- a/frontend/src/lang/en.json
+++ b/frontend/src/lang/en.json
@@ -13,6 +13,7 @@
"newProjectTooltip": "Create a new project",
"openProjectString": "Open",
"openProjectTooltip": "Open an existing project",
+ "unsavedProjectFlag": "unsaved",
"projectPropertiesTab": "Project properties",
"projectPropertiesTooltip": "The project properties",
"projectInputOutputTab": "Hardware",
@@ -57,6 +58,7 @@
"projectOpenedToast": "The project was opened:",
"projectOpenErrorToast": "Unable to open the project",
"projectCreatedToast": "The project was created",
+ "projectCreateErrorToast": "Unable to create the project",
"peripheralSettingSaveErrorToast": "Unable to save the peripheral settings",
"os2lIp": "OS2L server IP",
diff --git a/go.mod b/go.mod
index 0601c5e..f721582 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ toolchain go1.21.3
require (
github.com/mattrtaylor/go-rtmidi v0.0.0-20220428034745-af795b1c1a79
+ github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.33.0
github.com/wailsapp/wails/v2 v2.9.1
gopkg.in/yaml.v2 v2.4.0
@@ -26,7 +27,6 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
- github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect
diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go
index 4d1fc29..3a748e2 100644
--- a/hardware/FTDIFinder.go
+++ b/hardware/FTDIFinder.go
@@ -1,50 +1,89 @@
package hardware
import (
- "bufio"
"context"
_ "embed"
+ "errors"
"fmt"
- "os"
- "os/exec"
- "path/filepath"
goRuntime "runtime"
- "strconv"
- "strings"
"sync"
"time"
+ "unsafe"
"github.com/rs/zerolog/log"
)
-const (
- ftdiFinderExecutableName = "FTDI_finder.exe"
- ftdiSenderExecutableName = "FTDI_sender.exe"
-)
+/*
+#include
+#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldetectFTDI
+#include "cpp/include/detectFTDIBridge.h"
+*/
+import "C"
-// FTDIFinder represents how the protocol is defined
+// FTDIFinder manages all the FTDI peripherals
type FTDIFinder struct {
- findTicker time.Ticker // Peripherals find ticker
- peripherals map[string]Peripheral // The list of peripherals handled by this finder
- scanChannel chan struct{} // The channel to trigger a scan event
- goWait sync.WaitGroup // Check goroutines execution
+ wg sync.WaitGroup
+
+ 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
}
// NewFTDIFinder creates a new FTDI finder
func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder {
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created")
return &FTDIFinder{
- findTicker: *time.NewTicker(findPeriod),
- peripherals: make(map[string]Peripheral),
- scanChannel: make(chan struct{}),
+ findTicker: time.NewTicker(findPeriod),
+ foundPeripherals: make(map[string]PeripheralInfo),
+ registeredPeripherals: make(map[string]*FTDIPeripheral),
+ scanChannel: make(chan struct{}),
}
}
-//go:embed third-party/ftdi/detectFTDI.exe
-var finderExe []byte
+// 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")
-//go:embed third-party/ftdi/dmxSender.exe
-var senderExe []byte
+ // 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")
+ }
+ // 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
+ 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 {
+ // Deactivating peripheral
+ err := peripheral.Deactivate(ctx)
+ if err != nil {
+ return err
+ }
+ // Disconnecting peripheral
+ err = peripheral.Disconnect()
+ if err != nil {
+ return err
+ }
+ }
+ delete(f.registeredPeripherals, peripheralID)
+ return nil
+}
// Initialize initializes the FTDI finder
func (f *FTDIFinder) Initialize() error {
@@ -53,43 +92,15 @@ func (f *FTDIFinder) Initialize() error {
log.Error().Str("file", "FTDIFinder").Str("platform", goRuntime.GOOS).Msg("FTDI finder not compatible with your platform")
return fmt.Errorf("the FTDI finder is not compatible with your platform yet (%s)", goRuntime.GOOS)
}
- // Create the FTDI executables
- err := createExecutable(ftdiFinderExecutableName, finderExe)
- if err != nil {
- return err
- }
- createExecutable(ftdiSenderExecutableName, senderExe)
- if err != nil {
- return err
- }
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder initialized")
return nil
}
-// createExecutable creates and writes an executable to the temporary directory of the system
-func createExecutable(fileName string, storedFile []byte) error {
- tempFile, err := os.Create(filepath.Join(os.TempDir(), fileName))
- if err != nil {
- log.Err(err).Str("file", "FTDIFinder").Str("fileName", fileName).Msg("unable to create an FTDI executable")
- return err
- }
- log.Trace().Str("file", "FTDIFinder").Str("filePath", tempFile.Name()).Msg("FTDI executable created")
-
- // Write the embedded executable to the temp file
- if _, err := tempFile.Write(storedFile); err != nil {
- log.Err(err).Str("file", "FTDIFinder").Str("fileName", fileName).Msg("unable to write the content to an FTDI executable")
- return err
- }
- tempFile.Close()
- log.Trace().Str("file", "FTDIPeripheral").Str("fileName", fileName).Msg("FTDI executable written")
- return nil
-}
-
// Start starts the finder and search for peripherals
func (f *FTDIFinder) Start(ctx context.Context) error {
- f.goWait.Add(1)
+ f.wg.Add(1)
go func() {
- defer f.goWait.Done()
+ defer f.wg.Done()
for {
select {
case <-ctx.Done():
@@ -114,29 +125,11 @@ func (f *FTDIFinder) Start(ctx context.Context) error {
// ForceScan explicily asks for scanning peripherals
func (f *FTDIFinder) ForceScan() {
- f.scanChannel <- struct{}{}
-}
-
-// Stop stops the finder
-func (f *FTDIFinder) Stop() error {
- log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
- // Wait for goroutines to stop
- f.goWait.Wait()
- // Stop the ticker
- f.findTicker.Stop()
- // Delete the FTDI executable files
- fileToDelete := filepath.Join(os.TempDir(), ftdiFinderExecutableName)
- err := os.Remove(fileToDelete)
- if err != nil {
- log.Warn().Str("file", "FTDIFinder").Str("fileName", fileToDelete).AnErr("error", err).Msg("unable to remove the executable file")
+ select {
+ case f.scanChannel <- struct{}{}:
+ default:
+ // Ignore if the channel is full or if it is closed
}
- fileToDelete = filepath.Join(os.TempDir(), ftdiSenderExecutableName)
- err = os.Remove(fileToDelete)
- if err != nil {
- log.Warn().Str("file", "FTDIFinder").Str("fileName", fileToDelete).AnErr("error", err).Msg("unable to remove the executable file")
- }
- log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
- return nil
}
// GetName returns the name of the driver
@@ -144,88 +137,103 @@ func (f *FTDIFinder) GetName() string {
return "FTDI"
}
-// GetPeripheral gets the peripheral that correspond to the specified ID
-func (f *FTDIFinder) GetPeripheral(peripheralID string) (Peripheral, bool) {
+// GetPeripheralSettings gets the peripheral settings
+func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
// Return the specified peripheral
- peripheral := f.peripherals[peripheralID]
- if peripheral == 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, false
+ 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, true
+ return peripheral.GetSettings(), 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)
}
// scanPeripherals scans the FTDI peripherals
func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
- detectionCtx, cancel := context.WithCancel(ctx)
- defer cancel()
-
log.Trace().Str("file", "FTDIFinder").Msg("FTDI scan triggered")
- ftdiPeripherals := make(map[string]Peripheral)
+ count := int(C.get_peripherals_number())
- finder := exec.CommandContext(detectionCtx, filepath.Join(os.TempDir(), ftdiFinderExecutableName))
- log.Trace().Str("file", "FTDIFinder").Msg("has executed the FIND executable")
+ log.Info().Int("number", count).Msg("number of FTDI devices connected")
- stdout, err := finder.StdoutPipe()
- if err != nil {
- return fmt.Errorf("unable to create the stdout pipe: %s", err)
- }
- defer stdout.Close()
+ // Allocating C array
+ size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIPeripheralC{}))
+ devicesPtr := C.malloc(size)
+ defer C.free(devicesPtr)
- stderr, err := finder.StderrPipe()
- if err != nil {
- return fmt.Errorf("unable to create the stderr pipe: %s", err)
- }
- defer stderr.Close()
+ devices := (*[1 << 20]C.FTDIPeripheralC)(devicesPtr)[:count:count]
- err = finder.Start()
- if err != nil {
- return fmt.Errorf("unable to find FTDI peripherals: %s", err)
- }
+ C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
- scannerErr := bufio.NewScanner(stderr)
- for scannerErr.Scan() {
- return fmt.Errorf("unable to find FTDI peripherals: %s", scannerErr.Text())
- }
+ temporaryPeripherals := make(map[string]PeripheralInfo)
- scanner := bufio.NewScanner(stdout)
- for scanner.Scan() {
- peripheralString := scanner.Text()
- // The program output is like '0:1:2' where 0 is the location, 1 is the S/N and 2 is the name
- peripheralInfo := strings.Split(peripheralString, ":")
+ for i := 0; i < count; i++ {
+ d := devices[i]
- log.Trace().Str("file", "FTDIFinder").Str("scannedString", peripheralString).Str("peripheralName", peripheralInfo[2]).Str("peripheralSN", peripheralInfo[1]).Msg("new FTDI peripheral detected")
- // Convert the location to an integer
- location, err := strconv.Atoi(peripheralInfo[0])
- if err != nil {
- log.Warn().Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("no location provided for this FTDI peripheral")
- location = -1
+ sn := C.GoString(d.serialNumber)
+ desc := C.GoString(d.description)
+ isOpen := d.isOpen != 0
+
+ temporaryPeripherals[sn] = PeripheralInfo{
+ SerialNumber: sn,
+ Name: desc,
+ IsOpen: isOpen,
+ ProtocolName: "FTDI",
}
- // Add the peripheral to the temporary list
- peripheral, err := NewFTDIPeripheral(peripheralInfo[2], peripheralInfo[1], location)
- if err != nil {
- return fmt.Errorf("unable to create the FTDI peripheral: %v", err)
- }
- log.Trace().Any("periph", &peripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("has been created")
- ftdiPeripherals[peripheralInfo[1]] = peripheral
- log.Trace().Any("periph", ftdiPeripherals).Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("successfully added the FTDI peripheral to the finder")
+ // Free C memory
+ C.free_ftdi_device(&d)
}
+
+ log.Info().Any("peripherals", temporaryPeripherals).Msg("available FTDI peripherals")
+
// Emit the peripherals changes to the front
- emitPeripheralsChanges(ctx, f.peripherals, ftdiPeripherals)
+ emitPeripheralsChanges(ctx, f.foundPeripherals, temporaryPeripherals)
// Store the new peripherals list
- f.peripherals = ftdiPeripherals
+ f.foundPeripherals = temporaryPeripherals
return nil
}
-// CreatePeripheral is not implemented here
-func (f *FTDIFinder) CreatePeripheral(context.Context) (Peripheral, error) {
- return nil, nil
-}
+// WaitStop stops the finder
+func (f *FTDIFinder) WaitStop() error {
+ log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
-// DeletePeripheral is not implemented here
-func (f *FTDIFinder) DeletePeripheral(serialNumber string) error {
+ // Stop the ticker
+ f.findTicker.Stop()
+
+ // Close the channel
+ 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))
+ }
+ }
+
+ // Wait for goroutines to stop
+ f.wg.Wait()
+
+ // Returning errors
+ 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 a005cc9..1c22062 100644
--- a/hardware/FTDIPeripheral.go
+++ b/hardware/FTDIPeripheral.go
@@ -1,208 +1,167 @@
package hardware
import (
- "bufio"
"context"
_ "embed"
- "fmt"
- "io"
+ "sync"
+ "unsafe"
+
+ "github.com/pkg/errors"
"github.com/rs/zerolog/log"
-
- "os"
- "os/exec"
+ "github.com/wailsapp/wails/v2/pkg/runtime"
)
-const (
- activateCommandString = 0x01
- deactivateCommandString = 0x02
- setCommandString = 0x03
-)
+/*
+#include
+#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldmxSender
+#include "cpp/include/dmxSenderBridge.h"
+*/
+import "C"
// FTDIPeripheral contains the data of an FTDI peripheral
type FTDIPeripheral struct {
- name string // The name of the peripheral
- serialNumber string // The S/N of the FTDI peripheral
- location int // The location of the peripheral
- programName string // The temp file name of the executable
- settings map[string]interface{} // The settings of the peripheral
- dmxSender *exec.Cmd // The command to pilot the DMX sender program
- stdin io.WriteCloser // For writing in the DMX sender
- stdout io.ReadCloser // For reading from the DMX sender
- stderr io.ReadCloser // For reading the errors
- disconnectChan chan struct{} // Channel to cancel the connection
- errorsChan chan error // Channel to get the errors
+ wg sync.WaitGroup
+
+ info PeripheralInfo // The peripheral basic data
+ dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
}
// NewFTDIPeripheral creates a new FTDI peripheral
-func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPeripheral, error) {
- log.Info().Str("file", "FTDIPeripheral").Str("name", name).Str("s/n", serialNumber).Int("location", location).Msg("FTDI peripheral created")
- settings := make(map[string]interface{})
+func NewFTDIPeripheral(info PeripheralInfo) (*FTDIPeripheral, error) {
+ log.Info().Str("file", "FTDIPeripheral").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI peripheral created")
return &FTDIPeripheral{
- name: name,
- dmxSender: nil,
- serialNumber: serialNumber,
- location: location,
- settings: settings,
- disconnectChan: make(chan struct{}),
- errorsChan: make(chan error, 1),
+ info: info,
+ dmxSender: nil,
}, nil
}
// Connect connects the FTDI peripheral
func (p *FTDIPeripheral) Connect(ctx context.Context) error {
- // Connect if no connection is already running
- log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("connecting FTDI peripheral...")
-
- // Check if the connection has already been established
+ // Check if the device has already been created
if p.dmxSender != nil {
- log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender already initialized")
- return nil
+ return errors.Errorf("the DMX device has already been created!")
}
- // Initialize the exec.Command for running the process
- p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location))
- log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender instance created")
+ // Create the DMX sender
+ p.dmxSender = C.dmx_create()
- // Create the pipes for stdin, stdout, and stderr asynchronously without blocking
- var err error
- if p.stdout, err = p.dmxSender.StdoutPipe(); err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stdout pipe")
- return fmt.Errorf("unable to create stdout pipe: %v", err)
- }
- if p.stdin, err = p.dmxSender.StdinPipe(); err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stdin pipe")
- return fmt.Errorf("unable to create stdin pipe: %v", err)
- }
- if p.stderr, err = p.dmxSender.StderrPipe(); err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stderr pipe")
- return fmt.Errorf("unable to create stderr pipe: %v", err)
+ // 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'")
}
- // Launch a goroutine to read stderr asynchronously
+ p.wg.Add(1)
go func() {
- scanner := bufio.NewScanner(p.stderr)
- for scanner.Scan() {
- // Process each line read from stderr
- log.Err(fmt.Errorf(scanner.Text())).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error detected in dmx sender")
- }
- if err := scanner.Err(); err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error reading from stderr")
- }
+ defer p.wg.Done()
+ <-ctx.Done()
+ _ = p.Disconnect()
}()
- // Launch the command asynchronously in another goroutine
- go func() {
- // Run the command, respecting the context cancellation
- err := p.dmxSender.Run()
- select {
- case <-ctx.Done():
- // If the context is canceled, handle it gracefully
- log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender was canceled by context")
- return
- default:
- // Handle command exit normally
- if err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while execution of dmx sender")
- if exitError, ok := err.(*exec.ExitError); ok {
- log.Warn().Str("file", "FTDIPeripheral").Int("exitCode", exitError.ExitCode()).Str("s/n", p.serialNumber).Msg("dmx sender exited with code")
- }
- } else {
- log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmx sender exited successfully")
- }
- }
- }()
+ // 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")
- log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender process started successfully")
return nil
}
// Disconnect disconnects the FTDI peripheral
-func (p *FTDIPeripheral) Disconnect(ctx context.Context) error {
- log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("disconnecting FTDI peripheral...")
- if p.dmxSender != nil {
- log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
- _, err := io.WriteString(p.stdin, string([]byte{0x04, 0x00, 0x00, 0x00}))
- if err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
- return fmt.Errorf("unable to disconnect: %v", err)
- }
- p.stdin.Close()
- p.stdout.Close()
- p.dmxSender = nil
- err = os.Remove(p.programName)
- if err != nil {
- log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Str("senderPath", p.programName).Msg("unable to delete the dmx sender temporary file")
- return fmt.Errorf("unable to delete the temporary file: %v", err)
- }
- return nil
+func (p *FTDIPeripheral) Disconnect() error {
+ // Check if the device has already been created
+ if p.dmxSender == nil {
+ return errors.Errorf("the DMX device has not been connected!")
}
- log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while disconnecting: not connected")
- return fmt.Errorf("unable to disconnect: not connected")
+
+ // Destroy the dmx sender
+ C.dmx_destroy(p.dmxSender)
+ return nil
}
// Activate activates the FTDI peripheral
func (p *FTDIPeripheral) Activate(ctx context.Context) error {
- if p.dmxSender != nil {
- log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
- _, err := io.WriteString(p.stdin, string([]byte{0x01, 0x00, 0x00, 0x00}))
- if err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
- return fmt.Errorf("unable to activate: %v", err)
- }
- return nil
+ // Check if the device has already been created
+ if p.dmxSender == nil {
+ return errors.Errorf("the DMX sender has not been created!")
}
- log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while activating: not connected")
- return fmt.Errorf("unable to activate: not connected")
+
+ log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("activating FTDI peripheral...")
+
+ err := C.dmx_activate(p.dmxSender)
+ if err != C.DMX_OK {
+ return errors.Errorf("unable to activate the DMX sender!")
+ }
+
+ 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
}
// Deactivate deactivates the FTDI peripheral
func (p *FTDIPeripheral) Deactivate(ctx context.Context) error {
- if p.dmxSender != nil {
- log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
- _, err := io.WriteString(p.stdin, string([]byte{0x02, 0x00, 0x00, 0x00}))
- if err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
- return fmt.Errorf("unable to deactivate: %v", err)
- }
- return nil
+ // Check if the device has already been created
+ if p.dmxSender == nil {
+ return errors.Errorf("the DMX device has not been created!")
}
- log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while deactivating: not connected")
- return fmt.Errorf("unable to deactivate: not connected")
-}
-// SetPeripheralSettings sets a specific setting for this peripheral
-func (p *FTDIPeripheral) SetPeripheralSettings(settings map[string]interface{}) error {
- p.settings = settings
+ log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("deactivating FTDI peripheral...")
+
+ err := C.dmx_deactivate(p.dmxSender)
+ if err != C.DMX_OK {
+ return errors.Errorf("unable to deactivate the DMX sender!")
+ }
+
+ log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully")
+
return nil
}
+// SetSettings sets a specific setting for this peripheral
+func (p *FTDIPeripheral) SetSettings(settings map[string]interface{}) error {
+ return errors.Errorf("unable to set the settings: not implemented")
+}
+
// SetDeviceProperty sends a command to the specified device
-func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, uint32, channelNumber uint32, channelValue byte) error {
- if p.dmxSender != nil {
- log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
- commandString := []byte{0x03, 0x01, 0x00, 0xff, 0x03, 0x02, 0x00, channelValue}
- _, err := io.WriteString(p.stdin, string(commandString))
- if err != nil {
- log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to write command to sender")
- return fmt.Errorf("unable to set device property: %v", err)
- }
- return nil
+func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, channelNumber uint32, channelValue byte) error {
+ // Check if the device has already been created
+ if p.dmxSender == nil {
+ return errors.Errorf("the DMX device has not been created!")
}
- log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while setting device property: not connected")
- return fmt.Errorf("unable to set device property: not connected")
+
+ log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("setting device property on FTDI peripheral...")
+
+ err := C.dmx_setValue(p.dmxSender, C.uint16_t(channelNumber), C.uint8_t(channelValue))
+ if err != C.DMX_OK {
+ return errors.Errorf("unable to update the channel value!")
+ }
+
+ log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("device property set on FTDI peripheral successfully")
+
+ return nil
}
// GetSettings gets the peripheral settings
func (p *FTDIPeripheral) GetSettings() map[string]interface{} {
- return p.settings
+ return map[string]interface{}{}
}
// GetInfo gets all the peripheral information
func (p *FTDIPeripheral) GetInfo() PeripheralInfo {
- return PeripheralInfo{
- Name: p.name,
- SerialNumber: p.serialNumber,
- ProtocolName: "FTDI",
- }
+ return p.info
+}
+
+// WaitStop wait about the peripheral to close
+func (p *FTDIPeripheral) WaitStop() error {
+ log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for FTDI peripheral to close...")
+ p.wg.Wait()
+ log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("FTDI peripheral closed!")
+ return nil
}
diff --git a/hardware/MIDIFinder.go b/hardware/MIDIFinder.go
index 4d7a65e..4feb2d8 100644
--- a/hardware/MIDIFinder.go
+++ b/hardware/MIDIFinder.go
@@ -15,18 +15,18 @@ import (
// MIDIFinder represents how the protocol is defined
type MIDIFinder struct {
- findTicker time.Ticker // Peripherals find ticker
- peripherals map[string]Peripheral // The list of peripherals
- scanChannel chan struct{} // The channel to trigger a scan event
- goWait sync.WaitGroup // Check goroutines execution
+ findTicker time.Ticker // Peripherals find ticker
+ registeredPeripherals map[string]MIDIPeripheral // The list of peripherals
+ scanChannel chan struct{} // The channel to trigger a scan event
+ goWait sync.WaitGroup // Check goroutines execution
}
// NewMIDIFinder creates a new DMXUSB protocol
func NewMIDIFinder(findPeriod time.Duration) *MIDIFinder {
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder created")
return &MIDIFinder{
- findTicker: *time.NewTicker(findPeriod),
- peripherals: make(map[string]Peripheral),
+ findTicker: *time.NewTicker(findPeriod),
+ registeredPeripherals: make(map[string]MIDIPeripheral),
}
}
@@ -36,6 +36,30 @@ func (f *MIDIFinder) Initialize() error {
return nil
}
+// RegisterPeripheral registers a new peripheral
+func (f *MIDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
+ peripheral, err := NewMIDIPeripheral(peripheralData)
+ if err != nil {
+ return "", fmt.Errorf("unable to create the MIDI peripheral: %v", err)
+ }
+ f.registeredPeripherals[peripheralData.SerialNumber] = *peripheral
+ log.Trace().Any("periph", &peripheral).Str("file", "MIDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created")
+ return peripheralData.SerialNumber, nil
+}
+
+// UnregisterPeripheral unregisters an existing peripheral
+func (f *MIDIFinder) UnregisterPeripheral(peripheralID string) error {
+ peripheral, registered := f.registeredPeripherals[peripheralID]
+ if registered {
+ err := peripheral.Disconnect()
+ if err != nil {
+ return err
+ }
+ }
+ delete(f.registeredPeripherals, peripheralID)
+ return nil
+}
+
// Start starts the finder and search for peripherals
func (f *MIDIFinder) Start(ctx context.Context) error {
f.goWait.Add(1)
@@ -79,16 +103,28 @@ func (f *MIDIFinder) GetName() string {
return "MIDI"
}
-// GetPeripheral gets the peripheral that correspond to the specified ID
-func (f *MIDIFinder) GetPeripheral(peripheralID string) (Peripheral, bool) {
+// GetPeripheralSettings gets the peripheral settings
+func (f *MIDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
// Return the specified peripheral
- peripheral, found := f.peripherals[peripheralID]
+ peripheral, found := f.registeredPeripherals[peripheralID]
if !found {
- log.Error().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral in the MIDI finder")
- return nil, false
+ log.Error().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
+ return nil, fmt.Errorf("unable to found the peripheral")
}
- log.Trace().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("MIDI peripheral found in the driver")
- return peripheral, true
+ log.Debug().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
+ return peripheral.GetSettings(), nil
+}
+
+// SetPeripheralSettings sets the peripheral settings
+func (f *MIDIFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error {
+ // Return the specified peripheral
+ peripheral, found := f.registeredPeripherals[peripheralID]
+ if !found {
+ log.Error().Str("file", "MIDIFinder").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", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
+ return peripheral.SetSettings(settings)
}
func splitStringAndNumber(input string) (string, int, error) {
@@ -119,7 +155,7 @@ func (f *MIDIFinder) ForceScan() {
// scanPeripherals scans the MIDI peripherals
func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
- midiPeripherals := make(map[string]Peripheral)
+ // midiPeripherals := make(map[string]Peripheral)
log.Trace().Str("file", "MIDIFinder").Msg("opening MIDI scanner port...")
midiScanner, err := rtmidi.NewMIDIInDefault()
if err != nil {
@@ -148,8 +184,8 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
}
log.Info().Str("file", "MIDIFinder").Str("name", name).Int("location", location).Msg("MIDI peripheral found")
// Add the peripheral to the temporary list
- sn := strings.ToLower(strings.Replace(name, " ", "_", -1))
- midiPeripherals[sn] = NewMIDIPeripheral(name, location, sn)
+ // sn := strings.ToLower(strings.Replace(name, " ", "_", -1))
+ // midiPeripherals[sn] = NewMIDIPeripheral(name, location, sn)
}
// Compare with the current peripherals to detect arrivals/removals
// removedList, addedList := comparePeripherals(f.peripherals, midiPeripherals)
@@ -159,7 +195,7 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
// emitPeripheralsEvents(ctx, addedList, PeripheralArrival)
log.Info().Str("file", "MIDIFinder").Msg("MIDI add list emitted to the front")
// Store the new peripherals list
- f.peripherals = midiPeripherals
+ // f.peripherals = midiPeripherals
return nil
}
diff --git a/hardware/MIDIPeripheral.go b/hardware/MIDIPeripheral.go
index e3083ba..16aadef 100644
--- a/hardware/MIDIPeripheral.go
+++ b/hardware/MIDIPeripheral.go
@@ -8,22 +8,18 @@ import (
// MIDIPeripheral contains the data of a MIDI peripheral
type MIDIPeripheral struct {
- name string // The name of the peripheral
- location int // The location of the peripheral
- serialNumber string // The S/N of the peripheral
- settings map[string]interface{} // The settings of the peripheral
+ info PeripheralInfo // The peripheral info
+ location int // The location of the peripheral
+ settings map[string]interface{} // The settings of the peripheral
}
// NewMIDIPeripheral creates a new MIDI peripheral
-func NewMIDIPeripheral(name string, location int, serialNumber string) *MIDIPeripheral {
- log.Trace().Str("file", "MIDIPeripheral").Str("name", name).Str("s/n", serialNumber).Int("location", location).Msg("MIDI peripheral created")
- settings := make(map[string]interface{})
+func NewMIDIPeripheral(peripheralData PeripheralInfo) (*MIDIPeripheral, error) {
+ log.Trace().Str("file", "MIDIPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("MIDI peripheral created")
return &MIDIPeripheral{
- name: name,
- location: location,
- serialNumber: serialNumber,
- settings: settings,
- }
+ info: peripheralData,
+ settings: peripheralData.Settings,
+ }, nil
}
// Connect connects the MIDI peripheral
@@ -32,7 +28,7 @@ func (p *MIDIPeripheral) Connect(ctx context.Context) error {
}
// Disconnect disconnects the MIDI peripheral
-func (p *MIDIPeripheral) Disconnect(ctx context.Context) error {
+func (p *MIDIPeripheral) Disconnect() error {
return nil
}
@@ -46,8 +42,8 @@ func (p *MIDIPeripheral) Deactivate(ctx context.Context) error {
return nil
}
-// SetPeripheralSettings sets a specific setting for this peripheral
-func (p *MIDIPeripheral) SetPeripheralSettings(settings map[string]interface{}) error {
+// SetSettings sets a specific setting for this peripheral
+func (p *MIDIPeripheral) SetSettings(settings map[string]interface{}) error {
p.settings = settings
return nil
}
@@ -64,9 +60,5 @@ func (p *MIDIPeripheral) GetSettings() map[string]interface{} {
// GetInfo gets the peripheral information
func (p *MIDIPeripheral) GetInfo() PeripheralInfo {
- return PeripheralInfo{
- Name: p.name,
- ProtocolName: "MIDI",
- SerialNumber: p.serialNumber,
- }
+ return p.info
}
diff --git a/hardware/OS2LFinder.go b/hardware/OS2LFinder.go
index 33fae6b..24a8d42 100644
--- a/hardware/OS2LFinder.go
+++ b/hardware/OS2LFinder.go
@@ -11,14 +11,14 @@ import (
// OS2LFinder represents how the protocol is defined
type OS2LFinder struct {
- peripherals map[string]Peripheral // The list of peripherals
+ registeredPeripherals map[string]OS2LPeripheral // The list of found peripherals
}
// NewOS2LFinder creates a new OS2L finder
func NewOS2LFinder() *OS2LFinder {
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder created")
return &OS2LFinder{
- peripherals: make(map[string]Peripheral),
+ registeredPeripherals: make(map[string]OS2LPeripheral),
}
}
@@ -28,21 +28,37 @@ func (f *OS2LFinder) Initialize() error {
return nil
}
-// CreatePeripheral creates a new OS2L peripheral
-func (f *OS2LFinder) CreatePeripheral(ctx context.Context) (Peripheral, error) {
+// RegisterPeripheral registers a new peripheral
+func (f *OS2LFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
// Create a random serial number for this peripheral
- randomSerialNumber := strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
- log.Trace().Str("file", "OS2LFinder").Str("serialNumber", randomSerialNumber).Msg("OS2L peripheral created")
- peripheral := NewOS2LPeripheral("OS2L", randomSerialNumber)
- f.peripherals[randomSerialNumber] = peripheral
- log.Info().Str("file", "OS2LFinder").Str("serialNumber", randomSerialNumber).Msg("OS2L peripheral created and registered")
- return peripheral, nil
+ peripheralData.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
+ log.Trace().Str("file", "OS2LFinder").Str("serialNumber", peripheralData.SerialNumber).Msg("OS2L peripheral created")
+
+ os2lPeripheral, err := NewOS2LPeripheral(peripheralData)
+ if err != nil {
+ return "", fmt.Errorf("unable to create the OS2L peripheral: %v", err)
+ }
+ // Connect this peripheral
+ err = os2lPeripheral.Connect(ctx)
+ if err != nil {
+ return "", err
+ }
+
+ f.registeredPeripherals[peripheralData.SerialNumber] = *os2lPeripheral
+ log.Trace().Any("periph", &os2lPeripheral).Str("file", "OS2LFinder").Str("peripheralName", peripheralData.Name).Msg("OS2L peripheral has been created")
+ return peripheralData.SerialNumber, nil
}
-// DeletePeripheral removes an OS2L peripheral
-func (f *OS2LFinder) DeletePeripheral(serialNumber string) error {
- delete(f.peripherals, serialNumber)
- log.Info().Str("file", "OS2LFinder").Str("serialNumber", serialNumber).Msg("OS2L peripheral removed")
+// UnregisterPeripheral unregisters an existing peripheral
+func (f *OS2LFinder) UnregisterPeripheral(peripheralID string) error {
+ peripheral, registered := f.registeredPeripherals[peripheralID]
+ if registered {
+ err := peripheral.Disconnect()
+ if err != nil {
+ return err
+ }
+ }
+ delete(f.registeredPeripherals, peripheralID)
return nil
}
@@ -51,16 +67,28 @@ func (f *OS2LFinder) GetName() string {
return "OS2L"
}
-// GetPeripheral gets the peripheral that correspond to the specified ID
-func (f *OS2LFinder) GetPeripheral(peripheralID string) (Peripheral, bool) {
+// GetPeripheralSettings gets the peripheral settings
+func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
// Return the specified peripheral
- peripheral, found := f.peripherals[peripheralID]
+ peripheral, found := f.registeredPeripherals[peripheralID]
if !found {
- log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral in the OS2L finder")
- return nil, false
+ log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the OS2L finder")
+ return nil, fmt.Errorf("unable to found the peripheral")
}
- log.Trace().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("OS2L peripheral found in the finder")
- return peripheral, true
+ log.Debug().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the OS2L finder")
+ return peripheral.GetSettings(), nil
+}
+
+// SetPeripheralSettings sets the peripheral settings
+func (f *OS2LFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error {
+ // Return the specified peripheral
+ peripheral, found := f.registeredPeripherals[peripheralID]
+ if !found {
+ log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
+ return fmt.Errorf("unable to found the peripheral")
+ }
+ log.Debug().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
+ return peripheral.SetSettings(settings)
}
// Start starts the finder
diff --git a/hardware/OS2LPeripheral.go b/hardware/OS2LPeripheral.go
index e6845e5..07e69a0 100644
--- a/hardware/OS2LPeripheral.go
+++ b/hardware/OS2LPeripheral.go
@@ -3,37 +3,42 @@ package hardware
import (
"context"
"fmt"
+ "time"
"github.com/rs/zerolog/log"
+ "github.com/wailsapp/wails/v2/pkg/runtime"
)
// OS2LPeripheral contains the data of an OS2L peripheral
type OS2LPeripheral struct {
- name string // The name of the peripheral
- serialNumber string // The serial number of the peripheral
- serverIP string // OS2L server IP
- serverPort int // OS2L server port
+ info PeripheralInfo // The basic info for this peripheral
+ serverIP string // OS2L server IP
+ serverPort int // OS2L server port
}
// NewOS2LPeripheral creates a new OS2L peripheral
-func NewOS2LPeripheral(name string, serialNumber string) *OS2LPeripheral {
- log.Trace().Str("file", "OS2LPeripheral").Str("name", name).Str("s/n", serialNumber).Msg("OS2L peripheral created")
+func NewOS2LPeripheral(peripheralData PeripheralInfo) (*OS2LPeripheral, error) {
+ log.Trace().Str("file", "OS2LPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("OS2L peripheral created")
return &OS2LPeripheral{
- name: name,
- serverIP: "127.0.0.1",
- serverPort: 9995,
- serialNumber: serialNumber,
- }
+ info: peripheralData,
+ serverIP: "127.0.0.1",
+ serverPort: 9005,
+ }, nil
}
// Connect connects the MIDI peripheral
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")
+ time.Sleep(5 * time.Second)
+ runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "disconnected")
+ }()
return nil
}
// Disconnect disconnects the MIDI peripheral
-func (p *OS2LPeripheral) Disconnect(ctx context.Context) error {
+func (p *OS2LPeripheral) Disconnect() error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected")
return nil
}
@@ -50,8 +55,8 @@ func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
return nil
}
-// SetPeripheralSettings sets a specific setting for this peripheral
-func (p *OS2LPeripheral) SetPeripheralSettings(settings map[string]interface{}) error {
+// SetSettings sets a specific setting for this peripheral
+func (p *OS2LPeripheral) SetSettings(settings map[string]interface{}) error {
// Check if the IP exists
serverIP, found := settings["os2lIp"]
if !found {
@@ -95,9 +100,5 @@ func (p *OS2LPeripheral) GetSettings() map[string]interface{} {
// GetInfo gets the peripheral information
func (p *OS2LPeripheral) GetInfo() PeripheralInfo {
- return PeripheralInfo{
- Name: p.name,
- SerialNumber: p.serialNumber,
- ProtocolName: "OS2L",
- }
+ return p.info
}
diff --git a/hardware/cpp/generate.bat b/hardware/cpp/generate.bat
new file mode 100644
index 0000000..4deba9c
--- /dev/null
+++ b/hardware/cpp/generate.bat
@@ -0,0 +1,22 @@
+@REM windres dmxSender.rc dmxSender.o
+@REM windres detectFTDI.rc detectFTDI.o
+
+@REM g++ -o dmxSender.exe dmxSender.cpp dmxSender.o -I"include" -L"lib" -lftd2xx -mwindows
+@REM g++ -o detectFTDI.exe detectFTDI.cpp detectFTDI.o -I"include" -L"lib" -lftd2xx -mwindows
+
+@REM g++ -o dmxSender.exe dmxSender.cpp -I"include" -L"lib" -lftd2xx
+@REM g++ -o detectFTDI.exe detectFTDI.cpp -I"include" -L"lib" -lftd2xx
+
+@REM g++ -c dmxSender.cpp -o dmxSender.o -I"include" -L"lib" -lftd2xx -mwindows
+
+@REM g++ -c dmxSender_wrapper.cpp -o dmxSender_wrapper.o -I"include" -L"lib" -lftd2xx -mwindows
+
+@REM g++ -shared -o ../../build/bin/libdmxSender.dll src/dmxSender.cpp -fPIC -Wl,--out-implib,../../build/bin/libdmxSender.dll.a -L"lib" -lftd2xx
+
+@REM Compiling DETECTFTDI library
+g++ -shared -o ../../build/bin/libdetectFTDI.dll src/detectFTDI.cpp -fPIC -Wl,--out-implib,../../build/bin/libdetectFTDI.dll.a -L"lib" -lftd2xx -mwindows
+
+@REM Compiling DMXSENDER library
+g++ -shared -o ../../build/bin/libdmxSender.dll src/dmxSender.cpp -fPIC -Wl,--out-implib,../../build/bin/libdmxSender.dll.a -L"lib" -lftd2xx -mwindows
+
+@REM g++ -shared -o libdmxSender.so dmxSender.cpp -fPIC -I"include" -L"lib" -lftd2xx -mwindows
\ No newline at end of file
diff --git a/hardware/cpp/include/detectFTDIBridge.h b/hardware/cpp/include/detectFTDIBridge.h
new file mode 100644
index 0000000..8cab84c
--- /dev/null
+++ b/hardware/cpp/include/detectFTDIBridge.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ char* serialNumber;
+ char* description;
+ int isOpen;
+} FTDIPeripheralC;
+
+int get_peripherals_number();
+void get_ftdi_devices(FTDIPeripheralC* devices, int count);
+void free_ftdi_device(FTDIPeripheralC* device);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/hardware/cpp/include/dmxSenderBridge.h b/hardware/cpp/include/dmxSenderBridge.h
new file mode 100644
index 0000000..1cc9ed4
--- /dev/null
+++ b/hardware/cpp/include/dmxSenderBridge.h
@@ -0,0 +1,30 @@
+// Declare the C++ function from the shared library
+
+#include
+
+typedef enum {
+ DMX_OK,
+ DMX_CHANNEL_TOO_LOW_ERROR,
+ DMX_CHANNEL_TOO_HIGH_ERROR,
+ DMX_VALUE_TOO_LOW_ERROR,
+ DMX_VALUE_TOO_HIGH_ERROR,
+ DMX_OPEN_ERROR,
+ DMX_SET_BAUDRATE_ERROR,
+ DMX_SET_DATA_CHARACTERISTICS_ERROR,
+ DMX_SET_FLOW_ERROR,
+ DMX_UNKNOWN_ERROR
+} DMXError;
+
+typedef void DMXDevice;
+
+extern DMXDevice* dmx_create();
+
+extern void* dmx_destroy(DMXDevice* dev);
+
+extern DMXError dmx_connect(DMXDevice* dev, char* serialNumber);
+
+extern DMXError dmx_activate(DMXDevice* dev);
+
+extern DMXError dmx_deactivate(DMXDevice* dev);
+
+extern DMXError dmx_setValue(DMXDevice* dev, uint16_t channel, uint8_t value);
\ No newline at end of file
diff --git a/hardware/third-party/ftdi/lib/ftd2xx.lib b/hardware/cpp/lib/ftd2xx.lib
similarity index 100%
rename from hardware/third-party/ftdi/lib/ftd2xx.lib
rename to hardware/cpp/lib/ftd2xx.lib
diff --git a/hardware/cpp/src/detectFTDI.cpp b/hardware/cpp/src/detectFTDI.cpp
new file mode 100644
index 0000000..a6266ee
--- /dev/null
+++ b/hardware/cpp/src/detectFTDI.cpp
@@ -0,0 +1,91 @@
+#include "../include/detectFTDIBridge.h"
+#include "detectFTDI.h"
+#include
+#include
+#include
+#include
+
+int getFTDIPeripheralsNumber() {
+ DWORD numDevs = 0;
+ if (FT_CreateDeviceInfoList(&numDevs) != FT_OK) {
+ std::cerr << "Unable to get FTDI devices: create list error\n";
+ }
+ return numDevs;
+}
+
+std::vector scanFTDIPeripherals() {
+ DWORD numDevs = 0;
+ if (FT_CreateDeviceInfoList(&numDevs) != FT_OK) {
+ std::cerr << "Unable to get FTDI devices: create list error\n";
+ return {};
+ }
+
+ if (numDevs == 0) {
+ return {};
+ }
+
+ std::vector devInfo(numDevs);
+ if (FT_GetDeviceInfoList(devInfo.data(), &numDevs) != FT_OK) {
+ std::cerr << "Unable to get FTDI devices: get list error\n";
+ return {};
+ }
+
+ std::vector peripherals;
+ peripherals.reserve(numDevs);
+
+ for (const auto& info : devInfo) {
+ if (info.SerialNumber[0] != '\0') {
+ peripherals.push_back({
+ info.SerialNumber,
+ info.Description,
+ static_cast(info.Flags & FT_FLAGS_OPENED)
+ });
+ }
+ }
+
+ return peripherals;
+}
+
+extern "C" {
+
+int get_peripherals_number() {
+ return getFTDIPeripheralsNumber();
+}
+
+void get_ftdi_devices(FTDIPeripheralC* devices, int count) {
+ if (!devices || count <= 0) {
+ return;
+ }
+
+ auto list = scanFTDIPeripherals();
+ int n = std::min(count, static_cast(list.size()));
+
+ for (int i = 0; i < n; ++i) {
+ const auto& src = list[i];
+ auto& dst = devices[i];
+
+ dst.serialNumber = static_cast(std::malloc(src.serialNumber.size() + 1));
+ std::strcpy(dst.serialNumber, src.serialNumber.c_str());
+
+ dst.description = static_cast(std::malloc(src.description.size() + 1));
+ std::strcpy(dst.description, src.description.c_str());
+
+ dst.isOpen = src.isOpen ? 1 : 0;
+ }
+}
+
+void free_ftdi_device(FTDIPeripheralC* device) {
+ if (!device) return;
+
+ if (device->serialNumber) {
+ std::free(device->serialNumber);
+ device->serialNumber = nullptr;
+ }
+
+ if (device->description) {
+ std::free(device->description);
+ device->description = nullptr;
+ }
+}
+
+} // extern "C"
\ No newline at end of file
diff --git a/hardware/cpp/src/detectFTDI.h b/hardware/cpp/src/detectFTDI.h
new file mode 100644
index 0000000..d22d4fb
--- /dev/null
+++ b/hardware/cpp/src/detectFTDI.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "ftd2xx.h"
+#include
+#include
+
+struct FTDIPeripheral {
+ std::string serialNumber;
+ std::string description;
+ bool isOpen;
+};
+
+int getFTDIPeripheralsNumber();
+std::vector scanFTDIPeripherals();
\ No newline at end of file
diff --git a/hardware/cpp/src/dmxSender.cpp b/hardware/cpp/src/dmxSender.cpp
new file mode 100644
index 0000000..9f372ca
--- /dev/null
+++ b/hardware/cpp/src/dmxSender.cpp
@@ -0,0 +1,230 @@
+//dmxSender.cpp
+
+#include "dmxSender.h"
+#include
+
+#define DMX_START_CODE 0x00
+#define BREAK_DURATION_US 110
+#define MAB_DURATION_US 16
+#define DMX_CHANNELS 512
+#define FREQUENCY 44
+#define INTERVAL (1000000 / FREQUENCY)
+
+// Initialize default values for starting the DMX device
+DMXDevice::DMXDevice(){
+ std::cout << " [DMXSENDER] " << "Creating a new DMXDevice..." << std::endl;
+ ftHandle = nullptr;
+ isOutputActivated = false;
+ resetChannels();
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ std::cout << " [DMXSENDER] " << "DMXDevice created!" << std::endl;
+}
+
+// Properly close the DMX device
+DMXDevice::~DMXDevice(){
+ std::cout << " [DMXSENDER] " << "Removing the DMXDevice..." << std::endl;
+ std::cout << " [DMXSENDER] " << "Deactivating the DMXDevice..." << std::endl;
+ deactivate();
+ std::cout << " [DMXSENDER] " << "DMXDevice deactivated!" << std::endl;
+
+ if (ftHandle != nullptr){
+ std::cout << " [DMXSENDER] " << "ftHandle not null, closing it..." << std::endl;
+ FT_Close(ftHandle);
+ std::cout << " [DMXSENDER] " << "FT_HANDLE closed!" << std::endl;
+ ftHandle = nullptr;
+ }
+ std::cout << " [DMXSENDER] " << "DMXDevice removed!" << std::endl;
+}
+
+// Connect the device on a specific port
+DMXError DMXDevice::connect(char* serialNumber){
+ std::cout << " [DMXSENDER] " << "Connecting the DMXDevice..." << std::endl;
+ ftStatus = FT_OpenEx((PVOID)serialNumber, FT_OPEN_BY_SERIAL_NUMBER, &ftHandle);
+ if (ftStatus != FT_OK) {
+ std::cout << " [DMXSENDER] " << "Error when connecting the DMXDevice..." << std::endl;
+ return DMX_OPEN_ERROR;
+ }
+
+ std::cout << " [DMXSENDER] " << "DMXDevice connected, setting up..." << std::endl;
+
+ ftStatus = FT_SetBaudRate(ftHandle, 250000);
+ if (ftStatus != FT_OK) {
+ std::cout << " [DMXSENDER] " << "Error when setting the baudrate..." << std::endl;
+ FT_Close(ftHandle);
+ return DMX_SET_BAUDRATE_ERROR;
+ }
+ ftStatus |= FT_SetDataCharacteristics(ftHandle, 8, FT_STOP_BITS_2, FT_PARITY_NONE); // 8 bits, no parity, 1 stop bit
+ if (ftStatus != FT_OK) {
+ std::cout << " [DMXSENDER] " << "Error when setting the data characteristics..." << std::endl;
+ FT_Close(ftHandle);
+ return DMX_SET_DATA_CHARACTERISTICS_ERROR;
+ }
+ ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_NONE, 0, 0);
+ if (ftStatus != FT_OK) {
+ std::cout << " [DMXSENDER] " << "Error when trying to set up the flow control..." << std::endl;
+ FT_Close(ftHandle);
+ return DMX_SET_FLOW_ERROR;
+ }
+
+ std::cout << " [DMXSENDER] " << "DMXDevice set up!" << std::endl;
+ std::cout << " [DMXSENDER] " << "Preparing sending job..." << std::endl;
+
+ // Send the DMX frames
+ std::thread updateThread([this]() {
+ this->sendDMX(ftHandle);
+ });
+
+ updateThread.detach();
+
+ std::cout << " [DMXSENDER] " << "Sending job executed!" << std::endl;
+ std::cout << " [DMXSENDER] " << "DMXDevice connected!" << std::endl;
+ return DMX_OK;
+}
+
+// Activate the DMX flow
+DMXError DMXDevice::activate(){
+ std::cout << " [DMXSENDER] " << "Activating the DMXDevice..." << std::endl;
+ isOutputActivated.store(true);
+ std::cout << " [DMXSENDER] " << "DMXDevice activated!" << std::endl;
+ return DMX_OK;
+}
+
+// Deactivate the DMX flow
+DMXError DMXDevice::deactivate(){
+ std::cout << " [DMXSENDER] " << "Deactivating the DMXDevice..." << std::endl;
+ std::cout << " [DMXSENDER] " << "Resetting channels..." << std::endl;
+ resetChannels();
+ std::cout << " [DMXSENDER] " << "Channels resetted!" << std::endl;
+ isOutputActivated.store(false);
+ std::cout << " [DMXSENDER] " << "DMXDevice deactivated!" << std::endl;
+ return DMX_OK;
+}
+
+// Set the value of a DMX channel
+DMXError DMXDevice::setValue(uint16_t channel, uint8_t value){
+ std::cout << " [DMXSENDER] " << "Setting a channel value..." << std::endl;
+ if (channel < 1) {
+ std::cout << " [DMXSENDER] " << "Unable to set channel value: channel number too low!" << std::endl;
+ return DMX_CHANNEL_TOO_LOW_ERROR;
+ }
+ if (channel > 512) {
+ std::cout << " [DMXSENDER] " << "Unable to set channel value: channel number too high!" << std::endl;
+ return DMX_CHANNEL_TOO_HIGH_ERROR;
+ }
+ if(value < 0) {
+ std::cout << " [DMXSENDER] " << "Unable to set channel value: channel value too low!" << std::endl;
+ return DMX_VALUE_TOO_LOW_ERROR;
+ }
+ if(value > 255) {
+ std::cout << " [DMXSENDER] " << "Unable to set channel value: channel value too high!" << std::endl;
+ return DMX_VALUE_TOO_HIGH_ERROR;
+ }
+ dmxData[channel].store(value);
+ std::cout << " [DMXSENDER] " << "Channel value set!" << std::endl;
+ return DMX_OK;
+}
+
+// Send a break line
+FT_STATUS DMXDevice::sendBreak(FT_HANDLE ftHandle) {
+ ftStatus = FT_SetBreakOn(ftHandle); // Set BREAK ON
+ if (ftStatus != FT_OK) {
+ std::cout << " [DMXSENDER] " << "Unable to put break signal ON!" << std::endl;
+ return ftStatus;
+ }
+ std::this_thread::sleep_for(std::chrono::microseconds(BREAK_DURATION_US));
+ ftStatus = FT_SetBreakOff(ftHandle); // Set BREAK OFF
+ if (ftStatus != FT_OK) {
+ std::cout << " [DMXSENDER] " << "Unable to put break signal OFF!" << std::endl;
+ return ftStatus;
+ }
+ return ftStatus;
+}
+
+// Continuously send the DMX frame
+void DMXDevice::sendDMX(FT_HANDLE ftHandle) {
+ while (true) {
+ if(isOutputActivated){
+ // Send the BREAK
+ ftStatus = sendBreak(ftHandle);
+ if (ftStatus != FT_OK) {
+ std::cout << " [DMXSENDER] " << "Unable to send break signal! Deactivating output..." << std::endl;
+ deactivate();
+ continue;
+ }
+
+ // Send the MAB
+ std::this_thread::sleep_for(std::chrono::microseconds(MAB_DURATION_US));
+
+ DWORD bytesWritten = 0;
+
+ // Send the DMX frame
+ ftStatus = FT_Write(ftHandle, dmxData, DMX_CHANNELS, &bytesWritten);
+ if (ftStatus != FT_OK || bytesWritten != DMX_CHANNELS) { // Error detected when trying to send the frame. Deactivate the line.
+ std::cout << " [DMXSENDER] " << "Error when trying to send the DMX frame! Deactivating output..." << std::endl;
+ deactivate();
+ continue;
+ }
+
+ // Wait before sending the next frame
+ std::this_thread::sleep_for(std::chrono::microseconds(INTERVAL - BREAK_DURATION_US - MAB_DURATION_US));
+ }
+ }
+}
+
+// Resetting the DMX channels
+void DMXDevice::resetChannels(){
+ for (auto &v : dmxData) {
+ v.store(0);
+ }
+ dmxData[0].store(DMX_START_CODE);
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+}
+
+// Linkable functions from Golang
+extern "C" {
+ // Create a new DMX device
+ DMXDevice* dmx_create() {
+ return new DMXDevice();
+ }
+
+ // Destroy a DMX device
+ void dmx_destroy(DMXDevice* dev) {
+ dev->~DMXDevice();
+ }
+
+ // Connect a DMX device
+ DMXError dmx_connect(DMXDevice* dev, char* serialNumber) {
+ try{
+ return dev->connect(serialNumber);
+ } catch (...) {
+ return DMX_UNKNOWN_ERROR;
+ }
+ }
+
+ // Activate a DMX device
+ DMXError dmx_activate(DMXDevice* dev) {
+ try{
+ return dev->activate();
+ } catch (...) {
+ return DMX_UNKNOWN_ERROR;
+ }
+ }
+
+ // Deactivate a DMX device
+ DMXError dmx_deactivate(DMXDevice* dev) {
+ try{
+ return dev->activate();
+ } catch (...) {
+ return DMX_UNKNOWN_ERROR;
+ }
+ }
+
+ // Set the channel value of a DMX device
+ DMXError dmx_setValue(DMXDevice* dev, int channel, int value) {
+ try {
+ return dev->setValue(channel, value);
+ } catch (...) {
+ return DMX_UNKNOWN_ERROR;
+ }
+ }
+}
\ No newline at end of file
diff --git a/hardware/cpp/src/dmxSender.h b/hardware/cpp/src/dmxSender.h
new file mode 100644
index 0000000..f0d3db1
--- /dev/null
+++ b/hardware/cpp/src/dmxSender.h
@@ -0,0 +1,65 @@
+// dmxSender.h
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include "ftd2xx.h"
+
+#define DMX_START_CODE 0x00
+#define BREAK_DURATION_US 110
+#define MAB_DURATION_US 16
+#define DMX_CHANNELS 512
+#define FREQUENCY 44
+#define INTERVAL (1000000 / FREQUENCY)
+
+typedef enum {
+ DMX_OK,
+ DMX_CHANNEL_TOO_LOW_ERROR,
+ DMX_CHANNEL_TOO_HIGH_ERROR,
+ DMX_VALUE_TOO_LOW_ERROR,
+ DMX_VALUE_TOO_HIGH_ERROR,
+ DMX_OPEN_ERROR,
+ DMX_SET_BAUDRATE_ERROR,
+ DMX_SET_DATA_CHARACTERISTICS_ERROR,
+ DMX_SET_FLOW_ERROR,
+ DMX_UNKNOWN_ERROR
+} DMXError;
+
+class DMXDevice {
+public:
+ // Initialize default values for starting the DMX device
+ DMXDevice();
+
+ // Properly close the DMX device
+ ~DMXDevice();
+
+ // Connect the device on a specific port
+ DMXError connect(char* serialNumber);
+
+ // Activate the DMX flow
+ DMXError activate();
+
+ // Deactivate the DMX flow
+ DMXError deactivate();
+
+ // Set the value of a DMX channel
+ DMXError setValue(uint16_t channel, uint8_t value);
+
+ // Resetting the DMX channels
+ void resetChannels();
+
+private:
+ FT_STATUS ftStatus; // FTDI peripheral status
+ FT_HANDLE ftHandle = nullptr; // FTDI object
+ std::atomic dmxData[DMX_CHANNELS + 1]; // For storing dynamically the DMX data
+ std::atomic isOutputActivated = false; // Boolean to start/stop the DMX flow
+
+ // Send a break line
+ FT_STATUS sendBreak(FT_HANDLE ftHandle);
+
+ // Continuously send the DMX frame
+ void sendDMX(FT_HANDLE ftHandle);
+};
\ No newline at end of file
diff --git a/hardware/third-party/ftdi/include/ftd2xx.h b/hardware/cpp/src/ftd2xx.h
similarity index 100%
rename from hardware/third-party/ftdi/include/ftd2xx.h
rename to hardware/cpp/src/ftd2xx.h
diff --git a/hardware/cpp/test/detectFTDI_test.cpp b/hardware/cpp/test/detectFTDI_test.cpp
new file mode 100644
index 0000000..181dded
--- /dev/null
+++ b/hardware/cpp/test/detectFTDI_test.cpp
@@ -0,0 +1,14 @@
+#include "../src/detectFTDI.h"
+#include
+
+int main(){
+ int peripheralsNumber = getFTDIPeripheralsNumber();
+
+
+ std::vector peripherals = scanFTDIPeripherals();
+
+ // for (const auto& peripheral : peripherals) {
+ // std::cout << peripheral.serialNumber << " (" << peripheral.description << ") -> IS OPEN: " << peripheral.isOpen << std::endl;
+ // }
+
+}
\ No newline at end of file
diff --git a/hardware/cpp/test/dmxSender_test.cpp b/hardware/cpp/test/dmxSender_test.cpp
new file mode 100644
index 0000000..12e4e77
--- /dev/null
+++ b/hardware/cpp/test/dmxSender_test.cpp
@@ -0,0 +1,122 @@
+#include "../src/dmxSender.h"
+
+#include
+#include
+#include
+
+int main(){
+ std::cout << "Debugging application DMXSENDER" << std::endl;
+
+ DMXDevice* dev = nullptr;
+ try {
+ dev = new DMXDevice();
+ }
+ catch(const std::exception &e){
+ std::cout << "Unable to create a DMX device: " << e.what() << std::endl;
+ }
+
+ if (!dev) {
+ std::cout << "Device not created, aborting." << std::endl;
+ return 1;
+ }
+
+ try {
+ bool err = dev->connect(0);
+
+ if (err == true) {
+ delete dev;
+ return 1;
+ }
+ }
+ catch (const std::exception &e){
+ std::cout << "Unable to connect" << e.what() << std::endl;
+ delete dev;
+ return 1;
+ }
+
+ try{
+ dev->activate();
+ }
+ catch(const std::exception &e){
+ std::cout << "Unable to activate" << e.what() << std::endl;
+ delete dev;
+ return 1;
+ }
+
+ try{
+ dev->setValue(1, 100);
+ dev->setValue(2, 255);
+ }
+ catch(const std::exception &e){
+ std::cout << "Unable to activate" << e.what() << std::endl;
+ delete dev;
+ return 1;
+ }
+
+ Sleep(500);
+
+ try{
+ dev->setValue(2, 127);
+ dev->setValue(3, 255);
+ }
+ catch(const std::exception &e){
+ std::cout << "Unable to activate" << e.what() << std::endl;
+ delete dev;
+ return 1;
+ }
+
+ Sleep(500);
+
+ try{
+ dev->setValue(3, 127);
+ dev->setValue(4, 255);
+ }
+ catch(const std::exception &e){
+ std::cout << "Unable to activate" << e.what() << std::endl;
+ delete dev;
+ return 1;
+ }
+
+ Sleep(500);
+
+
+ try{
+ dev->setValue(2, 0);
+ dev->setValue(3, 0);
+ dev->setValue(4, 0);
+ dev->setValue(5, 255);
+ }
+ catch(const std::exception &e){
+ std::cout << "Unable to activate" << e.what() << std::endl;
+ delete dev;
+ return 1;
+ }
+
+ Sleep(5000);
+
+
+ // try{
+ // dev->setValue(3, 255);
+ // }
+ // catch(const std::exception &e){
+ // std::cout << "Unable to activate" << e.what() << std::endl;
+ // delete dev;
+ // return 1;
+ // }
+
+ // Sleep(5000);
+
+ // try{
+ // dev->setValue(4, 255);
+ // }
+ // catch(const std::exception &e){
+ // std::cout << "Unable to activate" << e.what() << std::endl;
+ // delete dev;
+ // return 1;
+ // }
+
+ // Sleep(5000);
+
+ delete dev;
+ return 0;
+}
\ No newline at end of file
diff --git a/hardware/hardware.go b/hardware/hardware.go
index f7aeabf..cbc841d 100644
--- a/hardware/hardware.go
+++ b/hardware/hardware.go
@@ -2,9 +2,9 @@ package hardware
import (
"context"
+ "errors"
"fmt"
"sync"
- "time"
"github.com/rs/zerolog/log"
@@ -19,19 +19,18 @@ const (
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
)
-var (
- debounceTimer *time.Timer
-)
-
// HardwareManager is the class who manages the hardware
type HardwareManager struct {
+ wg sync.WaitGroup
+
finders map[string]PeripheralFinder // The map of peripherals finders
peripherals []Peripheral // The current list of peripherals
peripheralsScanTrigger chan struct{} // Trigger the peripherals scans
- goWait sync.WaitGroup // Wait for goroutines to terminate
}
// NewHardwareManager creates a new HardwareManager
@@ -46,6 +45,7 @@ func NewHardwareManager() *HardwareManager {
// Start starts to find new peripheral events
func (h *HardwareManager) Start(ctx context.Context) error {
+ // Initialize all the finders
for finderName, finder := range h.finders {
err := finder.Initialize()
if err != nil {
@@ -58,9 +58,11 @@ func (h *HardwareManager) Start(ctx context.Context) error {
return err
}
}
- h.goWait.Add(1)
+
+ // Periodically scan all the finders
+ h.wg.Add(1)
go func() {
- defer h.goWait.Done()
+ defer h.wg.Done()
for {
select {
case <-ctx.Done():
@@ -93,50 +95,24 @@ func (h *HardwareManager) RegisterFinder(finder PeripheralFinder) {
log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered")
}
-// GetPeripheral gets the peripheral object from the parent finder
-func (h *HardwareManager) GetPeripheral(finderName string, peripheralID string) (Peripheral, bool) {
- // Get the finder
- parentFinder, found := h.finders[finderName]
- // If no finder found, return false
- if !found {
- log.Error().Str("file", "hardware").Str("finderName", finderName).Msg("unable to get the finder")
- return nil, false
- }
- log.Trace().Str("file", "hardware").Str("finderName", parentFinder.GetName()).Msg("finder got")
- // Contact the finder to get the peripheral
- return parentFinder.GetPeripheral(peripheralID)
-}
-
// Scan scans all the peripherals for the registered finders
func (h *HardwareManager) Scan() error {
- h.peripheralsScanTrigger <- struct{}{}
- return nil
-}
-
-// Stop stops the hardware manager
-func (h *HardwareManager) Stop() error {
- log.Trace().Str("file", "hardware").Msg("closing the hardware manager")
- // Stop each finder
- for finderName, finder := range h.finders {
- err := finder.Stop()
- if err != nil {
- log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to stop the finder")
- }
+ select {
+ case h.peripheralsScanTrigger <- struct{}{}:
+ return nil
+ default:
+ return fmt.Errorf("scan trigger not available (manager stopped?)")
}
- // Wait for goroutines to finish
- h.goWait.Wait()
- log.Info().Str("file", "hardware").Msg("hardware manager stopped")
- return nil
}
// emitPeripheralsChanges compares the old and new peripherals to determine which ones have been added or removed.
-func emitPeripheralsChanges(ctx context.Context, oldPeripherals map[string]Peripheral, newPeripherals map[string]Peripheral) {
+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].GetInfo())
+ runtime.EventsEmit(ctx, string(PeripheralRemoval), oldPeripherals[oldPeriphName])
log.Trace().Str("file", "hardware").Str("event", string(PeripheralRemoval)).Msg("emit peripheral removal event")
}
}
@@ -144,8 +120,35 @@ func emitPeripheralsChanges(ctx context.Context, oldPeripherals map[string]Perip
// 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].GetInfo())
+ 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")
+
+ // Closing trigger channel
+ close(h.peripheralsScanTrigger)
+
+ // Stop each finder
+ var errs []error
+ for name, f := range h.finders {
+ if err := f.WaitStop(); err != nil {
+ errs = append(errs, fmt.Errorf("%s: %w", name, err))
+ }
+ }
+
+ // Wait for goroutines to finish
+ h.wg.Wait()
+
+ // Returning errors
+ if len(errs) > 0 {
+ return errors.Join(errs...)
+ }
+ log.Info().Str("file", "hardware").Msg("hardware manager stopped")
+
+ return nil
+}
diff --git a/hardware/interfaces.go b/hardware/interfaces.go
index 213c3e3..039cf13 100644
--- a/hardware/interfaces.go
+++ b/hardware/interfaces.go
@@ -4,12 +4,13 @@ import "context"
// Peripheral represents the methods used to manage a peripheral (input or output hardware)
type Peripheral interface {
- Connect(context.Context) error // Connect the peripheral
- Disconnect(context.Context) error // Disconnect the peripheral
- Activate(context.Context) error // Activate the peripheral
- Deactivate(context.Context) error // Deactivate the peripheral
- SetPeripheralSettings(map[string]interface{}) error // Set a peripheral setting
- SetDeviceProperty(context.Context, uint32, uint32, byte) error // Update a device property
+ Connect(context.Context) error // Connect the peripheral
+ IsConnected() bool // Return if the peripheral is connected or not
+ 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
+ SetDeviceProperty(context.Context, uint32, byte) error // Update a device property
GetInfo() PeripheralInfo // Get the peripheral information
GetSettings() map[string]interface{} // Get the peripheral settings
@@ -20,17 +21,19 @@ 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
}
// PeripheralFinder represents how compatible peripheral drivers are implemented
type PeripheralFinder interface {
- Initialize() error // Initializes the protocol
- Start(context.Context) error // Start the detection
- Stop() error // Stop the detection
- ForceScan() // Explicitly scans for peripherals
- CreatePeripheral(ctx context.Context) (Peripheral, error) // Creates a new peripheral
- DeletePeripheral(serialNumber string) error // Removes a peripheral
- GetName() string // Get the name of the finder
- GetPeripheral(string) (Peripheral, bool) // Get the peripheral
+ Initialize() error // Initializes the protocol
+ 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
+ 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/hardware/third-party/ftdi/detectFTDI.cpp b/hardware/third-party/ftdi/detectFTDI.cpp
deleted file mode 100644
index c1e8722..0000000
--- a/hardware/third-party/ftdi/detectFTDI.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include
-#include
-#include
-#include "ftd2xx.h"
-
-int main() {
- FT_STATUS ftStatus;
- FT_HANDLE ftHandle = nullptr;
- FT_DEVICE_LIST_INFO_NODE *devInfo;
- DWORD numDevs;
-
- // create the device information list
- ftStatus = FT_CreateDeviceInfoList(&numDevs);
- if (ftStatus != FT_OK) {
- std::cerr << "Unable to get the FTDI devices : create list error" << std::endl;
- return 1;
- }
- if (numDevs > 0) {
- // allocate storage for list based on numDevs
- devInfo =
- (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs);
- // get the device information list
- ftStatus = FT_GetDeviceInfoList(devInfo, &numDevs);
- if (ftStatus != FT_OK) {
- std::cerr << "Unable to get the FTDI devices : get list error" << std::endl;
- return 1;
- }
-
- for (int i = 0; i < numDevs; i++) {
- if (devInfo[i].SerialNumber[0] != '\0') {
- std::cout << i << ":" << devInfo[i].SerialNumber << ":" << devInfo[i].Description << std::endl;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/hardware/third-party/ftdi/detectFTDI.manifest b/hardware/third-party/ftdi/detectFTDI.manifest
deleted file mode 100644
index a048981..0000000
--- a/hardware/third-party/ftdi/detectFTDI.manifest
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- Detect FTDI
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/hardware/third-party/ftdi/dmxSender.cpp b/hardware/third-party/ftdi/dmxSender.cpp
deleted file mode 100644
index 20a6995..0000000
--- a/hardware/third-party/ftdi/dmxSender.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "ftd2xx.h"
-
-#define DMX_START_CODE 0x00
-#define BREAK_DURATION_US 110
-#define MAB_DURATION_US 16
-#define DMX_CHANNELS 512
-#define FREQUENCY 44
-#define INTERVAL (1000000 / FREQUENCY)
-
-std::atomic dmxData[DMX_CHANNELS + 1];
-std::atomic isOutputActivated = false;
-
-using namespace std;
-
-void sendBreak(FT_HANDLE ftHandle) {
- FT_SetBreakOn(ftHandle); // Envoie le signal de BREAK
- std::this_thread::sleep_for(std::chrono::microseconds(BREAK_DURATION_US));
- FT_SetBreakOff(ftHandle); // Arrête le signal de BREAK
-}
-
-void sendDMX(FT_HANDLE ftHandle) {
- while (true) {
- if(isOutputActivated){
- // Envoi du BREAK suivi du MAB
- sendBreak(ftHandle);
- std::this_thread::sleep_for(std::chrono::microseconds(MAB_DURATION_US));
-
- // Envoi de la trame DMX512
- DWORD bytesWritten = 0;
-
- // Envoyer la trame DMX512
- FT_STATUS status = FT_Write(ftHandle, dmxData, DMX_CHANNELS, &bytesWritten);
- if (status != FT_OK || bytesWritten != DMX_CHANNELS) {
- std::cerr << "Unable to send the DMX frame" << std::endl;
- FT_Close(ftHandle);
- return;
- }
-
- // Attendre avant d'envoyer la prochaine trame
- std::this_thread::sleep_for(std::chrono::microseconds(INTERVAL - BREAK_DURATION_US - MAB_DURATION_US));
- }
- }
-}
-
-void processCommand(const char* buffer) {
- if (buffer[0] == 0x01) {
- // Activate the DMX512
- isOutputActivated.store(true);
- } else if(buffer[0] == 0x02) {
- // Deactivate the DMX512
- isOutputActivated.store(false);
- } else if(buffer[0] == 0x03) {
- // Get the channel number
- uint16_t channelNumber = (static_cast(buffer[1]) |
- (static_cast(buffer[2]) << 8));
- // Get the channel value
- uint8_t channelValue = static_cast(buffer[3]);
- // // Update the DMX array
- dmxData[channelNumber].store(channelValue);
- } else if(buffer[0] == 0x04) {
- // Close this sender
- exit(0);
- } else {
- std::cerr << "Unknown command" << endl;
- }
-}
-
-// Entry point
-int main(int argc, char* argv[]) {
- #ifdef _WIN32
- _setmode(_fileno(stdin), _O_BINARY);
- #endif
-
- FT_STATUS ftStatus;
- FT_HANDLE ftHandle = nullptr;
-
- // Check if the serial port is specified
- if (argc != 2) {
- std::cerr << "Invalid call to DMX sender" << std::endl;
- return 1;
- }
-
- // Connect the serial port
- int deviceDev;
- try {
- deviceDev = std::stoi(argv[1]);
- }catch(const std::exception& e){
- std::cerr << "Invalid call to DMX sender" << std::endl;
- return 1;
- }
-
- ftStatus = FT_Open(deviceDev, &ftHandle);
- if (ftStatus != FT_OK) {
- std::cerr << "Unable to open the FTDI device" << std::endl;
- return 1;
- }
-
- ftStatus = FT_SetBaudRate(ftHandle, 250000);
- ftStatus |= FT_SetDataCharacteristics(ftHandle, 8, FT_STOP_BITS_2, FT_PARITY_NONE); // 8 bits, pas de parité, 1 bit de stop
- ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_NONE, 0, 0);
- if (ftStatus != FT_OK) {
- std::cerr << "Unable to configure the FTDI device" << std::endl;
- FT_Close(ftHandle);
- return 1;
- }
-
- // Send the DMX frames
- std::thread updateThread(sendDMX, ftHandle);
-
- // Intercept commands from the GO program
- char buffer[4]; // Tampon pour stocker les 4 octets d'une commande
-
- while (true) {
- std::cin.read(buffer, 4); // Attente bloquante jusqu'à ce que 4 octets soient lus
- if (std::cin.gcount() == 4) { // Vérifier que 4 octets ont été lus
- processCommand(buffer);
- } else if (std::cin.eof()) {
- std::cerr << "Fin de l'entrée standard (EOF)" << std::endl;
- break;
- } else if (std::cin.fail()) {
- std::cerr << "Erreur de lecture sur stdin" << std::endl;
- break;
- }
- }
- return 0;
-}
\ No newline at end of file
diff --git a/hardware/third-party/ftdi/dmxSender.manifest b/hardware/third-party/ftdi/dmxSender.manifest
deleted file mode 100644
index c87b039..0000000
--- a/hardware/third-party/ftdi/dmxSender.manifest
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- DMXSender
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/hardware/third-party/ftdi/generate.bat b/hardware/third-party/ftdi/generate.bat
deleted file mode 100644
index 000979c..0000000
--- a/hardware/third-party/ftdi/generate.bat
+++ /dev/null
@@ -1,8 +0,0 @@
-windres dmxSender.rc dmxSender.o
-windres detectFTDI.rc detectFTDI.o
-
-g++ -o dmxSender.exe dmxSender.cpp dmxSender.o -I"include" -L"lib" -lftd2xx -mwindows
-g++ -o detectFTDI.exe detectFTDI.cpp detectFTDI.o -I"include" -L"lib" -lftd2xx -mwindows
-
-@REM g++ -o dmxSender.exe dmxSender.cpp -I"include" -L"lib" -lftd2xx
-@REM g++ -o detectFTDI.exe detectFTDI.cpp -I"include" -L"lib" -lftd2xx
\ No newline at end of file
diff --git a/peripherals.go b/peripherals.go
index d98ea46..e128c1d 100644
--- a/peripherals.go
+++ b/peripherals.go
@@ -8,41 +8,48 @@ import (
)
// AddPeripheral adds a peripheral to the project
-func (a *App) AddPeripheral(protocolName string, peripheralID string) error {
+func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, error) {
// Get the peripheral from its finder
- p, found := a.hardwareManager.GetPeripheral(protocolName, peripheralID)
- if !found {
- log.Error().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("unable to found the specified peripheral")
- return fmt.Errorf("unable to found the peripheral ID '%s'", peripheralID)
+ f, err := a.hardwareManager.GetFinder(peripheralData.ProtocolName)
+ if err != nil {
+ log.Error().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Msg("unable to found the specified finder")
+ return "", fmt.Errorf("unable to found the peripheral ID '%s'", peripheralData.SerialNumber)
}
+ // Register this new peripheral
+ serialNumber, err := f.RegisterPeripheral(a.ctx, peripheralData)
+ if err != nil {
+ log.Trace().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", serialNumber).Msg("device registered to the finder")
+ return "", fmt.Errorf("unable to register the peripheral '%s'", serialNumber)
+ }
+ // Rewrite the serialnumber for virtual devices
+ peripheralData.SerialNumber = serialNumber
+
// Add the peripheral ID to the project
if a.projectInfo.PeripheralsInfo == nil {
a.projectInfo.PeripheralsInfo = make(map[string]hardware.PeripheralInfo)
}
- a.projectInfo.PeripheralsInfo[peripheralID] = p.GetInfo()
- log.Info().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("peripheral added to project")
- // TODO: Connect the peripheral
- return nil
+ a.projectInfo.PeripheralsInfo[peripheralData.SerialNumber] = peripheralData
+ log.Info().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", peripheralData.SerialNumber).Msg("peripheral added to project")
+ return peripheralData.SerialNumber, nil
}
// GetPeripheralSettings gets the peripheral settings
func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[string]interface{}, error) {
// Get the peripheral from its finder
- p, found := a.hardwareManager.GetPeripheral(protocolName, peripheralID)
- if !found {
+ f, err := a.hardwareManager.GetFinder(protocolName)
+ if err != nil {
log.Error().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("unable to found the specified peripheral")
return nil, fmt.Errorf("unable to found the peripheral ID '%s'", peripheralID)
}
- // Return the peripheral settings
- return p.GetSettings(), nil
+ return f.GetPeripheralSettings(peripheralID)
}
// UpdatePeripheralSettings updates a specific setting of a peripheral
func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settings map[string]interface{}) error {
- // Get the peripheral from its finder
- p, found := a.hardwareManager.GetPeripheral(protocolName, peripheralID)
- if !found {
+ // Sets the settings with the finder
+ f, err := a.hardwareManager.GetFinder(protocolName)
+ if err != nil {
log.Error().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("unable to found the specified peripheral")
return fmt.Errorf("unable to found the peripheral ID '%s'", peripheralID)
}
@@ -54,104 +61,65 @@ func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settin
pInfo.Settings = settings
a.projectInfo.PeripheralsInfo[peripheralID] = pInfo
// Apply changes in the peripheral
- return p.SetPeripheralSettings(pInfo.Settings)
+ return f.SetPeripheralSettings(peripheralID, pInfo.Settings)
}
-// RemovePeripheral adds a peripheral to the project
+// RemovePeripheral removes a peripheral from the project
func (a *App) RemovePeripheral(protocolName string, peripheralID string) error {
- // TODO: Disconnect the peripheral
+ // Unregister the peripheral from the finder
+ f, err := a.hardwareManager.GetFinder(protocolName)
+ if err != nil {
+ log.Err(err).Str("file", "peripherals").Str("protocolName", protocolName).Msg("unable to find the finder")
+ return fmt.Errorf("unable to find the finder")
+ }
+ err = f.UnregisterPeripheral(a.ctx, peripheralID)
+ if err != nil {
+ log.Err(err).Str("file", "peripherals").Str("peripheralID", peripheralID).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")
return nil
}
-// AddOS2LPeripheral adds a new OS2L peripheral
-func (a *App) AddOS2LPeripheral() (hardware.PeripheralInfo, error) {
- // Get the OS2L finder
- os2lDriver, err := a.hardwareManager.GetFinder("OS2L")
- if err != nil {
- log.Err(err).Str("file", "peripheral").Msg("unable to found the OS2L driver")
- return hardware.PeripheralInfo{}, err
- }
- log.Trace().Str("file", "peripheral").Msg("OS2L driver got")
-
- // Create a new OS2L peripheral with this finder
- os2lPeripheral, err := os2lDriver.CreatePeripheral(a.ctx)
- if err != nil {
- log.Err(err).Str("file", "peripheral").Msg("unable to create the OS2L peripheral")
- return hardware.PeripheralInfo{}, err
- }
-
- os2lInfo := os2lPeripheral.GetInfo()
- log.Info().Str("file", "peripheral").Str("s/n", os2lInfo.SerialNumber).Msg("OS2L peripheral created, adding to project")
- // Add this new peripheral to the project
- return os2lInfo, a.AddPeripheral(os2lDriver.GetName(), os2lInfo.SerialNumber)
-}
-
// FOR TESTING PURPOSE ONLY
-func (a *App) ConnectFTDI() error {
- // Connect the FTDI
- driver, err := a.hardwareManager.GetFinder("FTDI")
- if err != nil {
- return err
- }
- periph, found := driver.GetPeripheral("A50285BI")
- if !found {
- return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
- }
- return periph.Connect(a.ctx)
-}
+// func (a *App) ActivateFTDI() error {
+// // Connect the FTDI
+// driver, err := a.hardwareManager.GetFinder("FTDI")
+// if err != nil {
+// return err
+// }
+// periph, found := driver.GetPeripheral("A50285BI")
+// if !found {
+// return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
+// }
+// return periph.Activate(a.ctx)
+// }
-func (a *App) ActivateFTDI() error {
- // Connect the FTDI
- driver, err := a.hardwareManager.GetFinder("FTDI")
- if err != nil {
- return err
- }
- periph, found := driver.GetPeripheral("A50285BI")
- if !found {
- return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
- }
- return periph.Activate(a.ctx)
-}
+// func (a *App) SetDeviceFTDI(channelValue byte) error {
+// // Connect the FTDI
+// driver, err := a.hardwareManager.GetFinder("FTDI")
+// if err != nil {
+// return err
+// }
+// periph, found := driver.GetPeripheral("A50285BI")
+// if !found {
+// return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
+// }
+// return periph.SetDeviceProperty(a.ctx, 0, 0, channelValue)
+// }
-func (a *App) SetDeviceFTDI(channelValue byte) error {
- // Connect the FTDI
- driver, err := a.hardwareManager.GetFinder("FTDI")
- if err != nil {
- return err
- }
- periph, found := driver.GetPeripheral("A50285BI")
- if !found {
- return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
- }
- return periph.SetDeviceProperty(a.ctx, 0, 0, channelValue)
-}
-
-func (a *App) DeactivateFTDI() error {
- // Connect the FTDI
- driver, err := a.hardwareManager.GetFinder("FTDI")
- if err != nil {
- return err
- }
- periph, found := driver.GetPeripheral("A50285BI")
- if !found {
- return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
- }
- return periph.Deactivate(a.ctx)
-}
-
-func (a *App) DisconnectFTDI() error {
- // Connect the FTDI
- driver, err := a.hardwareManager.GetFinder("FTDI")
- if err != nil {
- return err
- }
- periph, found := driver.GetPeripheral("A50285BI")
- if !found {
- return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
- }
- return periph.Disconnect(a.ctx)
-}
+// func (a *App) DeactivateFTDI() error {
+// // Connect the FTDI
+// driver, err := a.hardwareManager.GetFinder("FTDI")
+// if err != nil {
+// return err
+// }
+// periph, found := driver.GetPeripheral("A50285BI")
+// if !found {
+// return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
+// }
+// return periph.Deactivate(a.ctx)
+// }
diff --git a/project.go b/project.go
deleted file mode 100644
index e2afa78..0000000
--- a/project.go
+++ /dev/null
@@ -1,191 +0,0 @@
-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() ShowInfo {
- date := time.Now()
- a.projectSave = ""
- a.projectInfo.ShowInfo = 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",
- }
- log.Info().Str("file", "project").Any("showInfo", a.projectInfo.ShowInfo).Msg("project has been created")
- return a.projectInfo.ShowInfo
-}
-
-// GetProjectInfo returns the information of the saved project
-func (a *App) GetProjectInfo(projectFile string) (ProjectInfo, 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 ProjectInfo{}, fmt.Errorf("unable to read the project file: %v", err)
- }
- log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project file read")
- a.projectInfo = ProjectInfo{}
- err = yaml.Unmarshal(content, &a.projectInfo)
- if err != nil {
- log.Err(err).Str("file", "project").Str("projectFile", projectFile).Msg("Unable to get the project information")
- return ProjectInfo{}, fmt.Errorf("unable to get the project information: %v", err)
- }
- log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project information got")
- // Load it into the app
- a.projectSave = projectFile
- // Return the show information
- log.Info().Str("file", "project").Any("projectInfo", a.projectInfo).Msg("got the project information")
- return a.projectInfo, 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
-}
diff --git a/wails.json b/wails.json
index b3676fe..279d20c 100644
--- a/wails.json
+++ b/wails.json
@@ -1,7 +1,7 @@
{
"$schema": "https://wails.io/schemas/config.v2.json",
"name": "dmxconnect",
- "outputfilename": "dmxconnect",
+ "outputfilename": "dmxconnect.exe",
"frontend:install": "npm install",
"frontend:build": "npm run build",
"frontend:dev:watcher": "npm run dev",