using Google.Protobuf.WellKnownTypes; using ZB.MOM.WW.HistorianGateway.Contracts.Grpc; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping; /// /// Maps a driver-agnostic onto a gateway wire /// for the SendEvent write path. /// internal static class AlarmEventMapper { /// /// Maps to the numeric OPC UA Part 9 priority (1-1000) carried in the /// wire event's Severity property, per the /// contract ("Mapped to Historian's numeric priority on the sink side"). The read side /// () int.TryParses this same property, so it /// must stay a numeric string — the enum's .ToString() name (e.g. "High") is NOT /// parseable and used to silently collapse every read-back severity to the minimum (1). The /// values mirror the existing native-alarm convention /// (ZB.MOM.WW.OtOpcUa.Runtime.Drivers.NativeAlarmProjector.MapSeverity). /// private static string ToNumericSeverity(AlarmSeverity severity) => severity switch { AlarmSeverity.Low => "200", AlarmSeverity.Medium => "500", AlarmSeverity.High => "700", AlarmSeverity.Critical => "900", _ => "500", }; /// Maps an alarm historian event to a gateway event. /// The driver-agnostic alarm event. /// The gateway wire event ready for SendEvent. public static HistorianEvent ToHistorianEvent(AlarmHistorianEvent alarm) { // Timestamp.FromDateTime requires a Utc-kind DateTime; coerce defensively (TimestampUtc is // already Utc by contract, but a caller could pass Unspecified). var eventTime = Timestamp.FromDateTime(DateTime.SpecifyKind(alarm.TimestampUtc, DateTimeKind.Utc)); var historianEvent = new HistorianEvent { // Deliberately DO NOT set HistorianEvent.Id: the gateway's SendEvent path rejects a // client-supplied event id (the server handler throws and the call fails permanently — // confirmed live). The historian assigns event identity server-side; the alarm's own id // is preserved below as a property for read-back correlation/traceability. SourceName = alarm.EquipmentPath, Type = alarm.AlarmTypeName, EventTime = eventTime, ReceivedTime = eventTime, // the server re-stamps the received time on the SQL path }; // Proto map values must be non-null — only insert non-null properties. if (!string.IsNullOrWhiteSpace(alarm.AlarmId)) historianEvent.Properties["AlarmId"] = alarm.AlarmId; historianEvent.Properties["AlarmName"] = alarm.AlarmName; historianEvent.Properties["EventKind"] = alarm.EventKind; historianEvent.Properties["Severity"] = ToNumericSeverity(alarm.Severity); historianEvent.Properties["User"] = alarm.User; historianEvent.Properties["Message"] = alarm.Message; if (alarm.Comment is not null) historianEvent.Properties["Comment"] = alarm.Comment; return historianEvent; } }