technischen Windows-Client erfolgreich mit Rot2Prog getestet
This commit is contained in:
parent
81b06a16a0
commit
39a1fea75d
17 changed files with 1154 additions and 255 deletions
44
cmd/rs2322tcp-client-test/main.go
Normal file
44
cmd/rs2322tcp-client-test/main.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.lang-dieter.de/rs2322tcp/internal/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
const configFile = "./configs/client.json"
|
||||||
|
|
||||||
|
application, err := client.NewApplication(configFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "NewApplication: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := application.Start(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Application.Start: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("rs2322tcp technical client started")
|
||||||
|
fmt.Println("Press Ctrl+C to stop.")
|
||||||
|
|
||||||
|
signals := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(
|
||||||
|
signals,
|
||||||
|
os.Interrupt,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
)
|
||||||
|
|
||||||
|
<-signals
|
||||||
|
|
||||||
|
fmt.Println("Stopping client...")
|
||||||
|
|
||||||
|
if err := application.Close(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Application.Close: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"server": {
|
"server": {
|
||||||
"address": "127.0.0.1",
|
"address": "100.64.0.1",
|
||||||
"port": 5000
|
"port": 5000
|
||||||
},
|
},
|
||||||
"virtual_port_range": {
|
"virtual_port_range": {
|
||||||
|
|
@ -9,12 +9,8 @@
|
||||||
},
|
},
|
||||||
"virtual_ports": [
|
"virtual_ports": [
|
||||||
{
|
{
|
||||||
"port": "/dev/ttyUSB100",
|
"port": "COM100",
|
||||||
"remote_device": "radio"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"port": "/dev/ttyUSB101",
|
|
||||||
"remote_device": "rotor"
|
"remote_device": "rotor"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -11,12 +11,7 @@
|
||||||
* und dem zugehörigen entfernten Gerät.
|
* und dem zugehörigen entfernten Gerät.
|
||||||
*
|
*
|
||||||
* Die konfigurierte lokale virtuelle Schnittstelle wird dabei exakt
|
* Die konfigurierte lokale virtuelle Schnittstelle wird dabei exakt
|
||||||
* übernommen. Eine Konfiguration wie
|
* übernommen.
|
||||||
*
|
|
||||||
* /dev/ttyUSB100 -> radio
|
|
||||||
*
|
|
||||||
* führt somit technisch auch zur Erzeugung beziehungsweise Verwendung von
|
|
||||||
* /dev/ttyUSB100 und nicht einfach zum nächsten freien Port.
|
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*/
|
*/
|
||||||
package client
|
package client
|
||||||
|
|
@ -24,53 +19,11 @@ package client
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.lang-dieter.de/rs2322tcp/internal/config"
|
"git.lang-dieter.de/rs2322tcp/internal/config"
|
||||||
"git.lang-dieter.de/rs2322tcp/internal/transport"
|
"git.lang-dieter.de/rs2322tcp/internal/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Virtual port configuration
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// virtualPortNumber converts a configured /dev/ttyUSBxxx path into its
|
|
||||||
// numeric virtual port number.
|
|
||||||
//
|
|
||||||
// The client configuration stores the complete Linux device path, while
|
|
||||||
// VirtualPortManager works with the numeric port number. Only the exact
|
|
||||||
// /dev/ttyUSB<number> format is accepted here.
|
|
||||||
func virtualPortNumber(path string) (int, error) {
|
|
||||||
const prefix = "/dev/ttyUSB"
|
|
||||||
|
|
||||||
if !strings.HasPrefix(path, prefix) {
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"invalid virtual port %q: expected /dev/ttyUSB<number>",
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
value := strings.TrimPrefix(path, prefix)
|
|
||||||
|
|
||||||
if value == "" {
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"invalid virtual port %q: port number is missing",
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
number, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"invalid virtual port %q: invalid port number",
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return number, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Virtual port connection
|
// Virtual port connection
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ func TestConnectVirtualPortNilClient(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
cfg := config.VirtualPortConfig{
|
cfg := config.VirtualPortConfig{
|
||||||
Port: "/dev/ttyUSB100",
|
Port: virtualPortPath(100),
|
||||||
RemoteDevice: "radio",
|
RemoteDevice: "radio",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ func TestConnectVirtualPortNilClient(t *testing.T) {
|
||||||
|
|
||||||
func TestConnectVirtualPortNilManager(t *testing.T) {
|
func TestConnectVirtualPortNilManager(t *testing.T) {
|
||||||
cfg := config.VirtualPortConfig{
|
cfg := config.VirtualPortConfig{
|
||||||
Port: "/dev/ttyUSB100",
|
Port: virtualPortPath(100),
|
||||||
RemoteDevice: "radio",
|
RemoteDevice: "radio",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,7 +209,7 @@ func TestConnectVirtualPortUnknownDevice(t *testing.T) {
|
||||||
client := newTestClient(t)
|
client := newTestClient(t)
|
||||||
|
|
||||||
cfg := config.VirtualPortConfig{
|
cfg := config.VirtualPortConfig{
|
||||||
Port: "/dev/ttyUSB100",
|
Port: virtualPortPath(100),
|
||||||
RemoteDevice: "unknown",
|
RemoteDevice: "unknown",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,7 +249,7 @@ func TestConnectVirtualPortClosesPortWhenDataConnectionFails(t *testing.T) {
|
||||||
client := newTestClient(t)
|
client := newTestClient(t)
|
||||||
|
|
||||||
cfg := config.VirtualPortConfig{
|
cfg := config.VirtualPortConfig{
|
||||||
Port: "/dev/ttyUSB100",
|
Port: virtualPortPath(100),
|
||||||
RemoteDevice: "radio",
|
RemoteDevice: "radio",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,7 +323,7 @@ func TestConnectVirtualPortSuccess(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
cfg := config.VirtualPortConfig{
|
cfg := config.VirtualPortConfig{
|
||||||
Port: "/dev/ttyUSB100",
|
Port: virtualPortPath(100),
|
||||||
RemoteDevice: "radio",
|
RemoteDevice: "radio",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,11 +348,11 @@ func TestConnectVirtualPortSuccess(t *testing.T) {
|
||||||
defer virtualPort.Close()
|
defer virtualPort.Close()
|
||||||
defer dataConn.Close()
|
defer dataConn.Close()
|
||||||
|
|
||||||
if virtualPort.Path() != "/dev/ttyUSB100" {
|
if virtualPort.Path() != virtualPortPath(100) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"virtual port path = %q, want %q",
|
"virtual port path = %q, want %q",
|
||||||
virtualPort.Path(),
|
virtualPort.Path(),
|
||||||
"/dev/ttyUSB100",
|
virtualPortPath(100),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,7 +386,7 @@ func TestConnectVirtualPortUsesConfiguredPort(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
cfg := config.VirtualPortConfig{
|
cfg := config.VirtualPortConfig{
|
||||||
Port: "/dev/ttyUSB150",
|
Port: virtualPortPath(100),
|
||||||
RemoteDevice: "radio",
|
RemoteDevice: "radio",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -411,24 +411,29 @@ func TestConnectVirtualPortUsesConfiguredPort(t *testing.T) {
|
||||||
defer virtualPort.Close()
|
defer virtualPort.Close()
|
||||||
defer dataConn.Close()
|
defer dataConn.Close()
|
||||||
|
|
||||||
if virtualPort.Path() != "/dev/ttyUSB150" {
|
if virtualPort.Path() != virtualPortPath(100) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"virtual port path = %q, want %q",
|
"virtual port path = %q, want %q",
|
||||||
virtualPort.Path(),
|
virtualPort.Path(),
|
||||||
"/dev/ttyUSB150",
|
virtualPortPath(100),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port 100 must still be available. This proves that the connection
|
// Port 100 is the configured port and therefore must be reserved.
|
||||||
// did not simply take the first free port from the configured range.
|
if !manager.used[100] {
|
||||||
|
t.Fatal("configured port 100 is not reserved")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port 101 must still be available. This proves that the connection
|
||||||
|
// did not simply take another free port.
|
||||||
number, err := manager.Reserve()
|
number, err := manager.Reserve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Reserve() after connection: %v", err)
|
t.Fatalf("Reserve() after connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if number != 100 {
|
if number != 101 {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"first free port after connection = %d, want 100",
|
"first free port after connection = %d, want 101",
|
||||||
number,
|
number,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -463,7 +468,7 @@ func TestConnectBridgeSuccess(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
cfg := config.VirtualPortConfig{
|
cfg := config.VirtualPortConfig{
|
||||||
Port: "/dev/ttyUSB100",
|
Port: virtualPortPath(100),
|
||||||
RemoteDevice: "radio",
|
RemoteDevice: "radio",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,11 +505,11 @@ func TestConnectBridgeSuccess(t *testing.T) {
|
||||||
t.Fatal("bridge TCP endpoint does not match data connection")
|
t.Fatal("bridge TCP endpoint does not match data connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
if virtualPort.Path() != "/dev/ttyUSB100" {
|
if virtualPort.Path() != virtualPortPath(100) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"virtual port path = %q, want %q",
|
"virtual port path = %q, want %q",
|
||||||
virtualPort.Path(),
|
virtualPort.Path(),
|
||||||
"/dev/ttyUSB100",
|
virtualPortPath(100),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,21 @@
|
||||||
* seriellen Ports.
|
* seriellen Ports.
|
||||||
*
|
*
|
||||||
* Der VirtualPortManager verwaltet den für rs2322tcp vorgesehenen Bereich
|
* Der VirtualPortManager verwaltet den für rs2322tcp vorgesehenen Bereich
|
||||||
* von /dev/ttyUSBxxx-Schnittstellen.
|
* virtueller serieller Schnittstellen.
|
||||||
|
*
|
||||||
|
* Die konkrete Darstellung eines virtuellen Ports ist plattformabhängig.
|
||||||
|
* Unter Linux werden beispielsweise /dev/ttyUSBxxx-Schnittstellen verwendet,
|
||||||
|
* während unter Windows COMxxx-Schnittstellen verwendet werden.
|
||||||
|
*
|
||||||
|
* Die eigentliche Erzeugung beziehungsweise Bereitstellung des seriellen
|
||||||
|
* Ports erfolgt ebenfalls plattformabhängig und ist nicht Bestandteil dieses
|
||||||
|
* Managers.
|
||||||
*
|
*
|
||||||
* Ports können entweder automatisch über den nächsten freien Port oder
|
* Ports können entweder automatisch über den nächsten freien Port oder
|
||||||
* gezielt über eine vorgegebene Portnummer reserviert werden.
|
* 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
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -37,12 +39,15 @@ import (
|
||||||
// VirtualPortManager
|
// VirtualPortManager
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// VirtualPortManager verwaltet den reservierten Bereich virtueller
|
// VirtualPortManager verwaltet den für rs2322tcp reservierten Bereich
|
||||||
// /dev/ttyUSBxxx-Schnittstellen.
|
// virtueller serieller Schnittstellen.
|
||||||
//
|
//
|
||||||
// Der reservierte Portbereich und die Belegungstabelle werden durch einen
|
// Der reservierte Portbereich und die Belegungstabelle werden durch einen
|
||||||
// Mutex geschützt, da mehrere Bridges beziehungsweise Goroutinen gleichzeitig
|
// Mutex geschützt, da mehrere Bridges beziehungsweise Goroutinen gleichzeitig
|
||||||
// virtuelle Ports öffnen und freigeben können.
|
// virtuelle Ports öffnen und freigeben können.
|
||||||
|
//
|
||||||
|
// Die konkrete Darstellung des sichtbaren Portnamens wird über
|
||||||
|
// virtualPortPath() von der jeweiligen Plattform bestimmt.
|
||||||
type VirtualPortManager struct {
|
type VirtualPortManager struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
|
@ -51,6 +56,10 @@ type VirtualPortManager struct {
|
||||||
used map[int]bool
|
used map[int]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// NewVirtualPortManager erzeugt einen VirtualPortManager aus dem
|
// NewVirtualPortManager erzeugt einen VirtualPortManager aus dem
|
||||||
// konfigurierten Portbereich.
|
// konfigurierten Portbereich.
|
||||||
func NewVirtualPortManager(
|
func NewVirtualPortManager(
|
||||||
|
|
@ -63,16 +72,32 @@ func NewVirtualPortManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Port path
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// PortPath erzeugt den für die externe Software sichtbaren Gerätenamen
|
// PortPath erzeugt den für die externe Software sichtbaren Gerätenamen
|
||||||
// für eine Portnummer.
|
// für eine Portnummer.
|
||||||
|
//
|
||||||
|
// Die konkrete Darstellung ist plattformabhängig:
|
||||||
|
//
|
||||||
|
// Linux /dev/ttyUSB100
|
||||||
|
// Windows COM100
|
||||||
|
//
|
||||||
|
// Die Plattformimplementierung befindet sich in
|
||||||
|
// virtual_port_path_linux.go beziehungsweise virtual_port_path_windows.go.
|
||||||
func (m *VirtualPortManager) PortPath(number int) string {
|
func (m *VirtualPortManager) PortPath(number int) string {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("/dev/ttyUSB%d", number)
|
return virtualPortPath(number)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Reservation
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Reserve reserviert den nächsten freien virtuellen Port.
|
// Reserve reserviert den nächsten freien virtuellen Port.
|
||||||
//
|
//
|
||||||
// Es wird immer mit dem kleinsten freien Port im konfigurierten Bereich
|
// Es wird immer mit dem kleinsten freien Port im konfigurierten Bereich
|
||||||
|
|
@ -141,6 +166,10 @@ func (m *VirtualPortManager) ReserveSpecific(number int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Release
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Release gibt einen zuvor reservierten Port wieder frei.
|
// Release gibt einen zuvor reservierten Port wieder frei.
|
||||||
func (m *VirtualPortManager) Release(number int) {
|
func (m *VirtualPortManager) Release(number int) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
|
|
|
||||||
163
internal/client/virtual_port_manager_open.go
Normal file
163
internal/client/virtual_port_manager_open.go
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_manager_open.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Plattformneutrale Verwaltung geöffneter virtueller Ports.
|
||||||
|
*
|
||||||
|
* Die konkrete Erzeugung eines virtuellen seriellen Ports erfolgt über
|
||||||
|
* openVirtualPort() in der jeweiligen Plattformimplementierung.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Managed virtual port
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// ManagedVirtualPort verbindet einen reservierten Port mit einer
|
||||||
|
// VirtualSerial-Instanz und verwaltet deren Lebenszyklus.
|
||||||
|
type ManagedVirtualPort struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
port *VirtualPort
|
||||||
|
manager *VirtualPortManager
|
||||||
|
number int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the device path visible to the external application.
|
||||||
|
func (p *ManagedVirtualPort) Path() string {
|
||||||
|
if p == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.port == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.port.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads data from the underlying virtual serial device.
|
||||||
|
func (p *ManagedVirtualPort) Read(b []byte) (int, error) {
|
||||||
|
if p == nil {
|
||||||
|
return 0, fmt.Errorf("managed virtual port is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
port := p.port
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if port == nil {
|
||||||
|
return 0, fmt.Errorf("managed virtual port is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return port.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes data to the underlying virtual serial device.
|
||||||
|
func (p *ManagedVirtualPort) Write(b []byte) (int, error) {
|
||||||
|
if p == nil {
|
||||||
|
return 0, fmt.Errorf("managed virtual port is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
port := p.port
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if port == nil {
|
||||||
|
return 0, fmt.Errorf("managed virtual port is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return port.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the managed virtual port and releases its reservation.
|
||||||
|
func (p *ManagedVirtualPort) Close() error {
|
||||||
|
if p == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
|
||||||
|
port := p.port
|
||||||
|
p.port = nil
|
||||||
|
|
||||||
|
manager := p.manager
|
||||||
|
number := p.number
|
||||||
|
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if port == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := port.Close()
|
||||||
|
|
||||||
|
if manager != nil {
|
||||||
|
manager.Release(number)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Open
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Open creates a new virtual serial port using the next free port number.
|
||||||
|
func (m *VirtualPortManager) Open() (*ManagedVirtualPort, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, fmt.Errorf("virtual port manager is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
number, err := m.Reserve()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := openVirtualPort(m, number)
|
||||||
|
if err != nil {
|
||||||
|
m.Release(number)
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenSpecific creates a new virtual serial port using exactly the specified
|
||||||
|
// port number.
|
||||||
|
func (m *VirtualPortManager) OpenSpecific(
|
||||||
|
number int,
|
||||||
|
) (*ManagedVirtualPort, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, fmt.Errorf("virtual port manager is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.ReserveSpecific(number); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := openVirtualPort(m, number)
|
||||||
|
if err != nil {
|
||||||
|
m.Release(number)
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
@ -107,8 +107,10 @@ func TestVirtualPortManagerRelease(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if reused != 100 {
|
if reused != 100 {
|
||||||
t.Errorf("Reserve() after Release() = %d, want 100",
|
t.Errorf(
|
||||||
reused)
|
"Reserve() after Release() = %d, want 100",
|
||||||
|
reused,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,15 +181,15 @@ func TestVirtualPortManagerPortPath(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
number: 100,
|
number: 100,
|
||||||
want: "/dev/ttyUSB100",
|
want: virtualPortPath(100),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
number: 123,
|
number: 123,
|
||||||
want: "/dev/ttyUSB123",
|
want: virtualPortPath(123),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
number: 199,
|
number: 199,
|
||||||
want: "/dev/ttyUSB199",
|
want: virtualPortPath(199),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
59
internal/client/virtual_port_number_linux.go
Normal file
59
internal/client/virtual_port_number_linux.go
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_number_linux.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Linux-spezifische Umwandlung eines konfigurierten virtuellen
|
||||||
|
* seriellen Ports in dessen numerische Portnummer.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Virtual port number
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// virtualPortNumber converts a configured /dev/ttyUSBxxx path into its
|
||||||
|
// numeric virtual port number.
|
||||||
|
func virtualPortNumber(path string) (int, error) {
|
||||||
|
const prefix = "/dev/ttyUSB"
|
||||||
|
|
||||||
|
if !strings.HasPrefix(path, prefix) {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"invalid virtual port %q: expected /dev/ttyUSB<number>",
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := strings.TrimPrefix(path, prefix)
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"invalid virtual port %q: port number is missing",
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
number, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"invalid virtual port %q: invalid port number",
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return number, nil
|
||||||
|
}
|
||||||
62
internal/client/virtual_port_number_windows.go
Normal file
62
internal/client/virtual_port_number_windows.go
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_number_windows.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Windows-spezifische Umwandlung eines konfigurierten virtuellen
|
||||||
|
* seriellen COM-Ports in dessen numerische Portnummer.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Virtual port number
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// virtualPortNumber converts a configured COMxxx path into its numeric
|
||||||
|
// virtual port number.
|
||||||
|
func virtualPortNumber(path string) (int, error) {
|
||||||
|
const prefix = "COM"
|
||||||
|
|
||||||
|
if !strings.HasPrefix(
|
||||||
|
strings.ToUpper(path),
|
||||||
|
prefix,
|
||||||
|
) {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"invalid virtual port %q: expected COM<number>",
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := path[len(prefix):]
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"invalid virtual port %q: port number is missing",
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
number, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"invalid virtual port %q: invalid port number",
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return number, nil
|
||||||
|
}
|
||||||
77
internal/client/virtual_port_open_linux.go
Normal file
77
internal/client/virtual_port_open_linux.go
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_open_linux.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Linux-spezifische Erzeugung eines verwalteten virtuellen rs2322tcp-Ports.
|
||||||
|
*
|
||||||
|
* Die virtuelle serielle Schnittstelle wird über ein PTY-Paar erzeugt.
|
||||||
|
* Der öffentliche /dev/ttyUSBxxx-Eintrag wird über den bereits vorhandenen
|
||||||
|
* internen Link auf den PTY-Slave abgebildet.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Open virtual port
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// openVirtualPort creates and initializes a Linux virtual serial port.
|
||||||
|
//
|
||||||
|
// The port number must already have been reserved by the
|
||||||
|
// VirtualPortManager.
|
||||||
|
//
|
||||||
|
// On failure, the caller remains responsible for releasing the reservation.
|
||||||
|
func openVirtualPort(
|
||||||
|
manager *VirtualPortManager,
|
||||||
|
number int,
|
||||||
|
) (*ManagedVirtualPort, error) {
|
||||||
|
if manager == nil {
|
||||||
|
return nil, fmt.Errorf("virtual port manager is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
portPath := manager.PortPath(number)
|
||||||
|
|
||||||
|
serial, err := newVirtualSerial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"create virtual serial for %s: %w",
|
||||||
|
portPath,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setVirtualPortLink(
|
||||||
|
portPath,
|
||||||
|
serial.Path(),
|
||||||
|
); err != nil {
|
||||||
|
_ = serial.Close()
|
||||||
|
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"bind %s to %s: %w",
|
||||||
|
portPath,
|
||||||
|
serial.Path(),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
port := NewVirtualPort(
|
||||||
|
portPath,
|
||||||
|
serial,
|
||||||
|
)
|
||||||
|
|
||||||
|
return &ManagedVirtualPort{
|
||||||
|
port: port,
|
||||||
|
manager: manager,
|
||||||
|
number: number,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
84
internal/client/virtual_port_open_windows.go
Normal file
84
internal/client/virtual_port_open_windows.go
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_open_windows.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Windows-spezifische Erzeugung eines verwalteten virtuellen rs2322tcp-Ports.
|
||||||
|
*
|
||||||
|
* Der konfigurierte virtuelle COM-Port wird über die bereits vorhandene
|
||||||
|
* serielle Infrastruktur geöffnet.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
bugserial "go.bug.st/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Open virtual port
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// openVirtualPort creates and initializes a Windows virtual serial port.
|
||||||
|
//
|
||||||
|
// The port number must already have been reserved by the
|
||||||
|
// VirtualPortManager.
|
||||||
|
//
|
||||||
|
// The visible COM port is provided by the platform-specific PortPath()
|
||||||
|
// implementation. The actual COM-port pair configuration is managed
|
||||||
|
// externally by the Windows virtual COM-port driver.
|
||||||
|
func openVirtualPort(
|
||||||
|
manager *VirtualPortManager,
|
||||||
|
number int,
|
||||||
|
) (*ManagedVirtualPort, error) {
|
||||||
|
if manager == nil {
|
||||||
|
return nil, fmt.Errorf("virtual port manager is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
portPath := manager.PortPath(number)
|
||||||
|
if portPath == "" {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"virtual port path is empty for port %d",
|
||||||
|
number,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := &bugserial.Mode{
|
||||||
|
BaudRate: 9600,
|
||||||
|
DataBits: 8,
|
||||||
|
Parity: bugserial.NoParity,
|
||||||
|
StopBits: bugserial.OneStopBit,
|
||||||
|
}
|
||||||
|
|
||||||
|
serial, err := bugserial.Open(portPath, mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"open virtual serial port %s: %w",
|
||||||
|
portPath,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualPort := NewVirtualPort(
|
||||||
|
portPath,
|
||||||
|
&windowsSerial{
|
||||||
|
port: serial,
|
||||||
|
path: portPath,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return &ManagedVirtualPort{
|
||||||
|
port: virtualPort,
|
||||||
|
manager: manager,
|
||||||
|
number: number,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
43
internal/client/virtual_port_path_linux.go
Normal file
43
internal/client/virtual_port_path_linux.go
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_path_linux.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Linux-spezifische Darstellung eines für rs2322tcp reservierten
|
||||||
|
* virtuellen seriellen Ports.
|
||||||
|
*
|
||||||
|
* Unter Linux werden die für rs2322tcp sichtbaren virtuellen Ports als
|
||||||
|
*
|
||||||
|
* /dev/ttyUSBxxx
|
||||||
|
*
|
||||||
|
* dargestellt.
|
||||||
|
*
|
||||||
|
* Die eigentliche Bereitstellung des virtuellen seriellen Ports erfolgt
|
||||||
|
* unabhängig davon in der Linux-spezifischen VirtualSerial-Implementierung.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Virtual port path
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// virtualPortPath erzeugt den unter Linux sichtbaren Gerätenamen für eine
|
||||||
|
// virtuelle rs2322tcp-Schnittstelle.
|
||||||
|
//
|
||||||
|
// Beispiel:
|
||||||
|
//
|
||||||
|
// number = 100
|
||||||
|
// result = /dev/ttyUSB100
|
||||||
|
func virtualPortPath(number int) string {
|
||||||
|
return fmt.Sprintf("/dev/ttyUSB%d", number)
|
||||||
|
}
|
||||||
48
internal/client/virtual_port_path_windows.go
Normal file
48
internal/client/virtual_port_path_windows.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_path_windows.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Windows-spezifische Darstellung eines für rs2322tcp reservierten
|
||||||
|
* virtuellen seriellen Ports.
|
||||||
|
*
|
||||||
|
* Unter Windows werden die für rs2322tcp sichtbaren virtuellen Ports als
|
||||||
|
*
|
||||||
|
* COMxxx
|
||||||
|
*
|
||||||
|
* dargestellt.
|
||||||
|
*
|
||||||
|
* Die eigentliche Bereitstellung des virtuellen COM-Ports erfolgt durch die
|
||||||
|
* Windows-spezifische serielle Infrastruktur beziehungsweise später durch
|
||||||
|
* den für rs2322tcp vorgesehenen virtuellen COM-Port-Treiber.
|
||||||
|
*
|
||||||
|
* Diese Datei enthält ausschließlich die plattformabhängige Darstellung des
|
||||||
|
* Portnamens. Die gemeinsame Portverwaltung bleibt in
|
||||||
|
* virtual_port_manager.go erhalten.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Virtual port path
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// virtualPortPath erzeugt den unter Windows sichtbaren Gerätenamen für eine
|
||||||
|
// virtuelle rs2322tcp-Schnittstelle.
|
||||||
|
//
|
||||||
|
// Beispiel:
|
||||||
|
//
|
||||||
|
// number = 10
|
||||||
|
// result = COM10
|
||||||
|
func virtualPortPath(number int) string {
|
||||||
|
return fmt.Sprintf("COM%d", number)
|
||||||
|
}
|
||||||
|
|
@ -7,13 +7,22 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* Beschreibung:
|
* Beschreibung:
|
||||||
* Vergleich der in client.json definierten virtuellen Ports mit den
|
* Plattformneutrale Prüfung der in client.json definierten virtuellen Ports
|
||||||
* tatsächlich vorhandenen virtuellen Schnittstellen.
|
* gegen die tatsächlich vorhandenen virtuellen Schnittstellen.
|
||||||
|
*
|
||||||
|
* Die Ermittlung der vorhandenen Ports erfolgt über die
|
||||||
|
* plattformabhängige Funktion existingVirtualPortLinks().
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*/
|
*/
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import "sort"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.lang-dieter.de/rs2322tcp/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Virtual port validation
|
// Virtual port validation
|
||||||
|
|
@ -62,10 +71,7 @@ func compareVirtualPorts(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Missing = append(
|
result.Missing = append(result.Missing, port)
|
||||||
result.Missing,
|
|
||||||
port,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for port := range availableSet {
|
for port := range availableSet {
|
||||||
|
|
@ -73,10 +79,7 @@ func compareVirtualPorts(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Extra = append(
|
result.Extra = append(result.Extra, port)
|
||||||
result.Extra,
|
|
||||||
port,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(result.Missing)
|
sort.Strings(result.Missing)
|
||||||
|
|
@ -84,3 +87,166 @@ func compareVirtualPorts(
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Local virtual-port validation
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// validateLocalVirtualPorts compares the virtual ports defined in
|
||||||
|
// client.json with the virtual ports currently available on the local
|
||||||
|
// computer.
|
||||||
|
//
|
||||||
|
// The detection of available ports is performed by the platform-specific
|
||||||
|
// existingVirtualPortLinks() implementation.
|
||||||
|
//
|
||||||
|
// A mismatch prevents the client from starting.
|
||||||
|
func validateLocalVirtualPorts(
|
||||||
|
cfg *config.ClientConfig,
|
||||||
|
) error {
|
||||||
|
if cfg == nil {
|
||||||
|
return fmt.Errorf("client configuration is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
configured := make(
|
||||||
|
[]string,
|
||||||
|
0,
|
||||||
|
len(cfg.VirtualPorts),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, virtualPort := range cfg.VirtualPorts {
|
||||||
|
configured = append(
|
||||||
|
configured,
|
||||||
|
virtualPort.Port,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
available, err := existingVirtualPortLinks(
|
||||||
|
cfg.VirtualPortRange,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"prüfen der virtuellen Ports fehlgeschlagen: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := compareVirtualPorts(
|
||||||
|
configured,
|
||||||
|
available,
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(result.Missing) == 0 &&
|
||||||
|
len(result.Extra) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var message strings.Builder
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Missing ports
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
if len(result.Missing) > 0 &&
|
||||||
|
len(result.Extra) == 0 {
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"Virtuelle Ports nicht eingerichtet",
|
||||||
|
)
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"\n\nFolgende Ports sind in client.json " +
|
||||||
|
"eingetragen, aber auf diesem Rechner " +
|
||||||
|
"nicht eingerichtet:\n\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, port := range result.Missing {
|
||||||
|
message.WriteString(" ")
|
||||||
|
message.WriteString(port)
|
||||||
|
message.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"\nBitte richten Sie diese virtuellen Ports " +
|
||||||
|
"entsprechend der Installationsanleitung ein.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s",
|
||||||
|
strings.TrimSpace(message.String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Extra ports
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
if len(result.Missing) == 0 &&
|
||||||
|
len(result.Extra) > 0 {
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"Virtuelle Ports nicht konfiguriert",
|
||||||
|
)
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"\n\nFolgende virtuelle Ports sind auf diesem " +
|
||||||
|
"Rechner eingerichtet, aber nicht in " +
|
||||||
|
"client.json eingetragen:\n\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, port := range result.Extra {
|
||||||
|
message.WriteString(" ")
|
||||||
|
message.WriteString(port)
|
||||||
|
message.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"\nBitte ergänzen Sie die entsprechenden " +
|
||||||
|
"Einträge in client.json.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s",
|
||||||
|
strings.TrimSpace(message.String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Missing and extra ports
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"Virtuelle Portkonfiguration stimmt nicht überein",
|
||||||
|
)
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"\n\nIn client.json eingetragen, aber nicht " +
|
||||||
|
"eingerichtet:\n\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, port := range result.Missing {
|
||||||
|
message.WriteString(" ")
|
||||||
|
message.WriteString(port)
|
||||||
|
message.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"\nEingerichtet, aber nicht in client.json " +
|
||||||
|
"eingetragen:\n\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, port := range result.Extra {
|
||||||
|
message.WriteString(" ")
|
||||||
|
message.WriteString(port)
|
||||||
|
message.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
message.WriteString(
|
||||||
|
"\nBitte korrigieren Sie die virtuelle " +
|
||||||
|
"Portkonfiguration.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s",
|
||||||
|
strings.TrimSpace(message.String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,176 +9,98 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* Beschreibung:
|
* Beschreibung:
|
||||||
* Linux-spezifische Prüfung der lokal vorhandenen virtuellen
|
* Linux-spezifische Ermittlung der lokal vorhandenen virtuellen
|
||||||
* rs2322tcp-Schnittstellen gegen die Client-Konfiguration.
|
* rs2322tcp-Schnittstellen.
|
||||||
|
*
|
||||||
|
* Die öffentliche /dev/ttyUSBxxx-Schnittstelle wird ausschließlich gelesen.
|
||||||
|
* Der Client verändert diese Schnittstellen hier nicht.
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*/
|
*/
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"git.lang-dieter.de/rs2322tcp/internal/config"
|
"git.lang-dieter.de/rs2322tcp/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Local virtual-port validation
|
// Existing virtual ports
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// validateLocalVirtualPorts compares the virtual ports defined in
|
// existingVirtualPortLinks returns all public virtual-port paths that
|
||||||
// client.json with the public symbolic links currently available on Linux.
|
// currently exist as symbolic links.
|
||||||
//
|
//
|
||||||
// The check is read-only and does not require root privileges.
|
// Only ports inside the configured virtual-port range are considered.
|
||||||
|
// The public /dev/ttyUSBxxx links are only inspected; they are never
|
||||||
|
// created, changed, or removed by this function.
|
||||||
//
|
//
|
||||||
// A mismatch prevents the client from starting.
|
// This check only requires read access and therefore does not require
|
||||||
func validateLocalVirtualPorts(
|
// root privileges.
|
||||||
cfg *config.ClientConfig,
|
func existingVirtualPortLinks(
|
||||||
) error {
|
cfg config.VirtualPortRangeConfig,
|
||||||
if cfg == nil {
|
) ([]string, error) {
|
||||||
return fmt.Errorf("client configuration is nil")
|
return existingVirtualPortLinksInDirectory(
|
||||||
}
|
cfg,
|
||||||
|
"/dev",
|
||||||
configured := make(
|
|
||||||
[]string,
|
|
||||||
0,
|
|
||||||
len(cfg.VirtualPorts),
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, virtualPort := range cfg.VirtualPorts {
|
|
||||||
configured = append(
|
|
||||||
configured,
|
|
||||||
virtualPort.Port,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
available, err := existingVirtualPortLinks(
|
|
||||||
cfg.VirtualPortRange,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"prüfen der virtuellen Ports fehlgeschlagen: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := compareVirtualPorts(
|
|
||||||
configured,
|
|
||||||
available,
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(result.Missing) == 0 &&
|
|
||||||
len(result.Extra) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var message strings.Builder
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Missing ports
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
if len(result.Missing) > 0 &&
|
|
||||||
len(result.Extra) == 0 {
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"Virtuelle Ports nicht eingerichtet",
|
|
||||||
)
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"\n\nFolgende Ports sind in client.json " +
|
|
||||||
"eingetragen, aber auf diesem Rechner " +
|
|
||||||
"nicht eingerichtet:\n\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, port := range result.Missing {
|
|
||||||
message.WriteString(" ")
|
|
||||||
message.WriteString(port)
|
|
||||||
message.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"\nBitte richten Sie diese virtuellen Ports " +
|
|
||||||
"entsprechend der Installationsanleitung ein.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return fmt.Errorf(
|
|
||||||
"%s",
|
|
||||||
strings.TrimSpace(message.String()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Extra ports
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
if len(result.Missing) == 0 &&
|
|
||||||
len(result.Extra) > 0 {
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"Virtuelle Ports nicht konfiguriert",
|
|
||||||
)
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"\n\nFolgende virtuelle Ports sind auf diesem " +
|
|
||||||
"Rechner eingerichtet, aber nicht in " +
|
|
||||||
"client.json eingetragen:\n\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, port := range result.Extra {
|
|
||||||
message.WriteString(" ")
|
|
||||||
message.WriteString(port)
|
|
||||||
message.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"\nBitte ergänzen Sie die entsprechenden " +
|
|
||||||
"Einträge in client.json.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return fmt.Errorf(
|
|
||||||
"%s",
|
|
||||||
strings.TrimSpace(message.String()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Missing and extra ports
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"Virtuelle Portkonfiguration stimmt nicht überein",
|
|
||||||
)
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"\n\nIn client.json eingetragen, aber nicht " +
|
|
||||||
"eingerichtet:\n\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, port := range result.Missing {
|
|
||||||
message.WriteString(" ")
|
|
||||||
message.WriteString(port)
|
|
||||||
message.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"\nEingerichtet, aber nicht in client.json " +
|
|
||||||
"eingetragen:\n\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, port := range result.Extra {
|
|
||||||
message.WriteString(" ")
|
|
||||||
message.WriteString(port)
|
|
||||||
message.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
message.WriteString(
|
|
||||||
"\nBitte korrigieren Sie die virtuelle " +
|
|
||||||
"Portkonfiguration.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return fmt.Errorf(
|
|
||||||
"%s",
|
|
||||||
strings.TrimSpace(message.String()),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Existing virtual ports in directory
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// existingVirtualPortLinksInDirectory returns all virtual-port paths in
|
||||||
|
// directory that currently exist as symbolic links.
|
||||||
|
//
|
||||||
|
// This helper is deliberately separated from existingVirtualPortLinks so
|
||||||
|
// the filesystem inspection can be tested without modifying /dev.
|
||||||
|
func existingVirtualPortLinksInDirectory(
|
||||||
|
cfg config.VirtualPortRangeConfig,
|
||||||
|
directory string,
|
||||||
|
) ([]string, error) {
|
||||||
|
if directory == "" {
|
||||||
|
return nil, fmt.Errorf("virtual port directory is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := NewVirtualPortManager(cfg)
|
||||||
|
if manager == nil {
|
||||||
|
return nil, fmt.Errorf("virtual port manager is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
links := make([]string, 0)
|
||||||
|
|
||||||
|
for number := cfg.First; number <= cfg.Last; number++ {
|
||||||
|
portName := filepath.Base(
|
||||||
|
manager.PortPath(number),
|
||||||
|
)
|
||||||
|
|
||||||
|
portPath := filepath.Join(directory, portName)
|
||||||
|
|
||||||
|
info, err := os.Lstat(portPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"inspect virtual port %q: %w",
|
||||||
|
portPath,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Mode()&os.ModeSymlink == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
links = append(
|
||||||
|
links,
|
||||||
|
filepath.Join(directory, portName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return links, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
132
internal/client/virtual_port_validation_windows.go
Normal file
132
internal/client/virtual_port_validation_windows.go
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_port_validation_windows.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Windows-spezifische Ermittlung der lokal vorhandenen seriellen
|
||||||
|
* Schnittstellen für rs2322tcp.
|
||||||
|
*
|
||||||
|
* Unter Windows werden virtuelle Ports als COMxxx-Schnittstellen
|
||||||
|
* bereitgestellt.
|
||||||
|
*
|
||||||
|
* Diese Implementierung prüft den konfigurierten Portbereich und liefert
|
||||||
|
* die tatsächlich vorhandenen COM-Schnittstellen zurück.
|
||||||
|
*
|
||||||
|
* Die Funktion verändert keine Geräte und installiert keine Treiber.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
|
||||||
|
"git.lang-dieter.de/rs2322tcp/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Existing virtual ports
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// existingVirtualPortLinks returns all COM ports in the configured range
|
||||||
|
// that are currently registered as serial ports in Windows.
|
||||||
|
//
|
||||||
|
// The Windows serial-port information is obtained from the Plug-and-Play
|
||||||
|
// device registry. No device is created, modified, or removed.
|
||||||
|
func existingVirtualPortLinks(
|
||||||
|
cfg config.VirtualPortRangeConfig,
|
||||||
|
) ([]string, error) {
|
||||||
|
key, err := registry.OpenKey(
|
||||||
|
registry.LOCAL_MACHINE,
|
||||||
|
`HARDWARE\DEVICEMAP\SERIALCOMM`,
|
||||||
|
registry.READ,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"open Windows serial-port registry: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
|
||||||
|
values, err := key.ReadValueNames(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"read Windows serial-port registry: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
available := make([]string, 0)
|
||||||
|
|
||||||
|
for _, valueName := range values {
|
||||||
|
portName, _, err := key.GetStringValue(valueName)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
number, ok := windowsCOMPortNumber(portName)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if number < cfg.First || number > cfg.Last {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
available = append(
|
||||||
|
available,
|
||||||
|
portName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return available, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// COM port number
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// windowsCOMPortNumber extracts the numeric part of a Windows COM port name.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// COM1 -> 1
|
||||||
|
// COM10 -> 10
|
||||||
|
//
|
||||||
|
// The comparison is case-insensitive.
|
||||||
|
func windowsCOMPortNumber(portName string) (int, bool) {
|
||||||
|
if len(portName) < 4 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portName[0] != 'C' && portName[0] != 'c') ||
|
||||||
|
(portName[1] != 'O' && portName[1] != 'o') ||
|
||||||
|
(portName[2] != 'M' && portName[2] != 'm') {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
number := 0
|
||||||
|
|
||||||
|
for _, char := range portName[3:] {
|
||||||
|
if char < '0' || char > '9' {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
number = number*10 + int(char-'0')
|
||||||
|
}
|
||||||
|
|
||||||
|
if number == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return number, true
|
||||||
|
}
|
||||||
114
internal/client/virtual_serial_windows.go
Normal file
114
internal/client/virtual_serial_windows.go
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: internal/client/virtual_serial_windows.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Windows-spezifische Implementierung der virtuellen seriellen Schnittstelle
|
||||||
|
* über go.bug.st/serial.
|
||||||
|
*
|
||||||
|
* Der konfigurierte COM-Port wird direkt geöffnet. Die Zuordnung eines
|
||||||
|
* virtuellen COM-Port-Paares erfolgt außerhalb des rs2322tcp-Clients.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
bugserial "go.bug.st/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// windowsSerial
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// windowsSerial implements VirtualSerial using a Windows COM port.
|
||||||
|
type windowsSerial struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
port bugserial.Port
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Path
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Path returns the Windows COM-port name.
|
||||||
|
func (p *windowsSerial) Path() string {
|
||||||
|
if p == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.path
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Read / Write
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Read reads raw bytes from the COM port.
|
||||||
|
func (p *windowsSerial) Read(b []byte) (int, error) {
|
||||||
|
if p == nil {
|
||||||
|
return 0, fmt.Errorf("virtual serial port is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
port := p.port
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if port == nil {
|
||||||
|
return 0, fmt.Errorf("virtual serial port is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return port.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes raw bytes to the COM port.
|
||||||
|
func (p *windowsSerial) Write(b []byte) (int, error) {
|
||||||
|
if p == nil {
|
||||||
|
return 0, fmt.Errorf("virtual serial port is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
port := p.port
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if port == nil {
|
||||||
|
return 0, fmt.Errorf("virtual serial port is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return port.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Close
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Close closes the Windows COM port.
|
||||||
|
func (p *windowsSerial) Close() error {
|
||||||
|
if p == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
|
||||||
|
if p.port == nil {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
port := p.port
|
||||||
|
p.port = nil
|
||||||
|
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
return port.Close()
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue