Files
mapo-iob-test/IOB-OPC-UA/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs
T
2022-03-30 11:30:34 +02:00

829 lines
25 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2019 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 Opc.Ua.Server;
namespace Opc.Ua.Sample
{
/// <summary>
/// Provides a basic monitored item implementation which does not support queuing.
/// </summary>
public class DataChangeMonitoredItem : IDataChangeMonitoredItem2
{
#region Constructors
/// <summary>
/// Constructs a new instance.
/// </summary>
public DataChangeMonitoredItem(
MonitoredNode source,
uint id,
uint attributeId,
NumericRange indexRange,
QualifiedName dataEncoding,
DiagnosticsMasks diagnosticsMasks,
TimestampsToReturn timestampsToReturn,
MonitoringMode monitoringMode,
uint clientHandle,
double samplingInterval,
bool alwaysReportUpdates)
{
m_source = source;
m_id = id;
m_attributeId = attributeId;
m_indexRange = indexRange;
m_dataEncoding = dataEncoding;
m_timestampsToReturn = timestampsToReturn;
m_diagnosticsMasks = diagnosticsMasks;
m_monitoringMode = monitoringMode;
m_clientHandle = clientHandle;
m_samplingInterval = samplingInterval;
m_nextSampleTime = DateTime.UtcNow.Ticks;
m_readyToPublish = false;
m_readyToTrigger = false;
m_alwaysReportUpdates = alwaysReportUpdates;
}
/// <summary>
/// Constructs a new instance.
/// </summary>
public DataChangeMonitoredItem(
MonitoredNode source,
uint id,
uint attributeId,
NumericRange indexRange,
QualifiedName dataEncoding,
DiagnosticsMasks diagnosticsMasks,
TimestampsToReturn timestampsToReturn,
MonitoringMode monitoringMode,
uint clientHandle,
double samplingInterval,
uint queueSize,
bool discardOldest,
DataChangeFilter filter,
Range range,
bool alwaysReportUpdates)
{
m_source = source;
m_id = id;
m_attributeId = attributeId;
m_indexRange = indexRange;
m_dataEncoding = dataEncoding;
m_timestampsToReturn = timestampsToReturn;
m_diagnosticsMasks = diagnosticsMasks;
m_monitoringMode = monitoringMode;
m_clientHandle = clientHandle;
m_samplingInterval = samplingInterval;
m_nextSampleTime = DateTime.UtcNow.Ticks;
m_readyToPublish = false;
m_readyToTrigger = false;
m_queue = null;
m_filter = filter;
m_range = 0;
m_alwaysReportUpdates = alwaysReportUpdates;
if (range != null)
{
m_range = range.High - range.Low;
}
if (queueSize > 1)
{
m_queue = new MonitoredItemQueue(id);
m_queue.SetQueueSize(queueSize, discardOldest, diagnosticsMasks);
m_queue.SetSamplingInterval(samplingInterval);
}
}
#endregion
#region Public Members
/// <summary>
/// Gets the id for the attribute being monitored.
/// </summary>
public uint AttributeId
{
get { return m_attributeId; }
}
/// <summary>
/// Gets the index range used to selected a subset of the value.
/// </summary>
public NumericRange IndexRange
{
get { return m_indexRange; }
}
/// <summary>
/// Gets the data encoding to use when returning the value.
/// </summary>
public QualifiedName DataEncoding
{
get { return m_dataEncoding; }
}
/// <summary>
/// Whether the monitored item should report a value without checking if it was changed.
/// </summary>
public bool AlwaysReportUpdates
{
get { return m_alwaysReportUpdates; }
set { m_alwaysReportUpdates = value; }
}
/// <summary>
/// The number of milliseconds until the next sample.
/// </summary>
public int TimeToNextSample
{
get
{
lock (m_lock)
{
if (m_monitoringMode == MonitoringMode.Disabled)
{
return Int32.MaxValue;
}
DateTime now = DateTime.UtcNow;
if (m_nextSampleTime <= now.Ticks)
{
return 0;
}
return (int)((m_nextSampleTime - now.Ticks) / TimeSpan.TicksPerMillisecond);
}
}
}
/// <summary>
/// The monitoring mode.
/// </summary>
public MonitoringMode MonitoringMode
{
get
{
return m_monitoringMode;
}
}
/// <summary>
/// The sampling interval.
/// </summary>
public double SamplingInterval
{
get
{
lock (m_lock)
{
return m_samplingInterval;
}
}
}
/// <summary>
/// Modifies the monitored item parameters,
/// </summary>
public ServiceResult Modify(
DiagnosticsMasks diagnosticsMasks,
TimestampsToReturn timestampsToReturn,
uint clientHandle,
double samplingInterval)
{
return Modify(diagnosticsMasks, timestampsToReturn, clientHandle, samplingInterval, 0, false, null, null);
}
/// <summary>
/// Modifies the monitored item parameters,
/// </summary>
public ServiceResult Modify(
DiagnosticsMasks diagnosticsMasks,
TimestampsToReturn timestampsToReturn,
uint clientHandle,
double samplingInterval,
uint queueSize,
bool discardOldest,
DataChangeFilter filter,
Range range)
{
lock (m_lock)
{
m_diagnosticsMasks = diagnosticsMasks;
m_timestampsToReturn = timestampsToReturn;
m_clientHandle = clientHandle;
// subtract the previous sampling interval.
long oldSamplingInterval = (long)(m_samplingInterval * TimeSpan.TicksPerMillisecond);
if (oldSamplingInterval < m_nextSampleTime)
{
m_nextSampleTime -= oldSamplingInterval;
}
m_samplingInterval = samplingInterval;
// calculate the next sampling interval.
long newSamplingInterval = (long)(m_samplingInterval * TimeSpan.TicksPerMillisecond);
if (m_samplingInterval > 0)
{
m_nextSampleTime += newSamplingInterval;
}
else
{
m_nextSampleTime = 0;
}
// update the filter and the range.
m_filter = filter;
m_range = 0;
if (range != null)
{
m_range = range.High - range.Low;
}
// update the queue size.
if (queueSize > 1)
{
if (m_queue == null)
{
m_queue = new MonitoredItemQueue(m_id);
}
m_queue.SetQueueSize(queueSize, discardOldest, diagnosticsMasks);
m_queue.SetSamplingInterval(samplingInterval);
}
else
{
m_queue = null;
}
return ServiceResult.Good;
}
}
/// <summary>
/// Called when the attribute being monitored changed. Reads and queues the value.
/// </summary>
public void ValueChanged(ISystemContext context)
{
DataValue value = new DataValue();
ServiceResult error = m_source.Node.ReadAttribute(context, m_attributeId, NumericRange.Empty, null, value);
if (ServiceResult.IsBad(error))
{
value = new DataValue(error.StatusCode);
}
value.ServerTimestamp = DateTime.UtcNow;
QueueValue(value, error, false);
}
#endregion
#region IMonitoredItem Members
/// <summary>
/// The node manager for the monitored item.
/// </summary>
public INodeManager NodeManager
{
get { return m_source.NodeManager; }
}
/// <summary>
/// The session for the monitored item.
/// </summary>
public Session Session
{
get
{
ISubscription subscription = m_subscription;
if (subscription != null)
{
return subscription.Session;
}
return null;
}
}
/// <summary>
/// The identifier for the subscription that the monitored item belongs to.
/// </summary>
public uint SubscriptionId
{
get
{
ISubscription subscription = m_subscription;
if (subscription != null)
{
return subscription.Id;
}
return 0;
}
}
/// <summary>
/// The unique identifier for the monitored item.
/// </summary>
public uint Id
{
get { return m_id; }
}
/// <summary>
/// The client handle.
/// </summary>
public uint ClientHandle
{
get { return m_clientHandle; }
}
/// <summary>
/// The callback to use to notify the subscription when values are ready to publish.
/// </summary>
public ISubscription SubscriptionCallback
{
get
{
return m_subscription;
}
set
{
m_subscription = value;
}
}
/// <summary>
/// The handle assigned to the monitored item by the node manager.
/// </summary>
public object ManagerHandle
{
get { return m_source; }
}
/// <summary>
/// The type of monitor item.
/// </summary>
public int MonitoredItemType
{
get { return MonitoredItemTypeMask.DataChange; }
}
/// <summary>
/// Returns true if the item is ready to publish.
/// </summary>
public bool IsReadyToPublish
{
get
{
lock (m_lock)
{
// check if not ready to publish.
if (!m_readyToPublish)
{
return false;
}
// check if monitoring was turned off.
if (m_monitoringMode != MonitoringMode.Reporting)
{
return false;
}
// re-queue if too little time has passed since the last publish.
long now = DateTime.UtcNow.Ticks;
if (m_nextSampleTime > now)
{
return false;
}
return true;
}
}
}
/// <summary>
/// Gets or Sets a value indicating whether the item is ready to trigger in case it has some linked items.
/// </summary>
public bool IsReadyToTrigger
{
get
{
lock (m_lock)
{
// only allow to trigger if sampling or reporting.
if (m_monitoringMode == MonitoringMode.Disabled)
{
return false;
}
return m_readyToTrigger;
}
}
set
{
lock (m_lock)
{
m_readyToTrigger = value;
}
}
}
/// <summary>
/// Returns the results for the create request.
/// </summary>
public ServiceResult GetCreateResult(out MonitoredItemCreateResult result)
{
lock (m_lock)
{
result = new MonitoredItemCreateResult();
result.MonitoredItemId = m_id;
result.StatusCode = StatusCodes.Good;
result.RevisedSamplingInterval = m_samplingInterval;
result.RevisedQueueSize = 0;
result.FilterResult = null;
if (m_queue != null)
{
result.RevisedQueueSize = m_queue.QueueSize;
}
return ServiceResult.Good;
}
}
/// <summary>
/// Returns the results for the modify request.
/// </summary>
public ServiceResult GetModifyResult(out MonitoredItemModifyResult result)
{
lock (m_lock)
{
result = new MonitoredItemModifyResult();
result.StatusCode = StatusCodes.Good;
result.RevisedSamplingInterval = m_samplingInterval;
result.RevisedQueueSize = 0;
result.FilterResult = null;
if (m_queue != null)
{
result.RevisedQueueSize = m_queue.QueueSize;
}
return ServiceResult.Good;
}
}
#endregion
#region IDataChangeMonitoredItem Members
/// <summary>
/// Queues a new data change.
/// </summary>
public void QueueValue(DataValue value, ServiceResult error)
{
QueueValue(value, error, false);
}
#endregion
#region IDataChangeMonitoredItem2 Members
/// <summary>
/// Queues a new data change.
/// </summary>
public void QueueValue(DataValue value, ServiceResult error, bool ignoreFilters)
{
lock (m_lock)
{
// check if value has changed.
if (!m_alwaysReportUpdates)
{
if (!Opc.Ua.Server.MonitoredItem.ValueChanged(value, error, m_lastValue, m_lastError, m_filter, m_range))
{
return;
}
}
// make a shallow copy of the value.
if (value != null)
{
DataValue copy = new DataValue();
copy.WrappedValue = value.WrappedValue;
copy.StatusCode = value.StatusCode;
copy.SourceTimestamp = value.SourceTimestamp;
copy.SourcePicoseconds = value.SourcePicoseconds;
copy.ServerTimestamp = value.ServerTimestamp;
copy.ServerPicoseconds = value.ServerPicoseconds;
value = copy;
// ensure the data value matches the error status code.
if (error != null && error.StatusCode.Code != 0)
{
value.StatusCode = error.StatusCode;
}
}
m_lastValue = value;
m_lastError = error;
// queue value.
if (m_queue != null)
{
m_queue.QueueValue(value, error);
}
// flag the item as ready to publish.
m_readyToPublish = true;
m_readyToTrigger = true;
}
}
/// <summary>
/// Sets a flag indicating that the semantics for the monitored node have changed.
/// </summary>
/// <remarks>
/// The StatusCode for next value reported by the monitored item will have the SemanticsChanged bit set.
/// </remarks>
public void SetSemanticsChanged()
{
lock (m_lock)
{
m_semanticsChanged = true;
}
}
/// <summary>
/// Sets a flag indicating that the structure of the monitored node has changed.
/// </summary>
/// <remarks>
/// The StatusCode for next value reported by the monitored item will have the StructureChanged bit set.
/// </remarks>
public void SetStructureChanged()
{
lock (m_lock)
{
m_structureChanged = true;
}
}
/// <summary>
/// Changes the monitoring mode.
/// </summary>
public MonitoringMode SetMonitoringMode(MonitoringMode monitoringMode)
{
lock (m_lock)
{
MonitoringMode previousMode = m_monitoringMode;
if (previousMode == monitoringMode)
{
return previousMode;
}
if (previousMode == MonitoringMode.Disabled)
{
m_nextSampleTime = DateTime.UtcNow.Ticks;
m_lastError = null;
m_lastValue = null;
}
m_monitoringMode = monitoringMode;
if (monitoringMode == MonitoringMode.Disabled)
{
m_readyToPublish = false;
m_readyToTrigger = false;
}
return previousMode;
}
}
/// <summary>
/// No filters supported.
/// </summary>
public DataChangeFilter DataChangeFilter
{
get { return m_filter; }
}
/// <summary>
/// Increments the sample time to the next interval.
/// </summary>
private void IncrementSampleTime()
{
// update next sample time.
long now = DateTime.UtcNow.Ticks;
long samplingInterval = (long)(m_samplingInterval * TimeSpan.TicksPerMillisecond);
if (m_nextSampleTime > 0)
{
long delta = now - m_nextSampleTime;
if (samplingInterval > 0 && delta >= 0)
{
m_nextSampleTime += ((delta / samplingInterval) + 1) * samplingInterval;
}
}
// set sampling time based on current time.
else
{
m_nextSampleTime = now + samplingInterval;
}
}
/// <summary>
/// Called by the subscription to publish any notification.
/// </summary>
public bool Publish(OperationContext context, Queue<MonitoredItemNotification> notifications, Queue<DiagnosticInfo> diagnostics)
{
lock (m_lock)
{
// check if not ready to publish.
if (!IsReadyToPublish)
{
return false;
}
// update sample time.
IncrementSampleTime();
// update publish flag.
m_readyToPublish = false;
m_readyToTrigger = false;
// check if queuing is enabled.
if (m_queue == null)
{
Publish(context, m_lastValue, m_lastError, notifications, diagnostics);
}
else
{
DataValue value = null;
ServiceResult error = null;
while (m_queue.Publish(out value, out error))
{
Publish(context, value, error, notifications, diagnostics);
}
}
return true;
}
}
/// <summary>
/// Publishes a value.
/// </summary>
private void Publish(
OperationContext context,
DataValue value,
ServiceResult error,
Queue<MonitoredItemNotification> notifications,
Queue<DiagnosticInfo> diagnostics)
{
// set semantics changed bit.
if (m_semanticsChanged)
{
if (value != null)
{
value.StatusCode = value.StatusCode.SetSemanticsChanged(true);
}
if (error != null)
{
error = new ServiceResult(
error.StatusCode.SetSemanticsChanged(true),
error.SymbolicId,
error.NamespaceUri,
error.LocalizedText,
error.AdditionalInfo,
error.InnerResult);
}
m_semanticsChanged = false;
}
// set structure changed bit.
if (m_structureChanged)
{
if (value != null)
{
value.StatusCode = value.StatusCode.SetStructureChanged(true);
}
if (error != null)
{
error = new ServiceResult(
error.StatusCode.SetStructureChanged(true),
error.SymbolicId,
error.NamespaceUri,
error.LocalizedText,
error.AdditionalInfo,
error.InnerResult);
}
m_structureChanged = false;
}
// copy data value.
MonitoredItemNotification item = new MonitoredItemNotification();
item.ClientHandle = m_clientHandle;
item.Value = value;
// apply timestamp filter.
if (m_timestampsToReturn != TimestampsToReturn.Server && m_timestampsToReturn != TimestampsToReturn.Both)
{
item.Value.ServerTimestamp = DateTime.MinValue;
}
if (m_timestampsToReturn != TimestampsToReturn.Source && m_timestampsToReturn != TimestampsToReturn.Both)
{
item.Value.SourceTimestamp = DateTime.MinValue;
}
notifications.Enqueue(item);
// update diagnostic info.
DiagnosticInfo diagnosticInfo = null;
if (m_lastError != null)
{
if ((m_diagnosticsMasks & DiagnosticsMasks.OperationAll) != 0)
{
diagnosticInfo = ServerUtils.CreateDiagnosticInfo(m_source.Server, context, m_lastError);
}
}
diagnostics.Enqueue(diagnosticInfo);
}
#endregion
#region Private Fields
private object m_lock = new object();
private MonitoredNode m_source;
private ISubscription m_subscription;
private uint m_id;
private DataValue m_lastValue;
private ServiceResult m_lastError;
private uint m_attributeId;
private NumericRange m_indexRange;
private QualifiedName m_dataEncoding;
private TimestampsToReturn m_timestampsToReturn;
private DiagnosticsMasks m_diagnosticsMasks;
private uint m_clientHandle;
private double m_samplingInterval;
private MonitoredItemQueue m_queue;
private DataChangeFilter m_filter;
private double m_range;
private MonitoringMode m_monitoringMode;
private long m_nextSampleTime;
private bool m_readyToPublish;
private bool m_readyToTrigger;
private bool m_alwaysReportUpdates;
private bool m_semanticsChanged;
private bool m_structureChanged;
#endregion
}
}