Startbytes für virtuelle Geräteverbindungen ergänzen
This commit is contained in:
parent
f2428a2007
commit
0eccd003f0
4 changed files with 259 additions and 59 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"server": {
|
||||
"address": "100.64.0.1",
|
||||
"address": "192.168.178.229",
|
||||
"port": 5000
|
||||
},
|
||||
"virtual_port_range": {
|
||||
|
|
@ -10,7 +10,8 @@
|
|||
"virtual_ports": [
|
||||
{
|
||||
"port": "COM100",
|
||||
"remote_device": "rotor"
|
||||
"remote_device": "rotor",
|
||||
"startup_bytes": "57 00 00 00 00 00 00 00 00 00 00 1F 20"
|
||||
}
|
||||
|
||||
]
|
||||
|
|
|
|||
|
|
@ -12,13 +12,18 @@
|
|||
*
|
||||
* Die konfigurierte lokale virtuelle Schnittstelle wird dabei exakt
|
||||
* übernommen.
|
||||
*
|
||||
* Optional können nach dem Aufbau der Datenverbindung konfigurierte
|
||||
* Startbytes an das entfernte Gerät gesendet werden.
|
||||
* ============================================================================
|
||||
*/
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"git.lang-dieter.de/rs2322tcp/internal/config"
|
||||
"git.lang-dieter.de/rs2322tcp/internal/transport"
|
||||
|
|
@ -36,6 +41,11 @@ import (
|
|||
// The local virtual port is opened using exactly the port number specified
|
||||
// in virtualPortConfig.Port. This preserves the user's configured mapping
|
||||
// between local virtual interfaces and remote devices.
|
||||
//
|
||||
// If StartupBytes are configured, they are sent once immediately after the
|
||||
// TCP data connection has been established. Invalid startup bytes do not
|
||||
// abort the connection setup. A warning is printed and the connection
|
||||
// continues normally.
|
||||
func connectVirtualPort(
|
||||
c *Client,
|
||||
manager *VirtualPortManager,
|
||||
|
|
@ -81,9 +91,63 @@ func connectVirtualPort(
|
|||
)
|
||||
}
|
||||
|
||||
if err := sendStartupBytes(conn, virtualPortConfig); err != nil {
|
||||
fmt.Printf(
|
||||
"warning: startup bytes for %q were not sent: %v\n",
|
||||
virtualPortConfig.RemoteDevice,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return virtualPort, conn, nil
|
||||
}
|
||||
|
||||
// sendStartupBytes sends the optional configured startup byte sequence over
|
||||
// the already established TCP data connection.
|
||||
//
|
||||
// An empty StartupBytes configuration does not send anything and is not an
|
||||
// error.
|
||||
//
|
||||
// The configured value may contain spaces between hexadecimal bytes, for
|
||||
// example:
|
||||
//
|
||||
// 57 00 00 00 00 00 00 00 00 00 00 1F 20
|
||||
func sendStartupBytes(
|
||||
conn net.Conn,
|
||||
virtualPortConfig config.VirtualPortConfig,
|
||||
) error {
|
||||
startupBytes := strings.TrimSpace(virtualPortConfig.StartupBytes)
|
||||
if startupBytes == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
hexText := strings.Join(strings.Fields(startupBytes), "")
|
||||
|
||||
data, err := hex.DecodeString(hexText)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid hexadecimal byte sequence: %w", err)
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
written, err := conn.Write(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("send startup bytes: %w", err)
|
||||
}
|
||||
|
||||
if written != len(data) {
|
||||
return fmt.Errorf(
|
||||
"send startup bytes: wrote %d of %d bytes",
|
||||
written,
|
||||
len(data),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Bridge connection
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ type VirtualPortRangeConfig struct {
|
|||
type VirtualPortConfig struct {
|
||||
Port string `json:"port"`
|
||||
RemoteDevice string `json:"remote_device"`
|
||||
StartupBytes string `json:"startup_bytes,omitempty"`
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -59,13 +59,15 @@ func TestLoadServer(t *testing.T) {
|
|||
}
|
||||
|
||||
if cfg.Listen.Address != "0.0.0.0" {
|
||||
t.Errorf("Listen.Address = %q, want %q",
|
||||
cfg.Listen.Address, "0.0.0.0")
|
||||
t.Errorf(
|
||||
"Listen.Address = %q, want %q",
|
||||
cfg.Listen.Address,
|
||||
"0.0.0.0",
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.Listen.Port != 5000 {
|
||||
t.Errorf("Listen.Port = %d, want %d",
|
||||
cfg.Listen.Port, 5000)
|
||||
t.Errorf("Listen.Port = %d, want %d", cfg.Listen.Port, 5000)
|
||||
}
|
||||
|
||||
if len(cfg.Devices) != 1 {
|
||||
|
|
@ -75,18 +77,19 @@ func TestLoadServer(t *testing.T) {
|
|||
device := cfg.Devices[0]
|
||||
|
||||
if device.ID != "radio" {
|
||||
t.Errorf("Device.ID = %q, want %q",
|
||||
device.ID, "radio")
|
||||
t.Errorf("Device.ID = %q, want %q", device.ID, "radio")
|
||||
}
|
||||
|
||||
if device.SerialPort != "/dev/ttyUSB0" {
|
||||
t.Errorf("Device.SerialPort = %q, want %q",
|
||||
device.SerialPort, "/dev/ttyUSB0")
|
||||
t.Errorf(
|
||||
"Device.SerialPort = %q, want %q",
|
||||
device.SerialPort,
|
||||
"/dev/ttyUSB0",
|
||||
)
|
||||
}
|
||||
|
||||
if device.BaudRate != 9600 {
|
||||
t.Errorf("Device.BaudRate = %d, want %d",
|
||||
device.BaudRate, 9600)
|
||||
t.Errorf("Device.BaudRate = %d, want %d", device.BaudRate, 9600)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,40 +124,106 @@ func TestLoadClient(t *testing.T) {
|
|||
}
|
||||
|
||||
if cfg.Server.Address != "100.64.0.10" {
|
||||
t.Errorf("Server.Address = %q, want %q",
|
||||
cfg.Server.Address, "100.64.0.10")
|
||||
t.Errorf(
|
||||
"Server.Address = %q, want %q",
|
||||
cfg.Server.Address,
|
||||
"100.64.0.10",
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.Server.Port != 5000 {
|
||||
t.Errorf("Server.Port = %d, want %d",
|
||||
cfg.Server.Port, 5000)
|
||||
t.Errorf("Server.Port = %d, want %d", cfg.Server.Port, 5000)
|
||||
}
|
||||
|
||||
if cfg.VirtualPortRange.First != config.DefaultVirtualPortFirst {
|
||||
t.Errorf("VirtualPortRange.First = %d, want %d",
|
||||
cfg.VirtualPortRange.First, config.DefaultVirtualPortFirst)
|
||||
t.Errorf(
|
||||
"VirtualPortRange.First = %d, want %d",
|
||||
cfg.VirtualPortRange.First,
|
||||
config.DefaultVirtualPortFirst,
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.VirtualPortRange.Last != config.DefaultVirtualPortLast {
|
||||
t.Errorf("VirtualPortRange.Last = %d, want %d",
|
||||
cfg.VirtualPortRange.Last, config.DefaultVirtualPortLast)
|
||||
t.Errorf(
|
||||
"VirtualPortRange.Last = %d, want %d",
|
||||
cfg.VirtualPortRange.Last,
|
||||
config.DefaultVirtualPortLast,
|
||||
)
|
||||
}
|
||||
|
||||
if len(cfg.VirtualPorts) != 1 {
|
||||
t.Fatalf("len(VirtualPorts) = %d, want 1",
|
||||
len(cfg.VirtualPorts))
|
||||
t.Fatalf(
|
||||
"len(VirtualPorts) = %d, want 1",
|
||||
len(cfg.VirtualPorts),
|
||||
)
|
||||
}
|
||||
|
||||
virtualPort := cfg.VirtualPorts[0]
|
||||
|
||||
if virtualPort.Port != "COM7" {
|
||||
t.Errorf("VirtualPort.Port = %q, want %q",
|
||||
virtualPort.Port, "COM7")
|
||||
t.Errorf(
|
||||
"VirtualPort.Port = %q, want %q",
|
||||
virtualPort.Port,
|
||||
"COM7",
|
||||
)
|
||||
}
|
||||
|
||||
if virtualPort.RemoteDevice != "radio" {
|
||||
t.Errorf("VirtualPort.RemoteDevice = %q, want %q",
|
||||
virtualPort.RemoteDevice, "radio")
|
||||
t.Errorf(
|
||||
"VirtualPort.RemoteDevice = %q, want %q",
|
||||
virtualPort.RemoteDevice,
|
||||
"radio",
|
||||
)
|
||||
}
|
||||
|
||||
if virtualPort.StartupBytes != "" {
|
||||
t.Errorf(
|
||||
"VirtualPort.StartupBytes = %q, want empty",
|
||||
virtualPort.StartupBytes,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadClientWithStartupBytes(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
filename := filepath.Join(dir, "client.json")
|
||||
|
||||
data := `{
|
||||
"server": {
|
||||
"address": "100.64.0.10",
|
||||
"port": 5000
|
||||
},
|
||||
"virtual_ports": [
|
||||
{
|
||||
"port": "COM7",
|
||||
"remote_device": "radio",
|
||||
"startup_bytes": "57 00 00 1F 20"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
if err := os.WriteFile(filename, []byte(data), 0644); err != nil {
|
||||
t.Fatalf("write test configuration: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := config.LoadClient(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadClient() failed: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.VirtualPorts) != 1 {
|
||||
t.Fatalf(
|
||||
"len(VirtualPorts) = %d, want 1",
|
||||
len(cfg.VirtualPorts),
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.VirtualPorts[0].StartupBytes != "57 00 00 1F 20" {
|
||||
t.Errorf(
|
||||
"VirtualPort.StartupBytes = %q, want %q",
|
||||
cfg.VirtualPorts[0].StartupBytes,
|
||||
"57 00 00 1F 20",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,13 +253,17 @@ func TestLoadClientCustomVirtualPortRange(t *testing.T) {
|
|||
}
|
||||
|
||||
if cfg.VirtualPortRange.First != 200 {
|
||||
t.Errorf("VirtualPortRange.First = %d, want 200",
|
||||
cfg.VirtualPortRange.First)
|
||||
t.Errorf(
|
||||
"VirtualPortRange.First = %d, want 200",
|
||||
cfg.VirtualPortRange.First,
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.VirtualPortRange.Last != 299 {
|
||||
t.Errorf("VirtualPortRange.Last = %d, want 299",
|
||||
cfg.VirtualPortRange.Last)
|
||||
t.Errorf(
|
||||
"VirtualPortRange.Last = %d, want 299",
|
||||
cfg.VirtualPortRange.Last,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -215,6 +288,7 @@ func TestSaveAndLoadClient(t *testing.T) {
|
|||
{
|
||||
Port: "COM7",
|
||||
RemoteDevice: "radio",
|
||||
StartupBytes: "57 00 00 1F 20",
|
||||
},
|
||||
{
|
||||
Port: "COM8",
|
||||
|
|
@ -241,9 +315,11 @@ func TestSaveAndLoadClient(t *testing.T) {
|
|||
}
|
||||
|
||||
if len(loaded.VirtualPorts) != len(original.VirtualPorts) {
|
||||
t.Fatalf("len(VirtualPorts) = %d, want %d",
|
||||
t.Fatalf(
|
||||
"len(VirtualPorts) = %d, want %d",
|
||||
len(loaded.VirtualPorts),
|
||||
len(original.VirtualPorts))
|
||||
len(original.VirtualPorts),
|
||||
)
|
||||
}
|
||||
|
||||
for i := range original.VirtualPorts {
|
||||
|
|
@ -402,8 +478,29 @@ func TestExampleClientConfig(t *testing.T) {
|
|||
t.Fatalf("LoadClient() failed: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.VirtualPorts) != 2 {
|
||||
t.Fatalf("len(VirtualPorts) = %d, want 2", len(cfg.VirtualPorts))
|
||||
if len(cfg.VirtualPorts) != 1 {
|
||||
t.Fatalf(
|
||||
"len(VirtualPorts) = %d, want 1",
|
||||
len(cfg.VirtualPorts),
|
||||
)
|
||||
}
|
||||
|
||||
port := cfg.VirtualPorts[0]
|
||||
|
||||
if port.Port != "COM100" {
|
||||
t.Errorf(
|
||||
"VirtualPorts[0].Port = %q, want %q",
|
||||
port.Port,
|
||||
"COM100",
|
||||
)
|
||||
}
|
||||
|
||||
if port.RemoteDevice != "rotor" {
|
||||
t.Errorf(
|
||||
"VirtualPorts[0].RemoteDevice = %q, want %q",
|
||||
port.RemoteDevice,
|
||||
"rotor",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -425,33 +522,51 @@ func TestDeviceRemoteDevice(t *testing.T) {
|
|||
remote := device.RemoteDevice()
|
||||
|
||||
if remote.ID != "radio" {
|
||||
t.Errorf("RemoteDevice.ID = %q, want %q",
|
||||
remote.ID, "radio")
|
||||
t.Errorf(
|
||||
"RemoteDevice.ID = %q, want %q",
|
||||
remote.ID,
|
||||
"radio",
|
||||
)
|
||||
}
|
||||
|
||||
if remote.Name != "Funkgerät" {
|
||||
t.Errorf("RemoteDevice.Name = %q, want %q",
|
||||
remote.Name, "Funkgerät")
|
||||
t.Errorf(
|
||||
"RemoteDevice.Name = %q, want %q",
|
||||
remote.Name,
|
||||
"Funkgerät",
|
||||
)
|
||||
}
|
||||
|
||||
if remote.BaudRate != 9600 {
|
||||
t.Errorf("RemoteDevice.BaudRate = %d, want %d",
|
||||
remote.BaudRate, 9600)
|
||||
t.Errorf(
|
||||
"RemoteDevice.BaudRate = %d, want %d",
|
||||
remote.BaudRate,
|
||||
9600,
|
||||
)
|
||||
}
|
||||
|
||||
if remote.DataBits != 8 {
|
||||
t.Errorf("RemoteDevice.DataBits = %d, want %d",
|
||||
remote.DataBits, 8)
|
||||
t.Errorf(
|
||||
"RemoteDevice.DataBits = %d, want %d",
|
||||
remote.DataBits,
|
||||
8,
|
||||
)
|
||||
}
|
||||
|
||||
if remote.Parity != "none" {
|
||||
t.Errorf("RemoteDevice.Parity = %q, want %q",
|
||||
remote.Parity, "none")
|
||||
t.Errorf(
|
||||
"RemoteDevice.Parity = %q, want %q",
|
||||
remote.Parity,
|
||||
"none",
|
||||
)
|
||||
}
|
||||
|
||||
if remote.StopBits != 1 {
|
||||
t.Errorf("RemoteDevice.StopBits = %d, want %d",
|
||||
remote.StopBits, 1)
|
||||
t.Errorf(
|
||||
"RemoteDevice.StopBits = %d, want %d",
|
||||
remote.StopBits,
|
||||
1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -490,28 +605,41 @@ func TestServerRemoteDevices(t *testing.T) {
|
|||
}
|
||||
|
||||
if list.Devices[0].ID != "radio" {
|
||||
t.Errorf("Devices[0].ID = %q, want %q",
|
||||
list.Devices[0].ID, "radio")
|
||||
t.Errorf(
|
||||
"Devices[0].ID = %q, want %q",
|
||||
list.Devices[0].ID,
|
||||
"radio",
|
||||
)
|
||||
}
|
||||
|
||||
if list.Devices[1].ID != "rotor" {
|
||||
t.Errorf("Devices[1].ID = %q, want %q",
|
||||
list.Devices[1].ID, "rotor")
|
||||
t.Errorf(
|
||||
"Devices[1].ID = %q, want %q",
|
||||
list.Devices[1].ID,
|
||||
"rotor",
|
||||
)
|
||||
}
|
||||
|
||||
if list.Devices[0].Name != "Funkgerät" {
|
||||
t.Errorf("Devices[0].Name = %q, want %q",
|
||||
list.Devices[0].Name, "Funkgerät")
|
||||
t.Errorf(
|
||||
"Devices[0].Name = %q, want %q",
|
||||
list.Devices[0].Name,
|
||||
"Funkgerät",
|
||||
)
|
||||
}
|
||||
|
||||
if list.Devices[0].BaudRate != 9600 {
|
||||
t.Errorf("Devices[0].BaudRate = %d, want 9600",
|
||||
list.Devices[0].BaudRate)
|
||||
t.Errorf(
|
||||
"Devices[0].BaudRate = %d, want 9600",
|
||||
list.Devices[0].BaudRate,
|
||||
)
|
||||
}
|
||||
|
||||
if list.Devices[1].BaudRate != 4800 {
|
||||
t.Errorf("Devices[1].BaudRate = %d, want 4800",
|
||||
list.Devices[1].BaudRate)
|
||||
t.Errorf(
|
||||
"Devices[1].BaudRate = %d, want 4800",
|
||||
list.Devices[1].BaudRate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -561,11 +689,17 @@ func TestRemoteDeviceJSON(t *testing.T) {
|
|||
}
|
||||
|
||||
if len(decoded.Devices) != 1 {
|
||||
t.Fatalf("len(decoded.Devices) = %d, want 1", len(decoded.Devices))
|
||||
t.Fatalf(
|
||||
"len(decoded.Devices) = %d, want 1",
|
||||
len(decoded.Devices),
|
||||
)
|
||||
}
|
||||
|
||||
if decoded.Devices[0].ID != "radio" {
|
||||
t.Errorf("Devices[0].ID = %q, want %q",
|
||||
decoded.Devices[0].ID, "radio")
|
||||
t.Errorf(
|
||||
"Devices[0].ID = %q, want %q",
|
||||
decoded.Devices[0].ID,
|
||||
"radio",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue