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
};
}
}
}