From b864f3ff7b737068a7fa4371c284129265f7481a Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Mon, 1 Dec 2025 19:12:01 +0100 Subject: [PATCH] hardware code cleaning --- hardware/devicesHandler.go | 21 +++ hardware/endpointsHandler.go | 217 ++++++++++++++++++++++++++++ hardware/hardware.go | 264 +---------------------------------- hardware/providersHandler.go | 37 +++++ 4 files changed, 281 insertions(+), 258 deletions(-) create mode 100644 hardware/devicesHandler.go create mode 100644 hardware/endpointsHandler.go create mode 100644 hardware/providersHandler.go diff --git a/hardware/devicesHandler.go b/hardware/devicesHandler.go new file mode 100644 index 0000000..7601af3 --- /dev/null +++ b/hardware/devicesHandler.go @@ -0,0 +1,21 @@ +package hardware + +// 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"` +} + +// Device represents the logical to be controlled +type Device struct { + SerialNumber string // The device s/n + Name string // The device name + Manufacturer string // The device manufacturer + Version string // The device version + + Endpoint Endpoint // The device endpoint which control this device +} diff --git a/hardware/endpointsHandler.go b/hardware/endpointsHandler.go new file mode 100644 index 0000000..d307bce --- /dev/null +++ b/hardware/endpointsHandler.go @@ -0,0 +1,217 @@ +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 + // AddDevice(Device) error // Add a device to the endpoint + // RemoveDevice(Device) error // Remove a device to 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 + + // 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()) +} diff --git a/hardware/hardware.go b/hardware/hardware.go index cafab50..104b76f 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -7,214 +7,30 @@ import ( "sync" "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" -) - -// 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"` -} - -// Device represents the methods used to manage a device (logic element include in a Endpoint) -type Device interface { - Configure() error // Load the mapping for the device -} - -// 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 - // AddDevice(Device) error // Add a device to the endpoint - // RemoveDevice(Device) error // Remove a device to 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 -} - -// EndpointProvider represents how compatible endpoint drivers are implemented -type EndpointProvider 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 -} - // Manager is the class who manages the hardware type Manager struct { mu sync.Mutex wg sync.WaitGroup - providers map[string]EndpointProvider // The map of endpoints providers - DetectedEndpoints map[string]Endpoint // The current list of endpoints - SavedEndpoints map[string]EndpointInfo // The list of stored endpoints + 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 } // NewManager creates a new hardware manager func NewManager() *Manager { log.Trace().Str("package", "hardware").Msg("Hardware instance created") return &Manager{ - providers: make(map[string]EndpointProvider), + providers: make(map[string]Provider, 0), + devices: make(map[string]Device, 0), DetectedEndpoints: make(map[string]Endpoint, 0), SavedEndpoints: make(map[string]EndpointInfo, 0), } } -// 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) -} - // Start starts to find new endpoint events func (h *Manager) Start(ctx context.Context) error { for providerName, provider := range h.providers { @@ -240,74 +56,6 @@ func (h *Manager) Start(ctx context.Context) error { return nil } -// 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 - - // 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()) -} - -// GetProvider returns a register provider -func (h *Manager) GetProvider(providerName string) (EndpointProvider, 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 EndpointProvider) { - h.providers[provider.GetName()] = provider - log.Info().Str("file", "hardware").Str("providerName", provider.GetName()).Msg("provider registered") -} - // WaitStop stops the hardware manager func (h *Manager) WaitStop() error { log.Trace().Str("file", "hardware").Msg("closing the hardware manager") diff --git a/hardware/providersHandler.go b/hardware/providersHandler.go new file mode 100644 index 0000000..8bcbeef --- /dev/null +++ b/hardware/providersHandler.go @@ -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") +}