using System; using Opc.Ua; using ZB.MOM.WW.OtOpcUa.Host.Domain; namespace ZB.MOM.WW.OtOpcUa.Host.OpcUa { /// /// Converts between domain Vtq and OPC UA DataValue. Handles all data_type_mapping.md types. (OPC-005, OPC-007) /// public static class DataValueConverter { /// /// Converts a bridge VTQ snapshot into an OPC UA data value. /// /// The VTQ snapshot to convert. /// An OPC UA data value suitable for reads and subscriptions. public static DataValue FromVtq(Vtq vtq) { var statusCode = new StatusCode(QualityMapper.MapToOpcUaStatusCode(vtq.Quality)); var dataValue = new DataValue { Value = ConvertToOpcUaValue(vtq.Value), StatusCode = statusCode, SourceTimestamp = vtq.Timestamp.Kind == DateTimeKind.Utc ? vtq.Timestamp : vtq.Timestamp.ToUniversalTime(), ServerTimestamp = DateTime.UtcNow }; return dataValue; } /// /// Converts an OPC UA data value back into a bridge VTQ snapshot. /// /// The OPC UA data value to convert. /// A VTQ snapshot containing the converted value, timestamp, and derived quality. public static Vtq ToVtq(DataValue dataValue) { var quality = MapStatusCodeToQuality(dataValue.StatusCode); var timestamp = dataValue.SourceTimestamp != DateTime.MinValue ? dataValue.SourceTimestamp : DateTime.UtcNow; return new Vtq(dataValue.Value, timestamp, quality); } private static object? ConvertToOpcUaValue(object? value) { if (value == null) return null; return value switch { bool _ => value, int _ => value, float _ => value, double _ => value, string _ => value, DateTime dt => dt.Kind == DateTimeKind.Utc ? dt : dt.ToUniversalTime(), TimeSpan ts => ts.TotalSeconds, // ElapsedTime → Double seconds short s => (int)s, long l => l, byte b => (int)b, bool[] _ => value, int[] _ => value, float[] _ => value, double[] _ => value, string[] _ => value, DateTime[] _ => value, _ => value.ToString() }; } private static Quality MapStatusCodeToQuality(StatusCode statusCode) { var code = statusCode.Code; if (StatusCode.IsGood(statusCode)) return Quality.Good; if (StatusCode.IsUncertain(statusCode)) return Quality.Uncertain; return code switch { StatusCodes.BadNotConnected => Quality.BadNotConnected, StatusCodes.BadCommunicationError => Quality.BadCommFailure, StatusCodes.BadConfigurationError => Quality.BadConfigError, StatusCodes.BadOutOfService => Quality.BadOutOfService, StatusCodes.BadWaitingForInitialData => Quality.BadWaitingForInitialData, _ => Quality.Bad }; } } }