220 lines
5 KiB
Go
220 lines
5 KiB
Go
/*
|
|
* ============================================================================
|
|
* Projekt.....: rs2322tcp
|
|
* Datei.......: internal/client/runtime.go
|
|
* Copyright (C) 2026 Dieter Lang
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* Beschreibung:
|
|
* Clientseitige Laufzeitverwaltung für die konfigurierten virtuellen
|
|
* seriellen Schnittstellen.
|
|
*
|
|
* Die Runtime verbindet die in der Client-Konfiguration angegebenen
|
|
* virtuellen Ports mit den zugehörigen entfernten Geräten.
|
|
*
|
|
* Für jedes konfigurierte virtuelle Gerät wird eine eigene Bridge gestartet:
|
|
*
|
|
* /dev/ttyUSBxxx ─── Bridge ─── TCP Data Connection
|
|
*
|
|
* Die Control-Verbindung zum Server wird gemeinsam von allen Bridges
|
|
* verwendet.
|
|
* ============================================================================
|
|
*/
|
|
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"git.lang-dieter.de/rs2322tcp/internal/config"
|
|
)
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Runtime
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Runtime manages the client-side virtual serial connections.
|
|
//
|
|
// A Runtime owns the Client control connection and the VirtualPortManager.
|
|
// Each configured virtual port is represented by one running Bridge.
|
|
type Runtime struct {
|
|
client *Client
|
|
manager *VirtualPortManager
|
|
|
|
mu sync.Mutex
|
|
bridges []*Bridge
|
|
}
|
|
|
|
// NewRuntime creates a new client Runtime.
|
|
//
|
|
// The Runtime does not establish a server connection and does not open any
|
|
// virtual ports until Run is called.
|
|
func NewRuntime(
|
|
client *Client,
|
|
manager *VirtualPortManager,
|
|
) (*Runtime, error) {
|
|
if client == nil {
|
|
return nil, fmt.Errorf("client is nil")
|
|
}
|
|
|
|
if manager == nil {
|
|
return nil, fmt.Errorf("virtual port manager is nil")
|
|
}
|
|
|
|
return &Runtime{
|
|
client: client,
|
|
manager: manager,
|
|
}, nil
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Run
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Run starts all configured virtual serial connections.
|
|
//
|
|
// The supplied configuration determines which local virtual ports are
|
|
// assigned to which remote devices.
|
|
//
|
|
// Run blocks until one bridge terminates or an error occurs while creating
|
|
// one of the configured bridges.
|
|
func (r *Runtime) Run(
|
|
cfg config.ClientConfig,
|
|
) error {
|
|
if r == nil || r.client == nil {
|
|
return fmt.Errorf("runtime is not initialized")
|
|
}
|
|
|
|
if r.manager == nil {
|
|
return fmt.Errorf("virtual port manager is nil")
|
|
}
|
|
|
|
if len(cfg.VirtualPorts) == 0 {
|
|
return nil
|
|
}
|
|
devices, err := r.client.GetDevices()
|
|
if err != nil {
|
|
return fmt.Errorf("get remote devices: %w", err)
|
|
}
|
|
|
|
for _, virtualPortConfig := range cfg.VirtualPorts {
|
|
if virtualPortConfig.RemoteDevice == "" {
|
|
continue
|
|
}
|
|
|
|
bridge, _, _, err := connectBridge(
|
|
r.client,
|
|
r.manager,
|
|
virtualPortConfig,
|
|
devices,
|
|
)
|
|
if err != nil {
|
|
r.Close()
|
|
|
|
return err
|
|
}
|
|
|
|
r.mu.Lock()
|
|
r.bridges = append(r.bridges, bridge)
|
|
r.mu.Unlock()
|
|
}
|
|
|
|
return r.waitForBridge()
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Bridge lifecycle
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// waitForBridge waits until one of the running bridges terminates.
|
|
//
|
|
// A bridge terminating is considered the end of the Runtime. The Bridge
|
|
// itself closes its serial and TCP endpoints when Run returns.
|
|
func (r *Runtime) waitForBridge() error {
|
|
results := make(chan error, len(r.bridges))
|
|
|
|
r.mu.Lock()
|
|
bridges := append([]*Bridge(nil), r.bridges...)
|
|
r.mu.Unlock()
|
|
|
|
for _, bridge := range bridges {
|
|
go func(b *Bridge) {
|
|
results <- b.Run()
|
|
}(bridge)
|
|
}
|
|
|
|
err := <-results
|
|
|
|
r.Close()
|
|
|
|
return err
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Close
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Close closes all resources owned by the Runtime.
|
|
//
|
|
// Closing the bridge endpoints causes running Bridge.Run calls to terminate.
|
|
// The client control connection is closed afterwards.
|
|
//
|
|
// Calling Close more than once is safe.
|
|
|
|
func (r *Runtime) Close() error {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
r.mu.Lock()
|
|
bridges := append([]*Bridge(nil), r.bridges...)
|
|
r.bridges = nil
|
|
r.mu.Unlock()
|
|
|
|
var firstErr error
|
|
|
|
for _, bridge := range bridges {
|
|
if bridge == nil {
|
|
continue
|
|
}
|
|
|
|
if bridge.serial != nil {
|
|
if err := bridge.serial.Close(); err != nil && firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
|
|
if bridge.conn != nil {
|
|
if err := bridge.conn.Close(); err != nil && firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
}
|
|
|
|
if r.client != nil {
|
|
if err := r.client.Close(); err != nil && firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
|
|
return firstErr
|
|
}
|
|
|
|
// Client returns the Client used by the Runtime.
|
|
func (r *Runtime) Client() *Client {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
return r.client
|
|
}
|
|
|
|
// Manager returns the VirtualPortManager used by the Runtime.
|
|
func (r *Runtime) Manager() *VirtualPortManager {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
return r.manager
|
|
}
|