v3(focas): adopt Modbus RawTagEntry exemplar (multi-device)

- Rename FocasEquipmentTagParser -> FocasTagDefinitionFactory; TryParse ->
  FromTagConfig(tagConfig, rawPath, out def). Drop leading-{ heuristic + the
  deviceHostAddress blob key; def.Name = rawPath. Add inverse ToTagConfig.
  writable: absent -> read-only, explicit true honoured (note preserved).
- FocasDriverOptions.Tags -> RawTags (IReadOnlyList<RawTagEntry>).
- FocasDriver: _tagsByRawPath (Ordinal); byName-only resolver; build table from
  RawTags via FromTagConfig; multi-device routing threads RawTagEntry.DeviceName
  -> ResolveDeviceHost match against options.Devices -> def.DeviceHostAddress
  (TODO v3 WaveC: live host from Device row DeviceConfig). Tolerant per-tag
  skip+log (mapper/address/matrix miss -> BadNodeIdUnknown); unknown device
  fails init fast.
- Factory: bind List<RawTagEntry> RawTags; retire FocasTagDto/ParseDataType.
- CLI FocasCommandBase.BuildOptions -> RawTags via ToTagConfig + DeviceName.
- Tests: FocasRawTags helper; migrate Tags= -> RawTags=; rename parser tests to
  FromTagConfig; rewrite retired blob-ref tests (equipment-tag/capability-gate/
  resolve-host) onto authored RawPath. 272 driver + 52 CLI green.
This commit is contained in:
Joseph Doherty
2026-07-15 20:19:55 -04:00
parent c379e246d0
commit 8b2c3fa04c
23 changed files with 467 additions and 350 deletions
@@ -74,9 +74,12 @@ public sealed class FocasCommandBaseBuildOptionsTests
options.Devices[0].Series.ShouldBe(FocasCncSeries.Zero_i_F);
}
/// <summary>Verifies that BuildOptions forwards tag list verbatim.</summary>
/// <summary>
/// Verifies that BuildOptions turns each typed def into a v3 <c>RawTagEntry</c> — RawPath = def.Name,
/// DeviceName = def.DeviceHostAddress, and a TagConfig blob that round-trips through the mapper.
/// </summary>
[Fact]
public void BuildOptions_forwards_tag_list_verbatim()
public void BuildOptions_maps_tags_to_rawtag_entries()
{
var sut = new ProbeOnly { CncHost = "h" };
var tag = new FocasTagDefinition(
@@ -88,7 +91,14 @@ public sealed class FocasCommandBaseBuildOptionsTests
var options = sut.Invoke([tag]);
options.Tags.Count.ShouldBe(1);
options.Tags[0].ShouldBeSameAs(tag);
options.RawTags.Count.ShouldBe(1);
var entry = options.RawTags[0];
entry.RawPath.ShouldBe("t");
entry.DeviceName.ShouldBe("focas://h:8193");
entry.WriteIdempotent.ShouldBeFalse();
FocasTagDefinitionFactory.FromTagConfig(entry.TagConfig, entry.RawPath, out var round).ShouldBeTrue();
round.Address.ShouldBe("R100");
round.DataType.ShouldBe(FocasDataType.Int16);
round.Writable.ShouldBeFalse();
}
}