67 lines
2.3 KiB
Python
67 lines
2.3 KiB
Python
"""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 |