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()) }