feat(alarms): EquipmentTagPlan.Alarm parsed byte-parity from TagConfig (Phase B WS-2)
This commit is contained in:
@@ -75,7 +75,9 @@ public sealed record ScriptedAlarmPlan(string ScriptedAlarmId, string EquipmentI
|
||||
/// mirrors the authored <c>Tag.AccessLevel == ReadWrite</c> so the materialised node is created
|
||||
/// <c>CurrentReadWrite</c> (the prerequisite for the inbound-write pipeline); a <c>Read</c> tag
|
||||
/// stays read-only. This flag is derived identically on the artifact-decode side
|
||||
/// (<c>DeploymentArtifact.BuildEquipmentTagPlans</c>) for byte-parity.
|
||||
/// (<c>DeploymentArtifact.BuildEquipmentTagPlans</c>) for byte-parity. <see cref="Alarm"/> carries
|
||||
/// the optional native-alarm intent parsed from <c>Tag.TagConfig</c>'s <c>alarm</c> object (null ⇒
|
||||
/// a plain value variable); it too is parsed identically on the artifact-decode side for byte-parity.
|
||||
/// </summary>
|
||||
public sealed record EquipmentTagPlan(
|
||||
string TagId,
|
||||
@@ -85,7 +87,13 @@ public sealed record EquipmentTagPlan(
|
||||
string Name,
|
||||
string DataType,
|
||||
string FullName,
|
||||
bool Writable);
|
||||
bool Writable,
|
||||
EquipmentTagAlarmInfo? Alarm);
|
||||
|
||||
/// <summary>Native-alarm intent parsed from an equipment tag's <c>TagConfig.alarm</c> object. Null ⇒
|
||||
/// the tag is a plain value variable. <see cref="AlarmType"/> is an OPC UA Part 9 subtype string
|
||||
/// (OffNormalAlarm/DiscreteAlarm/LimitAlarm/AlarmCondition); <see cref="Severity"/> is the 1..1000 scale.</summary>
|
||||
public sealed record EquipmentTagAlarmInfo(string AlarmType, int Severity);
|
||||
|
||||
/// <summary>
|
||||
/// One Equipment-namespace VirtualTag from a <see cref="VirtualTag"/> row (joined to its
|
||||
@@ -328,7 +336,8 @@ public static class Phase7Composer
|
||||
Name: t.Name,
|
||||
DataType: t.DataType,
|
||||
FullName: ExtractTagFullName(t.TagConfig),
|
||||
Writable: t.AccessLevel == TagAccessLevel.ReadWrite))
|
||||
Writable: t.AccessLevel == TagAccessLevel.ReadWrite,
|
||||
Alarm: ExtractTagAlarm(t.TagConfig)))
|
||||
.ToList();
|
||||
|
||||
// Per-equipment tag base = the shared substring-before-first-dot across each equipment's
|
||||
@@ -445,4 +454,24 @@ public static class Phase7Composer
|
||||
catch (JsonException) { /* fall through to raw blob */ }
|
||||
return tagConfig;
|
||||
}
|
||||
|
||||
/// <summary>Parses the optional <c>alarm</c> object from a tag's <c>TagConfig</c> JSON. Returns null
|
||||
/// when absent, non-object, or non-JSON (the tag is then a plain variable). Never throws. The
|
||||
/// artifact-decode side (<c>DeploymentArtifact.ExtractTagAlarm</c>) MUST parse identically (byte-parity).</summary>
|
||||
internal static EquipmentTagAlarmInfo? ExtractTagAlarm(string? tagConfig)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tagConfig)) return null;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(tagConfig);
|
||||
if (doc.RootElement.ValueKind != JsonValueKind.Object) return null;
|
||||
if (!doc.RootElement.TryGetProperty("alarm", out var a) || a.ValueKind != JsonValueKind.Object) return null;
|
||||
var type = a.TryGetProperty("alarmType", out var tEl) && tEl.ValueKind == JsonValueKind.String
|
||||
? (tEl.GetString() ?? "AlarmCondition") : "AlarmCondition";
|
||||
var sev = a.TryGetProperty("severity", out var sEl) && sEl.ValueKind == JsonValueKind.Number
|
||||
? sEl.GetInt32() : 500;
|
||||
return new EquipmentTagAlarmInfo(type, sev);
|
||||
}
|
||||
catch (JsonException) { return null; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,7 +445,8 @@ public static class DeploymentArtifact
|
||||
Name: name!,
|
||||
DataType: dataType ?? "BaseDataType",
|
||||
FullName: ExtractTagFullName(tagConfig),
|
||||
Writable: writable));
|
||||
Writable: writable,
|
||||
Alarm: ExtractTagAlarm(tagConfig)));
|
||||
}
|
||||
|
||||
result.Sort((a, b) =>
|
||||
@@ -651,6 +652,26 @@ public static class DeploymentArtifact
|
||||
return tagConfig;
|
||||
}
|
||||
|
||||
/// <summary>Parses the optional <c>alarm</c> object from a tag's <c>TagConfig</c> JSON. Returns null
|
||||
/// when absent, non-object, or non-JSON (the tag is then a plain variable). Never throws. The
|
||||
/// live-edit side (<c>Phase7Composer.ExtractTagAlarm</c>) MUST parse identically (byte-parity).</summary>
|
||||
private static EquipmentTagAlarmInfo? ExtractTagAlarm(string? tagConfig)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tagConfig)) return null;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(tagConfig);
|
||||
if (doc.RootElement.ValueKind != JsonValueKind.Object) return null;
|
||||
if (!doc.RootElement.TryGetProperty("alarm", out var a) || a.ValueKind != JsonValueKind.Object) return null;
|
||||
var type = a.TryGetProperty("alarmType", out var tEl) && tEl.ValueKind == JsonValueKind.String
|
||||
? (tEl.GetString() ?? "AlarmCondition") : "AlarmCondition";
|
||||
var sev = a.TryGetProperty("severity", out var sEl) && sEl.ValueKind == JsonValueKind.Number
|
||||
? sEl.GetInt32() : 500;
|
||||
return new EquipmentTagAlarmInfo(type, sev);
|
||||
}
|
||||
catch (JsonException) { return null; }
|
||||
}
|
||||
|
||||
private static IReadOnlyList<T> ReadArray<T>(JsonElement root, string propertyName, Func<JsonElement, T?> reader)
|
||||
where T : class
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user