mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-23 02:00:00 +08:00
1ac35fdec0
1. Upgraded from UWP 5.1.0 to 5.2.0 2. Upgraded all assemblies from RC to RTM bits 3. Renamed Common directory to Opc.Ua.Sample 4. Reimplemented HTTP transport due to removed classes in .Net Standard 5. Small UWP XML class cleanup due to changes in UWP 5.2.0
701 lines
22 KiB
C#
701 lines
22 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.Xml;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using Opc.Ua;
|
|
using Opc.Ua.Server;
|
|
using System.Diagnostics;
|
|
|
|
namespace MemoryBuffer
|
|
{
|
|
public partial class MemoryBufferState
|
|
{
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Initializes the buffer from the configuration.
|
|
/// </summary>
|
|
public MemoryBufferState(ISystemContext context, MemoryBufferInstance configuration) : base(null)
|
|
{
|
|
Initialize(context);
|
|
|
|
string dataType = "UInt32";
|
|
string name = dataType;
|
|
int count = 10;
|
|
|
|
if (configuration != null)
|
|
{
|
|
count = configuration.TagCount;
|
|
|
|
if (!String.IsNullOrEmpty(configuration.DataType))
|
|
{
|
|
dataType = configuration.DataType;
|
|
}
|
|
|
|
if (!String.IsNullOrEmpty(configuration.Name))
|
|
{
|
|
name = dataType;
|
|
}
|
|
}
|
|
|
|
this.SymbolicName = name;
|
|
|
|
BuiltInType elementType = BuiltInType.UInt32;
|
|
|
|
switch (dataType)
|
|
{
|
|
case "Double":
|
|
{
|
|
elementType = BuiltInType.Double;
|
|
break;
|
|
}
|
|
}
|
|
|
|
CreateBuffer(elementType, count);
|
|
}
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
/// <summary>
|
|
/// The server that the buffer belongs to.
|
|
/// </summary>
|
|
public IServerInternal Server
|
|
{
|
|
get { return m_server; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The node manager that the buffer belongs to.
|
|
/// </summary>
|
|
public INodeManager NodeManager
|
|
{
|
|
get { return m_nodeManager; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The built-in type for the values stored in the buffer.
|
|
/// </summary>
|
|
public BuiltInType ElementType
|
|
{
|
|
get { return m_elementType; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The size of each element in the buffer.
|
|
/// </summary>
|
|
public uint ElementSize
|
|
{
|
|
get { return (uint)m_elementSize; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The rate at which the buffer is scanned.
|
|
/// </summary>
|
|
public int MaximumScanRate
|
|
{
|
|
get { return m_maximumScanRate; }
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
/// <summary>
|
|
/// Initializes the buffer with enough space to hold the specified number of elements.
|
|
/// </summary>
|
|
/// <param name="elementName">The type of element.</param>
|
|
/// <param name="noOfElements">The number of elements.</param>
|
|
public void CreateBuffer(string elementName, int noOfElements)
|
|
{
|
|
if (String.IsNullOrEmpty(elementName))
|
|
{
|
|
elementName = "UInt32";
|
|
}
|
|
|
|
BuiltInType elementType = BuiltInType.UInt32;
|
|
|
|
switch (elementName)
|
|
{
|
|
case "Double":
|
|
{
|
|
elementType = BuiltInType.Double;
|
|
break;
|
|
}
|
|
}
|
|
|
|
CreateBuffer(elementType, noOfElements);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the buffer with enough space to hold the specified number of elements.
|
|
/// </summary>
|
|
/// <param name="elementType">The type of element.</param>
|
|
/// <param name="noOfElements">The number of elements.</param>
|
|
public void CreateBuffer(BuiltInType elementType, int noOfElements)
|
|
{
|
|
lock (m_dataLock)
|
|
{
|
|
m_elementType = elementType;
|
|
m_elementSize = 1;
|
|
|
|
switch (m_elementType)
|
|
{
|
|
case BuiltInType.UInt32:
|
|
{
|
|
m_elementSize = 4;
|
|
break;
|
|
}
|
|
|
|
case BuiltInType.Double:
|
|
{
|
|
m_elementSize = 8;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_lastScanTime = DateTime.UtcNow;
|
|
m_maximumScanRate = 1000;
|
|
|
|
m_buffer = new byte[m_elementSize * noOfElements];
|
|
SizeInBytes.Value = (uint)m_buffer.Length;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an object which can browser the tags in the buffer.
|
|
/// </summary>
|
|
public override INodeBrowser CreateBrowser(
|
|
ISystemContext context,
|
|
ViewDescription view,
|
|
NodeId referenceType,
|
|
bool includeSubtypes,
|
|
BrowseDirection browseDirection,
|
|
QualifiedName browseName,
|
|
IEnumerable<IReference> additionalReferences,
|
|
bool internalOnly)
|
|
{
|
|
NodeBrowser browser = new MemoryBufferBrowser(
|
|
context,
|
|
view,
|
|
referenceType,
|
|
includeSubtypes,
|
|
browseDirection,
|
|
browseName,
|
|
additionalReferences,
|
|
internalOnly,
|
|
this);
|
|
|
|
PopulateBrowser(context, browser);
|
|
|
|
return browser;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the read operation for an invidual tag.
|
|
/// </summary>
|
|
public ServiceResult ReadTagValue(
|
|
ISystemContext context,
|
|
NodeState node,
|
|
NumericRange indexRange,
|
|
QualifiedName dataEncoding,
|
|
ref object value,
|
|
ref StatusCode statusCode,
|
|
ref DateTime timestamp)
|
|
{
|
|
MemoryTagState tag = node as MemoryTagState;
|
|
|
|
if (tag == null)
|
|
{
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
|
|
if (NumericRange.Empty != indexRange)
|
|
{
|
|
return StatusCodes.BadIndexRangeInvalid;
|
|
}
|
|
|
|
if (!QualifiedName.IsNull(dataEncoding))
|
|
{
|
|
return StatusCodes.BadDataEncodingInvalid;
|
|
}
|
|
|
|
int offset = (int)tag.Offset;
|
|
|
|
lock (m_dataLock)
|
|
{
|
|
if (offset < 0 || offset >= m_buffer.Length)
|
|
{
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
|
|
if (m_buffer == null)
|
|
{
|
|
return StatusCodes.BadOutOfService;
|
|
}
|
|
|
|
value = GetValueAtOffset(offset).Value;
|
|
}
|
|
|
|
statusCode = StatusCodes.Good;
|
|
timestamp = m_lastScanTime;
|
|
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles a write operation for an individual tag.
|
|
/// </summary>
|
|
public ServiceResult WriteTagValue(
|
|
ISystemContext context,
|
|
NodeState node,
|
|
NumericRange indexRange,
|
|
QualifiedName dataEncoding,
|
|
ref object value,
|
|
ref StatusCode statusCode,
|
|
ref DateTime timestamp)
|
|
{
|
|
MemoryTagState tag = node as MemoryTagState;
|
|
|
|
if (tag == null)
|
|
{
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
|
|
if (NumericRange.Empty != indexRange)
|
|
{
|
|
return StatusCodes.BadIndexRangeInvalid;
|
|
}
|
|
|
|
if (!QualifiedName.IsNull(dataEncoding))
|
|
{
|
|
return StatusCodes.BadDataEncodingInvalid;
|
|
}
|
|
|
|
if (statusCode != StatusCodes.Good)
|
|
{
|
|
return StatusCodes.BadWriteNotSupported;
|
|
}
|
|
|
|
if (timestamp != DateTime.MinValue)
|
|
{
|
|
return StatusCodes.BadWriteNotSupported;
|
|
}
|
|
|
|
bool changed = false;
|
|
int offset = (int)tag.Offset;
|
|
|
|
lock (m_dataLock)
|
|
{
|
|
if (offset < 0 || offset >= m_buffer.Length)
|
|
{
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
|
|
if (m_buffer == null)
|
|
{
|
|
return StatusCodes.BadOutOfService;
|
|
}
|
|
|
|
byte[] bytes = null;
|
|
|
|
switch (m_elementType)
|
|
{
|
|
case BuiltInType.UInt32:
|
|
{
|
|
uint? valueToWrite = value as uint?;
|
|
|
|
if (valueToWrite == null)
|
|
{
|
|
return StatusCodes.BadTypeMismatch;
|
|
}
|
|
|
|
bytes = BitConverter.GetBytes(valueToWrite.Value);
|
|
break;
|
|
}
|
|
|
|
case BuiltInType.Double:
|
|
{
|
|
double? valueToWrite = value as double?;
|
|
|
|
if (valueToWrite == null)
|
|
{
|
|
return StatusCodes.BadTypeMismatch;
|
|
}
|
|
|
|
bytes = BitConverter.GetBytes(valueToWrite.Value);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
}
|
|
|
|
for (int ii = 0; ii < bytes.Length; ii++)
|
|
{
|
|
if (!changed)
|
|
{
|
|
if (m_buffer[offset + ii] != bytes[ii])
|
|
{
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
m_buffer[offset + ii] = bytes[ii];
|
|
}
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
OnBufferChanged(offset);
|
|
}
|
|
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the value at the specified offset.
|
|
/// </summary>
|
|
public Variant GetValueAtOffset(int offset)
|
|
{
|
|
lock (m_dataLock)
|
|
{
|
|
if (offset < 0 || offset >= m_buffer.Length)
|
|
{
|
|
return Variant.Null;
|
|
}
|
|
|
|
if (m_buffer == null)
|
|
{
|
|
return Variant.Null;
|
|
}
|
|
|
|
switch (m_elementType)
|
|
{
|
|
case BuiltInType.UInt32:
|
|
{
|
|
return new Variant(BitConverter.ToUInt32(m_buffer, offset));
|
|
}
|
|
|
|
case BuiltInType.Double:
|
|
{
|
|
return new Variant(BitConverter.ToDouble(m_buffer, offset));
|
|
}
|
|
}
|
|
|
|
return Variant.Null;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Monitoring Support Functions
|
|
/// <summary>
|
|
/// Initializes the instance with the context for the node being monitored.
|
|
/// </summary>
|
|
public void InitializeMonitoring(
|
|
IServerInternal server,
|
|
INodeManager nodeManager)
|
|
{
|
|
lock (m_dataLock)
|
|
{
|
|
m_server = server;
|
|
m_nodeManager = nodeManager;
|
|
m_nonValueMonitoredItems = new Dictionary<uint, MemoryBufferMonitoredItem>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new data change monitored item.
|
|
/// </summary>
|
|
public MemoryBufferMonitoredItem CreateDataChangeItem(
|
|
ServerSystemContext context,
|
|
MemoryTagState tag,
|
|
uint subscriptionId,
|
|
uint monitoredItemId,
|
|
ReadValueId itemToMonitor,
|
|
DiagnosticsMasks diagnosticsMasks,
|
|
TimestampsToReturn timestampsToReturn,
|
|
MonitoringMode monitoringMode,
|
|
uint clientHandle,
|
|
double samplingInterval)
|
|
|
|
/*
|
|
ISystemContext context,
|
|
MemoryTagState tag,
|
|
uint monitoredItemId,
|
|
uint attributeId,
|
|
DiagnosticsMasks diagnosticsMasks,
|
|
TimestampsToReturn timestampsToReturn,
|
|
MonitoringMode monitoringMode,
|
|
uint clientHandle,
|
|
double samplingInterval)*/
|
|
{
|
|
lock (m_dataLock)
|
|
{
|
|
MemoryBufferMonitoredItem monitoredItem = new MemoryBufferMonitoredItem(
|
|
m_server,
|
|
m_nodeManager,
|
|
this,
|
|
tag.Offset,
|
|
0,
|
|
monitoredItemId,
|
|
context.OperationContext.Session,
|
|
itemToMonitor,
|
|
diagnosticsMasks,
|
|
timestampsToReturn,
|
|
monitoringMode,
|
|
clientHandle,
|
|
null,
|
|
null,
|
|
null,
|
|
samplingInterval,
|
|
0,
|
|
false,
|
|
0);
|
|
|
|
/*
|
|
MemoryBufferMonitoredItem monitoredItem = new MemoryBufferMonitoredItem(
|
|
this,
|
|
monitoredItemId,
|
|
tag.Offset,
|
|
attributeId,
|
|
diagnosticsMasks,
|
|
timestampsToReturn,
|
|
monitoringMode,
|
|
clientHandle,
|
|
samplingInterval);
|
|
*/
|
|
|
|
if (itemToMonitor.AttributeId != Attributes.Value)
|
|
{
|
|
m_nonValueMonitoredItems.Add(monitoredItem.Id, monitoredItem);
|
|
return monitoredItem;
|
|
}
|
|
|
|
int elementCount = (int)(SizeInBytes.Value / ElementSize);
|
|
|
|
if (m_monitoringTable == null)
|
|
{
|
|
m_monitoringTable = new MemoryBufferMonitoredItem[elementCount][];
|
|
m_scanTimer = new Timer(DoScan, null, 100, 100);
|
|
}
|
|
|
|
int elementOffet = (int)(tag.Offset / ElementSize);
|
|
|
|
MemoryBufferMonitoredItem[] monitoredItems = m_monitoringTable[elementOffet];
|
|
|
|
if (monitoredItems == null)
|
|
{
|
|
monitoredItems = new MemoryBufferMonitoredItem[1];
|
|
}
|
|
else
|
|
{
|
|
monitoredItems = new MemoryBufferMonitoredItem[monitoredItems.Length + 1];
|
|
m_monitoringTable[elementOffet].CopyTo(monitoredItems, 0);
|
|
}
|
|
|
|
monitoredItems[monitoredItems.Length - 1] = monitoredItem;
|
|
m_monitoringTable[elementOffet] = monitoredItems;
|
|
m_itemCount++;
|
|
|
|
return monitoredItem;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scans the buffer and updates every other element.
|
|
/// </summary>
|
|
void DoScan(object state)
|
|
{
|
|
DateTime start1 = DateTime.UtcNow;
|
|
|
|
lock (m_dataLock)
|
|
{
|
|
for (int ii = 0; ii < m_buffer.Length; ii += m_elementSize)
|
|
{
|
|
m_buffer[ii]++;
|
|
|
|
// notify any monitored items that the value has changed.
|
|
OnBufferChanged(ii);
|
|
}
|
|
|
|
m_lastScanTime = DateTime.UtcNow;
|
|
}
|
|
|
|
DateTime end1 = DateTime.UtcNow;
|
|
|
|
double delta1 = ((double)(end1.Ticks-start1.Ticks))/TimeSpan.TicksPerMillisecond;
|
|
|
|
if (delta1 > 100)
|
|
{
|
|
Debug.WriteLine("SAMPLING DELAY ({0}ms)", delta1);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes the monitored item.
|
|
/// </summary>
|
|
public void DeleteItem(MemoryBufferMonitoredItem monitoredItem)
|
|
{
|
|
lock (m_dataLock)
|
|
{
|
|
if (monitoredItem.AttributeId != Attributes.Value)
|
|
{
|
|
m_nonValueMonitoredItems.Remove(monitoredItem.Id);
|
|
return;
|
|
}
|
|
|
|
if (m_monitoringTable != null)
|
|
{
|
|
int elementOffet = (int)(monitoredItem.Offset / ElementSize);
|
|
|
|
MemoryBufferMonitoredItem[] monitoredItems = m_monitoringTable[elementOffet];
|
|
|
|
if (monitoredItems != null)
|
|
{
|
|
int index = -1;
|
|
|
|
for (int ii = 0; ii < monitoredItems.Length; ii++)
|
|
{
|
|
if (Object.ReferenceEquals(monitoredItems[ii], monitoredItem))
|
|
{
|
|
index = ii;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index >= 0)
|
|
{
|
|
m_itemCount--;
|
|
|
|
if (monitoredItems.Length == 1)
|
|
{
|
|
monitoredItems = null;
|
|
}
|
|
else
|
|
{
|
|
monitoredItems = new MemoryBufferMonitoredItem[monitoredItems.Length - 1];
|
|
|
|
Array.Copy(m_monitoringTable[elementOffet], 0, monitoredItems, 0, index);
|
|
Array.Copy(m_monitoringTable[elementOffet], index + 1, monitoredItems, index, monitoredItems.Length - index);
|
|
}
|
|
|
|
m_monitoringTable[elementOffet] = monitoredItems;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles change events raised by the node.
|
|
/// </summary>
|
|
/// <param name="context">The system context.</param>
|
|
/// <param name="state">The node that raised the event.</param>
|
|
/// <param name="masks">What caused the event to be raised</param>
|
|
public void OnBufferChanged(int offset)
|
|
{
|
|
lock (m_dataLock)
|
|
{
|
|
if (m_monitoringTable != null)
|
|
{
|
|
int elementOffet = (int)(offset / ElementSize);
|
|
|
|
MemoryBufferMonitoredItem[] monitoredItems = m_monitoringTable[elementOffet];
|
|
|
|
if (monitoredItems != null)
|
|
{
|
|
DataValue value = new DataValue();
|
|
|
|
value.WrappedValue = GetValueAtOffset(offset);
|
|
value.StatusCode = StatusCodes.Good;
|
|
value.ServerTimestamp = DateTime.UtcNow;
|
|
value.SourceTimestamp = m_lastScanTime;
|
|
|
|
for (int ii = 0; ii < monitoredItems.Length; ii++)
|
|
{
|
|
monitoredItems[ii].QueueValue(value, null);
|
|
m_updateCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScanTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
DoScan(null);
|
|
}
|
|
|
|
void PublishTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
DateTime start1 = DateTime.UtcNow;
|
|
|
|
lock (m_dataLock)
|
|
{
|
|
if (m_itemCount > 0 && m_updateCount < m_itemCount)
|
|
{
|
|
Debug.WriteLine("{0:HH:mm:ss.fff} MEMORYBUFFER Reported {1}/{2} items ***.", DateTime.Now, m_updateCount, m_itemCount);
|
|
}
|
|
|
|
m_updateCount = 0;
|
|
}
|
|
|
|
DateTime end1 = DateTime.UtcNow;
|
|
|
|
double delta1 = ((double)(end1.Ticks-start1.Ticks))/TimeSpan.TicksPerMillisecond;
|
|
|
|
if (delta1 > 100)
|
|
{
|
|
Debug.WriteLine("****** PUBLISH DELAY ({0}ms) ******", delta1);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
private object m_dataLock = new object();
|
|
private IServerInternal m_server;
|
|
private INodeManager m_nodeManager;
|
|
private MemoryBufferMonitoredItem[][] m_monitoringTable;
|
|
private Dictionary<uint,MemoryBufferMonitoredItem> m_nonValueMonitoredItems;
|
|
private BuiltInType m_elementType;
|
|
private int m_elementSize;
|
|
private DateTime m_lastScanTime;
|
|
private int m_maximumScanRate;
|
|
private byte[] m_buffer;
|
|
private Timer m_scanTimer;
|
|
private int m_updateCount;
|
|
private int m_itemCount;
|
|
#endregion
|
|
}
|
|
}
|