24-activate-peripherals #26

Merged
thinkode merged 7 commits from 24-activate-peripherals into develop 2025-11-02 10:00:14 +00:00
17 changed files with 559 additions and 1904 deletions
Showing only changes of commit abcc3e0b5e - Show all commits

View File

@@ -1,34 +1,31 @@
package hardware
import (
"bufio"
"context"
_ "embed"
"fmt"
"os"
"os/exec"
"path/filepath"
goRuntime "runtime"
"strconv"
"strings"
"sync"
"time"
"unsafe"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
const (
ftdiFinderExecutableName = "FTDI_finder.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
type FTDIFinder struct {
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
goWait sync.WaitGroup // Check goroutines execution
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
goWait sync.WaitGroup // Check goroutines execution
}
// NewFTDIFinder creates a new FTDI finder
@@ -37,7 +34,7 @@ func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder {
return &FTDIFinder{
findTicker: *time.NewTicker(findPeriod),
foundPeripherals: make(map[string]PeripheralInfo),
registeredPeripherals: make(map[string]FTDIPeripheral),
registeredPeripherals: make(map[string]*FTDIPeripheral),
scanChannel: make(chan struct{}),
}
}
@@ -48,16 +45,33 @@ func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData Peri
if err != nil {
return "", fmt.Errorf("unable to create the FTDI peripheral: %v", err)
}
f.registeredPeripherals[peripheralData.SerialNumber] = *ftdiPeripheral
f.registeredPeripherals[peripheralData.SerialNumber] = ftdiPeripheral
log.Trace().Any("periph", &ftdiPeripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created")
// 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(peripheralID string) error {
func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID string) error {
peripheral, registered := f.registeredPeripherals[peripheralID]
if registered {
err := peripheral.Disconnect()
// Deactivating peripheral
err := peripheral.Deactivate(ctx)
if err != nil {
return err
}
// Disconnecting peripheral
err = peripheral.Disconnect(ctx)
if err != nil {
return err
}
@@ -66,9 +80,6 @@ func (f *FTDIFinder) UnregisterPeripheral(peripheralID string) error {
return nil
}
//go:embed third-party/ftdi/detectFTDI.exe
var finderExe []byte
// Initialize initializes the FTDI finder
func (f *FTDIFinder) Initialize() error {
// Check platform
@@ -76,34 +87,10 @@ 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
}
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)
@@ -143,12 +130,6 @@ func (f *FTDIFinder) Stop() error {
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")
}
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
return nil
}
@@ -184,78 +165,49 @@ func (f *FTDIFinder) SetPeripheralSettings(peripheralID string, settings map[str
// 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")
finder := exec.CommandContext(detectionCtx, filepath.Join(os.TempDir(), ftdiFinderExecutableName))
log.Trace().Str("file", "FTDIFinder").Msg("has executed the FIND executable")
count := int(C.get_peripherals_number())
stdout, err := finder.StdoutPipe()
if err != nil {
return fmt.Errorf("unable to create the stdout pipe: %s", err)
}
defer stdout.Close()
log.Info().Int("number", count).Msg("number of FTDI devices connected")
stderr, err := finder.StderrPipe()
if err != nil {
return fmt.Errorf("unable to create the stderr pipe: %s", err)
}
defer stderr.Close()
// Alloue un tableau de structures côté C
size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIPeripheralC{}))
devicesPtr := C.malloc(size)
defer C.free(devicesPtr)
err = finder.Start()
if err != nil {
return fmt.Errorf("unable to find FTDI peripherals: %s", err)
devices := (*[1 << 30]C.FTDIPeripheralC)(devicesPtr)[:count:count]
type device struct {
SerialNumber string
Description string
IsOpen bool
}
scannerErr := bufio.NewScanner(stderr)
for scannerErr.Scan() {
return fmt.Errorf("unable to find FTDI peripherals: %s", scannerErr.Text())
}
C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
temporaryPeripherals := make(map[string]PeripheralInfo)
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
peripheralString := scanner.Text()
// The program output is like '0:1:2:3' where 0 is the location, 1 is the S/N, 2 is the name and 3 is the open flag [O/C]
peripheralInfo := strings.Split(peripheralString, ":")
for i := 0; i < count; i++ {
d := devices[i]
log.Trace().Str("file", "FTDIFinder").Str("scannedString", peripheralString).Str("peripheralOpenFlag", peripheralInfo[3]).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
}
// Add the peripheral info to the found list
temporaryPeripherals[peripheralInfo[1]] = PeripheralInfo{
Name: peripheralInfo[2],
SerialNumber: peripheralInfo[1],
IsOpen: peripheralInfo[3] == "O",
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",
}
// If this peripheral is already registered, connect it and activate it
peripheral, registered := f.registeredPeripherals[peripheralInfo[1]]
if registered {
runtime.EventsEmit(ctx, string(PeripheralStatus), peripheral.info, "connecting")
err := peripheral.Connect(ctx, location)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo[1]).Msg("unable to connect the peripheral")
}
runtime.EventsEmit(ctx, string(PeripheralStatus), peripheral.info, "deactivated")
time.Sleep(2 * time.Second)
err = peripheral.Activate(ctx)
if err != nil {
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo[1]).Msg("unable to activate the peripheral")
}
runtime.EventsEmit(ctx, string(PeripheralStatus), peripheral.info, "activated")
}
log.Trace().Any("periph", temporaryPeripherals).Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("successfully added the FTDI peripheral to the finder")
// Libération mémoire allouée côté C
C.free_ftdi_device(&d)
}
log.Info().Any("peripherals", temporaryPeripherals).Msg("available FTDI peripherals")
// Emit the peripherals changes to the front
emitPeripheralsChanges(ctx, f.foundPeripherals, temporaryPeripherals)
// Store the new peripherals list

View File

@@ -3,12 +3,13 @@ package hardware
import (
"context"
_ "embed"
"io"
"sync"
"unsafe"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
/*
@@ -19,14 +20,11 @@ import "C"
// FTDIPeripheral contains the data of an FTDI peripheral
type FTDIPeripheral struct {
info PeripheralInfo // The peripheral basic data
settings map[string]interface{} // The settings of the peripheral
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
disconnectChan chan struct{} // Channel to cancel the connection
errorsChan chan error // Channel to get the errors
info PeripheralInfo // The peripheral basic data
settings map[string]interface{} // The settings of the peripheral
dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
waitGroup sync.WaitGroup // Waitgroup to wait goroutines
isConnected bool // If the FTDI is connected or not
}
// NewFTDIPeripheral creates a new FTDI peripheral
@@ -34,57 +32,83 @@ 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{
info: info,
dmxDevice: C.dmx_create(),
settings: settings,
disconnectChan: make(chan struct{}),
errorsChan: make(chan error, 1),
info: info,
dmxSender: nil,
settings: settings,
isConnected: false,
}, nil
}
// Connect connects the FTDI peripheral
func (p *FTDIPeripheral) Connect(ctx context.Context, location int) error {
func (p *FTDIPeripheral) Connect(ctx context.Context) error {
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "connecting")
// Check if the device has already been created
if p.dmxDevice == nil {
return errors.Errorf("the DMX device has not been created!")
if p.dmxSender != nil {
return errors.Errorf("the DMX device has already been created!")
}
// Create the DMX sender
p.dmxSender = C.dmx_create()
// Connect the peripheral
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("connecting FTDI peripheral...")
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")
err := C.dmx_connect(p.dmxSender, C.CString(p.info.SerialNumber))
if err != C.DMX_OK {
log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Any("err", err).Msg("unable to connect the DMX device")
return errors.Errorf("Unable to connect the DMX Device on the specified port")
}
//TODO: Destroy the object when context is done to avoid memory loss
p.waitGroup.Add(1)
go func() {
defer p.waitGroup.Done()
<-ctx.Done()
p.Disconnect(ctx)
}()
p.isConnected = true
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "deactivated")
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully")
return nil
}
// Disconnect disconnects the FTDI peripheral
func (p *FTDIPeripheral) Disconnect() error {
func (p *FTDIPeripheral) Disconnect(ctx context.Context) error {
// Check if the device has already been created
if p.dmxDevice == nil {
return errors.Errorf("the DMX device has not been created!")
if p.dmxSender == nil {
return errors.Errorf("the DMX device has not been connected!")
}
//TODO: What actions for disconnecting the DMX device?
// Destroy the dmx sender
C.dmx_destroy(p.dmxSender)
p.isConnected = false
return nil
}
// IsConnected returns if the FTDI is connected or not
func (p *FTDIPeripheral) IsConnected() bool {
return p.isConnected
}
// Activate activates the FTDI peripheral
func (p *FTDIPeripheral) Activate(ctx context.Context) error {
// Check if the device has already been created
if p.dmxDevice == nil {
return errors.Errorf("the DMX device has not been created!")
if p.dmxSender == nil {
return errors.Errorf("the DMX sender has not been created!")
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("activating FTDI peripheral...")
C.dmx_activate(p.dmxDevice)
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")
@@ -94,13 +118,16 @@ func (p *FTDIPeripheral) Activate(ctx context.Context) error {
// Deactivate deactivates the FTDI peripheral
func (p *FTDIPeripheral) Deactivate(ctx context.Context) error {
// Check if the device has already been created
if p.dmxDevice == nil {
if p.dmxSender == nil {
return errors.Errorf("the DMX device has not been created!")
}
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("deactivating FTDI peripheral...")
C.dmx_deactivate(p.dmxDevice)
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")
@@ -116,13 +143,16 @@ 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 {
// Check if the device has already been created
if p.dmxDevice == nil {
if p.dmxSender == nil {
return errors.Errorf("the DMX device has not been created!")
}
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))
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")

View File

@@ -13,9 +13,10 @@
@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 -L"lib" -lftd2xx
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

View 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

View File

@@ -1,15 +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 bool dmx_connect(DMXDevice* dev);
extern DMXError dmx_connect(DMXDevice* dev, char* serialNumber);
extern void dmx_activate(DMXDevice* dev);
extern DMXError dmx_activate(DMXDevice* dev);
extern void dmx_deactivate(DMXDevice* dev);
extern DMXError dmx_deactivate(DMXDevice* dev);
extern void dmx_setValue(DMXDevice* dev, int channel, int value);
extern DMXError dmx_setValue(DMXDevice* dev, uint16_t channel, uint8_t value);

View 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"

View 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();

View File

@@ -1,6 +1,7 @@
//dmxSender.cpp
#include "dmxSender.h"
#include <iostream>
#define DMX_START_CODE 0x00
#define BREAK_DURATION_US 110
@@ -11,61 +12,132 @@
// 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
bool DMXDevice::connect(int port){
ftStatus = FT_Open(port, &ftHandle);
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) {
return true;
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 true;
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);
});
return false;
updateThread.detach();
std::cout << " [DMXSENDER] " << "Sending job executed!" << std::endl;
std::cout << " [DMXSENDER] " << "DMXDevice connected!" << std::endl;
return DMX_OK;
}
// Activate the DMX flow
void DMXDevice::activate(){
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
void DMXDevice::deactivate(){
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 channe
void DMXDevice::setValue(int channel, int value){
// 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
void DMXDevice::sendBreak(FT_HANDLE ftHandle) {
FT_SetBreakOn(ftHandle); // Set BREAK ON
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));
FT_SetBreakOff(ftHandle); // Set BREAK OFF
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
@@ -73,7 +145,12 @@ void DMXDevice::sendDMX(FT_HANDLE ftHandle) {
while (true) {
if(isOutputActivated){
// Send the BREAK
sendBreak(ftHandle);
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));
@@ -81,8 +158,9 @@ void DMXDevice::sendDMX(FT_HANDLE ftHandle) {
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.
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;
}
@@ -93,6 +171,15 @@ void DMXDevice::sendDMX(FT_HANDLE ftHandle) {
}
}
// 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
@@ -106,22 +193,38 @@ extern "C" {
}
// Connect a DMX device
bool dmx_connect(DMXDevice* dev, int port) {
return dev->connect(port);
DMXError dmx_connect(DMXDevice* dev, char* serialNumber) {
try{
return dev->connect(serialNumber);
} catch (...) {
return DMX_UNKNOWN_ERROR;
}
}
// Activate a DMX device
void dmx_activate(DMXDevice* dev) {
dev->activate();
DMXError dmx_activate(DMXDevice* dev) {
try{
return dev->activate();
} catch (...) {
return DMX_UNKNOWN_ERROR;
}
}
// Deactivate a DMX device
void dmx_deactivate(DMXDevice* dev) {
dev->deactivate();
DMXError dmx_deactivate(DMXDevice* dev) {
try{
return dev->activate();
} catch (...) {
return DMX_UNKNOWN_ERROR;
}
}
// Set the channel value of a DMX device
void dmx_setValue(DMXDevice* dev, int channel, int value) {
dev->setValue(channel, value);
DMXError dmx_setValue(DMXDevice* dev, int channel, int value) {
try {
return dev->setValue(channel, value);
} catch (...) {
return DMX_UNKNOWN_ERROR;
}
}
}

View File

@@ -15,6 +15,19 @@
#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
@@ -24,25 +37,28 @@ public:
~DMXDevice();
// Connect the device on a specific port
bool connect(int port);
DMXError connect(char* serialNumber);
// Activate the DMX flow
void activate();
DMXError activate();
// Deactivate the DMX flow
void deactivate();
DMXError deactivate();
// Set the value of a DMX channel
void setValue(int channel, int value);
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<unsigned char> dmxData[DMX_CHANNELS + 1]; // For storing dynamically the DMX data
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
void sendBreak(FT_HANDLE ftHandle);
FT_STATUS sendBreak(FT_HANDLE ftHandle);
// Continuously send the DMX frame
void sendDMX(FT_HANDLE ftHandle);

View 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;
// }
}

View 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;
}

View File

@@ -5,7 +5,8 @@ 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() error // Disconnect the peripheral
IsConnected() bool // Return if the peripheral is connected or not
Disconnect(context.Context) 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
@@ -31,7 +32,7 @@ type PeripheralFinder interface {
Stop() error // Stop the detection
ForceScan() // Explicitly scans for peripherals
RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data
UnregisterPeripheral(string) error // Unregisters an existing peripheral
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

View File

@@ -1,44 +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 << ":";
// Add information about the hardware open state
if (devInfo[i].Flags & FT_FLAGS_OPENED) {
std::cout << "O" << std::endl;
} else {
std::cout << "C" << std::endl;
}
}
}
free(devInfo);
}
}

View File

@@ -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>

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -72,7 +72,7 @@ func (a *App) RemovePeripheral(protocolName string, peripheralID string) error {
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(peripheralID)
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")