7.5 KiB
7.5 KiB
Changelog
All notable changes to the OPC DA Diagnozer are documented here. The format follows Keep a Changelog; versions bump per SemVer. Dates are YYYY-MM-DD.
[Unreleased]
Added — Phase 2 reporters
- Rotating JSONL evidence log: one JSON event per line, config-digest header for provenance, size rotation with backups retained.
- Plain-text anomaly log (spec §9.1):
anomalies-YYYYMMDD.log, one line per detected anomaly intimestamp SEVERITY KIND messageorder with clear-text statuses; recoveries included so the file reads as an incident narrative. - Windows Event Log sink: severity-mapped (INFO/WARN/CRITICAL → Information/Warning/Error), WARN+ by default, delivery verified live.
- Optional HTTP webhook (
report.http): bounded queue + daemon sender thread, per-event POST with bearer auth; failures dropped and counted — never blocks the poll loop. - Reporter fan-out dispatcher: every sink isolated (one failing sink never
kills the others or polling); the
runverb wires all configured sinks.
Fixed
- Windows Event Log reporter initially lost every event silently:
win32evtlog.ReportEventtakes positional arguments only (keyword calls raiseTypeError, swallowed by the sink's own isolation) — now uses aRegisterEventSourcehandle with positional calls (live-verified). - HTTP webhook in injected-
postmode raised into the poll loop on a failing POST; both modes now count-and-drop.
Added — Phase 1 core engine (the continuous monitoring loop)
runverb: the foreground monitoring loop — coordinator + dedicated COM worker thread (the client is born on the worker; COM is per-thread), 1-second default polling of configured tags and device indicators, heartbeat (D15) everyheartbeat_s, clean Ctrl+C stop.- Detection engine, all pure and fake-client tested: latency spike / sustained
streak / rolling p95 (D4–D6), per-tag quality transitions, persistence and
flapping (D8–D11), stale timestamp & frozen value (D12–D13), mass-quality
degradation with latch (D14), device-link indicators (D16, edge-level
DOWN/UP against configurable
ok_values). - Hang watchdog (spec §10.4): polls exceeding
timeout_msraise the D7 soft-hang event and keep the thread; silence beyondhard_hang_sabandons the stuck COM thread (never joined — dead RPC channel) and rebuilds. - Connection state machine with exponential backoff (1 s → 60 s, reset on success) and reconnect accounting (D1/D2 with outage duration, D3 flapping).
- Fake-client scenario engine: scripted polls and whole-call failures power the entire poller/detector test suite without COM.
- Clear-text status rendering (
status_text):BAD / COMM FAILURE (0x18)everywhere events are shown.
Added — server discovery & browsing
list-serversverb: enumerates OPC DA servers locally or on a DCOM host via the Automation Wrapper (GetOPCServers), with a no-COM registry-scan fallback (--source registry|automation|auto), JSON output, and DA-version metadata.- Browsing of the connected server's address space, unified in the
list-items [all|branches|tags]verb:allprints the full structure tree (branches with/, tags with[ItemID: ...]),branchesthe structure-only flat list,tagsthe flat ItemID list for configtags:.--branch(dotted path or repeated segments),--depth N(1 default, 0 = whole subtree),--json. Replaces the earlier splitlist-devices/list-tags. browse.max_depthconfig key (default 32) as the address-space walk guard, validated bycheck-config; cycle-safe tree walk inopc/browse.py.SpaceNodetree data model (branch/leaf) replacing the earlier device-centricDeviceInfo.
Added — reading & CLI
readverb: one-shot read of explicit tags (or the config's tags) with a table or--json,--source device|cache, and a non-zero exit when any tag doesn't read GOOD — scriptable as a health check.probeverb: connect once and read every configured tag once.--server/--hostoverrides on every connecting verb; with--serverno config file is needed at all.- Frozen-exe default config path:
config.yamlnext to the exe (a Windows service starts withCWD = System32), not the working directory.
Added — monitoring design (D16) & config
devices:config section withDeviceEntry(name, tag, ok_values): D16 device-link indicators. The normal (connected) reading is configurable per indicator (ok_values, default[true]) — inverted fault flags[false, 0]and string states["RUN"]are first-class. Strict validation: duplicate indicators and indicator/tag-list overlap are rejected;check-configprints the indicators.- Detection rules D16 (
DEVICE_LINK_DOWN/DEVICE_LINK_UP) and the plain-text anomaly log reporter (report/textlog.pytask) specified in the docs (SPECIFICATION §6.1, §9.1; IMPLEMENTATION_PLAN Phases 1–2) — implementation pending, see TODO.md. - Clear-text status rule (spec §7): every reporter writes
NAME (0xNN)— e.g.BAD / communication failure (0x18).
Added — packaging
scripts/build_exe.ps1: PyInstaller build of the 32-bit console exe with hidden imports for the deferred COM modules (incl.win32timezone— see Fixed), embedded version metadata, a PE-bitness check, and smoke tests including a frozen-read canary against a live server.--versionand this CHANGELOG.md / TODO.md.
Fixed
- COM lifecycle: all COM references (browsers, groups, items) are released
inside the apartment before
CoUninitialize; retained exception tracebacks are cleared inas_com_error— both caused "Win32 exception occurred releasing IUnknown" noise and hard crashes at exit. - Frozen exe: every tag read failed with
SystemError: ... InvokeTypes ... exception setbecause pywin32 lazily importswin32timezoneduring VARIANT-DATE conversion and PyInstaller never bundles it — now a hidden import, with the build canary guarding it. - Reads: per-tag failures now carry the underlying exception's own type and
text (
_per_tag_error) instead of a bare "failed"; a degenerate device timestamp (never-updated alias, DST math) can no longer fail a read — the timestamp is metadata and is dropped toNone. - Matrikon browse quirk: servers may return an empty ItemID for special leaves (alias roots); the adapter falls back to the bare browse name.
- HRESULT labels:
OPC_E_UNKNOWNITEMID(0xC0040007) andOPC_E_INVALIDITEMID(0xC0040008) were swapped; now corrected everywhere.
Verified
- All CLI verbs live-tested against Matrikon OPC Simulation Server
(
Matrikon.OPC.Simulation.1): discovery, browse (all kinds/depths), reads (incl. alias tags after server-side alias repair), config-drivenmax_depth, and the frozen exe (onedir + onefile) including the read canary. - Test suite: 108 tests, all passing (pure logic + fake-client driven, runs on any OS; COM paths are live-smoke-tested on dev boxes only).
[0.1.0] - 2026-10-07
Added
- Initial project skeleton: typed YAML config with strict actionable
validation (
check-config), OPC DA quality decode tables (spec §7), event model skeletons (DiagEvent, D1–D15EventKinds), client/protocol abstractions, and the fake in-process client powering cross-OS tests. - Phase 0 decisions recorded: 32-bit interpreter mandatory (Automation Wrapper is a 32-bit in-proc DLL), ruamel.yaml over PyYAML (no 32-bit cp314 wheels), COM imports import-guarded for cross-OS test runs.