2025-01-18 14:53:29 +00:00
|
|
|
package hardware
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-11-02 10:57:53 +01:00
|
|
|
"errors"
|
2025-01-18 14:53:29 +00:00
|
|
|
"fmt"
|
|
|
|
|
"sync"
|
2025-11-24 19:23:50 +01:00
|
|
|
"time"
|
2025-01-18 14:53:29 +00:00
|
|
|
|
|
|
|
|
"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
|
|
|
|
|
|
2025-11-11 19:14:44 +00:00
|
|
|
// PeripheralStatus is the peripheral status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED)
|
|
|
|
|
type PeripheralStatus string
|
|
|
|
|
|
2025-01-18 14:53:29 +00:00
|
|
|
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"
|
2025-11-14 10:46:24 +00:00
|
|
|
// PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - deactivated - activated)
|
2025-11-11 19:14:44 +00:00
|
|
|
PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS"
|
2025-11-14 10:46:24 +00:00
|
|
|
// PeripheralEventEmitted is triggered when a peripheral event is emitted
|
|
|
|
|
PeripheralEventEmitted PeripheralEvent = "PERIPHERAL_EVENT_EMITTED"
|
2025-11-11 19:14:44 +00:00
|
|
|
// 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-01-18 14:53:29 +00:00
|
|
|
)
|
|
|
|
|
|
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-29 17:42:42 +01:00
|
|
|
finders map[string]PeripheralFinder // The map of peripherals finders
|
|
|
|
|
peripherals map[string]Peripheral // The current list of peripherals
|
|
|
|
|
savedPeripherals map[string]PeripheralInfo // The list of stored peripherals
|
2025-01-18 14:53:29 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-24 19:23:50 +01:00
|
|
|
// NewManager creates a new hardware manager
|
|
|
|
|
func NewManager() *Manager {
|
2025-01-18 14:53:29 +00:00
|
|
|
log.Trace().Str("package", "hardware").Msg("Hardware instance created")
|
2025-11-24 19:23:50 +01:00
|
|
|
return &Manager{
|
2025-11-29 17:42:42 +01:00
|
|
|
finders: make(map[string]PeripheralFinder),
|
|
|
|
|
peripherals: make(map[string]Peripheral, 0),
|
|
|
|
|
savedPeripherals: make(map[string]PeripheralInfo, 0),
|
2025-01-18 14:53:29 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 17:42:42 +01:00
|
|
|
// RegisterPeripheral registers a new peripheral
|
|
|
|
|
func (h *Manager) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
|
|
|
|
|
h.mu.Lock()
|
|
|
|
|
defer h.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
h.savedPeripherals[peripheralData.SerialNumber] = peripheralData
|
|
|
|
|
|
|
|
|
|
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
|
|
|
|
|
|
|
|
|
|
// If already detected, connect it
|
|
|
|
|
if peripheral, ok := h.peripherals[peripheralData.SerialNumber]; ok {
|
|
|
|
|
h.wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer h.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
|
2025-11-29 18:01:34 +01:00
|
|
|
runtime.EventsEmit(ctx, string(PeripheralLoad), peripheralData)
|
2025-11-29 17:42:42 +01:00
|
|
|
|
|
|
|
|
return peripheralData.SerialNumber, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UnregisterPeripheral unregisters an existing peripheral
|
|
|
|
|
func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error {
|
|
|
|
|
h.mu.Lock()
|
|
|
|
|
defer h.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if peripheral, detected := h.peripherals[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(h.savedPeripherals, peripheralData.SerialNumber)
|
2025-11-29 18:01:34 +01:00
|
|
|
runtime.EventsEmit(ctx, string(PeripheralUnload), peripheralData)
|
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
|
|
|
|
|
peripheral, found := h.peripherals[peripheralSN]
|
|
|
|
|
if !found {
|
|
|
|
|
// Peripheral not detected, return the last settings saved
|
|
|
|
|
if savedPeripheral, isFound := h.savedPeripherals[peripheralSN]; isFound {
|
|
|
|
|
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 {
|
|
|
|
|
peripheral, found := h.peripherals[peripheralSN]
|
|
|
|
|
if !found {
|
|
|
|
|
return fmt.Errorf("unable to found the FTDI peripheral")
|
|
|
|
|
}
|
|
|
|
|
return peripheral.SetSettings(ctx, settings)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-18 14:53:29 +00:00
|
|
|
// 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))
|
|
|
|
|
h.RegisterFinder(NewOS2LFinder())
|
|
|
|
|
h.RegisterFinder(NewMIDIFinder(3 * time.Second))
|
|
|
|
|
|
2025-01-18 14:53:29 +00:00
|
|
|
for finderName, finder := range h.finders {
|
2025-11-29 17:42:42 +01:00
|
|
|
|
|
|
|
|
// Initialize the finder
|
2025-01-18 14:53:29 +00:00
|
|
|
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
|
2025-01-18 14:53:29 +00:00
|
|
|
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
|
|
|
|
|
func (h *Manager) OnPeripheralArrival(ctx context.Context, peripheral Peripheral) {
|
|
|
|
|
// Add the peripheral to the detected hardware
|
|
|
|
|
h.peripherals[peripheral.GetInfo().SerialNumber] = peripheral
|
|
|
|
|
|
|
|
|
|
// If the peripheral is saved in the project, connect it
|
|
|
|
|
if _, saved := h.savedPeripherals[peripheral.GetInfo().SerialNumber]; saved {
|
|
|
|
|
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")
|
2025-01-18 14:53:29 +00:00
|
|
|
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-01-18 14:53:29 +00:00
|
|
|
}
|
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
|
|
|
|
|
delete(h.peripherals, peripheral.GetInfo().SerialNumber)
|
|
|
|
|
|
|
|
|
|
// TODO: Update the Peripheral reference in the corresponding devices
|
|
|
|
|
runtime.EventsEmit(ctx, string(PeripheralRemoval), peripheral.GetInfo())
|
2025-01-18 14:53:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetFinder returns a register finder
|
2025-11-24 19:23:50 +01:00
|
|
|
func (h *Manager) GetFinder(finderName string) (PeripheralFinder, error) {
|
2025-01-18 14:53:29 +00:00
|
|
|
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) {
|
2025-01-18 14:53:29 +00:00
|
|
|
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")
|
|
|
|
|
for registeredPeripheralSN, registeredPeripheral := range h.peripherals {
|
|
|
|
|
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
|
|
|
|
|
}
|