Seite 1 von 1

Python Server für ZusiDisplay

Verfasst: 14.05.2017 19:02:59
von zweann
Liebe Community,

erstmal vielen Dank für die Aufnahme!

Zu meinem Anliegen:
Ich würde gern ZusiDisplay mit einem Python-Skript ansteuern, d.h. ich möchte Daten wie z.B. die aktuelle Uhrzeit und Geschwindigkeit per TCP an ZusiDisplay senden. Allerdings habe ich nicht so ganz verstanden wie die Daten aufgebaut sein müssen, damit ZusiDisplay sie versteht.
Kann mir da jemand konkret helfen?

Grüße
Sven

Re: Python Server für ZusiDisplay

Verfasst: 14.05.2017 19:50:15
von Jens Haupert
Hallo Sven,

im Zusi3-Handbuch im Kapitel 11.3 (ab Seite 652) wird das genutzte Protokoll beschrieben.

Viele Grüße
Jens

Re: Python Server für ZusiDisplay

Verfasst: 14.05.2017 20:02:50
von jonathanp
Das Datenformat ist kein Geheimnis. Die Zusi 3 Dokumentation beschreibt das TCP-Protokoll und hat detaillierte Beispiele. Darüber hinaus können Sie mit dem Programm "TCPDemoOutput" genau die Daten sehen, die Zusi überträgt.

Allerdings, ist das Datum und die Zeit ein bisschen komisch. Ich berechne es auf C++ so:

Code: Alles auswählen

//Digital Time
time_t epoch = time(NULL);
struct tm buf;
localtime_s(&buf, &epoch);
data = new zusi::FloatFsDataItem(zusi::Fs_UhrzeitDigital(0x23), (buf.tm_hour * 3600 + buf.tm_min * 60 + buf.tm_sec) * (1.0f / 86400));

//Date
int daysSinceEpoch = static_cast<int>((epoch / (60 * 60 * 24)) + 25569);
data = new zusi::FloatFsDataItem(zusi::Fs_Datum(0x4B), static_cast<float>(daysSinceEpoch));
Leider ist die Arbeit mit binären Protokollen nicht so einfach in Python wie in C/C++.

Re: Python Server für ZusiDisplay

Verfasst: 14.05.2017 20:19:00
von zweann
Hallo Jens,

ich habe leider nur Zusidisplay und den TCP-Server zur Hand, da ich das ganze mit dem Trainsimulator verarbeiten möchte.

Viele Grüße
Sven

Re: Python Server für ZusiDisplay

Verfasst: 17.05.2017 16:18:46
von zweann
Hi Jens,

ich hab mich mal durch die TCP Doku von Zusi2 durchgewühlt und habe den Aufbau der Daten gefunden.
Vielen Dank für den Hinweis!

Re: Python Server für ZusiDisplay

Verfasst: 17.05.2017 17:48:56
von F. Schn.
Im Grundsatz findest du in den verschiedenen Zweigen von https://github.com/FSchn/ZusiTCP.NET/tr ... asterTest1" target="_blank auch Zusi-TCP-Master (für .Net), die du vielleicht auf deine Programmiersprache übertragen könntest. (Oder gibt es vielleicht sogar etwas, mit dem man .Net aus Python nutzen kann?)

Einen ähnlichen Fall gab es auch schon mal hier: https://forum.zusi.de/viewtopic.php?f=53&t=12087" target="_blank Der dürfte aber keine so ideale Vorgehensweise sein, wenn es auch anderst geht.

Re: Python Server für ZusiDisplay

Verfasst: 03.06.2017 17:22:21
von Bernhard
Für Zusi 2 habe ich vor vielen Jahren Server und Client in Python entwickelt, deren Architektur in groben Zügen hier nachzulesen ist. Leider hat man für das Datenformat von Zusi 3 statt auf eine etablierte Serialisierungsmethode auf eine Eigenentwicklung gesetzt, die noch dazu aufgrund des Verzichts auf ein herkömmliches Längenpräfix etwas mühsam zu parsen ist. Insbesondere für Zusi 3 kann ich daher die Bibliothek construct empfehlen, die einen deklarativen Parser für Binärdaten implementiert. Streng der Nomenklatur in Kapitel 11.3.1 des Handbuchs folgend, lässt sich das Grundgerüst des Protokolls etwa folgendermaßen beschreiben:

Code: Alles auswählen

attribute = Struct(
    'PACKET_LENGTH' / Int32ul,
    'ID' / Int16ul,
    'DATA' / Bytes(this.PACKET_LENGTH - 2),
)

node = Struct(
    'PACKET_LENGTH' / Int32ul, Check(this.PACKET_LENGTH == 0),
    'ID' / Int16ul,
    'ATTRIBUTES' / GreedyRange(attribute),
    'NODES' / GreedyRange(LazyBound(lambda ctx: node)),
    'END' / Const(b'\xFF\xFF\xFF\xFF'),
)
Eine praktische Anwendung auf den exemplarischen HELLO-Befehl aus Kapitel 11.3.4:

Code: Alles auswählen

HELLO = """
00 00 00 00
01 00

    00 00 00 00
    01 00

        04 00 00 00
        01 00
        02 00

        04 00 00 00
        02 00
        02 00

        0A 00 00 00
        03 00
        46 61 68 72 70 75 6C 74

        05 00 00 00
        04 00
        32 2E 30

    FF FF FF FF

FF FF FF FF
"""

def a2b(hexstr):
    return binascii.a2b_hex(''.join(hexstr.split()))

print(node.parse(a2b(HELLO))))
Die Ausgabe:

Code: Alles auswählen

Container: 
    PACKET_LENGTH = 0
    ID = 1
    ATTRIBUTES = ListContainer: 
    NODES = ListContainer: 
        Container: 
            PACKET_LENGTH = 0
            ID = 1
            ATTRIBUTES = ListContainer: 
                Container: 
                    PACKET_LENGTH = 4
                    ID = 1
                    DATA = \x02\x00 (total 2)
                Container: 
                    PACKET_LENGTH = 4
                    ID = 2
                    DATA = \x02\x00 (total 2)
                Container: 
                    PACKET_LENGTH = 10
                    ID = 3
                    DATA = Fahrpult (total 8)
                Container: 
                    PACKET_LENGTH = 5
                    ID = 4
                    DATA = 2.0 (total 3)
            NODES = ListContainer: 
            END = \xff\xff\xff\xff (total 4)
    END = \xff\xff\xff\xff (total 4)
Container und ListContainer sind Unterklassen von dict beziehungsweise list und können daher nach allen Regeln der Kunst auseinandergenommen werden.

Ausgehend von diesem sehr einfachen Beispiel sind einige Erweiterungen denkbar:
  • Natürlich gibt es bei der Definition des Datenformats noch Verbesserungspotential. Das beginnt bei der überflüssigen Namensvergabe für den konstanten String '\xFF\xFF\xFF\xFF' und geht bis zur Abbildung der strukturell nicht unähnlichen Attribute und Knoten in einem einzigen Konstrukt mittels IfThenElse.
  • Mit Hilfe eines Adapters kann das Ergebnis von node.parse in eine noch pythoneskere Datenstruktur oder sogar eine völlig eigene Objekthierarchie konvertiert werden.
  • GreedyRange(node).parse_stream liest so viele komplette Knoten wie möglich aus dem übergebenen Datenstrom ein und kann so perfekt mit Datenblöcken unbekannter Länge umgehen, wie sie typischerweise beim Lesen von Sockets vorkommen.
  • Das deklarativ definierte Datenformat eignet sich nicht nur zum Deserialisieren sondern auch zum Serialisieren von Befehlen. Aus einer abstrakten Datenstruktur kann durch Aufruf von node.build ohne zusätzlichen Code die entsprechende Binärrepräsentation generiert und danach auf die Reise geschickt werden.
  • Noch völlig außer Acht gelassen wurde die korrekte Interpretation der Attribut-Nutzdaten, die bis dato nur als scheinbar bedeutungslose Folgen von Bytes verarbeitet werden. Eine Nachrichten-Typhierarchie, deren Klassen die detaillierten Informationen der Kapitel 11.3.2 und 11.3.3 kapseln, ist die übliche Herangehensweise, aber eine sinnvolle Lösung dieses Problems sollte vom konkreten Anwendungsfall abhängen.

Re: Python Server für ZusiDisplay

Verfasst: 04.06.2017 01:02:06
von Michael Skupin
Hallo Bernhard,

genau danach habe ich auch gesucht, danke für den Tip.


Michael