rs2322tcp/cmd/rs2322tcp-device-sim/main.go
2026-08-10 12:42:22 +02:00

164 lines
4.1 KiB
Go

/*
* ============================================================================
* 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()
}
}