diff --git a/docs/Historian.md b/docs/Historian.md index 57153022..612e92bb 100644 --- a/docs/Historian.md +++ b/docs/Historian.md @@ -291,6 +291,56 @@ See [AlarmHistorian.md](AlarmHistorian.md) for the historian sidecar setup and --- +--- + +## Cross-driver TagConfig key reference + +The `TagConfig` JSON blob is the single extension surface for per-tag server-side +behaviours across all drivers. Keys from multiple phases coexist in the same blob; +unknown keys are preserved byte-stable on round-trip by all typed editors. + +### Historian + alarm keys (Phase B / Phase C) + +| Key | Type | Phase | Description | +|---|---|---|---| +| `isHistorized` | bool | C | Marks the tag historized (HistoryRead + Historizing=true). | +| `historianTagname` | string | C | Explicit historian tagname override (defaults to `FullName`). | +| `alarm` | object | B | Native alarm definition (`alarmType`, `severity`, `historizeToAveva`, …). | + +### Array keys (Phase 4c) + +| Key | Type | Phase | Description | +|---|---|---|---| +| `isArray` | bool | 4c | When `true` and `arrayLength >= 1`, materialises the node as a 1-D array (`ValueRank=OneDimension`). Absent or `false` → scalar. | +| `arrayLength` | uint (≥1) | 4c | Element count; sets `ArrayDimensions`. Required when `isArray: true`. | + +### Cross-driver array coverage matrix + +| Driver | Read mechanism | Live-verify status | +|---|---|---| +| **Modbus** | Contiguous FC03/FC04 block; String/BitInRegister modes supported | Mac-verifiable (sim `10.100.0.35:5020`) | +| **S7** | `ReadBytesAsync` block + per-element decode loop | Unit-proven (fixture down) | +| **AB CIP** | libplctag native array read (atomic + UDT member arrays) | Unit-proven (fixture down) | +| **AB Legacy** | PCCC multi-element file read via libplctag (cap 256 elements) | Unit-proven (fixture down) | +| **TwinCAT** | ADS native array symbol read | Unit-proven (fixture down) | + +Array writes, multi-dimensional arrays (ValueRank>1), and per-element historization are out of scope — see [Uns.md §Array tags](Uns.md#array-tags-1-d). + +--- + +## Closed stillpending.md §2 items (Phase 4 / Phase 4b) + +The following items from the `stillpending.md` backlog §2 were closed by earlier +phases and are recorded here so future audits don't re-flag them. + +| Item | Closed by | Commit | +|---|---|---| +| Modbus Int64 / UInt64 OPC UA node `DataType` correction | Phase 4 | `bd8fee61` | +| `HistoryAggregateType.Total` — `Total` aggregate | Phase 4 (derived client-side as time-weighted Average × interval-seconds) | `5e27b5f7` | +| Historian poison alarm-event indefinite retry — dead-letter cap (task #437) | Phase 4 | `fcb38014` | + +--- + ## See also - [docs/plans/2026-06-14-galaxy-phase-c-historian-design.md](plans/2026-06-14-galaxy-phase-c-historian-design.md) — full design and implementation notes @@ -298,3 +348,4 @@ See [AlarmHistorian.md](AlarmHistorian.md) for the historian sidecar setup and - [AlarmTracking.md](AlarmTracking.md) — OPC UA Part 9 alarm surface (event history source) - [Client.CLI.md](Client.CLI.md) — full `historyread` flag reference - [ScriptedAlarms.md](ScriptedAlarms.md) §"Native driver alarms" — the Phase B `alarm` object in `TagConfig` (parallel carrier) +- [Uns.md §Array tags](Uns.md#array-tags-1-d) — `isArray`/`arrayLength` keys, cross-driver coverage, and deferrals diff --git a/docs/Uns.md b/docs/Uns.md index af379475..ff14c934 100644 --- a/docs/Uns.md +++ b/docs/Uns.md @@ -169,6 +169,67 @@ behaviour, continuation-point paging, and aggregates. > alarms](ScriptedAlarms.md#native-driver-alarms-equipment-tag-path) for > details. +## Array tags (1-D) + +A tag can be made a **1-D OPC UA array node** by setting two keys in its `TagConfig` +JSON blob. The controls are exposed as first-class UI fields in the Tag modal (an +**Is array** checkbox + an **Array length** numeric input), available for all drivers +— typed editors (Modbus, S7, etc.) and the raw-JSON textarea (Galaxy) alike. + +### Canonical rule + +| Condition | OPC UA node shape | +|---|---| +| `isArray: true` AND `arrayLength >= 1` | 1-D array node (`ValueRank = OneDimension`, `ArrayDimensions = [arrayLength]`) | +| `isArray: false` (any `arrayLength`) | Scalar node (default) | +| `isArray` absent | Scalar node (default) | + +A single-element array (`isArray: true, arrayLength: 1`) is valid and materialises as a +`[1]` node. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| `isArray` | bool | no | When `true` (and `arrayLength >= 1`), makes the node a 1-D array. Absent or `false` → scalar. | +| `arrayLength` | uint (≥ 1) | when `isArray: true` | The element count for the OPC UA `ArrayDimensions` declaration. Must be ≥ 1 when `isArray` is set. | + +UI validation rejects `arrayLength = 0` when `isArray` is checked. + +### Examples + +```json +{"FullName":"PLC1.TemperatureArray","isArray":true,"arrayLength":10} +``` + +```json +{"Register":"HR100","DataType":"Float32","isArray":true,"arrayLength":5} +``` + +Combined with historization (values are arrays — history of the whole array snapshot): + +```json +{"Register":"HR200","DataType":"Int16","isArray":true,"arrayLength":20,"isHistorized":true} +``` + +### Per-driver read mechanism and live-verify status + +| Driver | Read mechanism | Live-verify | +|---|---|---| +| **Modbus** | Contiguous FC03/FC04 block (`arrayLength × registers-per-element`); String and BitInRegister array modes also supported | Mac-verifiable (sim `10.100.0.35:5020`) | +| **S7** | Contiguous `ReadBytesAsync` block over the declared address span + per-element decode loop | Unit-proven (sim fixture down) | +| **AB CIP** | libplctag native array read (atomic and UDT member arrays) | Unit-proven (sim fixture down) | +| **AB Legacy** | PCCC multi-element file read via libplctag (cap 256 elements) | Unit-proven (sim fixture down) | +| **TwinCAT** | ADS native array symbol read against the declared `SymbolPath` | Unit-proven (sim fixture down) | + +### Out of scope (named deferrals) + +- **Array writes** (inbound client→device write of an array value) — tagged for a follow-up phase. +- **Multi-dimensional arrays** (`ValueRank > 1`) — not supported; all arrays are 1-D. +- **Array historization** — a historized array tag materialises with the correct `Historizing` flag, but the Wonderware sidecar historian treats the value as an opaque blob; per-element history is out of scope. + +See the individual driver docs under `docs/drivers/` for per-driver implementation details. + ## Galaxy address picker — native-alarm pre-fill When the Galaxy address picker selects an attribute that is itself an alarm diff --git a/docs/drivers/AbCip.md b/docs/drivers/AbCip.md index b2b3e0e2..6b844d03 100644 --- a/docs/drivers/AbCip.md +++ b/docs/drivers/AbCip.md @@ -97,6 +97,28 @@ the projection semantics don't exactly mirror Rockwell FT A&E. - **Integration tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/` run against the `ab_server` Docker fixture. See [AbServer-Test-Fixture.md](AbServer-Test-Fixture.md) for the coverage map and the `AB_SERVER_ENDPOINT` wiring. - **Manual client** — [Driver.AbCip.Cli.md](../Driver.AbCip.Cli.md). +## 1-D array support + +An AB CIP tag becomes a **1-D OPC UA array node** when its `TagConfig` JSON carries +`"isArray": true` and `"arrayLength": N` (N ≥ 1). The canonical rule: +`isArray: true` + `arrayLength >= 1` → array; `isArray: false` (any length) → scalar. + +**Read mechanism** — the driver delegates to **libplctag's native array read**. The tag's +`TagPath` is passed to libplctag as a `[N]`-subscripted symbol (or the tag is created with +an element count of N so libplctag fetches the full block in one EIP/CIP request). Both +atomic types (`DINT[N]`, `REAL[N]`, …) and UDT member arrays are supported. + +**Unit test coverage** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/` covers +array reads via the fake tag runtime for atomic and UDT member cases. The `ab_server` +integration fixture is down during normal dev (Docker host fixture currently offline). + +**Live-verify** — integration-fixture-gated (requires `ab_server` Docker fixture on `10.100.0.35:44818`). + +**Deferrals** — array *writes*, multi-dimensional arrays, per-element historization. Safety-partition array tags (`SafetyTag: true`) are excluded (GuardLogix write constraints extend to array writes). + +See [Uns.md §Array tags](../Uns.md#array-tags-1-d) for the cross-driver coverage matrix +and the UI authoring flow. + ## Operational Notes - **Native heap is invisible to the GC.** `GetMemoryFootprint()` reports CLR allocations only; libplctag's native `Tag` heap does not show up there. Watch whole-process RSS, and use `ReinitializeAsync` (tears down + re-creates every device's libplctag handles) as the remediation for native-heap growth. diff --git a/docs/drivers/AbLegacy.md b/docs/drivers/AbLegacy.md index c0c23082..99d39819 100644 --- a/docs/drivers/AbLegacy.md +++ b/docs/drivers/AbLegacy.md @@ -93,6 +93,33 @@ reproduced in [docs/v2/driver-specs.md §4](../v2/driver-specs.md). - **Integration tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/` run against the AB Legacy Docker fixture. See [AbLegacy-Test-Fixture.md](AbLegacy-Test-Fixture.md) for the coverage map. - **Manual client** — [Driver.AbLegacy.Cli.md](../Driver.AbLegacy.Cli.md). +## 1-D array support + +An AB Legacy tag becomes a **1-D OPC UA array node** when its `TagConfig` JSON carries +`"isArray": true` and `"arrayLength": N` (N ≥ 1, capped at 256). The canonical rule: +`isArray: true` + `arrayLength >= 1` → array; `isArray: false` (any length) → scalar. + +**Read mechanism** — the driver issues a **PCCC multi-element file read** via libplctag. +The tag's file address (e.g. `N7:0`) is extended by `N` element count so libplctag fetches +`N` consecutive PCCC file elements in one EIP/PCCC request. The response is decoded into +N values of the declared `AbLegacyDataType`. Maximum element count is 256 (PCCC payload +limit). + +This closes the previously-noted limitation ("single-element addressing today") for the +array read path. Array *writes* remain a follow-up. + +**Unit test coverage** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/` covers +the multi-element read path via the fake tag runtime. The AB Legacy integration fixture is +down during normal dev. + +**Live-verify** — integration-fixture-gated (requires the AB Legacy Docker fixture). + +**Deferrals** — array *writes*, multi-dimensional arrays, per-element historization. +`arrayLength > 256` is rejected at init with an explicit error (PCCC payload cap). + +See [Uns.md §Array tags](../Uns.md#array-tags-1-d) for the cross-driver coverage matrix +and the UI authoring flow. + ## Operational Notes - **Native heap is invisible to the GC.** As with AB CIP, `GetMemoryFootprint()` reports CLR allocations only; watch whole-process RSS and use `ReinitializeAsync` to recycle libplctag handles. diff --git a/docs/drivers/Modbus.md b/docs/drivers/Modbus.md index 20e981b8..7023dc26 100644 --- a/docs/drivers/Modbus.md +++ b/docs/drivers/Modbus.md @@ -115,6 +115,34 @@ is reproduced in [docs/v2/driver-specs.md §2](../v2/driver-specs.md). The canonical stored/dispatched `DriverType` value is **`"Modbus"`** — this is what the runtime factory key, docker-dev seed, AdminUI editor-map, tag-config validator, and probe all use. The AdminUI displays the friendly label **"Modbus TCP"** and the new-driver route slug is `modbustcp`, but neither the slug nor the label is stored in the database. Any pre-existing `DriverInstance` rows that carry the legacy value `"ModbusTcp"` must be updated to `"Modbus"` to receive typed-editor dispatch and factory instantiation. +## 1-D array support + +A Modbus tag becomes a **1-D OPC UA array node** when its `TagConfig` JSON carries +`"isArray": true` and `"arrayLength": N` (N ≥ 1). The canonical rule: +`isArray: true` + `arrayLength >= 1` → array; `isArray: false` (any length) → scalar. + +**Read mechanism** — the driver reads a contiguous block of `N × (registers per element)` +holding/input registers in a single FC03/FC04 PDU, then splits the response into N +decoded values. Three element-type modes are supported: + +| Mode | How N elements are read | +|---|---| +| Numeric (Bool, Int16, UInt16, Int32, UInt32, Float32, Float64, Int64, UInt64) | N × element-width registers per PDU; coalescing rules apply as for scalar reads | +| String (`DataType: STR`) | N × ceil(len/2) registers per PDU; each element decoded as a fixed-length ASCII string | +| BitInRegister | N bits packed in one register (or straddling registers); bit index advances per element | + +**Unit test coverage** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests/` covers +numeric, String, and BitInRegister array reads. + +**Live-verify** — Mac-verifiable against the simulator at `10.100.0.35:5020` (no fixture +bring-up required; the sim is always-on on the shared Docker host). + +**Deferrals** — array *writes* (FC16 multi-register write for an array value) are a named +follow-up; multi-dimensional arrays (`arrayLength` is always 1-D); per-element historization. + +See [Uns.md §Array tags](../Uns.md#array-tags-1-d) for the cross-driver coverage matrix +and the UI authoring flow. + ## Operational Notes - **Wrong-endian readings are silently plausible.** A byte-order misconfiguration produces a wrong number, not a Bad quality code — surface byte-order mismatches as data-validation alerts, not status codes (see [docs/v2/driver-specs.md §2](../v2/driver-specs.md)). diff --git a/docs/drivers/S7.md b/docs/drivers/S7.md index fa163ac8..c10a27bf 100644 --- a/docs/drivers/S7.md +++ b/docs/drivers/S7.md @@ -140,6 +140,35 @@ Portal under *Protection & Security* for the CPU. - **CLI** — [Driver.S7.Cli.md](../Driver.S7.Cli.md) documents the standalone read/write/probe CLI for manual checks against a real or simulated CPU. +## 1-D array support + +An S7 tag becomes a **1-D OPC UA array node** when its `TagConfig` JSON carries +`"isArray": true` and `"arrayLength": N` (N ≥ 1). The canonical rule: +`isArray: true` + `arrayLength >= 1` → array; `isArray: false` (any length) → scalar. + +**Read mechanism** — the driver issues a single `ReadBytesAsync` call over the contiguous +memory span starting at the declared address for `N × (bytes per element)` bytes, then +loops over the response buffer decoding each element individually using the same +reinterpret/box logic as scalar reads. This keeps wire round-trips at 1 per array tag +regardless of N. + +**Supported element types** — any `S7DataType` that has a scalar decode path today +(`Bool`, `Byte`, `Int16`, `UInt16`, `Int32`, `UInt32`, `Float32`). The `ReadBytesAsync` +network half is a thin `S7.Net` call; the decode half is fully unit-tested. + +**Unit test coverage** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/` covers +the contiguous-block read and per-element decode loop. Integration fixture is down during +normal dev (the S7 sim is on the shared Docker host but currently offline). + +**Live-verify** — integration-fixture-gated (not Mac-verifiable without the S7 sim up). + +**Deferrals** — array *writes*, multi-dimensional arrays, per-element historization. The +`Timer` / `Counter` address types that are rejected at init for scalars are also excluded +for arrays. + +See [Uns.md §Array tags](../Uns.md#array-tags-1-d) for the cross-driver coverage matrix +and the UI authoring flow. + ## Further reading - [`docs/v2/driver-specs.md §5`](../v2/driver-specs.md) — full per-field spec, diff --git a/docs/drivers/TwinCAT.md b/docs/drivers/TwinCAT.md index 68933e01..05b0ac2c 100644 --- a/docs/drivers/TwinCAT.md +++ b/docs/drivers/TwinCAT.md @@ -121,6 +121,31 @@ writes use the driver-wide `Timeout`. - **CLI** — [Driver.TwinCAT.Cli.md](../Driver.TwinCAT.Cli.md) documents the standalone read/write/browse/probe CLI for manual checks. +## 1-D array support + +A TwinCAT tag becomes a **1-D OPC UA array node** when its `TagConfig` JSON carries +`"isArray": true` and `"arrayLength": N` (N ≥ 1). The canonical rule: +`isArray: true` + `arrayLength >= 1` → array; `isArray: false` (any length) → scalar. + +**Read mechanism** — the driver performs an **ADS native array symbol read** against the +declared `SymbolPath`. ADS returns the full array value buffer for a symbol that maps to a +TwinCAT array type (e.g. `ARRAY [0..9] OF REAL`); the driver reads `N` elements from the +response. When `UseNativeNotifications` is enabled, ADS also pushes array-value changes in +a single notification payload — no per-element polling. + +**Unit test coverage** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests/` covers +the array symbol read path via the fake ADS client factory. The TwinCAT integration +fixture is down during normal dev. + +**Live-verify** — integration-fixture-gated (requires the TwinCAT Docker fixture / AMS router). + +**Deferrals** — array *writes* (`ADS WriteValueAsync` for an array), multi-dimensional +arrays, per-element historization. `IRediscoverable` still fires and rebuilds array nodes +on PLC re-download (unchanged semantics from scalar nodes). + +See [Uns.md §Array tags](../Uns.md#array-tags-1-d) for the cross-driver coverage matrix +and the UI authoring flow. + ## Further reading - [`docs/v2/driver-specs.md §6`](../v2/driver-specs.md) — full per-field spec and