dohertj2 dohertj2
  • Joined on 2026-02-20
dohertj2 created branch abcip-pr7-isubscribable in dohertj2/lmxopcua 2026-04-19 17:13:26 -04:00
dohertj2 pushed to abcip-pr7-isubscribable at dohertj2/lmxopcua 2026-04-19 17:13:26 -04:00
33780eb64c AB CIP PR 7 — ISubscribable via shared PollGroupEngine. AbCipDriver now implements ISubscribable — Subscribe delegates into the PollGroupEngine extracted in PR 1, Unsubscribe releases the subscription, ShutdownAsync disposes the engine cancelling every active subscription. OnDataChange event wired through the engine's on-change callback so external subscribers see the driver as sender. The engine's reader delegate points at the driver's ReadAsync (already handles lazy runtime init + caching via EnsureTagRuntimeAsync) — each poll tick batch-reads every subscribed tag in one IReadable call. 100ms interval floor inherited from PollGroupEngine.DefaultMinInterval matches Modbus convention. Initial-data push on first poll preserved via forceRaise=true. Exception-tolerant loop preserved — individual read failures show up as DataValueSnapshot with non-Good StatusCode via the status-code mapping PR 3 established. 7 new unit tests in AbCipSubscriptionTests covering initial-poll raising per tag, unchanged value raising only once, value change between polls triggering a new event, Unsubscribe halting the loop, 100ms floor keeping a 5ms request from generating extra events against a stable value, ShutdownAsync cancelling active subscriptions, UDT member subscription routing through the synthesised Motor1.Speed full-reference (proving PR 6's fan-out composes correctly with PR 7's subscription path). Total AbCip unit tests now 137/137 passing (+7 from PR 6's 130). Validates that the shared PollGroupEngine from PR 1 works correctly for a second driver, closing the original motivation for the extraction. Full solution builds 0 errors; Modbus + other drivers untouched.
dohertj2 pushed to v2 at dohertj2/lmxopcua 2026-04-19 17:11:11 -04:00
521bcb2f68 Merge pull request (#113) - AbCip UDT members
b06a1ba607 AB CIP PR 6 — UDT member-declaration support. Declaration-driven UDT member fan-out — users declare a UDT-typed tag once with an explicit Members list and the driver (1) expands member-addressable tags synthetically at Initialize time so Read/Write/Subscribe hit individual native tags per member, (2) emits a folder + one Variable per member in DiscoverAsync instead of a single opaque Structure Variable. Matches the Logix 5000 addressing convention where members are reached via dotted syntax (Motor1.Speed, Motor1.Running) — AbCipTagPath already parsed this shape in PR 2, so PR 6 just had to wire config→TagPath composition. New AbCipStructureMember record — Name / DataType / Writable / WriteIdempotent — plus optional Members list on AbCipTagDefinition that's ignored for atomic types and optional for Structure types. When Structure has null or empty Members the driver falls back to emitting a single opaque Variable so downstream config can address members manually (the "black box" path documented in AbCipTagDefinition's docstring). AbCipDriver.InitializeAsync now iterates tags + for every Structure tag with non-empty Members synthesises a child AbCipTagDefinition per member (composed full-reference Parent.Member + composed TagPath parent.member passed through to libplctag as a normal symbolic read). Per-member Writable/WriteIdempotent metadata propagates so IWritable correctly rejects writes to members flagged non-writable even when the parent tag is writable — each member stands alone from the resilience + authz perspective. DiscoverAsync gains a matching branch — Structure with Members emits an intermediate folder named after the parent tag + one Variable per member under it (browse name = member.Name, FullName = Parent.Member). Members with Writable=false surface SecurityClassification.ViewOnly, WriteIdempotent flag passes through to the DriverAttributeInfo. Structure without Members falls through to the normal single-Variable path. Whole-UDT read optimization (one libplctag call returns the packed buffer + client-side member decode) is deferred — needs the CIP Template Object class 0x6C reader which is blocked on the same libplctag 1.5.2 TagInfoPlcMapper gap that deferred the real @tags walker in PR 5. AbCipTemplateCache shipped in PR 5 is the drop-in point when that reader lands. Per-member reads today are N native round-trips; whole-UDT optimisation is a perf win, not a correctness gap. 7 new unit tests in AbCipUdtMemberTests — UDT fan-out to Variable children under folder with correct SecurityClassification + WriteIdempotent propagation, member reads via synthesised full-reference with correct per-member values, member writes routing to correct TagPath, member Writable=false flag correctly blocking IWritable, Structure without Members falls back to single Variable, empty Members list treated identically to null, UDT tags coexist with flat tags in the discovery output. Total AbCip unit tests now 130/130 passing (+7 from PR 5's 123). Modbus + other drivers untouched; full solution builds 0 errors. Unblocks PR 7 (ISubscribable) — the poll engine already works with member-level full references.
Compare 2 commits »
dohertj2 merged pull request dohertj2/lmxopcua#113 2026-04-19 17:11:10 -04:00
AB CIP PR 6 � UDT member-declaration support
dohertj2 created pull request dohertj2/lmxopcua#113 2026-04-19 17:10:58 -04:00
AB CIP PR 6 � UDT member-declaration support
dohertj2 created branch abcip-pr6-udt-members in dohertj2/lmxopcua 2026-04-19 17:10:56 -04:00
dohertj2 pushed to abcip-pr6-udt-members at dohertj2/lmxopcua 2026-04-19 17:10:56 -04:00
b06a1ba607 AB CIP PR 6 — UDT member-declaration support. Declaration-driven UDT member fan-out — users declare a UDT-typed tag once with an explicit Members list and the driver (1) expands member-addressable tags synthetically at Initialize time so Read/Write/Subscribe hit individual native tags per member, (2) emits a folder + one Variable per member in DiscoverAsync instead of a single opaque Structure Variable. Matches the Logix 5000 addressing convention where members are reached via dotted syntax (Motor1.Speed, Motor1.Running) — AbCipTagPath already parsed this shape in PR 2, so PR 6 just had to wire config→TagPath composition. New AbCipStructureMember record — Name / DataType / Writable / WriteIdempotent — plus optional Members list on AbCipTagDefinition that's ignored for atomic types and optional for Structure types. When Structure has null or empty Members the driver falls back to emitting a single opaque Variable so downstream config can address members manually (the "black box" path documented in AbCipTagDefinition's docstring). AbCipDriver.InitializeAsync now iterates tags + for every Structure tag with non-empty Members synthesises a child AbCipTagDefinition per member (composed full-reference Parent.Member + composed TagPath parent.member passed through to libplctag as a normal symbolic read). Per-member Writable/WriteIdempotent metadata propagates so IWritable correctly rejects writes to members flagged non-writable even when the parent tag is writable — each member stands alone from the resilience + authz perspective. DiscoverAsync gains a matching branch — Structure with Members emits an intermediate folder named after the parent tag + one Variable per member under it (browse name = member.Name, FullName = Parent.Member). Members with Writable=false surface SecurityClassification.ViewOnly, WriteIdempotent flag passes through to the DriverAttributeInfo. Structure without Members falls through to the normal single-Variable path. Whole-UDT read optimization (one libplctag call returns the packed buffer + client-side member decode) is deferred — needs the CIP Template Object class 0x6C reader which is blocked on the same libplctag 1.5.2 TagInfoPlcMapper gap that deferred the real @tags walker in PR 5. AbCipTemplateCache shipped in PR 5 is the drop-in point when that reader lands. Per-member reads today are N native round-trips; whole-UDT optimisation is a perf win, not a correctness gap. 7 new unit tests in AbCipUdtMemberTests — UDT fan-out to Variable children under folder with correct SecurityClassification + WriteIdempotent propagation, member reads via synthesised full-reference with correct per-member values, member writes routing to correct TagPath, member Writable=false flag correctly blocking IWritable, Structure without Members falls back to single Variable, empty Members list treated identically to null, UDT tags coexist with flat tags in the discovery output. Total AbCip unit tests now 130/130 passing (+7 from PR 5's 123). Modbus + other drivers untouched; full solution builds 0 errors. Unblocks PR 7 (ISubscribable) — the poll engine already works with member-level full references.
dohertj2 pushed to v2 at dohertj2/lmxopcua 2026-04-19 17:07:07 -04:00
dd1389a8e7 Merge pull request (#112) - AbCip ITagDiscovery
447086892e AB CIP PR 5 — ITagDiscovery (pre-declared emission + controller-enumeration scaffolding). DiscoverAsync streams tags to IAddressSpaceBuilder with the same shape the Modbus driver uses, keyed by device host address so one driver instance exposing N PLCs produces N device folders under a shared "AbCip" root. Pre-declared tags from AbCipDriverOptions.Tags emit first, filtered through AbCipSystemTagFilter so __DEFVAL_* / __DEFAULT_* / Routine: / Task: / Local:N:X / Map: / Axis: / Cam: / MotionGroup: infrastructure names never reach the address space. Writable tags map to SecurityClassification.Operate, non-writable to ViewOnly. Controller enumeration (walking the Logix Symbol Object via @tags) is wired up through a new IAbCipTagEnumerator + IAbCipTagEnumeratorFactory abstraction — default EmptyAbCipTagEnumeratorFactory returns an empty sequence so the driver stays production-safe without a real decoder. Tests inject FakeEnumeratorFactory to exercise the discovered-tag path: discovered tags land under a Discovered/ sub-folder, program-scope produces Program:P.Name full references, the IsSystemTag hint + the AbCipSystemTagFilter both act as gates, ReadOnly surfaces SecurityClassification.ViewOnly. The real @tags walker is a follow-up because libplctag 1.5.2 (latest stable on NuGet) does not expose TagInfoPlcMapper / UdtInfoMapper — the DataTypes namespace only ships IPlcMapper<T>, so enumerating the Symbol Object requires either implementing a custom IPlcMapper for the CIP byte layout or raw-buffer decoding via plc_tag_get_raw — both non-trivial enough to warrant their own PR. Code comment on EmptyAbCipTagEnumerator documents the gap + points to the follow-up. AbCipTemplateCache placeholder ships with a ConcurrentDictionary<(device, templateInstanceId), AbCipUdtShape> + Put / TryGet / Clear / Count — the Template Object reader (CIP class 0x6C) populates it in PR 6 and FlushOptionalCachesAsync now clears it. AbCipUdtShape + AbCipUdtMember records describe UDT layout — type name + total size + ordered members with offset / type / array length. AbCipDriver ctor gains optional enumeratorFactory parameter matching the tagFactory pattern from PR 3. TemplateCache exposed internally for PR 6's reader to write into. 25 new unit tests in AbCipDriverDiscoveryTests covering — pre-declared emission under device folder, DeviceName fallback to host address, system-tag filter rejecting pre-declared infrastructure names, cross-device tag filtering (tags for a device this driver does not own are ignored), controller enumeration adds tags under Discovered/, system-tag hint + filter both enforced, ReadOnly → ViewOnly, AbCipTagCreateParams composition (gateway / port / CIP path / libplctag attribute / tag name "@tags" / timeout), default enumerator factory used when not injected, 13 Theory cases covering every AbCipSystemTagFilter pattern, template cache roundtrip + clear, FlushOptionalCachesAsync clears the cache. Total AbCip unit tests now 123/123 passing (+25 from PR 4's 98). Modbus + other existing tests untouched; full solution builds 0 errors. Unblocks PR 6 (UDT structured read/write) + PR 7 (subscriptions consuming PollGroupEngine from PR 1).
Compare 2 commits »
dohertj2 merged pull request dohertj2/lmxopcua#112 2026-04-19 17:07:05 -04:00
AB CIP PR 5 — ITagDiscovery (pre-declared + controller-enumeration scaffolding)
dohertj2 created pull request dohertj2/lmxopcua#112 2026-04-19 17:06:54 -04:00
AB CIP PR 5 — ITagDiscovery (pre-declared + controller-enumeration scaffolding)
dohertj2 created branch abcip-pr5-discovery in dohertj2/lmxopcua 2026-04-19 17:06:52 -04:00
dohertj2 pushed to abcip-pr5-discovery at dohertj2/lmxopcua 2026-04-19 17:06:52 -04:00
447086892e AB CIP PR 5 — ITagDiscovery (pre-declared emission + controller-enumeration scaffolding). DiscoverAsync streams tags to IAddressSpaceBuilder with the same shape the Modbus driver uses, keyed by device host address so one driver instance exposing N PLCs produces N device folders under a shared "AbCip" root. Pre-declared tags from AbCipDriverOptions.Tags emit first, filtered through AbCipSystemTagFilter so __DEFVAL_* / __DEFAULT_* / Routine: / Task: / Local:N:X / Map: / Axis: / Cam: / MotionGroup: infrastructure names never reach the address space. Writable tags map to SecurityClassification.Operate, non-writable to ViewOnly. Controller enumeration (walking the Logix Symbol Object via @tags) is wired up through a new IAbCipTagEnumerator + IAbCipTagEnumeratorFactory abstraction — default EmptyAbCipTagEnumeratorFactory returns an empty sequence so the driver stays production-safe without a real decoder. Tests inject FakeEnumeratorFactory to exercise the discovered-tag path: discovered tags land under a Discovered/ sub-folder, program-scope produces Program:P.Name full references, the IsSystemTag hint + the AbCipSystemTagFilter both act as gates, ReadOnly surfaces SecurityClassification.ViewOnly. The real @tags walker is a follow-up because libplctag 1.5.2 (latest stable on NuGet) does not expose TagInfoPlcMapper / UdtInfoMapper — the DataTypes namespace only ships IPlcMapper<T>, so enumerating the Symbol Object requires either implementing a custom IPlcMapper for the CIP byte layout or raw-buffer decoding via plc_tag_get_raw — both non-trivial enough to warrant their own PR. Code comment on EmptyAbCipTagEnumerator documents the gap + points to the follow-up. AbCipTemplateCache placeholder ships with a ConcurrentDictionary<(device, templateInstanceId), AbCipUdtShape> + Put / TryGet / Clear / Count — the Template Object reader (CIP class 0x6C) populates it in PR 6 and FlushOptionalCachesAsync now clears it. AbCipUdtShape + AbCipUdtMember records describe UDT layout — type name + total size + ordered members with offset / type / array length. AbCipDriver ctor gains optional enumeratorFactory parameter matching the tagFactory pattern from PR 3. TemplateCache exposed internally for PR 6's reader to write into. 25 new unit tests in AbCipDriverDiscoveryTests covering — pre-declared emission under device folder, DeviceName fallback to host address, system-tag filter rejecting pre-declared infrastructure names, cross-device tag filtering (tags for a device this driver does not own are ignored), controller enumeration adds tags under Discovered/, system-tag hint + filter both enforced, ReadOnly → ViewOnly, AbCipTagCreateParams composition (gateway / port / CIP path / libplctag attribute / tag name "@tags" / timeout), default enumerator factory used when not injected, 13 Theory cases covering every AbCipSystemTagFilter pattern, template cache roundtrip + clear, FlushOptionalCachesAsync clears the cache. Total AbCip unit tests now 123/123 passing (+25 from PR 4's 98). Modbus + other existing tests untouched; full solution builds 0 errors. Unblocks PR 6 (UDT structured read/write) + PR 7 (subscriptions consuming PollGroupEngine from PR 1).
dohertj2 merged pull request dohertj2/lmxopcua#111 2026-04-19 17:00:00 -04:00
AB CIP PR 4 — IWritable
dohertj2 pushed to v2 at dohertj2/lmxopcua 2026-04-19 17:00:00 -04:00
cee52a9134 Merge pull request (#111) - AbCip IWritable
257f4fd3f5 AB CIP PR 4 — IWritable implementation. LibplctagTagRuntime.EncodeValue fills in the switch for every atomic Logix type the driver currently surfaces — Bool (standalone BOOL via SetInt8 0/1), SInt/USInt (SetInt8/SetUInt8), Int/UInt (SetInt16/SetUInt16), DInt/UDInt (SetInt32/SetUInt32), LInt/ULInt (SetInt64/SetUInt64), Real (SetFloat32), LReal (SetFloat64), String (SetString 0), Dt (epoch DINT via SetInt32). BOOL-within-DINT writes throw NotSupportedException with a code comment matching the Modbus BitInRegister pattern at ModbusDriver.cs line 640 — the read-modify-write logic + lock-per-DINT discipline is a follow-up PR rather than squeezing it into the initial wire plumbing. Structure writes throw NotSupportedException pointing at PR 6 when UDT support lands. AbCipDriver now implements IWritable. WriteAsync iterates writes preserving order, short-circuits on unknown reference → BadNodeIdUnknown, on non-writable tag definition → BadNotWritable, on unknown device → BadNodeIdUnknown. Happy path materialises the cached runtime via EnsureTagRuntimeAsync (shares PR 3's lazy-init path so read+write on the same tag hits one native handle), EncodeValue into the tag's buffer, WriteAsync flushes, GetStatus confirms the wire status, maps libplctag error codes via AbCipStatusMapper.MapLibplctagStatus, sets health Healthy on success. Per plan decisions #44, #45, #143 the driver does NOT auto-retry writes — that's a resilience-layer concern (Polly pipeline sitting above) keyed on the tag's WriteIdempotent flag. Exception-mapping table — OperationCanceledException rethrows (honors cancellation), NotSupportedException → BadNotSupported (bit-in-DINT, Structure, future unsupported types), FormatException → BadTypeMismatch (Convert.ToInt32 of a non-numeric string), InvalidCastException → BadTypeMismatch (caller passed an object incompatible with the conversion target), OverflowException → BadOutOfRange (value exceeds target type range, e.g. Int16 write of 1_000_000), any other Exception → BadCommunicationError (wire drop, libplctag-internal failure). Health surface updates Degraded on every non-Cancellation exception path, Healthy on success. Introduces AbCipStatusMapper.BadTypeMismatch (0x80730000). 10 new unit tests in AbCipDriverWriteTests covering — unknown ref → BadNodeIdUnknown, non-writable tag → BadNotWritable, successful DInt write encodes + flushes the value + marks WriteCount=1, BOOL-in-DINT rejected as BadNotSupported (separate ThrowingBoolBitFake mirrors LibplctagTagRuntime's runtime check), non-zero libplctag status after write mapped via AbCipStatusMapper (timeout -5 → BadTimeout), FormatException from non-numeric-string write → BadTypeMismatch (RealConvertFake exercises real Convert.ToInt32), OverflowException from Int16 write of 1_000_000 → BadOutOfRange, generic exception during write → BadCommunicationError + health Degraded, batch with mixed success+failure preserves order across four request types, cancellation propagates as OperationCanceledException. FakeAbCipTag's test-fake base class methods made virtual so override hooks work correctly through the IAbCipTagRuntime interface (new-shadow was silently falling through to the base implementation). Total AbCip unit tests now 98/98 passing; Modbus + other existing tests untouched; full solution builds 0 errors.
Compare 2 commits »
dohertj2 created pull request dohertj2/lmxopcua#111 2026-04-19 16:59:50 -04:00
AB CIP PR 4 — IWritable
dohertj2 created branch abcip-pr4-iwritable in dohertj2/lmxopcua 2026-04-19 16:59:47 -04:00
dohertj2 pushed to abcip-pr4-iwritable at dohertj2/lmxopcua 2026-04-19 16:59:47 -04:00
257f4fd3f5 AB CIP PR 4 — IWritable implementation. LibplctagTagRuntime.EncodeValue fills in the switch for every atomic Logix type the driver currently surfaces — Bool (standalone BOOL via SetInt8 0/1), SInt/USInt (SetInt8/SetUInt8), Int/UInt (SetInt16/SetUInt16), DInt/UDInt (SetInt32/SetUInt32), LInt/ULInt (SetInt64/SetUInt64), Real (SetFloat32), LReal (SetFloat64), String (SetString 0), Dt (epoch DINT via SetInt32). BOOL-within-DINT writes throw NotSupportedException with a code comment matching the Modbus BitInRegister pattern at ModbusDriver.cs line 640 — the read-modify-write logic + lock-per-DINT discipline is a follow-up PR rather than squeezing it into the initial wire plumbing. Structure writes throw NotSupportedException pointing at PR 6 when UDT support lands. AbCipDriver now implements IWritable. WriteAsync iterates writes preserving order, short-circuits on unknown reference → BadNodeIdUnknown, on non-writable tag definition → BadNotWritable, on unknown device → BadNodeIdUnknown. Happy path materialises the cached runtime via EnsureTagRuntimeAsync (shares PR 3's lazy-init path so read+write on the same tag hits one native handle), EncodeValue into the tag's buffer, WriteAsync flushes, GetStatus confirms the wire status, maps libplctag error codes via AbCipStatusMapper.MapLibplctagStatus, sets health Healthy on success. Per plan decisions #44, #45, #143 the driver does NOT auto-retry writes — that's a resilience-layer concern (Polly pipeline sitting above) keyed on the tag's WriteIdempotent flag. Exception-mapping table — OperationCanceledException rethrows (honors cancellation), NotSupportedException → BadNotSupported (bit-in-DINT, Structure, future unsupported types), FormatException → BadTypeMismatch (Convert.ToInt32 of a non-numeric string), InvalidCastException → BadTypeMismatch (caller passed an object incompatible with the conversion target), OverflowException → BadOutOfRange (value exceeds target type range, e.g. Int16 write of 1_000_000), any other Exception → BadCommunicationError (wire drop, libplctag-internal failure). Health surface updates Degraded on every non-Cancellation exception path, Healthy on success. Introduces AbCipStatusMapper.BadTypeMismatch (0x80730000). 10 new unit tests in AbCipDriverWriteTests covering — unknown ref → BadNodeIdUnknown, non-writable tag → BadNotWritable, successful DInt write encodes + flushes the value + marks WriteCount=1, BOOL-in-DINT rejected as BadNotSupported (separate ThrowingBoolBitFake mirrors LibplctagTagRuntime's runtime check), non-zero libplctag status after write mapped via AbCipStatusMapper (timeout -5 → BadTimeout), FormatException from non-numeric-string write → BadTypeMismatch (RealConvertFake exercises real Convert.ToInt32), OverflowException from Int16 write of 1_000_000 → BadOutOfRange, generic exception during write → BadCommunicationError + health Degraded, batch with mixed success+failure preserves order across four request types, cancellation propagates as OperationCanceledException. FakeAbCipTag's test-fake base class methods made virtual so override hooks work correctly through the IAbCipTagRuntime interface (new-shadow was silently falling through to the base implementation). Total AbCip unit tests now 98/98 passing; Modbus + other existing tests untouched; full solution builds 0 errors.
dohertj2 pushed to v2 at dohertj2/lmxopcua 2026-04-19 16:41:05 -04:00
be2379107d Merge pull request (#110) - AbCip IReadable
cc35c77d64 AB CIP PR 3 — IReadable implementation against libplctag. Introduces IAbCipTagRuntime + IAbCipTagFactory abstraction matching the Modbus transport-factory pattern (ctor optional arg, default production impl injected) so the driver's read/status-mapping logic is unit-testable without a live PLC or the native libplctag binary. LibplctagTagRuntime is the default wire-backed implementation — wraps libplctag.Tag + translates our AbCipDataType enum into GetInt8/GetUInt8/GetInt16/GetUInt16/GetInt32/GetUInt32/GetInt64/GetUInt64/GetFloat32/GetFloat64/GetString/GetBit calls covering Bool (standalone + BOOL-in-DINT via .N bit selector), SInt/USInt, Int/UInt, DInt/UDInt, LInt/ULInt, Real, LReal, String, Dt (epoch DINT), with Structure deferred to PR 6. MapPlcType bridges our libplctag attribute strings (controllogix, compactlogix, micro800) to libplctag.PlcType enum; CompactLogix rolls under ControlLogix per libplctag's family grouping which matches the wire protocol reality. AbCipDriver now implements IReadable — ReadAsync iterates fullReferences preserving order, looks up each tag definition + its device, lazily materialises the tag runtime via EnsureTagRuntimeAsync on first touch (cached thereafter for the lifetime of the device), catches OperationCanceledException to honor cancellation, maps libplctag non-zero status via AbCipStatusMapper.MapLibplctagStatus, catches any other exception as BadCommunicationError. Health surface moves to Healthy on success + Degraded with the last error message on failure. Initialize-failure path disposes the half-created runtime before rethrowing so no native handles leak. DeviceState gains a Runtimes dict alongside the existing TagHandles collection; DisposeHandles walks both so ShutdownAsync + ReinitializeAsync cleanly destroy every native tag. 12 new unit tests in AbCipDriverReadTests using FakeAbCipTag / FakeAbCipTagFactory (test fake under tests/...AbCip.Tests/FakeAbCipTag.cs) covering unknown reference → BadNodeIdUnknown, unknown device → BadNodeIdUnknown, successful DInt read with correct Good status + captured value, lazy-init on first read with reuse across subsequent reads, non-zero libplctag status mapping via AbCipStatusMapper, exception during read surfacing as BadCommunicationError with health Degraded, batched reads preserving order + per-tag status, health Healthy after success, TagCreateParams composition from device + profile (gateway / port / CIP path / libplctag attribute / tag name wiring), cancellation propagation via OperationCanceledException, ShutdownAsync disposing every runtime, Initialize-failure disposing the aborted runtime. Total AbCip unit tests now 88/88 passing. Integration test project scaffolding — tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests with AbServerFixture (IAsyncLifetime that starts ab_server when the binary is on PATH, otherwise marks IsAvailable=false), AbServerFact attribute (Fact-equivalent that skips when ab_server is missing), one smoke test exercising DInt read end-to-end. Project runs cleanly — the single smoke test skips on boxes without ab_server (0 failed, 0 passed, 1 skipped) + runs on boxes with it. Follow-up work captured in comments — ab_server CI fixture (download prebuilt Windows x64 binary as GitHub release asset) + per-family JSON profiles + hand-rolled CIP stub for UDT fidelity ship in the PR 6/9-12 window. Solution file updated. Full solution builds 0 errors across all 28 projects. Modbus + other existing tests untouched.
Compare 2 commits »
dohertj2 merged pull request dohertj2/lmxopcua#110 2026-04-19 16:41:04 -04:00
AB CIP PR 3 — IReadable against libplctag (swappable factory + fake + skip-gated smoke)
dohertj2 created pull request dohertj2/lmxopcua#110 2026-04-19 16:40:53 -04:00
AB CIP PR 3 — IReadable against libplctag (swappable factory + fake + skip-gated smoke)