Captures uncommitted work that lived in the working tree on
v2-mxgw-integration but was orthogonal to the migration. Stashed
during the v2-mxgw merge to master (2026-04-30) and replanted here on
a feature branch off master so it's git-visible rather than living in
the stash list.
Two distinct buckets:
1. Tracked fixture/config refinements (10 files, ~36 lines):
- scripts/e2e/test-opcuaclient.ps1
- src/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json
- 5 docker-compose.yml under tests/.../IntegrationTests/Docker/
(AbCip, Modbus, OpcUaClient, S7)
- 4 fixture .cs files (AbServerFixture, ModbusSimulatorFixture,
OpcPlcFixture, Snap7ServerFixture)
2. Untracked driver-gaps queue artifacts (~8000 lines):
- docs/plans/{abcip,ablegacy,focas,opcuaclient,s7,twincat}-plan.md
— per-driver gap plans
- docs/featuregaps.md — cross-cutting analysis
- docs/v2/focas-deployment.md, docs/v2/implementation/focas-simulator-plan.md
- followup.md — auto/driver-gaps queue follow-ups
- scripts/queue/ — PR-queue automation tooling (12 files including
pr-manifest.yaml at 1473 lines)
This commit is a snapshot for recoverability — review and split into
focused PRs (or discard) before merging anywhere downstream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
683 lines
44 KiB
Markdown
683 lines
44 KiB
Markdown
# AbCip Driver — Implementation Plan
|
|
|
|
> Source of gap analysis: [featuregaps.md → AbCip](../featuregaps.md#abcip-allen-bradley-ethernetip--logix)
|
|
>
|
|
> This plan covers the **Build = Yes** items only. Skip-rated gaps are listed at the bottom for traceability.
|
|
|
|
## Summary
|
|
|
|
This plan closes the 16 Build-rated AbCip gaps in five phases ordered to ship correctness fixes
|
|
first, then engineering workflow, then performance, then operability, and finally redundancy.
|
|
Phase 1 lands the data-type fidelity work (LINT/ULINT, native STRINGnn, array slicing,
|
|
write-multi packing) that today silently truncates 64-bit values and serialises adjacent reads
|
|
into N round-trips. Phase 2 introduces the offline tag-import workflow (L5K/L5X + CSV) that
|
|
Studio 5000 shops require before they will switch off Kepware. Phase 3 exposes the
|
|
performance levers commercial drivers ship as field knobs — symbolic vs logical addressing,
|
|
configurable Connection Size, and the logical-blocking / logical-non-blocking strategy
|
|
selector. Phase 4 surfaces per-tag scan rates, write deadband, online tag-DB refresh trigger,
|
|
and the diagnostic system tags an HMI dashboard expects. Phase 5 adds HSBY paired-IP failover
|
|
for continuous-process plants. Headline outcome: parity with Kepware's Logix Database Settings
|
|
and TOP Server's protocol-mode picker, with measurable throughput wins (3-5x on dense rigs via
|
|
logical addressing, single-PDU reads on contiguous arrays, single-PDU writes on multi-tag
|
|
recipe pushes).
|
|
|
|
## Phased delivery
|
|
|
|
### Phase 1 — Data-type correctness (4 PRs)
|
|
|
|
Goal: stop silently losing data. None of the items in this phase are user-visible features —
|
|
they are correctness fixes against existing capability surfaces.
|
|
|
|
#### PR 1.1 — LINT / ULINT 64-bit fidelity
|
|
- **Scope**: replace the truncating `Int32` widening at `AbCipDataType.cs:53` with `Int64`
|
|
routing across decode + encode + the `DriverDataType` map. Includes `DT` (epoch-millis on
|
|
Logix v32+ surfaces as LINT, not DINT — verify against `LibplctagTagRuntime.cs:53` before
|
|
reusing the same code-path).
|
|
- **Files**: `AbCipDataType.cs` (mapping), `LibplctagTagRuntime.cs` (already calls
|
|
`_tag.GetInt64` / `SetInt64`, so the runtime is correct — the gap is the surface enum
|
|
flattening into `Int32`), `Core.Abstractions/DriverDataType.cs` may need an `Int64` /
|
|
`UInt64` member if not already present.
|
|
- **Test approach**: unit (xUnit + Shouldly) with a fake `IAbCipTagRuntime` that returns
|
|
`long.MaxValue` on `DecodeValueAt(LInt, ...)`; assert the snapshot value round-trips through
|
|
the read path without truncation. Integration test against pymodbus is N/A — needs a live
|
|
Logix or a libplctag mock-server fixture; keep this unit-only and rely on smoke testing on
|
|
the dev box with a real ControlLogix.
|
|
- **Effort**: S
|
|
- **Dependencies**: confirm `DriverDataType.Int64` exists; if not, that is a Core change
|
|
shared with the Modbus TODO at `AbCipDataType.cs:53`.
|
|
- **Docs / fixture / e2e**: appends a Logix-types row to the type-mapping table in
|
|
`docs/Driver.AbCip.Cli.md` (CLI gains `--type LInt` / `--type ULInt`); extends
|
|
`docs/drivers/AbServer-Test-Fixture.md` §"What it actually covers" to list `LINT` once
|
|
ab_server is reseeded with a `TestLINT:LINT[1]` tag; updates the
|
|
`tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/docker-compose.yml`
|
|
ControlLogix profile to seed `TestLINT`; adds a 64-bit assertion case in
|
|
`AbCipReadSmokeTests`; extends `scripts/e2e/test-abcip.ps1` with an LInt loopback
|
|
assertion (and a matching seeded `TestLINT` in `scripts/smoke/seed-abcip-smoke.sql`).
|
|
|
|
#### PR 1.2 — Native STRING / STRINGnn variant decoding
|
|
- **Scope**: Today `AbCipDataType.String` flattens any Logix `STRING` UDT into a .NET
|
|
string via libplctag's `_tag.GetString(0)`. Logix programs commonly define
|
|
`STRING_20`, `STRING_40`, `STRING_80` variants with different DATA-array sizes; libplctag
|
|
honours these when the tag name resolves to the user-defined type, but our discovery
|
|
emits them as the generic `String` placeholder. Add a `StringLength` field to
|
|
`AbCipStructureMember` + `AbCipTagDefinition` so declared variants carry their cap, and
|
|
thread it into the `Tag.Name` attribute or a libplctag string-cap hint.
|
|
- **Files**: `AbCipDataType.cs`, `AbCipDriverOptions.cs` (record fields), `LibplctagTagRuntime.cs`
|
|
(string-length aware decode/encode), and the discovery emit at `AbCipDriver.cs:715`.
|
|
- **Test approach**: unit test with a fake runtime returning `string` values shorter and
|
|
longer than the declared cap; integration test deferred until a sample L5X with mixed
|
|
STRING variants is available.
|
|
- **Effort**: M
|
|
- **Dependencies**: investigate libplctag's `str_max_capacity` / `str_count_word_bytes`
|
|
attributes — the docs reference them but the C# wrapper may not expose them; if not, this
|
|
PR must extend `LibplctagTagRuntime` with a raw-buffer decode path.
|
|
- **Docs / fixture / e2e**: extends `docs/Driver.AbCip.Cli.md` with a new `--string-size`
|
|
flag in the `read`/`write` cookbook plus a STRINGnn worked example; updates
|
|
`docs/drivers/AbServer-Test-Fixture.md` §"What it actually covers" to list
|
|
`STRING_20`/`STRING_80` once seeded; extends the ControlLogix profile in
|
|
`tests/.../Docker/docker-compose.yml` with `TestSTRING80:STRING[1]` (plus a `STRING_20`
|
|
variant if `ab_server` honours non-default DATA caps; otherwise documented as Emulate-tier
|
|
only); adds `tests/.../IntegrationTests/AbCipStringDecodingTests.cs` round-trip; adds a
|
|
short-string round-trip case to `scripts/e2e/test-abcip.ps1` and a `TestSTRING80` row to
|
|
`scripts/smoke/seed-abcip-smoke.sql`.
|
|
|
|
#### PR 1.3 — Array-slice read addressing `Tag[0..N]`
|
|
- **Scope**: today `AbCipTagPath` parses `Tag[3,5]` as a single element. Add slice syntax
|
|
`Tag[0..15]` (parsed in `AbCipTagPath.TryParse`) and a planner that issues one libplctag
|
|
read with `elem_count=N` per Rockwell array semantics, decoding the buffer at element
|
|
stride into N output snapshots. Mirrors the whole-UDT planner pattern.
|
|
- **Files**: `AbCipTagPath.cs` (parser — add `IsSlice` + `SliceLength` to the path segment
|
|
record, or carry it on `AbCipTagPath` itself), new `AbCipArrayReadPlanner.cs` next to
|
|
`AbCipUdtReadPlanner.cs`, `AbCipDriver.ReadAsync` to dispatch through the planner,
|
|
`IAbCipTagRuntime` to add `DecodeArrayAt(type, elementStride, count)` or build on
|
|
`DecodeValueAt`. Investigate libplctag's `elem_count` attribute on `Tag` create to confirm
|
|
the right wire-level switch.
|
|
- **Test approach**: parser unit tests for the new syntax, planner unit tests with fake
|
|
runtime, integration smoke against a live ControlLogix DINT[100] tag using the dev-box
|
|
PLC.
|
|
- **Effort**: L
|
|
- **Dependencies**: PR 1.1 must land first if the array element type is LINT — otherwise the
|
|
slice path silently truncates 64-bit elements.
|
|
- **Docs / fixture / e2e**: extends `docs/Driver.AbCip.Cli.md` `read` section with the
|
|
`Tag[0..N]` slice syntax + a worked example reading `Recipe[0..15]` in one round-trip;
|
|
updates `docs/drivers/AbServer-Test-Fixture.md` §"What it actually covers" to mention
|
|
the existing `DINT[16]` array tag is now exercised end-to-end via slicing; extends
|
|
`AbCipReadSmokeTests` with a slice-read assertion against the seeded `TestDINTArray`;
|
|
adds `tests/.../IntegrationTests/AbCipArraySliceTests.cs` covering edge cases
|
|
(boundary, single-element, full-range); adds a slice-read assertion to
|
|
`scripts/e2e/test-abcip.ps1`.
|
|
|
|
#### PR 1.4 — CIP multi-tag write packing
|
|
- **Scope**: `AbCipDriver.WriteAsync` (`AbCipDriver.cs:460-546`) loops over writes one-by-one.
|
|
Group writes by `(device, no-bit-RMW)` and submit one CIP Multi-Service Packet (0x0A)
|
|
carrying up to N write-singles per round-trip. Honours the per-family
|
|
`SupportsRequestPacking` flag at `AbCipPlcFamilyProfile.cs:36,43,51,59` — Micro800 falls
|
|
back to the existing per-write loop because its profile already disables packing.
|
|
- **Files**: `AbCipDriver.cs` (add a write planner mirroring the read planner), new
|
|
`AbCipMultiWritePlanner.cs`, possibly a new `IAbCipTagRuntime.WriteBatchAsync` method or a
|
|
new `IAbCipMultiWriter` capability since libplctag's high-level `Tag.WriteAsync` is
|
|
per-tag — investigate libplctag's `cip-msg-multi` raw-CIP path or whether building a
|
|
Multi-Service Packet via `plc_tag_create("name=@raw,...")` is feasible.
|
|
- **Test approach**: unit test the planner with a synthetic batch (mixed-device, mixed
|
|
bit-RMW, one Micro800); integration test recipe-style 50-tag write against ControlLogix
|
|
measuring round-trip count via Wireshark or via a libplctag debug-trace sink.
|
|
- **Effort**: L
|
|
- **Dependencies**: investigate libplctag multi-service-packet API; if absent, this PR may
|
|
need to drop down to raw CIP via the `@raw` pseudo-tag or be deferred.
|
|
- **Docs / fixture / e2e**: appends a "Multi-tag writes" subsection to
|
|
`docs/Driver.AbCip.Cli.md` (no flag — automatic batching when multiple writes queue
|
|
inside one publish) plus a note that Micro800 falls back per profile; updates
|
|
`docs/drivers/AbServer-Test-Fixture.md` §7 ("Capability surfaces beyond read") to flip
|
|
`IWritable.WriteAsync` from "no smoke test" to covered for the multi-write path; adds
|
|
`tests/.../IntegrationTests/AbCipMultiWriteTests.cs` asserting 50-tag batch lands in one
|
|
round-trip (count via libplctag debug-trace sink); extends `scripts/e2e/test-abcip.ps1`
|
|
with a recipe-style multi-write step; extends seed SQL with two extra DINT tags so the
|
|
e2e has a packing target.
|
|
|
|
### Phase 2 — Tag-import workflows (4 PRs)
|
|
|
|
Goal: replicate Kepware's Logix Database Settings — point the driver at an L5K/L5X export or
|
|
a CSV and have the tag table populate without an online controller.
|
|
|
|
#### PR 2.1 — L5K parser + ingest
|
|
- **Scope**: parse a Studio 5000 L5K export (a labelled-section text format with
|
|
`TAG ... END_TAG` blocks, `DATATYPE ... END_DATATYPE` UDT definitions, and program-scope
|
|
qualifiers). Produce `AbCipTagDefinition` + `AbCipStructureMember` records that match the
|
|
declarative options shape. Includes Description ingest (PR 2.3 lifts it to OPC UA
|
|
`Description`).
|
|
- **Files**: new `Import/L5kParser.cs`, new `Import/IL5kSource.cs` for testability, new
|
|
`Import/L5kIngest.cs` that converts parsed records into `AbCipTagDefinition`. Hook into
|
|
`AbCipDriverOptions` via a new `TagImports` collection (filenames or inline blobs) parsed
|
|
on `AbCipDriver.InitializeAsync`.
|
|
- **Test approach**: unit-only with sample L5K files in `tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/Import/Fixtures/`
|
|
covering controller-scope tags, program-scope tags, alias tags (skipped per Kepware
|
|
precedent), and UDTs with nested structures.
|
|
- **Effort**: L
|
|
- **Dependencies**: none — pure-text parser.
|
|
- **Docs / fixture / e2e**: new doc `docs/drivers/AbCip-TagImport.md` covering the L5K
|
|
format support matrix (controller-scope / program-scope / UDT / alias-skipped) and a
|
|
worked example of pointing `AbCipDriverOptions.TagImports` at an L5K export; appends a
|
|
`tag-import` command section to `docs/Driver.AbCip.Cli.md` (CLI gains
|
|
`tag-import --file foo.L5K`); fixture-side no change to `ab_server` (offline parse —
|
|
no PLC needed) but adds sample L5K files under
|
|
`tests/.../AbCip.Tests/Import/Fixtures/`; extends `scripts/e2e/test-abcip.ps1` with an
|
|
offline `tag-import` smoke that diffs the parsed tag set against a golden JSON.
|
|
|
|
#### PR 2.2 — L5X (XML) parser + ingest
|
|
- **Scope**: same surface as PR 2.1 but parses Studio 5000's XML export. L5X is the de-facto
|
|
modern format (Studio 5000 v21+) and carries richer metadata than L5K including
|
|
ExternalAccess attributes and AOI definitions.
|
|
- **Files**: new `Import/L5xParser.cs` using `System.Xml.XPath`, share the `IL5kSource` /
|
|
`L5kIngest` ingest layer with PR 2.1 by introducing a common `ParsedTagsBundle` record.
|
|
- **Test approach**: unit tests with sample L5X fixtures including an AOI-typed tag (sets up
|
|
the AOI work in PR 2.7).
|
|
- **Effort**: L
|
|
- **Dependencies**: PR 2.1 is preferred first to settle the shared ingest seam.
|
|
- **Docs / fixture / e2e**: extends `docs/drivers/AbCip-TagImport.md` (created in PR 2.1)
|
|
with the L5X-specific section — namespace handling, ExternalAccess attributes, AOI
|
|
references; extends the `tag-import` CLI section in `docs/Driver.AbCip.Cli.md` to note
|
|
L5X auto-detection by file extension; sample L5X files added under
|
|
`tests/.../AbCip.Tests/Import/Fixtures/` (one with an AOI-typed tag for PR 2.6);
|
|
reuses the offline `tag-import` step from `scripts/e2e/test-abcip.ps1` (now driven by
|
|
L5X) — no fixture container change because parse is offline; cross-links from
|
|
`tests/.../IntegrationTests/LogixProject/README.md` so the on-site Emulate L5X export
|
|
doubles as a parser fixture.
|
|
|
|
#### PR 2.3 — Tag descriptions surfaced as OPC UA `Description`
|
|
- **Scope**: extend `AbCipTagDefinition` with `Description` (string?), populate it from the
|
|
L5K/L5X parsers, and thread it through to `DriverAttributeInfo` so the address-space
|
|
builder sets the OPC UA `Description` attribute. Also lifts the description onto
|
|
`AbCipStructureMember` for member-level metadata.
|
|
- **Files**: `AbCipDriverOptions.cs` (record fields), `AbCipDriver.cs:760-770`
|
|
(`ToAttributeInfo` helper), `Core.Abstractions/DriverAttributeInfo.cs` (verify it carries
|
|
a Description field; if not, that becomes a Core PR shared across drivers).
|
|
- **Test approach**: unit — a discovery test asserts that a tag with a description ends up
|
|
with that description on the `DriverAttributeInfo` record.
|
|
- **Effort**: S
|
|
- **Dependencies**: PR 2.1 / PR 2.2 (descriptions only land via importer).
|
|
- **Docs / fixture / e2e**: appends a "Description metadata" subsection to
|
|
`docs/drivers/AbCip-TagImport.md` documenting how Studio 5000 descriptions surface as
|
|
OPC UA `Description`; no CLI surface change (read-side only — the existing
|
|
`otopcua-cli read` already projects `Description`); no fixture container change; adds
|
|
a cross-driver assertion to the existing OPC UA browse test in
|
|
`tests/.../IntegrationTests/` verifying the description survives the full
|
|
parser → driver → server → client path; extends `scripts/e2e/test-abcip.ps1` with a
|
|
one-line `Description != null` assertion after the import smoke step.
|
|
|
|
#### PR 2.4 — CSV tag import / export
|
|
- **Scope**: a CSV round-trip matching the Kepware column layout (`Tag Name, Address, Data
|
|
Type, Respect Data Type, Client Access, Scan Rate, Description, Scaling`). Import populates
|
|
`AbCipTagDefinition`; export dumps the live tag table for editing in Excel.
|
|
- **Files**: new `Import/CsvTagImporter.cs`, new `Import/CsvTagExporter.cs`, integration
|
|
point in `AbCipDriverOptions.TagImports` parallel to PR 2.1's hook. Export hook is
|
|
exposed via the CLI (`docs/Driver.AbCip.Cli.md`) — add a `tag-export` command.
|
|
- **Test approach**: unit tests for parser + writer with fixture CSVs; CLI integration test
|
|
using a synthetic options payload.
|
|
- **Effort**: M
|
|
- **Dependencies**: lighter than 2.1/2.2 — could ship in either order, but landing CSV after
|
|
L5X means the CSV export reuses the `ParsedTagsBundle` shape.
|
|
- **Docs / fixture / e2e**: appends a "CSV tag table" section to
|
|
`docs/drivers/AbCip-TagImport.md` documenting the column layout (Kepware-compatible) and
|
|
round-trip semantics; appends `tag-export` and CSV-flavour `tag-import` commands to
|
|
`docs/Driver.AbCip.Cli.md`; adds sample CSVs under
|
|
`tests/.../AbCip.Tests/Import/Fixtures/` plus a CLI integration test
|
|
(`tests/.../AbCip.Tests/Import/CsvRoundTripTests.cs`); extends
|
|
`scripts/e2e/test-abcip.ps1` with an export-then-import-then-diff scenario (no PLC
|
|
required); fixture-side no change.
|
|
|
|
#### PR 2.5 — Online tag-DB refresh trigger (`$Sys$UpdateTagInfo` parity)
|
|
- **Scope**: AVEVA exposes `$Sys$UpdateTagInfo` so an HMI can write `1` to force the driver
|
|
to re-walk the controller's symbol table after a Studio 5000 download — without restarting
|
|
the driver. Implement as a new `IDriverControl.RebrowseAsync()` invoked by the server or
|
|
via a system-tag write (PR 4.4 will surface system tags as browseable variables — once
|
|
that lands, this becomes the writeable system tag `_RefreshTagDb`). For now expose it
|
|
via the CLI and via a new `AbCipDriver.RebrowseAsync` method.
|
|
- **Files**: `AbCipDriver.cs` (new method that re-runs the `@tags` enumerator without going
|
|
through full `ReinitializeAsync`), CLI command in `src/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli/`,
|
|
documentation update in `docs/Driver.AbCip.Cli.md`.
|
|
- **Test approach**: unit test that two consecutive `RebrowseAsync` calls produce two
|
|
enumeration passes; integration smoke against the dev-box ControlLogix verifying the
|
|
address space picks up a tag added between rebrowses.
|
|
- **Effort**: M
|
|
- **Dependencies**: ties cleanly into PR 4.4 (system tags) but ships earlier as a
|
|
programmatic API.
|
|
- **Docs / fixture / e2e**: appends a `rebrowse` command to `docs/Driver.AbCip.Cli.md`
|
|
with a Studio 5000 download recipe ("after a download, run
|
|
`otopcua-abcip-cli rebrowse -g …`"); cross-references the future `_RefreshTagDb` system
|
|
tag once PR 4.4 lands; updates `docs/drivers/AbServer-Test-Fixture.md` §7 to mark
|
|
`ITagDiscovery.DiscoverAsync` as covered for the rebrowse path; adds
|
|
`tests/.../IntegrationTests/AbCipRebrowseTests.cs` driving two consecutive
|
|
enumerations (the second sees a tag added between calls — ab_server supports runtime
|
|
reseed via its REST hook); extends `scripts/e2e/test-abcip.ps1` with a
|
|
rebrowse-after-reseed assertion (or marks it `[OnlyIfRig]` if the simulator's reseed
|
|
hook isn't reachable).
|
|
|
|
#### PR 2.6 — AOI (Add-On Instruction) input/output handling
|
|
- **Scope**: AOIs are first-class types in L5X (`AddOnInstructionDefinition` blocks). The
|
|
Template Object decoder at `CipTemplateObjectDecoder.cs` likely already handles them at
|
|
the wire level (an AOI is a Logix UDT with InOut/Input/Output qualifiers). This PR adds:
|
|
(a) AOI-aware browse paths so an AOI instance shows up as a folder with `Inputs/`,
|
|
`Outputs/`, `InOut/` sub-folders; (b) skip-on-discovery for `InOut` parameters per
|
|
Kepware's documented limitation (InOut is a pointer, not a value).
|
|
- **Files**: extend `AbCipStructureMember` with an `AoiQualifier` enum
|
|
(Input/Output/InOut/Local), L5K/L5X parser extends to set it, `AbCipDriver.DiscoverAsync`
|
|
groups members into qualifier-named sub-folders.
|
|
- **Test approach**: unit test discovery against an AOI-containing fixture.
|
|
- **Effort**: M
|
|
- **Dependencies**: PR 2.2 (L5X) lands the AOI definition parsing.
|
|
- **Docs / fixture / e2e**: appends an "AOI handling" section to
|
|
`docs/drivers/AbCip-TagImport.md` covering Inputs/Outputs/InOut grouping + the InOut
|
|
skip rationale; updates `docs/drivers/AbServer-Test-Fixture.md` §"What it does NOT
|
|
cover" to keep AOIs flagged as ab_server-blocked but call out Logix Emulate as the
|
|
authoritative tier; adds a sample AOI-bearing L5X under
|
|
`tests/.../AbCip.Tests/Import/Fixtures/` and a discovery test that asserts the
|
|
Inputs/Outputs sub-folder shape; promotes
|
|
`tests/.../IntegrationTests/Emulate/AbCipEmulateAoiTests.cs` (gated on
|
|
`AB_SERVER_PROFILE=emulate`) — no `scripts/e2e/test-abcip.ps1` change because AOIs
|
|
need Emulate or a rig.
|
|
|
|
### Phase 3 — Performance levers (3 PRs)
|
|
|
|
Goal: expose the protocol-mode + connection-tuning knobs that commercial drivers expose as
|
|
device-level config.
|
|
|
|
#### PR 3.1 — Configurable CIP Connection Size per device
|
|
- **Scope**: today the family profile hard-codes 4002 / 504 / 488 at
|
|
`AbCipPlcFamilyProfile.cs:33,42,49`. Add an optional `ConnectionSize` field to
|
|
`AbCipDeviceOptions` that overrides the family default; thread it through to the
|
|
libplctag tag-create attribute (`connection_size=N`). Validate against a sensible range
|
|
(500-4002 per Kepware's slider).
|
|
- **Files**: `AbCipDriverOptions.cs:70-73` (extend `AbCipDeviceOptions` record),
|
|
`IAbCipTagRuntime.cs` (extend `AbCipTagCreateParams` with `ConnectionSize`),
|
|
`LibplctagTagRuntime.cs` (set the `Tag.PlcType`-adjacent attribute — investigate libplctag
|
|
C# wrapper's exposure of `connection_size`; may need to set via `Tag.AddAttribute` if a
|
|
named property doesn't exist).
|
|
- **Test approach**: unit test that custom Connection Size flows from options into the
|
|
`AbCipTagCreateParams`; integration smoke against the dev-box ControlLogix verifying
|
|
reduced-size connections succeed on legacy v19 firmware. Live test required because
|
|
libplctag rejects out-of-range values silently in some versions.
|
|
- **Effort**: S
|
|
- **Dependencies**: investigate libplctag `connection_size` attribute exposure.
|
|
- **Docs / fixture / e2e**: appends a "Connection Size" subsection to a new
|
|
`docs/drivers/AbCip-Performance.md` (consolidates the Phase 3 knobs in one place) and a
|
|
brief note + warning-symptom callout in `docs/Driver.AbCip.Cli.md` for the new
|
|
per-device option in the Driver config; updates
|
|
`docs/drivers/AbServer-Test-Fixture.md` §5 (CompactLogix narrow cap) noting that
|
|
ab_server still doesn't enforce the cap so live coverage stays Emulate/rig-only;
|
|
extends `scripts/smoke/seed-abcip-smoke.sql` with a `ConnectionSize` field demo;
|
|
no `scripts/e2e/test-abcip.ps1` change (boot-time config knob, no per-call surface).
|
|
|
|
#### PR 3.2 — Symbolic vs logical (instance-ID) addressing toggle
|
|
- **Scope**: libplctag exposes `use_connected_msg=1&allow_packet_response_packing=1&logical_segment=1`
|
|
(or similar — investigate the exact attribute name) for instance-ID addressing that skips
|
|
per-poll ASCII parsing. Add a per-device `AddressingMode` enum
|
|
(`Symbolic | Logical | Auto`) and thread it through `AbCipTagCreateParams`. `Auto` is the
|
|
default and matches today's behaviour; `Logical` flips libplctag into instance-ID mode.
|
|
Logical mode requires a one-time symbol-table walk to map names to instance IDs — reuse
|
|
`LibplctagTagEnumerator` for the bootstrap.
|
|
- **Files**: `AbCipDriverOptions.cs` (per-device enum), `IAbCipTagRuntime.cs`
|
|
(`AbCipTagCreateParams.AddressingMode`), `LibplctagTagRuntime.cs` (translate to libplctag
|
|
attributes), `AbCipDriver.cs` (run a one-time symbol-walk on first read in Logical mode).
|
|
- **Test approach**: unit test attribute construction; integration benchmark — read 1000
|
|
tags in Symbolic vs Logical and assert >2x throughput on the dev-box ControlLogix.
|
|
- **Effort**: L
|
|
- **Dependencies**: investigate libplctag's instance-ID API; the mapping pseudo-tag is
|
|
`@tags` (already used for browse) but the per-tag wire flag needs research. If libplctag
|
|
doesn't expose this cleanly, the PR drops down to the raw `cip_addr` attribute.
|
|
- **Docs / fixture / e2e**: appends an "Addressing mode" section to
|
|
`docs/drivers/AbCip-Performance.md` (Symbolic / Logical / Auto trade-offs); adds a
|
|
per-device `addressing-mode` knob to `docs/Driver.AbCip.Cli.md` (CLI gains
|
|
`--addressing-mode` on `read`/`subscribe`/`write` for ad-hoc benchmarking); updates
|
|
`docs/drivers/AbServer-Test-Fixture.md` §"What it actually covers" to add Logical-mode
|
|
reads if ab_server's symbol table walks correctly under instance IDs (otherwise
|
|
marked Emulate-tier-only); adds a benchmark test
|
|
`tests/.../IntegrationTests/AbCipAddressingModeBenchTests.cs`; extends
|
|
`scripts/e2e/test-abcip.ps1` with a Symbolic-vs-Logical sanity assertion
|
|
(read 1000 tags both modes, assert Logical >= Symbolic throughput).
|
|
|
|
#### PR 3.3 — Logical-blocking / non-blocking strategy selector
|
|
- **Scope**: TOP Server names two modes: "logical-blocking" (whole-UDT read, decode members
|
|
in-memory) and "logical-non-blocking" (per-member reads packed into one Multi-Service
|
|
Packet). We have one direction shipped via `AbCipUdtReadPlanner`. Add a per-device
|
|
`ReadStrategy` enum with three values: `WholeUdt` (current behaviour), `MultiPacket`
|
|
(new: use libplctag request-packing to bundle per-member reads into one PDU when the UDT
|
|
is sparse — i.e. only 2-of-50 members subscribed), and `Auto` (planner picks based on
|
|
fraction-of-members-subscribed threshold). Strategy is per-device because Micro800
|
|
doesn't support packing.
|
|
- **Files**: `AbCipDriverOptions.cs` (per-device enum), `AbCipUdtReadPlanner.cs` (add the
|
|
threshold heuristic), new `AbCipMultiPacketReadPlanner.cs`, `AbCipDriver.ReadAsync`
|
|
dispatch. Honours `AbCipPlcFamilyProfile.SupportsRequestPacking` at the family level so a
|
|
user-selected `MultiPacket` on Micro800 falls back to per-tag with a warning logged.
|
|
- **Test approach**: unit test the heuristic on synthetic batches of varying sparsity;
|
|
integration benchmark with a 50-member UDT where 5 members are subscribed — verify
|
|
MultiPacket beats WholeUdt by buffer-size delta.
|
|
- **Effort**: L
|
|
- **Dependencies**: PR 1.4 (multi-tag write packing) builds the same libplctag-multi-service
|
|
primitive; landing 1.4 first reduces scope here.
|
|
- **Docs / fixture / e2e**: appends a "Read strategy" section to
|
|
`docs/drivers/AbCip-Performance.md` covering WholeUdt / MultiPacket / Auto plus the
|
|
sparsity-threshold heuristic; updates `docs/drivers/AbServer-Test-Fixture.md` §1
|
|
(UDT coverage) with a note that strategy switching is decided in the planner and
|
|
unit-tested only — Emulate is the authoritative wire-level coverage; adds
|
|
`tests/.../IntegrationTests/Emulate/AbCipEmulateMultiPacketReadTests.cs` (gated on
|
|
`AB_SERVER_PROFILE=emulate`); no CLI surface change beyond the existing
|
|
per-device option, no `scripts/e2e/test-abcip.ps1` change because the simulator
|
|
doesn't differentiate the two strategies on the wire.
|
|
|
|
### Phase 4 — Operability (4 PRs)
|
|
|
|
Goal: make the driver behave like a SCADA driver — per-tag scan rates, write deadband,
|
|
diagnostic system tags, online refresh trigger.
|
|
|
|
#### PR 4.1 — Per-tag scan rate / scan group bucketing
|
|
- **Scope**: today subscriptions key on a single `publishingInterval` per
|
|
`_poll.Subscribe(...)` call. Add an optional `ScanRate` field to `AbCipTagDefinition` that,
|
|
when set, overrides the subscription interval for that tag. The shared `PollGroupEngine`
|
|
already buckets by interval — the change is to read the per-tag rate at subscribe-time
|
|
and place the tag into its own bucket.
|
|
- **Files**: `AbCipDriverOptions.cs` (record field), `AbCipDriver.SubscribeAsync` (look up
|
|
per-tag override before passing to `_poll.Subscribe`). `PollGroupEngine` may need a new
|
|
`Subscribe(tags, defaultInterval, perTagOverrides)` overload — check Core for the current
|
|
signature.
|
|
- **Test approach**: unit test that two tags with different ScanRate values produce two
|
|
poll buckets; integration test verifying the faster-rate tag publishes more frequently
|
|
than the slower-rate tag inside one subscription.
|
|
- **Effort**: M
|
|
- **Dependencies**: may require a small change to `PollGroupEngine` in Core.
|
|
- **Docs / fixture / e2e**: new doc `docs/drivers/AbCip-Operability.md` (consolidates the
|
|
Phase 4 knobs); appends a "Per-tag scan rate" section to it covering Kepware "scan
|
|
classes" parity + the OPC UA publishing-interval interaction; no CLI surface change;
|
|
fixture-side no change to ab_server; adds
|
|
`tests/.../IntegrationTests/AbCipPerTagScanRateTests.cs` driving two tags at
|
|
different rates against ab_server and asserting bucket separation; extends
|
|
`scripts/e2e/test-abcip.ps1` with a two-tag subscribe-rate-divergence assertion.
|
|
|
|
#### PR 4.2 — Write deadband / write-on-change
|
|
- **Scope**: `AbCipDriver.WriteAsync` writes every request through. Add per-tag
|
|
`WriteDeadband` (numeric) and `WriteOnChange` (boolean). When set, the driver tracks the
|
|
last successfully-written value per `(tag, deviceHostAddress)` and suppresses the next
|
|
write if `|new - last| < deadband` (numeric) or `new == last` (any). Suppressed writes
|
|
return `Good` so OPC UA semantics are unaffected.
|
|
- **Files**: `AbCipDriverOptions.cs` (record fields), new `AbCipWriteCoalescer.cs` holding
|
|
the per-tag last-value cache, `AbCipDriver.WriteAsync` consults the coalescer before
|
|
hitting the runtime.
|
|
- **Test approach**: unit tests with synthetic writes — assert that a sequence of jittery
|
|
setpoint values within deadband triggers a single PLC write.
|
|
- **Effort**: M
|
|
- **Dependencies**: none.
|
|
- **Docs / fixture / e2e**: appends a "Write deadband / write-on-change" section to
|
|
`docs/drivers/AbCip-Operability.md` with a worked setpoint-jitter example; updates
|
|
`docs/drivers/AbServer-Test-Fixture.md` §7 to flip the multi-write coverage line to
|
|
also cover suppression; adds
|
|
`tests/.../IntegrationTests/AbCipWriteDeadbandTests.cs` driving a jittery setpoint and
|
|
asserting the actual PLC write count via libplctag debug-trace; extends
|
|
`scripts/e2e/test-abcip.ps1` with a write-coalesce assertion (write the same value
|
|
twice, verify only one PLC-side change).
|
|
|
|
#### PR 4.3 — Diagnostic / system tags as browseable variables
|
|
- **Scope**: surface the `IHostConnectivityProbe` + `DriverHealth` data as browseable OPC UA
|
|
variables under `AbCip/<device>/_System/`. Variables: `_ConnectionStatus`, `_ScanRate`
|
|
(current effective publishing interval), `_TagCount`, `_DeviceError`, `_LastScanTimeMs`.
|
|
Read-only; updated on each driver health transition.
|
|
- **Files**: `AbCipDriver.DiscoverAsync` (`AbCipDriver.cs:674-758`) emits the system folder
|
|
per device; new `AbCipSystemTagSource.cs` produces the live values; `ReadAsync` routes
|
|
`_System/...` references to the source instead of the libplctag runtime.
|
|
- **Test approach**: unit test that the discovery emits the expected nodes; unit test that
|
|
reading a system tag returns the current health snapshot.
|
|
- **Effort**: M
|
|
- **Dependencies**: none, but PR 2.5 (online refresh trigger) becomes nicer once this lands —
|
|
`_RefreshTagDb` writes `1` to invoke `RebrowseAsync`.
|
|
- **Docs / fixture / e2e**: appends a "System tags / `_System` folder" section to
|
|
`docs/drivers/AbCip-Operability.md` enumerating `_ConnectionStatus`, `_ScanRate`,
|
|
`_TagCount`, `_DeviceError`, `_LastScanTimeMs`; cross-link from
|
|
`docs/Driver.AbCip.Cli.md` (the `read` cookbook gains a system-tag example); updates
|
|
`docs/drivers/AbServer-Test-Fixture.md` §7 to flip `IHostConnectivityProbe` state-
|
|
transition coverage from "no" to covered (system tag observation provides the assertion
|
|
hook); adds `tests/.../IntegrationTests/AbCipSystemTagDiscoveryTests.cs`; extends
|
|
`scripts/e2e/test-abcip.ps1` with a `_System/_ConnectionStatus` browse-and-read step.
|
|
|
|
#### PR 4.4 — Online tag-DB refresh trigger as `_RefreshTagDb` system tag
|
|
- **Scope**: thin follow-up to PR 2.5 + PR 4.3 — wire the writeable system tag to the
|
|
existing `RebrowseAsync` method.
|
|
- **Files**: `AbCipSystemTagSource.cs` (writeable variable), `AbCipDriver.WriteAsync`
|
|
intercepts `_RefreshTagDb` writes and dispatches to `RebrowseAsync`.
|
|
- **Test approach**: unit + CLI integration.
|
|
- **Effort**: S
|
|
- **Dependencies**: PR 2.5 and PR 4.3.
|
|
- **Docs / fixture / e2e**: extends the `_System` table in
|
|
`docs/drivers/AbCip-Operability.md` to mark `_RefreshTagDb` as writeable; appends a
|
|
"Refreshing the tag DB" recipe to `docs/Driver.AbCip.Cli.md` that pairs the system-tag
|
|
write with the existing `rebrowse` command from PR 2.5; reuses the
|
|
`AbCipRebrowseTests` fixture from PR 2.5 with an added system-tag-write entry point;
|
|
extends `scripts/e2e/test-abcip.ps1` with a `_RefreshTagDb` write-then-verify
|
|
assertion (chained off the rebrowse step from PR 2.5).
|
|
|
|
### Phase 5 — Redundancy (2 PRs)
|
|
|
|
Goal: HSBY paired-IP failover for continuous-process plants. Heavier than the rest because
|
|
it changes the `(device, hostName)` axiom — one logical device now has two host addresses.
|
|
|
|
#### PR 5.1 — Paired host address syntax + role probing
|
|
- **Scope**: extend `AbCipDeviceOptions` with `PartnerHostAddress` (optional). When set, the
|
|
device probes both gateways concurrently using the existing probe loop machinery
|
|
(`AbCipDriver.cs:235-281`). A ControlLogix HSBY pair exposes
|
|
`WallClockTime`/`Module.Status` tags that identify the active chassis — investigate the
|
|
exact tag name; `WallClockTime.SyncStatus` is one option, `S:34` (Module Status)
|
|
carries the role bit on some versions.
|
|
- **Files**: `AbCipDriverOptions.cs` (extend `AbCipDeviceOptions`), `AbCipDriver.cs`
|
|
(extend `DeviceState` with `ActiveAddress` field, run two probe loops), new
|
|
`AbCipHsbyRoleProber.cs` reading the role tag and returning Active/Standby.
|
|
- **Test approach**: unit test with two fake probe runtimes returning different role bits;
|
|
integration test deferred until a true HSBY pair is available — note in
|
|
`MEMORY.md/project_aveva_platform_installed.md` that the dev box has a single chassis.
|
|
- **Effort**: L
|
|
- **Dependencies**: investigate the canonical HSBY role tag — the AVEVA ABCIP docs name it
|
|
but the wire-level tag varies by firmware.
|
|
- **Docs / fixture / e2e**: new doc `docs/drivers/AbCip-HSBY.md` covering the paired-IP
|
|
config, the role-tag detection matrix (v20 / v24 / v32+), and the feature-flag gate
|
|
(`Redundancy.Hsby.Enabled`); extends `docs/Driver.AbCip.Cli.md` with a `--partner`
|
|
flag plus an `hsby-status` command that prints the active partner; updates
|
|
`docs/drivers/AbServer-Test-Fixture.md` §"What it does NOT cover" with a new entry
|
|
marking HSBY as ab_server-blocked but adds a "paired-fixture" mode to
|
|
`tests/.../Docker/docker-compose.yml` (two `controllogix` services on different
|
|
ports + a `hsby-mux` sidecar that flips the role bit on demand); adds
|
|
`tests/.../IntegrationTests/AbCipHsbyRoleProberTests.cs`; no
|
|
`scripts/e2e/test-abcip.ps1` change yet — HSBY e2e is gated behind a sibling
|
|
`scripts/e2e/test-abcip-hsby.ps1` script introduced in PR 5.2.
|
|
|
|
#### PR 5.2 — Failover routing in IPerCallHostResolver
|
|
- **Scope**: `AbCipDriver.ResolveHost` returns the device's primary address today
|
|
(`AbCipDriver.cs:307-312`). Change it to return the currently-Active partner. On role
|
|
transition, the existing bulkhead/breaker per-host keying isolates a stuck primary
|
|
without affecting the failover path because the partner address has its own breaker.
|
|
- **Files**: `AbCipDriver.cs:ResolveHost` consults `DeviceState.ActiveAddress`, plus a
|
|
small change to per-tag runtime caching so handles are keyed on the active address —
|
|
failover invalidates the handle cache and re-creates against the new gateway.
|
|
- **Test approach**: unit test that toggling the role flag flips `ResolveHost` output;
|
|
integration test deferred per PR 5.1.
|
|
- **Effort**: M
|
|
- **Dependencies**: PR 5.1.
|
|
- **Docs / fixture / e2e**: appends a "Failover behaviour" section to
|
|
`docs/drivers/AbCip-HSBY.md` documenting handle-cache invalidation + bulkhead key
|
|
semantics; appends a "Failure-mode walkthrough" to the same doc covering
|
|
primary-stuck / secondary-stuck / both-stuck cases; reuses the paired-fixture from
|
|
PR 5.1; adds `tests/.../IntegrationTests/AbCipHsbyFailoverTests.cs` driving the
|
|
role-flip via the `hsby-mux` sidecar and asserting reads route to the new active
|
|
partner; ships the new `scripts/e2e/test-abcip-hsby.ps1` (paired-fixture variant of
|
|
the standard e2e — flips the role mid-stream and asserts subscribe stream survives).
|
|
|
|
## Per-PR detail
|
|
|
|
The summary above already includes each PR's title, motivation (linked to the
|
|
featuregaps.md table row), files, test plan, and effort. To keep this section from
|
|
duplicating, here are the cross-cutting design notes and risks per phase rather than per PR.
|
|
|
|
### Phase 1 risks
|
|
- **Int64 surface change** (PR 1.1) ripples through the address-space builder + the OPC UA
|
|
variant emit. Confirm `Core.Abstractions.DriverDataType` already has `Int64`; if not, this
|
|
PR pulls in a Core change other drivers will share (Modbus has the same TODO).
|
|
- **STRINGnn variant addressing** (PR 1.2) is the smallest data-correctness PR but has the
|
|
highest unknown — libplctag's C# wrapper may flatten all string variants to its built-in
|
|
`GetString(0)` helper. If true, PR 1.2 must add a raw-buffer decode path and is then
|
|
upgraded from M to L.
|
|
- **Array-slice planner** (PR 1.3) introduces a third planner alongside the UDT planner +
|
|
the future write planner (1.4). Build them on a shared `IAbCipReadPlanner` seam so
|
|
Phase 3's strategy selector has one slot to pivot on, not three.
|
|
- **Multi-write packing** (PR 1.4) hinges on libplctag exposing CIP Multi-Service Packet
|
|
construction. If it does not, the work-around is a raw-CIP `@raw` send, which is a
|
|
bigger lift and may push 1.4 to an L-plus that drags into Phase 3.
|
|
|
|
### Phase 2 risks
|
|
- **L5K text format** has documented edge cases (escape sequences in DESCRIPTION strings,
|
|
alias resolution, nested DATATYPE blocks). Lean on Rockwell's published L5K BNF and treat
|
|
unknown sections as warnings, not failures.
|
|
- **L5X namespace handling** — Studio 5000 v32+ adds optional XML namespaces. Use
|
|
XPath with prefix-agnostic queries to avoid version-pinning the parser.
|
|
- **CSV column drift** — Kepware's column order has shifted over major versions. Implement
|
|
the importer to read by header name, not column index.
|
|
|
|
### Phase 3 risks
|
|
- **Logical addressing bootstrap cost** (PR 3.2) — symbol-table walk on first read can
|
|
stall the first poll batch. Cache the instance-ID map per `(device, last symbol-table
|
|
hash)` and persist it across `ReinitializeAsync` if feasible.
|
|
- **MultiPacket vs WholeUdt heuristic** (PR 3.3) — the threshold (e.g. "switch to
|
|
MultiPacket when fewer than 30% of UDT members are subscribed") needs benchmarking on
|
|
real rigs. Ship an explicit per-device override + pick a conservative default.
|
|
- **Connection Size on legacy firmware** (PR 3.1) — v19-and-earlier ControlLogix firmware
|
|
rejects Large Forward Open silently. Document the symptom in `docs/Driver.AbCip.md` and
|
|
emit a warning when ConnectionSize > 511 against a family profile that is
|
|
ControlLogix-typed but probed-as-v19.
|
|
|
|
### Phase 4 risks
|
|
- **Per-tag scan rate** (PR 4.1) interacts with the OPC UA subscription's
|
|
publishing-interval contract. Document that the per-tag override is a *driver-side*
|
|
publish bucket that fires `OnDataChange` events at the per-tag rate; the OPC UA layer
|
|
still aggregates them on its own publishing-interval and the client may see them at the
|
|
larger of the two intervals. This matches Kepware's "scan classes" semantics.
|
|
- **Write deadband** (PR 4.2) on UDT-fanned-out members must use the member-level cache, not
|
|
the parent UDT's cache.
|
|
|
|
### Phase 5 risks
|
|
- **HSBY role tag name** (PR 5.1) varies by firmware version; without a real HSBY pair on
|
|
the dev box the integration coverage is deferred to a customer-site smoke test. Consider
|
|
parking PR 5.1+5.2 behind a feature flag (`Redundancy.Hsby.Enabled`) and shipping unit
|
|
coverage only.
|
|
- **Bulkhead key** assumed to be `(driver, hostName)`; once `ResolveHost` returns the active
|
|
partner address that key is correct by construction, but verify Polly's per-key state is
|
|
invalidated cleanly when the active address changes mid-call.
|
|
|
|
## Documentation, fixture, and e2e impact
|
|
|
|
Cross-cutting roll-up of the per-PR `Docs / fixture / e2e` lines above. Read this before
|
|
starting any phase to plan doc + fixture + e2e work in parallel with the code change.
|
|
|
|
### New documents
|
|
|
|
- `docs/drivers/AbCip-TagImport.md` (Phase 2, lands with PR 2.1; extended by PR 2.2,
|
|
PR 2.3, PR 2.4, PR 2.6) — L5K / L5X / CSV / AOI tag-import reference.
|
|
- `docs/drivers/AbCip-Performance.md` (Phase 3, lands with PR 3.1; extended by PR 3.2,
|
|
PR 3.3) — Connection Size, Addressing Mode, Read Strategy.
|
|
- `docs/drivers/AbCip-Operability.md` (Phase 4, lands with PR 4.1; extended by PR 4.2,
|
|
PR 4.3, PR 4.4) — per-tag scan rate, write deadband, system tags.
|
|
- `docs/drivers/AbCip-HSBY.md` (Phase 5, lands with PR 5.1; extended by PR 5.2) —
|
|
paired-IP redundancy, role-tag matrix, failover semantics.
|
|
|
|
### Documents with appended sections
|
|
|
|
- `docs/Driver.AbCip.Cli.md` — gains type-table rows (PR 1.1), `--string-size` flag
|
|
(PR 1.2), slice syntax (PR 1.3), multi-write subsection (PR 1.4), `tag-import` /
|
|
`tag-export` commands (PR 2.1, PR 2.2, PR 2.4), `rebrowse` command (PR 2.5),
|
|
Connection Size note (PR 3.1), `--addressing-mode` flag (PR 3.2), system-tag
|
|
read example (PR 4.3), `_RefreshTagDb` recipe (PR 4.4), `--partner` flag plus
|
|
`hsby-status` command (PR 5.1).
|
|
- `docs/drivers/AbServer-Test-Fixture.md` — coverage map updated by every PR that
|
|
changes what ab_server actually exercises (1.1, 1.2, 1.3, 1.4, 2.6, 3.1, 3.2,
|
|
3.3, 4.2, 4.3, 5.1).
|
|
|
|
### Fixture / simulator scaffolding
|
|
|
|
- `tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/docker-compose.yml` —
|
|
ControlLogix profile gains seeded `TestLINT`, `TestSTRING80`, extra DINT tags for
|
|
multi-write (PRs 1.1, 1.2, 1.4); a new paired-fixture mode with `hsby-mux` sidecar
|
|
for HSBY (PR 5.1).
|
|
- `tests/.../AbCip.IntegrationTests/AbServerProfile.cs` — the `KnownProfiles`
|
|
records get extended Notes lines for each new seeded tag class.
|
|
- `tests/.../AbCip.Tests/Import/Fixtures/` — new directory hosting sample L5K, L5X,
|
|
and CSV files (PRs 2.1, 2.2, 2.6, 2.4).
|
|
- `tests/.../AbCip.IntegrationTests/Emulate/` — new gated tests for AOI (PR 2.6) and
|
|
MultiPacket strategy (PR 3.3); reuses the existing `AB_SERVER_PROFILE=emulate`
|
|
gate.
|
|
- `tests/.../AbCip.IntegrationTests/LogixProject/README.md` — cross-link added when
|
|
PR 2.2 lands so the on-site Studio 5000 export doubles as a parser fixture.
|
|
|
|
### Integration / e2e scripts
|
|
|
|
- `scripts/e2e/test-abcip.ps1` — gains assertions for: LInt loopback (1.1),
|
|
STRING round-trip (1.2), array-slice read (1.3), recipe multi-write (1.4),
|
|
tag-import diff (2.1, 2.2, 2.4), Description survival (2.3), rebrowse-after-reseed
|
|
(2.5), Symbolic-vs-Logical sanity (3.2), per-tag scan-rate divergence (4.1),
|
|
write-coalesce (4.2), `_System` browse-and-read (4.3), `_RefreshTagDb` write-then-
|
|
verify (4.4).
|
|
- `scripts/smoke/seed-abcip-smoke.sql` — extended with `TestLINT`, `TestSTRING80`,
|
|
multi-write target tags, and a `ConnectionSize` field demo (PRs 1.1, 1.2, 1.4,
|
|
3.1).
|
|
- `scripts/e2e/test-abcip-hsby.ps1` — new paired-fixture variant of the standard
|
|
e2e, ships with PR 5.2; not chained into `scripts/e2e/test-all.ps1` until HSBY
|
|
exits feature-flag gating.
|
|
|
|
### Cross-cutting work
|
|
|
|
- The `Docs / fixture / e2e` lines deliberately reuse the existing
|
|
`Test-Probe` / `Test-DriverLoopback` / `Test-ServerBridge` / `Test-OpcUaWriteBridge` /
|
|
`Test-SubscribeSeesChange` helpers in `scripts/e2e/_common.ps1` — no new helper
|
|
functions are required for Phases 1-4. Phase 5 is the first phase that introduces a
|
|
new helper (`Test-FailoverDuringSubscribe`) in `_common.ps1`, shipped alongside
|
|
PR 5.2; if other drivers (TwinCAT, S7) later adopt a paired-fixture mode they can
|
|
reuse it.
|
|
- `tests/.../AbCip.IntegrationTests/AbServerFixture.cs` may need a small extension in
|
|
PR 5.1 to support the paired-port probe; the change is additive (probe both
|
|
`127.0.0.1:44818` and `127.0.0.1:44819`), keeping single-fixture tests working
|
|
unchanged.
|
|
|
|
## Skip-rated items (for context)
|
|
|
|
Copied from the Recommendations table at `docs/featuregaps.md`:
|
|
|
|
- **#7 Inactivity timeout / keep-alive cadence** — Rarely an issue with libplctag-managed
|
|
connections.
|
|
- **#9 "Respect tag-specified scan rate" mode** — Niche; OPC UA subscription rate already
|
|
covers it.
|
|
- **#10 Initial value cache / first-update from cache** — OPC UA subscription sampling
|
|
already handles first-update.
|
|
- **#15 UDT as first-class OPC UA structured type** — Member fan-out already works;
|
|
structured-type plumbing is heavy.
|
|
- **#17 PLC-5 / SLC bridging through CLX** — AbLegacy driver covers this protocol family.
|
|
- **#21 Unsolicited CIP MSG ingestion** — Separate driver in commercial; design-heavy;
|
|
niche.
|
|
- **#22 CIP Generic / Class 3 passthrough** — Niche custom-tooling territory.
|
|
- **#23 Per-device connection count / pooling** — libplctag manages connections;
|
|
premature.
|
|
|
|
## Open questions
|
|
|
|
1. **libplctag instance-ID API** (PR 3.2) — does the C# wrapper expose
|
|
`logical_segment` / `cip_addr` attributes directly, or do we have to drop down to
|
|
`Tag.AddAttribute` calls? Affects scope of Phase 3.
|
|
2. **libplctag CIP Multi-Service Packet** (PR 1.4) — is there a wrapper-level multi-write
|
|
helper, or must we go through the `@raw` pseudo-tag? Affects scope of Phase 1.
|
|
3. **`DriverDataType.Int64` / `Int64Array`** (PR 1.1) — does Core already carry it, or is
|
|
this a shared Core change with Modbus's matching TODO?
|
|
4. **HSBY role tag** (PR 5.1) — confirm the canonical Active/Standby indicator across
|
|
ControlLogix v20 / v24 / v32+; without a known tag the role-prober is speculative.
|
|
5. **AOI InOut handling** (PR 2.6) — Kepware skips InOut parameters because they are
|
|
pointers, not values. Do we follow the same precedent or attempt to dereference at
|
|
read-time? Skip is the cheap default.
|
|
6. **L5K vs L5X coverage** — if the customer base has standardised on L5X (Studio 5000
|
|
v21+), can we ship PR 2.2 first and make PR 2.1 best-effort? Affects phasing within
|
|
Phase 2.
|
|
7. **HSBY scope for v2 vs v3** — Phase 5 carries the largest unknowns; if no continuous-
|
|
process customer demands it for the v2 release, deferring Phase 5 to v3 is reasonable.
|
|
8. **Per-tag scan rate plumbing** (PR 4.1) — does `PollGroupEngine` in Core already accept
|
|
per-reference interval overrides, or does that need a Core extension shared with the
|
|
other polling-overlay drivers (Modbus, FOCAS)?
|