🚧 Change module numbering
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""Example plant/PLC model plugin for the pump-station FAT scenario.
|
||||
|
||||
In a real FAT bench the PLC under test drives the outputs. For a
|
||||
headless demo (or pre-FAT scenario smoke tests) this plugin emulates
|
||||
the expected PLC behavior so the scenario can PASS end-to-end.
|
||||
|
||||
Register it in a scenario via:
|
||||
|
||||
plugins: [plugins.pump_plant.pump_plant]
|
||||
|
||||
The engine calls it once per tick.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# latch state per engine instance (module-level dict keyed by id)
|
||||
_LATCH = {}
|
||||
|
||||
|
||||
def pump_plant(engine, value) -> None:
|
||||
"""Emulated PLC with classic start/stop latch circuit:
|
||||
|
||||
- rising edge on start_button latches the pump ON
|
||||
- e-stop OFF breaks the latch (and the safe state follows)
|
||||
- valve follows the pump; speed setpoint follows level above 4000
|
||||
"""
|
||||
key = id(engine)
|
||||
start = bool(engine.alias_value("start_button"))
|
||||
em_stop = bool(engine.alias_value("em_stop_ok"))
|
||||
level = float(engine.alias_value("level_sensor"))
|
||||
|
||||
# rising edge of the start button sets the latch; e-stop clears it
|
||||
if engine.consume_rising_edge("start_button"):
|
||||
_LATCH[key] = True
|
||||
if not em_stop:
|
||||
_LATCH[key] = False
|
||||
|
||||
pump_on = _LATCH.get(key, False) and em_stop
|
||||
speed = int(max(0.0, (level - 4000.0) * 10.0)) if pump_on else 0
|
||||
|
||||
_set_do(engine, "pump_relay", pump_on)
|
||||
_set_do(engine, "valve_open", pump_on)
|
||||
_set_ao(engine, "speed_setpoint", speed)
|
||||
|
||||
|
||||
def _set_do(engine, alias_name: str, value: bool) -> None:
|
||||
"""Emulate a Modbus master writing the output coil.
|
||||
|
||||
Uses the engine's global alias location: ``loc.bit`` is the 0-based
|
||||
global digital channel index == the Modbus bit address (Table 122).
|
||||
"""
|
||||
loc = engine.alias_location(alias_name)
|
||||
result = engine.datastore.write_bits(loc.bit, [bool(value)])
|
||||
assert not isinstance(result, Exception), result
|
||||
|
||||
|
||||
def _set_ao(engine, alias_name: str, value: int) -> None:
|
||||
"""Emulate a Modbus master writing the output register.
|
||||
|
||||
``loc.word`` is the global analog output word index == the Modbus
|
||||
word address in output area 1 (0x0000-0x00FF).
|
||||
"""
|
||||
alias = engine._aliases[alias_name]
|
||||
loc = engine.alias_location(alias_name)
|
||||
raw = int(value * alias.scale + alias.offset) & 0xFFFF
|
||||
result = engine.datastore.write_words(loc.word, [raw])
|
||||
assert not isinstance(result, Exception), result
|
||||
Reference in New Issue
Block a user