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:
@@ -1,14 +1,15 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// CONV-4 — <see cref="AbCipDriver.ResolveHost"/> keys per-host resilience (bulkhead / circuit
|
||||
/// breaker). It must resolve an equipment-tag reference (raw TagConfig JSON) to its OWN device
|
||||
/// host, not fall back to the first device — otherwise a broken device B trips device A's
|
||||
/// breaker. Authored tags and unknown refs keep the current fallback.
|
||||
/// breaker). It must resolve an authored tag (by its RawPath) to its OWN device host, not fall back
|
||||
/// to the first device — otherwise a broken device B trips device A's breaker. Unknown refs keep the
|
||||
/// current first-device fallback.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class AbCipResolveHostTests
|
||||
@@ -16,21 +17,27 @@ public sealed class AbCipResolveHostTests
|
||||
private const string DeviceA = "ab://10.0.0.5/1,0";
|
||||
private const string DeviceB = "ab://10.0.0.6/1,0";
|
||||
|
||||
private static AbCipDriver NewDriver() => new(
|
||||
private static AbCipDriver NewDriver(params RawTagEntry[] rawTags) => new(
|
||||
new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions(DeviceA), new AbCipDeviceOptions(DeviceB)],
|
||||
RawTags = rawTags,
|
||||
Probe = new AbCipProbeOptions { Enabled = false },
|
||||
},
|
||||
"abcip-1", new FakeAbCipTagFactory());
|
||||
|
||||
/// <summary>An equipment-tag ref naming device B resolves to device B's host, not the first device.</summary>
|
||||
/// <summary>An authored tag routed to device B resolves to device B's host, not the first device.</summary>
|
||||
[Fact]
|
||||
public void ResolveHost_EquipmentTagRef_ReturnsItsOwnDeviceHost()
|
||||
public async Task ResolveHost_TagRoutedToDeviceB_ReturnsItsOwnDeviceHost()
|
||||
{
|
||||
var drv = NewDriver();
|
||||
var json = $$"""{"deviceHostAddress":"{{DeviceB}}","tagPath":"Motor1.Speed","dataType":"DInt"}""";
|
||||
drv.ResolveHost(json).ShouldBe(DeviceB);
|
||||
var drv = NewDriver(new RawTagEntry(
|
||||
RawPath: "line1/motorB/speed",
|
||||
TagConfig: """{"tagPath":"Motor1.Speed","dataType":"DInt"}""",
|
||||
WriteIdempotent: false,
|
||||
DeviceName: DeviceB));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
drv.ResolveHost("line1/motorB/speed").ShouldBe(DeviceB);
|
||||
}
|
||||
|
||||
/// <summary>An unresolvable ref keeps the current first-device fallback.</summary>
|
||||
|
||||
Reference in New Issue
Block a user