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>
30 KiB
SCL Generation from Parameterised Templates — Concept
Status: steps 1–3 and 5 of §11 done — templates for all 7 types with a reference call
site (1Sep, 1Sep1Swi, 1Sep2Swi, 1Sep_SSCC, Vario, Vario_workStation,
PinStore_Auto) usable with --templates, default output unchanged; the line decisions
(§6) are proposals still to be confirmed by a PLC engineer · As of: 2026-10-12
This is the plan for moving scl_skeleton.py from hard-coded SCL (Python f-strings in
the emit_* functions) to parameterised templates: the SCL structure for each FB
lives in a template file, and Python only supplies the data.
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-v4) TODO(E-Planung))
Nothing upstream of scl_skeleton changes. The TRO-JSON stays the only input, and the
CLI, output file name, TODO(E-Planung) marker and exit codes stay compatible. A
workflow that calls the chain today keeps working, and gets the new behaviour only by
passing the new optional switches.
1. Why
Today the knowledge about each FB call site is copied into Python by hand. That has two problems:
- It drifts. Compared with the real call site of
FB_ILS_MTRO_1Sep1Swi(TRO107), the generated code already differs: the priority-manager call parameters (stInPriorityManagerDir2/stOutPriorityManagerDir2,stInSettings.xPrioDir2),xInCarouselRun1and the jam sensorsstInSenJam2/3are missing; the separator times differ (600/200 ms instead of 100/50 ms); the parameter order differs. - It is in the wrong place for the people who own it. A PLC engineer can correct an SCL file; they will not edit Python f-strings.
With templates, the SCL structure is SCL again, the reference call sites become testable, and the project-specific names can be plugged in through a config (§7).
2. Reference material that exists today
doc/TRO_Katalog/scl_templates/ holds two different kinds of files. Both stay
read-only (see CLAUDE.md); they are the reference, not the templates we render.
| Kind | What it is | Use in this concept |
|---|---|---|
| Call site (43–138 lines) | A real REGION from an FB_Main: parameter lines plus the instance call, with real values (TRO107, cSep107.1, MB3306) |
Source for the parameterised template and the expected output of its round-trip test (§8.1) |
| FB source (800–1550 lines) | The full FUNCTION_BLOCK with VAR_INPUT/VAR_OUTPUT/VAR_IN_OUT |
The FB interface; every parameter a template writes must exist there (§8.2) |
Coverage per catalog type (lib/tro_catalog.py):
| Catalog type | FB | Call site | FB source | Template possible |
|---|---|---|---|---|
1Sep |
FB_ILS_MTRO_1Sep |
yes (TRO 100) | — | ✅ done |
1Sep1Swi |
FB_ILS_MTRO_1Sep1Swi |
yes (TRO 107) | — | ✅ done (first pilot) |
1Sep2Swi |
FB_ILS_MTRO_1Sep2Swi |
yes (TRO 267) | — | ✅ done |
1Sep_SSCC |
FB_ILS_MTRO_1Sep_SSCC |
yes (TRO 105) | — | ✅ done |
Vario |
FB_ILS_MTRO_Vario |
yes (TRO 115) | — | ✅ done |
PinStore_Auto |
FB_ILS_MTRO_PinStore_Auto |
yes (TRO 208) | — | ✅ done (outlier, largest) |
Vario_workStation |
FB_ILS_MTRO_Vario_workStation |
yes (TRO 421, _CallSite.scl) |
yes | ✅ done (interface check §8.2 still open) |
2Sep1Swi |
FB_ILS_MTRO_2Sep1Swi |
no | yes | from interface only — needs a real call site |
EmptyCarrBuffer |
FB_EmptyCarrBuffer |
no | yes | from interface only — needs a real call site |
LoadingBoom |
FB_LoadingBoom_INBOUND |
no | yes | from interface only — needs a real call site |
2Sep2Swi |
FB_ILS_MTRO_2Sep2Swi |
no | no | no — FB does not exist yet (Mubea needs it) |
So 7 types can be done with a test against real code. 3 can only be checked against the
interface until someone supplies a real call site. 2Sep2Swi stays on the fallback (§9).
3. Design principles
- Templates hold structure, Python holds data. No SCL text in Python except the file header and the fallback.
- Keep the item-based composition.
doc/HundM/suggestion.mdshows that allFB_ILS_MTRO_*share one skeleton and differ only in how many separators, switches, directions and jam areas they repeat. The templates mirror that: shared macros for the repeated blocks, one thin template per FB that arranges them in that FB's order. - Never invent a value. Anything without a source stays
TODO(E-Planung), as it does today. A plausible-looking wrong address is worse than an obvious gap. - Every value has exactly one origin, visible in the template (§6): layout, naming rule, reference default, E-Planung, or condition.
- The TRO-JSON is the contract. The render context is built only from
model.TroDocument; templates never read the CSV, the DXF or the catalog directly. - Fail loudly in templates. An undefined variable is an error (Jinja2
StrictUndefined), not an empty string in the SCL. - Additive migration. The existing
emit_*code stays as the fallback until every type has a template; with no new switch the output is byte-identical to today.
4. Files and folders
sps_skel/
├── doc/TRO_Katalog/scl_templates/*.scl reference, read-only (unchanged)
├── lib/
│ ├── scl_skeleton.py CLI + order + context + render (changed)
│ ├── scl_context.py NEW: TroDocument -> render context (pydantic)
│ ├── scl_naming.py NEW: reads the naming config, resolves names
│ └── scl_templates/ NEW: the parameterised templates
│ ├── _macros.scl.j2 shared blocks (separator, switch, jam, call, direction)
│ ├── FB_ILS_MTRO_1Sep.scl.j2 ✅
│ ├── FB_ILS_MTRO_1Sep1Swi.scl.j2 pilot ✅
│ ├── FB_ILS_MTRO_1Sep2Swi.scl.j2 ✅
│ ├── FB_ILS_MTRO_1Sep_SSCC.scl.j2 ✅
│ ├── FB_ILS_MTRO_Vario.scl.j2 ✅
│ ├── FB_ILS_MTRO_Vario_workStation.scl.j2 ✅
│ └── FB_ILS_MTRO_PinStore_Auto.scl.j2 ✅ (one per FB, named exactly like fbType)
├── cfg/
│ ├── scl_naming.ini NEW: default naming rules (from the reference plant)
│ └── <project>_scl_naming.ini NEW, optional: per-project override
└── tests/
├── test_scl_templates.py round trip against the reference call sites ✅
├── test_scl_skeleton_golden.py whole-file regression on mubea/HundM05 ✅
├── test_scl_context.py the derivations one by one ✅
└── fixtures/scl/
├── <name>_tro.json fixed inputs (mubea, HundM05)
├── <name>_FB_Main.scl expected output, generic
└── <name>_FB_Main_templates.scl expected output, with --templates
The file frame (header, VAR list, REGION loop) stays in Python (render_context) for
now; only the REGIONs come from templates. A _file.scl.j2 is only worth it once every
REGION does. The reference values of a round trip are set in the test itself
(tests/test_scl_templates.py) instead of a JSON fixture, so new context fields need no
fixture update.
Lookup: the template file name is the TRO's fbType from the catalog
(FB_ILS_MTRO_1Sep1Swi → FB_ILS_MTRO_1Sep1Swi.scl.j2). No second mapping table, so the
catalog stays the only place that links a type to its FB.
Dependency: jinja2 >= 3.1 is added to requirements.txt.
5. Template structure
5.1 Jinja2 settings
| Setting | Value | Reason |
|---|---|---|
undefined |
StrictUndefined |
A misspelled variable stops the run instead of writing empty SCL |
trim_blocks, lstrip_blocks |
True |
{% for %} lines leave no blank lines/indent in the SCL |
keep_trailing_newline |
True |
Stable files, clean diffs |
autoescape |
False |
SCL, not HTML |
SCL syntax does not clash with Jinja2: SCL uses #name, { S7_... } and //, never
{{, {% or {#. The one thing to avoid is writing {# directly (e.g. {#TRO…).
5.2 Shared macros (_macros.scl.j2)
One macro per repeated block. Each takes the TRO context and the block's own data:
{% macro separator(t, s) %}
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.nSeparatorNo := "{{ s.no }}";{{ s.no_note }}
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.tDelayToNextItem := "TimeForSepWaiting_Short";
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.tTrailingTime := {{ d.sep_trailing }}; // {{ TODO }}: pruefen
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.tHandlingTime := {{ d.sep_handling }}; // {{ TODO }}: pruefen
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.tJam := "TimeForSepJam";
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.xPluggedJam := TRUE;
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.xPluggedPart := FALSE;
#{{ t.id }}.stInSeparator{{ s.n }}.Settings.xPluggedScanner := {{ 'TRUE' if t.has_scanner else 'FALSE' }};
#{{ t.id }}.stInSeparator{{ s.n }}.stSenInSep := "DB_Inputs".Sensors["{{ s.sensor }}"];{{ s.sensor_note }}
{% endmacro %}
{% macro jam(kind, j) %}
arInOutJam{{ kind }}{{ j.n }} := {{ j.db }}.arCarrier, // {{ j.note }}
stInOutJam{{ kind }}{{ j.n }} := {{ j.db }}.stData,
{% endmacro %}
s.no is either a resolved name (cSep107.1) or the placeholder cSep_____, and
s.no_note is then // TODO(E-Planung) or empty. Python decides both (§7.3); the
template only places them.
5.3 One template per FB
Each FB template is the reference call site with values replaced by variables and
repeated parts replaced by loops. Sketch for FB_ILS_MTRO_1Sep1Swi.scl.j2:
{% import "_macros.scl.j2" as m %}
REGION {{ t.region_title }}
{{ m.comments(t) }}
{% for s in t.separators %}{{ m.separator(t, s) }}{% endfor %}
{% for w in t.switches %}{{ m.switch(t, w) }}{% endfor %}
#{{ t.id }}.stInSettings.xPrioDir2 := {{ t.prio_dir2 }};{{ t.prio_dir2_note }}
#{{ t.id }}(stInPriorityManagerDir2 := "DB_Prozessdaten".stPriorityManager.udt_{{ t.id }}.TroIn,
stOutPriorityManagerDir2 => "DB_Prozessdaten".stPriorityManager.udt_{{ t.id }}.TroOut,
nInMainTroNo := "{{ t.main_tro }}",{{ t.main_tro_note }}
xInSftyOk := {{ t.safety }},{{ t.safety_note }}
xInAllRdyToStart := TRUE,
{% if t.carousel_run %}
xInCarouselRun1 := {{ t.carousel_run }},
{% endif %}
xInRelease := {{ t.release }},
...
{% for j in t.jam_entries %}{{ m.jam("Entr", j) }}{% endfor %}
{% for j in t.jam_exits %}{{ m.jam("Exit", j) }}{% endfor %}
stInOutHMI := "DB_Interface_HMI".stTRO.{{ t.id }},
nOutStateLast => "DB_GLOB_TroState".arTroState["{{ t.main_tro }}"],
{% for s in t.separators %}
xOutStopper{{ s.n }} => "{{ s.stopper }}"{{ "," if not loop.last or t.switches else ");" }}{{ s.stopper_note }}
{% endfor %}
...);
{{ m.direction(t) }}
END_REGION
The order of the lines in the template is the parameter order of the reference call
site. That replaces the order that is hard-coded in emit_call today.
5.4 File frame and whitespace
The file header (source, timestamp, start reason, open-TODO note), FUNCTION_BLOCK "FB_Main", the VAR list of all instances and the REGION loop are written by
render_context in Python. Each REGION is rendered from the TRO's own template and
inserted there; TROs without a template get the generic text (§9). With --templates
the header names which FB types came from a template and which did not.
Templates do not use Jinja whitespace control ({%- … -%}) except inside the
one-line macros todo/note. Instead the renderer drops trailing blanks and collapses
runs of blank lines (SclTemplates.render). That keeps the templates readable as SCL and
the indentation intact.
Rule for every template: each line that contains a placeholder carries the
TODO(E-Planung) marker — also the second line of a pair such as
stInOutJamExit<n> … .stData. tests/test_scl_templates.py checks this.
6. Where each value comes from
Every line in a template belongs to exactly one of these classes. The class decides who supplies the value and what happens when it is missing.
| Class | Source | Example | If missing |
|---|---|---|---|
| L — layout | TRO-JSON (TroDocument) |
#TRO107, number of separators/switches, jam entries/exits per neighbour, destination list per switch exit, nTo1Destinations (single switch) |
cannot be missing — the JSON is validated |
| N — naming rule | naming config (§7) | cSep107.1, cSwi107.1, cMainTro107, udt_TRO107 |
TODO(E-Planung) placeholder |
| R — reference default | the reference call site, written in the template or in [defaults] |
tTrailingTime t#100ms, tJam "TimeForSepJam", xPluggedJam TRUE |
always present; marked pruefen where plant-specific |
| E — E-Planung | I/O list (not available yet) | cInBG3306, MB3306, MA…, safety zone, DB_JamArea0102.stJam0102_1 |
TODO(E-Planung) placeholder |
| C — condition | topology flag in the context | xInCarouselRun1 only next to a carousel; stInSenJam2/3 only with extra jam sensors |
line left out, or written as a TODO |
Class C is the real design work, not the conversion. Each reference call site is one
concrete location and contains lines that only exist because of that location. They have
to be decided once per template, together with a PLC engineer, and recorded in the
template as a comment. For the pilot 1Sep1Swi (TRO107):
| Line in reference | Implemented in the pilot | Context field | Status |
|---|---|---|---|
stInSenJam2, stInSenJam3 |
one per jam exit, sensor TODO(E-Planung) |
jam_exits[].sensor |
to confirm |
stInSettings.xPrioDir2 := TRUE |
written as TRUE, marked TODO(E-Planung) (priority is a planning decision) |
prio_dir2 |
to confirm |
stIn/OutPriorityManagerDir2 |
always; UDT udt_____ + TODO until the naming rule udt_{id} (step 4) |
priority_udt |
to confirm |
xInCarouselRun1 := #fbCarousel0102.xOutConveyorFullSpeed |
always written, marked TODO "nur am Kreisel, sonst Zeile loeschen". The proposed condition (a connection of kind umlauf) does not work: tro_flow emits bahn/normal/weiche/transfer in practice, umlauf never occurs in mubea or HundM05, so the TRO-JSON cannot tell whether a TRO sits at a carousel. Needs either a carousel flag from tro_flow or a manual decision. |
carousel_run |
to confirm |
xInRelease := TRUE (generic: "DB_TRO_TEST".<id>) |
reference value TRUE |
— (literal) | to confirm |
stInOutMachineState := #stInOutControlUnitCabinetOF1 |
placeholder #stInOutControlUnitCabinet + TODO (cabinet is plant-specific) |
machine_state |
to confirm |
separator times t#100ms / t#50ms |
reference values, marked pruefen; tSenFree/tSenWait of the generic path are not written (not in the reference) |
— (literal) | to confirm |
nTo1Destinations := 3 (TRO107 has 2 exits) |
number of exits, marked pruefen — the reference suggests it counts destinations, not exits (§13) |
switches[].to1_destinations |
open |
stInPriorityManager.xReleaseOk (generic only) |
not written — not in the reference | — | to confirm |
The same decisions were made for the other six templates (step 5). Each template's header comment lists them in full; this is the summary of what deviates from "copy the 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 = 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 — 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
area (handover jams use their own variables), jam_in/jam_out (same objects as the
first entry/exit), error_quit, automatic, is_border, correction_station, and the
blocks sscc, work_station, store (with one SclStoreLine per lane). The generic
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). 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).
7. Naming config
7.1 Purpose
Some names follow a rule in a given plant (cSep107.1 = separator 1 of TRO 107). Others
do not (cInBG3306 is whatever E-Planung assigned). The naming config holds the rules,
so that:
- rule-based names are generated instead of left as TODOs,
- a project with a different naming scheme changes a file, not code,
- anything without a rule stays
TODO(E-Planung), exactly as today.
7.2 File and lookup
INI, like connect.ini and the other files in %SKEL_CFG%. Lookup order (later wins):
%SKEL_CFG%/scl_naming.ini— defaults taken from the reference plant%SKEL_CFG%/<name>_scl_naming.ini— per project,<name>= stem of the TRO-JSON (mubea_tro.json→mubea_scl_naming.ini)--naming FILE— explicit file
A key that is absent or empty in all of them means "no rule" → placeholder +
TODO(E-Planung).
7.3 Syntax
; scl_naming.ini - Namensregeln fuer das FB_Main-Geruest
; Platzhalter: {id} TRO-ID (TRO107), {nr} Nummernteil (107), {n} laufende Nummer
; des Bauteils im TRO (1, 2 ...), {sep} Separatornummer aus dem Layout
[names]
separator_no = cSep{nr}.{n}
switch_no = cSwi{nr}.{n}
main_tro = cMainTro{nr}
priority_udt = udt_{id}
; ohne Regel -> TODO(E-Planung)
sensor_sep =
stopper =
switch_valve =
safety =
jam_area =
[defaults]
sep_trailing = t#100ms
sep_handling = t#50ms
release = TRUE
{nr}is the numeric part of the ID as written in the JSON (TRO01→01,TRO107→107); padding is not changed.{sep}is the separator's layout number fromseparators[](0040) — useful if a project names separators after the layout instead of the TRO.- An unknown placeholder in a rule is an error (exit 1), not a silent literal.
7.4 Later: E-Planung data
When an I/O list exists in machine-readable form, a further section can point to it
(e.g. [eplanung] file = …csv) and fill class E values per separator/switch. That is
a separate step; the config format leaves room for it but this concept does not build it.
8. Tests
All under tests/, run with python -m pytest tests/ like the existing ones.
8.1 Round trip per template (the main safety net)
For each template that has a reference call site (tests/test_scl_templates.py):
- The test builds the context for a TRO with the reference topology and sets the
real values of the reference (ID
TRO107,cSep107.1,cInBG3306,MB3306, the real jam DBs,carousel_run…). - The test renders the template with that context.
- It compares the result with the
REGION … END_REGIONpart of the reference file, normalised: whitespace collapsed, comments removed, case kept.
If they match, the parameterisation lost nothing: same lines, same order, same values. When someone later replaces a reference file with a newer call site, this test shows exactly where the template must follow.
8.2 Interface check (for FBs with source)
For Vario_workStation, 2Sep1Swi, EmptyCarrBuffer, LoadingBoom: parse
VAR_INPUT/VAR_OUTPUT/VAR_IN_OUT of the FB source and check that every parameter
name the template writes exists there, with the right direction (:= for in/in-out,
=> for out). This covers the types that have no call site.
8.3 Golden files for the whole output
tests/test_scl_skeleton_golden.py renders mubea_tro.json and HundM05_tro.json
(copied into tests/fixtures/) and compares with checked-in expected SCL, with the
timestamp line masked.
- During migration: the golden files are today's output, so switching the generator internals without enabling templates must give byte-identical files.
- When a template is switched on: the diff in the golden file is reviewed once (it is the intended change) and then committed as the new golden file.
8.4 Naming and context
Unit tests for scl_naming.py (placeholders, lookup order, empty = TODO, unknown
placeholder = error) and scl_context.py (jam numbering, destination lists, flags such
as carousel_run from connection kinds).
9. Fallback and errors
| Situation | Behaviour | Exit |
|---|---|---|
Template exists for the TRO's fbType |
render template | 0 |
No template (e.g. 2Sep2Swi) |
render with the current emit_* code; header lines // Aus Template : … / // Generisch : … (kein Template - von Hand pruefen) and an entry in the console summary |
0 |
No template and --strict |
stop, write nothing | 1 |
| Template uses an undefined variable / syntax error | stop, write nothing, show template file and line | 1 |
| Naming rule with unknown placeholder | stop, write nothing | 1 |
| Invalid TRO-JSON | unchanged from today | 1 |
With --templates the console summary gets a Templates line (implemented); the
Namensregeln line follows with step 4. Real output for mubea today:
Templates = 4 von 5 Typen (generisch: FB_ILS_MTRO_2Sep2Swi)
Offene Stellen = 482 (Marker 'TODO(E-Planung)')
10. CLI — additive only
Existing switches (--json, --out, --start) and defaults are unchanged.
| New switch | Argument | Default | Effect | Status |
|---|---|---|---|---|
--templates |
[DIR] (optional) |
off; without DIR: lib/scl_templates |
Render REGIONs from the FB templates; types without template stay generic | ✅ |
--strict |
— | off | With --templates: exit 1, write nothing, if any FB type of the plant has no template |
✅ |
--naming |
FILE |
lookup per §7.2 | Explicit naming config | step 4 |
Templates are opt-in (--templates) until step 6. Without the switch the output is
byte-identical to before (golden files). When templates become the default in step 6, a
--no-templates switch keeps the old path for one release.
11. Migration plan
Each step ends with all tests green and is usable on its own.
| Step | Work | Done when |
|---|---|---|
| 1 ✅ | Golden-file tests on today's output (mubea, HundM05) — tests/test_scl_skeleton_golden.py |
today's SCL is pinned |
| 2 ✅ | scl_context.py: build the render context from TroDocument; emit_* read from it — tests/test_scl_context.py |
golden files byte-identical (also checked on dematic and showroom, with and without --start) |
| 3 ✅ | Pilot FB_ILS_MTRO_1Sep1Swi.scl.j2 + _macros; switches --templates, --strict; class-C table (§6) filled with proposals — still to be confirmed by a PLC engineer; round-trip test |
round trip against TRO107 passes; only the 1Sep1Swi REGIONs (+ header) differ from the generic golden files |
| 4 | scl_naming.py + cfg/scl_naming.ini; switch --naming |
naming tests pass; Mubea renders with names instead of TODOs where a rule exists |
| 5 ✅ | Remaining call-site templates (1Sep, Vario, 1Sep2Swi, 1Sep_SSCC, Vario_workStation, PinStore_Auto), each with round trip against its reference (PinStore_Auto: the parts the reference abbreviates are left out of the comparison); decisions in §6 — still to be confirmed by a PLC engineer. Interface check (§8.2) for Vario_workStation still open |
every round trip passes; HundM05 fully from templates, mubea except 2Sep2Swi |
| 6 | Templates on by default; new --no-templates keeps the old path for one release |
golden files updated and reviewed; one import into TIA Portal (as for Mubea) |
| 7 | Remove emit_* once every type in use has a template (2Sep2Swi needs a call site first) |
fallback only for types without FB |
Docs touched along the way: doc/Python_Scripts.md §5b, doc/Commandlines.md §2.5,
CLAUDE.md (roadmap), this file.
12. What your workflow can rely on
For a workflow that runs export CSV → SCL:
Calls (unchanged):
bin\tro_flow.bat --file <name>.csv --json
bin\scl_skeleton.bat --json <name>_tro.json [--templates [DIR]] [--strict] [--naming <file>]
--templates and --strict exist today; --naming follows with step 4.
Inputs:
| File | Where | Who writes it |
|---|---|---|
<name>.csv |
%SKEL_DATA% |
dxfmakros EXPORTCSV |
<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 |
Outputs: %SKEL_RESULTS%/<name>_FB_Main.scl (same name as today).
Exit codes: 0 ok (also when some TROs used the fallback) · 1 input, template or
naming error, or a missing template with --strict. tro_flow keeps its own codes (3
= TRO validation errors → stop the workflow before scl_skeleton).
Quality gates a workflow can check:
- exit code of each step;
Offene Stellenin the summary (count ofTODO(E-Planung)), e.g. track it per run;Templates = x von y— any TRO on the fallback needs a manual look;grep "TODO(E-Planung)"in the SCL lists every open value with its line.
What stays manual: class E values (sensor/actuator addresses, safety signals,
jam-area DBs) until an E-Planung export exists, and the pruefen timings.
13. Open questions (to clarify with the PLC engineers)
- The class-C decisions per template (§6) — starting with
1Sep1Swi. - Is the reference naming (
cSep{nr}.{n},cMainTro{nr},udt_{id}) the standard for new projects, or plant-specific? - Separator timings: reference values (100/50 ms) or the current skeleton values (600/200 ms) as the default?
xInRelease:TRUEas in the reference, or the"DB_TRO_TEST"test release used today?- Real call sites for
2Sep1Swi,EmptyCarrBuffer,LoadingBoom— available from another plant? FB_ILS_MTRO_2Sep2Swi(Mubea): will it be created in the ILSLib, or should the two transfer points be modelled with existing types?nTo1Destinations: TRO107 has 2 exits but the value 3 ("Info noetig fuer HMI"). Does it count the destinations behind the switch rather than its exits? If so, which destinations — the per-exit target lists in the skeleton could supply them.xInCarouselRun1: how do we know a TRO sits at a carousel?tro_flowwould need to mark it (e.g. a carousel neighbour per TRO); today the line is always written as TODO.1Sep2Swi(TRO267): switch 2 has nonSwitchNoand the REGION has noFC_Directioncall — omission in the reference, or intended (direction elsewhere)?- Meaning of
stInSettings.xIsBorner(Vario) andstInSettings.bSepType.%X2(1Sep2Swi) — alwaysTRUE, or depending on the location? PinStore_Autowith more than one feed/exit (mubea: 2/2): the FB has oneJamEntr/JamExitplusJamBypass— how are the others connected?Vario_workStation: isxPluggedScanner FALSEright in general (scanner at the work station, not at the separator)?