v3(ablegacy): apply Modbus exemplar — RawTags delivery + RawPath mapper
- Rename AbLegacyEquipmentTagParser → AbLegacyTagDefinitionFactory; TryParse →
FromTagConfig(tagConfig, rawPath, out def). Drop the leading-{ heuristic and the
deviceHostAddress key; def.Name = rawPath. Add inverse ToTagConfig. Keep strict-enum
guards + Inspect.
- Options: Tags(IReadOnlyList<AbLegacyTagDefinition>) → RawTags(IReadOnlyList<RawTagEntry>).
- Driver: _tagsByRawPath (Ordinal); byName-only resolver; build table from _options.RawTags
via FromTagConfig, threading WriteIdempotent + resolving RawTagEntry.DeviceName to the
owning device host (ResolveDeviceHost; TODO(v3 WaveC) for the live Device-row host).
DiscoverAsync + family-profile validation iterate the built table. Probe picks its address
from RawTags via the mapper.
- Factory: retire the pre-declared tag DTO path; bind List<RawTagEntry>? RawTags; keep Devices.
- Cli BuildOptions: deliver typed defs as RawTagEntry via ToTagConfig.
- Tests: AbLegacyRawTags helper; migrate Tags= → RawTags; parser tests → FromTagConfig;
equipment-ref driver/ResolveHost tests re-authored as RawTagEntry + read by RawPath.
Driver+Contracts+Cli green; 213 driver + 36 Cli tests pass.
This commit is contained in:
@@ -22,7 +22,7 @@ public sealed class AbLegacyArrayTests
|
||||
var drv = new AbLegacyDriver(new AbLegacyDriverOptions
|
||||
{
|
||||
Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = tags,
|
||||
RawTags = AbLegacyRawTags.Entries(tags),
|
||||
Probe = new AbLegacyProbeOptions { Enabled = false },
|
||||
}, "ablegacy-array", factory);
|
||||
return (drv, factory);
|
||||
@@ -169,7 +169,7 @@ public sealed class AbLegacyArrayTests
|
||||
var drv = new AbLegacyDriver(new AbLegacyDriverOptions
|
||||
{
|
||||
Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = [new AbLegacyTagDefinition("Vector", "ab://10.0.0.5/1,0", "N7:0", AbLegacyDataType.Int, ArrayLength: 8)],
|
||||
RawTags = AbLegacyRawTags.Entries([new AbLegacyTagDefinition("Vector", "ab://10.0.0.5/1,0", "N7:0", AbLegacyDataType.Int, ArrayLength: 8)]),
|
||||
Probe = new AbLegacyProbeOptions { Enabled = false },
|
||||
}, "ablegacy-array", new FakeAbLegacyTagFactory());
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
@@ -231,30 +231,30 @@ public sealed class AbLegacyArrayTests
|
||||
|
||||
// ---- Equipment-tag resolver threads arrayLength ----
|
||||
|
||||
/// <summary>The equipment-tag parser threads <c>arrayLength</c> into the transient definition.</summary>
|
||||
/// <summary>The tag-definition factory threads <c>arrayLength</c> into the definition.</summary>
|
||||
[Fact]
|
||||
public void EquipmentTagParser_threads_arrayLength()
|
||||
public void TagDefinitionFactory_threads_arrayLength()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int","isArray":true,"arrayLength":10}""";
|
||||
AbLegacyEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
var json = """{"address":"N7:0","dataType":"Int","isArray":true,"arrayLength":10}""";
|
||||
AbLegacyTagDefinitionFactory.FromTagConfig(json, "eq/a", out var def).ShouldBeTrue();
|
||||
def!.ArrayLength.ShouldBe(10);
|
||||
}
|
||||
|
||||
/// <summary>The parser caps <c>arrayLength</c> at the PCCC 256-word file maximum (isArray:true).</summary>
|
||||
/// <summary>The factory caps <c>arrayLength</c> at the PCCC 256-word file maximum (isArray:true).</summary>
|
||||
[Fact]
|
||||
public void EquipmentTagParser_caps_arrayLength_at_256()
|
||||
public void TagDefinitionFactory_caps_arrayLength_at_256()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int","isArray":true,"arrayLength":99999}""";
|
||||
AbLegacyEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
var json = """{"address":"N7:0","dataType":"Int","isArray":true,"arrayLength":99999}""";
|
||||
AbLegacyTagDefinitionFactory.FromTagConfig(json, "eq/a", out var def).ShouldBeTrue();
|
||||
def!.ArrayLength.ShouldBe(256);
|
||||
}
|
||||
|
||||
/// <summary>A scalar equipment tag (no arrayLength) leaves ArrayLength null.</summary>
|
||||
/// <summary>A scalar tag (no arrayLength) leaves ArrayLength null.</summary>
|
||||
[Fact]
|
||||
public void EquipmentTagParser_scalar_leaves_arrayLength_null()
|
||||
public void TagDefinitionFactory_scalar_leaves_arrayLength_null()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int"}""";
|
||||
AbLegacyEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
var json = """{"address":"N7:0","dataType":"Int"}""";
|
||||
AbLegacyTagDefinitionFactory.FromTagConfig(json, "eq/a", out var def).ShouldBeTrue();
|
||||
def!.ArrayLength.ShouldBeNull();
|
||||
}
|
||||
|
||||
@@ -264,19 +264,19 @@ public sealed class AbLegacyArrayTests
|
||||
/// cleared / absent isArray must leave ArrayLength null and never produce an orphan array.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void EquipmentTagParser_arrayLength_without_isArray_is_scalar()
|
||||
public void TagDefinitionFactory_arrayLength_without_isArray_is_scalar()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int","arrayLength":8}""";
|
||||
AbLegacyEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
var json = """{"address":"N7:0","dataType":"Int","arrayLength":8}""";
|
||||
AbLegacyTagDefinitionFactory.FromTagConfig(json, "eq/a", out var def).ShouldBeTrue();
|
||||
def!.ArrayLength.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>C-2 regression: <c>isArray:false</c> with a positive arrayLength is a SCALAR.</summary>
|
||||
[Fact]
|
||||
public void EquipmentTagParser_isArray_false_with_arrayLength_is_scalar()
|
||||
public void TagDefinitionFactory_isArray_false_with_arrayLength_is_scalar()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int","isArray":false,"arrayLength":8}""";
|
||||
AbLegacyEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
var json = """{"address":"N7:0","dataType":"Int","isArray":false,"arrayLength":8}""";
|
||||
AbLegacyTagDefinitionFactory.FromTagConfig(json, "eq/a", out var def).ShouldBeTrue();
|
||||
def!.ArrayLength.ShouldBeNull();
|
||||
}
|
||||
|
||||
@@ -286,32 +286,33 @@ public sealed class AbLegacyArrayTests
|
||||
/// as a 1-element array.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void EquipmentTagParser_isArray_true_arrayLength_one_is_one_element_array()
|
||||
public void TagDefinitionFactory_isArray_true_arrayLength_one_is_one_element_array()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int","isArray":true,"arrayLength":1}""";
|
||||
AbLegacyEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
var json = """{"address":"N7:0","dataType":"Int","isArray":true,"arrayLength":1}""";
|
||||
AbLegacyTagDefinitionFactory.FromTagConfig(json, "eq/a", out var def).ShouldBeTrue();
|
||||
def!.ArrayLength.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end: an AbLegacy driver with NO authored tags reads an equipment-tag ref whose
|
||||
/// TagConfig carries <c>isArray:true</c> + <c>arrayLength</c> — the resolver threads the
|
||||
/// count and the read surfaces a typed array, capping the libplctag element count at 256.
|
||||
/// End-to-end: an AbLegacy driver reads a raw tag authored as a <see cref="RawTagEntry"/> whose
|
||||
/// TagConfig carries <c>isArray:true</c> + <c>arrayLength</c> — the mapper threads the count and
|
||||
/// the read surfaces a typed array, capping the libplctag element count at 256.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Driver_reads_equipment_array_ref_as_typed_array()
|
||||
public async Task Driver_reads_array_rawtag_as_typed_array()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int","isArray":true,"arrayLength":3}""";
|
||||
const string rawPath = "cell/ablegacy/dev1/Vec";
|
||||
var tagConfig = """{"address":"N7:0","dataType":"Int","isArray":true,"arrayLength":3}""";
|
||||
var factory = new FakeAbLegacyTagFactory { Customise = p => new FakeAbLegacyTag(p) { ArrayValue = new short[] { 11, 22, 33 } } };
|
||||
var drv = new AbLegacyDriver(new AbLegacyDriverOptions
|
||||
{
|
||||
Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = [],
|
||||
RawTags = [new RawTagEntry(rawPath, tagConfig, WriteIdempotent: false, DeviceName: "ab://10.0.0.5/1,0")],
|
||||
Probe = new AbLegacyProbeOptions { Enabled = false },
|
||||
}, "ablegacy-eq-array", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var r = await drv.ReadAsync([json], CancellationToken.None);
|
||||
var r = await drv.ReadAsync([rawPath], CancellationToken.None);
|
||||
|
||||
r[0].StatusCode.ShouldBe(AbLegacyStatusMapper.Good);
|
||||
var arr = r[0].Value.ShouldBeOfType<short[]>();
|
||||
@@ -320,24 +321,25 @@ public sealed class AbLegacyArrayTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C-2 end-to-end: an equipment-tag ref carrying <c>isArray:false, arrayLength:8</c> reads
|
||||
/// a single SCALAR value (the parser drops the orphan length), so the read never decodes a
|
||||
/// C-2 end-to-end: a raw tag whose TagConfig carries <c>isArray:false, arrayLength:8</c> reads
|
||||
/// a single SCALAR value (the mapper drops the orphan length), so the read never decodes a
|
||||
/// phantom 8-element array against a scalar node. ElementCount stays 1.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Driver_reads_isArray_false_with_arrayLength_as_scalar()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","address":"N7:0","dataType":"Int","isArray":false,"arrayLength":8}""";
|
||||
const string rawPath = "cell/ablegacy/dev1/Scl";
|
||||
var tagConfig = """{"address":"N7:0","dataType":"Int","isArray":false,"arrayLength":8}""";
|
||||
var factory = new FakeAbLegacyTagFactory { Customise = p => new FakeAbLegacyTag(p) { Value = 99 } };
|
||||
var drv = new AbLegacyDriver(new AbLegacyDriverOptions
|
||||
{
|
||||
Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = [],
|
||||
RawTags = [new RawTagEntry(rawPath, tagConfig, WriteIdempotent: false, DeviceName: "ab://10.0.0.5/1,0")],
|
||||
Probe = new AbLegacyProbeOptions { Enabled = false },
|
||||
}, "ablegacy-eq-scalar", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var r = await drv.ReadAsync([json], CancellationToken.None);
|
||||
var r = await drv.ReadAsync([rawPath], CancellationToken.None);
|
||||
|
||||
r[0].StatusCode.ShouldBe(AbLegacyStatusMapper.Good);
|
||||
r[0].Value.ShouldBe(99);
|
||||
|
||||
Reference in New Issue
Block a user