From 19ec0bec740c096370d64943fb24281404b2194d Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Fri, 14 Nov 2025 20:19:44 +0100 Subject: [PATCH 01/12] add MIDI discovery, connection and events --- app.go | 5 +- go.mod | 3 +- go.sum | 7 +- hardware/FTDIFinder.go | 4 +- hardware/MIDIFinder.go | 341 ++++++++++++++++++++++++++++--------- hardware/MIDIPeripheral.go | 68 +++++++- hardware/OS2LFinder.go | 4 +- 7 files changed, 331 insertions(+), 101 deletions(-) diff --git a/app.go b/app.go index 3b5b12f..b6f6736 100644 --- a/app.go +++ b/app.go @@ -30,9 +30,10 @@ type App struct { func NewApp() *App { // Create a new hadware manager hardwareManager := hardware.NewHardwareManager() - // hardwareManager.RegisterFinder(hardware.NewMIDIFinder(5 * time.Second)) - hardwareManager.RegisterFinder(hardware.NewFTDIFinder(5 * time.Second)) + hardwareManager.RegisterFinder(hardware.NewFTDIFinder(3 * time.Second)) hardwareManager.RegisterFinder(hardware.NewOS2LFinder()) + hardwareManager.RegisterFinder(hardware.NewMIDIFinder(3 * time.Second)) + return &App{ hardwareManager: hardwareManager, projectSave: "", diff --git a/go.mod b/go.mod index f721582..522220d 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,11 @@ go 1.21 toolchain go1.21.3 require ( - github.com/mattrtaylor/go-rtmidi v0.0.0-20220428034745-af795b1c1a79 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/wailsapp/wails/v2 v2.9.1 + gitlab.com/gomidi/midi v1.23.7 + gitlab.com/gomidi/rtmididrv v0.15.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 78505ec..25cae2e 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattrtaylor/go-rtmidi v0.0.0-20220428034745-af795b1c1a79 h1:CA1UHN3RuY70DlC0RlvgtB1e8h3kYzmvK7s8CFe+Ohw= -github.com/mattrtaylor/go-rtmidi v0.0.0-20220428034745-af795b1c1a79/go.mod h1:oBuZjmjlKSj9CZKrNhcx/adNhHiiE0hZknECjIP8Z0Q= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -70,6 +68,11 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/wails/v2 v2.9.1 h1:irsXnoQrCpeKzKTYZ2SUVlRRyeMR6I0vCO9Q1cvlEdc= github.com/wailsapp/wails/v2 v2.9.1/go.mod h1:7maJV2h+Egl11Ak8QZN/jlGLj2wg05bsQS+ywJPT0gI= +gitlab.com/gomidi/midi v1.21.0/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c= +gitlab.com/gomidi/midi v1.23.7 h1:I6qKoIk9s9dcX+pNf0jC+tziCzJFn82bMpuntRkLeik= +gitlab.com/gomidi/midi v1.23.7/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c= +gitlab.com/gomidi/rtmididrv v0.15.0 h1:52Heco8Y3Jjcl4t0yDUVikOxfI8FMF1Zq+qsG++TUeo= +gitlab.com/gomidi/rtmididrv v0.15.0/go.mod h1:p/6IL1LGgj7utcv3wXudsDWiD9spgAdn0O8LDsGIPG0= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go index b620730..c2b8e25 100644 --- a/hardware/FTDIFinder.go +++ b/hardware/FTDIFinder.go @@ -172,7 +172,6 @@ func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]inte } return nil, fmt.Errorf("unable to found the peripheral") } - log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") return peripheral.GetSettings(), nil } @@ -181,8 +180,7 @@ func (f *FTDIFinder) SetPeripheralSettings(ctx context.Context, peripheralID str // Return the specified peripheral peripheral, found := f.detected[peripheralID] if !found { - log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder") - return fmt.Errorf("unable to found the peripheral") + return fmt.Errorf("unable to found the FTDI peripheral") } log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") return peripheral.SetSettings(settings) diff --git a/hardware/MIDIFinder.go b/hardware/MIDIFinder.go index 4feb2d8..62b46af 100644 --- a/hardware/MIDIFinder.go +++ b/hardware/MIDIFinder.go @@ -2,6 +2,7 @@ package hardware import ( "context" + "errors" "fmt" "regexp" "strconv" @@ -9,27 +10,45 @@ import ( "sync" "time" - "github.com/mattrtaylor/go-rtmidi" "github.com/rs/zerolog/log" + "github.com/wailsapp/wails/v2/pkg/runtime" + "gitlab.com/gomidi/rtmididrv" ) // MIDIFinder represents how the protocol is defined type MIDIFinder struct { - findTicker time.Ticker // Peripherals find ticker - registeredPeripherals map[string]MIDIPeripheral // The list of peripherals - scanChannel chan struct{} // The channel to trigger a scan event - goWait sync.WaitGroup // Check goroutines execution + wg sync.WaitGroup + mu sync.Mutex + + saved map[string]PeripheralInfo // Peripherals saved in the project + detected map[string]*MIDIPeripheral // Detected peripherals + + scanEvery time.Duration // Scans peripherals periodically + + onArrival func(p PeripheralInfo) // When a peripheral arrives + onRemoval func(p PeripheralInfo) // When a peripheral goes away } -// NewMIDIFinder creates a new DMXUSB protocol -func NewMIDIFinder(findPeriod time.Duration) *MIDIFinder { +// NewMIDIFinder creates a new MIDI finder +func NewMIDIFinder(scanEvery time.Duration) *MIDIFinder { log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder created") return &MIDIFinder{ - findTicker: *time.NewTicker(findPeriod), - registeredPeripherals: make(map[string]MIDIPeripheral), + scanEvery: scanEvery, + saved: make(map[string]PeripheralInfo), + detected: make(map[string]*MIDIPeripheral), } } +// OnArrival is the callback function when a new peripheral arrives +func (f *MIDIFinder) OnArrival(cb func(p PeripheralInfo)) { + f.onArrival = cb +} + +// OnRemoval i the callback when a peripheral goes away +func (f *MIDIFinder) OnRemoval(cb func(p PeripheralInfo)) { + f.onRemoval = cb +} + // Initialize initializes the MIDI driver func (f *MIDIFinder) Initialize() error { log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder initialized") @@ -38,44 +57,73 @@ func (f *MIDIFinder) Initialize() error { // RegisterPeripheral registers a new peripheral func (f *MIDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { - peripheral, err := NewMIDIPeripheral(peripheralData) - if err != nil { - return "", fmt.Errorf("unable to create the MIDI peripheral: %v", err) + f.mu.Lock() + defer f.mu.Unlock() + + f.saved[peripheralData.SerialNumber] = peripheralData + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) + + // If already detected, connect it + if peripheral, ok := f.detected[peripheralData.SerialNumber]; ok { + f.wg.Add(1) + go func() { + defer f.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 + } + }() } - f.registeredPeripherals[peripheralData.SerialNumber] = *peripheral - log.Trace().Any("periph", &peripheral).Str("file", "MIDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created") + + // Emits the event in the hardware + runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) return peripheralData.SerialNumber, nil } // UnregisterPeripheral unregisters an existing peripheral -func (f *MIDIFinder) UnregisterPeripheral(peripheralID string) error { - peripheral, registered := f.registeredPeripherals[peripheralID] - if registered { - err := peripheral.Disconnect() +func (f *MIDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error { + f.mu.Lock() + defer f.mu.Unlock() + + if peripheral, detected := f.detected[peripheralData.SerialNumber]; detected { + // Deactivating peripheral + err := peripheral.Deactivate(ctx) if err != nil { - return err + 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(f.registeredPeripherals, peripheralID) + delete(f.saved, peripheralData.SerialNumber) + runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) return nil } // Start starts the finder and search for peripherals func (f *MIDIFinder) Start(ctx context.Context) error { - f.goWait.Add(1) + f.wg.Add(1) go func() { - defer f.goWait.Done() + ticker := time.NewTicker(f.scanEvery) + defer ticker.Stop() + defer f.wg.Done() + for { select { case <-ctx.Done(): return - case <-f.findTicker.C: - // Scan the peripherals - err := f.scanPeripherals(ctx) - if err != nil { - log.Err(err).Str("file", "MIDIFinder").Msg("unable to scan MIDI peripherals") - } - case <-f.scanChannel: + case <-ticker.C: // Scan the peripherals err := f.scanPeripherals(ctx) if err != nil { @@ -87,13 +135,30 @@ func (f *MIDIFinder) Start(ctx context.Context) error { return nil } -// Stop stops the finder -func (f *MIDIFinder) Stop() error { +// WaitStop stops the finder +func (f *MIDIFinder) WaitStop() error { log.Trace().Str("file", "MIDIFinder").Msg("stopping the MIDI finder...") + + // Close the channel + // close(f.scanChannel) + + // Wait for all the peripherals to close + log.Trace().Str("file", "MIDIFinder").Msg("closing all MIDI peripherals") + var errs []error + for registeredPeripheralSN, registeredPeripheral := range f.detected { + err := registeredPeripheral.WaitStop() + if err != nil { + errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err)) + } + } + // Wait for goroutines to stop - f.goWait.Wait() - // Stop the ticker - f.findTicker.Stop() + f.wg.Wait() + + // Returning errors + if len(errs) > 0 { + return errors.Join(errs...) + } log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder stopped") return nil } @@ -106,24 +171,25 @@ func (f *MIDIFinder) GetName() string { // GetPeripheralSettings gets the peripheral settings func (f *MIDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) { // Return the specified peripheral - peripheral, found := f.registeredPeripherals[peripheralID] + peripheral, found := f.detected[peripheralID] if !found { - log.Error().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder") + // FTDI not detected, return the last settings saved + if savedPeripheral, isFound := f.saved[peripheralID]; isFound { + return savedPeripheral.Settings, nil + } return nil, fmt.Errorf("unable to found the peripheral") } - log.Debug().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") return peripheral.GetSettings(), nil } // SetPeripheralSettings sets the peripheral settings -func (f *MIDIFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error { +func (f *MIDIFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]interface{}) error { // Return the specified peripheral - peripheral, found := f.registeredPeripherals[peripheralID] + peripheral, found := f.detected[peripheralID] if !found { - log.Error().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder") - return fmt.Errorf("unable to found the peripheral") + return fmt.Errorf("unable to found the MIDI peripheral") } - log.Debug().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") + log.Debug().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the MIDI finder") return peripheral.SetSettings(settings) } @@ -150,61 +216,168 @@ func splitStringAndNumber(input string) (string, int, error) { // ForceScan explicily asks for scanning peripherals func (f *MIDIFinder) ForceScan() { - f.scanChannel <- struct{}{} + // f.scanChannel <- struct{}{} } // scanPeripherals scans the MIDI peripherals func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { - // midiPeripherals := make(map[string]Peripheral) - log.Trace().Str("file", "MIDIFinder").Msg("opening MIDI scanner port...") - midiScanner, err := rtmidi.NewMIDIInDefault() + currentMap := make(map[string]*MIDIPeripheral) + + drv, err := rtmididrv.New() if err != nil { - log.Err(err).Str("file", "MIDIFinder").Msg("unable to open the MIDI scanner port...") - return fmt.Errorf("unable to open the MIDI scanner: %s", err) + return fmt.Errorf("unable to open the MIDI driver") } - defer midiScanner.Close() - midiScanner.SetCallback(func(m rtmidi.MIDIIn, b []byte, f float64) {}) - log.Trace().Str("file", "MIDIFinder").Msg("scanning MIDI peripherals...") - devicesCount, err := midiScanner.PortCount() + defer drv.Close() + + // Get MIDI INPUT ports + ins, err := drv.Ins() if err != nil { - log.Err(err).Str("file", "MIDIFinder").Msg("unable to scan MIDI peripherals...") - return fmt.Errorf("unable to scan MIDI peripherals: %s", err) + return fmt.Errorf("unable to scan MIDI IN ports: %s", err) } - for i := 0; i < devicesCount; i++ { - portName, err := midiScanner.PortName(i) - if err != nil { - log.Warn().Str("file", "MIDIPeripheral").Msg("found peripheral without a correct name, set it to unknown") - portName = "Unknown device 0" + + for _, port := range ins { + // Exclude microsoft wavetable from the list + if strings.Contains(port.String(), "GS Wavetable") { + continue } - // Separate data - name, location, err := splitStringAndNumber(portName) - if err != nil { - log.Err(err).Str("file", "MIDIFinder").Str("description", portName).Msg("invalid peripheral description") - return fmt.Errorf("invalid pripheral description: %s", err) + + baseName := normalizeName(port.String()) + + if _, ok := currentMap[baseName]; !ok { + currentMap[baseName] = &MIDIPeripheral{ + info: PeripheralInfo{ + Name: baseName, + SerialNumber: baseName, + ProtocolName: "MIDI", + }, + } + } + + currentMap[baseName].inputPorts = append(currentMap[baseName].inputPorts, port) + log.Info().Any("peripherals", currentMap).Msg("available MIDI IN ports") + } + + // Get MIDI OUTPUT ports + outs, err := drv.Outs() + if err != nil { + return fmt.Errorf("unable to scan MIDI OUT ports: %s", err) + } + + for _, port := range outs { + // Exclude microsoft wavetable from the list + if strings.Contains(port.String(), "GS Wavetable") { + continue + } + baseName := normalizeName(port.String()) + + if _, ok := currentMap[baseName]; !ok { + currentMap[baseName] = &MIDIPeripheral{ + info: PeripheralInfo{ + Name: baseName, + SerialNumber: baseName, + ProtocolName: "MIDI", + }, + } + } + + currentMap[baseName].outputsPorts = append(currentMap[baseName].outputsPorts, port) + log.Info().Any("peripherals", currentMap).Msg("available MIDI OUT ports") + } + + log.Debug().Any("value", currentMap).Msg("MIDI peripherals map") + + // Detect arrivals + for sn, discovery := range currentMap { + if _, known := f.detected[sn]; !known { + + peripheral := NewMIDIPeripheral(discovery.info, discovery.inputPorts, discovery.outputsPorts) + + f.detected[sn] = peripheral + + if f.onArrival != nil { + f.onArrival(discovery.GetInfo()) + } + log.Info().Str("sn", sn).Str("name", discovery.GetInfo().SerialNumber).Msg("[MIDI] New peripheral detected") + + // If the peripheral is saved in the project => connect + if _, saved := f.saved[sn]; saved { + f.wg.Add(1) + go func(p PeripheralInfo) { + defer f.wg.Done() + err := peripheral.Connect(ctx) + if err != nil { + log.Err(err).Str("sn", p.SerialNumber).Msg("unable to connect the MIDI peripheral") + return + } + err = peripheral.Activate(ctx) + if err != nil { + log.Err(err).Str("sn", p.SerialNumber).Msg("unable to activate the MIDI peripheral") + return + } + }(discovery.GetInfo()) + } + } + } + + // Detect removals + for sn, oldPeripheral := range f.detected { + if _, still := currentMap[sn]; !still { + + // Properly clean the DMX device + err := oldPeripheral.Deactivate(ctx) + if err != nil { + log.Err(err).Str("sn", sn).Msg("unable to deactivate the MIDI peripheral after disconnection") + } + err = oldPeripheral.Disconnect(ctx) + if err != nil { + log.Err(err).Str("sn", sn).Msg("unable to disconnect the MIDI peripheral after disconnection") + } + + // Delete it from the detected list + delete(f.detected, sn) + log.Info().Str("sn", sn).Str("name", oldPeripheral.GetInfo().Name).Msg("[MIDI] peripheral removed") + + // Execute the removal callback + if f.onRemoval != nil { + f.onRemoval(oldPeripheral.GetInfo()) + } } - log.Info().Str("file", "MIDIFinder").Str("name", name).Int("location", location).Msg("MIDI peripheral found") - // Add the peripheral to the temporary list - // sn := strings.ToLower(strings.Replace(name, " ", "_", -1)) - // midiPeripherals[sn] = NewMIDIPeripheral(name, location, sn) } - // Compare with the current peripherals to detect arrivals/removals - // removedList, addedList := comparePeripherals(f.peripherals, midiPeripherals) - // Emit the events - // emitPeripheralsEvents(ctx, removedList, PeripheralRemoval) - log.Info().Str("file", "MIDIFinder").Msg("MIDI remove list emitted to the front") - // emitPeripheralsEvents(ctx, addedList, PeripheralArrival) - log.Info().Str("file", "MIDIFinder").Msg("MIDI add list emitted to the front") - // Store the new peripherals list - // f.peripherals = midiPeripherals return nil } -// CreatePeripheral is not implemented here -func (f *MIDIFinder) CreatePeripheral(context.Context) (Peripheral, error) { - return nil, nil +func normalizeName(raw string) string { + name := strings.TrimSpace(raw) + + // Si parenthèses, prendre le texte à l'intérieur + start := strings.Index(name, "(") + end := strings.LastIndex(name, ")") + if start != -1 && end != -1 && start < end { + name = name[start+1 : end] + return strings.TrimSpace(name) + } + + // Sinon, supprimer le dernier mot s'il est un entier + parts := strings.Fields(name) // découpe en mots + if len(parts) > 1 { + if _, err := strconv.Atoi(parts[len(parts)-1]); err == nil { + parts = parts[:len(parts)-1] // retirer le dernier mot + } + } + + return strings.Join(parts, " ") } -// DeletePeripheral is not implemented here -func (f *MIDIFinder) DeletePeripheral(serialNumber string) error { - return nil -} +// func normalizeName(name string) string { +// // Cas communs : "MIDIIN2 (XXX)" → "XXX" +// if strings.Contains(name, "(") && strings.Contains(name, ")") { +// start := strings.Index(name, "(") +// end := strings.Index(name, ")") +// if start < end { +// return strings.TrimSpace(name[start+1 : end]) +// } +// } + +// // Sinon, on retourne tel quel +// return strings.TrimSpace(name) +// } diff --git a/hardware/MIDIPeripheral.go b/hardware/MIDIPeripheral.go index 16aadef..b54a564 100644 --- a/hardware/MIDIPeripheral.go +++ b/hardware/MIDIPeripheral.go @@ -2,43 +2,91 @@ package hardware import ( "context" + "fmt" + "sync" "github.com/rs/zerolog/log" + "github.com/wailsapp/wails/v2/pkg/runtime" + "gitlab.com/gomidi/midi" ) // MIDIPeripheral contains the data of a MIDI peripheral type MIDIPeripheral struct { + wg sync.WaitGroup + + inputPorts []midi.In + outputsPorts []midi.Out + info PeripheralInfo // The peripheral info - location int // The location of the peripheral settings map[string]interface{} // The settings of the peripheral } // NewMIDIPeripheral creates a new MIDI peripheral -func NewMIDIPeripheral(peripheralData PeripheralInfo) (*MIDIPeripheral, error) { +func NewMIDIPeripheral(peripheralData PeripheralInfo, inputs []midi.In, outputs []midi.Out) *MIDIPeripheral { log.Trace().Str("file", "MIDIPeripheral").Str("name", peripheralData.Name).Str("s/n", peripheralData.SerialNumber).Msg("MIDI peripheral created") return &MIDIPeripheral{ - info: peripheralData, - settings: peripheralData.Settings, - }, nil + info: peripheralData, + inputPorts: inputs, + outputsPorts: outputs, + settings: peripheralData.Settings, + } } // Connect connects the MIDI peripheral func (p *MIDIPeripheral) Connect(ctx context.Context) error { + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusConnecting) + + // Open input ports + for _, port := range p.inputPorts { + err := port.Open() + if err != nil { + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected) + return fmt.Errorf("unable to open the MIDI IN port: %w", err) + } + port.SetListener(func(msg []byte, delta int64) { + // Emit the event to the front + runtime.EventsEmit(ctx, string(PeripheralEventEmitted), p.info.SerialNumber, msg) + log.Debug().Str("message", string(msg)).Int64("delta", delta).Msg("message received") + }) + log.Info().Str("name", port.String()).Msg("port open successfully") + } + + p.wg.Add(1) + go func() { + defer p.wg.Done() + <-ctx.Done() + _ = p.Disconnect(ctx) + }() + + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated) + return nil } // Disconnect disconnects the MIDI peripheral -func (p *MIDIPeripheral) Disconnect() error { +func (p *MIDIPeripheral) Disconnect(ctx context.Context) error { + // Close all inputs ports + for _, port := range p.inputPorts { + err := port.Close() + if err != nil { + return fmt.Errorf("unable to close the MIDI IN port: %w", err) + } + } + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDisconnected) return nil } // Activate activates the MIDI peripheral func (p *MIDIPeripheral) Activate(ctx context.Context) error { + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusActivated) + return nil } // Deactivate deactivates the MIDI peripheral func (p *MIDIPeripheral) Deactivate(ctx context.Context) error { + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.GetInfo(), PeripheralStatusDeactivated) + return nil } @@ -62,3 +110,11 @@ func (p *MIDIPeripheral) GetSettings() map[string]interface{} { func (p *MIDIPeripheral) GetInfo() PeripheralInfo { return p.info } + +// WaitStop wait about the peripheral to close +func (p *MIDIPeripheral) WaitStop() error { + log.Info().Str("file", "MIDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for MIDI peripheral to close...") + p.wg.Wait() + log.Info().Str("file", "MIDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("MIDI peripheral closed!") + return nil +} diff --git a/hardware/OS2LFinder.go b/hardware/OS2LFinder.go index 26d534d..d4c0684 100644 --- a/hardware/OS2LFinder.go +++ b/hardware/OS2LFinder.go @@ -139,7 +139,6 @@ func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]any, log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the OS2L finder") return nil, fmt.Errorf("unable to found the peripheral") } - log.Debug().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the OS2L finder") return peripheral.GetSettings(), nil } @@ -148,8 +147,7 @@ func (f *OS2LFinder) SetPeripheralSettings(ctx context.Context, peripheralID str // Return the specified peripheral peripheral, found := f.saved[peripheralID] if !found { - log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder") - return fmt.Errorf("unable to found the peripheral") + return fmt.Errorf("unable to found the FTDI peripheral") } // Set the peripheral settings -- 2.49.1 From 76b705012ceea8de3b9ba4a4be51cf61ccfb2d36 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Sun, 16 Nov 2025 21:44:48 +0100 Subject: [PATCH 02/12] MIDI device start of implementation --- .gitignore | 1 + hardware/MIDIFinder.go | 19 +++++++++++-------- hardware/MIDIPeripheral.go | 13 +++++++++++-- hardware/interfaces.go | 22 ++++++++++++++++++++-- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 8195664..25e933e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build/bin projects +mapping node_modules frontend/.vscode frontend/dist diff --git a/hardware/MIDIFinder.go b/hardware/MIDIFinder.go index 62b46af..d062ef2 100644 --- a/hardware/MIDIFinder.go +++ b/hardware/MIDIFinder.go @@ -242,18 +242,19 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { } baseName := normalizeName(port.String()) + sn := strings.ReplaceAll(strings.ToLower(baseName), " ", "_") - if _, ok := currentMap[baseName]; !ok { - currentMap[baseName] = &MIDIPeripheral{ + if _, ok := currentMap[sn]; !ok { + currentMap[sn] = &MIDIPeripheral{ info: PeripheralInfo{ Name: baseName, - SerialNumber: baseName, + SerialNumber: sn, ProtocolName: "MIDI", }, } } - currentMap[baseName].inputPorts = append(currentMap[baseName].inputPorts, port) + currentMap[sn].inputPorts = append(currentMap[sn].inputPorts, port) log.Info().Any("peripherals", currentMap).Msg("available MIDI IN ports") } @@ -270,17 +271,19 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { } baseName := normalizeName(port.String()) - if _, ok := currentMap[baseName]; !ok { - currentMap[baseName] = &MIDIPeripheral{ + sn := strings.ReplaceAll(strings.ToLower(baseName), " ", "_") + + if _, ok := currentMap[sn]; !ok { + currentMap[sn] = &MIDIPeripheral{ info: PeripheralInfo{ Name: baseName, - SerialNumber: baseName, + SerialNumber: sn, ProtocolName: "MIDI", }, } } - currentMap[baseName].outputsPorts = append(currentMap[baseName].outputsPorts, port) + currentMap[sn].outputsPorts = append(currentMap[sn].outputsPorts, port) log.Info().Any("peripherals", currentMap).Msg("available MIDI OUT ports") } diff --git a/hardware/MIDIPeripheral.go b/hardware/MIDIPeripheral.go index b54a564..09d8243 100644 --- a/hardware/MIDIPeripheral.go +++ b/hardware/MIDIPeripheral.go @@ -10,15 +10,24 @@ import ( "gitlab.com/gomidi/midi" ) +// MIDIDevice represents the device to control +type MIDIDevice struct { + ID string // Device ID + Mapping MappingInfo // Device mapping configuration +} + +// ------------------- // + // MIDIPeripheral contains the data of a MIDI peripheral type MIDIPeripheral struct { wg sync.WaitGroup inputPorts []midi.In outputsPorts []midi.Out + info PeripheralInfo // The peripheral info + settings map[string]interface{} // The settings of the peripheral - info PeripheralInfo // The peripheral info - settings map[string]interface{} // The settings of the peripheral + devices []MIDIDevice // All the MIDI devices that the peripheral can handle } // NewMIDIPeripheral creates a new MIDI peripheral diff --git a/hardware/interfaces.go b/hardware/interfaces.go index 6c9ea82..dc8308d 100644 --- a/hardware/interfaces.go +++ b/hardware/interfaces.go @@ -2,6 +2,21 @@ package hardware import "context" +// 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 Peripheral) +type Device interface { + Configure() error // Load the mapping for the device +} + // Peripheral represents the methods used to manage a peripheral (input or output hardware) type Peripheral interface { Connect(context.Context) error // Connect the peripheral @@ -9,12 +24,15 @@ type Peripheral interface { Disconnect(context.Context) error // Disconnect the peripheral Activate(context.Context) error // Activate the peripheral Deactivate(context.Context) error // Deactivate the peripheral + AddDevice(Device) error // Add a device to the peripheral + RemoveDevice(Device) error // Remove a device to the peripheral + GetSettings() map[string]any // Get the peripheral settings SetSettings(context.Context, map[string]any) error // Set a peripheral setting SetDeviceProperty(context.Context, uint32, byte) error // Update a device property WaitStop() error // Properly close the peripheral - GetInfo() PeripheralInfo // Get the peripheral information - GetSettings() map[string]any // Get the peripheral settings + GetInfo() PeripheralInfo // Get the peripheral information + } // PeripheralInfo represents a peripheral information -- 2.49.1 From ea46430b7105f4ba3b0ff102774b6c9140d3a504 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Mon, 24 Nov 2025 19:23:50 +0100 Subject: [PATCH 03/12] syntax improvement --- app.go | 15 +++++---------- hardware/hardware.go | 29 +++++++++++++++++------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app.go b/app.go index b6f6736..2eb3264 100644 --- a/app.go +++ b/app.go @@ -5,7 +5,6 @@ import ( "dmxconnect/hardware" "fmt" "io" - "time" "github.com/rs/zerolog/log" @@ -20,20 +19,16 @@ type App struct { cancelFunc context.CancelFunc wait sync.WaitGroup - hardwareManager *hardware.HardwareManager // For managing all the hardware - wmiMutex sync.Mutex // Avoid some WMI operations at the same time - projectInfo ProjectInfo // The project information structure - projectSave string // The file name of the project + hardwareManager *hardware.Manager // For managing all the hardware + wmiMutex sync.Mutex // Avoid some WMI operations at the same time + projectInfo ProjectInfo // The project information structure + projectSave string // The file name of the project } // NewApp creates a new App application struct func NewApp() *App { // Create a new hadware manager - hardwareManager := hardware.NewHardwareManager() - hardwareManager.RegisterFinder(hardware.NewFTDIFinder(3 * time.Second)) - hardwareManager.RegisterFinder(hardware.NewOS2LFinder()) - hardwareManager.RegisterFinder(hardware.NewMIDIFinder(3 * time.Second)) - + hardwareManager := hardware.NewManager() return &App{ hardwareManager: hardwareManager, projectSave: "", diff --git a/hardware/hardware.go b/hardware/hardware.go index 81ba457..d581306 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sync" + "time" "github.com/rs/zerolog/log" "github.com/wailsapp/wails/v2/pkg/runtime" @@ -35,28 +36,32 @@ const ( PeripheralStatusActivated PeripheralStatus = "PERIPHERAL_ACTIVATED" ) -// HardwareManager is the class who manages the hardware -type HardwareManager struct { +// Manager is the class who manages the hardware +type Manager struct { wg sync.WaitGroup finders map[string]PeripheralFinder // The map of peripherals finders - peripherals []Peripheral // The current list of peripherals + peripherals []*Peripheral // The current list of peripherals peripheralsScanTrigger chan struct{} // Trigger the peripherals scans } -// NewHardwareManager creates a new HardwareManager -func NewHardwareManager() *HardwareManager { +// NewManager creates a new hardware manager +func NewManager() *Manager { log.Trace().Str("package", "hardware").Msg("Hardware instance created") - return &HardwareManager{ + return &Manager{ finders: make(map[string]PeripheralFinder), - peripherals: make([]Peripheral, 0), + peripherals: make([]*Peripheral, 0), peripheralsScanTrigger: make(chan struct{}), } } // Start starts to find new peripheral events -func (h *HardwareManager) Start(ctx context.Context) error { +func (h *Manager) Start(ctx context.Context) error { // Initialize all the finders and their callback functions + h.RegisterFinder(NewFTDIFinder(3 * time.Second)) + h.RegisterFinder(NewOS2LFinder()) + h.RegisterFinder(NewMIDIFinder(3 * time.Second)) + for finderName, finder := range h.finders { err := finder.Initialize() if err != nil { @@ -96,7 +101,7 @@ func (h *HardwareManager) Start(ctx context.Context) error { } // GetFinder returns a register finder -func (h *HardwareManager) GetFinder(finderName string) (PeripheralFinder, error) { +func (h *Manager) GetFinder(finderName string) (PeripheralFinder, error) { finder, exists := h.finders[finderName] if !exists { log.Error().Str("file", "hardware").Str("finderName", finderName).Msg("unable to get the finder") @@ -107,13 +112,13 @@ func (h *HardwareManager) GetFinder(finderName string) (PeripheralFinder, error) } // RegisterFinder registers a new peripherals finder -func (h *HardwareManager) RegisterFinder(finder PeripheralFinder) { +func (h *Manager) RegisterFinder(finder PeripheralFinder) { h.finders[finder.GetName()] = finder log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered") } // Scan scans all the peripherals for the registered finders -func (h *HardwareManager) Scan() error { +func (h *Manager) Scan() error { select { case h.peripheralsScanTrigger <- struct{}{}: return nil @@ -123,7 +128,7 @@ func (h *HardwareManager) Scan() error { } // WaitStop stops the hardware manager -func (h *HardwareManager) WaitStop() error { +func (h *Manager) WaitStop() error { log.Trace().Str("file", "hardware").Msg("closing the hardware manager") // Closing trigger channel -- 2.49.1 From 848d2758d7de8552615d0b007327356088d50cd9 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Sat, 29 Nov 2025 17:42:42 +0100 Subject: [PATCH 04/12] Adapting interfaces --- .../Settings/InputsOutputsContent.svelte | 16 +- hardware/FTDIFinder.go | 174 ++------------- hardware/FTDIPeripheral.go | 2 +- hardware/MIDIFinder.go | 167 ++------------- hardware/MIDIPeripheral.go | 4 +- hardware/OS2LFinder.go | 131 ++---------- hardware/OS2LPeripheral.go | 2 +- hardware/hardware.go | 202 ++++++++++++++---- hardware/interfaces.go | 32 ++- peripherals.go | 86 ++------ project.go | 12 +- 11 files changed, 264 insertions(+), 564 deletions(-) diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index f67b102..591be60 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -4,9 +4,19 @@ import { t, _ } from 'svelte-i18n' import { generateToast, needProjectSave, peripherals, colors } from "../../stores"; import { get } from "svelte/store" - import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral } from "../../../wailsjs/go/main/App"; + import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral, CreatePeripheral } from "../../../wailsjs/go/main/App"; import RoundedButton from "../General/RoundedButton.svelte"; + // Create a new peripheral + function createPeripheral(peripheral){ + // Add the peripheral to the project (backend) + CreatePeripheral(peripheral) + .catch((error) => { + console.log("Unable to create the peripheral: " + error) + generateToast('danger', 'bx-error', $_("addPeripheralErrorToast")) + }) + } + // Add the peripheral to the project function addPeripheral(peripheral){ // Add the peripheral to the project (backend) @@ -94,8 +104,8 @@

{$_("projectHardwareOthersLabel")}

- addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} - on:dblclick={() => addPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + createPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + on:dblclick={() => createPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} status="PERIPHERAL_CONNECTED" title={"OS2L virtual device"} type={"OS2L"} location={""} addable={true}/> diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go index c2b8e25..d0ce37a 100644 --- a/hardware/FTDIFinder.go +++ b/hardware/FTDIFinder.go @@ -2,7 +2,6 @@ package hardware import ( "context" - "errors" "fmt" goRuntime "runtime" "sync" @@ -10,7 +9,6 @@ import ( "unsafe" "github.com/rs/zerolog/log" - "github.com/wailsapp/wails/v2/pkg/runtime" ) /* @@ -25,13 +23,12 @@ type FTDIFinder struct { wg sync.WaitGroup mu sync.Mutex - saved map[string]PeripheralInfo // Peripherals saved in the project detected map[string]*FTDIPeripheral // Detected peripherals scanEvery time.Duration // Scans peripherals periodically - onArrival func(p PeripheralInfo) // When a peripheral arrives - onRemoval func(p PeripheralInfo) // When a peripheral goes away + onArrival func(context.Context, Peripheral) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewFTDIFinder creates a new FTDI finder @@ -39,79 +36,20 @@ func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder { log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created") return &FTDIFinder{ scanEvery: scanEvery, - saved: make(map[string]PeripheralInfo), detected: make(map[string]*FTDIPeripheral), } } // OnArrival is the callback function when a new peripheral arrives -func (f *FTDIFinder) OnArrival(cb func(p PeripheralInfo)) { +func (f *FTDIFinder) OnArrival(cb func(context.Context, Peripheral)) { f.onArrival = cb } // OnRemoval i the callback when a peripheral goes away -func (f *FTDIFinder) OnRemoval(cb func(p PeripheralInfo)) { +func (f *FTDIFinder) OnRemoval(cb func(context.Context, Peripheral)) { f.onRemoval = cb } -// RegisterPeripheral registers a new peripheral -func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { - f.mu.Lock() - defer f.mu.Unlock() - - f.saved[peripheralData.SerialNumber] = peripheralData - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) - - // If already detected, connect it - if peripheral, ok := f.detected[peripheralData.SerialNumber]; ok { - f.wg.Add(1) - go func() { - defer f.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 - runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) - - return peripheralData.SerialNumber, nil -} - -// UnregisterPeripheral unregisters an existing peripheral -func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error { - f.mu.Lock() - defer f.mu.Unlock() - - if peripheral, detected := f.detected[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(f.saved, peripheralData.SerialNumber) - runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) - - return nil -} - // Initialize initializes the FTDI finder func (f *FTDIFinder) Initialize() error { // Check platform @@ -123,6 +61,16 @@ func (f *FTDIFinder) Initialize() error { return nil } +// Create creates a new peripheral, based on the peripheral information (manually created) +func (f *FTDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { + return "", nil +} + +// Remove removes an existing peripheral (manually created) +func (f *FTDIFinder) Remove(ctx context.Context, peripheral Peripheral) error { + return nil +} + // Start starts the finder and search for peripherals func (f *FTDIFinder) Start(ctx context.Context) error { f.wg.Add(1) @@ -147,45 +95,11 @@ func (f *FTDIFinder) Start(ctx context.Context) error { return nil } -// ForceScan explicitly asks for scanning peripherals -func (f *FTDIFinder) ForceScan() { - // select { - // case f.scanChannel <- struct{}{}: - // default: - // // Ignore if the channel is full or if it is closed - // } -} - // GetName returns the name of the driver func (f *FTDIFinder) GetName() string { return "FTDI" } -// GetPeripheralSettings gets the peripheral settings -func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) { - // Return the specified peripheral - peripheral, found := f.detected[peripheralID] - if !found { - // FTDI not detected, return the last settings saved - if savedPeripheral, isFound := f.saved[peripheralID]; isFound { - return savedPeripheral.Settings, nil - } - return nil, fmt.Errorf("unable to found the peripheral") - } - return peripheral.GetSettings(), nil -} - -// SetPeripheralSettings sets the peripheral settings -func (f *FTDIFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error { - // Return the specified peripheral - peripheral, found := f.detected[peripheralID] - if !found { - return fmt.Errorf("unable to found the FTDI peripheral") - } - log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder") - return peripheral.SetSettings(settings) -} - // scanPeripherals scans the FTDI peripherals func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { log.Trace().Str("file", "FTDIFinder").Msg("FTDI scan triggered") @@ -227,56 +141,27 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { // Detect arrivals for sn, peripheralData := range currentMap { + // If the scanned peripheral isn't in the detected list, create it if _, known := f.detected[sn]; !known { + peripheral := NewFTDIPeripheral(peripheralData) - f.detected[sn] = peripheral if f.onArrival != nil { - f.onArrival(peripheralData) - } - log.Info().Str("sn", sn).Str("name", peripheralData.Name).Msg("[FTDI] New peripheral detected") - - // If the peripheral is saved in the project => connect - if _, saved := f.saved[sn]; saved { - f.wg.Add(1) - go func(p PeripheralInfo) { - defer f.wg.Done() - err := peripheral.Connect(ctx) - if err != nil { - log.Err(err).Str("sn", p.SerialNumber).Msg("unable to connect the FTDI peripheral") - return - } - err = peripheral.Activate(ctx) - if err != nil { - log.Err(err).Str("sn", p.SerialNumber).Msg("unable to activate the FTDI peripheral") - return - } - }(peripheralData) + f.onArrival(ctx, peripheral) } } } // Detect removals - for sn, oldPeripheral := range f.detected { - if _, still := currentMap[sn]; !still { - - // Properly clean the DMX device - err := oldPeripheral.Deactivate(ctx) - if err != nil { - log.Err(err).Str("sn", sn).Msg("unable to deactivate the FTDI peripheral after disconnection") - } - err = oldPeripheral.Disconnect(ctx) - if err != nil { - log.Err(err).Str("sn", sn).Msg("unable to disconnect the FTDI peripheral after disconnection") - } + for detectedSN, detectedPeripheral := range f.detected { + if _, still := currentMap[detectedSN]; !still { // Delete it from the detected list - delete(f.detected, sn) - log.Info().Str("sn", sn).Str("name", oldPeripheral.GetInfo().Name).Msg("[FTDI] peripheral removed") + delete(f.detected, detectedSN) // Execute the removal callback if f.onRemoval != nil { - f.onRemoval(oldPeripheral.GetInfo()) + f.onRemoval(ctx, detectedPeripheral) } } } @@ -287,26 +172,9 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { func (f *FTDIFinder) WaitStop() error { log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...") - // 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.detected { - 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 } diff --git a/hardware/FTDIPeripheral.go b/hardware/FTDIPeripheral.go index ac9a088..b205698 100644 --- a/hardware/FTDIPeripheral.go +++ b/hardware/FTDIPeripheral.go @@ -129,7 +129,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 { +func (p *FTDIPeripheral) SetSettings(ctx context.Context, settings map[string]any) error { return errors.Errorf("unable to set the settings: not implemented") } diff --git a/hardware/MIDIFinder.go b/hardware/MIDIFinder.go index d062ef2..ec3929b 100644 --- a/hardware/MIDIFinder.go +++ b/hardware/MIDIFinder.go @@ -2,7 +2,6 @@ package hardware import ( "context" - "errors" "fmt" "regexp" "strconv" @@ -11,7 +10,6 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/wailsapp/wails/v2/pkg/runtime" "gitlab.com/gomidi/rtmididrv" ) @@ -20,13 +18,12 @@ type MIDIFinder struct { wg sync.WaitGroup mu sync.Mutex - saved map[string]PeripheralInfo // Peripherals saved in the project detected map[string]*MIDIPeripheral // Detected peripherals scanEvery time.Duration // Scans peripherals periodically - onArrival func(p PeripheralInfo) // When a peripheral arrives - onRemoval func(p PeripheralInfo) // When a peripheral goes away + onArrival func(context.Context, Peripheral) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewMIDIFinder creates a new MIDI finder @@ -34,18 +31,17 @@ func NewMIDIFinder(scanEvery time.Duration) *MIDIFinder { log.Trace().Str("file", "MIDIFinder").Msg("MIDI finder created") return &MIDIFinder{ scanEvery: scanEvery, - saved: make(map[string]PeripheralInfo), detected: make(map[string]*MIDIPeripheral), } } // OnArrival is the callback function when a new peripheral arrives -func (f *MIDIFinder) OnArrival(cb func(p PeripheralInfo)) { +func (f *MIDIFinder) OnArrival(cb func(context.Context, Peripheral)) { f.onArrival = cb } // OnRemoval i the callback when a peripheral goes away -func (f *MIDIFinder) OnRemoval(cb func(p PeripheralInfo)) { +func (f *MIDIFinder) OnRemoval(cb func(context.Context, Peripheral)) { f.onRemoval = cb } @@ -55,59 +51,13 @@ func (f *MIDIFinder) Initialize() error { return nil } -// RegisterPeripheral registers a new peripheral -func (f *MIDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { - f.mu.Lock() - defer f.mu.Unlock() - - f.saved[peripheralData.SerialNumber] = peripheralData - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) - - // If already detected, connect it - if peripheral, ok := f.detected[peripheralData.SerialNumber]; ok { - f.wg.Add(1) - go func() { - defer f.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 - runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) - return peripheralData.SerialNumber, nil +// Create creates a new peripheral, based on the peripheral information (manually created) +func (f *MIDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { + return "", nil } -// UnregisterPeripheral unregisters an existing peripheral -func (f *MIDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error { - f.mu.Lock() - defer f.mu.Unlock() - - if peripheral, detected := f.detected[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(f.saved, peripheralData.SerialNumber) - runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) +// Remove removes an existing peripheral (manually created) +func (f *MIDIFinder) Remove(ctx context.Context, peripheral Peripheral) error { return nil } @@ -139,26 +89,9 @@ func (f *MIDIFinder) Start(ctx context.Context) error { func (f *MIDIFinder) WaitStop() error { log.Trace().Str("file", "MIDIFinder").Msg("stopping the MIDI finder...") - // Close the channel - // close(f.scanChannel) - - // Wait for all the peripherals to close - log.Trace().Str("file", "MIDIFinder").Msg("closing all MIDI peripherals") - var errs []error - for registeredPeripheralSN, registeredPeripheral := range f.detected { - 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", "MIDIFinder").Msg("MIDI finder stopped") return nil } @@ -168,31 +101,6 @@ func (f *MIDIFinder) GetName() string { return "MIDI" } -// GetPeripheralSettings gets the peripheral settings -func (f *MIDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) { - // Return the specified peripheral - peripheral, found := f.detected[peripheralID] - if !found { - // FTDI not detected, return the last settings saved - if savedPeripheral, isFound := f.saved[peripheralID]; isFound { - return savedPeripheral.Settings, nil - } - return nil, fmt.Errorf("unable to found the peripheral") - } - return peripheral.GetSettings(), nil -} - -// SetPeripheralSettings sets the peripheral settings -func (f *MIDIFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]interface{}) error { - // Return the specified peripheral - peripheral, found := f.detected[peripheralID] - if !found { - return fmt.Errorf("unable to found the MIDI peripheral") - } - log.Debug().Str("file", "MIDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the MIDI finder") - return peripheral.SetSettings(settings) -} - func splitStringAndNumber(input string) (string, int, error) { // Regular expression to match the text part and the number at the end re := regexp.MustCompile(`^(.*?)(\d+)$`) @@ -214,11 +122,6 @@ func splitStringAndNumber(input string) (string, int, error) { return "", 0, fmt.Errorf("no number found at the end of the string") } -// ForceScan explicily asks for scanning peripherals -func (f *MIDIFinder) ForceScan() { - // f.scanChannel <- struct{}{} -} - // scanPeripherals scans the MIDI peripherals func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { currentMap := make(map[string]*MIDIPeripheral) @@ -298,26 +201,7 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { f.detected[sn] = peripheral if f.onArrival != nil { - f.onArrival(discovery.GetInfo()) - } - log.Info().Str("sn", sn).Str("name", discovery.GetInfo().SerialNumber).Msg("[MIDI] New peripheral detected") - - // If the peripheral is saved in the project => connect - if _, saved := f.saved[sn]; saved { - f.wg.Add(1) - go func(p PeripheralInfo) { - defer f.wg.Done() - err := peripheral.Connect(ctx) - if err != nil { - log.Err(err).Str("sn", p.SerialNumber).Msg("unable to connect the MIDI peripheral") - return - } - err = peripheral.Activate(ctx) - if err != nil { - log.Err(err).Str("sn", p.SerialNumber).Msg("unable to activate the MIDI peripheral") - return - } - }(discovery.GetInfo()) + f.onArrival(ctx, discovery) } } } @@ -326,24 +210,15 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { for sn, oldPeripheral := range f.detected { if _, still := currentMap[sn]; !still { - // Properly clean the DMX device - err := oldPeripheral.Deactivate(ctx) - if err != nil { - log.Err(err).Str("sn", sn).Msg("unable to deactivate the MIDI peripheral after disconnection") - } - err = oldPeripheral.Disconnect(ctx) - if err != nil { - log.Err(err).Str("sn", sn).Msg("unable to disconnect the MIDI peripheral after disconnection") - } - - // Delete it from the detected list - delete(f.detected, sn) log.Info().Str("sn", sn).Str("name", oldPeripheral.GetInfo().Name).Msg("[MIDI] peripheral removed") // Execute the removal callback if f.onRemoval != nil { - f.onRemoval(oldPeripheral.GetInfo()) + f.onRemoval(ctx, oldPeripheral) } + + // Delete it from the detected list + delete(f.detected, sn) } } return nil @@ -370,17 +245,3 @@ func normalizeName(raw string) string { return strings.Join(parts, " ") } - -// func normalizeName(name string) string { -// // Cas communs : "MIDIIN2 (XXX)" → "XXX" -// if strings.Contains(name, "(") && strings.Contains(name, ")") { -// start := strings.Index(name, "(") -// end := strings.Index(name, ")") -// if start < end { -// return strings.TrimSpace(name[start+1 : end]) -// } -// } - -// // Sinon, on retourne tel quel -// return strings.TrimSpace(name) -// } diff --git a/hardware/MIDIPeripheral.go b/hardware/MIDIPeripheral.go index 09d8243..2a5b248 100644 --- a/hardware/MIDIPeripheral.go +++ b/hardware/MIDIPeripheral.go @@ -100,13 +100,13 @@ func (p *MIDIPeripheral) Deactivate(ctx context.Context) error { } // SetSettings sets a specific setting for this peripheral -func (p *MIDIPeripheral) SetSettings(settings map[string]interface{}) error { +func (p *MIDIPeripheral) SetSettings(ctx context.Context, settings map[string]any) error { p.settings = settings return nil } // SetDeviceProperty - not implemented for this kind of peripheral -func (p *MIDIPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error { +func (p *MIDIPeripheral) SetDeviceProperty(context.Context, uint32, byte) error { return nil } diff --git a/hardware/OS2LFinder.go b/hardware/OS2LFinder.go index d4c0684..a1ae9f9 100644 --- a/hardware/OS2LFinder.go +++ b/hardware/OS2LFinder.go @@ -2,7 +2,6 @@ package hardware import ( "context" - "errors" "fmt" "math/rand" "strings" @@ -17,17 +16,17 @@ type OS2LFinder struct { wg sync.WaitGroup mu sync.Mutex - saved map[string]*OS2LPeripheral // The list of saved peripherals + detected map[string]*OS2LPeripheral // The list of saved peripherals - onArrival func(p PeripheralInfo) // When a peripheral arrives - onRemoval func(p PeripheralInfo) // When a peripheral goes away + onArrival func(context.Context, Peripheral) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewOS2LFinder creates a new OS2L finder func NewOS2LFinder() *OS2LFinder { log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder created") return &OS2LFinder{ - saved: make(map[string]*OS2LPeripheral), + detected: make(map[string]*OS2LPeripheral), } } @@ -38,91 +37,47 @@ func (f *OS2LFinder) Initialize() error { } // OnArrival is the callback function when a new peripheral arrives -func (f *OS2LFinder) OnArrival(cb func(p PeripheralInfo)) { +func (f *OS2LFinder) OnArrival(cb func(context.Context, Peripheral)) { f.onArrival = cb } // OnRemoval i the callback when a peripheral goes away -func (f *OS2LFinder) OnRemoval(cb func(p PeripheralInfo)) { +func (f *OS2LFinder) OnRemoval(cb func(context.Context, Peripheral)) { f.onRemoval = cb } -// RegisterPeripheral registers a new peripheral -func (f *OS2LFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { - f.mu.Lock() - defer f.mu.Unlock() - +// Create creates a new peripheral, based on the peripheral information (manually created) +func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { // If the SerialNumber is empty, generate another one - if peripheralData.SerialNumber == "" { - peripheralData.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32))) + if peripheralInfo.SerialNumber == "" { + peripheralInfo.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32))) } // Create a new OS2L peripheral - peripheral, err := NewOS2LPeripheral(peripheralData) + peripheral, err := NewOS2LPeripheral(peripheralInfo) if err != nil { return "", fmt.Errorf("unable to create the OS2L peripheral: %w", err) } + // Set the event callback peripheral.SetEventCallback(func(event any) { - runtime.EventsEmit(ctx, string(PeripheralEventEmitted), peripheralData.SerialNumber, event) + runtime.EventsEmit(ctx, string(PeripheralEventEmitted), peripheralInfo.SerialNumber, event) }) - f.saved[peripheralData.SerialNumber] = peripheral - log.Trace().Str("file", "OS2LFinder").Str("serialNumber", peripheralData.SerialNumber).Msg("OS2L peripheral created") + f.detected[peripheralInfo.SerialNumber] = peripheral - // New OS2L peripheral has arrived if f.onArrival != nil { - f.onArrival(peripheral.GetInfo()) + f.onArrival(ctx, peripheral) } - - f.wg.Add(1) - go func() { - defer f.wg.Done() - // Connect the OS2L peripheral - err = peripheral.Connect(ctx) - if err != nil { - log.Err(err).Str("file", "OS2LFinder").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", "OS2LFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the OS2L peripheral") - return - } - }() - - // Emits the event in the hardware - runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) - - return peripheralData.SerialNumber, nil + return peripheralInfo.SerialNumber, err } -// UnregisterPeripheral unregisters an existing peripheral -func (f *OS2LFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error { - f.mu.Lock() - defer f.mu.Unlock() - - if peripheral, detected := f.saved[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 - } +// Remove removes an existing peripheral (manually created) +func (f *OS2LFinder) Remove(ctx context.Context, peripheral Peripheral) error { + if f.onRemoval != nil { + f.onRemoval(ctx, peripheral) } - - // The OS2L peripheral has gone - f.onRemoval(peripheralData) - - delete(f.saved, peripheralData.SerialNumber) - runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) + delete(f.detected, peripheral.GetInfo().SerialNumber) return nil } @@ -131,29 +86,6 @@ func (f *OS2LFinder) GetName() string { return "OS2L" } -// GetPeripheralSettings gets the peripheral settings -func (f *OS2LFinder) GetPeripheralSettings(peripheralID string) (map[string]any, error) { - // Return the specified peripheral - peripheral, found := f.saved[peripheralID] - if !found { - log.Error().Str("file", "OS2LFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the OS2L finder") - return nil, fmt.Errorf("unable to found the peripheral") - } - return peripheral.GetSettings(), nil -} - -// SetPeripheralSettings sets the peripheral settings -func (f *OS2LFinder) SetPeripheralSettings(ctx context.Context, peripheralID string, settings map[string]any) error { - // Return the specified peripheral - peripheral, found := f.saved[peripheralID] - if !found { - return fmt.Errorf("unable to found the FTDI peripheral") - } - - // Set the peripheral settings - return peripheral.SetSettings(ctx, settings) -} - // Start starts the finder func (f *OS2LFinder) Start(ctx context.Context) error { // No peripherals to scan here @@ -164,30 +96,9 @@ func (f *OS2LFinder) Start(ctx context.Context) error { func (f *OS2LFinder) WaitStop() error { log.Trace().Str("file", "OS2LFinder").Msg("stopping the OS2L finder...") - // Close the channel - // close(f.scanChannel) - - // Wait for all the peripherals to close - log.Trace().Str("file", "OS2LFinder").Msg("closing all OS2L peripherals") - var errs []error - for registeredPeripheralSN, registeredPeripheral := range f.saved { - err := registeredPeripheral.WaitStop() - if err != nil { - errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err)) - } - } - // Waiting internal tasks f.wg.Wait() - // Returning errors - if len(errs) > 0 { - return errors.Join(errs...) - } log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder stopped") return nil } - -// ForceScan scans the interfaces (not implemented) -func (f *OS2LFinder) ForceScan() { -} diff --git a/hardware/OS2LPeripheral.go b/hardware/OS2LPeripheral.go index ee17343..887e0dc 100644 --- a/hardware/OS2LPeripheral.go +++ b/hardware/OS2LPeripheral.go @@ -260,7 +260,7 @@ func (p *OS2LPeripheral) SetSettings(ctx context.Context, settings map[string]an } // SetDeviceProperty - not implemented for this kind of peripheral -func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error { +func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, byte) error { return nil } diff --git a/hardware/hardware.go b/hardware/hardware.go index d581306..e7ded7f 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -38,68 +38,188 @@ const ( // Manager is the class who manages the hardware type Manager struct { + mu sync.Mutex 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 + 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 } // NewManager creates a new hardware manager func NewManager() *Manager { log.Trace().Str("package", "hardware").Msg("Hardware instance created") return &Manager{ - finders: make(map[string]PeripheralFinder), - peripherals: make([]*Peripheral, 0), - peripheralsScanTrigger: make(chan struct{}), + finders: make(map[string]PeripheralFinder), + peripherals: make(map[string]Peripheral, 0), + savedPeripherals: make(map[string]PeripheralInfo, 0), } } +// 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 + runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) + + 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) + runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) + + 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) +} + // Start starts to find new peripheral events func (h *Manager) Start(ctx context.Context) error { - // Initialize all the finders and their callback functions + + // Register all the finders to use as hardware scanners h.RegisterFinder(NewFTDIFinder(3 * time.Second)) h.RegisterFinder(NewOS2LFinder()) h.RegisterFinder(NewMIDIFinder(3 * time.Second)) for finderName, finder := range h.finders { + + // Initialize the finder err := finder.Initialize() if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder") return err } - finder.OnArrival(func(p PeripheralInfo) { - runtime.EventsEmit(ctx, string(PeripheralArrival), p) - }) - finder.OnRemoval(func(p PeripheralInfo) { - runtime.EventsEmit(ctx, string(PeripheralRemoval), p) - }) + + // Set callback functions + finder.OnArrival(h.OnPeripheralArrival) + finder.OnRemoval(h.OnPeripheralRemoval) + + // Start the finder err = finder.Start(ctx) if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder") return err } } - - // Periodically scan all the finders - h.wg.Add(1) - go func() { - defer h.wg.Done() - for { - select { - case <-ctx.Done(): - return - case <-h.peripheralsScanTrigger: - for finderName, finder := range h.finders { - log.Trace().Str("file", "hardware").Str("finderName", finderName).Msg("force a finder to scan peripherals") - finder.ForceScan() - } - } - } - }() return nil } +// 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") + return + } + 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") + } + 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()) +} + // GetFinder returns a register finder func (h *Manager) GetFinder(finderName string) (PeripheralFinder, error) { finder, exists := h.finders[finderName] @@ -117,23 +237,10 @@ func (h *Manager) RegisterFinder(finder PeripheralFinder) { log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered") } -// Scan scans all the peripherals for the registered finders -func (h *Manager) Scan() error { - select { - case h.peripheralsScanTrigger <- struct{}{}: - return nil - default: - return fmt.Errorf("scan trigger not available (manager stopped?)") - } -} - // WaitStop stops the hardware manager func (h *Manager) 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 { @@ -142,6 +249,15 @@ func (h *Manager) WaitStop() error { } } + // 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)) + } + } + // Wait for goroutines to finish h.wg.Wait() diff --git a/hardware/interfaces.go b/hardware/interfaces.go index dc8308d..e630adf 100644 --- a/hardware/interfaces.go +++ b/hardware/interfaces.go @@ -19,13 +19,13 @@ type Device interface { // Peripheral represents the methods used to manage a peripheral (input or output hardware) type Peripheral interface { - Connect(context.Context) error // Connect the peripheral - SetEventCallback(func(any)) // Callback is called when an event is emitted from the peripheral - Disconnect(context.Context) error // Disconnect the peripheral - Activate(context.Context) error // Activate the peripheral - Deactivate(context.Context) error // Deactivate the peripheral - AddDevice(Device) error // Add a device to the peripheral - RemoveDevice(Device) error // Remove a device to the peripheral + Connect(context.Context) error // Connect the peripheral + // SetEventCallback(func(any)) // Callback is called when an event is emitted from the peripheral + Disconnect(context.Context) error // Disconnect the peripheral + Activate(context.Context) error // Activate the peripheral + Deactivate(context.Context) error // Deactivate the peripheral + // AddDevice(Device) error // Add a device to the peripheral + // RemoveDevice(Device) error // Remove a device to the peripheral GetSettings() map[string]any // Get the peripheral settings SetSettings(context.Context, map[string]any) error // Set a peripheral setting SetDeviceProperty(context.Context, uint32, byte) error // Update a device property @@ -45,15 +45,11 @@ type PeripheralInfo struct { // PeripheralFinder represents how compatible peripheral drivers are implemented type PeripheralFinder interface { - Initialize() error // Initializes the protocol - OnArrival(cb func(p PeripheralInfo)) // Callback function when a peripheral arrives - OnRemoval(cb func(p PeripheralInfo)) // Callback function when a peripheral goes away - Start(context.Context) error // Start 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, PeripheralInfo) error // Unregisters an existing peripheral - GetPeripheralSettings(string) (map[string]any, error) // Gets the peripheral settings - SetPeripheralSettings(context.Context, string, map[string]any) error // Sets the peripheral settings - GetName() string // Get the name of the finder + Initialize() error // Initializes the protocol + Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) // Manually create a peripheral + OnArrival(cb func(context.Context, Peripheral)) // Callback function when a peripheral arrives + OnRemoval(cb func(context.Context, Peripheral)) // Callback function when a peripheral goes away + Start(context.Context) error // Start the detection + WaitStop() error // Waiting for finder to close + GetName() string // Get the name of the finder } diff --git a/peripherals.go b/peripherals.go index f9fb409..b9ad7f6 100644 --- a/peripherals.go +++ b/peripherals.go @@ -9,14 +9,8 @@ import ( // AddPeripheral adds a peripheral to the project func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, error) { - // Get the peripheral from its finder - f, err := a.hardwareManager.GetFinder(peripheralData.ProtocolName) - if err != nil { - log.Error().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Msg("unable to found the specified finder") - return "", fmt.Errorf("unable to found the peripheral ID '%s'", peripheralData.SerialNumber) - } // Register this new peripheral - serialNumber, err := f.RegisterPeripheral(a.ctx, peripheralData) + serialNumber, err := a.hardwareManager.RegisterPeripheral(a.ctx, peripheralData) if err != nil { return "", fmt.Errorf("unable to register the peripheral '%s': %w", serialNumber, err) } @@ -35,25 +29,24 @@ func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, err return peripheralData.SerialNumber, nil } -// GetPeripheralSettings gets the peripheral settings -func (a *App) GetPeripheralSettings(protocolName, peripheralID string) (map[string]any, error) { - // Get the peripheral from its finder - f, err := a.hardwareManager.GetFinder(protocolName) +// CreatePeripheral creates a new peripheral +func (a *App) CreatePeripheral(peripheralData hardware.PeripheralInfo) (string, error) { + // Get the appropriate finder to create the device + finder, err := a.hardwareManager.GetFinder(peripheralData.ProtocolName) if err != nil { - log.Error().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("unable to found the specified peripheral") - return nil, fmt.Errorf("unable to found the peripheral ID '%s'", peripheralID) + return "", fmt.Errorf("unable to find the appropriate finder (%s): %w", peripheralData.ProtocolName, err) } - return f.GetPeripheralSettings(peripheralID) + // Manually create the peripheral from its finder + return finder.Create(a.ctx, peripheralData) +} + +// GetPeripheralSettings gets the peripheral settings +func (a *App) GetPeripheralSettings(protocolName, peripheralSN string) (map[string]any, error) { + return a.hardwareManager.GetPeripheralSettings(peripheralSN) } // UpdatePeripheralSettings updates a specific setting of a peripheral func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settings map[string]any) error { - // Sets the settings with the finder - f, err := a.hardwareManager.GetFinder(protocolName) - if err != nil { - log.Error().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("unable to found the specified peripheral") - return fmt.Errorf("unable to found the peripheral ID '%s'", peripheralID) - } // Save the settings in the application if a.projectInfo.PeripheralsInfo == nil { a.projectInfo.PeripheralsInfo = make(map[string]hardware.PeripheralInfo) @@ -62,65 +55,18 @@ func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settin pInfo.Settings = settings a.projectInfo.PeripheralsInfo[peripheralID] = pInfo // Apply changes in the peripheral - return f.SetPeripheralSettings(a.ctx, peripheralID, pInfo.Settings) + return a.hardwareManager.SetPeripheralSettings(a.ctx, peripheralID, pInfo.Settings) } // RemovePeripheral removes a peripheral from the project func (a *App) RemovePeripheral(peripheralData hardware.PeripheralInfo) error { // Unregister the peripheral from the finder - f, err := a.hardwareManager.GetFinder(peripheralData.ProtocolName) + err := a.hardwareManager.UnregisterPeripheral(a.ctx, peripheralData) if err != nil { - log.Err(err).Str("file", "peripherals").Str("protocolName", peripheralData.ProtocolName).Msg("unable to find the finder") - return fmt.Errorf("unable to find the finder") - } - err = f.UnregisterPeripheral(a.ctx, peripheralData) - if err != nil { - log.Err(err).Str("file", "peripherals").Str("peripheralID", peripheralData.SerialNumber).Msg("unable to unregister this peripheral") - return fmt.Errorf("unable to unregister this peripheral") + return fmt.Errorf("unable to unregister this peripheral: %w", err) } // Remove the peripheral ID from the project delete(a.projectInfo.PeripheralsInfo, peripheralData.SerialNumber) log.Info().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", peripheralData.SerialNumber).Msg("peripheral removed from project") return nil } - -// FOR TESTING PURPOSE ONLY - -// func (a *App) ActivateFTDI() error { -// // Connect the FTDI -// driver, err := a.hardwareManager.GetFinder("FTDI") -// if err != nil { -// return err -// } -// periph, found := driver.GetPeripheral("A50285BI") -// if !found { -// return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") -// } -// return periph.Activate(a.ctx) -// } - -// func (a *App) SetDeviceFTDI(channelValue byte) error { -// // Connect the FTDI -// driver, err := a.hardwareManager.GetFinder("FTDI") -// if err != nil { -// return err -// } -// periph, found := driver.GetPeripheral("A50285BI") -// if !found { -// return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") -// } -// return periph.SetDeviceProperty(a.ctx, 0, 0, channelValue) -// } - -// func (a *App) DeactivateFTDI() error { -// // Connect the FTDI -// driver, err := a.hardwareManager.GetFinder("FTDI") -// if err != nil { -// return err -// } -// periph, found := driver.GetPeripheral("A50285BI") -// if !found { -// return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI") -// } -// return periph.Deactivate(a.ctx) -// } diff --git a/project.go b/project.go index 9b9954d..be227d2 100644 --- a/project.go +++ b/project.go @@ -130,11 +130,7 @@ func (a *App) OpenProject(projectInfo ProjectInfo) error { // Load all peripherals of the project projectPeripherals := a.projectInfo.PeripheralsInfo for key, value := range projectPeripherals { - hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName) - if err != nil { - return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err) - } - _, err = hostFinder.RegisterPeripheral(a.ctx, value) + _, err = a.hardwareManager.RegisterPeripheral(a.ctx, value) if err != nil { return fmt.Errorf("unable to register the peripheral S/N '%s': %w", key, err) } @@ -147,11 +143,7 @@ func (a *App) CloseCurrentProject() error { // Unregistrer all peripherals of the project projectPeripherals := a.projectInfo.PeripheralsInfo for key, value := range projectPeripherals { - hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName) - if err != nil { - return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err) - } - err = hostFinder.UnregisterPeripheral(a.ctx, value) + err := a.hardwareManager.UnregisterPeripheral(a.ctx, value) if err != nil { return fmt.Errorf("unable to unregister the peripheral S/N '%s': %w", key, err) } -- 2.49.1 From 2dc6f4a38afabaef2621a63a4d7157ace14636a0 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Sat, 29 Nov 2025 18:01:34 +0100 Subject: [PATCH 05/12] load and unload syntax --- frontend/jsconfig.json | 38 ---------------------------------- frontend/src/runtime-events.js | 8 +++---- hardware/hardware.go | 8 +++++-- 3 files changed, 10 insertions(+), 44 deletions(-) delete mode 100644 frontend/jsconfig.json diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json deleted file mode 100644 index 3918b4f..0000000 --- a/frontend/jsconfig.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "compilerOptions": { - "moduleResolution": "Node", - "target": "ESNext", - "module": "ESNext", - /** - * svelte-preprocess cannot figure out whether you have - * a value or a type, so tell TypeScript to enforce using - * `import type` instead of `import` for Types. - */ - "importsNotUsedAsValues": "error", - "isolatedModules": true, - "resolveJsonModule": true, - /** - * To have warnings / errors of the Svelte compiler at the - * correct position, enable source maps by default. - */ - "sourceMap": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "baseUrl": ".", - /** - * Typecheck JS in `.svelte` and `.js` files by default. - * Disable this if you'd like to use dynamic types. - */ - "checkJs": true - }, - /** - * Use global.d.ts instead of compilerOptions.types - * to avoid limiting type declarations. - */ - "include": [ - "src/**/*.d.ts", - "src/**/*.js", - "src/**/*.svelte" - ] -} diff --git a/frontend/src/runtime-events.js b/frontend/src/runtime-events.js index d509a83..b67dfb8 100644 --- a/frontend/src/runtime-events.js +++ b/frontend/src/runtime-events.js @@ -165,10 +165,10 @@ export function initRuntimeEvents(){ EventsOn('LOAD_PROJECT', loadProject) // Handle a peripheral loaded in the project - EventsOn('LOAD_PERIPHERAL', loadPeripheral) + EventsOn('PERIPHERAL_LOAD', loadPeripheral) // Handle a peripheral unloaded from the project - EventsOn('UNLOAD_PERIPHERAL', unloadPeripheral) + EventsOn('PERIPHERAL_UNLOAD', unloadPeripheral) // Handle a peripheral event EventsOn('PERIPHERAL_EVENT_EMITTED', onPeripheralEvent) @@ -191,10 +191,10 @@ export function destroyRuntimeEvents(){ EventsOff('LOAD_PROJECT') // Handle a peripheral loaded in the project - EventsOff('LOAD_PERIPHERAL') + EventsOff('PERIPHERAL_LOAD') // Handle a peripheral unloaded from the project - EventsOff('UNLOAD_PERIPHERAL') + EventsOff('PERIPHERAL_UNLOAD') // Handle a peripheral event EventsOff('PERIPHERAL_EVENT_EMITTED') diff --git a/hardware/hardware.go b/hardware/hardware.go index e7ded7f..641e682 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -22,6 +22,10 @@ const ( PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL" // PeripheralRemoval is triggered when a peripheral has been disconnected from the system PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL" + // 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" // PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - deactivated - activated) PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS" // PeripheralEventEmitted is triggered when a peripheral event is emitted @@ -85,7 +89,7 @@ func (h *Manager) RegisterPeripheral(ctx context.Context, peripheralData Periphe } // Emits the event in the hardware - runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) + runtime.EventsEmit(ctx, string(PeripheralLoad), peripheralData) return peripheralData.SerialNumber, nil } @@ -110,7 +114,7 @@ func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralData Perip } } delete(h.savedPeripherals, peripheralData.SerialNumber) - runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData) + runtime.EventsEmit(ctx, string(PeripheralUnload), peripheralData) return nil } -- 2.49.1 From d1fda9f0759732f5b3af1a4f2145cd294b736788 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Sat, 29 Nov 2025 20:06:04 +0100 Subject: [PATCH 06/12] resolved created peripheral --- .../Settings/InputsOutputsContent.svelte | 4 +- frontend/src/lang/en.json | 1 + hardware/FTDIFinder.go | 12 ++-- hardware/MIDIFinder.go | 12 ++-- hardware/OS2LFinder.go | 21 +++--- hardware/hardware.go | 72 ++++++++++++++++++- hardware/interfaces.go | 15 ++-- peripherals.go | 17 ++--- 8 files changed, 112 insertions(+), 42 deletions(-) diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index 591be60..578aed7 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -7,9 +7,9 @@ import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral, CreatePeripheral } from "../../../wailsjs/go/main/App"; import RoundedButton from "../General/RoundedButton.svelte"; - // Create a new peripheral + // Create the peripheral to the project function createPeripheral(peripheral){ - // Add the peripheral to the project (backend) + // Create the peripheral to the project (backend) CreatePeripheral(peripheral) .catch((error) => { console.log("Unable to create the peripheral: " + error) diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json index f153022..9ea4f79 100644 --- a/frontend/src/lang/en.json +++ b/frontend/src/lang/en.json @@ -48,6 +48,7 @@ "projectSavedToast": "The project has been saved", "projectSaveErrorToast": "Unable to save the project:", "addPeripheralErrorToast": "Unable to add this peripheral to project", + "createPeripheralErrorToast": "Unable to create this peripheral", "removePeripheralErrorToast": "Unable to remove this peripheral from project", "os2lPeripheralCreatedToast": "Your OS2L peripheral has been created", "os2lPeripheralCreateErrorToast": "Unable to create the OS2L peripheral", diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go index d0ce37a..aa0e20e 100644 --- a/hardware/FTDIFinder.go +++ b/hardware/FTDIFinder.go @@ -27,8 +27,8 @@ type FTDIFinder struct { scanEvery time.Duration // Scans peripherals periodically - onArrival func(context.Context, Peripheral) // When a peripheral arrives - onRemoval func(context.Context, Peripheral) // When a peripheral goes away + onArrival func(context.Context, Peripheral, bool) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewFTDIFinder creates a new FTDI finder @@ -41,7 +41,7 @@ func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder { } // OnArrival is the callback function when a new peripheral arrives -func (f *FTDIFinder) OnArrival(cb func(context.Context, Peripheral)) { +func (f *FTDIFinder) OnArrival(cb func(context.Context, Peripheral, bool)) { f.onArrival = cb } @@ -62,8 +62,8 @@ func (f *FTDIFinder) Initialize() error { } // Create creates a new peripheral, based on the peripheral information (manually created) -func (f *FTDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { - return "", nil +func (f *FTDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) error { + return nil } // Remove removes an existing peripheral (manually created) @@ -147,7 +147,7 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { peripheral := NewFTDIPeripheral(peripheralData) if f.onArrival != nil { - f.onArrival(ctx, peripheral) + f.onArrival(ctx, peripheral, false) } } } diff --git a/hardware/MIDIFinder.go b/hardware/MIDIFinder.go index ec3929b..4a1e2f0 100644 --- a/hardware/MIDIFinder.go +++ b/hardware/MIDIFinder.go @@ -22,8 +22,8 @@ type MIDIFinder struct { scanEvery time.Duration // Scans peripherals periodically - onArrival func(context.Context, Peripheral) // When a peripheral arrives - onRemoval func(context.Context, Peripheral) // When a peripheral goes away + onArrival func(context.Context, Peripheral, bool) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewMIDIFinder creates a new MIDI finder @@ -36,7 +36,7 @@ func NewMIDIFinder(scanEvery time.Duration) *MIDIFinder { } // OnArrival is the callback function when a new peripheral arrives -func (f *MIDIFinder) OnArrival(cb func(context.Context, Peripheral)) { +func (f *MIDIFinder) OnArrival(cb func(context.Context, Peripheral, bool)) { f.onArrival = cb } @@ -52,8 +52,8 @@ func (f *MIDIFinder) Initialize() error { } // Create creates a new peripheral, based on the peripheral information (manually created) -func (f *MIDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { - return "", nil +func (f *MIDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) error { + return nil } // Remove removes an existing peripheral (manually created) @@ -201,7 +201,7 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { f.detected[sn] = peripheral if f.onArrival != nil { - f.onArrival(ctx, discovery) + f.onArrival(ctx, discovery, false) } } } diff --git a/hardware/OS2LFinder.go b/hardware/OS2LFinder.go index a1ae9f9..bca9819 100644 --- a/hardware/OS2LFinder.go +++ b/hardware/OS2LFinder.go @@ -18,26 +18,31 @@ type OS2LFinder struct { detected map[string]*OS2LPeripheral // The list of saved peripherals - onArrival func(context.Context, Peripheral) // When a peripheral arrives - onRemoval func(context.Context, Peripheral) // When a peripheral goes away + eventBus EventBus // The hardware event bus + + onArrival func(context.Context, Peripheral, bool) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewOS2LFinder creates a new OS2L finder -func NewOS2LFinder() *OS2LFinder { +func NewOS2LFinder(eventBus EventBus) *OS2LFinder { log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder created") return &OS2LFinder{ detected: make(map[string]*OS2LPeripheral), + eventBus: eventBus, } } // Initialize initializes the finder func (f *OS2LFinder) Initialize() error { + // Subscribe to all OS2L peripherals that are added to the project + f.eventBus.SubscribeType(f.GetName(), f) log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder initialized") return nil } // OnArrival is the callback function when a new peripheral arrives -func (f *OS2LFinder) OnArrival(cb func(context.Context, Peripheral)) { +func (f *OS2LFinder) OnArrival(cb func(context.Context, Peripheral, bool)) { f.onArrival = cb } @@ -47,7 +52,7 @@ func (f *OS2LFinder) OnRemoval(cb func(context.Context, Peripheral)) { } // Create creates a new peripheral, based on the peripheral information (manually created) -func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { +func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) error { // If the SerialNumber is empty, generate another one if peripheralInfo.SerialNumber == "" { peripheralInfo.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32))) @@ -56,7 +61,7 @@ func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) // Create a new OS2L peripheral peripheral, err := NewOS2LPeripheral(peripheralInfo) if err != nil { - return "", fmt.Errorf("unable to create the OS2L peripheral: %w", err) + return fmt.Errorf("unable to create the OS2L peripheral: %w", err) } // Set the event callback @@ -67,9 +72,9 @@ func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) f.detected[peripheralInfo.SerialNumber] = peripheral if f.onArrival != nil { - f.onArrival(ctx, peripheral) + f.onArrival(ctx, peripheral, true) // Ask to register the peripheral in the project } - return peripheralInfo.SerialNumber, err + return err } // Remove removes an existing peripheral (manually created) diff --git a/hardware/hardware.go b/hardware/hardware.go index 641e682..ffeb290 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -40,6 +40,51 @@ const ( PeripheralStatusActivated PeripheralStatus = "PERIPHERAL_ACTIVATED" ) +// EventBus handles events between hardware and finders +type EventBus struct { + register map[string][]PeripheralFinder // Register is a map[peripheralType (string)][]Finder +} + +// SubscribeType is called from finders that want to subscribe for a peripheral loaded in project +func (b *EventBus) SubscribeType(protocolName string, finder PeripheralFinder) { + // Check if we need to initialize the slice (key not found) + if _, found := b.register[protocolName]; !found { + b.register[protocolName] = make([]PeripheralFinder, 0) + } + // Add the finder to the list of subscription + b.register[protocolName] = append(b.register[protocolName], finder) +} + +// EmitSavedPeripheral emits a peripheral loaded event for all the finders suscribed to the peripheral type +func (b *EventBus) EmitSavedPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { + finders, found := b.register[peripheralInfo.ProtocolName] + if !found { + return "", nil + } + for _, finder := range finders { + err := finder.Create(ctx, peripheralInfo) + if err != nil { + log.Err(err).Str("finder", finder.GetName()).Str("S/N", peripheralInfo.SerialNumber).Msg("unable to create the peripheral") + } + } + return "", nil +} + +// EmitUnsavedPeripheral emits a peripheral unloaded event for all the finders suscribed to the peripheral type +func (b *EventBus) EmitUnsavedPeripheral(ctx context.Context, peripheral Peripheral) (string, error) { + finders, found := b.register[peripheral.GetInfo().ProtocolName] + if !found { + return "", nil + } + for _, finder := range finders { + err := finder.Remove(ctx, peripheral) + if err != nil { + log.Err(err).Str("finder", finder.GetName()).Str("S/N", peripheral.GetInfo().SerialNumber).Msg("unable to remove the peripheral") + } + } + return "", nil +} + // Manager is the class who manages the hardware type Manager struct { mu sync.Mutex @@ -48,6 +93,8 @@ type Manager struct { 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 + + eventBus EventBus // The hardware event bus } // NewManager creates a new hardware manager @@ -57,14 +104,28 @@ func NewManager() *Manager { finders: make(map[string]PeripheralFinder), peripherals: make(map[string]Peripheral, 0), savedPeripherals: make(map[string]PeripheralInfo, 0), + eventBus: EventBus{ + make(map[string][]PeripheralFinder), + }, } } +// CreatePeripheral asks the providers to create a new peripheral +func (h *Manager) CreatePeripheral(ctx context.Context, peripheralInfo PeripheralInfo) { + // Emit an event to the hardware event bus + h.eventBus.EmitSavedPeripheral(ctx, peripheralInfo) +} + // RegisterPeripheral registers a new peripheral func (h *Manager) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { h.mu.Lock() defer h.mu.Unlock() + // Do not save if the peripheral doesn't have a S/N + if peripheralData.SerialNumber == "" { + return "", nil + } + h.savedPeripherals[peripheralData.SerialNumber] = peripheralData runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) @@ -112,6 +173,8 @@ func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralData Perip log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral") return nil } + // Emit the unload event + h.eventBus.EmitUnsavedPeripheral(ctx, peripheral) } delete(h.savedPeripherals, peripheralData.SerialNumber) runtime.EventsEmit(ctx, string(PeripheralUnload), peripheralData) @@ -147,7 +210,7 @@ func (h *Manager) Start(ctx context.Context) error { // Register all the finders to use as hardware scanners h.RegisterFinder(NewFTDIFinder(3 * time.Second)) - h.RegisterFinder(NewOS2LFinder()) + h.RegisterFinder(NewOS2LFinder(h.eventBus)) h.RegisterFinder(NewMIDIFinder(3 * time.Second)) for finderName, finder := range h.finders { @@ -174,7 +237,12 @@ func (h *Manager) Start(ctx context.Context) error { } // OnPeripheralArrival is called when a peripheral arrives in the system -func (h *Manager) OnPeripheralArrival(ctx context.Context, peripheral Peripheral) { +func (h *Manager) OnPeripheralArrival(ctx context.Context, peripheral Peripheral, needSave bool) { + // Save the peripheral in the project if needed + if needSave { + h.RegisterPeripheral(ctx, peripheral.GetInfo()) + } + // Add the peripheral to the detected hardware h.peripherals[peripheral.GetInfo().SerialNumber] = peripheral diff --git a/hardware/interfaces.go b/hardware/interfaces.go index e630adf..e66cf65 100644 --- a/hardware/interfaces.go +++ b/hardware/interfaces.go @@ -45,11 +45,12 @@ type PeripheralInfo struct { // PeripheralFinder represents how compatible peripheral drivers are implemented type PeripheralFinder interface { - Initialize() error // Initializes the protocol - Create(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) // Manually create a peripheral - OnArrival(cb func(context.Context, Peripheral)) // Callback function when a peripheral arrives - OnRemoval(cb func(context.Context, Peripheral)) // Callback function when a peripheral goes away - Start(context.Context) error // Start the detection - WaitStop() error // Waiting for finder to close - GetName() string // Get the name of the finder + Initialize() error // Initializes the protocol + Create(ctx context.Context, peripheralInfo PeripheralInfo) error // Manually create a peripheral + Remove(ctx context.Context, peripheral Peripheral) error // Manually remove a peripheral + OnArrival(cb func(context.Context, Peripheral, bool)) // Callback function when a peripheral arrives + OnRemoval(cb func(context.Context, Peripheral)) // Callback function when a peripheral goes away + Start(context.Context) error // Start the detection + WaitStop() error // Waiting for finder to close + GetName() string // Get the name of the finder } diff --git a/peripherals.go b/peripherals.go index b9ad7f6..b14179d 100644 --- a/peripherals.go +++ b/peripherals.go @@ -7,6 +7,12 @@ import ( "github.com/rs/zerolog/log" ) +// CreatePeripheral creates a new peripheral from the hardware manager +func (a *App) CreatePeripheral(peripheralData hardware.PeripheralInfo) (string, error) { + a.hardwareManager.CreatePeripheral(a.ctx, peripheralData) + return "", nil +} + // AddPeripheral adds a peripheral to the project func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, error) { // Register this new peripheral @@ -29,17 +35,6 @@ func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, err return peripheralData.SerialNumber, nil } -// CreatePeripheral creates a new peripheral -func (a *App) CreatePeripheral(peripheralData hardware.PeripheralInfo) (string, error) { - // Get the appropriate finder to create the device - finder, err := a.hardwareManager.GetFinder(peripheralData.ProtocolName) - if err != nil { - return "", fmt.Errorf("unable to find the appropriate finder (%s): %w", peripheralData.ProtocolName, err) - } - // Manually create the peripheral from its finder - return finder.Create(a.ctx, peripheralData) -} - // GetPeripheralSettings gets the peripheral settings func (a *App) GetPeripheralSettings(protocolName, peripheralSN string) (map[string]any, error) { return a.hardwareManager.GetPeripheralSettings(peripheralSN) -- 2.49.1 From 7f60f7b8d7ae10ab5c23ee473338731fe9dee999 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Sun, 30 Nov 2025 18:33:00 +0100 Subject: [PATCH 07/12] fixed: create peripheral --- .../Settings/InputsOutputsContent.svelte | 4 +- hardware/FTDIFinder.go | 12 +- hardware/MIDIFinder.go | 12 +- hardware/OS2LFinder.go | 21 +-- hardware/hardware.go | 143 ++++++------------ hardware/interfaces.go | 16 +- peripherals.go | 6 - project.go | 7 +- 8 files changed, 83 insertions(+), 138 deletions(-) diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index 578aed7..7ecb948 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -4,13 +4,13 @@ import { t, _ } from 'svelte-i18n' import { generateToast, needProjectSave, peripherals, colors } from "../../stores"; import { get } from "svelte/store" - import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral, CreatePeripheral } from "../../../wailsjs/go/main/App"; + import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral } from "../../../wailsjs/go/main/App"; import RoundedButton from "../General/RoundedButton.svelte"; // Create the peripheral to the project function createPeripheral(peripheral){ // Create the peripheral to the project (backend) - CreatePeripheral(peripheral) + AddPeripheral(peripheral) .catch((error) => { console.log("Unable to create the peripheral: " + error) generateToast('danger', 'bx-error', $_("addPeripheralErrorToast")) diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go index aa0e20e..728e5d1 100644 --- a/hardware/FTDIFinder.go +++ b/hardware/FTDIFinder.go @@ -27,8 +27,8 @@ type FTDIFinder struct { scanEvery time.Duration // Scans peripherals periodically - onArrival func(context.Context, Peripheral, bool) // When a peripheral arrives - onRemoval func(context.Context, Peripheral) // When a peripheral goes away + onArrival func(context.Context, Peripheral) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewFTDIFinder creates a new FTDI finder @@ -41,7 +41,7 @@ func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder { } // OnArrival is the callback function when a new peripheral arrives -func (f *FTDIFinder) OnArrival(cb func(context.Context, Peripheral, bool)) { +func (f *FTDIFinder) OnArrival(cb func(context.Context, Peripheral)) { f.onArrival = cb } @@ -62,8 +62,8 @@ func (f *FTDIFinder) Initialize() error { } // Create creates a new peripheral, based on the peripheral information (manually created) -func (f *FTDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) error { - return nil +func (f *FTDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) { + return PeripheralInfo{}, nil } // Remove removes an existing peripheral (manually created) @@ -147,7 +147,7 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error { peripheral := NewFTDIPeripheral(peripheralData) if f.onArrival != nil { - f.onArrival(ctx, peripheral, false) + f.onArrival(ctx, peripheral) } } } diff --git a/hardware/MIDIFinder.go b/hardware/MIDIFinder.go index 4a1e2f0..705d8af 100644 --- a/hardware/MIDIFinder.go +++ b/hardware/MIDIFinder.go @@ -22,8 +22,8 @@ type MIDIFinder struct { scanEvery time.Duration // Scans peripherals periodically - onArrival func(context.Context, Peripheral, bool) // When a peripheral arrives - onRemoval func(context.Context, Peripheral) // When a peripheral goes away + onArrival func(context.Context, Peripheral) // When a peripheral arrives + onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewMIDIFinder creates a new MIDI finder @@ -36,7 +36,7 @@ func NewMIDIFinder(scanEvery time.Duration) *MIDIFinder { } // OnArrival is the callback function when a new peripheral arrives -func (f *MIDIFinder) OnArrival(cb func(context.Context, Peripheral, bool)) { +func (f *MIDIFinder) OnArrival(cb func(context.Context, Peripheral)) { f.onArrival = cb } @@ -52,8 +52,8 @@ func (f *MIDIFinder) Initialize() error { } // Create creates a new peripheral, based on the peripheral information (manually created) -func (f *MIDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) error { - return nil +func (f *MIDIFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) { + return PeripheralInfo{}, nil } // Remove removes an existing peripheral (manually created) @@ -201,7 +201,7 @@ func (f *MIDIFinder) scanPeripherals(ctx context.Context) error { f.detected[sn] = peripheral if f.onArrival != nil { - f.onArrival(ctx, discovery, false) + f.onArrival(ctx, discovery) } } } diff --git a/hardware/OS2LFinder.go b/hardware/OS2LFinder.go index bca9819..efbc12b 100644 --- a/hardware/OS2LFinder.go +++ b/hardware/OS2LFinder.go @@ -18,41 +18,36 @@ type OS2LFinder struct { detected map[string]*OS2LPeripheral // The list of saved peripherals - eventBus EventBus // The hardware event bus - - onArrival func(context.Context, Peripheral, bool) // When a peripheral arrives + onArrival func(context.Context, Peripheral) // When a peripheral arrives onRemoval func(context.Context, Peripheral) // When a peripheral goes away } // NewOS2LFinder creates a new OS2L finder -func NewOS2LFinder(eventBus EventBus) *OS2LFinder { +func NewOS2LFinder() *OS2LFinder { log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder created") return &OS2LFinder{ detected: make(map[string]*OS2LPeripheral), - eventBus: eventBus, } } // Initialize initializes the finder func (f *OS2LFinder) Initialize() error { - // Subscribe to all OS2L peripherals that are added to the project - f.eventBus.SubscribeType(f.GetName(), f) log.Trace().Str("file", "OS2LFinder").Msg("OS2L finder initialized") return nil } // OnArrival is the callback function when a new peripheral arrives -func (f *OS2LFinder) OnArrival(cb func(context.Context, Peripheral, bool)) { +func (f *OS2LFinder) OnArrival(cb func(context.Context, Peripheral)) { f.onArrival = cb } -// OnRemoval i the callback when a peripheral goes away +// OnRemoval if the callback when a peripheral goes away func (f *OS2LFinder) OnRemoval(cb func(context.Context, Peripheral)) { f.onRemoval = cb } // Create creates a new peripheral, based on the peripheral information (manually created) -func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) error { +func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) { // If the SerialNumber is empty, generate another one if peripheralInfo.SerialNumber == "" { peripheralInfo.SerialNumber = strings.ToUpper(fmt.Sprintf("%08x", rand.Intn(1<<32))) @@ -61,7 +56,7 @@ func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) // Create a new OS2L peripheral peripheral, err := NewOS2LPeripheral(peripheralInfo) if err != nil { - return fmt.Errorf("unable to create the OS2L peripheral: %w", err) + return PeripheralInfo{}, fmt.Errorf("unable to create the OS2L peripheral: %w", err) } // Set the event callback @@ -72,9 +67,9 @@ func (f *OS2LFinder) Create(ctx context.Context, peripheralInfo PeripheralInfo) f.detected[peripheralInfo.SerialNumber] = peripheral if f.onArrival != nil { - f.onArrival(ctx, peripheral, true) // Ask to register the peripheral in the project + f.onArrival(ctx, peripheral) // Ask to register the peripheral in the project } - return err + return peripheralInfo, err } // Remove removes an existing peripheral (manually created) diff --git a/hardware/hardware.go b/hardware/hardware.go index ffeb290..be3d195 100644 --- a/hardware/hardware.go +++ b/hardware/hardware.go @@ -40,144 +40,102 @@ const ( PeripheralStatusActivated PeripheralStatus = "PERIPHERAL_ACTIVATED" ) -// EventBus handles events between hardware and finders -type EventBus struct { - register map[string][]PeripheralFinder // Register is a map[peripheralType (string)][]Finder -} - -// SubscribeType is called from finders that want to subscribe for a peripheral loaded in project -func (b *EventBus) SubscribeType(protocolName string, finder PeripheralFinder) { - // Check if we need to initialize the slice (key not found) - if _, found := b.register[protocolName]; !found { - b.register[protocolName] = make([]PeripheralFinder, 0) - } - // Add the finder to the list of subscription - b.register[protocolName] = append(b.register[protocolName], finder) -} - -// EmitSavedPeripheral emits a peripheral loaded event for all the finders suscribed to the peripheral type -func (b *EventBus) EmitSavedPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { - finders, found := b.register[peripheralInfo.ProtocolName] - if !found { - return "", nil - } - for _, finder := range finders { - err := finder.Create(ctx, peripheralInfo) - if err != nil { - log.Err(err).Str("finder", finder.GetName()).Str("S/N", peripheralInfo.SerialNumber).Msg("unable to create the peripheral") - } - } - return "", nil -} - -// EmitUnsavedPeripheral emits a peripheral unloaded event for all the finders suscribed to the peripheral type -func (b *EventBus) EmitUnsavedPeripheral(ctx context.Context, peripheral Peripheral) (string, error) { - finders, found := b.register[peripheral.GetInfo().ProtocolName] - if !found { - return "", nil - } - for _, finder := range finders { - err := finder.Remove(ctx, peripheral) - if err != nil { - log.Err(err).Str("finder", finder.GetName()).Str("S/N", peripheral.GetInfo().SerialNumber).Msg("unable to remove the peripheral") - } - } - return "", nil -} - // Manager is the class who manages the hardware type Manager struct { mu sync.Mutex wg sync.WaitGroup - 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 - - eventBus EventBus // The hardware event bus + finders map[string]PeripheralFinder // The map of peripherals finders + DetectedPeripherals map[string]Peripheral // The current list of peripherals + SavedPeripherals map[string]PeripheralInfo // The list of stored peripherals } // NewManager creates a new hardware manager func NewManager() *Manager { log.Trace().Str("package", "hardware").Msg("Hardware instance created") return &Manager{ - finders: make(map[string]PeripheralFinder), - peripherals: make(map[string]Peripheral, 0), - savedPeripherals: make(map[string]PeripheralInfo, 0), - eventBus: EventBus{ - make(map[string][]PeripheralFinder), - }, + finders: make(map[string]PeripheralFinder), + DetectedPeripherals: make(map[string]Peripheral, 0), + SavedPeripherals: make(map[string]PeripheralInfo, 0), } } -// CreatePeripheral asks the providers to create a new peripheral -func (h *Manager) CreatePeripheral(ctx context.Context, peripheralInfo PeripheralInfo) { - // Emit an event to the hardware event bus - h.eventBus.EmitSavedPeripheral(ctx, peripheralInfo) -} - // RegisterPeripheral registers a new peripheral -func (h *Manager) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) { +func (h *Manager) RegisterPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) (string, error) { h.mu.Lock() defer h.mu.Unlock() - // Do not save if the peripheral doesn't have a S/N - if peripheralData.SerialNumber == "" { - return "", nil + // Create the peripheral from its finder (if needed) + if finder, found := h.finders[peripheralInfo.ProtocolName]; found { + var err error + peripheralInfo, err = finder.Create(ctx, peripheralInfo) + if err != nil { + return "", err + } } - h.savedPeripherals[peripheralData.SerialNumber] = peripheralData + // Do not save if the peripheral doesn't have a S/N + if peripheralInfo.SerialNumber == "" { + return "", fmt.Errorf("serial number is empty for this peripheral") + } - runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected) + h.SavedPeripherals[peripheralInfo.SerialNumber] = peripheralInfo + + runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralInfo, PeripheralStatusDisconnected) // If already detected, connect it - if peripheral, ok := h.peripherals[peripheralData.SerialNumber]; ok { + if peripheral, ok := h.DetectedPeripherals[peripheralInfo.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") + log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo.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") + log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralInfo.SerialNumber).Msg("unable to activate the FTDI peripheral") return } }() } // Emits the event in the hardware - runtime.EventsEmit(ctx, string(PeripheralLoad), peripheralData) + runtime.EventsEmit(ctx, string(PeripheralLoad), peripheralInfo) - return peripheralData.SerialNumber, nil + return peripheralInfo.SerialNumber, nil } // UnregisterPeripheral unregisters an existing peripheral -func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error { +func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralInfo PeripheralInfo) error { h.mu.Lock() defer h.mu.Unlock() - if peripheral, detected := h.peripherals[peripheralData.SerialNumber]; detected { + if peripheral, detected := h.DetectedPeripherals[peripheralInfo.SerialNumber]; detected { // Deactivating peripheral err := peripheral.Deactivate(ctx) if err != nil { - log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral") + log.Err(err).Str("sn", peripheralInfo.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") + log.Err(err).Str("sn", peripheralInfo.SerialNumber).Msg("unable to disconnect the peripheral") return nil } - // Emit the unload event - h.eventBus.EmitUnsavedPeripheral(ctx, peripheral) + // Remove the peripheral from its finder (if needed) + if finder, found := h.finders[peripheralInfo.ProtocolName]; found { + err = finder.Remove(ctx, peripheral) + if err != nil { + return err + } + } } - delete(h.savedPeripherals, peripheralData.SerialNumber) - runtime.EventsEmit(ctx, string(PeripheralUnload), peripheralData) + delete(h.SavedPeripherals, peripheralInfo.SerialNumber) + runtime.EventsEmit(ctx, string(PeripheralUnload), peripheralInfo) return nil } @@ -185,10 +143,10 @@ func (h *Manager) UnregisterPeripheral(ctx context.Context, peripheralData Perip // GetPeripheralSettings gets the peripheral settings func (h *Manager) GetPeripheralSettings(peripheralSN string) (map[string]any, error) { // Return the specified peripheral - peripheral, found := h.peripherals[peripheralSN] + peripheral, found := h.DetectedPeripherals[peripheralSN] if !found { // Peripheral not detected, return the last settings saved - if savedPeripheral, isFound := h.savedPeripherals[peripheralSN]; isFound { + if savedPeripheral, isFound := h.SavedPeripherals[peripheralSN]; isFound { return savedPeripheral.Settings, nil } return nil, fmt.Errorf("unable to found the peripheral") @@ -198,7 +156,7 @@ func (h *Manager) GetPeripheralSettings(peripheralSN string) (map[string]any, er // SetPeripheralSettings sets the peripheral settings func (h *Manager) SetPeripheralSettings(ctx context.Context, peripheralSN string, settings map[string]any) error { - peripheral, found := h.peripherals[peripheralSN] + peripheral, found := h.DetectedPeripherals[peripheralSN] if !found { return fmt.Errorf("unable to found the FTDI peripheral") } @@ -210,7 +168,7 @@ func (h *Manager) Start(ctx context.Context) error { // Register all the finders to use as hardware scanners h.RegisterFinder(NewFTDIFinder(3 * time.Second)) - h.RegisterFinder(NewOS2LFinder(h.eventBus)) + h.RegisterFinder(NewOS2LFinder()) h.RegisterFinder(NewMIDIFinder(3 * time.Second)) for finderName, finder := range h.finders { @@ -237,17 +195,12 @@ func (h *Manager) Start(ctx context.Context) error { } // OnPeripheralArrival is called when a peripheral arrives in the system -func (h *Manager) OnPeripheralArrival(ctx context.Context, peripheral Peripheral, needSave bool) { - // Save the peripheral in the project if needed - if needSave { - h.RegisterPeripheral(ctx, peripheral.GetInfo()) - } - +func (h *Manager) OnPeripheralArrival(ctx context.Context, peripheral Peripheral) { // Add the peripheral to the detected hardware - h.peripherals[peripheral.GetInfo().SerialNumber] = peripheral + h.DetectedPeripherals[peripheral.GetInfo().SerialNumber] = peripheral // If the peripheral is saved in the project, connect it - if _, saved := h.savedPeripherals[peripheral.GetInfo().SerialNumber]; saved { + if _, saved := h.SavedPeripherals[peripheral.GetInfo().SerialNumber]; saved { h.wg.Add(1) go func(p Peripheral) { defer h.wg.Done() @@ -286,7 +239,7 @@ func (h *Manager) OnPeripheralRemoval(ctx context.Context, peripheral Peripheral }(peripheral) // Remove the peripheral from the hardware - delete(h.peripherals, peripheral.GetInfo().SerialNumber) + delete(h.DetectedPeripherals, peripheral.GetInfo().SerialNumber) // TODO: Update the Peripheral reference in the corresponding devices runtime.EventsEmit(ctx, string(PeripheralRemoval), peripheral.GetInfo()) @@ -323,7 +276,7 @@ func (h *Manager) WaitStop() error { // Wait for all the peripherals to close log.Trace().Str("file", "MIDIFinder").Msg("closing all MIDI peripherals") - for registeredPeripheralSN, registeredPeripheral := range h.peripherals { + for registeredPeripheralSN, registeredPeripheral := range h.DetectedPeripherals { err := registeredPeripheral.WaitStop() if err != nil { errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err)) diff --git a/hardware/interfaces.go b/hardware/interfaces.go index e66cf65..a4bf9db 100644 --- a/hardware/interfaces.go +++ b/hardware/interfaces.go @@ -45,12 +45,12 @@ type PeripheralInfo struct { // PeripheralFinder represents how compatible peripheral drivers are implemented type PeripheralFinder interface { - Initialize() error // Initializes the protocol - Create(ctx context.Context, peripheralInfo PeripheralInfo) error // Manually create a peripheral - Remove(ctx context.Context, peripheral Peripheral) error // Manually remove a peripheral - OnArrival(cb func(context.Context, Peripheral, bool)) // Callback function when a peripheral arrives - OnRemoval(cb func(context.Context, Peripheral)) // Callback function when a peripheral goes away - Start(context.Context) error // Start the detection - WaitStop() error // Waiting for finder to close - GetName() string // Get the name of the finder + Initialize() error // Initializes the protocol + Create(ctx context.Context, peripheralInfo PeripheralInfo) (PeripheralInfo, error) // Manually create a peripheral + Remove(ctx context.Context, peripheral Peripheral) error // Manually remove a peripheral + OnArrival(cb func(context.Context, Peripheral)) // Callback function when a peripheral arrives + OnRemoval(cb func(context.Context, Peripheral)) // Callback function when a peripheral goes away + Start(context.Context) error // Start the detection + WaitStop() error // Waiting for finder to close + GetName() string // Get the name of the finder } diff --git a/peripherals.go b/peripherals.go index b14179d..05ff09c 100644 --- a/peripherals.go +++ b/peripherals.go @@ -7,12 +7,6 @@ import ( "github.com/rs/zerolog/log" ) -// CreatePeripheral creates a new peripheral from the hardware manager -func (a *App) CreatePeripheral(peripheralData hardware.PeripheralInfo) (string, error) { - a.hardwareManager.CreatePeripheral(a.ctx, peripheralData) - return "", nil -} - // AddPeripheral adds a peripheral to the project func (a *App) AddPeripheral(peripheralData hardware.PeripheralInfo) (string, error) { // Register this new peripheral diff --git a/project.go b/project.go index be227d2..7dcbb16 100644 --- a/project.go +++ b/project.go @@ -140,8 +140,8 @@ func (a *App) OpenProject(projectInfo ProjectInfo) error { // CloseCurrentProject closes the current project func (a *App) CloseCurrentProject() error { - // Unregistrer all peripherals of the project - projectPeripherals := a.projectInfo.PeripheralsInfo + // Unregister all peripherals of the project + projectPeripherals := a.hardwareManager.SavedPeripherals for key, value := range projectPeripherals { err := a.hardwareManager.UnregisterPeripheral(a.ctx, value) if err != nil { @@ -196,6 +196,9 @@ func (a *App) SaveProject() (string, error) { a.projectSave = fmt.Sprintf("%04d%02d%02d%02d%02d%02d%s", date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), projectExtension) log.Debug().Str("file", "project").Str("newProjectSave", a.projectSave).Msg("projectSave is null, getting a new one") } + // Get hardware info + a.projectInfo.PeripheralsInfo = a.hardwareManager.SavedPeripherals + // Marshal the project data, err := yaml.Marshal(a.projectInfo) if err != nil { log.Err(err).Str("file", "project").Any("projectInfo", a.projectInfo).Msg("unable to format the project information") -- 2.49.1 From 1c8607800a4aacb14a38cd598bc58bdfedd7d847 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Sun, 30 Nov 2025 18:57:20 +0100 Subject: [PATCH 08/12] renaming Finders and Peripherals to Providers and Endpoints --- app.go | 8 +- endpoints.go | 61 ++++ .../Settings/InputsOutputsContent.svelte | 128 ++++----- frontend/src/lang/en.json | 18 +- frontend/src/runtime-events.js | 156 +++++------ frontend/src/stores.js | 18 +- hardware/FTDIEndpoint.go | 171 ++++++++++++ hardware/FTDIFinder.go | 180 ------------ hardware/FTDIPeripheral.go | 171 ------------ hardware/FTDIProvider.go | 180 ++++++++++++ hardware/MIDIEndpoint.go | 129 +++++++++ hardware/MIDIPeripheral.go | 129 --------- hardware/{MIDIFinder.go => MIDIProvider.go} | 94 +++---- .../{OS2LPeripheral.go => OS2LEndpoint.go} | 96 +++---- hardware/OS2LFinder.go | 104 ------- hardware/OS2LProvider.go | 104 +++++++ hardware/cpp/include/detectFTDIBridge.h | 8 +- hardware/cpp/src/detectFTDI.cpp | 22 +- hardware/cpp/src/detectFTDI.h | 6 +- hardware/cpp/src/dmxSender.h | 2 +- hardware/cpp/test/detectFTDI_test.cpp | 8 +- hardware/hardware.go | 262 +++++++++--------- hardware/interfaces.go | 60 ++-- peripherals.go | 61 ---- project.go | 28 +- 25 files changed, 1102 insertions(+), 1102 deletions(-) create mode 100644 endpoints.go create mode 100644 hardware/FTDIEndpoint.go delete mode 100644 hardware/FTDIFinder.go delete mode 100644 hardware/FTDIPeripheral.go create mode 100644 hardware/FTDIProvider.go create mode 100644 hardware/MIDIEndpoint.go delete mode 100644 hardware/MIDIPeripheral.go rename hardware/{MIDIFinder.go => MIDIProvider.go} (59%) rename hardware/{OS2LPeripheral.go => OS2LEndpoint.go} (61%) delete mode 100644 hardware/OS2LFinder.go create mode 100644 hardware/OS2LProvider.go delete mode 100644 peripherals.go diff --git a/app.go b/app.go index 2eb3264..33abf17 100644 --- a/app.go +++ b/app.go @@ -33,7 +33,7 @@ func NewApp() *App { hardwareManager: hardwareManager, projectSave: "", projectInfo: ProjectInfo{ - PeripheralsInfo: make(map[string]hardware.PeripheralInfo), + EndpointsInfo: make(map[string]hardware.EndpointInfo), }, } } @@ -56,12 +56,12 @@ func (a *App) onStartup(ctx context.Context) { } // onReady is called when the DOM is ready -// We get the current peripherals connected +// We get the current endpoints connected func (a *App) onReady(ctx context.Context) { - // log.Debug().Str("file", "peripherals").Msg("getting peripherals...") + // log.Debug().Str("file", "endpoints").Msg("getting endpoints...") // err := a.hardwareManager.Scan() // if err != nil { - // log.Err(err).Str("file", "app").Msg("unable to get the peripherals") + // log.Err(err).Str("file", "app").Msg("unable to get the endpoints") // } return } diff --git a/endpoints.go b/endpoints.go new file mode 100644 index 0000000..cb71a35 --- /dev/null +++ b/endpoints.go @@ -0,0 +1,61 @@ +package main + +import ( + "dmxconnect/hardware" + "fmt" + + "github.com/rs/zerolog/log" +) + +// AddEndpoint adds a endpoint to the project +func (a *App) AddEndpoint(endpointData hardware.EndpointInfo) (string, error) { + // Register this new endpoint + serialNumber, err := a.hardwareManager.RegisterEndpoint(a.ctx, endpointData) + if err != nil { + return "", fmt.Errorf("unable to register the endpoint '%s': %w", serialNumber, err) + } + log.Trace().Str("file", "endpoint").Str("protocolName", endpointData.ProtocolName).Str("periphID", serialNumber).Msg("device registered to the provider") + + // Rewrite the serialnumber for virtual devices + endpointData.SerialNumber = serialNumber + + // Add the endpoint ID to the project + if a.projectInfo.EndpointsInfo == nil { + a.projectInfo.EndpointsInfo = make(map[string]hardware.EndpointInfo) + } + + a.projectInfo.EndpointsInfo[endpointData.SerialNumber] = endpointData + log.Info().Str("file", "endpoint").Str("protocolName", endpointData.ProtocolName).Str("periphID", endpointData.SerialNumber).Msg("endpoint added to project") + return endpointData.SerialNumber, nil +} + +// GetEndpointSettings gets the endpoint settings +func (a *App) GetEndpointSettings(protocolName, endpointSN string) (map[string]any, error) { + return a.hardwareManager.GetEndpointSettings(endpointSN) +} + +// UpdateEndpointSettings updates a specific setting of a endpoint +func (a *App) UpdateEndpointSettings(protocolName, endpointID string, settings map[string]any) error { + // Save the settings in the application + if a.projectInfo.EndpointsInfo == nil { + a.projectInfo.EndpointsInfo = make(map[string]hardware.EndpointInfo) + } + pInfo := a.projectInfo.EndpointsInfo[endpointID] + pInfo.Settings = settings + a.projectInfo.EndpointsInfo[endpointID] = pInfo + // Apply changes in the endpoint + return a.hardwareManager.SetEndpointSettings(a.ctx, endpointID, pInfo.Settings) +} + +// RemoveEndpoint removes a endpoint from the project +func (a *App) RemoveEndpoint(endpointData hardware.EndpointInfo) error { + // Unregister the endpoint from the provider + err := a.hardwareManager.UnregisterEndpoint(a.ctx, endpointData) + if err != nil { + return fmt.Errorf("unable to unregister this endpoint: %w", err) + } + // Remove the endpoint ID from the project + delete(a.projectInfo.EndpointsInfo, endpointData.SerialNumber) + log.Info().Str("file", "endpoint").Str("protocolName", endpointData.ProtocolName).Str("periphID", endpointData.SerialNumber).Msg("endpoint removed from project") + return nil +} diff --git a/frontend/src/components/Settings/InputsOutputsContent.svelte b/frontend/src/components/Settings/InputsOutputsContent.svelte index 7ecb948..282393f 100644 --- a/frontend/src/components/Settings/InputsOutputsContent.svelte +++ b/frontend/src/components/Settings/InputsOutputsContent.svelte @@ -2,87 +2,87 @@ import DeviceCard from "./DeviceCard.svelte"; import Input from "../General/Input.svelte"; import { t, _ } from 'svelte-i18n' - import { generateToast, needProjectSave, peripherals, colors } from "../../stores"; + import { generateToast, needProjectSave, endpoints, colors } from "../../stores"; import { get } from "svelte/store" - import { UpdatePeripheralSettings, GetPeripheralSettings, RemovePeripheral, AddPeripheral } from "../../../wailsjs/go/main/App"; + import { UpdateEndpointSettings, GetEndpointSettings, RemoveEndpoint, AddEndpoint } from "../../../wailsjs/go/main/App"; import RoundedButton from "../General/RoundedButton.svelte"; - // Create the peripheral to the project - function createPeripheral(peripheral){ - // Create the peripheral to the project (backend) - AddPeripheral(peripheral) + // Create the endpoint to the project + function createEndpoint(endpoint){ + // Create the endpoint to the project (backend) + AddEndpoint(endpoint) .catch((error) => { - console.log("Unable to create the peripheral: " + error) - generateToast('danger', 'bx-error', $_("addPeripheralErrorToast")) + console.log("Unable to create the endpoint: " + error) + generateToast('danger', 'bx-error', $_("addEndpointErrorToast")) }) } - // Add the peripheral to the project - function addPeripheral(peripheral){ - // Add the peripheral to the project (backend) - AddPeripheral(peripheral) + // Add the endpoint to the project + function addEndpoint(endpoint){ + // Add the endpoint to the project (backend) + AddEndpoint(endpoint) .catch((error) => { - console.log("Unable to add the peripheral to the project: " + error) - generateToast('danger', 'bx-error', $_("addPeripheralErrorToast")) + console.log("Unable to add the endpoint to the project: " + error) + generateToast('danger', 'bx-error', $_("addEndpointErrorToast")) }) } - // Remove the peripheral from the project - function removePeripheral(peripheral) { - // Delete the peripheral from the project (backend) - RemovePeripheral(peripheral) + // Remove the endpoint from the project + function removeEndpoint(endpoint) { + // Delete the endpoint from the project (backend) + RemoveEndpoint(endpoint) .catch((error) => { - console.log("Unable to remove the peripheral from the project: " + error) - generateToast('danger', 'bx-error', $_("removePeripheralErrorToast")) + console.log("Unable to remove the endpoint from the project: " + error) + generateToast('danger', 'bx-error', $_("removeEndpointErrorToast")) }) } - // Select the peripheral to edit its settings - let selectedPeripheralSN = null - let selectedPeripheralSettings = {} - function selectPeripheral(peripheral){ - // Load the settings array if the peripheral is detected - if (peripheral.isSaved){ - GetPeripheralSettings(peripheral.ProtocolName, peripheral.SerialNumber).then((peripheralSettings) => { - selectedPeripheralSettings = peripheralSettings - // Select the current peripheral - selectedPeripheralSN = peripheral.SerialNumber + // Select the endpoint to edit its settings + let selectedEndpointSN = null + let selectedEndpointSettings = {} + function selectEndpoint(endpoint){ + // Load the settings array if the endpoint is detected + if (endpoint.isSaved){ + GetEndpointSettings(endpoint.ProtocolName, endpoint.SerialNumber).then((endpointSettings) => { + selectedEndpointSettings = endpointSettings + // Select the current endpoint + selectedEndpointSN = endpoint.SerialNumber }).catch((error) => { - console.log("Unable to get the peripheral settings: " + error) - generateToast('danger', 'bx-error', $_("getPeripheralSettingsErrorToast")) + console.log("Unable to get the endpoint settings: " + error) + generateToast('danger', 'bx-error', $_("getEndpointSettingsErrorToast")) }) } } - // Unselect the peripheral if it is disconnected + // Unselect the endpoint if it is disconnected $: { - Object.entries($peripherals).filter(([serialNumber, peripheral]) => { - if (!peripheral.isDetected && peripheral.isSaved && selectedPeripheralSN == serialNumber) { - selectedPeripheralSN = null - selectedPeripheralSettings = {} + Object.entries($endpoints).filter(([serialNumber, endpoint]) => { + if (!endpoint.isDetected && endpoint.isSaved && selectedEndpointSN == serialNumber) { + selectedEndpointSN = null + selectedEndpointSettings = {} } }); } - // Get the number of saved peripherals - $: savedPeripheralNumber = Object.values($peripherals).filter(peripheral => peripheral.isSaved).length; + // Get the number of saved endpoints + $: savedEndpointNumber = Object.values($endpoints).filter(endpoint => endpoint.isSaved).length; - // Validate the peripheral settings + // Validate the endpoint settings function validate(settingName, settingValue){ - console.log("Peripheral setting '" + settingName + "' set to '" + settingValue + "'") + console.log("Endpoint setting '" + settingName + "' set to '" + settingValue + "'") // Get the old setting type and convert the new setting to this type const convert = { number: Number, string: String, boolean: Boolean, - }[typeof(selectedPeripheralSettings[settingName])] || (x => x) - selectedPeripheralSettings[settingName] = convert(settingValue) - let peripheralProtocolName = get(peripherals)[selectedPeripheralSN].ProtocolName - UpdatePeripheralSettings(peripheralProtocolName, selectedPeripheralSN, selectedPeripheralSettings).then(()=> { + }[typeof(selectedEndpointSettings[settingName])] || (x => x) + selectedEndpointSettings[settingName] = convert(settingValue) + let endpointProtocolName = get(endpoints)[selectedEndpointSN].ProtocolName + UpdateEndpointSettings(endpointProtocolName, selectedEndpointSN, selectedEndpointSettings).then(()=> { $needProjectSave = true }).catch((error) => { - console.log("Unable to save the peripheral setting: " + error) - generateToast('danger', 'bx-error', $_("peripheralSettingSaveErrorToast")) + console.log("Unable to save the endpoint setting: " + error) + generateToast('danger', 'bx-error', $_("endpointSettingSaveErrorToast")) }) } @@ -92,31 +92,31 @@

{$_("projectHardwareDetectedLabel")}

- {#each Object.entries($peripherals) as [serialNumber, peripheral]} - {#if peripheral.isDetected} - addPeripheral(peripheral)} on:dblclick={() => { - if(!peripheral.isSaved) - addPeripheral(peripheral) + {#each Object.entries($endpoints) as [serialNumber, endpoint]} + {#if endpoint.isDetected} + addEndpoint(endpoint)} on:dblclick={() => { + if(!endpoint.isSaved) + addEndpoint(endpoint) }} - status="PERIPHERAL_CONNECTED" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/> + status="PERIPHERAL_CONNECTED" title={endpoint.Name} type={endpoint.ProtocolName} location={endpoint.Location ? endpoint.Location : ""} line1={"S/N: " + endpoint.SerialNumber} addable={!endpoint.isSaved}/> {/if} {/each}

{$_("projectHardwareOthersLabel")}

- createPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} - on:dblclick={() => createPeripheral({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + createEndpoint({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} + on:dblclick={() => createEndpoint({Name: "OS2L virtual device", ProtocolName: "OS2L", SerialNumber: ""})} status="PERIPHERAL_CONNECTED" title={"OS2L virtual device"} type={"OS2L"} location={""} addable={true}/>

{$_("projectHardwareSavedLabel")}

- {#if savedPeripheralNumber > 0} - {#each Object.entries($peripherals) as [serialNumber, peripheral]} - {#if peripheral.isSaved} - removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} on:click={() => selectPeripheral(peripheral)} - title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} selected={serialNumber == selectedPeripheralSN} removable signalizable signalized={peripheral.eventEmitted}/> + {#if savedEndpointNumber > 0} + {#each Object.entries($endpoints) as [serialNumber, endpoint]} + {#if endpoint.isSaved} + removeEndpoint(endpoint)} on:dblclick={() => removeEndpoint(endpoint)} on:click={() => selectEndpoint(endpoint)} + title={endpoint.Name} type={endpoint.ProtocolName} location={endpoint.Location ? endpoint.Location : ""} line1={endpoint.SerialNumber ? "S/N: " + endpoint.SerialNumber : ""} selected={serialNumber == selectedEndpointSN} removable signalizable signalized={endpoint.eventEmitted}/> {/if} {/each} {:else} @@ -124,9 +124,9 @@ {/if}
- {#if Object.keys(selectedPeripheralSettings).length > 0} - {#each Object.entries(selectedPeripheralSettings) as [settingName, settingValue]} -
+ {#if Object.keys(selectedEndpointSettings).length > 0} + {#each Object.entries(selectedEndpointSettings) as [settingName, settingValue]} +
validate(settingName, event.detail.target.value)} label={$t(settingName)} type="{typeof(settingValue)}" width='100%' value="{settingValue}"/>
{/each} @@ -136,7 +136,7 @@