peripheral optimizations

This commit is contained in:
2025-01-04 00:36:29 +01:00
parent 556f24991e
commit e4392c8902
10 changed files with 408 additions and 342 deletions

View File

@@ -3,9 +3,8 @@ package hardware
import (
"context"
"fmt"
"syscall"
"sync"
"time"
"unsafe"
"github.com/rs/zerolog/log"
@@ -31,119 +30,63 @@ var (
// HardwareManager is the class who manages the hardware
type HardwareManager struct {
drivers map[string]PeripheralDriver // The map of peripherals finders
peripherals []Peripheral // The current list of peripherals
deviceChangedEvent chan struct{} // The event when the devices list changed
ctx context.Context
drivers 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
func NewHardwareManager() *HardwareManager {
log.Trace().Str("package", "hardware").Msg("Hardware instance created")
return &HardwareManager{
drivers: make(map[string]PeripheralDriver),
peripherals: make([]Peripheral, 0),
deviceChangedEvent: make(chan struct{}),
drivers: make(map[string]PeripheralFinder),
peripherals: make([]Peripheral, 0),
peripheralsScanTrigger: make(chan struct{}),
}
}
// Start starts to find new peripheral events
func (h *HardwareManager) Start(ctx context.Context) error {
// Configure wndProc callback
cb := windows.NewCallback(h.wndProc)
inst := win.GetModuleHandle(nil)
// Register window class
className, err := syscall.UTF16PtrFromString("DMXConnectPeripheralWatcher")
if err != nil {
return fmt.Errorf("failed to convert window class name to UTF16: %w", err)
}
wc := win.WNDCLASSEX{
CbSize: uint32(unsafe.Sizeof(win.WNDCLASSEX{})),
HInstance: inst,
LpfnWndProc: cb,
LpszClassName: className,
}
if win.RegisterClassEx(&wc) == 0 {
return fmt.Errorf("failed to register window class: %w", syscall.GetLastError())
}
// Create hidden window
windowName, err := syscall.UTF16PtrFromString("usbevent.exe")
if err != nil {
return fmt.Errorf("failed to convert window name to UTF16: %w", err)
}
hwnd := win.CreateWindowEx(
0,
wc.LpszClassName,
windowName,
win.WS_OVERLAPPEDWINDOW,
win.CW_USEDEFAULT,
win.CW_USEDEFAULT,
100,
100,
0,
0,
wc.HInstance,
nil,
)
if hwnd == 0 {
return fmt.Errorf("failed to create window: %w", syscall.GetLastError())
}
// Hide and update window
win.ShowWindow(hwnd, win.SW_HIDE)
win.UpdateWindow(hwnd)
// Start message loop in a goroutine
go messageLoop(ctx, hwnd)
// To handle the peripheral changed
go func() {
defer log.Debug().Str("file", "hardware").Msg("peripheral getter goroutine exited")
for {
select {
case <-ctx.Done():
return
case <-h.deviceChangedEvent:
log.Debug().Str("file", "hardware").Msg("peripheral change event, triggering scan...")
err := h.Scan(ctx)
if err != nil {
log.Err(err).Str("file", "hardware").Msg("unable to scan peripherals")
}
}
for finderName, finder := range h.drivers {
err := finder.Initialize()
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder")
return err
}
}()
err = finder.Start(ctx)
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder")
return err
}
}
// n, err := detector.Register()
// if err != nil {
// log.Err(err).Str("file", "hardware").Msg("error registering the usb event")
// }
// h.detector = n
// // Run the detector
// n.Run(ctx)
// h.goWait.Add(1)
// go func() {
// defer h.goWait.Done()
// for {
// select {
// case <-ctx.Done():
// return
// case <-h.detector.EventChannel:
// // Trigger hardware scans
// log.Info().Str("file", "hardware").Msg("peripheral change event")
// case <-h.peripheralsScanTrigger:
// log.Info().Str("file", "hardware").Msg("scan triggered")
// }
// }
// }()
return nil
}
func messageLoop(ctx context.Context, hwnd win.HWND) {
defer log.Debug().Str("file", "hardware").Msg("Peripheral watcher goroutine exited")
for {
select {
case <-ctx.Done():
win.PostQuitMessage(0) // Gracefully terminate message loop
return
default:
var msg win.MSG
result := win.GetMessage(&msg, hwnd, 0, 0)
if result > 0 {
win.TranslateMessage(&msg)
win.DispatchMessage(&msg)
} else if result == 0 {
log.Warn().Str("file", "hardware").Msg("WM_QUIT message received")
return
} else {
log.Error().Str("file", "hardware").Msg("GetMessage returned an error")
return
}
}
}
}
// GetDriver returns a register driver
func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error) {
func (h *HardwareManager) GetDriver(driverName string) (PeripheralFinder, error) {
driver, exists := h.drivers[driverName]
if !exists {
log.Error().Str("file", "hardware").Str("driverName", driverName).Msg("unable to get the driver")
@@ -154,7 +97,7 @@ func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error)
}
// RegisterDriver registers a new peripherals driver
func (h *HardwareManager) RegisterDriver(driver PeripheralDriver) {
func (h *HardwareManager) RegisterDriver(driver PeripheralFinder) {
h.drivers[driver.GetName()] = driver
log.Info().Str("file", "hardware").Str("driverName", driver.GetName()).Msg("driver registered")
}
@@ -174,41 +117,43 @@ func (h *HardwareManager) GetPeripheral(driverName string, peripheralID string)
}
// Scan scans all the peripherals for the registered finders
func (h *HardwareManager) Scan(ctx context.Context) error {
if len(h.drivers) == 0 {
log.Warn().Str("file", "hardware").Msg("no driver registered")
return fmt.Errorf("no driver registered")
}
for _, driver := range h.drivers {
driverCopy := driver
go func() {
err := driverCopy.Scan(ctx)
if err != nil {
log.Err(err).Str("file", "hardware").Str("driverName", driverCopy.GetName()).Msg("unable to scan peripheral")
return
}
}()
}
func (h *HardwareManager) Scan() error {
h.peripheralsScanTrigger <- struct{}{}
return nil
}
func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr {
log.Trace().Str("file", "hardware").Msg("wndProc triggered")
switch msg {
case win.WM_DEVICECHANGE:
log.Trace().Str("file", "hardware").Uint32("msg", msg).Msg("wndProc triggered")
if msg == win.WM_DEVICECHANGE {
// Trigger the devices scan when the last DEVICE_CHANGE event is received
if debounceTimer != nil {
debounceTimer.Stop()
log.Trace().Str("file", "hardware").Msg("scan debounce timer stopped")
log.Debug().Str("file", "hardware").Msg("scan debounce timer stopped")
}
debounceTimer = time.AfterFunc(debounceDuration, func() {
log.Debug().Str("file", "hardware").Msg("peripheral changed")
h.deviceChangedEvent <- struct{}{}
h.peripheralsScanTrigger <- struct{}{}
})
}
return win.DefWindowProc(win.HWND(hwnd), msg, wParam, lParam)
}
// 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.drivers {
err := finder.Stop()
if err != nil {
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to stop the finder")
}
}
// Wait for goroutines to finish
h.goWait.Wait()
log.Info().Str("file", "hardware").Msg("hardware manager stopped")
return nil
}
// peripheralsList emits a peripheral event
func emitPeripheralsEvents(ctx context.Context, peripheralsList map[string]Peripheral, peripheralEvent PeripheralEvent) {
for _, peripheral := range peripheralsList {