🎉 Initial commit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"""Application assembly: wiring datastore, registers, watchdog, node, server."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .datastore import WagoDatastore
|
||||
from .modules import ModuleType, VirtualNode
|
||||
from .registers import ConfigRegisterBank
|
||||
from .server import WagoSimServer
|
||||
from .stats import ModbusStatistics
|
||||
from .watchdog import ModbusWatchdog
|
||||
|
||||
|
||||
@dataclass
|
||||
class SimulatorApp:
|
||||
"""Fully wired simulator instance for one virtual node."""
|
||||
|
||||
datastore: WagoDatastore
|
||||
node: VirtualNode
|
||||
stats: ModbusStatistics
|
||||
watchdog: ModbusWatchdog
|
||||
registers: ConfigRegisterBank
|
||||
server: WagoSimServer
|
||||
|
||||
@property
|
||||
def modules(self) -> list[ModuleType]:
|
||||
return self.node.modules
|
||||
|
||||
|
||||
def build_app(
|
||||
*,
|
||||
modules: list[ModuleType],
|
||||
host: str = "0.0.0.0",
|
||||
port: int = 502,
|
||||
mac_id: tuple[int, int, int, int, int, int] = (0x00, 0x30, 0xDE, 0x11, 0x22, 0x33),
|
||||
firmware_description: str = "WAGO 750-362 Simulator",
|
||||
) -> SimulatorApp:
|
||||
"""Assemble all simulator components for a node configuration."""
|
||||
stats = ModbusStatistics()
|
||||
|
||||
server_ref: dict[str, WagoSimServer] = {}
|
||||
|
||||
def on_expire() -> None:
|
||||
datastore.clear_outputs()
|
||||
stats.slave_device_failure += 1
|
||||
|
||||
def on_connections_close() -> None:
|
||||
"""Watchdog 0x1009=1: close TCP connections (from sync context).
|
||||
|
||||
Called by the watchdog (possibly from its tick task). Schedules
|
||||
the async close on the running event loop when one exists.
|
||||
"""
|
||||
server = server_ref.get("server")
|
||||
if server is None:
|
||||
return
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
return
|
||||
loop.create_task(server.close_all_connections())
|
||||
|
||||
watchdog = ModbusWatchdog(
|
||||
on_expire=on_expire,
|
||||
on_connections_close=on_connections_close,
|
||||
)
|
||||
datastore = WagoDatastore(config_bank=None) # type: ignore[arg-type]
|
||||
|
||||
registers = ConfigRegisterBank(
|
||||
statistics=stats,
|
||||
watchdog=watchdog,
|
||||
tcp_connection_counter=lambda: server.connection_count,
|
||||
mac_id=mac_id,
|
||||
firmware_description=firmware_description,
|
||||
on_software_reset=lambda: datastore.clear_outputs(),
|
||||
on_kbus_reset=lambda: None,
|
||||
)
|
||||
datastore._config = registers # late binding to break the cycle
|
||||
|
||||
node = VirtualNode(datastore, modules)
|
||||
registers.set_process_image_bits(
|
||||
analog_in=node.analog_in_bits,
|
||||
analog_out=node.analog_out_bits,
|
||||
digital_in=node.digital_in_bits,
|
||||
digital_out=node.digital_out_bits,
|
||||
)
|
||||
registers.set_module_description(node.description())
|
||||
|
||||
server = WagoSimServer(
|
||||
datastore,
|
||||
stats,
|
||||
watchdog,
|
||||
host=host,
|
||||
port=port,
|
||||
response_delay_provider=lambda: registers.response_delay_ms,
|
||||
idle_timeout_provider=lambda: registers.idle_timeout_ms,
|
||||
)
|
||||
server_ref: dict[str, WagoSimServer] = {"server": server}
|
||||
return SimulatorApp(
|
||||
datastore=datastore,
|
||||
node=node,
|
||||
stats=stats,
|
||||
watchdog=watchdog,
|
||||
registers=registers,
|
||||
server=server,
|
||||
)
|
||||
Reference in New Issue
Block a user