Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Libraries/Opc.Ua.Server/ServerUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,9 @@ public static DiagnosticInfo CreateDiagnosticInfo(

ServiceResult translatedError = error;

if ((context.DiagnosticsMask & DiagnosticsMasks.LocalizedText) != 0)
// Only translate if ServiceLocalizedText is set, as DiagnosticInfo only uses
// the translation for ServiceLocalizedText, not OperationLocalizedText
if ((context.DiagnosticsMask & DiagnosticsMasks.ServiceLocalizedText) != 0)
{
translatedError = server.ResourceManager.Translate(context.PreferredLocales, error);
}
Expand Down
262 changes: 192 additions & 70 deletions Libraries/Opc.Ua.Server/Subscription/MonitoredItem/MonitoredItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public MonitoredItem(
ClientHandle = clientHandle;
Filter = originalFilter;
m_filterToUse = filterToUse;
m_cachedDataChangeFilter = filterToUse as DataChangeFilter;
m_range = 0;
m_samplingInterval = samplingInterval;
QueueSize = queueSize;
Expand Down Expand Up @@ -189,6 +190,7 @@ public MonitoredItem(
ClientHandle = storedMonitoredItem.ClientHandle;
Filter = storedMonitoredItem.OriginalFilter;
m_filterToUse = storedMonitoredItem.FilterToUse;
m_cachedDataChangeFilter = storedMonitoredItem.FilterToUse as DataChangeFilter;
m_range = storedMonitoredItem.Range;
m_samplingInterval = storedMonitoredItem.SamplingInterval;
QueueSize = storedMonitoredItem.QueueSize;
Expand Down Expand Up @@ -672,6 +674,7 @@ public ServiceResult ModifyAttributes(

Filter = originalFilter;
m_filterToUse = filterToUse;
m_cachedDataChangeFilter = filterToUse as DataChangeFilter;

if (range != null)
{
Expand Down Expand Up @@ -869,15 +872,7 @@ public virtual void QueueValue(DataValue value, ServiceResult error, bool ignore
value.WrappedValue);
}

value = new DataValue
{
WrappedValue = value.WrappedValue,
StatusCode = value.StatusCode,
SourceTimestamp = value.SourceTimestamp,
SourcePicoseconds = value.SourcePicoseconds,
ServerTimestamp = value.ServerTimestamp,
ServerPicoseconds = value.ServerPicoseconds
};
value = new DataValue(value);

// ensure the data value matches the error status code.
if (error != null && error.StatusCode.Code != 0)
Expand Down Expand Up @@ -926,7 +921,7 @@ public virtual void QueueValue(DataValue value, ServiceResult error, bool ignore
}

// apply filter to incoming item.
if (!AlwaysReportUpdates && !ignoreFilters && !ApplyFilter(value, error))
if (!ignoreFilters && !AlwaysReportUpdates && !ApplyFilter(value, error))
{
ServerUtils.ReportFilteredValue(NodeId, Id, value);
return;
Expand Down Expand Up @@ -1615,7 +1610,7 @@ protected virtual bool ApplyFilter(DataValue value, ServiceResult error)
error,
m_lastValue,
m_lastError,
m_filterToUse as DataChangeFilter,
m_cachedDataChangeFilter,
m_range);
}

Expand Down Expand Up @@ -1706,7 +1701,7 @@ public static bool ValueChanged(
}

// check if reference to same object.
if (!Equals(lastValue.Value, value.Value, deadbandType, deadband, range))
if (!Equals(lastValue.WrappedValue, value.WrappedValue, deadbandType, deadband, range))
{
return true;
}
Expand All @@ -1719,91 +1714,217 @@ public static bool ValueChanged(
/// Checks if the two values are equal.
/// </summary>
protected static bool Equals(
object value1,
object value2,
Variant value1,
Variant value2,
DeadbandType deadbandType,
double deadband,
double range)
{
// check if reference to same object.
if (ReferenceEquals(value1, value2))
{
return true;
}

// check for invalid values.
if (value1 == null || value2 == null)
{
return value1 == value2;
}

// check for type change.
if (value1.GetType() != value2.GetType())
{
return false;
}

// special case NaN is always not equal
if (value1.Equals(float.NaN) ||
value1.Equals(double.NaN) ||
value2.Equals(float.NaN) ||
value2.Equals(double.NaN))
{
return false;
}

// check if values are equal.
// check if equal
if (value1.Equals(value2))
{
return true;
}

if (value1 is not Array array1 || value2 is not Array array2)
// handle scalars.
if (value1.TypeInfo.ValueRank == ValueRanks.Scalar)
{
if (value1 is XmlElement xmlElement1 && value2 is XmlElement xmlElement2)
switch (value1.TypeInfo.BuiltInType)
{
return xmlElement1.OuterXml
.Equals(xmlElement2.OuterXml, StringComparison.Ordinal);
}
case BuiltInType.Double:
if (value1.TryGet(out double d1) && value2.TryGet(out double d2))
{
if (double.IsNaN(d1) || double.IsNaN(d2))
{
return false;
}

if (d1 == d2)
{
return true;
}

if (deadbandType == DeadbandType.None)
{
return false;
}

return !ExceedsDeadband(d1, d2, deadbandType, deadband, range);
}
return false;
case BuiltInType.Float:
if (value1.TryGet(out float f1) && value2.TryGet(out float f2))
{
if (float.IsNaN(f1) || float.IsNaN(f2))
{
return false;
}

if (f1 == f2)
{
return true;
}

if (deadbandType == DeadbandType.None)
{
return false;
}

return !ExceedsDeadband((double)f1, (double)f2, deadbandType, deadband, range);
}
return false;
case BuiltInType.XmlElement:
if (value1.TryGet(out XmlElement xml1) && value2.TryGet(out XmlElement xml2))
{
if (xml1 == null || xml2 == null)
{
return xml1 == xml2;
}

// nothing more to do if no deadband.
if (deadbandType == DeadbandType.None)
{
return false;
return xml1.OuterXml.Equals(xml2.OuterXml, StringComparison.Ordinal);
}
return false;
case BuiltInType.Integer:
case BuiltInType.UInteger:
case BuiltInType.Int32:
case BuiltInType.UInt32:
case BuiltInType.Int64:
case BuiltInType.UInt64:
case BuiltInType.Int16:
case BuiltInType.UInt16:
case BuiltInType.Byte:
case BuiltInType.SByte:
// Other numeric types check deadband if applicable
if (deadbandType != DeadbandType.None)
{
return !ExceedsDeadband(value1.Value, value2.Value, deadbandType, deadband, range);
}
return false;
}

// check deadband.
return !ExceedsDeadband(value1, value2, deadbandType, deadband, range);
}

// compare lengths.
if (array1.Length != array2.Length)
{
return false;
}

// compare each element.
bool isVariant = array1.GetType().GetElementType() == typeof(Variant);

for (int ii = 0; ii < array1.Length; ii++)
// handle arrays.
switch (value1.TypeInfo.BuiltInType)
{
object element1 = array1.GetValue(ii);
object element2 = array2.GetValue(ii);
case BuiltInType.Double:
{
if (value1.TryGet(out double[] dArray1) && value2.TryGet(out double[] dArray2))
{
if (dArray1.Length != dArray2.Length)
{
return false;
}

if (isVariant)
for (int ii = 0; ii < dArray1.Length; ii++)
{
double d1 = dArray1[ii];
double d2 = dArray2[ii];

if (double.IsNaN(d1) || double.IsNaN(d2))
{
return false;
}

if (d1 != d2)
{
if (deadbandType == DeadbandType.None ||
ExceedsDeadband(d1, d2, deadbandType, deadband, range))
{
return false;
}
}
}
return true;
}
return false;
}
case BuiltInType.Float:
{
element1 = ((Variant)element1).AsBoxedObject(); // TODO: Rewrite to avoid boxing
element2 = ((Variant)element2).AsBoxedObject();
if (value1.TryGet(out float[] fArray1) && value2.TryGet(out float[] fArray2))
{
if (fArray1.Length != fArray2.Length)
{
return false;
}

for (int ii = 0; ii < fArray1.Length; ii++)
{
float f1 = fArray1[ii];
float f2 = fArray2[ii];

if (float.IsNaN(f1) || float.IsNaN(f2))
{
return false;
}

if (f1 != f2)
{
if (deadbandType == DeadbandType.None ||
ExceedsDeadband((double)f1, (double)f2, deadbandType, deadband, range))
{
return false;
}
}
}
return true;
}
return false;
}
case BuiltInType.Variant:
{
if (value1.TryGet(out Variant[] vArray1) && value2.TryGet(out Variant[] vArray2))
{
if (vArray1.Length != vArray2.Length)
{
return false;
}

if (!Equals(element1, element2, deadbandType, deadband, range))
for (int ii = 0; ii < vArray1.Length; ii++)
{
if (!Equals(vArray1[ii], vArray2[ii], deadbandType, deadband, range))
{
return false;
}
}
return true;
}
return false;
}
case BuiltInType.XmlElement:
{
if (value1.TryGet(out XmlElement[] xArray1) && value2.TryGet(out XmlElement[] xArray2))
{
if (xArray1.Length != xArray2.Length)
{
return false;
}

for (int ii = 0; ii < xArray1.Length; ii++)
{
XmlElement xml1 = xArray1[ii];
XmlElement xml2 = xArray2[ii];

if (xml1 == null || xml2 == null)
{
if (xml1 != xml2)
{
return false;
}
}
else if (!xml1.OuterXml.Equals(xml2.OuterXml, StringComparison.Ordinal))
{
return false;
}
}
return true;
}
return false;
}
}

// must be equal.
return true;
return false;
}

/// <summary>
Expand Down Expand Up @@ -2094,6 +2215,7 @@ protected virtual void Dispose(bool disposing)
private NumericRange m_parsedIndexRange;
private TimestampsToReturn m_timestampsToReturn;
private MonitoringFilter m_filterToUse;
private DataChangeFilter m_cachedDataChangeFilter;
private double m_range;
private double m_samplingInterval;
private bool m_discardOldest;
Expand Down
Loading
Loading