v3(s7): apply Modbus RawTags exemplar to the S7 driver

Rename S7EquipmentTagParser -> S7TagDefinitionFactory; TryParse(reference)
-> FromTagConfig(tagConfig, rawPath, out def) (drops the leading-{ heuristic,
keys the def by RawPath, adds inverse ToTagConfig). Replace S7DriverOptions.Tags
(pre-declared S7TagDefinition list) with RawTags (IReadOnlyList<RawTagEntry>);
the driver builds its RawPath->def table from RawTags at Initialize via the
factory (skip+log on miss), resolver is byName-only, DiscoverAsync + init guards
+ address pre-parse now run off that table. Factory retires the pre-declared DTO
tag path (RawTags binding only). Cli BuildOptions serialises typed defs to
RawTagEntry. Tests migrated to a S7RawTags helper + FromTagConfig; parser test
files renamed. Coordinator's EquipmentTagConfigInspector S7 rename left to Wave-B
coordinator; Driver.S7.IntegrationTests (Docker-gated) left red per scope.

Driver.S7 + Cli build green; Driver.S7.Tests 264/264, S7.Cli.Tests 49/49.
This commit is contained in:
Joseph Doherty
2026-07-15 20:11:04 -04:00
parent c379e246d0
commit 1e26b9ab58
21 changed files with 425 additions and 251 deletions
@@ -1,5 +1,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
namespace ZB.MOM.WW.OtOpcUa.Driver.S7;
@@ -64,9 +65,10 @@ public static class S7DriverFactoryExtensions
Rack = dto.Rack ?? 0,
Slot = dto.Slot ?? 0,
Timeout = TimeSpan.FromMilliseconds(dto.TimeoutMs ?? 5_000),
Tags = dto.Tags is { Count: > 0 }
? [.. dto.Tags.Select(t => BuildTag(t, driverInstanceId))]
: [],
// v3: the deploy artifact (Wave C) populates rawTags with the cluster's authored raw S7
// tags. The driver maps each RawTagEntry's TagConfig blob through S7TagDefinitionFactory
// at Initialize; the legacy pre-declared DTO tag path (Name/Address → BuildTag) is retired.
RawTags = dto.RawTags is { Count: > 0 } ? [.. dto.RawTags] : [],
Probe = new S7ProbeOptions
{
Enabled = dto.Probe?.Enabled ?? true,
@@ -77,18 +79,6 @@ public static class S7DriverFactoryExtensions
};
}
private static S7TagDefinition BuildTag(S7TagDto t, string driverInstanceId) =>
new(
Name: t.Name ?? throw new InvalidOperationException(
$"S7 config for '{driverInstanceId}' has a tag missing Name"),
Address: t.Address ?? throw new InvalidOperationException(
$"S7 tag '{t.Name}' in '{driverInstanceId}' missing Address"),
DataType: ParseEnum<S7DataType>(t.DataType, driverInstanceId, "DataType",
tagName: t.Name),
Writable: t.Writable ?? true,
StringLength: t.StringLength ?? 254,
WriteIdempotent: t.WriteIdempotent ?? false);
private static T ParseEnum<T>(string? raw, string driverInstanceId, string field,
string? tagName = null, T? fallback = null) where T : struct, Enum
{
@@ -127,29 +117,13 @@ public static class S7DriverFactoryExtensions
public short? Slot { get; init; }
/// <summary>Gets the connection timeout in milliseconds.</summary>
public int? TimeoutMs { get; init; }
/// <summary>Gets the list of tag definitions.</summary>
public List<S7TagDto>? Tags { get; init; }
/// <summary>Gets the authored raw tags (RawPath + TagConfig blob + WriteIdempotent) the deploy
/// artifact delivers. The driver maps each through <see cref="S7TagDefinitionFactory"/> at Initialize.</summary>
public List<RawTagEntry>? RawTags { get; init; }
/// <summary>Gets the probe configuration.</summary>
public S7ProbeDto? Probe { get; init; }
}
/// <summary>Data transfer object for S7 tag definition.</summary>
internal sealed class S7TagDto
{
/// <summary>Gets the tag name.</summary>
public string? Name { get; init; }
/// <summary>Gets the S7 address (e.g., DB1.DBD0).</summary>
public string? Address { get; init; }
/// <summary>Gets the data type name.</summary>
public string? DataType { get; init; }
/// <summary>Gets a value indicating whether the tag is writable.</summary>
public bool? Writable { get; init; }
/// <summary>Gets the string length for string types.</summary>
public int? StringLength { get; init; }
/// <summary>Gets a value indicating whether write is idempotent.</summary>
public bool? WriteIdempotent { get; init; }
}
/// <summary>Data transfer object for S7 probe configuration.</summary>
internal sealed class S7ProbeDto
{