🎉 Initial commit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""Command-line interface: run a node configuration and a scenario.
|
||||
|
||||
Examples::
|
||||
|
||||
wago-sim serve --node config/pump_node.yaml --port 502
|
||||
wago-sim run --node config/pump_node.yaml --scenario scenarios/pump_station_fat.yaml
|
||||
wago-sim validate --scenario scenarios/pump_station_fat.yaml
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import inspect
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .app import build_app
|
||||
from .scenario.dsl import load_node_modules, load_yaml, validate_scenario
|
||||
from .scenario.engine import ScenarioEngine
|
||||
from .scenario.report import result_summary, write_csv, write_jsonl
|
||||
|
||||
|
||||
def _build_logger(verbose: bool) -> None:
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG if verbose else logging.INFO,
|
||||
format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
|
||||
)
|
||||
|
||||
|
||||
async def _serve(args: argparse.Namespace) -> int:
|
||||
node_config = load_yaml(args.node)
|
||||
modules = load_node_modules(node_config)
|
||||
app = build_app(modules=modules, host=args.host, port=args.port)
|
||||
await app.server.start()
|
||||
print(f"Simulating WAGO 750-362 with {len(modules)} modules "
|
||||
f"on {args.host}:{args.port} (Ctrl+C to stop)")
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
await app.server.stop()
|
||||
return 0
|
||||
|
||||
|
||||
def _run_scenario(args: argparse.Namespace) -> int:
|
||||
node_config = load_yaml(args.node)
|
||||
modules = load_node_modules(node_config)
|
||||
scenario = load_yaml(args.scenario)
|
||||
|
||||
problems = validate_scenario(scenario)
|
||||
if problems:
|
||||
print("Scenario validation problems:", file=sys.stderr)
|
||||
for p in problems:
|
||||
print(f" - {p}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
app = build_app(modules=modules)
|
||||
engine = ScenarioEngine(app.node, scenario)
|
||||
engine.start()
|
||||
result = None
|
||||
max_ticks = 2_000_000
|
||||
ticks = 0
|
||||
while result is None and ticks < max_ticks:
|
||||
result = engine.pump()
|
||||
ticks += 1
|
||||
if result is None:
|
||||
print("Scenario did not terminate (tick limit reached)", file=sys.stderr)
|
||||
return 3
|
||||
print(result_summary(result))
|
||||
if args.report_jsonl:
|
||||
write_jsonl(result, args.report_jsonl)
|
||||
if args.report_csv:
|
||||
write_csv(result, args.report_csv)
|
||||
return 0 if result.passed else 1
|
||||
|
||||
|
||||
def _validate(args: argparse.Namespace) -> int:
|
||||
scenario = load_yaml(args.scenario)
|
||||
problems = validate_scenario(scenario)
|
||||
if problems:
|
||||
print("Scenario validation problems:")
|
||||
for p in problems:
|
||||
print(f" - {p}")
|
||||
return 2
|
||||
print("OK")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="wago-sim",
|
||||
description="WAGO 750-362 Modbus TCP simulator for PLC FAT testing",
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
serve = sub.add_parser("serve", help="run the Modbus TCP server")
|
||||
serve.add_argument("--node", required=True, help="node config YAML")
|
||||
serve.add_argument("--host", default="0.0.0.0")
|
||||
serve.add_argument("--port", type=int, default=502)
|
||||
serve.set_defaults(func=_serve)
|
||||
|
||||
runp = sub.add_parser("run", help="run a scenario headless (no server)")
|
||||
runp.add_argument("--node", required=True, help="node config YAML")
|
||||
runp.add_argument("--scenario", required=True, help="scenario YAML")
|
||||
runp.add_argument("--report-jsonl", default=None)
|
||||
runp.add_argument("--report-csv", default=None)
|
||||
runp.set_defaults(func=_run_scenario)
|
||||
|
||||
val = sub.add_parser("validate", help="validate a scenario file")
|
||||
val.add_argument("--scenario", required=True)
|
||||
val.set_defaults(func=_validate)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
_build_logger(args.verbose)
|
||||
if inspect.iscoroutinefunction(args.func):
|
||||
return asyncio.run(args.func(args))
|
||||
return int(args.func(args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user