Auto: abcip-3.3 — read-strategy selector (WholeUdt / MultiPacket / Auto)
Closes #237
This commit is contained in:
@@ -306,3 +306,100 @@ the field defaults to `"Auto"`.
|
||||
"What it actually covers" — Logical-mode fixture coverage status.
|
||||
- [`AbCipAddressingModeBenchTests`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/AbCipAddressingModeBenchTests.cs) —
|
||||
scaffold for the wall-clock comparison; gated on `[AbServerFact]`.
|
||||
|
||||
## Read strategy (PR abcip-3.3)
|
||||
|
||||
A per-device toggle that controls how multi-member UDT batches are read.
|
||||
The default `Auto` value matches every previous build's behaviour for dense
|
||||
reads but switches to per-member bundling when only a handful of members of
|
||||
a large UDT are subscribed — the canonical "5 of 50" sparse-subscription
|
||||
case where reading the whole UDT buffer just to extract a few fields wastes
|
||||
wire bandwidth.
|
||||
|
||||
### Three modes
|
||||
|
||||
| Mode | When to use |
|
||||
|---|---|
|
||||
| `WholeUdt` | Most members of every subscribed UDT are read together. One libplctag read per parent UDT, members decoded in-memory at their byte offsets. The task #194 default. |
|
||||
| `MultiPacket` | A few members of a large UDT are subscribed at a time. One read per subscribed member, bundled per parent into one CIP Multi-Service Packet. |
|
||||
| `Auto` (default) | Planner picks per-batch from the subscribed-member fraction (see *Sparsity threshold*). |
|
||||
|
||||
### Sparsity threshold
|
||||
|
||||
Auto mode divides `subscribedMembers / totalMembers` for each parent UDT and
|
||||
picks `MultiPacket` when the fraction is **strictly less than** the
|
||||
threshold, else `WholeUdt`. Default threshold `0.25` — a 1/4 subscription is
|
||||
the rough break-even where the wire-cost of one whole-UDT read still beats
|
||||
N member reads on a ControlLogix 4002-byte connection-size buffer; above
|
||||
1/4, the per-member overhead dominates.
|
||||
|
||||
Tune via `AbCipDeviceOptions.MultiPacketSparsityThreshold` (clamped to
|
||||
`[0..1]`). Threshold `0.0` = "never MultiPacket"; `1.0` = "always MultiPacket
|
||||
when any member is subscribed."
|
||||
|
||||
### Family compatibility
|
||||
|
||||
`MultiPacket` requires CIP service `0x0A` (Multi-Service Packet) on the
|
||||
controller. Source of truth is
|
||||
[`AbCipPlcFamilyProfile.SupportsRequestPacking`](../../src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/PlcFamilies/AbCipPlcFamilyProfile.cs):
|
||||
|
||||
| Family | `SupportsRequestPacking` |
|
||||
|---|---|
|
||||
| ControlLogix | yes |
|
||||
| CompactLogix | yes |
|
||||
| GuardLogix | yes (wire identical to ControlLogix) |
|
||||
| Micro800 | **no** |
|
||||
| SLC500 / PLC5 (when those profiles ship) | **no** |
|
||||
|
||||
User-forced `MultiPacket` against a non-packing family logs a warning at
|
||||
device init and falls back to `WholeUdt`. `Auto` against a non-packing
|
||||
family stays `Auto` at the device level — the per-batch heuristic caps the
|
||||
strategy to `WholeUdt` so the wire never sees a Multi-Service-Packet against
|
||||
a controller that can't decode it.
|
||||
|
||||
### libplctag wrapper limitation
|
||||
|
||||
The libplctag .NET wrapper (1.5.x) does not expose the `0x0A` service as a
|
||||
public knob — same wrapper-version constraint that gates PR abcip-3.1's
|
||||
`connection_size` and PR abcip-3.2's instance-ID addressing. Today's
|
||||
MultiPacket runtime therefore issues N libplctag reads sequentially when
|
||||
the planner picks the strategy; the wire-level bundling lands cleanly when
|
||||
an upstream wrapper release exposes the primitive.
|
||||
|
||||
The driver-level bookkeeping (resolved strategy, per-batch heuristic,
|
||||
family-compat fall-back, per-device dispatch counters) is fully wired so
|
||||
the upgrade path is a wrapper-version bump only — the planner already
|
||||
produces the right plan, and `AbCipMultiPacketReadPlanner.Build` is
|
||||
covered by unit tests that pin the plan shape rather than wire bytes.
|
||||
|
||||
### Driver config JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"Devices": [
|
||||
{
|
||||
"HostAddress": "ab://10.0.0.5/1,0",
|
||||
"PlcFamily": "ControlLogix",
|
||||
"ReadStrategy": "Auto",
|
||||
"MultiPacketSparsityThreshold": 0.25
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`"Auto"`, `"WholeUdt"`, and `"MultiPacket"` parse case-insensitively.
|
||||
Omitting the field defaults to `"Auto"`. Omitting
|
||||
`MultiPacketSparsityThreshold` defaults to `0.25`.
|
||||
|
||||
### See also
|
||||
|
||||
- [`AbCipDriverOptions.ReadStrategy`](../../src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverOptions.cs) —
|
||||
enum definition + per-value docstrings.
|
||||
- [`AbCipMultiPacketReadPlanner`](../../src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipMultiPacketReadPlanner.cs) —
|
||||
plan shape + Auto-mode heuristic.
|
||||
- [`AbCipPlcFamilyProfile.SupportsRequestPacking`](../../src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/PlcFamilies/AbCipPlcFamilyProfile.cs) —
|
||||
family compatibility table source-of-truth.
|
||||
- [`AbCipReadStrategyTests`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/AbCipReadStrategyTests.cs) —
|
||||
device-init resolution, heuristic edges, dispatch counters, DTO round-trip.
|
||||
- [`AbCipEmulateMultiPacketReadTests`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Emulate/AbCipEmulateMultiPacketReadTests.cs) —
|
||||
golden-box-tier wire-level coverage scaffold; gated on `AB_SERVER_PROFILE=emulate`.
|
||||
|
||||
Reference in New Issue
Block a user