/// <summary> /// Updates the variable after receiving a notification that it has changed in the underlying system. /// </summary> public void OnDataChange(BaseVariableState variable, object value, StatusCode statusCode, DateTime timestamp) { lock (Lock) { variable.Value = value; variable.StatusCode = statusCode; variable.Timestamp = timestamp; // notifies any monitored items that the value has changed. variable.ClearChangeMasks(SystemContext, false); } }
/// <summary> /// Initialzies the variable as a counter. /// </summary> protected void InitializeVariable(ISystemContext context, BaseVariableState variable, uint numericId) { variable.NumericId = numericId; // provide an implementation that produces a random value on each read. if (SimulationActive.Value) { variable.OnReadValue = DoDeviceRead; } // set a valid initial value. TestDataSystem system = context.SystemHandle as TestDataSystem; if (system != null) { GenerateValue(system, variable); } // allow writes if the simulation is not active. if (!SimulationActive.Value) { variable.AccessLevel = variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite; } // set the EU range. BaseVariableState euRange = variable.FindChild(context, Opc.Ua.BrowseNames.EURange) as BaseVariableState; if (euRange != null) { if (context.TypeTable.IsTypeOf(variable.DataType, Opc.Ua.DataTypeIds.UInteger)) { euRange.Value = new Range(250, 50); } else { euRange.Value = new Range(100, -100); } } variable.OnSimpleWriteValue = OnWriteAnalogValue; }
public override void SetNode(BaseVariableState node) { m_node = node; m_inode = node; }
private object GetNewValue(BaseVariableState variable) { if (m_generator == null) { m_generator = new Opc.Ua.Test.DataGenerator(null); m_generator.BoundaryValueFrequency = 0; } object value = null; while (value == null) { value = m_generator.GetRandom(variable.DataType, variable.ValueRank, new uint[] { 10 }, Server.TypeTree); } return value; }
/// <summary> /// Updates a variable from a tag. /// </summary> /// <param name="context">The context.</param> /// <param name="tag">The tag.</param> /// <param name="variable">The variable to update.</param> private void UpdateVariable(ISystemContext context, UnderlyingSystemTag tag, BaseVariableState variable) { variable.Description = tag.Description; variable.Value = tag.Value; variable.Timestamp = tag.Timestamp; switch (tag.DataType) { case UnderlyingSystemDataType.Integer1: { variable.DataType = DataTypes.SByte; break; } case UnderlyingSystemDataType.Integer2: { variable.DataType = DataTypes.Int16; break; } case UnderlyingSystemDataType.Integer4: { variable.DataType = DataTypes.Int32; break; } case UnderlyingSystemDataType.Real4: { variable.DataType = DataTypes.Float; break; } case UnderlyingSystemDataType.String: { variable.DataType = DataTypes.String; break; } } variable.ValueRank = ValueRanks.Scalar; variable.ArrayDimensions = null; if (tag.IsWriteable) { variable.AccessLevel = AccessLevels.CurrentReadOrWrite; variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite; } else { variable.AccessLevel = AccessLevels.CurrentRead; variable.UserAccessLevel = AccessLevels.CurrentRead; } variable.MinimumSamplingInterval = MinimumSamplingIntervals.Continuous; variable.Historizing = false; switch (tag.TagType) { case UnderlyingSystemTagType.Analog: { AnalogItemState node = variable as AnalogItemState; if (tag.EuRange != null) { if (tag.EuRange.Length >= 2 && node.EURange != null) { Range range = new Range(tag.EuRange[0], tag.EuRange[1]); node.EURange.Value = range; node.EURange.Timestamp = tag.Block.Timestamp; } if (tag.EuRange.Length >= 4 && node.InstrumentRange != null) { Range range = new Range(tag.EuRange[2], tag.EuRange[3]); node.InstrumentRange.Value = range; node.InstrumentRange.Timestamp = tag.Block.Timestamp; } } if (!String.IsNullOrEmpty(tag.EngineeringUnits) && node.EngineeringUnits != null) { EUInformation info = new EUInformation(); info.DisplayName = tag.EngineeringUnits; info.NamespaceUri = Namespaces.HistoricalAccess; node.EngineeringUnits.Value = info; node.EngineeringUnits.Timestamp = tag.Block.Timestamp; } break; } case UnderlyingSystemTagType.Digital: { TwoStateDiscreteState node = variable as TwoStateDiscreteState; if (tag.Labels != null && node.TrueState != null && node.FalseState != null) { if (tag.Labels.Length >= 2) { node.TrueState.Value = new LocalizedText(tag.Labels[0]); node.TrueState.Timestamp = tag.Block.Timestamp; node.FalseState.Value = new LocalizedText(tag.Labels[1]); node.FalseState.Timestamp = tag.Block.Timestamp; } } break; } case UnderlyingSystemTagType.Enumerated: { MultiStateDiscreteState node = variable as MultiStateDiscreteState; if (tag.Labels != null) { LocalizedText[] strings = new LocalizedText[tag.Labels.Length]; for (int ii = 0; ii < tag.Labels.Length; ii++) { strings[ii] = new LocalizedText(tag.Labels[ii]); } node.EnumStrings.Value = strings; node.EnumStrings.Timestamp = tag.Block.Timestamp; } break; } } }
/// <summary> /// Reads the history for the variable value. /// </summary> protected virtual ServiceResult HistoryReadAtTime( ISystemContext context, BaseVariableState source, ReadAtTimeDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueId nodeToRead, HistoryReadResult result) { return StatusCodes.BadHistoryOperationUnsupported; }
/// <summary> /// Generates a new value for the variable. /// </summary> protected void GenerateValue(TestDataSystem system, BaseVariableState variable) { variable.Value = system.ReadValue(variable); variable.Timestamp = DateTime.UtcNow; variable.StatusCode = StatusCodes.Good; }
/// <summary> /// Returns the history data source for a node. /// </summary> protected virtual ServiceResult GetHistoryDataSource( Opc.Ua.Server.ServerSystemContext context, BaseVariableState variable, out IHistoryDataSource datasource) { datasource = m_system.GetHistoryFile(variable); if (datasource == null) { return StatusCodes.BadNotReadable; } return ServiceResult.Good; }
/// <summary> /// Creates an archive for the variable. /// </summary> public void EnableHistoryArchiving(BaseVariableState variable) { if (variable == null) { return; } if (variable.ValueRank == ValueRanks.Scalar) { m_historyArchive.CreateRecord(variable.NodeId, TypeInfo.GetBuiltInType(variable.DataType)); } }
/// <summary> /// Returns a new value for the variable. /// </summary> public object ReadValue(BaseVariableState variable) { lock (m_lock) { switch (variable.NumericId) { case TestData.Variables.ScalarValueObjectType_BooleanValue: case TestData.Variables.UserScalarValueObjectType_BooleanValue: { return m_generator.GetRandom<bool>(false); } case TestData.Variables.ScalarValueObjectType_SByteValue: case TestData.Variables.UserScalarValueObjectType_SByteValue: { return m_generator.GetRandom<sbyte>(false); } case TestData.Variables.AnalogScalarValueObjectType_SByteValue: { return (sbyte)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } case TestData.Variables.ScalarValueObjectType_ByteValue: case TestData.Variables.UserScalarValueObjectType_ByteValue: { return m_generator.GetRandom<byte>(false); } case TestData.Variables.AnalogScalarValueObjectType_ByteValue: { return (byte)((m_generator.GetRandom<uint>(false)%201) + 50); } case TestData.Variables.ScalarValueObjectType_Int16Value: case TestData.Variables.UserScalarValueObjectType_Int16Value: { return m_generator.GetRandom<short>(false); } case TestData.Variables.AnalogScalarValueObjectType_Int16Value: { return (short)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } case TestData.Variables.ScalarValueObjectType_UInt16Value: case TestData.Variables.UserScalarValueObjectType_UInt16Value: { return m_generator.GetRandom<ushort>(false); } case TestData.Variables.AnalogScalarValueObjectType_UInt16Value: { return (ushort)((m_generator.GetRandom<uint>(false)%201) + 50); } case TestData.Variables.ScalarValueObjectType_Int32Value: case TestData.Variables.UserScalarValueObjectType_Int32Value: { return m_generator.GetRandom<int>(false); } case TestData.Variables.AnalogScalarValueObjectType_Int32Value: case TestData.Variables.AnalogScalarValueObjectType_IntegerValue: { return (int)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } case TestData.Variables.ScalarValueObjectType_UInt32Value: case TestData.Variables.UserScalarValueObjectType_UInt32Value: { return m_generator.GetRandom<uint>(false); } case TestData.Variables.AnalogScalarValueObjectType_UInt32Value: case TestData.Variables.AnalogScalarValueObjectType_UIntegerValue: { return (uint)((m_generator.GetRandom<uint>(false)%201) + 50); } case TestData.Variables.ScalarValueObjectType_Int64Value: case TestData.Variables.UserScalarValueObjectType_Int64Value: { return m_generator.GetRandom<long>(false); } case TestData.Variables.AnalogScalarValueObjectType_Int64Value: { return (long)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } case TestData.Variables.ScalarValueObjectType_UInt64Value: case TestData.Variables.UserScalarValueObjectType_UInt64Value: { return m_generator.GetRandom<ulong>(false); } case TestData.Variables.AnalogScalarValueObjectType_UInt64Value: { return (ulong)((m_generator.GetRandom<uint>(false)%201) + 50); } case TestData.Variables.ScalarValueObjectType_FloatValue: case TestData.Variables.UserScalarValueObjectType_FloatValue: { return m_generator.GetRandom<float>(false); } case TestData.Variables.AnalogScalarValueObjectType_FloatValue: { return (float)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } case TestData.Variables.ScalarValueObjectType_DoubleValue: case TestData.Variables.UserScalarValueObjectType_DoubleValue: { return m_generator.GetRandom<double>(false); } case TestData.Variables.AnalogScalarValueObjectType_DoubleValue: case TestData.Variables.AnalogScalarValueObjectType_NumberValue: { return (double)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } case TestData.Variables.ScalarValueObjectType_StringValue: case TestData.Variables.UserScalarValueObjectType_StringValue: { return m_generator.GetRandom<string>(false); } case TestData.Variables.ScalarValueObjectType_DateTimeValue: case TestData.Variables.UserScalarValueObjectType_DateTimeValue: { return m_generator.GetRandom<DateTime>(false); } case TestData.Variables.ScalarValueObjectType_GuidValue: case TestData.Variables.UserScalarValueObjectType_GuidValue: { return m_generator.GetRandom<Guid>(false); } case TestData.Variables.ScalarValueObjectType_ByteStringValue: case TestData.Variables.UserScalarValueObjectType_ByteStringValue: { return m_generator.GetRandom<byte[]>(false); } case TestData.Variables.ScalarValueObjectType_XmlElementValue: case TestData.Variables.UserScalarValueObjectType_XmlElementValue: { return m_generator.GetRandom<XmlElement>(false); } case TestData.Variables.ScalarValueObjectType_NodeIdValue: case TestData.Variables.UserScalarValueObjectType_NodeIdValue: { return m_generator.GetRandom<Opc.Ua.NodeId>(false); } case TestData.Variables.ScalarValueObjectType_ExpandedNodeIdValue: case TestData.Variables.UserScalarValueObjectType_ExpandedNodeIdValue: { return m_generator.GetRandom<ExpandedNodeId>(false); } case TestData.Variables.ScalarValueObjectType_QualifiedNameValue: case TestData.Variables.UserScalarValueObjectType_QualifiedNameValue: { return m_generator.GetRandom<QualifiedName>(false); } case TestData.Variables.ScalarValueObjectType_LocalizedTextValue: case TestData.Variables.UserScalarValueObjectType_LocalizedTextValue: { return m_generator.GetRandom<LocalizedText>(false); } case TestData.Variables.ScalarValueObjectType_StatusCodeValue: case TestData.Variables.UserScalarValueObjectType_StatusCodeValue: { return m_generator.GetRandom<StatusCode>(false); } case TestData.Variables.ScalarValueObjectType_VariantValue: case TestData.Variables.UserScalarValueObjectType_VariantValue: { return m_generator.GetRandomVariant(false).Value; } case TestData.Variables.ScalarValueObjectType_StructureValue: { return GetRandomStructure(); } case TestData.Variables.ScalarValueObjectType_EnumerationValue: { return m_generator.GetRandom<int>(false); } case TestData.Variables.ScalarValueObjectType_NumberValue: { return m_generator.GetRandom(BuiltInType.Number); } case TestData.Variables.ScalarValueObjectType_IntegerValue: { return m_generator.GetRandom(BuiltInType.Integer); } case TestData.Variables.ScalarValueObjectType_UIntegerValue: { return m_generator.GetRandom(BuiltInType.UInteger); } case TestData.Variables.ArrayValueObjectType_BooleanValue: case TestData.Variables.UserArrayValueObjectType_BooleanValue: { return m_generator.GetRandomArray<bool>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_SByteValue: case TestData.Variables.UserArrayValueObjectType_SByteValue: { return m_generator.GetRandomArray<sbyte>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_SByteValue: { sbyte[] values = m_generator.GetRandomArray<sbyte>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (sbyte)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } return values; } case TestData.Variables.ArrayValueObjectType_ByteValue: case TestData.Variables.UserArrayValueObjectType_ByteValue: { return m_generator.GetRandomArray<byte>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_ByteValue: { byte[] values = m_generator.GetRandomArray<byte>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (byte)((m_generator.GetRandom<uint>(false)%201) + 50); } return values; } case TestData.Variables.ArrayValueObjectType_Int16Value: case TestData.Variables.UserArrayValueObjectType_Int16Value: { return m_generator.GetRandomArray<short>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_Int16Value: { short[] values = m_generator.GetRandomArray<short>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (short)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } return values; } case TestData.Variables.ArrayValueObjectType_UInt16Value: case TestData.Variables.UserArrayValueObjectType_UInt16Value: { return m_generator.GetRandomArray<ushort>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_UInt16Value: { ushort[] values = m_generator.GetRandomArray<ushort>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (ushort)((m_generator.GetRandom<uint>(false)%201) + 50); } return values; } case TestData.Variables.ArrayValueObjectType_Int32Value: case TestData.Variables.UserArrayValueObjectType_Int32Value: { return m_generator.GetRandomArray<int>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_Int32Value: case TestData.Variables.AnalogArrayValueObjectType_IntegerValue: { int[] values = m_generator.GetRandomArray<int>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (int)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } return values; } case TestData.Variables.ArrayValueObjectType_UInt32Value: case TestData.Variables.UserArrayValueObjectType_UInt32Value: { return m_generator.GetRandomArray<uint>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_UInt32Value: case TestData.Variables.AnalogArrayValueObjectType_UIntegerValue: { uint[] values = m_generator.GetRandomArray<uint>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (uint)((m_generator.GetRandom<uint>(false)%201) + 50); } return values; } case TestData.Variables.ArrayValueObjectType_Int64Value: case TestData.Variables.UserArrayValueObjectType_Int64Value: { return m_generator.GetRandomArray<long>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_Int64Value: { long[] values = m_generator.GetRandomArray<long>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (long)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } return values; } case TestData.Variables.ArrayValueObjectType_UInt64Value: case TestData.Variables.UserArrayValueObjectType_UInt64Value: { return m_generator.GetRandomArray<ulong>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_UInt64Value: { ulong[] values = m_generator.GetRandomArray<ulong>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (ulong)((m_generator.GetRandom<uint>(false)%201) + 50); } return values; } case TestData.Variables.ArrayValueObjectType_FloatValue: case TestData.Variables.UserArrayValueObjectType_FloatValue: { return m_generator.GetRandomArray<float>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_FloatValue: { float[] values = m_generator.GetRandomArray<float>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (float)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } return values; } case TestData.Variables.ArrayValueObjectType_DoubleValue: case TestData.Variables.UserArrayValueObjectType_DoubleValue: { return m_generator.GetRandomArray<double>(false, 100, false); } case TestData.Variables.AnalogArrayValueObjectType_DoubleValue: case TestData.Variables.AnalogArrayValueObjectType_NumberValue: { double[] values = m_generator.GetRandomArray<double>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (double)(((int)(m_generator.GetRandom<uint>(false)%201)) - 100); } return values; } case TestData.Variables.ArrayValueObjectType_StringValue: case TestData.Variables.UserArrayValueObjectType_StringValue: { return m_generator.GetRandomArray<string>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_DateTimeValue: case TestData.Variables.UserArrayValueObjectType_DateTimeValue: { return m_generator.GetRandomArray<DateTime>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_GuidValue: case TestData.Variables.UserArrayValueObjectType_GuidValue: { return m_generator.GetRandomArray<Guid>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_ByteStringValue: case TestData.Variables.UserArrayValueObjectType_ByteStringValue: { return m_generator.GetRandomArray<byte[]>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_XmlElementValue: case TestData.Variables.UserArrayValueObjectType_XmlElementValue: { return m_generator.GetRandomArray<XmlElement>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_NodeIdValue: case TestData.Variables.UserArrayValueObjectType_NodeIdValue: { return m_generator.GetRandomArray<Opc.Ua.NodeId>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_ExpandedNodeIdValue: case TestData.Variables.UserArrayValueObjectType_ExpandedNodeIdValue: { return m_generator.GetRandomArray<ExpandedNodeId>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_QualifiedNameValue: case TestData.Variables.UserArrayValueObjectType_QualifiedNameValue: { return m_generator.GetRandomArray<QualifiedName>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_LocalizedTextValue: case TestData.Variables.UserArrayValueObjectType_LocalizedTextValue: { return m_generator.GetRandomArray<LocalizedText>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_StatusCodeValue: case TestData.Variables.UserArrayValueObjectType_StatusCodeValue: { return m_generator.GetRandomArray<StatusCode>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_VariantValue: case TestData.Variables.UserArrayValueObjectType_VariantValue: { return m_generator.GetRandomArray<object>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_StructureValue: { ExtensionObject[] values = m_generator.GetRandomArray<ExtensionObject>(false, 10, false); for (int ii = 0; values != null && ii < values.Length; ii++) { values[ii] = GetRandomStructure(); } return values; } case TestData.Variables.ArrayValueObjectType_EnumerationValue: { return m_generator.GetRandomArray<int>(false, 100, false); } case TestData.Variables.ArrayValueObjectType_NumberValue: { return m_generator.GetRandomArray(BuiltInType.Number, false, 100, false); } case TestData.Variables.ArrayValueObjectType_IntegerValue: { return m_generator.GetRandomArray(BuiltInType.Integer, false, 100, false); } case TestData.Variables.ArrayValueObjectType_UIntegerValue: { return m_generator.GetRandomArray(BuiltInType.UInteger, false, 100, false); } } return null; } }
/// <summary> /// Assigns the OPC value the instance of the <see cref="BaseVariableState"/>. /// </summary> /// <param name="value">The OPC DA value to be assigned.</param> /// <param name="node">The target node of the assign operation.</param> public static void AssignValue(ItemValue value, BaseVariableState node) { BuiltInType nt; try { bool isGood = StatusCode.IsGood(node.StatusCode); nt = TypeInfo.GetBuiltInType(node.DataType); node.Value = value.Value; //TODO Resolve the problem with Cast method if (value.TimestampSpecified) { node.Timestamp = value.Timestamp; } else { node.Timestamp = DateTime.Now; } if (value.QualitySpecified) { switch (value.Quality.QualityBits) { case qualityBits.good: node.StatusCode = StatusCodes.Good; break; case qualityBits.goodLocalOverride: node.StatusCode = StatusCodes.GoodLocalOverride; break; case qualityBits.bad: node.StatusCode = StatusCodes.Bad; break; case qualityBits.badConfigurationError: node.StatusCode = StatusCodes.BadConfigurationError; break; case qualityBits.badNotConnected: node.StatusCode = StatusCodes.BadNotConnected; break; case qualityBits.badDeviceFailure: node.StatusCode = StatusCodes.BadDeviceFailure; break; case qualityBits.badSensorFailure: node.StatusCode = StatusCodes.BadSensorFailure; break; case qualityBits.badLastKnownValue: node.StatusCode = StatusCodes.UncertainLastUsableValue; break; case qualityBits.badCommFailure: node.StatusCode = StatusCodes.BadCommunicationError; break; case qualityBits.badOutOfService: node.StatusCode = StatusCodes.BadOutOfService; break; case qualityBits.badWaitingForInitialData: node.StatusCode = StatusCodes.BadWaitingForInitialData; break; case qualityBits.uncertain: node.StatusCode = StatusCodes.Uncertain; break; case qualityBits.uncertainLastUsableValue: node.StatusCode = StatusCodes.UncertainLastUsableValue; break; case qualityBits.uncertainSensorNotAccurate: node.StatusCode = StatusCodes.UncertainSensorNotAccurate; break; case qualityBits.uncertainEUExceeded: node.StatusCode = StatusCodes.UncertainEngineeringUnitsExceeded; break; case qualityBits.uncertainSubNormal: node.StatusCode = StatusCodes.UncertainSubNormal; break; default: node.StatusCode = StatusCodes.BadInvalidArgument; break; } } else { node.StatusCode = StatusCodes.Uncertain; } } catch (Exception Ex) { node.Value = null; node.StatusCode = StatusCodes.BadDataTypeIdUnknown; string msg = "There is a problem {0}while assigning value to {1}"; TraceEvent.Tracer.TraceInformation(105, typeof(Helpers).Name, string.Format(msg, node.BrowseName, Ex.Message)); } node.ClearChangeMasks(null, true); }
/// <summary> /// Updates a variable from a tag. /// </summary> /// <param name="context">The context.</param> /// <param name="tag">The tag.</param> /// <param name="variable">The variable to update.</param> private void UpdateVariable(ISystemContext context, UnderlyingSystemTag tag, BaseVariableState variable) { System.Diagnostics.Contracts.Contract.Assume(context != null); variable.Description = tag.Description; variable.Value = tag.Value; variable.Timestamp = tag.Timestamp; switch (tag.DataType) { case UnderlyingSystemDataType.Integer1: { variable.DataType = DataTypes.SByte; break; } case UnderlyingSystemDataType.Integer2: { variable.DataType = DataTypes.Int16; break; } case UnderlyingSystemDataType.Integer4: { variable.DataType = DataTypes.Int32; break; } case UnderlyingSystemDataType.Real4: { variable.DataType = DataTypes.Float; break; } case UnderlyingSystemDataType.String: { variable.DataType = DataTypes.String; break; } } variable.ValueRank = ValueRanks.Scalar; variable.ArrayDimensions = null; if (tag.IsWriteable) { variable.AccessLevel = AccessLevels.CurrentReadOrWrite; variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite; } else { variable.AccessLevel = AccessLevels.CurrentRead; variable.UserAccessLevel = AccessLevels.CurrentRead; } variable.MinimumSamplingInterval = MinimumSamplingIntervals.Continuous; variable.Historizing = false; switch (tag.TagType) { case UnderlyingSystemTagType.Analog: { var node = variable as AnalogItemState; if (tag.EuRange != null) { if (tag.EuRange.Length >= 2 && node.EURange != null) { var range = new Range(tag.EuRange[0], tag.EuRange[1]); node.EURange.Value = range; node.EURange.Timestamp = tag.Block.Timestamp; } if (tag.EuRange.Length >= 4 && node.InstrumentRange != null) { var range = new Range(tag.EuRange[2], tag.EuRange[3]); node.InstrumentRange.Value = range; node.InstrumentRange.Timestamp = tag.Block.Timestamp; } } if (!string.IsNullOrEmpty(tag.EngineeringUnits) && node.EngineeringUnits != null) { var info = new EUInformation { DisplayName = tag.EngineeringUnits, NamespaceUri = Namespaces.DataAccess }; node.EngineeringUnits.Value = info; node.EngineeringUnits.Timestamp = tag.Block.Timestamp; } break; } case UnderlyingSystemTagType.Digital: { var node = variable as TwoStateDiscreteState; if (tag.Labels != null && node.TrueState != null && node.FalseState != null) { if (tag.Labels.Length >= 2) { node.TrueState.Value = new LocalizedText(tag.Labels[0]); node.TrueState.Timestamp = tag.Block.Timestamp; node.FalseState.Value = new LocalizedText(tag.Labels[1]); node.FalseState.Timestamp = tag.Block.Timestamp; } } break; } case UnderlyingSystemTagType.Enumerated: { var node = variable as MultiStateDiscreteState; if (tag.Labels != null) { var strings = new LocalizedText[tag.Labels.Length]; for (var ii = 0; ii < tag.Labels.Length; ii++) { strings[ii] = new LocalizedText(tag.Labels[ii]); } node.EnumStrings.Value = strings; node.EnumStrings.Timestamp = tag.Block.Timestamp; } break; } } }
/// <summary> /// Reads the raw data for a variable /// </summary> protected ServiceResult HistoryReadRaw( ISystemContext context, BaseVariableState source, ReadRawModifiedDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueId nodeToRead, HistoryReadResult result) { ServerSystemContext serverContext = context as ServerSystemContext; HistoryDataReader reader = null; HistoryData data = new HistoryData(); if (nodeToRead.ContinuationPoint != null && nodeToRead.ContinuationPoint.Length > 0) { // restore the continuation point. reader = RestoreDataReader(serverContext, nodeToRead.ContinuationPoint); if (reader == null) { return(StatusCodes.BadContinuationPointInvalid); } // node id must match previous node id. if (reader.VariableId != nodeToRead.NodeId) { Utils.SilentDispose(reader); return(StatusCodes.BadContinuationPointInvalid); } // check if releasing continuation points. if (releaseContinuationPoints) { Utils.SilentDispose(reader); return(ServiceResult.Good); } } else { // get the source for the variable. IHistoryDataSource datasource = null; ServiceResult error = GetHistoryDataSource(serverContext, source, out datasource); if (ServiceResult.IsBad(error)) { return(error); } // create a reader. reader = new HistoryDataReader(nodeToRead.NodeId, datasource); // start reading. reader.BeginReadRaw( serverContext, details, timestampsToReturn, nodeToRead.ParsedIndexRange, nodeToRead.DataEncoding, data.DataValues); } // continue reading data until done or max values reached. bool complete = reader.NextReadRaw( serverContext, timestampsToReturn, nodeToRead.ParsedIndexRange, nodeToRead.DataEncoding, data.DataValues); // save continuation point. if (!complete) { SaveDataReader(serverContext, reader); result.StatusCode = StatusCodes.GoodMoreData; } // return the dat. result.HistoryData = new ExtensionObject(data); return(result.StatusCode); }
public void addVariableConnection(BaseVariableState collectorNode, ReferenceDescription item, object clientIdentifier) { child_server[clientIdentifier].addConnection(collectorNode, item); }
/// <summary> /// Creates a new set of monitored items for a set of variables. /// </summary> /// <remarks> /// This method only handles data change subscriptions. Event subscriptions are created by the SDK. /// </remarks> protected override ServiceResult CreateMonitoredItem(ServerSystemContext context, NodeHandle handle, uint subscriptionId, double publishingInterval, DiagnosticsMasks diagnosticsMasks, TimestampsToReturn timestampsToReturn, MonitoredItemCreateRequest itemToCreate, ref long globalIdCounter, out MonitoringFilterResult filterResult, out IMonitoredItem monitoredItem) { filterResult = null; monitoredItem = null; // validate parameters. MonitoringParameters parameters = itemToCreate.RequestedParameters; // validate attribute. if (!Attributes.IsValid(handle.Node.NodeClass, itemToCreate.ItemToMonitor.AttributeId)) { return(StatusCodes.BadAttributeIdInvalid); } NodeState cachedNode = AddNodeToComponentCache(context, handle, handle.Node); // check if the node is already being monitored. MonitoredNode2 monitoredNode = null; if (!MonitoredNodes.TryGetValue(handle.Node.NodeId, out monitoredNode)) { MonitoredNodes[handle.Node.NodeId] = monitoredNode = new MonitoredNode2(this, cachedNode); } handle.Node = monitoredNode.Node; handle.MonitoredNode = monitoredNode; // create a globally unique identifier. uint monitoredItemId = Utils.IncrementIdentifier(ref globalIdCounter); // determine the sampling interval. double samplingInterval = itemToCreate.RequestedParameters.SamplingInterval; if (samplingInterval < 0) { samplingInterval = publishingInterval; } // ensure minimum sampling interval is not exceeded. if (itemToCreate.ItemToMonitor.AttributeId == Attributes.Value) { BaseVariableState variable = handle.Node as BaseVariableState; if (variable != null && samplingInterval < variable.MinimumSamplingInterval) { samplingInterval = variable.MinimumSamplingInterval; } } // put a large upper limit on sampling. if (samplingInterval == Double.MaxValue) { samplingInterval = 365 * 24 * 3600 * 1000.0; } // put an upper limit on queue size. uint queueSize = itemToCreate.RequestedParameters.QueueSize; if (queueSize > MaxQueueSize) { queueSize = MaxQueueSize; } // validate the monitoring filter. Range euRange = null; MonitoringFilter filterToUse = null; ServiceResult error = ValidateMonitoringFilter( context, handle, itemToCreate.ItemToMonitor.AttributeId, samplingInterval, queueSize, parameters.Filter, out filterToUse, out euRange, out filterResult); if (ServiceResult.IsBad(error)) { return(error); } // create the item. MonitoredItem datachangeItem = new ComMonitoredItem( Server, this, handle, subscriptionId, monitoredItemId, context.OperationContext.Session, itemToCreate.ItemToMonitor, diagnosticsMasks, timestampsToReturn, itemToCreate.MonitoringMode, itemToCreate.RequestedParameters.ClientHandle, filterToUse, filterToUse, euRange, samplingInterval, queueSize, itemToCreate.RequestedParameters.DiscardOldest, 0); // report the initial value. ReadInitialValue(context, handle, datachangeItem); // update monitored item list. monitoredItem = datachangeItem; // save the monitored item. MonitoredItems.Add(monitoredItemId, datachangeItem); monitoredNode.Add(datachangeItem); // report change. OnMonitoredItemCreated(context, handle, datachangeItem); return(error); }
/// <summary> /// Collects instance declarations nodes from with a type. /// </summary> public static void CollectInstanceDeclarations( Session session, ComNamespaceMapper mapper, NodeState node, AeEventAttribute parent, List <AeEventAttribute> instances, IDictionary <string, AeEventAttribute> map) { List <BaseInstanceState> children = new List <BaseInstanceState>(); node.GetChildren(session.SystemContext, children); if (children.Count == 0) { return; } // process the children. for (int ii = 0; ii < children.Count; ii++) { BaseInstanceState instance = children[ii]; // only interested in objects and variables. if (instance.NodeClass != NodeClass.Object && instance.NodeClass != NodeClass.Variable) { return; } // ignore instances without a modelling rule. if (NodeId.IsNull(instance.ModellingRuleId)) { return; } // create a new declaration. AeEventAttribute declaration = new AeEventAttribute(); declaration.RootTypeId = (parent != null)?parent.RootTypeId:node.NodeId; declaration.NodeId = (NodeId)instance.NodeId; declaration.BrowseName = instance.BrowseName; declaration.NodeClass = instance.NodeClass; declaration.Description = (instance.Description != null)?instance.Description.ToString():null; // get data type information. BaseVariableState variable = instance as BaseVariableState; if (variable != null) { declaration.DataType = variable.DataType; declaration.ValueRank = variable.ValueRank; if (!NodeId.IsNull(variable.DataType)) { declaration.BuiltInType = DataTypes.GetBuiltInType(declaration.DataType, session.TypeTree); } } if (!LocalizedText.IsNullOrEmpty(instance.DisplayName)) { declaration.DisplayName = instance.DisplayName.Text; } else { declaration.DisplayName = instance.BrowseName.Name; } if (parent != null) { declaration.BrowsePath = new QualifiedNameCollection(parent.BrowsePath); declaration.BrowsePathDisplayText = Utils.Format("{0}/{1}", parent.BrowsePathDisplayText, mapper.GetLocalBrowseName(instance.BrowseName)); declaration.DisplayPath = Utils.Format("{0}/{1}", parent.DisplayPath, instance.DisplayName); } else { declaration.BrowsePath = new QualifiedNameCollection(); declaration.BrowsePathDisplayText = Utils.Format("{0}", instance.BrowseName); declaration.DisplayPath = Utils.Format("{0}", instance.DisplayName); } declaration.BrowsePath.Add(instance.BrowseName); // check if reading an overridden declaration. AeEventAttribute overriden = null; if (map.TryGetValue(declaration.BrowsePathDisplayText, out overriden)) { declaration.OverriddenDeclaration = overriden; } map[declaration.BrowsePathDisplayText] = declaration; // only interested in variables. if (instance.NodeClass == NodeClass.Variable) { instances.Add(declaration); } // recusively build tree. CollectInstanceDeclarations(session, mapper, instance, declaration, instances, map); } }
/// <summary> /// Updates the display with a new value for a monitored variable. /// </summary> private void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { if (this.InvokeRequired) { this.BeginInvoke(new MonitoredItemNotificationEventHandler(MonitoredItem_Notification), monitoredItem, e); return; } try { EventFieldList notification = e.NotificationValue as EventFieldList; if (notification == null) { return; } // check the type of event. NodeId eventTypeId = FormUtils.FindEventType(monitoredItem, notification); // ignore unknown events. if (NodeId.IsNull(eventTypeId)) { return; } // check for refresh start. if (eventTypeId == ObjectTypeIds.RefreshStartEventType) { ConditionsLV.Items.Clear(); return; } // check for refresh end. if (eventTypeId == ObjectTypeIds.RefreshEndEventType) { return; } // construct the condition object. ConditionState condition = FormUtils.ConstructEvent( m_session, monitoredItem, notification, m_eventTypeMappings) as ConditionState; if (condition == null) { return; } ListViewItem item = null; item = new ListViewItem(String.Empty); item.SubItems.Add(String.Empty); // Condition item.SubItems.Add(String.Empty); // Branch item.SubItems.Add(String.Empty); // Type item.SubItems.Add(String.Empty); // Severity item.SubItems.Add(String.Empty); // Time item.SubItems.Add(String.Empty); // State item.SubItems.Add(String.Empty); // Message item.SubItems.Add(String.Empty); // Comment //ConditionsLV.Items.Add(item); // look up the condition type metadata in the local cache. INode type = m_session.NodeCache.Find(condition.TypeDefinitionId); // Source if (condition.SourceName != null) { item.SubItems[0].Text = Utils.Format("{0}", condition.SourceName.Value); } else { item.SubItems[0].Text = null; } // Condition if (condition.ConditionName != null) { item.SubItems[1].Text = Utils.Format("{0}", condition.ConditionName.Value); } else { item.SubItems[1].Text = null; } // Branch if (condition.BranchId != null && !NodeId.IsNull(condition.BranchId.Value)) { item.SubItems[2].Text = Utils.Format("{0}", condition.BranchId.Value); } else { item.SubItems[2].Text = null; } // Type if (type != null) { item.SubItems[3].Text = Utils.Format("{0}", type); } else { item.SubItems[3].Text = null; } // Severity if (condition.Severity != null) { item.SubItems[4].Text = Utils.Format("{0}", (EventSeverity)condition.Severity.Value); } else { item.SubItems[4].Text = null; } // Time if (condition.Time != null) { item.SubItems[5].Text = Utils.Format("{0:HH:mm:ss.fff}", condition.Time.Value.ToLocalTime()); } else { item.SubItems[5].Text = null; } // State if (condition.EnabledState != null && condition.EnabledState.EffectiveDisplayName != null) { item.SubItems[6].Text = Utils.Format("{0}", condition.EnabledState.EffectiveDisplayName.Value); } else { item.SubItems[6].Text = null; } // Message if (condition.Message != null) { item.SubItems[7].Text = Utils.Format("{0}", condition.Message.Value); } else { item.SubItems[7].Text = null; } // Comment if (condition.Comment != null) { item.SubItems[8].Text = Utils.Format("{0}", condition.Comment.Value); } else { item.SubItems[8].Text = null; } item.Tag = condition; // set the color based on the retain bit. if (!BaseVariableState.GetValue(condition.Retain)) { item.ForeColor = Color.DimGray; } else { if (NodeId.IsNull(BaseVariableState.GetValue(condition.BranchId))) { item.ForeColor = Color.Empty; } else { item.ForeColor = Color.DarkGray; } } //ConditionsLV.Items.Add(item); ConditionsLV.Items.Insert(0, item); // adjust the width of the columns. if (this.ColumnAutoAdjust) { for (int ii = 0; ii < ConditionsLV.Columns.Count; ii++) { ConditionsLV.Columns[ii].Width = -2; } } //TODO: Add this as a menu option //ConditionsLV.EnsureVisible(ConditionsLV.Items.Count -1); while (ConditionsLV.Items.Count > MaximumItems) { ConditionsLV.Items.RemoveAt(ConditionsLV.Items.Count - 1); } } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } }
/// <summary> /// Returns the history file for the variable. /// </summary> public IHistoryDataSource GetHistoryFile(BaseVariableState variable) { if (variable == null) { return null; } return m_historyArchive.GetHistoryFile(variable.NodeId); }
/// <summary> /// Returns a new value for the variable. /// </summary> public object ReadValue(BaseVariableState variable) { lock (m_lock) { switch (variable.NumericId) { case TestData.Variables.ScalarValueObjectType_BooleanValue: case TestData.Variables.UserScalarValueObjectType_BooleanValue: { return(m_generator.GetRandom <bool>(false)); } case TestData.Variables.ScalarValueObjectType_SByteValue: case TestData.Variables.UserScalarValueObjectType_SByteValue: { return(m_generator.GetRandom <sbyte>(false)); } case TestData.Variables.AnalogScalarValueObjectType_SByteValue: { return((sbyte)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100)); } case TestData.Variables.ScalarValueObjectType_ByteValue: case TestData.Variables.UserScalarValueObjectType_ByteValue: { return(m_generator.GetRandom <byte>(false)); } case TestData.Variables.AnalogScalarValueObjectType_ByteValue: { return((byte)((m_generator.GetRandom <uint>(false) % 201) + 50)); } case TestData.Variables.ScalarValueObjectType_Int16Value: case TestData.Variables.UserScalarValueObjectType_Int16Value: { return(m_generator.GetRandom <short>(false)); } case TestData.Variables.AnalogScalarValueObjectType_Int16Value: { return((short)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100)); } case TestData.Variables.ScalarValueObjectType_UInt16Value: case TestData.Variables.UserScalarValueObjectType_UInt16Value: { return(m_generator.GetRandom <ushort>(false)); } case TestData.Variables.AnalogScalarValueObjectType_UInt16Value: { return((ushort)((m_generator.GetRandom <uint>(false) % 201) + 50)); } case TestData.Variables.ScalarValueObjectType_Int32Value: case TestData.Variables.UserScalarValueObjectType_Int32Value: { return(m_generator.GetRandom <int>(false)); } case TestData.Variables.AnalogScalarValueObjectType_Int32Value: case TestData.Variables.AnalogScalarValueObjectType_IntegerValue: { return((int)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100)); } case TestData.Variables.ScalarValueObjectType_UInt32Value: case TestData.Variables.UserScalarValueObjectType_UInt32Value: { return(m_generator.GetRandom <uint>(false)); } case TestData.Variables.AnalogScalarValueObjectType_UInt32Value: case TestData.Variables.AnalogScalarValueObjectType_UIntegerValue: { return((uint)((m_generator.GetRandom <uint>(false) % 201) + 50)); } case TestData.Variables.ScalarValueObjectType_Int64Value: case TestData.Variables.UserScalarValueObjectType_Int64Value: { return(m_generator.GetRandom <long>(false)); } case TestData.Variables.AnalogScalarValueObjectType_Int64Value: { return((long)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100)); } case TestData.Variables.ScalarValueObjectType_UInt64Value: case TestData.Variables.UserScalarValueObjectType_UInt64Value: { return(m_generator.GetRandom <ulong>(false)); } case TestData.Variables.AnalogScalarValueObjectType_UInt64Value: { return((ulong)((m_generator.GetRandom <uint>(false) % 201) + 50)); } case TestData.Variables.ScalarValueObjectType_FloatValue: case TestData.Variables.UserScalarValueObjectType_FloatValue: { return(m_generator.GetRandom <float>(false)); } case TestData.Variables.AnalogScalarValueObjectType_FloatValue: { return((float)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100)); } case TestData.Variables.ScalarValueObjectType_DoubleValue: case TestData.Variables.UserScalarValueObjectType_DoubleValue: { return(m_generator.GetRandom <double>(false)); } case TestData.Variables.AnalogScalarValueObjectType_DoubleValue: case TestData.Variables.AnalogScalarValueObjectType_NumberValue: { return((double)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100)); } case TestData.Variables.ScalarValueObjectType_StringValue: case TestData.Variables.UserScalarValueObjectType_StringValue: { return(m_generator.GetRandom <string>(false)); } case TestData.Variables.ScalarValueObjectType_DateTimeValue: case TestData.Variables.UserScalarValueObjectType_DateTimeValue: { return(m_generator.GetRandom <DateTime>(false)); } case TestData.Variables.ScalarValueObjectType_GuidValue: case TestData.Variables.UserScalarValueObjectType_GuidValue: { return(m_generator.GetRandom <Guid>(false)); } case TestData.Variables.ScalarValueObjectType_ByteStringValue: case TestData.Variables.UserScalarValueObjectType_ByteStringValue: { return(m_generator.GetRandom <byte[]>(false)); } case TestData.Variables.ScalarValueObjectType_XmlElementValue: case TestData.Variables.UserScalarValueObjectType_XmlElementValue: { return(m_generator.GetRandom <XmlElement>(false)); } case TestData.Variables.ScalarValueObjectType_NodeIdValue: case TestData.Variables.UserScalarValueObjectType_NodeIdValue: { return(m_generator.GetRandom <Opc.Ua.NodeId>(false)); } case TestData.Variables.ScalarValueObjectType_ExpandedNodeIdValue: case TestData.Variables.UserScalarValueObjectType_ExpandedNodeIdValue: { return(m_generator.GetRandom <ExpandedNodeId>(false)); } case TestData.Variables.ScalarValueObjectType_QualifiedNameValue: case TestData.Variables.UserScalarValueObjectType_QualifiedNameValue: { return(m_generator.GetRandom <QualifiedName>(false)); } case TestData.Variables.ScalarValueObjectType_LocalizedTextValue: case TestData.Variables.UserScalarValueObjectType_LocalizedTextValue: { return(m_generator.GetRandom <LocalizedText>(false)); } case TestData.Variables.ScalarValueObjectType_StatusCodeValue: case TestData.Variables.UserScalarValueObjectType_StatusCodeValue: { return(m_generator.GetRandom <StatusCode>(false)); } case TestData.Variables.ScalarValueObjectType_VariantValue: case TestData.Variables.UserScalarValueObjectType_VariantValue: { return(m_generator.GetRandomVariant(false).Value); } case TestData.Variables.ScalarValueObjectType_StructureValue: { return(GetRandomStructure()); } case TestData.Variables.ScalarValueObjectType_EnumerationValue: { return(m_generator.GetRandom <int>(false)); } case TestData.Variables.ScalarValueObjectType_NumberValue: { return(m_generator.GetRandom(BuiltInType.Number)); } case TestData.Variables.ScalarValueObjectType_IntegerValue: { return(m_generator.GetRandom(BuiltInType.Integer)); } case TestData.Variables.ScalarValueObjectType_UIntegerValue: { return(m_generator.GetRandom(BuiltInType.UInteger)); } case TestData.Variables.ArrayValueObjectType_BooleanValue: case TestData.Variables.UserArrayValueObjectType_BooleanValue: { return(m_generator.GetRandomArray <bool>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_SByteValue: case TestData.Variables.UserArrayValueObjectType_SByteValue: { return(m_generator.GetRandomArray <sbyte>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_SByteValue: { sbyte[] values = m_generator.GetRandomArray <sbyte>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (sbyte)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100); } return(values); } case TestData.Variables.ArrayValueObjectType_ByteValue: case TestData.Variables.UserArrayValueObjectType_ByteValue: { return(m_generator.GetRandomArray <byte>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_ByteValue: { byte[] values = m_generator.GetRandomArray <byte>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (byte)((m_generator.GetRandom <uint>(false) % 201) + 50); } return(values); } case TestData.Variables.ArrayValueObjectType_Int16Value: case TestData.Variables.UserArrayValueObjectType_Int16Value: { return(m_generator.GetRandomArray <short>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_Int16Value: { short[] values = m_generator.GetRandomArray <short>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (short)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100); } return(values); } case TestData.Variables.ArrayValueObjectType_UInt16Value: case TestData.Variables.UserArrayValueObjectType_UInt16Value: { return(m_generator.GetRandomArray <ushort>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_UInt16Value: { ushort[] values = m_generator.GetRandomArray <ushort>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (ushort)((m_generator.GetRandom <uint>(false) % 201) + 50); } return(values); } case TestData.Variables.ArrayValueObjectType_Int32Value: case TestData.Variables.UserArrayValueObjectType_Int32Value: { return(m_generator.GetRandomArray <int>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_Int32Value: case TestData.Variables.AnalogArrayValueObjectType_IntegerValue: { int[] values = m_generator.GetRandomArray <int>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (int)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100); } return(values); } case TestData.Variables.ArrayValueObjectType_UInt32Value: case TestData.Variables.UserArrayValueObjectType_UInt32Value: { return(m_generator.GetRandomArray <uint>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_UInt32Value: case TestData.Variables.AnalogArrayValueObjectType_UIntegerValue: { uint[] values = m_generator.GetRandomArray <uint>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (uint)((m_generator.GetRandom <uint>(false) % 201) + 50); } return(values); } case TestData.Variables.ArrayValueObjectType_Int64Value: case TestData.Variables.UserArrayValueObjectType_Int64Value: { return(m_generator.GetRandomArray <long>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_Int64Value: { long[] values = m_generator.GetRandomArray <long>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (long)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100); } return(values); } case TestData.Variables.ArrayValueObjectType_UInt64Value: case TestData.Variables.UserArrayValueObjectType_UInt64Value: { return(m_generator.GetRandomArray <ulong>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_UInt64Value: { ulong[] values = m_generator.GetRandomArray <ulong>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (ulong)((m_generator.GetRandom <uint>(false) % 201) + 50); } return(values); } case TestData.Variables.ArrayValueObjectType_FloatValue: case TestData.Variables.UserArrayValueObjectType_FloatValue: { return(m_generator.GetRandomArray <float>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_FloatValue: { float[] values = m_generator.GetRandomArray <float>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (float)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100); } return(values); } case TestData.Variables.ArrayValueObjectType_DoubleValue: case TestData.Variables.UserArrayValueObjectType_DoubleValue: { return(m_generator.GetRandomArray <double>(false, 100, false)); } case TestData.Variables.AnalogArrayValueObjectType_DoubleValue: case TestData.Variables.AnalogArrayValueObjectType_NumberValue: { double[] values = m_generator.GetRandomArray <double>(false, 100, false); for (int ii = 0; ii < values.Length; ii++) { values[ii] = (double)(((int)(m_generator.GetRandom <uint>(false) % 201)) - 100); } return(values); } case TestData.Variables.ArrayValueObjectType_StringValue: case TestData.Variables.UserArrayValueObjectType_StringValue: { return(m_generator.GetRandomArray <string>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_DateTimeValue: case TestData.Variables.UserArrayValueObjectType_DateTimeValue: { return(m_generator.GetRandomArray <DateTime>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_GuidValue: case TestData.Variables.UserArrayValueObjectType_GuidValue: { return(m_generator.GetRandomArray <Guid>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_ByteStringValue: case TestData.Variables.UserArrayValueObjectType_ByteStringValue: { return(m_generator.GetRandomArray <byte[]>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_XmlElementValue: case TestData.Variables.UserArrayValueObjectType_XmlElementValue: { return(m_generator.GetRandomArray <XmlElement>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_NodeIdValue: case TestData.Variables.UserArrayValueObjectType_NodeIdValue: { return(m_generator.GetRandomArray <Opc.Ua.NodeId>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_ExpandedNodeIdValue: case TestData.Variables.UserArrayValueObjectType_ExpandedNodeIdValue: { return(m_generator.GetRandomArray <ExpandedNodeId>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_QualifiedNameValue: case TestData.Variables.UserArrayValueObjectType_QualifiedNameValue: { return(m_generator.GetRandomArray <QualifiedName>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_LocalizedTextValue: case TestData.Variables.UserArrayValueObjectType_LocalizedTextValue: { return(m_generator.GetRandomArray <LocalizedText>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_StatusCodeValue: case TestData.Variables.UserArrayValueObjectType_StatusCodeValue: { return(m_generator.GetRandomArray <StatusCode>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_VariantValue: case TestData.Variables.UserArrayValueObjectType_VariantValue: { return(m_generator.GetRandomArray <object>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_StructureValue: { ExtensionObject[] values = m_generator.GetRandomArray <ExtensionObject>(false, 10, false); for (int ii = 0; values != null && ii < values.Length; ii++) { values[ii] = GetRandomStructure(); } return(values); } case TestData.Variables.ArrayValueObjectType_EnumerationValue: { return(m_generator.GetRandomArray <int>(false, 100, false)); } case TestData.Variables.ArrayValueObjectType_NumberValue: { return(m_generator.GetRandomArray(BuiltInType.Number, false, 100, false)); } case TestData.Variables.ArrayValueObjectType_IntegerValue: { return(m_generator.GetRandomArray(BuiltInType.Integer, false, 100, false)); } case TestData.Variables.ArrayValueObjectType_UIntegerValue: { return(m_generator.GetRandomArray(BuiltInType.UInteger, false, 100, false)); } } return(null); } }
public void StartMonitoringValue(uint monitoredItemId, double samplingInterval, BaseVariableState variable) { lock (m_lock) { if (m_monitoredNodes == null) { m_monitoredNodes = new Dictionary<uint,BaseVariableState>(); } m_monitoredNodes[monitoredItemId] = variable; SetSamplingInterval(samplingInterval); } }
public void StartMonitoringValue(uint monitoredItemId, double samplingInterval, BaseVariableState variable) { lock (m_lock) { if (m_monitoredNodes == null) { m_monitoredNodes = new Dictionary <uint, BaseVariableState>(); } m_monitoredNodes[monitoredItemId] = variable; SetSamplingInterval(samplingInterval); } }
/// <summary> /// Updates the metadata cached for the server. /// </summary> private void DoMetadataUpdate(object state) { try { if (!Server.IsRunning) { return; } ComClientManager system = (ComClientManager)SystemContext.SystemHandle; ComClient client = (ComClient)system.SelectClient(SystemContext, true); int[] availableLocales = client.QueryAvailableLocales(); if (availableLocales != null) { lock (Server.DiagnosticsLock) { // check if the server is running. if (!Server.IsRunning) { return; } // get the LocaleIdArray property. BaseVariableState localeArray = Server.DiagnosticsNodeManager.Find(Opc.Ua.VariableIds.Server_ServerCapabilities_LocaleIdArray) as BaseVariableState; List <string> locales = new List <string>(); // preserve any existing locales. string[] existingLocales = localeArray.Value as string[]; if (existingLocales != null) { locales.AddRange(existingLocales); } for (int ii = 0; ii < availableLocales.Length; ii++) { if (availableLocales[ii] == 0 || availableLocales[ii] == ComUtils.LOCALE_SYSTEM_DEFAULT || availableLocales[ii] == ComUtils.LOCALE_USER_DEFAULT) { continue; } try { System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.GetCultureInfo(availableLocales[ii]); if (!locales.Contains(culture.Name)) { locales.Add(culture.Name); } } catch (Exception e) { Utils.Trace(e, "Can't process an invalid locale id: {0:X4}.", availableLocales[ii]); } } localeArray.Value = locales.ToArray(); } } // invoke callback. if (m_metadataUpdateCallback != null) { m_metadataUpdateCallback(state); } } catch (Exception e) { Utils.Trace(e, "Unexpected error updating HDA server metadata."); } }
/// <summary> /// Adds a node to the set. /// </summary> public void Export(ISystemContext context, NodeState node) { if (node == null) { throw new ArgumentNullException("node"); } if (Opc.Ua.NodeId.IsNull(node.NodeId)) { throw new ArgumentException("A non-null NodeId must be specified."); } UANode exportedNode = null; switch (node.NodeClass) { case NodeClass.Object: { BaseObjectState o = (BaseObjectState)node; UAObject value = new UAObject(); value.EventNotifier = o.EventNotifier; if (o.Parent != null) { value.ParentNodeId = ExportAlias(o.Parent.NodeId, context.NamespaceUris); } exportedNode = value; break; } case NodeClass.Variable: { BaseVariableState o = (BaseVariableState)node; UAVariable value = new UAVariable(); value.DataType = ExportAlias(o.DataType, context.NamespaceUris); value.ValueRank = o.ValueRank; value.ArrayDimensions = Export(o.ArrayDimensions); value.AccessLevel = o.AccessLevel; value.UserAccessLevel = o.UserAccessLevel; value.MinimumSamplingInterval = o.MinimumSamplingInterval; value.Historizing = o.Historizing; if (o.Parent != null) { value.ParentNodeId = ExportAlias(o.Parent.NodeId, context.NamespaceUris); } if (o.Value != null) { XmlEncoder encoder = CreateEncoder(context); Variant variant = new Variant(o.Value); encoder.WriteVariantContents(variant.Value, variant.TypeInfo); XmlDocument document = new XmlDocument(); document.InnerXml = encoder.Close(); value.Value = document.DocumentElement; } exportedNode = value; break; } case NodeClass.Method: { MethodState o = (MethodState)node; UAMethod value = new UAMethod(); value.Executable = o.Executable; value.UserExecutable = o.UserExecutable; if (o.Parent != null) { value.ParentNodeId = ExportAlias(o.Parent.NodeId, context.NamespaceUris); } exportedNode = value; break; } case NodeClass.View: { ViewState o = (ViewState)node; UAView value = new UAView(); value.ContainsNoLoops = o.ContainsNoLoops; exportedNode = value; break; } case NodeClass.ObjectType: { BaseObjectTypeState o = (BaseObjectTypeState)node; UAObjectType value = new UAObjectType(); value.IsAbstract = o.IsAbstract; exportedNode = value; break; } case NodeClass.VariableType: { BaseVariableTypeState o = (BaseVariableTypeState)node; UAVariableType value = new UAVariableType(); value.IsAbstract = o.IsAbstract; value.DataType = ExportAlias(o.DataType, context.NamespaceUris); value.ValueRank = o.ValueRank; value.ArrayDimensions = Export(o.ArrayDimensions); if (o.Value != null) { XmlEncoder encoder = CreateEncoder(context); Variant variant = new Variant(o.Value); encoder.WriteVariantContents(variant.Value, variant.TypeInfo); XmlDocument document = new XmlDocument(); document.InnerXml = encoder.Close(); value.Value = document.DocumentElement; } exportedNode = value; break; } case NodeClass.DataType: { DataTypeState o = (DataTypeState)node; UADataType value = new UADataType(); value.IsAbstract = o.IsAbstract; value.Definition = Export(o.Definition, context.NamespaceUris); exportedNode = value; break; } case NodeClass.ReferenceType: { ReferenceTypeState o = (ReferenceTypeState)node; UAReferenceType value = new UAReferenceType(); value.IsAbstract = o.IsAbstract; value.InverseName = Export(new Opc.Ua.LocalizedText[] { o.InverseName }); value.Symmetric = o.Symmetric; exportedNode = value; break; } } exportedNode.NodeId = Export(node.NodeId, context.NamespaceUris); exportedNode.BrowseName = Export(node.BrowseName, context.NamespaceUris); exportedNode.DisplayName = Export(new Opc.Ua.LocalizedText[] { node.DisplayName }); exportedNode.Description = Export(new Opc.Ua.LocalizedText[] { node.Description }); exportedNode.WriteMask = (uint)node.WriteMask; exportedNode.UserWriteMask = (uint)node.UserWriteMask; if (!String.IsNullOrEmpty(node.SymbolicName) && node.SymbolicName != node.BrowseName.Name) { exportedNode.SymbolicName = node.SymbolicName; } // export references. INodeBrowser browser = node.CreateBrowser(context, null, null, true, BrowseDirection.Both, null, null, true); List <Reference> exportedReferences = new List <Reference>(); IReference reference = browser.Next(); while (reference != null) { Reference exportedReference = new Reference(); exportedReference.ReferenceType = ExportAlias(reference.ReferenceTypeId, context.NamespaceUris); exportedReference.IsForward = !reference.IsInverse; exportedReference.Value = Export(reference.TargetId, context.NamespaceUris, context.ServerUris); exportedReferences.Add(exportedReference); reference = browser.Next(); } exportedNode.References = exportedReferences.ToArray(); // add node to list. UANode[] nodes = null; int count = 1; if (this.Items == null) { nodes = new UANode[count]; } else { count += this.Items.Length; nodes = new UANode[count]; Array.Copy(this.Items, nodes, this.Items.Length); } nodes[count - 1] = exportedNode; this.Items = nodes; // recusively process children. List <BaseInstanceState> children = new List <BaseInstanceState>(); node.GetChildren(context, children); for (int ii = 0; ii < children.Count; ii++) { Export(context, children[ii]); } }
/// <summary> /// Reads the raw data for a variable /// </summary> protected ServiceResult HistoryReadRaw( ISystemContext context, BaseVariableState source, ReadRawModifiedDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueId nodeToRead, HistoryReadResult result) { ServerSystemContext serverContext = context as ServerSystemContext; HistoryDataReader reader = null; HistoryData data = new HistoryData(); if (nodeToRead.ContinuationPoint != null && nodeToRead.ContinuationPoint.Length > 0) { // restore the continuation point. reader = RestoreDataReader(serverContext, nodeToRead.ContinuationPoint); if (reader == null) { return StatusCodes.BadContinuationPointInvalid; } // node id must match previous node id. if (reader.VariableId != nodeToRead.NodeId) { Utils.SilentDispose(reader); return StatusCodes.BadContinuationPointInvalid; } // check if releasing continuation points. if (releaseContinuationPoints) { Utils.SilentDispose(reader); return ServiceResult.Good; } } else { // get the source for the variable. IHistoryDataSource datasource = null; ServiceResult error = GetHistoryDataSource(serverContext, source, out datasource); if (ServiceResult.IsBad(error)) { return error; } // create a reader. reader = new HistoryDataReader(nodeToRead.NodeId, datasource); // start reading. reader.BeginReadRaw( serverContext, details, timestampsToReturn, nodeToRead.ParsedIndexRange, nodeToRead.DataEncoding, data.DataValues); } // continue reading data until done or max values reached. bool complete = reader.NextReadRaw( serverContext, timestampsToReturn, nodeToRead.ParsedIndexRange, nodeToRead.DataEncoding, data.DataValues); // save continuation point. if (!complete) { SaveDataReader(serverContext, reader); result.StatusCode = StatusCodes.GoodMoreData; } // return the dat. result.HistoryData = new ExtensionObject(data); return result.StatusCode; }
/// <summary> /// Imports a node from the set. /// </summary> private NodeState Import(ISystemContext context, UANode node) { NodeState importedNode = null; NodeClass nodeClass = NodeClass.Unspecified; if (node is UAObject) { nodeClass = NodeClass.Object; } else if (node is UAVariable) { nodeClass = NodeClass.Variable; } else if (node is UAMethod) { nodeClass = NodeClass.Method; } else if (node is UAObjectType) { nodeClass = NodeClass.ObjectType; } else if (node is UAVariableType) { nodeClass = NodeClass.VariableType; } else if (node is UADataType) { nodeClass = NodeClass.DataType; } else if (node is UAReferenceType) { nodeClass = NodeClass.ReferenceType; } else if (node is UAView) { nodeClass = NodeClass.View; } switch (nodeClass) { case NodeClass.Object: { UAObject o = (UAObject)node; BaseObjectState value = new BaseObjectState(null); value.EventNotifier = o.EventNotifier; importedNode = value; break; } case NodeClass.Variable: { UAVariable o = (UAVariable)node; NodeId typeDefinitionId = null; if (node.References != null) { for (int ii = 0; ii < node.References.Length; ii++) { Opc.Ua.NodeId referenceTypeId = ImportNodeId(node.References[ii].ReferenceType, context.NamespaceUris, true); bool isInverse = !node.References[ii].IsForward; Opc.Ua.ExpandedNodeId targetId = ImportExpandedNodeId(node.References[ii].Value, context.NamespaceUris, context.ServerUris); if (referenceTypeId == ReferenceTypeIds.HasTypeDefinition && !isInverse) { typeDefinitionId = Opc.Ua.ExpandedNodeId.ToNodeId(targetId, context.NamespaceUris); break; } } } BaseVariableState value = null; if (typeDefinitionId == Opc.Ua.VariableTypeIds.PropertyType) { value = new PropertyState(null); } else { value = new BaseDataVariableState(null); } value.DataType = ImportNodeId(o.DataType, context.NamespaceUris, true); value.ValueRank = o.ValueRank; value.ArrayDimensions = ImportArrayDimensions(o.ArrayDimensions); value.AccessLevel = o.AccessLevel; value.UserAccessLevel = o.UserAccessLevel; value.MinimumSamplingInterval = o.MinimumSamplingInterval; value.Historizing = o.Historizing; if (o.Value != null) { XmlDecoder decoder = CreateDecoder(context, o.Value); TypeInfo typeInfo = null; value.Value = decoder.ReadVariantContents(out typeInfo); decoder.Close(); } importedNode = value; break; } case NodeClass.Method: { UAMethod o = (UAMethod)node; MethodState value = new MethodState(null); value.Executable = o.Executable; value.UserExecutable = o.UserExecutable; importedNode = value; break; } case NodeClass.View: { UAView o = (UAView)node; ViewState value = new ViewState(); value.ContainsNoLoops = o.ContainsNoLoops; importedNode = value; break; } case NodeClass.ObjectType: { UAObjectType o = (UAObjectType)node; BaseObjectTypeState value = new BaseObjectTypeState(); value.IsAbstract = o.IsAbstract; importedNode = value; break; } case NodeClass.VariableType: { UAVariableType o = (UAVariableType)node; BaseVariableTypeState value = new BaseDataVariableTypeState(); value.IsAbstract = o.IsAbstract; value.DataType = ImportNodeId(o.DataType, context.NamespaceUris, true); value.ValueRank = o.ValueRank; value.ArrayDimensions = ImportArrayDimensions(o.ArrayDimensions); if (o.Value != null) { XmlDecoder decoder = CreateDecoder(context, o.Value); TypeInfo typeInfo = null; value.Value = decoder.ReadVariantContents(out typeInfo); decoder.Close(); } importedNode = value; break; } case NodeClass.DataType: { UADataType o = (UADataType)node; DataTypeState value = new DataTypeState(); value.IsAbstract = o.IsAbstract; value.Definition = Import(o.Definition, context.NamespaceUris); importedNode = value; break; } case NodeClass.ReferenceType: { UAReferenceType o = (UAReferenceType)node; ReferenceTypeState value = new ReferenceTypeState(); value.IsAbstract = o.IsAbstract; value.InverseName = Import(o.InverseName); value.Symmetric = o.Symmetric; importedNode = value; break; } } importedNode.NodeId = ImportNodeId(node.NodeId, context.NamespaceUris, false); importedNode.BrowseName = ImportQualifiedName(node.BrowseName, context.NamespaceUris); importedNode.DisplayName = Import(node.DisplayName); importedNode.Description = Import(node.Description); importedNode.WriteMask = (AttributeWriteMask)node.WriteMask; importedNode.UserWriteMask = (AttributeWriteMask)node.UserWriteMask; if (!String.IsNullOrEmpty(node.SymbolicName)) { importedNode.SymbolicName = node.SymbolicName; } if (node.References != null) { BaseInstanceState instance = importedNode as BaseInstanceState; BaseTypeState type = importedNode as BaseTypeState; for (int ii = 0; ii < node.References.Length; ii++) { Opc.Ua.NodeId referenceTypeId = ImportNodeId(node.References[ii].ReferenceType, context.NamespaceUris, true); bool isInverse = !node.References[ii].IsForward; Opc.Ua.ExpandedNodeId targetId = ImportExpandedNodeId(node.References[ii].Value, context.NamespaceUris, context.ServerUris); if (instance != null) { if (referenceTypeId == ReferenceTypeIds.HasModellingRule && !isInverse) { instance.ModellingRuleId = Opc.Ua.ExpandedNodeId.ToNodeId(targetId, context.NamespaceUris); continue; } if (referenceTypeId == ReferenceTypeIds.HasTypeDefinition && !isInverse) { instance.TypeDefinitionId = Opc.Ua.ExpandedNodeId.ToNodeId(targetId, context.NamespaceUris); continue; } } if (type != null) { if (referenceTypeId == ReferenceTypeIds.HasSubtype && isInverse) { type.SuperTypeId = Opc.Ua.ExpandedNodeId.ToNodeId(targetId, context.NamespaceUris); continue; } } importedNode.AddReference(referenceTypeId, isInverse, targetId); } } return(importedNode); }
/// <summary> /// Calculates the sampling interval. /// </summary> private double CalculateSamplingInterval(BaseVariableState variable, double samplingInterval) { if (samplingInterval < variable.MinimumSamplingInterval) { samplingInterval = variable.MinimumSamplingInterval; } if ((samplingInterval % m_minimumSamplingInterval) != 0) { samplingInterval = Math.Truncate(samplingInterval/m_minimumSamplingInterval); samplingInterval += 1; samplingInterval *= m_minimumSamplingInterval; } return samplingInterval; }
/// <summary> /// Initializes the instance from an event notification. /// </summary> /// <param name="context">The context.</param> /// <param name="fields">The fields selected for the event notification.</param> /// <param name="e">The event notification.</param> /// <remarks> /// This method creates components based on the browse paths in the event field and sets /// the NodeId or Value based on values in the event notification. /// </remarks> public void Update( ISystemContext context, SimpleAttributeOperandCollection fields, EventFieldList e) { for (int ii = 0; ii < fields.Count; ii++) { SimpleAttributeOperand field = fields[ii]; object value = e.EventFields[ii].Value; // check if value provided. if (value == null) { continue; } // extract the NodeId for the event. if (field.BrowsePath.Count == 0) { if (field.AttributeId == Attributes.NodeId) { this.NodeId = value as NodeId; continue; } } // extract the type definition for the event. if (field.BrowsePath.Count == 1) { if (field.AttributeId == Attributes.Value) { if (field.BrowsePath[0] == BrowseNames.EventType) { m_typeDefinitionId = value as NodeId; continue; } } } // save value for child node. NodeState parent = this; for (int jj = 0; jj < field.BrowsePath.Count; jj++) { // find a predefined child identified by the browse name. BaseInstanceState child = parent.CreateChild(context, field.BrowsePath[jj]); // create a placeholder for unknown children. if (child == null) { if (field.AttributeId == Attributes.Value) { child = new BaseDataVariableState(parent); } else { child = new BaseObjectState(parent); } parent.AddChild(child); } // ensure the browse name is set. if (QualifiedName.IsNull(child.BrowseName)) { child.BrowseName = field.BrowsePath[jj]; } // ensure the display name is set. if (LocalizedText.IsNullOrEmpty(child.DisplayName)) { child.DisplayName = child.BrowseName.Name; } // process next element in path. if (jj < field.BrowsePath.Count - 1) { parent = child; continue; } // save the variable value. if (field.AttributeId == Attributes.Value) { BaseVariableState variable = child as BaseVariableState; if (variable != null && field.AttributeId == Attributes.Value) { try { variable.WrappedValue = e.EventFields[ii]; } catch (Exception) { variable.Value = null; } } break; } // save the node id. child.NodeId = value as NodeId; } } }