generated from thinkode/modelRepository
create subpackages for different endpoints type
This commit is contained in:
172
hardware/genericftdi/FTDIEndpoint.go
Normal file
172
hardware/genericftdi/FTDIEndpoint.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package genericftdi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dmxconnect/hardware"
|
||||
"sync"
|
||||
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../../build/bin -ldmxSender
|
||||
#include "cpp/include/dmxSenderBridge.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Endpoint contains the data of an FTDI endpoint
|
||||
type Endpoint struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
info hardware.EndpointInfo // The endpoint basic data
|
||||
dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
|
||||
}
|
||||
|
||||
// NewEndpoint creates a new FTDI endpoint
|
||||
func NewEndpoint(info hardware.EndpointInfo) *Endpoint {
|
||||
log.Info().Str("file", "FTDIEndpoint").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI endpoint created")
|
||||
return &Endpoint{
|
||||
info: info,
|
||||
dmxSender: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect connects the FTDI endpoint
|
||||
func (p *Endpoint) Connect(ctx context.Context) error {
|
||||
// Check if the device has already been created
|
||||
if p.dmxSender != nil {
|
||||
return errors.Errorf("the DMX device has already been created!")
|
||||
}
|
||||
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusConnecting)
|
||||
|
||||
// Create the DMX sender
|
||||
p.dmxSender = C.dmx_create()
|
||||
|
||||
// Connect the FTDI
|
||||
serialNumber := C.CString(p.info.SerialNumber)
|
||||
defer C.free(unsafe.Pointer(serialNumber))
|
||||
if C.dmx_connect(p.dmxSender, serialNumber) != C.DMX_OK {
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDisconnected)
|
||||
log.Error().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device")
|
||||
return errors.Errorf("unable to connect '%s'", p.info.SerialNumber)
|
||||
}
|
||||
|
||||
p.wg.Add(1)
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
<-ctx.Done()
|
||||
_ = p.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDeactivated)
|
||||
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the FTDI endpoint
|
||||
func (p *Endpoint) Disconnect(ctx context.Context) error {
|
||||
// Check if the device has already been created
|
||||
if p.dmxSender == nil {
|
||||
return errors.Errorf("the DMX device has not been connected!")
|
||||
}
|
||||
|
||||
// Destroy the dmx sender
|
||||
C.dmx_destroy(p.dmxSender)
|
||||
|
||||
// Reset the pointer to the endpoint
|
||||
p.dmxSender = nil
|
||||
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDisconnected)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate activates the FTDI endpoint
|
||||
func (p *Endpoint) Activate(ctx context.Context) error {
|
||||
// Check if the device has already been created
|
||||
if p.dmxSender == nil {
|
||||
return errors.Errorf("the DMX sender has not been created!")
|
||||
}
|
||||
|
||||
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("activating FTDI endpoint...")
|
||||
|
||||
err := C.dmx_activate(p.dmxSender)
|
||||
if err != C.DMX_OK {
|
||||
return errors.Errorf("unable to activate the DMX sender!")
|
||||
}
|
||||
|
||||
// Test only
|
||||
C.dmx_setValue(p.dmxSender, C.uint16_t(1), C.uint8_t(255))
|
||||
C.dmx_setValue(p.dmxSender, C.uint16_t(5), C.uint8_t(255))
|
||||
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusActivated)
|
||||
|
||||
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("DMX device activated successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deactivate deactivates the FTDI endpoint
|
||||
func (p *Endpoint) Deactivate(ctx context.Context) error {
|
||||
// Check if the device has already been created
|
||||
if p.dmxSender == nil {
|
||||
return errors.Errorf("the DMX device has not been created!")
|
||||
}
|
||||
|
||||
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("deactivating FTDI endpoint...")
|
||||
|
||||
err := C.dmx_deactivate(p.dmxSender)
|
||||
if err != C.DMX_OK {
|
||||
return errors.Errorf("unable to deactivate the DMX sender!")
|
||||
}
|
||||
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDeactivated)
|
||||
log.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSettings sets a specific setting for this endpoint
|
||||
func (p *Endpoint) SetSettings(ctx context.Context, settings map[string]any) error {
|
||||
return errors.Errorf("unable to set the settings: not implemented")
|
||||
}
|
||||
|
||||
// SetDeviceProperty sends a command to the specified device
|
||||
func (p *Endpoint) 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.Trace().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("setting device property on FTDI endpoint...")
|
||||
|
||||
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", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("device property set on FTDI endpoint successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSettings gets the endpoint settings
|
||||
func (p *Endpoint) GetSettings() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
// GetInfo gets all the endpoint information
|
||||
func (p *Endpoint) GetInfo() hardware.EndpointInfo {
|
||||
return p.info
|
||||
}
|
||||
|
||||
// WaitStop wait about the endpoint to close
|
||||
func (p *Endpoint) WaitStop() error {
|
||||
log.Info().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("waiting for FTDI endpoint to close...")
|
||||
p.wg.Wait()
|
||||
log.Info().Str("file", "FTDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("FTDI endpoint closed!")
|
||||
return nil
|
||||
}
|
||||
181
hardware/genericftdi/FTDIProvider.go
Normal file
181
hardware/genericftdi/FTDIProvider.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package genericftdi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dmxconnect/hardware"
|
||||
"fmt"
|
||||
goRuntime "runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../../build/bin -ldetectFTDI
|
||||
#include "cpp/include/detectFTDIBridge.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Provider manages all the FTDI endpoints
|
||||
type Provider struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
|
||||
detected map[string]*Endpoint // Detected endpoints
|
||||
|
||||
scanEvery time.Duration // Scans endpoints periodically
|
||||
|
||||
onArrival func(context.Context, hardware.Endpoint) // When a endpoint arrives
|
||||
onRemoval func(context.Context, hardware.Endpoint) // When a endpoint goes away
|
||||
}
|
||||
|
||||
// NewProvider creates a new FTDI provider
|
||||
func NewProvider(scanEvery time.Duration) *Provider {
|
||||
log.Trace().Str("file", "FTDIProvider").Msg("FTDI provider created")
|
||||
return &Provider{
|
||||
scanEvery: scanEvery,
|
||||
detected: make(map[string]*Endpoint),
|
||||
}
|
||||
}
|
||||
|
||||
// OnArrival is the callback function when a new endpoint arrives
|
||||
func (f *Provider) OnArrival(cb func(context.Context, hardware.Endpoint)) {
|
||||
f.onArrival = cb
|
||||
}
|
||||
|
||||
// OnRemoval i the callback when a endpoint goes away
|
||||
func (f *Provider) OnRemoval(cb func(context.Context, hardware.Endpoint)) {
|
||||
f.onRemoval = cb
|
||||
}
|
||||
|
||||
// Initialize initializes the FTDI provider
|
||||
func (f *Provider) Initialize() error {
|
||||
// Check platform
|
||||
if goRuntime.GOOS != "windows" {
|
||||
log.Error().Str("file", "FTDIProvider").Str("platform", goRuntime.GOOS).Msg("FTDI provider not compatible with your platform")
|
||||
return fmt.Errorf("the FTDI provider is not compatible with your platform yet (%s)", goRuntime.GOOS)
|
||||
}
|
||||
log.Trace().Str("file", "FTDIProvider").Msg("FTDI provider initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates a new endpoint, based on the endpoint information (manually created)
|
||||
func (f *Provider) Create(ctx context.Context, endpointInfo hardware.EndpointInfo) (hardware.EndpointInfo, error) {
|
||||
return hardware.EndpointInfo{}, nil
|
||||
}
|
||||
|
||||
// Remove removes an existing endpoint (manually created)
|
||||
func (f *Provider) Remove(ctx context.Context, endpoint hardware.Endpoint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the provider and search for endpoints
|
||||
func (f *Provider) Start(ctx context.Context) error {
|
||||
f.wg.Add(1)
|
||||
go func() {
|
||||
ticker := time.NewTicker(f.scanEvery)
|
||||
defer ticker.Stop()
|
||||
defer f.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
// Scan the endpoints
|
||||
err := f.scanEndpoints(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIProvider").Msg("unable to scan FTDI endpoints")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (f *Provider) GetName() string {
|
||||
return "FTDI"
|
||||
}
|
||||
|
||||
// scanEndpoints scans the FTDI endpoints
|
||||
func (f *Provider) scanEndpoints(ctx context.Context) error {
|
||||
log.Trace().Str("file", "FTDIProvider").Msg("FTDI scan triggered")
|
||||
|
||||
count := int(C.get_endpoints_number())
|
||||
|
||||
log.Info().Int("number", count).Msg("number of FTDI devices connected")
|
||||
|
||||
// Allocating C array
|
||||
size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIEndpointC{}))
|
||||
devicesPtr := C.malloc(size)
|
||||
defer C.free(devicesPtr)
|
||||
|
||||
devices := (*[1 << 20]C.FTDIEndpointC)(devicesPtr)[:count:count]
|
||||
|
||||
C.get_ftdi_devices((*C.FTDIEndpointC)(devicesPtr), C.int(count))
|
||||
|
||||
currentMap := make(map[string]hardware.EndpointInfo)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
d := devices[i]
|
||||
|
||||
sn := C.GoString(d.serialNumber)
|
||||
desc := C.GoString(d.description)
|
||||
// isOpen := d.isOpen != 0
|
||||
|
||||
currentMap[sn] = hardware.EndpointInfo{
|
||||
SerialNumber: sn,
|
||||
Name: desc,
|
||||
// IsOpen: isOpen,
|
||||
ProtocolName: "FTDI",
|
||||
}
|
||||
|
||||
// Free C memory
|
||||
C.free_ftdi_device(&d)
|
||||
}
|
||||
|
||||
log.Info().Any("endpoints", currentMap).Msg("available FTDI endpoints")
|
||||
|
||||
// Detect arrivals
|
||||
for sn, endpointData := range currentMap {
|
||||
// If the scanned endpoint isn't in the detected list, create it
|
||||
if _, known := f.detected[sn]; !known {
|
||||
|
||||
endpoint := NewEndpoint(endpointData)
|
||||
|
||||
if f.onArrival != nil {
|
||||
f.onArrival(ctx, endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect removals
|
||||
for detectedSN, detectedEndpoint := range f.detected {
|
||||
if _, still := currentMap[detectedSN]; !still {
|
||||
|
||||
// Delete it from the detected list
|
||||
delete(f.detected, detectedSN)
|
||||
|
||||
// Execute the removal callback
|
||||
if f.onRemoval != nil {
|
||||
f.onRemoval(ctx, detectedEndpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitStop stops the provider
|
||||
func (f *Provider) WaitStop() error {
|
||||
log.Trace().Str("file", "FTDIProvider").Msg("stopping the FTDI provider...")
|
||||
|
||||
// Wait for goroutines to stop
|
||||
f.wg.Wait()
|
||||
|
||||
log.Trace().Str("file", "FTDIProvider").Msg("FTDI provider stopped")
|
||||
return nil
|
||||
}
|
||||
7
hardware/genericftdi/cpp/generate.bat
Normal file
7
hardware/genericftdi/cpp/generate.bat
Normal file
@@ -0,0 +1,7 @@
|
||||
@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/genericftdi/cpp/include/detectFTDIBridge.h
Normal file
19
hardware/genericftdi/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;
|
||||
} FTDIEndpointC;
|
||||
|
||||
int get_endpoints_number();
|
||||
void get_ftdi_devices(FTDIEndpointC* devices, int count);
|
||||
void free_ftdi_device(FTDIEndpointC* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
30
hardware/genericftdi/cpp/include/dmxSenderBridge.h
Normal file
30
hardware/genericftdi/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);
|
||||
BIN
hardware/genericftdi/cpp/lib/ftd2xx.lib
Normal file
BIN
hardware/genericftdi/cpp/lib/ftd2xx.lib
Normal file
Binary file not shown.
91
hardware/genericftdi/cpp/src/detectFTDI.cpp
Normal file
91
hardware/genericftdi/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 getFTDIEndpointsNumber() {
|
||||
DWORD numDevs = 0;
|
||||
if (FT_CreateDeviceInfoList(&numDevs) != FT_OK) {
|
||||
std::cerr << "Unable to get FTDI devices: create list error\n";
|
||||
}
|
||||
return numDevs;
|
||||
}
|
||||
|
||||
std::vector<FTDIEndpoint> scanFTDIEndpoints() {
|
||||
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<FTDIEndpoint> endpoints;
|
||||
endpoints.reserve(numDevs);
|
||||
|
||||
for (const auto& info : devInfo) {
|
||||
if (info.SerialNumber[0] != '\0') {
|
||||
endpoints.push_back({
|
||||
info.SerialNumber,
|
||||
info.Description,
|
||||
static_cast<bool>(info.Flags & FT_FLAGS_OPENED)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int get_endpoints_number() {
|
||||
return getFTDIEndpointsNumber();
|
||||
}
|
||||
|
||||
void get_ftdi_devices(FTDIEndpointC* devices, int count) {
|
||||
if (!devices || count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto list = scanFTDIEndpoints();
|
||||
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(FTDIEndpointC* 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/genericftdi/cpp/src/detectFTDI.h
Normal file
14
hardware/genericftdi/cpp/src/detectFTDI.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "ftd2xx.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct FTDIEndpoint {
|
||||
std::string serialNumber;
|
||||
std::string description;
|
||||
bool isOpen;
|
||||
};
|
||||
|
||||
int getFTDIEndpointsNumber();
|
||||
std::vector<FTDIEndpoint> scanFTDIEndpoints();
|
||||
225
hardware/genericftdi/cpp/src/dmxSender.cpp
Normal file
225
hardware/genericftdi/cpp/src/dmxSender.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
//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 connected!" << std::endl;
|
||||
return DMX_OK;
|
||||
}
|
||||
|
||||
// Activate the DMX flow
|
||||
DMXError DMXDevice::activate(){
|
||||
std::cout << " [DMXSENDER] " << "Activating the DMXDevice..." << std::endl;
|
||||
|
||||
isOutputActivated.store(true);
|
||||
// Send the DMX frames
|
||||
std::thread updateThread([this]() {
|
||||
this->sendDMX(ftHandle);
|
||||
});
|
||||
|
||||
updateThread.detach();
|
||||
|
||||
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 (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/genericftdi/cpp/src/dmxSender.h
Normal file
65
hardware/genericftdi/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 endpoint 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);
|
||||
};
|
||||
1667
hardware/genericftdi/cpp/src/ftd2xx.h
Normal file
1667
hardware/genericftdi/cpp/src/ftd2xx.h
Normal file
File diff suppressed because it is too large
Load Diff
14
hardware/genericftdi/cpp/test/detectFTDI_test.cpp
Normal file
14
hardware/genericftdi/cpp/test/detectFTDI_test.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "../src/detectFTDI.h"
|
||||
#include <iostream>
|
||||
|
||||
int main(){
|
||||
int endpointsNumber = getFTDIEndpointsNumber();
|
||||
|
||||
|
||||
std::vector<FTDIEndpoint> endpoints = scanFTDIEndpoints();
|
||||
|
||||
// for (const auto& endpoint : endpoints) {
|
||||
// std::cout << endpoint.serialNumber << " (" << endpoint.description << ") -> IS OPEN: " << endpoint.isOpen << std::endl;
|
||||
// }
|
||||
|
||||
}
|
||||
122
hardware/genericftdi/cpp/test/dmxSender_test.cpp
Normal file
122
hardware/genericftdi/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;
|
||||
}
|
||||
Reference in New Issue
Block a user