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//_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,,2,. 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 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."