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) { if (alarm.DeliveryId == Guid.Empty) throw new ArgumentException("Alarm delivery identity must be nonzero.", nameof(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 { // Identity belongs to the event/durable queue, never to an individual RPC attempt. Id = alarm.DeliveryId.ToString("D"), 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; } }