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"
|
||||
}
|
||||
}
|
||||
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
|
||||
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
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
|
||||
const (
|
||||
ftdiFinderExecutableName = "FTDI_finder.exe"
|
||||
ftdiSenderExecutableName = "FTDI_sender.exe"
|
||||
)
|
||||
|
||||
// FTDIFinder represents how the protocol is defined
|
||||
@@ -70,9 +69,6 @@ func (f *FTDIFinder) UnregisterPeripheral(peripheralID string) error {
|
||||
//go:embed third-party/ftdi/detectFTDI.exe
|
||||
var finderExe []byte
|
||||
|
||||
//go:embed third-party/ftdi/dmxSender.exe
|
||||
var senderExe []byte
|
||||
|
||||
// Initialize initializes the FTDI finder
|
||||
func (f *FTDIFinder) Initialize() error {
|
||||
// Check platform
|
||||
@@ -85,10 +81,6 @@ func (f *FTDIFinder) Initialize() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = createExecutable(ftdiSenderExecutableName, senderExe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder initialized")
|
||||
return nil
|
||||
}
|
||||
@@ -157,11 +149,6 @@ func (f *FTDIFinder) Stop() error {
|
||||
if err != nil {
|
||||
log.Warn().Str("file", "FTDIFinder").Str("fileName", fileToDelete).AnErr("error", err).Msg("unable to remove the executable file")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
activateCommandString = 0x01
|
||||
deactivateCommandString = 0x02
|
||||
setCommandString = 0x03
|
||||
)
|
||||
/*
|
||||
#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 {
|
||||
info PeripheralInfo // The peripheral basic data
|
||||
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
|
||||
dmxDevice unsafe.Pointer // The command object for piloting the DMX ouptut
|
||||
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
|
||||
@@ -38,9 +34,8 @@ func NewFTDIPeripheral(info PeripheralInfo) (*FTDIPeripheral, error) {
|
||||
log.Info().Str("file", "FTDIPeripheral").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI peripheral created")
|
||||
settings := make(map[string]interface{})
|
||||
return &FTDIPeripheral{
|
||||
programName: filepath.Join(os.TempDir(), ftdiSenderExecutableName),
|
||||
info: info,
|
||||
dmxSender: nil,
|
||||
dmxDevice: C.dmx_create(),
|
||||
settings: settings,
|
||||
disconnectChan: make(chan struct{}),
|
||||
errorsChan: make(chan error, 1),
|
||||
@@ -49,128 +44,67 @@ func NewFTDIPeripheral(info PeripheralInfo) (*FTDIPeripheral, error) {
|
||||
|
||||
// Connect connects the FTDI peripheral
|
||||
func (p *FTDIPeripheral) Connect(ctx context.Context, location int) error {
|
||||
// Connect if no connection is already running
|
||||
// Check if the device has already been created
|
||||
if p.dmxDevice == nil {
|
||||
return errors.Errorf("the DMX device has not been created!")
|
||||
}
|
||||
|
||||
// Connect the peripheral
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("connecting FTDI peripheral...")
|
||||
|
||||
// Check if the connection has already been established
|
||||
if p.dmxSender != nil {
|
||||
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("dmxSender already initialized")
|
||||
return nil
|
||||
err := C.dmx_connect(p.dmxDevice)
|
||||
if err {
|
||||
log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device")
|
||||
return errors.Errorf("Unable to connect the DMX Device on the specified port")
|
||||
}
|
||||
|
||||
// Initialize the exec.Command for running the process
|
||||
p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", location))
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("dmxSender instance created")
|
||||
//TODO: Destroy the object when context is done to avoid memory loss
|
||||
|
||||
// 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.info.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.info.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.info.SerialNumber).Msg("unable to create stderr pipe")
|
||||
return fmt.Errorf("unable to create stderr pipe: %v", err)
|
||||
}
|
||||
|
||||
// Launch a goroutine to read stderr asynchronously
|
||||
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.info.SerialNumber).Msg("error detected in dmx sender")
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("error reading from stderr")
|
||||
}
|
||||
}()
|
||||
|
||||
// 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
|
||||
err = p.Disconnect()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIPeripheral").Msg("unable to disconnect the peripheral")
|
||||
}
|
||||
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.info.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.info.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.info.SerialNumber).Msg("dmx sender exited with code")
|
||||
}
|
||||
} else {
|
||||
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("dmx sender exited successfully")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("dmxSender process started successfully")
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the FTDI peripheral
|
||||
func (p *FTDIPeripheral) Disconnect() error {
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("disconnecting FTDI peripheral...")
|
||||
if p.dmxSender != nil {
|
||||
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.info.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.info.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.info.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
|
||||
// Check if the device has already been created
|
||||
if p.dmxDevice == nil {
|
||||
return errors.Errorf("the DMX device has not been created!")
|
||||
}
|
||||
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("error while disconnecting: not connected")
|
||||
return fmt.Errorf("unable to disconnect: not connected")
|
||||
|
||||
//TODO: What actions for disconnecting the DMX device?
|
||||
|
||||
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.info.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.info.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.dmxDevice == nil {
|
||||
return errors.Errorf("the DMX device has not been created!")
|
||||
}
|
||||
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.info.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...")
|
||||
|
||||
C.dmx_activate(p.dmxDevice)
|
||||
|
||||
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.info.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.info.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.dmxDevice == nil {
|
||||
return errors.Errorf("the DMX device has not been created!")
|
||||
}
|
||||
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("error while deactivating: not connected")
|
||||
return fmt.Errorf("unable to deactivate: not connected")
|
||||
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("deactivating FTDI peripheral...")
|
||||
|
||||
C.dmx_deactivate(p.dmxDevice)
|
||||
|
||||
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
|
||||
@@ -181,18 +115,18 @@ func (p *FTDIPeripheral) SetSettings(settings map[string]interface{}) error {
|
||||
|
||||
// SetDeviceProperty sends a command to the specified device
|
||||
func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, channelNumber uint32, channelValue byte) error {
|
||||
if p.dmxSender != nil {
|
||||
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("dmxsender is defined for this FTDI")
|
||||
commandString := []byte{0x03, byte(channelNumber & 0xFF), byte((channelNumber >> 8) & 0xFF), channelValue}
|
||||
_, err := io.WriteString(p.stdin, string(commandString))
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("unable to write command to sender")
|
||||
return fmt.Errorf("unable to set device property: %v", err)
|
||||
}
|
||||
return nil
|
||||
// Check if the device has already been created
|
||||
if p.dmxDevice == nil {
|
||||
return errors.Errorf("the DMX device has not been created!")
|
||||
}
|
||||
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.info.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...")
|
||||
|
||||
C.dmx_setValue(p.dmxDevice, C.int(channelNumber), C.int(channelValue))
|
||||
|
||||
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
|
||||
|
||||
21
hardware/cpp/generate.bat
Normal file
21
hardware/cpp/generate.bat
Normal file
@@ -0,0 +1,21 @@
|
||||
@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 DMXSENDER library
|
||||
|
||||
g++ -shared -o ../../build/bin/libdmxSender.dll src/dmxSender.cpp -fPIC -L"lib" -lftd2xx
|
||||
|
||||
|
||||
@REM g++ -shared -o libdmxSender.so dmxSender.cpp -fPIC -I"include" -L"lib" -lftd2xx -mwindows
|
||||
15
hardware/cpp/include/dmxSenderBridge.h
Normal file
15
hardware/cpp/include/dmxSenderBridge.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// Declare the C++ function from the shared library
|
||||
|
||||
typedef void DMXDevice;
|
||||
|
||||
extern DMXDevice* dmx_create();
|
||||
|
||||
extern void* dmx_destroy(DMXDevice* dev);
|
||||
|
||||
extern bool dmx_connect(DMXDevice* dev);
|
||||
|
||||
extern void dmx_activate(DMXDevice* dev);
|
||||
|
||||
extern void dmx_deactivate(DMXDevice* dev);
|
||||
|
||||
extern void dmx_setValue(DMXDevice* dev, int channel, int value);
|
||||
BIN
hardware/cpp/lib/ftd2xx.lib
Normal file
BIN
hardware/cpp/lib/ftd2xx.lib
Normal file
Binary file not shown.
127
hardware/cpp/src/dmxSender.cpp
Normal file
127
hardware/cpp/src/dmxSender.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
//dmxSender.cpp
|
||||
|
||||
#include "dmxSender.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)
|
||||
|
||||
// Initialize default values for starting the DMX device
|
||||
DMXDevice::DMXDevice(){
|
||||
ftHandle = nullptr;
|
||||
isOutputActivated = false;
|
||||
}
|
||||
|
||||
// Properly close the DMX device
|
||||
DMXDevice::~DMXDevice(){
|
||||
deactivate();
|
||||
if (ftHandle != nullptr){
|
||||
FT_Close(ftHandle);
|
||||
ftHandle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Connect the device on a specific port
|
||||
bool DMXDevice::connect(int port){
|
||||
ftStatus = FT_Open(port, &ftHandle);
|
||||
if (ftStatus != FT_OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ftStatus = FT_SetBaudRate(ftHandle, 250000);
|
||||
ftStatus |= FT_SetDataCharacteristics(ftHandle, 8, FT_STOP_BITS_2, FT_PARITY_NONE); // 8 bits, no parity, 1 stop bit
|
||||
ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_NONE, 0, 0);
|
||||
if (ftStatus != FT_OK) {
|
||||
FT_Close(ftHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send the DMX frames
|
||||
std::thread updateThread([this]() {
|
||||
this->sendDMX(ftHandle);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Activate the DMX flow
|
||||
void DMXDevice::activate(){
|
||||
isOutputActivated.store(true);
|
||||
}
|
||||
|
||||
// Deactivate the DMX flow
|
||||
void DMXDevice::deactivate(){
|
||||
isOutputActivated.store(false);
|
||||
}
|
||||
|
||||
// Set the value of a DMX channe
|
||||
void DMXDevice::setValue(int channel, int value){
|
||||
dmxData[channel].store(value);
|
||||
}
|
||||
|
||||
// Send a break line
|
||||
void DMXDevice::sendBreak(FT_HANDLE ftHandle) {
|
||||
FT_SetBreakOn(ftHandle); // Set BREAK ON
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(BREAK_DURATION_US));
|
||||
FT_SetBreakOff(ftHandle); // Set BREAK OFF
|
||||
}
|
||||
|
||||
// Continuously send the DMX frame
|
||||
void DMXDevice::sendDMX(FT_HANDLE ftHandle) {
|
||||
while (true) {
|
||||
if(isOutputActivated){
|
||||
// Send the BREAK
|
||||
sendBreak(ftHandle);
|
||||
|
||||
// Send the MAB
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(MAB_DURATION_US));
|
||||
|
||||
DWORD bytesWritten = 0;
|
||||
|
||||
// Send the DMX frame
|
||||
FT_STATUS status = FT_Write(ftHandle, dmxData, DMX_CHANNELS, &bytesWritten);
|
||||
if (status != FT_OK || bytesWritten != DMX_CHANNELS) { // Error detected when trying to send the frame. Deactivate the line.
|
||||
deactivate();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait before sending the next frame
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(INTERVAL - BREAK_DURATION_US - MAB_DURATION_US));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
bool dmx_connect(DMXDevice* dev, int port) {
|
||||
return dev->connect(port);
|
||||
}
|
||||
|
||||
// Activate a DMX device
|
||||
void dmx_activate(DMXDevice* dev) {
|
||||
dev->activate();
|
||||
}
|
||||
|
||||
// Deactivate a DMX device
|
||||
void dmx_deactivate(DMXDevice* dev) {
|
||||
dev->deactivate();
|
||||
}
|
||||
|
||||
// Set the channel value of a DMX device
|
||||
void dmx_setValue(DMXDevice* dev, int channel, int value) {
|
||||
dev->setValue(channel, value);
|
||||
}
|
||||
}
|
||||
49
hardware/cpp/src/dmxSender.h
Normal file
49
hardware/cpp/src/dmxSender.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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)
|
||||
|
||||
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
|
||||
bool connect(int port);
|
||||
|
||||
// Activate the DMX flow
|
||||
void activate();
|
||||
|
||||
// Deactivate the DMX flow
|
||||
void deactivate();
|
||||
|
||||
// Set the value of a DMX channel
|
||||
void setValue(int channel, int value);
|
||||
|
||||
private:
|
||||
FT_STATUS ftStatus; // FTDI peripheral status
|
||||
FT_HANDLE ftHandle = nullptr; // FTDI object
|
||||
std::atomic<unsigned char> 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
|
||||
void sendBreak(FT_HANDLE ftHandle);
|
||||
|
||||
// Continuously send the DMX frame
|
||||
void sendDMX(FT_HANDLE ftHandle);
|
||||
};
|
||||
1667
hardware/cpp/src/ftd2xx.h
Normal file
1667
hardware/cpp/src/ftd2xx.h
Normal file
File diff suppressed because it is too large
Load Diff
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
|
||||
@@ -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