Files
sps_skel/doc/Klassenhierarchie.md
T
Michael StanglandClaude Opus 5.5 e2a80e6de1 Doku: Datenquellen Layout/Hardwareplanung, Konzept Layout+Hardware -> SCL
- Klassenhierarchie.md, Kapitel Daten: vollstaendige Rohangaben aus
  Anlagen-Hardware (CSV-Spalten, Merkmale je TeileArt) und Hardwareplanung
  (EA-Liste, TIA-Import, WSCAD, positionsdraw)
- Datenquellen_Layout_Hardwareplanung.md: was TROs/SCL aus Layout,
  Hardwareplanung und Defaults bekommen; 1Sep1Swi- und Vario-Aufruf
  zeilenweise mit Herkunft
- Konzept_Layout_Hardware_SCL.md: Zielablauf tro_flow -> hw_import ->
  hw_assign -> scl_skeleton, Rolle der bestehenden Skripte, Festlegungen
  zu TRO-IDs (stabil ueber Lage, --renumber) und Staubereichen
- CLAUDE.md: beide neuen Dokumente in der Leseliste

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:33:26 +02:00

737 lines
45 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Klassenhierarchie
Bestandsaufnahme aller Python-Klassen in `lib/` (Stand 2026-10-10). Kurzes
Ergebnis vorweg: es gibt praktisch **keine Vererbung** in diesem Code (eine
Ausnahme: `DerivedTro(TroBase)`, plus die Standard-Exception
`RegistrationError(RuntimeError)`). Alles andere sind flache, eigenständige
Klassen — entweder `@dataclass` (veränderlicher Laufzeit-/Analysezustand) oder
Pydantic `BaseModel` (validierte Daten, die auch als JSON ein-/ausgehen). Eine
klassische "ist-ein"-Hierarchie zu suchen wäre hier der falsche Blickwinkel;
interessanter ist, **wer wen enthält/referenziert** und in welchem Modul-Layer
eine Klasse sitzt (siehe Layer-Kommentar oben in `model.py`).
## Modul-Layer (wer darf wen importieren)
```
tro_catalog.py (Typ-Ebene: was ein TRO-Typ ist)
|
v
model.py (Instanz-Ebene: ein konkreter TRO, die TRO-JSON)
|
v
tro_flow.py (Ableitung: CSV-Graph -> Analysis -> TroDocument)
|
+----------------------+
v v
scl_context.py tro_annotate.py (Ausgabe: annotierte Zeichnung)
(TroDocument -> SclFile, Render-Kontext)
|
v
scl_skeleton.py (Ausgabe: SclFile -> FB_Main.scl, generisch oder per Jinja-Template)
material_flow.py dxf_registration.py (eigenständig, ohne Abhängigkeit auf obige)
tro_overrides.py (importiert model.Connection / model.TroCandidate)
e_planning.py (Brücke zur E-Planung; importiert dxf_registration, liest das
CSV über material_flow; scl_context kennt nur EplanDocument)
```
`tro_flow.py` und `tro_annotate.py` definieren keine eigenen Klassen (nur
Funktionen). `scl_skeleton.py` hat genau eine (`SclTemplates`, Jinja-Anbindung).
`material_flow.py` hat Graph-Dataclasses — das ist CSV-Rohmodell, nicht
TRO-Modell.
## Vererbung (ASCII)
```
RuntimeError (stdlib) BaseModel (pydantic, stdlib: pydantic)
| |
+-- RegistrationError +-- _Positioned (model.py, Mixin)
(dxf_registration.py) |
+-- TroBase
| +-- DerivedTro
+-- TroCandidate
```
Das ist die gesamte `class X(Y)`-Liste im Projekt — alle anderen Klassen unten
haben `object`/`BaseModel` direkt als einzige Basis und erscheinen hier nur
noch als Gruppen nach Datei.
## Klassen nach Datei, mit Komposition ("enthält")
```
lib/tro_catalog.py [Typ-Katalog, keine Instanzdaten]
├── TroItem (Konstanten-Klasse: Bauteilarten wie SEPARATOR, SWITCH)
├── TroSymbol (Konstanten-Klasse: Zeichensymbole wie CIRCLE, TRIANGLE)
├── TroStyle(BaseModel) Farbe/Darstellung einer Typgruppe
└── TroDefinition(BaseModel)
└─ style: TroStyle (1 TroDefinition hat genau 1 TroStyle)
lib/model.py [Instanz-Ebene / TRO-JSON]
├── TroBase(BaseModel)
│ └─ definition() --> TroDefinition (gelesen aus tro_catalog, nicht gespeichert)
│ └── DerivedTro(TroBase) [einzige echte Vererbung im Projekt]
├── _Positioned(BaseModel) (Mixin: position {x,y,z}, describe_position)
│ ├── TroBase, TroCandidate
├── Connection(BaseModel) (from/to/kind zwischen zwei TRO-IDs)
├── TroCandidate(_Positioned) (gemeldete Stelle, an der ein TRO erwartet wird)
├── Analysis(BaseModel)
│ ├─ tros: list[DerivedTro]
│ ├─ isolated: list[DerivedTro]
│ └─ boundaries: list[DerivedTro]
└── TroDocument(BaseModel) (= <csv>_tro.json)
├─ tros: list[DerivedTro]
├─ boundaries: list[DerivedTro]
├─ connections: list[Connection]
└─ candidates: list[TroCandidate]
lib/material_flow.py [CSV-Rohmodell -> Graph, @dataclass]
├── Element (1 CSV-Zeile)
├── Node
│ └─ element: Element
├── Edge
├── Graph
│ ├─ nodes: dict[str, Node]
│ ├─ edges: list[Edge]
│ └─ unassigned: list[Element]
├── TrackNode
│ └─ element: Element | None
├── TrackEdge
└── TrackGraph
├─ nodes: dict[str, TrackNode]
└─ edges: list[TrackEdge]
lib/tro_overrides.py [manuelle Korrekturen aus cfg/tro_overrides.ini, @dataclass]
├── TroOverride (Typkorrektur an einem abgeleiteten TRO: tro_id ODER separator)
├── SplitSpec (TRO aus einem bestehenden heraustrennen)
├── MergeSpec (mehrere TROs zu einem zusammenfassen)
└── Overrides (Sammelcontainer für eine CSV-Datei)
├─ tros: dict[str, TroOverride]
├─ splits: list[SplitSpec]
├─ merges: list[MergeSpec]
├─ connections: list[model.Connection]
└─ candidates: list[model.TroCandidate]
lib/dxf_registration.py [CSV<->DXF Koordinatentransform]
├── RegistrationError(RuntimeError)
└── Transform(BaseModel)
lib/scl_context.py [Render-Kontext für die SCL, BaseModel]
├── SclName (Wert + Merker todo: noch aus der E-Planung offen?)
├── SclSeparator (ein stInSeparator<n>-Block)
├── SclSwitch (ein stInSwitch<n>-Block + FC_Direction-Aufruf)
├── SclSwitchExit (Weichenabgang + darüber erreichbare TROs)
├── SclScanner (ein stInBarcodeReader-Block)
├── SclJam (Staubereich an Ein-/Ausgang)
├── SclTro (alles für eine REGION)
│ ├─ separators: list[SclSeparator]
│ ├─ switches: list[SclSwitch]
│ ├─ switch_exits: list[SclSwitchExit]
│ ├─ scanners: list[SclScanner]
│ ├─ jam_entries / jam_exits: list[SclJam]
│ └─ main_tro, safety, ... : SclName
└── SclFile (die ganze Datei)
└─ tros: list[SclTro] (in Fliessrichtung)
lib/scl_skeleton.py [SCL-Ausgabe]
└── SclTemplates (Ordner mit <fbType>.scl.j2, hält ein jinja2.Environment)
lib/e_planning.py [Brücke Layout <-> E-Planung]
├── EDevice(BaseModel) (ein BMK aus *_positionsdraw.json: VERW, Bezeichnung, Position)
├── LayoutPoint (@dataclass: punktartiges Layout-Bauteil in CSV-Koordinaten)
├── Rule (@dataclass: eine Regel aus cfg/e_planning.ini)
├── Assignment(BaseModel) (BMK in einer Rolle an einem Layout-Bauteil, mit Abstand)
├── Drive(BaseModel) (Motor MA ↔ Motorschutz FC)
└── EplanDocument(BaseModel) (= <csv>_eplan.json)
├─ transform: Transform (aus dxf_registration)
├─ assignments: list[Assignment]
└─ drives: list[Drive]
```
## Auffälligkeiten für ein mögliches Refactoring
1. ~~`OpenPoint` existiert zweimal~~ — erledigt 2026-10-10: zu
`model.TroCandidate` zusammengefasst (`expect` → `type_name`, `x/y/z` →
`position`); `ConnectionSpec` durch `model.Connection` ersetzt;
`tro_overrides.py` importiert beide aus `model.py`.
2. **Zwei parallele Graph-Hierarchien** in `material_flow.py`
(`Node/Edge/Graph` vs. `TrackNode/TrackEdge/TrackGraph`) teilen sich keine
Basisklasse, obwohl Struktur und Feldnamen sich stark ähneln (`node_id,
kind, ...` / `src, dst, ...`). Eine gemeinsame leichte Basis (z. B. ein
generisches `GraphBase[NodeT, EdgeT]`) wäre möglich, aber angesichts der
wenigen Stellen, die beide Typen anfassen, vermutlich mehr Abstraktion als
Nutzen.
3. **`TroItem` und `TroSymbol` sind reine Konstanten-Namespaces** (keine
Instanzen, keine Methoden außer `TroItem.all()`), kein Pydantic-Modell wie
der Rest von `tro_catalog.py`. Das ist bewusst so (siehe Docstring: "damit
Tippfehler auffallen") und funktioniert wie ein `Enum`-Ersatz — ein `StrEnum`
wäre eine nahe liegende, idiomatischere Alternative, falls ein Refactoring
ansteht.
4. Die einzige echte Vererbung (`DerivedTro(TroBase)`) ist sauber: `TroBase`
ist bereits so geschnitten, dass es für Systemgrenzen (`boundaries`)
wiederverwendet wird, `DerivedTro` ergänzt nur das, was die CSV-Ableitung
zusätzlich weiß. Kein Handlungsbedarf.
**Fazit:** Die Code-Basis nutzt Vererbung bewusst kaum — die Architektur trennt
stattdessen über Module (Typ-Katalog vs. Instanz vs. Ableitung vs. Ausgabe,
siehe `model.py`-Kopfkommentar) und über Komposition. Ein Refactoring in
Richtung "mehr Vererbung" wäre hier eher kontraproduktiv. Die doppelte
`OpenPoint`-Definition (1) ist behoben; offen bleibt nur optional `StrEnum`
statt Konstanten-Klassen (3).
## Die komplexen Klassen im Einzelnen
Die folgenden Abschnitte beschreiben je Klasse, **wozu** sie da ist, **was
darin passiert** und **wer sie erzeugt bzw. liest**. Einfache Datenhalter
ohne eigene Logik — `Connection`, `TroCandidate`, `Edge`, `TrackEdge`,
`TroItem`/`TroSymbol` und die kleinen Blöcke `SclSeparator`, `SclSwitch`,
`SclSwitchExit`, `SclScanner`, `SclJam` — sind im Baum oben ausreichend
beschrieben und bekommen keinen eigenen Abschnitt.
### `TroDefinition` (+ `TroStyle`) — `lib/tro_catalog.py`
**Wozu:** Beschreibt einen TRO-**Typ**, nicht eine Stelle in der Anlage: Kurzname
(`1Sep1Swi`), Siemens-FB (`FB_ILS_MTRO_1Sep1Swi`), Bauteile
(`{"Separator": 1, "Switch": 1}`), Farbgruppe und CAD-Symbol. Der Katalog
aller Definitionen ist die ausführbare Form von `doc/TRO_Typen.md`.
**Was passiert darin:** Pydantic mit `validate_assignment` und `extra="forbid"` —
eine Definition kann nie ungültig werden, auch nicht über die `set_*`-Methoden.
`_clean_items` wirft Bauteile mit Anzahl ≤ 0 heraus. Ein eigener `__init__`
erlaubt die alte Positionsform `TroDefinition("1Sep", "FB_...", {...})`. Dazu
kommen Abfragen (`get_items`, `has_item`, `get_item_fb`, `get_total_items`) und
Darstellungshelfer (`get_dot_attrs`, `mermaid_class/_classdef`,
`describe_items`). `TroStyle` liefert nur die Farben einer Gruppe für Graphviz,
Mermaid und CAD.
**Wer nutzt sie:** Über `get_tro(name)`/`catalog()` jeder, der vom Typnamen auf
FB oder Bauteile kommen muss — vor allem `TroBase.definition()`; außerdem
`tro_flow` (Typprüfung), `tro_annotate` (Farbe/Symbol, `--emit-lisp`) und die
Doku-Grafiken.
### `TroBase` / `DerivedTro` — `lib/model.py`
**Wozu:** Ein konkreter TRO in einer Anlage (Instanz). `TroBase` ist der Kern, den
jeder TRO hat, egal woher er stammt: `tro_id`, `type_name`, `separators`,
`predecessors`/`successors`, `position`. `DerivedTro` ergänzt, was `tro_flow`
beim Ableiten aus dem CSV zusätzlich weiß: `hosts`, `scanners`, `reason`,
`colocated`, `position_from`/`position_points`. Systemgrenzen (`SYSn`) sind
ebenfalls `DerivedTro`.
**Was passiert darin:**
- `fb_block` und `items` sind **berechnete** Felder: bei jedem Zugriff aus dem
Katalog gelesen (`definition()` → `get_tro(type_name)`), nie gespeichert. In der
JSON stehen sie trotzdem (`fbType`, `items`), damit sie lesbar ist.
- `_check_catalog_fields` (Validator vor dem Einlesen): steht in einer JSON ein
`items`/`fbType`, das nicht zum Typ passt, ist das ein Fehler — "den Typ ändern,
nicht die Bauteile". Danach werden die Felder verworfen.
- Aliase: in der JSON `id`/`type`, im Code `tro_id`/`type_name`
(`populate_by_name`).
- `_Positioned` (Mixin) wandelt `position` zwischen Tupel und
`{"x","y","z"}` und liefert `x`/`y`/`z` und `describe_position()`.
- `DerivedTro.connected`: hat der TRO überhaupt Nachbarn?
**Wer nutzt sie:** `tro_flow.derive_tros`/`derive_boundaries` erzeugen sie,
`scl_context` liest sie (als `TroBase`), `tro_annotate` zeichnet sie.
### `Analysis` — `lib/model.py`
**Wozu:** Das Arbeitsergebnis eines Ableitungslaufs, bevor daraus eine Datei wird.
Enthält mehr als die JSON: Kanten als Tupel, Kantenarten, isolierte TROs,
unbekannte Typen, Befunde, die verwendete Methode.
**Was passiert darin:** Fast nur Daten; `by_id()` sucht über TROs und
Systemgrenzen. Befüllt wird sie in `tro_flow.analyse_tros` in fester
Reihenfolge: `derive_tros` → `derive_boundaries` → `locate_tros` (Position,
nötig für die Reihenfolge auf einer Kreiselbahn) → `link_tros` (Kanten und
Kantenarten) → Prüfung gegen den Katalog.
**Wer nutzt sie:** `tro_flow` (Bericht, Diagramm, `--json`) und `tro_annotate`
(zeichnet direkt aus der `Analysis`, ohne Umweg über die JSON).
### `TroDocument` — `lib/model.py`
**Wozu:** Die TRO-JSON `<csv>_tro.json` (Schema `sps_skel-tro-v3`) — die
Schnittstelle zwischen Ableitung (`tro_flow --json`) und SCL-Ausgabe
(`scl_skeleton`). Weil beide Seiten dieselbe Klasse benutzen, kann das Format
nicht unbemerkt auseinanderlaufen.
**Was passiert darin:** `from_analysis()` macht aus einer `Analysis` ein Dokument
(Kanten + Kantenarten → `Connection`-Liste). `to_json()` schreibt mit den
JSON-Aliasen, `from_json()` liest und prüft. `extra="forbid"` sorgt dafür, dass ein
umbenannter oder falscher Schlüssel als Fehler auffällt statt still einen
Standardwert zu bekommen; `scl_skeleton.main` meldet bei falschem `schema`
gezielt "mit tro_flow neu erzeugen".
**Wer nutzt sie:** geschrieben von `tro_flow --json`, gelesen von
`scl_skeleton.load_document` → `scl_context.build_context`.
### `Element`, `Graph`, `TrackGraph` — `lib/material_flow.py`
**Wozu:** Das Rohmodell aus dem CSV-Export (ILS 2.0), noch ohne TRO-Begriff.
- `Element` = eine CSV-Zeile: Art, ID, Name, Nachbarn, `track_ids`,
Merkmale, Position, Bounding-Box, Anschlusspunkte K1..K4.
- `Graph` (`Node`/`Edge`) = grober **Materialfluss-Graph**: Transportobjekte
(Strecke, Gefällestrecke, Kreisel-Bahn) als Knoten, Geräte (Separatoren,
Weichen, Scanner …) an die Knoten gehängt (`Node.devices`), Stationen und
Übergaben als Kanten.
- `TrackGraph` (`TrackNode`/`TrackEdge`) = feiner **Bahnzusammenhang**: jedes
Bahnelement (AS, SEP, ES, Weiche, BTMT …) ist ein Knoten, verkettet in
Bau- bzw. Drehrichtung (siehe `doc/track.md`).
**Was passiert darin:** `Element` hat viele abgeleitete Properties: Klassifizierung
(`is_transport`, `is_circle`, `is_device`, `is_station`, `is_transfer`,
`is_entry`/`is_exit`), Geometrie (`x/y/z`, `bbox_x/y`, `height`,
`height_from/to`, `rise`), Antrieb (`drive_dir`, `rotation`). Die Graphklassen
selbst sind reine Container; die Logik steckt in den Funktionen
`build_graph()` (Knoten bilden, Geräte zuordnen, Kanten sammeln, Fliessrichtung
aus Gefälle/Antrieb, Stationen und Übergaben einhängen) und
`build_track_graph()` (Ketten aus der Spalte `TrackIds`; ein Element in zwei
Containern verbindet diese automatisch). `TrackNode` kann ohne `element`
existieren (Platzhalter für eine nicht auflösbare Bahn, generisches
Ein-/Ausgang-Symbol).
**Wer nutzt sie:** `material_flow` selbst (DOT/SVG/connect.ini/Bericht) und
`tro_flow.analyse_tros(graph, elements)` als Eingang der TRO-Ableitung;
`tro_annotate` für Ankerpunkte des Lagebezugs.
### `Overrides` (+ `TroOverride`, `SplitSpec`, `MergeSpec`) — `lib/tro_overrides.py`
**Wozu:** Von Hand gepflegte, vor Ort bestätigte Korrekturen aus
`cfg/tro_overrides.ini` für das, was die Ableitung nicht erkennen kann.
- `TroOverride`: Typ eines TRO korrigieren. Adressiert über `tro_id` **oder**
— stabiler — über einen `separator`, weil TRO-Nummern sich zwischen Läufen
verschieben, Separatornummern aus dem Export nicht (`matches()`).
- `SplitSpec`: Separatoren/Scanner aus einem TRO herauslösen und einen neuen TRO
bilden (Übergabestelle, deren Zuordnung auf die Strecke statt auf die
Kreiselbahn zeigt).
- `MergeSpec`: mehrere TROs zu einem Steuerobjekt zusammenfassen (z. B. zwei
Sperren beiderseits eines Kreisel-Berührpunkts).
- `Overrides`: Sammelcontainer je CSV-Datei, dazu gesetzte Topologie
(`connections`, ersetzt die abgeleiteten Kanten vollständig), erwartete
TROs (`candidates`), Protokoll (`applied`) und `warnings`. `bool(overrides)`
ist True, sobald irgendeine Korrektur vorhanden ist.
**Was passiert darin:** `load_overrides(csv_name)` liest den INI-Abschnitt der
CSV-Datei und baut daraus die Objekte; Lesefehler landen in `warnings` statt
den Lauf abzubrechen.
**Wer nutzt sie:** Stand heute **nur** `material_flow --connect`
(`render_connect_ini` schreibt sie als `[<Anlage>.manual]`-Abschnitt). `tro_flow`
wendet sie (noch) nicht an — sie fliessen also nicht in die TRO-JSON und nicht
in die SCL.
### `Transform` (+ `RegistrationError`) — `lib/dxf_registration.py`
**Wozu:** Der Lagebezug zwischen CSV-Export und BricsCAD-Zeichnung: eine starre
Transformation `csv = R(rotation) · dxf + (dx, dy)`. Ohne sie landen die
TRO-Marker in `tro_annotate` an der falschen Stelle.
**Was passiert darin:** `to_csv()`/`to_dxf()` rechnen Punkte hin und zurück,
`describe()` fasst Verschiebung, Drehung, Punktzahl und Restfehler zusammen.
Die Ermittlung steckt in Funktionen: `derive_transform()` ordnet punktartige
Objekte gleicher Rolle (Separator, Scanner) aus CSV und DXF zu, stimmt über
Verschiebungen ab, gleicht aus und wählt den besten Kandidaten;
`verify_transform()` rechnet die Restfehler nach und schreibt sie ins Objekt;
`load_transform()`/`save_transform()` lesen/schreiben
`cfg/dxf_registration.json`. `RegistrationError` heisst: der Bezug liess sich
nicht **nachweisbar** bestimmen — dann lieber abbrechen als falsch zeichnen.
**Wer nutzt sie:** `tro_annotate.resolve_transform` (Reihenfolge: `--offset` →
abgelegter Bezug → neu ermitteln, jeweils mit Prüfung), dazu das geparkte
`tro_extract`.
### `SclFile`, `SclTro`, `SclName` — `lib/scl_context.py`
**Wozu:** Der **Render-Kontext** für das FB_Main-Gerüst. Alles, was aus der
TRO-JSON *abgeleitet* wird, steht hier fertig drin; die Ausgabe
(`emit_*`-Funktionen oder Jinja-Templates) rechnet nichts mehr selbst, sie
liest nur noch. So können generischer Aufbau und Templates nicht
unterschiedlich ableiten.
**Was passiert darin:**
- `build_context(document, source, start_id)` baut aus einem `TroDocument` ein
`SclFile`: Kopfdaten plus die TROs in Fliessrichtung.
- `flow_order()` bestimmt die Reihenfolge der REGIONs: Start bei `--start`, sonst
beim TRO ohne Vorgänger, sonst beim TRO, den eine Station (`SYSn`) speist,
sonst wird der Kreislauf willkürlich aufgetrennt — der Grund steht im
SCL-Kopf (`start_reason`). Danach Breitensuche; nicht erreichte TROs werden
hinten angehängt, damit keiner verloren geht.
- `_tro_context()` baut je TRO ein `SclTro`: so viele Separator-, Weichen- und
Scannerblöcke, wie der Katalogtyp Bauteile hat (`items`); je Vorgänger ein
Staubereich am Eingang, je Nachfolger einer am Ausgang; `to1_destinations`
nur bei genau einer Weiche (bei mehreren ist die Aufteilung eine
Planungsentscheidung); die Zielliste je Weichenabgang über
`reachable_via()` (Erreichbarkeit im TRO-Graphen, ohne über die Weiche
zurückzulaufen).
- `SclName` = Wert + Merker `todo`. Alles, was aus der E-Planung kommen muss
(Separator-/Weichennummern, Sensor-/Aktoradressen, MainTro, Staubereichs-DB …),
ist heute ein Platzhalter mit `todo=True`; die Ausgabe hängt daran den
Kommentar `TODO(E-Planung)`. Spätere Namensregeln ersetzen nur den Wert und
löschen den Merker — die Ausgabe ändert sich dafür nicht.
**Wer nutzt sie:** erzeugt in `scl_skeleton.render()`/`main()`, gelesen von
`render_context()`, den `emit_*`-Funktionen und den Templates (als `t`).
### `SclTemplates` — `lib/scl_skeleton.py`
**Wozu:** Die Anbindung der parametrisierten SCL-Aufrufstellen
(`lib/scl_templates/*.scl.j2`) an die Ausgabe. Ein Objekt steht für einen
Template-Ordner.
**Was passiert darin:** Der Konstruktor baut ein `jinja2.Environment` mit
`StrictUndefined` (ein Tippfehler im Template wird zum Fehler statt zu einer
leeren SCL-Stelle), `trim_blocks`/`lstrip_blocks` und der globalen Variable
`TODO`. `has(fb)` prüft, ob `<fb>.scl.j2` existiert; `render(tro)` rendert das
Template mit `t = SclTro` und fasst danach mehrfache Leerzeilen zusammen (statt
das mit `{%- -%}` im Template zu tun, was die Einrückung bricht).
#### Template ↔ TRO: Zuordnung und Einstieg in die Produktion
**Zuordnung — es gibt keine Tabelle, nur eine Namenskonvention:
Dateiname = FB-Typ.** Die Kette:
```text
DerivedTro.type_name "1Sep1Swi" (TRO-JSON, Feld "type")
-> tro_catalog.get_tro() TroDefinition
-> TroDefinition.fb_block "FB_ILS_MTRO_1Sep1Swi"
-> TroBase.fb_block (berechnet, nicht gespeichert)
-> SclTro.fb (scl_context._tro_context)
-> SclTemplates.has/render lib/scl_templates/FB_ILS_MTRO_1Sep1Swi.scl.j2
```
Ein neues Template für einen weiteren Typ heisst also einfach
`<fbType>.scl.j2` und wird automatisch für alle TROs dieses Typs verwendet.
Heute gibt es nur den Piloten `FB_ILS_MTRO_1Sep1Swi.scl.j2` (plus
`_macros.scl.j2` mit gemeinsamen Makros). Alle anderen Typen laufen über den
generischen Aufbau (`emit_region()`).
**Einstieg — nur per Schalter (opt-in):**
- `scl_skeleton --templates [DIR]` (ohne DIR: `lib/scl_templates`). `main()`
legt dann ein `SclTemplates` an; `render_context()` nimmt pro TRO
`templates.render(tro)`, wenn `templates.has(tro.fb)`, sonst `emit_region()`.
Der SCL-Kopf listet, welche FB-Typen aus Template bzw. generisch kamen.
- `--strict` bricht ab (Exit 1, keine SCL), wenn ein FB-Typ der Anlage kein
Template hat. Ein `TemplateError` (fehlende Variable, Syntaxfehler) bricht
ebenfalls ab — lieber keine SCL als eine mit stillen Lücken.
- Ohne `--templates` werden Templates gar nicht geladen; die Ausgabe ist die
bisherige (Golden-Files `tests/fixtures/scl/*_FB_Main.scl`). Mit Templates
pinnen `*_FB_Main_templates.scl` das Ergebnis.
- `bin/scl_skeleton.bat`/`.sh` reichen die Schalter nur durch; der Standardaufruf
ohne `--templates` nutzt also **keine** Templates.
**Nicht Produktion:** `doc/TRO_Katalog/scl_templates/*.scl` sind die realen
Referenz-Aufrufstellen. Gegen sie prüft `tests/test_scl_templates.py` jedes
Template im Round-Trip; in die erzeugte SCL fliessen sie nicht ein.
## Daten
Bestandsaufnahme aller Daten, die in den Referenz-Aufrufstellen und FB-Schnittstellen
unter `doc/TRO_Katalog/scl_templates/*.scl` vorkommen (Stand 2026-10-10), und woher
der Generator sie bekommen muss. Grundlage: die sieben Aufrufstellen
(`FB_ILS_MTRO_1Sep`, `_1Sep1Swi`, `_1Sep2Swi`, `_1Sep_SSCC`, `_PinStore_Auto`,
`_Vario`, `_Vario_workStation_CallSite`) und die `VAR_INPUT/OUTPUT/IN_OUT`-Abschnitte der
vier vollständig vorliegenden FBs (`FB_ILS_MTRO_2Sep1Swi`, `FB_ILS_MTRO_Vario_workStation`,
`FB_EmptyCarrBuffer`, `FB_LoadingBoom_INBOUND`). Die Beispielwerte stammen aus diesen
Dateien.
**Quellen** (Spalte "Quelle"):
| Kürzel | Quelle | Was sie liefert |
|---|---|---|
| **L** | Anlagen-Hardware = mechanisches Layout (CSV-Export ILS 2.0, `data/*.csv`) | Bauteile (`TeileArt`: Separator, Scanner, Weiche, Kreisel, Strecke, Gefällestrecke, BTMT Be-/Entladung, Ein-/Ausschleuselement), `Nachbarn`/`TrackIds`, `Merkmale` (Drehrichtung, Antriebfahrtrichtung, Höhen, Längen, Anzahl Separatoren/Scanner, Zuordnung). Daraus abgeleitet: TRO-Typ, Topologie, Reihenfolge, Ziele. |
| **E** | Hardwareplanung = Elektroplanung (EA-Liste, TIA-Import, WSCAD-Import, `*_positionsdraw.json`; siehe `doc/HundM/EA-Listen-Analyse.md`) | Betriebsmittelkennzeichen und Variablennamen (`BG…` Sensoren, `MB…` Ventile, `MA…` Motoren, `FC…` Motorschutz, `SF…` Taster, `BX…` Scanner), Hardware-Kennungen, welche optionalen Sensoren verbaut sind, Zuordnung zu Schaltschrank und Not-Halt-Kreis. |
| **K** | Namenskonvention | Name folgt aus TRO-Nummer bzw. Staubereichs-/Weichennummer (`cMainTro<n>`, `cSep<n>.<i>`, `"DB_Interface_HMI".stTRO.TRO<n>` …). Keine externe Quelle nötig, sobald Nummernkreis und Regel feststehen. |
| **P** | Parameter / Projektstandard | Weder Layout noch E-Planung: Zeiten, Schwellwerte, Projektkonstanten, WCS-/Inbetriebnahme-Logik. Kandidaten für Typ-Defaults (`doc/TRO_Typen.md`). |
`L + E` heisst: das Layout sagt, **dass/wie viele** es gibt und **wo**, die
E-Planung liefert den **Namen/die Adresse**. Das ist der häufigste Fall.
Die zwei folgenden Abschnitte listen **alle Angaben**, die die beiden externen
Quellen heute enthalten (Stand 2026-10-11, geprüft an `data/*.csv` und an den
Exporten unter `kabellaengen/work/`: `easy_sps-1_EA/_TIA/_WSCAD.xlsx`,
`Mubea_positionsdraw.json`). Was sich daraus für TROs und SCL gewinnen lässt und
zwei kommentierte SCL-Beispiele: `doc/Datenquellen_Layout_Hardwareplanung.md`.
### Angaben 1: Anlagen-Hardware (mechanisches Layout, CSV)
Eine Zeile je Bauteil, Trennzeichen `;`, `utf-8-sig`. Gelesen von
`material_flow.load_elements()`.
#### Spalten (alle Bauteilarten)
| Spalte | Beispiel | Bedeutung | Nutzen für TRO/SCL |
|---|---|---|---|
| `Elementnummer` | `1` | laufende Zeilennummer des Exports | keiner (nicht stabil) |
| `TeileArt` | `ILS 2.0 Separator` | Bauteilart (Liste unten) | Knotenart, TRO-Typ |
| `TeileId` | `0008` | stabile ID im Layout | Schlüssel für Topologie, Overrides, E-Brücke |
| `Bezeichnung` | `VF_1`, `Kreisel :3` | Klartext aus der Zeichnung | Anzeige, REGION-Kommentar |
| `Planquadrat` | `B/1-C/4` | Raster der Zeichnung | Bericht/Suche |
| `Anzahl` | `1` | Stückzahl | keiner |
| `Position` | `5878.63, 4330.96, 2128.70` | Einfügepunkt x, y, z in mm | Lagebezug zur E-Planung (`dxf_registration`), Abstände |
| `Boundingbox` | `2837.82, 6404.92, 731.58` | Ausdehnung x, y, z in mm | Lage von Weichen/Bahnen |
| `Insertpoint` | `TV0UEkxg…` | kodierter Einfügepunkt (Base64) | intern |
| `K1`–`K4` | `AK41AC7XAFQk` | Anschluss-Koordinatensysteme (Base64, je x/y/z) | Anschlussseite, Lage der AS/ES (`decode_kpoint`) |
| `Nachbarn` | `0006-L, 0014` | berührende Bauteile, bei Kreiseln mit Bahn `-L`/`-R` | Topologie (Kanten) |
| `TrackIds` | `0044, 0027, 0026` | Anbauteile eines Containers in Baureihenfolge bzw. Umlaufrichtung | Reihenfolge Separator/AS/ES auf Strecke und Kreisel |
| `Fehler`, `Warnungen` | `nur ein Partner`, `unverbunden` | Prüfmeldungen des Exports | Qualitätsprüfung |
| `Merkmale` | JSON | artspezifische Eigenschaften (unten) | siehe unten |
#### Merkmale je `TeileArt`
| TeileArt | Merkmale | Nutzen für TRO/SCL |
|---|---|---|
| `ILS 2.0 Kreisel` | `Name`, `Kreiselart` (`STANDARD`), `Drehrichtung` (`UZS`/`GUZ`), `Drehung`, `Höhe in m`, `Abstand (Kreiselachse A - Kreiselachse) in Meter`, `Anzahl der Separatoren`, `Anzahl der Scanner`, `Anzahl der Rampen`, `Geruest fuer Einzelmodul` | Host eines TRO (`xInCarouselRun1/2`), Umlaufrichtung → Reihenfolge, Länge → Staulänge; Anzahlen zur Plausibilisierung |
| `ILS 2.0 Strecke` | `Typ` (`Streckengruppe`), `Seite_AS`, `Seite_ES`, `Winkel`, `VF_Winkel`, `DeltaH_mm`, `DeltaL_mm`, `L_VF_m` (angetriebene Teilstücke), `L_GF_m` (Gefälleteilstücke), `Montagehoehe_m`, `Hoehe_Von_mm`, `Hoehe_Bis_mm`, `Antriebfahrtrichtung` (`horizontal`/`Auf`), `Anzahl_Separator`, `Anzahl_Scanner`, `Separatorlist` | angetrieben (`L_VF_m`, `Auf`) → Vario/Antrieb; Längen → `tConvEmpty`, Staulänge; Anzahlen zur Plausibilisierung |
| `ILS 2.0 Gefaellestrecke` | wie Strecke, `L_VF_m` und `Antriebfahrtrichtung` leer | Host "Gefälle" → `1Sep`, `xInCarouselRun1 := TRUE` |
| `ILS 2.0 Separator` | `Zuordnung` (Host, ggf. mit Bahn `-L`/`-R`), `ARTINR`, `Höhe`, `Drehung`, `Typ` | **TRO-Anker**: jeder Separator ist ein TRO oder Teil davon; Host → TRO-Typ |
| `ILS 2.0 Scanner` | `Zuordnung`, `Naechster Separator`, `ARTINR`, `Höhe`, `Drehung`, `Typ` | Scanner am TRO (`xPluggedScanner`, `stInBarcodeReader`, REGION-Titel) |
| `ILS Weiche` | `Kreisel A`, `Kreisel B`, `Achse von Kreisel`, `Beruehrung` (`Kreis/Kreis`), `Erzeugt aus` | Kreisel-Kreisel-Weiche → `1SepNSwi`, `xCar2NecRight/Left`, zweiter Kreisel `xInCarouselRun2` |
| `ILS Ausschleuselement` | `Kreisel`, `Strecke`/`Förderer`, `Streckenende` (`Anfang`), `Element` (`AS_Element_90_links_3D`), `ARTINR`, `Erzeugt aus` | Abgang Kreisel → Strecke: Weichenausgang `Exit3/4`, Ziel je Abgang |
| `ILS Einschleuselement` | wie Ausschleuselement, `Streckenende` `Ende` | Zusammenführung Strecke → Kreisel: Vorfahrt (`xPrioDir`, PriorityManager) |
| `ILS 2.0 BTMT Beladung` / `Entladung` | `ARTINR`, `Höhe`, `Drehung` | Systemgrenze `SYSn`, `xIsBorner`, Ziele; Ladeschleife |
| `Eingang` / `Ausgang` | `ARTINR`, `Höhe`, `Drehung` | Systemgrenze zu Nachbaranlage, Übergabe-Staubereich |
`ARTINR` (Artikelnummer) unterscheidet Bauarten, z. B. Separator `200000077`,
Scanner `829534306`; daran ließe sich die Weichenbauart (`xNewSwitch`) prüfen.
Zusätzlich gibt es den **Sivas-Stücklisten-Export** (`data/*_sivas.csv`:
`SivasNummer`, `Dispogruppe`, `Laenge[mm]`, `IDs`, `details` mit
Förder-/Gefällegruppen, Kurven, `MotorSeite`). Er ist eine Stückliste, keine
Topologie, und wird nicht gelesen; brauchbar wäre `MotorSeite` und die
Gruppenlängen.
**Nicht im Layout:** Betriebsmittelkennzeichen, E/A-Adressen, welche optionalen
Sensoren verbaut sind, Schaltschrank/Steuerung, Zeiten, Ziel-Nummern des WCS.
### Angaben 2: Hardwareplanung (EA-Liste, TIA-Import, WSCAD-Import, positionsdraw)
Vier Exporte derselben E-Planung. Gemeinsamer Schlüssel ist das
**Betriebsmittelkennzeichen (BMK)** `BG3306`, `MB3306` …; nur `positionsdraw`
kennt eine Lage, nur EA/TIA/WSCAD kennen die E/A-Adresse.
**`<projekt>-<n>_EA.xlsx`** — Blatt "Ein-, Ausgaenge", je SPS `n` eine Datei
| Spalte | Beispiel | Bedeutung |
|---|---|---|
| `Name` | `E324.0` | E/A-Adresse |
| `Kommentar` | `Stausensor 1 (ILS-CV M0108)` | Funktionsrolle, lfd. Nr., Fördereinheit (M-Nummer), Bereich |
| `Zeile` | `2` | Verweis Schaltplan |
**`<projekt>-<n>_TIA.xlsx`** — Importdatei für das TIA Portal
| Blatt / Spalte | Beispiel | Bedeutung | Nutzen für SCL |
|---|---|---|---|
| PLC Tags `Name` | `BG3240`, `MB3306`, `MA0062`, `FC0062` | BMK = Variablenname | direkt die Namen in `FB_Main` (`=> "MB3306"`, `:= "FC0145"`) |
| PLC Tags `Path` | `Import` | Variablentabelle | Ordner der Variablentabelle |
| PLC Tags `Data Type` | `Bool` | Datentyp (nur Bool) | – |
| PLC Tags `Logical Address` | `%E324.0`, `%A006.2` | Absolutadresse | Prüfung, `FB_CallSensors` |
| PLC Tags `Comment` | `Stausensor 1 (ILS-CV M0108)` | wie EA-Kommentar | Rolle, M-Nummer |
| PLC Tags `Hmi Visible/Accessible/Writeable` | `True` | HMI-Flags | – |
| PLC Tags `Typeobject ID`, `Version ID` | leer | – | – |
| Constants `Name` | `cInBG3240`, `cInFC0062`, `cOutMA0062` | Index-Konstante je Signal | genau die Indizes in `"DB_Inputs".Sensors["cInBG3306"]` |
| Constants `Data Type`, `Value` | `Int`, `1` | Array-Index | Dimensionierung `DB_Inputs` |
**`<projekt>-<n>_WSCAD.xlsx`** — Importdatei für das E-CAD
| Spalte | Beispiel | Bedeutung | Nutzen für SCL |
|---|---|---|---|
| `Anschluss` | `E324.0` | E/A-Adresse | Schlüssel zu EA/TIA |
| `Kommentar` | wie EA | | |
| `Bezug` | `=A01+UC0101-KF1DI1` | Anlage, Feldstation (`UC…`), Baugruppe (`KF1DI1`, `KF1FDQ2` = F-Karte) | Steuerungs-/Schaltschrankzuordnung, Safety-Signale |
| `Symboltext`, `Sondertext 1..8` | meist leer | | |
**`<zeichnung>_positionsdraw.json`** — Kabellängen-Tool, E-Symbole aus der Zeichnung
| Schlüssel / Attribut | Beispiel | Bedeutung | Nutzen für TRO/SCL |
|---|---|---|---|
| `sensors{BMK@SPS}` | `BG0542@UNKNOWN` | Feldgeräte (BG, MB, MA …) | Hauptquelle der Brücke |
| `.IO` / `.B` | `BG0542` | BMK | Schlüssel zu TIA |
| `.pos` | `[-42369.8, -11437.2]` | Lage in mm | **Zuordnung zum Layout-Bauteil** (`e_planning.py`) |
| `.VERW` | `IN SEP`, `SEP`, `STAU_REF-LT`, `AE ABZWEIG`, `ABZWEIG-K104`, `BT BELADEN`, `SYNC P1..4`, `ABFR.FINGER`, `ÜBERGABE`, `ÜBERNAHME`, `BANDBRUCH`, `KETTENBRUCH`, `ZAPPLER P1`, `MOT_0,37kW` | Verwendung (kontrolliertes Vokabular) | **Rolle** des Signals im TRO |
| `.BEZEICHNUNG` | `STAUSENSOR STR. 01`, `PRÜFUNG BT. POS.1 (KR-K0102 EINFAHRT PUFFER)` | Klartext | Strecken-/Kreiselnummer, Plausibilisierung |
| `.BESCHR` | `STAUSENSOR` | Gerätebeschreibung | – |
| `.KENNZEICHNUNG` | `=A1+UH01-KF102` | Steuerung `UH01`, E/A-Station `KF102` | Steuerung, TRO-Nummernkreis, Schaltschrank |
| `.ARTINR` | `829434300` | Artikelnummer Gerät | Gerätetyp |
| `.SPS` | `''`, `A/B` | SPS-Nummer (oft leer) | Steuerung |
| `.A`, `.C`, `.ID`, `.MENGE`, `.POSITION`, `.GRUPPE`, `.ETIKETTE`, `.AUFLOESE`, `.TEXT-D/E/ES/F` | | Blockattribute des Symbols | – |
| `schaltschrank_elemente{…}` | `FC0200` `MOTORSCHUTZ CONVEYER #100`, `SF…` Taster, `PF…` Leuchten, `QA7000` Hauptschütze, `GF` Encoder, `BP` Druckschalter | Elemente im Schrank, ohne Feldlage | Motorschutz ↔ Motor, Bedienelemente, Encoder |
| `mappings` | `UH01: [BMK …]` bzw. Verteiler → BMK | Zuordnung zur Steuerung/Verteiler aus der Verkabelung | Steuerungsaufteilung |
| `distributors`, `racks`, `tunnels` | | Verteiler, Trassen | – |
| `warnings`, `not_found`, `double_ids` | | Prüfprotokoll | Qualitätsprüfung |
**BMK-Präfixe:** `BG` Sensor, `SF` Taster/Schalter, `DI` Diagnose, `BP`
Druckschalter, `GF` Encoder, `BX` Scanner (nur PROFINET, nicht in den Digitallisten),
`MB` Ventil (Stopper, Weiche, Ausschleuser), `MA`/`QA` Motor/Schütz, `DQ`
Sonderausgang, `FC` Motorschutz, `PF` Leuchtmelder.
**Nicht in der Hardwareplanung:** Topologie (wer auf wen folgt), TRO-Grenzen,
Ziele/Weichenrichtungen, Zeiten, Analogwerte und PROFINET-Geräte (Cognex-Variablen
stehen in der TIA-Gerätekonfiguration, nicht im Tag-Import).
### TRO-Kopf und Instanzaufruf
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Existenz, Anzahl, Reihenfolge der TROs | `REGION TRO 100 …` | L | `tro_flow` → `DerivedTro`; REGION-Reihenfolge `scl_context.flow_order()` |
| TRO-Typ / FB-Typ | `FB_ILS_MTRO_1Sep1Swi` | L (→ Katalog) | Typ aus Separator-Host (`doc/500573_Mubea/TRO_Identifikation_500573.md`), FB aus `tro_catalog` |
| TRO-Nummer, Instanzname | `#TRO107` | L + E | Anzahl aus L; Nummernkreis je Steuerung (100er = UH01, 200er = UH02 …) ist Projektvorgabe |
| REGION-Titel | `TRO 267 (1Sep2Switch)+Scanner(BX2100)` | L + E | Typ aus L, Scanner-BMK aus E |
| `nInMainTroNo` | `"cMainTro100"` | K | Wert der Konstante in der PLC-Konstantentabelle |
| `nOutStateLast` | `"DB_GLOB_TroState".arTroState["cMainTro100"]` | K | |
| `stInOutHMI` | `"DB_Interface_HMI".stTRO.TRO100` | K | PinStore/Puffer: eigener UDT `UDT_HMI_TRO_PinStore` |
| `xInRelease` | `"DB_TRO_TEST".TRO100`, `TRUE`, WCS-Verbindung aktiv | P | Freigabe-/Testlogik, Inbetriebnahme |
| `xInAllRdyToStart`, `xInCancel`, `xInReset` | `TRUE`, `"cTodoFalse"` | P | meist Konstante |
| `stInSettings.xIsBorner` (`xIsBorner1/2`) | `TRUE` | L | erster TRO nach Einfahrt in die Anlage (Beladestation / Systemgrenze `SYSn`) |
| `stInSettings.nBornerDest(1/2)` | – | P | Standardziel für neu angelegte Carrier (WCS) |
| `stInSettings.bSepType` (`bSep1Type/2Type`) | `.%X2 := TRUE` | E + P | X0 erstes Bein halten, X1 Beladekontrolle, X2 Beladeerfassung — hängt an verbauter Sensorik und Prozess |
| `stInSettings.nTroStopper1/2`, `nTroSwitchExit2/3` | – | L + K | Nummern der Nachbar-TROs |
| `stInSettings.sJamSrc` / `sJamDest` | – | L + K | Namen der benachbarten Staubereiche |
| `stInSettings.xCar2NecRight/Left` | – | L | zweiter Kreisel nötig für Richtung rechts/links (Topologie an einer Kreisel-Weiche) |
| `tPartSenHigh`, `tInSenHigh`, `tDummyTimeNormal`, `tDummyTimeBorner` | FB-Defaults 500 ms / 3 s / 0 ms | P | Typ-Defaults |
### Separator (`stInSeparator<n>`, `xOutStopper<n>`)
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Anzahl Separatoren je TRO | `stInSeparator1`, `stInSeparator2` | L | `ILS 2.0 Separator` + `Zuordnung`, Typ-Katalog `items` |
| `Settings.nSeparatorNo` | `"cSep107.1"` | K | Layout-Separator-ID (`TeileId`) ist **nicht** diese Nummer |
| Sensor im Stopper `stSenInSep` | `"DB_Inputs".Sensors["cInBG3242"]` | E | |
| Sensor nach Stopper `stSenSepLeft`, Beladesensor `stSenPart`, Scan-Start `stSenStartScan` | `["cInBG4465"]` | E | nur wenn verbaut |
| Stopperventil `xOutStopper<n>` / `xOutStopper` | `=> "MB3242"` | E | BG- und MB-Nummer sind in der E-Planung meist gleich (BG3242/MB3242) |
| `Settings.xPluggedJam` | `TRUE` | E | Stausensor verbaut? |
| `Settings.xPluggedPart` | `FALSE` | E | Beladesensor verbaut? |
| `Settings.xPluggedConfirm`, `xPluggedNullBT` | `FALSE` | E | |
| `Settings.xPluggedScanner` | `TRUE` | L | Scanner am Separator (`DerivedTro.scanners`) |
| `Settings.tDelayToNextItem` | `"TimeForSepWaiting_Short"` / `_Medium` | P (+ L) | Wahl Short/Medium hängt am Host (Kreisel/Strecke/Gefälle) |
| `Settings.tTrailingTime`, `tHandlingTime` | `t#600ms`, `t#200ms`, `"cTimeSepTrailing"` | P | Typ-Default, IBN |
| `Settings.tJam` | `"TimeForSepJam"` | P | Projektkonstante |
| `Settings.tSenFree`, `tSenWait` | `T#1500ms`, `T#4000ms` | P | |
### Staubereiche (Jam)
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Anzahl Ein-/Ausgänge und Zuordnung Exit-Nr ↔ Nachbar | `arInOutJamEntr1`, `arInOutJamExit2..4` | L | je Vorgänger ein Eingang, je Nachfolger/Weichenabgang ein Ausgang (`SclJam`) |
| Gleicher Staubereich auf beiden Seiten einer Kante | Exit von A = Entr von B | L | Identität aus der Topologie |
| Staubereichs-DB und -Index | `"DB_JamArea0102".stJam0102_1.arCarrier` / `.stData` | K (+ E) | Nummerierung der Staubereiche ist Projektvorgabe |
| Stausensor je Ausgang | `stInSenJam2 := "DB_Inputs".Sensors["cInBG3243"]` | E | |
| Übergabe-Staubereich an Steuerungsgrenze | `#arCarrierHandoverJam5001`, `#stJamDataHandoverJam5001` | L + E | Steuerungsaufteilung aus der E-Planung |
| `xInJamAtExit2/3` | – | P | NIU |
### Weiche (`stInSwitch<n>`, `FC_Direction`)
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Anzahl Weichen je TRO | `stInSwitch1`, `stInSwitch2` | L | `ILS Weiche`, Ausschleuselement, Typ-Katalog |
| `Settings.nSwitchNo` | `"cSwi107.1"` | K | |
| `Settings.nTo1Destinations` | `3` | L | Erreichbarkeit im TRO-Graphen (`reachable_via()`); bei mehreren Weichen Planungsentscheidung |
| Zielliste je Abgang | `cDest…` | L + P | welche Ziele: L; Zielnummern: Projekt/WCS |
| `Settings.xPluggedExSen3/4`, `stSenExit3/4` | `FALSE` | E | Abgangssensoren verbaut? |
| `Settings.xNewSwitch` | `TRUE` | E | Weichenbauart (ggf. über `ARTINR` im CSV prüfbar) |
| Weichenventile `xOutSw<n>ExTo3/4`, `xOutSw1EntrTo1/2` | `=> "MB3304"` | E | |
| `FC_Direction`-Aufruf | `nInSwitchNo := "cSwi107.1"`, `InOutCarrier := …stJam0102_1.arCarrier[1]` | K + L | Carrier aus dem Eingangs-Staubereich |
| Vorfahrt `stInSettings.xPrioDir<n>`, `stInPriorityManagerDir<n>` | `"DB_Prozessdaten".stPriorityManager.udt_TRO107.TroIn` | L + K | Vorfahrt nötig, wo zwei Ströme zusammenlaufen (Kreuzung/Zusammenführung) |
| `stInPriorityManager.xReleaseOk` | `TRUE` ("braucht keine Vorfahrtsregelung") | L | TRUE, wenn keine Zusammenführung |
### Scanner / Barcode / SSCC
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Scanner vorhanden, Anzahl | `stInBarcodeReader`, `stInBarcodeReader1/2` | L | `ILS 2.0 Scanner`, `Anzahl_Scanner` |
| Scanner-BMK und Index | `#arCognexInterface["cScannerBX2100"]` | E | |
| Cognex-Variablen | `"bx2100-TriggerReady"`, `-TriggerAck`, `-Results Available`, `-General Fault`, `-TriggerEnable`, `-Trigger`, `-ResultsAck` | E | aus dem BMK nach Regel bildbar, wenn die TIA-Variablentabelle dem Muster folgt |
| Hardware-Kennung Ergebnisse | `"=A01+UC021-BX2100~Ergebnisse_–_64_Bytes_1"` | E | |
| Kein Scanner | `#stCognexInterfaceDummy` | L | |
| `Settings.xActivateCheckASCI`, `tTimeout` | `TRUE`, `T#5s` | P | |
| `Settings.nStation`, `nScannerPurpose` | `111`, `0` | P | WCS |
| Zweck SSCC-Scanner, `xInPluggedSSCC` | `#stCognexIntfTro105` | E + P | Layout kennt den zweiten Scanner, nicht seinen Zweck |
| `stSenEndMeasurement` | `["cInBG3281"]` | E | |
| `FC_CARR_GLOB_CORRECTION` | `nInStation := 0`, Carrier aus Eingangs-Staubereich | P + L | |
| WCS-Telegramm, HMI-SSCC-Ausgabe | `FC_Send_GOHDReqM`, `"DB_Interface_HMI".stSSCC.SSCC1107` | P | WCS-Schnittstelle |
### Vario (`stInVario`) und Arbeitsplatz
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Vario vorhanden | `FB_ILS_MTRO_Vario` | L | angetriebene Strecke / Variofoerderer |
| Sensoren `stSenCarrInPos`, `stSenFinger`, `stSenLastPos`, `stSenJam`, `stSenNextSep`, `stSenSepVario` | `["cInBG3026"]` … | E | |
| Motor `xOutVarioMotor`, Motorschutz `xInMotorProtection` | `"MA0145"`, `"FC0145"` | E | |
| Finger-Staubereich `arInOutJamFinger` | `"DB_JamArea4103".stJam4103_1` | L + K | |
| `Settings.tConvEmpty` | `T#45000ms` | L + P | aus Förderlänge (`L_VF_m`) und Geschwindigkeit ableitbar |
| `Settings.nCountCarrier`, `tWaitForJog`, `tWaitForStop`, `tCarrierInPosDelay`, `tCarrierInPosEdgeDelay`, `tRasterHigh/Low`, `xJamSenDelayActive`, `tJamSenDelay`, `xContinousEmptying`, `SettingsVarioFingerMonitoring` | `1`, `T#300ms` … | P | |
| Arbeitsplatz `stInStationSensor`, `xInReleaseStation`, `fbConvVario.xBeladenError` | `["cInBG3314"]`, `#"rTrig_Workstation 1".CLK` | E | Taster/Sensor am Arbeitsplatz |
### Antrieb, Sicherheit, Betriebsart
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Fördertechnik, die den TRO trägt | `xInCarouselRun1 := #fbCarousel0102.xOutConveyorFullSpeed`; Gefälle: `TRUE` | L | Host-Kreisel aus `DerivedTro.hosts`; Instanzname nach K |
| `xInCarouselRun2` | zweiter Kreisel | L | TRO an der Berührung zweier Kreisel |
| `xInConveyorRunIn/Out` (PinStore) | `#fbSortConv1101`, `#fbExitConv1103` | L + K | |
| `xInSftyOk` | `"DB_InterfaceSafety".stFromSafety.EStopMainOF1.Q` | E | Not-Halt-Kreis |
| `stInOutMachineState` | `#stInOutControlUnitCabinetOF1` | E | Schaltschrank (OF1/OF2) |
| `xInAutomatic`, `xInErrorQuit`, `xInQuitLocal` | `#t_Automatik_OF1 AND "SF0120"`, `"SF0002"`, `"SF0121" OR "SF0117"` | E | Bedienelemente |
| `nInTestTarget` | `"IBN".nTestTarget111` | P | |
### Pin-Lager (`FB_ILS_MTRO_PinStore_Auto`) und Leerträgerpuffer (`FB_EmptyCarrBuffer`)
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Anzahl Lagerlinien | `nNoOfLines := 19`, `arInSep[1..5]` | L | Separatoren auf Kreisel mit Pinband / `Separatorlist` |
| Positionen je Linie, Staulänge | `nNoOfPositions := 70`, `nJamLength := 4` / `"cStorage111ExitJam2"` | L + P | aus Linienlänge und Teilung |
| Liniensensoren `arInSep[n].stSenTrolley`, `arInJamSensor[n]`, `stInSenJamExit` | `["cInBG3270"]` | E | |
| Linienventile `arOutSeparatorStore[n]` | `"MB3270"` | E | |
| Pin-Separator `stInSeparatorPin`, `xOutSeparatorPin` | `["cInBG3000"]`, `"MB3000"` | E | |
| Referenzsensor "Zappler" `stInSensorReference` | `["cInBG3001"]` | E | |
| Encoder `stInEncoderParameter` | `#fbEncoderGF2200.nOutPosActDif` | E | |
| `nDestGeneral`, `nConvStations`, `nStationsMax`, `nStorageThreshold` | `"cDest11100"`, `"cHooksSorterMaxPin111"` | P | WCS/Projekt |
| `nInBufferNr`, `xInBufferReq/Release/ReleaseNext`, `xInPause` | `101`/`102` | P | Pufferlogik |
| Lager-/Stations-DBs | `"DB_Storage1.1.1".arLine`, `"DB_ConveyorStations"`, `"DB_HandshakeStorage"` | K + P | |
| Linien-Zeiten, Simulation | `"cTimeSepHandlingStorageLine"`, `stSimu.*` | P | |
### Ladeschleife (`FB_LoadingBoom_INBOUND`)
| Daten | SCL-Stelle (Beispiel) | Quelle | Bemerkung |
|---|---|---|---|
| Ladeschleife vorhanden, Lage | – | L | Beladestation (`ILS 2.0 BTMT Beladung`), prüfen |
| Bedienelemente `stInControl.*` | Taster, Knebel-/Schlüsselschalter | E | |
| Ausleger/Stützfuß `stInBoom.*`, `stInFoot.*` | Endlagen, Motorschutz, Bumper | E | |
| Analogwerte `stInTilt.wTilt`, `stInDistance.wDistance` | Neigung, Abstand | E | |
| Sensoren `stInSen2mThreshold`, `stInSenJamEntr/Exit1/Exit2`, `stInSenStopper1/2`, `stInSenGrid` | | E | |
| Ausgänge `stOutStopper.*`, `stOutBoom.*`, `stOutFoot.*`, `xOutLedMovement`, `xOutLight`, `xOutLedBumper`, `xOutCarousel` | | E | |
| Schwellwerte `nDiff`, `nZeroOk`, `nStartSlow/Fast`, `nDistanceZero`, `tDiff`, `tZero`, `nTiltComp` | | P | Inbetriebnahme |
| `stInSettings.*` Zeiten | `tBoomInput := T#3500ms` … | P | |
| Exit-Staubereich `arInOutCarrierExit`, `stInOutCarrierExit` | | L + K | |
### Zusammenfassung
- **Aus dem Hardwarelayout (L)** kommt die **Struktur**: welche TROs es gibt, ihr Typ
und damit FB und Anzahl der Parameterblöcke (Separatoren, Weichen, Scanner, Vario,
Lagerlinien), Vorgänger/Nachfolger und damit Ein-/Ausgangs-Staubereiche,
REGION-Reihenfolge, Ziele je Weichenabgang (`nTo1Destinations`), Vorfahrt ja/nein,
Systemgrenzen (`xIsBorner`), die tragende Fördertechnik (`xInCarouselRun`, Gefälle →
`TRUE`), ob ein Scanner am Separator sitzt (`xPluggedScanner`) und Längen als
Grundlage für Zeiten/Staulängen. Das meiste davon ist heute in `DerivedTro` →
`SclTro` abgebildet; Vorfahrt, `xIsBorner` und Längen noch nicht.
- **Aus der Elektroplanung (E)** kommen **alle Namen und Adressen**: jeder Sensor
(`BG`), jedes Ventil (`MB`), Motor (`MA`), Motorschutz (`FC`), Taster (`SF`), Scanner
(`BX`) samt Cognex-Variablen und Hardware-Kennung, die `xPlugged*`-Schalter (was ist
tatsächlich verbaut), Weichenbauart, Not-Halt-Kreis, Schaltschrank und
Steuerungsgrenzen. Im Render-Kontext sind das die `SclName` mit `todo=True`
(`TODO(E-Planung)`). Offen ist die Brücke: die EA-Liste kennt keine Topologie; es
braucht eine Zuordnung Layout-Bauteil (Separator-`TeileId`) ↔ BMK (`BG3242`/`MB3242`).
Diese Brücke baut `lib/e_planning.py` über die Symbolpositionen in
`*_positionsdraw.json` des Kabellängen-Tools (`doc/E-Planung_Bruecke.md`);
heute kommen damit Separator-Sensor und Stopperventil in die SCL
(`scl_skeleton --eplan`).
- **Namenskonvention (K)**: `cMainTro`, `cSep`, `cSwi`, Staubereichs-DBs, HMI- und
State-Pfade — sobald Nummernkreise feststehen, ohne weitere Quelle erzeugbar.
- **Parameter (P)**: Zeiten, Projektkonstanten, WCS-Ziele/Stationen, Test-/IBN-Signale —
weder L noch E; gehören als Typ-Defaults in den Katalog bzw. in eine
Projektkonfiguration.