ToB企服应用市场:ToB评测及商务社交产业平台

标题: OPCUA探讨(四)——客户端代码解读2 [打印本页]

作者: 我可以不吃啊    时间: 2024-12-9 10:19
标题: 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 浏览函数

使用浏览函数,则需了解要传哪些参数。
浏览函数的参数:
点击查看代码
  1. public virtual ResponseHeader Browse(
  2. RequestHeader requestHeader,     //*客户端请求报文的头部,可为null
  3. ViewDescription view,            //*所浏览的视图的ID,可为null
  4. NodeId nodeToBrowse,             //所浏览的节点ID
  5. uint maxResultsToReturn,         //最大返回结果数目
  6. BrowseDirection browseDirection, //浏览方向(正向浏览子节点/反向浏览父节点)
  7. NodeId referenceTypeId,          //浏览的引用类型ID,即要浏览哪类引用
  8. bool includeSubtypes,            //是否包括子类型
  9. uint nodeClassMask,              //对查询结果的节点类进行筛选
  10. out byte[] continuationPoint,    //接续点,由于OPCUA报文长度限制,服务器可能不会一次返回所有浏览结果(太长),通过接续点再次发送请求可以继续获取剩余的浏览结果(服务器返回)
  11. out ReferenceDescriptionCollection references //返回浏览到的引用信息集合(服务器返回)
  12. )
复制代码
接续点作用:

点击查看代码
  1. /// <summary>
  2. /// 浏览节点
  3. /// 通过nodeId遍历其子节点(根节点nodeId="")
  4. /// 反向获取父节点时inverse=true
  5. /// </summary>
  6. /// <param name="nodeId">想要浏览的节点的ID</param>
  7. /// <param name="inverse">是否反向浏览</param>
  8. /// <returns></returns>
  9. public ReferenceDescriptionCollection Browse(string nodeId="",bool inverse=false)
  10. {
  11.     if (current_session == null) //浏览功能建立在会话的基础上
  12.         return null;
  13.     if (current_session.Connected == false)//浏览功能建立在会话连接的基础上
  14.         return null;
  15.     ReferenceDescriptionCollection referenceDescriptionCollection; //当前获取到的子节点的信息
  16.     byte[] continuationPoint; //接续点。由于OPCUA报文长度限制,服务器可能不会一次返回所有浏览结果(太长),通过接续点再次发送请求可以继续获取剩余的浏览结果
  17.    
  18.     if (nodeId == "") //nodeId为空时,默认浏览根节点
  19.     {
  20.         current_session.Browse(
  21.             null, //requestHeader
  22.             null, //ViewDescription view
  23.             ObjectIds.RootFolder, //NodeId-根目录
  24.             0u, //maxResultToReturn
  25.             BrowseDirection.Forward, //正向-遍历子节点(反向Inverse-回溯父节点)
  26.             ReferenceTypeIds.HierarchicalReferences, //NodeId ReferenceTypeId
  27.             true, //includeSubtypes
  28.             (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, //nodeClassMask
  29.             out continuationPoint, //byte[] continuationPoint(用于获取后续结果)
  30.             out referenceDescriptionCollection
  31.         ); //references
  32.     }
  33.     else if(inverse == true) //反向查询父节点
  34.     {
  35.         //MessageBox.Show(nodeId);
  36.         current_session.Browse(null, null,
  37.             nodeId,
  38.             1, //仅返回一个引用(父节点就一个)
  39.             BrowseDirection.Inverse, //反向查询标志
  40.             ReferenceTypeIds.HierarchicalReferences,
  41.             true,
  42.             0,
  43.             out continuationPoint,
  44.             out referenceDescriptionCollection);
  45.     }
  46.     else //正向查询子节点
  47.     {
  48.         ReferenceDescriptionCollection nextreferenceDescriptionCollection; //当前获取到的子节点的信息
  49.         byte[] revisedContinuationPoint; //接续点,用于获取后续结果
  50.         current_session.Browse(
  51.             null,
  52.             null,
  53.             nodeId,
  54.             0u,
  55.             BrowseDirection.Forward,
  56.             ReferenceTypeIds.HierarchicalReferences,
  57.             true,
  58.             0,//不进行nodeClass筛选
  59.             out continuationPoint,
  60.             out referenceDescriptionCollection
  61.         );
  62.         while (continuationPoint != null)//循着起始点遍历全部子节点
  63.         {
  64.             current_session.BrowseNext(null, false, continuationPoint, out revisedContinuationPoint, out nextreferenceDescriptionCollection);//继续浏览下一个节点,返回的nextreferenceDescriptionCollection为所需信息
  65.             referenceDescriptionCollection.AddRange(nextreferenceDescriptionCollection);
  66.             continuationPoint = revisedContinuationPoint;//更新
  67.         }
  68.     }
  69.     return referenceDescriptionCollection;
  70. }
复制代码
1.3 浏览结果的剖析

可以看到浏览(Browse)函数返回的是一个类型为ReferenceDescriptionCollection的结果,即ReferenceDescription的集合。
一个ReferenceDescription对象可包含以下信息(成员):

通过其中的NodeId可以获取更详细的节点信息。
二、获取某个节点的详细信息

已知以下两个函数的功能:
1.调用ReadNode函数可以获取除了节点值之外的全部属性信息。
2.调用ReadValue函数可以得到节点值。
因此,通过调用上述两个函数可以得到节点全部属性信息。以下为示例:
点击查看代码
  1. /// <summary>
  2. /// 获取某个节点的详细信息
  3. /// </summary>
  4. /// <param name="refDesc">节点描述信息,用于读取详细信息</param>
  5. /// <returns></returns>
  6. public object[] GetNodeInfo(ReferenceDescription refDesc)
  7. {
  8.     if(refDesc==null)
  9.     {
  10.         return null;
  11.     }
  12.     object[] rows=null;
  13.     Node node = current_session.ReadNode(refDesc.NodeId.ToString());
  14.     //节点值需要单独获取,使用ReadValue函数
  15.     string value = this.ReadValue(refDesc.NodeId.ToString());
  16.     try
  17.     {
  18.         VariableNode variableNode = new VariableNode();
  19.         string[] rowx = new string[] { "节点值", value };
  20.         string[] row1 = new string[] { "节点类", refDesc.NodeClass.ToString() };
  21.         string[] row2 = new string[] { "节点ID", refDesc.NodeId.ToString() };
  22.         string[] row3 = new string[] { "命名空间索引", refDesc.NodeId.NamespaceIndex.ToString() };
  23.         string[] row4 = new string[] { "Identifier Type", refDesc.NodeId.IdType.ToString() };
  24.         string[] row5 = new string[] { "Identifier", refDesc.NodeId.Identifier.ToString() };
  25.         string[] row6 = new string[] { "浏览名称", refDesc.BrowseName.ToString() };
  26.         string[] row7 = new string[] { "显示名称", refDesc.DisplayName.ToString() };
  27.         string[] row8 = new string[] { "描述", "null" };
  28.         if (node.Description != null)
  29.         {
  30.             try { row8 = new string[] { "Description", node.Description.ToString() }; }
  31.             catch { row8 = new string[] { "Description", "null" }; }
  32.         }
  33.         string typeDefinition = "";
  34.         if ((NodeId)refDesc.TypeDefinition.NamespaceIndex == 0)
  35.         {
  36.             typeDefinition = refDesc.TypeDefinition.ToString();
  37.         }
  38.         else
  39.         {
  40.             typeDefinition = "Struct/UDT: " + refDesc.TypeDefinition.ToString(); //类型为结构体或数据表
  41.         }
  42.         string[] row9 = new string[] { "类型定义", typeDefinition };
  43.         string[] row10 = new string[] { "Write Mask", node.WriteMask.ToString() };
  44.         string[] row11 = new string[] { "User Write Mask", node.UserWriteMask.ToString() };
  45.         if (node.NodeClass == NodeClass.Variable)
  46.         {
  47.             variableNode = (VariableNode)node.DataLock;
  48.             List<NodeId> nodeIds = new List<NodeId>();
  49.             IList<string> displayNames = new List<string>();
  50.             IList<ServiceResult> errors = new List<ServiceResult>();
  51.             NodeId nodeId = new NodeId(variableNode.DataType);
  52.             nodeIds.Add(nodeId);
  53.             current_session.ReadDisplayName(nodeIds, out displayNames, out errors);
  54.             int valueRank = variableNode.ValueRank;
  55.             List<string> arrayDimension = new List<string>();
  56.             
  57.             string[] row12 = new string[] { "数据类型", displayNames[0] };
  58.             string[] row13 = new string[] { "Value Rank", valueRank.ToString() };
  59.             //Define array dimensions depending on the value rank
  60.             if (valueRank > 0) //More dimensional arrays
  61.             {
  62.                 for (int i = 0; i < valueRank; i++)
  63.                 {
  64.                     arrayDimension.Add(variableNode.ArrayDimensions.ElementAtOrDefault(i).ToString());
  65.                 }
  66.             }
  67.             else
  68.             {
  69.                 arrayDimension.Add("标量");
  70.             }
  71.             string[] row14 = new string[] { "数组维数", String.Join(";", arrayDimension.ToArray()) };
  72.             string[] row15 = new string[] { "访问级别", variableNode.AccessLevel.ToString() };
  73.             string[] row16 = new string[] { "最小采样间隔", variableNode.MinimumSamplingInterval.ToString() };
  74.             string[] row17 = new string[] { "历史化", variableNode.Historizing.ToString() };
  75.             rows = new object[] { rowx,  row1, row2, row3, row4, row5, row6, row7, row8, row9, row10, row11, row12, row13, row14, row15, row16, row17 };
  76.         }
  77.         else
  78.         {
  79.             rows = new object[] { rowx,  row1, row2, row3, row4, row5, row6, row7, row8, row9, row10, row11 };
  80.         }
  81.         //MessageBox.Show(refDesc.ToString());
  82.         
  83.     }
  84.     catch (Exception ex)
  85.     {
  86.         MessageBox.Show(ex.Message, "Error");
  87.     }
  88.     return rows;
  89. }
  90. /// <summary>
  91. /// 读取节点的值(Value)
  92. /// 根据节点ID,读取节点变量值
  93. /// </summary>
  94. /// <param name="nodeIdString"></param>
  95. /// <returns></returns>
  96. public string ReadValue(string nodeIdString)
  97. {
  98.     NodeId nodeId=new NodeId(nodeIdString);
  99.     //MessageBox.Show(nodeIdString);
  100.     try
  101.     {
  102.         var res = current_session.ReadValue(nodeId);
  103.         if (res != null)
  104.         {
  105.             if (res.Value != null)
  106.             {
  107.                 return res.Value.ToString();
  108.             }
  109.         }
  110.     }
  111.     catch(Exception ex)
  112.     {
  113.         return "null";
  114.     }
  115.     return "";
  116. }
复制代码
附录

OPCUA官方文档——地址空间
.NET Based OPC UA Client/Server/PubSub SDK  4.0.2.550
总结

本文介绍了如何浏览OPCUA服务器上的节点以及如何获取节点详细信息。
*附言

由于作者水平有限,可能在文章中出现错误或不当描述,如有发现此类情况希望您能实时提供反馈,非常感谢!
如果感觉本文对您有所帮助,希望为文章点个推荐,谢谢。
作者联系方式,163邮箱:zuoquangong@163.com

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4