implement OS2L protocol

This commit is contained in:
2025-11-13 11:39:27 +01:00
parent 1b63698059
commit d730cf4f1c

View File

@@ -2,17 +2,34 @@ package hardware
import (
"context"
"encoding/json"
"fmt"
"net"
"sync"
"time"
"github.com/rs/zerolog/log"
)
// OS2LMessage represents an OS2L message
type OS2LMessage struct {
Event string `json:"evt"`
Name string `json:"name"`
State string `json:"state"`
ID int64 `json:"id"`
Param float64 `json:"param"`
}
// OS2LPeripheral contains the data of an OS2L peripheral
type OS2LPeripheral struct {
wg sync.WaitGroup
info PeripheralInfo // The basic info for this peripheral
serverIP string // OS2L server IP
serverPort int // OS2L server port
listener net.Listener // Net listener (TCP)
listenerCancel context.CancelFunc // Call this function to cancel the peripheral activation
}
// NewOS2LPeripheral creates a new OS2L peripheral
@@ -21,33 +38,126 @@ func NewOS2LPeripheral(peripheralData PeripheralInfo) (*OS2LPeripheral, error) {
return &OS2LPeripheral{
info: peripheralData,
serverIP: "127.0.0.1",
serverPort: 9005,
serverPort: 9995,
listener: nil,
}, nil
}
// Connect connects the MIDI peripheral
func (p *OS2LPeripheral) Connect(ctx context.Context) error {
var err error
addr := net.TCPAddr{Port: p.serverPort, IP: net.ParseIP(p.serverIP)}
p.listener, err = net.ListenTCP("tcp", &addr)
if err != nil {
return fmt.Errorf("unable to set the OS2L TCP listener")
}
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected")
return nil
}
time.Sleep(5 * time.Second)
func handleMessage(raw []byte) error {
message := OS2LMessage{}
err := json.Unmarshal(raw, &message)
if err != nil {
return fmt.Errorf("Unable to parse the OS2L message: %w", err)
}
// Display the message
fmt.Printf("Event: %s, Name: %s, State: %s, ID: %d, Param: %f\n", message.Event, message.Name, message.State, message.ID, message.Param)
return nil
}
// Disconnect disconnects the MIDI peripheral
func (p *OS2LPeripheral) Disconnect() error {
// Close the TCP listener if not null
if p.listener != nil {
p.listener.Close()
}
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral disconnected")
return nil
}
// Activate activates the MIDI peripheral
// Activate activates the OS2L peripheral
func (p *OS2LPeripheral) Activate(ctx context.Context) error {
// Create a derived context to handle deactivation
var listenerCtx context.Context
listenerCtx, p.listenerCancel = context.WithCancel(ctx)
if p.listener == nil {
return fmt.Errorf("the listener isn't defined")
}
p.wg.Add(1)
go func() {
defer p.wg.Done()
for {
select {
case <-listenerCtx.Done():
return
default:
p.listener.(*net.TCPListener).SetDeadline(time.Now().Add(1 * time.Second))
conn, err := p.listener.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue
}
log.Err(err).Str("file", "OS2LPeripheral").Msg("unable to accept the connection")
continue
}
// Every client is handled in a dedicated goroutine
p.wg.Add(1)
go func(c net.Conn) {
defer p.wg.Done()
defer c.Close()
buffer := make([]byte, 1024)
for {
select {
case <-listenerCtx.Done():
return
default:
c.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
n, err := c.Read(buffer)
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
// Lecture a expiré → vérifier si le contexte est annulé
select {
case <-listenerCtx.Done():
return
default:
continue // pas annulé → relancer Read
}
}
return // autre erreur ou EOF
}
handleMessage(buffer[:n])
}
}
}(conn)
}
}
}()
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral activated")
return nil
}
// Deactivate deactivates the MIDI peripheral
// Deactivate deactivates the OS2L peripheral
func (p *OS2LPeripheral) Deactivate(ctx context.Context) error {
if p.listener == nil {
return fmt.Errorf("the listener isn't defined")
}
// Cancel listener by context
if p.listenerCancel != nil {
p.listenerCancel()
}
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral deactivated")
return nil
}
@@ -102,5 +212,8 @@ func (p *OS2LPeripheral) GetInfo() PeripheralInfo {
// WaitStop stops the peripheral
func (p *OS2LPeripheral) WaitStop() error {
log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("waiting for OS2L peripheral to close...")
p.wg.Wait()
log.Info().Str("file", "OS2LPeripheral").Str("s/n", p.info.SerialNumber).Msg("OS2L peripheral closed!")
return nil
}