/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Server; using Opc.Ua.Server.Historian; using Opc.Ua.Server.Historian.InMemory; using Quickstarts.Servers; namespace TestData { /// /// Receives simulated data changes and value-generation requests. /// public interface ITestDataSystemCallback { /// /// Reports a newly sampled value for a test-data variable. /// /// The variable whose value changed. /// The new simulated value. /// The quality of the sampled value. /// The timestamp associated with the sampled value. void OnDataChange( BaseVariableState variable, Variant value, StatusCode statusCode, DateTime timestamp); /// /// Requests regeneration of a test-data variable's simulated value. /// /// The variable whose value should be generated. void OnGenerateValues(BaseVariableState variable); } /// /// Generates simulated values for a test-data variable. /// public interface ITestDataSystemValuesGenerator { /// /// Generates a new simulated value using the supplied system context. /// /// The context used to generate and publish the value. /// The status of the value-generation operation. StatusCode OnGenerateValues(ISystemContext context); } // CA1001: quickstart sample; ownership/disposal patterns documented in the // class methods rather than via IDisposable. #pragma warning disable CA1001 public class TestDataSystem #pragma warning restore CA1001 { public TestDataSystem( ITestDataSystemCallback callback, NamespaceTable namespaceUris, StringTable serverUris, ITelemetryContext telemetry, TimeProvider? timeProvider = null) { m_callback = callback; m_logger = telemetry.CreateLogger(); m_minimumSamplingInterval = int.MaxValue; m_monitoredNodes = []; m_samplingNodes = null; m_timeProvider = timeProvider ?? TimeProvider.System; Generator = new Opc.Ua.Test.DataGenerator(null, telemetry) { NamespaceUris = namespaceUris, ServerUris = serverUris }; Historian = new InMemoryHistorianProvider(new InMemoryHistorianOptions { // This history is seeded once rather than continuously captured. RawDataRetentionPeriod = TimeSpan.Zero }, m_timeProvider); } /// /// The number of nodes being monitored. /// public int MonitoredNodeCount { get { lock (m_lock) { if (m_monitoredNodes == null) { return 0; } return m_monitoredNodes.Count; } } } /// /// Gets or sets the current system status. /// public StatusCode SystemStatus { get { lock (m_lock) { return m_systemStatus; } } set { lock (m_lock) { m_systemStatus = value; } } } /// /// Gets the data generator used for random value generation. /// public Opc.Ua.Test.DataGenerator Generator { get; } /// /// The shared used by /// historizing variables created by this system. /// public InMemoryHistorianProvider Historian { get; } /// /// Creates an archive for the variable and seeds initial samples. /// /// is null. public void EnableHistoryArchiving(ServerSystemContext systemContext, BaseVariableState variable) { if (systemContext == null) { throw new ArgumentNullException(nameof(systemContext)); } if (variable == null) { return; } if (variable.ValueRank != ValueRanks.Scalar) { return; } BuiltInType dataType = TypeInfo.GetBuiltInType(variable.DataType); Historian.Register(variable.NodeId); SeedHistory(systemContext, variable.NodeId, dataType); } private void SeedHistory(ServerSystemContext systemContext, NodeId nodeId, BuiltInType dataType) { // Match the historic behaviour of the previous HistoryArchive // sample: ~1000 samples spaced 10 seconds apart leading up to now. DateTime now = m_timeProvider.GetUtcNow().UtcDateTime; var seed = new List(1001); for (int ii = 1000; ii >= 0; ii--) { Variant variant = Variant.Null; if (dataType == BuiltInType.Int32) { variant = new Variant(1000 - ii); } seed.Add(new DataValue( variant, StatusCodes.Good, sourceTimestamp: now.AddSeconds(-(ii * 10)).AddMilliseconds(1234), serverTimestamp: now.AddSeconds(-(ii * 10)))); } using var opContext = new OperationContext(new RequestHeader(), null, RequestType.HistoryUpdate, RequestLifetime.None); var historianContext = new HistorianOperationContext(systemContext, opContext, null, HistoryUpdateType.Insert); // The TestDataNodeManager override is synchronous (CreateAddressSpace // runs under a lock), so we bridge the async InsertAsync via Task.Run // to escape any captured SynchronizationContext that could otherwise // deadlock an async provider. _ = System.Threading.Tasks.Task.Run( () => Historian.InsertAsync(historianContext, nodeId, seed, default).AsTask()) .GetAwaiter().GetResult(); } /// /// Returns the historian provider; legacy method retained for /// backwards compatibility with sample-server callers (will be /// removed in a future release). /// [Obsolete("Use the Historian property directly. Will be removed in a future release.")] public IHistorianProvider? GetHistoryFile(BaseVariableState variable) { return variable == null ? null : Historian; } /// /// Returns a new value for the variable. /// public Variant ReadValue(BaseVariableState variable) { lock (m_lock) { switch (variable.NumericId) { case Variables.ScalarValueObjectType_BooleanValue: case Variables.UserScalarValueObjectType_BooleanValue: return Generator.GetRandomBoolean(); case Variables.ScalarValueObjectType_SByteValue: case Variables.UserScalarValueObjectType_SByteValue: return Generator.GetRandomSByte(); case Variables.AnalogScalarValueObjectType_SByteValue: return (sbyte)(((int)(Generator.GetRandomUInt32() % 201)) - 100); case Variables.ScalarValueObjectType_ByteValue: case Variables.UserScalarValueObjectType_ByteValue: return Generator.GetRandomByte(); case Variables.AnalogScalarValueObjectType_ByteValue: return (byte)((Generator.GetRandomUInt32() % 201) + 50); case Variables.ScalarValueObjectType_Int16Value: case Variables.UserScalarValueObjectType_Int16Value: return Generator.GetRandomInt16(); case Variables.AnalogScalarValueObjectType_Int16Value: return (short)(((int)(Generator.GetRandomUInt32() % 201)) - 100); case Variables.ScalarValueObjectType_UInt16Value: case Variables.UserScalarValueObjectType_UInt16Value: return Generator.GetRandomUInt16(); case Variables.AnalogScalarValueObjectType_UInt16Value: return Variant.From((ushort)((Generator.GetRandomUInt32() % 201) + 50)); case Variables.ScalarValueObjectType_Int32Value: case Variables.UserScalarValueObjectType_Int32Value: return Generator.GetRandomInt32(); case Variables.AnalogScalarValueObjectType_Int32Value: case Variables.AnalogScalarValueObjectType_IntegerValue: return Variant.From(((int)(Generator.GetRandomUInt32() % 201)) - 100); case Variables.ScalarValueObjectType_UInt32Value: case Variables.UserScalarValueObjectType_UInt32Value: return Generator.GetRandomUInt32(); case Variables.AnalogScalarValueObjectType_UInt32Value: case Variables.AnalogScalarValueObjectType_UIntegerValue: return Variant.From((Generator.GetRandomUInt32() % 201) + 50); case Variables.ScalarValueObjectType_Int64Value: case Variables.UserScalarValueObjectType_Int64Value: return Generator.GetRandomInt64(); case Variables.AnalogScalarValueObjectType_Int64Value: return Variant.From((long)(((int)(Generator.GetRandomUInt32() % 201)) - 100)); case Variables.ScalarValueObjectType_UInt64Value: case Variables.UserScalarValueObjectType_UInt64Value: return Generator.GetRandomUInt64(); case Variables.AnalogScalarValueObjectType_UInt64Value: return Variant.From((ulong)((Generator.GetRandomUInt32() % 201) + 50)); case Variables.ScalarValueObjectType_FloatValue: case Variables.UserScalarValueObjectType_FloatValue: return Generator.GetRandomFloat(); case Variables.AnalogScalarValueObjectType_FloatValue: return Variant.From((float)(((int)(Generator.GetRandomUInt32() % 201)) - 100)); case Variables.ScalarValueObjectType_DoubleValue: case Variables.UserScalarValueObjectType_DoubleValue: return Generator.GetRandomDouble(); case Variables.AnalogScalarValueObjectType_DoubleValue: case Variables.AnalogScalarValueObjectType_NumberValue: return Variant.From((double)(((int)(Generator.GetRandomUInt32() % 201)) - 100)); case Variables.ScalarValueObjectType_StringValue: case Variables.UserScalarValueObjectType_StringValue: return Generator.GetRandomString(); case Variables.ScalarValueObjectType_DateTimeValue: case Variables.UserScalarValueObjectType_DateTimeValue: return Generator.GetRandomDateTime(); case Variables.ScalarValueObjectType_GuidValue: case Variables.UserScalarValueObjectType_GuidValue: return Generator.GetRandomGuid(); case Variables.ScalarValueObjectType_ByteStringValue: case Variables.UserScalarValueObjectType_ByteStringValue: return Generator.GetRandomByteString(); case Variables.ScalarValueObjectType_XmlElementValue: case Variables.UserScalarValueObjectType_XmlElementValue: return Generator.GetRandomXmlElement(); case Variables.ScalarValueObjectType_NodeIdValue: case Variables.UserScalarValueObjectType_NodeIdValue: return Generator.GetRandomNodeId(); case Variables.ScalarValueObjectType_ExpandedNodeIdValue: case Variables.UserScalarValueObjectType_ExpandedNodeIdValue: return Generator.GetRandomExpandedNodeId(); case Variables.ScalarValueObjectType_QualifiedNameValue: case Variables.UserScalarValueObjectType_QualifiedNameValue: return Generator.GetRandomQualifiedName(); case Variables.ScalarValueObjectType_LocalizedTextValue: case Variables.UserScalarValueObjectType_LocalizedTextValue: return Generator.GetRandomLocalizedText(); case Variables.ScalarValueObjectType_StatusCodeValue: case Variables.UserScalarValueObjectType_StatusCodeValue: return Generator.GetRandomStatusCode(); case Variables.ScalarValueObjectType_VariantValue: case Variables.UserScalarValueObjectType_VariantValue: return Generator.GetRandomVariant(); case Variables.ScalarValueObjectType_StructureValue: return GetRandomStructure(); case Variables.ScalarValueObjectType_EnumerationValue: return Generator.GetRandomInt32(); case Variables.ScalarValueObjectType_NumberValue: return Generator.GetRandomScalar(BuiltInType.Number); case Variables.ScalarValueObjectType_IntegerValue: return Generator.GetRandomScalar(BuiltInType.Integer); case Variables.ScalarValueObjectType_UIntegerValue: return Generator.GetRandomScalar(BuiltInType.UInteger); case Variables.Data_Static_Structure_VectorStructure: case Variables.Data_Dynamic_Structure_VectorStructure: case Variables.StructureValueObjectType_VectorStructure: case Variables.ScalarValueObjectType_VectorValue: return Variant.FromStructure(GetRandomVector()); case Variables.ArrayValueObjectType_VectorValue: return Variant.FromStructure(GetRandomArray(GetRandomVector)); // VectorUnion - Scalar case Variables.ScalarValueObjectType_VectorUnionValue: return Variant.FromStructure(GetRandomVectorUnion()); // VectorUnion - Array case Variables.ArrayValueObjectType_VectorUnionValue: return Variant.FromStructure(GetRandomArray(GetRandomVectorUnion)); // VectorWithOptionalFields - Scalar case Variables.ScalarValueObjectType_VectorWithOptionalFieldsValue: return Variant.FromStructure(GetRandomVectorWithOptionalFields()); // VectorWithOptionalFields - Array case Variables.ArrayValueObjectType_VectorWithOptionalFieldsValue: return Variant.FromStructure(GetRandomArray(GetRandomVectorWithOptionalFields)); // MultipleVectors - Scalar case Variables.ScalarValueObjectType_MultipleVectorsValue: return Variant.FromStructure(GetRandomMultipleVectors()); // MultipleVectors - Array case Variables.ArrayValueObjectType_MultipleVectorsValue: return Variant.FromStructure(GetRandomArray(GetRandomMultipleVectors)); case Variables.ArrayValueObjectType_BooleanValue: case Variables.UserArrayValueObjectType_BooleanValue: return Generator.GetRandomBooleanArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_SByteValue: case Variables.UserArrayValueObjectType_SByteValue: return Generator.GetRandomSByteArray(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_SByteValue: { sbyte[] values = Generator.GetRandomSByteArray(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (sbyte)(((int)(Generator.GetRandomUInt32() % 201)) - 100); } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_ByteValue: case Variables.UserArrayValueObjectType_ByteValue: return Generator.GetRandomByteArray(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_ByteValue: { byte[] values = Generator.GetRandomByteArray(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (byte)((Generator.GetRandomUInt32() % 201) + 50); } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_Int16Value: case Variables.UserArrayValueObjectType_Int16Value: return Generator.GetRandomInt16Array(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_Int16Value: { short[] values = Generator.GetRandomInt16Array(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (short)(((int)(Generator.GetRandomUInt32() % 201)) - 100); } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_UInt16Value: case Variables.UserArrayValueObjectType_UInt16Value: return Generator.GetRandomUInt16Array(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_UInt16Value: { ushort[] values = Generator.GetRandomUInt16Array(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (ushort)((Generator.GetRandomUInt32() % 201) + 50); } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_Int32Value: case Variables.UserArrayValueObjectType_Int32Value: return Generator.GetRandomInt32Array(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_Int32Value: case Variables.AnalogArrayValueObjectType_IntegerValue: { int[] values = Generator.GetRandomInt32Array(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = ((int)(Generator.GetRandomUInt32() % 201)) - 100; } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_UInt32Value: case Variables.UserArrayValueObjectType_UInt32Value: return Generator.GetRandomUInt32Array(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_UInt32Value: case Variables.AnalogArrayValueObjectType_UIntegerValue: { uint[] values = Generator.GetRandomUInt32Array(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (Generator.GetRandomUInt32() % 201) + 50; } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_Int64Value: case Variables.UserArrayValueObjectType_Int64Value: return Generator.GetRandomInt64Array(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_Int64Value: { long[] values = Generator.GetRandomInt64Array(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = ((int)(Generator.GetRandomUInt32() % 201)) - 100; } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_UInt64Value: case Variables.UserArrayValueObjectType_UInt64Value: return Generator.GetRandomUInt64Array(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_UInt64Value: { ulong[] values = Generator.GetRandomUInt64Array(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (Generator.GetRandomUInt32() % 201) + 50; } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_FloatValue: case Variables.UserArrayValueObjectType_FloatValue: return Generator.GetRandomFloatArray(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_FloatValue: { float[] values = Generator.GetRandomFloatArray(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = ((int)(Generator.GetRandomUInt32() % 201)) - 100; } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_DoubleValue: case Variables.UserArrayValueObjectType_DoubleValue: return Generator.GetRandomDoubleArray(false, 100, false).ToArrayOf(); case Variables.AnalogArrayValueObjectType_DoubleValue: case Variables.AnalogArrayValueObjectType_NumberValue: { double[] values = Generator.GetRandomDoubleArray(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = ((int)(Generator.GetRandomUInt32() % 201)) - 100; } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_StringValue: case Variables.UserArrayValueObjectType_StringValue: return Generator.GetRandomStringArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_DateTimeValue: case Variables.UserArrayValueObjectType_DateTimeValue: return Generator.GetRandomDateTimeArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_GuidValue: case Variables.UserArrayValueObjectType_GuidValue: return Generator.GetRandomGuidArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_ByteStringValue: case Variables.UserArrayValueObjectType_ByteStringValue: return Generator.GetRandomByteStringArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_XmlElementValue: case Variables.UserArrayValueObjectType_XmlElementValue: return Generator.GetRandomXmlElementArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_NodeIdValue: case Variables.UserArrayValueObjectType_NodeIdValue: return Generator.GetRandomNodeIdArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_ExpandedNodeIdValue: case Variables.UserArrayValueObjectType_ExpandedNodeIdValue: return Generator.GetRandomExpandedNodeIdArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_QualifiedNameValue: case Variables.UserArrayValueObjectType_QualifiedNameValue: return Generator.GetRandomQualifiedNameArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_LocalizedTextValue: case Variables.UserArrayValueObjectType_LocalizedTextValue: return Generator.GetRandomLocalizedTextArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_StatusCodeValue: case Variables.UserArrayValueObjectType_StatusCodeValue: return Generator.GetRandomStatusCodeArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_VariantValue: case Variables.UserArrayValueObjectType_VariantValue: return Generator.GetRandomVariantArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_StructureValue: { ExtensionObject[] values = Generator.GetRandomExtensionObjectArray( false, 10, false); for (int ii = 0; values != null && ii < values.Length; ii++) { values[ii] = GetRandomStructure(); } return values.ToArrayOf(); } case Variables.ArrayValueObjectType_EnumerationValue: return Generator.GetRandomInt32Array(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_NumberValue: return Generator.GetRandomNumberArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_IntegerValue: return Generator.GetRandomIntegerArray(false, 100, false).ToArrayOf(); case Variables.ArrayValueObjectType_UIntegerValue: return Generator.GetRandomUIntegerArray(false, 100, false).ToArrayOf(); case Variables.Data_Static_Structure_ScalarStructure: case Variables.Data_Dynamic_Structure_ScalarStructure: case Variables.StructureValueObjectType_ScalarStructure: return Variant.FromStructure(GetRandomScalarStructureDataType()); case Variables.Data_Static_Structure_ScalarStructure_BooleanValue: case Variables.Data_Dynamic_Structure_ScalarStructure_BooleanValue: return Generator.GetRandomBoolean(); case Variables.Data_Static_Structure_ScalarStructure_SByteValue: case Variables.Data_Dynamic_Structure_ScalarStructure_SByteValue: return Generator.GetRandomSByte(); case Variables.Data_Static_Structure_ScalarStructure_ByteValue: case Variables.Data_Dynamic_Structure_ScalarStructure_ByteValue: return Generator.GetRandomByte(); case Variables.Data_Static_Structure_ScalarStructure_Int16Value: case Variables.Data_Dynamic_Structure_ScalarStructure_Int16Value: return Generator.GetRandomInt16(); case Variables.Data_Static_Structure_ScalarStructure_UInt16Value: case Variables.Data_Dynamic_Structure_ScalarStructure_UInt16Value: return Generator.GetRandomUInt16(); case Variables.Data_Static_Structure_ScalarStructure_Int32Value: case Variables.Data_Dynamic_Structure_ScalarStructure_Int32Value: return Generator.GetRandomInt32(); case Variables.Data_Static_Structure_ScalarStructure_UInt32Value: case Variables.Data_Dynamic_Structure_ScalarStructure_UInt32Value: return Generator.GetRandomUInt32(); case Variables.Data_Static_Structure_ScalarStructure_Int64Value: case Variables.Data_Dynamic_Structure_ScalarStructure_Int64Value: return Generator.GetRandomInt64(); case Variables.Data_Static_Structure_ScalarStructure_UInt64Value: case Variables.Data_Dynamic_Structure_ScalarStructure_UInt64Value: return Generator.GetRandomUInt64(); case Variables.Data_Static_Structure_ScalarStructure_FloatValue: case Variables.Data_Dynamic_Structure_ScalarStructure_FloatValue: return Generator.GetRandomFloat(); case Variables.Data_Static_Structure_ScalarStructure_DoubleValue: case Variables.Data_Dynamic_Structure_ScalarStructure_DoubleValue: return Generator.GetRandomDouble(); case Variables.Data_Static_Structure_ScalarStructure_StringValue: case Variables.Data_Dynamic_Structure_ScalarStructure_StringValue: return Generator.GetRandomString(); case Variables.Data_Static_Structure_ScalarStructure_DateTimeValue: case Variables.Data_Dynamic_Structure_ScalarStructure_DateTimeValue: return Generator.GetRandomDateTime(); case Variables.Data_Static_Structure_ScalarStructure_GuidValue: case Variables.Data_Dynamic_Structure_ScalarStructure_GuidValue: return Generator.GetRandomGuid(); case Variables.Data_Static_Structure_ScalarStructure_ByteStringValue: case Variables.Data_Dynamic_Structure_ScalarStructure_ByteStringValue: return Generator.GetRandomByteString(); case Variables.Data_Static_Structure_ScalarStructure_XmlElementValue: case Variables.Data_Dynamic_Structure_ScalarStructure_XmlElementValue: return Generator.GetRandomXmlElement(); case Variables.Data_Static_Structure_ScalarStructure_NodeIdValue: case Variables.Data_Dynamic_Structure_ScalarStructure_NodeIdValue: return Generator.GetRandomNodeId(); case Variables.Data_Static_Structure_ScalarStructure_ExpandedNodeIdValue: case Variables.Data_Dynamic_Structure_ScalarStructure_ExpandedNodeIdValue: return Generator.GetRandomExpandedNodeId(); case Variables.Data_Static_Structure_ScalarStructure_QualifiedNameValue: case Variables.Data_Dynamic_Structure_ScalarStructure_QualifiedNameValue: return Generator.GetRandomQualifiedName(); case Variables.Data_Static_Structure_ScalarStructure_LocalizedTextValue: case Variables.Data_Dynamic_Structure_ScalarStructure_LocalizedTextValue: return Generator.GetRandomLocalizedText(); case Variables.Data_Static_Structure_ScalarStructure_StatusCodeValue: case Variables.Data_Dynamic_Structure_ScalarStructure_StatusCodeValue: return Generator.GetRandomStatusCode(); case Variables.Data_Static_Structure_ScalarStructure_VariantValue: case Variables.Data_Dynamic_Structure_ScalarStructure_VariantValue: return Generator.GetRandomVariant(); case Variables.Data_Static_Structure_ScalarStructure_EnumerationValue: case Variables.Data_Dynamic_Structure_ScalarStructure_EnumerationValue: return Generator.GetRandomByte(); case Variables.Data_Static_Structure_ScalarStructure_StructureValue: case Variables.Data_Dynamic_Structure_ScalarStructure_StructureValue: return GetRandomStructure(); case Variables.Data_Static_Structure_ScalarStructure_NumberValue: case Variables.Data_Dynamic_Structure_ScalarStructure_NumberValue: return Generator.GetRandomNumber(); case Variables.Data_Static_Structure_ScalarStructure_IntegerValue: case Variables.Data_Dynamic_Structure_ScalarStructure_IntegerValue: return Generator.GetRandomInteger(); case Variables.Data_Static_Structure_ScalarStructure_UIntegerValue: case Variables.Data_Dynamic_Structure_ScalarStructure_UIntegerValue: return Generator.GetRandomUInteger(); default: return Variant.Null; } } } /// /// Gets a random Array (one to eight elements). /// /// The type of the elements /// Method, to create a single element private ArrayOf GetRandomArray(Func methodForSingleObject) { int size = (Generator.GetRandomByte() % 8) + 1; var result = new T[size]; for (int ii = 0; ii < size; ii++) { result[ii] = methodForSingleObject(); } return result; } /// /// Return random vector. /// public Vector GetRandomVector() { return new Vector { X = Generator.GetRandomDouble(), Y = Generator.GetRandomDouble(), Z = Generator.GetRandomDouble() }; } public VectorUnion GetRandomVectorUnion() { return new VectorUnion { SwitchField = (VectorUnionFields)(Generator.GetRandomUInt16() % 4), X = Generator.GetRandomDouble(), Y = Generator.GetRandomDouble(), Z = Generator.GetRandomDouble() }; } public VectorWithOptionalFields GetRandomVectorWithOptionalFields() { VectorWithOptionalFieldsFields encodingMask = VectorWithOptionalFieldsFields.None; if (Generator.GetRandomBoolean()) { encodingMask |= VectorWithOptionalFieldsFields.X; } if (Generator.GetRandomBoolean()) { encodingMask |= VectorWithOptionalFieldsFields.Y; } if (Generator.GetRandomBoolean()) { encodingMask |= VectorWithOptionalFieldsFields.Z; } return new VectorWithOptionalFields { EncodingMask = (uint)encodingMask, X = Generator.GetRandomDouble(), Y = Generator.GetRandomDouble(), Z = Generator.GetRandomDouble() }; } public MultipleVectors GetRandomMultipleVectors() { return new MultipleVectors { Vector = GetRandomVector(), VectorUnion = GetRandomVectorUnion(), VectorWithOptionalFields = GetRandomVectorWithOptionalFields(), VectorArray = GetRandomArray(GetRandomVector), VectorUnionArray = GetRandomArray(GetRandomVectorUnion), VectorWithOptionalFieldsArray = GetRandomArray(GetRandomVectorWithOptionalFields) }; } /// /// Returns a random structure. /// private ExtensionObject GetRandomStructure() { if (Generator.GetRandomBoolean()) { ScalarStructureDataType value = GetRandomScalarStructureDataType(); return new ExtensionObject(value); } else { ArrayValueDataType value = GetRandomArrayValueDataType(); return new ExtensionObject(value); } } public ScalarStructureDataType GetRandomScalarStructureDataType() { return new ScalarStructureDataType { BooleanValue = Generator.GetRandomBoolean(), SByteValue = Generator.GetRandomSByte(), ByteValue = Generator.GetRandomByte(), Int16Value = Generator.GetRandomInt16(), UInt16Value = Generator.GetRandomUInt16(), Int32Value = Generator.GetRandomInt32(), UInt32Value = Generator.GetRandomUInt32(), Int64Value = Generator.GetRandomInt64(), UInt64Value = Generator.GetRandomUInt64(), FloatValue = Generator.GetRandomFloat(), DoubleValue = Generator.GetRandomDouble(), StringValue = Generator.GetRandomString(), DateTimeValue = Generator.GetRandomDateTime(), GuidValue = Generator.GetRandomGuid(), ByteStringValue = Generator.GetRandomByteString(), XmlElementValue = Generator.GetRandomXmlElement(), NodeIdValue = Generator.GetRandomNodeId(), ExpandedNodeIdValue = Generator.GetRandomExpandedNodeId(), QualifiedNameValue = Generator.GetRandomQualifiedName(), LocalizedTextValue = Generator.GetRandomLocalizedText(), StatusCodeValue = Generator.GetRandomStatusCode(), VariantValue = Generator.GetRandomVariant(), IntegerValue = Generator.GetRandomInteger(), UIntegerValue = Generator.GetRandomUInteger(), NumberValue = Generator.GetRandomNumber() }; } public ArrayValueDataType GetRandomArrayValueDataType() { return new ArrayValueDataType { BooleanValue = Generator.GetRandomBooleanArray(false, 10, false), SByteValue = Generator.GetRandomSByteArray(false, 10, false), ByteValue = Generator.GetRandomByteArray(false, 10, false), Int16Value = Generator.GetRandomInt16Array(false, 10, false), UInt16Value = Generator.GetRandomUInt16Array(false, 10, false), Int32Value = Generator.GetRandomInt32Array(false, 10, false), UInt32Value = Generator.GetRandomUInt32Array(false, 10, false), Int64Value = Generator.GetRandomInt64Array(false, 10, false), UInt64Value = Generator.GetRandomUInt64Array(false, 10, false), FloatValue = Generator.GetRandomFloatArray(false, 10, false), DoubleValue = Generator.GetRandomDoubleArray(false, 10, false), StringValue = Generator.GetRandomStringArray(false, 10, false), DateTimeValue = Generator.GetRandomDateTimeArray(false, 10, false), GuidValue = Generator.GetRandomGuidArray(false, 10, false), ByteStringValue = Generator.GetRandomByteStringArray(false, 10, false), XmlElementValue = Generator.GetRandomXmlElementArray(false, 10, false), NodeIdValue = Generator.GetRandomNodeIdArray(false, 10, false), ExpandedNodeIdValue = Generator.GetRandomExpandedNodeIdArray(false, 10, false), QualifiedNameValue = Generator.GetRandomQualifiedNameArray(false, 10, false), LocalizedTextValue = Generator.GetRandomLocalizedTextArray(false, 10, false), StatusCodeValue = Generator.GetRandomStatusCodeArray(false, 10, false), VariantValue = Generator.GetRandomVariantArray(false, 10, false) }; } public void StartMonitoringValue( uint monitoredItemId, double samplingInterval, BaseVariableState variable) { lock (m_lock) { m_monitoredNodes ??= []; m_monitoredNodes[monitoredItemId] = variable; m_samplingNodes = null; SetSamplingInterval(samplingInterval); } } public void SetSamplingInterval(double samplingInterval) { lock (m_lock) { if (samplingInterval < 0) { // m_samplingEvent.Set(); m_minimumSamplingInterval = int.MaxValue; m_timer?.Dispose(); m_timer = null; return; } if (m_minimumSamplingInterval > samplingInterval) { m_minimumSamplingInterval = (int)samplingInterval; if (m_minimumSamplingInterval < 100) { m_minimumSamplingInterval = 100; } m_timer?.Dispose(); m_timer = null; m_timer = m_timeProvider.CreateTimer( DoSample, null, TimeSpan.FromMilliseconds(m_minimumSamplingInterval), TimeSpan.FromMilliseconds(m_minimumSamplingInterval)); } } } private void DoSample(object? state) { if (m_logger.IsEnabled(LogLevel.Trace)) { m_logger.DoSample( m_timeProvider.GetUtcNow().UtcDateTime, DateTime.UtcNow); } var samples = new Queue(); var generateValues = new List(); lock (m_lock) { if (m_monitoredNodes == null) { return; } m_samplingNodes ??= [ .. m_monitoredNodes.Values.Distinct(new NodeStateComparer()) .Cast() ]; foreach (BaseVariableState variable in m_samplingNodes) { if (variable is ITestDataSystemValuesGenerator) { generateValues.Add(variable); } else if (variable.Parent is ITestDataSystemValuesGenerator and BaseVariableState parentVariable) { generateValues.Add(parentVariable); } else { Variant value = ReadValue(variable); if (!value.IsNull) { var sample = new Sample { Variable = variable, Value = value, StatusCode = StatusCodes.Good, Timestamp = DateTime.UtcNow }; samples.Enqueue(sample); } } } } while (samples.Count > 0) { Sample sample = samples.Dequeue(); m_callback.OnDataChange( sample.Variable, sample.Value, sample.StatusCode, sample.Timestamp); } foreach (BaseVariableState generateValue in generateValues) { m_callback.OnGenerateValues(generateValue); } } public void StopMonitoringValue(uint monitoredItemId) { lock (m_lock) { if (m_monitoredNodes == null) { return; } m_monitoredNodes.Remove(monitoredItemId); m_samplingNodes = null; if (m_monitoredNodes.Count == 0) { SetSamplingInterval(-1); } } } private sealed class Sample { public BaseVariableState Variable = null!; public Variant Value; public StatusCode StatusCode; public DateTime Timestamp; } private readonly Lock m_lock = new(); private readonly ITestDataSystemCallback m_callback; private readonly ILogger m_logger; private int m_minimumSamplingInterval; private Dictionary? m_monitoredNodes; private IList? m_samplingNodes; private ITimer? m_timer; private readonly TimeProvider m_timeProvider; private StatusCode m_systemStatus; } internal static partial class TestDataSystemLog { [LoggerMessage( EventId = QuickstartsServersEventIds.TestDataSystem + 0, Level = LogLevel.Trace, Message = "DoSample HiRes={HiRes:ss.ffff} Now={CurrentTime:ss.ffff}")] public static partial void DoSample(this ILogger logger, DateTime hiRes, DateTime currentTime); } }