rs2322tcp/internal/client/virtual_port_manager.go
2026-08-11 11:29:49 +02:00

154 lines
3.7 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: internal/client/virtual_port_manager.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Verwaltung und Auswahl der für rs2322tcp reservierten virtuellen
* seriellen Ports.
*
* Der VirtualPortManager verwaltet den für rs2322tcp vorgesehenen Bereich
* von /dev/ttyUSBxxx-Schnittstellen.
*
* Ports können entweder automatisch über den nächsten freien Port oder
* gezielt über eine vorgegebene Portnummer reserviert werden.
*
* Die gezielte Reservierung ist insbesondere für die aus client.json
* geladene Konfiguration erforderlich. Dadurch bleibt eine Zuordnung wie
*
* /dev/ttyUSB100 -> radio
*
* auch technisch exakt erhalten.
* ============================================================================
*/
package client
import (
"fmt"
"sync"
"git.lang-dieter.de/rs2322tcp/internal/config"
)
///////////////////////////////////////////////////////////////////////////////
// VirtualPortManager
///////////////////////////////////////////////////////////////////////////////
// VirtualPortManager verwaltet den reservierten Bereich virtueller
// /dev/ttyUSBxxx-Schnittstellen.
//
// Der reservierte Portbereich und die Belegungstabelle werden durch einen
// Mutex geschützt, da mehrere Bridges beziehungsweise Goroutinen gleichzeitig
// virtuelle Ports öffnen und freigeben können.
type VirtualPortManager struct {
mu sync.Mutex
first int
last int
used map[int]bool
}
// NewVirtualPortManager erzeugt einen VirtualPortManager aus dem
// konfigurierten Portbereich.
func NewVirtualPortManager(
cfg config.VirtualPortRangeConfig,
) *VirtualPortManager {
return &VirtualPortManager{
first: cfg.First,
last: cfg.Last,
used: make(map[int]bool),
}
}
// PortPath erzeugt den für die externe Software sichtbaren Gerätenamen
// für eine Portnummer.
func (m *VirtualPortManager) PortPath(number int) string {
if m == nil {
return ""
}
return fmt.Sprintf("/dev/ttyUSB%d", number)
}
// Reserve reserviert den nächsten freien virtuellen Port.
//
// Es wird immer mit dem kleinsten freien Port im konfigurierten Bereich
// begonnen.
//
// Diese Methode bleibt für Fälle erhalten, in denen keine bestimmte
// Portnummer vorgegeben ist.
func (m *VirtualPortManager) Reserve() (int, error) {
if m == nil {
return 0, fmt.Errorf("virtual port manager is nil")
}
m.mu.Lock()
defer m.mu.Unlock()
for number := m.first; number <= m.last; number++ {
if m.used[number] {
continue
}
m.used[number] = true
return number, nil
}
return 0, fmt.Errorf(
"no free virtual port in range %d-%d",
m.first,
m.last,
)
}
// ReserveSpecific reserviert exakt den angegebenen virtuellen Port.
//
// Der Port muss innerhalb des konfigurierten Bereichs liegen und darf
// momentan nicht bereits reserviert sein.
//
// Diese Methode wird für konfigurierte Zuordnungen verwendet, bei denen
// die Portnummer aus client.json verbindlich ist.
func (m *VirtualPortManager) ReserveSpecific(number int) error {
if m == nil {
return fmt.Errorf("virtual port manager is nil")
}
m.mu.Lock()
defer m.mu.Unlock()
if number < m.first || number > m.last {
return fmt.Errorf(
"virtual port %d is outside configured range %d-%d",
number,
m.first,
m.last,
)
}
if m.used[number] {
return fmt.Errorf(
"virtual port %d is already reserved",
number,
)
}
m.used[number] = true
return nil
}
// Release gibt einen zuvor reservierten Port wieder frei.
func (m *VirtualPortManager) Release(number int) {
if m == nil {
return
}
m.mu.Lock()
defer m.mu.Unlock()
delete(m.used, number)
}