v3(b1-abcip): RawPath read-path for AbCip (multi-device)
Apply the reviewed Modbus exemplar to the AbCip multi-device driver:
- Mapper AbCipEquipmentTagParser -> AbCipTagDefinitionFactory; TryParse -> FromTagConfig(tagConfig, rawPath, out def); drop leading-{ heuristic + the deviceHostAddress key; add inverse ToTagConfig; round-trip arrays/members/safety for coverage.
- Options: Tags (typed) -> RawTags (RawTagEntry list); Devices list kept.
- Driver: byName-only resolver over _tagsByRawPath (Ordinal); table built from RawTags via FromTagConfig, threading entry.DeviceName (device routing key) + entry.WriteIdempotent onto the def. Multi-device routing keys on DeviceName-or-host via a _devicesByRoutingKey index; discovery groups _declaredTags by RoutesToDevice.
- Factory: retire the pre-declared Tags DTO/BuildTag; bind List<RawTagEntry> RawTags.
- Probe: derive probe tag path from RawTags via the mapper.
- Cli BuildOptions projects typed tags -> RawTagEntry (ToTagConfig).
- Tests: AbCipRawTags helper (typed def -> RawTagEntry); parser tests -> FromTagConfig; blob-fallback driver tests rewritten to author via RawTags + read by RawPath. Driver.AbCip.Tests 342/342, Cli.Tests 42/42.
TODO(v3 WaveC): DeviceName becomes a pure logical name once the device connection host moves to the Device row's DeviceConfig.
This commit is contained in:
@@ -19,11 +19,9 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
var drv = new AbCipDriver(new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0", DeviceName: "Line1-PLC")],
|
||||
Tags =
|
||||
[
|
||||
RawTags = AbCipRawTags.From(
|
||||
new AbCipTagDefinition("Speed", "ab://10.0.0.5/1,0", "Motor1.Speed", AbCipDataType.DInt),
|
||||
new AbCipTagDefinition("Temperature", "ab://10.0.0.5/1,0", "T", AbCipDataType.Real, Writable: false),
|
||||
],
|
||||
new AbCipTagDefinition("Temperature", "ab://10.0.0.5/1,0", "T", AbCipDataType.Real, Writable: false)),
|
||||
}, "drv-1");
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
@@ -61,12 +59,10 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
var drv = new AbCipDriver(new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags =
|
||||
[
|
||||
RawTags = AbCipRawTags.From(
|
||||
new AbCipTagDefinition("__DEFVAL_X", "ab://10.0.0.5/1,0", "__DEFVAL_X", AbCipDataType.DInt),
|
||||
new AbCipTagDefinition("Routine:SomeRoutine", "ab://10.0.0.5/1,0", "R", AbCipDataType.DInt),
|
||||
new AbCipTagDefinition("UserTag", "ab://10.0.0.5/1,0", "U", AbCipDataType.DInt),
|
||||
],
|
||||
new AbCipTagDefinition("UserTag", "ab://10.0.0.5/1,0", "U", AbCipDataType.DInt)),
|
||||
}, "drv-1");
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
@@ -83,7 +79,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
var drv = new AbCipDriver(new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = [new AbCipTagDefinition("Orphan", "ab://10.0.0.99/1,0", "O", AbCipDataType.DInt)],
|
||||
RawTags = AbCipRawTags.From(new AbCipTagDefinition("Orphan", "ab://10.0.0.99/1,0", "O", AbCipDataType.DInt)),
|
||||
}, "drv-1");
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
@@ -418,21 +414,19 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A discovered member-path read goes end-to-end through the driver: with NO pre-declared
|
||||
/// tag for the member, <see cref="AbCipDriver.ReadAsync"/> resolves the authored TagConfig
|
||||
/// JSON blob (FullName = <c>Parent.Member</c>) via the equipment-tag resolver, materialises a
|
||||
/// runtime on the device, reads it, and decodes the member's atomic value — proving discovered
|
||||
/// members are readable with no extra registration. The libplctag tag name the driver builds
|
||||
/// for the member equals the dotted member path.
|
||||
/// A member-path read goes end-to-end through the driver: an authored raw tag whose TagConfig
|
||||
/// addresses a dotted member path (<c>Motor1.Status.Code</c>) is delivered as a
|
||||
/// <see cref="RawTagEntry"/>, resolves by its RawPath, materialises a runtime on the device, and
|
||||
/// decodes the member's atomic value. The libplctag tag name the driver builds equals the dotted
|
||||
/// member path (the TagConfig's tagPath), independent of the RawPath identity.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Discovered_member_path_reads_through_driver_as_member_atomic_type()
|
||||
public async Task Member_path_tag_reads_through_driver_as_member_atomic_type()
|
||||
{
|
||||
const string device = "ab://10.0.0.5/1,0";
|
||||
// The wire reference handed to ReadAsync for a discovered member is the authored TagConfig
|
||||
// JSON blob (FullName = Motor1.Status.Code, atomic type Real here). No tag is pre-declared.
|
||||
const string tagConfig =
|
||||
"{\"tagPath\":\"Motor1.Status.Code\",\"dataType\":\"Real\",\"deviceHostAddress\":\"ab://10.0.0.5/1,0\"}";
|
||||
const string rawPath = "line1/motor1/status/code";
|
||||
// The authored TagConfig addresses the dotted member path (atomic Real here).
|
||||
const string tagConfig = "{\"tagPath\":\"Motor1.Status.Code\",\"dataType\":\"Real\"}";
|
||||
|
||||
var tagFactory = new FakeAbCipTagFactory
|
||||
{
|
||||
@@ -443,16 +437,17 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
var drv = new AbCipDriver(new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions(device)],
|
||||
RawTags = [new RawTagEntry(rawPath, tagConfig, WriteIdempotent: false, DeviceName: device)],
|
||||
}, "drv-1", tagFactory: tagFactory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var results = await drv.ReadAsync([tagConfig], CancellationToken.None);
|
||||
var results = await drv.ReadAsync([rawPath], CancellationToken.None);
|
||||
|
||||
results.Count.ShouldBe(1);
|
||||
results[0].StatusCode.ShouldBe(AbCipStatusMapper.Good);
|
||||
results[0].Value.ShouldBe(12.5f);
|
||||
// The driver built a runtime under the dotted member tag name — confirming the member-path
|
||||
// read addresses the member directly (no parent-UDT registration needed).
|
||||
// read addresses the member directly.
|
||||
tagFactory.Tags.ShouldContainKey("Motor1.Status.Code");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user