v3(b1-twincat): apply Modbus exemplar pattern to TwinCAT (multi-device)
- Rename TwinCATEquipmentTagParser -> TwinCATTagDefinitionFactory; TryParse ->
FromTagConfig(tagConfig, rawPath, out def). Drop leading-'{' heuristic + the
deviceHostAddress key; def.Name = rawPath. Keep strict-enum + Inspect; add a
Structure guard (sole tag path now) + inverse ToTagConfig.
- Options: Tags -> RawTags (RawTagEntry); keep Devices + protocol fields.
- Driver: _tagsByRawPath (Ordinal); byName-only resolver; build table from
_options.RawTags via FromTagConfig, threading WriteIdempotent + DeviceName.
Multi-device: ResolveDeviceHost matches entry.DeviceName against options.Devices
(by name, then host) -> def.DeviceHostAddress (TODO(v3 WaveC): live host from the
Device row's DeviceConfig). DiscoverAsync now emits from the table.
- Factory: retire pre-declared tags[] DTO path; bind rawTags; keep Devices.
- Cli: BuildOptions serialises typed defs -> RawTagEntry via ToTagConfig.
- Tests: TwinCATRawTags helper; migrate Tags= -> RawTags=; rename parser tests to
FromTagConfig; equipment/resolve-host/array tests delivered via RawTags.
Gate: Contracts + Driver build green; Driver.TwinCAT.Tests 192 pass, Cli.Tests 70 pass.
This commit is contained in:
+8
-23
@@ -182,30 +182,15 @@ public sealed class TwinCATLowFindingsRegressionTests
|
||||
|
||||
// ---- Driver.TwinCAT-016 — gap-fill tests for previously closed findings ----
|
||||
|
||||
/// <summary>Verifies that structure-typed pre-declared tags are rejected at configuration parse time.</summary>
|
||||
/// <summary>Verifies that a Structure-typed raw tag is rejected by the tag-definition factory.</summary>
|
||||
[Fact]
|
||||
public void Structure_typed_pre_declared_tag_is_rejected_at_config_parse()
|
||||
public void Structure_typed_raw_tag_is_rejected_by_the_factory()
|
||||
{
|
||||
// Driver.TwinCAT-003 — config-time rejection. A Structure tag must fail loudly with a
|
||||
// clear error rather than reading as a garbage int blob or failing late on a write.
|
||||
var json = JsonSerializer.Serialize(new
|
||||
{
|
||||
devices = new[] { new { hostAddress = DeviceA } },
|
||||
tags = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
name = "Udt1",
|
||||
deviceHostAddress = DeviceA,
|
||||
symbolPath = "MAIN.fbInstance",
|
||||
dataType = "Structure",
|
||||
},
|
||||
},
|
||||
});
|
||||
var ex = Should.Throw<InvalidOperationException>(() =>
|
||||
TwinCATDriverFactoryExtensions.ParseOptionsForTests(json, "drv-1"));
|
||||
ex.Message.ShouldContain("Structure");
|
||||
ex.Message.ShouldContain("Udt1");
|
||||
// Driver.TwinCAT-003 — v3 the mapper is the sole tag path. A Structure tag must NOT map to a
|
||||
// definition (returns false ⇒ the driver skips it ⇒ BadNodeIdUnknown) rather than reading as a
|
||||
// garbage int blob or failing late on a write.
|
||||
var blob = """{"symbolPath":"MAIN.fbInstance","dataType":"Structure"}""";
|
||||
TwinCATTagDefinitionFactory.FromTagConfig(blob, "equip/Udt1", out _).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the probe loop and read operations share one client per device.</summary>
|
||||
@@ -223,7 +208,7 @@ public sealed class TwinCATLowFindingsRegressionTests
|
||||
var drv = new TwinCATDriver(new TwinCATDriverOptions
|
||||
{
|
||||
Devices = [new TwinCATDeviceOptions(DeviceA)],
|
||||
Tags = [new TwinCATTagDefinition("X", DeviceA, "GVL.X", TwinCATDataType.DInt)],
|
||||
RawTags = TwinCATRawTags.Entries([new TwinCATTagDefinition("X", DeviceA, "GVL.X", TwinCATDataType.DInt)]),
|
||||
Probe = new TwinCATProbeOptions
|
||||
{
|
||||
Enabled = true,
|
||||
|
||||
Reference in New Issue
Block a user