Files
sps_skel/doc/SCL_Templates.md
T
Samer AyadiandClaude Opus 5.5 0c69faf3db 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>
2026-10-14 09:24:20 +02:00

30 KiB
Raw Blame History

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:

  1. 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), xInCarouselRun1 and the jam sensors stInSenJam2/3 are missing; the separator times differ (600/200 ms instead of 100/50 ms); the parameter order differs.
  2. 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

  1. Templates hold structure, Python holds data. No SCL text in Python except the file header and the fallback.
  2. Keep the item-based composition. doc/HundM/suggestion.md shows that all FB_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.
  3. 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.
  4. Every value has exactly one origin, visible in the template (§6): layout, naming rule, reference default, E-Planung, or condition.
  5. 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.
  6. Fail loudly in templates. An undefined variable is an error (Jinja2 StrictUndefined), not an empty string in the SCL.
  7. 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):

  1. %SKEL_CFG%/scl_naming.ini — defaults taken from the reference plant
  2. %SKEL_CFG%/<name>_scl_naming.ini — per project, <name> = stem of the TRO-JSON (mubea_tro.json → mubea_scl_naming.ini)
  3. --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 from separators[] (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):

  1. 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 …).
  2. The test renders the template with that context.
  3. It compares the result with the REGION … END_REGION part 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 Stellen in the summary (count of TODO(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)

  1. The class-C decisions per template (§6) — starting with 1Sep1Swi.
  2. Is the reference naming (cSep{nr}.{n}, cMainTro{nr}, udt_{id}) the standard for new projects, or plant-specific?
  3. Separator timings: reference values (100/50 ms) or the current skeleton values (600/200 ms) as the default?
  4. xInRelease: TRUE as in the reference, or the "DB_TRO_TEST" test release used today?
  5. Real call sites for 2Sep1Swi, EmptyCarrBuffer, LoadingBoom — available from another plant?
  6. FB_ILS_MTRO_2Sep2Swi (Mubea): will it be created in the ILSLib, or should the two transfer points be modelled with existing types?
  7. 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.
  8. xInCarouselRun1: how do we know a TRO sits at a carousel? tro_flow would need to mark it (e.g. a carousel neighbour per TRO); today the line is always written as TODO.
  9. 1Sep2Swi (TRO267): switch 2 has no nSwitchNo and the REGION has no FC_Direction call — omission in the reference, or intended (direction elsewhere)?
  10. Meaning of stInSettings.xIsBorner (Vario) and stInSettings.bSepType.%X2 (1Sep2Swi) — always TRUE, or depending on the location?
  11. PinStore_Auto with more than one feed/exit (mubea: 2/2): the FB has one JamEntr/JamExit plus JamBypass — how are the others connected?
  12. Vario_workStation: is xPluggedScanner FALSE right in general (scanner at the work station, not at the separator)?