Merge e-planung-bruecke: stabile TRO-IDs, TRO-JSON v4, E-Planung-Bruecke

Uebernimmt Michael Stangls Zweig (tro_ids, Slots/jamAreas v4, e_planning
--eplan, Konzept- und Datenquellen-Doku) in master, auf dem seitdem die
SCL-Templates fuer alle 7 Typen mit Referenz-Aufrufstelle liegen.

Konfliktloesung:
- lib/scl_context.py: beide Seiten zusammengefuehrt - Michaels _separator
  (E-Planung-Namen) und Staubereiche (_entries/_exit_area, ein JamEntr je
  eigenem Staubereich) plus die Template-Felder (_scanner, _store,
  carriers/data, part_sensor). _jam() fuehrt jetzt auch jam_id. Die beim
  automatischen Zusammenfuehren verlorene Property SclJam.title
  wiederhergestellt.
- Templates 1Sep, 1Sep2Swi, 1Sep_SSCC, Vario, Vario_workStation, PinStore_Auto
  auf v4 nachgezogen: Kommentar "Staubereich J.... von/vor ..." wie im
  Pilot; PinStore-TODO zaehlt Staubereiche statt Nachbarn (mubea: 2 Zulaeufe
  laufen in einen Eingang).
- Golden-Files *_templates neu erzeugt; geaendert sind ausschliesslich
  Staubereichs-Zeilen (Kennung im Kommentar, ein Eingang statt einem je
  Vorgaenger). Generische Golden-Files = Michaels Stand.
- Neuer Test: E-Planung-Namen kommen auch in der Template-Ausgabe an.
- Doku: README Schema v4, SCL_Templates.md Staubereiche und TODO-Zahlen.

Geprueft: 71 Tests gruen; tro_flow --json + scl_skeleton --templates auf
allen 8 CSVs ohne Fehler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-10-14 09:24:20 +02:00
co-authored by Claude Opus 5.5
32 changed files with 3631 additions and 324 deletions
+31 -5
View File
@@ -27,13 +27,18 @@ Template" below). What exists today is:
tool in `lib/` (see "Environment scripts" below)
- `lib/*.py` — CLI tools and libraries that turn a CSV export into a material-flow graph,
a derived TRO list/diagram, a TRO-JSON (`lib/model.py`), an `FB_Main` SCL skeleton, and
(with the BricsCAD DXF) an annotated copy of the drawing for visual checking.
(with the BricsCAD DXF) an annotated copy of the drawing for visual checking;
`lib/e_planning.py` assigns the E-planning's sensors/valves (from the cable-length
tool's `*_positionsdraw.json`) to the layout parts so the skeleton can carry real names.
See **`doc/Python_Scripts.md`** for what each script does and which switches it takes.
- `data/` — input CSV exports (gitignored, not committed)
- `cad/tro_types.lsp` — generated type list for the BricsCAD `TROEDIT` dialog (written by
`tro_annotate.py --emit-lisp`)
- `cfg/dxf_registration.json` — persisted, verified CSV↔DXF coordinate transform;
`cfg/tro_overrides.ini` — hand-maintained corrections to the derived TROs (see `tro_overrides.py`)
`cfg/tro_overrides.ini` — hand-maintained corrections to the derived TROs (see `tro_overrides.py`);
`cfg/e_planning.ini` — project-specific rules for assigning E-planning symbols to layout parts;
`cfg/<csv>_tro_ids.ini` — per-plant TRO-ID file (versioned, IDs renamed by the SPS
programmers), created/extended by `tro_flow.py` via `lib/tro_ids.py`
- `doc/*.md` — analysis documents that reverse-engineer the SCL patterns and propose the
fuller JSON layout schema for the parts of the pipeline still to be built out
@@ -90,7 +95,7 @@ this order for onboarding:
0. **`doc/Python_Scripts.md`** — the existing CLI tooling (`lib/material_flow.py`,
`lib/tro_flow.py`, `lib/tro_annotate.py`, `lib/tro_extract.py`, `lib/scl_skeleton.py`,
plus the `lib/tro_catalog.py`, `lib/model.py`, `lib/dxf_registration.py` and
`lib/e_planning.py`, plus the `lib/tro_catalog.py`, `lib/model.py`, `lib/dxf_registration.py` and
`lib/tro_overrides.py` libraries they share): what each script does, its switches, inputs/outputs. This is
working code, not a proposal — read it before touching `lib/`.
1. **`doc/HundM/Json_Layout-Konzept.md`** — the core proposal: a JSON file as single source
@@ -135,6 +140,16 @@ this order for onboarding:
its generated images come from `doc/TRO_Katalog/tro_graphs/gen_tro_graphs.py`
(doc-tooling, not part of the generator — see `doc/Python_Scripts.md` §8) built from
`doc/TRO_Katalog/connect.ini`.
9. **`doc/Datenquellen_Layout_Hardwareplanung.md`** — which TRO/SCL data comes from the
mechanical layout (CSV), which from the hardware planning (EA list, TIA import,
WSCAD, `positionsdraw`) and which is a default; two reference call sites annotated
line by line. The full raw field lists of both sources are in
`doc/Klassenhierarchie.md`, chapter "Daten".
10. **`doc/Konzept_Layout_Hardware_SCL.md`** — the target workflow (planned, not built):
layout → `<csv>_tro.json` (with slots, `jamAreas`), hardware planning →
`hw_import.py` → `<projekt>_hw.json`, `hw_assign.py` → `<csv>_anlage.json`,
then `scl_skeleton.py`; what happens to `e_planning.py` (split),
`tro_annotate.py` (viewer reading the JSON) and `tro_extract.py` (dropped).
### Used libraries (referenced but not vendored)
@@ -167,8 +182,12 @@ and status: `doc/SCL_Templates.md`. Golden files in `tests/fixtures/scl/` pin th
byte — rewrite them (`SKEL_UPDATE_GOLDEN=1`) only for a reviewed, intended change.
1. **`lib/tro_flow.py --json`** (implemented) — writes `<csv>_tro.json` (schema
`sps_skel-tro-v3`, `model.TroDocument`): TROs, system boundaries `SYSn`, connections with
their kind, findings. See `doc/Python_Scripts.md` §4.
`sps_skel-tro-v4`, `model.TroDocument`): TROs with their hardware `slots`, system
boundaries `SYSn`, connections with their kind, `jamAreas` (one per TRO entry, key
`J<Separator>`, all predecessors that feed it), findings. TRO-IDs come from `cfg/<csv>_tro_ids.ini` (`lib/tro_ids.py`): each
TRO is found again by the position of its separators, so IDs survive a new layout export
(TeileIds do not); `--renumber` renumbers along the material flow, `--ids` points another
export of the same plant at the same file. See `doc/Python_Scripts.md` §4.
2. **`lib/scl_skeleton.py`** (implemented) — reads that TRO-JSON through `TroDocument` (a
wrong/renamed key or `items` not matching the catalog stops the run) and emits an `FB_Main` SCL
skeleton (`<source>_FB_Main.scl`): one `REGION` per TRO with the instance call and every
@@ -184,6 +203,13 @@ byte — rewrite them (`SKEL_UPDATE_GOLDEN=1`) only for a reviewed, intended cha
`sps_skel-tro-extract-v1` JSON, which `scl_skeleton.py` no longer accepts. Not deleted, not
extended; if drawing edits (`TRO_INSERT`/`TRO_EDIT`) are ever needed again, it should
write `model.TroDocument`. See `doc/Python_Scripts.md` §5a.
5. **`lib/e_planning.py`** (implemented, first version) — CSV + E-planning
`*_positionsdraw.json` → `<csv>_eplan.json` (`EplanDocument`): registers the E drawing to
the CSV (`dxf_registration.py`) and assigns BMKs to layout parts by rules in
`cfg/e_planning.ini` (distance, target distance, numbering rule `MB = BG + 1`).
`scl_skeleton.py --eplan` fills separator sensor and stopper valve from it; jam sensors
and outfeed/switch valves are assigned but not yet placed in the SCL. See
`doc/Python_Scripts.md` §5c and `doc/E-Planung_Bruecke.md`.
What is **not** built out yet, relative to the original two-tool design:
+1 -1
View File
@@ -15,7 +15,7 @@ einem CSV-Export der mechanischen Planung (ILS 2.0) entstehen automatisch:
- der **Materialfluss-Graph** der mechanischen Objekte (Strecken,
Gefällestrecken, Kreisel),
- die daraus abgeleitete **TRO-Liste** samt eigenem Flussdiagramm,
- die **TRO-JSON** `<name>_tro.json` (Schema `sps_skel-tro-v3`, Modell
- die **TRO-JSON** `<name>_tro.json` (Schema `sps_skel-tro-v4`, Modell
`lib/model.py`) — die Schnittstelle zwischen Ableitung und SCL-Erzeugung,
- daraus ein **SCL-Gerüst für `FB_Main`** — ein `REGION` pro TRO mit
Instanzaufruf und allen Parametern in der richtigen Reihenfolge, aber
+33
View File
@@ -0,0 +1,33 @@
@echo off
REM ================================================================
REM SPS_SKEL - E-Planung den Bauteilen des Layouts zuordnen
REM ================================================================
REM Aufruf:
REM bin\e_planning.bat --file mubea.csv --eplan Mubea_positionsdraw.json
REM bin\e_planning.bat --file mubea.csv --eplan C:\pfad\Mubea_positionsdraw.json --re-register
REM
REM CSV und *_positionsdraw.json (Kabellaengen-Tool) werden in %SKEL_DATA%
REM gesucht (oder als Pfad angegeben). Regeln: %SKEL_CFG%\e_planning.ini.
REM Ergebnis <csv>_eplan.json und Bericht <csv>_eplan.md in %SKEL_RESULTS%.
REM
REM Weiter: bin\scl_skeleton.bat --json <name>_tro.json --eplan <name>_eplan.json
REM ================================================================
setlocal
REM Umgebung setzen (SKEL_DATA, SKEL_RESULTS, SKEL_LIB, PYTHONPATH ...)
call "%~dp0setenv.bat" >nul
REM Virtuelle Umgebung nutzen, falls vorhanden (pydantic wird benoetigt)
if exist "%SPS_SKEL%\.venv\Scripts\activate.bat" (
call "%SPS_SKEL%\.venv\Scripts\activate.bat"
)
REM Python-Interpreter bestimmen (py-Launcher bevorzugt, sonst python)
set "PY=py"
py --version >nul 2>&1 || set "PY=python"
"%PY%" "%SKEL_LIB%\e_planning.py" %*
set "RC=%ERRORLEVEL%"
endlocal & exit /b %RC%
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# ================================================================
# SPS_SKEL - E-Planung den Bauteilen des Layouts zuordnen
# ================================================================
# Aufruf:
# bash bin/e_planning.sh --file mubea.csv --eplan Mubea_positionsdraw.json
# bash bin/e_planning.sh --file mubea.csv --eplan /pfad/Mubea_positionsdraw.json --re-register
#
# CSV und *_positionsdraw.json (Kabellaengen-Tool) werden in $SKEL_DATA
# gesucht (oder als Pfad angegeben). Regeln: $SKEL_CFG/e_planning.ini.
# Ergebnis <csv>_eplan.json und Bericht <csv>_eplan.md in $SKEL_RESULTS.
#
# Weiter: bin/scl_skeleton.sh --json <name>_tro.json --eplan <name>_eplan.json
# ================================================================
set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Umgebung setzen (SKEL_DATA, SKEL_RESULTS, SKEL_LIB, PYTHONPATH ...)
# shellcheck source=/dev/null
. "$SCRIPT_DIR/setenv.sh" >/dev/null
# Virtuelle Umgebung nutzen, falls vorhanden (pydantic wird benoetigt)
if [ -f "$SPS_SKEL/.venv/bin/activate" ]; then
# shellcheck source=/dev/null
. "$SPS_SKEL/.venv/bin/activate"
fi
# Python-Interpreter bestimmen
PY="python3"
command -v python3 >/dev/null 2>&1 || PY="python"
exec "$PY" "$SKEL_LIB/e_planning.py" "$@"
+81
View File
@@ -0,0 +1,81 @@
; ================================================================
; e_planning.ini - Regeln fuer lib/e_planning.py
; ================================================================
; Ordnet die Sensoren/Ventile der E-Planung (*_positionsdraw.json des
; Kabellaengen-Tools) den Bauteilen des Layouts (CSV) zu. Hintergrund und
; Befunde: doc/E-Planung_Bruecke.md.
;
; [Registrierung]
; anker VERW-Muster der E-Symbole, die auf einem Separator sitzen;
; daraus wird der Lagebezug E-Zeichnung -> CSV ermittelt.
;
; Jeder Abschnitt "<Art>.<Rolle>" ist eine Regel; sie werden in dieser
; Reihenfolge angewendet, ein E-Bauteil wird hoechstens einmal vergeben.
; layout Muster fuer die Layout-Art (Separator, Ausschleus,
; Einschleus, Weiche, Scanner, Beladung, Entladung).
; Standard: der Teil vor dem Punkt im Abschnittsnamen.
; verw Muster fuer das E-Attribut VERW (fnmatch, Komma-Liste,
; Gross-/Kleinschreibung egal)
; praefix BMK-Praefix (BG, MB ...), leer = egal
; max_mm raeumlich: Hoechstabstand nach dem Lagebezug in mm
; soll_mm raeumlich: Sollabstand - das Symbol liegt immer gleich weit
; neben dem Bauteil; gewertet wird die Abweichung vom Soll
; (nicht der kleinste Abstand), zulaessig bis toleranz_mm
; toleranz_mm zulaessige Abweichung vom Sollabstand (Standard 150)
; nummer_von Nummernregel: BMK-Nummer = Nummer der genannten Rolle
; nummer_versatz ... plus dieser Versatz (Mubea: Ventil = Sensor + 1)
; Mit nummer_von wird zuerst die Nummer versucht, danach (wenn max_mm oder
; soll_mm gesetzt) der Abstand fuer die uebrigen Bauteile.
;
; Lieber eine Luecke als ein falscher Name: was keine Regel sicher trifft,
; bleibt in der SCL TODO(E-Planung) und steht im Bericht <csv>_eplan.md.
;
; Die VERW-Texte und die Nummernregel sind projektabhaengig. Die Werte hier
; stammen aus Mubea (500573); in den Referenz-SCL (UH01..UH05) haben Sensor
; und Stopper dieselbe Nummer (BG3242/MB3242) -> dort nummer_versatz = 0.
; ================================================================
[Registrierung]
anker = IN SEP
; Sensor im Stopper -> stInSeparator<n>.stSenInSep
; sitzt in der Zeichnung auf dem Separator (Mubea Speicher: 0-1 mm).
; An den Kreiseln liegen Sensoren 200-550 mm daneben, teils naeher an
; Aus-/Einschleuselementen - unsicher, deshalb bewusst eng.
[Separator.sensor]
verw = IN SEP
praefix = BG
max_mm = 300
; Stopperventil -> xOutStopper<n>
; liegt ~270 mm neben dem Separator, im 800-mm-Raster der Speicherlinien
; naeher am Nachbarn als ueber die Nummer -> Nummernregel zuerst
[Separator.stopper]
verw = SEP
praefix = MB
nummer_von = Separator.sensor
nummer_versatz = 1
max_mm = 600
soll_mm = 270
; Stausensor hinter dem Ausschleuselement (Mubea: konstant ~1390 mm)
[Ausschleus.stau]
verw = STAU*
praefix = BG
max_mm = 1600
soll_mm = 1390
; Ausschleusventil (Mubea: konstant ~1414 mm, Nummer = Stausensor + 1)
[Ausschleus.ventil]
verw = AE ABZW*
praefix = MB
nummer_von = Ausschleus.stau
nummer_versatz = 1
max_mm = 1600
soll_mm = 1414
; Weichenventil an einer Kreisel-Weiche
[Weiche.ventil]
verw = ABZWEIG-*, WEICHE*
praefix = MB
max_mm = 1000
+246
View File
@@ -0,0 +1,246 @@
# Datenquellen für TROs und SCL: mechanisches Layout und Hardwareplanung
Stand 2026-10-11. Welche Angaben für die TRO-Liste und die erzeugten `.scl`-Bausteine
aus dem **mechanischen Layout** kommen, welche aus der **Hardwareplanung** (EA-Liste
und TIA-Import), und was als **Defaultvorbelegung** gesetzt wird. Die vollständige
Liste der Rohangaben beider Quellen (Spalten, Merkmale, Attribute) steht in
`doc/Klassenhierarchie.md`, Kapitel "Daten", Abschnitte "Angaben 1" und "Angaben 2";
die Zuordnung jedes SCL-Parameters zu seiner Quelle in den Tabellen danach.
Wie beide Quellen künftig über eine gemeinsame JSON zum SCL werden:
`doc/Konzept_Layout_Hardware_SCL.md`.
## Die drei Quellen
| Quelle | Dateien | Liefert im Kern |
|---|---|---|
| **Mechanisches Layout** (Anlagen-Hardware) | CSV-Export ILS 2.0 (`data/*.csv`), aus der BricsCAD-Layoutzeichnung | **Struktur**: welche Bauteile, wer folgt auf wen, Drehrichtung, Längen und Höhen, Anzahl Separatoren/Scanner |
| **Hardwareplanung** (E-Planung) | `<projekt>-<n>_EA.xlsx`, `<projekt>-<n>_TIA.xlsx` (PLC Tags + Constants), `<projekt>-<n>_WSCAD.xlsx`, `<zeichnung>_positionsdraw.json` | **Namen und Adressen**: jedes Signal mit BMK, Adresse, Index-Konstante, Rolle, Steuerung, Schaltschrank; mit `positionsdraw` auch die Lage |
| **Defaultvorbelegung** | Typ-Katalog (`lib/tro_catalog.py`), Namensregeln (`scl_context.py`), Projektkonstanten | alles, was weder Layout noch E-Planung kennen: Zeiten, Konstanten-Namen nach Regel, Freigaben, WCS-Ziele |
Die Schlüssel zwischen den Quellen:
```text
Layout-Bauteil (TeileId) --[Lage, e_planning.py]--> E-Symbol (BMK, positionsdraw)
E-Symbol (BMK) --[Name]-----------------> TIA PLC Tag (Adresse, Kommentar)
--[cIn<BMK>]--------------> TIA Constant (Index in DB_Inputs)
E/A-Adresse --[Anschluss]-------------> WSCAD Bezug (Feldstation, Karte)
```
EA-Liste und TIA-Import allein haben **keine Lage**, `positionsdraw` hat **keine
Adresse**. Erst über das BMK als gemeinsamen Schlüssel wird aus "Sensor neben
Separator 0008" ein vollständiger SCL-Eintrag mit Adresse und Indexkonstante.
## Was die Hardwareplanung liefert
### Für die TROs
| Angabe | aus | Regel |
|---|---|---|
| Signal gehört zu welchem Layout-Bauteil | `positionsdraw` `pos` | räumlich, `cfg/e_planning.ini` (heute umgesetzt) |
| Rolle des Signals im TRO | `positionsdraw` `VERW`; ersatzweise Kommentar der EA/TIA-Liste | `IN SEP` → Sensor im Separator, `SEP` → Stopperventil, `STAU_REF-LT` → Stausensor, `AE ABZWEIG` → Ausschleusventil, `BT BELADEN` → Beladesensor, `MT in Position`/`ABFR.FINGER`/`conveyor full` → Vario |
| Welche optionalen Sensoren verbaut sind | vorhanden / nicht vorhanden je Rolle | ergibt die `xPlugged*`-Schalter |
| Fördereinheit (M-Nummer) | Kommentar `(ILS-CV M0108)`, `BEZEICHNUNG` `KR-K0102` | Instanzname des Antriebs (`#fbCarousel0102`), Name der Staubereiche (`DB_JamArea0102`) |
| Steuerung, TRO-Nummernkreis | `KENNZEICHNUNG` `=A1+UH01-KF102`, Dateinummer `-<n>`, `mappings` | `UH01` → TRO 1xx |
| Schaltschrank / Bedienfeld | `KENNZEICHNUNG`, WSCAD `Bezug`, `schaltschrank_elemente` | `stInOutMachineState`, Taster `SF…` |
| Motor ↔ Motorschutz | `MA`/`FC` mit gleicher Nummer | `xOutVarioMotor`, `xInMotorProtection` |
| Safety-Signale | WSCAD `Bezug` auf F-Karte (`KF1FDQ…`) | Not-Halt-Kreis, nur Hinweis |
| Plausibilisierung des TRO-Typs | Anzahl "in Separator n", "AE Abzweig", "Pin Abfrage" je M-Nummer | Gegenprüfung des aus dem Layout abgeleiteten Typs |
### Für die SCL-Dateien
| Ziel | aus | Bemerkung |
|---|---|---|
| Namen in `FB_Main` (`=> "MB3306"`, `:= "FC0145"`, `"SF0002"`) | TIA PLC Tags `Name` | 1:1 |
| Sensor-Indizes `"DB_Inputs".Sensors["cInBG3306"]` | TIA Constants `cIn<BMK>` | 1:1; jede Konstante, die der Generator schreibt, muss in diesem Blatt stehen → **Prüfung möglich** |
| `FB_CallSensors`, Größe `DB_Inputs` | alle `cInBG…`/`cInFC…` mit `Value` | noch nicht generiert |
| Variablen- und Konstantentabelle im TIA Portal | der TIA-Import selbst | wird nicht generiert, sondern importiert |
| `stInOutMachineState`, `xInAutomatic`, `xInErrorQuit` | Schrankelemente `SF…`, `KENNZEICHNUNG` | heute `TODO(E-Planung)` |
| `xInSftyOk` | Safety-Programm; WSCAD nur als Hinweis | heute `TODO(E-Planung)` |
| Scanner `BX…`, Cognex-Variablen | **nicht** in EA/TIA-Tagliste (PROFINET) | TIA-Gerätekonfiguration oder `positionsdraw` mit `BX` |
## Was das mechanische Layout liefert
### Für die TROs
| Angabe | aus | Regel / Modul |
|---|---|---|
| Existenz und Grenzen der TROs | Separatoren (`ILS 2.0 Separator`) und ihr Host (`Zuordnung`, `TrackIds`) | `tro_flow.analyse_tros()` |
| TRO-Typ und damit FB | Host Kreisel/Strecke/Gefälle, Weiche, Aus-/Einschleuselement, Scanner | `doc/500573_Mubea/TRO_Identifikation_500573.md`, `tro_catalog` |
| Anzahl Separatoren, Weichen, Scanner je TRO | `Zuordnung`, `Naechster Separator`, Weichen-Merkmale | Parameterblöcke `stInSeparator<n>`, `stInSwitch<n>` |
| Vorgänger/Nachfolger | `Nachbarn` (mit Bahn `-L`/`-R`), `TrackIds`, `Drehrichtung` | `material_flow` → Kanten, Ein-/Ausgangs-Staubereiche |
| Systemgrenzen | `BTMT Beladung`/`Entladung`, `Eingang`/`Ausgang` | `SYSn`, `xIsBorner` |
| Ziele je Weichenabgang | Erreichbarkeit im TRO-Graphen | `nTo1Destinations`, Zielliste |
| Vorfahrt nötig | Einschleuselement / zwei Ströme in einen Staubereich | `xPrioDir`, `xReleaseOk` |
| Tragende Fördertechnik | Host-Kreisel bzw. Gefälle | `xInCarouselRun1/2` |
| angetriebene Strecke | `L_VF_m`, `Antriebfahrtrichtung` `Auf` | Vario |
| Längen | `L_VF_m`, `L_GF_m`, Kreiselabstand | Grundlage für `tConvEmpty`, Staulängen |
### Für die SCL-Dateien
REGION-Reihenfolge, FB-Typ, Instanzname, Zahl und Reihenfolge der Parameterzeilen,
Staubereichs-Verkettung (Exit von A = Entr von B), `FC_Direction`-Aufruf mit dem
Carrier aus dem Eingangs-Staubereich, `nTo1Destinations`, Cognex-Dummy bei TROs ohne
Scanner. Das ist heute der Teil, den `scl_skeleton.py` schon ohne E-Planung füllt.
## Überschneidungen: gegenseitige Prüfung
Beide Quellen beschreiben dieselbe Anlage, aber unabhängig voneinander. Wo sie sich
überschneiden, prüft die eine die andere:
| Angabe | Layout | Hardwareplanung |
|---|---|---|
| Separatoren je Strecke/Kreisel | `Anzahl_Separator`, `Zuordnung` | Anzahl `IN SEP`/"in Separator n" je M-Nummer |
| Weiche/Abzweig vorhanden | `ILS Weiche`, `ILS Ausschleuselement` | `AE ABZWEIG`, `ABZWEIG-K104`, `ES branch` |
| Scanner | `ILS 2.0 Scanner` | `BX…` (nur in `positionsdraw` mancher Projekte) |
| angetriebene Strecke | `L_VF_m`, `Auf` | `MA…`/`FC…` mit Bezeichnung der Strecke |
| Lage | `Position` | `pos` (nach Lagebezug `dxf_registration`) |
Ein Widerspruch (z. B. Layout 6 Separatoren, E-Planung 5 `IN SEP`) heißt meist:
unterschiedliche Planungsstände. Er gehört in den Bericht `<csv>_eplan.md`, nicht
stillschweigend in die SCL.
## Beispielzeilen mit Herkunft
Zwei reale Aufrufstellen aus `doc/TRO_Katalog/scl_templates/` (UH01), Zeile für Zeile
mit der Quelle als Kommentar:
- **Layout** = mechanisches Layout (CSV)
- **HW-Planung** = Hardwareplanung (EA-Liste, TIA-Import, WSCAD, `positionsdraw`)
- **Default** = Defaultvorbelegung (Typ-Default, Namensregel, Projektkonstante)
Bei zwei Quellen steht die führende zuerst: "HW-Planung + Layout" heißt, der Name
kommt aus der Hardwareplanung, die Zuordnung zu diesem TRO bzw. Ausgang aus dem
Layout. Der Status heute (Generator setzt / `TODO(E-Planung)`) steht in den
Tabellen von `doc/Klassenhierarchie.md`.
Die Codeblöcke sind als `pascal` ausgezeichnet: Markdown-Renderer (VS Code,
GitHub, Gitea) kennen kein `scl`, und SCL ist Pascal-basiert. So werden
Schlüsselwörter, Zuweisungen, Zahlen und `//`-Kommentare hervorgehoben.
### `FB_ILS_MTRO_1Sep1Swi` (TRO 107, Separator mit Weiche am Kreisel)
```pascal
REGION TRO 107(1Sep1Swi) // Layout: Typ aus Separator am Kreisel + Ausschleuselement; Nummer 107: Default (Nummernkreis UH01 aus HW-Planung)
#TRO107.stInSeparator1.Settings.nSeparatorNo := "cSep107.1"; // Default: Namensregel cSep<TRO>.<n>; Anzahl Separatoren: Layout
#TRO107.stInSeparator1.Settings.tDelayToNextItem := "TimeForSepWaiting_Short"; // Default; Short/Medium je Host (Kreisel/Strecke): Layout
#TRO107.stInSeparator1.Settings.tTrailingTime := t#100ms; // Default: Typ-Default, bei IBN prüfen
#TRO107.stInSeparator1.Settings.tHandlingTime := t#50ms; // Default: Typ-Default, bei IBN prüfen
#TRO107.stInSeparator1.Settings.tJam := "TimeForSepJam"; // Default: Projektkonstante
#TRO107.stInSeparator1.Settings.xPluggedJam := TRUE; // HW-Planung: Stausensor (VERW STAU_REF-LT) vorhanden
#TRO107.stInSeparator1.Settings.xPluggedPart := FALSE; // HW-Planung: kein Beladesensor (VERW BT BELADEN)
#TRO107.stInSeparator1.Settings.xPluggedScanner := FALSE; // Layout: kein ILS 2.0 Scanner mit "Naechster Separator" = dieser
#TRO107.stInSeparator1.stSenInSep := "DB_Inputs".Sensors["cInBG3306"]; // HW-Planung + Layout: BG mit VERW IN SEP am Separator; cInBG3306 aus TIA Constants
#TRO107.stInSenJam2 := "DB_Inputs".Sensors["cInBG3305"]; // HW-Planung + Layout: Stausensor; Ausgang 2 = Weiterfahrt auf dem Kreisel
#TRO107.stInSenJam3 := "DB_Inputs".Sensors["cInBG3330"]; // HW-Planung + Layout: Stausensor; Ausgang 3 = Abzweig am Ausschleuselement
#TRO107.stInSwitch1.Settings.nSwitchNo := "cSwi107.1"; // Default: Namensregel cSwi<TRO>.<n>; Anzahl Weichen: Layout
#TRO107.stInSwitch1.Settings.xPluggedExSen3 := FALSE; // HW-Planung: Abgangssensor Exit 3 verbaut?
#TRO107.stInSwitch1.Settings.xPluggedExSen4 := FALSE; // HW-Planung: Abgangssensor Exit 4 verbaut?
#TRO107.stInSwitch1.Settings.nTo1Destinations := 3; // Layout: über Abgang 1 erreichbare Ziele (reachable_via)
#TRO107.stInSettings.xPrioDir2 := TRUE; // Layout: Zusammenführung vor Ausgang 2 -> Vorfahrt nötig
#TRO107(stInPriorityManagerDir2 := "DB_Prozessdaten".stPriorityManager.udt_TRO107.TroIn, // Default: Namensregel udt_TRO<n>; ob nötig: Layout
stOutPriorityManagerDir2 => "DB_Prozessdaten".stPriorityManager.udt_TRO107.TroOut, // Default: Namensregel
nInMainTroNo := "cMainTro107", // Default: Namensregel cMainTro<n>
xInSftyOk := "DB_InterfaceSafety".stFromSafety.EStopMainOF1.Q, // HW-Planung: Not-Halt-Kreis OF1 (Safety-Programm, F-Karten im WSCAD)
xInAllRdyToStart := TRUE, // Default
xInCarouselRun1 := #fbCarousel0102.xOutConveyorFullSpeed, // Layout: Host-Kreisel; Name 0102: HW-Planung (KR-K0102, MA/FC-Bezeichnung)
xInRelease := TRUE, // Default: Freigabe/Testlogik
stInOutMachineState := #stInOutControlUnitCabinetOF1, // HW-Planung: Schaltschrank OF1 (KENNZEICHNUNG, WSCAD Bezug)
stInOutCognexInterface := #stCognexInterfaceDummy, // Layout: kein Scanner am TRO
arInOutJamEntr1 := "DB_JamArea0102".stJam0102_1.arCarrier, // Layout: Vorgänger-Staubereich; DB-Name 0102: HW-Planung (Kreisel K0102) + Default (Index _1)
stInOutJamEntr1 := "DB_JamArea0102".stJam0102_1.stData, // wie oben
arInOutJamExit2 := "DB_JamArea0103".stJam0103_1.arCarrier, // Layout: Nachfolger an Ausgang 2; DB-Name: HW-Planung + Default
stInOutJamExit2 := "DB_JamArea0103".stJam0103_1.stData, // wie oben
arInOutJamExit3 := "DB_JamArea0102".stJam0102_2.arCarrier, // Layout: Nachfolger an Ausgang 3; DB-Name: HW-Planung + Default
stInOutJamExit3 := "DB_JamArea0102".stJam0102_2.stData, // wie oben
stInOutHMI := "DB_Interface_HMI".stTRO.TRO107, // Default: Namensregel
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro107"], // Default: Namensregel
xOutStopper1 => "MB3306", // HW-Planung: MB mit VERW SEP, Nummernregel ab BG3306 (TIA PLC Tag)
xOutSw1ExTo3 => "MB3304"); // HW-Planung + Layout: MB AE ABZWEIG am Ausschleuselement von Ausgang 3
REGION Direction
"FC_Direction"(nInSwitchNo := "cSwi107.1", // Default: Namensregel
nOutDirection => #TRO107.nInDirection, // Default: fest
InOutCarrier := "DB_JamArea0102".stJam0102_1.arCarrier[1]); // Layout: Carrier aus dem Eingangs-Staubereich
END_REGION
END_REGION
```
### `FB_ILS_MTRO_Vario` (TRO 115, angetriebene Strecke mit Separator)
```pascal
REGION TRO 115 (Vario) // Layout: Strecke mit L_VF_m / Antriebfahrtrichtung "Auf"; HW-Planung bestätigt (MA, MT in Position)
#TRO115.stInSeparator1.Settings.nSeparatorNo := "cSep115.1"; // Default: Namensregel; Anzahl Separatoren: Layout
#TRO115.stInSeparator1.Settings.tDelayToNextItem := "TimeForSepWaiting_Short"; // Default; Wahl je Host: Layout
#TRO115.stInSeparator1.Settings.tTrailingTime := t#800ms; // Default: Typ-Default Vario, bei IBN prüfen
#TRO115.stInSeparator1.Settings.tHandlingTime := t#300ms; // Default: Typ-Default Vario, bei IBN prüfen
#TRO115.stInSeparator1.Settings.tJam := "TimeForSepJam"; // Default: Projektkonstante
#TRO115.stInSeparator1.Settings.xPluggedJam := FALSE; // HW-Planung: kein Stausensor am Separator
#TRO115.stInSeparator1.Settings.xPluggedPart := FALSE; // HW-Planung: kein Beladesensor
#TRO115.stInSeparator1.Settings.xPluggedScanner := FALSE; // Layout: kein Scanner
#TRO115.stInSeparator1.stSenInSep := "DB_Inputs".Sensors["cInBG3025"]; // HW-Planung + Layout: BG IN SEP am Separator; Index aus TIA Constants
#TRO115.stInSeparator1.Settings.tSenFree := t#2000ms; // Default: Typ-Default
#TRO115.stInSeparator1.Settings.tSenWait := T#5000ms; // Default: Typ-Default
#TRO115.stInVario.Settings.xContinousEmptying := FALSE; // Default: Prozessentscheidung
#TRO115.stInVario.Settings.tWaitForJog := T#300ms; // Default: Typ-Default
#TRO115.stInVario.Settings.tConvEmpty := T#45000ms; // Layout: Förderlänge L_VF_m / Geschwindigkeit (Default) -> heute fest
#TRO115.stInVario.Settings.nCountCarrier := 1; // Default: Prozessentscheidung
#TRO115.stInVario.Settings.tWaitForStop := t#3s; // Default: Typ-Default
#TRO115.stInVario.Settings.tCarrierInPosDelay := t#1200ms; // Default: Typ-Default
#TRO115.stInVario.Settings.tCarrierInPosEdgeDelay := t#100ms; // Default: Typ-Default
#TRO115.stInVario.Settings.tRasterHigh := t#5s; // Default: Typ-Default
#TRO115.stInVario.Settings.tRasterLow := t#5s; // Default: Typ-Default
#TRO115.stInVario.stSenCarrInPos := "DB_Inputs".Sensors["cInBG3026"]; // HW-Planung: VERW "MT in Position" an dieser Strecke
#TRO115.stInVario.stSenFinger := "DB_Inputs".Sensors["cInBG3027"]; // HW-Planung: VERW "Finger in position" / ABFR.FINGER
#TRO115.stInVario.stSenLastPos := "DB_Inputs".Sensors["cInBG3000"]; // HW-Planung: VERW "conveyor full"
#TRO115.stInVario.stSenJam := "DB_Inputs".Sensors["cInBG3001"]; // HW-Planung: VERW "Jam detector" / STAU_REF-LT
#TRO115.stInPriorityManager.xReleaseOk := TRUE; // Layout: keine Zusammenführung -> keine Vorfahrt
#TRO115.stInVario.Settings.xJamSenDelayActive := TRUE; // Default (nur sinnvoll, wenn HW-Planung einen Stausensor hat)
#TRO115.stInVario.Settings.tJamSenDelay := T#2s; // Default: Typ-Default
#TRO115.stInSettings.xIsBorner := TRUE; // Layout: erster TRO nach Systemgrenze (Beladung/Eingang)
#TRO115(nInMainTroNo := "cMainTro115", // Default: Namensregel
xInSftyOk := "DB_InterfaceSafety".stFromSafety.EStopMainOF1.Q, // HW-Planung: Not-Halt-Kreis
xInMotorProtection := "FC0145", // HW-Planung: FC mit Nummer des Motors (schaltschrank_elemente, TIA "Meldekontakt Motorschutzschalter")
xInAllRdyToStart := TRUE, // Default
xInReset := "cTodoFalse", // Default
xInErrorQuit := "SF0002", // HW-Planung: Quittiertaster (SF, Schrank)
xInAutomatic := #t_Automatik_OF1 AND "SF0120", // HW-Planung: Schlüsselschalter SF0120; Verknüpfung: Default
xInRelease := "DB_TRO_TEST".TRO115, // Default: Test-/IBN-Freigabe
arInOutJamEntr := "DB_JamArea4104".stJam4104_1.arCarrier, // Layout: Vorgänger-Staubereich; Nummer: HW-Planung (M-Nummer) + Default
stInOutJamEntr := "DB_JamArea4104".stJam4104_1.stData, // wie oben
arInOutJamFinger := "DB_JamArea4103".stJam4103_1.arCarrier, // Layout: Staubereich auf der Vario-Strecke selbst; Nummer: HW-Planung + Default
stInOutJamFinger := "DB_JamArea4103".stJam4103_1.stData, // wie oben
stInOutMachineState := #stInOutControlUnitCabinetOF1, // HW-Planung: Schaltschrank OF1
stInOutCognexInterface := #stCognexInterfaceDummy, // Layout: kein Scanner
stInOutHMI := "DB_Interface_HMI".stTRO.TRO115, // Default: Namensregel
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro115"], // Default: Namensregel
xOutStopper => "MB3025", // HW-Planung: MB VERW SEP, Nummernregel ab BG3025
xOutVarioMotor => "MA0145"); // HW-Planung + Layout: MA an dieser angetriebenen Strecke (Lage bzw. Bezeichnung)
END_REGION
```
### Auszählung der beiden Beispiele
| Führende Quelle | 1Sep1Swi (39 Zeilen) | Vario (47 Zeilen) |
|---|---|---|
| Layout | 13 | 10 |
| HW-Planung | 11 | 14 |
| Default | 15 | 23 |
Gezählt sind alle kommentierten Zeilen (Zuweisungen und REGION-Kopf) nach der zuerst
genannten Quelle; "wie oben" zählt wie die Zeile davor. Die Struktur (welche Zeilen es gibt, wie oft, in welcher Reihenfolge) kommt
immer aus dem Layout; die Tabelle zählt, woher der **Wert** einer Zeile kommt. Der
Großteil der Defaults sind Namensregeln und Zeiten, die nach dem Typ-Katalog
vorbelegt und bei der Inbetriebnahme geprüft werden. Fast alle HW-Planung-Zeilen
lassen sich aus TIA-Import und `positionsdraw` füllen; offen bleiben `xInSftyOk`
(Safety-Programm) und die Scanner (PROFINET, nicht im Tag-Import).
+161
View File
@@ -0,0 +1,161 @@
# Brücke Layout ↔ E-Planung über `*_positionsdraw.json`
Stand 2026-10-10. Erst Machbarkeitsprüfung, inzwischen umgesetzt als
`lib/e_planning.py` (Regeln `cfg/e_planning.ini`, Aufruf `bin/e_planning.bat`,
`scl_skeleton --eplan`); Bedienung in `doc/Python_Scripts.md` §5c, Stand der
Umsetzung unten.
## Problem
Das Kapitel "Daten" in `doc/Klassenhierarchie.md` zeigt: das Hardwarelayout (CSV)
liefert die **Struktur** (welche TROs, welche Separatoren/Weichen, wer mit wem), die
E-Planung die **Namen und Adressen** (`BG…`, `MB…`, `MA…`, `FC…`). Die EA-Liste allein
kennt aber keine Topologie. Es fehlt die Zuordnung **Layout-Bauteil (CSV-`TeileId`) →
Betriebsmittelkennzeichen (BMK)**.
## Idee
Die E-Planung wird als Layer über der Hardwareplanung gezeichnet: Sensor- und
Aktorsymbole liegen in der Zeichnung neben dem Bauteil, zu dem sie gehören. Das
Kabellängen-Tool (`C:\10-Develop\gitrepos\kabellaengen`, `lib/getpositions.py`)
schreibt diese Symbole mit Position und Attributen nach `<zeichnung>_positionsdraw.json`.
Damit ist die Brücke eine **räumliche Zuordnung**: E-Symbol → nächstes Layout-Bauteil
passender Art.
## Datenquelle `*_positionsdraw.json`
| Schlüssel | Inhalt | Nutzen für sps_skel |
|---|---|---|
| `sensors` | je BMK (`BG0642@UNKNOWN`): `pos` [x, y], `VERW` (Verwendung), `BEZEICHNUNG` (Klartext), `BESCHR`, `ARTINR`, `KENNZEICHNUNG` (`=A1+UH01-KF107`) | Hauptquelle: Sensoren/Ventile/Motoren im Feld |
| `schaltschrank_elemente` | Elemente im Schrank (Motorschutz `FC…`, Taster, Leuchten, `QA…`) mit `VERW`, `BEZEICHNUNG`, `KENNZEICHNUNG`, ohne Feldposition | Motorschutz ↔ Motor, Bedienelemente |
| `mappings` | Steuerung → Liste ihrer BMK (`UH01: [...]`) | Steuerungsaufteilung, Nummernkreis |
| `racks`, `distributors`, `tunnels` | Kabeltrassen, Verteiler | für sps_skel ohne Belang |
| `double_ids`, `warnings` | doppelte BMK, fehlende Attribute | Qualitätsprüfung der E-Daten |
Die Erdungs-Beispiele (`PR500501_…_Erdung_…`, `ST500652_…`) enthalten nur
Potentialausgleich (`POT…`) und taugen nicht für die Brücke. Brauchbar sind
Dateien mit `BG`/`MB`/`MA`: `Mubea_positionsdraw.json` (157 Feld-BMK),
`ST500592_ILS_positionsdraw.json` (~3100), `ST500592_10_5-10_Omniflo_…` (~900),
`ST-Fortna_positions_.json` (mit 38 Scannern `BX`).
## Test an Mubea
Eingaben: `data/mubea.csv` (Layout, 38 Separatoren) und
`kabellaengen/work/Mubea_positionsdraw.json` (E-Planung). Die Zahlen in diesem
Abschnitt stammen aus der ersten Untersuchung (Umkreis 700 mm, ohne Regeln); das
Ergebnis der Umsetzung steht unten.
### 1. Lagebezug
Die Ermittlung aus `lib/dxf_registration.py` (`derive_transform`) funktioniert
unverändert, wenn man die E-Symbole mit `VERW` `SEP`/`IN SEP` als Rolle "Separator"
einsetzt:
```text
mubea.csv <- Mubea_positionsdraw.json: dx +50613.6 mm, dy +8555.8 mm, Drehung 0
26/38 Separatoren im Toleranzband
```
Das entspricht dem schon gespeicherten Bezug `mubea.csv|500573_60_1.dxf`
(dx +50833.7, dy +8414.5) bis auf ~250 mm: die E-Zeichnung liegt im selben
Koordinatensystem wie die Layoutzeichnung. Zum Prüfen genügt also der
bestehende Mechanismus; als Schlüssel in `cfg/dxf_registration.json` kann
`<csv>|<positionsdraw.json>` dienen.
### 2. Was sich zuordnen lässt
| E-Symbol (`VERW`) | Layout-Partner | Abstand | Ergebnis |
|---|---|---|---|
| `IN SEP` (BG, Sensor im Separator) | Separator | **0–1 mm** im Pin-Speicher (20/20), sonst 200–500 mm | Anker: das Symbol sitzt auf dem Separator |
| `SEP` (MB, Stopperventil) | Separator | konstant **~270 mm** daneben | eindeutig über Nummernregel (unten), nicht über Abstand |
| `AE ABZWEIG` (MB, Ausschleusventil) | Ausschleuselement | konstant **1414 mm** (20/20, Versatz (−1405, −159)) | eindeutig, wenn der Versatz je Symbolart bekannt ist |
| `STAU_REF-LT` (BG, Stausensor) | Ausschleuselement | konstant **1391 mm** (20/20) | wie oben |
| `ABZWEIG-K104` (MB, Weiche) | Weiche | 783 mm | eindeutig (nur eine Weiche) |
| `BANDBRUCH`, `KETTENBRUCH`, `ÜBERGABE`/`ÜBERNAHME`, `SYNC P1..4`, `ZAPPLER` | Kreisel/Strecke/Station | uneinheitlich, teils > 50 m (Symbol an Konvejoren außerhalb des CSV-Ausschnitts) | nicht über Abstand, nur über `BEZEICHNUNG` |
Nebeneinander liegende Separatoren (Pin-Speicher, Raster 800 mm) machen den
Abstand allein mehrdeutig: das MB von Strecke n liegt 270 mm von Separator n und
530 mm von Separator n+1. Eindeutig wird es erst mit der Nummernregel.
### 3. Nummernregel der E-Planung
Ventil = Sensor + 1, gleiche Funktionsgruppe:
| Paar | Treffer (von 58 MB) |
|---|---|
| `MB(n)` `SEP` ↔ `BG(n−1)` `IN SEP` (z. B. BG0642 / MB0643) | 31 |
| `MB(n)` `AE ABZWEIG` ↔ `BG(n−1)` `STAU_REF-LT` (z. B. BG0542 / MB0543) | 21 |
| sonstige Paare (`VEREINZ(CP)`, `STOPPER (CP)`, `AE ABZW.`) | 3 |
| ohne BG-Partner | 3 |
Damit reicht es, den **Sensor** räumlich zuzuordnen (der `IN SEP` liegt auf dem
Separator); das Ventil folgt aus der Nummer. Die SCL-Referenzen zeigen dasselbe
Muster (`stSenInSep := …["cInBG3242"]`, `xOutStopper1 => "MB3242"`) — dort sogar
mit gleicher Nummer; die Regel ist also projektabhängig und gehört in eine
Konfiguration, nicht in den Code.
Gleiches gilt für Motor ↔ Motorschutz: `MA0200` "PIN CONVEYOR#100" ↔
`FC0200` "MOTORSCHUTZ CONVEYER #100" (gleiche Nummer, `schaltschrank_elemente`),
wie in der SCL `xOutVarioMotor => "MA0145"`, `xInMotorProtection := "FC0145"`.
### 4. Was nicht passt
12 von 38 Layout-Separatoren (vor allem an den Kreiseln K0102/K0104/K0105) haben im
Umkreis von 700 mm kein `IN SEP`/`SEP`; umgekehrt liegen mehrere `IN SEP` des
Kreisels K0102 neben Aus-/Einschleuselementen statt neben einem Separator. Vermutlich
ist die CSV (aus `500573_60_1.dxf`) ein anderer Planungsstand als die E-Zeichnung
`Mubea.dxf` des Kabellängen-Tools, oder Kreisel-Separatoren sind im CSV anders
modelliert. Das muss mit zueinander passenden Ständen geprüft werden, bevor man
eine Quote angibt.
`Mubea_positionsdraw.json` enthält **keine Scanner** (`BX`). Für Scanner-Zuordnung
ist ein anderes Projekt nötig (`ST-Fortna_positions_.json`, `ILS-Baugruppe`).
## Was die Brücke für das Kapitel "Daten" liefert
| SCL-Datum | aus `positionsdraw.json` | Regel |
|---|---|---|
| `stInSeparator<n>.stSenInSep` | BG mit `VERW` `IN SEP` | räumlich am Separator (Anker) |
| `xOutStopper<n>` | MB mit `VERW` `SEP` | Nummernregel ab dem BG |
| `stInSenJam<n>`, `Settings.xPluggedJam` | BG `STAU_REF-LT` | räumlich, Versatz je Symbolart; vorhanden ⇒ `xPluggedJam := TRUE` |
| `xOutSw<n>ExTo3/4` | MB `AE ABZWEIG` / Weichen-MB | Nummernregel ab dem Stausensor bzw. räumlich an Weiche/Ausschleuselement |
| `Settings.xPluggedPart`, `stSenPart` | BG `BT BELADEN` | räumlich; vorhanden ⇒ `TRUE` |
| `xInMotorProtection`, `xOutVarioMotor` | MA im Feld + FC in `schaltschrank_elemente` | gleiche Nummer |
| `xInCarouselRun` (welcher Antrieb) | FC/MA-`BEZEICHNUNG` "KREISEL-K0102", "CONVEYOR #100" | Textabgleich mit dem Host-Kreisel des TRO |
| Steuerung, TRO-Nummernkreis, `stInOutMachineState` | `KENNZEICHNUNG` `=A1+UH01-KF107`, `mappings` | `UH01` = Steuerung, `KF107` = E/A-Station |
| Plausibilisierung | `BEZEICHNUNG` "SEPARATOR STR. 14", "KR-K0102 VOR BT-BELADUNG" | Strecken-/Kreiselnummer gegen Layout prüfen |
Nicht aus dieser Quelle: Not-Halt-Kreis (`xInSftyOk`), Taster für Automatik/Quittieren
(stehen nur als Schrankelemente ohne Bezug zum TRO), Scanner-Hardwarekennungen,
Weichenbauart (`xNewSwitch`).
## Umsetzung (Stand 2026-10-10)
1. **`lib/e_planning.py`**: liest `*_positionsdraw.json` in `EDevice` (BMK, VERW,
Bezeichnung, Kennzeichnung → Steuerung/E/A-Station, Position).
2. **Lagebezug** über `dxf_registration.derive_transform` mit den `IN SEP`-Sensoren
als Rolle "Separator"; abgelegt in `cfg/dxf_registration.json` unter
`<csv>|<positionsdraw.json>`.
3. **Regeln in `cfg/e_planning.ini`** (projektabhängig): je Rolle Layout-Art, VERW,
Höchstabstand, **Sollabstand** (für die konstanten Versätze von Stausensor und
Ausschleusventil — sonst gewinnt bei 0080/0084 ein näherer, fremder Stausensor
des Kreisels K0102) und die Nummernregel (`MB = BG + 1`).
4. **Ausgabe** `<csv>_eplan.json` (`EplanDocument`) und Bericht `<csv>_eplan.md`.
`scl_skeleton --eplan` setzt Separator-Sensor und Stopperventil ein
(`SclName.todo = False`); alles andere bleibt `TODO(E-Planung)`.
Ergebnis Mubea mit den ausgelieferten Regeln: Separator-Sensor 21/38,
Stopper 22/38, Stausensor 20/23, Ausschleusventil 20/23, Weichenventil 1/1. Den
Separator-Sensor nimmt die Regel nur bis 300 mm; die Kreisel-Fälle mit 470–540 mm
(z. B. BG0532 an 0044) bleiben bewusst offen.
Offen:
- Stausensoren und Ausschleus-/Weichenventile stehen in JSON und Bericht, kommen
aber noch nicht in die SCL: die TRO-JSON sagt nicht, welches Ausschleuselement
bzw. welche Weiche zu welchem TRO-Ausgang gehört.
- Die Lagerlinien eines `PinStore`-TRO (`arInSep[n]`) sind zugeordnet, der
Generator erzeugt die Linienparameter aber noch nicht.
- Scanner (`BX`) fehlen in den Mubea-Daten; Regel und Test mit
`ST-Fortna_positions_.json` stehen aus.
- Ein Abgleich mit passenden Ständen von CSV und E-Zeichnung für die Kreisel.
+329 -4
View File
@@ -31,6 +31,8 @@ scl_skeleton.py (Ausgabe: SclFile -> FB_Main.scl, generisch oder per Jin
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
@@ -68,8 +70,11 @@ lib/model.py [Instanz-Ebene / TRO-JSON]
├── TroBase(BaseModel)
│ └─ definition() --> TroDefinition (gelesen aus tro_catalog, nicht gespeichert)
│ └── DerivedTro(TroBase) [einzige echte Vererbung im Projekt]
│ └─ slots: list[Slot] (Stellen fuer die Hardwareplanung)
├── _Positioned(BaseModel) (Mixin: position {x,y,z}, describe_position)
│ ├── TroBase, TroCandidate
│ ├── TroBase, TroCandidate, JamArea
├── Slot(BaseModel) (separator1/switch1/scanner1/drive + Rollen)
├── JamArea(_Positioned) (Staubereich J<Sep>: Eingang eines TRO)
├── Connection(BaseModel) (from/to/kind zwischen zwei TRO-IDs)
├── TroCandidate(_Positioned) (gemeldete Stelle, an der ein TRO erwartet wird)
├── Analysis(BaseModel)
@@ -80,7 +85,8 @@ lib/model.py [Instanz-Ebene / TRO-JSON]
├─ tros: list[DerivedTro]
├─ boundaries: list[DerivedTro]
├─ connections: list[Connection]
└─ candidates: list[TroCandidate]
├─ candidates: list[TroCandidate]
└─ jam_areas: list[JamArea] (JSON: jamAreas)
lib/material_flow.py [CSV-Rohmodell -> Graph, @dataclass]
├── Element (1 CSV-Zeile)
@@ -132,6 +138,17 @@ lib/scl_context.py [Render-Kontext für die SCL, BaseM
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
@@ -238,13 +255,14 @@ Kantenarten) → Prüfung gegen den Katalog.
### `TroDocument` — `lib/model.py`
**Wozu:** Die TRO-JSON `<csv>_tro.json` (Schema `sps_skel-tro-v3`) — die
**Wozu:** Die TRO-JSON `<csv>_tro.json` (Schema `sps_skel-tro-v4`) — 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
(Kanten + Kantenarten → `Connection`-Liste, je TRO die `slots` aus
`derive_slots()`, die Staubereiche aus `derive_jam_areas()`). `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`
@@ -414,3 +432,310 @@ generischen Aufbau (`emit_region()`).
**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 | ein Eingang je eigenem Staubereich (`TroDocument.jamAreas`, Kennung `J<Separator>`; mehrere Vorgänger fahren in denselben ein), 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.
+379
View File
@@ -0,0 +1,379 @@
# Konzept: Layout und Hardwareplanung → Anlagen-JSON → SCL
Stand 2026-10-11. Zielbild für den Ausbau der Pipeline, damit die beiden
Fachabteilungen unabhängig voneinander und zeitversetzt liefern können. Welche
Angaben aus welcher Quelle kommen, steht in `doc/Datenquellen_Layout_Hardwareplanung.md`;
die Rohangaben beider Quellen in `doc/Klassenhierarchie.md`, Kapitel "Daten".
## Ausgangslage
Die Produktion läuft über zwei Abteilungen:
1. **Anlagenlayout** (aus dem Angebotswesen, detaillierter aus der Projektierung).
Liefert alles für die TROs: Bauteile, Abfolge, Weichen, Staubereiche,
Systemgrenzen.
2. **Hardwareplanung** (Karten, Kabel, Sensoren, Aktoren). Liefert die globalen
Variablen und die tatsächlichen Sensoren und Aktoren mit Namen und Adressen.
Die Hardwareplanung kommt **Tage bis Wochen später** als das Layout. Das System
muss deshalb mit dem Layout allein ein brauchbares SCL-Skelett erzeugen und die
Hardware später nachtragen können, ohne Layout-Arbeit zu wiederholen.
## Zielbild
```text
Abteilung 1: Anlagenlayout Abteilung 2: Hardwareplanung (später)
CSV ──tro_flow──> <csv>_tro.json EA/TIA/WSCAD.xlsx, positionsdraw.json
(TROs, Abfolge, ──hw_import──> <projekt>_hw.json
Staubereiche, Slots) (globale Variablen, Geräte)
│ │
└──────────── hw_assign ─────────────────┘
│
<csv>_anlage.json (Layout + Zuordnungen)
│
scl_skeleton ──> FB_Main.scl, FB_CallSensors.scl, …
```
| Stufe | Skript | Eingabe | Ausgabe | Status |
|---|---|---|---|---|
| 1 Layout | `tro_flow.py --json` | CSV, `cfg/tro_overrides.ini` | `<csv>_tro.json` | vorhanden, wird erweitert |
| 2 Hardware | `hw_import.py` | `*_TIA.xlsx`, `*_EA.xlsx`, `*_WSCAD.xlsx`, `*_positionsdraw.json` | `<projekt>_hw.json` | neu |
| 3 Zuordnung | `hw_assign.py` | `<csv>_tro.json`, `<projekt>_hw.json`, Regeln, `cfg/hw_overrides.ini` | `<csv>_anlage.json`, Bericht `.md` | neu (Logik aus `e_planning.py`) |
| 4 SCL | `scl_skeleton.py` | `<csv>_anlage.json` (oder nur `<csv>_tro.json`) | `.scl` | vorhanden, wird umgestellt |
Grundsätze:
- **Stufe 1 allein reicht für ein Skelett.** Ohne Hardware wird jeder offene Wert
wie heute `TODO(E-Planung)`.
- **Die Stufen verändern sich nicht gegenseitig.** Die Zuordnung legt ihre
Ergebnisse neben das Layout, statt es zu überschreiben. Ein neuer Layoutstand
oder ein neuer Hardwarestand heißt: die betroffene Stufe und alles danach neu
ausführen.
- **Lieber eine Lücke als ein falscher Name** (wie heute in `cfg/e_planning.ini`):
was keine Regel sicher trifft, bleibt offen und steht im Bericht.
- **Drei Quellen, jede sichtbar:** Layout, Hardwareplanung und Defaults
(`cfg/defaults.ini`, Typ-Katalog). Jede SCL-Zeile lässt sich einer davon zuordnen.
## Umbauten
### 1. TRO-JSON zur Layout-JSON ausbauen (Stufe 1)
Heute fehlt der Zuordnung ein Ziel: die TRO-JSON sagt nicht, welche Stellen ein
TRO hat, an die Hardware gehört. Daran scheitern heute Stausensoren und
Weichenventile (`doc/E-Planung_Bruecke.md`, "Offen").
- **Slots je TRO:** je Separator, Weiche, Ausgang und Antrieb ein Eintrag mit der
Layout-ID des Bauteils und den Rollen, die die Hardwareplanung dort füllen muss.
```json
"slots": [
{"slot": "separator1", "layoutId": "0008", "roles": ["sensor", "stopper", "jamSensor?", "partSensor?"]},
{"slot": "switch1", "layoutId": "0037", "exit": 3, "roles": ["valve", "exitSensor?"]},
{"slot": "drive", "layoutId": "0002", "roles": ["carouselRun"]}
]
```
Die Slots ergeben sich aus dem Katalogtyp (`items`) und der Topologie; `?` =
optional, fehlt das Gerät, wird der zugehörige `xPlugged*`-Schalter `FALSE`.
- **Staubereiche als eigene Liste `jamAreas[]`:** ein Staubereich je TRO-Eingang,
mit allen Vorgängern, dem Förderer, auf dem er liegt, und seiner Lage auf dem
Förderer (siehe "Staubereiche" unten). Heute nur abgeleitet beim Rendern
(`scl_context.SclJam`). Dabei korrigieren: `scl_skeleton` erzeugt heute je
Vorgänger einen eigenen Eingang (`arInOutJamEntr1`, `…Entr2`); in den Referenzen
laufen mehrere Vorgänger in **einem** Staubereich zusammen, der TRO hat einen
Eingang (Ausnahme `2Sep1Swi`, PinStore).
- **Weitere Layout-Angaben:** `xIsBorner`, Vorfahrt ja/nein, tragender Förderer
mit Rolle (heute `hosts` ohne Rolle), Längen für `tConvEmpty` und Staulängen.
- **Ableitungen aus `scl_context` nach `tro_flow`/`model` verschieben,** damit die
JSON sie trägt und nicht erst das Rendern sie kennt.
- Schema `sps_skel-tro-v4`.
**Stand (2026-10-11), erster Teil umgesetzt:**
- `model.Slot` und `DerivedTro.slots`: `separator<n>`, `switch<n>`,
`scanner<n>`, `drive` mit Rollen, Anzahl aus dem Katalog (`derive_slots`).
Weichen-Slots haben noch keine `layoutId`: `tro_flow` hält das Weichen- bzw.
Ausschleuselement nicht am TRO fest.
- `model.JamArea` und `TroDocument.jamAreas`: je Separator laut Katalog ein
Eingangs-Staubereich `J<Separator>` mit allen Vorgängern, dem Host und der
Lage (`derive_jam_areas`). Bei 2Sep-Typen ist die Aufteilung der Vorgänger
auf die zwei Eingänge offen (Hinweis im Feld `note`); beim PinStore heißt
der Eingang vorläufig nach dem ersten Linien-Separator.
- `scl_context` liest die Staubereiche aus der JSON: **ein** `JamEntr` je
eigenem Staubereich statt je Vorgänger (Abweichung zu UH01–UH05 behoben),
jeder Ausgang nennt im Kommentar den Staubereich des Nachfolgers. Golden
Files neu erzeugt. Eine v3-Datei ohne `jamAreas` wird weiter gelesen.
- **Noch offen aus Umbau 1:** Layout-ID der Weichen, `xIsBorner`, Vorfahrt,
Host-Rolle, Längen (`tConvEmpty`, Staulängen), `<NNNN>`/`<k>` für den
SCL-Namen der Staubereiche.
### 2. Neues Skript `hw_import.py` (Stufe 2)
- Liest TIA-Import (PLC Tags + Constants), EA-Liste, WSCAD-Import und optional
`positionsdraw.json`.
- Schreibt ein neues Modell `HardwareDocument`:
- **Geräte** je BMK: Adresse, Index-Konstante (`cInBG3306`), Kommentar, VERW,
Steuerung, Station/Schrank, Position (falls vorhanden);
- **globale Variablen**: alle Tags und Konstanten, Motor↔Motorschutz-Paare,
Taster, Not-Halt.
- Prüft die Exporte gegeneinander: BMK fehlt in einer Liste, doppelte BMK,
Adresse ohne Tag.
- Übernimmt aus `e_planning.py`: `load_positionsdraw`, `EDevice`, `pair_drives`.
- Erste Quelle: der TIA-Import, weil er Namen und Konstanten 1:1 liefert und die
spätere Prüfung des SCL ermöglicht.
### 3. Neues Skript `hw_assign.py` (Stufe 3)
- Eingabe: `<csv>_tro.json` + `<projekt>_hw.json`; das CSV wird nicht mehr gelesen.
- Zuordnungswege, als Regeln in einer INI (heute `cfg/e_planning.ini`):
- **räumlich** über `positionsdraw`: die bestehende Logik aus `e_planning.py`
(Lagebezug über `dxf_registration`, Höchst-/Sollabstand), jetzt gegen die Slots;
- **Nummernregel**: `MB = BG + 1` bzw. gleiche Nummer, Motor = Motorschutz;
- **nur EA-Liste, ohne Positionen**: über die Kommentare
("in Separator 1 (ILS-CV M0108)") und eine von Hand gepflegte Tabelle
M-Nummer → Layout-Förderer.
- Handkorrekturen in `cfg/hw_overrides.ini` (analog `cfg/tro_overrides.ini`).
- Ausgabe `<csv>_anlage.json`: Layoutteil unverändert, daneben
```json
"bindings": [
{"layoutId": "0008", "role": "sensor", "bmk": "BG3306", "constant": "cInBG3306",
"address": "%E330.6", "source": "positionsdraw", "rule": "raeumlich",
"distance": 12.0, "status": "zugeordnet"}
]
```
`status`: `zugeordnet` | `offen` | `widerspruch`.
- **Stabile Schlüssel:** Zuordnungen hängen an TRO-ID + Slot + Rolle. Die TRO-IDs
sind ab der Festlegung durch die SPS-Programmierer stabil (siehe "TRO-IDs"
unten). Die Layout-`TeileId` taugt dafür **nicht**: sie ändert sich zwischen
zwei CSV-Exporten (siehe dort). `layoutId` in `bindings` dient nur zur
Nachverfolgung im aktuellen Stand.
- **Gegenprüfung** beider Quellen (Anzahl Separatoren vs. `IN SEP`, Weichen vs.
`AE ABZWEIG`, angetriebene Strecken vs. `MA`) in den Bericht.
### 4. `scl_skeleton.py` / `scl_context.py` (Stufe 4)
- Liest **eine** Eingabe: `<csv>_anlage.json`; ohne Hardwareteil wie heute
`<csv>_tro.json`.
- `--eplan` und die Zusammenführung in `scl_context._separator` entfallen; der
Kontext liest Namen aus `bindings`, offene Slots werden `TODO(E-Planung)`.
- **Prüfung gegen die globalen Variablen:** jeder Tag und jede `cIn…`-Konstante im
SCL muss im TIA-Import stehen, sonst Abbruch mit Liste.
- **Neue Bausteine** nach Stufe 2: `FB_CallSensors` aus der Sensorliste,
Antriebsaufrufe (`fbCarousel0102`) aus den Motorpaaren, Staubereichs-DBs.
### 5. Defaults als dritte Quelle
Zeiten und Projektkonstanten, die heute fest in `scl_skeleton.py` stehen
(`t#600ms`, `T#1500ms` …), wandern in den Typ-Katalog (`tro_catalog.py`) bzw. eine
Projektdatei `cfg/defaults.ini`.
## Bestehende Programme
| Programm | Rolle im neuen Ablauf |
|---|---|
| `material_flow.py`, `tro_flow.py` | bleiben Stufe 1; Ausbau um Slots und `jamAreas` |
| `tro_catalog.py`, `model.py`, `tro_overrides.py`, `dxf_registration.py` | bleiben Bibliotheken; `model.py` bekommt Slots, `jamAreas`, Anlagen-JSON |
| `e_planning.py` | wird aufgeteilt (unten) |
| `tro_annotate.py` | bleibt als Kontrollansicht, liest künftig die JSON (unten) |
| `tro_extract.py` | entfällt bzw. wird umgebaut (unten) |
| `scl_skeleton.py`, `scl_context.py` | Stufe 4, Umstellung auf `<csv>_anlage.json` |
### `e_planning.py` → `hw_import` + `hw_assign`
| Teil heute | Wohin |
|---|---|
| `load_positionsdraw`, `EDevice`, `pair_drives` | `hw_import.py` |
| `resolve_transform`, `load_rules`, `_assign_spatial`, `_assign_by_number`, Bericht | `hw_assign.py`, Ziele = Slots statt CSV-Bauteile |
| `cfg/e_planning.ini` | Regeldatei von `hw_assign` |
| `EplanDocument` / `<csv>_eplan.json` | entfällt, ersetzt durch `bindings[]` |
Übergang: `e_planning.py` bleibt kurz als Hülle, die `hw_import` und `hw_assign`
nacheinander aufruft, damit `bin/e_planning.bat` weiter funktioniert; danach
löschen.
### `tro_annotate.py` → Kontrollansicht für Layout und Zuordnung
- **Aus der JSON zeichnen** statt `analyse_tros` selbst aufzurufen: die Zeichnung
zeigt dann genau das, woraus das SCL entsteht.
- **Zuordnungen einzeichnen:** neben jedem Slot das BMK (`BG3306`, `MB3306`),
offene Slots und Widersprüche rot. Für die Hardwareplanung die schnellste
Kontrolle der räumlichen Zuordnung.
- `--emit-lisp` (`TROEDIT`) bleibt, solange Marker in BricsCAD bearbeitet werden;
hängt an der Entscheidung zu `tro_extract`.
### `tro_extract.py` → entfällt im neuen Ablauf
Es wäre ein dritter Korrekturweg neben `cfg/tro_overrides.ini` (Layout) und
`cfg/hw_overrides.ini` (Zuordnung) und schreibt noch das alte Schema
`sps_skel-tro-extract-v1`, das niemand mehr liest.
- **Löschen** (Empfehlung), zusammen mit `TROEDIT`/`--emit-lisp`. Korrekturen nur
in den INI-Dateien: nachvollziehbar und versioniert.
- **Umbauen**, falls die Projektierung in BricsCAD korrigieren will: es schreibt
dann Einträge in `tro_overrides.ini`/`hw_overrides.ini`, keine eigene JSON.
Bis zur Entscheidung bleibt es geparkt.
## Festlegungen (2026-10-11)
### TRO-IDs
**Festgelegt:** Die TRO-IDs vergeben die SPS-Programmierer, die zugleich die
Anwender der Skripte sind. Eine ID muss nur **eindeutig** sein. Üblich ist eine
Nummerierung entlang des Materialflusses und eine Benennung nach den Bauteilen
(Kreisel usw.), vorgeschrieben ist das nicht. Sobald größere Teile des SPS-Codes
stehen, ändern sich die IDs nicht mehr; in der frühen Projektphase dürfen sie
mehrmals neu vergeben werden.
**Befund:** Heute vergibt `tro_flow` laufende Nummern je Lauf (`TRO01`, `TRO02` …,
`tro_flow.py` Zeile ~1163); `cfg/tro_overrides.ini` warnt bereits, dass sich die
Nummern bei neuer Datenbasis verschieben. Die Layout-`TeileId` ist ebenfalls nicht
stabil: Vergleich zweier Exporte derselben Anlage
| Vergleich | Bauteile | gleiche Id und Lage | gleiche Lage, andere Id |
|---|---|---|---|
| `mubea.csv` ↔ `mubea_export.csv` | 119 / 125 | 46 | 27 |
| `HundM05.csv` ↔ `HundM05_14.csv` | 45 / 44 | 22 | 16 |
Stabil ist nur die **Lage** des Bauteils.
**Umgesetzt (2026-10-11, `lib/tro_ids.py`, Schalter in `tro_flow.py`;
Einzelheiten in `doc/Python_Scripts.md` §4 "TRO-IDs"):**
- Eine versionierte ID-Datei je Anlage, `cfg/<csv>_tro_ids.ini`, Abschnitt
`[tro]`: je TRO die ID und die Lage seines Anker-Separators (x, y in mm), der
Kommentar dahinter nennt Typ, Bezeichnung und Separatoren. Von `tro_flow`
angelegt, von den SPS-Programmierern umbenannt. Weitere Abschnitte (später
Separator- und Förderernummern) bleiben bei jedem Lauf erhalten. Ein anderer
Export derselben Anlage nutzt dieselbe Datei über `--ids`.
```ini
[tro]
TRO01 = -400.0, 13920.0 ; 1Sep1Swi | Separator 0003 auf Kreisel2 Bahn L | Sep 0003
K0302_1 = 10310.0, -440.1 ; von Hand umbenannt
```
- **Standard (IDs halten):** `tro_flow` liest die ID-Datei und findet jeden TRO
über die Lage seiner Separatoren wieder (Toleranz `--id-tolerance`, Standard
100 mm, nächster Treffer zuerst, je TRO ein Eintrag). Gefundene TROs behalten
ihre ID, auch umbenannte. Neue TROs bekommen die nächste freie Nummer und
werden an `[tro]` angehängt; weggefallene stehen im Bericht (mit dem TRO, der
ihren Separator jetzt enthält), bleiben in der Datei, ihre ID wird nicht
wiederverwendet. `tro_annotate` liest dieselbe Datei, schreibt sie aber nicht.
- **Programmschalter `--renumber` (frühe Phase):** alle IDs neu entlang des
Materialflusses vergeben (Tiefensuche ab den Beladestationen, auf Kreisel und
Strecke in `TrackIds`-Reihenfolge) und `[tro]` neu schreiben. Ohne ID-Datei
geschieht dasselbe beim ersten Lauf.
- **Geprüft** mit `mubea.csv` → `mubea_export.csv` (andere TeileIds): 18 TROs
wiedergefunden, darunter eine umbenannte ID; `TRO14` als weggefallen gemeldet,
weil der Export die Separatoren 0005 und 0036 zu einem `2Sep2Swi` (`TRO13`)
zusammenfasst.
- **Noch offen:** die Abschnitte `[mubea.csv.tro.TRO03]` usw. in
`cfg/tro_overrides.ini` hängen an der alten Nummerierung und passen schon
heute nicht mehr (`TRO03` ist dort Sep 0010, im aktuellen Lauf Sep 0011).
Sie werden auf die IDs der ID-Datei umgestellt, sobald `tro_flow` die
Overrides wieder liest.
- Dadurch werden auch die Abschnitte `[<csv>.tro.<TRO-ID>]` in
`cfg/tro_overrides.ini` und die Zuordnungen von `hw_assign` stabil.
### Staubereiche
**Bisheriges Namensschema** (Referenzen UH01–UH05, `doc/TRO_Katalog/connect.ini`,
286 Staubereiche):
```text
"DB_JamArea<NNNN>".stJam<NNNN>_<k> Kurzform JA<NNNN>_<k>
```
- `<NNNN>` = **Förderer**, auf dem der Staubereich liegt, ein DB je Förderer:
`0102` = Kreisel K0102 (vgl. `#fbCarousel0102`), `0103`/`0104` = Strecken,
`4103`/`4104` = Vario-Förderer.
- `<k>` = laufende Nummer **entlang des Materialflusses** auf diesem Förderer;
Kreisel K0102 hat `_1` … `_6` in Umlaufrichtung.
- 249 der 286 folgen diesem Schema. Sonderfälle: Lagerlinien `Storage1.1.2` (24),
Übergabe an eine andere Steuerung `IO_Device_UHxx_TO_UHyy…` bzw. lokal
`#arCarrierHandoverJam5000` (9), `JamBuffer101` im Leerträgerpuffer (2), F2H (1).
**Prüfung der Annahme "ein Staubereich = Ausgang des Vorgängers und Eingang des
Nachfolgers":**
- **Kein** Staubereich hat mehr als einen Nachfolger-TRO.
- **65** Staubereiche haben mehrere Vorgänger: dort laufen Ströme zusammen, z. B.
`JA0102_2` mit Vorgängern TRO101, TRO106 und TRO107 (Weichenausgang), alle mit
Nachfolger TRO112.
- Jeder TRO hat genau einen Eingangs-Staubereich; einzige Ausnahme sind die
PinStore-TROs (zusätzlich die Lagerlinie).
Ein Staubereich ist also immer **der Eingang genau eines TRO**, aber nicht immer
der Ausgang genau eines Vorgängers. Ein Name nach dem Schema `ja_tro3tro4` ist bei
Zusammenführungen nicht eindeutig.
**Vorschlag:**
**Ergänzung (2026-10-11):** In einem Mubea-Layout heißen die Staubereiche nach
**ihrem Separator**: der Staubereich vor `Sep1024` heißt `J1024`. Das passt zum
Befund oben: der Staubereich ist die Warteschlange vor dem Stopper, also der
Eingang genau eines TRO bzw. genau eines Separators. Das Layout liegt nicht im
Repo; Quelle der Separatornummer (Zeichnungsbeschriftung, E-Planung, Vergabe durch
die SPS-Programmierer) noch klären.
- **Kennung nach dem Separator:** `J<Separatornummer>`, z. B. `J1024`. Das ist
feiner als eine Kennung je TRO und deckt TROs mit mehreren Separatoren
(`2Sep1Swi` mit `JamEntr1`/`JamEntr2`) ohne Sonderregel ab. Zusammenführungen
sind kein Problem, weil der Name nur am Nachfolger hängt.
- **Separatornummern wie TRO-IDs behandeln:** in derselben ID-Datei, über die Lage
des Separators wiedergefunden, von den SPS-Programmierern vergeben bzw.
übernommen; `--renumber` vergibt sie in der frühen Phase neu.
- **Name im SCL — festgelegt: Standard sind die SCL-Dateien UH01–UH05,** also
`"DB_JamArea<NNNN>".stJam<NNNN>_<k>`. `J<Separatornummer>` ist nur die Kennung
in der JSON (und die Beschriftung im Layout), nicht der SCL-Name. Für den
SCL-Namen braucht es je Staubereich:
- `<NNNN>`: die **Förderernummer** des Förderers, auf dem der Staubereich liegt
(Kreisel K0102 → `0102`); vergeben von den SPS-Programmierern bzw. aus der
Hardwareplanung (`KR-K0102`), gepflegt in der ID-Datei wie TRO-IDs und
Separatornummern;
- `<k>`: laufende Nummer **entlang des Materialflusses** auf diesem Förderer,
aus dem Layout abgeleitet (Kreisel in Umlaufrichtung, beginnend an einer
festzulegenden Stelle, z. B. nach der Einschleusung).
Ohne Förderernummer bleibt der Name `TODO(E-Planung)` wie heute.
- Ausnahmen bleiben Sonderfälle mit eigenem Namen: Lagerlinien, Übergabe an eine
andere Steuerung, Leerträgerpuffer.
### Korrekturen in BricsCAD
**Zurückgestellt.** Eine Alternative wäre, die `<csv>_tro.json` zu visualisieren
und von Hand zu bearbeiten. Bis dahin bleibt `tro_extract.py` geparkt, `TROEDIT`
unverändert.
## Reihenfolge
1. **TRO-IDs stabil machen:** ID-Datei, Wiederfinden über die Lage,
`--renumber`. Darauf bauen Overrides, Staubereiche und Zuordnungen auf.
**Erledigt** (2026-10-11).
2. Slots und `jamAreas` in die TRO-JSON (Umbau 1), ein Eingangs-Staubereich je
TRO — ohne Slots hat die Zuordnung kein Ziel. **Erster Teil erledigt**
(2026-10-11, siehe Umbau 1 "Stand").
3. `hw_import` mit dem TIA-Import als erster Quelle.
4. `hw_assign` mit der räumlichen Logik aus `e_planning.py`; `e_planning.py` als
Übergangshülle.
5. `scl_skeleton` auf `<csv>_anlage.json` umstellen, `--eplan` entfernen;
`tro_annotate` auf JSON umstellen.
6. Defaults (`cfg/defaults.ini`), `FB_CallSensors`, EA-Liste ohne Positionen.
Zu jedem Schritt: `bin/`-Wrapper für neue Skripte, Tests unter `tests/`
(kleine TIA-Datei als Testdaten), Golden Files bei Schemawechsel bewusst neu
erzeugen, `doc/Python_Scripts.md` ergänzen.
## Offene Entscheidungen
- **Staubereiche:** woher die Separatornummer (`Sep1024` → `J1024`) stammt; wo auf
einem Kreisel die Zählung `<k>` beginnt (Referenz K0102 prüfen). Das SCL-Schema
ist festgelegt (UH01–UH05).
- **Toleranz** beim Wiederfinden der TROs über die Lage: vorläufig 100 mm
(`--id-tolerance`). Ein weiter verschobener Separator wird als "neu" und
"weggefallen" gemeldet, der Programmierer übernimmt die ID von Hand in der
ID-Datei. Mit echten Layoutänderungen prüfen, ob der Wert passt.
- **Korrekturen in BricsCAD oder in der JSON:** zurückgestellt.
+152 -14
View File
@@ -1,14 +1,16 @@
# Python Scripts — CLI Tooling Overview
**As of:** 2026-10-09
**As of:** 2026-10-10
`lib/` currently holds ten Python modules (five of them runnable CLI tools,
`lib/` currently holds eleven Python modules (six of them runnable CLI tools,
five supporting libraries), plus one documentation-generator script that lives
under `doc/TRO_Katalog/tro_graphs/`. Together the CLI tools turn a CSV export
of the mechanical layout (ILS 2.0) into a material-flow graph, a derived TRO
list/diagram, a TRO-JSON (shared model `lib/model.py`), an `FB_Main` SCL
skeleton generated from that JSON, and an annotated copy of the BricsCAD
drawing for visual checking.
drawing for visual checking. `e_planning.py` bridges to the electrical
planning: it assigns the sensors/valves of the E-planning drawing to the
layout parts, so the skeleton can carry their real names.
This document is the practical "what does each script do and which switches
does it take" reference. For the underlying domain model (TRO types, JSON
@@ -34,6 +36,11 @@ tro_flow.py ──► <name>_tro_flow.dot / .svg, _tro_doc.md, _tro_error
│ scl_skeleton.py ──► <name>_FB_Main.scl
│ (validates the JSON against model.py; one REGION per TRO;
│ E-planning values left as TODO(E-Planung) gaps)
│ ▲
│ │ --eplan <name>_eplan.json (optional)
│ e_planning.py ◄── <drawing>_positionsdraw.json (kabellaengen)
│ (assigns BG/MB of the E-planning drawing to the layout
│ parts by position + numbering rule; see §5c)
│
└── same derivation ──► tro_annotate.py ──► <name>_annotated.dxf (VIEWER ONLY)
(also needs the BricsCAD DXF; uses dxf_registration.py for the CSV↔DXF
@@ -66,6 +73,10 @@ Supporting libraries (no CLI of their own, imported by the tools above):
- **`lib/dxf_registration.py`** — derives, verifies and persists the rigid
(rotation + offset) transform between the CSV export's coordinate system and
the DXF drawing's.
- **`lib/tro_ids.py`** — the per-plant ID file `%SKEL_CFG%\<name>_tro_ids.ini`:
reads it, finds each TRO again by the position of its separators, assigns
new IDs, writes/extends the file. Used by `tro_flow.py` (read and write) and
`tro_annotate.py` (read only). See §4, "TRO-IDs".
- **`lib/tro_overrides.py`** — reads the hand-maintained
`%SKEL_CFG%\tro_overrides.ini`; only `material_flow.py --connect` still uses
it. See §6a.
@@ -91,6 +102,8 @@ Each CLI tool is invoked through its `bin/<name>.bat` (Windows) / `bin/<name>.sh
bin\material_flow.bat --file mubea.csv --tosvg --use-cords --doc
bin\tro_flow.bat --file mubea.csv --tosvg --use-cords --doc --json
bin\scl_skeleton.bat --json mubea_tro.json
bin\e_planning.bat --file mubea.csv --eplan Mubea_positionsdraw.json
bin\scl_skeleton.bat --json mubea_tro.json --eplan mubea_eplan.json
bin\tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb --legend
```
@@ -102,8 +115,9 @@ Conventions shared by the CLI tools:
- `--file` / `--dxf` accept either a bare filename — resolved against
`%SKEL_DATA%` — or a full path.
- All generated output is written to `%SKEL_RESULTS%`.
- `tro_annotate.py` additionally persists the verified CSV↔DXF coordinate
offset to `%SKEL_CFG%\dxf_registration.json`.
- `tro_annotate.py` and `e_planning.py` additionally persist the verified
coordinate offset (CSV↔DXF, CSV↔E-planning drawing) to
`%SKEL_CFG%\dxf_registration.json`.
- Every run prints a `====` bordered report to stdout (counts, warnings,
output file paths); non-fatal findings are printed as `?`/`!` lines and also
do not stop the run.
@@ -294,17 +308,45 @@ colors all come from `lib/tro_catalog.py` — nothing is hard-coded here.
| `--doc` | — | off | Also write a Markdown report (`<name>_tro_doc.md`) with the TRO list, connections, findings and the full type catalog. |
| `--connect` | — | off | Also write the TRO topology as `<name>_tro_connect.ini` (nodes, connections, system boundaries, predecessors/successors per TRO). Pure output, never read back. |
| `--true-scale` | — | off | Only with `--use-cords`: draw to scale. By default empty stretches longer than 1500 mm between nodes are shortened (order and neighbourhood are kept). |
| `--json` | — | off | Also write the TRO-JSON `<name>_tro.json` (schema `sps_skel-tro-v3`, model `TroDocument` in `lib/model.py`): TROs, system boundaries (`SYSn`), connections with their kind, findings. Input for `scl_skeleton.py`. |
| `--json` | — | off | Also write the TRO-JSON `<name>_tro.json` (schema `sps_skel-tro-v4`, model `TroDocument` in `lib/model.py`): TROs with their `slots`, system boundaries (`SYSn`), connections with their kind, `jamAreas`, findings. Input for `scl_skeleton.py`. |
| `--ids` | `DATEI` | `<name>_tro_ids.ini` | ID file of the plant (name in `%SKEL_CFG%`, or a path). Use it to run another export of the same plant against the same IDs, e.g. `--file mubea_export.csv --ids mubea_tro_ids.ini`. |
| `--renumber` | — | off | Number all TROs afresh along the material flow and rewrite the `[tro]` section of the ID file (early project phase). Renamed IDs are lost; other sections of the file are kept. |
| `--id-tolerance` | `MM` (float) | `100.0` | Maximum distance between an ID-file entry and a separator of a TRO for the TRO to count as found again. |
TROs, types and connections always come from the layout alone;
`cfg/tro_overrides.ini` is not read. Errors in the derivation are fixed in
`lib/tro_flow.py`, not in a correction file.
**TRO-IDs** (`lib/tro_ids.py`). The IDs belong to the SPS programmers and must
survive a new layout export; the layout `TeileId` changes between exports, the
position does not. So every run reads `%SKEL_CFG%\<name>_tro_ids.ini`:
```ini
[tro]
TRO01 = -400.0, 13920.0 ; 1Sep1Swi | Separator 0003 auf Kreisel2 Bahn L | Sep 0003
K0302_1 = 10310.0, -440.1 ; renamed by hand
```
- Each entry is `ID = x, y` (mm, CSV coordinates) of the TRO's anchor
separator. An entry finds the TRO with a separator within `--id-tolerance`
(closest first, one entry per TRO), and that TRO keeps the ID — renamed IDs
included. An ID must be unique and a valid SCL identifier; `SYSn` is
reserved for system boundaries.
- No file yet: the TROs are numbered along the material flow (depth-first from
the loading stations over the material-flow graph, on a carousel or Strecke in
`TrackIds` order) and the file is created.
- A TRO not found gets the next free `TRO<n>` and is appended to `[tro]`;
an entry without a TRO is reported ("weggefallen", with the TRO that now
holds its separator, e.g. after two TROs merged into a `2Sep2Swi`) and stays
in the file; its ID is not reused.
- The console report lists found / new / dropped IDs.
**Output** (in `%SKEL_RESULTS%`): `<name>_tro_flow.dot` (always, with `pos`
attributes when `--use-cords` was used), `<name>_tro_flow.svg` (with
`--tosvg`), `<name>_tro_doc.md` (with `--doc`), `<name>_tro.json` (with
`--json`), `<name>_tro_errors.md` (only if TROs are unconnected or of an
unknown type).
unknown type). In `%SKEL_CFG%`: `<name>_tro_ids.ini` (created, extended or,
with `--renumber`, rewritten — see above).
**Exit codes:** `0` ok · `1` input/CLI error · `2` SVG rendering failed
(Graphviz) · `3` TRO validation errors found (error file written).
@@ -355,6 +397,7 @@ overridden.
| `--legend` | — | off | Draw a legend of the color groups. |
| `--marker-size` | `MM` (float) | `300.0` | Marker radius in mm. |
| `--text-height` | `MM` (float) | `220.0` | Text height in mm. |
| `--ids` | `DATEI` | `<csv-stem>_tro_ids.ini` | ID file as for `tro_flow.py`, **read only**: the markers carry the same IDs as the TRO-JSON. Without the file the TROs are numbered along the material flow, as `tro_flow.py` would. |
| `--offset` | `DX,DY` | — | Set the CSV↔DXF offset by hand instead of deriving/loading it. For a drawing built with dxfmakros (CSV exported from the same drawing) this is `0,0`. |
| `--re-register` | — | off | Re-derive the coordinate offset even if a verified one is already cached in `%SKEL_CFG%`. |
| `--check` | — | off | Dry run: only analyze and report, write no output file. |
@@ -446,7 +489,8 @@ is missed.
|---|---|
| REGION order (material flow), instance list, FB type per TRO | sensor/actuator addresses (`cInBG`, `MB`, `MA`) |
| number of separator / switch / scanner parameter blocks | separator and switch numbers, `cMainTro` |
| `nTo1Destinations` when the TRO has exactly one switch | jam areas (`DB_JamArea…`) |
| `nTo1Destinations` when the TRO has exactly one switch | jam-area DB names (`DB_JamArea…`) |
| number of jam entries (one per own jam area, not per predecessor) and which jam area each exit leads to (`J<Separator>` in the comment) | |
| destination list per switch exit (graph reachability) | times marked "pruefen" |
| cross-reference to the layout parts (separator numbers, coordinate) | |
@@ -460,7 +504,7 @@ than a guess.
Switches: `--json NAME` (default `export_tro.json`, looked up in
`%SKEL_RESULTS%`; written by `tro_flow.py --json`), `--out NAME` (default
`<source>_FB_Main.scl`), `--start TRO`. The last one matters while the plant has
`<source>_FB_Main.scl`), `--eplan NAME` (see below), `--start TRO`. The last one matters while the plant has
no loading/unloading station: the flow is then a closed loop with no entry, so
the order has to be broken somewhere arbitrary. The chosen start and the reason
are recorded in the file header.
@@ -494,6 +538,16 @@ by a PLC engineer (`doc/SCL_Templates.md` §6). `2Sep1Swi`, `EmptyCarrBuffer`,
`LoadingBoom` and `2Sep2Swi` have no call site yet and stay generic. `--strict` stops (exit `1`, no SCL) if any FB type has no
template. Without `--templates` the output is unchanged.
**Names from the E-planning (opt-in, `--eplan NAME`).** Reads the assignment
`<csv>_eplan.json` written by `e_planning.py` (§5c; bare name looked up in
`%SKEL_RESULTS%`). For every separator it assigns, the separator sensor
(`stSenInSep := "DB_Inputs".Sensors["cInBG0642"]`) and the stopper valve
(`xOutStopper<n> => "MB0643"`) are filled in and lose their `TODO(E-Planung)`
marker (`SclName.todo = False`); everything else stays open. The header names
the E-planning file. An assignment made for a different CSV (`csv_file` ≠ the
TRO-JSON's `source`) stops the run with exit `1` — separator numbers are only
unique within one export. Without `--eplan` the output is unchanged.
Tests: `tests/test_scl_context.py` (the derivations one by one),
`tests/test_scl_templates.py` (each template rendered with the real values of
its reference call site must equal that call site, comments and whitespace
@@ -502,6 +556,86 @@ HundM05 byte for byte against `tests/fixtures/scl/*_FB_Main.scl` and
`*_FB_Main_templates.scl`, timestamp masked; an intended change is written
back with `SKEL_UPDATE_GOLDEN=1`).
## 5c. `e_planning.py` — assign the E-planning to the layout parts
The bridge between layout and electrical planning (background and findings:
[`E-Planung_Bruecke.md`](E-Planung_Bruecke.md)). The layout knows which
separators, switches and outfeed elements exist and where; the E-planning knows
what their sensors and valves are called. The I/O list alone has no topology —
but the E-planning is drawn as a layer over the hardware layout, every sensor or
actuator symbol sits next to its part, and the cable-length tool
(`kabellaengen/lib/getpositions.py`) writes those symbols with position and
attributes to `<drawing>_positionsdraw.json`.
```
<name>.csv (layout) + <drawing>_positionsdraw.json (E-planning)
│
▼
e_planning.py ──► <name>_eplan.json (EplanDocument, schema sps_skel-eplan-v1)
──► <name>_eplan.md (report: assignments, gaps, drives)
│
▼
scl_skeleton.py --eplan <name>_eplan.json
```
Steps:
1. **Read** `sensors` (field devices) and `schaltschrank_elemente` (cabinet) of
the positionsdraw file into `EDevice` (BMK, `VERW`, `BEZEICHNUNG`,
`KENNZEICHNUNG` → controller `UH01` / I/O station `KF107`, position). A BMK
listed twice keeps the entry that has a `VERW`.
2. **Registration** E-planning drawing → CSV with `dxf_registration.py`: the
E symbols matching `[Registrierung] anker` (default `IN SEP`, the sensor that
sits on the separator) against the CSV separators. Stored under the key
`<csv>|<positionsdraw>` in `%SKEL_CFG%\dxf_registration.json` and re-verified
on every run; `--re-register` forces a new derivation.
3. **Rules** from `%SKEL_CFG%\e_planning.ini`, one section `<kind>.<role>` per
rule, applied in file order; each E device is given out at most once, each
role of a layout part filled at most once:
| key | meaning |
|---|---|
| `layout` | layout kind pattern (default: the part before the dot): `Separator`, `Ausschleus`, `Einschleus`, `Weiche`, `Scanner`, `Beladung`, `Entladung` |
| `verw`, `praefix` | pattern(s) for the E attribute `VERW`, BMK prefix (`BG`, `MB`) |
| `max_mm` | spatial: maximum distance after registration |
| `soll_mm`, `toleranz_mm` | spatial: the symbol always sits this far from the part; candidates are ranked by their deviation from it, not by the smallest distance |
| `nummer_von`, `nummer_versatz` | numbering rule: BMK number = number of the given role + offset (Mubea: valve = sensor + 1) — tried first, distance only for the rest |
Shipped rules (tuned on Mubea): `Separator.sensor`, `Separator.stopper`,
`Ausschleus.stau`, `Ausschleus.ventil`, `Weiche.ventil`. `VERW` texts and the
numbering rule are project-specific — in the reference SCL (UH01..UH05)
sensor and stopper share the number (BG3242/MB3242), i.e. offset `0`.
4. **Drives**: motor `MAnnnn` ↔ motor protection `FCnnnn` (same number), for the
report.
Uncertain cases stay open on purpose: a gap is better than a plausible wrong
name. The report lists, per role, the layout parts without a match, the field
devices without a match, and distance and runner-up distance of every match
(close values = check by hand).
Switches: `--file NAME` (CSV, default `export.csv`), `--eplan NAME`
(positionsdraw JSON, required; bare name resolved against `%SKEL_DATA%`, or a
full path), `--rules FILE` (default `%SKEL_CFG%\e_planning.ini`),
`--re-register`. Exit `0` also when not everything was assigned; `1` on input
errors or when the registration cannot be proven.
**Today only separator sensor and stopper reach the SCL.** Jam sensors and
outfeed/switch valves are assigned in the JSON/report, but the TRO-JSON does not
yet say which outfeed element or switch belongs to which TRO exit, so
`scl_skeleton` cannot place them. The same holds for the storage lines of a
`PinStore` TRO (`arInSep[n]`): their sensors are assigned but the generator does
not emit per-line parameters yet.
Result on Mubea (`mubea.csv` + `Mubea_positionsdraw.json`): registration
dx +50613.6 / dy +8555.8 mm; separator sensor 21/38, stopper 22/38 (all 20
storage lines exact, 0–1 mm), jam sensor 20/23, outfeed valve 20/23, switch
valve 1/1. The open separators are on the carousels K0102/K0104/K0105, where
the E-planning drawing and the CSV apparently differ.
Tests: `tests/test_e_planning.py` (reading, rules, numbering vs. distance on a
storage-line grid, target distance, uniqueness, registration, drives, the path
into `scl_context` and `scl_skeleton --eplan`).
## 6. `tro_catalog.py` — TRO type catalog (library, no CLI)
The single source of truth for everything needed to generate or draw a TRO
@@ -535,8 +669,8 @@ TRO in a plant, and the whole TRO-JSON, look like. It imports only the catalog.
```
tro_catalog.py type level TroDefinition (items, FB block, color, symbol)
▲ get_tro()
model.py instance level TroBase ─► DerivedTro, Connection, TroCandidate,
Analysis, TroDocument
model.py instance level TroBase ─► DerivedTro, Slot, JamArea, Connection,
TroCandidate, Analysis, TroDocument
▲
tro_flow.py (writes) · scl_skeleton.py (reads) · tro_annotate.py (draws)
```
@@ -544,11 +678,13 @@ tro_flow.py (writes) · scl_skeleton.py (reads) · tro_annotate.py (draws)
| Class | What it is |
|---|---|
| `TroBase` | Core of every TRO: `id`, `type`, `confidence`, `separators`, `predecessors`, `successors`, `position` (`{x,y,z}` in JSON). `definition()`, `fb_block` (JSON `fbType`) and `items` are **read from the catalog** on every access, never stored — so they follow a type change and cannot go stale. |
| `DerivedTro` | `TroBase` + what `tro_flow.py` knows from the derivation: `label`, `hosts`, `scanners`, `reason`, `colocated`, `positionFrom`, `positionPoints`. Also used for the system boundaries `SYSn`. |
| `DerivedTro` | `TroBase` + what `tro_flow.py` knows from the derivation: `label`, `hosts`, `scanners`, `reason`, `colocated`, `positionFrom`, `positionPoints`, `slots`. Also used for the system boundaries `SYSn` (no slots). |
| `Slot` | A place on a TRO where hardware belongs: `slot` (`separator1`, `switch1`, `scanner1`, `drive` — the stable key together with the TRO-ID), `layoutId` (TeileId in the current export, for tracing only; empty for switches, whose element `tro_flow` does not keep yet), `roles` the hardware planning must fill (`sensor`, `stopper`, `jamSensor?` …; `?` = optional). Built by `derive_slots()` from the catalog type. |
| `JamArea` | A jam area = the queue in front of a separator, the entry of exactly one TRO: `id` (`J<Separator>`, e.g. `J0042`), `tro`, `entry` (1, 2 for 2Sep types), `separator`, `host`, `predecessors` (all TROs/stations feeding it — merges have several), `note`, `position`. Built by `derive_jam_areas()`; the SCL name `"DB_JamArea<NNNN>".stJam<NNNN>_<k>` needs the conveyor number from the hardware planning and is not stored. |
| `Connection` | Directed edge `from` → `to` with its `kind` (transfer / umlauf / weiche / unbestimmt / normal / bahn). |
| `TroCandidate` | A spot reported in a review where a TRO is **expected** but not derived (formerly `OpenPoint`): `name`, `type` (the expected catalog type), `position`, `belongsTo` (the TRO that holds the part today), `note`. Not a TRO — no separators, no connections, no SCL. Built by `tro_overrides.py` from `[<csv>.openpoint.<Name>]`; empty in the TRO-JSON for now. |
| `Analysis` | Working result of `tro_flow.analyse_tros` (moved here from `tro_flow.py`). |
| `TroDocument` | The TRO-JSON: `schema` (`sps_skel-tro-v3`), `source`, `created`, `note`, `tros`, `boundaries`, `connections`, `candidates`, `findings`. `from_analysis()`, `to_json()`, `from_json()`. |
| `TroDocument` | The TRO-JSON: `schema` (`sps_skel-tro-v4`), `source`, `created`, `note`, `tros`, `boundaries`, `connections`, `candidates`, `jamAreas`, `findings`. `from_analysis()` (derives slots and jam areas), `to_json()`, `from_json()`, `jam_areas_or_derived()` (a v3 file without `jamAreas` still renders). |
`TroBase` and `TroCandidate` share the `position` handling (JSON `{x,y,z}`,
`x`/`y`/`z`, `describe_position()`) through the private mixin `_Positioned`.
@@ -560,7 +696,9 @@ All models validate on creation and assignment and reject unknown keys
(`extra="forbid"`). JSON keys keep the camelCase names of the TRO-JSON (`id`,
`type`, `fbType`, `belongsTo` …); Python code uses the field names
(`tro_id`, `type_name`, `fb_block`, `belongs_to`) — both are accepted on input.
Schema v3 differs from v2 only in `candidates` replacing `openPoints`.
Schema v3 differs from v2 only in `candidates` replacing `openPoints`; v4 adds
`slots` per TRO and `jamAreas`. A v3 file still reads (both default to empty;
`scl_skeleton` then derives the jam areas while rendering).
Tests: `tests/test_model.py` (round trip, catalog-backed fields, rejection of
wrong keys/items, SCL rendering from a document) — run with
+7 -6
View File
@@ -14,7 +14,7 @@ It is written so that it fits the existing main path unchanged:
```
export.csv ─► tro_flow --json ─► <name>_tro.json ─► scl_skeleton ─► <name>_FB_Main.scl
(dxfmakros (derive) (TroDocument, (render) (skeleton with
EXPORTCSV) sps_skel-tro-v3) TODO(E-Planung))
EXPORTCSV) sps_skel-tro-v4) TODO(E-Planung))
```
Nothing upstream of `scl_skeleton` changes. The TRO-JSON stays the only input, and the
@@ -274,11 +274,11 @@ reference line":
| Template (reference) | Decisions to confirm |
|---|---|
| `1Sep` (TRO100) | times 600/200 ms and `tSenFree`/`tSenWait` 1500/4000 ms as in the reference (`pruefen`); `xInCarouselRun` always written as TODO; `xInRelease := "DB_TRO_TEST".<id>` as in the reference |
| `Vario` (TRO115) | times 800/300 ms, `xPluggedJam FALSE`, `tSenFree`/`tSenWait` 2000/5000 ms, `tRasterHigh/Low` 5 s (`pruefen`); `stInSettings.xIsBorner := TRUE` marked TODO (meaning unclear); `JamEntr` = first predecessor, `JamFinger` = first successor; `xInErrorQuit`/`xInAutomatic` open |
| `Vario` (TRO115) | times 800/300 ms, `xPluggedJam FALSE`, `tSenFree`/`tSenWait` 2000/5000 ms, `tRasterHigh/Low` 5 s (`pruefen`); `stInSettings.xIsBorner := TRUE` marked TODO (meaning unclear); `JamEntr` = the TRO's own entry jam area (TRO-JSON v4: several predecessors merge into it), `JamFinger` = the entry jam area of the first successor; `xInErrorQuit`/`xInAutomatic` open |
| `Vario_workStation` (TRO421) | `xPluggedScanner FALSE` although the catalog lists a scanner (it sits at the work station, not the separator — `pruefen`); work-station sensor, release and `xBeladenError` sensor open |
| `1Sep2Swi` (TRO267) | separator without times, `xPluggedPart TRUE` + `stSenPart` as in the reference; **switch 2 has no `nSwitchNo` in the reference** — written as a commented-out proposal; `nTo1Destinations` per switch open (split of the exits); `bSepType.%X2 := TRUE` marked `pruefen`; one `xInCarouselRun<n>` per switch; **no `FC_Direction`** at this place in the reference — only the destination list as a comment; no `nOutStateLast` (not in the reference) |
| `1Sep_SSCC` (TRO105) | second (SSCC) camera with its own interface structure, end-measurement sensor and HMI key open; `xInRelease` written as `"DB_TRO_TEST".<id>` with a TODO (the reference releases via the WCS connection); `FC_CARR_GLOB_CORRECTION` with `nInStation` open; the WCS telegram region of the reference is only noted as TODO |
| `PinStore_Auto` (TRO208) | **number of lines from the layout** (one per storage lane, `nNoOfLines`), not the 19 of the reference; all plant constants (encoder, destinations, stations, thresholds, storage DB, removal handshake) open; pin separator times 0/50 ms (`pruefen`); simulation settings `stSimu` of the reference not written; the FB has one `JamEntr`/`JamExit` — a store with more feeds/exits (mubea: 2/2) gets a TODO in the REGION header |
| `PinStore_Auto` (TRO208) | **number of lines from the layout** (one per storage lane, `nNoOfLines`), not the 19 of the reference; all plant constants (encoder, destinations, stations, thresholds, storage DB, removal handshake) open; pin separator times 0/50 ms (`pruefen`); simulation settings `stSimu` of the reference not written; the FB has one `JamEntr`/`JamExit` — since v4 several feeds merge into the one entry jam area (mubea: 2 feeds → 1 entry); a store with more than one entry or exit jam area (mubea: 2 exits) gets a TODO in the REGION header |
New context fields for these templates (`lib/scl_context.py`): Cognex wiring per scanner
(`interface`, `tag`, `hw_results`), `part_sensor` per separator, `carriers`/`data` per jam
@@ -290,7 +290,8 @@ path does not use them — its golden files are unchanged.
Effect on the open-value count with `--templates`: with the pilot only, mubea 282 → 302
and HundM05 323 → 377; with all seven templates mubea → 489 and HundM05 → 475
`TODO(E-Planung)` (HundM05 now comes completely from templates, mubea except the
`2Sep2Swi`). More reference lines are written and every open value is marked; the count
`2Sep2Swi`). Since TRO-JSON v4 (one entry jam area per TRO instead of one per
predecessor) it is: mubea 277 generic → 482 with templates, HundM05 316 → 461. More reference lines are written and every open value is marked; the count
drops again with the naming rules (step 4).
---
@@ -420,7 +421,7 @@ With `--templates` the console summary gets a `Templates` line (implemented); th
```
Templates = 4 von 5 Typen (generisch: FB_ILS_MTRO_2Sep2Swi)
Offene Stellen = 489 (Marker 'TODO(E-Planung)')
Offene Stellen = 482 (Marker 'TODO(E-Planung)')
```
---
@@ -478,7 +479,7 @@ bin\scl_skeleton.bat --json <name>_tro.json [--templates [DIR]] [--strict] [--na
| File | Where | Who writes it |
|---|---|---|
| `<name>.csv` | `%SKEL_DATA%` | dxfmakros `EXPORTCSV` |
| `<name>_tro.json` | `%SKEL_RESULTS%` | `tro_flow --json` (schema `sps_skel-tro-v3`) |
| `<name>_tro.json` | `%SKEL_RESULTS%` | `tro_flow --json` (schema `sps_skel-tro-v4`) |
| `scl_naming.ini`, `<name>_scl_naming.ini` | `%SKEL_CFG%` | you, once per project (optional) |
| `lib/scl_templates/*.scl.j2` | repo | maintained with the PLC engineers |
+715
View File
@@ -0,0 +1,715 @@
# -*- coding: utf-8 -*-
# Absichtlich ohne Shebang: der Windows-py-Launcher wuerde daraus "python3"
# ableiten und scheitert, wenn keine PythonCore-Installation registriert ist.
"""
e_planning.py - Bruecke Layout <-> E-Planung ueber die Symbolpositionen.
Das Layout (CSV) sagt, welche Separatoren, Weichen, Aus-/Einschleuselemente
es gibt und wo; die E-Planung, wie ihre Sensoren und Ventile heissen
(BG0642, MB0643 ...). Die EA-Liste allein kennt keine Topologie. Die
E-Planung wird aber als Layer ueber der Hardwareplanung gezeichnet: jedes
Sensor-/Aktorsymbol liegt neben dem Bauteil, zu dem es gehoert. Das
Kabellaengen-Tool (kabellaengen/lib/getpositions.py) schreibt diese Symbole
mit Position und Attributen nach <zeichnung>_positionsdraw.json.
Hier wird daraus die Zuordnung Layout-Bauteil -> BMK:
1. E-Bauteile einlesen (`sensors` und `schaltschrank_elemente`).
2. Lagebezug E-Zeichnung -> CSV mit lib/dxf_registration.py; Anker sind die
Separator-Sensoren der E-Planung gegen die Separatoren des CSV. Der
gepruefte Bezug wird in SKEL_CFG/dxf_registration.json abgelegt.
3. Regeln aus SKEL_CFG/e_planning.ini anwenden, je Rolle (z. B.
"Separator.sensor"): welche Layout-Art, welche E-Verwendung (VERW),
welcher Hoechstabstand - oder "Nummer = Nummer des Partners + Versatz"
(Ventil MB0643 zum Sensor BG0642). Die Zuordnung ist eindeutig: jedes
E-Bauteil und jede Rolle eines Layout-Bauteils wird hoechstens einmal
vergeben, die kuerzesten Abstaende zuerst.
4. Ergebnis als <csv>_eplan.json (EplanDocument) und Bericht
<csv>_eplan.md nach SKEL_RESULTS.
scl_skeleton.py --eplan liest die JSON und setzt die gefundenen Namen in die
SCL ein (SclName mit todo=False). Was keine Regel trifft, bleibt dort
TODO(E-Planung) - geraten wird nicht.
Schichten: e_planning importiert material_flow (CSV) und dxf_registration
(Lagebezug); scl_context importiert nur das Modell EplanDocument.
Aufruf ueber bin/e_planning.bat bzw. bin/e_planning.sh.
"""
from __future__ import annotations
import argparse
import configparser
import fnmatch
import json
import math
import re
import sys
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from pydantic import BaseModel, ConfigDict, Field
from dxf_registration import (
RegistrationError,
Transform,
derive_transform,
load_transform,
save_transform,
verify_transform,
)
__all__ = [
"EPLAN_SCHEMA",
"EDevice",
"LayoutPoint",
"Rule",
"Assignment",
"Drive",
"EplanDocument",
"load_positionsdraw",
"load_rules",
"assign",
"pair_drives",
]
EPLAN_SCHEMA = "sps_skel-eplan-v1"
RULES_FILE = "e_planning.ini"
_CONFIG = ConfigDict(extra="forbid")
# "=A1+UH01-KF107" -> Ort/Steuerung "UH01", Geraet "KF107"
_KENNZEICHNUNG = re.compile(r"\+([A-Za-z0-9]+)-([A-Za-z0-9]+)")
# "MB0643" -> ("MB", "0643")
_BMK = re.compile(r"^([A-Za-z]+)(\d+)$")
# ---------------------------------------------------------------------------
# Modell
# ---------------------------------------------------------------------------
class EDevice(BaseModel):
"""Ein Sensor/Aktor/Schrankelement der E-Planung."""
model_config = _CONFIG
bmk: str
verw: str = ""
bezeichnung: str = ""
kennzeichnung: str = ""
# Position in der E-Zeichnung (mm); None bei Schrankelementen ohne Lage
pos: tuple[float, float] | None = None
# True: aus "schaltschrank_elemente" (im Schrank, nicht im Feld)
cabinet: bool = False
@property
def controller(self) -> str:
"""Steuerung/Ort aus der Kennzeichnung ("UH01"), "" wenn unbekannt."""
m = _KENNZEICHNUNG.search(self.kennzeichnung)
return m.group(1) if m else ""
@property
def station(self) -> str:
"""E/A-Station aus der Kennzeichnung ("KF107"), "" wenn unbekannt."""
m = _KENNZEICHNUNG.search(self.kennzeichnung)
return m.group(2) if m else ""
@property
def prefix(self) -> str:
m = _BMK.match(self.bmk)
return m.group(1) if m else ""
@property
def number(self) -> int | None:
m = _BMK.match(self.bmk)
return int(m.group(2)) if m else None
@dataclass
class LayoutPoint:
"""Ein punktartiges Bauteil des Layouts in CSV-Koordinaten."""
layout_id: str
kind: str
x: float
y: float
name: str = ""
@dataclass
class Rule:
"""Eine Zuordnungsregel aus e_planning.ini (Abschnitt "<Art>.<Rolle>")."""
role: str # "Separator.sensor"
layout: list[str] # Muster fuer die Layout-Art
verw: list[str] # Muster fuer VERW
max_mm: float | None = None # raeumlich: Hoechstabstand
number_from: str = "" # Nummernregel: Rolle des Partners ...
number_offset: int = 0 # ... und Versatz der Nummer
prefix: str = "" # BMK-Praefix, leer = egal
# Sollabstand: das Symbol liegt immer gleich weit neben dem Bauteil.
# Dann zaehlt die Abweichung vom Soll, nicht der kleinste Abstand.
target_mm: float | None = None
tolerance_mm: float = 150.0
def score(self, dist: float) -> float | None:
"""Rangfolge eines Kandidaten (kleiner = besser), None = nicht zulaessig."""
if self.max_mm is not None and dist > self.max_mm:
return None
if self.target_mm is None:
return dist
dev = abs(dist - self.target_mm)
return dev if dev <= self.tolerance_mm else None
class Assignment(BaseModel):
"""Ein E-Bauteil in einer Rolle an einem Layout-Bauteil."""
model_config = _CONFIG
layout_id: str
layout_kind: str
role: str
bmk: str
verw: str = ""
bezeichnung: str = ""
controller: str = ""
station: str = ""
# Abstand nach dem Lagebezug in mm (auch bei der Nummernregel zur Kontrolle)
distance: float
# "raeumlich" | "nummer"
rule: str
# Abstand des naechstbesten Kandidaten derselben Rolle (None: keiner)
runner_up: float | None = None
class Drive(BaseModel):
"""Motor im Feld und sein Motorschutz im Schrank (gleiche Nummer)."""
model_config = _CONFIG
motor: str
protection: str = ""
bezeichnung: str = ""
protection_bezeichnung: str = ""
class EplanDocument(BaseModel):
"""Die Zuordnungs-JSON <csv>_eplan.json."""
model_config = ConfigDict(extra="forbid", populate_by_name=True)
schema_id: str = Field(default=EPLAN_SCHEMA, alias="schema")
created: str = ""
csv_file: str = ""
eplan_file: str = ""
transform: Transform | None = None
assignments: list[Assignment] = Field(default_factory=list)
drives: list[Drive] = Field(default_factory=list)
# E-Bauteile im Feld ohne Zuordnung (BMK)
unassigned: list[str] = Field(default_factory=list)
warnings: list[str] = Field(default_factory=list)
def for_layout(self, layout_id: str) -> dict[str, Assignment]:
"""Rollen eines Layout-Bauteils: {"sensor": Assignment, ...}."""
return {
a.role.split(".", 1)[-1]: a
for a in self.assignments if a.layout_id == layout_id
}
def to_json(self) -> str:
return self.model_dump_json(by_alias=True, indent=2) + "\n"
@classmethod
def from_json(cls, text: str) -> "EplanDocument":
return cls.model_validate_json(text)
# ---------------------------------------------------------------------------
# Einlesen
# ---------------------------------------------------------------------------
def _device(key: str, raw: dict, cabinet: bool) -> EDevice:
# Schluessel "BG0642@UNKNOWN" / "QA7000@A/B": vor dem @ steht das BMK.
bmk = str(raw.get("IO") or raw.get("B") or key.split("@", 1)[0]).strip()
pos = raw.get("pos")
return EDevice(
bmk=bmk,
verw=str(raw.get("VERW") or "").strip(),
bezeichnung=str(raw.get("BEZEICHNUNG") or "").strip(),
kennzeichnung=str(raw.get("KENNZEICHNUNG") or "").strip(),
pos=(float(pos[0]), float(pos[1])) if pos and len(pos) >= 2 else None,
cabinet=cabinet,
)
def load_positionsdraw(path: Path, warnings: list[str] | None = None) -> list[EDevice]:
"""E-Bauteile aus einer *_positionsdraw.json lesen.
Ein BMK kommt hoechstens einmal vor. Steht es doppelt da (im Schrank einmal
mit und einmal ohne Attribute), gewinnt der Eintrag mit VERW.
"""
warnings = warnings if warnings is not None else []
data = json.loads(path.read_text(encoding="utf-8"))
devices: dict[str, EDevice] = {}
for section, cabinet in (("sensors", False), ("schaltschrank_elemente", True)):
for key, raw in (data.get(section) or {}).items():
if not isinstance(raw, dict):
continue
dev = _device(key, raw, cabinet)
if not dev.bmk:
continue
old = devices.get(dev.bmk)
if old is None or (not old.verw and dev.verw):
devices[dev.bmk] = dev
for bmk, positions in (data.get("double_ids") or {}).items():
warnings.append(f"E-Planung: {bmk} steht {len(positions)}x in der Zeichnung "
f"- verwendet wird ein Eintrag")
return list(devices.values())
def _patterns(text: str) -> list[str]:
return [p.strip() for p in text.split(",") if p.strip()]
def load_rules(path: Path) -> tuple[list[Rule], list[str]]:
"""Regeln und Anker-Verwendungen aus e_planning.ini lesen.
Abschnitt [Registrierung] -> anker (VERW-Muster fuer den Lagebezug);
jeder Abschnitt "<Art>.<Rolle>" ist eine Regel, in Dateireihenfolge.
"""
parser = configparser.ConfigParser(inline_comment_prefixes=(";", "#"),
interpolation=None)
parser.optionxform = str # Schluessel nicht kleinschreiben
with path.open(encoding="utf-8") as fh:
parser.read_file(fh)
anchors = _patterns(parser.get("Registrierung", "anker", fallback="IN SEP"))
rules = []
for section in parser.sections():
if "." not in section:
continue
s = parser[section]
max_mm = s.get("max_mm")
soll = s.get("soll_mm")
rules.append(Rule(
role=section,
layout=_patterns(s.get("layout", section.split(".", 1)[0])),
verw=_patterns(s.get("verw", "*")),
max_mm=float(max_mm) if max_mm else None,
number_from=s.get("nummer_von", "").strip(),
number_offset=int(s.get("nummer_versatz", "0")),
prefix=s.get("praefix", "").strip(),
target_mm=float(soll) if soll else None,
tolerance_mm=float(s.get("toleranz_mm", "150")),
))
known = {r.role for r in rules}
for r in rules:
if r.number_from and r.number_from not in known:
raise ValueError(f"{path.name} [{r.role}]: nummer_von = {r.number_from} "
f"ist keine Regel")
return rules, anchors
def _match(text: str, patterns: list[str]) -> bool:
return any(fnmatch.fnmatchcase(text.upper(), p.upper()) for p in patterns)
# ---------------------------------------------------------------------------
# Zuordnung
# ---------------------------------------------------------------------------
@dataclass
class _State:
positions: dict[str, tuple[float, float]]
used: set[str] = field(default_factory=set)
result: list[Assignment] = field(default_factory=list)
# (layout_id, role) -> Assignment
by_slot: dict[tuple[str, str], Assignment] = field(default_factory=dict)
def _make(point: LayoutPoint, rule: Rule, dev: EDevice, dist: float, how: str,
runner_up: float | None) -> Assignment:
return Assignment(
layout_id=point.layout_id, layout_kind=point.kind, role=rule.role,
bmk=dev.bmk, verw=dev.verw, bezeichnung=dev.bezeichnung,
controller=dev.controller, station=dev.station,
distance=round(dist, 1), rule=how,
runner_up=None if runner_up is None else round(runner_up, 1),
)
def _candidates(rule: Rule, devices: list[EDevice], state: _State) -> list[EDevice]:
return [
d for d in devices
if d.bmk not in state.used and d.bmk in state.positions
and _match(d.verw, rule.verw)
and (not rule.prefix or d.prefix.upper() == rule.prefix.upper())
]
def _assign_spatial(rule: Rule, points: list[LayoutPoint], devices: list[EDevice],
state: _State) -> None:
"""Beste Bewertung zuerst (Rule.score); je Bauteil und E-Bauteil hoechstens einmal."""
cands = _candidates(rule, devices, state)
pairs = []
for p in points:
if (p.layout_id, rule.role) in state.by_slot:
continue
for d in cands:
dist = math.dist((p.x, p.y), state.positions[d.bmk])
score = rule.score(dist)
if score is not None:
pairs.append((score, dist, p.layout_id, d.bmk))
pairs.sort()
# je Bauteil die Kandidaten in Rangfolge - fuer "naechstbester Kandidat"
by_point: dict[str, list[tuple[str, float]]] = {}
for _, dist, pid, bmk in pairs:
by_point.setdefault(pid, []).append((bmk, dist))
point_of = {p.layout_id: p for p in points}
dev_of = {d.bmk: d for d in cands}
for _, dist, pid, bmk in pairs:
if (pid, rule.role) in state.by_slot or bmk in state.used:
continue
others = [x for b, x in by_point[pid] if b != bmk]
a = _make(point_of[pid], rule, dev_of[bmk], dist, "raeumlich",
others[0] if others else None)
state.used.add(bmk)
state.by_slot[(pid, rule.role)] = a
state.result.append(a)
def _assign_by_number(rule: Rule, points: list[LayoutPoint], devices: list[EDevice],
state: _State) -> None:
"""Nummer = Nummer des Partners (Rolle nummer_von) + Versatz."""
cands = {(d.number): d for d in _candidates(rule, devices, state)
if d.number is not None}
for p in points:
if (p.layout_id, rule.role) in state.by_slot:
continue
partner = state.by_slot.get((p.layout_id, rule.number_from))
if partner is None:
continue
m = _BMK.match(partner.bmk)
if not m:
continue
dev = cands.get(int(m.group(2)) + rule.number_offset)
if dev is None or dev.bmk in state.used:
continue
dist = math.dist((p.x, p.y), state.positions[dev.bmk])
# Auch die Nummernregel darf nicht quer ueber die Anlage greifen
if rule.max_mm is not None and dist > rule.max_mm:
continue
a = _make(p, rule, dev, dist, "nummer", None)
state.used.add(dev.bmk)
state.by_slot[(p.layout_id, rule.role)] = a
state.result.append(a)
def assign(points: list[LayoutPoint], devices: list[EDevice], rules: list[Rule],
transform: Transform) -> list[Assignment]:
"""Regeln der Reihe nach anwenden.
Eine Regel mit nummer_von versucht zuerst die Nummernregel und nimmt fuer
die verbleibenden Bauteile den Abstand (wenn max_mm/soll_mm gesetzt ist). So wird
das Ventil eines Separators ueber die Nummer gefunden, auch wenn das
Ventil des Nachbarn naeher liegt (Speicherlinien im 800-mm-Raster).
"""
state = _State(positions={
d.bmk: transform.to_csv(*d.pos) for d in devices
if d.pos is not None and not d.cabinet
})
for rule in rules:
targets = [p for p in points if _match(p.kind, rule.layout)]
if rule.number_from:
_assign_by_number(rule, targets, devices, state)
if rule.max_mm is not None or rule.target_mm is not None:
_assign_spatial(rule, targets, devices, state)
return sorted(state.result, key=lambda a: (a.layout_id, a.role))
def pair_drives(devices: list[EDevice]) -> list[Drive]:
"""Motor MAnnnn <-> Motorschutz FCnnnn (gleiche Nummer)."""
fc = {d.number: d for d in devices if d.prefix.upper() == "FC" and d.number is not None}
drives = []
for d in sorted(devices, key=lambda d: d.bmk):
if d.prefix.upper() != "MA" or d.number is None:
continue
schutz = fc.get(d.number)
drives.append(Drive(
motor=d.bmk,
protection=schutz.bmk if schutz else "",
bezeichnung=d.bezeichnung,
protection_bezeichnung=schutz.bezeichnung if schutz else "",
))
return drives
# ---------------------------------------------------------------------------
# Layout und Lagebezug
# ---------------------------------------------------------------------------
# Element.kind der punktartigen Layout-Bauteile
LAYOUT_KINDS = ("Separator", "Scanner", "Weiche", "Ausschleus", "Einschleus",
"Beladung", "Entladung")
def layout_points(elements) -> list[LayoutPoint]:
"""Punktartige Bauteile aus den CSV-Elementen (material_flow.Element)."""
points = []
for e in elements:
if e.position is None:
continue
kind = next((k for k in LAYOUT_KINDS
if k.lower() in f"{e.kind} {e.teile_art}".lower()), "")
if kind:
points.append(LayoutPoint(e.teile_id, kind, e.x, e.y, e.name))
return points
def _anchor_points(points: list[LayoutPoint], devices: list[EDevice],
anchors: list[str]):
csv_pts = {"Separator": [(p.x, p.y) for p in points if p.kind == "Separator"]}
e_pts = {"Separator": [d.pos for d in devices
if d.pos is not None and not d.cabinet
and _match(d.verw, anchors)]}
return csv_pts, e_pts
def resolve_transform(points, devices, anchors, cfg_dir: Path, csv_name: str,
eplan_name: str, re_register: bool,
warnings: list[str]) -> Transform:
"""Abgelegten Lagebezug nachrechnen oder neu ermitteln und ablegen."""
csv_pts, e_pts = _anchor_points(points, devices, anchors)
if not re_register:
stored = load_transform(cfg_dir, csv_name, eplan_name)
if stored:
verify_transform(stored, csv_pts, e_pts)
return stored
transform = derive_transform(csv_pts, e_pts, csv_name, eplan_name)
saved = save_transform(cfg_dir, transform)
warnings.append(f"Lagebezug neu ermittelt und abgelegt: {saved}")
return transform
# ---------------------------------------------------------------------------
# Bericht
# ---------------------------------------------------------------------------
def render_report(doc: EplanDocument, rules: list[Rule], points: list[LayoutPoint],
devices: list[EDevice]) -> str:
out = []
add = out.append
add(f"# E-Planung -> Layout: {doc.csv_file}")
add("")
add(f"- E-Planung: `{doc.eplan_file}`")
add(f"- Erzeugt: {doc.created}")
if doc.transform:
add(f"- Lagebezug: {doc.transform.describe()}")
add("")
add("## Zuordnung je Rolle")
add("")
add("| Rolle | Layout-Bauteile | zugeordnet | davon Nummernregel |")
add("|---|---|---|---|")
for r in rules:
n_pts = sum(1 for p in points if _match(p.kind, r.layout))
hits = [a for a in doc.assignments if a.role == r.role]
n_num = sum(1 for a in hits if a.rule == "nummer")
add(f"| {r.role} | {n_pts} | {len(hits)} | {n_num} |")
add("")
add("## Zuordnungen")
add("")
add("Abstand nach dem Lagebezug; \"2.\" = Abstand des naechstbesten Kandidaten "
"(knapp beieinander = pruefen).")
add("")
add("| Layout | Art | Rolle | BMK | VERW | Bezeichnung | Abstand mm | 2. | Regel | Station |")
add("|---|---|---|---|---|---|---|---|---|---|")
for a in doc.assignments:
second = "" if a.runner_up is None else f"{a.runner_up:.0f}"
add(f"| {a.layout_id} | {a.layout_kind} | {a.role.split('.', 1)[-1]} | {a.bmk} "
f"| {a.verw} | {a.bezeichnung} | {a.distance:.0f} | {second} | {a.rule} "
f"| {a.controller}-{a.station} |")
add("")
roles_of: dict[str, set[str]] = {}
for a in doc.assignments:
roles_of.setdefault(a.layout_id, set()).add(a.role)
add("## Layout-Bauteile ohne Zuordnung")
add("")
any_missing = False
for r in rules:
missing = [p for p in points
if _match(p.kind, r.layout) and r.role not in roles_of.get(p.layout_id, set())]
if missing:
any_missing = True
add(f"- **{r.role}**: {', '.join(p.layout_id for p in missing)}")
if not any_missing:
add("- keine")
add("")
by_bmk = {d.bmk: d for d in devices}
add("## E-Bauteile im Feld ohne Zuordnung")
add("")
if doc.unassigned:
add("| BMK | VERW | Bezeichnung | Station |")
add("|---|---|---|---|")
for bmk in doc.unassigned:
d = by_bmk[bmk]
add(f"| {bmk} | {d.verw} | {d.bezeichnung} | {d.controller}-{d.station} |")
else:
add("- keine")
add("")
add("## Antriebe (Motor <-> Motorschutz)")
add("")
if doc.drives:
add("| Motor | Bezeichnung | Motorschutz | Bezeichnung |")
add("|---|---|---|---|")
for dr in doc.drives:
add(f"| {dr.motor} | {dr.bezeichnung} | {dr.protection or '-'} "
f"| {dr.protection_bezeichnung} |")
else:
add("- keine Motoren in der E-Planung")
add("")
if doc.warnings:
add("## Hinweise")
add("")
for w in doc.warnings:
add(f"- {w}")
add("")
return "\n".join(out)
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def build_document(csv_path: Path, eplan_path: Path, rules_path: Path, cfg_dir: Path,
re_register: bool = False
) -> tuple[EplanDocument, list[Rule], list[LayoutPoint], list[EDevice]]:
"""Kompletter Lauf ohne Ausgabe: CSV + positionsdraw -> EplanDocument."""
from material_flow import read_elements # erst hier: Tests ohne CSV brauchen es nicht
warnings: list[str] = []
elements = read_elements(csv_path, warnings)
points = layout_points(elements)
devices = load_positionsdraw(eplan_path, warnings)
rules, anchors = load_rules(rules_path)
transform = resolve_transform(points, devices, anchors, cfg_dir, csv_path.name,
eplan_path.name, re_register, warnings)
assignments = assign(points, devices, rules, transform)
used = {a.bmk for a in assignments}
unassigned = sorted(d.bmk for d in devices
if not d.cabinet and d.pos is not None and d.bmk not in used)
doc = EplanDocument(
created=datetime.now().strftime("%Y-%m-%d %H:%M"),
csv_file=csv_path.name,
eplan_file=eplan_path.name,
transform=transform,
assignments=assignments,
drives=pair_drives(devices),
unassigned=unassigned,
warnings=warnings,
)
return doc, rules, points, devices
def main(argv: list[str] | None = None) -> int:
from material_flow import env_dir, resolve_input
args = parse_args(argv)
csv_path = resolve_input(args.file)
eplan_path = resolve_input(args.eplan)
cfg_dir = env_dir("SKEL_CFG", "cfg")
rules_path = Path(args.rules) if args.rules else cfg_dir / RULES_FILE
for label, path in (("CSV", csv_path), ("E-Planung", eplan_path),
("Regeln", rules_path)):
if not path.is_file():
print(f"FEHLER: {label} nicht gefunden: {path}", file=sys.stderr)
return 1
try:
doc, rules, points, devices = build_document(
csv_path, eplan_path, rules_path, cfg_dir, args.re_register)
except RegistrationError as exc:
print(f"FEHLER: Lagebezug E-Planung -> CSV nicht nachweisbar: {exc}",
file=sys.stderr)
print(" Passen CSV und E-Zeichnung zusammen? Anker-VERW in "
f"{rules_path.name} [Registrierung] pruefen.", file=sys.stderr)
return 1
except (OSError, ValueError) as exc:
print(f"FEHLER: {exc}", file=sys.stderr)
return 1
results = env_dir("SKEL_RESULTS", "results")
results.mkdir(parents=True, exist_ok=True)
json_file = results / f"{csv_path.stem}_eplan.json"
md_file = results / f"{csv_path.stem}_eplan.md"
json_file.write_text(doc.to_json(), encoding="utf-8")
md_file.write_text(render_report(doc, rules, points, devices), encoding="utf-8")
print("")
print("================================================================")
print("E-PLANUNG -> LAYOUT")
print("================================================================")
print(f"CSV = {csv_path}")
print(f"E-Planung = {eplan_path}")
print(f"Lagebezug = {doc.transform.describe() if doc.transform else '-'}")
for r in rules:
n_pts = sum(1 for p in points if _match(p.kind, r.layout))
hits = sum(1 for a in doc.assignments if a.role == r.role)
print(f" {r.role:<26} {hits:4} von {n_pts}")
print(f"Ohne Zuordnung = {len(doc.unassigned)} E-Bauteile im Feld")
print(f"Antriebe = {len(doc.drives)} "
f"({sum(1 for d in doc.drives if d.protection)} mit Motorschutz)")
for w in doc.warnings:
print(f" ! {w}")
print(f"JSON = {json_file}")
print(f"Bericht = {md_file}")
print("================================================================")
print("")
return 0
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="e_planning",
description="Ordnet die Sensoren/Ventile der E-Planung "
"(*_positionsdraw.json des Kabellaengen-Tools) den Bauteilen "
"des Layouts (CSV) zu. Ergebnis: <csv>_eplan.json fuer "
"scl_skeleton --eplan und ein Bericht <csv>_eplan.md.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="Beispiele:\n"
" e_planning.bat --file mubea.csv --eplan Mubea_positionsdraw.json\n"
" e_planning.bat --file mubea.csv --eplan C:\\pfad\\Mubea_positionsdraw.json "
"--re-register\n"
"\n"
"Exit-Codes:\n"
" 0 ok (auch wenn nicht alles zugeordnet wurde - siehe Bericht)\n"
" 1 Eingabe-, Aufruf- oder Lagebezugsfehler\n",
)
parser.add_argument("--file", default="export.csv", metavar="NAME",
help="CSV-Export in %%SKEL_DATA%% (oder ein Pfad). "
"Standard: %(default)s")
parser.add_argument("--eplan", required=True, metavar="NAME",
help="*_positionsdraw.json der E-Planung in %%SKEL_DATA%% "
"(oder ein Pfad)")
parser.add_argument("--rules", metavar="DATEI",
help=f"Regeldatei. Standard: %%SKEL_CFG%%\\{RULES_FILE}")
parser.add_argument("--re-register", action="store_true",
help="Lagebezug neu ermitteln, auch wenn in %%SKEL_CFG%% "
"einer abgelegt ist")
return parser.parse_args(argv)
if __name__ == "__main__":
sys.exit(main())
+145 -5
View File
@@ -21,6 +21,11 @@ Klassen:
Connection eine gerichtete Verbindung zwischen zwei TROs/Stationen
TroCandidate eine im Review gemeldete Stelle, an der ein TRO erwartet
wird, die aber (noch) keiner ist
Slot eine Stelle eines TRO, an die Hardware gehoert (Separator,
Weiche, Scanner, Antrieb) mit den Rollen, die die
Hardwareplanung dort fuellen muss
JamArea ein Staubereich: Eingang genau eines TRO (je Separator
laut Katalog), mit allen Vorgaengern, die dort einfahren
Analysis Arbeitsergebnis von tro_flow.analyse_tros
TroDocument die TRO-JSON (<csv>_tro.json): von tro_flow --json
geschrieben, von scl_skeleton gelesen und geprueft
@@ -47,22 +52,28 @@ from pydantic import (
model_validator,
)
from tro_catalog import TroDefinition, get_tro
from tro_catalog import TroDefinition, TroItem, get_tro
__all__ = [
"SCHEMA",
"TroBase",
"Slot",
"JamArea",
"DerivedTro",
"Connection",
"TroCandidate",
"Analysis",
"TroDocument",
"derive_slots",
"derive_jam_areas",
]
# Kennung der TRO-JSON. v1 war die Ausgabe von tro_extract (aus der Zeichnung),
# v2 die von tro_flow --json (aus dem CSV, ueber dieses Modell), v3 wie v2 mit
# `candidates` statt `openPoints`.
SCHEMA = "sps_skel-tro-v3"
# `candidates` statt `openPoints`, v4 wie v3 mit `slots` je TRO und `jamAreas`.
# Eine v3-Datei liest sich weiter (die neuen Felder fehlen dann und werden beim
# Rendern abgeleitet, siehe TroDocument.jam_areas_or_derived).
SCHEMA = "sps_skel-tro-v4"
# Gemeinsame Einstellungen: Pruefung bei Erzeugung und Zuweisung, unbekannte
# Schluessel sind ein Fehler (ein umbenannter JSON-Schluessel faellt auf statt
@@ -191,6 +202,50 @@ class TroBase(_Positioned):
return data
class Slot(BaseModel):
"""
Eine Stelle eines TRO, an die Hardware gehoert (Konzept Umbau 1).
``slot`` ist der stabile Schluessel innerhalb des TRO (``separator1``,
``switch1``, ``scanner1``, ``drive``); zusammen mit der TRO-ID adressiert er
die Zuordnung der Hardwareplanung. ``layout_id`` ist die TeileId des
Bauteils im aktuellen Export - nur zur Nachverfolgung, nicht stabil ("",
wenn das Bauteil im Layout nicht bekannt ist). Rollen mit ``?`` sind
optional: fehlt das Geraet, wird der zugehoerige xPlugged*-Schalter FALSE.
"""
model_config = _CONFIG
slot: str
layout_id: str = Field(default="", alias="layoutId")
roles: list[str] = Field(default_factory=list)
class JamArea(_Positioned):
"""
Ein Staubereich: die Warteschlange vor einem Separator, Eingang genau eines
TRO (``tro``, Eingang Nummer ``entry``).
Kennung ``J<Separator>`` (Mubea: Sep1024 -> J1024). Mehrere Vorgaenger
koennen in denselben Staubereich einfahren (Zusammenfuehrung); deren
Ausgang zeigt dann auf diesen Staubereich. Der SCL-Name
``"DB_JamArea<NNNN>".stJam<NNNN>_<k>`` (Standard UH01-UH05) braucht die
Foerderernummer aus der Hardwareplanung und steht deshalb nicht hier.
"""
model_config = _CONFIG
jam_id: str = Field(alias="id")
tro: str
entry: int = 1
separator: str = ""
# tragender Foerderer (erster Host des TRO im Layout)
host: str = ""
predecessors: list[str] = Field(default_factory=list)
note: str = ""
position: tuple[float, float, float] | None = None
class DerivedTro(TroBase):
"""
Ein von tro_flow aus dem CSV abgeleiteter TRO (oder eine Systemgrenze).
@@ -207,6 +262,8 @@ class DerivedTro(TroBase):
# Mittelpunkt der Bauteile, aus denen dieser TRO gebildet wurde
position_from: str = Field(default="", alias="positionFrom")
position_points: int = Field(default=0, alias="positionPoints")
# Stellen fuer die Hardwareplanung (derive_slots); leer bei Systemgrenzen
slots: list[Slot] = Field(default_factory=list)
@property
def connected(self) -> bool:
@@ -288,16 +345,23 @@ class TroDocument(BaseModel):
boundaries: list[DerivedTro] = Field(default_factory=list)
connections: list[Connection] = Field(default_factory=list)
candidates: list[TroCandidate] = Field(default_factory=list)
jam_areas: list[JamArea] = Field(default_factory=list, alias="jamAreas")
findings: list[str] = Field(default_factory=list)
@classmethod
def from_analysis(cls, analysis: Analysis, source: str, note: str = "") -> TroDocument:
"""Dokument aus dem Ergebnis von tro_flow.analyse_tros bilden."""
"""Dokument aus dem Ergebnis von tro_flow.analyse_tros bilden.
Slots und Staubereiche werden hier abgeleitet, damit die JSON sie
traegt und nicht erst das Rendern sie kennt.
"""
tros = [t.model_copy(update={"slots": derive_slots(t)}) for t in analysis.tros]
return cls(
source=source,
note=note,
tros=analysis.tros,
tros=tros,
boundaries=analysis.boundaries,
jam_areas=derive_jam_areas(tros),
connections=[
Connection(src=src, dst=dst, kind=analysis.edge_kinds.get((src, dst), ""))
for src, dst in analysis.edges
@@ -310,6 +374,10 @@ class TroDocument(BaseModel):
"""JSON-Text einlesen und pruefen (pydantic.ValidationError bei Fehlern)."""
return cls.model_validate_json(text)
def jam_areas_or_derived(self) -> list[JamArea]:
"""jamAreas aus der Datei; bei einer v3-Datei (ohne jamAreas) abgeleitet."""
return self.jam_areas or derive_jam_areas(self.tros)
def to_json(self) -> str:
"""Als JSON-Text mit den Schluesseln der TRO-JSON (id, type, fbType ...)."""
return self.model_dump_json(by_alias=True, indent=2) + "\n"
@@ -319,3 +387,75 @@ class TroDocument(BaseModel):
if tro.tro_id == tro_id:
return tro
return None
# ---------------------------------------------------------------------------
# Ableitungen aus Katalogtyp und Topologie
# ---------------------------------------------------------------------------
# Rollen je Slot-Art (doc/Konzept_Layout_Hardware_SCL.md, Umbau 1); "?" = optional
_SEPARATOR_ROLES = ("sensor", "stopper", "jamSensor?", "partSensor?")
_SWITCH_ROLES = ("valve", "exitSensor?")
_SCANNER_ROLES = ("reader",)
_DRIVE_ROLES = ("motor", "motorProtection", "senCarrInPos", "senFinger",
"senLastPos", "senJam")
def _at(values: list[str], index: int) -> str:
return values[index] if index < len(values) else ""
def derive_slots(tro: DerivedTro) -> list[Slot]:
"""Slots eines TRO: Anzahl aus dem Katalog, Layout-ID aus dem TRO.
Weichen tragen noch keine Layout-ID - tro_flow speichert das Weichen-
bzw. Ausschleuselement heute nicht am TRO.
"""
items = tro.items
slots: list[Slot] = []
for i in range(items.get(TroItem.SEPARATOR, 0)):
slots.append(Slot(slot=f"separator{i + 1}", layout_id=_at(tro.separators, i),
roles=list(_SEPARATOR_ROLES)))
for i in range(items.get(TroItem.SWITCH, 0)):
slots.append(Slot(slot=f"switch{i + 1}", roles=list(_SWITCH_ROLES)))
for i in range(items.get(TroItem.SCANNER, 0)):
slots.append(Slot(slot=f"scanner{i + 1}", layout_id=_at(tro.scanners, i),
roles=list(_SCANNER_ROLES)))
if items.get(TroItem.VARIO_DRIVE):
slots.append(Slot(slot="drive", layout_id=_at(tro.hosts, 0),
roles=list(_DRIVE_ROLES)))
return slots
def derive_jam_areas(tros: list[DerivedTro]) -> list[JamArea]:
"""Staubereiche: je Separator laut Katalog ein Eingang des TRO.
Kennung J<Separator>; fehlt der Separator im Layout, J<TRO-ID>_<n>. Alle
Vorgaenger fahren in den ersten Eingang ein - bei zwei Eingaengen
(2Sep-Typen) ist die Aufteilung aus dem Layout nicht ableitbar und als
Hinweis vermerkt.
"""
areas: list[JamArea] = []
for tro in tros:
n_entries = max(1, tro.items.get(TroItem.SEPARATOR, 1))
if n_entries > 1 and tro.predecessors:
note = "Aufteilung der Vorgaenger auf die Eingaenge offen"
elif tro.items.get(TroItem.STORAGE_LINE):
# Die Separatoren eines Speichers sitzen auf den Lagerlinien; der
# Eingang liegt davor und hat im Layout keinen eigenen Separator.
note = "Eingang des Speichers - Kennung vorlaeufig nach erstem Linien-Separator"
else:
note = ""
for n in range(1, n_entries + 1):
sep = _at(tro.separators, n - 1)
areas.append(JamArea(
jam_id=f"J{sep}" if sep else f"J{tro.tro_id}_{n}",
tro=tro.tro_id,
entry=n,
separator=sep,
host=_at(tro.hosts, 0),
predecessors=list(tro.predecessors) if n == 1 else [],
note=note,
position=tro.position if n == 1 else None,
))
return areas
+105 -27
View File
@@ -16,8 +16,11 @@ Schichten:
Namen (Separator-, Weichen-, MainTro-Nummer, Adressen ...) sind ein
:class:`SclName`: Wert plus Merker, ob er noch aus der E-Planung kommen muss.
Heute sind alle Platzhalter mit ``todo=True``; die Namensregeln aus Schritt 4
(cfg/scl_naming.ini) ersetzen dann nur den Wert und loeschen den Merker.
Ohne E-Planung sind alle Platzhalter ``todo=True``. Mit einer Zuordnung aus
lib/e_planning.py (``eplan``) werden Separator-Sensor und Stopperventil der
zugeordneten Separatoren eingesetzt und ``todo`` geloescht; alles andere
bleibt offen. Die Namensregeln aus Schritt 4 (cfg/scl_naming.ini) arbeiten
spaeter genauso: Wert ersetzen, Merker loeschen.
"""
from __future__ import annotations
@@ -25,12 +28,16 @@ from __future__ import annotations
import re
from collections import deque
from datetime import datetime
from typing import TYPE_CHECKING
from pydantic import BaseModel, ConfigDict, Field
from model import TroBase, TroDocument
from model import JamArea, TroBase, TroDocument
from tro_catalog import TroItem
if TYPE_CHECKING:
from e_planning import EplanDocument
__all__ = [
"TODO",
"SclName",
@@ -69,6 +76,16 @@ def _offen(platzhalter: str) -> SclName:
return SclName(value=platzhalter, todo=True)
def _bekannt(wert: str) -> SclName:
return SclName(value=wert, todo=False)
# Rolle in e_planning.ini -> wie der Name in der SCL geschrieben wird
# (Sensor als Konstante der DB_Inputs-Sensorliste, Ventil als Variable)
_SENSOR = "cIn{bmk}"
_VENTIL = "{bmk}"
class SclSeparator(BaseModel):
"""Ein stInSeparator<n>-Block."""
@@ -130,12 +147,19 @@ class SclScanner(BaseModel):
class SclJam(BaseModel):
"""Ein Staubereich am Ein- oder Ausgang (JamEntr<n> / JamExit<n>)."""
"""Ein Staubereich am Ein- oder Ausgang (JamEntr<n> / JamExit<n>).
Ein Eingang ist ein Staubereich des TRO selbst (TroDocument.jamAreas), ein
Ausgang der Eingangs-Staubereich des Nachfolgers.
"""
model_config = _CONFIG
n: int
# Nachbar als Text fuer den Kommentar, SYSn als Station markiert
# Kennung aus der TRO-JSON (J<Separator>); "" am Ausgang zu einer Station
jam_id: str = ""
# Eingang: die Vorgaenger, Ausgang: der Nachfolger - als Text fuer den
# Kommentar, SYSn als Station markiert
neighbour: str
db: SclName
# Stausensor des Ausgangs (stInSenJam<n>); None an Eingaengen
@@ -145,6 +169,11 @@ class SclJam(BaseModel):
carriers: SclName
data: SclName
@property
def title(self) -> str:
"""Fuer den Kommentar: 'Staubereich J0003' (ohne Kennung nur 'Staubereich')."""
return f"Staubereich {self.jam_id}" if self.jam_id else "Staubereich"
class SclSscc(BaseModel):
"""SSCC-Scanner eines 1Sep_SSCC: zweite Kamera, Endmessung, HMI-Anzeige."""
@@ -209,6 +238,11 @@ class SclStore(BaseModel):
# Eine Linie je Speicherbahn im Layout (nicht die 19 aus dem Katalog)
lines: list[SclStoreLine]
@property
def title(self) -> str:
"""Fuer den Kommentar: 'Staubereich J0003' (ohne Kennung nur 'Staubereich')."""
return f"Staubereich {self.jam_id}" if self.jam_id else "Staubereich"
class SclTro(BaseModel):
"""Alles, was eine REGION fuer einen TRO braucht."""
@@ -282,6 +316,8 @@ class SclFile(BaseModel):
created: str
start_reason: str
tros: list[SclTro] = Field(default_factory=list)
# Zuordnung zur E-Planung, aus der Namen eingesetzt wurden ("" = keine)
eplan_source: str = ""
# ---------------------------------------------------------------------------
@@ -388,9 +424,10 @@ def _scanner(n: int, suffix: str, interface: str = "cScanner____") -> SclScanner
)
def _jam(n: int, neighbour: str, jam_db: str, sensor: SclName | None = None) -> SclJam:
def _jam(n: int, neighbour: str, jam_db: str, sensor: SclName | None = None,
jam_id: str = "") -> SclJam:
return SclJam(
n=n, neighbour=neighbour, db=_offen(jam_db), sensor=sensor,
n=n, jam_id=jam_id, neighbour=neighbour, db=_offen(jam_db), sensor=sensor,
carriers=_offen(f"{jam_db}.arCarrier"), data=_offen(f"{jam_db}.stData"),
)
@@ -434,23 +471,55 @@ def _store(tro: TroBase) -> SclStore:
)
def _tro_context(tro: TroBase, alle: dict[str, TroBase]) -> SclTro:
def _separator(i: int, layout: str, eplan: EplanDocument | None) -> SclSeparator:
rollen = eplan.for_layout(layout) if eplan is not None and layout != "?" else {}
sensor = rollen.get("sensor")
stopper = rollen.get("stopper")
return SclSeparator(
n=i,
layout=layout,
no=_offen("cSep_____"),
sensor=_bekannt(_SENSOR.format(bmk=sensor.bmk)) if sensor else _offen("cInBG____"),
stopper=_bekannt(_VENTIL.format(bmk=stopper.bmk)) if stopper else _offen("MB____"),
# Teilesensor kennt die E-Planung-Bruecke noch nicht - offen
part_sensor=_offen("cInBG____"),
)
def _entries(tro: TroBase, eigene: list[JamArea],
alle: dict[str, TroBase], jam_db: str) -> list[SclJam]:
"""Ein JamEntr je eigenem Staubereich - nicht je Vorgaenger: mehrere
Vorgaenger fahren in denselben Staubereich ein (Referenzen UH01-UH05)."""
eingaenge = []
for area in eigene:
von = ", ".join(_nachbar(v, alle) for v in area.predecessors) or "-"
if area.note:
von = f"{von} ({area.note})"
eingaenge.append(_jam(area.entry, von, jam_db, jam_id=area.jam_id))
return eingaenge
def _exit_area(tro_id: str, nach: str,
bereiche: dict[str, list[JamArea]]) -> str:
"""Kennung des Staubereichs, in den tro_id beim Nachfolger einfaehrt."""
ziel = bereiche.get(nach, [])
passend = [a for a in ziel if tro_id in a.predecessors] or ziel
return passend[0].jam_id if passend else ""
def _tro_context(tro: TroBase, alle: dict[str, TroBase],
eplan: EplanDocument | None = None,
bereiche: dict[str, list[JamArea]] | None = None) -> SclTro:
items = tro.items
n_sep = items.get(TroItem.SEPARATOR, 1)
n_swi = items.get(TroItem.SWITCH, 0)
n_scan = items.get(TroItem.SCANNER, 0)
tro_id = tro.tro_id
jam_db = '"DB_JamArea____".stJam____'
bereiche = bereiche or {}
separators = [
SclSeparator(
n=i,
layout=tro.separators[i - 1] if i <= len(tro.separators) else "?",
no=_offen("cSep_____"),
sensor=_offen("cInBG____"),
stopper=_offen("MB____"),
part_sensor=_offen("cInBG____"),
)
_separator(i, tro.separators[i - 1] if i <= len(tro.separators) else "?", eplan)
for i in range(1, n_sep + 1)
]
switches = [
@@ -475,15 +544,14 @@ def _tro_context(tro: TroBase, alle: dict[str, TroBase]) -> SclTro:
SclSwitchExit(via=abgang, targets=reachable_via(tro_id, abgang, alle))
for abgang in tro.successors
] if n_swi else []
# Staubereiche: je ankommender/abgehender Verbindung einer. Welche
# DB-Struktur dahintersteht, ist eine Planungsentscheidung - hier steht
# nur, WIE VIELE es sein muessen und zu welchem Nachbarn sie gehoeren.
jam_entries = [
_jam(n, _nachbar(vor, alle), jam_db)
for n, vor in enumerate(tro.predecessors, start=1)
]
# Staubereiche aus der TRO-JSON (jamAreas): Eingaenge sind die eigenen
# (ein JamEntr je eigenem Staubereich, nicht je Vorgaenger), Ausgaenge die
# Eingaenge der Nachfolger. Der DB-Name braucht die Foerderernummer aus der
# Hardwareplanung und bleibt offen.
jam_entries = _entries(tro, bereiche.get(tro_id, []), alle, jam_db)
jam_exits = [
_jam(n, _nachbar(nach, alle), jam_db, sensor=_offen("cInBG____"))
_jam(n, _nachbar(nach, alle), jam_db, sensor=_offen("cInBG____"),
jam_id=_exit_area(tro_id, nach, bereiche))
for n, nach in enumerate(tro.successors, start=2)
]
@@ -541,13 +609,23 @@ def _tro_context(tro: TroBase, alle: dict[str, TroBase]) -> SclTro:
def build_context(document: TroDocument, source: str, start_id: str = "",
created: str | None = None) -> SclFile:
"""TroDocument -> SclFile (TROs in Fliessrichtung)."""
created: str | None = None,
eplan: EplanDocument | None = None) -> SclFile:
"""TroDocument -> SclFile (TROs in Fliessrichtung).
eplan: Zuordnung Layout -> E-Planung (e_planning.py); ohne sie bleiben
alle Namen offen und die Ausgabe ist die bisherige.
"""
alle = {t.tro_id: t for t in document.tros}
bereiche: dict[str, list[JamArea]] = {}
for area in document.jam_areas_or_derived():
bereiche.setdefault(area.tro, []).append(area)
geordnet, startgrund = flow_order(document.tros, start_id)
return SclFile(
source=source,
created=created or datetime.now().strftime("%Y-%m-%d %H:%M"),
start_reason=startgrund,
tros=[_tro_context(t, alle) for t in geordnet],
tros=[_tro_context(t, alle, eplan, bereiche) for t in geordnet],
eplan_source=(f"{eplan.eplan_file} (Zuordnung vom {eplan.created})"
if eplan is not None else ""),
)
+58 -6
View File
@@ -51,6 +51,7 @@ from jinja2 import (
)
from pydantic import ValidationError
from e_planning import EPLAN_SCHEMA, EplanDocument
from material_flow import env_dir
from model import SCHEMA, TroDocument
from scl_context import TODO, SclFile, SclName, SclTro, build_context
@@ -220,11 +221,11 @@ def emit_call(add, tro: SclTro) -> None:
for jam in tro.jam_entries:
zeilen.append((f"arInOutJamEntr{jam.n} := {jam.db.value}.arCarrier",
offen(jam.db, f"Staubereich von {jam.neighbour}")))
offen(jam.db, f"{jam.title} von {jam.neighbour}")))
zeilen.append((f"stInOutJamEntr{jam.n} := {jam.db.value}.stData", ""))
for jam in tro.jam_exits:
zeilen.append((f"arInOutJamExit{jam.n} := {jam.db.value}.arCarrier",
offen(jam.db, f"Staubereich nach {jam.neighbour}")))
offen(jam.db, f"{jam.title} vor {jam.neighbour}")))
zeilen.append((f"stInOutJamExit{jam.n} := {jam.db.value}.stData", ""))
for s in tro.separators:
@@ -329,6 +330,12 @@ def render_context(context: SclFile, templates: SclTemplates | None = None) -> s
add("//")
add("// Reihenfolge der REGIONs = Materialfluss.")
add(f"// Anfang bei {context.tros[0].id}: {context.start_reason}.")
if context.eplan_source:
add("//")
add(f"// E-Planung: {context.eplan_source}")
add("// Separator-Sensoren und Stopperventile aus der Zuordnung sind")
add("// eingesetzt (ohne Marker); was die Zuordnung nicht sicher fand,")
add(f"// bleibt '{TODO}'. Details: <csv>_eplan.md (lib/e_planning.py).")
if templates is not None:
mit, ohne = template_usage(context, templates)
add("//")
@@ -372,9 +379,7 @@ def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
results = env_dir("SKEL_RESULTS", "results")
json_file = Path(args.json)
if not json_file.is_absolute() and json_file.parent == Path("."):
json_file = results / args.json
json_file = _in_results(args.json, results)
if not json_file.is_file():
print(f"FEHLER: JSON nicht gefunden: {json_file}", file=sys.stderr)
print(" Zuerst bin/tro_flow.bat --file <name>.csv --json laufen lassen.",
@@ -412,7 +417,28 @@ def main(argv: list[str] | None = None) -> int:
return 1
templates = SclTemplates(ordner)
context = build_context(document, json_file.name, args.start)
eplan = None
if args.eplan:
eplan_file = _in_results(args.eplan, results)
try:
eplan = EplanDocument.from_json(eplan_file.read_text(encoding="utf-8"))
except OSError as exc:
print(f"FEHLER: E-Planungs-Zuordnung nicht lesbar: {exc}", file=sys.stderr)
print(" Zuerst bin/e_planning.bat --file <name>.csv --eplan "
"<zeichnung>_positionsdraw.json laufen lassen.", file=sys.stderr)
return 1
except ValidationError as exc:
print(f"FEHLER: {eplan_file} passt nicht zum Schema '{EPLAN_SCHEMA}' "
f"({exc.error_count()} Abweichungen).", file=sys.stderr)
return 1
# Eine Zuordnung zu einem anderen Layout setzt falsche Namen ein -
# Separatornummern sind nur innerhalb eines CSV-Exports eindeutig.
if Path(eplan.csv_file).name != Path(document.source).name:
print(f"FEHLER: {eplan_file.name} gehoert zu '{eplan.csv_file}', die "
f"TRO-JSON zu '{document.source}'.", file=sys.stderr)
return 1
context = build_context(document, json_file.name, args.start, eplan=eplan)
mit, ohne = template_usage(context, templates)
if args.strict and ohne:
print(f"FEHLER: kein Template fuer {', '.join(ohne)} (--strict) - "
@@ -440,6 +466,9 @@ def main(argv: list[str] | None = None) -> int:
if templates is not None:
print(f"Templates = {len(mit)} von {len(mit) + len(ohne)} Typen "
f"(generisch: {', '.join(ohne) or '-'})")
if eplan is not None:
print(f"E-Planung = {eplan_file}")
print(f"Eingesetzt = {_eingesetzt(context)} Namen aus der E-Planung")
print(f"Offene Stellen = {offen} (Marker '{TODO}')")
print(f"SCL = {out_file}")
print("================================================================")
@@ -447,6 +476,21 @@ def main(argv: list[str] | None = None) -> int:
return 0
def _in_results(name: str, results: Path) -> Path:
"""Blosser Dateiname -> in SKEL_RESULTS; Pfadangaben bleiben unveraendert."""
path = Path(name)
if not path.is_absolute() and path.parent == Path("."):
return results / name
return path
def _eingesetzt(context: SclFile) -> int:
"""Wie viele Separator-Namen aus der E-Planung kamen."""
return sum(not name.todo
for tro in context.tros for s in tro.separators
for name in (s.sensor, s.stopper))
def _schema_of(path: Path) -> str:
"""Schema-Kennung einer JSON-Datei, nur fuer die Fehlermeldung ("" wenn unlesbar)."""
try:
@@ -465,6 +509,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
" scl_skeleton.bat\n"
" scl_skeleton.bat --json export_tro.json --out FB_Main.scl\n"
" scl_skeleton.bat --json mubea_tro.json --templates\n"
" scl_skeleton.bat --json mubea_tro.json --eplan mubea_eplan.json\n"
"\n"
"Exit-Codes:\n"
" 0 ok (auch wenn TROs ohne Template generisch erzeugt wurden)\n"
@@ -500,6 +545,13 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"Ohne DIR: lib/scl_templates. FB-Typen ohne Template bleiben "
"generisch. Ohne diesen Schalter: bisherige Ausgabe.",
)
parser.add_argument(
"--eplan",
metavar="NAME",
help="Zuordnung zur E-Planung aus e_planning.py (<csv>_eplan.json in "
"%%SKEL_RESULTS%% oder ein Pfad): Separator-Sensoren und "
"Stopperventile werden eingesetzt statt als TODO markiert.",
)
parser.add_argument(
"--strict",
action="store_true",
+2 -2
View File
@@ -50,11 +50,11 @@ REGION {{ t.region_title }}
{% set _ = z.append(("stInOutMachineState := " ~ t.machine_state.value, m.note(t.machine_state, "Schaltschrank"))) %}
{% set _ = z.append(("stInOutCognexInterface := #stCognexInterfaceDummy", "")) %}
{% for j in t.jam_entries %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, "Staubereich von " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, j.title ~ " von " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamEntr" ~ j.n ~ " := " ~ j.data.value, m.note(j.data))) %}
{% endfor %}
{% for j in t.jam_exits %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, "Staubereich nach " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, j.title ~ " vor " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamExit" ~ j.n ~ " := " ~ j.data.value, m.note(j.data))) %}
{% endfor %}
{% for s in t.separators %}
@@ -42,11 +42,11 @@ REGION {{ t.region_title }}
{% set _ = z.append(("stInOutMachineState := " ~ t.machine_state.value, m.note(t.machine_state, "Schaltschrank"))) %}
{% set _ = z.append(("stInOutCognexInterface := #stCognexInterfaceDummy", "")) %}
{% for j in t.jam_entries %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.db.value ~ ".arCarrier", m.note(j.db, "Staubereich von " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.db.value ~ ".arCarrier", m.note(j.db, j.title ~ " von " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamEntr" ~ j.n ~ " := " ~ j.db.value ~ ".stData", m.note(j.db))) %}
{% endfor %}
{% for j in t.jam_exits %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.db.value ~ ".arCarrier", m.note(j.db, "Staubereich nach " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.db.value ~ ".arCarrier", m.note(j.db, j.title ~ " vor " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamExit" ~ j.n ~ " := " ~ j.db.value ~ ".stData", m.note(j.db))) %}
{% endfor %}
{% set _ = z.append(('stInOutHMI := "DB_Interface_HMI".stTRO.' ~ t.id, "")) %}
@@ -83,11 +83,11 @@ REGION {{ t.region_title }}
{% set _ = z.append(("stInOutCognexInterface := #stCognexInterfaceDummy", "")) %}
{% endif %}
{% for j in t.jam_entries %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, "Staubereich von " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, j.title ~ " von " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamEntr" ~ j.n ~ " := " ~ j.data.value, m.note(j.data))) %}
{% endfor %}
{% for j in t.jam_exits %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, "Staubereich nach " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, j.title ~ " vor " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamExit" ~ j.n ~ " := " ~ j.data.value, m.note(j.data))) %}
{% endfor %}
{% for s in t.separators %}
@@ -77,11 +77,11 @@ REGION {{ t.region_title }}
{% endif %}
{% set _ = z.append(("stInOutCognexInterfaceSSCC := " ~ ss.scanner.interface.value, m.note(ss.scanner.interface))) %}
{% for j in t.jam_entries %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, "Staubereich von " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamEntr" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, j.title ~ " von " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamEntr" ~ j.n ~ " := " ~ j.data.value, m.note(j.data))) %}
{% endfor %}
{% for j in t.jam_exits %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, "Staubereich nach " ~ j.neighbour))) %}
{% set _ = z.append(("arInOutJamExit" ~ j.n ~ " := " ~ j.carriers.value, m.note(j.carriers, j.title ~ " vor " ~ j.neighbour))) %}
{% set _ = z.append(("stInOutJamExit" ~ j.n ~ " := " ~ j.data.value, m.note(j.data))) %}
{% endfor %}
{% set _ = z.append(('stInOutHMI := "DB_Interface_HMI".stTRO.' ~ t.id, "")) %}
@@ -15,8 +15,10 @@
Sep Store n nJamLength offen (Referenz: 4 bzw. "cStorage111ExitJam<n>"),
Zeiten ueber die Konstanten cTimeSep...StorageLine
arInSep[7..19].stSimu Simulationsmodus der Referenz - nicht geschrieben
JamEntr/JamExit je einer: erster Vorgaenger/Nachfolger. Hat das
Lager mehr Zu-/Ablaeufe, steht ein TODO im Kopf.
JamEntr/JamExit je einer: der eigene Eingangs-Staubereich (mehrere
Zulaeufe laufen dort zusammen, TRO-JSON v4) und der
des ersten Nachfolgers. Hat das Lager mehr
Staubereiche, steht ein TODO im Kopf.
JamBypass Platzhalter wie Referenz
Carr_Glob_Correction nInStation offen (Referenz 1), Traeger vom Eingang
Alle noch mit den SPS-Kollegen zu bestaetigen.
@@ -28,9 +30,9 @@
{% set kamera = "" if sc.tag.todo else " (" ~ (sc.tag.value|upper) ~ ")" %}
REGION {{ t.region_title }}
{{ m.kopf(t) }}
{% if t.predecessors|length > 1 or t.successors|length > 1 %}
// {{ TODO }}: {{ t.predecessors|length }} Zulaeufe / {{ t.successors|length }} Ablaeufe im Layout -
// der FB hat je nur einen Staubereich (JamEntr/JamExit, dazu JamBypass)
{% if t.jam_entries|length > 1 or t.jam_exits|length > 1 %}
// {{ TODO }}: {{ t.jam_entries|length }} Eingangs- / {{ t.jam_exits|length }} Ausgangs-Staubereiche im Layout -
// der FB hat je nur einen (JamEntr/JamExit, dazu JamBypass)
{% endif %}
// Linien (Separatoren aus dem Layout): {{ st.lines|map(attribute="layout")|join(", ") or "-" }}
@@ -103,9 +105,9 @@ REGION {{ t.region_title }}
{% set _ = z.append(("xInConveyorRunOut := " ~ st.run_out.value, m.note(st.run_out, "Foerderer am Ablauf"))) %}
{% set _ = z.append(("stInOutControlUnitCabinet := " ~ t.machine_state.value, m.note(t.machine_state, "Schaltschrank"))) %}
{% set _ = z.append(("arInOutStorageLines := " ~ st.db.value ~ ".arLine", m.note(st.db))) %}
{% set _ = z.append(("arInOutJamEntr := " ~ t.jam_in.carriers.value, m.note(t.jam_in.carriers, "Staubereich von " ~ t.jam_in.neighbour))) %}
{% set _ = z.append(("arInOutJamEntr := " ~ t.jam_in.carriers.value, m.note(t.jam_in.carriers, t.jam_in.title ~ " von " ~ t.jam_in.neighbour))) %}
{% set _ = z.append(("stInOutJamEntr := " ~ t.jam_in.data.value, m.note(t.jam_in.data))) %}
{% set _ = z.append(("arInOutJamExit := " ~ t.jam_out.carriers.value, m.note(t.jam_out.carriers, "Staubereich nach " ~ t.jam_out.neighbour))) %}
{% set _ = z.append(("arInOutJamExit := " ~ t.jam_out.carriers.value, m.note(t.jam_out.carriers, t.jam_out.title ~ " vor " ~ t.jam_out.neighbour))) %}
{% set _ = z.append(("stInOutJamExit := " ~ t.jam_out.data.value, m.note(t.jam_out.data))) %}
{% set _ = z.append(("arInOutJamBypass := #arCarrierPlaceHolder", "")) %}
{% set _ = z.append(("stInOutJamBypass := #stJamDataPlaceHolder", "")) %}
+2 -2
View File
@@ -69,9 +69,9 @@ REGION {{ t.region_title }}
{% set _ = z.append(("xInErrorQuit := " ~ t.error_quit.value, m.note(t.error_quit, "Quittierung"))) %}
{% set _ = z.append(("xInAutomatic := " ~ t.automatic.value, m.note(t.automatic, "Automatik des Schaltschranks"))) %}
{% set _ = z.append(("xInRelease := " ~ t.release, "")) %}
{% set _ = z.append(("arInOutJamEntr := " ~ t.jam_in.carriers.value, m.note(t.jam_in.carriers, "Staubereich von " ~ t.jam_in.neighbour))) %}
{% set _ = z.append(("arInOutJamEntr := " ~ t.jam_in.carriers.value, m.note(t.jam_in.carriers, t.jam_in.title ~ " von " ~ t.jam_in.neighbour))) %}
{% set _ = z.append(("stInOutJamEntr := " ~ t.jam_in.data.value, m.note(t.jam_in.data))) %}
{% set _ = z.append(("arInOutJamFinger := " ~ t.jam_out.carriers.value, m.note(t.jam_out.carriers, "Staubereich nach " ~ t.jam_out.neighbour))) %}
{% set _ = z.append(("arInOutJamFinger := " ~ t.jam_out.carriers.value, m.note(t.jam_out.carriers, t.jam_out.title ~ " vor " ~ t.jam_out.neighbour))) %}
{% set _ = z.append(("stInOutJamFinger := " ~ t.jam_out.data.value, m.note(t.jam_out.data))) %}
{% set _ = z.append(("stInOutMachineState := " ~ t.machine_state.value, m.note(t.machine_state, "Schaltschrank"))) %}
{% set _ = z.append(("stInOutCognexInterface := #stCognexInterfaceDummy", "")) %}
@@ -71,9 +71,9 @@ REGION {{ t.region_title }}
{% set _ = z.append(("xInMotorProtection := " ~ t.motor_protection.value, m.note(t.motor_protection))) %}
{% set _ = z.append(('stInStationSensor := "DB_Inputs".Sensors["' ~ ws.station_sensor.value ~ '"]', m.note(ws.station_sensor, "Arbeitsplatz"))) %}
{% set _ = z.append(("xInReleaseStation := " ~ ws.station_release.value, m.note(ws.station_release, "Freigabe Arbeitsplatz"))) %}
{% set _ = z.append(("arInOutJamEntr := " ~ t.jam_in.carriers.value, m.note(t.jam_in.carriers, "Staubereich von " ~ t.jam_in.neighbour))) %}
{% set _ = z.append(("arInOutJamEntr := " ~ t.jam_in.carriers.value, m.note(t.jam_in.carriers, t.jam_in.title ~ " von " ~ t.jam_in.neighbour))) %}
{% set _ = z.append(("stInOutJamEntr := " ~ t.jam_in.data.value, m.note(t.jam_in.data))) %}
{% set _ = z.append(("arInOutJamFinger := " ~ t.jam_out.carriers.value, m.note(t.jam_out.carriers, "Staubereich nach " ~ t.jam_out.neighbour))) %}
{% set _ = z.append(("arInOutJamFinger := " ~ t.jam_out.carriers.value, m.note(t.jam_out.carriers, t.jam_out.title ~ " vor " ~ t.jam_out.neighbour))) %}
{% set _ = z.append(("stInOutJamFinger := " ~ t.jam_out.data.value, m.note(t.jam_out.data))) %}
{% set _ = z.append(("stInOutMachineState := " ~ t.machine_state.value, m.note(t.machine_state, "Schaltschrank"))) %}
{% set _ = z.append(("stInOutCognexInterface := #stCognexInterfaceDummy", "")) %}
+16 -2
View File
@@ -116,7 +116,9 @@ from tro_flow import (
analyse_tros,
find_sibling_raw_json,
load_sepliste_counts,
resolve_ids_file,
)
from tro_ids import TroIds
# ---------------------------------------------------------------------------
# Konstanten
@@ -844,6 +846,9 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
metavar="MM", help="Markerradius in mm. Standard: %(default)s")
parser.add_argument("--text-height", type=float, default=DEFAULT_TEXT,
metavar="MM", help="Texthoehe in mm. Standard: %(default)s")
parser.add_argument("--ids", metavar="DATEI",
help="ID-Datei der TROs wie bei tro_flow (nur gelesen). "
"Standard: <csv>_tro_ids.ini in %%SKEL_CFG%%.")
parser.add_argument("--offset", metavar="DX,DY",
help="Lagebezug von Hand setzen statt ermitteln.")
parser.add_argument("--re-register", action="store_true",
@@ -907,12 +912,21 @@ def main(argv: list[str] | None = None) -> int:
return 1
graph = build_graph(elements, csv_file.name, read_warnings)
# Derselbe Aufruf wie in tro_flow.main (ohne cfg/tro_overrides.ini, mit
# der Separator-Gegenprobe aus <name>_raw.json), damit die Zeichnung genau
# der Separator-Gegenprobe aus <name>_raw.json und der ID-Datei), damit die Zeichnung genau
# die TROs der TRO-JSON zeigt - sie ist nur Ansicht, keine Datenquelle.
raw_json_file = find_sibling_raw_json(csv_file)
sepliste_counts = (load_sepliste_counts(raw_json_file, read_warnings)
if raw_json_file is not None else {})
analysis = analyse_tros(graph, elements, sepliste_counts=sepliste_counts)
# Dieselbe ID-Datei wie tro_flow, aber nur lesen: neue TROs bekommen
# dieselbe vorlaeufige ID, die tro_flow anhaengen wuerde.
ids_file = resolve_ids_file(args.ids, csv_file)
try:
tro_ids = TroIds.load(ids_file) if ids_file.is_file() else None
except (OSError, ValueError) as exc:
print(f"FEHLER: ID-Datei nicht lesbar: {exc}", file=sys.stderr)
return 1
analysis = analyse_tros(graph, elements, sepliste_counts=sepliste_counts,
tro_ids=tro_ids)
if not analysis.tros:
print(f"FEHLER: keine TROs aus {csv_file} ableitbar.", file=sys.stderr)
return 1
+186 -8
View File
@@ -90,6 +90,14 @@ Ausgabe (alles in SKEL_RESULTS)
fuer scl_skeleton.py
<name>_tro_errors.md nur wenn TRO-Fehler gefunden wurden
TRO-IDs (ID-Datei in SKEL_CFG)
------------------------------
Die IDs kommen aus %SKEL_CFG%/<name>_tro_ids.ini (lib/tro_ids.py): jeder TRO
wird ueber die Lage seiner Separatoren wiedergefunden und behaelt seine ID,
auch wenn die SPS-Programmierer sie umbenannt haben. Fehlt die Datei, werden
die TROs entlang des Materialflusses durchnummeriert und die Datei angelegt;
neue TROs werden angehaengt. --renumber nummeriert alles neu.
Aufruf ueber bin/tro_flow.bat bzw. bin/tro_flow.sh.
"""
@@ -126,6 +134,16 @@ from material_flow import (
resolve_input,
)
from model import Analysis, DerivedTro, TroDocument
from tro_ids import (
DEFAULT_TOLERANCE,
TroIds,
assign_ids,
ids_path,
insert_entries,
other_sections,
render_ids,
render_new_entries,
)
from tro_catalog import (
CATALOG_AS_OF,
ITEM_FB,
@@ -655,6 +673,7 @@ def derive_tros(
findings: list[str],
*,
sepliste_counts: dict[str, int] | None = None,
tro_ids: TroIds | None = None,
) -> list[DerivedTro]:
"""TRO-Liste aus den Separatoren und ihren angeschlossenen Objekten bilden.
@@ -666,6 +685,10 @@ def derive_tros(
der CSV abgeleitete Separatorenzahl eines Strecke/Gefaellestrecke-Hosts
nicht mit dieser Zahl ueberein, wird ein Finding angehaengt; die TRO-Daten
selbst bleiben unangetastet (CSV bleibt die massgebliche Quelle).
tro_ids (optional): ID-Datei der Anlage. Ohne sie werden die TROs entlang
des Materialflusses neu durchnummeriert; mit ihr behalten wiedergefundene
TROs ihre ID (Ergebnis in tro_ids.result).
"""
def resolve_host(sep: Element, findings: list[str]) -> str | None:
return _default_resolve_host(sep, findings, graph)
@@ -1156,11 +1179,16 @@ def derive_tros(
tro.reason = f"{tro.reason}; {len(exits)} abgehende Wege -> Weiche"
tro.confidence = "niedrig"
# 5) Stabile, laufende Nummern in Reihenfolge der Host-Knoten
tros.sort(key=lambda t: (t.hosts[0], t.separators[0] if t.separators else ""))
width = max(2, len(str(len(tros))))
for index, tro in enumerate(tros, start=1):
tro.tro_id = f"TRO{index:0{width}d}"
# 5) IDs: Reihenfolge entlang des Materialflusses, wiedergefundene TROs
# behalten ihre ID aus der ID-Datei (siehe lib/tro_ids.py)
tros.sort(key=_flow_key(graph, elements_by_id))
anchors = separator_anchors(elements)
if tro_ids is None:
assign_ids(tros, anchors, [], renumber=True)
else:
tro_ids.result = assign_ids(
tros, anchors, tro_ids.entries,
tolerance=tro_ids.tolerance, renumber=tro_ids.renumber)
# 6) Scanner am Separator: 1Sep_SSCC laesst sich mechanisch nicht erkennen
for tro in tros:
@@ -1174,6 +1202,56 @@ def derive_tros(
return tros
def separator_anchors(elements: list[Element]) -> dict[str, tuple[float, float]]:
"""Separator-TeileId -> (x, y) in mm, Bezugslage fuer die ID-Datei."""
return {e.teile_id: (e.position[0], e.position[1])
for e in elements if e.kind == "Separator" and e.position}
def _flow_rank(graph: Graph) -> dict[str, int]:
"""Knoten -> Rang entlang des Materialflusses (Tiefensuche).
Start an den Materialeingaengen (Beladung/Eingang), danach an jedem noch
nicht erreichten Knoten - eine geschlossene Anlage ohne Station wird so
trotzdem vollstaendig erfasst. Gleichrangige Nachfolger nach Knotennamen.
"""
succ: dict[str, list[str]] = {}
for edge in graph.edges:
succ.setdefault(edge.src, []).append(edge.dst)
starts = sorted(n for n, node in graph.nodes.items() if node.element.is_entry)
rank: dict[str, int] = {}
for start in starts + sorted(graph.nodes):
stack = [start]
while stack:
node = stack.pop()
if node in rank:
continue
rank[node] = len(rank)
stack.extend(n for n in sorted(succ.get(node, []), reverse=True)
if n not in rank)
return rank
def _flow_key(graph: Graph, elements_by_id: dict[str, Element]):
"""Sortierschluessel fuer TROs entlang des Materialflusses.
Erst der Rang des Host-Knotens, dann die Lage des Separators in der
TrackIds-Kette des Hosts (Kreisel: Umlaufrichtung, Strecke: Baureihenfolge).
"""
rank = _flow_rank(graph)
def key(tro: DerivedTro) -> tuple:
host_rank = min((rank.get(h, len(rank)) for h in tro.hosts),
default=len(rank))
sep = tro.separators[0] if tro.separators else ""
host = elements_by_id.get(tro.hosts[0]) if tro.hosts else None
track = host.track_ids if host else []
in_track = track.index(sep) if sep in track else len(track)
return (host_rank, in_track, sep)
return key
# Typnamen fuer Systemgrenz-Knoten (siehe derive_boundaries) - bewusst NICHT
# in TRO_CATALOG: eine Be-/Entladestation (BTMT oder generisches Eingang/
# Ausgang-Symbol) ist kein Steuerobjekt mit eigenem FB-Baustein, nur der
@@ -1781,11 +1859,15 @@ def analyse_tros(
elements: list[Element],
*,
sepliste_counts: dict[str, int] | None = None,
tro_ids: TroIds | None = None,
) -> Analysis:
"""TROs ermitteln, verknuepfen, verorten und gegen die Typdefinition pruefen."""
"""TROs ermitteln, verknuepfen, verorten und gegen die Typdefinition pruefen.
tro_ids: ID-Datei der Anlage (siehe derive_tros); None = neu nummerieren.
"""
analysis = Analysis()
analysis.tros = derive_tros(graph, elements, analysis.findings,
sepliste_counts=sepliste_counts)
sepliste_counts=sepliste_counts, tro_ids=tro_ids)
analysis.boundaries = derive_boundaries(elements, graph)
# Fuer Verortung und Verknuepfung zaehlen TROs und Systemgrenzen zusammen:
# link_tros() registriert eine Kante nur zu einem Knoten, der Host eines
@@ -2638,6 +2720,8 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
" tro_flow.bat --file mubea.csv --tosvg --doc\n"
" tro_flow.bat --file mubea.csv --tosvg --use-cords\n"
" tro_flow.bat --file mubea.csv --json\n"
" tro_flow.bat --file mubea_export.csv --ids mubea_tro_ids.ini --json\n"
" tro_flow.bat --file mubea.csv --renumber\n"
"\n"
"Exit-Codes:\n"
" 0 ok\n"
@@ -2694,9 +2778,93 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
f"Leerstrecken ueber {COORD_MAX_LUECKE} mm zwischen den Knoten "
"(Reihenfolge und Nachbarschaft bleiben erhalten).",
)
parser.add_argument(
"--ids",
metavar="DATEI",
help="ID-Datei der Anlage (Name in %%SKEL_CFG%% oder vollstaendiger "
"Pfad). Standard: <name>_tro_ids.ini in %%SKEL_CFG%%. Fehlt sie, "
"wird sie angelegt.",
)
parser.add_argument(
"--renumber",
action="store_true",
help="Alle TRO-IDs neu entlang des Materialflusses vergeben und die "
"Abschnitt [tro] der ID-Datei neu schreiben (fruehe Projektphase - "
"umbenannte IDs gehen verloren, andere Abschnitte bleiben).",
)
parser.add_argument(
"--id-tolerance",
dest="id_tolerance",
type=float,
default=DEFAULT_TOLERANCE,
metavar="MM",
help="Hoechster Abstand (mm) zwischen Eintrag der ID-Datei und "
"Separator, bis zu dem ein TRO als wiedergefunden gilt. "
"Standard: %(default)s",
)
return parser.parse_args(argv)
def resolve_ids_file(arg: str | None, csv_file: Path) -> Path:
"""Pfad der ID-Datei: --ids (Name in SKEL_CFG oder Pfad), sonst Standard."""
cfg_dir = env_dir("SKEL_CFG", "cfg")
if not arg:
return ids_path(csv_file.stem, cfg_dir)
path = Path(arg)
if path.is_absolute() or path.parent != Path("."):
return path
return cfg_dir / path
def write_ids_file(tro_ids: TroIds, analysis: Analysis, elements: list[Element],
source: str) -> str:
"""ID-Datei anlegen, neu schreiben oder neue TROs anhaengen.
Rueckgabe: kurze Beschreibung fuer den Bericht.
"""
result = tro_ids.result
if result is None:
return "unveraendert"
anchors = separator_anchors(elements)
if result.renumbered:
tro_ids.path.parent.mkdir(parents=True, exist_ok=True)
rest = other_sections(tro_ids.path)
text = render_ids(analysis.tros, anchors, source)
tro_ids.path.write_text(text + (f"\n{rest}\n" if rest else ""),
encoding="utf-8")
return "neu geschrieben" if tro_ids.exists else "angelegt"
if result.new:
insert_entries(tro_ids.path,
render_new_entries(analysis.tros, anchors, result.new, source))
return f"{len(result.new)} neue(r) TRO(s) angehaengt"
return "unveraendert"
def report_ids(tro_ids: TroIds, status: str) -> None:
"""Abschnitt TRO-IDs im Konsolenbericht."""
result = tro_ids.result
if result is None:
return
print(f"TRO-IDs = {tro_ids.path} ({status})")
if result.renumbered:
grund = "--renumber" if tro_ids.renumber else "keine ID-Datei"
print(f" neu entlang des Materialflusses nummeriert "
f"({grund})")
return
print(f" wiedergefunden: {len(result.kept)}, "
f"neu: {len(result.new)}, weggefallen: {len(result.missing)} "
f"(Toleranz {tro_ids.tolerance:g} mm)")
if result.new:
print(f" + neu (vorlaeufige ID, in der ID-Datei umbenennbar): "
f"{', '.join(result.new)}")
for entry in result.missing:
merged = result.merged_into.get(entry.tro_id)
grund = (f"Separator gehoert jetzt zu {merged}" if merged else
f"kein Separator innerhalb {tro_ids.tolerance:g} mm")
print(f" - weggefallen: {entry.tro_id} bei {entry.x:.1f}, {entry.y:.1f} "
f"({grund}; Eintrag bleibt stehen, ID wird nicht wiederverwendet)")
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
@@ -2739,11 +2907,20 @@ def main(argv: list[str] | None = None) -> int:
connect_target = results / f"{stem}_tro_connect.ini"
json_target = results / f"{stem}_tro.json"
analysis = analyse_tros(graph, elements, sepliste_counts=sepliste_counts)
try:
tro_ids = TroIds.load(resolve_ids_file(args.ids, csv_file),
tolerance=args.id_tolerance, renumber=args.renumber)
except (OSError, ValueError) as exc:
print(f"FEHLER: ID-Datei nicht lesbar: {exc}", file=sys.stderr)
return 1
analysis = analyse_tros(graph, elements, sepliste_counts=sepliste_counts,
tro_ids=tro_ids)
if not analysis.tros:
print(f"FEHLER: {csv_file} enthaelt keine Separatoren - keine TROs "
f"ableitbar.", file=sys.stderr)
return 1
ids_status = write_ids_file(tro_ids, analysis, elements, csv_file.name)
dot_file = results / f"{stem}_tro_flow.dot"
svg_file = results / f"{stem}_tro_flow.svg"
@@ -2806,6 +2983,7 @@ def main(argv: list[str] | None = None) -> int:
report(analysis, csv_file, dot_file, created_svg, error_file, doc_file,
warnings, engine=engine)
report_ids(tro_ids, ids_status)
if connect_file:
print(f"connect.ini = {connect_file}")
if json_file:
+309
View File
@@ -0,0 +1,309 @@
# -*- coding: utf-8 -*-
"""
tro_ids.py - stabile TRO-IDs ueber eine ID-Datei je Anlage.
Die TRO-IDs vergeben die SPS-Programmierer; eine ID muss nur eindeutig sein.
Sobald SCL-Code steht, darf sich eine ID nicht mehr aendern, nur weil das
Layout neu exportiert wurde. Die Layout-TeileId taugt dafuer nicht (sie
wechselt zwischen zwei Exporten derselben Anlage), stabil ist nur die Lage.
Siehe doc/Konzept_Layout_Hardware_SCL.md, "Festlegungen" -> "TRO-IDs".
Die ID-Datei (`%SKEL_CFG%/<csv>_tro_ids.ini`, versioniert, von Hand
umbenennbar) haelt je TRO die ID und die Lage seines Anker-Separators:
[tro]
TRO01 = 1234.5, -567.8 ; 1Sep1Swi | Separator 0003 auf Kreisel2 Bahn L
K0102_1 = 2210.0, 410.0 ; von Hand umbenannt
Ablauf (assign_ids):
- ohne ID-Datei bzw. mit renumber: alle TROs in der uebergebenen Reihenfolge
(entlang des Materialflusses) neu durchnummerieren, TRO01, TRO02, ...
- mit ID-Datei: jeder Eintrag findet den TRO, dessen Separator seiner Lage am
naechsten liegt (innerhalb der Toleranz, je TRO hoechstens ein Eintrag).
Nicht gefundene TROs bekommen die naechste freie Nummer, nicht gefundene
Eintraege bleiben in der Datei stehen und ihre ID wird nicht wiederverwendet.
Das Modul liest und schreibt nur die Datei; Lage und Reihenfolge der TROs
liefert tro_flow.py.
"""
from __future__ import annotations
import configparser
import math
import re
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
__all__ = [
"SECTION",
"DEFAULT_TOLERANCE",
"IdEntry",
"IdAssignment",
"TroIds",
"ids_path",
"load_ids",
"assign_ids",
"render_ids",
"render_new_entries",
"insert_entries",
"other_sections",
]
SECTION = "tro"
ID_FILE_SUFFIX = "_tro_ids.ini"
PREFIX = "TRO"
# Abstand (mm) zwischen Eintrag und Separator, bis zu dem ein TRO als
# wiedergefunden gilt. Separatoren derselben Bahn liegen deutlich weiter
# auseinander; ein verschobenes Bauteil faellt so als "neu" auf.
DEFAULT_TOLERANCE = 100.0
# Eine ID wird zum SCL-Bezeichner (Instanz #<ID>, "DB_TRO_TEST".<ID>).
_ID_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
# SYSn sind die Systemgrenzen (tro_flow.derive_boundaries), keine TROs.
_RESERVED = re.compile(r"^SYS\d+$")
_NUMBERED = re.compile(rf"^{PREFIX}(\d+)$")
@dataclass
class IdEntry:
"""Ein Eintrag der ID-Datei: ID und Lage des Anker-Separators (mm)."""
tro_id: str
x: float
y: float
@dataclass
class IdAssignment:
"""Ergebnis von assign_ids, fuer den Bericht."""
renumbered: bool = False
# (ID, Abstand in mm) der wiedergefundenen TROs
kept: list[tuple[str, float]] = field(default_factory=list)
# IDs, die in diesem Lauf neu vergeben wurden (ohne renumbered)
new: list[str] = field(default_factory=list)
# Eintraege der Datei ohne passenden TRO
missing: list[IdEntry] = field(default_factory=list)
# weggefallene ID -> TRO, dessen Separator dort liegt, der aber schon eine
# naehere ID traegt (z. B. zwei TROs zu einem 2Sep2Swi zusammengelegt)
merged_into: dict[str, str] = field(default_factory=dict)
@dataclass
class TroIds:
"""ID-Datei einer Anlage plus Einstellungen fuer einen Lauf.
entries ist leer, wenn die Datei (noch) nicht existiert. result fuellt
tro_flow.derive_tros ueber assign_ids.
"""
path: Path
entries: list[IdEntry] = field(default_factory=list)
exists: bool = False
tolerance: float = DEFAULT_TOLERANCE
renumber: bool = False
result: IdAssignment | None = None
@classmethod
def load(cls, path: Path, *, tolerance: float = DEFAULT_TOLERANCE,
renumber: bool = False) -> "TroIds":
exists = path.is_file()
entries = load_ids(path) if exists else []
return cls(path=path, entries=entries, exists=exists,
tolerance=tolerance, renumber=renumber)
def ids_path(csv_stem: str, cfg_dir: Path) -> Path:
"""Standardpfad der ID-Datei zu einer CSV: <cfg>/<stem>_tro_ids.ini."""
return cfg_dir / f"{csv_stem}{ID_FILE_SUFFIX}"
def _parser() -> configparser.ConfigParser:
parser = configparser.ConfigParser(
inline_comment_prefixes=(";", "#"),
interpolation=None,
strict=True,
)
parser.optionxform = str # IDs nicht in Kleinbuchstaben wandeln
return parser
def load_ids(path: Path) -> list[IdEntry]:
"""ID-Datei lesen. ValueError bei doppelter ID, falscher Lage oder ID."""
parser = _parser()
try:
parser.read(path, encoding="utf-8")
except configparser.Error as exc:
raise ValueError(f"{path}: {exc}") from exc
if not parser.has_section(SECTION):
raise ValueError(f"{path}: Abschnitt [{SECTION}] fehlt")
entries: list[IdEntry] = []
for tro_id, value in parser.items(SECTION):
if not _ID_PATTERN.match(tro_id):
raise ValueError(f"{path}: '{tro_id}' ist kein gueltiger SCL-Bezeichner")
if _RESERVED.match(tro_id):
raise ValueError(f"{path}: '{tro_id}' ist fuer Systemgrenzen reserviert")
parts = [p.strip() for p in value.split(",")]
try:
x, y = (float(p) for p in parts)
except ValueError:
raise ValueError(f"{path}: {tro_id} = '{value}' - erwartet 'x, y' in mm") \
from None
entries.append(IdEntry(tro_id, x, y))
return entries
def _distance(tro, entry: IdEntry, anchors: dict[str, tuple[float, float]]) -> float:
"""Kleinster Abstand eines Separators des TRO zum Eintrag (inf ohne Lage)."""
best = math.inf
for sep in tro.separators:
pos = anchors.get(sep)
if pos is not None:
best = min(best, math.hypot(pos[0] - entry.x, pos[1] - entry.y))
return best
def _width(count: int) -> int:
return max(2, len(str(count)))
def assign_ids(
tros: list,
anchors: dict[str, tuple[float, float]],
entries: list[IdEntry],
*,
tolerance: float = DEFAULT_TOLERANCE,
renumber: bool = False,
) -> IdAssignment:
"""tro.tro_id fuer alle TROs setzen (Reihenfolge = Reihenfolge der Liste).
anchors: Separator-TeileId -> (x, y) in mm.
"""
result = IdAssignment(renumbered=renumber or not entries)
if result.renumbered:
width = _width(len(tros))
for index, tro in enumerate(tros, start=1):
tro.tro_id = f"{PREFIX}{index:0{width}d}"
return result
# Paare nach Abstand; jeder TRO und jeder Eintrag hoechstens einmal
pairs = sorted(
(d, i, j)
for i, tro in enumerate(tros)
for j, entry in enumerate(entries)
if (d := _distance(tro, entry, anchors)) <= tolerance
)
tro_taken: dict[int, tuple[int, float]] = {}
entry_taken: set[int] = set()
for d, i, j in pairs:
if i in tro_taken or j in entry_taken:
continue
tro_taken[i] = (j, d)
entry_taken.add(j)
used = {e.tro_id for e in entries}
numbers = [int(m.group(1)) for e in entries if (m := _NUMBERED.match(e.tro_id))]
widths = [len(m.group(1)) for e in entries if (m := _NUMBERED.match(e.tro_id))]
width = max(widths + [_width(len(tros))])
next_number = max(numbers, default=0) + 1
for i, tro in enumerate(tros):
if i in tro_taken:
j, d = tro_taken[i]
tro.tro_id = entries[j].tro_id
result.kept.append((tro.tro_id, d))
continue
while f"{PREFIX}{next_number:0{width}d}" in used:
next_number += 1
tro.tro_id = f"{PREFIX}{next_number:0{width}d}"
used.add(tro.tro_id)
next_number += 1
result.new.append(tro.tro_id)
result.missing = [e for j, e in enumerate(entries) if j not in entry_taken]
for d, i, j in pairs:
if j not in entry_taken:
result.merged_into.setdefault(entries[j].tro_id, tros[i].tro_id)
return result
def _anchor(tro, anchors: dict[str, tuple[float, float]]) -> tuple[float, float] | None:
for sep in tro.separators:
if sep in anchors:
return anchors[sep]
return None
def _entry_lines(tros: list, anchors: dict[str, tuple[float, float]]) -> list[str]:
lines = []
for tro in tros:
pos = _anchor(tro, anchors)
if pos is None:
continue
seps = ", ".join(tro.separators)
lines.append(f"{tro.tro_id} = {pos[0]:.1f}, {pos[1]:.1f}"
f" ; {tro.type_name} | {tro.label} | Sep {seps}")
return lines
def render_ids(tros: list, anchors: dict[str, tuple[float, float]], source: str) -> str:
"""Komplette ID-Datei (erster Lauf oder renumber)."""
head = [
"; ============================================================",
f"; TRO-IDs zu {source} - erzeugt von lib/tro_flow.py "
f"am {datetime.now():%Y-%m-%d}",
";",
"; Je TRO: ID = x, y (mm) seines Anker-Separators im CSV-Koordinatensystem.",
"; Die IDs duerfen umbenannt werden (eindeutig, gueltiger SCL-Bezeichner);",
"; tro_flow findet jeden TRO ueber die Lage wieder. Neue TROs werden",
"; unten angehaengt, weggefallene bleiben stehen und werden gemeldet.",
"; Alles neu nummerieren: tro_flow --renumber (ueberschreibt diese Datei).",
"; Siehe doc/Konzept_Layout_Hardware_SCL.md, Abschnitt TRO-IDs.",
"; ============================================================",
"",
f"[{SECTION}]",
]
return "\n".join(head + _entry_lines(tros, anchors)) + "\n"
def render_new_entries(tros: list, anchors: dict[str, tuple[float, float]],
new_ids: list[str], source: str) -> str:
"""Anhang fuer neu gefundene TROs an eine bestehende ID-Datei."""
neu = [t for t in tros if t.tro_id in set(new_ids)]
head = [f"; neu aus {source} am {datetime.now():%Y-%m-%d}"]
return "\n".join(head + _entry_lines(neu, anchors)) + "\n"
def other_sections(path: Path) -> str:
"""Alle Abschnitte ausser [tro] als Text (bleiben bei renumber erhalten)."""
if not path.is_file():
return ""
header = re.compile(r"^\s*\[(.+)\]\s*$")
keep: list[str] = []
current = None
for line in path.read_text(encoding="utf-8").splitlines():
if m := header.match(line):
current = m.group(1).strip()
if current is not None and current != SECTION:
keep.append(line)
return "\n".join(keep).strip("\n")
def insert_entries(path: Path, block: str) -> None:
"""block am Ende des Abschnitts [tro] einfuegen, Rest der Datei unveraendert."""
lines = path.read_text(encoding="utf-8").splitlines()
header = re.compile(r"^\s*\[(.+)\]\s*$")
start = next(i for i, line in enumerate(lines)
if (m := header.match(line)) and m.group(1).strip() == SECTION)
end = next((i for i in range(start + 1, len(lines)) if header.match(lines[i])),
len(lines))
# vor Leerzeilen am Abschnittsende einfuegen
while end > start + 1 and not lines[end - 1].strip():
end -= 1
lines[end:end] = block.rstrip("\n").splitlines()
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
+46 -60
View File
@@ -72,13 +72,9 @@ REGION TRO20 (1Sep) Sep 0007
xInRelease := "DB_TRO_TEST".TRO20,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von SYS1 (Station, kein TRO)
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0007 von SYS1 (Station, kein TRO), SYS2 (Station, kein TRO), TRO19
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von SYS2 (Station, kein TRO)
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO19
stInOutJamEntr3 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO01
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0008 vor TRO01
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO20,
@@ -122,11 +118,11 @@ REGION TRO01 (1Sep1Swi) Sep 0008
xInRelease := "DB_TRO_TEST".TRO01,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO20
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0008 von TRO20
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO02
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0025 vor TRO02
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO06
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0010 vor TRO06
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutSw1ExTo3 => "MB____", // TODO(E-Planung)
@@ -183,9 +179,9 @@ REGION TRO02 (Vario) Sep 0025
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO01
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0025 von TRO01
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO03
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0026 vor TRO03
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -230,13 +226,11 @@ REGION TRO06 (1Sep1Swi) Sep 0010
xInRelease := "DB_TRO_TEST".TRO06,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO01
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0010 von TRO01, TRO05
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO05
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO07
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0031 vor TRO07
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO09
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 vor TRO09
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutSw1ExTo3 => "MB____", // TODO(E-Planung)
@@ -277,9 +271,9 @@ REGION TRO03 (1Sep) Sep 0026
xInRelease := "DB_TRO_TEST".TRO03,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO02
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0026 von TRO02
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO04
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0027 vor TRO04
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO03,
@@ -329,9 +323,9 @@ REGION TRO07 (Vario) Sep 0031
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0031 von TRO06
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO08
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0032 vor TRO08
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -376,13 +370,11 @@ REGION TRO09 (1Sep1Swi) Sep 0012
xInRelease := "DB_TRO_TEST".TRO09,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 von TRO06, TRO08
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO08
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO10
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0035 vor TRO10
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO12
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0014 vor TRO12
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutSw1ExTo3 => "MB____", // TODO(E-Planung)
@@ -439,9 +431,9 @@ REGION TRO04 (Vario) Sep 0027
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO03
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0027 von TRO03
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO05
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0028 vor TRO05
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -476,9 +468,9 @@ REGION TRO08 (1Sep) Sep 0032
xInRelease := "DB_TRO_TEST".TRO08,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO07
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0032 von TRO07
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO09
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 vor TRO09
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO08,
@@ -528,9 +520,9 @@ REGION TRO10 (Vario) Sep 0035
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO09
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0035 von TRO09
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO11
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0036 vor TRO11
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -575,13 +567,11 @@ REGION TRO12 (1Sep1Swi) Sep 0014
xInRelease := "DB_TRO_TEST".TRO12,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO09
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0014 von TRO09, TRO11
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO11
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO13
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0039 vor TRO13
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO15
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0016 vor TRO15
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutSw1ExTo3 => "MB____", // TODO(E-Planung)
@@ -622,9 +612,9 @@ REGION TRO05 (1Sep) Sep 0028
xInRelease := "DB_TRO_TEST".TRO05,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO04
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0028 von TRO04
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO06
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0010 vor TRO06
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO05,
@@ -658,9 +648,9 @@ REGION TRO11 (1Sep) Sep 0036
xInRelease := "DB_TRO_TEST".TRO11,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0036 von TRO10
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO12
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0014 vor TRO12
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO11,
@@ -710,9 +700,9 @@ REGION TRO13 (Vario) Sep 0039
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO12
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0039 von TRO12
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO14
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 vor TRO14
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -757,13 +747,11 @@ REGION TRO15 (1Sep1Swi) Sep 0016
xInRelease := "DB_TRO_TEST".TRO15,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO12
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0016 von TRO12, TRO14
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO14
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO16
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0018 vor TRO16
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO17
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 vor TRO17
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutSw1ExTo3 => "MB____", // TODO(E-Planung)
@@ -804,9 +792,9 @@ REGION TRO14 (1Sep) Sep 0040
xInRelease := "DB_TRO_TEST".TRO14,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO13
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 von TRO13
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO15
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0016 vor TRO15
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO14,
@@ -840,11 +828,9 @@ REGION TRO16 (1Sep) Sep 0018
xInRelease := "DB_TRO_TEST".TRO16,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO15
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0018 von TRO15, TRO18
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO18
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO19
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0020 vor TRO19
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO16,
@@ -894,9 +880,9 @@ REGION TRO17 (Vario) Sep 0043
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO15
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 von TRO15
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO18
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 vor TRO18
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -931,11 +917,11 @@ REGION TRO19 (1Sep) Sep 0020
xInRelease := "DB_TRO_TEST".TRO19,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO16
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0020 von TRO16
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach SYS3 (Station, kein TRO)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich vor SYS3 (Station, kein TRO)
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO20
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0007 vor TRO20
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO19,
@@ -969,9 +955,9 @@ REGION TRO18 (1Sep) Sep 0044
xInRelease := "DB_TRO_TEST".TRO18,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO17
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 von TRO17
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO16
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0018 vor TRO16
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO18,
+46 -60
View File
@@ -79,13 +79,9 @@ REGION TRO20 (1Sep) Sep 0007
xInRelease := "DB_TRO_TEST".TRO20,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von SYS1 (Station, kein TRO)
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0007 von SYS1 (Station, kein TRO), SYS2 (Station, kein TRO), TRO19
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von SYS2 (Station, kein TRO)
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO19
stInOutJamEntr3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO01
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0008 vor TRO01
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -133,11 +129,11 @@ REGION TRO01 (1Sep1Swi) Sep 0008
xInRelease := TRUE,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO20
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0008 von TRO20
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO02
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0025 vor TRO02
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO06
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0010 vor TRO06
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO01,
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -204,9 +200,9 @@ REGION TRO02 (Vario) Sep 0025
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO02,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO01
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0025 von TRO01
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO03
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0026 vor TRO03
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -257,13 +253,11 @@ REGION TRO06 (1Sep1Swi) Sep 0010
xInRelease := TRUE,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO01
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0010 von TRO01, TRO05
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO05
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO07
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0031 vor TRO07
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO09
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 vor TRO09
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO06,
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -309,9 +303,9 @@ REGION TRO03 (1Sep) Sep 0026
xInRelease := "DB_TRO_TEST".TRO03,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO02
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0026 von TRO02
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO04
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0027 vor TRO04
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -371,9 +365,9 @@ REGION TRO07 (Vario) Sep 0031
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO07,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0031 von TRO06
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO08
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0032 vor TRO08
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -424,13 +418,11 @@ REGION TRO09 (1Sep1Swi) Sep 0012
xInRelease := TRUE,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 von TRO06, TRO08
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO08
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO10
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0035 vor TRO10
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO12
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0014 vor TRO12
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO09,
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -497,9 +489,9 @@ REGION TRO04 (Vario) Sep 0027
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO04,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO03
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0027 von TRO03
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO05
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0028 vor TRO05
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -541,9 +533,9 @@ REGION TRO08 (1Sep) Sep 0032
xInRelease := "DB_TRO_TEST".TRO08,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO07
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0032 von TRO07
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO09
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 vor TRO09
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -603,9 +595,9 @@ REGION TRO10 (Vario) Sep 0035
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO10,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO09
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0035 von TRO09
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO11
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0036 vor TRO11
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -656,13 +648,11 @@ REGION TRO12 (1Sep1Swi) Sep 0014
xInRelease := TRUE,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO09
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0014 von TRO09, TRO11
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO11
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO13
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0039 vor TRO13
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO15
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0016 vor TRO15
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO12,
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -708,9 +698,9 @@ REGION TRO05 (1Sep) Sep 0028
xInRelease := "DB_TRO_TEST".TRO05,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO04
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0028 von TRO04
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO06
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0010 vor TRO06
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -749,9 +739,9 @@ REGION TRO11 (1Sep) Sep 0036
xInRelease := "DB_TRO_TEST".TRO11,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0036 von TRO10
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO12
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0014 vor TRO12
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -811,9 +801,9 @@ REGION TRO13 (Vario) Sep 0039
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO13,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO12
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0039 von TRO12
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO14
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 vor TRO14
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -864,13 +854,11 @@ REGION TRO15 (1Sep1Swi) Sep 0016
xInRelease := TRUE,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO12
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0016 von TRO12, TRO14
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO14
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO16
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0018 vor TRO16
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO17
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 vor TRO17
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO15,
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -916,9 +904,9 @@ REGION TRO14 (1Sep) Sep 0040
xInRelease := "DB_TRO_TEST".TRO14,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO13
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 von TRO13
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO15
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0016 vor TRO15
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -957,11 +945,9 @@ REGION TRO16 (1Sep) Sep 0018
xInRelease := "DB_TRO_TEST".TRO16,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO15
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0018 von TRO15, TRO18
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO18
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO19
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0020 vor TRO19
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -1021,9 +1007,9 @@ REGION TRO17 (Vario) Sep 0043
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO17,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO15
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 von TRO15
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO18
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 vor TRO18
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -1066,11 +1052,11 @@ REGION TRO19 (1Sep) Sep 0020
xInRelease := "DB_TRO_TEST".TRO19,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO16
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0020 von TRO16
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach SYS3 (Station, kein TRO)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich vor SYS3 (Station, kein TRO)
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO20
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0007 vor TRO20
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -1109,9 +1095,9 @@ REGION TRO18 (1Sep) Sep 0044
xInRelease := "DB_TRO_TEST".TRO18,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO17
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 von TRO17
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO16
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0018 vor TRO16
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
+44 -54
View File
@@ -70,11 +70,9 @@ REGION TRO03 (1Sep) Sep 0042
xInRelease := "DB_TRO_TEST".TRO03,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von SYS1 (Station, kein TRO)
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0042 von SYS1 (Station, kein TRO), TRO04
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO04
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO05
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 vor TRO05
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO03,
@@ -108,9 +106,9 @@ REGION TRO05 (1Sep) Sep 0044
xInRelease := "DB_TRO_TEST".TRO05,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO03
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 von TRO03
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO18
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0066 vor TRO18
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO05,
@@ -150,13 +148,11 @@ REGION TRO18 (PinStore_Auto) Sep 0066, 0069, 0072, 0075, 0078, 0081, 0084, 0087,
xInRelease := "DB_TRO_TEST".TRO18,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO05
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0066 von TRO05, TRO12 (Eingang des Speichers - Kennung vorlaeufig nach erstem Linien-Separator)
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO12
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO07
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0045 vor TRO07
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO10
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0005 vor TRO10
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO18,
@@ -190,11 +186,9 @@ REGION TRO07 (1Sep) Sep 0045
xInRelease := "DB_TRO_TEST".TRO07,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO09
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0045 von TRO09, TRO18
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO18
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO04
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 vor TRO04
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO07,
@@ -265,17 +259,15 @@ REGION TRO10 (2Sep2Swi) Sep 0005, 0036
xInRelease := "DB_TRO_TEST".TRO10,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO01
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0005 von TRO01, TRO06, TRO18 (Aufteilung der Vorgaenger auf die Eingaenge offen)
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0036 von - (Aufteilung der Vorgaenger auf die Eingaenge offen)
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO18
stInOutJamEntr3 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO01
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 vor TRO01
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO02
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 vor TRO02
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO11
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0058 vor TRO11
stInOutJamExit4 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutStopper2 => "MB____", // TODO(E-Planung)
@@ -322,9 +314,9 @@ REGION TRO04 (1Sep) Sep 0043
xInRelease := "DB_TRO_TEST".TRO04,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO07
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 von TRO07
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO03
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0042 vor TRO03
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO04,
@@ -358,9 +350,9 @@ REGION TRO01 (1Sep) Sep 0040
xInRelease := "DB_TRO_TEST".TRO01,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 von TRO10
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO10
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0005 vor TRO10
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO01,
@@ -394,13 +386,13 @@ REGION TRO02 (1Sep) Sep 0012
xInRelease := "DB_TRO_TEST".TRO02,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 von TRO10
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach SYS2 (Station, kein TRO)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich vor SYS2 (Station, kein TRO)
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach SYS3 (Station, kein TRO)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich vor SYS3 (Station, kein TRO)
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO13
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0011 vor TRO13
stInOutJamExit4 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO02,
@@ -450,9 +442,9 @@ REGION TRO11 (Vario) Sep 0058
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0058 von TRO10
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO12
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0059 vor TRO12
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -497,11 +489,11 @@ REGION TRO13 (1Sep1Swi) Sep 0011
xInRelease := "DB_TRO_TEST".TRO13,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO02
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0011 von TRO02
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO16
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0062 vor TRO16
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO17
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0063 vor TRO17
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutSw1ExTo3 => "MB____", // TODO(E-Planung)
@@ -542,9 +534,9 @@ REGION TRO12 (1Sep) Sep 0059
xInRelease := "DB_TRO_TEST".TRO12,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO11
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0059 von TRO11
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO18
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0066 vor TRO18
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO12,
@@ -594,9 +586,9 @@ REGION TRO16 (Vario) Sep 0062
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO13
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0062 von TRO13
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO15
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0038 vor TRO15
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -631,11 +623,9 @@ REGION TRO17 (1Sep) Sep 0063
xInRelease := "DB_TRO_TEST".TRO17,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO13
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0063 von TRO13, TRO14
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO14
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO06
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0003 vor TRO06
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO17,
@@ -669,9 +659,9 @@ REGION TRO15 (1Sep) Sep 0038
xInRelease := "DB_TRO_TEST".TRO15,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO16
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0038 von TRO16
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO14
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0037 vor TRO14
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO15,
@@ -715,11 +705,11 @@ REGION TRO06 (1Sep1Swi) Sep 0003
xInRelease := "DB_TRO_TEST".TRO06,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO17
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0003 von TRO17
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO08
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0054 vor TRO08
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO10
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0005 vor TRO10
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutSw1ExTo3 => "MB____", // TODO(E-Planung)
@@ -760,9 +750,9 @@ REGION TRO14 (1Sep) Sep 0037
xInRelease := "DB_TRO_TEST".TRO14,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO15
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0037 von TRO15
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO17
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0063 vor TRO17
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO14,
@@ -812,9 +802,9 @@ REGION TRO08 (Vario) Sep 0054
xInAutomatic := #t_automatik,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0054 von TRO06
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO09
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0055 vor TRO09
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutVarioMotor => "MA____", // TODO(E-Planung)
@@ -849,9 +839,9 @@ REGION TRO09 (1Sep) Sep 0055
xInRelease := "DB_TRO_TEST".TRO09,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO08
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0055 von TRO08
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO07
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0045 vor TRO07
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO09,
+45 -53
View File
@@ -77,11 +77,9 @@ REGION TRO03 (1Sep) Sep 0042
xInRelease := "DB_TRO_TEST".TRO03,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von SYS1 (Station, kein TRO)
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0042 von SYS1 (Station, kein TRO), TRO04
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO04
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO05
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 vor TRO05
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -120,9 +118,9 @@ REGION TRO05 (1Sep) Sep 0044
xInRelease := "DB_TRO_TEST".TRO05,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO03
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0044 von TRO03
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO18
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0066 vor TRO18
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -136,8 +134,8 @@ REGION TRO18 (PinStore_Auto) Sep 0066, 0069, 0072, 0075, 0078, 0081, 0084, 0087,
// Nachfolger: TRO07, TRO10
// Einstufung: hoch
// Aufbau nach Template FB_ILS_MTRO_PinStore_Auto.scl.j2 (Referenz: doc/TRO_Katalog/scl_templates)
// TODO(E-Planung): 2 Zulaeufe / 2 Ablaeufe im Layout -
// der FB hat je nur einen Staubereich (JamEntr/JamExit, dazu JamBypass)
// TODO(E-Planung): 1 Eingangs- / 2 Ausgangs-Staubereiche im Layout -
// der FB hat je nur einen (JamEntr/JamExit, dazu JamBypass)
// Linien (Separatoren aus dem Layout): 0066, 0069, 0072, 0075, 0078, 0081, 0084, 0087, 0090, 0093, 0096, 0099, 0102, 0105, 0108, 0111, 0114, 0117, 0120, 0123
REGION Encoder Settings
@@ -417,9 +415,9 @@ REGION TRO18 (PinStore_Auto) Sep 0066, 0069, 0072, 0075, 0078, 0081, 0084, 0087,
xInConveyorRunOut := #fbExitConv____.xOutConveyorFullSpeed, // TODO(E-Planung) - Foerderer am Ablauf
stInOutControlUnitCabinet := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
arInOutStorageLines := "DB_Storage____".arLine, // TODO(E-Planung)
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO05
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0066 von TRO05, TRO12 (Eingang des Speichers - Kennung vorlaeufig nach erstem Linien-Separator)
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO07
arInOutJamExit := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0045 vor TRO07
stInOutJamExit := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamBypass := #arCarrierPlaceHolder,
stInOutJamBypass := #stJamDataPlaceHolder,
@@ -501,11 +499,9 @@ REGION TRO07 (1Sep) Sep 0045
xInRelease := "DB_TRO_TEST".TRO07,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO09
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0045 von TRO09, TRO18
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO18
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO04
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 vor TRO04
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -577,17 +573,15 @@ REGION TRO10 (2Sep2Swi) Sep 0005, 0036
xInRelease := "DB_TRO_TEST".TRO10,
stInOutMachineState := #stInOutControlUnitCabinet,
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO01
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0005 von TRO01, TRO06, TRO18 (Aufteilung der Vorgaenger auf die Eingaenge offen)
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0036 von - (Aufteilung der Vorgaenger auf die Eingaenge offen)
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData,
arInOutJamEntr3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO18
stInOutJamEntr3 := "DB_JamArea____".stJam____.stData,
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO01
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 vor TRO01
stInOutJamExit2 := "DB_JamArea____".stJam____.stData,
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO02
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 vor TRO02
stInOutJamExit3 := "DB_JamArea____".stJam____.stData,
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO11
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0058 vor TRO11
stInOutJamExit4 := "DB_JamArea____".stJam____.stData,
xOutStopper1 => "MB____", // TODO(E-Planung)
xOutStopper2 => "MB____", // TODO(E-Planung)
@@ -638,9 +632,9 @@ REGION TRO04 (1Sep) Sep 0043
xInRelease := "DB_TRO_TEST".TRO04,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO07
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0043 von TRO07
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO03
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0042 vor TRO03
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -679,9 +673,9 @@ REGION TRO01 (1Sep) Sep 0040
xInRelease := "DB_TRO_TEST".TRO01,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0040 von TRO10
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO10
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0005 vor TRO10
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -722,13 +716,13 @@ REGION TRO02 (1Sep) Sep 0012
xInRelease := "DB_TRO_TEST".TRO02,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0012 von TRO10
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach SYS2 (Station, kein TRO)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich vor SYS2 (Station, kein TRO)
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach SYS3 (Station, kein TRO)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich vor SYS3 (Station, kein TRO)
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO13
arInOutJamExit4 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0011 vor TRO13
stInOutJamExit4 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -788,9 +782,9 @@ REGION TRO11 (Vario) Sep 0058
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO11,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO10
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0058 von TRO10
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO12
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0059 vor TRO12
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -841,11 +835,11 @@ REGION TRO13 (1Sep1Swi) Sep 0011
xInRelease := TRUE,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO02
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0011 von TRO02
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO16
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0062 vor TRO16
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO17
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0063 vor TRO17
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO13,
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -891,9 +885,9 @@ REGION TRO12 (1Sep) Sep 0059
xInRelease := "DB_TRO_TEST".TRO12,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO11
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0059 von TRO11
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO18
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0066 vor TRO18
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -953,9 +947,9 @@ REGION TRO16 (Vario) Sep 0062
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO16,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO13
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0062 von TRO13
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO15
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0038 vor TRO15
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -997,11 +991,9 @@ REGION TRO17 (1Sep) Sep 0063
xInRelease := "DB_TRO_TEST".TRO17,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO13
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0063 von TRO13, TRO14
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamEntr2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO14
stInOutJamEntr2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO06
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0003 vor TRO06
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -1040,9 +1032,9 @@ REGION TRO15 (1Sep) Sep 0038
xInRelease := "DB_TRO_TEST".TRO15,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO16
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0038 von TRO16
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO14
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0037 vor TRO14
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -1090,11 +1082,11 @@ REGION TRO06 (1Sep1Swi) Sep 0003
xInRelease := TRUE,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO17
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0003 von TRO17
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO08
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0054 vor TRO08
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO10
arInOutJamExit3 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0005 vor TRO10
stInOutJamExit3 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutHMI := "DB_Interface_HMI".stTRO.TRO06,
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -1140,9 +1132,9 @@ REGION TRO14 (1Sep) Sep 0037
xInRelease := "DB_TRO_TEST".TRO14,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO15
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0037 von TRO15
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO17
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0063 vor TRO17
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
@@ -1202,9 +1194,9 @@ REGION TRO08 (Vario) Sep 0054
xInErrorQuit := "SF____", // TODO(E-Planung) - Quittierung
xInAutomatic := #t_automatik, // TODO(E-Planung) - Automatik des Schaltschranks
xInRelease := "DB_TRO_TEST".TRO08,
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO06
arInOutJamEntr := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0054 von TRO06
stInOutJamEntr := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO09
arInOutJamFinger := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0055 vor TRO09
stInOutJamFinger := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
@@ -1246,9 +1238,9 @@ REGION TRO09 (1Sep) Sep 0055
xInRelease := "DB_TRO_TEST".TRO09,
stInOutMachineState := #stInOutControlUnitCabinet, // TODO(E-Planung) - Schaltschrank
stInOutCognexInterface := #stCognexInterfaceDummy,
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich von TRO08
arInOutJamEntr1 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0055 von TRO08
stInOutJamEntr1 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich nach TRO07
arInOutJamExit2 := "DB_JamArea____".stJam____.arCarrier, // TODO(E-Planung) - Staubereich J0045 vor TRO07
stInOutJamExit2 := "DB_JamArea____".stJam____.stData, // TODO(E-Planung)
xOutStopper1 => "MB____", // TODO(E-Planung)
nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"], // TODO(E-Planung)
+253
View File
@@ -0,0 +1,253 @@
# -*- coding: utf-8 -*-
"""Tests fuer lib/e_planning.py - Zuordnung Layout <-> E-Planung.
Die Geometrie ist synthetisch und nachgebaut nach den Befunden an Mubea
(doc/E-Planung_Bruecke.md): Speicherlinien im 800-mm-Raster, Sensor auf dem
Separator, Ventil ~270 mm daneben, Stausensor konstant ~1390 mm vom
Ausschleuselement. Der Lagebezug ist hier die Identitaet.
"""
import json
import shutil
from pathlib import Path
import pytest
import scl_skeleton
from dxf_registration import Transform
from e_planning import (
EDevice,
EplanDocument,
LayoutPoint,
Rule,
assign,
load_positionsdraw,
load_rules,
pair_drives,
)
from scl_context import build_context
from scl_skeleton import load_document
FIXTURES = Path(__file__).resolve().parent / "fixtures" / "scl"
RULES = Path(__file__).resolve().parent.parent / "cfg" / "e_planning.ini"
IDENT = Transform(dx=0.0, dy=0.0)
def _dev(bmk: str, verw: str, x: float, y: float, **extra) -> EDevice:
return EDevice(bmk=bmk, verw=verw, pos=(x, y),
kennzeichnung="=A1+UH01-KF107", **extra)
def _rolle(assignments, layout_id: str, role: str):
return next((a for a in assignments
if a.layout_id == layout_id and a.role == role), None)
# ---------------------------------------------------------------------------
# Einlesen
# ---------------------------------------------------------------------------
def test_positionsdraw_einlesen(tmp_path):
datei = tmp_path / "x_positionsdraw.json"
datei.write_text(json.dumps({
"sensors": {
"BG0642@UNKNOWN": {"B": "BG0642", "VERW": "IN SEP",
"BEZEICHNUNG": "TEIL IN SEPARATOR STR. 20",
"KENNZEICHNUNG": "=A1+UH01-KF107", "pos": [1.0, 2.0]},
},
"schaltschrank_elemente": {
# Derselbe Motorschutz zweimal: einmal mit, einmal ohne Attribute
"FC0200@": {"IO": "FC0200", "VERW": "MOTORSCHUTZ",
"BEZEICHNUNG": "MOTORSCHUTZ CONVEYER #100", "pos": [5, 5]},
"FC0200": {"B": "FC0200", "pos": [6, 6]},
},
"double_ids": {"BG0516": [[0, 0], [1, 1]]},
}), encoding="utf-8")
warnungen: list[str] = []
geraete = {d.bmk: d for d in load_positionsdraw(datei, warnungen)}
assert set(geraete) == {"BG0642", "FC0200"}
bg = geraete["BG0642"]
assert (bg.verw, bg.pos, bg.cabinet) == ("IN SEP", (1.0, 2.0), False)
assert (bg.controller, bg.station, bg.prefix, bg.number) == ("UH01", "KF107", "BG", 642)
assert geraete["FC0200"].verw == "MOTORSCHUTZ"
assert geraete["FC0200"].cabinet
assert any("BG0516" in w for w in warnungen)
def test_regeln_aus_der_projektkonfiguration():
regeln, anker = load_rules(RULES)
rollen = [r.role for r in regeln]
assert anker == ["IN SEP"]
# Reihenfolge = Dateireihenfolge; der Sensor muss vor dem Ventil kommen
assert rollen.index("Separator.sensor") < rollen.index("Separator.stopper")
stopper = next(r for r in regeln if r.role == "Separator.stopper")
assert (stopper.layout, stopper.number_from, stopper.number_offset) == (
["Separator"], "Separator.sensor", 1)
def test_nummer_von_muss_eine_regel_sein(tmp_path):
ini = tmp_path / "r.ini"
ini.write_text("[Separator.stopper]\nverw = SEP\nnummer_von = Separator.gibtsnicht\n",
encoding="utf-8")
with pytest.raises(ValueError, match="gibtsnicht"):
load_rules(ini)
# ---------------------------------------------------------------------------
# Zuordnung
# ---------------------------------------------------------------------------
SENSOR = Rule(role="Separator.sensor", layout=["Separator"], verw=["IN SEP"],
prefix="BG", max_mm=300)
STOPPER = Rule(role="Separator.stopper", layout=["Separator"], verw=["SEP"],
prefix="MB", number_from="Separator.sensor", number_offset=1,
max_mm=600, target_mm=270)
def _speicher():
"""Drei Speicherlinien im 800-mm-Raster.
Das Ventil jeder Linie liegt 530 mm von ihrem Separator - und nur 270 mm
vom Separator der Nachbarlinie. Raeumlich waere das falsch; die
Nummernregel (MB = BG + 1) muss gewinnen.
"""
punkte = [LayoutPoint(f"00{i}", "Separator", 0.0, 800.0 * i) for i in (1, 2, 3)]
geraete = []
for i, bg in ((1, 642), (2, 644), (3, 646)):
geraete.append(_dev(f"BG0{bg}", "IN SEP", 0.0, 800.0 * i))
geraete.append(_dev(f"MB0{bg + 1}", "SEP", 0.0, 800.0 * i + 530.0))
return punkte, geraete
def test_ventil_ueber_die_nummer_nicht_ueber_den_abstand():
punkte, geraete = _speicher()
ergebnis = assign(punkte, geraete, [SENSOR, STOPPER], IDENT)
for sep, bg, mb in (("001", "BG0642", "MB0643"), ("002", "BG0644", "MB0645"),
("003", "BG0646", "MB0647")):
assert _rolle(ergebnis, sep, "Separator.sensor").bmk == bg
stopper = _rolle(ergebnis, sep, "Separator.stopper")
assert (stopper.bmk, stopper.rule) == (mb, "nummer")
def test_jedes_e_bauteil_nur_einmal():
"""Zwei Separatoren, ein Sensor dazwischen: nur der naehere bekommt ihn."""
punkte = [LayoutPoint("A", "Separator", 0, 0), LayoutPoint("B", "Separator", 0, 250)]
geraete = [_dev("BG0100", "IN SEP", 0, 100)]
ergebnis = assign(punkte, geraete, [SENSOR], IDENT)
assert [(a.layout_id, a.bmk) for a in ergebnis] == [("A", "BG0100")]
assert ergebnis[0].runner_up is None
def test_zu_weit_weg_bleibt_offen():
punkte = [LayoutPoint("A", "Separator", 0, 0)]
geraete = [_dev("BG0100", "IN SEP", 0, 400)]
assert assign(punkte, geraete, [SENSOR], IDENT) == []
def test_sollabstand_schlaegt_kleinsten_abstand():
"""Mubea 0080: ein fremder Stausensor liegt 269 mm nah, der eigene 1391 mm."""
stau = Rule(role="Ausschleus.stau", layout=["Ausschleus"], verw=["STAU*"],
prefix="BG", max_mm=1600, target_mm=1390)
punkte = [LayoutPoint("0080", "Ausschleus", 0, 0)]
geraete = [_dev("BG0525", "STAU_REF-LT", 269, 0),
_dev("BG0550", "STAU_REF-LT", -1391, 0)]
ergebnis = assign(punkte, geraete, [stau], IDENT)
assert [a.bmk for a in ergebnis] == ["BG0550"]
def test_lagebezug_wird_angewendet():
"""E-Koordinaten werden mit dem Lagebezug ins CSV gebracht."""
punkte = [LayoutPoint("A", "Separator", 1000, 2000)]
geraete = [_dev("BG0100", "IN SEP", 0, 0)]
ergebnis = assign(punkte, geraete, [SENSOR], Transform(dx=1000, dy=2000))
assert ergebnis[0].bmk == "BG0100" and ergebnis[0].distance == 0
def test_schrankelemente_werden_nicht_raeumlich_zugeordnet():
punkte = [LayoutPoint("A", "Separator", 0, 0)]
geraete = [_dev("BG0100", "IN SEP", 0, 0, cabinet=True)]
assert assign(punkte, geraete, [SENSOR], IDENT) == []
def test_motor_und_motorschutz_ueber_die_nummer():
geraete = [_dev("MA0200", "MOT_0,37kW", 0, 0, bezeichnung="PIN CONVEYOR#100"),
EDevice(bmk="FC0200", verw="MOTORSCHUTZ", cabinet=True),
_dev("MA0300", "MOT", 0, 0)]
antriebe = {d.motor: d.protection for d in pair_drives(geraete)}
assert antriebe == {"MA0200": "FC0200", "MA0300": ""}
# ---------------------------------------------------------------------------
# Bis in die SCL
# ---------------------------------------------------------------------------
def _eplan_fuer_mubea(csv_file: str = "mubea.csv") -> EplanDocument:
"""Zuordnung fuer Separator 0012 (TRO02 der Fixture) - nur Sensor und Ventil."""
punkte = [LayoutPoint("0012", "Separator", 0, 0)]
geraete = [_dev("BG0712", "IN SEP", 0, 0), _dev("MB0713", "SEP", 0, 300)]
return EplanDocument(csv_file=csv_file, eplan_file="Mubea_positionsdraw.json",
created="2026-01-01 00:00",
assignments=assign(punkte, geraete, [SENSOR, STOPPER], IDENT))
def test_kontext_setzt_namen_ein_und_loescht_den_merker():
document = load_document(FIXTURES / "mubea_tro.json")
kontext = build_context(document, "mubea_tro.json", eplan=_eplan_fuer_mubea())
tro02 = next(t for t in kontext.tros if t.id == "TRO02")
sep = tro02.separators[0]
assert (sep.layout, sep.sensor.value, sep.sensor.todo) == ("0012", "cInBG0712", False)
assert (sep.stopper.value, sep.stopper.todo) == ("MB0713", False)
# Was die E-Planung nicht liefert, bleibt offen
assert sep.no.todo
tro01 = next(t for t in kontext.tros if t.id == "TRO01")
assert tro01.separators[0].sensor.todo
assert kontext.eplan_source.startswith("Mubea_positionsdraw.json")
def _lauf(tmp_path, monkeypatch, eplan: EplanDocument,
*weitere: str) -> tuple[int, Path]:
shutil.copy(FIXTURES / "mubea_tro.json", tmp_path / "mubea_tro.json")
(tmp_path / "mubea_eplan.json").write_text(eplan.to_json(), encoding="utf-8")
monkeypatch.setenv("SKEL_RESULTS", str(tmp_path))
rc = scl_skeleton.main(["--json", "mubea_tro.json", "--eplan", "mubea_eplan.json",
*weitere])
return rc, tmp_path / "mubea_FB_Main.scl"
def test_scl_skeleton_mit_eplan(tmp_path, monkeypatch):
rc, scl = _lauf(tmp_path, monkeypatch, _eplan_fuer_mubea())
text = scl.read_text(encoding="utf-8")
assert rc == 0
assert '#TRO02.stInSeparator1.stSenInSep := "DB_Inputs".Sensors["cInBG0712"];\n' in text
assert 'xOutStopper1 => "MB0713",\n' in text
assert "// E-Planung: Mubea_positionsdraw.json" in text
def test_scl_skeleton_mit_eplan_und_templates(tmp_path, monkeypatch):
"""Die Namen aus der E-Planung kommen auch in den Templates an (TRO02 = 1Sep)."""
rc, scl = _lauf(tmp_path, monkeypatch, _eplan_fuer_mubea(), "--templates")
text = scl.read_text(encoding="utf-8")
assert rc == 0
region = text[text.index("REGION TRO02 "):]
region = region[:region.index("\nEND_REGION")]
assert "Aufbau nach Template FB_ILS_MTRO_1Sep.scl.j2" in region
assert '#TRO02.stInSeparator1.stSenInSep := "DB_Inputs".Sensors["cInBG0712"];\n' in region
assert 'xOutStopper1 => "MB0713",\n' in region
def test_scl_skeleton_lehnt_fremde_zuordnung_ab(tmp_path, monkeypatch):
rc, scl = _lauf(tmp_path, monkeypatch, _eplan_fuer_mubea("anderes.csv"))
assert rc == 1
assert not scl.exists()
def test_eplan_json_rundlauf():
doc = _eplan_fuer_mubea()
zurueck = EplanDocument.from_json(doc.to_json())
assert zurueck == doc
assert json.loads(doc.to_json())["schema"] == "sps_skel-eplan-v1"
assert set(zurueck.for_layout("0012")) == {"sensor", "stopper"}
+62
View File
@@ -136,3 +136,65 @@ def test_scl_skeleton_renders_from_document(document):
# Systemgrenzen sind keine Instanzen
assert "SYS1 :" not in scl
assert "Anfang bei TRO01" in scl
# ---------------------------------------------------------------------------
# Schema v4: Slots und Staubereiche
# ---------------------------------------------------------------------------
def test_slots_aus_katalog_und_layout(document):
weiche = document.by_id("TRO01")
assert [(s.slot, s.layout_id) for s in weiche.slots] == [
("separator1", "0040"), ("switch1", "")]
assert weiche.slots[0].roles == ["sensor", "stopper", "jamSensor?", "partSensor?"]
vario = document.by_id("TRO03")
assert [s.slot for s in vario.slots] == ["separator1", "drive"]
assert vario.slots[1].layout_id == "0001" # Host = angetriebene Strecke
# Systemgrenzen haben keine Slots
assert document.by_id("SYS1").slots == []
def test_ein_staubereich_je_tro_eingang_nach_separator(document):
areas = {a.jam_id: a for a in document.jam_areas}
assert sorted(areas) == ["J0040", "J0041", "J0042"]
assert areas["J0040"].tro == "TRO01"
assert areas["J0040"].predecessors == ["SYS1"]
assert areas["J0040"].position == (1030.0, 4700.0, 1500.0)
assert areas["J0041"].predecessors == ["TRO01"]
def test_zusammenfuehrung_ein_staubereich_mehrere_vorgaenger():
analysis = Analysis(tros=[
_tro("TRO01", "1Sep", separators=["0001"], successors=["TRO03"]),
_tro("TRO02", "1Sep", separators=["0002"], successors=["TRO03"]),
_tro("TRO03", "1Sep", separators=["0003"], predecessors=["TRO01", "TRO02"]),
])
doc = TroDocument.from_analysis(analysis, source="t")
eingang = [a for a in doc.jam_areas if a.tro == "TRO03"]
assert [(a.jam_id, a.predecessors) for a in eingang] == [("J0003", ["TRO01", "TRO02"])]
def test_zwei_separatoren_zwei_eingaenge_aufteilung_offen():
analysis = Analysis(tros=[
_tro("TRO01", "2Sep2Swi", separators=["0005", "0036"], predecessors=["TRO09"]),
])
areas = TroDocument.from_analysis(analysis, source="t").jam_areas
assert [(a.jam_id, a.entry, a.predecessors) for a in areas] == [
("J0005", 1, ["TRO09"]), ("J0036", 2, [])]
assert all("offen" in a.note for a in areas)
def test_v4_round_trip_und_v3_ohne_jam_areas(document):
data = json.loads(document.to_json())
assert data["schema"] == "sps_skel-tro-v4"
assert data["jamAreas"][0]["id"] == "J0040"
assert data["tros"][0]["slots"][0]["layoutId"] == "0040"
assert TroDocument.from_json(document.to_json()) == document
# v3-Datei: keine jamAreas/slots -> beim Rendern abgeleitet
del data["jamAreas"]
for tro in data["tros"]:
del tro["slots"]
data["schema"] = "sps_skel-tro-v3"
alt = TroDocument.from_json(json.dumps(data))
assert alt.jam_areas == []
assert [a.jam_id for a in alt.jam_areas_or_derived()] == ["J0040", "J0041", "J0042"]
+126
View File
@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
"""Tests fuer lib/tro_ids.py: ID-Datei lesen/schreiben, TROs ueber die Lage
wiederfinden (doc/Konzept_Layout_Hardware_SCL.md, Abschnitt TRO-IDs)."""
from __future__ import annotations
from dataclasses import dataclass, field
import pytest
from tro_ids import (
IdEntry,
TroIds,
assign_ids,
insert_entries,
load_ids,
other_sections,
render_ids,
render_new_entries,
)
@dataclass
class FakeTro:
separators: list[str]
type_name: str = "1Sep"
label: str = ""
tro_id: str = ""
hosts: list[str] = field(default_factory=list)
ANCHORS = {
"0001": (0.0, 0.0),
"0002": (1000.0, 0.0),
"0003": (2000.0, 0.0),
"0004": (2000.0, 500.0),
}
def _tros(*seps: list[str]) -> list[FakeTro]:
return [FakeTro(separators=list(s)) for s in seps]
def test_ohne_eintraege_wird_durchnummeriert():
tros = _tros(["0001"], ["0002"], ["0003"])
result = assign_ids(tros, ANCHORS, [])
assert result.renumbered
assert [t.tro_id for t in tros] == ["TRO01", "TRO02", "TRO03"]
def test_wiederfinden_ueber_lage_auch_umbenannt():
# Neuer Export: andere TeileIds, gleiche Lage, andere Reihenfolge
anchors = {"0101": (2000.0, 0.0), "0102": (0.0, 0.0), "0103": (1000.0, 30.0)}
tros = _tros(["0101"], ["0102"], ["0103"])
entries = [IdEntry("TRO01", 0.0, 0.0), IdEntry("K0102_1", 1000.0, 0.0),
IdEntry("TRO03", 2000.0, 0.0)]
result = assign_ids(tros, anchors, entries)
assert [t.tro_id for t in tros] == ["TRO03", "TRO01", "K0102_1"]
assert not result.new and not result.missing
def test_neue_tros_naechste_freie_nummer_weggefallene_nicht_wiederverwendet():
tros = _tros(["0001"], ["0003"])
entries = [IdEntry("TRO01", 0.0, 0.0), IdEntry("TRO02", 1000.0, 0.0)]
result = assign_ids(tros, ANCHORS, entries)
assert [t.tro_id for t in tros] == ["TRO01", "TRO03"]
assert result.new == ["TRO03"]
assert [e.tro_id for e in result.missing] == ["TRO02"]
def test_ausserhalb_toleranz_gilt_als_neu():
tros = _tros(["0001"])
entries = [IdEntry("TRO01", 150.0, 0.0)]
result = assign_ids(tros, ANCHORS, entries, tolerance=100.0)
assert tros[0].tro_id == "TRO02"
assert result.missing[0].tro_id == "TRO01"
assert assign_ids(tros, ANCHORS, entries, tolerance=200.0).kept[0][0] == "TRO01"
def test_zusammengelegte_tros_naechster_eintrag_gewinnt():
# zwei Separatoren, frueher zwei TROs, jetzt ein 2Sep-TRO
tros = _tros(["0003", "0004"])
entries = [IdEntry("TRO05", 2000.0, 0.0), IdEntry("TRO06", 2000.0, 510.0)]
result = assign_ids(tros, ANCHORS, entries)
assert tros[0].tro_id == "TRO05"
assert result.merged_into == {"TRO06": "TRO05"}
def test_renumber_ignoriert_eintraege():
tros = _tros(["0001"], ["0002"])
result = assign_ids(tros, ANCHORS, [IdEntry("X", 1000.0, 0.0)], renumber=True)
assert result.renumbered
assert [t.tro_id for t in tros] == ["TRO01", "TRO02"]
def test_datei_rundlauf_und_anhaengen(tmp_path):
path = tmp_path / "anlage_tro_ids.ini"
tros = _tros(["0001"], ["0002"])
assign_ids(tros, ANCHORS, [])
path.write_text(render_ids(tros, ANCHORS, "anlage.csv")
+ "\n[separator]\n; spaeter\n", encoding="utf-8")
entries = load_ids(path)
assert [(e.tro_id, e.x, e.y) for e in entries] == [
("TRO01", 0.0, 0.0), ("TRO02", 1000.0, 0.0)]
tros = _tros(["0001"], ["0002"], ["0003"])
result = assign_ids(tros, ANCHORS, entries)
insert_entries(path, render_new_entries(tros, ANCHORS, result.new, "anlage.csv"))
assert [e.tro_id for e in load_ids(path)] == ["TRO01", "TRO02", "TRO03"]
# neuer Eintrag steht in [tro], nicht im folgenden Abschnitt
assert other_sections(path) == "[separator]\n; spaeter"
assert TroIds.load(path).exists
@pytest.mark.parametrize("inhalt, meldung", [
("[tro]\nTRO01 = 1, 2\nTRO01 = 3, 4\n", "already exists"),
("[tro]\nSYS1 = 1, 2\n", "reserviert"),
("[tro]\nTRO-1 = 1, 2\n", "SCL-Bezeichner"),
("[tro]\nTRO01 = 1\n", "erwartet"),
("[andere]\n", "fehlt"),
])
def test_fehlerhafte_datei(tmp_path, inhalt, meldung):
path = tmp_path / "x.ini"
path.write_text(inhalt, encoding="utf-8")
with pytest.raises(ValueError, match=meldung):
load_ids(path)