generated from thinkode/modelRepository
fixed tooltip
This commit is contained in:
117
hardware/FTDIFinder.go
Normal file
117
hardware/FTDIFinder.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
goRuntime "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
scanDelay = 4 * time.Second // Waiting delay before scanning the FTDI devices
|
||||
)
|
||||
|
||||
// FTDIFinder represents how the protocol is defined
|
||||
type FTDIFinder struct {
|
||||
peripherals map[string]Peripheral
|
||||
}
|
||||
|
||||
// NewFTDIFinder creates a new FTDI finder
|
||||
func NewFTDIFinder() *FTDIFinder {
|
||||
return &FTDIFinder{
|
||||
peripherals: make(map[string]Peripheral),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize initializes the FTDI finder
|
||||
func (f *FTDIFinder) Initialize() error {
|
||||
if goRuntime.GOOS != "windows" {
|
||||
return fmt.Errorf("<!> The FTDI finder is not compatible with your platform yet (%s)", goRuntime.GOOS)
|
||||
}
|
||||
fmt.Println("FTDI finder initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the finder
|
||||
func (f *FTDIFinder) GetName() string {
|
||||
return "FTDI Finder"
|
||||
}
|
||||
|
||||
//go:embed third-party/ftdi/detectFTDI.exe
|
||||
var findFTDI []byte
|
||||
|
||||
// Scan scans the FTDI peripherals
|
||||
func (f *FTDIFinder) Scan(ctx context.Context) error {
|
||||
time.Sleep(scanDelay)
|
||||
|
||||
// Create a temporary file
|
||||
tempFile, err := os.CreateTemp("", "findFTDI*.exe")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// Write the embedded executable to the temp file
|
||||
if _, err := tempFile.Write(findFTDI); err != nil {
|
||||
return err
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
ftdiPeripherals := make(map[string]Peripheral)
|
||||
|
||||
finder := exec.Command(tempFile.Name())
|
||||
|
||||
stdout, err := finder.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create the stdout pipe: %s", err)
|
||||
}
|
||||
|
||||
stderr, err := finder.StderrPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create the stderr pipe: %s", err)
|
||||
}
|
||||
|
||||
err = finder.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find FTDI devices: %s", err)
|
||||
}
|
||||
|
||||
scannerErr := bufio.NewScanner(stderr)
|
||||
for scannerErr.Scan() {
|
||||
return fmt.Errorf("Unable to find FTDI devices: %s", scannerErr.Text())
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
deviceString := scanner.Text()
|
||||
// The program output is like '0:1:2' where 0 is the location, 1 is the S/N and 2 is the name
|
||||
deviceInfo := strings.Split(deviceString, ":")
|
||||
|
||||
fmt.Println("FTDI device: " + deviceString)
|
||||
// Convert the location to an integer
|
||||
location, err := strconv.Atoi(deviceInfo[0])
|
||||
if err != nil {
|
||||
location = -1
|
||||
}
|
||||
// Add the peripheral to the temporary list
|
||||
peripheral, err := NewFTDIPeripheral(deviceInfo[2], deviceInfo[1], location)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create the FTDI peripheral: %v", err)
|
||||
}
|
||||
ftdiPeripherals[deviceInfo[1]] = peripheral
|
||||
}
|
||||
// Compare with the current peripherals to detect arrivals/removals
|
||||
removedList, addedList := comparePeripherals(f.peripherals, ftdiPeripherals)
|
||||
// Emit the events
|
||||
emitPeripheralsEvents(ctx, removedList, PeripheralRemoval)
|
||||
emitPeripheralsEvents(ctx, addedList, PeripheralArrival)
|
||||
// Store the new peripherals list
|
||||
f.peripherals = ftdiPeripherals
|
||||
return nil
|
||||
}
|
||||
202
hardware/FTDIPeripheral.go
Normal file
202
hardware/FTDIPeripheral.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
activateCommandString = 0x01
|
||||
deactivateCommandString = 0x02
|
||||
setCommandString = 0x03
|
||||
)
|
||||
|
||||
//go:embed third-party/ftdi/dmxSender.exe
|
||||
var dmxSender []byte
|
||||
|
||||
// FTDIPeripheral contains the data of an FTDI peripheral
|
||||
type FTDIPeripheral struct {
|
||||
name string // The name of the peripheral
|
||||
serialNumber string // The S/N of the FTDI peripheral
|
||||
location int // The location of the peripheral
|
||||
universesNumber int // The number of DMX universes handled by this peripheral
|
||||
programName string // The temp file name of the executable
|
||||
dmxSender *exec.Cmd // The command to pilot the DMX sender program
|
||||
stdin io.WriteCloser // For writing in the DMX sender
|
||||
stdout io.ReadCloser // For reading from the DMX sender
|
||||
stderr io.ReadCloser // For reading the errors
|
||||
disconnectChan chan struct{} // Channel to cancel the connection
|
||||
errorsChan chan error // Channel to get the errors
|
||||
wg sync.WaitGroup // Tasks management
|
||||
}
|
||||
|
||||
// NewFTDIPeripheral creates a new FTDI peripheral
|
||||
func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPeripheral, error) {
|
||||
// Create a temporary file
|
||||
tempFile, err := os.CreateTemp("", "dmxSender*.exe")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the embedded executable to the temp file
|
||||
if _, err := tempFile.Write(dmxSender); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempFile.Close()
|
||||
|
||||
return &FTDIPeripheral{
|
||||
name: name,
|
||||
programName: tempFile.Name(),
|
||||
serialNumber: serialNumber,
|
||||
location: location,
|
||||
universesNumber: 1,
|
||||
disconnectChan: make(chan struct{}),
|
||||
errorsChan: make(chan error, 1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Connect connects the FTDI peripheral
|
||||
func (p *FTDIPeripheral) Connect() error {
|
||||
// Connect if no connection is already running
|
||||
fmt.Println(p.dmxSender)
|
||||
if p.dmxSender == nil {
|
||||
// Executing the command
|
||||
p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location))
|
||||
|
||||
var err error
|
||||
p.stdout, err = p.dmxSender.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create the stdout pipe: %v", err)
|
||||
}
|
||||
|
||||
p.stdin, err = p.dmxSender.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create the stdin pipe: %v", err)
|
||||
}
|
||||
|
||||
p.stderr, err = p.dmxSender.StderrPipe()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create the stderr pipe: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(p.stderr)
|
||||
for scanner.Scan() {
|
||||
// Traitez chaque ligne lue depuis stderr
|
||||
fmt.Printf("Erreur : %s\n", scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Printf("Error reading from stderr: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
p.wg.Add(1)
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
err = p.dmxSender.Run()
|
||||
if err != nil {
|
||||
// Affiche l'erreur (cela inclut également les erreurs de retour du programme)
|
||||
fmt.Printf("Erreur lors de l'exécution : %v\n", err)
|
||||
|
||||
// Vérifie si l'erreur est liée au code de retour
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
// Récupère et affiche le code de retour
|
||||
fmt.Printf("Le programme s'est terminé avec le code : %d\n", exitError.ExitCode())
|
||||
fmt.Println(p.stderr)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Le programme s'est terminé avec succès.")
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the FTDI peripheral
|
||||
func (p *FTDIPeripheral) Disconnect() error {
|
||||
if p.dmxSender != nil {
|
||||
_, err := io.WriteString(p.stdin, string([]byte{0x04, 0x00, 0x00, 0x00}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to disconnect: %v", err)
|
||||
}
|
||||
p.stdin.Close()
|
||||
p.stdout.Close()
|
||||
p.dmxSender = nil
|
||||
err = os.Remove(p.programName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to delete the temporary file: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Unable to disconnect: not connected")
|
||||
// if p.dmxSender != nil {
|
||||
// err := p.dmxSender.Process.Kill()
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("Unable to disconnect: %v", err)
|
||||
// }
|
||||
// p.stdin.Close()
|
||||
// p.stdout.Close()
|
||||
// p.dmxSender = nil
|
||||
// err = os.Remove(p.programName)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("Unable to delete the temporary file: %v", err)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
// return fmt.Errorf("Unable to disconnect: not connected")
|
||||
}
|
||||
|
||||
// Activate activates the FTDI peripheral
|
||||
func (p *FTDIPeripheral) Activate() error {
|
||||
if p.dmxSender != nil {
|
||||
_, err := io.WriteString(p.stdin, string([]byte{0x01, 0x00, 0x00, 0x00}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to activate: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Unable to activate: not connected")
|
||||
}
|
||||
|
||||
// Deactivate deactivates the FTDI peripheral
|
||||
func (p *FTDIPeripheral) Deactivate() error {
|
||||
if p.dmxSender != nil {
|
||||
_, err := io.WriteString(p.stdin, string([]byte{0x02, 0x00, 0x00, 0x00}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to deactivate: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Unable to deactivate: not connected")
|
||||
}
|
||||
|
||||
// SetDeviceProperty sends a command to the specified device
|
||||
func (p *FTDIPeripheral) SetDeviceProperty(uint32, channelNumber uint32, channelValue byte) error {
|
||||
if p.dmxSender != nil {
|
||||
fmt.Println(channelValue)
|
||||
commandString := []byte{0x03, 0x01, 0x00, 0xff, 0x03, 0x02, 0x00, channelValue}
|
||||
_, err := io.WriteString(p.stdin, string(commandString))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to set device property: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Unable to set device property: not connected")
|
||||
}
|
||||
|
||||
// GetInfo gets all the peripheral information
|
||||
func (p *FTDIPeripheral) GetInfo() PeripheralInfo {
|
||||
return PeripheralInfo{
|
||||
Name: p.name,
|
||||
SerialNumber: p.serialNumber,
|
||||
ProtocolName: "FTDI",
|
||||
UniversesNumber: p.universesNumber,
|
||||
}
|
||||
}
|
||||
112
hardware/MIDIFinder.go
Normal file
112
hardware/MIDIFinder.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattrtaylor/go-rtmidi"
|
||||
)
|
||||
|
||||
/*
|
||||
DMXConnect
|
||||
DMXUSBProtocol.go
|
||||
|
||||
Copyright (c) Valentin Boulanger
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// MIDIFinder represents how the protocol is defined
|
||||
type MIDIFinder struct {
|
||||
peripherals map[string]Peripheral // The list of peripherals
|
||||
}
|
||||
|
||||
// NewMIDIFinder creates a new DMXUSB protocol
|
||||
func NewMIDIFinder() *MIDIFinder {
|
||||
return &MIDIFinder{
|
||||
peripherals: make(map[string]Peripheral),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize initializes the DMX-USB finder
|
||||
func (f *MIDIFinder) Initialize() error {
|
||||
fmt.Println("MIDI finder initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the finder
|
||||
func (f *MIDIFinder) GetName() string {
|
||||
return "MIDI"
|
||||
}
|
||||
|
||||
func splitStringAndNumber(input string) (string, int, error) {
|
||||
// Regular expression to match the text part and the number at the end
|
||||
re := regexp.MustCompile(`^(.*?)(\d+)$`)
|
||||
matches := re.FindStringSubmatch(input)
|
||||
|
||||
// Check if the regex found both a text part and a number
|
||||
if len(matches) == 3 {
|
||||
// matches[1]: text part (might contain trailing spaces)
|
||||
// matches[2]: numeric part as a string
|
||||
textPart := strings.TrimSpace(matches[1]) // Remove any trailing spaces from the text
|
||||
numberPart, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return "", 0, err // Return error if the number conversion fails
|
||||
}
|
||||
return textPart, numberPart, nil
|
||||
}
|
||||
|
||||
// Return an error if no trailing number is found
|
||||
return "", 0, fmt.Errorf("no number found at the end of the string")
|
||||
}
|
||||
|
||||
// Scan scans the interfaces compatible with the MIDI protocol
|
||||
func (f *MIDIFinder) Scan(ctx context.Context) error {
|
||||
midiPeripherals := make(map[string]Peripheral)
|
||||
fmt.Println("Opening MIDI scanner port...")
|
||||
midiScanner, err := rtmidi.NewMIDIInDefault()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error when opening the MIDI scanner: %s", err)
|
||||
}
|
||||
defer midiScanner.Close()
|
||||
fmt.Println("Scanning MIDI devices...")
|
||||
devicesCount, err := midiScanner.PortCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error when scanning MIDI devices: %s", err)
|
||||
}
|
||||
for i := 0; i < devicesCount; i++ {
|
||||
portName, err := midiScanner.PortName(i)
|
||||
if err != nil {
|
||||
portName = "Unknown device 0"
|
||||
}
|
||||
// Separate data
|
||||
name, location, err := splitStringAndNumber(portName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get information from the string: %s", err)
|
||||
}
|
||||
fmt.Printf("New MIDI device found: %s on %i\n", name, location)
|
||||
// Add the peripheral to the temporary list
|
||||
midiPeripherals[portName] = NewMIDIPeripheral(name, location)
|
||||
}
|
||||
// Compare with the current peripherals to detect arrivals/removals
|
||||
removedList, addedList := comparePeripherals(f.peripherals, midiPeripherals)
|
||||
// Emit the events
|
||||
emitPeripheralsEvents(ctx, removedList, PeripheralRemoval)
|
||||
emitPeripheralsEvents(ctx, addedList, PeripheralArrival)
|
||||
// Store the new peripherals list
|
||||
f.peripherals = midiPeripherals
|
||||
return nil
|
||||
}
|
||||
48
hardware/MIDIPeripheral.go
Normal file
48
hardware/MIDIPeripheral.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package hardware
|
||||
|
||||
// MIDIPeripheral contains the data of a MIDI peripheral
|
||||
type MIDIPeripheral struct {
|
||||
name string // The name of the peripheral
|
||||
location int // The location of the peripheral
|
||||
}
|
||||
|
||||
// NewMIDIPeripheral creates a new MIDI peripheral
|
||||
func NewMIDIPeripheral(name string, location int) *MIDIPeripheral {
|
||||
return &MIDIPeripheral{
|
||||
name: name,
|
||||
location: location,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect connects the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Disconnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate activates the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Activate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deactivate deactivates the MIDI peripheral
|
||||
func (p *MIDIPeripheral) Deactivate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeviceProperty - not implemented for this kind of peripheral
|
||||
func (p *MIDIPeripheral) SetDeviceProperty(uint32, uint32, byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInfo gets the peripheral information
|
||||
func (p *MIDIPeripheral) GetInfo() PeripheralInfo {
|
||||
return PeripheralInfo{
|
||||
Name: p.name,
|
||||
ProtocolName: "MIDI",
|
||||
}
|
||||
}
|
||||
190
hardware/hardware.go
Normal file
190
hardware/hardware.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/lxn/win"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// PeripheralEvent is trigger by the finders when the scan is complete
|
||||
type PeripheralEvent string
|
||||
|
||||
const (
|
||||
// PeripheralArrival is triggerd when a peripheral has been connected to the system
|
||||
PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL"
|
||||
// PeripheralRemoval is triggered when a peripheral has been disconnected from the system
|
||||
PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL"
|
||||
debounceDuration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
debounceTimer *time.Timer
|
||||
)
|
||||
|
||||
// HardwareManager is the class who manages the hardware
|
||||
type HardwareManager struct {
|
||||
finders []PeripheralFinder // The list of peripherals finders
|
||||
peripherals []Peripheral // The current list of peripherals
|
||||
deviceChangedEvent chan struct{} // The event when the devices list changed
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewHardwareManager creates a new HardwareManager
|
||||
func NewHardwareManager() *HardwareManager {
|
||||
return &HardwareManager{
|
||||
finders: make([]PeripheralFinder, 0),
|
||||
peripherals: make([]Peripheral, 0),
|
||||
deviceChangedEvent: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts to finding new device events
|
||||
func (h *HardwareManager) Start(ctx context.Context) error {
|
||||
cb := windows.NewCallback(h.wndProc)
|
||||
|
||||
inst := win.GetModuleHandle(nil)
|
||||
cn, err := syscall.UTF16PtrFromString("DMXConnect device watcher")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert window class name to UTF16: %w", err)
|
||||
}
|
||||
wc := win.WNDCLASSEX{
|
||||
HInstance: inst,
|
||||
LpfnWndProc: cb,
|
||||
LpszClassName: cn,
|
||||
}
|
||||
wc.CbSize = uint32(unsafe.Sizeof(wc))
|
||||
if win.RegisterClassEx(&wc) == 0 {
|
||||
return fmt.Errorf("failed to register window class: %w", syscall.GetLastError())
|
||||
}
|
||||
wName, err := syscall.UTF16PtrFromString("usbevent.exe")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert window name to UTF16: %w", err)
|
||||
}
|
||||
wdw := win.CreateWindowEx(
|
||||
0,
|
||||
wc.LpszClassName,
|
||||
wName,
|
||||
win.WS_MINIMIZE|win.WS_OVERLAPPEDWINDOW,
|
||||
win.CW_USEDEFAULT,
|
||||
win.CW_USEDEFAULT,
|
||||
100,
|
||||
100,
|
||||
0,
|
||||
0,
|
||||
wc.HInstance,
|
||||
nil)
|
||||
if wdw == 0 {
|
||||
return fmt.Errorf("failed to create window: %w", syscall.GetLastError())
|
||||
}
|
||||
|
||||
_ = win.ShowWindow(wdw, win.SW_HIDE)
|
||||
win.UpdateWindow(wdw)
|
||||
|
||||
// To continuously get the devices events from Windows
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
var msg win.MSG
|
||||
got := win.GetMessage(&msg, win.HWND(windows.HWND(wdw)), 0, 0)
|
||||
if got == 0 {
|
||||
win.TranslateMessage(&msg)
|
||||
win.DispatchMessage(&msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// To handle the peripheral changed
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-h.deviceChangedEvent:
|
||||
fmt.Println("This is the list of devices")
|
||||
h.Scan(ctx)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterFinder registers a new peripherals finder
|
||||
func (h *HardwareManager) RegisterFinder(finder PeripheralFinder) {
|
||||
h.finders = append(h.finders, finder)
|
||||
fmt.Printf("Success registered the %s finder\n", finder.GetName())
|
||||
}
|
||||
|
||||
// Scan scans all the peripherals for the registered finders
|
||||
func (h *HardwareManager) Scan(ctx context.Context) error {
|
||||
if len(h.finders) == 0 {
|
||||
return fmt.Errorf("No peripherals finder registered")
|
||||
}
|
||||
for _, finder := range h.finders {
|
||||
finder := finder
|
||||
go func() {
|
||||
err := finder.Scan(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to scan peripherals with the %s finder: %s\n", finder.GetName(), err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
switch msg {
|
||||
case win.WM_DEVICECHANGE:
|
||||
// Trigger the devices scan when the last DEVICE_CHANGE event is received
|
||||
if debounceTimer != nil {
|
||||
debounceTimer.Stop()
|
||||
}
|
||||
debounceTimer = time.AfterFunc(debounceDuration, func() {
|
||||
fmt.Printf("Devices list has changed, refresh the devices list\n")
|
||||
h.deviceChangedEvent <- struct{}{}
|
||||
})
|
||||
}
|
||||
return win.DefWindowProc(win.HWND(hwnd), msg, wParam, lParam)
|
||||
}
|
||||
|
||||
// peripheralsList emits a peripheral event
|
||||
func emitPeripheralsEvents(ctx context.Context, peripheralsList map[string]Peripheral, peripheralEvent PeripheralEvent) {
|
||||
for _, peripheral := range peripheralsList {
|
||||
runtime.EventsEmit(ctx, string(peripheralEvent), peripheral.GetInfo())
|
||||
}
|
||||
}
|
||||
|
||||
// comparePeripherals compares the peripherals to determine which has been inserted or removed
|
||||
func comparePeripherals(oldPeripherals map[string]Peripheral, newPeripherals map[string]Peripheral) (map[string]Peripheral, map[string]Peripheral) {
|
||||
// Duplicate the lists
|
||||
oldList := make(map[string]Peripheral)
|
||||
newList := make(map[string]Peripheral)
|
||||
|
||||
for key, value := range oldPeripherals {
|
||||
oldList[key] = value
|
||||
}
|
||||
for key, value := range newPeripherals {
|
||||
newList[key] = value
|
||||
}
|
||||
// Remove in these lists all the commons peripherals
|
||||
for key := range newList {
|
||||
if _, exists := oldList[key]; exists {
|
||||
delete(oldList, key)
|
||||
delete(newList, key)
|
||||
}
|
||||
}
|
||||
// Now the old list contains the removed peripherals, and the new list contains the added peripherals
|
||||
fmt.Printf("%s\n", oldList)
|
||||
fmt.Printf("%s\n", newList)
|
||||
return oldList, newList
|
||||
}
|
||||
29
hardware/interfaces.go
Normal file
29
hardware/interfaces.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package hardware
|
||||
|
||||
import "context"
|
||||
|
||||
// Peripheral represents the methods used to manage a peripheral (input or output hardware)
|
||||
type Peripheral interface {
|
||||
Connect() error // Connect the peripheral
|
||||
Disconnect() error // Disconnect the peripheral
|
||||
Activate() error // Activate the peripheral
|
||||
Deactivate() error // Deactivate the peripheral
|
||||
SetDeviceProperty(uint32, uint32, byte) error // Update a device property
|
||||
|
||||
GetInfo() PeripheralInfo // Get the peripheral information
|
||||
}
|
||||
|
||||
// PeripheralInfo represents a peripheral information
|
||||
type PeripheralInfo struct {
|
||||
Name string // Name of the peripheral
|
||||
SerialNumber string // S/N of the peripheral
|
||||
ProtocolName string // Protocol name of the peripheral
|
||||
UniversesNumber int // Number of DMX universes handled by the peripheral
|
||||
}
|
||||
|
||||
// PeripheralFinder represents how compatible peripheral finders are implemented
|
||||
type PeripheralFinder interface {
|
||||
Initialize() error // Initializes the protocol
|
||||
GetName() string // Get the name of the finder
|
||||
Scan(context.Context) error // Scan for peripherals
|
||||
}
|
||||
34
hardware/third-party/ftdi/detectFTDI.cpp
vendored
Normal file
34
hardware/third-party/ftdi/detectFTDI.cpp
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include "ftd2xx.h"
|
||||
|
||||
int main() {
|
||||
FT_STATUS ftStatus;
|
||||
FT_HANDLE ftHandle = nullptr;
|
||||
FT_DEVICE_LIST_INFO_NODE *devInfo;
|
||||
DWORD numDevs;
|
||||
// create the device information list
|
||||
ftStatus = FT_CreateDeviceInfoList(&numDevs);
|
||||
if (ftStatus != FT_OK) {
|
||||
std::cerr << "Unable to get the FTDI devices : create list error" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (numDevs > 0) {
|
||||
// allocate storage for list based on numDevs
|
||||
devInfo =
|
||||
(FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs);
|
||||
// get the device information list
|
||||
ftStatus = FT_GetDeviceInfoList(devInfo, &numDevs);
|
||||
if (ftStatus != FT_OK) {
|
||||
std::cerr << "Unable to get the FTDI devices : get list error" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numDevs; i++) {
|
||||
if (devInfo[i].SerialNumber[0] != '\0') {
|
||||
std::cout << i << ":" << devInfo[i].SerialNumber << ":" << devInfo[i].Description << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
hardware/third-party/ftdi/detectFTDI.manifest
vendored
Normal file
12
hardware/third-party/ftdi/detectFTDI.manifest
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="DMXSender" type="win32"/>
|
||||
<description>Detect FTDI</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
142
hardware/third-party/ftdi/dmxSender.cpp
vendored
Normal file
142
hardware/third-party/ftdi/dmxSender.cpp
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <math.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include "ftd2xx.h"
|
||||
|
||||
#define DMX_START_CODE 0x00
|
||||
#define BREAK_DURATION_US 110
|
||||
#define MAB_DURATION_US 16
|
||||
#define DMX_CHANNELS 512
|
||||
#define FREQUENCY 44
|
||||
#define INTERVAL (1000000 / FREQUENCY)
|
||||
|
||||
std::atomic<unsigned char> dmxData[DMX_CHANNELS + 1];
|
||||
std::atomic<bool> isOutputActivated = false;
|
||||
|
||||
using namespace std;
|
||||
|
||||
void sendBreak(FT_HANDLE ftHandle) {
|
||||
FT_SetBreakOn(ftHandle); // Envoie le signal de BREAK
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(BREAK_DURATION_US));
|
||||
FT_SetBreakOff(ftHandle); // Arrête le signal de BREAK
|
||||
}
|
||||
|
||||
void sendDMX(FT_HANDLE ftHandle) {
|
||||
while (true) {
|
||||
if(isOutputActivated){
|
||||
// Envoi du BREAK suivi du MAB
|
||||
sendBreak(ftHandle);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(MAB_DURATION_US));
|
||||
|
||||
// Envoi de la trame DMX512
|
||||
DWORD bytesWritten = 0;
|
||||
|
||||
// Envoyer la trame DMX512
|
||||
FT_STATUS status = FT_Write(ftHandle, dmxData, DMX_CHANNELS, &bytesWritten);
|
||||
if (status != FT_OK || bytesWritten != DMX_CHANNELS) {
|
||||
std::cerr << "Unable to send the DMX frame" << std::endl;
|
||||
FT_Close(ftHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attendre avant d'envoyer la prochaine trame
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(INTERVAL - BREAK_DURATION_US - MAB_DURATION_US));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processCommand(const char* buffer) {
|
||||
if (buffer[0] == 0x01) {
|
||||
// Activate the DMX512
|
||||
isOutputActivated.store(true);
|
||||
} else if(buffer[0] == 0x02) {
|
||||
// Deactivate the DMX512
|
||||
isOutputActivated.store(false);
|
||||
} else if(buffer[0] == 0x03) {
|
||||
// Get the channel number
|
||||
uint16_t channelNumber = (static_cast<unsigned char>(buffer[1]) |
|
||||
(static_cast<unsigned char>(buffer[2]) << 8));
|
||||
// Get the channel value
|
||||
uint8_t channelValue = static_cast<unsigned char>(buffer[3]);
|
||||
// // Update the DMX array
|
||||
dmxData[channelNumber].store(channelValue);
|
||||
} else if(buffer[0] == 0x04) {
|
||||
// Close this sender
|
||||
exit(0);
|
||||
} else {
|
||||
std::cerr << "Unknown command" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
#endif
|
||||
|
||||
FT_STATUS ftStatus;
|
||||
FT_HANDLE ftHandle = nullptr;
|
||||
|
||||
// Check if the serial port is specified
|
||||
if (argc != 2) {
|
||||
std::cerr << "Invalid call to DMX sender" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Connect the serial port
|
||||
int deviceDev;
|
||||
try {
|
||||
deviceDev = std::stoi(argv[1]);
|
||||
}catch(const std::exception& e){
|
||||
std::cerr << "Invalid call to DMX sender" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
ftStatus = FT_Open(deviceDev, &ftHandle);
|
||||
if (ftStatus != FT_OK) {
|
||||
std::cerr << "Unable to open the FTDI device" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
ftStatus = FT_SetBaudRate(ftHandle, 250000);
|
||||
ftStatus |= FT_SetDataCharacteristics(ftHandle, 8, FT_STOP_BITS_2, FT_PARITY_NONE); // 8 bits, pas de parité, 1 bit de stop
|
||||
ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_NONE, 0, 0);
|
||||
if (ftStatus != FT_OK) {
|
||||
std::cerr << "Unable to configure the FTDI device" << std::endl;
|
||||
FT_Close(ftHandle);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Send the DMX frames
|
||||
std::thread updateThread(sendDMX, ftHandle);
|
||||
|
||||
// Intercept commands from the GO program
|
||||
char buffer[4]; // Tampon pour stocker les 4 octets d'une commande
|
||||
|
||||
while (true) {
|
||||
std::cin.read(buffer, 4); // Attente bloquante jusqu'à ce que 4 octets soient lus
|
||||
if (std::cin.gcount() == 4) { // Vérifier que 4 octets ont été lus
|
||||
processCommand(buffer);
|
||||
} else if (std::cin.eof()) {
|
||||
std::cerr << "Fin de l'entrée standard (EOF)" << std::endl;
|
||||
break;
|
||||
} else if (std::cin.fail()) {
|
||||
std::cerr << "Erreur de lecture sur stdin" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
12
hardware/third-party/ftdi/dmxSender.manifest
vendored
Normal file
12
hardware/third-party/ftdi/dmxSender.manifest
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="DMXSender" type="win32"/>
|
||||
<description>DMXSender</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
8
hardware/third-party/ftdi/generate.bat
vendored
Normal file
8
hardware/third-party/ftdi/generate.bat
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
windres dmxSender.rc dmxSender.o
|
||||
windres detectFTDI.rc detectFTDI.o
|
||||
|
||||
g++ -o dmxSender.exe dmxSender.cpp dmxSender.o -I"include" -L"lib" -lftd2xx
|
||||
g++ -o detectFTDI.exe detectFTDI.cpp detectFTDI.o -I"include" -L"lib" -lftd2xx
|
||||
|
||||
@REM g++ -o dmxSender.exe dmxSender.cpp -I"include" -L"lib" -lftd2xx
|
||||
@REM g++ -o detectFTDI.exe detectFTDI.cpp -I"include" -L"lib" -lftd2xx
|
||||
1667
hardware/third-party/ftdi/include/ftd2xx.h
vendored
Normal file
1667
hardware/third-party/ftdi/include/ftd2xx.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
hardware/third-party/ftdi/lib/ftd2xx.lib
vendored
Normal file
BIN
hardware/third-party/ftdi/lib/ftd2xx.lib
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user