generated from thinkode/modelRepository
11-hardware-definition #17
33
app.go
33
app.go
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"changeme/hardware"
|
||||
"context"
|
||||
"dmxconnect/hardware"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
// App struct
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
hardwareManager *hardware.HardwareManager // For managing all the hardware
|
||||
wmiMutex sync.Mutex // Avoid some WMI operations at the same time
|
||||
projectInfo ProjectInfo // The project information structure
|
||||
@@ -26,9 +28,9 @@ type App struct {
|
||||
func NewApp() *App {
|
||||
// Create a new hadware manager
|
||||
hardwareManager := hardware.NewHardwareManager()
|
||||
hardwareManager.RegisterDriver(hardware.NewMIDIDriver())
|
||||
hardwareManager.RegisterDriver(hardware.NewFTDIDriver())
|
||||
hardwareManager.RegisterDriver(hardware.NewOS2LDriver())
|
||||
hardwareManager.RegisterDriver(hardware.NewMIDIFinder(5 * time.Second))
|
||||
hardwareManager.RegisterDriver(hardware.NewFTDIFinder(5 * time.Second))
|
||||
// hardwareManager.RegisterDriver(hardware.NewOS2LDriver())
|
||||
return &App{
|
||||
hardwareManager: hardwareManager,
|
||||
projectSave: "",
|
||||
@@ -41,8 +43,8 @@ func NewApp() *App {
|
||||
// startup is called when the app starts. The context is saved
|
||||
// so we can call the runtime methods
|
||||
func (a *App) onStartup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
err := a.hardwareManager.Start(ctx)
|
||||
a.ctx, a.cancelFunc = context.WithCancel(ctx)
|
||||
err := a.hardwareManager.Start(a.ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "app").Msg("unable to start the hardware manager")
|
||||
return
|
||||
@@ -52,18 +54,25 @@ func (a *App) onStartup(ctx context.Context) {
|
||||
// onReady is called when the DOM is ready
|
||||
// We get the current peripherals connected
|
||||
func (a *App) onReady(ctx context.Context) {
|
||||
log.Debug().Str("file", "peripherals").Msg("getting peripherals...")
|
||||
err := a.hardwareManager.Scan(a.ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "app").Msg("unable to get the peripherals")
|
||||
}
|
||||
// log.Debug().Str("file", "peripherals").Msg("getting peripherals...")
|
||||
// err := a.hardwareManager.Scan()
|
||||
// if err != nil {
|
||||
// log.Err(err).Str("file", "app").Msg("unable to get the peripherals")
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
// onShutdown is called when the app is closing
|
||||
// We stop all the pending processes
|
||||
func (a *App) onShutdown(ctx context.Context) {
|
||||
log.Warn().Str("file", "app").Msg("app is closing")
|
||||
// Close the application properly
|
||||
log.Trace().Str("file", "app").Msg("app is closing")
|
||||
// Explicitly close the context
|
||||
a.cancelFunc()
|
||||
err := a.hardwareManager.Stop()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "app").Msg("unable to stop the hardware manager")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
goRuntime "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
scanDelay = 4 * time.Second // Waiting delay before scanning the FTDI devices
|
||||
)
|
||||
|
||||
// FTDIDriver represents how the protocol is defined
|
||||
type FTDIDriver struct {
|
||||
peripherals map[string]Peripheral
|
||||
}
|
||||
|
||||
// NewFTDIDriver creates a new FTDI finder
|
||||
func NewFTDIDriver() *FTDIDriver {
|
||||
log.Trace().Str("file", "FTDIDriver").Msg("FTDI driver created")
|
||||
return &FTDIDriver{
|
||||
peripherals: make(map[string]Peripheral),
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed third-party/ftdi/detectFTDI.exe
|
||||
var findFTDI []byte
|
||||
|
||||
// Initialize initializes the FTDI driver
|
||||
func (d *FTDIDriver) Initialize() error {
|
||||
// Check platform
|
||||
if goRuntime.GOOS != "windows" {
|
||||
log.Error().Str("file", "FTDIDriver").Str("platform", goRuntime.GOOS).Msg("FTDI driver not compatible with your platform")
|
||||
return fmt.Errorf("<!> The FTDI driver is not compatible with your platform yet (%s)", goRuntime.GOOS)
|
||||
}
|
||||
log.Trace().Str("file", "FTDIDriver").Msg("FTDI driver initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (d *FTDIDriver) GetName() string {
|
||||
return "FTDI"
|
||||
}
|
||||
|
||||
// GetPeripheral gets the peripheral that correspond to the specified ID
|
||||
func (d *FTDIDriver) GetPeripheral(peripheralID string) (Peripheral, bool) {
|
||||
// Return the specified peripheral
|
||||
peripheral := d.peripherals[peripheralID]
|
||||
if peripheral == nil {
|
||||
log.Error().Str("file", "FTDIDriver").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI driver")
|
||||
return nil, false
|
||||
}
|
||||
log.Debug().Str("file", "FTDIDriver").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI driver")
|
||||
return peripheral, true
|
||||
}
|
||||
|
||||
// Scan scans the FTDI peripherals
|
||||
func (d *FTDIDriver) Scan(ctx context.Context) error {
|
||||
log.Trace().Str("file", "FTDIDriver").Msg("FTDI scan triggered")
|
||||
time.Sleep(scanDelay)
|
||||
|
||||
// Create a temporary file
|
||||
tempFile, err := os.CreateTemp("", "findFTDI*.exe")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
log.Trace().Str("file", "FTDIDriver").Msg("has created the FIND executable temp")
|
||||
|
||||
// Write the embedded executable to the temp file
|
||||
if _, err := tempFile.Write(findFTDI); err != nil {
|
||||
return err
|
||||
}
|
||||
tempFile.Close()
|
||||
log.Trace().Str("file", "FTDIDriver").Msg("has written the FIND executable")
|
||||
|
||||
ftdiPeripherals := make(map[string]Peripheral)
|
||||
|
||||
finder := exec.Command(tempFile.Name())
|
||||
log.Trace().Str("file", "FTDIDriver").Msg("has executed the FIND executable")
|
||||
|
||||
stdout, err := finder.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the stdout pipe: %s", err)
|
||||
}
|
||||
|
||||
stderr, err := finder.StderrPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the stderr pipe: %s", err)
|
||||
}
|
||||
|
||||
err = finder.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to find FTDI devices: %s", err)
|
||||
}
|
||||
|
||||
scannerErr := bufio.NewScanner(stderr)
|
||||
for scannerErr.Scan() {
|
||||
return fmt.Errorf("unable to find FTDI devices: %s", scannerErr.Text())
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
peripheralString := scanner.Text()
|
||||
// The program output is like '0:1:2' where 0 is the location, 1 is the S/N and 2 is the name
|
||||
peripheralInfo := strings.Split(peripheralString, ":")
|
||||
|
||||
log.Debug().Str("file", "FTDIDriver").Str("peripheralName", peripheralInfo[2]).Str("peripheralSN", peripheralInfo[1]).Msg("new FTDI peripheral detected")
|
||||
// Convert the location to an integer
|
||||
location, err := strconv.Atoi(peripheralInfo[0])
|
||||
if err != nil {
|
||||
log.Warn().Str("file", "FTDIDriver").Str("peripheralName", peripheralInfo[2]).Msg("no location provided for this FTDI peripheral")
|
||||
location = -1
|
||||
}
|
||||
// Add the peripheral to the temporary list
|
||||
peripheral, err := NewFTDIPeripheral(peripheralInfo[2], peripheralInfo[1], location)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create the FTDI peripheral: %v", err)
|
||||
}
|
||||
ftdiPeripherals[peripheralInfo[1]] = peripheral
|
||||
log.Trace().Str("file", "FTDIDriver").Str("peripheralName", peripheralInfo[2]).Msg("successfully added the FTDI peripheral to the driver")
|
||||
}
|
||||
// Compare with the current peripherals to detect arrivals/removals
|
||||
removedList, addedList := comparePeripherals(d.peripherals, ftdiPeripherals)
|
||||
// Emit the events
|
||||
emitPeripheralsEvents(ctx, removedList, PeripheralRemoval)
|
||||
log.Info().Str("file", "FTDIDriver").Msg("FTDI remove list emitted to the front")
|
||||
emitPeripheralsEvents(ctx, addedList, PeripheralArrival)
|
||||
log.Info().Str("file", "FTDIDriver").Msg("FTDI add list emitted to the front")
|
||||
// Store the new peripherals list
|
||||
d.peripherals = ftdiPeripherals
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePeripheral is not implemented here
|
||||
func (d *FTDIDriver) CreatePeripheral(context.Context) (Peripheral, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// RemovePeripheral is not implemented here
|
||||
func (d *FTDIDriver) RemovePeripheral(serialNumber string) error {
|
||||
return nil
|
||||
}
|
||||
234
hardware/FTDIFinder.go
Normal file
234
hardware/FTDIFinder.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
goRuntime "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
ftdiFinderExecutableName = "FTDI_finder.exe"
|
||||
ftdiSenderExecutableName = "FTDI_sender.exe"
|
||||
)
|
||||
|
||||
// FTDIFinder represents how the protocol is defined
|
||||
type FTDIFinder struct {
|
||||
findTicker time.Ticker // Peripherals find ticker
|
||||
peripherals map[string]Peripheral // The list of peripherals handled by this finder
|
||||
scanChannel chan struct{} // The channel to trigger a scan event
|
||||
goWait sync.WaitGroup // Check goroutines execution
|
||||
}
|
||||
|
||||
// NewFTDIFinder creates a new FTDI finder
|
||||
func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder {
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created")
|
||||
return &FTDIFinder{
|
||||
findTicker: *time.NewTicker(findPeriod),
|
||||
peripherals: make(map[string]Peripheral),
|
||||
scanChannel: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed third-party/ftdi/detectFTDI.exe
|
||||
var finderExe []byte
|
||||
|
||||
//go:embed third-party/ftdi/dmxSender.exe
|
||||
var senderExe []byte
|
||||
|
||||
// Initialize initializes the FTDI finder
|
||||
func (f *FTDIFinder) Initialize() error {
|
||||
// Check platform
|
||||
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)
|
||||
}
|
||||
// Create the FTDI executables
|
||||
err := createExecutable(ftdiFinderExecutableName, finderExe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createExecutable(ftdiSenderExecutableName, senderExe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// createExecutable creates and writes an executable to the temporary directory of the system
|
||||
func createExecutable(fileName string, storedFile []byte) error {
|
||||
tempFile, err := os.Create(filepath.Join(os.TempDir(), fileName))
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("fileName", fileName).Msg("unable to create an FTDI executable")
|
||||
return err
|
||||
}
|
||||
log.Trace().Str("file", "FTDIFinder").Str("filePath", tempFile.Name()).Msg("FTDI executable created")
|
||||
|
||||
// Write the embedded executable to the temp file
|
||||
if _, err := tempFile.Write(storedFile); err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("fileName", fileName).Msg("unable to write the content to an FTDI executable")
|
||||
return err
|
||||
}
|
||||
tempFile.Close()
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("fileName", fileName).Msg("FTDI executable written")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the finder and search for peripherals
|
||||
func (f *FTDIFinder) Start(ctx context.Context) error {
|
||||
f.goWait.Add(1)
|
||||
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", "FTDIFinder").Msg("unable to scan FTDI peripherals")
|
||||
}
|
||||
case <-f.scanChannel:
|
||||
// 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 explicily asks for scanning peripherals
|
||||
func (f *FTDIFinder) ForceScan() {
|
||||
f.scanChannel <- struct{}{}
|
||||
}
|
||||
|
||||
// Stop stops the finder
|
||||
func (f *FTDIFinder) Stop() error {
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
|
||||
// Wait for goroutines to stop
|
||||
f.goWait.Wait()
|
||||
// Stop the ticker
|
||||
f.findTicker.Stop()
|
||||
// Delete the FTDI executable files
|
||||
fileToDelete := filepath.Join(os.TempDir(), ftdiFinderExecutableName)
|
||||
err := os.Remove(fileToDelete)
|
||||
if err != nil {
|
||||
log.Warn().Str("file", "FTDIFinder").Str("fileName", fileToDelete).AnErr("error", err).Msg("unable to remove the executable file")
|
||||
}
|
||||
fileToDelete = filepath.Join(os.TempDir(), ftdiSenderExecutableName)
|
||||
err = os.Remove(fileToDelete)
|
||||
if err != nil {
|
||||
log.Warn().Str("file", "FTDIFinder").Str("fileName", fileToDelete).AnErr("error", err).Msg("unable to remove the executable file")
|
||||
}
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
func (f *FTDIFinder) GetName() string {
|
||||
return "FTDI"
|
||||
}
|
||||
|
||||
// GetPeripheral gets the peripheral that correspond to the specified ID
|
||||
func (f *FTDIFinder) GetPeripheral(peripheralID string) (Peripheral, bool) {
|
||||
// Return the specified peripheral
|
||||
peripheral := f.peripherals[peripheralID]
|
||||
if peripheral == nil {
|
||||
log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
return nil, false
|
||||
}
|
||||
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
return peripheral, true
|
||||
}
|
||||
|
||||
// scanPeripherals scans the FTDI peripherals
|
||||
func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
|
||||
detectionCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI scan triggered")
|
||||
|
||||
ftdiPeripherals := make(map[string]Peripheral)
|
||||
|
||||
finder := exec.CommandContext(detectionCtx, filepath.Join(os.TempDir(), ftdiFinderExecutableName))
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("has executed the FIND executable")
|
||||
|
||||
stdout, err := finder.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the stdout pipe: %s", err)
|
||||
}
|
||||
defer stdout.Close()
|
||||
|
||||
stderr, err := finder.StderrPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the stderr pipe: %s", err)
|
||||
}
|
||||
defer stderr.Close()
|
||||
|
||||
err = finder.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to find FTDI peripherals: %s", err)
|
||||
}
|
||||
|
||||
scannerErr := bufio.NewScanner(stderr)
|
||||
for scannerErr.Scan() {
|
||||
return fmt.Errorf("unable to find FTDI peripherals: %s", scannerErr.Text())
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
peripheralString := scanner.Text()
|
||||
// The program output is like '0:1:2' where 0 is the location, 1 is the S/N and 2 is the name
|
||||
peripheralInfo := strings.Split(peripheralString, ":")
|
||||
|
||||
log.Debug().Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Str("peripheralSN", peripheralInfo[1]).Msg("new FTDI peripheral detected")
|
||||
// Convert the location to an integer
|
||||
location, err := strconv.Atoi(peripheralInfo[0])
|
||||
if err != nil {
|
||||
log.Warn().Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("no location provided for this FTDI peripheral")
|
||||
location = -1
|
||||
}
|
||||
// Add the peripheral to the temporary list
|
||||
peripheral, err := NewFTDIPeripheral(peripheralInfo[2], peripheralInfo[1], location)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the FTDI peripheral: %v", err)
|
||||
}
|
||||
ftdiPeripherals[peripheralInfo[1]] = peripheral
|
||||
log.Trace().Str("file", "FTDIFinder").Str("peripheralName", peripheralInfo[2]).Msg("successfully added the FTDI peripheral to the finder")
|
||||
}
|
||||
// Compare with the current peripherals to detect arrivals/removals
|
||||
removedList, addedList := comparePeripherals(f.peripherals, ftdiPeripherals)
|
||||
// Emit the events
|
||||
emitPeripheralsEvents(ctx, removedList, PeripheralRemoval)
|
||||
log.Info().Str("file", "FTDIFinder").Msg("FTDI remove list emitted to the front")
|
||||
emitPeripheralsEvents(ctx, addedList, PeripheralArrival)
|
||||
log.Info().Str("file", "FTDIFinder").Msg("FTDI add list emitted to the front")
|
||||
// Store the new peripherals list
|
||||
f.peripherals = ftdiPeripherals
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePeripheral is not implemented here
|
||||
func (f *FTDIFinder) CreatePeripheral(context.Context) (Peripheral, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeletePeripheral is not implemented here
|
||||
func (f *FTDIFinder) DeletePeripheral(serialNumber string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -34,31 +34,12 @@ type FTDIPeripheral struct {
|
||||
errorsChan chan error // Channel to get the errors
|
||||
}
|
||||
|
||||
//go:embed third-party/ftdi/dmxSender.exe
|
||||
var dmxSender []byte
|
||||
|
||||
// NewFTDIPeripheral creates a new FTDI peripheral
|
||||
func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPeripheral, error) {
|
||||
log.Info().Str("file", "FTDIPeripheral").Str("name", name).Str("s/n", serialNumber).Int("location", location).Msg("FTDI peripheral created")
|
||||
// Create a temporary file
|
||||
tempFile, err := os.Create(fmt.Sprintf("dmxSender-%s.exe", serialNumber))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", serialNumber).Msg("FTDI sender temp created")
|
||||
|
||||
// Write the embedded executable to the temp file
|
||||
if _, err := tempFile.Write(dmxSender); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", serialNumber).Msg("FTDI sender written")
|
||||
|
||||
tempFile.Close()
|
||||
|
||||
return &FTDIPeripheral{
|
||||
name: name,
|
||||
dmxSender: nil,
|
||||
programName: tempFile.Name(),
|
||||
serialNumber: serialNumber,
|
||||
location: location,
|
||||
universesNumber: 1,
|
||||
|
||||
@@ -6,44 +6,88 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattrtaylor/go-rtmidi"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// MIDIDriver represents how the protocol is defined
|
||||
type MIDIDriver struct {
|
||||
// MIDIFinder represents how the protocol is defined
|
||||
type MIDIFinder struct {
|
||||
findTicker time.Ticker // Peripherals find ticker
|
||||
peripherals map[string]Peripheral // The list of peripherals
|
||||
scanChannel chan struct{} // The channel to trigger a scan event
|
||||
goWait sync.WaitGroup // Check goroutines execution
|
||||
}
|
||||
|
||||
// NewMIDIDriver creates a new DMXUSB protocol
|
||||
func NewMIDIDriver() *MIDIDriver {
|
||||
log.Trace().Str("file", "MIDIDriver").Msg("MIDI driver created")
|
||||
return &MIDIDriver{
|
||||
// NewMIDIFinder creates a new DMXUSB protocol
|
||||
func NewMIDIFinder(findPeriod time.Duration) *MIDIFinder {
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder created")
|
||||
return &MIDIFinder{
|
||||
findTicker: *time.NewTicker(findPeriod),
|
||||
peripherals: make(map[string]Peripheral),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize initializes the MIDI driver
|
||||
func (d *MIDIDriver) Initialize() error {
|
||||
log.Trace().Str("file", "MIDIDriver").Msg("MIDI driver initialized")
|
||||
func (f *MIDIFinder) Initialize() error {
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder initialized")
|
||||
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 (d *MIDIDriver) GetName() string {
|
||||
func (f *MIDIFinder) GetName() string {
|
||||
return "MIDI"
|
||||
}
|
||||
|
||||
// GetPeripheral gets the peripheral that correspond to the specified ID
|
||||
func (d *MIDIDriver) GetPeripheral(peripheralID string) (Peripheral, bool) {
|
||||
func (f *MIDIFinder) GetPeripheral(peripheralID string) (Peripheral, bool) {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := d.peripherals[peripheralID]
|
||||
peripheral, found := f.peripherals[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "MIDIDriver").Str("peripheralID", peripheralID).Msg("unable to get this peripheral in the MIDI driver")
|
||||
log.Error().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral in the MIDI finder")
|
||||
return nil, false
|
||||
}
|
||||
log.Trace().Str("file", "MIDIDriver").Str("peripheralID", peripheralID).Msg("MIDI peripheral found in the driver")
|
||||
log.Trace().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("MIDI peripheral found in the driver")
|
||||
return peripheral, true
|
||||
}
|
||||
|
||||
@@ -68,23 +112,26 @@ func splitStringAndNumber(input string) (string, int, error) {
|
||||
return "", 0, fmt.Errorf("no number found at the end of the string")
|
||||
}
|
||||
|
||||
// Scan scans the interfaces compatible with the MIDI protocol
|
||||
func (d *MIDIDriver) Scan(ctx context.Context) error {
|
||||
// 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", "MIDIDriver").Msg("opening MIDI scanner port...")
|
||||
log.Trace().Str("file", "MIDIFinder").Msg("opening MIDI scanner port...")
|
||||
midiScanner, err := rtmidi.NewMIDIInDefault()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "MIDIDriver").Msg("unable to open the MIDI scanner port...")
|
||||
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", "MIDIDriver").Msg("scanning MIDI peripherals...")
|
||||
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", "MIDIDriver").Msg("unable to scan MIDI peripherals...")
|
||||
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++ {
|
||||
@@ -96,32 +143,32 @@ func (d *MIDIDriver) Scan(ctx context.Context) error {
|
||||
// Separate data
|
||||
name, location, err := splitStringAndNumber(portName)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "MIDIDriver").Str("description", portName).Msg("invalid peripheral description")
|
||||
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", "MIDIDriver").Str("name", name).Int("location", location).Msg("MIDI peripheral found")
|
||||
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(d.peripherals, midiPeripherals)
|
||||
removedList, addedList := comparePeripherals(f.peripherals, midiPeripherals)
|
||||
// Emit the events
|
||||
emitPeripheralsEvents(ctx, removedList, PeripheralRemoval)
|
||||
log.Info().Str("file", "MIDIDriver").Msg("MIDI remove list emitted to the front")
|
||||
log.Info().Str("file", "MIDIFinder").Msg("MIDI remove list emitted to the front")
|
||||
emitPeripheralsEvents(ctx, addedList, PeripheralArrival)
|
||||
log.Info().Str("file", "MIDIDriver").Msg("MIDI add list emitted to the front")
|
||||
log.Info().Str("file", "MIDIFinder").Msg("MIDI add list emitted to the front")
|
||||
// Store the new peripherals list
|
||||
d.peripherals = midiPeripherals
|
||||
f.peripherals = midiPeripherals
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePeripheral is not implemented here
|
||||
func (d *MIDIDriver) CreatePeripheral(context.Context) (Peripheral, error) {
|
||||
func (f *MIDIFinder) CreatePeripheral(context.Context) (Peripheral, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// RemovePeripheral is not implemented here
|
||||
func (d *MIDIDriver) RemovePeripheral(serialNumber string) error {
|
||||
// DeletePeripheral is not implemented here
|
||||
func (f *MIDIFinder) DeletePeripheral(serialNumber string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -3,9 +3,8 @@ package hardware
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
@@ -31,119 +30,63 @@ var (
|
||||
|
||||
// HardwareManager is the class who manages the hardware
|
||||
type HardwareManager struct {
|
||||
drivers map[string]PeripheralDriver // The map of peripherals finders
|
||||
peripherals []Peripheral // The current list of peripherals
|
||||
deviceChangedEvent chan struct{} // The event when the devices list changed
|
||||
ctx context.Context
|
||||
drivers map[string]PeripheralFinder // The map of peripherals finders
|
||||
peripherals []Peripheral // The current list of peripherals
|
||||
peripheralsScanTrigger chan struct{} // Trigger the peripherals scans
|
||||
goWait sync.WaitGroup // Wait for goroutines to terminate
|
||||
}
|
||||
|
||||
// NewHardwareManager creates a new HardwareManager
|
||||
func NewHardwareManager() *HardwareManager {
|
||||
log.Trace().Str("package", "hardware").Msg("Hardware instance created")
|
||||
return &HardwareManager{
|
||||
drivers: make(map[string]PeripheralDriver),
|
||||
peripherals: make([]Peripheral, 0),
|
||||
deviceChangedEvent: make(chan struct{}),
|
||||
drivers: make(map[string]PeripheralFinder),
|
||||
peripherals: make([]Peripheral, 0),
|
||||
peripheralsScanTrigger: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts to find new peripheral events
|
||||
func (h *HardwareManager) Start(ctx context.Context) error {
|
||||
// Configure wndProc callback
|
||||
cb := windows.NewCallback(h.wndProc)
|
||||
inst := win.GetModuleHandle(nil)
|
||||
|
||||
// Register window class
|
||||
className, err := syscall.UTF16PtrFromString("DMXConnectPeripheralWatcher")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert window class name to UTF16: %w", err)
|
||||
}
|
||||
wc := win.WNDCLASSEX{
|
||||
CbSize: uint32(unsafe.Sizeof(win.WNDCLASSEX{})),
|
||||
HInstance: inst,
|
||||
LpfnWndProc: cb,
|
||||
LpszClassName: className,
|
||||
}
|
||||
if win.RegisterClassEx(&wc) == 0 {
|
||||
return fmt.Errorf("failed to register window class: %w", syscall.GetLastError())
|
||||
}
|
||||
|
||||
// Create hidden window
|
||||
windowName, err := syscall.UTF16PtrFromString("usbevent.exe")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert window name to UTF16: %w", err)
|
||||
}
|
||||
hwnd := win.CreateWindowEx(
|
||||
0,
|
||||
wc.LpszClassName,
|
||||
windowName,
|
||||
win.WS_OVERLAPPEDWINDOW,
|
||||
win.CW_USEDEFAULT,
|
||||
win.CW_USEDEFAULT,
|
||||
100,
|
||||
100,
|
||||
0,
|
||||
0,
|
||||
wc.HInstance,
|
||||
nil,
|
||||
)
|
||||
if hwnd == 0 {
|
||||
return fmt.Errorf("failed to create window: %w", syscall.GetLastError())
|
||||
}
|
||||
|
||||
// Hide and update window
|
||||
win.ShowWindow(hwnd, win.SW_HIDE)
|
||||
win.UpdateWindow(hwnd)
|
||||
|
||||
// Start message loop in a goroutine
|
||||
go messageLoop(ctx, hwnd)
|
||||
|
||||
// To handle the peripheral changed
|
||||
go func() {
|
||||
defer log.Debug().Str("file", "hardware").Msg("peripheral getter goroutine exited")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-h.deviceChangedEvent:
|
||||
log.Debug().Str("file", "hardware").Msg("peripheral change event, triggering scan...")
|
||||
err := h.Scan(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Msg("unable to scan peripherals")
|
||||
}
|
||||
}
|
||||
for finderName, finder := range h.drivers {
|
||||
err := finder.Initialize()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder")
|
||||
return err
|
||||
}
|
||||
}()
|
||||
err = finder.Start(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder")
|
||||
return err
|
||||
}
|
||||
}
|
||||
// n, err := detector.Register()
|
||||
// if err != nil {
|
||||
// log.Err(err).Str("file", "hardware").Msg("error registering the usb event")
|
||||
// }
|
||||
// h.detector = n
|
||||
// // Run the detector
|
||||
// n.Run(ctx)
|
||||
// h.goWait.Add(1)
|
||||
// go func() {
|
||||
// defer h.goWait.Done()
|
||||
// for {
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return
|
||||
// case <-h.detector.EventChannel:
|
||||
// // Trigger hardware scans
|
||||
// log.Info().Str("file", "hardware").Msg("peripheral change event")
|
||||
// case <-h.peripheralsScanTrigger:
|
||||
// log.Info().Str("file", "hardware").Msg("scan triggered")
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
return nil
|
||||
}
|
||||
|
||||
func messageLoop(ctx context.Context, hwnd win.HWND) {
|
||||
defer log.Debug().Str("file", "hardware").Msg("Peripheral watcher goroutine exited")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
win.PostQuitMessage(0) // Gracefully terminate message loop
|
||||
return
|
||||
default:
|
||||
var msg win.MSG
|
||||
result := win.GetMessage(&msg, hwnd, 0, 0)
|
||||
if result > 0 {
|
||||
win.TranslateMessage(&msg)
|
||||
win.DispatchMessage(&msg)
|
||||
} else if result == 0 {
|
||||
log.Warn().Str("file", "hardware").Msg("WM_QUIT message received")
|
||||
return
|
||||
} else {
|
||||
log.Error().Str("file", "hardware").Msg("GetMessage returned an error")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetDriver returns a register driver
|
||||
func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error) {
|
||||
func (h *HardwareManager) GetDriver(driverName string) (PeripheralFinder, error) {
|
||||
driver, exists := h.drivers[driverName]
|
||||
if !exists {
|
||||
log.Error().Str("file", "hardware").Str("driverName", driverName).Msg("unable to get the driver")
|
||||
@@ -154,7 +97,7 @@ func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error)
|
||||
}
|
||||
|
||||
// RegisterDriver registers a new peripherals driver
|
||||
func (h *HardwareManager) RegisterDriver(driver PeripheralDriver) {
|
||||
func (h *HardwareManager) RegisterDriver(driver PeripheralFinder) {
|
||||
h.drivers[driver.GetName()] = driver
|
||||
log.Info().Str("file", "hardware").Str("driverName", driver.GetName()).Msg("driver registered")
|
||||
}
|
||||
@@ -174,41 +117,43 @@ func (h *HardwareManager) GetPeripheral(driverName string, peripheralID string)
|
||||
}
|
||||
|
||||
// Scan scans all the peripherals for the registered finders
|
||||
func (h *HardwareManager) Scan(ctx context.Context) error {
|
||||
if len(h.drivers) == 0 {
|
||||
log.Warn().Str("file", "hardware").Msg("no driver registered")
|
||||
return fmt.Errorf("no driver registered")
|
||||
}
|
||||
for _, driver := range h.drivers {
|
||||
driverCopy := driver
|
||||
go func() {
|
||||
err := driverCopy.Scan(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("driverName", driverCopy.GetName()).Msg("unable to scan peripheral")
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
func (h *HardwareManager) Scan() error {
|
||||
h.peripheralsScanTrigger <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
log.Trace().Str("file", "hardware").Msg("wndProc triggered")
|
||||
switch msg {
|
||||
case win.WM_DEVICECHANGE:
|
||||
log.Trace().Str("file", "hardware").Uint32("msg", msg).Msg("wndProc triggered")
|
||||
if msg == win.WM_DEVICECHANGE {
|
||||
// Trigger the devices scan when the last DEVICE_CHANGE event is received
|
||||
if debounceTimer != nil {
|
||||
debounceTimer.Stop()
|
||||
log.Trace().Str("file", "hardware").Msg("scan debounce timer stopped")
|
||||
log.Debug().Str("file", "hardware").Msg("scan debounce timer stopped")
|
||||
}
|
||||
debounceTimer = time.AfterFunc(debounceDuration, func() {
|
||||
log.Debug().Str("file", "hardware").Msg("peripheral changed")
|
||||
h.deviceChangedEvent <- struct{}{}
|
||||
h.peripheralsScanTrigger <- struct{}{}
|
||||
})
|
||||
}
|
||||
return win.DefWindowProc(win.HWND(hwnd), msg, wParam, lParam)
|
||||
}
|
||||
|
||||
// Stop stops the hardware manager
|
||||
func (h *HardwareManager) Stop() error {
|
||||
log.Trace().Str("file", "hardware").Msg("closing the hardware manager")
|
||||
// Stop each finder
|
||||
for finderName, finder := range h.drivers {
|
||||
err := finder.Stop()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to stop the finder")
|
||||
}
|
||||
}
|
||||
// Wait for goroutines to finish
|
||||
h.goWait.Wait()
|
||||
log.Info().Str("file", "hardware").Msg("hardware manager stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// peripheralsList emits a peripheral event
|
||||
func emitPeripheralsEvents(ctx context.Context, peripheralsList map[string]Peripheral, peripheralEvent PeripheralEvent) {
|
||||
for _, peripheral := range peripheralsList {
|
||||
|
||||
@@ -21,12 +21,14 @@ type PeripheralInfo struct {
|
||||
Settings []interface{} `yaml:"settings"` // Number of DMX universes handled by the peripheral
|
||||
}
|
||||
|
||||
// PeripheralDriver represents how compatible peripheral drivers are implemented
|
||||
type PeripheralDriver interface {
|
||||
// PeripheralFinder represents how compatible peripheral drivers are implemented
|
||||
type PeripheralFinder interface {
|
||||
Initialize() error // Initializes the protocol
|
||||
Start(context.Context) error // Start the detection
|
||||
Stop() error // Stop the detection
|
||||
ForceScan() // Explicitly scans for peripherals
|
||||
CreatePeripheral(ctx context.Context) (Peripheral, error) // Creates a new peripheral
|
||||
DeletePeripheral(serialNumber string) error // Removes a peripheral
|
||||
GetName() string // Get the name of the finder
|
||||
GetPeripheral(string) (Peripheral, bool) // Get the peripheral
|
||||
Scan(context.Context) error // Scan for peripherals
|
||||
CreatePeripheral(ctx context.Context) (Peripheral, error) // Creates a new peripheral
|
||||
RemovePeripheral(serialNumber string) error // Removes a peripheral
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"changeme/hardware"
|
||||
"dmxconnect/hardware"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"changeme/hardware"
|
||||
"dmxconnect/hardware"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
Reference in New Issue
Block a user