11-hardware-definition #17

Merged
thinkode merged 13 commits from 11-hardware-definition into develop 2025-01-18 14:53:30 +00:00
12 changed files with 264 additions and 169 deletions
Showing only changes of commit 556f24991e - Show all commits

28
app.go
View File

@@ -5,7 +5,9 @@ import (
"context"
"fmt"
"io"
"log"
"github.com/rs/zerolog/log"
"os"
"strings"
"sync"
@@ -18,8 +20,6 @@ type App struct {
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
// FOR TESTING PURPOSE ONLY
ftdi *hardware.FTDIPeripheral
}
// NewApp creates a new App application struct
@@ -40,15 +40,33 @@ func NewApp() *App {
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
func (a *App) onStartup(ctx context.Context) {
a.ctx = ctx
err := a.hardwareManager.Start(ctx)
if err != nil {
log.Fatalf("Unable to start the device manager: %s", err)
log.Err(err).Str("file", "app").Msg("unable to start the hardware manager")
return
}
}
// onReady is called when the DOM is ready
// We get the current peripherals connected
func (a *App) onReady(ctx context.Context) {
log.Debug().Str("file", "peripherals").Msg("getting peripherals...")
err := a.hardwareManager.Scan(a.ctx)
if err != nil {
log.Err(err).Str("file", "app").Msg("unable to get the peripherals")
}
return
}
// onShutdown is called when the app is closing
// We stop all the pending processes
func (a *App) onShutdown(ctx context.Context) {
log.Warn().Str("file", "app").Msg("app is closing")
return
}
func formatString(input string) string {
// Convertir en minuscules
lowerCaseString := strings.ToLower(input)

View File

@@ -13,7 +13,7 @@
import { SaveProject } from '../wailsjs/go/main/App.js';
import { construct_svelte_component } from 'svelte/internal';
import { EventsOn } from '../wailsjs/runtime'
import { CreateProject, GetPeripherals } from "../wailsjs/go/main/App";
import { CreateProject } from "../wailsjs/go/main/App";
import { WindowSetTitle } from "../wailsjs/runtime/runtime"
import { get } from "svelte/store"
import ToastNotification from './components/General/ToastNotification.svelte';
@@ -83,11 +83,6 @@
$needProjectSave = true
})
// Request the list of peripherals
GetPeripherals().catch((error) => {
generateToast('danger', 'bx-error', 'Unable to get the list of peripherals: ' + error)
})
// Handle window shortcuts
document.addEventListener('keydown', function(event) {
// Check the CTRL+S keys

View File

@@ -106,6 +106,9 @@
generateToast('danger', 'bx-error', 'Unable to create the OS2L peripheral')
})
}
// Get the number of saved peripherals
$: savedPeripheralNumber = Object.values($peripherals).filter(peripheral => peripheral.isSaved).length;
</script>
<div class="hardware">
@@ -113,15 +116,15 @@
<p style="margin-bottom: 1em;">Available peripherals</p>
<div class="availableHardware">
<p style="color: var(--first-color);"><i class='bx bxs-plug'></i> Detected</p>
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isDetected}
<DeviceCard on:add={() => addPeripheral(peripheral)} on:dblclick={() => {
if(!peripheral.isSaved)
addPeripheral(peripheral)
}}
title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/>
{/if}
{/each}
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isDetected}
<DeviceCard on:add={() => addPeripheral(peripheral)} on:dblclick={() => {
if(!peripheral.isSaved)
addPeripheral(peripheral)
}}
title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/>
{/if}
{/each}
<p style="color: var(--first-color);"><i class='bx bxs-network-chart' ></i> Others</p>
<RoundedButton on:click={createOS2L} text="Add an OS2L peripheral" icon="bx-plus-circle" tooltip="Configure an OS2L connection"/>
</div>
@@ -130,12 +133,16 @@
<div style="padding: 0.5em; flex:2; width:100%;">
<p style="margin-bottom: 1em;">Project peripherals</p>
<div class="configuredHardware">
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isSaved}
<DeviceCard on:delete={() => removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)}
disconnected={!peripheral.isDetected} title={peripheral.Name == "" ? "Please wait..." : peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} removable signalizable/>
{/if}
{/each}
{#if savedPeripheralNumber > 0}
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
{#if peripheral.isSaved}
<DeviceCard on:delete={() => removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)}
disconnected={!peripheral.isDetected} title={peripheral.Name == "" ? "Please wait..." : peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} removable signalizable/>
{/if}
{/each}
{:else}
<i>No hardware saved for this project.</i>
{/if}
</div>
<p style="margin-bottom: 1em;">Peripheral settings</p>
<div>

View File

@@ -27,30 +27,46 @@
})
}
// Unsave peripherals from the store and remove the disconnected peripherals
function unsavePeripherals(){
peripherals.update((storedPeripherals) => {
// Set all the isSaved keys to false and delete the disconnected peripherals
for (let peripheralID in storedPeripherals) {
storedPeripherals[peripheralID].isSaved = false
if (!storedPeripherals[peripheralID].isDetected) {
delete storedPeripherals[peripheralID]
}
}
return {...storedPeripherals}
})
}
// Load the saved peripherals into the store
function loadPeripherals(peripheralsInfo){
peripherals.update((storedPeripherals) => {
// Add the saved peripherals of the project
// If already exists pass the isSaved key to true, if not create the peripheral and set it to disconnected
for (let peripheralID in peripheralsInfo){
// Add the peripheral to the list of peripherals, with the last isDetected key and the isSaved key to true
let lastDetectedKey = storedPeripherals[peripheralID]?.isDetected
storedPeripherals[peripheralID] = peripheralsInfo[peripheralID]
storedPeripherals[peripheralID].isDetected = (lastDetectedKey === true) ? true : false
storedPeripherals[peripheralID].isSaved = true
}
return {...storedPeripherals}
})
}
// Open the selected project
function openSelectedProject(event){
let selectedOption = event.detail.key
// Open the selected project
GetProjectInfo(selectedOption).then((projectInfo) => {
$showInformation = projectInfo.ShowInfo
peripherals.update((storedPeripherals) => {
// Set all the isSaved keys to false and delete the disconnected peripherals
for (let peripheralID in storedPeripherals) {
storedPeripherals[peripheralID].isSaved = false
if (!storedPeripherals[peripheralID].isDetected) {
delete storedPeripherals[peripheralID]
}
}
// Add the saved peripherals of the project
// If already exists pass the isSaved key to true, if not create the peripheral and set it to disconnected
for (let peripheralID in projectInfo.PeripheralsInfo){
// Add the peripheral to the list of peripherals, with the last isDetected key and the isSaved key to true
let lastDetectedKey = storedPeripherals[peripheralID]?.isDetected
storedPeripherals[peripheralID] = projectInfo.PeripheralsInfo[peripheralID]
storedPeripherals[peripheralID].isDetected = (lastDetectedKey === true) ? true : false
storedPeripherals[peripheralID].isSaved = true
}
return {...storedPeripherals}
})
// Remove the saved peripherals ofthe current project
unsavePeripherals()
// Load the new project peripherals
loadPeripherals(projectInfo.PeripheralsInfo)
needProjectSave.set(false)
generateToast('info', 'bx-folder-open', 'The project <b>' + projectInfo.ShowInfo.Name + '</b> was opened')
}).catch((error) => {
@@ -63,6 +79,8 @@
// Instanciate a new project
CreateProject().then((showInfo) => {
$showInformation = showInfo
// Remove the saved peripherals ofthe current project
unsavePeripherals()
$needProjectSave = true
generateToast('info', 'bxs-folder-plus', 'The project was created')
})

View File

@@ -32,8 +32,12 @@ func NewFTDIDriver() *FTDIDriver {
}
}
//go:embed third-party/ftdi/detectFTDI.exe
var findFTDI []byte
// Initialize initializes the FTDI driver
func (d *FTDIDriver) Initialize() error {
// Check platform
if goRuntime.GOOS != "windows" {
log.Error().Str("file", "FTDIDriver").Str("platform", goRuntime.GOOS).Msg("FTDI driver not compatible with your platform")
return fmt.Errorf("<!> The FTDI driver is not compatible with your platform yet (%s)", goRuntime.GOOS)
@@ -59,9 +63,6 @@ func (d *FTDIDriver) GetPeripheral(peripheralID string) (Peripheral, bool) {
return peripheral, true
}
//go:embed third-party/ftdi/detectFTDI.exe
var findFTDI []byte
// Scan scans the FTDI peripherals
func (d *FTDIDriver) Scan(ctx context.Context) error {
log.Trace().Str("file", "FTDIDriver").Msg("FTDI scan triggered")

View File

@@ -2,6 +2,7 @@ package hardware
import (
"bufio"
"context"
_ "embed"
"fmt"
"io"
@@ -10,7 +11,6 @@ import (
"os"
"os/exec"
"sync"
)
const (
@@ -19,9 +19,6 @@ const (
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
@@ -35,14 +32,16 @@ type FTDIPeripheral struct {
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
}
//go:embed third-party/ftdi/dmxSender.exe
var dmxSender []byte
// NewFTDIPeripheral creates a new FTDI peripheral
func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPeripheral, error) {
log.Info().Str("file", "FTDIPeripheral").Str("name", name).Str("s/n", serialNumber).Int("location", location).Msg("FTDI peripheral created")
// Create a temporary file
tempFile, err := os.CreateTemp("", "dmxSender*.exe")
tempFile, err := os.Create(fmt.Sprintf("dmxSender-%s.exe", serialNumber))
if err != nil {
return nil, err
}
@@ -58,6 +57,7 @@ func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPer
return &FTDIPeripheral{
name: name,
dmxSender: nil,
programName: tempFile.Name(),
serialNumber: serialNumber,
location: location,
@@ -68,64 +68,75 @@ func NewFTDIPeripheral(name string, serialNumber string, location int) (*FTDIPer
}
// Connect connects the FTDI peripheral
func (p *FTDIPeripheral) Connect() error {
func (p *FTDIPeripheral) Connect(ctx context.Context) error {
// Connect if no connection is already running
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("connecting FTDI peripheral...")
if p.dmxSender == nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("no instance of dmxSender for this FTDI")
// Executing the command
p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location))
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("no instance of dmxSender for this FTDI")
// Check if the connection has already been established
if p.dmxSender != nil {
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender already initialized")
return nil
}
var err error
p.stdout, err = p.dmxSender.StdoutPipe()
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stdout pipe")
return fmt.Errorf("unable to create the stdout pipe: %v", err)
// Initialize the exec.Command for running the process
p.dmxSender = exec.Command(p.programName, fmt.Sprintf("%d", p.location))
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender instance created")
// Create the pipes for stdin, stdout, and stderr asynchronously without blocking
var err error
if p.stdout, err = p.dmxSender.StdoutPipe(); err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stdout pipe")
return fmt.Errorf("unable to create stdout pipe: %v", err)
}
if p.stdin, err = p.dmxSender.StdinPipe(); err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stdin pipe")
return fmt.Errorf("unable to create stdin pipe: %v", err)
}
if p.stderr, err = p.dmxSender.StderrPipe(); err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create stderr pipe")
return fmt.Errorf("unable to create stderr pipe: %v", err)
}
// Launch a goroutine to read stderr asynchronously
go func() {
scanner := bufio.NewScanner(p.stderr)
for scanner.Scan() {
// Process each line read from stderr
log.Err(fmt.Errorf(scanner.Text())).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error detected in dmx sender")
}
p.stdin, err = p.dmxSender.StdinPipe()
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stdin pipe")
return fmt.Errorf("unable to create the stdin pipe: %v", err)
if err := scanner.Err(); err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error reading from stderr")
}
}()
p.stderr, err = p.dmxSender.StderrPipe()
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to create the stderr pipe")
return fmt.Errorf("unable to create the stderr pipe: %v", err)
}
go func() {
scanner := bufio.NewScanner(p.stderr)
for scanner.Scan() {
// Traitez chaque ligne lue depuis stderr
log.Err(fmt.Errorf(scanner.Text())).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error detected in dmx sender")
}
if err := scanner.Err(); err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error reading from stderr")
}
}()
p.wg.Add(1)
go func() {
defer p.wg.Done()
err = p.dmxSender.Run()
// Launch the command asynchronously in another goroutine
go func() {
// Run the command, respecting the context cancellation
err := p.dmxSender.Run()
select {
case <-ctx.Done():
// If the context is canceled, handle it gracefully
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender was canceled by context")
return
default:
// Handle command exit normally
if err != nil {
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while execution of dmw sender")
log.Err(err).Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("error while execution of dmx sender")
if exitError, ok := err.(*exec.ExitError); ok {
log.Warn().Str("file", "FTDIPeripheral").Int("exitCode", exitError.ExitCode()).Str("s/n", p.serialNumber).Msg("dmx sender exited with code")
}
} else {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmx sender exited successfully")
}
}()
}
}
}()
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxSender process started successfully")
return nil
}
// Disconnect disconnects the FTDI peripheral
func (p *FTDIPeripheral) Disconnect() error {
func (p *FTDIPeripheral) Disconnect(ctx context.Context) error {
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("disconnecting FTDI peripheral...")
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
@@ -139,7 +150,7 @@ func (p *FTDIPeripheral) Disconnect() error {
p.dmxSender = nil
err = os.Remove(p.programName)
if err != nil {
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("unable to delete the dmx sender temporary file")
log.Warn().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Str("senderPath", p.programName).Msg("unable to delete the dmx sender temporary file")
return fmt.Errorf("unable to delete the temporary file: %v", err)
}
return nil
@@ -149,7 +160,7 @@ func (p *FTDIPeripheral) Disconnect() error {
}
// Activate activates the FTDI peripheral
func (p *FTDIPeripheral) Activate() error {
func (p *FTDIPeripheral) Activate(ctx context.Context) error {
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
_, err := io.WriteString(p.stdin, string([]byte{0x01, 0x00, 0x00, 0x00}))
@@ -164,7 +175,7 @@ func (p *FTDIPeripheral) Activate() error {
}
// Deactivate deactivates the FTDI peripheral
func (p *FTDIPeripheral) Deactivate() error {
func (p *FTDIPeripheral) Deactivate(ctx context.Context) error {
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
_, err := io.WriteString(p.stdin, string([]byte{0x02, 0x00, 0x00, 0x00}))
@@ -179,7 +190,7 @@ func (p *FTDIPeripheral) Deactivate() error {
}
// SetDeviceProperty sends a command to the specified device
func (p *FTDIPeripheral) SetDeviceProperty(uint32, channelNumber uint32, channelValue byte) error {
func (p *FTDIPeripheral) SetDeviceProperty(ctx context.Context, uint32, channelNumber uint32, channelValue byte) error {
if p.dmxSender != nil {
log.Debug().Str("file", "FTDIPeripheral").Str("s/n", p.serialNumber).Msg("dmxsender is defined for this FTDI")
commandString := []byte{0x03, 0x01, 0x00, 0xff, 0x03, 0x02, 0x00, channelValue}

View File

@@ -1,6 +1,10 @@
package hardware
import "github.com/rs/zerolog/log"
import (
"context"
"github.com/rs/zerolog/log"
)
// MIDIPeripheral contains the data of a MIDI peripheral
type MIDIPeripheral struct {
@@ -20,27 +24,27 @@ func NewMIDIPeripheral(name string, location int, serialNumber string) *MIDIPeri
}
// Connect connects the MIDI peripheral
func (p *MIDIPeripheral) Connect() error {
func (p *MIDIPeripheral) Connect(ctx context.Context) error {
return nil
}
// Disconnect disconnects the MIDI peripheral
func (p *MIDIPeripheral) Disconnect() error {
func (p *MIDIPeripheral) Disconnect(ctx context.Context) error {
return nil
}
// Activate activates the MIDI peripheral
func (p *MIDIPeripheral) Activate() error {
func (p *MIDIPeripheral) Activate(ctx context.Context) error {
return nil
}
// Deactivate deactivates the MIDI peripheral
func (p *MIDIPeripheral) Deactivate() error {
func (p *MIDIPeripheral) Deactivate(ctx context.Context) error {
return nil
}
// SetDeviceProperty - not implemented for this kind of peripheral
func (p *MIDIPeripheral) SetDeviceProperty(uint32, uint32, byte) error {
func (p *MIDIPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error {
return nil
}

View File

@@ -1,6 +1,8 @@
package hardware
import (
"context"
"github.com/rs/zerolog/log"
)
@@ -20,31 +22,31 @@ func NewOS2LPeripheral(name string, serialNumber string) *OS2LPeripheral {
}
// Connect connects the MIDI peripheral
func (p *OS2LPeripheral) Connect() error {
func (p *OS2LPeripheral) Connect(ctx context.Context) error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected")
return nil
}
// Disconnect disconnects the MIDI peripheral
func (p *OS2LPeripheral) Disconnect() error {
func (p *OS2LPeripheral) Disconnect(ctx context.Context) error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected")
return nil
}
// Activate activates the MIDI peripheral
func (p *OS2LPeripheral) Activate() error {
func (p *OS2LPeripheral) Activate(ctx context.Context) error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated")
return nil
}
// Deactivate deactivates the MIDI peripheral
func (p *OS2LPeripheral) Deactivate() error {
func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated")
return nil
}
// SetDeviceProperty - not implemented for this kind of peripheral
func (p *OS2LPeripheral) SetDeviceProperty(uint32, uint32, byte) error {
func (p *OS2LPeripheral) SetDeviceProperty(context.Context, uint32, uint32, byte) error {
return nil
}

View File

@@ -49,41 +49,35 @@ func NewHardwareManager() *HardwareManager {
// Start starts to find new peripheral events
func (h *HardwareManager) Start(ctx context.Context) error {
// Configure wndProc callback
cb := windows.NewCallback(h.wndProc)
log.Trace().Str("file", "hardware").Msg("wndProc callback set")
inst := win.GetModuleHandle(nil)
log.Trace().Str("file", "hardware").Msg("got windows API instance")
cn, err := syscall.UTF16PtrFromString("DMXConnect peripheral watcher")
// Register window class
className, err := syscall.UTF16PtrFromString("DMXConnectPeripheralWatcher")
if err != nil {
log.Err(err).Str("file", "hardware").Msg("failed to convert window class name to UTF16")
return fmt.Errorf("failed to convert window class name to UTF16: %w", err)
}
wc := win.WNDCLASSEX{
CbSize: uint32(unsafe.Sizeof(win.WNDCLASSEX{})),
HInstance: inst,
LpfnWndProc: cb,
LpszClassName: cn,
LpszClassName: className,
}
log.Trace().Str("file", "hardware").Msg("windows API class created")
wc.CbSize = uint32(unsafe.Sizeof(wc))
if win.RegisterClassEx(&wc) == 0 {
log.Err(syscall.GetLastError()).Str("file", "hardware").Msg("failed to register window class")
return fmt.Errorf("failed to register window class: %w", syscall.GetLastError())
}
log.Trace().Str("file", "hardware").Msg("window class registered")
wName, err := syscall.UTF16PtrFromString("usbevent.exe")
// Create hidden window
windowName, err := syscall.UTF16PtrFromString("usbevent.exe")
if err != nil {
log.Err(err).Str("file", "hardware").Msg("failed to convert window class name to UTF16")
return fmt.Errorf("failed to convert window name to UTF16: %w", err)
}
wdw := win.CreateWindowEx(
hwnd := win.CreateWindowEx(
0,
wc.LpszClassName,
wName,
win.WS_MINIMIZE|win.WS_OVERLAPPEDWINDOW,
windowName,
win.WS_OVERLAPPEDWINDOW,
win.CW_USEDEFAULT,
win.CW_USEDEFAULT,
100,
@@ -91,34 +85,18 @@ func (h *HardwareManager) Start(ctx context.Context) error {
0,
0,
wc.HInstance,
nil)
if wdw == 0 {
log.Err(syscall.GetLastError()).Str("file", "hardware").Msg("failed to create window")
nil,
)
if hwnd == 0 {
return fmt.Errorf("failed to create window: %w", syscall.GetLastError())
}
log.Trace().Str("file", "hardware").Msg("window created successfully")
_ = win.ShowWindow(wdw, win.SW_HIDE)
win.UpdateWindow(wdw)
log.Trace().Str("file", "hardware").Msg("window shown and updated")
// Hide and update window
win.ShowWindow(hwnd, win.SW_HIDE)
win.UpdateWindow(hwnd)
// To continuously get the devices events from Windows
go func() {
defer log.Debug().Str("file", "hardware").Msg("peripheral watcher goroutine exited")
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)
}
}
}
}()
// Start message loop in a goroutine
go messageLoop(ctx, hwnd)
// To handle the peripheral changed
go func() {
@@ -139,6 +117,31 @@ func (h *HardwareManager) Start(ctx context.Context) error {
return nil
}
func messageLoop(ctx context.Context, hwnd win.HWND) {
defer log.Debug().Str("file", "hardware").Msg("Peripheral watcher goroutine exited")
for {
select {
case <-ctx.Done():
win.PostQuitMessage(0) // Gracefully terminate message loop
return
default:
var msg win.MSG
result := win.GetMessage(&msg, hwnd, 0, 0)
if result > 0 {
win.TranslateMessage(&msg)
win.DispatchMessage(&msg)
} else if result == 0 {
log.Warn().Str("file", "hardware").Msg("WM_QUIT message received")
return
} else {
log.Error().Str("file", "hardware").Msg("GetMessage returned an error")
return
}
}
}
}
// GetDriver returns a register driver
func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error) {
driver, exists := h.drivers[driverName]
@@ -190,6 +193,7 @@ func (h *HardwareManager) Scan(ctx context.Context) error {
}
func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr {
log.Trace().Str("file", "hardware").Msg("wndProc triggered")
switch msg {
case win.WM_DEVICECHANGE:
// Trigger the devices scan when the last DEVICE_CHANGE event is received

View File

@@ -4,11 +4,11 @@ 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
Connect(context.Context) error // Connect the peripheral
Disconnect(context.Context) error // Disconnect the peripheral
Activate(context.Context) error // Activate the peripheral
Deactivate(context.Context) error // Deactivate the peripheral
SetDeviceProperty(context.Context, uint32, uint32, byte) error // Update a device property
GetInfo() PeripheralInfo // Get the peripheral information
}

View File

@@ -40,7 +40,9 @@ func main() {
AssetServer: &assetserver.Options{
Assets: assets,
},
OnStartup: app.startup,
OnStartup: app.onStartup,
OnDomReady: app.onReady,
OnShutdown: app.onShutdown,
Bind: []interface{}{
app,
},

View File

@@ -7,12 +7,6 @@ import (
"github.com/rs/zerolog/log"
)
// GetPeripherals gets all the peripherals connected
func (a *App) GetPeripherals() error {
log.Debug().Str("file", "peripherals").Msg("getting peripherals...")
return a.hardwareManager.Scan(a.ctx)
}
// AddPeripheral adds a peripheral to the project
func (a *App) AddPeripheral(protocolName string, peripheralID string) error {
// Get the device from its finder
@@ -64,27 +58,66 @@ func (a *App) AddOS2LPeripheral() (hardware.PeripheralInfo, error) {
// FOR TESTING PURPOSE ONLY
func (a *App) ConnectFTDI() error {
// Create a new FTDI object
var err error
a.ftdi, err = hardware.NewFTDIPeripheral("FTDI TEST INTERFACE", "A50825I", 0)
// Connect the FTDI
driver, err := a.hardwareManager.GetDriver("FTDI")
if err != nil {
return err
}
return a.ftdi.Connect()
periph, found := driver.GetPeripheral("A50285BI")
if !found {
return fmt.Errorf("unable to find the peripheral s/n %s", "A50285BI")
}
return periph.Connect(a.ctx)
}
func (a *App) ActivateFTDI() error {
return a.ftdi.Activate()
// Connect the FTDI
driver, err := a.hardwareManager.GetDriver("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 {
return a.ftdi.SetDeviceProperty(0, 0, channelValue)
// Connect the FTDI
driver, err := a.hardwareManager.GetDriver("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 {
return a.ftdi.Deactivate()
// Connect the FTDI
driver, err := a.hardwareManager.GetDriver("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)
}
func (a *App) DisconnectFTDI() error {
return a.ftdi.Disconnect()
// Connect the FTDI
driver, err := a.hardwareManager.GetDriver("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.Disconnect(a.ctx)
}