Files
UA-.NETStandard/ComIOP/Common/Client/Da/ComDaClient.cs
T

1865 lines
61 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 System.Text;
using System.Runtime.InteropServices;
using OpcRcw.Comn;
using OpcRcw.Da;
using Opc.Ua;
using Opc.Ua.Server;
using Opc.Ua.Com;
using Opc.Ua.Com.Client;
namespace Opc.Ua.Com.Client
{
/// <summary>
/// Provides access to a COM DA server.
/// </summary>
public class ComDaClient : ComClient
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ComDaClient"/> class.
/// </summary>
/// <param name="configuration"></param>
public ComDaClient(ComDaClientConfiguration configuration) : base(configuration)
{
m_cache = new Dictionary<string, DaElement>();
m_configuration = configuration;
}
#endregion
#region Public Methods
/// <summary>
/// Finds the branch or item.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <returns>The metadata for the branch.</returns>
public DaElement FindElement(string itemId)
{
if (String.IsNullOrEmpty(itemId))
{
return null;
}
// check the cache.
DaElement element = null;
lock (m_cache)
{
if (m_cache.TryGetValue(itemId, out element))
{
return element;
}
}
// try extracting the name by parsing the item id.
string name = null;
if (m_configuration.ItemIdParser.Parse(this, m_configuration, itemId, out name))
{
element = CreateElement(itemId, name, null);
}
// need to do it the hard way by searching the address space.
else
{
IDaElementBrowser browser = CreateBrowser(itemId);
element = browser.Find(itemId, false);
browser.Dispose();
}
// save element in the cache.
if (element != null)
{
SaveElementInCache(element);
}
return element;
}
/// <summary>
/// Finds the property metadata for the specified item id.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="propertyId">The property id.</param>
/// <returns>The metadata for the property.</returns>
public DaProperty FindProperty(string itemId, int propertyId)
{
if (String.IsNullOrEmpty(itemId))
{
return null;
}
// check the cache.
DaElement element = null;
lock (m_cache)
{
if (m_cache.TryGetValue(itemId, out element))
{
if (element.Properties != null)
{
for (int ii = 0; ii < element.Properties.Length; ii++)
{
if (element.Properties[ii].PropertyId == propertyId)
{
return element.Properties[ii];
}
}
}
}
}
// check if the element has to be loaded.
if (element == null)
{
element = FindElement(itemId);
if (element == null)
{
return null;
}
}
// update the property list.
element.Properties = ReadAvailableProperties(itemId, false);
if (element.Properties != null)
{
for (int ii = 0; ii < element.Properties.Length; ii++)
{
if (element.Properties[ii].PropertyId == propertyId)
{
return element.Properties[ii];
}
}
}
// not found.
return null;
}
/// <summary>
/// Finds the item id for the parent of the element.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <returns>The item id for the parent of the element.</returns>
public string FindElementParentId(string itemId)
{
if (String.IsNullOrEmpty(itemId))
{
return null;
}
// check in cache.
DaElement element = null;
lock (m_cache)
{
if (m_cache.TryGetValue(itemId, out element))
{
if (element.ParentId != null)
{
return element.ParentId;
}
}
}
// check if the element is a branch.
if (!ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_TO, itemId))
{
return null;
}
// need to fetch list of branches to search.
EnumString enumerator = CreateEnumerator(true);
if (enumerator == null)
{
return null;
}
// find the child with the same item id.
try
{
// process all branches.
string name = null;
do
{
name = enumerator.Next();
// a null indicates the end of list.
if (name == null)
{
break;
}
// fetch the item id.
string targetId = GetItemId(name);
// check if target found.
if (targetId == itemId)
{
return GetItemId(String.Empty);
}
}
while (name != null);
}
finally
{
enumerator.Dispose();
}
return null;
}
/// <summary>
/// Creates a browser.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <returns>The browser object.</returns>
public IDaElementBrowser CreateBrowser(string itemId)
{
// check if DA3 browse interface is supported.
if (SupportsInterface<IOPCBrowse>())
{
return new Da20ElementBrowser(this, itemId, m_configuration.BrowseToNotSupported);
}
return new Da20ElementBrowser(this, itemId, m_configuration.BrowseToNotSupported);
}
/// <summary>
/// Creates a new instance of the client with the same configuration.
/// </summary>
/// <returns>The copy of the client.</returns>
public ComDaClient CloneClient()
{
ComDaClient clone = new ComDaClient(m_configuration);
clone.LocaleId = this.LocaleId;
clone.UserIdentity = this.UserIdentity;
clone.Key = this.Key;
return clone;
}
/// <summary>
/// Reads the status from the server.
/// </summary>
public OPCSERVERSTATUS? GetStatus()
{
string methodName = "IOPCServer.GetStatus";
try
{
IOPCServer server = BeginComCall<IOPCServer>(methodName, true);
IntPtr ppServerStatus;
server.GetStatus(out ppServerStatus);
if (ppServerStatus == IntPtr.Zero)
{
return null;
}
OpcRcw.Da.OPCSERVERSTATUS pStatus = (OpcRcw.Da.OPCSERVERSTATUS)Marshal.PtrToStructure(ppServerStatus, typeof(OpcRcw.Da.OPCSERVERSTATUS));
Marshal.DestroyStructure(ppServerStatus, typeof(OpcRcw.Da.OPCSERVERSTATUS));
Marshal.FreeCoTaskMem(ppServerStatus);
return pStatus;
}
catch (Exception e)
{
ComCallError(methodName, e);
return null;
}
finally
{
EndComCall(methodName);
}
}
/// <summary>
/// Reads the item and property values from the server.
/// </summary>
/// <param name="requests">The requests.</param>
public void Read(ReadRequestCollection requests)
{
if (m_supportsIOPCItemIO)
{
Da30ReadItemValues(requests);
}
else
{
Da20ReadItemValues(requests);
}
ReadPropertyValues(requests);
}
/// <summary>
/// Writes the item and property values to the server.
/// </summary>
/// <param name="requests">The requests.</param>
public void Write(WriteRequestCollection requests)
{
if (m_supportsIOPCItemIO)
{
Da30WriteItemValues(requests);
}
else
{
Da20WriteItemValues(requests);
}
}
#region Group Management
/// <summary>
/// Creates a group.
/// </summary>
public object CreateGroup(
int groupId,
int samplingInterval,
float deadband,
out int groupHandle,
out int revisedSamplingInterval)
{
groupHandle = 0;
revisedSamplingInterval = 0;
GCHandle hDeadband = GCHandle.Alloc(deadband, GCHandleType.Pinned);
string methodName = "IOPCServer.AddGroup";
try
{
IOPCServer server = BeginComCall<IOPCServer>(methodName, true);
object group = null;
Guid iid = typeof(OpcRcw.Da.IOPCItemMgt).GUID;
server.AddGroup(
String.Empty,
1,
samplingInterval,
0,
IntPtr.Zero,
hDeadband.AddrOfPinnedObject(),
LocaleId,
out groupHandle,
out revisedSamplingInterval,
ref iid,
out group);
return group;
}
catch (Exception e)
{
ComUtils.TraceComError(e, methodName);
return null;
}
finally
{
EndComCall(methodName);
hDeadband.Free();
}
}
/// <summary>
/// Removes a group.
/// </summary>
public void RemoveGroup(int groupHandle)
{
string methodName = "IOPCServer.RemoveGroup";
try
{
IOPCServer server = BeginComCall<IOPCServer>(methodName, true);
server.RemoveGroup(groupHandle, 0);
}
catch (Exception e)
{
ComUtils.TraceComError(e, methodName);
return;
}
finally
{
EndComCall(methodName);
}
}
#endregion
/// <summary>
/// Reads the property values and stores the results in the request objects.
/// </summary>
/// <param name="requests">The requests.</param>
public void ReadPropertyValues(List<ReadRequest> requests)
{
for (int ii = 0; ii < requests.Count; ii++)
{
ReadRequest request = requests[ii];
if (request != null && request.PropertyIds != null)
{
request.PropertyValues = ReadPropertyValues(
request.ItemId,
request.PropertyIds.ToArray());
}
}
}
/// <summary>
/// Read the available non-built in properties from the server.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="updateCache">if set to <c>true</c> the cache is updated.</param>
/// <returns>The array of properties.</returns>
public DaProperty[] ReadAvailableProperties(string itemId, bool updateCache)
{
string methodName = "IOPCItemProperties.QueryAvailableProperties";
// query for available properties.
int count = 0;
IntPtr pPropertyIds = IntPtr.Zero;
IntPtr pDescriptions = IntPtr.Zero;
IntPtr pDataTypes = IntPtr.Zero;
try
{
IOPCItemProperties server = BeginComCall<IOPCItemProperties>(methodName, true);
server.QueryAvailableProperties(
itemId,
out count,
out pPropertyIds,
out pDescriptions,
out pDataTypes);
}
catch (Exception e)
{
if (ComUtils.IsUnknownError(e, ResultIds.E_FAIL, ResultIds.E_INVALIDARG, ResultIds.E_UNKNOWNITEMID, ResultIds.E_INVALIDITEMID))
{
ComUtils.TraceComError(e, methodName);
}
return null;
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
int[] propertyIds = ComUtils.GetInt32s(ref pPropertyIds, count, true);
string[] descriptions = ComUtils.GetUnicodeStrings(ref pDescriptions, count, true);
short[] datatype = ComUtils.GetInt16s(ref pDataTypes, count, true);
List<DaProperty> properties = new List<DaProperty>();
for (int ii = 0; ii < count; ii++)
{
// do not return any of the built in properties.
if (propertyIds[ii] <= PropertyIds.TimeZone)
{
continue;
}
DaProperty property = new DaProperty();
property.PropertyId = propertyIds[ii];
property.Name = descriptions[ii];
property.DataType = datatype[ii];
properties.Add(property);
}
// fetch the item ids.
if (properties.Count > 0)
{
DaProperty[] array = properties.ToArray();
GetPropertyItemIds(itemId, array);
// update the cache.
if (updateCache)
{
lock (m_cache)
{
DaElement element = null;
if (m_cache.TryGetValue(itemId, out element))
{
element.Properties = array;
}
}
}
return array;
}
return null;
}
#endregion
#region Private Methods
/// <summary>
/// Saves the element in cache.
/// </summary>
/// <param name="element">The element.</param>
private void SaveElementInCache(DaElement element)
{
lock (m_cache)
{
m_cache[element.ItemId] = element;
}
}
/// <summary>
/// Creates a new element.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="name">The name.</param>
/// <param name="parentId">The parent id.</param>
/// <returns>The element.</returns>
private DaElement CreateElement(string itemId, string name, string parentId)
{
DaElement element = new DaElement();
element.ItemId = itemId;
UpdateElement(element, name, parentId);
return element;
}
/// <summary>
/// Updates a element.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="name">The name.</param>
/// <param name="parentId">The parent id.</param>
private void UpdateElement(DaElement element, string name, string parentId)
{
// only update the name if specified.
if (name != null)
{
element.Name = name;
}
// same for the parent id.
if (parentId != null)
{
element.ParentId = parentId;
}
// read item property values.
DaValue[] values = ReadPropertyValues(element.ItemId, m_CoreProperties);
// handle errors gracefully.
if (values == null || values.Length == 0)
{
element.ElementType = DaElementType.Branch;
return;
}
// must be an item if the data type property exists.
if (values[0].Error >= 0)
{
element.ElementType = DaElementType.Item;
element.DataType = values[0].GetValue<short>();
element.AccessRights = values[1].GetValue<int>();
element.ScanRate = values[2].GetValue<float>();
element.Description = values[3].GetValue<string>();
element.EngineeringUnits = values[4].GetValue<string>();
element.EuInfo = values[5].GetValue<string[]>();
element.EuType = values[6].GetValue<int>();
element.HighEU = values[7].GetValue<double>();
element.LowEU = values[8].GetValue<double>();
element.OpenLabel = values[9].GetValue<string>();
element.CloseLabel = values[10].GetValue<string>();
element.HighIR = values[11].GetValue<double>();
element.LowIR = values[12].GetValue<double>();
// check if the time zone is specified.
if (values[13].Error >= 0)
{
element.TimeZone = values[13].GetValue<int>();
}
// check for analog item (checks for HighEU if EuType property not supported).
if ((values[6].Error < 0 && values[7].Error >= 0) || element.EuType == (int)OPCEUTYPE.OPC_ANALOG)
{
element.ElementType = DaElementType.AnalogItem;
}
// check for enumerated item.
else if (element.EuType == (int)OPCEUTYPE.OPC_ENUMERATED)
{
element.ElementType = DaElementType.EnumeratedItem;
}
// check for digital item (checks for CloseLabel property).
else if (values[10].Error >= 0)
{
element.ElementType = DaElementType.DigitalItem;
}
}
// the element must be a branch.
else
{
element.ElementType = DaElementType.Branch;
// branches could have description property.
element.Description = values[3].GetValue<string>();
}
}
/// <summary>
/// Finds the element and updates the name if it is not already cached.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="name">The name.</param>
/// <param name="parentId">The parent id.</param>
/// <returns>The element.</returns>
private DaElement FindElement(string itemId, string name, string parentId)
{
if (String.IsNullOrEmpty(itemId))
{
return null;
}
// look in cache for existing element.
DaElement element = null;
lock (m_cache)
{
if (!m_cache.TryGetValue(itemId, out element))
{
element = null;
}
}
// create a new element.
if (element == null)
{
element = CreateElement(itemId, name, parentId);
SaveElementInCache(element);
}
// update the element.
element.Name = name;
element.ParentId = parentId;
return element;
}
/// <summary>
/// The core properties used to determine an item type.
/// </summary>
private static readonly int[] m_CoreProperties = new int[]
{
PropertyIds.DataType,
PropertyIds.AccessRights,
PropertyIds.ScanRate,
PropertyIds.Description,
PropertyIds.EngineeringUnits,
PropertyIds.EuInfo,
PropertyIds.EuType,
PropertyIds.HighEU,
PropertyIds.LowEU,
PropertyIds.OpenLabel,
PropertyIds.CloseLabel,
PropertyIds.HighIR,
PropertyIds.LowIR,
PropertyIds.TimeZone
};
/// <summary>
/// Changes the browse position.
/// </summary>
/// <param name="direction">The direction.</param>
/// <param name="name">The name.</param>
/// <returns>True if the operation was successful.</returns>
private bool ChangeBrowsePosition(OPCBROWSEDIRECTION direction, string name)
{
string methodName = "IOPCBrowseServerAddressSpace.ChangeBrowsePosition";
if (name == null)
{
name = String.Empty;
}
try
{
IOPCBrowseServerAddressSpace server = BeginComCall<IOPCBrowseServerAddressSpace>(methodName, true);
server.ChangeBrowsePosition(direction, name);
}
catch (Exception e)
{
if (ComUtils.IsUnknownError(e, ResultIds.E_FAIL, ResultIds.E_INVALIDARG))
{
ComCallError(methodName, e);
}
return false;
}
finally
{
EndComCall(methodName);
}
return true;
}
/// <summary>
/// Creates an enumerator for the current browse position.
/// </summary>
/// <param name="branches">if set to <c>true</c> then branches are enumerated.</param>
/// <returns>The wrapped enumerator.</returns>
private EnumString CreateEnumerator(bool branches)
{
IEnumString unknown = null;
string methodName = "IOPCBrowseServerAddressSpace.BrowseOPCItemIDs";
try
{
IOPCBrowseServerAddressSpace server = BeginComCall<IOPCBrowseServerAddressSpace>(methodName, true);
OPCBROWSETYPE browseType = OPCBROWSETYPE.OPC_BRANCH;
if (!branches)
{
browseType = OPCBROWSETYPE.OPC_LEAF;
}
server.BrowseOPCItemIDs(browseType, String.Empty, 0, 0, out unknown);
}
catch (Exception e)
{
ComCallError(methodName, e);
return null;
}
finally
{
EndComCall(methodName);
}
// wrapper the enumrator. hardcoding a buffer size of 256.
return new EnumString(unknown, 256);
}
/// <summary>
/// Looks up the item id for the node relative to the current browse position.
/// </summary>
/// <param name="browseName">Name of the element to lookup.</param>
/// <returns>The item id. Null if an error occurs.</returns>
private string GetItemId(string browseName)
{
string methodName = "IOPCBrowseServerAddressSpace.GetItemID";
try
{
IOPCBrowseServerAddressSpace server = BeginComCall<IOPCBrowseServerAddressSpace>(methodName, true);
string itemId = null;
server.GetItemID(browseName, out itemId);
return itemId;
}
catch (Exception e)
{
if (ComUtils.IsUnknownError(e, ResultIds.E_FAIL))
{
ComCallError(methodName, e);
}
return null;
}
finally
{
EndComCall(methodName);
}
}
/// <summary>
/// Reads the values of the properties from the server.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="propertyIds">The list of property ids to read. All properties are read if null.</param>
/// <returns>True if the element has properies.</returns>
private DaValue[] ReadPropertyValues(string itemId, params int[] propertyIds)
{
string methodName = "IOPCItemProperties.GetItemProperties";
// check for trivial case.
if (propertyIds == null)
{
return null;
}
int count = propertyIds.Length;
DaValue[] results = new DaValue[count];
// check for empty list.
if (count == 0)
{
return results;
}
// get the values from the server.
IntPtr pValues = IntPtr.Zero;
IntPtr pErrors = IntPtr.Zero;
try
{
IOPCItemProperties server = BeginComCall<IOPCItemProperties>(methodName, true);
server.GetItemProperties(
itemId,
count,
propertyIds,
out pValues,
out pErrors);
}
catch (Exception e)
{
if (ComUtils.IsUnknownError(e, ResultIds.E_FAIL, ResultIds.E_INVALIDARG, ResultIds.E_UNKNOWNITEMID, ResultIds.E_INVALIDITEMID))
{
ComUtils.TraceComError(e, methodName);
return null;
}
for (int ii = 0; ii < count; ii++)
{
DaValue result = results[ii] = new DaValue();
result.Value = null;
result.Quality = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD;
result.Timestamp = DateTime.UtcNow;
result.Error = Marshal.GetHRForException(e);
}
return results;
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
object[] values = ComUtils.GetVARIANTs(ref pValues, count, true);
int[] errors = ComUtils.GetInt32s(ref pErrors, count, true);
for (int ii = 0; ii < count; ii++)
{
DaValue result = results[ii] = new DaValue();
Variant convertedValue;
result.Error = ComDaClientNodeManager.RemoteToLocalValue(values[ii], out convertedValue);
if (result.Error < 0)
{
continue;
}
result.Value = convertedValue.Value;
result.Quality = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD;
result.Timestamp = DateTime.UtcNow;
result.Error = errors[ii];
}
return results;
}
/// <summary>
/// Gets the property item ids.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="properties">The properties to update.</param>
private void GetPropertyItemIds(string itemId, DaProperty[] properties)
{
string methodName = "IOPCItemProperties.LookupItemIDs";
int count = properties.Length;
// get list of ids to request.
int[] propertyIds = new int[count];
for (int ii = 0; ii < propertyIds.Length; ii++)
{
propertyIds[ii] = properties[ii].PropertyId;
}
// request the item ids.
IntPtr pItemIds = IntPtr.Zero;
IntPtr pErrors = IntPtr.Zero;
try
{
IOPCItemProperties server = BeginComCall<IOPCItemProperties>(methodName, true);
server.LookupItemIDs(
itemId,
count,
propertyIds,
out pItemIds,
out pErrors);
}
catch (Exception e)
{
if (ComUtils.IsUnknownError(e, ResultIds.E_FAIL, ResultIds.E_UNKNOWNITEMID, ResultIds.E_INVALIDITEMID))
{
ComUtils.TraceComError(e, methodName);
}
return;
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
string[] itemIds = ComUtils.GetUnicodeStrings(ref pItemIds, count, true);
int[] errors = ComUtils.GetInt32s(ref pErrors, count, true);
// update the objects.
for (int ii = 0; ii < count; ii++)
{
if (errors[ii] >= 0)
{
properties[ii].ItemId = itemIds[ii];
}
else
{
properties[ii].ItemId = null;
}
}
}
//private ComDaGroup m_group = null;
//private object m_groupLock = new object();
/// <summary>
/// An overrideable version of the Dispose.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
//lock (m_groupLock)
//{
// if (m_group != null)
// {
// Utils.SilentDispose(m_group);
// m_group = null;
// }
//}
}
base.Dispose(disposing);
}
/// <summary>
/// Reads the item values and stores the results in the request object.
/// </summary>
/// <param name="requests">The requests.</param>
private void Da20ReadItemValues(List<ReadRequest> requests)
{
// lock (m_groupLock)
{
// if (m_group == null)
//{
ComDaGroup m_group = new ComDaGroup(this, false);
//}
try
{
int count1 = 0;
GroupItem[] items = new GroupItem[requests.Count];
ReadRequest[] addItemRequests = new ReadRequest[requests.Count];
// create the items in the temporary group.
for (int ii = 0; ii < requests.Count; ii++)
{
ReadRequest request = requests[ii];
if (request == null)
{
continue;
}
if (!request.ValueRequired)
{
continue;
}
// add the item.
items[count1] = m_group.CreateItem(request.ItemId, 0, 0, true);
addItemRequests[count1] = request;
count1++;
}
// create the items on the server.
m_group.ApplyChanges();
// build the list of values to write.
int count2 = 0;
int[] serverHandles = new int[count1];
ReadRequest[] readRequests = new ReadRequest[count1];
for (int ii = 0; ii < count1; ii++)
{
// check for error on create.
GroupItem item = items[ii];
ReadRequest request = addItemRequests[ii];
if (item.ErrorId < 0)
{
request.Value = new DaValue();
request.Value.Error = item.ErrorId;
continue;
}
serverHandles[count2] = item.ServerHandle;
readRequests[count2] = request;
count2++;
}
if (count2 > 0)
{
// write values to the server.
DaValue[] values = m_group.SyncRead(serverHandles, count2);
// read the values.
for (int ii = 0; ii < count2; ii++)
{
if (values != null && values.Length > ii)
{
readRequests[ii].Value = values[ii];
}
else
{
readRequests[ii].Value = new DaValue() { Error = ResultIds.E_FAIL, Timestamp = DateTime.UtcNow };
}
}
// delete the items.
for (int ii = 0; ii < count1; ii++)
{
GroupItem item = items[ii];
if (item.ErrorId >= 0)
{
m_group.RemoveItem(item);
}
}
m_group.ApplyChanges();
}
}
finally
{
// delete the group and items.
m_group.Delete();
}
}
}
/// <summary>
/// Writes the item values to servers.
/// </summary>
/// <param name="requests">The requests.</param>
private void Da20WriteItemValues(List<WriteRequest> requests)
{
//lock (m_groupLock)
{
//if (m_group == null)
//{
ComDaGroup m_group = new ComDaGroup(this, false);
//}
try
{
int count1 = 0;
GroupItem[] items = new GroupItem[requests.Count];
WriteRequest[] addItemRequests = new WriteRequest[requests.Count];
object[] convertedValues = new object[requests.Count];
// create the items in the temporary group.
for (int ii = 0; ii < requests.Count; ii++)
{
WriteRequest request = requests[ii];
if (request == null)
{
continue;
}
// status code writes not supported.
if (request.Value.StatusCode != StatusCodes.Good)
{
request.Error = ResultIds.E_NOTSUPPORTED;
continue;
}
// timestamp writes not supported.
if (request.Value.ServerTimestamp != DateTime.MinValue)
{
request.Error = ResultIds.E_NOTSUPPORTED;
continue;
}
// timestamp writes not supported.
if (request.Value.SourceTimestamp != DateTime.MinValue)
{
request.Error = ResultIds.E_NOTSUPPORTED;
continue;
}
// convert to a DA compatible type.
object convertedValue = null;
request.Error = ComDaClientNodeManager.LocalToRemoteValue(request.Value.WrappedValue, out convertedValue);
if (request.Error < 0)
{
continue;
}
// add the item.
items[count1] = m_group.CreateItem(request.ItemId, 0, 0, true);
addItemRequests[count1] = request;
convertedValues[count1] = convertedValue;
count1++;
}
// create the items on the server.
m_group.ApplyChanges();
// build the list of values to write.
int count2 = 0;
int[] serverHandles = new int[count1];
object[] values = new object[count1];
WriteRequest[] writeRequests = new WriteRequest[count1];
for (int ii = 0; ii < count1; ii++)
{
// check for error on create.
GroupItem item = items[ii];
WriteRequest request = addItemRequests[ii];
if (item.ErrorId < 0)
{
request.Error = item.ErrorId;
continue;
}
serverHandles[count2] = item.ServerHandle;
values[count2] = convertedValues[ii];
writeRequests[count2] = request;
count2++;
}
if (count2 > 0)
{
// write values to the server.
int[] errors = m_group.SyncWrite(serverHandles, values, count2);
// read the errors.
for (int ii = 0; ii < count2; ii++)
{
if (errors != null && errors.Length > ii)
{
writeRequests[ii].Error = errors[ii];
}
else
{
writeRequests[ii].Error = ResultIds.E_FAIL;
}
}
// delete the items.
for (int ii = 0; ii < count1; ii++)
{
GroupItem item = items[ii];
if (item.ErrorId >= 0)
{
m_group.RemoveItem(item);
}
}
m_group.ApplyChanges();
}
}
finally
{
// delete the group and items.
m_group.Delete();
}
}
}
/// <summary>
/// Reads the values using the DA3 interfaces.
/// </summary>
/// <param name="requests">The requests.</param>
private void Da30ReadItemValues(List<ReadRequest> requests)
{
string[] itemIds = new string[requests.Count];
int[] maxAge = new int[requests.Count];
List<int> requestIndexes = new List<int>();
int count = 0;
for (int ii = 0; ii < requests.Count; ii++)
{
if (!requests[ii].ValueRequired)
{
continue;
}
itemIds[count] = requests[ii].ItemId;
maxAge[count] = 0;
requestIndexes.Add(ii);
count++;
}
if (count <= 0)
{
return;
}
IntPtr ppValues = IntPtr.Zero;
IntPtr ppQualities = IntPtr.Zero;
IntPtr ppTimeStamps = IntPtr.Zero;
IntPtr ppErrors = IntPtr.Zero;
string methodName = "IOPCItemIO.Read";
try
{
IOPCItemIO server = BeginComCall<IOPCItemIO>(methodName, true);
server.Read(
requestIndexes.Count,
itemIds,
maxAge,
out ppValues,
out ppQualities,
out ppTimeStamps,
out ppErrors);
}
catch (Exception e)
{
ComUtils.TraceComError(e, methodName);
return;
}
finally
{
EndComCall(methodName);
}
System.Runtime.InteropServices.ComTypes.FILETIME[] timeStamps = ComUtils.GetFILETIMEs(ref ppTimeStamps, requestIndexes.Count);
object[] values = ComUtils.GetVARIANTs(ref ppValues, count, true);
short[] qualities = ComUtils.GetInt16s(ref ppQualities, count, true);
int[] errors = ComUtils.GetInt32s(ref ppErrors, count, true);
DaValue[] daValues = ComDaGroup.GetItemValues(count, values, qualities, timeStamps, errors);
for (int ii = 0; ii < requestIndexes.Count; ii++)
{
requests[requestIndexes[ii]].Value = daValues[ii];
}
}
/// <summary>
/// Writes the values using the DA3 interfaces.
/// </summary>
/// <param name="requests">The requests.</param>
private void Da30WriteItemValues(List<WriteRequest> requests)
{
int count = 0;
string[] itemIDs = new string[requests.Count];
OpcRcw.Da.OPCITEMVQT[] values = new OpcRcw.Da.OPCITEMVQT[requests.Count];
WriteRequest[] writeRequests = new WriteRequest[requests.Count];
for (int ii = 0; ii < requests.Count; ii++)
{
WriteRequest request = requests[ii];
if (request == null)
{
continue;
}
// convert to a DA compatible type.
object convertedValue = null;
request.Error = ComDaClientNodeManager.LocalToRemoteValue(request.Value.WrappedValue, out convertedValue);
if (request.Error < 0)
{
continue;
}
itemIDs[count] = request.ItemId;
values[count].vDataValue = convertedValue;
values[count].bQualitySpecified = 0;
values[count].bTimeStampSpecified = 0;
// check for quality.
values[count].wQuality = ComUtils.GetQualityCode(request.Value.StatusCode);
if (values[count].wQuality != Qualities.OPC_QUALITY_GOOD)
{
values[count].bQualitySpecified = 1;
}
// check for server timestamp.
if (request.Value.ServerTimestamp != DateTime.MinValue)
{
values[count].ftTimeStamp = ComUtils.GetFILETIME(request.Value.ServerTimestamp);
values[count].bTimeStampSpecified = 1;
}
// ignore server timestamp if source timestamp is provided.
if (request.Value.SourceTimestamp != DateTime.MinValue)
{
values[count].ftTimeStamp = ComUtils.GetFILETIME(request.Value.SourceTimestamp);
values[count].bTimeStampSpecified = 1;
}
writeRequests[count] = request;
count++;
}
IntPtr ppErrors = IntPtr.Zero;
string methodName = "IOPCItemIO.WriteVQT";
try
{
IOPCItemIO server = BeginComCall<IOPCItemIO>(methodName, true);
server.WriteVQT(
count,
itemIDs,
values,
out ppErrors);
}
catch (Exception e)
{
ComUtils.TraceComError(e, methodName);
return;
}
finally
{
EndComCall(methodName);
}
int[] errors = ComUtils.GetInt32s(ref ppErrors, count, true);
for (int ii = 0; ii < count; ii++)
{
writeRequests[ii].Error = errors[ii];
}
}
/// <summary>
/// Called immediately after connecting to the server.
/// </summary>
protected override void OnConnected()
{
m_supportsIOPCItemIO = SupportsInterface<IOPCItemIO>();
}
#endregion
#region Da20ElementBrowser Class
/// <summary>
/// Browses for DA elements using the DA 2.0 interfaces.
/// </summary>
private class Da20ElementBrowser : IDaElementBrowser
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Da20ElementBrowser"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="itemId">The item id.</param>
/// <param name="browseToNotSupported">if set to <c>true</c> BROWSE_TO not supported.</param>
public Da20ElementBrowser(
ComDaClient client,
string itemId,
bool browseToNotSupported)
{
m_client = client;
m_itemId = itemId;
m_branches = false;
m_browseToNotSupported = browseToNotSupported;
}
#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)
{
Utils.SilentDispose(m_enumerator);
m_enumerator = null;
Utils.SilentDispose(m_clone);
m_clone = null;
}
}
#endregion
/// <summary>
/// Sets the browse position using the BROWSE_DOWN operator.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <returns>True if the item id was found.</returns>
private bool SearchAndSetBrowsePosition(string itemId)
{
EnumString enumerator = m_clone.CreateEnumerator(true);
// a null indicates an error.
if (enumerator == null)
{
return false;
}
// collect names.
Queue<string> names = new Queue<string>();
try
{
do
{
// fetch the next name.
string name = enumerator.Next();
// a null indicates the end of list.
if (name == null)
{
break;
}
// fetch the item id.
string branchId = m_clone.GetItemId(name);
if (branchId == itemId)
{
return m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_DOWN, name);
}
names.Enqueue(name);
}
while (true);
}
finally
{
enumerator.Dispose();
}
// recursively search tree.
while (names.Count > 0)
{
string name = names.Dequeue();
if (!m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_DOWN, name))
{
continue;
}
if (SearchAndSetBrowsePosition(itemId))
{
return true;
}
m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, null);
}
// not found anywhere.
return false;
}
/// <summary>
/// Sets the browse position.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <returns>True if the item id was found.</returns>
private bool SetBrowsePosition(string itemId)
{
// go directly to item id.
if (m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_TO, itemId))
{
return true;
}
// nothing more to do isf browse to supported.
if (!m_browseToNotSupported)
{
return false;
}
// do it the hard way.
while (m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, null))
{
// skip to root.
}
// nothing more to do at root.
if (String.IsNullOrEmpty(itemId))
{
return true;
}
// find item id in tree.
return SearchAndSetBrowsePosition(itemId);
}
#region IDaElementBrowser Members
/// <summary>
/// Returns the next DA element.
/// </summary>
/// <returns>A DA element. Null if nothing left to browse.</returns>
public DaElement Next()
{
// check if already completed.
if (m_completed)
{
return null;
}
// create the enumerator if not already created.
if (m_enumerator == null)
{
// need to clone the client since ChangeBrowsePosition prevents multiple
// simultaneous browse operations.
m_clone = m_client.CloneClient();
m_clone.CreateInstance();
// nothing to browse if change browse position failed.
if (!SetBrowsePosition(m_itemId))
{
return null;
}
m_enumerator = m_clone.CreateEnumerator(false);
m_branches = false;
// a null indicates an error.
if (m_enumerator == null)
{
m_completed = true;
return null;
}
}
// need a loop in case errors occur fetching element metadata.
DaElement element = null;
do
{
// fetch the next name.
string name = m_enumerator.Next();
// a null indicates the end of list.
if (name == null)
{
if (!m_branches)
{
m_branches = true;
m_enumerator.Dispose();
m_enumerator = m_clone.CreateEnumerator(true);
continue;
}
m_completed = true;
return null;
}
// suppress duplicates when a item is also a branch.
if (m_branches)
{
if (m_itemNames != null)
{
if (m_itemNames.ContainsKey(name))
{
continue;
}
}
}
// save the item name to allow checks for duplicates.
else
{
if (m_itemNames == null)
{
m_itemNames = new Dictionary<string, object>();
}
m_itemNames[name] = null;
}
// fetch the item id.
string itemId = m_clone.GetItemId(name);
// fetch the metadata.
element = m_client.FindElement(itemId, name, m_itemId);
}
while (element == null);
// return element.
return element;
}
/// <summary>
/// Finds the element with the specified item id.
/// </summary>
/// <param name="targetId">The target id.</param>
/// <param name="isItem">if set to <c>true</c> [is item].</param>
/// <returns>The element if found.</returns>
public DaElement Find(string targetId, bool isItem)
{
// check for root.
if (String.IsNullOrEmpty(targetId))
{
m_client.FindElement(targetId, String.Empty, String.Empty);
}
// create the clone used to find the item.
if (m_clone == null)
{
m_clone = m_client.CloneClient();
m_clone.CreateInstance();
}
bool recursive = false;
// check if it is possible to browse to the item.
if (m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_TO, targetId))
{
if (!m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, String.Empty))
{
return null;
}
}
// need to do it the hard way from the root of the hierarchy.
else
{
while (m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, String.Empty));
recursive = true;
}
// get the item id for the parent.
string parentId = m_clone.GetItemId(String.Empty);
// find the target.
return Find(parentId, targetId, false, recursive);
}
/// <summary>
/// Finds the element with the specified item id.
/// </summary>
/// <param name="parentId">The parent id.</param>
/// <param name="targetId">The target id.</param>
/// <param name="isItem">if set to <c>true</c> the element is a item.</param>
/// <param name="recursive">if set to <c>true</c> [recursive].</param>
/// <returns>The element if found.</returns>
public DaElement Find(string parentId, string targetId, bool isItem, bool recursive)
{
// create the enumerator if not already created.
m_enumerator = m_clone.CreateEnumerator(false);
// a null indicates an error.
if (m_enumerator == null)
{
return null;
}
string name = null;
// process all items.
do
{
// fetch the next name.
name = m_enumerator.Next();
// a null indicates the end of list.
if (name == null)
{
break;
}
// fetch the item id.
string itemId = m_clone.GetItemId(name);
// fetch the metadata.
DaElement element = m_client.FindElement(itemId, name, parentId);
// check if target found.
if (targetId == itemId)
{
return element;
}
}
while (true);
m_enumerator.Dispose();
m_enumerator = null;
List<DaElement> branches = new List<DaElement>();
// fetch the branches of the target is a branch or if a recursive search is required.
if (!isItem || recursive)
{
// need to fetch list of branches to search.
m_enumerator = m_clone.CreateEnumerator(true);
if (m_enumerator == null)
{
return null;
}
// process all branches.
do
{
name = m_enumerator.Next();
// a null indicates the end of list.
if (name == null)
{
break;
}
// fetch the item id.
string itemId = m_clone.GetItemId(name);
// fetch the metadata.
DaElement element = m_client.FindElement(itemId, name, parentId);
// save branch for recursive search if not found at this level.
if (recursive)
{
branches.Add(element);
}
// check if target found.
if (targetId == itemId)
{
return element;
}
}
while (name != null);
m_enumerator.Dispose();
m_enumerator = null;
}
// all done if not doing a recursive search.
if (!recursive)
{
return null;
}
// recursively search hierarchy.
for (int ii = 0; ii < branches.Count; ii++)
{
m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_DOWN, branches[ii].Name);
DaElement element = Find(branches[ii].ItemId, targetId, isItem, recursive);
if (element != null)
{
return element;
}
m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, String.Empty);
}
// not found.
return null;
}
#endregion
#region Private Fields
private ComDaClient m_client;
private ComDaClient m_clone;
private string m_itemId;
private Opc.Ua.Com.Client.EnumString m_enumerator;
private bool m_branches;
private bool m_completed;
private Dictionary<string,object> m_itemNames;
private bool m_browseToNotSupported;
#endregion
}
#endregion
#region Private Fields
private Dictionary<string,DaElement> m_cache;
private ComDaClientConfiguration m_configuration;
private bool m_supportsIOPCItemIO;
#endregion
}
/// <summary>
/// An interface to an object that browses a DA COM server.
/// </summary>
public interface IDaElementBrowser : IDisposable
{
/// <summary>
/// Returns the next element.
/// </summary>
/// <returns>The next element. Null if nothing found or an error occurs.</returns>
DaElement Next();
/// <summary>
/// Finds the element with the specified item id.
/// </summary>
/// <param name="targetId">The target id.</param>
/// <param name="isBranch">if set to <c>true</c> the element is a branch.</param>
/// <returns>The element if found.</returns>
DaElement Find(string targetId, bool isBranch);
}
}