This commit is contained in:
Jonas Bengtsson
2026-10-08 06:30:54 +02:00
parent 76b1cc5ccf
commit a1af8d68c9
12 changed files with 966 additions and 21 deletions
+90
View File
@@ -0,0 +1,90 @@
# 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.