OPCUA讨论(四)——客户端代码解读2
本系列文章:
OPCUA 评论(一)——测验与开发环境树立
OPCUA 评论(二)——服务器节点初探
OPCUA 评论(三)——客户端代码解读
OPCUA 评论(四)——客户端代码解读2
前文中咱们评论了OPCUA客户端运用的根本装备,以及怎么与OPCUA服务器树立会话(Session)。
OPCUA评论(三)——客户端代码解读1
该项目源码地址:https://gitee.com/zuoquangong/opcuaapi
本文咱们将评论怎么完成服务器节点阅读(Browse)功用。
一、阅读(Browse)
1.1 节点(Node)与引证(Reference)
首要咱们要了解OPCUA服务器上的内容是怎样安排的。下图是一个简化示例:
能够看到这个示例展现的是一个树状结构。
节点(Node) 是根本单位,节点内部包括了多个特点,不同节点内部特点数目因 节点类(NodeClass)不同而有所差异。
引证(Reference)理解为节点之间的联系,用图的视点来说是“边”。引证也分多个品种,例如咱们在Prosys的OPCUA服务器上能够看到:
引证的不同类型标志着节点之间的不同联系(HasTypeDefinition、Organizes等)。能够暂不关怀这些引证具体有哪些类型,因为不影响咱们阅读。想具体了解引证的界说和类型,请检查官方文档规范引证类型(Standard Reference Types)
1.2 阅读函数
运用阅读函数,则需了解要传哪些参数。
阅读函数的参数:
public virtual ResponseHeader Browse(
RequestHeader requestHeader, //*客户端恳求报文的头部,可为null
ViewDescription view, //*所阅读的视图的ID,可为null
NodeId nodeToBrowse, //所阅读的节点ID
uint maxResultsToReturn, //最大回来成果数目
BrowseDirection browseDirection, //阅读方向(正向阅读子节点/反向阅读父节点)
NodeId referenceTypeId, //阅读的引证类型ID,即要阅读哪类引证
bool includeSubtypes, //是否包括子类型
uint nodeClassMask, //对查询成果的节点类进行挑选
out byte[] continuationPoint, //接续点,因为OPCUA报文长度约束,服务器或许不会一次回来一切阅读成果(太长),经过接续点再次发送恳求能够持续获取剩下的阅读成果(服务器回来)
out ReferenceDescriptionCollection references //回来阅读到的引证信息调集(服务器回来)
)
接续点效果:
点击检查代码
/// <summary>
/// 阅读节点
/// 经过nodeId遍历其子节点(根节点nodeId="")
/// 反向获取父节点时inverse=true
/// </summary>
/// <param name="nodeId">想要阅读的节点的ID</param>
/// <param name="inverse">是否反向阅读</param>
/// <returns></returns>
public ReferenceDescriptionCollection Browse(string nodeId="",bool inverse=false)
{
if (current_session == null) //阅读功用树立在会话的基础上
return null;
if (current_session.Connected == false)//阅读功用树立在会话衔接的基础上
return null;
ReferenceDescriptionCollection referenceDescriptionCollection; //当时获取到的子节点的信息
byte[] continuationPoint; //接续点。因为OPCUA报文长度约束,服务器或许不会一次回来一切阅读成果(太长),经过接续点再次发送恳求能够持续获取剩下的阅读成果
if (nodeId == "") //nodeId为空时,默许阅读根节点
{
current_session.Browse(
null, //requestHeader
null, //ViewDescription view
ObjectIds.RootFolder, //NodeId-根目录
0u, //maxResultToReturn
BrowseDirection.Forward, //正向-遍历子节点(反向Inverse-回溯父节点)
ReferenceTypeIds.HierarchicalReferences, //NodeId ReferenceTypeId
true, //includeSubtypes
(uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, //nodeClassMask
out continuationPoint, //byte[] continuationPoint(用于获取后续成果)
out referenceDescriptionCollection
); //references
}
else if(inverse == true) //反向查询父节点
{
//MessageBox.Show(nodeId);
current_session.Browse(null, null,
nodeId,
1, //仅回来一个引证(父节点就一个)
BrowseDirection.Inverse, //反向查询标志
ReferenceTypeIds.HierarchicalReferences,
true,
0,
out continuationPoint,
out referenceDescriptionCollection);
}
else //正向查询子节点
{
ReferenceDescriptionCollection nextreferenceDescriptionCollection; //当时获取到的子节点的信息
byte[] revisedContinuationPoint; //接续点,用于获取后续成果
current_session.Browse(
null,
null,
nodeId,
0u,
BrowseDirection.Forward,
ReferenceTypeIds.HierarchicalReferences,
true,
0,//不进行nodeClass挑选
out continuationPoint,
out referenceDescriptionCollection
);
while (continuationPoint != null)//循着起始点遍历悉数子节点
{
current_session.BrowseNext(null, false, continuationPoint, out revisedContinuationPoint, out nextreferenceDescriptionCollection);//持续阅读下一个节点,回来的nextreferenceDescriptionCollection为所需信息
referenceDescriptionCollection.AddRange(nextreferenceDescriptionCollection);
continuationPoint = revisedContinuationPoint;//更新
}
}
return referenceDescriptionCollection;
}
1.3 阅读成果的解析
能够看到阅读(Browse)函数回来的是一个类型为ReferenceDescriptionCollection
的成果,即ReferenceDescription
的调集。
一个ReferenceDescription
目标可包括以下信息(成员):
经过其间的NodeId
能够获取更具体的节点信息。
二、获取某个节点的具体信息
已知以下两个函数的功用:
1.调用ReadNode
函数能够获取除了节点值之外的悉数特点信息。
2.调用ReadValue
函数能够获得节点值。
因而,经过调用上述两个函数能够获得节点一切特点信息。以下为示例:
点击检查代码/// <summary>
/// 获取某个节点的具体信息
/// </summary>
/// <param name="refDesc">节点描绘信息,用于读取具体信息</param>
/// <returns></returns>
public object[] GetNodeInfo(ReferenceDescription refDesc)
{
if(refDesc==null)
{
return null;
}
object[] rows=null;
Node node = current_session.ReadNode(refDesc.NodeId.ToString());
//节点值需求独自获取,运用ReadValue函数
string value = this.ReadValue(refDesc.NodeId.ToString());
try
{
VariableNode variableNode = new VariableNode();
string[] rowx = new string[] { "节点值", value };
string[] row1 = new string[] { "节点类", refDesc.NodeClass.ToString() };
string[] row2 = new string[] { "节点ID", refDesc.NodeId.ToString() };
string[] row3 = new string[] { "命名空间索引", refDesc.NodeId.NamespaceIndex.ToString() };
string[] row4 = new string[] { "Identifier Type", refDesc.NodeId.IdType.ToString() };
string[] row5 = new string[] { "Identifier", refDesc.NodeId.Identifier.ToString() };
string[] row6 = new string[] { "阅读称号", refDesc.BrowseName.ToString() };
string[] row7 = new string[] { "显现称号", refDesc.DisplayName.ToString() };
string[] row8 = new string[] { "描绘", "null" };
if (node.Description != null)
{
try { row8 = new string[] { "Description", node.Description.ToString() }; }
catch { row8 = new string[] { "Description", "null" }; }
}
string typeDefinition = "";
if ((NodeId)refDesc.TypeDefinition.NamespaceIndex == 0)
{
typeDefinition = refDesc.TypeDefinition.ToString();
}
else
{
typeDefinition = "Struct/UDT: " + refDesc.TypeDefinition.ToString(); //类型为结构体或数据表
}
string[] row9 = new string[] { "类型界说", typeDefinition };
string[] row10 = new string[] { "Write Mask", node.WriteMask.ToString() };
string[] row11 = new string[] { "User Write Mask", node.UserWriteMask.ToString() };
if (node.NodeClass == NodeClass.Variable)
{
variableNode = (VariableNode)node.DataLock;
List<NodeId> nodeIds = new List<NodeId>();
IList<string> displayNames = new List<string>();
IList<ServiceResult> errors = new List<ServiceResult>();
NodeId nodeId = new NodeId(variableNode.DataType);
nodeIds.Add(nodeId);
current_session.ReadDisplayName(nodeIds, out displayNames, out errors);
int valueRank = variableNode.ValueRank;
List<string> arrayDimension = new List<string>();
string[] row12 = new string[] { "数据类型", displayNames[0] };
string[] row13 = new string[] { "Value Rank", valueRank.ToString() };
//Define array dimensions depending on the value rank
if (valueRank > 0) //More dimensional arrays
{
for (int i = 0; i < valueRank; i++)
{
arrayDimension.Add(variableNode.ArrayDimensions.ElementAtOrDefault(i).ToString());
}
}
else
{
arrayDimension.Add("标量");
}
string[] row14 = new string[] { "数组维数", String.Join(";", arrayDimension.ToArray()) };
string[] row15 = new string[] { "拜访等级", variableNode.AccessLevel.ToString() };
string[] row16 = new string[] { "最小采样距离", variableNode.MinimumSamplingInterval.ToString() };
string[] row17 = new string[] { "前史化", variableNode.Historizing.ToString() };
rows = new object[] { rowx, row1, row2, row3, row4, row5, row6, row7, row8, row9, row10, row11, row12, row13, row14, row15, row16, row17 };
}
else
{
rows = new object[] { rowx, row1, row2, row3, row4, row5, row6, row7, row8, row9, row10, row11 };
}
//MessageBox.Show(refDesc.ToString());
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
return rows;
}
/// <summary>
/// 读取节点的值(Value)
/// 依据节点ID,读取节点变量值
/// </summary>
/// <param name="nodeIdString"></param>
/// <returns></returns>
public string ReadValue(string nodeIdString)
{
NodeId nodeId=new NodeId(nodeIdString);
//MessageBox.Show(nodeIdString);
try
{
var res = current_session.ReadValue(nodeId);
if (res != null)
{
if (res.Value != null)
{
return res.Value.ToString();
}
}
}
catch(Exception ex)
{
return "null";
}
return "";
}
附录
OPCUA官方文档——地址空间
.NET Based OPC UA Client/Server/PubSub SDK 4.0.2.550
总结
本文介绍了怎么阅读OPCUA服务器上的节点以及怎么获取节点具体信息。
*附言
因为作者水平有限,或许在文章中呈现过错或不妥描绘,如有发现此类情况期望您能及时供给反应,非常感谢!
假如感觉本文对您有所协助,期望为文章点个引荐,谢谢。
作者联系方式,163邮箱:[email protected]