Files
Mapo-IOB-WIN/IOB-OPC-UA/Libraries/Opc.Ua.Server/NodeManager/SamplingGroup.cs
T
2021-03-25 18:25:25 +01:00

497 lines
17 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2020 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.Threading;
using System.Security.Principal;
using System.Globalization;
using System.Threading.Tasks;
namespace Opc.Ua.Server
{
/// <summary>
/// An object which periodically reads the items and updates the cache.
/// </summary>
public class SamplingGroup : IDisposable
{
#region Constructors
/// <summary>
/// Creates a new instance of a sampling group.
/// </summary>
public SamplingGroup(
IServerInternal server,
INodeManager nodeManager,
List<SamplingRateGroup> samplingRates,
OperationContext context,
double samplingInterval)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (nodeManager == null) throw new ArgumentNullException(nameof(nodeManager));
if (samplingRates == null) throw new ArgumentNullException(nameof(samplingRates));
m_server = server;
m_nodeManager = nodeManager;
m_samplingRates = samplingRates;
m_session = context.Session;
m_diagnosticsMask = (DiagnosticsMasks)context.DiagnosticsMask & DiagnosticsMasks.OperationAll;
m_samplingInterval = AdjustSamplingInterval(samplingInterval);
m_itemsToAdd = new List<ISampledDataChangeMonitoredItem>();
m_itemsToRemove = new List<ISampledDataChangeMonitoredItem>();
m_items = new Dictionary<uint, ISampledDataChangeMonitoredItem>();
// create a event to signal shutdown.
m_shutdownEvent = new ManualResetEvent(true);
}
#endregion
#region IDisposable Members
/// <summary>
/// Frees any unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// An overrideable version of the Dispose.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (m_lock)
{
m_shutdownEvent.Set();
m_samplingRates.Clear();
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Starts the sampling thread which periodically reads the items in the group.
/// </summary>
public void Startup()
{
lock (m_lock)
{
m_shutdownEvent.Reset();
Task.Run(() =>
{
SampleMonitoredItems(m_samplingInterval);
});
}
}
/// <summary>
/// Stops the sampling thread.
/// </summary>
public void Shutdown()
{
lock (m_lock)
{
m_shutdownEvent.Set();
m_items.Clear();
}
}
/// <summary>
/// Checks if the monitored item can be handled by the group.
/// </summary>
/// <returns>
/// True if the item was added to the group.
/// </returns>
/// <remarks>
/// The ApplyChanges() method must be called to actually start sampling the item.
/// </remarks>
public bool StartMonitoring(OperationContext context, ISampledDataChangeMonitoredItem monitoredItem)
{
lock (m_lock)
{
if (MeetsGroupCriteria(context, monitoredItem))
{
m_itemsToAdd.Add(monitoredItem);
monitoredItem.SetSamplingInterval(m_samplingInterval);
return true;
}
return false;
}
}
/// <summary>
/// Checks if the monitored item can still be handled by the group.
/// </summary>
/// <returns>
/// False if the item has be marked for removal from the group.
/// </returns>
/// <remarks>
/// The ApplyChanges() method must be called to actually stop sampling the item.
/// </remarks>
public bool ModifyMonitoring(OperationContext context, ISampledDataChangeMonitoredItem monitoredItem)
{
lock (m_lock)
{
if (m_items.ContainsKey(monitoredItem.Id))
{
if (MeetsGroupCriteria(context, monitoredItem))
{
monitoredItem.SetSamplingInterval(m_samplingInterval);
return true;
}
m_itemsToRemove.Add(monitoredItem);
}
return false;
}
}
/// <summary>
/// Stops monitoring the item.
/// </summary>
/// <returns>
/// Returns true if the items was marked for removal from the group.
/// </returns>
public bool StopMonitoring(ISampledDataChangeMonitoredItem monitoredItem)
{
lock (m_lock)
{
if (m_items.ContainsKey(monitoredItem.Id))
{
m_itemsToRemove.Add(monitoredItem);
return true;
}
return false;
}
}
/// <summary>
/// Updates the group by apply any pending changes.
/// </summary>
/// <returns>
/// Returns true if the group has no more items and can be dropped.
/// </returns>
public bool ApplyChanges()
{
lock (m_lock)
{
// add items.
List<ISampledDataChangeMonitoredItem> itemsToSample = new List<ISampledDataChangeMonitoredItem>();
for (int ii = 0; ii < m_itemsToAdd.Count; ii++)
{
ISampledDataChangeMonitoredItem monitoredItem = m_itemsToAdd[ii];
if (!m_items.ContainsKey(monitoredItem.Id))
{
m_items.Add(monitoredItem.Id, monitoredItem);
if (monitoredItem.MonitoringMode != MonitoringMode.Disabled)
{
itemsToSample.Add(monitoredItem);
}
}
}
m_itemsToAdd.Clear();
// collect first sample.
if (itemsToSample.Count > 0)
{
Task.Run(() =>
{
DoSample(itemsToSample);
});
}
// remove items.
for (int ii = 0; ii < m_itemsToRemove.Count; ii++)
{
m_items.Remove(m_itemsToRemove[ii].Id);
}
m_itemsToRemove.Clear();
// start the group if it is not running.
if (m_items.Count > 0)
{
Startup();
}
// stop the group if it is running.
else if (m_items.Count == 0)
{
Shutdown();
}
// can be shutdown if no items left.
return m_items.Count == 0;
}
}
#endregion
#region Private Methods
/// <summary>
/// Checks if the item meets the group's criteria.
/// </summary>
private bool MeetsGroupCriteria(OperationContext context, ISampledDataChangeMonitoredItem monitoredItem)
{
// can only sample variables.
if ((monitoredItem.MonitoredItemType & MonitoredItemTypeMask.DataChange) == 0)
{
return false;
}
// can't sample disabled items.
if (monitoredItem.MonitoringMode == MonitoringMode.Disabled)
{
return false;
}
// check sampling interval.
if (AdjustSamplingInterval(monitoredItem.SamplingInterval) != m_samplingInterval)
{
return false;
}
// compare session.
if (context.SessionId != m_session.Id)
{
return false;
}
// check the diagnostics marks.
if (m_diagnosticsMask != (context.DiagnosticsMask & DiagnosticsMasks.OperationAll))
{
return false;
}
return true;
}
/// <summary>
/// Ensures the requested sampling interval lines up with one of the supported sampling rates.
/// </summary>
private double AdjustSamplingInterval(double samplingInterval)
{
foreach (SamplingRateGroup samplingRate in m_samplingRates)
{
// groups are ordered by start rate.
if (samplingInterval <= samplingRate.Start)
{
return samplingRate.Start;
}
// check if within range specfied by the group.
double maxSamplingRate = samplingRate.Start;
if (samplingRate.Increment > 0)
{
maxSamplingRate += samplingRate.Increment*samplingRate.Count;
}
if (samplingInterval > maxSamplingRate)
{
continue;
}
// find sampling rate within rate group.
if (samplingInterval == maxSamplingRate)
{
return maxSamplingRate;
}
for (double ii = samplingRate.Start; ii <= maxSamplingRate; ii += samplingRate.Increment)
{
if (ii >= samplingInterval)
{
return ii;
}
}
}
return samplingInterval;
}
/// <summary>
/// Periodically checks if the sessions have timed out.
/// </summary>
private void SampleMonitoredItems(object data)
{
try
{
//Utils.Trace("Server: {0} Thread Started.", Thread.CurrentThread.Name);
int sleepCycle = Convert.ToInt32(data, CultureInfo.InvariantCulture);
int timeToWait = sleepCycle;
while (m_server.IsRunning)
{
DateTime start = DateTime.UtcNow;
// wait till next sample.
if (m_shutdownEvent.WaitOne(timeToWait))
{
break;
}
// get current list of items to sample.
List<ISampledDataChangeMonitoredItem> items = new List<ISampledDataChangeMonitoredItem>();
lock (m_lock)
{
uint disabledItemCount = 0;
Dictionary<uint,ISampledDataChangeMonitoredItem>.Enumerator enumerator = m_items.GetEnumerator();
while (enumerator.MoveNext())
{
ISampledDataChangeMonitoredItem monitoredItem = enumerator.Current.Value;
if (monitoredItem.MonitoringMode == MonitoringMode.Disabled)
{
disabledItemCount++;
continue;
}
// check whether the item should be sampled.
//if (!monitoredItem.SamplingIntervalExpired())
//{
// continue;
//}
items.Add(monitoredItem);
}
}
// sample the values.
DoSample(items);
int delay = (int)(DateTime.UtcNow - start).TotalMilliseconds;
timeToWait = sleepCycle;
if (delay > sleepCycle)
{
timeToWait = 2*sleepCycle - delay;
if (timeToWait < 0)
{
Utils.Trace("WARNING: SamplingGroup cannot sample fast enough. TimeToSample={0}ms, SamplingInterval={1}ms", delay, sleepCycle);
timeToWait = sleepCycle;
}
}
}
//Utils.Trace("Server: {0} Thread Exited Normally.", Thread.CurrentThread.Name);
}
catch (Exception e)
{
Utils.Trace(e, "Server: SampleMonitoredItems Thread Exited Unexpectedly.");
}
}
/// <summary>
/// Samples the values of the items.
/// </summary>
private void DoSample(object state)
{
try
{
List<ISampledDataChangeMonitoredItem> items = state as List<ISampledDataChangeMonitoredItem>;
// read values for all enabled items.
if (items != null && items.Count > 0)
{
ReadValueIdCollection itemsToRead = new ReadValueIdCollection(items.Count);
DataValueCollection values = new DataValueCollection(items.Count);
List<ServiceResult> errors = new List<ServiceResult>(items.Count);
// allocate space for results.
for (int ii = 0; ii < items.Count; ii++)
{
ReadValueId readValueId = items[ii].GetReadValueId();
readValueId.Processed = false;
itemsToRead.Add(readValueId);
values.Add(null);
errors.Add(null);
}
OperationContext context = new OperationContext(m_session, m_diagnosticsMask);
// read values.
m_nodeManager.Read(
context,
0,
itemsToRead,
values,
errors);
// update monitored items.
for (int ii = 0; ii < items.Count; ii++)
{
if (values[ii] == null)
{
values[ii] = new DataValue(StatusCodes.BadInternalError, DateTime.UtcNow);
}
items[ii].QueueValue(values[ii], errors[ii]);
}
}
}
catch (Exception e)
{
Utils.Trace(e, "Server: Unexpected error sampling values.");
}
}
#endregion
#region Private Fields
private object m_lock = new object();
private IServerInternal m_server;
private INodeManager m_nodeManager;
private Session m_session;
private DiagnosticsMasks m_diagnosticsMask;
private double m_samplingInterval;
private List<ISampledDataChangeMonitoredItem> m_itemsToAdd;
private List<ISampledDataChangeMonitoredItem> m_itemsToRemove;
private Dictionary<uint, ISampledDataChangeMonitoredItem> m_items;
private ManualResetEvent m_shutdownEvent;
private List<SamplingRateGroup> m_samplingRates;
#endregion
}
}