🚧 WIP
This commit is contained in:
@@ -27,7 +27,7 @@ opcda-diagnozer/
|
||||
├── src/opcda_diagnozer/
|
||||
│ ├── __init__.py
|
||||
│ ├── __main__.py # python -m opcda_diagnozer
|
||||
│ ├── main.py # CLI: run / install / uninstall / check-config / probe / list-devices / list-tags
|
||||
│ ├── main.py # CLI: run / install / uninstall / check-config / probe / read / list-items
|
||||
│ ├── config.py # pydantic-free typed config (dataclasses) + YAML load + validation
|
||||
│ ├── events.py # DiagEvent, Severity, EventKind
|
||||
│ ├── quality.py # OPC DA quality decode tables
|
||||
@@ -44,6 +44,7 @@ opcda-diagnozer/
|
||||
│ ├── report/
|
||||
│ │ ├── base.py # Reporter protocol
|
||||
│ │ ├── jsonl.py # rotating JSONL file
|
||||
│ │ ├── textlog.py # plain-text anomaly log (human-readable, §9.1)
|
||||
│ │ ├── eventlog.py # Windows Event Log (WARN+)
|
||||
│ │ └── http.py # optional webhook
|
||||
│ └── service.py # pywin32 ServiceFramework wrapper
|
||||
@@ -82,7 +83,13 @@ Tasks:
|
||||
|
||||
1. `config.py` — YAML config (see `config.example.yaml` sketch in README), dataclass
|
||||
model, strict validation, `check-config` messages that explain exactly what is wrong.
|
||||
2. `quality.py` — decode tables (spec §7), `decode_quality(raw: int) -> QualityInfo`.
|
||||
Includes the **device-link indicators** (spec §6.1 / D16): `devices:` — each entry a
|
||||
human-readable device name plus its connection-indicator tag ItemID. Indicators are
|
||||
added to the polled tag set automatically; `check-config` warns when an indicator tag
|
||||
is also listed in `tags:` (it should not be) and fails on an empty device name.
|
||||
2. `quality.py` — decode tables (spec §7), `decode_quality(raw: int) -> QualityInfo`,
|
||||
plus the **clear-text status helper**: `status_text(raw) -> "NAME (0xNN)"` used by
|
||||
every reporter (spec §7 closing rule — e.g. `BAD / communication failure (0x18)`).
|
||||
3. `opc/base.py` — client abstraction:
|
||||
```python
|
||||
class OpcClient(Protocol):
|
||||
@@ -99,6 +106,14 @@ Tasks:
|
||||
6. `monitor/detectors.py` — D1–D15 as small pure functions/classes over
|
||||
`PollOutcome` (list[ReadResult] + rtt_ms + error), using an injected clock for
|
||||
testability. Rolling window (deque, size `window_polls`) for p95/streak logic.
|
||||
**D16 device-link detector** (spec §6.1): per configured indicator — quality
|
||||
not GOOD, a read error, or a GOOD read whose value is **not in the
|
||||
configurable `ok_values` list** (default `[true]`; string-equality compare
|
||||
of the rendered value, so inverted fault flags `[false, 0]` and string states
|
||||
`["RUN"]` both work) ⇒ `DEVICE_LINK_DOWN` (CRITICAL) carrying the device name,
|
||||
indicator tag, clear-text status and value; recovery (`DEVICE_LINK_UP`) when
|
||||
it reads GOOD with an ok value again. Edge-level per device, so flapping
|
||||
links don't spam (one event per transition).
|
||||
7. `monitor/poller.py` — the single COM thread; watchdog logic for hang detection
|
||||
(spec §10.4): a second thread timestamps poll starts/ends; on `hard_hang_s` the COM
|
||||
thread is abandoned and rebuilt.
|
||||
@@ -107,17 +122,26 @@ Tasks:
|
||||
hangs, quality flips) → powers the entire unit-test suite without COM/hardware.
|
||||
|
||||
**Exit:** `opcda-diagnozer run` (foreground console) produces correct events against the
|
||||
fake client; unit tests for all detectors.
|
||||
fake client; unit tests for all detectors — including D16 edge semantics (false /
|
||||
non-GOOD / read-error / misconfigured value / recovery) against scripted indicator reads.
|
||||
|
||||
## 5. Phase 2 — Reporters (≈1 day)
|
||||
|
||||
1. `report/jsonl.py` — rotation (size + backups), atomic-ish append, header line with
|
||||
config digest for provenance.
|
||||
2. `report/eventlog.py` — pywin32 `win32evtlog` usage; map severities; source
|
||||
2. `report/textlog.py` — **plain-text anomaly log** (spec §9.1): `anomalies-YYYYMMDD.log`
|
||||
in the same directory as the JSONL files, one human-readable line per detected
|
||||
anomaly (`timestamp SEVERITY KIND message`) with clear-text status `NAME (0xNN)`.
|
||||
Anomalies only — heartbeats/CONNECTED are filtered out (a small `is_anomaly(event)`
|
||||
predicate on `EventKind`); recoveries (D2, `DEVICE_LINK_UP`, quality back to GOOD)
|
||||
**are** logged, so the file reads as an incident narrative. Daily + size rotation
|
||||
with `backups` kept, same scheme as the JSONL reporter. Every line corresponds to
|
||||
a JSONL record (text = convenience view, JSONL = source of truth).
|
||||
3. `report/eventlog.py` — pywin32 `win32evtlog` usage; map severities; source
|
||||
registration via `install` step.
|
||||
3. `report/http.py` — optional webhook with timeout + simple retry/drop on failure
|
||||
4. `report/http.py` — optional webhook with timeout + simple retry/drop on failure
|
||||
(never blocks the poll loop; queue with bound).
|
||||
4. Heartbeat assembly (D15): uptime, reconnects, RTT percentiles, tags-bad counts.
|
||||
5. Heartbeat assembly (D15): uptime, reconnects, RTT percentiles, tags-bad counts.
|
||||
|
||||
**Exit:** events visible in file + Event Log; heartbeat cadence verified.
|
||||
|
||||
@@ -150,6 +174,7 @@ Scenario matrix against a simulation server (and, when available, the real serve
|
||||
| Quality oscillation | toggling sim items | D11 flapping |
|
||||
| Stale/frozen tag | simulation server static item marked `expect_change` | D12 / D13 |
|
||||
| Mass bad quality | all sim items to bad | D14 |
|
||||
| **Device link down** | indicator tag flipped to `false` / set BAD (alias a bool sim item per device, e.g. `P5001.Connected` on Matrikon) | D16 `DEVICE_LINK_DOWN`, recovery line when restored |
|
||||
| Config errors | bad ProgID, unknown tag | fast fail + actionable message; `OPC_E_INVALIDITEMID` surfaced at startup |
|
||||
| Soak | 24 h healthy run | no leaks, heartbeats every 5 min, zero false positives |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user