This commit is contained in:
Michael Stangl
2026-10-10 10:08:33 +02:00
18 changed files with 7276 additions and 212 deletions
+6 -1
View File
@@ -155,12 +155,17 @@ Details: [`Python_Scripts.md` §5b](Python_Scripts.md#5b-scl_skeletonpy--generat
| `--json` | `NAME` | `export_tro.json` | TRO-JSON input (looked up in `%SKEL_RESULTS%`). |
| `--out` | `NAME` | `<source>_FB_Main.scl` | Output SCL filename. |
| `--start` | `TRO` | — | TRO where the REGION order begins. Only needed while the flow is a closed loop without a loading/unloading station (`SYSn`). |
| `--templates` | `[DIR]` | off | Build REGIONs from the FB templates (`lib/scl_templates` if `DIR` is omitted); FB types without a template stay generic. Without it: the previous output, unchanged. See [`SCL_Templates.md`](SCL_Templates.md). |
| `--strict` | — | off | With `--templates`: exit `1` and write nothing if any FB type of the plant has no template. |
Exit codes: `0` ok · `1` input/CLI error or JSON does not match the model.
Exit codes: `0` ok (also when TROs without a template were built generically) · `1`
input/CLI error, JSON does not match the model, template error, or a missing template
with `--strict`.
```bat
bin\scl_skeleton.bat --json mubea_tro.json
bin\scl_skeleton.bat --json mubea_tro.json --start TRO01
bin\scl_skeleton.bat --json mubea_tro.json --templates
```
## 3. End-to-end example
+27
View File
@@ -473,6 +473,33 @@ so `--start` is no longer needed once the export contains a station. In the
jam-area comments they are marked "(Station, kein TRO)", and an exit station
shows up as a destination in the per-switch lists.
**Internal structure.** `scl_skeleton.py` works in two stages:
`lib/scl_context.py` derives everything from the TRO-JSON into a render context
(`build_context` → `SclFile` / `SclTro`: flow order, separator/switch/scanner
blocks, jam entries/exits, destination lists, names), and `scl_skeleton.py` only
turns that context into text (`render_context`). Every name or address is an
`SclName` (value + `todo` flag); today all of them are placeholders with
`todo=True`. This split prepares the move to parameterised templates and
per-project naming rules described in [`SCL_Templates.md`](SCL_Templates.md).
**Templates (opt-in, `--templates [DIR]`).** With this switch every TRO whose FB
has a template in `lib/scl_templates/<fbType>.scl.j2` (Jinja2) is rendered from
it; the others keep the generic text above. The header and the console summary
name which FB types came from a template. Today only the pilot
`FB_ILS_MTRO_1Sep1Swi` exists — it follows the real call site of TRO107 line for
line (priority manager `Dir2`, `stInSenJam<n>`, `xInCarouselRun1`, reference
times 100/50 ms), and every value that is still open carries
`TODO(E-Planung)`. `--strict` stops (exit `1`, no SCL) if any FB type has no
template. Without `--templates` 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
ignored) and `tests/test_scl_skeleton_golden.py` (the whole output of mubea and
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`).
## 6. `tro_catalog.py` — TRO type catalog (library, no CLI)
The single source of truth for everything needed to generate or draw a TRO
+492
View File
@@ -0,0 +1,492 @@
# SCL Generation from Parameterised Templates — Concept
**Status:** steps 1–3 of §11 done — pilot template `FB_ILS_MTRO_1Sep1Swi` usable with
`--templates`, default output unchanged · **As of:** 2026-10-09
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-v2) 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) | — | yes |
| `1Sep1Swi` | `FB_ILS_MTRO_1Sep1Swi` | yes (TRO 107) | — | yes — **first pilot** |
| `1Sep2Swi` | `FB_ILS_MTRO_1Sep2Swi` | yes (TRO 267) | — | yes |
| `1Sep_SSCC` | `FB_ILS_MTRO_1Sep_SSCC` | yes (TRO 105) | — | yes |
| `Vario` | `FB_ILS_MTRO_Vario` | yes (TRO 115) | — | yes |
| `PinStore_Auto` | `FB_ILS_MTRO_PinStore_Auto` | yes (TRO 208) | — | yes (outlier, largest) |
| `Vario_workStation` | `FB_ILS_MTRO_Vario_workStation` | yes (TRO 421, `_CallSite.scl`) | yes | yes, with interface check |
| `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_1Sep1Swi.scl.j2 pilot ✅
│ └── ... 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:
```jinja
{% 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`:
```jinja
{% 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 table is filled in for every template before it is switched on (§11, step 3).
Effect on the open-value count with `--templates`: mubea 282 → 302, HundM05 323 → 377
`TODO(E-Planung)`. More reference lines are written and every open value is now marked;
it 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
```ini
; 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 = 1 von 5 Typen (generisch: FB_ILS_MTRO_1Sep, FB_ILS_MTRO_2Sep2Swi, FB_ILS_MTRO_PinStore_Auto, FB_ILS_MTRO_Vario)
Offene Stellen = 302 (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 one by one (`1Sep`, `Vario`, `1Sep2Swi`, `1Sep_SSCC`, `Vario_workStation`, `PinStore_Auto`), each with round trip; interface check for the FB-source types | each type moved off the fallback |
| 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):**
```bat
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-v2`) |
| `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.