Files
UA-.NETStandard/SampleApplications/Samples/Common/TestData/TestDataNodeManager.cs
T
2016-04-15 09:14:38 -07:00

727 lines
28 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2016 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.Text;
using System.Diagnostics;
using System.Xml;
using System.IO;
using System.Threading;
using Opc.Ua;
using Opc.Ua.Server;
using System.Reflection;
namespace TestData
{
/// <summary>
/// A node manager for a variety of test data.
/// </summary>
public class TestDataNodeManager : CustomNodeManager2, ITestDataSystemCallback
{
#region Constructors
/// <summary>
/// Initializes the node manager.
/// </summary>
public TestDataNodeManager(Opc.Ua.Server.IServerInternal server, ApplicationConfiguration configuration)
:
base(server, configuration)
{
// update the namespaces.
List<string> namespaceUris = new List<string>();
namespaceUris.Add(Namespaces.TestData);
namespaceUris.Add(Namespaces.TestData + "/Instance");
NamespaceUris = namespaceUris;
// get the configuration for the node manager.
m_configuration = configuration.ParseExtension<TestDataNodeManagerConfiguration>();
// use suitable defaults if no configuration exists.
if (m_configuration == null)
{
m_configuration = new TestDataNodeManagerConfiguration();
}
m_lastUsedId = m_configuration.NextUnusedId-1;
// create the object used to access the test system.
m_system = new TestDataSystem(this, server.NamespaceUris, server.ServerUris);
// update the default context.
SystemContext.SystemHandle = m_system;
}
#endregion
#region ITestDataSystemCallback Members
/// <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);
}
}
#endregion
#region INodeIdFactory Members
/// <summary>
/// Creates the NodeId for the specified node.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="node">The node.</param>
/// <returns>The new NodeId.</returns>
public override NodeId New(ISystemContext context, NodeState node)
{
uint id = Utils.IncrementIdentifier(ref m_lastUsedId);
return new NodeId(id, m_namespaceIndex);
}
#endregion
#region INodeManager Members
/// <summary>
/// Does any initialization required before the address space can be used.
/// </summary>
/// <remarks>
/// The externalReferences is an out parameter that allows the node manager to link to nodes
/// in other node managers. For example, the 'Objects' node is managed by the CoreNodeManager and
/// should have a reference to the root folder node(s) exposed by this node manager.
/// </remarks>
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
{
lock (Lock)
{
// ensure the namespace used by the node manager is in the server's namespace table.
m_typeNamespaceIndex = Server.NamespaceUris.GetIndexOrAppend(Namespaces.TestData);
m_namespaceIndex = Server.NamespaceUris.GetIndexOrAppend(Namespaces.TestData + "/Instance");
base.CreateAddressSpace(externalReferences);
// start monitoring the system status.
m_systemStatusCondition = (TestSystemConditionState)FindPredefinedNode(
new NodeId(Objects.Data_Conditions_SystemStatus, m_typeNamespaceIndex),
typeof(TestSystemConditionState));
if (m_systemStatusCondition != null)
{
m_systemStatusTimer = new Timer(OnCheckSystemStatus, null, 5000, 5000);
m_systemStatusCondition.Retain.Value = true;
}
// link all conditions to the conditions folder.
NodeState conditionsFolder = (NodeState)FindPredefinedNode(
new NodeId(Objects.Data_Conditions, m_typeNamespaceIndex),
typeof(NodeState));
foreach (NodeState node in PredefinedNodes.Values)
{
ConditionState condition = node as ConditionState;
if (condition != null && !Object.ReferenceEquals(condition.Parent, conditionsFolder))
{
condition.AddNotifier(SystemContext, null, true, conditionsFolder);
conditionsFolder.AddNotifier(SystemContext, null, false, condition);
}
}
// enable history for all numeric scalar values.
ScalarValueObjectState scalarValues = (ScalarValueObjectState)FindPredefinedNode(
new NodeId(Objects.Data_Dynamic_Scalar, m_typeNamespaceIndex),
typeof(ScalarValueObjectState));
scalarValues.Int32Value.Historizing = true;
scalarValues.Int32Value.AccessLevel = (byte)(scalarValues.Int32Value.AccessLevel | AccessLevels.HistoryRead);
m_system.EnableHistoryArchiving(scalarValues.Int32Value);
}
}
/// <summary>
/// Loads a node set from a file or resource and addes them to the set of predefined nodes.
/// </summary>
protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
{
NodeStateCollection predefinedNodes = new NodeStateCollection();
predefinedNodes.LoadFromBinaryResource(context, "Opc.Ua.Sample.TestData.TestData.PredefinedNodes.uanodes", this.GetType().GetTypeInfo().Assembly, true);
return predefinedNodes;
}
/// <summary>
/// Replaces the generic node with a node specific to the model.
/// </summary>
protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context, NodeState predefinedNode)
{
BaseObjectState passiveNode = predefinedNode as BaseObjectState;
if (passiveNode == null)
{
return predefinedNode;
}
NodeId typeId = passiveNode.TypeDefinitionId;
if (!IsNodeIdInNamespace(typeId) || typeId.IdType != IdType.Numeric)
{
return predefinedNode;
}
switch ((uint)typeId.Identifier)
{
case ObjectTypes.TestSystemConditionType:
{
if (passiveNode is TestSystemConditionState)
{
break;
}
TestSystemConditionState activeNode = new TestSystemConditionState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
case ObjectTypes.ScalarValueObjectType:
{
if (passiveNode is ScalarValueObjectState)
{
break;
}
ScalarValueObjectState activeNode = new ScalarValueObjectState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
case ObjectTypes.AnalogScalarValueObjectType:
{
if (passiveNode is AnalogScalarValueObjectState)
{
break;
}
AnalogScalarValueObjectState activeNode = new AnalogScalarValueObjectState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
case ObjectTypes.ArrayValueObjectType:
{
if (passiveNode is ArrayValueObjectState)
{
break;
}
ArrayValueObjectState activeNode = new ArrayValueObjectState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
case ObjectTypes.AnalogArrayValueObjectType:
{
if (passiveNode is AnalogArrayValueObjectState)
{
break;
}
AnalogArrayValueObjectState activeNode = new AnalogArrayValueObjectState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
case ObjectTypes.UserScalarValueObjectType:
{
if (passiveNode is UserScalarValueObjectState)
{
break;
}
UserScalarValueObjectState activeNode = new UserScalarValueObjectState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
case ObjectTypes.UserArrayValueObjectType:
{
if (passiveNode is UserArrayValueObjectState)
{
break;
}
UserArrayValueObjectState activeNode = new UserArrayValueObjectState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
case ObjectTypes.MethodTestType:
{
if (passiveNode is MethodTestState)
{
break;
}
MethodTestState activeNode = new MethodTestState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
if (passiveNode.Parent != null)
{
passiveNode.Parent.ReplaceChild(context, activeNode);
}
return activeNode;
}
}
return predefinedNode;
}
/// <summary>
/// Restores a previously cached history reader.
/// </summary>
protected virtual HistoryDataReader RestoreDataReader(Opc.Ua.Server.ServerSystemContext context, byte[] continuationPoint)
{
if (context == null || context.OperationContext == null || context.OperationContext.Session == null)
{
return null;
}
HistoryDataReader reader = context.OperationContext.Session.RestoreHistoryContinuationPoint(continuationPoint) as HistoryDataReader;
if (reader == null)
{
return null;
}
return reader;
}
/// <summary>
/// Saves a history data reader.
/// </summary>
protected virtual void SaveDataReader(Opc.Ua.Server.ServerSystemContext context, HistoryDataReader reader)
{
if (context == null || context.OperationContext == null || context.OperationContext.Session == null)
{
return;
}
context.OperationContext.Session.SaveHistoryContinuationPoint(reader.Id, reader);
}
/// <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>
/// 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>
/// Returns true if the system must be scanning to provide updates for the monitored item.
/// </summary>
private bool SystemScanRequired(MonitoredNode2 monitoredNode, IDataChangeMonitoredItem2 monitoredItem)
{
// ingore other types of monitored items.
if (monitoredItem == null)
{
return false;
}
// only care about variables.
BaseDataVariableState source = monitoredNode.Node as BaseDataVariableState;
if (source == null)
{
return false;
}
// check for variables that need to be scanned.
if (monitoredItem.AttributeId == Attributes.Value)
{
TestDataObjectState test = source.Parent as TestDataObjectState;
if (test != null && test.SimulationActive.Value)
{
return true;
}
}
return false;
}
/// <summary>
/// Called after creating a MonitoredItem.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="handle">The handle for the node.</param>
/// <param name="monitoredItem">The monitored item.</param>
protected override void OnMonitoredItemCreated(
ServerSystemContext context,
NodeHandle handle,
MonitoredItem monitoredItem)
{
if (SystemScanRequired(handle.MonitoredNode, monitoredItem))
{
if (monitoredItem.MonitoringMode != MonitoringMode.Disabled)
{
m_system.StartMonitoringValue(
monitoredItem.Id,
monitoredItem.SamplingInterval,
handle.Node as BaseVariableState);
}
}
}
/// <summary>
/// Called after modifying a MonitoredItem.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="handle">The handle for the node.</param>
/// <param name="monitoredItem">The monitored item.</param>
protected override void OnMonitoredItemModified(
ServerSystemContext context,
NodeHandle handle,
MonitoredItem monitoredItem)
{
if (SystemScanRequired(handle.MonitoredNode, monitoredItem))
{
if (monitoredItem.MonitoringMode != MonitoringMode.Disabled)
{
BaseVariableState source = handle.Node as BaseVariableState;
m_system.StopMonitoringValue(monitoredItem.Id);
m_system.StartMonitoringValue(monitoredItem.Id, monitoredItem.SamplingInterval, source);
}
}
}
/// <summary>
/// Called after deleting a MonitoredItem.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="handle">The handle for the node.</param>
/// <param name="monitoredItem">The monitored item.</param>
protected override void OnMonitoredItemDeleted(
ServerSystemContext context,
NodeHandle handle,
MonitoredItem monitoredItem)
{
// check for variables that need to be scanned.
if (SystemScanRequired(handle.MonitoredNode, monitoredItem))
{
m_system.StopMonitoringValue(monitoredItem.Id);
}
}
/// <summary>
/// Called after changing the MonitoringMode for a MonitoredItem.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="handle">The handle for the node.</param>
/// <param name="monitoredItem">The monitored item.</param>
/// <param name="previousMode">The previous monitoring mode.</param>
/// <param name="monitoringMode">The current monitoring mode.</param>
protected override void OnMonitoringModeChanged(
ServerSystemContext context,
NodeHandle handle,
MonitoredItem monitoredItem,
MonitoringMode previousMode,
MonitoringMode monitoringMode)
{
if (SystemScanRequired(handle.MonitoredNode, monitoredItem))
{
BaseVariableState source = handle.Node as BaseVariableState;
if (previousMode != MonitoringMode.Disabled && monitoredItem.MonitoringMode == MonitoringMode.Disabled)
{
m_system.StopMonitoringValue(monitoredItem.Id);
}
if (previousMode == MonitoringMode.Disabled && monitoredItem.MonitoringMode != MonitoringMode.Disabled)
{
m_system.StartMonitoringValue(monitoredItem.Id, monitoredItem.SamplingInterval, source);
}
}
}
#endregion
/// <summary>
/// Peridically checks the system state.
/// </summary>
private void OnCheckSystemStatus(object state)
{
#if CONDITION_SAMPLES
lock (Lock)
{
try
{
// create the dialog.
if (m_dialog == null)
{
m_dialog = new DialogConditionState(null);
CreateNode(
SystemContext,
ExpandedNodeId.ToNodeId(ObjectIds.Data_Conditions, SystemContext.NamespaceUris),
ReferenceTypeIds.HasComponent,
new QualifiedName("ResetSystemDialog", m_namespaceIndex),
m_dialog);
m_dialog.OnAfterResponse = OnDialogComplete;
}
StatusCode systemStatus = m_system.SystemStatus;
m_systemStatusCondition.UpdateStatus(systemStatus);
// cycle through different status codes in order to simulate a real system.
if (StatusCode.IsGood(systemStatus))
{
m_systemStatusCondition.UpdateSeverity((ushort)EventSeverity.Low);
m_system.SystemStatus = StatusCodes.Uncertain;
}
else if (StatusCode.IsUncertain(systemStatus))
{
m_systemStatusCondition.UpdateSeverity((ushort)EventSeverity.Medium);
m_system.SystemStatus = StatusCodes.Bad;
}
else
{
m_systemStatusCondition.UpdateSeverity((ushort)EventSeverity.High);
m_system.SystemStatus = StatusCodes.Good;
}
// request a reset if status is bad.
if (StatusCode.IsBad(systemStatus))
{
m_dialog.RequestResponse(
SystemContext,
"Reset the test system?",
(uint)(int)(DialogConditionChoice.Ok | DialogConditionChoice.Cancel),
(ushort)EventSeverity.MediumHigh);
}
// report the event.
TranslationInfo info = new TranslationInfo(
"TestSystemStatusChange",
"en-US",
"The TestSystem status is now {0}.",
systemStatus);
m_systemStatusCondition.ReportConditionChange(
SystemContext,
null,
new LocalizedText(info),
false);
}
catch (Exception e)
{
Utils.Trace(e, "Unexpected error monitoring system status.");
}
}
#endif
}
#if CONDITION_SAMPLES
/// <summary>
/// Handles a user response to a dialog.
/// </summary>
private ServiceResult OnDialogComplete(
ISystemContext context,
DialogConditionState dialog,
DialogConditionChoice response)
{
if (m_dialog != null)
{
DeleteNode(SystemContext, m_dialog.NodeId);
m_dialog = null;
}
return ServiceResult.Good;
}
#endif
#region Private Fields
private TestDataNodeManagerConfiguration m_configuration;
private ushort m_namespaceIndex;
private ushort m_typeNamespaceIndex;
private TestDataSystem m_system;
private long m_lastUsedId;
private Timer m_systemStatusTimer;
private TestSystemConditionState m_systemStatusCondition;
#if CONDITION_SAMPLES
private DialogConditionState m_dialog;
#endif
#endregion
}
}