diff --git a/CHANGELOG.md b/CHANGELOG.md index a95d314..ca2ae8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,58 +2,102 @@ Alle wesentlichen Änderungen am Projekt werden in dieser Datei dokumentiert. -## [Unreleased] +## [0.0.4] - 2026-08-10 ### Added -- Linux-PTY-Unterstützung für die clientseitige virtuelle serielle Schnittstelle + +- Interaktiver Geräte-Simulator `rs2322tcp-device-sim` +- Senden beliebiger Bytefolgen über eine interaktive Hex-Eingabe +- Ausgabe empfangener Daten als Hex-Dump +- Unterstützung von Hex-Eingaben mit und ohne Leerzeichen +- Fehlerbehandlung für ungültige Hex-Eingaben +- Automatisierte Tests für die Hex-Konvertierung +- Erweiterte Dokumentation des Geräte-Simulators und des PTY-Testaufbaus +- Möglichkeit, den Simulator als virtuelle serielle Gegenstelle für + externe Software zu verwenden + +### Tests + +- Byte-orientierte Übertragung mit beliebigen Binärdaten getestet +- Übertragung aller Bytewerte `00` bis `FF` getestet +- Übertragung vom PTY-Slave zum PTY-Master getestet +- Übertragung vom PTY-Master zum PTY-Slave getestet +- `go test ./...` erfolgreich +- `go test -race ./...` erfolgreich + +--- + +## [0.0.3] - 2026-08-10 + +### 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 +- Eigenständiges Programm `rs2322tcp-device-sim` als Simulator für ein + angeschlossenes serielles Gerät - Hexadezimale Darstellung der vom Geräte-Simulator empfangenen Rohdaten + +### Tests + +- PTY-Slave und PTY-Master erfolgreich miteinander getestet +- Bidirektionale Übertragung von Binärdaten getestet +- Bytefolgen mit Werten von `00` bis `FF` erfolgreich übertragen +- Empfang beliebiger Datenblöcke über den PTY-Master getestet + +--- + +## [0.0.2] - 2026-08-09 + +### Added + - Implementierung der seriellen Schnittstellenschicht unter `internal/serial` - 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 -- Öffnen und Schließen serieller Schnittstellen über die interne Serial-Abstraktion -- Lesen und Schreiben von RS232-Daten über die Serial-Abstraktion -- Dynamische TCP-Data-Listener für konfigurierte Geräte -- Session-Verwaltung für dynamische Data-Listener -- Session-Verwaltung für aktive DataConnections -- Bidirektionale Datenübertragung zwischen TCP und RS232 -- Öffnen der seriellen Schnittstelle erst beim Aufbau einer tatsächlichen Data-Verbindung -- Begrenzung auf eine aktive Data-Verbindung pro konfiguriertem Gerät -- Integrationstest mit virtuellen seriellen Schnittstellen über `socat` -- Tests für TCP → RS232 und RS232 → TCP -- Tests für Session-Reconnect und Ressourcenverwaltung +- Serielle Geräte können über Konfiguration definiert werden +- Server-Control-Verbindung +- Session-Verwaltung +- Dynamische Data-Ports pro Gerät +- Serverseitiger Aufbau der TCP-Data-Verbindung +- `DataConnection` für die bidirektionale Datenübertragung +- `DataListener` zur Annahme von Data-Verbindungen +- TCP → RS232 Datenübertragung +- RS232 → TCP Datenübertragung - Client-Control-Verbindung unter `internal/client` - Clientseitige Verarbeitung von `HELLO` - Clientseitige Abfrage der konfigurierten Geräte über `GET_DEVICES` -- Clientseitige Verarbeitung der vom Server gelieferten Geräteinformationen einschließlich dynamischem Data-Port +- Clientseitige Verarbeitung der vom Server gelieferten Geräteinformationen + einschließlich dynamischem Data-Port - Clientseitiger Aufbau der TCP-Data-Verbindung - Integrationstest für die vollständige Client-/Server-Kommunikation - End-to-End-Test der TCP-/RS232-Datenübertragung in beide Richtungen ### Changed -- Go-Version des Projektes auf Go `1.25.0` aktualisiert -- `go.bug.st/serial` wird in Version `v1.7.1` verwendet -- `golang.org/x/sys v0.43.0` wird als indirekte Abhängigkeit verwendet -- Die Serial-Bibliothek `go.bug.st/serial` wurde zusätzlich auf dem privaten Git-Server des Projektes als Ausfallsicherung gespiegelt: - `git.lang-dieter.de/third-party/go-serial` -- Der `DataListener` wurde nebenläufigkeitssicher implementiert -- Das Schließen eines `DataListener` kann gleichzeitig mit einem laufenden `Accept()` erfolgen +- Serial-Kommunikation vollständig über `go.bug.st/serial` +- Server öffnet serielle Schnittstellen erst bei aktiver Data-Verbindung +- Pro Gerät wird nur eine aktive Data-Verbindung zugelassen +- Session- und Ressourcenverwaltung überarbeitet +- Race Condition im `DataListener` behoben ### Tests -- `go test ./...` erfolgreich -- `go test -race ./...` erfolgreich -- Race Condition im `DataListener` erkannt und behoben +- Unit-Tests für Serial +- Unit-Tests für Session +- Unit-Tests für DataConnection +- Unit-Tests für DataListener +- Integrationstest mit virtuellen seriellen Schnittstellen über `socat` +- Tests für TCP → RS232 und RS232 → TCP +- Tests für Session-Reconnect und Ressourcenverwaltung - Client-/Server-Integrationstest 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 -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 die Control-Verbindung sowie die zugehörige +Data-Verbindung aufbauen. Der aktuelle Datenpfad ist: @@ -103,49 +147,19 @@ 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. +Die interaktive TX-Funktion des Geräte-Simulators sowie die vollständige +Integration der virtuellen seriellen Schnittstelle in den produktiven +Client-Datenpfad sind noch nicht abgeschlossen. + --- ## [0.0.1] - 2026-08-09 ### Added -- Grundstruktur des Projektes `rs2322tcp` -- Go-Modul `git.lang-dieter.de/rs2322tcp` -- separates Programmgerüst für `rs2322tcp-client` -- separates Programmgerüst für `rs2322tcp-server` -- gemeinsame Versionierungsinformationen -- Git-basierte Build-Informationen -- zentrales Build-Skript `scripts/build.sh` -- automatische Quellcode-Formatierung beim Build -- automatische Tests beim Build -- SHA256-Prüfsummen der erzeugten Binärdateien -- `.gitignore` für Build- und Entwicklungsdateien -- erste Projekt- und Architekturdokumentation - -### Build-Ziele - -Der erste Build unterstützt: - -- Linux amd64 – Client und Server -- Linux arm64 – Server -- Windows amd64 – Client - -### Architekturentscheidungen - -- TCP wird für die Übertragung der RS232-Daten verwendet. -- Windows und Linux werden als gleichberechtigte Client-Plattformen betrachtet. -- Der Server ist zunächst für den Raspberry Pi 5 vorgesehen. -- Tailscale wird als vertrauenswürdiges Netzwerk verwendet. -- Eine zusätzliche TLS-Schicht ist zunächst nicht vorgesehen. -- Für eine spätere Audioübertragung ist ein separates UDP-basiertes Transportkonzept vorgesehen. -- `socat` kann für Entwicklung und Diagnose eingesetzt werden. -- Eine Sniffer-/Logging-Funktion für den übertragenen Datenverkehr ist vorgesehen. - -### Status - -Version `0.0.1` enthält ausschließlich die Projektgrundlage. - -Die eigentliche TCP-/RS232-Kommunikation und die virtuelle serielle Schnittstelle sind noch nicht implementiert. +- Initiale Projektstruktur +- Go-Modul und Build-Grundlagen +- Basis für Server und Client +- Konfigurationsgrundlagen +- Versionsverwaltung und Build-Metadaten +- Erste Dokumentation diff --git a/README.md b/README.md index a689995..0275fb7 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ Verbindung über ein Netzwerk erfolgt. Typische Anwendungen sind beispielsweise: -- Funkgeräte mit CAT-Steuerung -- Antennenrotoren mit herstellerspezifischen seriellen Protokollen -- andere Geräte, die über RS232 gesteuert werden +- Funkgeräte mit CAT-Steuerung +- Antennenrotoren mit herstellerspezifischen seriellen Protokollen +- andere Geräte, die über RS232 gesteuert werden ## Architektur -``` text +```text Client-PC Raspberry Pi 5 ───────────────── ───────────────── @@ -52,7 +52,7 @@ rs2322tcp-client │ Die serverseitige TCP-/RS232-Verbindung ist implementiert: -``` text +```text TCP-Control │ ▼ @@ -80,7 +80,7 @@ Pro konfiguriertem Gerät ist nur eine aktive Data-Verbindung vorgesehen. Die Datenübertragung erfolgt bidirektional: -``` text +```text TCP ───────────────► RS232 TCP ◄────────────── RS232 ``` @@ -90,8 +90,8 @@ TCP ◄────────────── RS232 Der Client soll gleichberechtigt unter folgenden Betriebssystemen eingesetzt werden können: -- Windows -- Linux +- Windows +- Linux Die plattformspezifische Bereitstellung der virtuellen seriellen Schnittstelle wird vom gemeinsamen Client-Kern getrennt. @@ -126,7 +126,7 @@ Die serverseitige Serial-Kommunikation verwendet `go.bug.st/serial`. Für die Ansteuerung der seriellen Schnittstellen wird derzeit verwendet: -``` text +```text go.bug.st/serial v1.7.1 ``` @@ -134,7 +134,7 @@ 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 ``` @@ -161,14 +161,14 @@ Client und Server erhalten jeweils eine eigene JSON-Konfiguration. Über die Konfiguration werden unter anderem festgelegt: -- Netzwerkparameter -- serielle Schnittstelle -- Baudrate -- Datenbits -- Parität -- Stopbits -- Bezeichnung des Gerätes -- weitere für die jeweilige Schnittstelle erforderliche Parameter +- Netzwerkparameter +- serielle Schnittstelle +- Baudrate +- Datenbits +- Parität +- Stopbits +- Bezeichnung des Gerätes +- weitere für die jeweilige Schnittstelle erforderliche Parameter Die konkrete Konfigurationsstruktur befindet sich in der Entwicklung. @@ -191,7 +191,7 @@ Für den Linux-Client wird die virtuelle serielle Schnittstelle direkt Dabei entsteht ein PTY-Paar: -``` text +```text /dev/ptmx │ ▼ @@ -223,42 +223,157 @@ 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. +Der Simulator kann: + +- Daten von einer seriellen Schnittstelle oder einem Linux-PTY empfangen +- empfangene Rohdaten als Hexdump anzeigen +- beliebige Bytefolgen über eine interaktive Hex-Eingabe senden +- ungültige Hex-Eingaben erkennen +- als virtuelle serielle Gegenstelle für externe Software dienen Beispiel: -``` bash -rs2322tcp-device-sim -port /dev/pts/7 +```bash +go run ./cmd/rs2322tcp-device-sim -port /dev/pts/7 ``` -Beispielausgabe: +Zum Senden wird eine Hex-Folge eingegeben: -``` text -rs2322tcp-device-sim -Port: /dev/pts/7 -Warte auf Daten ... +```text +TX> 46 41 00 10 0D +``` -12:34:01.123 RX 5 Bytes +Der Simulator überträgt diese fünf Bytes. + +Empfangene Daten werden beispielsweise so angezeigt: + +```text +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. +Die Hex-Eingabe kann mit oder ohne Leerzeichen erfolgen: + +```text +46 41 00 10 0D +``` + +und: + +```text +464100100D +``` + +sind gleichwertig. + +Auch Groß- und Kleinschreibung kann gemischt werden: + +```text +0a FF 01 b7 +``` + +Ungültige Eingaben wie + +```text +GG +``` + +oder + +```text +4 +``` + +werden erkannt und führen nicht zum Beenden des Simulators. 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: +Geplante bzw. mögliche Erweiterungen sind: -- manuelles Senden von Rohbytes -- Anzeige von RX und TX -- automatische Geräteantworten -- Protokollsimulation -- Aufzeichnung und Wiedergabe von Datenströmen +- automatische Geräteantworten +- Protokollsimulation +- Aufzeichnung und Wiedergabe von Datenströmen + +## PTY-Testprogramm + +Für die Entwicklung existiert zusätzlich das separate Testprogramm: + +```text +~/rs2322tcp-pty-test +``` + +Es erzeugt unter Linux ein PTY, setzt den Slave in den Raw Mode und zeigt +den Namen des Slave-Gerätes an. + +Beispiel: + +```bash +cd ~/rs2322tcp-pty-test +go run . +``` + +Ausgabe: + +```text +PTY Slave: /dev/pts/2 +PTY ist im Raw Mode. + +Warte auf Daten vom PTY-Slave ... +Mit Ctrl+C beenden. +``` + +Das Programm zeigt anschließend beliebige empfangene Bytes unmittelbar +als Hexdump an. + +Der Testaufbau ermöglicht damit Tests in beiden Richtungen: + +```text +Master → Slave +Slave → Master +``` + +und eignet sich auch als einfacher Beobachtungspunkt für den Datenstrom. + +## Sniffer-Funktion + +Der Empfänger des Testaufbaus kann gleichzeitig als einfacher Sniffer +verwendet werden. + +Dadurch kann später auch externe Software angeschlossen werden, um zu +beobachten, welche Kommandos diese Software tatsächlich über die serielle +Schnittstelle versendet. + +Besonders für die Entwicklung von Protokollen für Funkgeräte oder andere +Hardware ist das hilfreich. + +Der Sniffer verändert die empfangenen Daten nicht, sondern zeigt die +empfangenen Bytes lediglich an. + +## Externe USB-to-Seriell-Adapter + +Für den späteren praktischen Einsatz sind USB-to-Seriell-Adapter +vorgesehen. + +Unter Linux werden solche Adapter normalerweise als serielle Gerätedateien +bereitgestellt, beispielsweise: + +```text +/dev/ttyUSB0 +``` + +Die Kommunikation erfolgt über die normale serielle Schnittstelle des +Betriebssystems und die vorhandene Serial-Abstraktion. + +Das Projekt benötigt für die serielle Kommunikation keine direkte +`libusb`-Anbindung. Bei üblichen USB-to-Seriell-Adaptern, beispielsweise +mit FTDI-Chips, wird die serielle Schnittstelle über den Linux-Kernel- +Treiber bereitgestellt. + +Eine spezielle Hardware-Anbindung über `libusb` ist für `rs2322tcp` +daher nicht vorgesehen. ## Tests @@ -266,46 +381,36 @@ Die wichtigsten Komponenten verfügen über automatisierte Tests. Der aktuelle Stand wird unter anderem mit folgenden Befehlen geprüft: -``` bash +```bash go test ./... ``` und: -``` bash +```bash go test -race ./... ``` Der Race Detector wird eingesetzt, um Probleme bei der nebenläufigen -Verarbeitung von Sessions, Data-Listenern und DataConnections zu -erkennen. +Verarbeitung von Sessions, Data-Listenern, DataConnections und Client- +Komponenten zu erkennen. -Die Linux-PTY-Komponente verfügt über einen automatisierten -bidirektionalen Byte-Test. +Die Linux-PTY-Komponente wurde mit einem vollständigen Bytebereich von +`0x00` bis `0xFF` geprüft. -Dabei werden alle 256 möglichen Bytewerte übertragen: +Der Geräte-Simulator verfügt zusätzlich über automatisierte Tests für +die Hex-Konvertierung und die Behandlung ungültiger Eingaben. -``` text -00 01 02 03 ... FD FE FF +Für die Prüfung des Arbeitsbaums: + +```bash +git diff --check +git status ``` -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 -``` text +```text rs2322tcp/ ├── cmd/ │ ├── rs2322tcp-client/ @@ -332,26 +437,26 @@ rs2322tcp/ Der Build erfolgt über das zentrale Build-Skript: -``` bash +```bash ./scripts/build.sh ``` Das Skript führt unter anderem folgende Schritte aus: -- Formatierung des Go-Quellcodes -- Ausführung der Tests -- Bereinigung der Go-Module -- Ermittlung der Git-Versionsinformationen -- Build der vorgesehenen Zielplattformen -- Einbettung der Versionsinformationen über den Go-Linker -- Erzeugung von SHA256-Prüfsummen +- Formatierung des Go-Quellcodes +- Ausführung der Tests +- Bereinigung der Go-Module +- Ermittlung der Git-Versionsinformationen +- Build der vorgesehenen Zielplattformen +- Einbettung der Versionsinformationen über den Go-Linker +- Erzeugung von SHA256-Prüfsummen Aktuell werden folgende Builds erzeugt: -- Linux amd64 -- Client und Server -- Linux arm64 -- Server -- Linux armv7 -- Server -- Linux armv6 -- Server +- Linux amd64 -- Client und Server +- Linux arm64 -- Server +- Linux armv7 -- Server +- Linux armv6 -- Server ## Versionierung @@ -363,41 +468,45 @@ Build-Datum erfasst. Release-Versionen werden über Git-Tags gekennzeichnet. +Aktueller Stand: + +- `v0.0.1` – erste Projektversion +- `v0.0.2` – Client-Control- und Data-Verbindungen +- `v0.0.3` – Linux-PTY-Unterstützung und Geräte-Simulator +- `v0.0.4` – in Vorbereitung: interaktive TX-Funktion und automatisierte + Tests des Geräte-Simulators + ## Entwicklungsstand 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 clientseitige Grundlage für virtuelle serielle Schnittstellen -erweitert. - Implementiert und getestet sind derzeit: -- Server-Control-Verbindung -- Session-Verwaltung -- dynamische Data-Ports -- DataConnection -- bidirektionale TCP-/RS232-Datenübertragung -- Serial-Abstraktion -- Konfiguration der seriellen Parameter -- Client-Control-Verbindung -- Client-DataConnection -- Client-Integrationstests -- Linux-PTY-Abstraktion -- Linux-PTY im Raw Mode -- automatisierter bidirektionaler 256-Byte-PTY-Test -- nebenläufigkeitssichere Data-Listener -- Race-Detection -- eigenständiger Geräte-Simulator mit RX-Hexdump +- Server-Control-Verbindung +- Session-Verwaltung +- dynamische Data-Ports +- DataConnection +- bidirektionale TCP-/RS232-Datenübertragung +- Serial-Abstraktion +- Konfiguration der seriellen Parameter +- Client-Control-Verbindung +- Client-DataConnection +- Client-Integrationstests +- Linux-PTY-Abstraktion +- Linux-PTY im Raw Mode +- automatisierter bidirektionaler 256-Byte-PTY-Test +- nebenläufigkeitssichere Data-Listener +- Race-Detection +- eigenständiger Geräte-Simulator mit RX-Hexdump +- interaktive TX-Hex-Eingabe des Geräte-Simulators +- automatisierte Tests der Hex-Konvertierung 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 +- die vollständige Anbindung der virtuellen seriellen Schnittstelle an + den produktiven Client-Anwendungsdatenpfad +- die Windows-Implementierung der virtuellen seriellen Schnittstelle +- automatische Geräteantworten und Protokollsimulation ## Lizenz diff --git a/cmd/rs2322tcp-device-sim/main.go b/cmd/rs2322tcp-device-sim/main.go index 4ec516f..6d3b2d8 100644 --- a/cmd/rs2322tcp-device-sim/main.go +++ b/cmd/rs2322tcp-device-sim/main.go @@ -7,24 +7,37 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * Beschreibung: - * Einfacher Simulator für ein serielles Endgerät. + * Interaktiver Simulator für ein serielles Endgerät. * * Der Simulator öffnet eine angegebene serielle Schnittstelle und protokolliert * alle vom angeschlossenen System empfangenen Bytes als Hexdump. * + * Zusätzlich können über die Standardeingabe hexadezimal angegebene Bytefolgen + * an das serielle Gerät gesendet werden. Dadurch kann der Simulator sowohl als + * passiver Sniffer als auch als einfaches interaktives Testgerät verwendet + * werden. + * * 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. + * Der Simulator dient zunächst als externes Gegenstück zum + * rs2322tcp-server. Er ermöglicht insbesondere, die vom Server an ein + * simuliertes Gerät übertragenen Bytes sichtbar zu machen und umgekehrt + * definierte Bytefolgen an den Server zurückzusenden. * - * 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. + * Eingabeformat: + * + * TX> 46 41 00 10 0D + * + * Die Leerzeichen zwischen den Bytes sind optional. Groß- und Kleinschreibung + * der Hex-Ziffern spielt keine Rolle. + * + * Zukünftige Erweiterungen können unter anderem automatische Geräteantworten, + * Protokollsimulation und das Aufzeichnen bzw. Wiedergeben von Datenströmen + * umfassen. * * Aufruf: * @@ -36,12 +49,16 @@ * Port: /dev/pts/7 * Warte auf Daten ... * - * Empfangene Daten werden mit Zeitstempel, Richtung und Byteanzahl ausgegeben: + * Empfangene und gesendete 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 + * 12:34:05.456 TX 5 Bytes + * 46 41 01 10 0D + * + * Der Simulator interpretiert die seriellen Daten nicht. Die Darstellung * erfolgt ausschließlich als Rohdaten in hexadezimaler Form. * * ============================================================================ @@ -50,10 +67,14 @@ package main import ( + "bufio" + "encoding/hex" "flag" "fmt" "io" "log" + "os" + "strings" "time" "golang.org/x/sys/unix" @@ -92,6 +113,13 @@ func main() { fmt.Printf("Port: %s\n", *port) fmt.Println("Warte auf Daten ...") fmt.Println() + fmt.Println("Hex-Bytes senden mit:") + fmt.Println("TX> 46 41 00 10 0D") + fmt.Println() + + // Die Standardeingabe wird parallel zur seriellen Schnittstelle gelesen. + // Dadurch kann der Simulator gleichzeitig Daten empfangen und senden. + go readTX(fd) // Der Simulator arbeitet byteorientiert. Die Größe des Puffers ist // bewusst ausreichend groß gewählt, damit auch größere Datenblöcke @@ -135,15 +163,130 @@ func main() { } } +// readTX liest Zeilen von stdin und interpretiert sie als Folge von +// hexadezimal angegebenen Bytes. +// +// Die Eingabe wird bewusst zeilenorientiert verarbeitet. Dadurch kann eine +// komplette Bytefolge zuerst geprüft werden, bevor sie an die serielle +// Schnittstelle gesendet wird. +func readTX(fd int) { + scanner := bufio.NewScanner(os.Stdin) + + fmt.Print("TX> ") + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if line == "" { + fmt.Print("TX> ") + continue + } + + data, err := parseHexBytes(line) + if err != nil { + fmt.Printf("Ungültige Hex-Eingabe: %v\n", err) + fmt.Print("TX> ") + continue + } + + if len(data) == 0 { + fmt.Print("TX> ") + continue + } + + if err := writeAll(fd, data); err != nil { + fmt.Fprintf(os.Stderr, "Senden: %v\n", err) + return + } + + printTX(data) + fmt.Print("TX> ") + } + + if err := scanner.Err(); err != nil { + fmt.Fprintf(os.Stderr, "Lesen von stdin: %v\n", err) + } + + // Bei EOF (Ctrl+D) wird der Dateideskriptor geschlossen. Dadurch wird + // der blockierende Lesevorgang im Haupt-Goroutine beendet und der + // Simulator kann sauber verlassen werden. + _ = unix.Close(fd) +} + +// parseHexBytes wandelt eine zeilenorientierte Hex-Eingabe in einen +// Byte-Block um. +// +// Leerzeichen zwischen den Bytes sind erlaubt und werden entfernt. Dadurch +// werden beispielsweise sowohl +// +// 46 41 00 10 0D +// +// als auch +// +// 464100100D +// +// akzeptiert. Die Funktion selbst enthält keine Ausgabe und ist deshalb +// unabhängig vom interaktiven Terminalbetrieb testbar. +func parseHexBytes(line string) ([]byte, error) { + fields := strings.Fields(line) + hexText := strings.Join(fields, "") + + if hexText == "" { + return nil, nil + } + + data, err := hex.DecodeString(hexText) + if err != nil { + return nil, err + } + + return data, nil +} + +// writeAll schreibt alle Bytes des angegebenen Datenblocks auf den seriellen +// Dateideskriptor. unix.Write() darf weniger Bytes als angefordert schreiben. +func writeAll(fd int, data []byte) error { + for len(data) > 0 { + n, err := unix.Write(fd, data) + if err != nil { + if err == unix.EINTR { + continue + } + + return err + } + + if n == 0 { + return io.ErrShortWrite + } + + data = data[n:] + } + + return nil +} + // 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) { + printHexDump("RX", data) +} + +// printTX gibt einen gesendeten Datenblock als Hexdump aus. +func printTX(data []byte) { + printHexDump("TX", data) +} + +// printHexDump gibt einen Datenblock mit Zeitstempel, Richtung und Byteanzahl +// als Hexdump aus. +func printHexDump(direction string, data []byte) { fmt.Printf( - "%s RX %d Bytes\n", + "%s %s %d Bytes\n", time.Now().Format("15:04:05.000"), + direction, len(data), ) diff --git a/cmd/rs2322tcp-device-sim/main_test.go b/cmd/rs2322tcp-device-sim/main_test.go new file mode 100644 index 0000000..004ad1c --- /dev/null +++ b/cmd/rs2322tcp-device-sim/main_test.go @@ -0,0 +1,92 @@ +/* + * ============================================================================ + * Projekt.....: rs2322tcp + * Datei.......: main_test.go + * Copyright (C) 2026 Dieter Lang + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Beschreibung: + * Unit-Tests für die Hex-Eingabe des Geräte-Simulators. + * + * Getestet werden gültige Bytefolgen, optionale Leerzeichen, Groß- und + * Kleinschreibung sowie typische ungültige Eingaben. + * + * ============================================================================ + */ + +package main + +import ( + "bytes" + "testing" +) + +func TestParseHexBytes(t *testing.T) { + tests := []struct { + name string + input string + want []byte + wantErr bool + }{ + { + name: "mehrere Bytes mit Leerzeichen", + input: "46 41 00 10 0D", + want: []byte{0x46, 0x41, 0x00, 0x10, 0x0D}, + }, + { + name: "mehrere Bytes ohne Leerzeichen", + input: "464100100D", + want: []byte{0x46, 0x41, 0x00, 0x10, 0x0D}, + }, + { + name: "gemischte Groß- und Kleinschreibung", + input: "0a FF 01 b7", + want: []byte{0x0A, 0xFF, 0x01, 0xB7}, + }, + { + name: "ein Byte", + input: "00", + want: []byte{0x00}, + }, + { + name: "leere Eingabe", + input: "", + want: nil, + }, + { + name: "nur Leerzeichen", + input: " ", + want: nil, + }, + { + name: "ungültiges Hex-Zeichen", + input: "GG", + wantErr: true, + }, + { + name: "ungerade Hex-Länge", + input: "4", + wantErr: true, + }, + { + name: "dreistellige Eingabe", + input: "100", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseHexBytes(tt.input) + + if (err != nil) != tt.wantErr { + t.Fatalf("Fehlerstatus: got %v, wantErr %v", err, tt.wantErr) + } + + if !bytes.Equal(got, tt.want) { + t.Fatalf("Ergebnis: got % X, want % X", got, tt.want) + } + }) + } +}