# Changelog All notable changes to the OPC DA Diagnozer are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions bump per [SemVer](https://semver.org/). Dates are YYYY-MM-DD. ## [Unreleased] ### Added — server discovery & browsing - `list-servers` verb: 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: `all` prints the full structure tree (branches with `/`, tags with `[ItemID: ...]`), `branches` the structure-only flat list, `tags` the flat ItemID list for config `tags:`. `--branch` (dotted path or repeated segments), `--depth N` (1 default, 0 = whole subtree), `--json`. Replaces the earlier split `list-devices`/`list-tags`. - `browse.max_depth` config key (default 32) as the address-space walk guard, validated by `check-config`; cycle-safe tree walk in `opc/browse.py`. - `SpaceNode` tree data model (branch/leaf) replacing the earlier device-centric `DeviceInfo`. ### Added — reading & CLI - `read` verb: 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. - `probe` verb: connect once and read every configured tag once. - `--server` / `--host` overrides on every connecting verb; with `--server` no config file is needed at all. - Frozen-exe default config path: `config.yaml` next to the exe (a Windows service starts with `CWD = System32`), not the working directory. ### Added — monitoring design (D16) & config - `devices:` config section with `DeviceEntry(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-config` prints the indicators. - Detection rules D16 (`DEVICE_LINK_DOWN`/`DEVICE_LINK_UP`) and the plain-text anomaly log reporter (`report/textlog.py` task) 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. - `--version` and 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 in `as_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 set` because pywin32 lazily imports `win32timezone` during 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 to `None`. - 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) and `OPC_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-driven `max_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–D15 `EventKind`s), 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.