Zum Inhalt

stsgraph

Classes:

  • GraphClient

    Erweiterter PluginClient mit Graphdarstellung der Basisdaten vom Simulator.

Functions:

  • test

    Testprogramm

GraphClient

GraphClient(name: str, autor: str, version: str, text: str)

              flowchart TD
              stskit.plugin.stsgraph.GraphClient[GraphClient]
              stskit.plugin.stsplugin.PluginClient[PluginClient]

                              stskit.plugin.stsplugin.PluginClient --> stskit.plugin.stsgraph.GraphClient
                


              click stskit.plugin.stsgraph.GraphClient href "" "stskit.plugin.stsgraph.GraphClient"
              click stskit.plugin.stsplugin.PluginClient href "" "stskit.plugin.stsplugin.PluginClient"
            

Erweiterter PluginClient mit Graphdarstellung der Basisdaten vom Simulator.

Attributes:

  • signalgraph

    Der Signalgraph enthält das Gleisbild aus der Wegeliste der Plugin-Schnittstelle mit sämtlichen Knoten und Kanten. Das 'typ'-Attribut wird auf den sts-Knotentyp (int) gesetzt. Kanten werden entsprechend der Nachbarrelationen aus der Wegeliste ('typ'-attribut 'gleis') gesetzt. Der Graph ist gerichtet, da die nachbarbeziehung i.a. nicht reziprok ist. Die Kante zeigt auf die Knoten, die als Nachbarn aufgeführt sind. Meist werden von der Schnittstelle jedoch Kanten in beide Richtungen angegeben, weshalb z.B. nicht herausgefunden werden kann, für welche Richtung ein Signal gilt.

    Der Graph sollte nicht verändert werden. Es wird nicht erwartet, dass sich der Graph im Laufe eines Spiels ändert.

    Die Signaldistanz wird am Anfang auf 1 gesetzt.

  • bahnsteiggraph

    Der Bahnsteiggraph enthält alle Bahnsteige aus der Bahnsteigliste der Plugin-Schnittstelle als Knoten. Kanten werden entsprechend der Nachbarrelationen gesetzt. Der Graph ist ungerichtet, da die Nachbarbeziehung als reziprok aufgefasst wird.

    Der Graph sollte nicht verändert werden. Es wird nicht erwartet, dass sich der Graph im Laufe eines Spiels ändert.

  • zuggraph

    Der Zuggraph enthält alle Züge aus der Zugliste der Plugin-Schnittstelle als Knoten. Kanten werden aus den Ersatz-, Kuppeln- und Flügeln-Flags gebildet.

    Der Zuggraph verändert sich im Laufe eines Spiels. Neue Züge werden hinzugefügt. Ausgefahrene und ersetzte Züge werden beibehalten und als "ausgefahren" markiert.

  • zielgraph

    Der Zielgraph enthält die Zielpunkte aller Züge. Die Punkte sind gemäss Anordnung im Fahrplan sowie planmässigen Abhängigkeiten (Ersatz, Kuppeln, Flügeln) verbunden.

    Bei Ein- und Ausfahrten wird die Ankunfts- und Abfahrtszeit auf 1 Minute vor bzw. nach dem Halt geschätzt.

  • bahnhofteile (dict[str, str]) –

    Ordnet jedem Gleis einen Bahnhofteil zu. Der Bahnhofteil entspricht dem alphabetisch ersten Gleis in der Nachbarschaft. Der Dictionary wird durch _bahnhofteile_gruppieren gefüllt.

Methods:

Source code in stskit/plugin/stsgraph.py
def __init__(self, name: str, autor: str, version: str, text: str):
    super().__init__(name, autor, version, text)

    self.signalgraph = SignalGraph()
    self.bahnsteiggraph = BahnsteigGraph()
    self.zuggraph = ZugGraph()
    self.zielgraph = ZielGraph()
    self.bahnhofteile: dict[str, str] = {}
    self.anschlussgruppen: dict[int, str] = {}

antwort_channel_in property

antwort_channel_in: SendChannel[Element]

Eingang asynchrone Warteschlange für Antworten vom Simulator

Antworten vom Simulator werden während des Parsens als Element-Objekte an diese Warteschlange übergeben.

antwort_channel_out property

antwort_channel_out: ReceiveChannel[Element]

Ausgang asynchrone Warteschlange für Antworten vom Simulator

Antworten vom Simulator können asynchron als Element-Objekte aus dieser Warteschlange entnommen und verarbeitet werden.

ereignis_channel_in property

ereignis_channel_in: SendChannel[Ereignis]

Eingang asynchrone Warteschlange für Ereignismeldungen vom Simulator

Ereignismeldungen vom Simulator werden während des Parsens als Ereignis-Objekte an diese Warteschlange übergeben.

ereignis_channel_out property

ereignis_channel_out: ReceiveChannel[Ereignis]

Ausgang asynchrone Warteschlange für Ereignismeldungen vom Simulator

Ereignismeldungen vom Simulator können asynchron als Ereignis-Objekte aus dieser Warteschlange entnommen und verarbeitet werden.

stream property

stream: Stream

Asynchroner Kommunikationsstream zum Simulator

calc_simzeit

calc_simzeit() -> datetime

Simulatorzeit ohne Serverabfrage abschätzen.

Der time_offset muss vorher einmal mittels request_simzeit kalibriert worden sein.

Der Rückgabewert enthält das aktuelle (Client-)Datum. Das ist nötig, damit mit der Uhrzeit gerechnet werden kann. Da der Simulator kein Datum kennt, sollten die Datumsfelder nach der Rechnung nicht beachtet werden. Der Fahrplan (in FahrplanZeile) enthält lediglich datetime.time-Objekte. Ein datetime.time-Objekt kann einfach über die time-Methode extrahiert werden.

Returns:

  • datetime

    Extrapolierte Simulatorzeit

Source code in stskit/plugin/stsplugin.py
def calc_simzeit(self) -> datetime.datetime:
    """
    Simulatorzeit ohne Serverabfrage abschätzen.

    Der `time_offset` muss vorher einmal mittels `request_simzeit` kalibriert worden sein.

    Der Rückgabewert enthält das aktuelle (Client-)Datum.
    Das ist nötig, damit mit der Uhrzeit gerechnet werden kann.
    Da der Simulator kein Datum kennt, sollten die Datumsfelder nach der Rechnung nicht beachtet werden.
    Der Fahrplan (in `FahrplanZeile`) enthält lediglich `datetime.time`-Objekte.
    Ein `datetime.time`-Objekt kann einfach über die `time`-Methode extrahiert werden.

    Returns:
        Extrapolierte Simulatorzeit
    """
    return datetime.datetime.now() + self.time_offset

gleis_abgleichen

gleis_abgleichen(gleis_name: str) -> str

Gleisnamen mit Bahnsteigliste abgleichen und ev. korrigieren

Die Methode versucht, Gleisnamen, die in der Bahnsteigliste nicht enthalten sind, mittels des gleisabgleich-Dict aufzulösen, um Gross/Kleinschreibung und ggf. andere Schreibfehler im Zugfahrplan zu korrigieren.

Parameters:

  • gleis_name

    (str) –

    Original-Gleisname

Returns:

  • str

    abgeglichener Name

Source code in stskit/plugin/stsplugin.py
def gleis_abgleichen(self, gleis_name: str) -> str:
    """
    Gleisnamen mit Bahnsteigliste abgleichen und ev. korrigieren

    Die Methode versucht, Gleisnamen, die in der Bahnsteigliste nicht enthalten sind,
    mittels des gleisabgleich-Dict aufzulösen,
    um Gross/Kleinschreibung und ggf. andere Schreibfehler im Zugfahrplan zu korrigieren.

    Args:
        gleis_name: Original-Gleisname

    Returns:
        abgeglichener Name
    """
    if gleis_name in self.bahnsteigliste:
        return gleis_name
    else:
        return self.gleisabgleich.get(gleis_name.lower(), gleis_name)

receiver async

receiver(*, task_status=TASK_STATUS_IGNORED)

Empfangsschleife: Antworten empfangen und verteilen

Alle Antworten ausser Ereignisse werden in untangle.Element-Objekte gepackt und an die Antworten-Queue übergeben. Ereignisse werden als stskit.model.Ereignis-Objekte an die Ereignisse-Queue übergeben.

Diese Coroutine muss explizit in einer trio.nursery gestartet werden und läuft, bis die Verbindung unterbrochen wird.

Source code in stskit/plugin/stsplugin.py
async def receiver(self, *, task_status=trio.TASK_STATUS_IGNORED):
    """
    Empfangsschleife: Antworten empfangen und verteilen

    Alle Antworten ausser Ereignisse werden in untangle.Element-Objekte gepackt
    und an die Antworten-Queue übergeben.
    Ereignisse werden als stskit.model.Ereignis-Objekte an die Ereignisse-Queue übergeben.

    Diese Coroutine muss explizit in einer trio.nursery gestartet werden
    und läuft, bis die Verbindung unterbrochen wird.
    """

    parser: Any = xml.sax.make_parser()
    handler = untangle.Handler()
    parser.setContentHandler(handler)
    ro = re.compile(r"&[a-z]+;")

    def resolve_char_ref(match) -> str:
        try:
            cp = html.entities.name2codepoint[match.group(0)[1:-1]]
            return f"&#{cp};"
        except (KeyError, IndexError):
            return "?"

    self._antwort_channel_in, self._antwort_channel_out = trio.open_memory_channel(0)
    self._ereignis_channel_in, self._ereignis_channel_out = trio.open_memory_channel(0)
    task_status.started()

    async with self.antwort_channel_in:
        async with self.ereignis_channel_in:
            async for bs in self.stream:
                for s in bs.decode().split('\n'):
                    logger.debug("empfang: " + s)
                    if not s:
                        continue

                    s = re.sub(ro, resolve_char_ref, s)
                    try:
                        parser.feed(s)
                    except xml.sax.SAXException:
                        logger.exception("error parsing xml: " + s)

                    # xml tag complete?
                    if len(handler.elements) == 0:
                        element = handler.root
                        try:
                            parser.close()
                        except xml.sax.SAXParseException as e:
                            # rare parse exception: unclosed element
                            logger.exception(e)
                            logger.error(f"offending string: {s}")
                            print(e.getMessage(), file=sys.stderr)
                            continue

                        handler.root = untangle.Element(None, None)
                        handler.root.is_root = True

                        try:
                            tag = dir(element)[0]
                        except IndexError:
                            # leeres element
                            continue
                        else:
                            if tag == "ereignis":
                                ereignis = Ereignis().update(getattr(element, tag))
                                ereignis.zeit = self.calc_simzeit()
                                await self.ereignis_channel_in.send(ereignis)
                            else:
                                await self.antwort_channel_in.send(element)

register async

register() -> None

Klient beim Simulator registrieren.

Diese Funktion muss als erste nach dem Verbinden aufgerufen werden, da sie auch die Statusantwort nach der Verbindungsaufnahme auswertet.

Source code in stskit/plugin/stsplugin.py
async def register(self) -> None:
    """
    Klient beim Simulator registrieren.

    Diese Funktion muss als erste nach dem Verbinden aufgerufen werden,
    da sie auch die Statusantwort nach der Verbindungsaufnahme auswertet.
    """
    status = await self.antwort_channel_out.receive()
    check_status(status)

    await self._send_request("register", name=self.name, autor=self.autor, version=self.version,
                             protokoll='1', text=self.text)
    status = await self.antwort_channel_out.receive()
    check_status(status)
    self.registered.set()

request_anlageninfo async

request_anlageninfo() -> None

Anlageninfo anfordern.

Die Antwort wird im anlageninfo-Attribut gespeichert.

Source code in stskit/plugin/stsplugin.py
async def request_anlageninfo(self) -> None:
    """
    Anlageninfo anfordern.

    Die Antwort wird im anlageninfo-Attribut gespeichert.
    """
    await self._send_request(AnlagenInfo.tag)
    response = await self.antwort_channel_out.receive()
    self.anlageninfo = AnlagenInfo().update(response.anlageninfo)

request_ereignis async

request_ereignis(art: str, zids: Iterable[int]) -> None

Ereignismeldung anfordern

Nach Nummernwechsel muss man Ereignismeldungen neu anfordern. Ausser für "Einfahrt" schicken wir daher Anforderungen nur, wenn der Zug sichtbar ist.

Anforderungen werden in registrierte_ereignisse notiert, damit sie nicht wiederholt gesendet werden.

Parameters:

  • art

    (str) –

    art des ereignisses, cf. model.Ereignis.arten

  • zids

    (Iterable[int]) –

    menge oder sequenz von zug-id-nummern

Source code in stskit/plugin/stsplugin.py
async def request_ereignis(self, art: str, zids: Iterable[int]) -> None:
    """
    Ereignismeldung anfordern

    Nach Nummernwechsel muss man Ereignismeldungen neu anfordern.
    Ausser für "Einfahrt" schicken wir daher Anforderungen nur, wenn der Zug sichtbar ist.

    Anforderungen werden in `registrierte_ereignisse` notiert,
    damit sie nicht wiederholt gesendet werden.

    Args:
        art: art des ereignisses, cf. model.Ereignis.arten
        zids: menge oder sequenz von zug-id-nummern
    """
    zids = set(zids).difference(self.registrierte_ereignisse[art])
    for zid in zids:
        if zid in self.zugliste and (art == "einfahrt" or self.zugliste[zid].sichtbar):
            await self._send_request("ereignis", art=art, zid=zid)
            self.registrierte_ereignisse[art].add(zid)

request_simzeit async

request_simzeit() -> datetime

Simulatorzeit anfragen.

Die Funktion fragt die aktuelle Simulatorzeit an und liefert sie in einem datetime.time-Objekt.

Basierend auf der Antwort setzt sie ausserdem client_datetime, server_datetime und time_offset. Diese Attribute können benutzt werden, um die Simulatorzeit zu berechnen (calc_simzeit-Funktion), ohne dass eine erneute Anfrage geschickt werden muss.

Info

client_datetime und server_datetime enthalten das aktuelle Datum. Das ist nötig, um den time_offset als timedelta zu berechnen. Da der Simulator kein Datum kennt, sollten die Datumsfelder nicht beachtet werden. Die datetime.datetime.time-Methode ist ein schneller Weg, ein datetime.time-Objekt zu erhalten.

Returns:

Source code in stskit/plugin/stsplugin.py
async def request_simzeit(self) -> datetime.datetime:
    """
    Simulatorzeit anfragen.

    Die Funktion fragt die aktuelle Simulatorzeit an und liefert sie in einem `datetime.time`-Objekt.

    Basierend auf der Antwort setzt sie ausserdem `client_datetime`, `server_datetime` und `time_offset`.
    Diese Attribute können benutzt werden, um die Simulatorzeit zu berechnen (`calc_simzeit`-Funktion),
    ohne dass eine erneute Anfrage geschickt werden muss.

    Info:
        `client_datetime` und `server_datetime` enthalten das aktuelle Datum.
        Das ist nötig, um den `time_offset` als `timedelta` zu berechnen.
        Da der Simulator kein Datum kennt, sollten die Datumsfelder nicht beachtet werden.
        Die `datetime.datetime.time`-Methode ist ein schneller Weg, ein `datetime.time`-Objekt zu erhalten.

    Returns:
        Aktuelle Simulatorzeit
    """
    self.client_datetime = datetime.datetime.now()
    await self._send_request("simzeit", sender=0)
    simzeit = await self.antwort_channel_out.receive()
    secs, msecs = divmod(int(simzeit.simzeit['zeit']), 1000)
    mins, secs = divmod(secs, 60)
    hrs, mins = divmod(mins, 60)
    t = datetime.time(hour=hrs, minute=mins, second=secs, microsecond=msecs * 1000)
    self.server_datetime = datetime.datetime.combine(self.client_datetime, t)
    self.time_offset = (self.server_datetime - self.client_datetime)
    return self.server_datetime

request_zug async

request_zug(zid: int) -> ZugDetails | None

Einzelnen Zug und Fahrplan anfragen.

Der Zug wird in die Zugliste eingetragen bzw. aktualisiert und als ZugDetails-Objekt zurückgegeben.

Parameters:

  • zid

    (int) –

    einzelne zug-id

Returns:

  • ZugDetails | None

    ZugDetails inkl. Fahrplan.

  • ZugDetails | None

    None, wenn der Zug nicht verzeichnet ist.

Source code in stskit/plugin/stsplugin.py
async def request_zug(self, zid: int) -> ZugDetails | None:
    """
    Einzelnen Zug und Fahrplan anfragen.

    Der Zug wird in die Zugliste eingetragen bzw. aktualisiert und als `ZugDetails`-Objekt zurückgegeben.

    Args:
        zid: einzelne zug-id

    Returns:
        `ZugDetails` inkl. Fahrplan.
        None, wenn der Zug nicht verzeichnet ist.
    """
    zid = int(zid)
    if zid:
        await self.request_zugdetails(zid)
        await self.request_zugfahrplan(zid)
    else:
        return None

    try:
        zug = self.zugliste[zid]
        return zug
    except KeyError:
        return None

request_zugdetails async

request_zugdetails(zid: int | Iterable[int] | None = None) -> None

ZugDetails eines, mehrerer oder aller Züge anfragen.

Wenn ein ZugDetails-Objekt mit der zid bereits in der Zugliste angelegt ist, wird es aktualisiert, andernfalls neu angelegt. Wenn ein Fehler auftritt (weil z.B. der Zug nicht mehr im Stellwerk ist), wird der Zug aus der Zugliste gelöscht.

Parameters:

  • zid

    (int | Iterable[int] | None, default: None ) –

    Einzelne Zug-ID, Iterable von Zug-IDs, oder None (alle in der Zugliste).

Source code in stskit/plugin/stsplugin.py
async def request_zugdetails(self, zid: int | Iterable[int] | None = None) -> None:
    """
    ZugDetails eines, mehrerer oder aller Züge anfragen.

    Wenn ein ZugDetails-Objekt mit der zid bereits in der Zugliste angelegt ist,
    wird es aktualisiert, andernfalls neu angelegt.
    Wenn ein Fehler auftritt (weil z.B. der Zug nicht mehr im Stellwerk ist),
    wird der Zug aus der Zugliste gelöscht.

    Args:
        zid: Einzelne Zug-ID, Iterable von Zug-IDs, oder None (alle in der Zugliste).
    """

    if zid is not None:
        if isinstance(zid, Iterable):
            zids = list(iter(zid))
        else:
            zids = [int(zid)]
    else:
        zids = list(self.zugliste.keys())

    for zid in sorted(map(int, zids)):
        await self.request_zugdetails_einzeln(zid)

request_zugfahrplan async

request_zugfahrplan(zid: int | Iterable[int] | None = None)

Fahrplan eines, mehrerer oder aller Züge anfragen.

Das ZugDetails-Objekt muss in der Zugliste bereits existieren.

Abgefahrene Wegpunkte sind im Fahrplan nicht mehr vorhanden.

Parameters:

  • zid

    (int | Iterable[int] | None, default: None ) –

    einzelne zug-id, iterable von zug-ids, oder None (alle in der liste).

Source code in stskit/plugin/stsplugin.py
async def request_zugfahrplan(self, zid: int | Iterable[int] | None = None):
    """
    Fahrplan eines, mehrerer oder aller Züge anfragen.

    Das ZugDetails-Objekt muss in der Zugliste bereits existieren.

    Abgefahrene Wegpunkte sind im Fahrplan nicht mehr vorhanden.

    Args:
        zid: einzelne zug-id, iterable von zug-ids, oder None (alle in der liste).
    """

    if zid is not None:
        zids = [zid]
    else:
        zids = self.zugliste.keys()
    for zid in sorted(map(int, zids)):
        if zid in self.zugliste:
            await self.request_zugfahrplan_einzeln(zid)

resolve_zugflags async

resolve_zugflags(zid: int | Iterable[int] | None = None) -> None

Folgezüge aus den Zugflags auflösen.

Da request_zugliste die Folgezüge (Ersatz-, Flügel- und Kuppelzüge) nicht automatisch erhält, lesen wir diese aus den Zugflags aus und fragen ihre Details und Fahrpläne explizit an. Die Funktion arbeitet iterativ, bis alle Folgezüge aufgelöst sind. Die Züge werden in die Zugliste eingetragen und im Stammzug referenziert.

Info

zids sind nicht geordnet. Ersatzzüge können eine tiefere zid als der Stammzug haben.

Parameters:

  • zid

    (int | Iterable[int] | None, default: None ) –

    Einzelne Zug-ID, Iterable von Zug-IDs, oder None (alle in der Liste).

Source code in stskit/plugin/stsplugin.py
async def resolve_zugflags(self, zid: int | Iterable[int] | None = None) -> None:
    """
    Folgezüge aus den Zugflags auflösen.

    Da `request_zugliste` die Folgezüge (Ersatz-, Flügel- und Kuppelzüge) nicht automatisch erhält,
    lesen wir diese aus den Zugflags aus und fragen ihre Details und Fahrpläne explizit an.
    Die Funktion arbeitet iterativ, bis alle Folgezüge aufgelöst sind.
    Die Züge werden in die Zugliste eingetragen und im Stammzug referenziert.

    Info:
        zids sind nicht geordnet. Ersatzzüge können eine tiefere zid als der Stammzug haben.

    Args:
        zid: Einzelne Zug-ID, Iterable von Zug-IDs, oder None (alle in der Liste).
    """
    if zid is not None:
        zids = [int(zid)]
    else:
        zids = list(self.zugliste.keys())

    erledigte_zids = []
    while zids:
        zid = zids.pop(0)
        if zid in erledigte_zids:
            continue  # unendliche rekursion verhindern
        else:
            erledigte_zids.append(zid)

        try:
            zug = self.zugliste[zid]
        except KeyError:
            continue

        for planzeile in zug.fahrplan:
            if zid2 := planzeile.ersatz_zid():
                logger.info(f"zid {zid2} als ersatz für {zid} anfragen")
                zug2 = await self.request_zug(zid2)
                if zug2 is not None:
                    planzeile.ersatzzug = zug2
                    zug2.stamm_zids.add(zug.zid)
                    zug2.verspaetung = zug.verspaetung
                    zids.append(zid2)
                else:
                    logger.warning(f"keine antwort für zug {zid2}")
            if zid2 := planzeile.fluegel_zid():
                logger.info(f"zid {zid2} als flügel für {zid} anfragen")
                zug2 = await self.request_zug(zid2)
                if zug2 is not None:
                    planzeile.fluegelzug = zug2
                    zug2.stamm_zids.add(zug.zid)
                    zug2.verspaetung = zug.verspaetung
                    zids.append(zid2)
                else:
                    logger.warning(f"keine antwort für zug {zid2}")
            if zid2 := planzeile.kuppel_zid():
                logger.info(f"zid {zid2} als kuppel für {zid} anfragen")
                zug2 = await self.request_zug(zid2)
                if zug2 is not None:
                    planzeile.kuppelzug = zug2
                    zug2.stamm_zids.add(zug.zid)
                    zids.append(zid2)
                else:
                    logger.warning(f"keine antwort für zug {zid2}")

update_bahnsteig_zuege

update_bahnsteig_zuege()

Züge in Bahnsteigliste eintragen.

Im zuege-attribut der Bahnsteige werden die an den Bahnsteig disponierten Züge aufgelistet. zuege ist ein Dictionary und bildet zid auf ZugDetails ab. Die ZugDetails sind weak References.

Source code in stskit/plugin/stsplugin.py
def update_bahnsteig_zuege(self):
    """
    Züge in Bahnsteigliste eintragen.

    Im `zuege`-attribut der Bahnsteige werden die an den Bahnsteig disponierten Züge aufgelistet.
    `zuege` ist ein Dictionary und bildet zid auf ZugDetails ab.
    Die ZugDetails sind weak References.
    """

    for bahnsteig in self.bahnsteigliste.values():
        bahnsteig.zuege.clear()

    for zid in self.zugliste.keys():
        zug = self.zugliste[zid]
        for fahrplanzeile in zug.fahrplan:
            try:
                bahnsteig = self.bahnsteigliste[fahrplanzeile.gleis]
            except KeyError:
                pass
            else:
                bahnsteig.zuege[zid] = zug

update_wege_zuege

update_wege_zuege()

Züge in Wegelisten eintragen.

Im Züge-Attribut der Wege und Knoten (Einfahrten, Ausfahrten, Haltepunkte) werden die fahrplanmässig daran vorbei kommenden Züge aufgelistet.

Source code in stskit/plugin/stsplugin.py
def update_wege_zuege(self):
    """
    Züge in Wegelisten eintragen.

    Im Züge-Attribut der Wege und Knoten (Einfahrten, Ausfahrten, Haltepunkte)
    werden die fahrplanmässig daran vorbei kommenden Züge aufgelistet.
    """
    for knoten in self.wege.values():
        knoten.zuege.clear()

    einfahrten = {knoten.name: knoten for knoten in self.wege_nach_typ[6]}
    ausfahrten = {knoten.name: knoten for knoten in self.wege_nach_typ[7]}
    bahnsteige = {knoten.name: knoten for knoten in self.wege_nach_typ[5]}
    haltepunkte = {knoten.name: knoten for knoten in self.wege_nach_typ[12]}
    haltepunkte.update(bahnsteige)

    for zid in self.zugliste.keys():
        zug = self.zugliste[zid]

        try:
            einfahrten[zug.von].zuege[zid] = zug
        except KeyError:
            pass
        try:
            ausfahrten[zug.nach].zuege[zid] = zug
        except KeyError:
            pass
        for fahrplanzeile in zug.fahrplan:
            try:
                haltepunkte[fahrplanzeile.gleis].zuege[zid] = zug
            except KeyError:
                pass

test async

test(*args, **kwargs) -> GraphClient

Testprogramm

Das testprogramm fragt alle Daten einmalig vom Simulator ab.

Der GraphClient bleibt bestehen, damit weitere Details aus den statischen Attributen ausgelesen werden können. Die Kommunikation mit dem Simulator wird jedoch geschlossen.

Source code in stskit/plugin/stsgraph.py
async def test(*args, **kwargs) -> GraphClient:
    """
    Testprogramm

    Das testprogramm fragt alle Daten einmalig vom Simulator ab.

    Der GraphClient bleibt bestehen, damit weitere Details aus den statischen Attributen ausgelesen werden können.
    Die Kommunikation mit dem Simulator wird jedoch geschlossen.
    """

    client = GraphClient(name='test', autor='tester', version='0.0', text='testing the graph client')
    await client.connect()

    try:
        async with client.stream:
            async with trio.open_nursery() as nursery:
                await nursery.start(client.receiver)
                await client.register()
                await client.request_simzeit()
                await client.request_anlageninfo()
                await client.request_bahnsteigliste()
                await client.request_wege()
                await client.request_zugliste()
                await client.request_zugdetails()
                await client.request_zugfahrplan()
                await client.resolve_zugflags()
                raise TaskDone()
    except TaskDone:
        pass

    return client