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:
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user