feat(abcip): resolve equipment-tag refs (read + write) via EquipmentTagRefResolver
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
|
||||
/// <summary>Parses an equipment tag's <c>TagConfig</c> JSON (the shape authored by the AdminUI
|
||||
/// <c>AbCipTagConfigModel</c>) into a transient <see cref="AbCipTagDefinition"/> whose
|
||||
/// <see cref="AbCipTagDefinition.Name"/> equals the reference string itself, so a value the
|
||||
/// driver publishes back keys the runtime's forward router correctly.</summary>
|
||||
public static class AbCipEquipmentTagParser
|
||||
{
|
||||
/// <summary>Attempts to parse an equipment-tag reference into a transient definition.</summary>
|
||||
/// <param name="reference">The equipment tag's TagConfig JSON (also used as the def identity).</param>
|
||||
/// <param name="def">The transient definition when parsing succeeds.</param>
|
||||
/// <returns><see langword="true"/> when <paramref name="reference"/> is an AbCip TagConfig object.</returns>
|
||||
public static bool TryParse(string reference, out AbCipTagDefinition def)
|
||||
{
|
||||
def = null!;
|
||||
// Authored tag names never start with '{' (AdminUI name validation), so a leading brace marks an equipment-tag TagConfig blob.
|
||||
if (string.IsNullOrWhiteSpace(reference) || reference[0] != '{') return false;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(reference);
|
||||
var root = doc.RootElement;
|
||||
// AbCip is a symbolic driver: the mandatory addressing field is the Logix tag path.
|
||||
if (root.ValueKind != JsonValueKind.Object
|
||||
|| !root.TryGetProperty("tagPath", out var tagPathEl)
|
||||
|| tagPathEl.ValueKind != JsonValueKind.String)
|
||||
return false;
|
||||
var tagPath = tagPathEl.GetString();
|
||||
if (string.IsNullOrWhiteSpace(tagPath)) return false;
|
||||
|
||||
var deviceHostAddress = ReadString(root, "deviceHostAddress");
|
||||
var dataType = ReadEnum(root, "dataType", AbCipDataType.DInt);
|
||||
def = new AbCipTagDefinition(
|
||||
Name: reference, DeviceHostAddress: deviceHostAddress, TagPath: tagPath,
|
||||
DataType: dataType, Writable: true);
|
||||
return true;
|
||||
}
|
||||
catch (JsonException) { return false; }
|
||||
catch (FormatException) { return false; }
|
||||
catch (InvalidOperationException) { return false; }
|
||||
}
|
||||
|
||||
private static TEnum ReadEnum<TEnum>(JsonElement o, string name, TEnum fallback) where TEnum : struct, Enum
|
||||
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
|
||||
&& Enum.TryParse<TEnum>(e.GetString(), ignoreCase: true, out var v) ? v : fallback;
|
||||
|
||||
private static string ReadString(JsonElement o, string name)
|
||||
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
|
||||
? e.GetString() ?? "" : "";
|
||||
}
|
||||
@@ -36,6 +36,12 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
private readonly PollGroupEngine _poll;
|
||||
private readonly Dictionary<string, DeviceState> _devices = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, AbCipTagDefinition> _tagsByName = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Resolves a read/write/subscribe fullReference to a tag definition, bridging the two
|
||||
// authoring models: an authored tag-table entry (by name) OR an equipment tag whose
|
||||
// reference is its raw TagConfig JSON (parsed once via AbCipEquipmentTagParser, cached).
|
||||
private readonly EquipmentTagRefResolver<AbCipTagDefinition> _resolver;
|
||||
|
||||
private readonly ILogger<AbCipDriver> _logger;
|
||||
private AbCipAlarmProjection _alarmProjection;
|
||||
private DriverHealth _health = new(DriverState.Unknown, null, null);
|
||||
@@ -73,6 +79,9 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
_enumeratorFactory = enumeratorFactory ?? new LibplctagTagEnumeratorFactory();
|
||||
_templateReaderFactory = templateReaderFactory ?? new LibplctagTemplateReaderFactory();
|
||||
_logger = logger ?? NullLogger<AbCipDriver>.Instance;
|
||||
_resolver = new EquipmentTagRefResolver<AbCipTagDefinition>(
|
||||
r => _tagsByName.TryGetValue(r, out var t) ? t : null,
|
||||
r => AbCipEquipmentTagParser.TryParse(r, out var d) ? d : null);
|
||||
_poll = new PollGroupEngine(
|
||||
reader: ReadAsync,
|
||||
onChange: (handle, tagRef, snapshot) =>
|
||||
@@ -300,6 +309,7 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
}
|
||||
_devices.Clear();
|
||||
_tagsByName.Clear();
|
||||
_resolver.Clear(); // drop transient equipment-tag parses so a config change re-parses
|
||||
_health = new DriverHealth(DriverState.Unknown, _health.LastSuccessfulRead, null);
|
||||
}
|
||||
|
||||
@@ -497,7 +507,7 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
private async Task ReadSingleAsync(
|
||||
AbCipUdtReadFallback fb, string reference, DataValueSnapshot[] results, DateTime now, CancellationToken ct)
|
||||
{
|
||||
if (!_tagsByName.TryGetValue(reference, out var def))
|
||||
if (!_resolver.TryResolve(reference, out var def))
|
||||
{
|
||||
results[fb.OriginalIndex] = new DataValueSnapshot(null, AbCipStatusMapper.BadNodeIdUnknown, null, now);
|
||||
return;
|
||||
@@ -653,7 +663,7 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
for (var i = 0; i < writes.Count; i++)
|
||||
{
|
||||
var w = writes[i];
|
||||
if (!_tagsByName.TryGetValue(w.FullReference, out var def))
|
||||
if (!_resolver.TryResolve(w.FullReference, out var def))
|
||||
{
|
||||
results[i] = new WriteResult(AbCipStatusMapper.BadNodeIdUnknown);
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user