using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms; namespace ZB.MOM.WW.OtOpcUa.Server.Phase7; /// /// adapter exposing each scripted alarm's current /// as an OPC UA boolean. Phase 7 follow-up (task #245). /// /// /// /// Paired with the dispatch in /// DriverNodeManager.OnReadValue. Full-reference lookup is the /// ScriptedAlarmId the walker wrote into DriverAttributeInfo.FullName /// when emitting the alarm variable node. /// /// /// Unknown alarm ids return BadNodeIdUnknown so misconfiguration surfaces /// instead of silently reading false. Alarms whose predicate has never /// been evaluated (brand new, before the engine's first cascade tick) report /// via , /// which matches the Part 9 initial-state semantics. /// /// public sealed class ScriptedAlarmReadable : IReadable { /// OPC UA StatusCodes.BadNodeIdUnknown — kept local so we don't pull the OPC stack. private const uint BadNodeIdUnknown = 0x80340000; private readonly ScriptedAlarmEngine _engine; public ScriptedAlarmReadable(ScriptedAlarmEngine engine) { _engine = engine ?? throw new ArgumentNullException(nameof(engine)); } public Task> ReadAsync( IReadOnlyList fullReferences, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(fullReferences); var now = DateTime.UtcNow; var results = new DataValueSnapshot[fullReferences.Count]; for (var i = 0; i < fullReferences.Count; i++) { var alarmId = fullReferences[i]; var state = _engine.GetState(alarmId); if (state is null) { results[i] = new DataValueSnapshot(null, BadNodeIdUnknown, null, now); continue; } var active = state.Active == AlarmActiveState.Active; results[i] = new DataValueSnapshot(active, 0u, now, now); } return Task.FromResult>(results); } }