mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 15:11:53 +08:00
## Proposed changes This PR updates the server monitored-item pipeline to be `IAsyncNodeManager`-aware and introduces channel-based FIFO queueing in the monitored-node path to improve behavior under load. It also includes follow-up fixes from review/CI feedback: - Adjusted `AsyncCustomNodeManagerTests.WriteAsync_PublishesValueToMonitoredItemQueueAsync` to account for channel-based delivery timing/coalescing while still asserting the written value is observed. - Merged latest `master` into this branch and resolved merge conflicts (notably in `ConfigurationNodeManager`), keeping async address-space lookup behavior intact. - Applied small compile-safety corrections needed for successful validation.
678 lines
28 KiB
C#
678 lines
28 KiB
C#
// Archie - December 17 2024
|
|
// Requires discussion with Part 9 Editor
|
|
#define AddActiveState
|
|
|
|
// CA2000: test code; many disposables are ownership-transferred to test fixtures or short-lived,
|
|
// making CA2000 noisy without a real leak risk. Disabled file-level for the suite.
|
|
#pragma warning disable CA2000
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using BenchmarkDotNet.Attributes;
|
|
using Moq;
|
|
using NUnit.Framework;
|
|
using Opc.Ua.Tests;
|
|
|
|
namespace Opc.Ua.Server.Tests
|
|
{
|
|
/// <summary>
|
|
/// Test FilterRetain on MonitoredItem
|
|
/// </summary>
|
|
[TestFixture]
|
|
[Category("MonitoredItem")]
|
|
[SetCulture("en-us")]
|
|
[SetUICulture("en-us")]
|
|
[Parallelizable]
|
|
[MemoryDiagnoser]
|
|
public class FilterRetainTests
|
|
{
|
|
private SystemContext m_systemContext;
|
|
private IFilterContext m_filterContext;
|
|
|
|
internal static readonly LocalizedText InService = new("en", "In Service");
|
|
internal static readonly LocalizedText OutOfService = new("en", "Out of Service");
|
|
internal static readonly LocalizedText Unsuppressed = new("en-US", "Unsuppressed");
|
|
|
|
internal static readonly LocalizedText Active = new("en-US", "Active");
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Should not pass filter")]
|
|
[TestCase(true, Description = "Should pass filter")]
|
|
public void TestNotFilterTarget(bool pass)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
ExclusiveLevelAlarmState alarm = GetExclusiveLevelAlarm(
|
|
addFilterRetain: false,
|
|
filterRetainValue: false,
|
|
telemetry: telemetry);
|
|
LimitAlarmStates desiredState = LimitAlarmStates.Inactive;
|
|
if (pass)
|
|
{
|
|
desiredState = LimitAlarmStates.High;
|
|
}
|
|
alarm.SetLimitState(systemContext, desiredState);
|
|
|
|
EventFilter filter = GetHighOnlyEventFilter(addClauses: true, telemetry);
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
CanSendFilteredAlarm(monitoredItem, GetFilterContext(telemetry), filter, alarm, pass, telemetry);
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Should not pass filter")]
|
|
[TestCase(true, Description = "Should pass filter")]
|
|
public void TestNonConditionState(bool pass)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
var alarm = new DeviceFailureEventState(null);
|
|
alarm.Create(
|
|
systemContext,
|
|
new NodeId(12345, 1),
|
|
new QualifiedName("AnyAlarm", 1),
|
|
new LocalizedText(string.Empty, "AnyAlarm"),
|
|
true);
|
|
|
|
alarm.EventType.Value = ObjectTypeIds.DeviceFailureEventType;
|
|
|
|
IFilterContext context = GetFilterContext(telemetry);
|
|
|
|
EventFilter filter = GetHighOnlyEventFilter(addClauses: !pass, telemetry);
|
|
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
CanSendFilteredAlarm(monitoredItem, context, filter, alarm, pass, telemetry);
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Should not pass filter")]
|
|
[TestCase(true, Description = "Should pass filter")]
|
|
public void TestNonEvent(bool pass)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
var certificateType = new ApplicationCertificateState(null);
|
|
|
|
IFilterContext context = GetFilterContext(telemetry);
|
|
|
|
EventFilter filter = GetHighOnlyEventFilter(addClauses: !pass, telemetry);
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
CanSendFilteredAlarm(monitoredItem, context, filter, certificateType, pass, telemetry);
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Set SupportsFilteredRetain False")]
|
|
[TestCase(true, Description = "Set SupportsFilteredRetain True")]
|
|
public void TestFilteredRetainExists(bool supportsFilteredRetain)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
ExclusiveLevelAlarmState alarm = GetExclusiveLevelAlarm(
|
|
addFilterRetain: true,
|
|
filterRetainValue: supportsFilteredRetain,
|
|
telemetry: telemetry);
|
|
|
|
alarm.SetLimitState(GetSystemContext(telemetry), LimitAlarmStates.Inactive);
|
|
|
|
EventFilter filter = GetHighOnlyEventFilter(addClauses: true, telemetry);
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
|
|
CanSendFilteredAlarm(monitoredItem, GetFilterContext(telemetry), filter, alarm, expected: false, telemetry);
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Should not pass filter")]
|
|
[TestCase(true, Description = "Should pass filter")]
|
|
public void TestCanSendMultiple(bool supportsFilteredRetain)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
ExclusiveLevelAlarmState alarm = GetExclusiveLevelAlarm(
|
|
addFilterRetain: true,
|
|
filterRetainValue: supportsFilteredRetain,
|
|
telemetry: telemetry);
|
|
|
|
IFilterContext filterContext = GetFilterContext(telemetry);
|
|
|
|
EventFilter filter = GetHighOnlyEventFilter(addClauses: true, telemetry);
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: false, telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: true, telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.HighHigh);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(
|
|
monitoredItem,
|
|
filterContext,
|
|
filter,
|
|
alarm,
|
|
expected: supportsFilteredRetain,
|
|
telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: false, telemetry);
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Should not pass filter")]
|
|
[TestCase(true, Description = "Should pass filter")]
|
|
public void TestCanSendOnceSimple(bool supportsFilteredRetain)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
ExclusiveLevelAlarmState alarm = GetExclusiveLevelAlarm(
|
|
addFilterRetain: true,
|
|
filterRetainValue: supportsFilteredRetain,
|
|
telemetry: telemetry);
|
|
|
|
EventFilter filter = GetHighOnlyEventFilter(addClauses: true, telemetry);
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
IFilterContext filterContext = GetFilterContext(telemetry);
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: false, telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: true, telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(
|
|
monitoredItem,
|
|
filterContext,
|
|
filter,
|
|
alarm,
|
|
expected: supportsFilteredRetain,
|
|
telemetry);
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Should not pass filter")]
|
|
[TestCase(true, Description = "Should pass filter")]
|
|
public void TestSendMultiple(bool supportsFilteredRetain)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
ExclusiveLevelAlarmState alarm = GetExclusiveLevelAlarm(
|
|
addFilterRetain: true,
|
|
filterRetainValue: supportsFilteredRetain,
|
|
telemetry: telemetry);
|
|
|
|
EventFilter filter = GetHighOnlyEventFilter(addClauses: true, telemetry);
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
IFilterContext filterContext = GetFilterContext(telemetry);
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: false, telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: true, telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.HighHigh);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(
|
|
monitoredItem,
|
|
filterContext,
|
|
filter,
|
|
alarm,
|
|
expected: supportsFilteredRetain,
|
|
telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: true, telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(
|
|
monitoredItem,
|
|
filterContext,
|
|
filter,
|
|
alarm,
|
|
expected: supportsFilteredRetain,
|
|
telemetry);
|
|
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Low);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected: false, telemetry);
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(false, Description = "Should not pass filter")]
|
|
[TestCase(true, Description = "Should pass filter")]
|
|
public void SpecB14(bool supportsFilteredRetain)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
|
|
// https://reference.opcfoundation.org/Core/Part9/v105/docs/B.1.4
|
|
|
|
ExclusiveLevelAlarmState alarm = GetExclusiveLevelAlarm(
|
|
addFilterRetain: true,
|
|
filterRetainValue: supportsFilteredRetain,
|
|
telemetry: telemetry);
|
|
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
|
|
alarm.SetSuppressedState(systemContext, suppressed: false);
|
|
alarm.OutOfServiceState.Value = InService;
|
|
|
|
IFilterContext filterContext = GetFilterContext(telemetry);
|
|
var filter = new EventFilter
|
|
{
|
|
SelectClauses = GetSelectFields(),
|
|
WhereClause = GetStateFilter()
|
|
};
|
|
_ = filter.Validate(filterContext);
|
|
|
|
using MonitoredItem monitoredItem = CreateMonitoredItem(filter, telemetry);
|
|
|
|
// 16 States in Table B.3
|
|
|
|
// 1 Alarm Goes Active
|
|
Debug.WriteLine("// 1 Alarm Goes Active");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
bool expected = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 2 Placed Out of Service
|
|
Debug.WriteLine("// 2 Placed Out of Service");
|
|
alarm.OutOfServiceState.Value = OutOfService;
|
|
if (!supportsFilteredRetain)
|
|
{
|
|
expected = false;
|
|
}
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 3 Alarm Suppressed; No event since OutOfService
|
|
Debug.WriteLine("// 3 Alarm Suppressed; No event since OutOfService");
|
|
alarm.SetSuppressedState(systemContext, suppressed: true);
|
|
expected = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 4 Alarm goes inactive; No event since OutOfService
|
|
Debug.WriteLine("// 4 Alarm goes inactive; No event since OutOfService");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 5 Alarm not Suppressed; No event since not active
|
|
Debug.WriteLine("// 5 Alarm not Suppressed; No event since not active");
|
|
alarm.SetSuppressedState(systemContext, suppressed: false);
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 6 Alarm goes active; No event since OutOfService
|
|
Debug.WriteLine("// 6 Alarm goes active; No event since OutOfService");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 7 Alarm no longer OutOfService; Event generated
|
|
Debug.WriteLine("// 7 Alarm no longer OutOfService; Event generated");
|
|
alarm.OutOfServiceState.Value = InService;
|
|
expected = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 8 Alarm goes inactive
|
|
Debug.WriteLine("// 8 Alarm goes inactive");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
if (!supportsFilteredRetain)
|
|
{
|
|
expected = false;
|
|
}
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 9 Alarm Suppressed; No event since not active
|
|
Debug.WriteLine("// 9 Alarm Suppressed; No event since not active");
|
|
alarm.SetSuppressedState(systemContext, suppressed: true);
|
|
expected = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 10 Alarm goes active; No event since Suppressed
|
|
Debug.WriteLine("// 10 Alarm goes active; No event since Suppressed");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 11 Alarm goes inactive; No event since Suppressed
|
|
Debug.WriteLine("// 11 Alarm goes inactive; No event since Suppressed");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 12 Alarm no longer Suppressed
|
|
Debug.WriteLine("// 12 Alarm no longer Suppressed");
|
|
alarm.SetSuppressedState(systemContext, suppressed: false);
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 13 Placed OutOfService
|
|
Debug.WriteLine("// 13 Placed OutOfService");
|
|
alarm.OutOfServiceState.Value = OutOfService;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 14 Alarm goes active; No event since OutOfService
|
|
Debug.WriteLine("// 14 Alarm goes active; No event since OutOfService");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.High);
|
|
alarm.Retain.Value = true;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 15 Alarm goes inactive; No event since OutOfService
|
|
Debug.WriteLine("// 15 Alarm goes inactive; No event since OutOfService");
|
|
alarm.SetLimitState(systemContext, LimitAlarmStates.Inactive);
|
|
alarm.Retain.Value = false;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
|
|
// 16 Alarm no longer OutOfService
|
|
Debug.WriteLine("// 16 Alarm no longer OutOfService");
|
|
alarm.OutOfServiceState.Value = InService;
|
|
CanSendFilteredAlarm(monitoredItem, filterContext, filter, alarm, expected, telemetry);
|
|
}
|
|
|
|
private void CanSendFilteredAlarm(
|
|
MonitoredItem monitoredItem,
|
|
IFilterContext context,
|
|
EventFilter filter,
|
|
BaseObjectState alarm,
|
|
bool expected,
|
|
ITelemetryContext telemetry)
|
|
{
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
|
|
var eventSnapshot = new InstanceStateSnapshot();
|
|
eventSnapshot.Initialize(systemContext, alarm);
|
|
|
|
const BindingFlags eFlags = BindingFlags.Instance | BindingFlags.NonPublic;
|
|
MethodInfo methodInfo = typeof(MonitoredItem).GetMethod("CanSendFilteredAlarm", eFlags);
|
|
Debug.WriteLine("Expecting " + expected.ToString());
|
|
object result = methodInfo.Invoke(monitoredItem, [context, filter, eventSnapshot]);
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result.GetType().Name, Is.EqualTo("Boolean"));
|
|
Assert.That((bool)result, Is.EqualTo(expected));
|
|
}
|
|
|
|
private ExclusiveLevelAlarmState GetExclusiveLevelAlarm(
|
|
bool addFilterRetain,
|
|
bool filterRetainValue,
|
|
ITelemetryContext telemetry)
|
|
{
|
|
var alarm = new ExclusiveLevelAlarmState(null);
|
|
SystemContext context = GetSystemContext(telemetry);
|
|
alarm.Create(
|
|
context,
|
|
new NodeId(12345, 1),
|
|
new QualifiedName("AnyAlarm", 1),
|
|
new LocalizedText(string.Empty, "AnyAlarm"),
|
|
true);
|
|
|
|
alarm.EventType.Value = ObjectTypeIds.ExclusiveLevelAlarmType;
|
|
alarm.AddOutOfServiceState(context);
|
|
alarm.AddSuppressedState(context);
|
|
alarm.AddSilenceState(context);
|
|
alarm.AddShelvingState(context);
|
|
alarm.AddSeverityLowLow(context);
|
|
if (addFilterRetain)
|
|
{
|
|
alarm.SupportsFilteredRetain = new PropertyState<bool>.Implementation<VariantBuilder>(alarm)
|
|
{
|
|
Value = filterRetainValue
|
|
};
|
|
}
|
|
|
|
return alarm;
|
|
}
|
|
|
|
private static ArrayOf<SimpleAttributeOperand> GetSelectFields()
|
|
{
|
|
int eventIndexCounter = 0;
|
|
var desiredEventFields = new Dictionary<int, ArrayOf<QualifiedName>>
|
|
{
|
|
{ eventIndexCounter++, [QualifiedName.From(BrowseNames.EventId) ] },
|
|
{ eventIndexCounter++, [QualifiedName.From(BrowseNames.EventType) ] },
|
|
{ eventIndexCounter++, [QualifiedName.From(BrowseNames.Time) ] },
|
|
{ eventIndexCounter++, [QualifiedName.From(BrowseNames.ActiveState) ] },
|
|
{ eventIndexCounter++, [QualifiedName.From(BrowseNames.Message) ] },
|
|
{ eventIndexCounter++, [
|
|
QualifiedName.From(BrowseNames.LimitState),
|
|
QualifiedName.From(BrowseNames.CurrentState)
|
|
]
|
|
},
|
|
{ eventIndexCounter++, [
|
|
QualifiedName.From(BrowseNames.LimitState),
|
|
QualifiedName.From(BrowseNames.CurrentState),
|
|
QualifiedName.From(BrowseNames.Id)
|
|
]
|
|
},
|
|
{ eventIndexCounter++, [
|
|
QualifiedName.From(BrowseNames.LimitState),
|
|
QualifiedName.From(BrowseNames.LastTransition)
|
|
]
|
|
}
|
|
};
|
|
|
|
var simpleAttributeOperands = new List<SimpleAttributeOperand>();
|
|
foreach (ArrayOf<QualifiedName> desiredEventField in desiredEventFields.Values)
|
|
{
|
|
simpleAttributeOperands.Add(
|
|
new SimpleAttributeOperand
|
|
{
|
|
AttributeId = Attributes.Value,
|
|
TypeDefinitionId = ObjectTypeIds.BaseEventType,
|
|
BrowsePath = desiredEventField
|
|
});
|
|
}
|
|
|
|
// ConditionId
|
|
simpleAttributeOperands.Add(
|
|
new SimpleAttributeOperand
|
|
{
|
|
AttributeId = Attributes.NodeId,
|
|
TypeDefinitionId = ObjectTypeIds.ConditionType
|
|
});
|
|
|
|
return simpleAttributeOperands;
|
|
}
|
|
|
|
private EventFilter GetHighOnlyEventFilter(bool addClauses, ITelemetryContext telemetry)
|
|
{
|
|
var filter = new EventFilter();
|
|
if (addClauses)
|
|
{
|
|
filter.SelectClauses = GetSelectFields();
|
|
filter.WhereClause = GetHighOnlyFilter();
|
|
}
|
|
_ = filter.Validate(GetFilterContext(telemetry));
|
|
return filter;
|
|
}
|
|
|
|
private static ContentFilter GetHighOnlyFilter()
|
|
{
|
|
var whereClause = new ContentFilter();
|
|
|
|
var eventLevel = new SimpleAttributeOperand
|
|
{
|
|
AttributeId = Attributes.Value,
|
|
TypeDefinitionId = ObjectTypeIds.ExclusiveLevelAlarmType,
|
|
BrowsePath =
|
|
[
|
|
.. new QualifiedName[]
|
|
{
|
|
QualifiedName.From(BrowseNames.LimitState),
|
|
QualifiedName.From(BrowseNames.CurrentState),
|
|
QualifiedName.From(BrowseNames.Id)
|
|
}
|
|
]
|
|
};
|
|
|
|
var desiredEventLevel = new LiteralOperand
|
|
{
|
|
Value = new Variant(new NodeId(Objects.ExclusiveLimitStateMachineType_High))
|
|
};
|
|
|
|
whereClause.Push(
|
|
FilterOperator.Equals,
|
|
Variant.FromStructure(eventLevel),
|
|
Variant.FromStructure(desiredEventLevel));
|
|
|
|
return whereClause;
|
|
}
|
|
|
|
private static ContentFilter GetStateFilter()
|
|
{
|
|
var whereClause = new ContentFilter();
|
|
|
|
var notOutOfServiceState = new SimpleAttributeOperand
|
|
{
|
|
AttributeId = Attributes.Value,
|
|
TypeDefinitionId = default,
|
|
BrowsePath = [.. new QualifiedName[] { QualifiedName.From(BrowseNames.OutOfServiceState) }]
|
|
};
|
|
|
|
var desiredOutOfServiceValue = new LiteralOperand { Value = new Variant(InService) };
|
|
|
|
whereClause.Push(
|
|
FilterOperator.Equals,
|
|
Variant.FromStructure(notOutOfServiceState),
|
|
Variant.FromStructure(desiredOutOfServiceValue));
|
|
|
|
var notSuppressed = new SimpleAttributeOperand
|
|
{
|
|
AttributeId = Attributes.Value,
|
|
TypeDefinitionId = default,
|
|
BrowsePath = [.. new QualifiedName[] { QualifiedName.From(BrowseNames.SuppressedState) }]
|
|
};
|
|
|
|
var desiredSuppressedValue = new LiteralOperand { Value = new Variant(Unsuppressed) };
|
|
|
|
whereClause.Push(
|
|
FilterOperator.Equals,
|
|
Variant.FromStructure(notSuppressed),
|
|
Variant.FromStructure(desiredSuppressedValue));
|
|
|
|
#if AddActiveState
|
|
var activeState = new SimpleAttributeOperand
|
|
{
|
|
AttributeId = Attributes.Value,
|
|
TypeDefinitionId = default,
|
|
BrowsePath = [.. new QualifiedName[] { QualifiedName.From(BrowseNames.ActiveState) }]
|
|
};
|
|
|
|
var activeValue = new LiteralOperand { Value = new Variant(Active) };
|
|
|
|
whereClause.Push(
|
|
FilterOperator.Equals,
|
|
Variant.FromStructure(activeState),
|
|
Variant.FromStructure(activeValue));
|
|
|
|
whereClause.Push(
|
|
FilterOperator.And,
|
|
Variant.FromStructure(new ElementOperand(1)),
|
|
Variant.FromStructure(new ElementOperand(2)));
|
|
|
|
#endif
|
|
whereClause.Push(
|
|
FilterOperator.And,
|
|
Variant.FromStructure(new ElementOperand(0)),
|
|
Variant.FromStructure(new ElementOperand(1)));
|
|
|
|
return whereClause;
|
|
}
|
|
|
|
private SystemContext GetSystemContext(ITelemetryContext telemetry)
|
|
{
|
|
if (m_systemContext == null)
|
|
{
|
|
m_systemContext = new SystemContext(telemetry) { NamespaceUris = new NamespaceTable() };
|
|
m_systemContext.NamespaceUris.Append(Ua.Namespaces.OpcUa);
|
|
var typeTable = new TypeTable(m_systemContext.NamespaceUris);
|
|
typeTable.AddSubtype(ObjectTypeIds.BaseObjectType, default);
|
|
typeTable.AddSubtype(ObjectTypeIds.BaseEventType, ObjectTypeIds.BaseObjectType);
|
|
typeTable.AddSubtype(ObjectTypeIds.ConditionType, ObjectTypeIds.BaseEventType);
|
|
typeTable.AddSubtype(
|
|
ObjectTypeIds.AcknowledgeableConditionType,
|
|
ObjectTypeIds.ConditionType);
|
|
typeTable.AddSubtype(
|
|
ObjectTypeIds.AlarmConditionType,
|
|
ObjectTypeIds.AcknowledgeableConditionType);
|
|
typeTable.AddSubtype(
|
|
ObjectTypeIds.LimitAlarmType,
|
|
ObjectTypeIds.AlarmConditionType);
|
|
typeTable.AddSubtype(
|
|
ObjectTypeIds.ExclusiveLimitAlarmType,
|
|
ObjectTypeIds.LimitAlarmType);
|
|
typeTable.AddSubtype(
|
|
ObjectTypeIds.ExclusiveLevelAlarmType,
|
|
ObjectTypeIds.ExclusiveLimitAlarmType);
|
|
|
|
m_systemContext.TypeTable = typeTable;
|
|
}
|
|
|
|
return m_systemContext;
|
|
}
|
|
|
|
private IFilterContext GetFilterContext(ITelemetryContext telemetry)
|
|
{
|
|
if (m_filterContext == null)
|
|
{
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
m_filterContext = new FilterContext(
|
|
systemContext.NamespaceUris,
|
|
systemContext.TypeTable,
|
|
systemContext.Telemetry);
|
|
}
|
|
|
|
return m_filterContext;
|
|
}
|
|
|
|
private MonitoredItem CreateMonitoredItem(MonitoringFilter filter, ITelemetryContext telemetry)
|
|
{
|
|
var serverMock = new Mock<IServerInternal>();
|
|
|
|
SystemContext systemContext = GetSystemContext(telemetry);
|
|
serverMock.Setup(s => s.Telemetry).Returns(telemetry);
|
|
serverMock.Setup(s => s.NamespaceUris).Returns(systemContext.NamespaceUris);
|
|
serverMock.Setup(s => s.TypeTree).Returns((TypeTable)systemContext.TypeTable);
|
|
using var queueFactory = new MonitoredItemQueueFactory(telemetry);
|
|
serverMock.Setup(s => s.MonitoredItemQueueFactory)
|
|
.Returns(queueFactory);
|
|
|
|
var nodeMangerMock = new Mock<IAsyncNodeManager>();
|
|
|
|
return new MonitoredItem(
|
|
serverMock.Object,
|
|
nodeMangerMock.Object,
|
|
null,
|
|
1,
|
|
2,
|
|
new ReadValueId(),
|
|
DiagnosticsMasks.All,
|
|
TimestampsToReturn.Server,
|
|
MonitoringMode.Reporting,
|
|
3,
|
|
filter,
|
|
filter,
|
|
null,
|
|
1000.0,
|
|
10,
|
|
false,
|
|
1000);
|
|
}
|
|
}
|
|
}
|