Files
dmxconnect/hardware/hardware.go

297 lines
10 KiB
Go
Raw Normal View History

package hardware
import (
"context"
2025-11-02 10:57:53 +01:00
"errors"
"fmt"
"sync"
2025-11-24 19:23:50 +01:00
"time"
"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"
2025-11-29 18:01:34 +01:00
// PeripheralLoad is triggered when a peripheral is added to the project
PeripheralLoad PeripheralEvent = "PERIPHERAL_LOAD"
// PeripheralUnload is triggered when a peripheral is removed from the project
PeripheralUnload PeripheralEvent = "PERIPHERAL_UNLOAD"
// 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"
)
2025-11-24 19:23:50 +01:00
// Manager is the class who manages the hardware
type Manager struct {
2025-11-29 17:42:42 +01:00
mu sync.Mutex
2025-11-02 10:57:53 +01:00
wg sync.WaitGroup
2025-11-30 18:33:00 +01:00
finders map[string]PeripheralFinder // The map of peripherals finders
DetectedPeripherals map[string]Peripheral // The current list of peripherals
SavedPeripherals map[string]PeripheralInfo // The list of stored peripherals
}
2025-11-24 19:23:50 +01:00
// NewManager creates a new hardware manager
func NewManager() *Manager {
log.Trace().Str("package", "hardware").Msg("Hardware instance created")
2025-11-24 19:23:50 +01:00
return &Manager{
2025-11-30 18:33:00 +01:00
finders: make(map[string]PeripheralFinder),
DetectedPeripherals: make(map[string]Peripheral, 0),
SavedPeripherals: make(map[string]PeripheralInfo, 0),
}
}
2025-11-29 17:42:42 +01:00
// RegisterPeripheral registers a new peripheral
2025-11-30 18:33:00 +01:00
func (h *Manager) RegisterPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) {
2025-11-29 17:42:42 +01:00
h.mu.Lock()
defer h.mu.Unlock()
2025-11-30 18:33:00 +01:00
// Create the peripheral from its finder (if needed)
if finder, found := h.finders[peripheralInfo.ProtocolName]; found {
var err error
peripheralInfo, err = finder.Create(ctx, peripheralInfo)
if err != nil {
return "", err
}
}
2025-11-29 20:06:04 +01:00
// Do not save if the peripheral doesn't have a S/N
2025-11-30 18:33:00 +01:00
if peripheralInfo.SerialNumber == "" {
return "", fmt.Errorf("serial number is empty for this peripheral")
2025-11-29 20:06:04 +01:00
}
2025-11-30 18:33:00 +01:00
h.SavedPeripherals[peripheralInfo.SerialNumber] = peripheralInfo
2025-11-29 17:42:42 +01:00
2025-11-30 18:33:00 +01:00
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralInfo, PeripheralStatusDisconnected)
2025-11-29 17:42:42 +01:00
// If already detected, connect it
2025-11-30 18:33:00 +01:00
if peripheral, ok := h.DetectedPeripherals[peripheralInfo.SerialNumber]; ok {
2025-11-29 17:42:42 +01:00
h.wg.Add(1)
go func() {
defer h.wg.Done()
err := peripheral.Connect(ctx)
if err != nil {
2025-11-30 18:33:00 +01:00
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo.SerialNumber).Msg("unable to connect the peripheral")
2025-11-29 17:42:42 +01:00
return
}
// Peripheral connected, activate it
err = peripheral.Activate(ctx)
if err != nil {
2025-11-30 18:33:00 +01:00
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo.SerialNumber).Msg("unable to activate the FTDI peripheral")
2025-11-29 17:42:42 +01:00
return
}
}()
}
// Emits the event in the hardware
2025-11-30 18:33:00 +01:00
runtime.EventsEmit(ctx, string(PeripheralLoad), peripheralInfo)
2025-11-29 17:42:42 +01:00
2025-11-30 18:33:00 +01:00
return peripheralInfo.SerialNumber, nil
2025-11-29 17:42:42 +01:00
}
// UnregisterPeripheral unregisters an existing peripheral
2025-11-30 18:33:00 +01:00
func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) error {
2025-11-29 17:42:42 +01:00
h.mu.Lock()
defer h.mu.Unlock()
2025-11-30 18:33:00 +01:00
if peripheral, detected := h.DetectedPeripherals[peripheralInfo.SerialNumber]; detected {
2025-11-29 17:42:42 +01:00
// Deactivating peripheral
err := peripheral.Deactivate(ctx)
if err != nil {
2025-11-30 18:33:00 +01:00
log.Err(err).Str("sn", peripheralInfo.SerialNumber).Msg("unable to deactivate the peripheral")
2025-11-29 17:42:42 +01:00
return nil
}
// Disconnecting peripheral
err = peripheral.Disconnect(ctx)
if err != nil {
2025-11-30 18:33:00 +01:00
log.Err(err).Str("sn", peripheralInfo.SerialNumber).Msg("unable to disconnect the peripheral")
2025-11-29 17:42:42 +01:00
return nil
}
2025-11-30 18:33:00 +01:00
// Remove the peripheral from its finder (if needed)
if finder, found := h.finders[peripheralInfo.ProtocolName]; found {
err = finder.Remove(ctx, peripheral)
if err != nil {
return err
}
}
2025-11-29 17:42:42 +01:00
}
2025-11-30 18:33:00 +01:00
delete(h.SavedPeripherals, peripheralInfo.SerialNumber)
runtime.EventsEmit(ctx, string(PeripheralUnload), peripheralInfo)
2025-11-29 17:42:42 +01:00
return nil
}
// GetPeripheralSettings gets the peripheral settings
func (h *Manager) GetPeripheralSettings(peripheralSN string) (map[string]any, error) {
// Return the specified peripheral
2025-11-30 18:33:00 +01:00
peripheral, found := h.DetectedPeripherals[peripheralSN]
2025-11-29 17:42:42 +01:00
if !found {
// Peripheral not detected, return the last settings saved
2025-11-30 18:33:00 +01:00
if savedPeripheral, isFound := h.SavedPeripherals[peripheralSN]; isFound {
2025-11-29 17:42:42 +01:00
return savedPeripheral.Settings, nil
}
return nil, fmt.Errorf("unable to found the peripheral")
}
return peripheral.GetSettings(), nil
}
// SetPeripheralSettings sets the peripheral settings
func (h *Manager) SetPeripheralSettings(ctx context.Context, peripheralSN string, settings map[string]any) error {
2025-11-30 18:33:00 +01:00
peripheral, found := h.DetectedPeripherals[peripheralSN]
2025-11-29 17:42:42 +01:00
if !found {
return fmt.Errorf("unable to found the FTDI peripheral")
}
return peripheral.SetSettings(ctx, settings)
}
// Start starts to find new peripheral events
2025-11-24 19:23:50 +01:00
func (h *Manager) Start(ctx context.Context) error {
2025-11-29 17:42:42 +01:00
// Register all the finders to use as hardware scanners
2025-11-24 19:23:50 +01:00
h.RegisterFinder(NewFTDIFinder(3 * time.Second))
2025-11-30 18:33:00 +01:00
h.RegisterFinder(NewOS2LFinder())
2025-11-24 19:23:50 +01:00
h.RegisterFinder(NewMIDIFinder(3 * time.Second))
for finderName, finder := range h.finders {
2025-11-29 17:42:42 +01:00
// Initialize the finder
err := finder.Initialize()
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder")
return err
}
2025-11-29 17:42:42 +01:00
// Set callback functions
finder.OnArrival(h.OnPeripheralArrival)
finder.OnRemoval(h.OnPeripheralRemoval)
// Start the finder
err = finder.Start(ctx)
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder")
return err
}
}
2025-11-29 17:42:42 +01:00
return nil
}
2025-11-02 10:57:53 +01:00
2025-11-29 17:42:42 +01:00
// OnPeripheralArrival is called when a peripheral arrives in the system
2025-11-30 18:33:00 +01:00
func (h *Manager) OnPeripheralArrival(ctx context.Context, peripheral Peripheral) {
2025-11-29 17:42:42 +01:00
// Add the peripheral to the detected hardware
2025-11-30 18:33:00 +01:00
h.DetectedPeripherals[peripheral.GetInfo().SerialNumber] = peripheral
2025-11-29 17:42:42 +01:00
// If the peripheral is saved in the project, connect it
2025-11-30 18:33:00 +01:00
if _, saved := h.SavedPeripherals[peripheral.GetInfo().SerialNumber]; saved {
2025-11-29 17:42:42 +01:00
h.wg.Add(1)
go func(p Peripheral) {
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 peripheral")
return
}
2025-11-29 17:42:42 +01:00
err = p.Activate(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to activate the FTDI peripheral")
return
}
}(peripheral)
}
// TODO: Update the Peripheral reference in the corresponding devices
runtime.EventsEmit(ctx, string(PeripheralArrival), peripheral.GetInfo())
}
// OnPeripheralRemoval is called when a peripheral exits the system
func (h *Manager) OnPeripheralRemoval(ctx context.Context, peripheral Peripheral) {
// Properly deactivating and disconnecting the peripheral
h.wg.Add(1)
go func(p Peripheral) {
defer h.wg.Done()
err := p.Deactivate(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to deactivate peripheral after disconnection")
}
2025-11-29 17:42:42 +01:00
err = p.Disconnect(ctx)
if err != nil {
log.Err(err).Str("sn", p.GetInfo().SerialNumber).Msg("unable to disconnect the peripheral after disconnection")
}
}(peripheral)
// Remove the peripheral from the hardware
2025-11-30 18:33:00 +01:00
delete(h.DetectedPeripherals, peripheral.GetInfo().SerialNumber)
2025-11-29 17:42:42 +01:00
// TODO: Update the Peripheral reference in the corresponding devices
runtime.EventsEmit(ctx, string(PeripheralRemoval), peripheral.GetInfo())
}
// GetFinder returns a register finder
2025-11-24 19:23:50 +01:00
func (h *Manager) 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
2025-11-24 19:23:50 +01:00
func (h *Manager) RegisterFinder(finder PeripheralFinder) {
h.finders[finder.GetName()] = finder
log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered")
}
2025-11-02 10:57:53 +01:00
// WaitStop stops the hardware manager
2025-11-24 19:23:50 +01:00
func (h *Manager) WaitStop() error {
2025-11-02 10:57:53 +01:00
log.Trace().Str("file", "hardware").Msg("closing the hardware manager")
// Stop each finder
var errs []error
for name, f := range h.finders {
if err := f.WaitStop(); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", name, err))
}
}
2025-11-29 17:42:42 +01:00
// Wait for all the peripherals to close
log.Trace().Str("file", "MIDIFinder").Msg("closing all MIDI peripherals")
2025-11-30 18:33:00 +01:00
for registeredPeripheralSN, registeredPeripheral := range h.DetectedPeripherals {
2025-11-29 17:42:42 +01:00
err := registeredPeripheral.WaitStop()
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
}
}
2025-11-02 10:57:53 +01:00
// Wait for goroutines to finish
h.wg.Wait()
// Returning errors
if len(errs) > 0 {
return errors.Join(errs...)
}
log.Info().Str("file", "hardware").Msg("hardware manager stopped")
return nil
}