2b0eed1a8f
- ALARM: selectors (HeidenhainAlarmSelector, Contracts): ALARM:*, ALARM:OTHER, ALARM:CLASS=<r>[/GROUP=<r>][/NUMBER=<r>] (<r> = n | lo..hi; negative numbers allowed). An ALARM: address and the cross-driver alarm object only work together — the factory refuses either alone, so editor, save path, CSV and deploy inspector agree. Data type fixed Boolean; never a value (ReadAsync BadNotSupported), never in a data subscription. - HeidenhainDriver implements IAlarmSource: one condition per ALARM: tag at its RawPath, driven by the SAME allowlisted R_RI 27/28 walk on the control's single loop, on the new Polling.AlarmMs cadence (default 2000 ms, only for controls carrying an ALARM: tag). FOCAS #647 transition rules (Raise / Retrigger / Clear), identity = class+group+number+text, deterministic display order, told-record on the driver instance, recorded only while a handler listens. A failed walk (refusal, overflow, comms loss) is quality-only, never a clear. - Acknowledge is LOCAL-ONLY: AcknowledgeAsync is a no-op success (FOCAS/MTConnect posture); Lsv2ReadOnlyPolicy is unchanged — no ack or write request exists. - Opt-in per tag, no driver flag (FOCAS #647 / MTConnect #626 precedent); Alarms/* arrays unchanged. - IRediscoverable: a reconnect whose R_VR identity (model / NC / PLC / options) differs from the previous session raises OnRediscoveryNeeded once (advisory re-browse chip). No extra traffic. - AdminUI: Heidenhain joins NativeAlarmModel's alarm drivers; the typed editor fixes ALARM: tags at Boolean and seeds/drops the alarm object with the selector; driver form gains "Alarm conditions (ms)". (HeidenhainDriverFormModel also carries the #830 credential-notice change.) Tests: HeidenhainAlarmTests (grammar, pairing, rules, simulator e2e with the captured iTNC 530 alarm record, local-only ack = zero bytes sent, quality-only outage/refusal, rediscovery), Runtime.Tests HeidenhainNativeAlarmEndToEndTests (real DriverHostActor condition), a DraftValidator case, and the NativeAlarmDriverSetGuard now scans the Heidenhain driver.
107 lines
4.2 KiB
C#
107 lines
4.2 KiB
C#
using System.Text.Json.Nodes;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
|
|
|
/// <summary>Gitea #642 — the shared NativeAlarmEditor's pure-model behaviour: remove-on-off, composition
|
|
/// with the per-driver editors' models, validation parity with the deploy gate, driver-type gating.</summary>
|
|
public sealed class NativeAlarmEditorModelTests
|
|
{
|
|
[Fact]
|
|
public void ToJson_with_IsAlarm_false_removes_the_alarm_key_and_keeps_the_rest()
|
|
{
|
|
var m = NativeAlarmModel.FromJson("""{"FullName":"X.Y","alarm":{"alarmType":"LimitAlarm","severity":300,"extra":1}}""");
|
|
m.IsAlarm = false;
|
|
|
|
var o = JsonNode.Parse(m.ToJson())!.AsObject();
|
|
o.ContainsKey("alarm").ShouldBeFalse();
|
|
o["FullName"]!.GetValue<string>().ShouldBe("X.Y");
|
|
}
|
|
|
|
[Fact]
|
|
public void Toggle_off_then_on_again_starts_from_defaults()
|
|
{
|
|
var off = NativeAlarmModel.Edit("""{"alarm":{"severity":300}}""", m => m.IsAlarm = false);
|
|
var on = NativeAlarmModel.Edit(off, m => m.IsAlarm = true);
|
|
var back = NativeAlarmModel.FromJson(on);
|
|
back.IsAlarm.ShouldBeTrue();
|
|
back.Severity.ShouldBe(500);
|
|
back.AlarmType.ShouldBe("AlarmCondition");
|
|
}
|
|
|
|
[Fact]
|
|
public void Edit_preserves_unknown_keys_at_both_levels()
|
|
{
|
|
var json = NativeAlarmModel.Edit("""{"FullName":"A","zz":1,"alarm":{"alarmType":"OffNormalAlarm","custom":"k"}}""",
|
|
m => m.Severity = 900);
|
|
var o = JsonNode.Parse(json)!.AsObject();
|
|
o["zz"]!.GetValue<int>().ShouldBe(1);
|
|
o["alarm"]!["custom"]!.GetValue<string>().ShouldBe("k");
|
|
o["alarm"]!["severity"]!.GetValue<int>().ShouldBe(900);
|
|
}
|
|
|
|
[Fact]
|
|
public void Alarm_and_mtconnect_driver_editor_compose_in_either_order()
|
|
{
|
|
const string start = """{"fullName":"Xact"}""";
|
|
|
|
// alarm first, then driver editor (driver model re-parses the alarm-bearing JSON)
|
|
var a1 = NativeAlarmModel.Edit(start, m => { m.IsAlarm = true; m.Severity = 800; });
|
|
var d1 = MTConnectTagConfigModel.FromJson(a1);
|
|
d1.FullName = "Yact";
|
|
var r1 = d1.ToJson();
|
|
|
|
// driver editor first, then alarm
|
|
var d2 = MTConnectTagConfigModel.FromJson(start);
|
|
d2.FullName = "Yact";
|
|
var r2 = NativeAlarmModel.Edit(d2.ToJson(), m => { m.IsAlarm = true; m.Severity = 800; });
|
|
|
|
foreach (var r in new[] { r1, r2 })
|
|
{
|
|
var o = JsonNode.Parse(r)!.AsObject();
|
|
o["fullName"]!.GetValue<string>().ShouldBe("Yact");
|
|
o["alarm"]!["severity"]!.GetValue<int>().ShouldBe(800);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Modbus")]
|
|
[InlineData("S7")]
|
|
[InlineData("AbCip")]
|
|
[InlineData("AbLegacy")]
|
|
[InlineData("TwinCAT")]
|
|
[InlineData("FOCAS")]
|
|
[InlineData("OpcUaClient")]
|
|
[InlineData("Sql")]
|
|
[InlineData("Mqtt")]
|
|
[InlineData("MTConnect")]
|
|
public void Every_typed_driver_model_preserves_an_alarm_object(string driverType)
|
|
{
|
|
const string json = """{"alarm":{"alarmType":"LimitAlarm","severity":123}}""";
|
|
var round = TagConfigRoundTrip.Through(driverType, json);
|
|
NativeAlarmModel.FromJson(round).Severity.ShouldBe(123);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_is_not_stricter_than_the_deploy_gate()
|
|
{
|
|
// The gate/parser accepts any severity (clamped 1..1000 at publish) and any alarmType string.
|
|
var m = NativeAlarmModel.FromJson("""{"alarm":{"alarmType":"Whatever","severity":5000}}""");
|
|
m.Validate().ShouldBeNull();
|
|
m.Warnings().ShouldNotBeEmpty(); // advisory only
|
|
}
|
|
|
|
[Fact]
|
|
public void SupportsNativeAlarms_covers_exactly_the_IAlarmSource_drivers()
|
|
{
|
|
foreach (var t in new[] { DriverTypeNames.MTConnect, DriverTypeNames.AbCip, DriverTypeNames.FOCAS,
|
|
DriverTypeNames.OpcUaClient, DriverTypeNames.Galaxy, DriverTypeNames.Heidenhain })
|
|
NativeAlarmModel.SupportsNativeAlarms(t).ShouldBeTrue(t);
|
|
foreach (var t in new[] { DriverTypeNames.Modbus, DriverTypeNames.S7, DriverTypeNames.Mqtt, null })
|
|
NativeAlarmModel.SupportsNativeAlarms(t).ShouldBeFalse(t);
|
|
}
|
|
}
|