scl_context: Staubereichs-Namen je Foerderer in Fliessrichtung

Staubereiche heissen DB_JamArea<NNNN>.stJam<NNNN>_<k> (Regel der
SPS-Programmierung, Dematic): NNNN = Foerderer, auf dem der Staubereich
liegt (Kreisel- bzw. Vario-Motor), k zaehlt auf diesem Foerderer in
Fliessrichtung. Foerderer: eigener Vario-Antrieb, sonst Separator auf einem
Kreisel (staubereich_kreisel_max_mm in cfg/parameter.ini [scl_context]),
sonst Kreisel-Host, sonst letzter Antriebs-TRO davor auf derselben Strecke.
k folgt der Nachfolgerkette auf dem Foerderer, nicht der REGION-Reihenfolge
(Breitensuche, Dematic Kreisel3 sonst TRO20 vor TRO23). Programm-DBs zaehlen
in der --strict-Gegenpruefung nicht als Geraete.

merge_hw_layout: von Hand gesetzte Antriebe (hw_overrides.ini) bekommen
keinen zusaetzlichen automatischen Widerspruch-Eintrag mehr.

hw_overrides.ini Dematic: VF_6 = MA0043 (TRO04/05) + MA0044 (TRO06/07),
VF_4 = MA0045 (TRO01/02, K0045 = 0008); Separatoren nach Klartext statt
kleinstem Abstand - TRO04 hatte BG0111 von VF_4.

Dematic: alle 25 Staubereiche benannt, 123 Slot-Rollen, 0 Widersprueche,
offene Stellen 615 -> 494.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 16:26:10 +02:00
parent 2fbd2f119d
commit 404d500271
9 changed files with 312 additions and 38 deletions
+3 -1
View File
@@ -235,7 +235,9 @@ byte — rewrite them (`SKEL_UPDATE_GOLDEN=1`) only for a reviewed, intended cha
numbers in `cfg/parameter.ini [merge_hw_layout]`. See `doc/Python_Scripts.md` §5e.
8. **`scl_skeleton.py --json <csv>_anlage.json`** (stage 4, names) — reads the plant JSON
(schema detected) and fills sensors, valves, drives and conveyor numbers from `bindings`/
`conveyors` via `scl_context.HwSource`; open/contradicting bindings stay `TODO(E-Planung)`;
`conveyors` via `scl_context.HwSource`, and jam-area names `"DB_JamArea<NNNN>".stJam<NNNN>_<k>`
(conveyor = Kreisel/Vario motor, `<k>` along the flow; `scl_context.jam_names`);
open/contradicting bindings stay `TODO(E-Planung)`;
inserted names are checked against `<projekt>_hw.json` (`--strict` aborts). Templates are
unchanged. See `doc/Python_Scripts.md` §5b.
+28
View File
@@ -28,3 +28,31 @@
; [mubea.csv.conveyors]
; K0105 = 0001
; ================================================================
; ----------------------------------------------------------------
; Dematic (dematic_merge.csv + dematic_merge..dxf), 2026-10-22
; ----------------------------------------------------------------
; VF_6 (0010) traegt zwei Varios: MA0043 (TRO04 + TRO05 dahinter) und MA0044
; (TRO06 + TRO07); VF_4 (0008) den Vario MA0045 (TRO01 + TRO02) - Angabe der
; SPS-Programmierung, bestaetigt durch die Klartexte der Zeichnung.
; Die E-Symbole liegen dort 0,4-1 m versetzt in verschiedene Richtungen; der
; kleinste Abstand gab TRO04 den Separator von VF_4 (BG0111). Zuordnung
; deshalb nach Klartext:
; BG0080 "in Separator vor ILS-VARIO (MA0043)" -> TRO04 (0082)
; BG0085 "in Separator vor Eintaeter nach (MA0043)" -> TRO05 (0083)
; BG0111 "in Separator vor ILS-VARIO (MA0045)" -> TRO01 (0074; das
; Layout-Bauteil steht ~5,5 m daneben - Mechanik noch unvollstaendig)
[dematic_merge.csv.bindings]
TRO01.separator1.sensor = BG0111
TRO01.separator1.stopper = MB0111
TRO04.separator1.sensor = BG0080
TRO04.separator1.stopper = MB0080
TRO05.separator1.sensor = BG0085
TRO05.separator1.stopper = MB0085
TRO04.drive.motor = MA0043
TRO04.drive.motorProtection = FC0043
TRO06.drive.motor = MA0044
TRO06.drive.motorProtection = FC0044
[dematic_merge.csv.conveyors]
K0045 = 0008
+8
View File
@@ -197,3 +197,11 @@ conveyor_min_share = 0.6
; Layout-Separatoren einen Partner findet (0..1) - Hinweis auf verschiedene
; Planungsstaende von Layout und E-Zeichnung.
registration_min_share = 0.8
[scl_context]
; Staubereichs-Namen "DB_JamArea<NNNN>".stJam<NNNN>_<k>: ein Staubereich liegt
; auf einem Kreisel, wenn sein Separator hoechstens so weit von dessen
; Huellbox entfernt ist (die Separatoren sitzen auf den Bahnen am Rand der
; Box; Dematic 0048/0040: 0 mm, naechster Separator einer Strecke >500 mm).
staubereich_kreisel_max_mm = 300.0
+28 -8
View File
@@ -607,7 +607,26 @@ layout is `anlage.layout`; names come from `bindings[]` by TRO-ID + slot + role
| `line<n>` `sensor` / `stopper` / `jamSensor` | PinStore storage lines |
| `switch<n>` `valve` | `xOutSw<n>ExTo3` — the slot names the switch part from `switches` of the TRO-JSON (Ausschleuselement or ILS Weiche; `switch<i>` belongs to `separator<i>` in 2Sep types) |
| `drive` `motor` / `motorProtection` / `sen*` | Vario motor, `xInMotorProtection`, `stSenCarrInPos` … (motor/protection fall back to the conveyor of the TRO's host) |
| `conveyors[]` on the TRO's host | `xInCarouselRun` = `#fbCarousel<NNNN>.xOutConveyorFullSpeed` (Kreisel only); jam DB `"DB_JamArea<NNNN>".stJam<NNNN>_____` — still `TODO`, the running number `<k>` is not fixed yet |
| `conveyors[]` on the TRO's host | `xInCarouselRun` = `#fbCarousel<NNNN>.xOutConveyorFullSpeed` (Kreisel only) |
| `conveyors[]` + drive bindings | jam DB `"DB_JamArea<NNNN>".stJam<NNNN>_<k>` (`scl_context.jam_names`, see below) |
**Jam-area names** (rule of the PLC programmers, Dematic 2026-10-22).
`<NNNN>` is the conveyor the jam area sits on — the number of its Kreisel or
Vario motor — and `<k>` counts up along the material flow on that conveyor
(Vario MA0051 → `stJam0051_1`, `stJam0051_2`). The conveyor of a jam area, in
this order: (1) the TRO's own drive (Vario); (2) its separator lies on a
Kreisel (within `staubereich_kreisel_max_mm` of the Kreisel's bounding box,
`cfg/parameter.ini [scl_context]`) — catches TROs whose host is the outgoing
line; (3) the host is a Kreisel; (4) the host is a line carrying drive TROs →
the drive of the last drive TRO before it on that line (a 1Sep behind a Vario
belongs to that Vario's conveyor; VF_6 carries two Varios). `<k>` = 1 is the
area the flow reaches first on that conveyor (on a Kreisel: where material
comes on); from there the count follows the successor chain on the same
conveyor, not the REGION order — that is a breadth-first search over the whole
plant and reaches a Kreisel TRO through another conveyor first. A jam area
without a conveyor stays a placeholder and does not count. Dematic: all 25 jam
areas named. Jam DB names are program DBs, not devices — the `--strict`
cross-check skips them.
Not filled yet: jam sensors at the exits (`stInSenJam<n>`, the `Ausschleus.stau`
bindings have no slot), scanners, separator/switch/MainTro numbers. Switch
@@ -860,7 +879,8 @@ Steps:
on the same layout conveyor (Dematic VF_6: two Vario TROs), the drive is a
`widerspruch` with the conveyor's motor as a suggestion — each TRO has its
own motor; set it in `hw_overrides.ini`. `scl_skeleton` then keeps
`MA____` instead of falling back to the conveyor's motor.
`MA____` instead of falling back to the conveyor's motor. A drive set by
hand in `hw_overrides.ini` is left alone by this step.
5. **Corrections** from `hw_overrides.ini` (last):
`[<csv>.bindings] TRO01.separator1.sensor = BG0532` (or `= -` to clear) and
`[<csv>.conveyors] K0102 = 0010`.
@@ -886,12 +906,12 @@ conveyors (Aufgabestation #100/#101, Vario #106–#109) are outside the CSV or
have a single vote and stay open.
Result on Dematic (`dematic_merge_tro.json` + `dematic_hw.json`, rules
`e_planning_dematic.ini`): 113 slot roles assigned (all 27 storage lines with
sensor and stopper, 23 of 25 separators, 5 of 6 switch valves, 6 Vario drives),
39 open (Vario sensors, scanners, VF_4/VF_6 where mechanical parts are still
missing), 4 contradictions (the two Vario TROs on VF_6). Then
`scl_skeleton --templates --strict`: 115 names inserted, all known in TIA,
open gaps 730 → 615.
`e_planning_dematic.ini`, corrections for VF_4/VF_6 in `hw_overrides.ini`): 123
slot roles assigned (all 27 storage lines with sensor and stopper, all 25
separators, 5 of 6 switch valves, all 7 Vario drives), 33 open (Vario sensors,
scanners, one switch valve), 0 contradictions. Then
`scl_skeleton --templates --strict`: 200 names inserted (75 of them jam-area
names), all devices known in TIA, open gaps 730 → 494.
**Stage 4:** `scl_skeleton --json <csv>_anlage.json` reads the plant JSON
directly (§5b). `--eplan` still works for a TRO-JSON and is phased out together
+7 -3
View File
@@ -541,14 +541,17 @@ def _conveyor_kinds(hw: HardwareDocument) -> dict[str, str]:
def bind_drives(doc: TroDocument, conveyors: list[ConveyorBinding],
hw: HardwareDocument) -> list[Binding]:
hw: HardwareDocument,
skip: set[tuple[str, str, str]] | None = None) -> list[Binding]:
"""Antriebs-Slots (Vario): Motor und Motorschutz des zugeordneten Foerderers.
Tragen mehrere TROs einen Antriebs-Slot auf demselben Layout-Foerderer
(Dematic VF_6: zwei Vario-TROs, drei Vario-Motoren), hat jeder TRO seinen
eigenen Motor - welcher, sagt die Mehrheit je Foerderer nicht. Dann wird
der Antrieb ein Widerspruch (BMK als Vorschlag), Korrektur von Hand.
Rollen in ``skip`` (TRO, Slot, Rolle - von Hand gesetzt) bleiben unberuehrt.
"""
skip = skip or set()
devices = hw.by_bmk()
by_layout = {c.layout_id: c for c in conveyors if c.status == "zugeordnet"}
sharing: dict[str, list[str]] = {}
@@ -568,7 +571,7 @@ def bind_drives(doc: TroDocument, conveyors: list[ConveyorBinding],
shared = sharing.get(s.layout_id.split("-", 1)[0], [])
for role, attr in _DRIVE_ROLES.items():
bmk = getattr(conv, attr)
if role not in roles or not bmk:
if role not in roles or not bmk or (tro.tro_id, s.slot, role) in skip:
continue
dev = devices.get(bmk)
status = "zugeordnet" if dev and dev.in_tia else "widerspruch"
@@ -742,7 +745,8 @@ def build_anlage(doc: TroDocument, hw: HardwareDocument, rules: list[Rule],
bindings = bind_slots(doc, assignments, rules, hw)
if overrides is not None:
bindings = _apply_overrides(doc, bindings, conveyors, overrides, hw, warnings)
bindings += bind_drives(doc, conveyors, hw)
bindings += bind_drives(doc, conveyors, hw,
{(b.tro, b.slot, b.role) for b in bindings if b.rule == "hand"})
bindings = _open_and_conflicts(doc, bindings)
used = {b.bmk for b in bindings if b.bmk}
+135 -20
View File
@@ -41,6 +41,7 @@ from typing import TYPE_CHECKING
from pydantic import BaseModel, ConfigDict, Field
from model import AnlageDocument, ConveyorBinding, JamArea, TroBase, TroDocument
from parameter import get_float
from tro_catalog import TroItem
if TYPE_CHECKING:
@@ -63,11 +64,15 @@ __all__ = [
"HwSource",
"build_context",
"flow_order",
"jam_names",
"reachable_via",
]
TODO = "TODO(E-Planung)"
# Zahlenwerte aus cfg/parameter.ini [scl_context] - Bedeutung steht dort.
STAUBEREICH_KREISEL_MAX_MM = get_float("scl_context", "staubereich_kreisel_max_mm")
_CONFIG = ConfigDict(extra="forbid")
@@ -524,10 +529,11 @@ def _scanner(n: int, suffix: str, interface: str = "cScanner____") -> SclScanner
def _jam(n: int, neighbour: str, jam_db: str, sensor: SclName | None = None,
jam_id: str = "") -> SclJam:
jam_id: str = "", known: bool = False) -> SclJam:
name = _bekannt if known else _offen
return SclJam(
n=n, jam_id=jam_id, neighbour=neighbour, db=_offen(jam_db), sensor=sensor,
carriers=_offen(f"{jam_db}.arCarrier"), data=_offen(f"{jam_db}.stData"),
n=n, jam_id=jam_id, neighbour=neighbour, db=name(jam_db), sensor=sensor,
carriers=name(f"{jam_db}.arCarrier"), data=name(f"{jam_db}.stData"),
)
@@ -610,22 +616,126 @@ def _separator(i: int, tro_id: str, layout: str, hw: HwSource) -> SclSeparator:
_JAM_DB = '"DB_JamArea____".stJam____'
def _jam_db(area: JamArea | None, hw: HwSource) -> str:
"""Platzhalter des Staubereichs-DB, mit Foerderernummer soweit bekannt.
def _jam_db(area: JamArea | None, hw: HwSource,
namen: dict[str, str] | None = None) -> tuple[str, bool]:
"""Name des Staubereichs-DB und ob er feststeht.
Schema der Referenzen: "DB_JamArea<NNNN>".stJam<NNNN>_<k>. <NNNN> ist der
Foerderer, auf dem der Staubereich liegt (Anlagen-JSON, conveyors[]);
<k> - die laufende Nummer entlang des Flusses auf diesem Foerderer - ist
noch nicht festgelegt (doc/Konzept_Layout_Hardware_SCL.md "Staubereiche").
Der Name bleibt deshalb immer offen.
Schema der Referenzen: "DB_JamArea<NNNN>".stJam<NNNN>_<k> (jam_names).
Steht der Name nicht fest, bleibt ein Platzhalter - mit Foerderernummer,
soweit der Layout-Foerderer sie kennt.
"""
if area is not None and namen and area.jam_id in namen:
return namen[area.jam_id], True
conveyor = hw.conveyor(area.host) if area is not None else None
nr = _foerderer_nr(conveyor) if conveyor is not None else ""
return f'"DB_JamArea{nr}".stJam{nr}_____' if nr else _JAM_DB
return (f'"DB_JamArea{nr}".stJam{nr}_____' if nr else _JAM_DB), False
def jam_names(document: TroDocument, geordnet: list[TroBase],
hw: HwSource) -> dict[str, str]:
"""Staubereichs-Namen je Kennung (J0048 -> '"DB_JamArea0042".stJam0042_1').
Regel der SPS-Programmierer (Dematic, 2026-10-22): <NNNN> ist der
Foerderer, auf dem der Staubereich liegt (Nummer des Kreisel- bzw.
Vario-Motors), <k> zaehlt auf diesem Foerderer in Fliessrichtung hoch.
<k> = 1 ist der Staubereich, den der Fluss (flow_order) auf diesem
Foerderer zuerst erreicht - auf einem Kreisel die Stelle, an der das
Material auf den Kreisel kommt; von dort geht es die Nachfolger entlang
(_entlang), nicht nach REGION-Reihenfolge: die ist Breitensuche ueber die
ganze Anlage und erreicht einen Kreisel-TRO oft zuerst ueber einen anderen
Foerderer (Dematic Kreisel3: TRO17 -> TRO14 -> TRO23 -> TRO20).
Foerderer eines Staubereichs, in dieser Reihenfolge:
1. der TRO hat einen eigenen Antrieb (Vario) -> dessen Foerderer;
2. der Separator liegt auf einem Kreisel (hoechstens
STAUBEREICH_KREISEL_MAX_MM von dessen Huellbox) -> der Kreisel;
3. der Host ist ein Kreisel -> der Kreisel;
4. der Host ist eine Strecke mit Antriebs-TROs -> der Antrieb des
letzten Antriebs-TRO davor auf dieser Strecke (ein 1Sep hinter einem
Vario gehoert zu dessen Foerderer; VF_6 traegt zwei Varios).
Sonst bleibt der Staubereich offen und zaehlt nicht mit.
"""
anlage = hw.anlage
if anlage is None:
return {}
rang = {t.tro_id: i for i, t in enumerate(geordnet)}
nr_of_motor = {c.motor: _foerderer_nr(c) for c in anlage.conveyors if c.motor}
def antrieb(tro_id: str) -> str:
b = anlage.binding(tro_id, "drive", "motor")
if b is None or b.status != "zugeordnet" or not b.bmk:
return ""
return nr_of_motor.get(b.bmk, "")
antriebe: dict[str, list[tuple[int, str]]] = {}
for t in geordnet:
nr = antrieb(t.tro_id)
hosts = list(getattr(t, "hosts", []) or [])
if nr and hosts:
antriebe.setdefault(hosts[0].split("-", 1)[0], []).append((rang[t.tro_id], nr))
kreisel = [(p, anlage.conveyor_for(p.layout_id)) for p in document.parts
if p.kind == "Kreisel"]
kreisel = [(p, c) for p, c in kreisel if c is not None]
def auf_kreisel(separator: str) -> str:
part = document.part(separator) if separator else None
if part is None or part.position is None:
return ""
nah = [(d, c) for p, c in kreisel
if (d := p.distance_to_box(part.position[0], part.position[1])) is not None
and d <= STAUBEREICH_KREISEL_MAX_MM]
return _foerderer_nr(min(nah, key=lambda x: x[0])[1]) if nah else ""
def foerderer(area: JamArea) -> str:
nr = antrieb(area.tro) or auf_kreisel(area.separator)
if not nr and hw.is_carousel(area.host):
conv = hw.conveyor(area.host)
nr = _foerderer_nr(conv) if conv is not None else ""
if not nr:
davor = [n for r, n in antriebe.get(area.host.split("-", 1)[0], [])
if r < rang.get(area.tro, -1)]
nr = davor[-1] if davor else ""
return nr
areas = sorted(document.jam_areas_or_derived(),
key=lambda a: (rang.get(a.tro, len(rang)), a.entry))
je_foerderer: dict[str, list[JamArea]] = {}
for area in areas:
nr = foerderer(area)
if nr:
je_foerderer.setdefault(nr, []).append(area)
nachfolger = {t.tro_id: list(t.successors) for t in geordnet}
namen: dict[str, str] = {}
for nr, liste in je_foerderer.items():
for k, area in enumerate(_entlang(liste, nachfolger), start=1):
namen[area.jam_id] = f'"DB_JamArea{nr}".stJam{nr}_{k}'
return namen
def _entlang(areas: list[JamArea], nachfolger: dict[str, list[str]]) -> list[JamArea]:
"""Staubereiche eines Foerderers in Fliessrichtung.
``areas`` kommt nach flow_order sortiert; der erste ist der Anfang. Danach
jeweils der naechste Eingang desselben TRO, sonst ein Staubereich eines
Nachfolger-TRO auf diesem Foerderer, sonst (Kette unterbrochen) der
naechste nach flow_order.
"""
rest = list(areas)
folge = [rest.pop(0)]
while rest:
jetzt = folge[-1].tro
weiter = ([a for a in rest if a.tro == jetzt]
or [a for a in rest if a.tro in nachfolger.get(jetzt, [])]
or rest)
folge.append(weiter[0])
rest.remove(weiter[0])
return folge
def _entries(tro: TroBase, eigene: list[JamArea],
alle: dict[str, TroBase], hw: HwSource) -> list[SclJam]:
alle: dict[str, TroBase], hw: HwSource,
namen: dict[str, str] | None = None) -> list[SclJam]:
"""Ein JamEntr je eigenem Staubereich - nicht je Vorgaenger: mehrere
Vorgaenger fahren in denselben Staubereich ein (Referenzen UH01-UH05)."""
eingaenge = []
@@ -633,7 +743,8 @@ def _entries(tro: TroBase, eigene: list[JamArea],
von = ", ".join(_nachbar(v, alle) for v in area.predecessors) or "-"
if area.note:
von = f"{von} ({area.note})"
eingaenge.append(_jam(area.entry, von, _jam_db(area, hw), jam_id=area.jam_id))
db, known = _jam_db(area, hw, namen)
eingaenge.append(_jam(area.entry, von, db, jam_id=area.jam_id, known=known))
return eingaenge
@@ -686,7 +797,8 @@ def _carousel_run(tro: TroBase, hw: HwSource) -> SclName:
def _tro_context(tro: TroBase, alle: dict[str, TroBase],
hw: HwSource | None = None,
bereiche: dict[str, list[JamArea]] | None = None) -> SclTro:
bereiche: dict[str, list[JamArea]] | None = None,
namen: dict[str, str] | None = None) -> SclTro:
items = tro.items
n_sep = items.get(TroItem.SEPARATOR, 1)
n_swi = items.get(TroItem.SWITCH, 0)
@@ -728,15 +840,17 @@ def _tro_context(tro: TroBase, alle: dict[str, TroBase],
] if n_swi else []
# Staubereiche aus der TRO-JSON (jamAreas): Eingaenge sind die eigenen
# (ein JamEntr je eigenem Staubereich, nicht je Vorgaenger), Ausgaenge die
# Eingaenge der Nachfolger. Der DB-Name braucht Foerderernummer (aus der
# Anlagen-JSON) und laufende Nummer (nicht festgelegt) und bleibt offen.
jam_entries = _entries(tro, bereiche.get(tro_id, []), alle, hw)
# Eingaenge der Nachfolger. Der DB-Name (jam_names) braucht Foerderer-
# nummer und laufende Nummer; ohne beide bleibt er offen.
jam_entries = _entries(tro, bereiche.get(tro_id, []), alle, hw, namen)
jam_exits = []
for n, nach in enumerate(tro.successors, start=2):
area = _exit_area(tro_id, nach, bereiche)
jam_exits.append(_jam(n, _nachbar(nach, alle), _jam_db(area, hw),
db, known = _jam_db(area, hw, namen)
jam_exits.append(_jam(n, _nachbar(nach, alle), db,
sensor=_offen("cInBG____"),
jam_id=area.jam_id if area is not None else ""))
jam_id=area.jam_id if area is not None else "",
known=known))
return SclTro(
id=tro_id,
@@ -810,11 +924,12 @@ def build_context(document: TroDocument, source: str, start_id: str = "",
for area in document.jam_areas_or_derived():
bereiche.setdefault(area.tro, []).append(area)
geordnet, startgrund = flow_order(document.tros, start_id)
namen = jam_names(document, geordnet, hw)
return SclFile(
source=source,
created=created or datetime.now().strftime("%Y-%m-%d %H:%M"),
start_reason=startgrund,
tros=[_tro_context(t, alle, hw, bereiche) for t in geordnet],
tros=[_tro_context(t, alle, hw, bereiche, namen) for t in geordnet],
eplan_source=hw.source if eplan is not None else "",
hw_source=hw.source if anlage is not None else "",
)
+4 -2
View File
@@ -585,13 +585,15 @@ def unknown_names(context: SclFile, hw: HardwareDocument) -> list[str]:
"""Eingesetzte Namen, die die Hardwareplanung nicht kennt.
Geprueft werden Symbole und Konstanten (cInBG0532, MB0533, "FC0202");
Instanzzugriffe wie #fbCarousel0102.xOut... sind keine globalen Namen.
Instanzzugriffe wie #fbCarousel0102.xOut... und Zugriffe auf Programm-DBs
wie "DB_JamArea0102".stJam0102_1 sind keine Geraete der Hardwareplanung.
"""
bekannt = {n for d in hw.devices for n in (d.bmk, d.constant) if n}
bekannt |= {c.name for c in hw.constants}
bekannt |= {n for d in hw.drives for n in (d.motor, d.protection, d.encoder) if n}
return [name for name in known_names(context)
if not name.startswith("#") and name.strip('"') not in bekannt]
if not name.startswith(("#", '"DB_'))
and name.strip('"') not in bekannt]
def _schema_of(path: Path) -> str:
+11
View File
@@ -196,6 +196,17 @@ def test_zwei_antriebs_tros_auf_einem_foerderer_sind_widerspruch():
assert anlage.binding(tro, "drive", "motorProtection").status == "widerspruch"
def test_antrieb_von_hand_wird_nicht_ueberschrieben():
"""hw_overrides.ini legt den Motor je Vario fest - die automatische
Antriebszuordnung haengt keinen zweiten (Widerspruch-)Eintrag daneben."""
overrides = merge_hw_layout.Overrides(bindings={("TRO03", "drive", "motor"): "MA0210"})
anlage = run(doc=layout(second_vario=True), overrides=overrides)
motors = [b for b in anlage.bindings
if (b.tro, b.slot, b.role) == ("TRO03", "drive", "motor")]
assert [(b.bmk, b.rule, b.status) for b in motors] == [("MA0210", "hand", "zugeordnet")]
assert anlage.binding("TRO06", "drive", "motor").status == "widerspruch"
def test_gemeinsame_zeichnung_ohne_lagebezug(tmp_path):
"""hw_import --dxf: E-Symbole stehen schon in Layout-Koordinaten - kein
Lagebezug wird ermittelt oder abgelegt."""
+88 -4
View File
@@ -103,15 +103,99 @@ def test_kreisel_freigabe_und_staubereich_aus_der_foerderernummer():
tro01 = _by_id(ctx, "TRO01")
assert tro01.carousel_run.value == "#fbCarousel0102.xOutConveyorFullSpeed"
assert not tro01.carousel_run.todo
# Staubereich: Foerderer bekannt, laufende Nummer <k> nicht -> weiter offen
assert tro01.jam_entries[0].db.value == '"DB_JamArea0102".stJam0102_____'
assert tro01.jam_entries[0].db.todo
# Ausgang zu TRO03 liegt auf VF_1 (K0106)
# Staubereich am Kreisel: Foerderer K0102, in Fliessrichtung gezaehlt
assert tro01.jam_entries[0].db.value == '"DB_JamArea0102".stJam0102_1'
assert not tro01.jam_entries[0].db.todo
assert tro01.jam_exits[0].db.value == '"DB_JamArea0102".stJam0102_2'
# Ausgang zu TRO03 liegt auf VF_1 (K0106); ohne Antrieb des Vario steht
# die laufende Nummer nicht fest -> offen
assert tro01.jam_exits[1].db.value == '"DB_JamArea0106".stJam0106_____'
assert tro01.jam_exits[1].db.todo
# TRO03 sitzt auf einer Strecke, nicht am Kreisel -> Freigabe bleibt offen
assert _by_id(ctx, "TRO03").carousel_run.todo
def test_staubereiche_je_foerderer_in_fliessrichtung_gezaehlt():
"""Regel der SPS (Dematic): "DB_JamArea<NNNN>".stJam<NNNN>_<k>, NNNN =
Kreisel- bzw. Vario-Motor, k zaehlt in Fliessrichtung. Nachbau von VF_6:
zwei Varios auf einer Strecke, je ein 1Sep dahinter; davor ein TRO, dessen
Separator auf dem Kreisel liegt, obwohl sein Host die Strecke ist."""
def tro(tro_id, typ, sep, host, vor, nach):
return _tro(tro_id, typ, [sep], [host], predecessors=[vor], successors=[nach])
tros = [
tro("TRO01", "1Sep", "0048", "0008", "SYS1", "TRO02"),
tro("TRO02", "Vario", "0082", "0010", "TRO01", "TRO03"),
tro("TRO03", "1Sep", "0083", "0010", "TRO02", "TRO04"),
tro("TRO04", "Vario", "0084", "0010", "TRO03", "TRO05"),
tro("TRO05", "1Sep", "0085", "0010", "TRO04", "TRO06"),
tro("TRO06", "1Sep", "0049", "0004-L", "TRO05", "SYS2"),
]
parts = [
LayoutPart(layout_id="0004", kind="Kreisel", name="Kreisel4",
position=(0.0, 0.0, 0.0), bbox=(800.0, 10000.0, 1.0)),
LayoutPart(layout_id="0010", kind="Strecke", name="VF_6",
position=(8000.0, 0.0, 0.0), bbox=(12000.0, 800.0, 1.0)),
LayoutPart(layout_id="0048", kind="Separator", name="S48",
position=(350.0, 100.0, 0.0)),
]
layout = TroDocument.from_analysis(Analysis(tros=tros), source="vf6.csv", parts=parts)
doc = AnlageDocument(
layout=layout,
bindings=[_bind("TRO02", "drive", "motor", "MA0043", "cOutMA0043"),
_bind("TRO04", "drive", "motor", "MA0044", "cOutMA0044")],
conveyors=[
ConveyorBinding(eplan_name="K0042", layout_id="0004", motor="MA0042"),
ConveyorBinding(eplan_name="K0043", layout_id="0010", motor="MA0043"),
ConveyorBinding(eplan_name="K0044", layout_id="0010", motor="MA0044",
status="widerspruch"),
],
)
ctx = _kontext(doc)
namen = {t.id: t.jam_entries[0].db for t in ctx.tros}
assert {k: v.value for k, v in namen.items()} == {
"TRO01": '"DB_JamArea0042".stJam0042_1', # Separator auf dem Kreisel
"TRO02": '"DB_JamArea0043".stJam0043_1',
"TRO03": '"DB_JamArea0043".stJam0043_2', # 1Sep hinter Vario MA0043
"TRO04": '"DB_JamArea0044".stJam0044_1',
"TRO05": '"DB_JamArea0044".stJam0044_2',
"TRO06": '"DB_JamArea0042".stJam0042_2', # Host Kreisel, weiter gezaehlt
}
assert not any(v.todo for v in namen.values())
# Ausgang = Eingang des Nachfolgers, derselbe Name
assert _by_id(ctx, "TRO02").jam_exits[0].carriers.value == \
'"DB_JamArea0043".stJam0043_2.arCarrier'
def test_staubereiche_am_kreisel_entlang_des_umlaufs():
"""Die Breitensuche erreicht T4 ueber die Strecke (T5) vor T6; auf dem
Kreisel kommt aber erst T6, dann T4 - gezaehlt wird entlang des Umlaufs."""
def tro(tro_id, host, vor, nach):
return _tro(tro_id, "1Sep", [f"9{tro_id[-1]}00"], [host],
predecessors=vor, successors=nach)
tros = [
tro("TRO01", "0004-L", ["SYS1"], ["TRO02", "TRO05"]),
tro("TRO02", "0004-L", ["TRO01"], ["TRO03"]),
tro("TRO03", "0004-L", ["TRO02"], ["TRO06"]),
tro("TRO05", "0010", ["TRO01"], ["TRO04"]),
tro("TRO06", "0004-R", ["TRO03"], ["TRO04"]),
tro("TRO04", "0004-R", ["TRO06", "TRO05"], ["SYS2"]),
]
layout = TroDocument.from_analysis(
Analysis(tros=tros), source="loop.csv",
parts=[_part("0004", "Kreisel", "Kreisel4"), _part("0010", "Strecke", "VF")])
doc = AnlageDocument(layout=layout, conveyors=[
ConveyorBinding(eplan_name="K0042", layout_id="0004", motor="MA0042")])
ctx = _kontext(doc)
assert [t.id for t in ctx.tros][:5] == ["TRO01", "TRO02", "TRO05", "TRO03", "TRO04"]
k = {t.id: t.jam_entries[0].db.value.rsplit("_", 1)[-1] for t in ctx.tros
if not t.jam_entries[0].db.todo}
assert k == {"TRO01": "1", "TRO02": "2", "TRO03": "3", "TRO06": "4", "TRO04": "5"}
# Strecke ohne Foerderer der Hardwareplanung: bleibt offen
assert _by_id(ctx, "TRO05").jam_entries[0].db.todo
def test_vario_antrieb_ueber_den_foerderer():
tro = _by_id(_kontext(anlage()), "TRO03")
assert (tro.vario_motor.value, tro.vario_motor.todo) == ("MA0210", False)