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
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
@@ -10,8 +11,16 @@ public sealed class FocasDriverOptions
{
/// <summary>Gets the list of configured CNC devices.</summary>
public IReadOnlyList<FocasDeviceOptions> Devices { get; init; } = [];
/// <summary>Gets the list of FOCAS tag definitions.</summary>
public IReadOnlyList<FocasTagDefinition> Tags { get; init; } = [];
/// <summary>
/// Authored raw tags this driver serves. The deploy artifact hands each authored raw
/// <c>Tag</c> as a <see cref="RawTagEntry"/> (RawPath identity + driver <c>TagConfig</c> blob +
/// WriteIdempotent flag + <see cref="RawTagEntry.DeviceName"/>); the driver maps each through
/// <see cref="FocasTagDefinitionFactory.FromTagConfig"/> into its RawPath → definition table,
/// routing each tag to its device by <see cref="RawTagEntry.DeviceName"/>. FOCAS has no online
/// tag-discovery for user tags — the driver serves exactly these (plus the fixed-node tree).
/// </summary>
public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];
/// <summary>Gets the probe options.</summary>
public FocasProbeOptions Probe { get; init; } = new();
/// <summary>Gets the timeout duration for operations.</summary>