- model: Slot (separator/switch/scanner/drive + Rollen), JamArea (J<Separator>, Eingang eines TRO mit allen Vorgaengern), derive_slots, derive_jam_areas; Schema sps_skel-tro-v4, v3-Dateien werden weiter gelesen - scl_context: ein JamEntr je eigenem Staubereich statt je Vorgaenger (wie UH01-UH05), Ausgaenge nennen den Staubereich des Nachfolgers - Golden Files neu erzeugt, Tests fuer Slots/Staubereiche, Doku nachgezogen Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
493 lines
25 KiB
Markdown
493 lines
25 KiB
Markdown
# 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-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) | — | 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-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.
|