generated from thinkode/modelRepository
24-activate-peripherals #26
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -58,6 +58,9 @@
|
||||
"streambuf": "cpp",
|
||||
"thread": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp"
|
||||
"variant": "cpp",
|
||||
"queue": "cpp",
|
||||
"ranges": "cpp",
|
||||
"text_encoding": "cpp"
|
||||
}
|
||||
}
|
||||
38
app.go
38
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
|
||||
}
|
||||
|
||||
|
||||
55
build.bat
Normal file
55
build.bat
Normal file
@@ -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
|
||||
@@ -54,9 +54,24 @@
|
||||
generateToast('warning', 'bxs-hdd', $_("peripheralRemovalToast") + ' <b>' + peripheralInfo.Name + '</b>')
|
||||
})
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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 @@
|
||||
</script>
|
||||
|
||||
<div class="card" on:dblclick={dblclick}>
|
||||
<div class="profile {selected ? "selected" : "unselected"}" on:mousedown={click} style="color: {disconnected ? $colors.first : $colors.white};">
|
||||
<div>
|
||||
<p>{#if disconnected}<i class='bx bx-no-signal' style="font-size:100%; color: var(--nok-color);"></i> {/if}{title}</p>
|
||||
<div class="{selected ? "selected" : "unselected"} {status == "connecting" ? "waiting" : ""}" on:mousedown={click} style="color: {(status == "disconnected") ? $colors.first : $colors.white};">
|
||||
<div style="z-index: 1;">
|
||||
<p>{#if status == "disconnected" }<i class='bx bx-no-signal' style="font-size:100%; color: var(--nok-color);"></i> {/if}{title}</p>
|
||||
<h6 class="subtitle">{type} {location != '' ? "- " : ""}<i>{location}</i></h6>
|
||||
{#if disconnected}
|
||||
{#if status == "disconnected"}
|
||||
<h6><b>Disconnected</b></h6>
|
||||
{:else}
|
||||
<h6>{line1}</h6>
|
||||
@@ -50,15 +50,13 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<InfoButton on:click={add} color="{disconnected ? $colors.first : $colors.white}" style="margin: 0.2em; display: { addable ? 'flex' : 'none' }" icon='bxs-message-square-add' interactive message={$_("projectHardwareAddTooltip")}/>
|
||||
<InfoButton on:click={remove} color="{disconnected ? $colors.first : $colors.white}" style="margin: 0.2em; display: { removable ? 'flex' : 'none' }" icon='bx-trash' interactive message={$_("projectHardwareDeleteTooltip")}/>
|
||||
<InfoButton style="margin: 0.2em;" background={ signalized ? $colors.orange : $colors.first } icon='bx-pulse' hide={!signalizable}/>
|
||||
<InfoButton on:click={add} color="{(status == "disconnected") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { addable ? 'flex' : 'none' }" icon='bxs-message-square-add' interactive message={$_("projectHardwareAddTooltip")}/>
|
||||
<InfoButton on:click={remove} color="{(status == "disconnected") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { removable ? 'flex' : 'none' }" icon='bx-trash' interactive message={$_("projectHardwareDeleteTooltip")}/>
|
||||
<InfoButton style="margin: 0.2em; display: { (status == "activated" || status == "deactivated") ? 'flex' : 'none' }" background={ (status == "activated") ? $colors.ok : (status == "deactivated") ? $colors.nok : null} icon='bx-pulse' hide={!signalizable}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.unselected:hover{
|
||||
background: linear-gradient(to bottom right, var(--second-color), var(--third-color));
|
||||
@@ -77,23 +75,12 @@
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.unselected{
|
||||
background-color: var(--third-color);
|
||||
background: fixed;
|
||||
margin: 0.2em;
|
||||
padding-left: 0.3em;
|
||||
padding-bottom: 0.3em;
|
||||
border-radius: 0.2em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.subtitle{
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.actions {
|
||||
margin-left: 0.2em;
|
||||
z-index: 2;
|
||||
}
|
||||
p{
|
||||
margin: 0;
|
||||
@@ -102,4 +89,46 @@
|
||||
margin: 0;
|
||||
font-weight: 1;
|
||||
}
|
||||
|
||||
.waiting::before{
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: linear-gradient(var(--second-color), var(--first-color));
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
animation: rotate 3s linear infinite;
|
||||
}
|
||||
|
||||
.waiting::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: var(--second-color);
|
||||
inset: 2px;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
.unselected{
|
||||
background-color: var(--third-color);
|
||||
background: fixed;
|
||||
position: relative;
|
||||
margin: 0.2em;
|
||||
padding-left: 0.3em;
|
||||
padding-bottom: 0.3em;
|
||||
border-radius: 0.2em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Définition de l'animation */
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg); /* Début de la rotation */
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg); /* Fin de la rotation */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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}
|
||||
<p style="color: var(--first-color);"><i class='bx bxs-network-chart' ></i> {$_("projectHardwareOthersLabel")}</p>
|
||||
<RoundedButton on:click={createOS2L} text="Add an OS2L peripheral" icon="bx-plus-circle" tooltip="Configure an OS2L connection"/>
|
||||
<RoundedButton on:click={()=>addPeripheral({Name: "OS2L connection", ProtocolName: "OS2L"})} text="Add an OS2L peripheral" icon="bx-plus-circle" tooltip="Configure an OS2L connection"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -189,8 +134,8 @@
|
||||
{#if savedPeripheralNumber > 0}
|
||||
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
|
||||
{#if peripheral.isSaved}
|
||||
<DeviceCard on:delete={() => 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/>
|
||||
<DeviceCard status="{peripheral.Status}" on:delete={() => removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} on:click={() => selectPeripheral(peripheral)}
|
||||
title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} selected={serialNumber == selectedPeripheralSN} removable signalizable/>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
@@ -208,14 +153,6 @@
|
||||
{:else}
|
||||
<i>{$_("projectHardwareNoSettingLabel")}</i>
|
||||
{/if}
|
||||
|
||||
<!-- <button on:click={ftdiConnect}>Connect FTDI 0</button>
|
||||
<button on:click={ftdiActivate}>Activate FTDI 0</button>
|
||||
<div class="slidecontainer">
|
||||
<input type="range" min="0" max="255" class="slider" bind:value={sliderValue} on:input={() => ftdiSetDevice(sliderValue)}>
|
||||
</div>
|
||||
<button on:click={ftdiDeactivate}>Deactivate FTDI 0</button>
|
||||
<button on:click={ftdiDisconnect}>Disconnect FTDI 0</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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") + ' <b>' + projectInfo.ShowInfo.Name + '</b>')
|
||||
})
|
||||
|
||||
// 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") + ' <b>' + projectInfo.ShowInfo.Name + '</b>')
|
||||
}).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()
|
||||
</script>
|
||||
|
||||
<!-- Project buttons -->
|
||||
|
||||
@@ -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",
|
||||
|
||||
2
go.mod
2
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
|
||||
|
||||
@@ -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 <stdlib.h>
|
||||
#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
|
||||
}
|
||||
|
||||
@@ -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 <stdlib.h>
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldmxSender
|
||||
#include "cpp/include/dmxSenderBridge.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// FTDIPeripheral contains the data of an FTDI peripheral
|
||||
type FTDIPeripheral struct {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
22
hardware/cpp/generate.bat
Normal file
22
hardware/cpp/generate.bat
Normal file
@@ -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
|
||||
19
hardware/cpp/include/detectFTDIBridge.h
Normal file
19
hardware/cpp/include/detectFTDIBridge.h
Normal file
@@ -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
|
||||
30
hardware/cpp/include/dmxSenderBridge.h
Normal file
30
hardware/cpp/include/dmxSenderBridge.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Declare the C++ function from the shared library
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
91
hardware/cpp/src/detectFTDI.cpp
Normal file
91
hardware/cpp/src/detectFTDI.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "../include/detectFTDIBridge.h"
|
||||
#include "detectFTDI.h"
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
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<FTDIPeripheral> 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<FT_DEVICE_LIST_INFO_NODE> devInfo(numDevs);
|
||||
if (FT_GetDeviceInfoList(devInfo.data(), &numDevs) != FT_OK) {
|
||||
std::cerr << "Unable to get FTDI devices: get list error\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<FTDIPeripheral> peripherals;
|
||||
peripherals.reserve(numDevs);
|
||||
|
||||
for (const auto& info : devInfo) {
|
||||
if (info.SerialNumber[0] != '\0') {
|
||||
peripherals.push_back({
|
||||
info.SerialNumber,
|
||||
info.Description,
|
||||
static_cast<bool>(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<int>(list.size()));
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const auto& src = list[i];
|
||||
auto& dst = devices[i];
|
||||
|
||||
dst.serialNumber = static_cast<char*>(std::malloc(src.serialNumber.size() + 1));
|
||||
std::strcpy(dst.serialNumber, src.serialNumber.c_str());
|
||||
|
||||
dst.description = static_cast<char*>(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"
|
||||
14
hardware/cpp/src/detectFTDI.h
Normal file
14
hardware/cpp/src/detectFTDI.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "ftd2xx.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct FTDIPeripheral {
|
||||
std::string serialNumber;
|
||||
std::string description;
|
||||
bool isOpen;
|
||||
};
|
||||
|
||||
int getFTDIPeripheralsNumber();
|
||||
std::vector<FTDIPeripheral> scanFTDIPeripherals();
|
||||
230
hardware/cpp/src/dmxSender.cpp
Normal file
230
hardware/cpp/src/dmxSender.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
//dmxSender.cpp
|
||||
|
||||
#include "dmxSender.h"
|
||||
#include <iostream>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
hardware/cpp/src/dmxSender.h
Normal file
65
hardware/cpp/src/dmxSender.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// dmxSender.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#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<uint8_t> dmxData[DMX_CHANNELS + 1]; // For storing dynamically the DMX data
|
||||
std::atomic<bool> 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);
|
||||
};
|
||||
14
hardware/cpp/test/detectFTDI_test.cpp
Normal file
14
hardware/cpp/test/detectFTDI_test.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "../src/detectFTDI.h"
|
||||
#include <iostream>
|
||||
|
||||
int main(){
|
||||
int peripheralsNumber = getFTDIPeripheralsNumber();
|
||||
|
||||
|
||||
std::vector<FTDIPeripheral> peripherals = scanFTDIPeripherals();
|
||||
|
||||
// for (const auto& peripheral : peripherals) {
|
||||
// std::cout << peripheral.serialNumber << " (" << peripheral.description << ") -> IS OPEN: " << peripheral.isOpen << std::endl;
|
||||
// }
|
||||
|
||||
}
|
||||
122
hardware/cpp/test/dmxSender_test.cpp
Normal file
122
hardware/cpp/test/dmxSender_test.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "../src/dmxSender.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
35
hardware/third-party/ftdi/detectFTDI.cpp
vendored
35
hardware/third-party/ftdi/detectFTDI.cpp
vendored
@@ -1,35 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
hardware/third-party/ftdi/detectFTDI.manifest
vendored
12
hardware/third-party/ftdi/detectFTDI.manifest
vendored
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="DMXSender" type="win32"/>
|
||||
<description>Detect FTDI</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
142
hardware/third-party/ftdi/dmxSender.cpp
vendored
142
hardware/third-party/ftdi/dmxSender.cpp
vendored
@@ -1,142 +0,0 @@
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <math.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#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<unsigned char> dmxData[DMX_CHANNELS + 1];
|
||||
std::atomic<bool> 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<unsigned char>(buffer[1]) |
|
||||
(static_cast<unsigned char>(buffer[2]) << 8));
|
||||
// Get the channel value
|
||||
uint8_t channelValue = static_cast<unsigned char>(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;
|
||||
}
|
||||
12
hardware/third-party/ftdi/dmxSender.manifest
vendored
12
hardware/third-party/ftdi/dmxSender.manifest
vendored
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="DMXSender" type="win32"/>
|
||||
<description>DMXSender</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
8
hardware/third-party/ftdi/generate.bat
vendored
8
hardware/third-party/ftdi/generate.bat
vendored
@@ -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
|
||||
176
peripherals.go
176
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)
|
||||
// }
|
||||
|
||||
191
project.go
191
project.go
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user