clean up arch

This commit is contained in:
2025-11-02 10:57:53 +01:00
parent abcc3e0b5e
commit 15d0f8b61b
11 changed files with 175 additions and 334 deletions

View File

@@ -3,6 +3,7 @@ package hardware
import (
"context"
_ "embed"
"errors"
"fmt"
goRuntime "runtime"
"sync"
@@ -19,20 +20,21 @@ import (
*/
import "C"
// FTDIFinder represents how the protocol is defined
// FTDIFinder manages all the FTDI peripherals
type FTDIFinder struct {
findTicker time.Ticker // Peripherals find ticker
wg sync.WaitGroup
findTicker *time.Ticker // Peripherals find ticker
foundPeripherals map[string]PeripheralInfo // The list of peripherals handled by this finder
registeredPeripherals map[string]*FTDIPeripheral // The list of found peripherals
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),
findTicker: time.NewTicker(findPeriod),
foundPeripherals: make(map[string]PeripheralInfo),
registeredPeripherals: make(map[string]*FTDIPeripheral),
scanChannel: make(chan struct{}),
@@ -41,12 +43,15 @@ func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder {
// RegisterPeripheral registers a new peripheral
func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
// Create a new FTDI peripheral
ftdiPeripheral, err := NewFTDIPeripheral(peripheralData)
if err != nil {
return "", fmt.Errorf("unable to create the FTDI peripheral: %v", err)
}
// Register it in the finder
f.registeredPeripherals[peripheralData.SerialNumber] = ftdiPeripheral
log.Trace().Any("periph", &ftdiPeripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created")
// Peripheral created, connect it
err = ftdiPeripheral.Connect(ctx)
if err != nil {
@@ -71,7 +76,7 @@ func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID stri
return err
}
// Disconnecting peripheral
err = peripheral.Disconnect(ctx)
err = peripheral.Disconnect()
if err != nil {
return err
}
@@ -93,9 +98,9 @@ func (f *FTDIFinder) Initialize() error {
// Start starts the finder and search for peripherals
func (f *FTDIFinder) Start(ctx context.Context) error {
f.goWait.Add(1)
f.wg.Add(1)
go func() {
defer f.goWait.Done()
defer f.wg.Done()
for {
select {
case <-ctx.Done():
@@ -120,18 +125,11 @@ func (f *FTDIFinder) Start(ctx context.Context) error {
// 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()
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
return nil
select {
case f.scanChannel <- struct{}{}:
default:
// Ignore if the channel is full or if it is closed
}
}
// GetName returns the name of the driver
@@ -171,18 +169,12 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
log.Info().Int("number", count).Msg("number of FTDI devices connected")
// Alloue un tableau de structures côté C
// Allocating C array
size := C.size_t(count) * C.size_t(unsafe.Sizeof(C.FTDIPeripheralC{}))
devicesPtr := C.malloc(size)
defer C.free(devicesPtr)
devices := (*[1 << 30]C.FTDIPeripheralC)(devicesPtr)[:count:count]
type device struct {
SerialNumber string
Description string
IsOpen bool
}
devices := (*[1 << 20]C.FTDIPeripheralC)(devicesPtr)[:count:count]
C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
@@ -202,7 +194,7 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
ProtocolName: "FTDI",
}
// Libération mémoire allouée côté C
// Free C memory
C.free_ftdi_device(&d)
}
@@ -214,3 +206,34 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
f.foundPeripherals = temporaryPeripherals
return nil
}
// WaitStop stops the finder
func (f *FTDIFinder) WaitStop() error {
log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
// Stop the ticker
f.findTicker.Stop()
// Close the channel
close(f.scanChannel)
// Wait for all the peripherals to close
log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals")
var errs []error
for registeredPeripheralSN, registeredPeripheral := range f.registeredPeripherals {
err := registeredPeripheral.WaitStop()
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
}
}
// Wait for goroutines to stop
f.wg.Wait()
// Returning errors
if len(errs) > 0 {
return errors.Join(errs...)
}
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
return nil
}

View File

@@ -13,6 +13,7 @@ import (
)
/*
#include <stdlib.h>
#cgo LDFLAGS: -L${SRCDIR}/../build/bin -ldmxSender
#include "cpp/include/dmxSenderBridge.h"
*/
@@ -20,29 +21,23 @@ import "C"
// FTDIPeripheral contains the data of an FTDI peripheral
type FTDIPeripheral struct {
info PeripheralInfo // The peripheral basic data
settings map[string]interface{} // The settings of the peripheral
dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
waitGroup sync.WaitGroup // Waitgroup to wait goroutines
isConnected bool // If the FTDI is connected or not
wg sync.WaitGroup
info PeripheralInfo // The peripheral basic data
dmxSender unsafe.Pointer // The command object for piloting the DMX ouptut
}
// NewFTDIPeripheral creates a new FTDI peripheral
func NewFTDIPeripheral(info PeripheralInfo) (*FTDIPeripheral, error) {
log.Info().Str("file", "FTDIPeripheral").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI peripheral created")
settings := make(map[string]interface{})
return &FTDIPeripheral{
info: info,
dmxSender: nil,
settings: settings,
isConnected: false,
info: info,
dmxSender: nil,
}, nil
}
// Connect connects the FTDI peripheral
func (p *FTDIPeripheral) Connect(ctx context.Context) error {
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "connecting")
// Check if the device has already been created
if p.dmxSender != nil {
return errors.Errorf("the DMX device has already been created!")
@@ -51,30 +46,31 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error {
// Create the DMX sender
p.dmxSender = C.dmx_create()
// Connect the peripheral
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("connecting FTDI peripheral...")
err := C.dmx_connect(p.dmxSender, C.CString(p.info.SerialNumber))
if err != C.DMX_OK {
log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Any("err", err).Msg("unable to connect the DMX device")
return errors.Errorf("Unable to connect the DMX Device on the specified port")
// Connect the FTDI
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "connecting")
serialNumber := C.CString(p.info.SerialNumber)
defer C.free(unsafe.Pointer(serialNumber))
if C.dmx_connect(p.dmxSender, serialNumber) != C.DMX_OK {
log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device")
return errors.Errorf("unable to connect '%s'")
}
p.waitGroup.Add(1)
p.wg.Add(1)
go func() {
defer p.waitGroup.Done()
defer p.wg.Done()
<-ctx.Done()
p.Disconnect(ctx)
_ = p.Disconnect()
}()
p.isConnected = true
// Send deactivated state (connected but not activated for now)
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "deactivated")
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully")
return nil
}
// Disconnect disconnects the FTDI peripheral
func (p *FTDIPeripheral) Disconnect(ctx context.Context) error {
func (p *FTDIPeripheral) Disconnect() error {
// Check if the device has already been created
if p.dmxSender == nil {
return errors.Errorf("the DMX device has not been connected!")
@@ -82,15 +78,9 @@ func (p *FTDIPeripheral) Disconnect(ctx context.Context) error {
// Destroy the dmx sender
C.dmx_destroy(p.dmxSender)
p.isConnected = false
return nil
}
// IsConnected returns if the FTDI is connected or not
func (p *FTDIPeripheral) IsConnected() bool {
return p.isConnected
}
// Activate activates the FTDI peripheral
func (p *FTDIPeripheral) Activate(ctx context.Context) error {
// Check if the device has already been created
@@ -136,8 +126,7 @@ func (p *FTDIPeripheral) Deactivate(ctx context.Context) error {
// SetSettings sets a specific setting for this peripheral
func (p *FTDIPeripheral) SetSettings(settings map[string]interface{}) error {
p.settings = settings
return nil
return errors.Errorf("unable to set the settings: not implemented")
}
// SetDeviceProperty sends a command to the specified device
@@ -161,10 +150,18 @@ func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, channelNumber ui
// GetSettings gets the peripheral settings
func (p *FTDIPeripheral) GetSettings() map[string]interface{} {
return p.settings
return map[string]interface{}{}
}
// GetInfo gets all the peripheral information
func (p *FTDIPeripheral) GetInfo() PeripheralInfo {
return p.info
}
// WaitStop wait about the peripheral to close
func (p *FTDIPeripheral) WaitStop() error {
log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for FTDI peripheral to close...")
p.wg.Wait()
log.Info().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("FTDI peripheral closed!")
return nil
}

View File

@@ -2,9 +2,9 @@ package hardware
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/rs/zerolog/log"
@@ -24,16 +24,13 @@ const (
// debounceDuration = 500 * time.Millisecond
)
var (
debounceTimer *time.Timer
)
// HardwareManager is the class who manages the hardware
type HardwareManager struct {
wg sync.WaitGroup
finders 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
@@ -48,6 +45,7 @@ func NewHardwareManager() *HardwareManager {
// Start starts to find new peripheral events
func (h *HardwareManager) Start(ctx context.Context) error {
// Initialize all the finders
for finderName, finder := range h.finders {
err := finder.Initialize()
if err != nil {
@@ -60,9 +58,11 @@ func (h *HardwareManager) Start(ctx context.Context) error {
return err
}
}
h.goWait.Add(1)
// Periodically scan all the finders
h.wg.Add(1)
go func() {
defer h.goWait.Done()
defer h.wg.Done()
for {
select {
case <-ctx.Done():
@@ -95,40 +95,14 @@ func (h *HardwareManager) RegisterFinder(finder PeripheralFinder) {
log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered")
}
// // GetPeripheral gets the peripheral object from the parent finder
// func (h *HardwareManager) GetPeripheral(finderName string, peripheralID string) (Peripheral, bool) {
// // Get the finder
// parentFinder, found := h.finders[finderName]
// // If no finder found, return false
// if !found {
// log.Error().Str("file", "hardware").Str("finderName", finderName).Msg("unable to get the finder")
// return nil, false
// }
// log.Trace().Str("file", "hardware").Str("finderName", parentFinder.GetName()).Msg("finder got")
// // Contact the finder to get the peripheral
// return parentFinder.GetPeripheral(peripheralID)
// }
// Scan scans all the peripherals for the registered finders
func (h *HardwareManager) Scan() error {
h.peripheralsScanTrigger <- struct{}{}
return nil
}
// 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.finders {
err := finder.Stop()
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to stop the finder")
}
select {
case h.peripheralsScanTrigger <- struct{}{}:
return nil
default:
return fmt.Errorf("scan trigger not available (manager stopped?)")
}
// Wait for goroutines to finish
h.goWait.Wait()
log.Info().Str("file", "hardware").Msg("hardware manager stopped")
return nil
}
// emitPeripheralsChanges compares the old and new peripherals to determine which ones have been added or removed.
@@ -151,3 +125,30 @@ func emitPeripheralsChanges(ctx context.Context, oldPeripherals map[string]Perip
}
}
}
// WaitStop stops the hardware manager
func (h *HardwareManager) WaitStop() error {
log.Trace().Str("file", "hardware").Msg("closing the hardware manager")
// Closing trigger channel
close(h.peripheralsScanTrigger)
// 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))
}
}
// 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
}

View File

@@ -6,7 +6,7 @@ import "context"
type Peripheral interface {
Connect(context.Context) error // Connect the peripheral
IsConnected() bool // Return if the peripheral is connected or not
Disconnect(context.Context) error // Disconnect the peripheral
Disconnect() error // Disconnect the peripheral
Activate(context.Context) error // Activate the peripheral
Deactivate(context.Context) error // Deactivate the peripheral
SetSettings(map[string]interface{}) error // Set a peripheral setting
@@ -29,7 +29,7 @@ type PeripheralInfo struct {
type PeripheralFinder interface {
Initialize() error // Initializes the protocol
Start(context.Context) error // Start the detection
Stop() error // Stop the detection
WaitStop() error // Waiting for finder to close
ForceScan() // Explicitly scans for peripherals
RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data
UnregisterPeripheral(context.Context, string) error // Unregisters an existing peripheral