Add Linux PTY support and device simulator
This commit is contained in:
parent
49f72c376a
commit
2a61b5a79c
6 changed files with 714 additions and 120 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -5,7 +5,11 @@ Alle wesentlichen Änderungen am Projekt werden in dieser Datei dokumentiert.
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Linux-PTY-Unterstützung für die clientseitige virtuelle serielle Schnittstelle
|
||||||
|
- Abstraktion der virtuellen seriellen Schnittstelle über `VirtualSerial`
|
||||||
|
- Automatisierter bidirektionaler PTY-Test mit allen 256 möglichen Bytewerten
|
||||||
|
- Eigenständiges Programm `rs2322tcp-device-sim` als Simulator für ein angeschlossenes serielles Gerät
|
||||||
|
- Hexadezimale Darstellung der vom Geräte-Simulator empfangenen Rohdaten
|
||||||
- Implementierung der seriellen Schnittstellenschicht unter `internal/serial`
|
- Implementierung der seriellen Schnittstellenschicht unter `internal/serial`
|
||||||
- Verwendung von `go.bug.st/serial v1.7.1` für die Ansteuerung der seriellen Schnittstellen
|
- Verwendung von `go.bug.st/serial v1.7.1` für die Ansteuerung der seriellen Schnittstellen
|
||||||
- Konfigurationsunterstützung für Baudrate, Datenbits, Parität und Stopbits
|
- Konfigurationsunterstützung für Baudrate, Datenbits, Parität und Stopbits
|
||||||
|
|
@ -45,7 +49,8 @@ Alle wesentlichen Änderungen am Projekt werden in dieser Datei dokumentiert.
|
||||||
- Race Condition im `DataListener` erkannt und behoben
|
- Race Condition im `DataListener` erkannt und behoben
|
||||||
- Client-/Server-Integrationstest erfolgreich
|
- Client-/Server-Integrationstest erfolgreich
|
||||||
- Race-Test des Client-/Server-Integrationstests erfolgreich
|
- Race-Test des Client-/Server-Integrationstests erfolgreich
|
||||||
|
- Automatisierter bidirektionaler PTY-Test mit einem vollständigen Bytebereich von `0x00` bis `0xFF`
|
||||||
|
- Race-Detection für die neue Linux-PTY-Komponente erfolgreich
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
Die grundlegende TCP-/RS232-Datenübertragung ist serverseitig implementiert und der Client kann inzwischen die Control-Verbindung sowie die zugehörige Data-Verbindung aufbauen.
|
Die grundlegende TCP-/RS232-Datenübertragung ist serverseitig implementiert und der Client kann inzwischen die Control-Verbindung sowie die zugehörige Data-Verbindung aufbauen.
|
||||||
|
|
@ -91,8 +96,16 @@ TCP → RS232
|
||||||
RS232 → TCP
|
RS232 → TCP
|
||||||
```
|
```
|
||||||
|
|
||||||
Die clientseitige Bereitstellung einer virtuellen seriellen Schnittstelle für die vorhandene Hersteller-Software ist noch nicht implementiert.
|
Die clientseitige Linux-PTY-Grundlage für eine virtuelle serielle Schnittstelle
|
||||||
|
ist implementiert und durch automatisierte Tests abgesichert.
|
||||||
|
|
||||||
|
Der Geräte-Simulator ist als eigenständiges Testprogramm vorhanden und kann
|
||||||
|
eine serielle Schnittstelle öffnen und empfangene Rohdaten hexadezimal
|
||||||
|
anzeigen.
|
||||||
|
|
||||||
|
Die TX-Funktion des Geräte-Simulators sowie die vollständige Integration der
|
||||||
|
virtuellen seriellen Schnittstelle in den Client-Datenpfad sind noch nicht
|
||||||
|
implementiert.
|
||||||
---
|
---
|
||||||
|
|
||||||
## [0.0.1] - 2026-08-09
|
## [0.0.1] - 2026-08-09
|
||||||
|
|
|
||||||
355
README.md
355
README.md
|
|
@ -1,26 +1,34 @@
|
||||||
# rs2322tcp
|
# rs2322tcp
|
||||||
|
|
||||||
`rs2322tcp` ermöglicht die transparente Übertragung von RS232-Daten über TCP/IP.
|
`rs2322tcp` ermöglicht die transparente Übertragung von RS232-Daten über
|
||||||
|
TCP/IP.
|
||||||
|
|
||||||
Das Projekt ist für den Einsatz geeignet, bei dem eine vorhandene Software eine serielle Schnittstelle erwartet, das zugehörige Gerät sich jedoch an einem entfernten Standort befindet.
|
Das Projekt ist für den Einsatz geeignet, bei dem eine vorhandene
|
||||||
|
Software eine serielle Schnittstelle erwartet, das zugehörige Gerät sich
|
||||||
|
jedoch an einem entfernten Standort befindet.
|
||||||
|
|
||||||
## Ziel
|
## Ziel
|
||||||
|
|
||||||
Auf der Client-Seite soll vorhandene Hersteller-Software eine normale serielle Schnittstelle vorfinden.
|
Auf der Client-Seite soll vorhandene Hersteller-Software eine normale
|
||||||
|
serielle Schnittstelle vorfinden.
|
||||||
|
|
||||||
Die Daten werden vom `rs2322tcp-client` über TCP an einen entfernten `rs2322tcp-server` übertragen. Der Server verbindet die Netzwerkverbindung mit einer realen seriellen Schnittstelle.
|
Die Daten werden vom `rs2322tcp-client` über TCP an einen entfernten
|
||||||
|
`rs2322tcp-server` übertragen. Der Server verbindet die
|
||||||
|
Netzwerkverbindung mit einer realen seriellen Schnittstelle.
|
||||||
|
|
||||||
Damit sollen weder die vorhandene Software auf dem Client noch die angeschlossenen Geräte auf der Remote-Seite erkennen müssen, dass die Verbindung über ein Netzwerk erfolgt.
|
Damit sollen weder die vorhandene Software auf dem Client noch die
|
||||||
|
angeschlossenen Geräte auf der Remote-Seite erkennen müssen, dass die
|
||||||
|
Verbindung über ein Netzwerk erfolgt.
|
||||||
|
|
||||||
Typische Anwendungen sind beispielsweise:
|
Typische Anwendungen sind beispielsweise:
|
||||||
|
|
||||||
- Funkgeräte mit CAT-Steuerung
|
- Funkgeräte mit CAT-Steuerung
|
||||||
- Antennenrotoren mit herstellerspezifischen seriellen Protokollen
|
- Antennenrotoren mit herstellerspezifischen seriellen Protokollen
|
||||||
- andere Geräte, die über RS232 gesteuert werden
|
- andere Geräte, die über RS232 gesteuert werden
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
|
|
||||||
```text
|
``` text
|
||||||
Client-PC Raspberry Pi 5
|
Client-PC Raspberry Pi 5
|
||||||
───────────────── ─────────────────
|
───────────────── ─────────────────
|
||||||
|
|
||||||
|
|
@ -40,79 +48,77 @@ rs2322tcp-client │
|
||||||
Funkgerät Rotor
|
Funkgerät Rotor
|
||||||
```
|
```
|
||||||
|
|
||||||
## Aktueller Datenpfad
|
## Aktueller Server-Datenpfad
|
||||||
|
|
||||||
Die serverseitige TCP-/RS232-Verbindung sowie die grundlegende Client-Control- und Client-Data-Kommunikation sind inzwischen implementiert:
|
Die serverseitige TCP-/RS232-Verbindung ist implementiert:
|
||||||
|
|
||||||
```text
|
``` text
|
||||||
Client
|
TCP-Control
|
||||||
│
|
│
|
||||||
├── TCP-Control
|
▼
|
||||||
│ │
|
Session
|
||||||
│ ▼
|
│
|
||||||
│ Session
|
├── Gerät 1 ──► dynamischer Data-Port
|
||||||
│ │
|
│ │
|
||||||
│ ▼
|
│ ▼
|
||||||
│ GET_DEVICES
|
│ TCP Data
|
||||||
│ │
|
│ │
|
||||||
│ ▼
|
│ ▼
|
||||||
│ Geräteinformationen
|
│ Serial Layer
|
||||||
│ │
|
│ │
|
||||||
│ ▼
|
│ ▼
|
||||||
│ dynamischer Data-Port
|
│ RS232
|
||||||
│
|
│
|
||||||
└── TCP-Data
|
└── Gerät 2 ──► dynamischer Data-Port
|
||||||
│
|
|
||||||
▼
|
|
||||||
DataListener
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
DataConnection
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Serial Layer
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
RS232
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Die serielle Schnittstelle wird erst geöffnet, wenn ein Client tatsächlich eine Data-Verbindung zum entsprechenden dynamischen TCP-Port aufbaut.
|
Die serielle Schnittstelle wird erst geöffnet, wenn ein Client
|
||||||
|
tatsächlich eine Data-Verbindung zum entsprechenden dynamischen TCP-Port
|
||||||
|
aufbaut.
|
||||||
|
|
||||||
Pro konfiguriertem Gerät ist nur eine aktive Data-Verbindung vorgesehen.
|
Pro konfiguriertem Gerät ist nur eine aktive Data-Verbindung vorgesehen.
|
||||||
|
|
||||||
Die Datenübertragung erfolgt bidirektional:
|
Die Datenübertragung erfolgt bidirektional:
|
||||||
|
|
||||||
```text
|
``` text
|
||||||
TCP ───────────────► RS232
|
TCP ───────────────► RS232
|
||||||
TCP ◄────────────── RS232
|
TCP ◄────────────── RS232
|
||||||
```
|
```
|
||||||
|
|
||||||
Der komplette Client-/Server-Datenpfad wird inzwischen durch einen Integrationstest mit virtuellen seriellen Schnittstellen geprüft.
|
|
||||||
|
|
||||||
## Client
|
## Client
|
||||||
|
|
||||||
Der Client soll gleichberechtigt unter folgenden Betriebssystemen eingesetzt werden können:
|
Der Client soll gleichberechtigt unter folgenden Betriebssystemen
|
||||||
|
eingesetzt werden können:
|
||||||
|
|
||||||
- Windows
|
- Windows
|
||||||
- Linux
|
- Linux
|
||||||
|
|
||||||
Die plattformspezifische Bereitstellung der virtuellen seriellen Schnittstelle wird vom gemeinsamen Client-Kern getrennt.
|
Die plattformspezifische Bereitstellung der virtuellen seriellen
|
||||||
|
Schnittstelle wird vom gemeinsamen Client-Kern getrennt.
|
||||||
|
|
||||||
Der gemeinsame Client-Kern kann inzwischen:
|
Unter Linux ist die virtuelle serielle Schnittstelle inzwischen über
|
||||||
|
einen direkt aus Go erzeugten PTY implementiert. Die Implementierung
|
||||||
|
verwendet `/dev/ptmx` und stellt den zugehörigen PTY-Slave unter
|
||||||
|
`/dev/pts/N` bereit.
|
||||||
|
|
||||||
- die Control-Verbindung zum Server aufbauen
|
Die PTY-Schnittstelle wird im Raw Mode betrieben. Dadurch werden die
|
||||||
- den Control-Handshake durchführen
|
übertragenen Daten als transparenter Byte-Strom behandelt.
|
||||||
- die konfigurierten Geräte vom Server abfragen
|
|
||||||
- den vom Server gelieferten dynamischen Data-Port verwenden
|
|
||||||
- die TCP-Data-Verbindung zum Server aufbauen
|
|
||||||
|
|
||||||
Die clientseitige virtuelle serielle Schnittstelle ist derzeit noch nicht implementiert.
|
Die allgemeine Client-Logik verwendet dafür die Abstraktion
|
||||||
|
`VirtualSerial`. Die Linux-spezifische Implementierung ist von dieser
|
||||||
|
Abstraktion getrennt.
|
||||||
|
|
||||||
|
Für die PTY-Funktion ist keine externe Laufzeitabhängigkeit wie `socat`
|
||||||
|
erforderlich.
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
|
|
||||||
Der Server ist zunächst für den Betrieb auf einem Raspberry Pi 5 vorgesehen.
|
Der Server ist zunächst für den Betrieb auf einem Raspberry Pi 5
|
||||||
|
vorgesehen.
|
||||||
|
|
||||||
Mehrere USB-to-RS232-Adapter können angeschlossen werden. Anzahl, Bezeichnung und serielle Parameter der Anschlüsse werden über eine Konfigurationsdatei festgelegt.
|
Mehrere USB-to-RS232-Adapter können angeschlossen werden. Anzahl,
|
||||||
|
Bezeichnung und serielle Parameter der Anschlüsse werden über eine
|
||||||
|
Konfigurationsdatei festgelegt.
|
||||||
|
|
||||||
Die serverseitige Serial-Kommunikation verwendet `go.bug.st/serial`.
|
Die serverseitige Serial-Kommunikation verwendet `go.bug.st/serial`.
|
||||||
|
|
||||||
|
|
@ -120,52 +126,139 @@ Die serverseitige Serial-Kommunikation verwendet `go.bug.st/serial`.
|
||||||
|
|
||||||
Für die Ansteuerung der seriellen Schnittstellen wird derzeit verwendet:
|
Für die Ansteuerung der seriellen Schnittstellen wird derzeit verwendet:
|
||||||
|
|
||||||
```text
|
``` text
|
||||||
go.bug.st/serial v1.7.1
|
go.bug.st/serial v1.7.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Die Bibliothek wurde zusätzlich auf dem privaten Git-Server des Projektes gespiegelt, um bei einem Ausfall des ursprünglichen Anbieters weiterhin auf den verwendeten Quellstand zugreifen zu können:
|
Die Bibliothek wurde zusätzlich auf dem privaten Git-Server des
|
||||||
|
Projektes gespiegelt, um bei einem Ausfall des ursprünglichen Anbieters
|
||||||
|
weiterhin auf den verwendeten Quellstand zugreifen zu können:
|
||||||
|
|
||||||
```text
|
``` text
|
||||||
git.lang-dieter.de/third-party/go-serial
|
git.lang-dieter.de/third-party/go-serial
|
||||||
```
|
```
|
||||||
|
|
||||||
Die verwendete Version `v1.7.1` ist auf dem Backup-Repository einschließlich Git-Tag vorhanden.
|
Die verwendete Version `v1.7.1` ist auf dem Backup-Repository
|
||||||
|
einschließlich Git-Tag vorhanden.
|
||||||
|
|
||||||
## Netzwerk
|
## Netzwerk
|
||||||
|
|
||||||
Für die Übertragung der RS232-Daten wird TCP verwendet.
|
Für die Übertragung der RS232-Daten wird TCP verwendet.
|
||||||
|
|
||||||
Die beteiligten Systeme werden zunächst über ein vertrauenswürdiges Tailscale-Netz verbunden.
|
Die beteiligten Systeme werden zunächst über ein vertrauenswürdiges
|
||||||
|
Tailscale-Netz verbunden.
|
||||||
|
|
||||||
Eine zusätzliche TLS-Verschlüsselung ist deshalb derzeit nicht vorgesehen.
|
Eine zusätzliche TLS-Verschlüsselung ist deshalb derzeit nicht
|
||||||
|
vorgesehen.
|
||||||
|
|
||||||
Für eine spätere Übertragung von Audio-Daten ist ein separates Netzwerk-/Transportkonzept vorgesehen. Hierfür soll UDP verwendet bzw. untersucht werden.
|
Für eine spätere Übertragung von Audio-Daten ist ein separates
|
||||||
|
Netzwerk-/Transportkonzept vorgesehen. Hierfür soll UDP verwendet bzw.
|
||||||
|
untersucht werden.
|
||||||
|
|
||||||
## Konfiguration
|
## Konfiguration
|
||||||
|
|
||||||
Client und Server erhalten jeweils eine eigene JSON-Konfiguration.
|
Client und Server erhalten jeweils eine eigene JSON-Konfiguration.
|
||||||
|
|
||||||
Über die Konfiguration sollen unter anderem festgelegt werden können:
|
Über die Konfiguration werden unter anderem festgelegt:
|
||||||
|
|
||||||
- Netzwerkparameter
|
- Netzwerkparameter
|
||||||
- serielle Schnittstelle
|
- serielle Schnittstelle
|
||||||
- Baudrate
|
- Baudrate
|
||||||
- Datenbits
|
- Datenbits
|
||||||
- Parität
|
- Parität
|
||||||
- Stopbits
|
- Stopbits
|
||||||
- Bezeichnung des Gerätes
|
- Bezeichnung des Gerätes
|
||||||
- weitere für die jeweilige Schnittstelle erforderliche Parameter
|
- weitere für die jeweilige Schnittstelle erforderliche Parameter
|
||||||
|
|
||||||
Die konkrete Konfigurationsstruktur wird im weiteren Projektverlauf festgelegt.
|
Die konkrete Konfigurationsstruktur befindet sich in der Entwicklung.
|
||||||
|
|
||||||
## Diagnose und Logging
|
## Diagnose und Logging
|
||||||
|
|
||||||
Für Debugging und Diagnose soll eine Protokollierung des übertragenen Datenverkehrs möglich sein.
|
Für Debugging und Diagnose soll eine Protokollierung des übertragenen
|
||||||
|
Datenverkehrs möglich sein.
|
||||||
|
|
||||||
Insbesondere soll eine Darstellung der übertragenen Bytes möglich sein, um Probleme bei der Kommunikation zwischen Hersteller-Software und Gerät analysieren zu können.
|
Insbesondere soll eine Darstellung der übertragenen Bytes möglich sein,
|
||||||
|
um Probleme bei der Kommunikation zwischen Hersteller-Software und Gerät
|
||||||
|
analysieren zu können.
|
||||||
|
|
||||||
Während der Entwicklung kann `socat` als zusätzliches Werkzeug für Tests und Diagnose eingesetzt werden.
|
Während der Entwicklung kann `socat` als zusätzliches Werkzeug für Tests
|
||||||
|
und Diagnose eingesetzt werden.
|
||||||
|
|
||||||
|
## Virtuelle serielle Schnittstelle unter Linux
|
||||||
|
|
||||||
|
Für den Linux-Client wird die virtuelle serielle Schnittstelle direkt
|
||||||
|
über den Linux-PTY-Mechanismus erzeugt.
|
||||||
|
|
||||||
|
Dabei entsteht ein PTY-Paar:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
/dev/ptmx
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
PTY Master
|
||||||
|
│
|
||||||
|
↕
|
||||||
|
PTY Slave
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
/dev/pts/N
|
||||||
|
```
|
||||||
|
|
||||||
|
Die externe Hersteller-Software arbeitet mit dem PTY-Slave `/dev/pts/N`.
|
||||||
|
Der `rs2322tcp-client` verwendet den PTY-Master und transportiert die
|
||||||
|
Bytes über TCP zum Server.
|
||||||
|
|
||||||
|
Die PTY-Implementierung arbeitet im Raw Mode und wurde mit einem
|
||||||
|
vollständigen Bytebereich von `0x00` bis `0xFF` geprüft.
|
||||||
|
|
||||||
|
Dabei wurde die bidirektionale Übertragung aller 256 möglichen Bytewerte
|
||||||
|
erfolgreich getestet.
|
||||||
|
|
||||||
|
## Geräte-Simulator
|
||||||
|
|
||||||
|
Das Programm `rs2322tcp-device-sim` dient als Simulator für ein an der
|
||||||
|
seriellen Schnittstelle angeschlossenes Gerät.
|
||||||
|
|
||||||
|
Der Simulator ist bewusst als eigenständiges Programm ausgeführt. Der
|
||||||
|
`rs2322tcp-server` muss nicht wissen, ob am seriellen Port ein echtes
|
||||||
|
Gerät oder der Simulator angeschlossen ist.
|
||||||
|
|
||||||
|
Der aktuelle Simulator kann eine serielle Schnittstelle öffnen und
|
||||||
|
empfangene Rohdaten als Hexdump anzeigen.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
rs2322tcp-device-sim -port /dev/pts/7
|
||||||
|
```
|
||||||
|
|
||||||
|
Beispielausgabe:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
rs2322tcp-device-sim
|
||||||
|
Port: /dev/pts/7
|
||||||
|
Warte auf Daten ...
|
||||||
|
|
||||||
|
12:34:01.123 RX 5 Bytes
|
||||||
|
46 41 00 10 0D
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Daten werden zunächst nicht interpretiert. Dadurch kann der
|
||||||
|
Simulator auch zum Beobachten unbekannter serieller Protokolle verwendet
|
||||||
|
werden.
|
||||||
|
|
||||||
|
Der Simulator soll insbesondere auch als Sniffer dienen können, wenn
|
||||||
|
externe Hersteller-Software untersucht werden soll. Dadurch lassen sich
|
||||||
|
die von einer Software gesendeten seriellen Kommandos zunächst als
|
||||||
|
Rohdaten beobachten.
|
||||||
|
|
||||||
|
Geplante Erweiterungen sind:
|
||||||
|
|
||||||
|
- manuelles Senden von Rohbytes
|
||||||
|
- Anzeige von RX und TX
|
||||||
|
- automatische Geräteantworten
|
||||||
|
- Protokollsimulation
|
||||||
|
- Aufzeichnung und Wiedergabe von Datenströmen
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
|
|
@ -173,35 +266,50 @@ Die wichtigsten Komponenten verfügen über automatisierte Tests.
|
||||||
|
|
||||||
Der aktuelle Stand wird unter anderem mit folgenden Befehlen geprüft:
|
Der aktuelle Stand wird unter anderem mit folgenden Befehlen geprüft:
|
||||||
|
|
||||||
```bash
|
``` bash
|
||||||
go test ./...
|
go test ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
und:
|
und:
|
||||||
|
|
||||||
```bash
|
``` bash
|
||||||
go test -race ./...
|
go test -race ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Race Detector wird eingesetzt, um Probleme bei der nebenläufigen Verarbeitung von Sessions, Data-Listenern und DataConnections zu erkennen.
|
Der Race Detector wird eingesetzt, um Probleme bei der nebenläufigen
|
||||||
|
Verarbeitung von Sessions, Data-Listenern und DataConnections zu
|
||||||
|
erkennen.
|
||||||
|
|
||||||
Für die serielle Datenübertragung werden virtuelle serielle Schnittstellen über `socat` verwendet. Dadurch kann der Datenpfad ohne angeschlossene RS232-Hardware getestet werden.
|
Die Linux-PTY-Komponente verfügt über einen automatisierten
|
||||||
|
bidirektionalen Byte-Test.
|
||||||
|
|
||||||
Dabei werden insbesondere beide Übertragungsrichtungen geprüft:
|
Dabei werden alle 256 möglichen Bytewerte übertragen:
|
||||||
|
|
||||||
```text
|
``` text
|
||||||
TCP → RS232
|
00 01 02 03 ... FD FE FF
|
||||||
RS232 → TCP
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Zusätzlich wird die vollständige Client-/Server-Kommunikation über den echten `ControlServer`-Lifecycle (`Listen()` / `Serve()`) getestet.
|
Geprüft werden beide Richtungen:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
PTY-Slave → Client
|
||||||
|
Client → PTY-Slave
|
||||||
|
```
|
||||||
|
|
||||||
|
Für den Integrationstest des Client-/Server-Datenpfades werden virtuelle
|
||||||
|
serielle Schnittstellen verwendet. Während der Entwicklung kann hierfür
|
||||||
|
`socat` eingesetzt werden.
|
||||||
|
|
||||||
|
Damit kann der Datenpfad ohne angeschlossene RS232-Hardware getestet
|
||||||
|
werden.
|
||||||
|
|
||||||
## Projektstruktur
|
## Projektstruktur
|
||||||
|
|
||||||
```text
|
``` text
|
||||||
rs2322tcp/
|
rs2322tcp/
|
||||||
├── cmd/
|
├── cmd/
|
||||||
│ ├── rs2322tcp-client/
|
│ ├── rs2322tcp-client/
|
||||||
|
│ ├── rs2322tcp-device-sim/
|
||||||
│ └── rs2322tcp-server/
|
│ └── rs2322tcp-server/
|
||||||
├── configs/
|
├── configs/
|
||||||
├── docs/
|
├── docs/
|
||||||
|
|
@ -224,31 +332,34 @@ rs2322tcp/
|
||||||
|
|
||||||
Der Build erfolgt über das zentrale Build-Skript:
|
Der Build erfolgt über das zentrale Build-Skript:
|
||||||
|
|
||||||
```bash
|
``` bash
|
||||||
./scripts/build.sh
|
./scripts/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Das Skript führt unter anderem folgende Schritte aus:
|
Das Skript führt unter anderem folgende Schritte aus:
|
||||||
|
|
||||||
- Formatierung des Go-Quellcodes
|
- Formatierung des Go-Quellcodes
|
||||||
- Ausführung der Tests
|
- Ausführung der Tests
|
||||||
- Bereinigung der Go-Module
|
- Bereinigung der Go-Module
|
||||||
- Ermittlung der Git-Versionsinformationen
|
- Ermittlung der Git-Versionsinformationen
|
||||||
- Build der vorgesehenen Zielplattformen
|
- Build der vorgesehenen Zielplattformen
|
||||||
- Einbettung der Versionsinformationen über den Go-Linker
|
- Einbettung der Versionsinformationen über den Go-Linker
|
||||||
- Erzeugung von SHA256-Prüfsummen
|
- Erzeugung von SHA256-Prüfsummen
|
||||||
|
|
||||||
Aktuell werden folgende Builds erzeugt:
|
Aktuell werden folgende Builds erzeugt:
|
||||||
|
|
||||||
- Linux amd64 – Client und Server
|
- Linux amd64 -- Client und Server
|
||||||
- Linux arm64 – Server
|
- Linux arm64 -- Server
|
||||||
- Windows amd64 – Client
|
- Linux armv7 -- Server
|
||||||
|
- Linux armv6 -- Server
|
||||||
|
|
||||||
## Versionierung
|
## Versionierung
|
||||||
|
|
||||||
Die Versionierung erfolgt über Git.
|
Die Versionierung erfolgt über Git.
|
||||||
|
|
||||||
Die Git-Informationen werden beim Build in die Binärdateien eingebettet. Neben der Version werden unter anderem Build-Nummer, Commit und Build-Datum erfasst.
|
Die Git-Informationen werden beim Build in die Binärdateien eingebettet.
|
||||||
|
Neben der Version werden unter anderem Build-Nummer, Commit und
|
||||||
|
Build-Datum erfasst.
|
||||||
|
|
||||||
Release-Versionen werden über Git-Tags gekennzeichnet.
|
Release-Versionen werden über Git-Tags gekennzeichnet.
|
||||||
|
|
||||||
|
|
@ -256,27 +367,37 @@ Release-Versionen werden über Git-Tags gekennzeichnet.
|
||||||
|
|
||||||
Das Projekt befindet sich weiterhin in der frühen Entwicklungsphase.
|
Das Projekt befindet sich weiterhin in der frühen Entwicklungsphase.
|
||||||
|
|
||||||
Die ursprüngliche Projektgrundlage aus Version `0.0.1` wurde inzwischen um eine funktionierende serverseitige TCP-/RS232-Datenübertragung und die grundlegende Client-Control-/Data-Kommunikation erweitert.
|
Die ursprüngliche Projektgrundlage aus Version `0.0.1` wurde inzwischen
|
||||||
|
um eine funktionierende serverseitige TCP-/RS232-Datenübertragung und
|
||||||
|
die clientseitige Grundlage für virtuelle serielle Schnittstellen
|
||||||
|
erweitert.
|
||||||
|
|
||||||
Implementiert und getestet sind derzeit:
|
Implementiert und getestet sind derzeit:
|
||||||
|
|
||||||
- Server-Control-Verbindung
|
- Server-Control-Verbindung
|
||||||
- Client-Control-Verbindung
|
- Session-Verwaltung
|
||||||
- `HELLO`-Handshake
|
- dynamische Data-Ports
|
||||||
- `GET_DEVICES`
|
- DataConnection
|
||||||
- Session-Verwaltung
|
- bidirektionale TCP-/RS232-Datenübertragung
|
||||||
- dynamische Data-Ports
|
- Serial-Abstraktion
|
||||||
- Clientseitiger Aufbau der Data-Verbindung
|
- Konfiguration der seriellen Parameter
|
||||||
- DataConnection
|
- Client-Control-Verbindung
|
||||||
- bidirektionale TCP-/RS232-Datenübertragung
|
- Client-DataConnection
|
||||||
- Serial-Abstraktion
|
- Client-Integrationstests
|
||||||
- Konfiguration der seriellen Parameter
|
- Linux-PTY-Abstraktion
|
||||||
- virtuelle serielle Integrationstests
|
- Linux-PTY im Raw Mode
|
||||||
- Client-/Server-End-to-End-Integrationstest
|
- automatisierter bidirektionaler 256-Byte-PTY-Test
|
||||||
- nebenläufigkeitssichere Data-Listener
|
- nebenläufigkeitssichere Data-Listener
|
||||||
- Race-Detection
|
- Race-Detection
|
||||||
|
- eigenständiger Geräte-Simulator mit RX-Hexdump
|
||||||
|
|
||||||
Noch nicht implementiert ist insbesondere die clientseitige virtuelle serielle Schnittstelle für Windows und Linux.
|
Noch nicht vollständig implementiert sind insbesondere:
|
||||||
|
|
||||||
|
- die vollständige Anbindung der virtuellen seriellen Schnittstelle an
|
||||||
|
den produktiven Client-Anwendungsdatenpfad
|
||||||
|
- die Windows-Implementierung der virtuellen seriellen Schnittstelle
|
||||||
|
- TX-Funktion des Geräte-Simulators
|
||||||
|
- automatische Geräteantworten und Protokollsimulation
|
||||||
|
|
||||||
## Lizenz
|
## Lizenz
|
||||||
|
|
||||||
|
|
|
||||||
164
cmd/rs2322tcp-device-sim/main.go
Normal file
164
cmd/rs2322tcp-device-sim/main.go
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* ============================================================================
|
||||||
|
* Projekt.....: rs2322tcp
|
||||||
|
* Datei.......: main.go
|
||||||
|
* Copyright (C) 2026 Dieter Lang
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* Beschreibung:
|
||||||
|
* Einfacher Simulator für ein serielles Endgerät.
|
||||||
|
*
|
||||||
|
* Der Simulator öffnet eine angegebene serielle Schnittstelle und protokolliert
|
||||||
|
* alle vom angeschlossenen System empfangenen Bytes als Hexdump.
|
||||||
|
*
|
||||||
|
* Der Simulator stellt bewusst keine eigene PTY-Erzeugung bereit. Für
|
||||||
|
* Entwicklung und Integrationstests kann ihm beispielsweise ein unter Linux
|
||||||
|
* erzeugter PTY-Slave (/dev/pts/N) übergeben werden. Im späteren Testaufbau
|
||||||
|
* kann derselbe Simulator jedoch auch direkt mit einer echten seriellen
|
||||||
|
* Schnittstelle verbunden werden.
|
||||||
|
*
|
||||||
|
* Der Simulator dient zunächst als passives Gegenstück zum
|
||||||
|
* rs2322tcp-server. Er soll insbesondere ermöglichen, die vom Server an ein
|
||||||
|
* simuliertes Gerät übertragenen Bytes sichtbar zu machen.
|
||||||
|
*
|
||||||
|
* Zukünftige Erweiterungen können unter anderem das manuelle Senden von
|
||||||
|
* Bytes, automatische Geräteantworten, Protokollsimulation und das Aufzeichnen
|
||||||
|
* bzw. Wiedergeben von Datenströmen umfassen.
|
||||||
|
*
|
||||||
|
* Aufruf:
|
||||||
|
*
|
||||||
|
* rs2322tcp-device-sim -port /dev/pts/7
|
||||||
|
*
|
||||||
|
* Ausgabe:
|
||||||
|
*
|
||||||
|
* rs2322tcp-device-sim
|
||||||
|
* Port: /dev/pts/7
|
||||||
|
* Warte auf Daten ...
|
||||||
|
*
|
||||||
|
* Empfangene Daten werden mit Zeitstempel, Richtung und Byteanzahl ausgegeben:
|
||||||
|
*
|
||||||
|
* 12:34:01.123 RX 5 Bytes
|
||||||
|
* 46 41 00 10 0D
|
||||||
|
*
|
||||||
|
* Der Simulator interpretiert die empfangenen Daten nicht. Die Darstellung
|
||||||
|
* erfolgt ausschließlich als Rohdaten in hexadezimaler Form.
|
||||||
|
*
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := flag.String(
|
||||||
|
"port",
|
||||||
|
"",
|
||||||
|
"serielle Schnittstelle, z.B. /dev/pts/7",
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *port == "" {
|
||||||
|
log.Fatal("serielle Schnittstelle fehlt: -port <device>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die serielle Schnittstelle wird als normaler Dateideskriptor geöffnet.
|
||||||
|
// Der Simulator benötigt zunächst keine eigene Serial-Abstraktion, da er
|
||||||
|
// bewusst ein möglichst einfaches externes Testprogramm darstellen soll.
|
||||||
|
fd, err := unix.Open(
|
||||||
|
*port,
|
||||||
|
unix.O_RDWR|unix.O_NOCTTY,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(
|
||||||
|
"serielle Schnittstelle %s öffnen: %v",
|
||||||
|
*port,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
defer unix.Close(fd)
|
||||||
|
|
||||||
|
fmt.Println("rs2322tcp-device-sim")
|
||||||
|
fmt.Printf("Port: %s\n", *port)
|
||||||
|
fmt.Println("Warte auf Daten ...")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Der Simulator arbeitet byteorientiert. Die Größe des Puffers ist
|
||||||
|
// bewusst ausreichend groß gewählt, damit auch größere Datenblöcke
|
||||||
|
// ohne unnötige Fragmentierung gelesen werden können.
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := unix.Read(fd, buf)
|
||||||
|
if err != nil {
|
||||||
|
// Ein unterbrochener Systemaufruf ist kein Fehler des
|
||||||
|
// seriellen Datenstroms und wird einfach wiederholt.
|
||||||
|
if err == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bei einem PTY kann das Schließen der Gegenstelle unter
|
||||||
|
// Linux als EIO gemeldet werden. Für den Simulator bedeutet
|
||||||
|
// dies, dass die serielle Gegenstelle nicht mehr verfügbar ist.
|
||||||
|
if err == unix.EIO {
|
||||||
|
fmt.Println("Serielle Schnittstelle wurde geschlossen.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
fmt.Println("Serielle Schnittstelle wurde geschlossen.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatalf(
|
||||||
|
"Lesen von %s: %v",
|
||||||
|
*port,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
printRX(buf[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printRX gibt einen empfangenen Datenblock als Hexdump aus.
|
||||||
|
//
|
||||||
|
// Die Daten werden absichtlich nicht als Text interpretiert. Dadurch bleiben
|
||||||
|
// auch nicht druckbare Bytes und Bytes oberhalb von 0x7F sichtbar und können
|
||||||
|
// bei der Analyse serieller Protokolle eindeutig nachvollzogen werden.
|
||||||
|
func printRX(data []byte) {
|
||||||
|
fmt.Printf(
|
||||||
|
"%s RX %d Bytes\n",
|
||||||
|
time.Now().Format("15:04:05.000"),
|
||||||
|
len(data),
|
||||||
|
)
|
||||||
|
|
||||||
|
for offset := 0; offset < len(data); offset += 16 {
|
||||||
|
end := offset + 16
|
||||||
|
if end > len(data) {
|
||||||
|
end = len(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" ")
|
||||||
|
|
||||||
|
for _, b := range data[offset:end] {
|
||||||
|
fmt.Printf("%02X ", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
167
internal/client/pty_linux.go
Normal file
167
internal/client/pty_linux.go
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ptySerial struct {
|
||||||
|
masterFD int
|
||||||
|
slaveFD int
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVirtualSerial() (VirtualSerial, error) {
|
||||||
|
masterFD, err := unix.Open(
|
||||||
|
"/dev/ptmx",
|
||||||
|
unix.O_RDWR|unix.O_NOCTTY,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open PTY master: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn die weitere Initialisierung fehlschlägt,
|
||||||
|
// darf der Master nicht offen bleiben.
|
||||||
|
closeMaster := true
|
||||||
|
defer func() {
|
||||||
|
if closeMaster {
|
||||||
|
_ = unix.Close(masterFD)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// PTY-Slave entsperren.
|
||||||
|
if err := unix.IoctlSetPointerInt(
|
||||||
|
masterFD,
|
||||||
|
unix.TIOCSPTLCK,
|
||||||
|
0,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("unlock PTY slave: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PTY-Nummer ermitteln.
|
||||||
|
ptsNumber, err := unix.IoctlGetInt(
|
||||||
|
masterFD,
|
||||||
|
unix.TIOCGPTN,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get PTY number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/dev/pts/%d", ptsNumber)
|
||||||
|
|
||||||
|
// Slave öffnen, um die Terminalparameter konfigurieren zu können.
|
||||||
|
slaveFD, err := unix.Open(
|
||||||
|
path,
|
||||||
|
unix.O_RDWR|unix.O_NOCTTY,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open PTY slave %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSlave := true
|
||||||
|
defer func() {
|
||||||
|
if closeSlave {
|
||||||
|
_ = unix.Close(slaveFD)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := configurePTY(slaveFD); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMaster = false
|
||||||
|
closeSlave = false
|
||||||
|
|
||||||
|
return &ptySerial{
|
||||||
|
masterFD: masterFD,
|
||||||
|
slaveFD: slaveFD,
|
||||||
|
path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func configurePTY(fd int) error {
|
||||||
|
termios, err := unix.IoctlGetTermios(
|
||||||
|
fd,
|
||||||
|
unix.TCGETS,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get PTY termios: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw Mode:
|
||||||
|
// keine Canonical-Verarbeitung,
|
||||||
|
// kein Echo,
|
||||||
|
// keine Signalinterpretation,
|
||||||
|
// keine CR/LF-Konvertierung,
|
||||||
|
// keine XON/XOFF-Verarbeitung.
|
||||||
|
termios.Iflag &^= unix.IGNBRK |
|
||||||
|
unix.BRKINT |
|
||||||
|
unix.PARMRK |
|
||||||
|
unix.ISTRIP |
|
||||||
|
unix.INLCR |
|
||||||
|
unix.IGNCR |
|
||||||
|
unix.ICRNL |
|
||||||
|
unix.IXON
|
||||||
|
|
||||||
|
termios.Oflag &^= unix.OPOST
|
||||||
|
|
||||||
|
termios.Lflag &^= unix.ECHO |
|
||||||
|
unix.ECHONL |
|
||||||
|
unix.ICANON |
|
||||||
|
unix.ISIG |
|
||||||
|
unix.IEXTEN
|
||||||
|
|
||||||
|
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||||
|
termios.Cflag |= unix.CS8
|
||||||
|
|
||||||
|
// Byte-orientiertes, blockierendes Lesen.
|
||||||
|
termios.Cc[unix.VMIN] = 1
|
||||||
|
termios.Cc[unix.VTIME] = 0
|
||||||
|
|
||||||
|
if err := unix.IoctlSetTermios(
|
||||||
|
fd,
|
||||||
|
unix.TCSETS,
|
||||||
|
termios,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("set PTY raw mode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ptySerial) Path() string {
|
||||||
|
return p.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ptySerial) Read(b []byte) (int, error) {
|
||||||
|
return unix.Read(p.masterFD, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ptySerial) Write(b []byte) (int, error) {
|
||||||
|
return unix.Write(p.masterFD, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ptySerial) Close() error {
|
||||||
|
var firstErr error
|
||||||
|
|
||||||
|
if p.masterFD >= 0 {
|
||||||
|
if err := unix.Close(p.masterFD); err != nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
p.masterFD = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.slaveFD >= 0 {
|
||||||
|
if err := unix.Close(p.slaveFD); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
p.slaveFD = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
112
internal/client/pty_linux_test.go
Normal file
112
internal/client/pty_linux_test.go
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVirtualSerialPTYByteStream(t *testing.T) {
|
||||||
|
serial, err := newVirtualSerial()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("newVirtualSerial() failed: %v", err)
|
||||||
|
}
|
||||||
|
defer serial.Close()
|
||||||
|
|
||||||
|
path := serial.Path()
|
||||||
|
|
||||||
|
if path == "" {
|
||||||
|
t.Fatal("virtual serial returned an empty path")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("virtual serial path: %s", path)
|
||||||
|
|
||||||
|
// Die externe Software wird durch diesen direkten Zugriff
|
||||||
|
// auf den PTY-Slave simuliert.
|
||||||
|
slaveFD, err := unix.Open(
|
||||||
|
path,
|
||||||
|
unix.O_RDWR|unix.O_NOCTTY,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open PTY slave %s: %v", path, err)
|
||||||
|
}
|
||||||
|
defer unix.Close(slaveFD)
|
||||||
|
|
||||||
|
expected := make([]byte, 256)
|
||||||
|
|
||||||
|
for i := range expected {
|
||||||
|
expected[i] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Slave -> Client
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
n, err := unix.Write(slaveFD, expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("write to PTY slave failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(expected) {
|
||||||
|
t.Fatalf(
|
||||||
|
"write to PTY slave incomplete: got %d, want %d",
|
||||||
|
n,
|
||||||
|
len(expected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
received := make([]byte, len(expected))
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
for offset < len(received) {
|
||||||
|
n, err := serial.Read(received[offset:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read from virtual serial failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += n
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(received, expected) {
|
||||||
|
t.Fatalf("Slave -> Client data mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Client -> Slave
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
n, err = serial.Write(expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("write to virtual serial failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(expected) {
|
||||||
|
t.Fatalf(
|
||||||
|
"write from virtual serial incomplete: got %d, want %d",
|
||||||
|
n,
|
||||||
|
len(expected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
received = make([]byte, len(expected))
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
for offset < len(received) {
|
||||||
|
n, err := unix.Read(slaveFD, received[offset:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read from PTY slave failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += n
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(received, expected) {
|
||||||
|
t.Fatalf("Client -> Slave data mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("256-byte bidirectional PTY test successful")
|
||||||
|
}
|
||||||
17
internal/client/virtual_serial.go
Normal file
17
internal/client/virtual_serial.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// VirtualSerial repräsentiert eine vom Client bereitgestellte
|
||||||
|
// virtuelle serielle Schnittstelle.
|
||||||
|
//
|
||||||
|
// Path liefert den Namen der für die Anwendung sichtbaren
|
||||||
|
// seriellen Schnittstelle, zum Beispiel /dev/pts/1.
|
||||||
|
//
|
||||||
|
// Das io.ReadWriteCloser-Interface repräsentiert die interne
|
||||||
|
// Byte-Verbindung zum virtuellen seriellen Gerät.
|
||||||
|
type VirtualSerial interface {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
|
||||||
|
Path() string
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue