AB CIP PR 6 � UDT member-declaration support #113

Merged
dohertj2 merged 1 commits from abcip-pr6-udt-members into v2 2026-04-19 17:11:10 -04:00
Owner

Summary

Declaration-driven UDT member fan-out � user declares a UDT tag once with a Members list; driver (1) synthesises member-addressable tag entries at init so Read/Write/Subscribe work per-member, (2) emits a folder + Variable-per-member in discovery instead of a single opaque Structure Variable.

New types: AbCipStructureMember (Name, DataType, Writable, WriteIdempotent); AbCipTagDefinition.Members optional list.

Behaviour:

  • UDT with declared Members ? folder + per-member Variables (FullName = Parent.Member)
  • UDT without Members ? single opaque Variable (black-box passthrough)
  • Per-member Writable=false correctly blocks writes in IWritable
  • Member WriteIdempotent propagates to DriverAttributeInfo

Test plan

  • 7 new tests � fan-out, read-via-synthesized-ref, write-via-synthesized-ref, Writable=false blocked, null/empty Members fallback, mixed flat+UDT
  • 130/130 AbCip tests pass (+7 from PR 5)
  • Full solution builds 0 errors

Deferred

  • Whole-UDT read optimization (one native call + client decode) � needs CIP Template Object class 0x6C reader, blocked on libplctag 1.5.2 TagInfoPlcMapper gap. AbCipTemplateCache from PR 5 is the drop-in.

Merges to v2.

## Summary Declaration-driven UDT member fan-out � user declares a UDT tag once with a `Members` list; driver (1) synthesises member-addressable tag entries at init so Read/Write/Subscribe work per-member, (2) emits a folder + Variable-per-member in discovery instead of a single opaque Structure Variable. **New types:** `AbCipStructureMember` (Name, DataType, Writable, WriteIdempotent); `AbCipTagDefinition.Members` optional list. **Behaviour:** - UDT with declared Members ? folder + per-member Variables (FullName = `Parent.Member`) - UDT without Members ? single opaque Variable (black-box passthrough) - Per-member `Writable=false` correctly blocks writes in IWritable - Member WriteIdempotent propagates to DriverAttributeInfo ## Test plan - [x] 7 new tests � fan-out, read-via-synthesized-ref, write-via-synthesized-ref, Writable=false blocked, null/empty Members fallback, mixed flat+UDT - [x] **130/130 AbCip tests pass** (+7 from PR 5) - [x] Full solution builds 0 errors ## Deferred - Whole-UDT read optimization (one native call + client decode) � needs CIP Template Object class 0x6C reader, blocked on libplctag 1.5.2 TagInfoPlcMapper gap. `AbCipTemplateCache` from PR 5 is the drop-in. Merges to `v2`.
dohertj2 added 1 commit 2026-04-19 17:10:58 -04:00
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. b06a1ba607
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit 521bcb2f68 into v2 2026-04-19 17:11:10 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#113