Doku dazu

This commit is contained in:
2026-07-08 16:04:32 +02:00
parent 8bc52d866c
commit 9663daf75b
2 changed files with 317 additions and 0 deletions
+317
View File
@@ -0,0 +1,317 @@
# awl2scl — AWL/STL → SCL Konvertierung
Werkzeuge zur (halb-)automatischen Umwandlung der Siemens-AWL/STL-Bausteine
dieses Projekts nach SCL für den Umstieg auf TIA Portal V20.
- **`lib/stlxml2awl.py`** — wandelt TIA-Openness-XML (SimaticML `StatementList`)
in lesbaren AWL-Text (`.awl`) um und schätzt je Baustein eine Aufwands­kategorie
A/B/C. Aufruf über `bin\xmls2awl.bat`.
- **`lib/awl2scl.py`** — wandelt `.awl`-Dateien mechanisch nach `.scl` um, optional
mit Lesbarkeits- und Parameter-Optimierung. Aufruf über `bin\awl2scl.bat` bzw.
`bin\awl2scl_deep.bat`.
- **`lib/sclopt.py`** — SCL-Zwischenstruktur (IR) und die `--improve-gotos`-Durchläufe.
- **`lib/paramize.py`** — die `--globals2params`-Umwandlung samt persistenter
toChange-Liste für die Aufrufer.
> **Wichtig:** Das Werkzeug erzeugt syntaktisch zielgerichteten, mechanisch
> verhaltensgleichen SCL-Text — **keine** in TIA verifizierte Korrektheit. Die
> Freigabe (Kompilat in TIA Portal V20, PLCSIM-Vergleich AWL↔SCL) bleibt ein
> manueller Schritt pro Baustein (siehe `SCL-Export/README.md`).
## Inhaltsverzeichnis
- [Datenfluss](#datenfluss)
- [Kategorien A/B/C](#kategorien-abc)
- [`bin`-Skripte](#bin-skripte)
- [CLI-Optionen](#cli-optionen-libawl2sclpy)
- [`--improve-gotos`](#--improve-gotos)
- [`--globals2params`](#--globals2params)
- [Schutz handgeschriebener Dateien](#schutz-handgeschriebener-dateien)
- [Log & Ausgaben](#log--ausgaben)
## Datenfluss
```
TIA-Openness-XML lib/stlxml2awl.py lib/awl2scl.py
(SimaticML StatementList) ───────────────► *.awl ───────────────► *.scl
(bin\xmls2awl.bat) (bin\awl2scl.bat /
bin\awl2scl_deep.bat)
```
`awl2scl.py` liest **zwei** `.awl`-Formate transparent ein:
1. **gerendert** — von `stlxml2awl.py` erzeugt, Kopf `=== FC 181: Name [STL] ===`.
2. **nativ** — klassische STEP7/TIA-AWL-Quelle, Kopf `FUNCTION_BLOCK "Name"`,
`BEGIN`/`NETWORK`, deutsche Mnemonik (`U`/`UN`/`O`/`SPA`/`SPB`/`AUF` …).
## Kategorien A/B/C
Die Kategorie wird je Datei **selbst** bestimmt (nicht aus einem Log übernommen),
mit denselben Kriterien wie in `stlxml2awl.py` (Namensmuster aus
`SCL-Export/README.md` + Sprung-/Pointer-Analyse):
| Kat. | Bedeutung | Behandlung |
|------|-----------|------------|
| **A** | einfache Verknüpfungslogik, mechanisch übersetzbar | wird übersetzt |
| **B** | Ablaufsteuerung / viele Sprungmarken | nur mit `--category B` |
| **C** | Pointer-/Register-Indirektzugriffe | nur mit `--category C` |
Bausteine, die trotz passender Kategorie ein technisch nicht sicher übersetzbares
Konstrukt enthalten (Timer/Zähler-Mnemonik, `OPN DB[var]`, registerindirekte
Operanden, unbekannte Sprünge), werden **nicht** übersetzt, sondern im Log unter
**„Manuelle Prüfung nötig"** mit Grund gelistet.
## `bin`-Skripte
| Skript | Zweck |
|--------|-------|
| `bin\xmls2awl.bat` | XML → `.awl` (+ Kategorie-Log) |
| `bin\awl2scl.bat` | `.awl``.scl`, **mechanisch** (Argumente durchgereicht) |
| `bin\awl2scl_deep.bat` | `.awl``.scl` **mit** `--improve-gotos --globals2params` |
Beispiele:
```bat
REM Nur Kategorie A, mechanisch nach SCL-Export\<Station>\
bin\awl2scl.bat --dir . --category A
REM Tiefenlauf: Kontrollfluss aufräumen + Globale zu Parametern
bin\awl2scl_deep.bat --dir . --category A
```
## CLI-Optionen (`lib/awl2scl.py`)
| Option | Wirkung |
|--------|---------|
| `<datei.awl> …` | einzelne Dateien |
| `--dir <verz>` | rekursiv alle `.awl` |
| `--category A[,B,C]` | zu übersetzende Kategorien (Default `A`) |
| `--target-dir <pfad>` | Zielordner (Default `$PV_SCL_EXPORT` bzw. `<Projekt>\SCL-Export`) |
| `--improve-gotos` | Kontrollfluss aufräumen (verlustfrei) |
| `--globals2params` | globale Merker → Parameter + toChange für Aufrufer |
| `--force` | vorhandene **generierte** `.scl` überschreiben |
| `--tochange <pfad>` | toChange-Liste (Default `<Projekt>\data\globals2params_tochange.json`) |
| `--log <pfad>` | Log-Datei |
`--improve-gotos` und `--globals2params` implizieren `--force` (Regenerierung).
Der Zielpfad je Baustein ist `<target-dir>\<Station>\<Name>.scl`.
---
## `--improve-gotos`
Räumt den mechanisch erzeugten Kontrollfluss **verlustfrei** auf. Die Übersetzung
läuft dazu über eine typisierte Zwischenstruktur (IR, `lib/sclopt.py`); ohne den
Schalter ist die Ausgabe byte-identisch zur rein mechanischen Variante. Durchläufe:
- **`merge_adjacent_ifdo`** — aufeinanderfolgende `IF`-Blöcke mit identischer
Bedingung zusammenfassen (aus dem AWL-Idiom `S x; S y; JC L` entsteht sonst
dieselbe Bedingung mehrfach).
- **`guard_skip_to_if`** — `IF c THEN GOTO L; … ; L:``IF NOT c THEN … END_IF`.
- **`synth_case`** / **`synth_case_across_networks`** — Verteilung auf **eine**
Variable gegen Konstanten → `CASE` (auch über REGION-Grenzen hinweg; die
REGION-Titel werden zu Branch-Kommentaren).
- **`drop_unused_labels`** — nicht mehr angesprungene Marken entfernen.
- **`normalize_bool`** — überflüssige Klammern und `NOT`-Verschachtelungen
vereinfachen (`NOT (a <> b)``a = b`).
Nicht sauber auflösbare Sprünge (Rücksprünge, gemeinsame Sammel-Austrittsmarken)
bleiben als strukturierter `GOTO` erhalten — die Passes greifen nur beim sicher
erkannten Muster.
### Beispiel 1 — `HWtoAMRZielFC` (FC 181): Guard-Sprünge → `CASE`
Zwei Netzwerke prüfen dieselbe Variable `"MW190"` gegen Konstanten. Mechanisch:
```scl
REGION Schleuse 3004: (BST 3)
IF ("MW190" <> 3004) THEN GOTO X3004E; END_IF;
"M180.6" := TRUE;
RETURN;
X3004E: ;
END_REGION
REGION Schleuse 3565: (BST 4)
IF ("MW190" <> 3565) THEN GOTO X3565E; END_IF;
"M180.6" := TRUE;
RETURN;
X3565E: ;
END_REGION
```
Mit `--improve-gotos` (Guard→IF + netzwerkübergreifende `CASE`-Synthese):
```scl
REGION CASE "MW190"
CASE "MW190" OF
3004: // Schleuse 3004: (BST 3)
"M180.6" := TRUE;
3565: // Schleuse 3565: (BST 4)
"M180.6" := TRUE;
END_CASE;
END_REGION
```
### Beispiel 2 — `MBlinken` (FB): mehrfache Bedingung zusammenfassen
Das AWL setzt mit *einer* Verknüpfung drei Merker und springt (`R x; R y; R z; SPB L`).
Mechanisch entsteht viermal dieselbe Bedingung:
```scl
REGION Anzeige bei Error 1-10 1-10 mal blinken lassen
IF (((((((((NOT (#Error_1) AND NOT (#Error_2)) AND …) AND NOT (#Error_10))) THEN #hAnzeige := FALSE; END_IF;
IF (((((((((NOT (#Error_1) AND NOT (#Error_2)) AND …) AND NOT (#Error_10))) THEN #Flanke_Ausschalttakt := FALSE; END_IF;
IF (((((((((NOT (#Error_1) AND NOT (#Error_2)) AND …) AND NOT (#Error_10))) THEN #Kein_Blinken := FALSE; END_IF;
IF (((((((((NOT (#Error_1) AND NOT (#Error_2)) AND …) AND NOT (#Error_10))) THEN GOTO NoAn; END_IF;
```
Mit `--improve-gotos` (`merge_adjacent_ifdo` + Klammer-Normalisierung):
```scl
REGION Anzeige bei Error 1-10 1-10 mal blinken lassen
IF ((((((((NOT (#Error_1) AND NOT (#Error_2)) AND …) AND NOT (#Error_9)) AND NOT (#Error_10) THEN
#hAnzeige := FALSE;
#Flanke_Ausschalttakt := FALSE;
#Kein_Blinken := FALSE;
GOTO NoAn;
END_IF;
```
(Das verbleibende `GOTO NoAn` zeigt auf eine gemeinsame Austrittsmarke und bleibt
bewusst als strukturierter Sprung erhalten.)
---
## `--globals2params`
Baut **globale Merker-Zugriffe** eines Bausteins in echte Parameter um und
verwaltet eine persistente **toChange-Liste** (`data\globals2params_tochange.json`)
für die Anpassung der Aufrufer — auch solcher, die noch als `.awl` (nicht
konvertiert) vorliegen.
**Ablauf je Baustein** (`lib/paramize.py`):
1. **Richtungsanalyse:** jedes Global (`"MW190"`, `"M180.6"`, `Mx.y`, `Ex`, `Ax` …)
wird als `Input` (nur gelesen), `Output` (nur geschrieben) oder `InOut` (beides,
bzw. uneindeutig — konservativ) eingestuft.
2. **Parametername** deterministisch aus dem Global-Token (`"MW190"``MW190`,
`"M180.6"``M180_6`), Kommentar `// war "…"`. Semantische Umbenennung bleibt manuell.
3. **Rumpf umschreiben:** jeder Global-Zugriff wird `#Param`.
4. **Aufrufer finden** (direkte `CALL "B"` und indirekte `UC/CC FC[..]`) über **alle**
`.awl`-Quellen und je Aufrufer den parameterbasierten Aufruf vormerken.
**Verhaltenserhaltend by construction:** Jeder Aufrufer forwardet exakt dasselbe
Global an den Parameter (`Input` mit `:=`, `Output`/`InOut` mit `=>`), solange er
das Global vor/nach dem Aufruf wie bisher nutzt.
**Grenze:** indirekte Aufrufe (`UC/CC FC[var]`, z.B. die CASE-Verteiler in
`MHMOmniSchleuse`) können nicht automatisch umgeschrieben werden → in toChange als
`manual` markiert.
### Beispiel 1 — `HWtoAMRZielFC`: globale Merker werden Parameter
`--globals2params` (hier mit `--improve-gotos` kombiniert) erzeugt aus dem Baustein,
der `"MW190"` liest und `"M180.6"` schreibt:
```scl
FUNCTION "HWtoAMRZielFC" : Void
VAR_INPUT
MW190 : Word; // war "MW190"
END_VAR
VAR_OUTPUT
M180_6 : Bool; // war "M180.6"
END_VAR
BEGIN
REGION CASE #MW190
CASE #MW190 OF
3004: // Schleuse 3004: (BST 3)
#M180_6 := TRUE;
3565: // Schleuse 3565: (BST 4)
#M180_6 := TRUE;
END_CASE;
END_REGION
```
`HWtoAMRZielFC` wird nur **indirekt** (über `UC FC[..]`-Verteiler) aufgerufen —
daher ein `manual`-Eintrag in der toChange-Liste:
```json
"=A01+UH07-KF00|DatenIn": {
"path": "…\\DatenIn.awl",
"changes": [
{ "func": "HWtoAMRZielFC",
"call": "// MANUELL: indirekter Aufruf von HWtoAMRZielFC auf Parameter umstellen",
"status": "manual" }
]
}
```
### Beispiel 2 — direkter Aufrufer wird automatisch umgeschrieben (2-Phasen)
Ein Baustein `Sub_Callee`, der `"MW50"` liest und `"M60.0"` schreibt, und ein
direkter Aufrufer `Top_Caller` (`CALL FC "Sub_Callee"`).
**Lauf 1**`Sub_Callee` wird parametrisiert; der Aufruf-Rewrite wird als
`pending` in `data\globals2params_tochange.json` vermerkt:
```scl
FUNCTION "Sub_Callee" : Void
VAR_INPUT
MW50 : Word; // war "MW50"
END_VAR
VAR_OUTPUT
M60_0 : Bool; // war "M60.0"
END_VAR
BEGIN
REGION test
#M60_0 := (#MW50 >= 100);
END_REGION
```
```json
"_|Top_Caller": {
"changes": [
{ "func": "Sub_Callee",
"call": "\"Sub_Callee\"(M60_0 => \"M60.0\", MW50 := \"MW50\");",
"status": "pending" }
]
}
```
**Lauf 2** — beim Konvertieren von `Top_Caller` wird der vorgemerkte Rewrite
angewandt (Status `pending``applied`); aus dem mechanischen `"Sub_Callee"();`
wird der parameterbasierte Aufruf:
```scl
REGION ruft callee
"Sub_Callee"(M60_0 => "M60.0", MW50 := "MW50");
END_REGION
```
So bleibt die toChange-Liste über mehrere Konvertierungsläufe hinweg gültig, bis
jeder Aufrufer konvertiert und sein Aufruf angepasst wurde.
---
## Schutz handgeschriebener Dateien
Generierte `.scl` tragen die Kopfzeile
`// Mechanische AWL->SCL-Konvertierung (awl2scl.py)`. Mit `--force` werden **nur**
Dateien mit diesem Marker überschrieben; handgeschriebene Piloten (z.B.
`SCL-Export\=A01+UH06-KF00\MZylinder.scl`, `MHMOmniSchleuse.scl`, `FifoIn.scl`,
`DatenOut.scl`) bleiben unangetastet und erscheinen im Log unter
**„Geschützt, handgeschriebene .scl nicht überschrieben"**.
## Log & Ausgaben
Jeder Lauf schreibt `log\awl2scl_<Zeitstempel>.log` mit:
- Kopfstatistik (angeforderte Kategorien, aktive Schalter, Zähler),
- **Manuelle Prüfung nötig** (Baustein + Grund),
- **Parametrisiert** (neue Parameter je Baustein + Anzahl Aufrufer/davon manuell),
- **GOTO-lastig** (viele Sprünge, nur ohne `--improve-gotos`),
- **Geschützt** / **Übersprungen** (Kategorie bzw. Ziel existiert),
- **Fehler**.
Bei `--globals2params` zusätzlich der Pfad der toChange-Liste.
BIN
View File
Binary file not shown.