generated from thinkode/modelRepository
34-midi (#35)
Closes #34 Implement MIDI peripherals Implement device concept Cleaning project Reviewed-on: #35
This commit was merged in pull request #35.
This commit is contained in:
@@ -1,314 +0,0 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
goRuntime "runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldetectFTDI
|
||||
#include "cpp/include/detectFTDIBridge.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// FTDIFinder manages all the FTDI peripherals
|
||||
type FTDIFinder struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
|
||||
saved map[string]PeripheralInfo // Peripherals saved in the project
|
||||
detected map[string]*FTDIPeripheral // Detected peripherals
|
||||
|
||||
scanEvery time.Duration // Scans peripherals periodically
|
||||
|
||||
onArrival func(p PeripheralInfo) // When a peripheral arrives
|
||||
onRemoval func(p PeripheralInfo) // When a peripheral goes away
|
||||
}
|
||||
|
||||
// NewFTDIFinder creates a new FTDI finder
|
||||
func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder {
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created")
|
||||
return &FTDIFinder{
|
||||
scanEvery: scanEvery,
|
||||
saved: make(map[string]PeripheralInfo),
|
||||
detected: make(map[string]*FTDIPeripheral),
|
||||
}
|
||||
}
|
||||
|
||||
// OnArrival is the callback function when a new peripheral arrives
|
||||
func (f *FTDIFinder) OnArrival(cb func(p PeripheralInfo)) {
|
||||
f.onArrival = cb
|
||||
}
|
||||
|
||||
// OnRemoval i the callback when a peripheral goes away
|
||||
func (f *FTDIFinder) OnRemoval(cb func(p PeripheralInfo)) {
|
||||
f.onRemoval = cb
|
||||
}
|
||||
|
||||
// RegisterPeripheral registers a new peripheral
|
||||
func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
f.saved[peripheralData.SerialNumber] = peripheralData
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
|
||||
|
||||
// If already detected, connect it
|
||||
if peripheral, ok := f.detected[peripheralData.SerialNumber]; ok {
|
||||
f.wg.Add(1)
|
||||
go func() {
|
||||
defer f.wg.Done()
|
||||
err := peripheral.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral")
|
||||
return
|
||||
}
|
||||
// Peripheral connected, activate it
|
||||
err = peripheral.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the FTDI peripheral")
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Emits the event in the hardware
|
||||
runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData)
|
||||
|
||||
return peripheralData.SerialNumber, nil
|
||||
}
|
||||
|
||||
// UnregisterPeripheral unregisters an existing peripheral
|
||||
func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
if peripheral, detected := f.detected[peripheralData.SerialNumber]; detected {
|
||||
// Deactivating peripheral
|
||||
err := peripheral.Deactivate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral")
|
||||
return nil
|
||||
}
|
||||
// Disconnecting peripheral
|
||||
err = peripheral.Disconnect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
delete(f.saved, peripheralData.SerialNumber)
|
||||
runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize initializes the FTDI finder
|
||||
func (f *FTDIFinder) Initialize() error {
|
||||
// Check platform
|
||||
if goRuntime.GOOS != "windows" {
|
||||
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)
|
||||
}
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the finder and search for peripherals
|
||||
func (f *FTDIFinder) 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 peripherals
|
||||
err := f.scanPeripherals(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Msg("unable to scan FTDI peripherals")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceScan explicitly asks for scanning peripherals
|
||||
func (f *FTDIFinder) ForceScan() {
|
||||
// select {
|
||||
// case f.scanChannel <- struct{}{}:
|
||||
// default:
|
||||
// // Ignore if the channel is full or if it is closed
|
||||
// }
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (f *FTDIFinder) GetName() string {
|
||||
return "FTDI"
|
||||
}
|
||||
|
||||
// GetPeripheralSettings gets the peripheral settings
|
||||
func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.detected[peripheralID]
|
||||
if !found {
|
||||
// FTDI not detected, return the last settings saved
|
||||
if savedPeripheral, isFound := f.saved[peripheralID]; isFound {
|
||||
return savedPeripheral.Settings, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
return peripheral.GetSettings(), nil
|
||||
}
|
||||
|
||||
// SetPeripheralSettings sets the peripheral settings
|
||||
func (f *FTDIFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.detected[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
return fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
return peripheral.SetSettings(settings)
|
||||
}
|
||||
|
||||
// scanPeripherals scans the FTDI peripherals
|
||||
func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI scan triggered")
|
||||
|
||||
count := int(C.get_peripherals_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.FTDIPeripheralC{}))
|
||||
devicesPtr := C.malloc(size)
|
||||
defer C.free(devicesPtr)
|
||||
|
||||
devices := (*[1 << 20]C.FTDIPeripheralC)(devicesPtr)[:count:count]
|
||||
|
||||
C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
|
||||
|
||||
currentMap := make(map[string]PeripheralInfo)
|
||||
|
||||
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] = PeripheralInfo{
|
||||
SerialNumber: sn,
|
||||
Name: desc,
|
||||
// IsOpen: isOpen,
|
||||
ProtocolName: "FTDI",
|
||||
}
|
||||
|
||||
// Free C memory
|
||||
C.free_ftdi_device(&d)
|
||||
}
|
||||
|
||||
log.Info().Any("peripherals", currentMap).Msg("available FTDI peripherals")
|
||||
|
||||
// Detect arrivals
|
||||
for sn, peripheralData := range currentMap {
|
||||
if _, known := f.detected[sn]; !known {
|
||||
peripheral := NewFTDIPeripheral(peripheralData)
|
||||
f.detected[sn] = peripheral
|
||||
|
||||
if f.onArrival != nil {
|
||||
f.onArrival(peripheralData)
|
||||
}
|
||||
log.Info().Str("sn", sn).Str("name", peripheralData.Name).Msg("[FTDI] New peripheral detected")
|
||||
|
||||
// If the peripheral is saved in the project => connect
|
||||
if _, saved := f.saved[sn]; saved {
|
||||
f.wg.Add(1)
|
||||
go func(p PeripheralInfo) {
|
||||
defer f.wg.Done()
|
||||
err := peripheral.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", p.SerialNumber).Msg("unable to connect the FTDI peripheral")
|
||||
return
|
||||
}
|
||||
err = peripheral.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", p.SerialNumber).Msg("unable to activate the FTDI peripheral")
|
||||
return
|
||||
}
|
||||
}(peripheralData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect removals
|
||||
for sn, oldPeripheral := range f.detected {
|
||||
if _, still := currentMap[sn]; !still {
|
||||
|
||||
// Properly clean the DMX device
|
||||
err := oldPeripheral.Deactivate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", sn).Msg("unable to deactivate the FTDI peripheral after disconnection")
|
||||
}
|
||||
err = oldPeripheral.Disconnect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", sn).Msg("unable to disconnect the FTDI peripheral after disconnection")
|
||||
}
|
||||
|
||||
// Delete it from the detected list
|
||||
delete(f.detected, sn)
|
||||
log.Info().Str("sn", sn).Str("name", oldPeripheral.GetInfo().Name).Msg("[FTDI] peripheral removed")
|
||||
|
||||
// Execute the removal callback
|
||||
if f.onRemoval != nil {
|
||||
f.onRemoval(oldPeripheral.GetInfo())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitStop stops the finder
|
||||
func (f *FTDIFinder) WaitStop() error {
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
|
||||
|
||||
// Close the channel
|
||||
// close(f.scanChannel)
|
||||
|
||||
// Wait for all the peripherals to close
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals")
|
||||
var errs []error
|
||||
for registeredPeripheralSN, registeredPeripheral := range f.detected {
|
||||
err := registeredPeripheral.WaitStop()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for goroutines to stop
|
||||
f.wg.Wait()
|
||||
|
||||
// Returning errors
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
|
||||
return nil
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
|
||||
// FTDIPeripheral contains the data of an FTDI peripheral
|
||||
type FTDIPeripheral struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
info PeripheralInfo // The peripheral basic data
|
||||
dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
|
||||
}
|
||||
|
||||
// NewFTDIPeripheral creates a new FTDI peripheral
|
||||
func NewFTDIPeripheral(info PeripheralInfo) *FTDIPeripheral {
|
||||
log.Info().Str("file", "FTDIPeripheral").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI peripheral created")
|
||||
return &FTDIPeripheral{
|
||||
info: info,
|
||||
dmxSender: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect connects the FTDI peripheral
|
||||
func (p *FTDIPeripheral) 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(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting)
|
||||
|
||||
// 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(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
|
||||
log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device")
|
||||
return errors.Errorf("unable to connect '%s'", p.info.SerialNumber)
|
||||
}
|
||||
|
||||
p.wg.Add(1)
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
<-ctx.Done()
|
||||
_ = p.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
|
||||
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(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 peripheral
|
||||
p.dmxSender = nil
|
||||
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate activates the FTDI peripheral
|
||||
func (p *FTDIPeripheral) 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", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("activating FTDI peripheral...")
|
||||
|
||||
err := C.dmx_activate(p.dmxSender)
|
||||
if err != C.DMX_OK {
|
||||
return errors.Errorf("unable to activate the DMX sender!")
|
||||
}
|
||||
|
||||
// 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(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated)
|
||||
|
||||
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 {
|
||||
// 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", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("deactivating FTDI peripheral...")
|
||||
|
||||
err := C.dmx_deactivate(p.dmxSender)
|
||||
if err != C.DMX_OK {
|
||||
return errors.Errorf("unable to deactivate the DMX sender!")
|
||||
}
|
||||
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSettings sets a specific setting for this peripheral
|
||||
func (p *FTDIPeripheral) SetSettings(settings map[string]interface{}) error {
|
||||
return errors.Errorf("unable to set the settings: not implemented")
|
||||
}
|
||||
|
||||
// SetDeviceProperty sends a command to the specified device
|
||||
func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, 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", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("setting device property on FTDI peripheral...")
|
||||
|
||||
err := C.dmx_setValue(p.dmxSender, C.uint16_t(channelNumber), C.uint8_t(channelValue))
|
||||
if err != C.DMX_OK {
|
||||
return errors.Errorf("unable to update the channel value!")
|
||||
}
|
||||
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("device property set on FTDI peripheral successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSettings gets the peripheral settings
|
||||
func (p *FTDIPeripheral) GetSettings() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
// GetInfo gets all the peripheral information
|
||||
func (p *FTDIPeripheral) GetInfo() PeripheralInfo {
|
||||
return p.info
|
||||
}
|
||||
|
||||
// WaitStop wait about the peripheral to close
|
||||
func (p *FTDIPeripheral) WaitStop() error {
|
||||
log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for FTDI peripheral to close...")
|
||||
p.wg.Wait()
|
||||
log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("FTDI peripheral closed!")
|
||||
return nil
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattrtaylor/go-rtmidi"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// MIDIFinder represents how the protocol is defined
|
||||
type MIDIFinder struct {
|
||||
findTicker time.Ticker // Peripherals find ticker
|
||||
registeredPeripherals map[string]MIDIPeripheral // The list of peripherals
|
||||
scanChannel chan struct{} // The channel to trigger a scan event
|
||||
goWait sync.WaitGroup // Check goroutines execution
|
||||
}
|
||||
|
||||
// NewMIDIFinder creates a new DMXUSB protocol
|
||||
func NewMIDIFinder(findPeriod time.Duration) *MIDIFinder {
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder created")
|
||||
return &MIDIFinder{
|
||||
findTicker: *time.NewTicker(findPeriod),
|
||||
registeredPeripherals: make(map[string]MIDIPeripheral),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize initializes the MIDI driver
|
||||
func (f *MIDIFinder) Initialize() error {
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterPeripheral registers a new peripheral
|
||||
func (f *MIDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
|
||||
peripheral, err := NewMIDIPeripheral(peripheralData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create the MIDI peripheral: %v", err)
|
||||
}
|
||||
f.registeredPeripherals[peripheralData.SerialNumber] = *peripheral
|
||||
log.Trace().Any("periph", &peripheral).Str("file", "MIDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created")
|
||||
return peripheralData.SerialNumber, nil
|
||||
}
|
||||
|
||||
// UnregisterPeripheral unregisters an existing peripheral
|
||||
func (f *MIDIFinder) UnregisterPeripheral(peripheralID string) error {
|
||||
peripheral, registered := f.registeredPeripherals[peripheralID]
|
||||
if registered {
|
||||
err := peripheral.Disconnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
delete(f.registeredPeripherals, peripheralID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the finder and search for peripherals
|
||||
func (f *MIDIFinder) Start(ctx context.Context) error {
|
||||
f.goWait.Add(1)
|
||||
go func() {
|
||||
defer f.goWait.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-f.findTicker.C:
|
||||
// Scan the peripherals
|
||||
err := f.scanPeripherals(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "MIDIFinder").Msg("unable to scan MIDI peripherals")
|
||||
}
|
||||
case <-f.scanChannel:
|
||||
// Scan the peripherals
|
||||
err := f.scanPeripherals(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "MIDIFinder").Msg("unable to scan MIDI peripherals")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the finder
|
||||
func (f *MIDIFinder) Stop() error {
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("stopping the MIDI finder...")
|
||||
// Wait for goroutines to stop
|
||||
f.goWait.Wait()
|
||||
// Stop the ticker
|
||||
f.findTicker.Stop()
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (f *MIDIFinder) GetName() string {
|
||||
return "MIDI"
|
||||
}
|
||||
|
||||
// GetPeripheralSettings gets the peripheral settings
|
||||
func (f *MIDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.registeredPeripherals[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
return nil, fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
log.Debug().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
return peripheral.GetSettings(), nil
|
||||
}
|
||||
|
||||
// SetPeripheralSettings sets the peripheral settings
|
||||
func (f *MIDIFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.registeredPeripherals[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
return fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
log.Debug().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
return peripheral.SetSettings(settings)
|
||||
}
|
||||
|
||||
func splitStringAndNumber(input string) (string, int, error) {
|
||||
// Regular expression to match the text part and the number at the end
|
||||
re := regexp.MustCompile(`^(.*?)(\d+)$`)
|
||||
matches := re.FindStringSubmatch(input)
|
||||
|
||||
// Check if the regex found both a text part and a number
|
||||
if len(matches) == 3 {
|
||||
// matches[1]: text part (might contain trailing spaces)
|
||||
// matches[2]: numeric part as a string
|
||||
textPart := strings.TrimSpace(matches[1]) // Remove any trailing spaces from the text
|
||||
numberPart, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return "", 0, err // Return error if the number conversion fails
|
||||
}
|
||||
return textPart, numberPart, nil
|
||||
}
|
||||
|
||||
// Return an error if no trailing number is found
|
||||
return "", 0, fmt.Errorf("no number found at the end of the string")
|
||||
}
|
||||
|
||||
// ForceScan explicily asks for scanning peripherals
|
||||
func (f *MIDIFinder) ForceScan() {
|
||||
f.scanChannel <- struct{}{}
|
||||
}
|
||||
|
||||
// scanPeripherals scans the MIDI peripherals
|
||||
func (f *MIDIFinder) scanPeripherals(ctx context.Context) error {
|
||||
// midiPeripherals := make(map[string]Peripheral)
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("opening MIDI scanner port...")
|
||||
midiScanner, err := rtmidi.NewMIDIInDefault()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "MIDIFinder").Msg("unable to open the MIDI scanner port...")
|
||||
return fmt.Errorf("unable to open the MIDI scanner: %s", err)
|
||||
}
|
||||
defer midiScanner.Close()
|
||||
midiScanner.SetCallback(func(m rtmidi.MIDIIn, b []byte, f float64) {})
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("scanning MIDI peripherals...")
|
||||
devicesCount, err := midiScanner.PortCount()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "MIDIFinder").Msg("unable to scan MIDI peripherals...")
|
||||
return fmt.Errorf("unable to scan MIDI peripherals: %s", err)
|
||||
}
|
||||
for i := 0; i < devicesCount; i++ {
|
||||
portName, err := midiScanner.PortName(i)
|
||||
if err != nil {
|
||||
log.Warn().Str("file", "MIDIPeripheral").Msg("found peripheral without a correct name, set it to unknown")
|
||||
portName = "Unknown device 0"
|
||||
}
|
||||
// Separate data
|
||||
name, location, err := splitStringAndNumber(portName)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "MIDIFinder").Str("description", portName).Msg("invalid peripheral description")
|
||||
return fmt.Errorf("invalid pripheral description: %s", err)
|
||||
}
|
||||
log.Info().Str("file", "MIDIFinder").Str("name", name).Int("location", location).Msg("MIDI peripheral found")
|
||||
// Add the peripheral to the temporary list
|
||||
// sn := strings.ToLower(strings.Replace(name, " ", "_", -1))
|
||||
// midiPeripherals[sn] = NewMIDIPeripheral(name, location, sn)
|
||||
}
|
||||
// Compare with the current peripherals to detect arrivals/removals
|
||||
// removedList, addedList := comparePeripherals(f.peripherals, midiPeripherals)
|
||||
// Emit the events
|
||||
// emitPeripheralsEvents(ctx, removedList, PeripheralRemoval)
|
||||
log.Info().Str("file", "MIDIFinder").Msg("MIDI remove list emitted to the front")
|
||||
// emitPeripheralsEvents(ctx, addedList, PeripheralArrival)
|
||||
log.Info().Str("file", "MIDIFinder").Msg("MIDI add list emitted to the front")
|
||||
// Store the new peripherals list
|
||||
// f.peripherals = midiPeripherals
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePeripheral is not implemented here
|
||||
func (f *MIDIFinder) CreatePeripheral(context.Context) (Peripheral, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeletePeripheral is not implemented here
|
||||
func (f *MIDIFinder) DeletePeripheral(serialNumber string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// MIDIPeripheral contains the data of a MIDI peripheral
|
||||
type MIDIPeripheral struct {
|
||||
info PeripheralInfo // The peripheral info
|
||||
location int // The location of the peripheral
|
||||
settings map[string]interface{} // The settings of the peripheral
|
||||
}
|
||||
|
||||
// NewMIDIPeripheral creates a new MIDI peripheral
|
||||
func NewMIDIPeripheral(peripheralData PeripheralInfo) (*MIDIPeripheral, error) {
|
||||
log.Trace().Str("file", "MIDIPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("MIDI peripheral created")
|
||||
return &MIDIPeripheral{
|
||||
info: peripheralData,
|
||||
settings: peripheralData.Settings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Connect connects the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Connect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Disconnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate activates the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Activate(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deactivate deactivates the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Deactivate(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSettings sets a specific setting for this peripheral
|
||||
func (p *MIDIPeripheral) SetSettings(settings map[string]interface{}) error {
|
||||
p.settings = settings
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeviceProperty - not implemented for this kind of peripheral
|
||||
func (p *MIDIPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSettings gets the peripheral settings
|
||||
func (p *MIDIPeripheral) GetSettings() map[string]interface{} {
|
||||
return p.settings
|
||||
}
|
||||
|
||||
// GetInfo gets the peripheral information
|
||||
func (p *MIDIPeripheral) GetInfo() PeripheralInfo {
|
||||
return p.info
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// OS2LFinder represents how the protocol is defined
|
||||
type OS2LFinder struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
|
||||
saved map[string]*OS2LPeripheral // The list of saved peripherals
|
||||
|
||||
onArrival func(p PeripheralInfo) // When a peripheral arrives
|
||||
onRemoval func(p PeripheralInfo) // When a peripheral goes away
|
||||
}
|
||||
|
||||
// NewOS2LFinder creates a new OS2L finder
|
||||
func NewOS2LFinder() *OS2LFinder {
|
||||
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder created")
|
||||
return &OS2LFinder{
|
||||
saved: make(map[string]*OS2LPeripheral),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize initializes the finder
|
||||
func (f *OS2LFinder) Initialize() error {
|
||||
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnArrival is the callback function when a new peripheral arrives
|
||||
func (f *OS2LFinder) OnArrival(cb func(p PeripheralInfo)) {
|
||||
f.onArrival = cb
|
||||
}
|
||||
|
||||
// OnRemoval i the callback when a peripheral goes away
|
||||
func (f *OS2LFinder) OnRemoval(cb func(p PeripheralInfo)) {
|
||||
f.onRemoval = cb
|
||||
}
|
||||
|
||||
// RegisterPeripheral registers a new peripheral
|
||||
func (f *OS2LFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
// If the SerialNumber is empty, generate another one
|
||||
if peripheralData.SerialNumber == "" {
|
||||
peripheralData.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
|
||||
}
|
||||
|
||||
// Create a new OS2L peripheral
|
||||
peripheral, err := NewOS2LPeripheral(peripheralData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create the OS2L peripheral: %w", err)
|
||||
}
|
||||
// Set the event callback
|
||||
peripheral.SetEventCallback(func(event any) {
|
||||
runtime.EventsEmit(ctx, string(PeripheralEventEmitted), peripheralData.SerialNumber, event)
|
||||
})
|
||||
|
||||
f.saved[peripheralData.SerialNumber] = peripheral
|
||||
log.Trace().Str("file", "OS2LFinder").Str("serialNumber", peripheralData.SerialNumber).Msg("OS2L peripheral created")
|
||||
|
||||
// New OS2L peripheral has arrived
|
||||
if f.onArrival != nil {
|
||||
f.onArrival(peripheral.GetInfo())
|
||||
}
|
||||
|
||||
f.wg.Add(1)
|
||||
go func() {
|
||||
defer f.wg.Done()
|
||||
// Connect the OS2L peripheral
|
||||
err = peripheral.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "OS2LFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral")
|
||||
return
|
||||
}
|
||||
// Peripheral connected, activate it
|
||||
err = peripheral.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "OS2LFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the OS2L peripheral")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Emits the event in the hardware
|
||||
runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData)
|
||||
|
||||
return peripheralData.SerialNumber, nil
|
||||
}
|
||||
|
||||
// UnregisterPeripheral unregisters an existing peripheral
|
||||
func (f *OS2LFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
if peripheral, detected := f.saved[peripheralData.SerialNumber]; detected {
|
||||
// Deactivating peripheral
|
||||
err := peripheral.Deactivate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral")
|
||||
return nil
|
||||
}
|
||||
// Disconnecting peripheral
|
||||
err = peripheral.Disconnect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// The OS2L peripheral has gone
|
||||
f.onRemoval(peripheralData)
|
||||
|
||||
delete(f.saved, peripheralData.SerialNumber)
|
||||
runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (f *OS2LFinder) GetName() string {
|
||||
return "OS2L"
|
||||
}
|
||||
|
||||
// GetPeripheralSettings gets the peripheral settings
|
||||
func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]any, error) {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.saved[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the OS2L finder")
|
||||
return nil, fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
log.Debug().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the OS2L finder")
|
||||
return peripheral.GetSettings(), nil
|
||||
}
|
||||
|
||||
// SetPeripheralSettings sets the peripheral settings
|
||||
func (f *OS2LFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.saved[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
return fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
|
||||
// Set the peripheral settings
|
||||
return peripheral.SetSettings(ctx, settings)
|
||||
}
|
||||
|
||||
// Start starts the finder
|
||||
func (f *OS2LFinder) Start(ctx context.Context) error {
|
||||
// No peripherals to scan here
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitStop stops the finder
|
||||
func (f *OS2LFinder) WaitStop() error {
|
||||
log.Trace().Str("file", "OS2LFinder").Msg("stopping the OS2L finder...")
|
||||
|
||||
// Close the channel
|
||||
// close(f.scanChannel)
|
||||
|
||||
// Wait for all the peripherals to close
|
||||
log.Trace().Str("file", "OS2LFinder").Msg("closing all OS2L peripherals")
|
||||
var errs []error
|
||||
for registeredPeripheralSN, registeredPeripheral := range f.saved {
|
||||
err := registeredPeripheral.WaitStop()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Waiting internal tasks
|
||||
f.wg.Wait()
|
||||
|
||||
// Returning errors
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceScan scans the interfaces (not implemented)
|
||||
func (f *OS2LFinder) ForceScan() {
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
@REM windres dmxSender.rc dmxSender.o
|
||||
@REM windres detectFTDI.rc detectFTDI.o
|
||||
|
||||
@REM g++ -o dmxSender.exe dmxSender.cpp dmxSender.o -I"include" -L"lib" -lftd2xx -mwindows
|
||||
@REM g++ -o detectFTDI.exe detectFTDI.cpp detectFTDI.o -I"include" -L"lib" -lftd2xx -mwindows
|
||||
|
||||
@REM g++ -o dmxSender.exe dmxSender.cpp -I"include" -L"lib" -lftd2xx
|
||||
@REM g++ -o detectFTDI.exe detectFTDI.cpp -I"include" -L"lib" -lftd2xx
|
||||
|
||||
@REM g++ -c dmxSender.cpp -o dmxSender.o -I"include" -L"lib" -lftd2xx -mwindows
|
||||
|
||||
@REM g++ -c dmxSender_wrapper.cpp -o dmxSender_wrapper.o -I"include" -L"lib" -lftd2xx -mwindows
|
||||
|
||||
@REM g++ -shared -o ../../build/bin/libdmxSender.dll src/dmxSender.cpp -fPIC -Wl,--out-implib,../../build/bin/libdmxSender.dll.a -L"lib" -lftd2xx
|
||||
|
||||
@REM Compiling DETECTFTDI library
|
||||
g++ -shared -o ../../build/bin/libdetectFTDI.dll src/detectFTDI.cpp -fPIC -Wl,--out-implib,../../build/bin/libdetectFTDI.dll.a -L"lib" -lftd2xx -mwindows
|
||||
|
||||
@REM Compiling DMXSENDER library
|
||||
g++ -shared -o ../../build/bin/libdmxSender.dll src/dmxSender.cpp -fPIC -Wl,--out-implib,../../build/bin/libdmxSender.dll.a -L"lib" -lftd2xx -mwindows
|
||||
|
||||
@REM g++ -shared -o libdmxSender.so dmxSender.cpp -fPIC -I"include" -L"lib" -lftd2xx -mwindows
|
||||
@@ -1,14 +0,0 @@
|
||||
#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;
|
||||
// }
|
||||
|
||||
}
|
||||
72
hardware/devicesHandler.go
Normal file
72
hardware/devicesHandler.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeviceEvent is triggered when a device event changes
|
||||
type DeviceEvent string
|
||||
|
||||
const (
|
||||
// DeviceArrival is triggered when a device arrival
|
||||
DeviceArrival DeviceEvent = "DEVICE_ARRIVAL"
|
||||
// DeviceRemoval is triggered when a device removal
|
||||
DeviceRemoval EndpointEvent = "DEVICE_REMOVAL"
|
||||
)
|
||||
|
||||
// MappingInfo is the configuration for each device
|
||||
type MappingInfo struct {
|
||||
DeviceInfo struct {
|
||||
Name string `yaml:"name"`
|
||||
Manufacturer string `yaml:"manufacturer"`
|
||||
Type string `yaml:"type"`
|
||||
} `yaml:"device"`
|
||||
Features map[string]any `yaml:"features"`
|
||||
}
|
||||
|
||||
// DeviceInfo represents the device data
|
||||
type DeviceInfo struct {
|
||||
SerialNumber string // The device s/n
|
||||
Name string // The device name
|
||||
Manufacturer string // The device manufacturer
|
||||
Version string // The device version
|
||||
}
|
||||
|
||||
// Device represents the logical to be controlled
|
||||
type Device struct {
|
||||
DeviceInfo DeviceInfo // The device base information
|
||||
Endpoint Endpoint // The device endpoint which control this device
|
||||
}
|
||||
|
||||
// AddDevice adds a new device to the manager
|
||||
func (h *Manager) AddDevice(ctx context.Context, device DeviceInfo, endpoint Endpoint) error {
|
||||
// If the SerialNumber is empty, generate another one
|
||||
if device.SerialNumber == "" {
|
||||
device.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
|
||||
}
|
||||
|
||||
// Add or replace the device to the manager
|
||||
h.devices[device.SerialNumber] = &Device{device, endpoint}
|
||||
|
||||
// Send the event to the front
|
||||
runtime.EventsEmit(ctx, string(DeviceArrival), device)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDevice removes a device from the manager
|
||||
func (h *Manager) RemoveDevice(ctx context.Context, serialNumber string) error {
|
||||
// Delete the device from the manager
|
||||
if serialNumber == "" {
|
||||
return fmt.Errorf("the device s/n is empty")
|
||||
}
|
||||
delete(h.devices, serialNumber)
|
||||
|
||||
// Send the event to the front
|
||||
runtime.EventsEmit(ctx, string(DeviceRemoval), serialNumber)
|
||||
return nil
|
||||
}
|
||||
221
hardware/endpointsHandler.go
Normal file
221
hardware/endpointsHandler.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// EndpointEvent is trigger by the providers when the scan is complete
|
||||
type EndpointEvent string
|
||||
|
||||
// EndpointStatus is the endpoint status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED)
|
||||
type EndpointStatus string
|
||||
|
||||
const (
|
||||
// EndpointArrival is triggerd when a endpoint has been connected to the system
|
||||
EndpointArrival EndpointEvent = "PERIPHERAL_ARRIVAL"
|
||||
// EndpointRemoval is triggered when a endpoint has been disconnected from the system
|
||||
EndpointRemoval EndpointEvent = "PERIPHERAL_REMOVAL"
|
||||
// EndpointLoad is triggered when a endpoint is added to the project
|
||||
EndpointLoad EndpointEvent = "PERIPHERAL_LOAD"
|
||||
// EndpointUnload is triggered when a endpoint is removed from the project
|
||||
EndpointUnload EndpointEvent = "PERIPHERAL_UNLOAD"
|
||||
// EndpointStatusUpdated is triggered when a endpoint status has been updated (disconnected - connecting - deactivated - activated)
|
||||
EndpointStatusUpdated EndpointEvent = "PERIPHERAL_STATUS"
|
||||
// EndpointEventEmitted is triggered when a endpoint event is emitted
|
||||
EndpointEventEmitted EndpointEvent = "PERIPHERAL_EVENT_EMITTED"
|
||||
// EndpointStatusDisconnected : endpoint is now disconnected
|
||||
EndpointStatusDisconnected EndpointStatus = "PERIPHERAL_DISCONNECTED"
|
||||
// EndpointStatusConnecting : endpoint is now connecting
|
||||
EndpointStatusConnecting EndpointStatus = "PERIPHERAL_CONNECTING"
|
||||
// EndpointStatusDeactivated : endpoint is now deactivated
|
||||
EndpointStatusDeactivated EndpointStatus = "PERIPHERAL_DEACTIVATED"
|
||||
// EndpointStatusActivated : endpoint is now activated
|
||||
EndpointStatusActivated EndpointStatus = "PERIPHERAL_ACTIVATED"
|
||||
)
|
||||
|
||||
// Endpoint represents the methods used to manage a endpoint (input or output hardware)
|
||||
type Endpoint interface {
|
||||
Connect(context.Context) error // Connect the endpoint
|
||||
// SetEventCallback(func(any)) // Callback is called when an event is emitted from the endpoint
|
||||
Disconnect(context.Context) error // Disconnect the endpoint
|
||||
Activate(context.Context) error // Activate the endpoint
|
||||
Deactivate(context.Context) error // Deactivate the endpoint
|
||||
SetDeviceArrivalCallback(func(context.Context, DeviceInfo, Endpoint) error) // Set the callback function when a new device is detected by the endpoint
|
||||
SetDeviceRemovalCallback(func(context.Context, string) error) // Set the callback function when a device is not detected anymore by the endpoint
|
||||
GetSettings() map[string]any // Get the endpoint settings
|
||||
SetSettings(context.Context, map[string]any) error // Set a endpoint setting
|
||||
SetDeviceProperty(context.Context, uint32, byte) error // Update a device property
|
||||
WaitStop() error // Properly close the endpoint
|
||||
|
||||
GetInfo() EndpointInfo // Get the endpoint information
|
||||
}
|
||||
|
||||
// EndpointInfo represents a endpoint information
|
||||
type EndpointInfo struct {
|
||||
Name string `yaml:"name"` // Name of the endpoint
|
||||
SerialNumber string `yaml:"sn"` // S/N of the endpoint
|
||||
ProtocolName string `yaml:"protocol"` // Protocol name of the endpoint
|
||||
Settings map[string]any `yaml:"settings"` // Endpoint settings
|
||||
}
|
||||
|
||||
// RegisterEndpoint registers a new endpoint
|
||||
func (h *Manager) RegisterEndpoint(ctx context.Context, endpointInfo EndpointInfo) (string, error) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// Create the endpoint from its provider (if needed)
|
||||
if provider, found := h.providers[endpointInfo.ProtocolName]; found {
|
||||
var err error
|
||||
endpointInfo, err = provider.Create(ctx, endpointInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Do not save if the endpoint doesn't have a S/N
|
||||
if endpointInfo.SerialNumber == "" {
|
||||
return "", fmt.Errorf("serial number is empty for this endpoint")
|
||||
}
|
||||
|
||||
h.SavedEndpoints[endpointInfo.SerialNumber] = endpointInfo
|
||||
|
||||
runtime.EventsEmit(ctx, string(EndpointStatusUpdated), endpointInfo, EndpointStatusDisconnected)
|
||||
|
||||
// If already detected, connect it
|
||||
if endpoint, ok := h.DetectedEndpoints[endpointInfo.SerialNumber]; ok {
|
||||
h.wg.Add(1)
|
||||
go func() {
|
||||
defer h.wg.Done()
|
||||
err := endpoint.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIProvider").Str("endpointSN", endpointInfo.SerialNumber).Msg("unable to connect the endpoint")
|
||||
return
|
||||
}
|
||||
// Endpoint connected, activate it
|
||||
err = endpoint.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIProvider").Str("endpointSN", endpointInfo.SerialNumber).Msg("unable to activate the FTDI endpoint")
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Emits the event in the hardware
|
||||
runtime.EventsEmit(ctx, string(EndpointLoad), endpointInfo)
|
||||
|
||||
return endpointInfo.SerialNumber, nil
|
||||
}
|
||||
|
||||
// UnregisterEndpoint unregisters an existing endpoint
|
||||
func (h *Manager) UnregisterEndpoint(ctx context.Context, endpointInfo EndpointInfo) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if endpoint, detected := h.DetectedEndpoints[endpointInfo.SerialNumber]; detected {
|
||||
// Deactivating endpoint
|
||||
err := endpoint.Deactivate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", endpointInfo.SerialNumber).Msg("unable to deactivate the endpoint")
|
||||
return nil
|
||||
}
|
||||
// Disconnecting endpoint
|
||||
err = endpoint.Disconnect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", endpointInfo.SerialNumber).Msg("unable to disconnect the endpoint")
|
||||
return nil
|
||||
}
|
||||
// Remove the endpoint from its provider (if needed)
|
||||
if provider, found := h.providers[endpointInfo.ProtocolName]; found {
|
||||
err = provider.Remove(ctx, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(h.SavedEndpoints, endpointInfo.SerialNumber)
|
||||
runtime.EventsEmit(ctx, string(EndpointUnload), endpointInfo)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEndpointSettings gets the endpoint settings
|
||||
func (h *Manager) GetEndpointSettings(endpointSN string) (map[string]any, error) {
|
||||
// Return the specified endpoint
|
||||
endpoint, found := h.DetectedEndpoints[endpointSN]
|
||||
if !found {
|
||||
// Endpoint not detected, return the last settings saved
|
||||
if savedEndpoint, isFound := h.SavedEndpoints[endpointSN]; isFound {
|
||||
return savedEndpoint.Settings, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to found the endpoint")
|
||||
}
|
||||
return endpoint.GetSettings(), nil
|
||||
}
|
||||
|
||||
// SetEndpointSettings sets the endpoint settings
|
||||
func (h *Manager) SetEndpointSettings(ctx context.Context, endpointSN string, settings map[string]any) error {
|
||||
endpoint, found := h.DetectedEndpoints[endpointSN]
|
||||
if !found {
|
||||
return fmt.Errorf("unable to found the FTDI endpoint")
|
||||
}
|
||||
return endpoint.SetSettings(ctx, settings)
|
||||
}
|
||||
|
||||
// OnEndpointArrival is called when a endpoint arrives in the system
|
||||
func (h *Manager) OnEndpointArrival(ctx context.Context, endpoint Endpoint) {
|
||||
// Add the endpoint to the detected hardware
|
||||
h.DetectedEndpoints[endpoint.GetInfo().SerialNumber] = endpoint
|
||||
|
||||
// Specify the callback functions to manages devices
|
||||
endpoint.SetDeviceArrivalCallback(h.AddDevice)
|
||||
endpoint.SetDeviceRemovalCallback(h.RemoveDevice)
|
||||
|
||||
// If the endpoint is saved in the project, connect it
|
||||
if _, saved := h.SavedEndpoints[endpoint.GetInfo().SerialNumber]; saved {
|
||||
h.wg.Add(1)
|
||||
go func(p Endpoint) {
|
||||
defer h.wg.Done()
|
||||
err := p.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to connect the FTDI endpoint")
|
||||
return
|
||||
}
|
||||
err = p.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to activate the FTDI endpoint")
|
||||
return
|
||||
}
|
||||
}(endpoint)
|
||||
}
|
||||
|
||||
// TODO: Update the Endpoint reference in the corresponding devices
|
||||
|
||||
runtime.EventsEmit(ctx, string(EndpointArrival), endpoint.GetInfo())
|
||||
}
|
||||
|
||||
// OnEndpointRemoval is called when a endpoint exits the system
|
||||
func (h *Manager) OnEndpointRemoval(ctx context.Context, endpoint Endpoint) {
|
||||
// Properly deactivating and disconnecting the endpoint
|
||||
h.wg.Add(1)
|
||||
go func(p Endpoint) {
|
||||
defer h.wg.Done()
|
||||
err := p.Deactivate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to deactivate endpoint after disconnection")
|
||||
}
|
||||
err = p.Disconnect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to disconnect the endpoint after disconnection")
|
||||
}
|
||||
}(endpoint)
|
||||
|
||||
// Remove the endpoint from the hardware
|
||||
delete(h.DetectedEndpoints, endpoint.GetInfo().SerialNumber)
|
||||
|
||||
// TODO: Update the Endpoint reference in the corresponding devices
|
||||
runtime.EventsEmit(ctx, string(EndpointRemoval), endpoint.GetInfo())
|
||||
}
|
||||
180
hardware/genericftdi/FTDIEndpoint.go
Normal file
180
hardware/genericftdi/FTDIEndpoint.go
Normal file
@@ -0,0 +1,180 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// SetDeviceArrivalCallback is called when we need to add a new device to the hardware
|
||||
func (p *Endpoint) SetDeviceArrivalCallback(adc func(context.Context, hardware.DeviceInfo, hardware.Endpoint) error) {
|
||||
}
|
||||
|
||||
// SetDeviceRemovalCallback is called when we need to remove a device from the hardware
|
||||
func (p *Endpoint) SetDeviceRemovalCallback(rdc func(context.Context, string) error) {
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -8,11 +8,11 @@ typedef struct {
|
||||
char* serialNumber;
|
||||
char* description;
|
||||
int isOpen;
|
||||
} FTDIPeripheralC;
|
||||
} FTDIEndpointC;
|
||||
|
||||
int get_peripherals_number();
|
||||
void get_ftdi_devices(FTDIPeripheralC* devices, int count);
|
||||
void free_ftdi_device(FTDIPeripheralC* device);
|
||||
int get_endpoints_number();
|
||||
void get_ftdi_devices(FTDIEndpointC* devices, int count);
|
||||
void free_ftdi_device(FTDIEndpointC* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
int getFTDIPeripheralsNumber() {
|
||||
int getFTDIEndpointsNumber() {
|
||||
DWORD numDevs = 0;
|
||||
if (FT_CreateDeviceInfoList(&numDevs) != FT_OK) {
|
||||
std::cerr << "Unable to get FTDI devices: create list error\n";
|
||||
@@ -13,7 +13,7 @@ int getFTDIPeripheralsNumber() {
|
||||
return numDevs;
|
||||
}
|
||||
|
||||
std::vector<FTDIPeripheral> scanFTDIPeripherals() {
|
||||
std::vector<FTDIEndpoint> scanFTDIEndpoints() {
|
||||
DWORD numDevs = 0;
|
||||
if (FT_CreateDeviceInfoList(&numDevs) != FT_OK) {
|
||||
std::cerr << "Unable to get FTDI devices: create list error\n";
|
||||
@@ -30,12 +30,12 @@ std::vector<FTDIPeripheral> scanFTDIPeripherals() {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<FTDIPeripheral> peripherals;
|
||||
peripherals.reserve(numDevs);
|
||||
std::vector<FTDIEndpoint> endpoints;
|
||||
endpoints.reserve(numDevs);
|
||||
|
||||
for (const auto& info : devInfo) {
|
||||
if (info.SerialNumber[0] != '\0') {
|
||||
peripherals.push_back({
|
||||
endpoints.push_back({
|
||||
info.SerialNumber,
|
||||
info.Description,
|
||||
static_cast<bool>(info.Flags & FT_FLAGS_OPENED)
|
||||
@@ -43,21 +43,21 @@ std::vector<FTDIPeripheral> scanFTDIPeripherals() {
|
||||
}
|
||||
}
|
||||
|
||||
return peripherals;
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int get_peripherals_number() {
|
||||
return getFTDIPeripheralsNumber();
|
||||
int get_endpoints_number() {
|
||||
return getFTDIEndpointsNumber();
|
||||
}
|
||||
|
||||
void get_ftdi_devices(FTDIPeripheralC* devices, int count) {
|
||||
void get_ftdi_devices(FTDIEndpointC* devices, int count) {
|
||||
if (!devices || count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto list = scanFTDIPeripherals();
|
||||
auto list = scanFTDIEndpoints();
|
||||
int n = std::min(count, static_cast<int>(list.size()));
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
@@ -74,7 +74,7 @@ void get_ftdi_devices(FTDIPeripheralC* devices, int count) {
|
||||
}
|
||||
}
|
||||
|
||||
void free_ftdi_device(FTDIPeripheralC* device) {
|
||||
void free_ftdi_device(FTDIEndpointC* device) {
|
||||
if (!device) return;
|
||||
|
||||
if (device->serialNumber) {
|
||||
@@ -4,11 +4,11 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct FTDIPeripheral {
|
||||
struct FTDIEndpoint {
|
||||
std::string serialNumber;
|
||||
std::string description;
|
||||
bool isOpen;
|
||||
};
|
||||
|
||||
int getFTDIPeripheralsNumber();
|
||||
std::vector<FTDIPeripheral> scanFTDIPeripherals();
|
||||
int getFTDIEndpointsNumber();
|
||||
std::vector<FTDIEndpoint> scanFTDIEndpoints();
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
void resetChannels();
|
||||
|
||||
private:
|
||||
FT_STATUS ftStatus; // FTDI peripheral status
|
||||
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
|
||||
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;
|
||||
// }
|
||||
|
||||
}
|
||||
140
hardware/genericmidi/MIDIEndpoint.go
Normal file
140
hardware/genericmidi/MIDIEndpoint.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package genericmidi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dmxconnect/hardware"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"gitlab.com/gomidi/midi"
|
||||
)
|
||||
|
||||
// Device represents the device to control
|
||||
type Device struct {
|
||||
ID string // Device ID
|
||||
Mapping hardware.MappingInfo // Device mapping configuration
|
||||
}
|
||||
|
||||
// ------------------- //
|
||||
|
||||
// Endpoint contains the data of a MIDI endpoint
|
||||
type Endpoint struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
inputPorts []midi.In
|
||||
outputsPorts []midi.Out
|
||||
info hardware.EndpointInfo // The endpoint info
|
||||
settings map[string]interface{} // The settings of the endpoint
|
||||
|
||||
devices []Device // All the MIDI devices that the endpoint can handle
|
||||
}
|
||||
|
||||
// NewEndpoint creates a new MIDI endpoint
|
||||
func NewEndpoint(endpointData hardware.EndpointInfo, inputs []midi.In, outputs []midi.Out) *Endpoint {
|
||||
log.Trace().Str("file", "MIDIEndpoint").Str("name", endpointData.Name).Str("s/n", endpointData.SerialNumber).Msg("MIDI endpoint created")
|
||||
return &Endpoint{
|
||||
info: endpointData,
|
||||
inputPorts: inputs,
|
||||
outputsPorts: outputs,
|
||||
settings: endpointData.Settings,
|
||||
}
|
||||
}
|
||||
|
||||
// SetDeviceArrivalCallback is called when we need to add a new device to the hardware
|
||||
func (p *Endpoint) SetDeviceArrivalCallback(adc func(context.Context, hardware.DeviceInfo, hardware.Endpoint) error) {
|
||||
|
||||
}
|
||||
|
||||
// SetDeviceRemovalCallback is called when we need to remove a device from the hardware
|
||||
func (p *Endpoint) SetDeviceRemovalCallback(rdc func(context.Context, string) error) {
|
||||
|
||||
}
|
||||
|
||||
// Connect connects the MIDI endpoint
|
||||
func (p *Endpoint) Connect(ctx context.Context) error {
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusConnecting)
|
||||
|
||||
// Open input ports
|
||||
for _, port := range p.inputPorts {
|
||||
err := port.Open()
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDisconnected)
|
||||
return fmt.Errorf("unable to open the MIDI IN port: %w", err)
|
||||
}
|
||||
port.SetListener(func(msg []byte, delta int64) {
|
||||
// Emit the event to the front
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointEventEmitted), p.info.SerialNumber, msg)
|
||||
log.Debug().Str("message", string(msg)).Int64("delta", delta).Msg("message received")
|
||||
})
|
||||
log.Info().Str("name", port.String()).Msg("port open successfully")
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the MIDI endpoint
|
||||
func (p *Endpoint) Disconnect(ctx context.Context) error {
|
||||
// Close all inputs ports
|
||||
for _, port := range p.inputPorts {
|
||||
err := port.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to close the MIDI IN port: %w", err)
|
||||
}
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDisconnected)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate activates the MIDI endpoint
|
||||
func (p *Endpoint) Activate(ctx context.Context) error {
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusActivated)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deactivate deactivates the MIDI endpoint
|
||||
func (p *Endpoint) Deactivate(ctx context.Context) error {
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDeactivated)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSettings sets a specific setting for this endpoint
|
||||
func (p *Endpoint) SetSettings(ctx context.Context, settings map[string]any) error {
|
||||
p.settings = settings
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeviceProperty - not implemented for this kind of endpoint
|
||||
func (p *Endpoint) SetDeviceProperty(context.Context, uint32, byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSettings gets the endpoint settings
|
||||
func (p *Endpoint) GetSettings() map[string]interface{} {
|
||||
return p.settings
|
||||
}
|
||||
|
||||
// GetInfo gets 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", "MIDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("waiting for MIDI endpoint to close...")
|
||||
p.wg.Wait()
|
||||
log.Info().Str("file", "MIDIEndpoint").Str("s/n", p.info.SerialNumber).Msg("MIDI endpoint closed!")
|
||||
return nil
|
||||
}
|
||||
248
hardware/genericmidi/MIDIProvider.go
Normal file
248
hardware/genericmidi/MIDIProvider.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package genericmidi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dmxconnect/hardware"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/gomidi/rtmididrv"
|
||||
)
|
||||
|
||||
// Provider represents how the protocol is defined
|
||||
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 MIDI provider
|
||||
func NewProvider(scanEvery time.Duration) *Provider {
|
||||
log.Trace().Str("file", "MIDIProvider").Msg("MIDI 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 MIDI driver
|
||||
func (f *Provider) Initialize() error {
|
||||
log.Trace().Str("file", "MIDIProvider").Msg("MIDI 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", "MIDIProvider").Msg("unable to scan MIDI endpoints")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitStop stops the provider
|
||||
func (f *Provider) WaitStop() error {
|
||||
log.Trace().Str("file", "MIDIProvider").Msg("stopping the MIDI provider...")
|
||||
|
||||
// Wait for goroutines to stop
|
||||
f.wg.Wait()
|
||||
|
||||
log.Trace().Str("file", "MIDIProvider").Msg("MIDI provider stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (f *Provider) GetName() string {
|
||||
return "MIDI"
|
||||
}
|
||||
|
||||
func splitStringAndNumber(input string) (string, int, error) {
|
||||
// Regular expression to match the text part and the number at the end
|
||||
re := regexp.MustCompile(`^(.*?)(\d+)$`)
|
||||
matches := re.FindStringSubmatch(input)
|
||||
|
||||
// Check if the regex found both a text part and a number
|
||||
if len(matches) == 3 {
|
||||
// matches[1]: text part (might contain trailing spaces)
|
||||
// matches[2]: numeric part as a string
|
||||
textPart := strings.TrimSpace(matches[1]) // Remove any trailing spaces from the text
|
||||
numberPart, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return "", 0, err // Return error if the number conversion fails
|
||||
}
|
||||
return textPart, numberPart, nil
|
||||
}
|
||||
|
||||
// Return an error if no trailing number is found
|
||||
return "", 0, fmt.Errorf("no number found at the end of the string")
|
||||
}
|
||||
|
||||
// scanEndpoints scans the MIDI endpoints
|
||||
func (f *Provider) scanEndpoints(ctx context.Context) error {
|
||||
currentMap := make(map[string]*Endpoint)
|
||||
|
||||
drv, err := rtmididrv.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open the MIDI driver")
|
||||
}
|
||||
defer drv.Close()
|
||||
|
||||
// Get MIDI INPUT ports
|
||||
ins, err := drv.Ins()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to scan MIDI IN ports: %s", err)
|
||||
}
|
||||
|
||||
for _, port := range ins {
|
||||
// Exclude microsoft wavetable from the list
|
||||
if strings.Contains(port.String(), "GS Wavetable") {
|
||||
continue
|
||||
}
|
||||
|
||||
baseName := normalizeName(port.String())
|
||||
sn := strings.ReplaceAll(strings.ToLower(baseName), " ", "_")
|
||||
|
||||
if _, ok := currentMap[sn]; !ok {
|
||||
currentMap[sn] = &Endpoint{
|
||||
info: hardware.EndpointInfo{
|
||||
Name: baseName,
|
||||
SerialNumber: sn,
|
||||
ProtocolName: "MIDI",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
currentMap[sn].inputPorts = append(currentMap[sn].inputPorts, port)
|
||||
log.Info().Any("endpoints", currentMap).Msg("available MIDI IN ports")
|
||||
}
|
||||
|
||||
// Get MIDI OUTPUT ports
|
||||
outs, err := drv.Outs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to scan MIDI OUT ports: %s", err)
|
||||
}
|
||||
|
||||
for _, port := range outs {
|
||||
// Exclude microsoft wavetable from the list
|
||||
if strings.Contains(port.String(), "GS Wavetable") {
|
||||
continue
|
||||
}
|
||||
baseName := normalizeName(port.String())
|
||||
|
||||
sn := strings.ReplaceAll(strings.ToLower(baseName), " ", "_")
|
||||
|
||||
if _, ok := currentMap[sn]; !ok {
|
||||
currentMap[sn] = &Endpoint{
|
||||
info: hardware.EndpointInfo{
|
||||
Name: baseName,
|
||||
SerialNumber: sn,
|
||||
ProtocolName: "MIDI",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
currentMap[sn].outputsPorts = append(currentMap[sn].outputsPorts, port)
|
||||
log.Info().Any("endpoints", currentMap).Msg("available MIDI OUT ports")
|
||||
}
|
||||
|
||||
log.Debug().Any("value", currentMap).Msg("MIDI endpoints map")
|
||||
|
||||
// Detect arrivals
|
||||
for sn, discovery := range currentMap {
|
||||
if _, known := f.detected[sn]; !known {
|
||||
|
||||
endpoint := NewEndpoint(discovery.info, discovery.inputPorts, discovery.outputsPorts)
|
||||
|
||||
f.detected[sn] = endpoint
|
||||
|
||||
if f.onArrival != nil {
|
||||
f.onArrival(ctx, discovery)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect removals
|
||||
for sn, oldEndpoint := range f.detected {
|
||||
if _, still := currentMap[sn]; !still {
|
||||
|
||||
log.Info().Str("sn", sn).Str("name", oldEndpoint.GetInfo().Name).Msg("[MIDI] endpoint removed")
|
||||
|
||||
// Execute the removal callback
|
||||
if f.onRemoval != nil {
|
||||
f.onRemoval(ctx, oldEndpoint)
|
||||
}
|
||||
|
||||
// Delete it from the detected list
|
||||
delete(f.detected, sn)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeName(raw string) string {
|
||||
name := strings.TrimSpace(raw)
|
||||
|
||||
// Si parenthèses, prendre le texte à l'intérieur
|
||||
start := strings.Index(name, "(")
|
||||
end := strings.LastIndex(name, ")")
|
||||
if start != -1 && end != -1 && start < end {
|
||||
name = name[start+1 : end]
|
||||
return strings.TrimSpace(name)
|
||||
}
|
||||
|
||||
// Sinon, supprimer le dernier mot s'il est un entier
|
||||
parts := strings.Fields(name) // découpe en mots
|
||||
if len(parts) > 1 {
|
||||
if _, err := strconv.Atoi(parts[len(parts)-1]); err == nil {
|
||||
parts = parts[:len(parts)-1] // retirer le dernier mot
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
@@ -7,136 +7,76 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// PeripheralEvent is trigger by the finders when the scan is complete
|
||||
type PeripheralEvent string
|
||||
|
||||
// PeripheralStatus is the peripheral status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED)
|
||||
type PeripheralStatus string
|
||||
|
||||
const (
|
||||
// PeripheralArrival is triggerd when a peripheral has been connected to the system
|
||||
PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL"
|
||||
// PeripheralRemoval is triggered when a peripheral has been disconnected from the system
|
||||
PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL"
|
||||
// PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - deactivated - activated)
|
||||
PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS"
|
||||
// PeripheralEventEmitted is triggered when a peripheral event is emitted
|
||||
PeripheralEventEmitted PeripheralEvent = "PERIPHERAL_EVENT_EMITTED"
|
||||
// PeripheralStatusDisconnected : peripheral is now disconnected
|
||||
PeripheralStatusDisconnected PeripheralStatus = "PERIPHERAL_DISCONNECTED"
|
||||
// PeripheralStatusConnecting : peripheral is now connecting
|
||||
PeripheralStatusConnecting PeripheralStatus = "PERIPHERAL_CONNECTING"
|
||||
// PeripheralStatusDeactivated : peripheral is now deactivated
|
||||
PeripheralStatusDeactivated PeripheralStatus = "PERIPHERAL_DEACTIVATED"
|
||||
// PeripheralStatusActivated : peripheral is now activated
|
||||
PeripheralStatusActivated PeripheralStatus = "PERIPHERAL_ACTIVATED"
|
||||
)
|
||||
|
||||
// HardwareManager is the class who manages the hardware
|
||||
type HardwareManager struct {
|
||||
// Manager is the class who manages the hardware
|
||||
type Manager struct {
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
|
||||
finders map[string]PeripheralFinder // The map of peripherals finders
|
||||
peripherals []Peripheral // The current list of peripherals
|
||||
peripheralsScanTrigger chan struct{} // Trigger the peripherals scans
|
||||
providers map[string]Provider // The map of endpoints providers
|
||||
devices map[string]*Device // The map of devices
|
||||
DetectedEndpoints map[string]Endpoint // The current list of endpoints
|
||||
SavedEndpoints map[string]EndpointInfo // The list of stored endpoints
|
||||
}
|
||||
|
||||
// NewHardwareManager creates a new HardwareManager
|
||||
func NewHardwareManager() *HardwareManager {
|
||||
// NewManager creates a new hardware manager
|
||||
func NewManager() *Manager {
|
||||
log.Trace().Str("package", "hardware").Msg("Hardware instance created")
|
||||
return &HardwareManager{
|
||||
finders: make(map[string]PeripheralFinder),
|
||||
peripherals: make([]Peripheral, 0),
|
||||
peripheralsScanTrigger: make(chan struct{}),
|
||||
return &Manager{
|
||||
providers: make(map[string]Provider, 0),
|
||||
devices: make(map[string]*Device, 0),
|
||||
DetectedEndpoints: make(map[string]Endpoint, 0),
|
||||
SavedEndpoints: make(map[string]EndpointInfo, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts to find new peripheral events
|
||||
func (h *HardwareManager) Start(ctx context.Context) error {
|
||||
// Initialize all the finders and their callback functions
|
||||
for finderName, finder := range h.finders {
|
||||
err := finder.Initialize()
|
||||
// Start starts to find new endpoint events
|
||||
func (h *Manager) Start(ctx context.Context) error {
|
||||
for providerName, provider := range h.providers {
|
||||
|
||||
// Initialize the provider
|
||||
err := provider.Initialize()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder")
|
||||
log.Err(err).Str("file", "hardware").Str("providerName", providerName).Msg("unable to initialize provider")
|
||||
return err
|
||||
}
|
||||
finder.OnArrival(func(p PeripheralInfo) {
|
||||
runtime.EventsEmit(ctx, string(PeripheralArrival), p)
|
||||
})
|
||||
finder.OnRemoval(func(p PeripheralInfo) {
|
||||
runtime.EventsEmit(ctx, string(PeripheralRemoval), p)
|
||||
})
|
||||
err = finder.Start(ctx)
|
||||
|
||||
// Set callback functions
|
||||
provider.OnArrival(h.OnEndpointArrival)
|
||||
provider.OnRemoval(h.OnEndpointRemoval)
|
||||
|
||||
// Start the provider
|
||||
err = provider.Start(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder")
|
||||
log.Err(err).Str("file", "hardware").Str("providerName", providerName).Msg("unable to start provider")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Periodically scan all the finders
|
||||
h.wg.Add(1)
|
||||
go func() {
|
||||
defer h.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-h.peripheralsScanTrigger:
|
||||
for finderName, finder := range h.finders {
|
||||
log.Trace().Str("file", "hardware").Str("finderName", finderName).Msg("force a finder to scan peripherals")
|
||||
finder.ForceScan()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFinder returns a register finder
|
||||
func (h *HardwareManager) GetFinder(finderName string) (PeripheralFinder, error) {
|
||||
finder, exists := h.finders[finderName]
|
||||
if !exists {
|
||||
log.Error().Str("file", "hardware").Str("finderName", finderName).Msg("unable to get the finder")
|
||||
return nil, fmt.Errorf("unable to locate the '%s' finder", finderName)
|
||||
}
|
||||
log.Debug().Str("file", "hardware").Str("finderName", finderName).Msg("got finder")
|
||||
return finder, nil
|
||||
}
|
||||
|
||||
// RegisterFinder registers a new peripherals finder
|
||||
func (h *HardwareManager) RegisterFinder(finder PeripheralFinder) {
|
||||
h.finders[finder.GetName()] = finder
|
||||
log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered")
|
||||
}
|
||||
|
||||
// Scan scans all the peripherals for the registered finders
|
||||
func (h *HardwareManager) Scan() error {
|
||||
select {
|
||||
case h.peripheralsScanTrigger <- struct{}{}:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("scan trigger not available (manager stopped?)")
|
||||
}
|
||||
}
|
||||
|
||||
// WaitStop stops the hardware manager
|
||||
func (h *HardwareManager) WaitStop() error {
|
||||
func (h *Manager) WaitStop() error {
|
||||
log.Trace().Str("file", "hardware").Msg("closing the hardware manager")
|
||||
|
||||
// Closing trigger channel
|
||||
close(h.peripheralsScanTrigger)
|
||||
|
||||
// Stop each finder
|
||||
// Stop each provider
|
||||
var errs []error
|
||||
for name, f := range h.finders {
|
||||
for name, f := range h.providers {
|
||||
if err := f.WaitStop(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all the endpoints to close
|
||||
log.Trace().Str("file", "MIDIProvider").Msg("closing all MIDI endpoints")
|
||||
for registeredEndpointSN, registeredEndpoint := range h.DetectedEndpoints {
|
||||
err := registeredEndpoint.WaitStop()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", registeredEndpointSN, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for goroutines to finish
|
||||
h.wg.Wait()
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package hardware
|
||||
|
||||
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
|
||||
SetEventCallback(func(any)) // Callback is called when an event is emitted from the peripheral
|
||||
Disconnect(context.Context) error // Disconnect the peripheral
|
||||
Activate(context.Context) error // Activate the peripheral
|
||||
Deactivate(context.Context) error // Deactivate the peripheral
|
||||
SetSettings(context.Context, map[string]any) error // Set a peripheral setting
|
||||
SetDeviceProperty(context.Context, uint32, byte) error // Update a device property
|
||||
WaitStop() error // Properly close the peripheral
|
||||
|
||||
GetInfo() PeripheralInfo // Get the peripheral information
|
||||
GetSettings() map[string]any // Get the peripheral settings
|
||||
}
|
||||
|
||||
// PeripheralInfo represents a peripheral information
|
||||
type PeripheralInfo struct {
|
||||
Name string `yaml:"name"` // Name of the peripheral
|
||||
SerialNumber string `yaml:"sn"` // S/N of the peripheral
|
||||
ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral
|
||||
Settings map[string]any `yaml:"settings"` // Peripheral settings
|
||||
}
|
||||
|
||||
// PeripheralFinder represents how compatible peripheral drivers are implemented
|
||||
type PeripheralFinder interface {
|
||||
Initialize() error // Initializes the protocol
|
||||
OnArrival(cb func(p PeripheralInfo)) // Callback function when a peripheral arrives
|
||||
OnRemoval(cb func(p PeripheralInfo)) // Callback function when a peripheral goes away
|
||||
Start(context.Context) error // Start the detection
|
||||
WaitStop() error // Waiting for finder to close
|
||||
ForceScan() // Explicitly scans for peripherals
|
||||
RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data
|
||||
UnregisterPeripheral(context.Context, PeripheralInfo) error // Unregisters an existing peripheral
|
||||
GetPeripheralSettings(string) (map[string]any, error) // Gets the peripheral settings
|
||||
SetPeripheralSettings(context.Context, string, map[string]any) error // Sets the peripheral settings
|
||||
GetName() string // Get the name of the finder
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package hardware
|
||||
package os2l
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dmxconnect/hardware"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// OS2LMessage represents an OS2L message
|
||||
type OS2LMessage struct {
|
||||
// Message represents an OS2L message
|
||||
type Message struct {
|
||||
Event string `json:"evt"`
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
@@ -22,38 +23,50 @@ type OS2LMessage struct {
|
||||
Param float64 `json:"param"`
|
||||
}
|
||||
|
||||
// OS2LPeripheral contains the data of an OS2L peripheral
|
||||
type OS2LPeripheral struct {
|
||||
// Endpoint contains the data of an OS2L endpoint
|
||||
type Endpoint struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
info PeripheralInfo // The basic info for this peripheral
|
||||
serverIP string // OS2L server IP
|
||||
serverPort int // OS2L server port
|
||||
listener net.Listener // Net listener (TCP)
|
||||
listenerCancel context.CancelFunc // Call this function to cancel the peripheral activation
|
||||
info hardware.EndpointInfo // The basic info for this endpoint
|
||||
serverIP string // OS2L server IP
|
||||
serverPort int // OS2L server port
|
||||
listener net.Listener // Net listener (TCP)
|
||||
listenerCancel context.CancelFunc // Call this function to cancel the endpoint activation
|
||||
|
||||
eventCallback func(any) // This callback is called for returning events
|
||||
eventCallback func(any) // This callback is called for returning events
|
||||
addDeviceCallback func(context.Context, hardware.DeviceInfo, hardware.Endpoint) error // Add a device to the hardware
|
||||
removeDeviceCallback func(context.Context, string) error // Remove a device from the hardware
|
||||
}
|
||||
|
||||
// NewOS2LPeripheral creates a new OS2L peripheral
|
||||
func NewOS2LPeripheral(peripheralData PeripheralInfo) (*OS2LPeripheral, error) {
|
||||
peripheral := &OS2LPeripheral{
|
||||
info: peripheralData,
|
||||
// NewOS2LEndpoint creates a new OS2L endpoint
|
||||
func NewOS2LEndpoint(endpointData hardware.EndpointInfo) (*Endpoint, error) {
|
||||
endpoint := &Endpoint{
|
||||
info: endpointData,
|
||||
listener: nil,
|
||||
eventCallback: nil,
|
||||
}
|
||||
log.Trace().Str("file", "OS2LPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("OS2L peripheral created")
|
||||
return peripheral, peripheral.loadSettings(peripheralData.Settings)
|
||||
log.Trace().Str("file", "OS2LEndpoint").Str("name", endpointData.Name).Str("s/n", endpointData.SerialNumber).Msg("OS2L endpoint created")
|
||||
return endpoint, endpoint.loadSettings(endpointData.Settings)
|
||||
}
|
||||
|
||||
// SetEventCallback sets the callback for returning events
|
||||
func (p *OS2LPeripheral) SetEventCallback(eventCallback func(any)) {
|
||||
func (p *Endpoint) SetEventCallback(eventCallback func(any)) {
|
||||
p.eventCallback = eventCallback
|
||||
}
|
||||
|
||||
// Connect connects the OS2L peripheral
|
||||
func (p *OS2LPeripheral) Connect(ctx context.Context) error {
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting)
|
||||
// SetDeviceArrivalCallback is called when we need to add a new device to the hardware
|
||||
func (p *Endpoint) SetDeviceArrivalCallback(adc func(context.Context, hardware.DeviceInfo, hardware.Endpoint) error) {
|
||||
p.addDeviceCallback = adc
|
||||
}
|
||||
|
||||
// SetDeviceRemovalCallback is called when we need to remove a device from the hardware
|
||||
func (p *Endpoint) SetDeviceRemovalCallback(rdc func(context.Context, string) error) {
|
||||
p.removeDeviceCallback = rdc
|
||||
}
|
||||
|
||||
// Connect connects the OS2L endpoint
|
||||
func (p *Endpoint) Connect(ctx context.Context) error {
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusConnecting)
|
||||
|
||||
var err error
|
||||
addr := net.TCPAddr{Port: p.serverPort, IP: net.ParseIP(p.serverIP)}
|
||||
@@ -61,32 +74,40 @@ func (p *OS2LPeripheral) Connect(ctx context.Context) error {
|
||||
log.Debug().Any("addr", addr).Msg("parametres de connexion à la connexion")
|
||||
p.listener, err = net.ListenTCP("tcp", &addr)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDisconnected)
|
||||
return fmt.Errorf("unable to set the OS2L TCP listener")
|
||||
}
|
||||
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
|
||||
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected")
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDeactivated)
|
||||
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint connected")
|
||||
|
||||
// TODO: To remove : simulate a device arrival/removal
|
||||
p.addDeviceCallback(ctx, hardware.DeviceInfo{
|
||||
SerialNumber: "0DE3FF",
|
||||
Name: "OS2L test device",
|
||||
Manufacturer: "BlueSig",
|
||||
Version: "0.1.0-dev",
|
||||
}, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleMessage handles an OS2L message
|
||||
func (p *OS2LPeripheral) handleMessage(raw []byte) error {
|
||||
message := OS2LMessage{}
|
||||
func (p *Endpoint) handleMessage(raw []byte) error {
|
||||
message := Message{}
|
||||
err := json.Unmarshal(raw, &message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to parse the OS2L message: %w", err)
|
||||
}
|
||||
log.Debug().Str("event", message.Event).Str("name", message.Name).Str("state", message.State).Int("ID", int(message.ID)).Float64("param", message.Param).Msg("OS2L event received")
|
||||
// Return the event to the finder
|
||||
// Return the event to the provider
|
||||
if p.eventCallback != nil {
|
||||
go p.eventCallback(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadSettings check and load the settings in the peripheral
|
||||
func (p *OS2LPeripheral) loadSettings(settings map[string]any) error {
|
||||
// loadSettings check and load the settings in the endpoint
|
||||
func (p *Endpoint) loadSettings(settings map[string]any) error {
|
||||
// Check if the IP exists
|
||||
serverIP, found := settings["os2lIp"]
|
||||
if !found {
|
||||
@@ -118,22 +139,24 @@ func (p *OS2LPeripheral) loadSettings(settings map[string]any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the MIDI peripheral
|
||||
func (p *OS2LPeripheral) Disconnect(ctx context.Context) error {
|
||||
// Disconnect disconnects the MIDI endpoint
|
||||
func (p *Endpoint) Disconnect(ctx context.Context) error {
|
||||
|
||||
// Close the TCP listener if not null
|
||||
if p.listener != nil {
|
||||
p.listener.Close()
|
||||
}
|
||||
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected)
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDisconnected)
|
||||
|
||||
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected")
|
||||
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint disconnected")
|
||||
// TODO: To remove : simulate a device arrival/removal
|
||||
p.removeDeviceCallback(ctx, "0DE3FF")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate activates the OS2L peripheral
|
||||
func (p *OS2LPeripheral) Activate(ctx context.Context) error {
|
||||
// Activate activates the OS2L endpoint
|
||||
func (p *Endpoint) Activate(ctx context.Context) error {
|
||||
// Create a derived context to handle deactivation
|
||||
var listenerCtx context.Context
|
||||
listenerCtx, p.listenerCancel = context.WithCancel(ctx)
|
||||
@@ -160,7 +183,7 @@ func (p *OS2LPeripheral) Activate(ctx context.Context) error {
|
||||
if strings.Contains(err.Error(), "use of closed network connection") || strings.Contains(err.Error(), "invalid argument") {
|
||||
return
|
||||
}
|
||||
log.Err(err).Str("file", "OS2LPeripheral").Msg("unable to accept the connection")
|
||||
log.Err(err).Str("file", "OS2LEndpoint").Msg("unable to accept the connection")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -198,15 +221,15 @@ func (p *OS2LPeripheral) Activate(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
}()
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated)
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusActivated)
|
||||
|
||||
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated")
|
||||
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint activated")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deactivate deactivates the OS2L peripheral
|
||||
func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
|
||||
// Deactivate deactivates the OS2L endpoint
|
||||
func (p *Endpoint) Deactivate(ctx context.Context) error {
|
||||
if p.listener == nil {
|
||||
return fmt.Errorf("the listener isn't defined")
|
||||
}
|
||||
@@ -216,19 +239,19 @@ func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
|
||||
p.listenerCancel()
|
||||
}
|
||||
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated)
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointStatusUpdated), p.GetInfo(), hardware.EndpointStatusDeactivated)
|
||||
|
||||
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated")
|
||||
log.Info().Str("file", "OS2LEndpoint").Msg("OS2L endpoint deactivated")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSettings sets a specific setting for this peripheral
|
||||
func (p *OS2LPeripheral) SetSettings(ctx context.Context, settings map[string]any) error {
|
||||
// SetSettings sets a specific setting for this endpoint
|
||||
func (p *Endpoint) SetSettings(ctx context.Context, settings map[string]any) error {
|
||||
err := p.loadSettings(settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load settings: %w", err)
|
||||
}
|
||||
// Reconnect the peripheral
|
||||
// Reconnect the endpoint
|
||||
p.wg.Add(1)
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
@@ -255,32 +278,32 @@ func (p *OS2LPeripheral) SetSettings(ctx context.Context, settings map[string]an
|
||||
return
|
||||
}
|
||||
}()
|
||||
log.Info().Str("sn", p.GetInfo().SerialNumber).Msg("peripheral settings set")
|
||||
log.Info().Str("sn", p.GetInfo().SerialNumber).Msg("endpoint settings set")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeviceProperty - not implemented for this kind of peripheral
|
||||
func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error {
|
||||
// SetDeviceProperty - not implemented for this kind of endpoint
|
||||
func (p *Endpoint) SetDeviceProperty(context.Context, uint32, byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSettings gets the peripheral settings
|
||||
func (p *OS2LPeripheral) GetSettings() map[string]any {
|
||||
// GetSettings gets the endpoint settings
|
||||
func (p *Endpoint) GetSettings() map[string]any {
|
||||
return map[string]any{
|
||||
"os2lIp": p.serverIP,
|
||||
"os2lPort": p.serverPort,
|
||||
}
|
||||
}
|
||||
|
||||
// GetInfo gets the peripheral information
|
||||
func (p *OS2LPeripheral) GetInfo() PeripheralInfo {
|
||||
// GetInfo gets the endpoint information
|
||||
func (p *Endpoint) GetInfo() hardware.EndpointInfo {
|
||||
return p.info
|
||||
}
|
||||
|
||||
// WaitStop stops the peripheral
|
||||
func (p *OS2LPeripheral) WaitStop() error {
|
||||
log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for OS2L peripheral to close...")
|
||||
// WaitStop stops the endpoint
|
||||
func (p *Endpoint) WaitStop() error {
|
||||
log.Info().Str("file", "OS2LEndpoint").Str("s/n", p.info.SerialNumber).Msg("waiting for OS2L endpoint to close...")
|
||||
p.wg.Wait()
|
||||
log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("OS2L peripheral closed!")
|
||||
log.Info().Str("file", "OS2LEndpoint").Str("s/n", p.info.SerialNumber).Msg("OS2L endpoint closed!")
|
||||
return nil
|
||||
}
|
||||
106
hardware/os2l/OS2LProvider.go
Normal file
106
hardware/os2l/OS2LProvider.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package os2l
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dmxconnect/hardware"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// Provider represents how the protocol is defined
|
||||
type Provider struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
|
||||
detected map[string]*Endpoint // The list of saved endpoints
|
||||
|
||||
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 OS2L provider
|
||||
func NewProvider() *Provider {
|
||||
log.Trace().Str("file", "OS2LProvider").Msg("OS2L provider created")
|
||||
return &Provider{
|
||||
detected: make(map[string]*Endpoint),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize initializes the provider
|
||||
func (f *Provider) Initialize() error {
|
||||
log.Trace().Str("file", "OS2LProvider").Msg("OS2L provider initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnArrival is the callback function when a new endpoint arrives
|
||||
func (f *Provider) OnArrival(cb func(context.Context, hardware.Endpoint)) {
|
||||
f.onArrival = cb
|
||||
}
|
||||
|
||||
// OnRemoval if the callback when a endpoint goes away
|
||||
func (f *Provider) OnRemoval(cb func(context.Context, hardware.Endpoint)) {
|
||||
f.onRemoval = cb
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// If the SerialNumber is empty, generate another one
|
||||
// TODO: Move the serialnumber generator to the endpoint itself
|
||||
if endpointInfo.SerialNumber == "" {
|
||||
endpointInfo.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32)))
|
||||
}
|
||||
|
||||
// Create a new OS2L endpoint
|
||||
endpoint, err := NewOS2LEndpoint(endpointInfo)
|
||||
if err != nil {
|
||||
return hardware.EndpointInfo{}, fmt.Errorf("unable to create the OS2L endpoint: %w", err)
|
||||
}
|
||||
|
||||
// Set the event callback
|
||||
endpoint.SetEventCallback(func(event any) {
|
||||
runtime.EventsEmit(ctx, string(hardware.EndpointEventEmitted), endpointInfo.SerialNumber, event)
|
||||
})
|
||||
|
||||
f.detected[endpointInfo.SerialNumber] = endpoint
|
||||
|
||||
if f.onArrival != nil {
|
||||
f.onArrival(ctx, endpoint) // Ask to register the endpoint in the project
|
||||
}
|
||||
return endpointInfo, err
|
||||
}
|
||||
|
||||
// Remove removes an existing endpoint (manually created)
|
||||
func (f *Provider) Remove(ctx context.Context, endpoint hardware.Endpoint) error {
|
||||
if f.onRemoval != nil {
|
||||
f.onRemoval(ctx, endpoint)
|
||||
}
|
||||
delete(f.detected, endpoint.GetInfo().SerialNumber)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (f *Provider) GetName() string {
|
||||
return "OS2L"
|
||||
}
|
||||
|
||||
// Start starts the provider
|
||||
func (f *Provider) Start(ctx context.Context) error {
|
||||
// No endpoints to scan here
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitStop stops the provider
|
||||
func (f *Provider) WaitStop() error {
|
||||
log.Trace().Str("file", "OS2LProvider").Msg("stopping the OS2L provider...")
|
||||
|
||||
// Waiting internal tasks
|
||||
f.wg.Wait()
|
||||
|
||||
log.Trace().Str("file", "OS2LProvider").Msg("OS2L provider stopped")
|
||||
return nil
|
||||
}
|
||||
37
hardware/providersHandler.go
Normal file
37
hardware/providersHandler.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Provider represents how compatible endpoint drivers are implemented
|
||||
type Provider interface {
|
||||
Initialize() error // Initializes the protocol
|
||||
Create(ctx context.Context, endpointInfo EndpointInfo) (EndpointInfo, error) // Manually create a endpoint
|
||||
Remove(ctx context.Context, endpoint Endpoint) error // Manually remove a endpoint
|
||||
OnArrival(cb func(context.Context, Endpoint)) // Callback function when a endpoint arrives
|
||||
OnRemoval(cb func(context.Context, Endpoint)) // Callback function when a endpoint goes away
|
||||
Start(context.Context) error // Start the detection
|
||||
WaitStop() error // Waiting for provider to close
|
||||
GetName() string // Get the name of the provider
|
||||
}
|
||||
|
||||
// GetProvider returns a register provider
|
||||
func (h *Manager) GetProvider(providerName string) (Provider, error) {
|
||||
provider, exists := h.providers[providerName]
|
||||
if !exists {
|
||||
log.Error().Str("file", "hardware").Str("providerName", providerName).Msg("unable to get the provider")
|
||||
return nil, fmt.Errorf("unable to locate the '%s' provider", providerName)
|
||||
}
|
||||
log.Debug().Str("file", "hardware").Str("providerName", providerName).Msg("got provider")
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// RegisterProvider registers a new endpoints provider
|
||||
func (h *Manager) RegisterProvider(provider Provider) {
|
||||
h.providers[provider.GetName()] = provider
|
||||
log.Info().Str("file", "hardware").Str("providerName", provider.GetName()).Msg("provider registered")
|
||||
}
|
||||
Reference in New Issue
Block a user