🚧 WIP
This commit is contained in:
@@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import threading
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
|
||||
@@ -35,7 +36,6 @@ def _default_config_path() -> str:
|
||||
return DEFAULT_CONFIG
|
||||
|
||||
_NOT_IMPLEMENTED: dict[str, str] = {
|
||||
"run": "Phase 1 (core engine)",
|
||||
"install": "Phase 3 (Windows service)",
|
||||
"uninstall": "Phase 3 (Windows service)",
|
||||
"start": "Phase 3 (Windows service)",
|
||||
@@ -81,8 +81,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
||||
sub = parser.add_subparsers(dest="verb", metavar="verb", required=True)
|
||||
|
||||
p = sub.add_parser("run", help="run in the foreground (console)")
|
||||
p = sub.add_parser(
|
||||
"run",
|
||||
help="run the monitoring loop in the foreground (Ctrl+C stops)",
|
||||
)
|
||||
_add_config_option(p)
|
||||
_add_server_overrides(p)
|
||||
|
||||
p = sub.add_parser(
|
||||
"read",
|
||||
@@ -550,11 +554,67 @@ def _cmd_probe(
|
||||
return _cmd_read(config_path, server, host, (), source, as_json=False)
|
||||
|
||||
|
||||
def _cmd_run(config_path: str, server: str | None, host: str | None) -> int:
|
||||
"""Foreground monitoring loop: poll → detect → print (Ctrl+C stops)."""
|
||||
import signal
|
||||
|
||||
from opcda_diagnozer.monitor.poller import Poller
|
||||
from opcda_diagnozer.opc.automation import AutomationOpcClient
|
||||
|
||||
try:
|
||||
config = load_config(config_path)
|
||||
except ConfigError as exc:
|
||||
print(f"run: FAILED\n {exc}", file=sys.stderr)
|
||||
return 2
|
||||
# --server/--host override opc.* for this run (same as the other verbs)
|
||||
if server is not None or host is not None:
|
||||
from dataclasses import replace
|
||||
|
||||
from opcda_diagnozer.config import OpcConfig
|
||||
|
||||
opc = replace(
|
||||
config.opc,
|
||||
server=server or config.opc.server,
|
||||
host=config.opc.host if host is None else host,
|
||||
)
|
||||
config = replace(config, opc=opc)
|
||||
|
||||
def client_factory() -> AutomationOpcClient:
|
||||
# Built on the worker thread: CoInitialize is per-thread (spec §10.3).
|
||||
return AutomationOpcClient(
|
||||
server=config.opc.server,
|
||||
host=config.opc.host,
|
||||
connect_timeout_s=config.opc.connect_timeout_s,
|
||||
)
|
||||
|
||||
def reporter(event) -> None:
|
||||
# Console = the human view (spec §9): clear text, status NAME (0xNN)
|
||||
line = f"{event.time_utc.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} UTC {event.severity.value:<8} {event.kind.value}"
|
||||
extras = " ".join(f"{k}={v}" for k, v in event.detail.items())
|
||||
print(f"{line} {extras}" if extras else line, flush=True)
|
||||
|
||||
poller = Poller(client_factory, config, reporter)
|
||||
stop_requested = threading.Event()
|
||||
|
||||
def _on_ctrl_c(signum, frame) -> None: # noqa: ANN001, ARG001
|
||||
if stop_requested.is_set():
|
||||
raise KeyboardInterrupt # second Ctrl+C aborts immediately
|
||||
stop_requested.set()
|
||||
poller.request_stop()
|
||||
print("\nrun: stop requested - finishing current poll ...", file=sys.stderr, flush=True)
|
||||
|
||||
signal.signal(signal.SIGINT, _on_ctrl_c)
|
||||
poller.run_forever()
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
"""CLI entry point (console script + ``python -m opcda_diagnozer``)."""
|
||||
args = _build_parser().parse_args(argv)
|
||||
if args.verb == "check-config":
|
||||
return _cmd_check_config(args.config)
|
||||
if args.verb == "run":
|
||||
return _cmd_run(args.config, args.server, args.host)
|
||||
if args.verb == "list-servers":
|
||||
return _cmd_list_servers(args.host, args.source, args.json)
|
||||
if args.verb == "list-items":
|
||||
|
||||
Reference in New Issue
Block a user