Files
lmxopcua/scripts/queue/pr-manifest.yaml
Joseph Doherty 2d07d716dc Recover stashed driver-gaps work from pre-v2-mxgw-merge working tree
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>
2026-04-30 08:28:01 -04:00

1474 lines
66 KiB
YAML

prs:
- id: abcip-1.1
driver: abcip
phase: 1
plan_pr_id: "1.1"
title: "AbCip — LINT/ULINT 64-bit fidelity"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Replace the truncating Int32 widening at AbCipDataType.cs:53 with Int64 routing across decode, encode, and the DriverDataType map. Includes DT (epoch-millis on Logix v32+ surfaces as LINT, not DINT). The runtime already calls GetInt64/SetInt64 correctly; the gap is the surface enum flattening into Int32. May require adding Int64/UInt64 members to Core.Abstractions/DriverDataType.cs.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDataType.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverDataType.cs]
docs: [docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-abcip.ps1, scripts/smoke/seed-abcip-smoke.sql]
effort: S
deps: []
cross_driver: false
notes: "Possible Core.Abstractions enum extension."
- id: abcip-1.2
driver: abcip
phase: 1
plan_pr_id: "1.2"
title: "AbCip — Native STRING / STRINGnn variant decoding"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Today AbCipDataType.String flattens any Logix STRING UDT into a .NET string via libplctag GetString(0). Logix programs commonly define STRING_20/40/80 variants. Add StringLength field to AbCipStructureMember and AbCipTagDefinition, thread into the libplctag string-cap hint. Investigate libplctag's str_max_capacity / str_count_word_bytes attributes; extend LibplctagTagRuntime if needed.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDataType.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-abcip.ps1, scripts/smoke/seed-abcip-smoke.sql]
effort: M
deps: []
cross_driver: false
notes: ""
- id: abcip-1.3
driver: abcip
phase: 1
plan_pr_id: "1.3"
title: "AbCip — Array-slice read addressing Tag[0..N]"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Add slice syntax Tag[0..15] to 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: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipTagPath.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipArrayReadPlanner.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/IAbCipTagRuntime.cs]
docs: [docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: L
deps: [abcip-1.1]
cross_driver: false
notes: ""
- id: abcip-1.4
driver: abcip
phase: 1
plan_pr_id: "1.4"
title: "AbCip — CIP multi-tag write packing"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
AbCipDriver.WriteAsync loops over writes one-by-one. Group writes by device and submit one CIP Multi-Service Packet (0x0A) carrying up to N write-singles per round-trip. Honours per-family SupportsRequestPacking. May require new IAbCipTagRuntime.WriteBatchAsync.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipMultiWritePlanner.cs]
docs: [docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1, scripts/smoke/seed-abcip-smoke.sql]
effort: L
deps: []
cross_driver: false
notes: "May need raw CIP via @raw if libplctag lacks multi-write."
- id: abcip-2.1
driver: abcip
phase: 2
plan_pr_id: "2.1"
title: "AbCip — L5K parser + ingest"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Parse Studio 5000 L5K export (TAG/END_TAG, DATATYPE/END_DATATYPE, program-scope qualifiers). Produce AbCipTagDefinition + AbCipStructureMember records. Hook into AbCipDriverOptions.TagImports parsed on InitializeAsync.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Import/L5kParser.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Import/IL5kSource.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Import/L5kIngest.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs]
docs: [docs/drivers/AbCip-TagImport.md, docs/Driver.AbCip.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/Import/Fixtures/]
e2e: [scripts/e2e/test-abcip.ps1]
effort: L
deps: []
cross_driver: false
notes: ""
- id: abcip-2.2
driver: abcip
phase: 2
plan_pr_id: "2.2"
title: "AbCip — L5X (XML) parser + ingest"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Same surface as 2.1 but parses Studio 5000 XML export with richer metadata (ExternalAccess, AOI). Share IL5kSource/L5kIngest layer with 2.1 via a common ParsedTagsBundle record.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Import/L5xParser.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Import/L5kIngest.cs]
docs: [docs/drivers/AbCip-TagImport.md, docs/Driver.AbCip.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/Import/Fixtures/]
e2e: [scripts/e2e/test-abcip.ps1]
effort: L
deps: [abcip-2.1]
cross_driver: false
notes: ""
- id: abcip-2.3
driver: abcip
phase: 2
plan_pr_id: "2.3"
title: "AbCip — Tag descriptions surfaced as OPC UA Description"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Extend AbCipTagDefinition with Description, populate from L5K/L5X parsers, thread to DriverAttributeInfo so the address-space builder sets OPC UA Description attribute.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs, src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverAttributeInfo.cs]
docs: [docs/drivers/AbCip-TagImport.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: S
deps: [abcip-2.1, abcip-2.2]
cross_driver: false
notes: ""
- id: abcip-2.4
driver: abcip
phase: 2
plan_pr_id: "2.4"
title: "AbCip — CSV tag import / export"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
CSV round-trip matching Kepware column layout. Import populates AbCipTagDefinition; export dumps live tag table for Excel editing. CsvTagImporter, CsvTagExporter, CLI tag-export command.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Import/CsvTagImporter.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Import/CsvTagExporter.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs]
docs: [docs/drivers/AbCip-TagImport.md, docs/Driver.AbCip.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/Import/Fixtures/]
e2e: [scripts/e2e/test-abcip.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: abcip-2.5
driver: abcip
phase: 2
plan_pr_id: "2.5"
title: "AbCip — Online tag-DB refresh trigger"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Implement IDriverControl.RebrowseAsync to force re-walk of controller symbol table without driver restart. Expose via CLI and AbCipDriver.RebrowseAsync.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli/]
docs: [docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: abcip-2.6
driver: abcip
phase: 2
plan_pr_id: "2.6"
title: "AbCip — AOI input/output handling"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
AOI-aware browse paths: AOI instance shows up as folder with Inputs/Outputs/InOut sub-folders. AbCipStructureMember gains AoiQualifier enum; L5K/L5X parser populates it.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipStructureMember.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/drivers/AbCip-TagImport.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/Import/Fixtures/]
e2e: []
effort: M
deps: [abcip-2.2]
cross_driver: false
notes: ""
- id: abcip-3.1
driver: abcip
phase: 3
plan_pr_id: "3.1"
title: "AbCip — Configurable CIP Connection Size per device"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Add ConnectionSize field to AbCipDeviceOptions overriding family default (4002/504/488). Thread to libplctag connection_size attribute. Range 500-4002.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/IAbCipTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs]
docs: [docs/drivers/AbCip-Performance.md, docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: [scripts/smoke/seed-abcip-smoke.sql]
effort: S
deps: []
cross_driver: false
notes: ""
- id: abcip-3.2
driver: abcip
phase: 3
plan_pr_id: "3.2"
title: "AbCip — Symbolic vs logical (instance-ID) addressing toggle"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
AddressingMode enum (Symbolic/Logical/Auto). Logical flips libplctag into instance-ID mode for ~3-5x throughput. Requires symbol-table walk on init.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/IAbCipTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/drivers/AbCip-Performance.md, docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: L
deps: []
cross_driver: false
notes: ""
- id: abcip-3.3
driver: abcip
phase: 3
plan_pr_id: "3.3"
title: "AbCip — Logical-blocking / non-blocking strategy selector"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
ReadStrategy enum (WholeUdt / MultiPacket / Auto). MultiPacket bundles per-member reads via libplctag request-packing for sparse UDTs. Honours SupportsRequestPacking.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipUdtReadPlanner.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipMultiPacketReadPlanner.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/drivers/AbCip-Performance.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: []
effort: L
deps: [abcip-1.4]
cross_driver: false
notes: ""
- id: abcip-4.1
driver: abcip
phase: 4
plan_pr_id: "4.1"
title: "AbCip — Per-tag scan rate / scan group bucketing"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Optional ScanRate field on AbCipTagDefinition overrides subscription interval. PollGroupEngine new Subscribe overload for per-tag overrides.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/drivers/AbCip-Operability.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: abcip-4.2
driver: abcip
phase: 4
plan_pr_id: "4.2"
title: "AbCip — Write deadband / write-on-change"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Per-tag WriteDeadband / WriteOnChange. Track last-written value; suppress next write if delta < deadband. Suppressed writes return Good for OPC UA semantics.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipWriteCoalescer.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/drivers/AbCip-Operability.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: abcip-4.3
driver: abcip
phase: 4
plan_pr_id: "4.3"
title: "AbCip — Diagnostic / system tags as browseable variables"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Surface IHostConnectivityProbe + DriverHealth as browseable variables under AbCip/<device>/_System/. Variables _ConnectionStatus, _ScanRate, _TagCount, _DeviceError, _LastScanTimeMs.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipSystemTagSource.cs]
docs: [docs/drivers/AbCip-Operability.md, docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: abcip-4.4
driver: abcip
phase: 4
plan_pr_id: "4.4"
title: "AbCip — _RefreshTagDb writeable system tag"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
Writeable system tag wiring: writes to _RefreshTagDb dispatch to RebrowseAsync (PR 2.5).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipSystemTagSource.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/drivers/AbCip-Operability.md, docs/Driver.AbCip.Cli.md]
fixture: []
e2e: [scripts/e2e/test-abcip.ps1]
effort: S
deps: [abcip-2.5, abcip-4.3]
cross_driver: false
notes: ""
- id: abcip-5.1
driver: abcip
phase: 5
plan_pr_id: "5.1"
title: "AbCip — HSBY paired-IP probing"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
AbCipDeviceOptions.PartnerHostAddress; both gateways probed concurrently. AbCipHsbyRoleProber reads the role tag (WallClockTime.SyncStatus or S:34) returning Active/Standby.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipHsbyRoleProber.cs]
docs: [docs/drivers/AbCip-HSBY.md, docs/Driver.AbCip.Cli.md, docs/drivers/AbServer-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/docker-compose.yml]
e2e: []
effort: L
deps: []
cross_driver: false
notes: "HSBY role tag varies by firmware."
- id: abcip-5.2
driver: abcip
phase: 5
plan_pr_id: "5.2"
title: "AbCip — IPerCallHostResolver failover routing"
plan_anchor: "docs/plans/abcip-plan.md"
summary: |
AbCipDriver.ResolveHost returns the currently-Active partner. On role transition the existing per-host breaker isolates a stuck primary.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs]
docs: [docs/drivers/AbCip-HSBY.md]
fixture: []
e2e: [scripts/e2e/test-abcip-hsby.ps1]
effort: M
deps: [abcip-5.1]
cross_driver: false
notes: ""
- id: ablegacy-1
driver: ablegacy
phase: 1
plan_pr_id: "1"
title: "AbLegacy — PLC-5 octal I/O addressing"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Add family-aware TryParse for octal I:/O: addressing on PLC-5 (octal in RSLogix 5; today silently accepted as decimal). AbLegacyPlcFamilyProfile gains OctalIoAddressing flag.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/PlcFamilies/AbLegacyPlcFamilyProfile.cs]
docs: [docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-ablegacy.ps1]
effort: S
deps: []
cross_driver: false
notes: ""
- id: ablegacy-2
driver: ablegacy
phase: 1
plan_pr_id: "2"
title: "AbLegacy — MicroLogix function-file letters"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Recognise multi-letter function files (RTC/HSC/DLS/MMI/PTO/PWM/STI/EII/IOS/BHI) when family is MicroLogix. Add SupportsFunctionFiles flag and per-type sub-element catalogue.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDataType.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/PlcFamilies/AbLegacyPlcFamilyProfile.cs]
docs: [docs/drivers/AbLegacy-MicroLogix-FunctionFiles.md, docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-ablegacy.ps1]
effort: M
deps: [ablegacy-1]
cross_driver: false
notes: ""
- id: ablegacy-3
driver: ablegacy
phase: 1
plan_pr_id: "3"
title: "AbLegacy — Sub-element bit semantics for Timer/Counter/Control"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
.DN/.EN/.TT/.CU/.CD/.OV/.UN/.ER surface as Boolean. EffectiveDriverDataType helper; ReadAsync decodes parent word + masks bit.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDataType.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/LibplctagLegacyTagRuntime.cs]
docs: [docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-ablegacy.ps1, scripts/smoke/seed-ablegacy-smoke.sql]
effort: M
deps: []
cross_driver: false
notes: ""
- id: ablegacy-4
driver: ablegacy
phase: 1
plan_pr_id: "4"
title: "AbLegacy — Indirect / indexed addressing parser"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Accept N7:[N7:0] (read N7 word indexed by N7:0) and N[N7:0]:5. Nullable IndirectFileSource / IndirectWordSource on AbLegacyAddress, depth 1.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDataType.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs]
docs: [docs/drivers/AbLegacy-Indirect-Addressing.md, docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-ablegacy.ps1]
effort: M
deps: [ablegacy-1]
cross_driver: false
notes: ""
- id: ablegacy-5
driver: ablegacy
phase: 2
plan_pr_id: "5"
title: "AbLegacy — PD/MG/PLS/BT structure files"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Add PD (PID), MG (Message), PLS (Programmable Limit Switch), BT (Block Transfer) file types. New enum members + per-type sub-element catalogue (SP/PV/CV/KP/KI/KD as Float; EN/DN/MO/PE as Boolean).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDataType.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/LibplctagLegacyTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/PlcFamilies/AbLegacyPlcFamilyProfile.cs]
docs: [docs/drivers/AbLegacy-Structure-Files.md, docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-ablegacy.ps1, scripts/smoke/seed-ablegacy-smoke.sql]
effort: M
deps: [ablegacy-3]
cross_driver: false
notes: ""
- id: ablegacy-6
driver: ablegacy
phase: 2
plan_pr_id: "6"
title: "AbLegacy — ST string read/write production verification"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Verify libplctag's GetString/SetString handles ST 82-byte length-word format. Round-trip 82/0/41-char, embedded null, non-ASCII, both Slc500 and Plc5 families. BadOutOfRange on over-length writes.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/LibplctagLegacyTagRuntime.cs, tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyReadSmokeTests.cs, tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/AbLegacyStringEncodingTests.cs]
docs: [docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-ablegacy.ps1, scripts/smoke/seed-ablegacy-smoke.sql]
effort: S
deps: []
cross_driver: false
notes: ""
- id: ablegacy-7
driver: ablegacy
phase: 3
plan_pr_id: "7"
title: "AbLegacy — Array contiguous block addressing"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Expose array tags with IsArray=true + ArrayDim, backed by libplctag elem_count=N. Parser accepts ,N and [N] suffixes. Target ≥5x latency vs individual tags.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/IAbLegacyTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/LibplctagLegacyTagRuntime.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs]
docs: [docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-ablegacy.ps1, scripts/smoke/seed-ablegacy-smoke.sql]
effort: M
deps: [ablegacy-1]
cross_driver: false
notes: ""
- id: ablegacy-8
driver: ablegacy
phase: 3
plan_pr_id: "8"
title: "AbLegacy — Per-tag deadband / change filter"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
AbsoluteDeadband and PercentDeadband per tag. Per-tag last-published-value cache, deadband test wraps PollGroupEngine callback. Booleans bypass deadband.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs]
docs: [docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-ablegacy.ps1, scripts/smoke/seed-ablegacy-smoke.sql]
effort: S
deps: []
cross_driver: false
notes: ""
- id: ablegacy-9
driver: ablegacy
phase: 4
plan_pr_id: "9"
title: "AbLegacy — Per-device timeout / retry overrides"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
AbLegacyDeviceOptions.Timeout / Retries. SLC 5/01 ~5s vs 5/05 ~2s vs ML1100 ~3s. Driver-wide AbLegacyDriverOptions.Timeout becomes default.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs]
docs: [docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: []
e2e: [scripts/smoke/seed-ablegacy-smoke.sql]
effort: S
deps: []
cross_driver: false
notes: ""
- id: ablegacy-10
driver: ablegacy
phase: 4
plan_pr_id: "10"
title: "AbLegacy — Diagnostic counters as tags"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Per-device counters (RequestCount, ResponseCount, ErrorCount, RetryCount, LastErrorCode, LastErrorMessage, CommFailures) as auto-generated _Diagnostics/* variables.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDiagnosticTags.cs]
docs: [docs/drivers/AbLegacy-Diagnostics.md, docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-ablegacy.ps1, scripts/smoke/seed-ablegacy-smoke.sql]
effort: M
deps: []
cross_driver: false
notes: ""
- id: ablegacy-11
driver: ablegacy
phase: 4
plan_pr_id: "11"
title: "AbLegacy — RSLogix 500/PLC-5 symbol import"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
v1 scopes to .SLC/.CSV text exports (Symbol,Address,Description,DataType,Scope). RsLogixSymbolImport, IRsLogixImporter abstraction, AddRsLogixImport extension method, import-rslogix CLI subcommand.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/Import/RsLogixSymbolImport.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/Import/IRsLogixImporter.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriverFactoryExtensions.cs]
docs: [docs/drivers/AbLegacy-RSLogix-Import.md, docs/Driver.AbLegacy.Cli.md, docs/DriverClis.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/Fixtures/rslogix-canonical.csv]
e2e: [scripts/e2e/test-ablegacy.ps1]
effort: L
deps: [ablegacy-1, ablegacy-2, ablegacy-3, ablegacy-4, ablegacy-5]
cross_driver: false
notes: ""
- id: ablegacy-12
driver: ablegacy
phase: 5
plan_pr_id: "12"
title: "AbLegacy — Auto-demote on comm failure"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
AbLegacyDemoteOptions { FailureThreshold=3, DemoteFor=30s, Enabled=true }. ConsecutiveFailures, DemotedUntilUtc on DeviceState. Adds HostState.Demoted enum value.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs]
docs: [docs/drivers/AbLegacy-AutoDemote.md, docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-ablegacy.ps1, scripts/smoke/seed-ablegacy-smoke.sql]
effort: M
deps: [ablegacy-10]
cross_driver: false
notes: ""
- id: ablegacy-13
driver: ablegacy
phase: 5
plan_pr_id: "13"
title: "AbLegacy — DH+ via 1756-DHRIO bridging"
plan_anchor: "docs/plans/ablegacy-plan.md"
summary: |
Validate and document CIP path for DH+ bridging: 1,<slot>,2,<dh+_station_octal>. Surface BackplaneSlot/DhPlusPort/DhPlusStation. Plc5-only on family profile.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyHostAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/PlcFamilies/AbLegacyPlcFamilyProfile.cs]
docs: [docs/drivers/AbLegacy-DH-Bridging.md, docs/Driver.AbLegacy.Cli.md, docs/drivers/AbLegacy-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-ablegacy.ps1]
effort: S
deps: [ablegacy-1]
cross_driver: false
notes: "Hardware-gated; can't simulate DHRIO."
- id: focas-f1a
driver: focas
phase: 1
plan_pr_id: "F1-a"
title: "FOCAS — ODBST status flags as fixed-tree nodes"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Project 9 fields of cnc_rdcncstat (tmmode/aut/run/motion/mstb/emergency/alarm/edit/dummy) under Status/. Existing boolean probe preserved; full-struct cached on every poll tick.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/drivers/FOCAS-Test-Fixture.md, docs/v2/implementation/focas-wire-protocol.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: focas-f1b
driver: focas
phase: 1
plan_pr_id: "F1-b"
title: "FOCAS — Parts count + cycle time"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Surface cnc_rdparam(6711/6712/6713) under Production/, plus Production/CycleTimeSeconds promoted from Timers/CycleSeconds. Pure projection-layer addition.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs]
docs: [docs/drivers/FOCAS.md, docs/Driver.FOCAS.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/profiles/]
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: focas-f1c
driver: focas
phase: 1
plan_pr_id: "F1-c"
title: "FOCAS — Modal G/M/T codes + override values"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
cnc_modal projecting Modal/G_Group{n} (groups 1..21), Modal/MCode/SCode/TCode/BCode, Override/Feed/Rapid/Spindle/Jog from cnc_rdparam. Per-device operator overrides for MTB-specific cases.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireModels.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/Driver.FOCAS.Cli.md, docs/v2/focas-version-matrix.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: focas-f1d
driver: focas
phase: 1
plan_pr_id: "F1-d"
title: "FOCAS — Tool number / tool life + work coordinate offsets"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
cnc_rdtofs / cnc_rdtlife* / cnc_rdzofs. Project Tooling/CurrentTool, Tooling/CurrentOffset, Tooling/Life/{group}/{Remaining,Total}, Offsets/G54..G59[+ extended]/{X,Y,Z}.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/focas-version-matrix.md, docs/drivers/FOCAS-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: []
effort: L
deps: []
cross_driver: false
notes: ""
- id: focas-f1e
driver: focas
phase: 1
plan_pr_id: "F1-e"
title: "FOCAS — Operator messages + currently-executing block text"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
cnc_rdopmsg3 (all four FANUC opmsg classes) and cnc_rdactpt (current block text). Messages/External as ring-buffer; Program/CurrentBlock as string. Trim-stable round-trip.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/implementation/focas-wire-protocol.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: focas-f1f
driver: focas
phase: 1
plan_pr_id: "F1-f"
title: "FOCAS — cnc_getfigure scaling + connection statistics"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Cache per-axis decimal-place counts; divide position values before publishing. Diagnostics/ subtree (ReadCount, ReadFailureCount, LastErrorMessage, LastSuccessfulRead, ReconnectCount). FixedTree.ApplyFigureScaling flag.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/focas-deployment.md, docs/drivers/FOCAS-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: [scripts/e2e/test-focas.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: focas-f2a
driver: focas
phase: 2
plan_pr_id: "F2-a"
title: "FOCAS — DIAG: address scheme"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
New FocasAreaKind.Diagnostic parsed from DIAG:nnn / DIAG:nnn/axis, dispatched to cnc_rddiag (or cnc_rddiagdgn). DiagnosticRange added to capability matrix.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs]
docs: [docs/Driver.FOCAS.Cli.md, docs/drivers/FOCAS.md, docs/v2/focas-version-matrix.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: focas-f2b
driver: focas
phase: 2
plan_pr_id: "F2-b"
title: "FOCAS — Multi-path / multi-channel CNC"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Optional Path segment in FocasAddress (PARAM:1815@2, R100@3.0, MACRO:500@2). Per-device PathCount via cnc_rdpathnum. Defaults to PathId=1 for back-compat.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasAddress.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/Driver.FOCAS.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: [scripts/e2e/test-focas.ps1]
effort: L
deps: []
cross_driver: false
notes: ""
- id: focas-f2c
driver: focas
phase: 2
plan_pr_id: "F2-c"
title: "FOCAS — PMC F/G letters for 16i"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Capability-matrix correctness: PmcLetters(Sixteen_i) currently {X,Y,R,D}; widen to include F/G. Real 16i ladders use F/G for handshakes.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs]
docs: [docs/v2/focas-version-matrix.md]
fixture: []
e2e: [scripts/e2e/test-focas.ps1]
effort: S
deps: []
cross_driver: false
notes: ""
- id: focas-f2d
driver: focas
phase: 2
plan_pr_id: "F2-d"
title: "FOCAS — Bulk PMC range read coalescing"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Coalescer groups same-letter contiguous PMC bytes from request batch into one wire call per group. Cap ≤256 bytes per range.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasPmcCoalescer.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/implementation/focas-wire-protocol.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: []
effort: M
deps: [focas-f1f]
cross_driver: false
notes: ""
- id: focas-f3a
driver: focas
phase: 3
plan_pr_id: "F3-a"
title: "FOCAS — cnc_rdalmhistry alarm-history extension"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
FocasAlarmProjection ActiveOnly | ActivePlusHistory. On connect + cadence (default 5min) issue cnc_rdalmhistry. Dedup key (timestamp, alarmNumber, type).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasAlarmProjection.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverOptions.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/focas-deployment.md, docs/drivers/FOCAS-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: focas-f4a
driver: focas
phase: 4
plan_pr_id: "F4-a"
title: "FOCAS — Write infrastructure + per-tag opt-in"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Drop BadNotWritable short-circuit; kind-based dispatch. Honour FocasTagDefinition.Writable (default false). Driver-level Writes.Enabled (default false). Revokes read-only callouts in 3 user-facing docs.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/WireFocasClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/drivers/FOCAS-Test-Fixture.md, docs/Driver.FOCAS.Cli.md, docs/v2/decisions.md, docs/v2/focas-deployment.md, docs/featuregaps.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: [scripts/e2e/test-focas.ps1]
effort: M
deps: []
cross_driver: false
notes: "Phase 4 writes; revokes read-only design."
- id: focas-f4b
driver: focas
phase: 4
plan_pr_id: "F4-b"
title: "FOCAS — cnc_wrmacro + cnc_wrparam"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Macro and parameter writes. Granular Writes.AllowParameter / Writes.AllowMacro. ACL: WriteConfigure for parameters, WriteOperate for macros.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/WireFocasClient.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/focas-deployment.md, docs/Driver.FOCAS.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: [scripts/e2e/test-focas.ps1]
effort: M
deps: [focas-f4a]
cross_driver: false
notes: "Phase 4 writes."
- id: focas-f4c
driver: focas
phase: 4
plan_pr_id: "F4-c"
title: "FOCAS — pmc_wrpmcrng"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
PMC range writes with read-modify-write for bit-level (wire is byte-addressed). Writes.AllowPmc granular flag. Loud safety callout.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/WireFocasClient.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/focas-deployment.md, docs/Driver.FOCAS.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: [scripts/e2e/test-focas.ps1]
effort: M
deps: [focas-f4a]
cross_driver: false
notes: "Phase 4 writes."
- id: focas-f4d
driver: focas
phase: 4
plan_pr_id: "F4-d"
title: "FOCAS — Password / unlock parameter"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Password on FocasDeviceOptions (redacted in logs). cnc_wrunlockparam during connect. Re-issue + retry once on EW_PASSWD.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/focas-deployment.md, docs/Driver.FOCAS.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/]
e2e: [scripts/e2e/test-focas.ps1]
effort: S
deps: [focas-f4a, focas-f4b]
cross_driver: false
notes: ""
- id: focas-f5a
driver: focas
phase: 5
plan_pr_id: "F5-a"
title: "FOCAS — Cycle time per part / last cycle delta"
plan_anchor: "docs/plans/focas-plan.md"
summary: |
Production/LastCycleSeconds and Production/LastCycleStartUtc derived from CycleSeconds delta on parts-count increments. Pure derivation; no new wire calls.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs]
docs: [docs/drivers/FOCAS.md, docs/v2/focas-deployment.md]
fixture: []
e2e: []
effort: S
deps: [focas-f1b]
cross_driver: false
notes: ""
- id: opcuaclient-1
driver: opcuaclient
phase: 1
plan_pr_id: "1"
title: "OpcUaClient — Per-subscription tuning"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Lift hard-coded subscription parameters (KeepAliveCount=10, LifetimeCount=1000, MaxNotificationsPerPublish=0, Priority=0, PublishingInterval floor 50ms) into OpcUaClientDriverOptions.Subscriptions.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Client.CLI.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcPlcFixture.cs]
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-2
driver: opcuaclient
phase: 1
plan_pr_id: "2"
title: "OpcUaClient — Per-tag advanced subscription tuning incl. deadband"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Surface SamplingInterval, QueueSize, DiscardOldest, MonitoringMode, DataChangeFilter (DeadbandType=Absolute/Percent + Trigger) per-tag. Extend ISubscribable with additive overload accepting MonitoredTagSpec list.
files: [src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ISubscribable.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Client.CLI.md, docs/Client.UI.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcPlcFixture.cs]
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: L
deps: [opcuaclient-1]
cross_driver: false
notes: ""
- id: opcuaclient-3
driver: opcuaclient
phase: 1
plan_pr_id: "3"
title: "OpcUaClient — Honor server OperationLimits"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Read Server.ServerCapabilities.OperationLimits.MaxNodesPerRead/Write/Browse/HistoryReadData via Session.FetchOperationLimitsAsync; cache; chunk batch operations client-side. Treat zero as no limit.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-4
driver: opcuaclient
phase: 1
plan_pr_id: "4"
title: "OpcUaClient — Diagnostics counters"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Per-driver counters (publish-request, notifications-per-second EWMA, missing-publish-request, dropped-notification, session-resets) on DriverHealth/sibling DriverDiagnostics. Hook Session events.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs, src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IDriver.cs]
docs: [docs/drivers/OpcUaClient.md]
fixture: []
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-5
driver: opcuaclient
phase: 1
plan_pr_id: "5"
title: "OpcUaClient — CRL / revocation handling"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Explicit revoked-cert handling in CertificateValidator; RejectSHA1SignedCertificates, RejectUnknownRevocationStatus, MinimumCertificateKeySize options. Surface revoked vs untrusted distinctly.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverOptions.cs]
docs: [docs/drivers/OpcUaClient.md, docs/security.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-6
driver: opcuaclient
phase: 2
plan_pr_id: "6"
title: "OpcUaClient — Discovery URL FindServers"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Accept discovery URL pointing at LDS or server's discovery endpoint. DiscoveryClient.CreateAsync + FindServersAsync + GetEndpointsAsync. DiscoveryUrl knob feeds failover candidate list.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverOptions.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Client.CLI.md]
fixture: []
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-7
driver: opcuaclient
phase: 2
plan_pr_id: "7"
title: "OpcUaClient — Selective import / namespace remap"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Per-branch include/exclude rules, namespace-URI remapping, re-keyed BrowseNames. Curation section in options. Glob: * and ? only.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md, docs/drivers/OpcUaClient-Test-Fixture.md]
fixture: []
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-8
driver: opcuaclient
phase: 2
plan_pr_id: "8"
title: "OpcUaClient — Type definition mirroring"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Walk upstream Types folder (ObjectTypes, VariableTypes, DataTypes, ReferenceTypes); project into local address space. Pass-3 in DiscoverAsync that walks i=86 under curation. Session.NodeCache.FetchNode, Session.LoadDataTypeSystem, Session.FetchTypeTree.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs, src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IAddressSpaceBuilder.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Client.UI.md]
fixture: []
e2e: []
effort: L
deps: [opcuaclient-7]
cross_driver: false
notes: ""
- id: opcuaclient-9
driver: opcuaclient
phase: 2
plan_pr_id: "9"
title: "OpcUaClient — Method node mirroring + Call passthrough"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Discover NodeClass.Method nodes; expose locally; forward Call invocations as Session.CallAsync. Adds new IMethodInvoker capability interface (9th capability). Browse HasProperty for InputArguments/OutputArguments.
files: [src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Client.CLI.md, docs/Client.UI.md]
fixture: []
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: L
deps: []
cross_driver: false
notes: "9th capability interface."
- id: opcuaclient-10
driver: opcuaclient
phase: 3
plan_pr_id: "10"
title: "OpcUaClient — Auto re-import on ModelChangeEvent"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Subscribe BaseModelChangeEventType / GeneralModelChangeEventType on i=2253 Server; debounced re-discover (2-5s window). WatchModelChanges, ModelChangeDebounce options.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverOptions.cs]
docs: [docs/drivers/OpcUaClient.md]
fixture: []
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-11
driver: opcuaclient
phase: 4
plan_pr_id: "11"
title: "OpcUaClient — Reverse Connect"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Server-initiated client connect for OT-DMZ outbound-only firewalls. ReverseConnectManager singleton listener; Session.Create accepting ITransportWaitingConnection. ReverseConnect options (Enabled, ListenerUrl, ExpectedServerUri).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md, docs/drivers/OpcUaClient-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: L
deps: []
cross_driver: false
notes: "Listen-vs-dial direction change; highest-risk PR."
- id: opcuaclient-12
driver: opcuaclient
phase: 5
plan_pr_id: "12"
title: "OpcUaClient — IHistoryProvider.ReadEventsAsync interface fix + impl"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Extend IHistoryProvider.ReadEventsAsync with EventFilter SelectClauses parameter. Implement OPC UA Client passthrough using Session.HistoryReadAsync with ReadEventDetails. Touches Core.Abstractions; affects Galaxy IHistoryProvider impl.
files: [src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IHistoryProvider.cs, src/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy/, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Client.CLI.md]
fixture: []
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: L
deps: []
cross_driver: true
notes: "Cross-driver: Core.Abstractions interface change; Galaxy override updates."
- id: opcuaclient-13
driver: opcuaclient
phase: 5
plan_pr_id: "13"
title: "OpcUaClient — Full Aggregate function set"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Extend HistoryAggregateType from 5 to 30+ Part 13 aggregates (TimeAverage, Interpolative, Range, PercentGood/Bad, StandardDeviation*, Variance*, Start/End/Delta, DurationInState*, etc.). Pure mapping work.
files: [src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IHistoryProvider.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Client.CLI.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: opcuaclient-14
driver: opcuaclient
phase: 5
plan_pr_id: "14"
title: "OpcUaClient — ServerUriArray redundant failover"
plan_anchor: "docs/plans/opcuaclient-plan.md"
summary: |
Read upstream Server.ServerArray + ServerRedundancyType.RedundancySupport. Subscribe Server_ServiceLevel; on low ServiceLevel open parallel session and TransferSubscriptionsAsync. Redundancy options block.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverOptions.cs]
docs: [docs/drivers/OpcUaClient.md, docs/Redundancy.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/docker-compose.yml]
e2e: [scripts/e2e/test-opcuaclient.ps1]
effort: L
deps: []
cross_driver: false
notes: ""
- id: s7-a1
driver: s7
phase: 1
plan_pr_id: "PR-S7-A1"
title: "S7 — 64-bit scalar types (LInt/ULInt/LReal/LWord)"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Closes the NotSupportedException cliff for Float64/Int64/UInt64. Extend S7Size with LWord; teach parser to accept DBL/LD suffix. S7netplus has no native LD; falls back to byte-range reads with explicit big-endian conversion.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7AddressParser.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/server.py, tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/profiles/s7_1500.json]
e2e: [scripts/e2e/test-s7.ps1]
effort: M
deps: []
cross_driver: false
notes: ""
- id: s7-a2
driver: s7
phase: 1
plan_pr_id: "PR-S7-A2"
title: "S7 — STRING / WSTRING / CHAR / WCHAR"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
S7 STRING (2-byte header + ASCII), WSTRING (4-byte header + UTF-16BE), CHAR, WCHAR codecs. Header-bug clamp on read; reject on write. New S7DataType members; respect StringLength.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/server.py]
e2e: [scripts/e2e/test-s7.ps1]
effort: M
deps: [s7-a1]
cross_driver: false
notes: ""
- id: s7-a3
driver: s7
phase: 1
plan_pr_id: "PR-S7-A3"
title: "S7 — DTL / DT / S5TIME / TIME / TOD / DATE"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
DTL (12-byte), DATE_AND_TIME (8-byte BCD), S5TIME (16-bit BCD), TIME (Int32 ms), TOD (UInt32 ms), DATE (UInt16 days since 1990). New S7DateTimeCodec.cs static class. Golden byte vectors from STEP 7 V18 reference.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DateTimeCodec.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/server.py]
e2e: []
effort: L
deps: [s7-a1]
cross_driver: false
notes: ""
- id: s7-a4
driver: s7
phase: 1
plan_pr_id: "PR-S7-A4"
title: "S7 — Array tags (ValueRank=1)"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
S7TagDefinition gets ArrayDim/ElementCount. Read/write path issues one byte-range read covering N elements; client-side slicing. Multi-dim deferred. Array-of-UDT lands with PR-S7-D2.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/server.py]
e2e: [scripts/e2e/test-s7.ps1]
effort: M
deps: [s7-a1]
cross_driver: false
notes: ""
- id: s7-a5
driver: s7
phase: 1
plan_pr_id: "PR-S7-A5"
title: "S7 — LOGO! 8 + S7-200 V-memory"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Teach S7AddressParser to accept V area letter, mapping to S7Area.DataBlock with DbNumber=1 for S7-200 (and per-firmware VM-mapping for LOGO!).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7AddressParser.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: s7-b1
driver: s7
phase: 2
plan_pr_id: "PR-S7-B1"
title: "S7 — Multi-variable PDU packing"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Replace per-tag plc.ReadAsync loop with packer using S7.Net.Types.DataItem + plc.ReadMultipleVarsAsync. Per-item errors fan out to per-tag StatusCodes. Packing budget: negotiatedPduSize - 18 - 12·N.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md]
fixture: []
e2e: []
effort: L
deps: []
cross_driver: false
notes: ""
- id: s7-b2
driver: s7
phase: 2
plan_pr_id: "PR-S7-B2"
title: "S7 — Block-read coalescing for contiguous DBs"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Planner pass groups same-DB tags by contiguous byte ranges; one Plc.ReadBytes per range. Default gap-merge 16 bytes (configurable). STRING/array tags opt out.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md]
fixture: []
e2e: []
effort: M
deps: [s7-b1]
cross_driver: false
notes: ""
- id: s7-c1
driver: s7
phase: 3
plan_pr_id: "PR-S7-C1"
title: "S7 — PDU size negotiation surfaced"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Read Plc.MaxPDUSize after OpenAsync; expose via GetHealth().Diagnostics["NegotiatedPduSize"]. Coordinate with Modbus diagnostic surface for Core DriverHealth.Diagnostics dictionary.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md, docs/drivers/S7-Test-Fixture.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: true
notes: "Core DriverHealth.Diagnostics shape change."
- id: s7-c2
driver: s7
phase: 3
plan_pr_id: "PR-S7-C2"
title: "S7 — TSAP / Connection Type selector"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
TsapMode enum (Auto/Pg/Op/S7Basic/Other) + LocalTsap/RemoteTsap overrides. Auto preserves current behaviour. Branches into raw-TSAP Plc constructor.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md]
fixture: []
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: s7-c3
driver: s7
phase: 3
plan_pr_id: "PR-S7-C3"
title: "S7 — Per-tag scan group / publish rate"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
S7TagDefinition.ScanGroup string. SubscribeAsync partitions into one poll loop per distinct interval (driver-local PollGroupEngine). _gate semaphore still serializes; document fairness-queue follow-up.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md]
fixture: []
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: s7-c4
driver: s7
phase: 3
plan_pr_id: "PR-S7-C4"
title: "S7 — Deadband / on-change with thresholds"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
DeadbandAbsolute and DeadbandPercent options on S7TagDefinition. Per-tag in PollOnceAsync for numeric types; non-numeric falls to exact equality. Edge cases: NaN, Infinity, sign flip, near-zero percent.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: s7-c5
driver: s7
phase: 3
plan_pr_id: "PR-S7-C5"
title: "S7 — Pre-flight PUT/GET enablement test"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Pre-flight read during InitializeAsync against MW0 / Probe.ProbeAddress. On PUT/GET-disabled PlcException, throw S7PutGetDisabledException with config hint.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: s7-d1
driver: s7
phase: 4
plan_pr_id: "PR-S7-D1"
title: "S7 — Symbol-table / TIA Portal export browse"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
SymbolImport directory: TiaCsvImporter (TIA Portal "Show all tags" CSV) and AwlImporter (best-effort STEP 7 Classic). Locale-comma TIA exports detected from header. Admin UI button + POST endpoint. UDT-typed symbols import as placeholders.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/SymbolImport/TiaCsvImporter.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/SymbolImport/AwlImporter.cs]
docs: [docs/drivers/S7-TIA-Import.md, docs/v2/s7.md, docs/Driver.S7.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Fixtures/]
e2e: []
effort: L
deps: []
cross_driver: false
notes: ""
- id: s7-d2
driver: s7
phase: 4
plan_pr_id: "PR-S7-D2"
title: "S7 — UDT / STRUCT / nested-DB handling"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
S7TagDefinition.UdtName + IReadOnlyList<S7UdtDefinition> Udts. Driver fans UDT-typed tags into per-member sub-tags. Caps UDT-of-UDT at 4 levels.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md, docs/drivers/S7-TIA-Import.md, docs/drivers/S7-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/server.py]
e2e: [scripts/e2e/test-s7.ps1]
effort: L
deps: [s7-d1]
cross_driver: false
notes: ""
- id: s7-d3
driver: s7
phase: 4
plan_pr_id: "PR-S7-D3"
title: "S7 — Instance-DB / FB parameter access"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Extend TIA importer to recognize "instance DB" entries; translate MyFB_Instance.MyParam → DBn.DBW_offset using FB interface declaration.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/SymbolImport/TiaCsvImporter.cs]
docs: [docs/drivers/S7-TIA-Import.md, docs/v2/s7.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Fixtures/]
e2e: []
effort: M
deps: [s7-d1, s7-d2]
cross_driver: false
notes: ""
- id: s7-e1
driver: s7
phase: 5
plan_pr_id: "PR-S7-E1"
title: "S7 — CPU diagnostic buffer / SZL reads"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Virtual @System.* tags dispatched via S7netplus's ReadSzlAsync. @System.CpuType/Firmware/OrderNo (SZL 0x0011), @System.CycleMs.{Min,Max,Avg} (SZL 0x0132/0x0432), @System.DiagBuffer[0..N] (SZL 0x00A0).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: []
e2e: []
effort: L
deps: [s7-c1]
cross_driver: false
notes: ""
- id: s7-e2
driver: s7
phase: 5
plan_pr_id: "PR-S7-E2"
title: "S7 — PLC password / protection-level handling"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Password and ProtectionLevel options. SendPassword call (with raw-PDU fallback per Siemens Communication Function Manual §5.2). Wire path gated on library support.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7DriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs]
docs: [docs/v2/s7.md, docs/Driver.S7.Cli.md, docs/drivers/S7-Test-Fixture.md]
fixture: []
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: s7-f
driver: s7
phase: 6
plan_pr_id: "PR-S7-F"
title: "S7 — Optimized DB / S7Plus (decision PR)"
plan_anchor: "docs/plans/s7-plan.md"
summary: |
Architectural decision PR. Three tracks: (1) document constraint and stay on S7netplus, (2) migrate to S7Plus-capable lib, (3) bridge via OPC UA Client driver against S7-1500's onboard UA server. Recommendation: ship Track 1 docs + Track 3 workflow path.
files: []
docs: [docs/v2/s7.md, docs/featuregaps.md, docs/drivers/S7-Test-Fixture.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: "Architectural decision PR."
- id: twincat-1.1
driver: twincat
phase: 1
plan_pr_id: "1.1"
title: "TwinCAT — Int64 fidelity for LINT/ULINT"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Map LInt/ULInt to DriverDataType.Int64 instead of silently truncating to Int32. Remove the truncation comment "matches Int64 gap". May add Int64 to Core.Abstractions DriverDataType enum.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDataType.cs, src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverDataType.cs]
docs: [docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/PLC/GVLs/GVL_Primitives.TcGVL]
e2e: []
effort: S
deps: []
cross_driver: false
notes: "Hardware-gated TWINCAT_TARGET_NETID."
- id: twincat-1.2
driver: twincat
phase: 1
plan_pr_id: "1.2"
title: "TwinCAT — TIME/DATE/DT/TOD as native UA types"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Stop marshalling IEC TIME/DATE/DT/TOD as raw UDINT; convert to native UA Duration/DateTime via post-processing in ReadValueAsync, ConvertForWrite, OnAdsNotificationEx. May expose Duration in DriverDataType.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDataType.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs]
docs: [docs/Driver.TwinCAT.Cli.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/PLC/GVLs/GVL_Primitives.TcGVL]
e2e: []
effort: M
deps: []
cross_driver: false
notes: "Hardware-gated."
- id: twincat-1.3
driver: twincat
phase: 1
plan_pr_id: "1.3"
title: "TwinCAT — Bit-indexed BOOL writes (RMW)"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Replace NotSupportedException at AdsTwinCATClient.cs:99 with read-modify-write on parent word; serialize concurrent bit writes via keyed SemaphoreSlim.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs]
docs: [docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: ""
- id: twincat-1.4
driver: twincat
phase: 1
plan_pr_id: "1.4"
title: "TwinCAT — Multi-dim and whole-array reads"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Whole-array reads in single AdsClient call. Surface IsArray + ArrayDimensions on TwinCATTagDefinition + DriverAttributeInfo.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriverOptions.cs]
docs: [docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/PLC/GVLs/GVL_Arrays.TcGVL]
e2e: []
effort: M
deps: []
cross_driver: false
notes: "Hardware-gated."
- id: twincat-1.5
driver: twincat
phase: 1
plan_pr_id: "1.5"
title: "TwinCAT — ENUM and ALIAS at discovery"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Inspect symbol.DataType + Category from TwinCAT.TypeSystem; DataTypeCategory.Enum walks EnumValues; Alias resolves to base atomic recursively. POINTER/REFERENCE/INTERFACE/UNION out of scope.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs]
docs: [docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: []
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: twincat-2.1
driver: twincat
phase: 2
plan_pr_id: "2.1"
title: "TwinCAT — ADS Sum-read / Sum-write"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Replace per-tag ReadValueAsync loops with SumSymbolRead/SumSymbolWrite (IndexGroup 0xF080-0xF084). Bucket by DeviceHostAddress. Targets ~10x throughput. Perf-tier test gated TWINCAT_PERF=1.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/ITwinCATClient.cs]
docs: [docs/v3/twincat-backlog.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/PLC/GVLs/GVL_Perf.TcGVL]
e2e: []
effort: L
deps: []
cross_driver: false
notes: "Perf-tier gated."
- id: twincat-2.2
driver: twincat
phase: 2
plan_pr_id: "2.2"
title: "TwinCAT — Handle-based access with caching"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Cache CreateVariableHandleAsync results. On DeviceSymbolVersionInvalid (0x710) evict and retry once. Clear on AdsClient reconnect until 2.3 ships.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs]
docs: [docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: []
e2e: []
effort: M
deps: []
cross_driver: false
notes: ""
- id: twincat-2.3
driver: twincat
phase: 2
plan_pr_id: "2.3"
title: "TwinCAT — Symbol-version invalidation listener"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
AddDeviceNotificationAsync on AdsReservedIndexGroup.SymbolVersion (0xF008); wipe handle cache on online-change bumps.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs]
docs: [docs/drivers/TwinCAT-Test-Fixture.md]
fixture: []
e2e: []
effort: M
deps: [twincat-2.2]
cross_driver: false
notes: "Hardware-gated."
- id: twincat-3.1
driver: twincat
phase: 3
plan_pr_id: "3.1"
title: "TwinCAT — Per-tag MaxDelay tuning"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
NotificationSettings.MaxDelay as per-tag option (default 0). Plumb int? MaxDelayMs through TwinCATTagDefinition, SubscribeAsync, AddNotificationAsync.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriverOptions.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs]
docs: [docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: []
e2e: []
effort: S
deps: []
cross_driver: false
notes: "Hardware-gated."
- id: twincat-3.2
driver: twincat
phase: 3
plan_pr_id: "3.2"
title: "TwinCAT — Cycle-time / jitter / PLC-state diagnostics"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Probe loop reads _AppInfo.OnlineChangeCnt/AppName + _TaskInfo[1].CycleTime/LastExecTime; surface as TwinCATDeviceDiagnostics on DeviceState; emit through IDriverDiagnostics (cross-driver from Modbus task #154).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATSystemSymbolFilter.cs]
docs: [docs/drivers/TwinCAT-Test-Fixture.md, docs/Driver.TwinCAT.Cli.md, docs/v3/twincat-backlog.md]
fixture: []
e2e: []
effort: M
deps: []
cross_driver: true
notes: "Reuses IDriverDiagnostics from Modbus #154."
- id: twincat-4.1
driver: twincat
phase: 4
plan_pr_id: "4.1"
title: "TwinCAT — Nested UDT browse via online type walker"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
Recurse BrowseSymbolsAsync into IStructType.SubItems yielding one TwinCATDiscoveredSymbol per leaf with dotted instance paths. Expand arrays-of-structs up to 1024. Online runtime path only — TMC offline parsing deferred.
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATTypeWalker.cs]
docs: [docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md, docs/v3/twincat-backlog.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/PLC/DUTs/]
e2e: []
effort: L
deps: [twincat-1.5]
cross_driver: false
notes: "Hardware-gated."
- id: twincat-5.1
driver: twincat
phase: 5
plan_pr_id: "5.1"
title: "TwinCAT — IAlarmSource via TC3 EventLogger"
plan_anchor: "docs/plans/twincat-plan.md"
summary: |
IAlarmSource over TcEventLogger on AMS port 110 so PLC alarms surface as OPC UA AC events. Spike-first to determine managed wrapper vs raw AMS port 110. EnableAlarms option (default false).
files: [src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATAlarmSource.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs, src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriverOptions.cs]
docs: [docs/drivers/TwinCAT.md, docs/v3/twincat-eventlogger-spike.md, docs/Driver.TwinCAT.Cli.md, docs/drivers/TwinCAT-Test-Fixture.md]
fixture: [tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/PLC/POUs/FB_AlarmHarness.TcPOU]
e2e: [scripts/e2e/test-twincat.ps1]
effort: L
deps: []
cross_driver: false
notes: "Spike-first."