Files
HundM_awl/doc/awl2scl.md
T
2026-07-08 16:04:32 +02:00

318 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.