当前位置:首页 > 后端开发 > 正文内容

OPCUA讨论(四)——客户端代码解读2

邻居的猫1个月前 (12-09)后端开发2033

本系列文章:
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]

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=142

标签: OPCUA
分享给朋友:

“OPCUA讨论(四)——客户端代码解读2” 的相关文章

还在为慢速数据传输苦恼?Linux 零复制技能来帮你!

还在为慢速数据传输苦恼?Linux 零复制技能来帮你!

前语 程序员的终极寻求是什么?当体系流量大增,用户体会却丝滑仍旧?没错!但是,在很多文件传输、数据传递的场景中,传统的“数据转移”却拖慢了功能。为了处理这一痛点,Linux 推出了 零仿制 技能,让数据高效传输简直无需 CPU 操心。今日,我就用最浅显的言语解说零仿制的作业原理、常见完结办法和实践运...

php工具,提升效率,优化代码质量

php工具,提升效率,优化代码质量

1. 集成开发环境(IDE): PHPStorm:由 JetBrains 开发,是一个功能强大的 PHP IDE,支持代码自动完成、调试、重构和版本控制等功能。 Visual Studio Code:微软开发的轻量级代码编辑器,可以通过安装扩展来支持 PHP 开发。 Sublime...

php一句话,php官网

请提供具体的上下文或问题,以便我能提供相关的PHP代码示例。深入解析PHP一句话木马:原理、构造与免杀技巧一、PHP一句话木马原理PHP一句话木马,顾名思义,就是只需要一行代码就能实现攻击目的的木马。其核心原理是利用PHP中的eval()函数。eval()函数可以将字符串当作PHP代码执行,从而实现...

c语言按位取反

c语言按位取反

在C语言中,按位取反可以通过按位取反运算符 `~` 来实现。这个运算符会将操作数的每一位都取反,即0变成1,1变成0。下面是一个简单的例子,演示如何使用按位取反运算符:```cinclude int main { int num = 5; // 二进制表示为 101 int invert...

scala柯里化,什么是Scala柯里化?

scala柯里化,什么是Scala柯里化?

Scala 中的柯里化是一种函数式编程技术,它允许将一个接受多个参数的函数转换成一系列接受单个参数的函数。这种方法在处理具有多个参数的函数时特别有用,因为它可以简化函数的调用和重用。柯里化的基本思想是将一个多参数函数转换为一系列嵌套的单参数函数。例如,一个接受两个参数的函数 f 可以被柯里化为两个嵌...

DART探测器,人类行星防御的先锋

DART探测器,人类行星防御的先锋

DART探测器是美国国家航空航天局(NASA)于2021年11月发射的一项行星防御任务,旨在测试通过动能撞击改变小行星轨道的技术。以下是DART探测器的主要信息:1. 任务背景: DART全称为“双小行星重定向测试”(Double Asteroid Redirection Test),是美国宇...