• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C# 读写opc ua服务器,浏览所有节点,读写节点,读历史数据,调用方法,订阅,批量订 ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

转载地址:

 

OPC UA简介


OPC是应用于工业通信的,在windows环境的下一种通讯技术,原有的通信技术难以满足日益复杂的环境,在可扩展性,安全性,跨平台性方面的不足日益明显,所以OPC基金会在几年前提出了面向未来的架构设计的OPC 统一架构,简称OPC UA,截止目前为止,越来越多公司将OPC UA作为开放的数据标准,在未来工业4.0行业上也将大放异彩。

 

在OPC UA的服务器端。会公开一些数据节点,或是方法等信息,允许第三方使用标准的OPC协议来进行访问,在传输层已经安全的处理所有的消息,对于客户端的访问来说,应该是非常清楚简单的。

 

本篇文章是讲述如何开发C#的OPC UA客户端的方式,关于如何开发OPC UA可配置的服务器,请参照另一篇博客:http://www.cnblogs.com/dathlin/p/8976955.html 这篇博客讲述了如何创建基于三菱,西门子,欧姆龙,ModbusTcp客户端,异形ModbusTcp客户端的OPC UA服务器引擎。

联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation

2.0版本说明


2018年8月18日 20:09:24  基于OPC UA的最新官方库,重新调整了订阅的代码实现,开源地址:https://github.com/dathlin/OpcUaHelper 除了组件的源代码之外,还包含了一个服务器的示例,就是下面的的示例操作。

更加详细的代码说明可以参照GitHub上的readme文件

前期准备


准备好开发的IDE,首选Visual Studio2017版本,新建项目,或是在你原有的项目上进行扩展。注意:项目的.NET Framework版本最低为4.6

打开NuGet管理器,输入指令(如果不明白,参考http://www.cnblogs.com/dathlin/p/7705014.html):

1
Install-Package OpcUaHelper

或者:

然后在窗体的界面新增引用:

1
using OpcUaHelper;

接下就可以愉快码代码了。

 

 

OPC UA服务器准备


此处有一个供网友测试的服务器:opc.tcp://118.24.36.220:62547/DataAccessServer

当然,一般的网友都会使用Kepware软件,在此处介绍一个我自己开发的OPC UA网关服务器,支持三菱,西门子,欧姆龙,modbustcp客户端转化成OPC UA服务器,支持创建modbus服务器,异形服务器,地址是

https://github.com/dathlin/SharpNodeSettings

 

节点浏览器


我们在得到一个OPC UA的服务器之后,第一件事就是使用节点浏览器对所有的节点进行访问,不然你根本就不知道服务器公开了什么东西,此处我使用了一个测试服务器,该地址为云端地址,不保证以后会不会继续支持访问,目前来说还是可以访问的。

比如这个地址:opc.tcp://118.24.36.220:62547/DataAccessServer

OK,然后我们可以使用代码来显示这个服务器到底有什么数据了!在窗体上新增一个按钮,双击它进入点击事件,写上

1
2
3
4
5
6
7
private void button1_Click(object sender, EventArgs e)
{
    using (FormBrowseServer form = new FormBrowseServer())
    {
        form.ShowDialog();
    }
}

然后就会显示如下的界面:在地址栏输入上述地址,点击连接(此处能连接上的条件是服务器配置为允许匿名登录):

 

左边区域可以随便点击看看,可以看到所有公开的数据,比如点击一个数据节点,下面图片中的Name节点,右边编辑框会显示该节点的ID标识,这个标识很重要,关系到等会的读写操作。

客户端实例化


 

1
2
3
4
5
6
7
8
9
10
11
private OpcUaClient opcUaClient = new OpcUaClient();
 
private async void Form1_Load(object sender, EventArgs e)
{
    await opcUaClient.ConnectServer("opc.tcp://118.24.36.220:62547/DataAccessServer");
}
 
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    opcUaClient.Disconnect();
}

如上所示,在窗体载入的时候实例化,在窗体关闭的时候断开连接。下面的节点操作和其他操作使用的实例都是这个opcUaClient,如果你连接的服务器是需要用户名和密码的,那么修改Load中的代码如下:

1
2
3
private async void Form1_Load(object sender, EventArgs e)
        {
            opcUaClient.UserIdentity = new Opc.Ua.UserIdentity("admin", "123456");
1
await opcUaClient.ConnectServer("opc.tcp://118.24.36.220:62547/DataAccessServer");
1
}

  

节点读取操作


我们要读取一个节点数据,有两个信息是必须知道的

  • 节点的ID标识,就是在上述节点浏览器中的编辑框的信息("ns=2;s=Machines/Machine A/Name")
  • 节点的数据类型,这个是必须知道的,不然也不好读取数据。(“string”)

上面的两个信息都可以通过节点浏览器来获取到信息,现在,我们已经获取到了这两个信息,就上面的括号里的数据,然后我们在新增一个按钮,来读取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void button2_Click(object sender, EventArgs e)
{
    try
    {
        string value = opcUaClient.ReadNode<string>("ns=2;s=Machines/Machine A/Name");
        MessageBox.Show(value); // 显示测试数据
    }
    catch(Exception ex)
    {
        // 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
        ClientUtils.HandleException(Text, ex);
    }
}

可以看到,真正的读取数据的操作只有一行代码,但是此处展示了一个良好的编程习惯,使用try..catch..,关于错误捕获的使用以后会专门开篇文章讲解。在展示一个读取float数据类型的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
private void button2_Click(object sender, EventArgs e)
{
    try
    {
        float value = opcUaClient.ReadNode<float>("ns=2;s=Machines/Machine B/TestValueFloat");
        MessageBox.Show(value.ToString()); // 显示100.5
    }
    catch(Exception ex)
    {
        // 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
        ClientUtils.HandleException(Text, ex);
    }
}

其他的类型参照这种写法就行,哪怕是数组类型也是没有关系的。

类型未知节点读取操作


我们要读取一个节点数据,假设我们只知道一个节点的ID,或者说这个节点的类型是可能变化的,那么我们需要读取到值的同时读取到这个数据的类型,那么代码参照下面

  • 节点的ID标识,就是在上述节点浏览器中的编辑

节点的数据类型最终由 value.WrappedValue.TypeInfo 来决定,有两个属性,是否是数组和基础类型,下面的代码只有int类型进行了严格的数组判断,其他类型参照即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    private void button3_Click(object sender, EventArgs e)
    {
        Opc.Ua.DataValue value = opcUaClient.ReadNode("ns=2;s=Robots/RobotA/RobotMode");
        // 一个数据的类型是不是数组由 value.WrappedValue.TypeInfo.ValueRank 来决定的
        // -1 说明是一个数值
        // 1  说明是一维数组,如果类型BuiltInType是Int32,那么实际是int[]
        // 2  说明是二维数组,如果类型BuiltInType是Int32,那么实际是int[,]
        if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Int32)
        {
            if (value.WrappedValue.TypeInfo.ValueRank == -1)
            {
                int temp = (int)value.WrappedValue.Value;               // 最终值
            }
            else if (value.WrappedValue.TypeInfo.ValueRank == 1)
            {
                int[] temp = (int[])value.WrappedValue.Value;           // 最终值
            }
            else if (value.WrappedValue.TypeInfo.ValueRank == 2)
            {
                int[,] temp = (int[,])value.WrappedValue.Value;         // 最终值
            }
        }
        else if(value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.UInt32)
        {
            uint temp = (uint)value.WrappedValue.Value;                 // 数组的情况参照上面的例子
        }
        else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Float)
        {
            float temp = (float)value.WrappedValue.Value;               // 数组的情况参照上面的例子
        }
        else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.String)
        {
            string temp = (string)value.WrappedValue.Value;             // 数组的情况参照上面的例子
        }
        else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.DateTime)
        {
            DateTime temp = (DateTime)value.WrappedValue.Value;         // 数组的情况参照上面的例子
        }
    }
}

 

批量节点读取操作


批量读取节点时,有个麻烦之处在于类型不一定都是一致的,所以为了支持更加广泛的读取操作,只提供Opc.Ua.DataValue的读取,读取到数据后需要自己做一些转换,根据类型来自己转,参照上面类型未知的节点操作代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
try
{
    // 添加所有的读取的节点,此处的示例是类型不一致的情况
    List<NodeId> nodeIds = new List<NodeId>( );
    nodeIds.Add( new NodeId( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度" ) );
    nodeIds.Add( new NodeId( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/风俗" ) );
    nodeIds.Add( new NodeId( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速" ) );
    nodeIds.Add( new NodeId( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/机器人关节" ) );
    nodeIds.Add( new NodeId( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/cvsdf" ) );
    nodeIds.Add( new NodeId( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/条码" ) );
    nodeIds.Add( new NodeId( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/开关量" ) );
 
    // dataValues按顺序定义的值,每个值里面需要重新判断类型
    List<DataValue> dataValues = opcUaClient.ReadNodes( nodeIds.ToArray() );
    // 然后遍历你的数据信息
    foreach (var dataValue in dataValues)
    {
        // 获取你的实际的数据
        object value = dataValue.WrappedValue.Value;
    }
 
 
 
 
    // 如果你批量读取的值的类型都是一样的,比如float,那么有简便的方式
    List<string> tags = new List<string>( );
    tags.Add( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/风俗" );
    tags.Add( "ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速" );
 
    // 按照顺序定义的值
    List<float> values = opcUaClient.ReadNodes<float>( tags.ToArray() );
 
}
catch (Exception ex)
{
    ClientUtils.HandleException( this.Text, ex );
}

  

 

 

节点写入操作


 

节点的写入操作和读取类似,我们还是必须要先知道节点的ID和数据类型,和读取最大的区别是,写入的操作很有可能会失败,因为服务器对于数据的输入都是很敏感的,这部分权限肯定会控制的,也就是很有可能会发生写入拒绝,此处的测试服务器允许写入,下面举例在Name节点写入“abcd测试写入啊”信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void button3_Click(object sender, EventArgs e)
{
    try
    {
        bool IsSuccess = opcUaClient.WriteNode("ns=2;s=Machines/Machine B/Name","abcd测试写入啊");
        MessageBox.Show(IsSuccess.ToString()); // 显示True,如果成功的话
    }
    catch(Exception ex)
    {
        // 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
        ClientUtils.HandleException(Text, ex);
    }
}

再写个例子,写入Float数据

1
2
3
4
5
6
7
8
9
10
11
12
13
private void button3_Click(object sender, EventArgs e)
{
    try
    {
        bool IsSuccess = opcUaClient.WriteNode("ns=2;s=Machines/Machine B/TestValueFloat",123.456f);
        MessageBox.Show(IsSuccess.ToString()); // 显示True,如果成功的话
    }
    catch(Exception ex)
    {
        // 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
        ClientUtils.HandleException(Text, ex);
    }
}

要想查看是否真的写入,可以使用节点数据浏览器来查看是否真的写入。

批量节点写入操作


 写入节点操作时,类型并不一定是统一的,所以此处提供统一的object数组写入,需要注意,对应的节点名称和值的类型必须一致!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void button5_Click(object sender, EventArgs e)
{
    // 批量写入的代码
    string[] nodes = new string[]
    {
        "ns=2;s=Robots/RobotA/RobotMode",
        "ns=2;s=Robots/RobotA/UserFloat"
    };
    object[] data = new object[]
    {
        4,
        new float[]{5,3,1,5,7,8}
    };
 
    // 都成功返回True,否则返回False
    bool result = opcUaClient.WriteNodes(nodes, data);
}

 

数据订阅


下面举例说明订阅ns=2;s=Machines/Machine B/TestValueFloat的数据,我们假设这个在服务器上是不断变化的,按照如下的方式进行数据订阅:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void button2_Click( object sender, EventArgs e )
{
    // sub
    OpcUaClient.AddSubscription( "A", "ns=2;s=Machines/Machine B/TestValueFloat", SubCallback );
}
 
private void SubCallback(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args )
{
    if (InvokeRequired)
    {
        Invoke( new Action<string, MonitoredItem, MonitoredItemNotificationEventArgs>( SubCallback ), key, monitoredItem, args );
        return;
    }
 
    if (key == "A")
    {
        // 如果有多个的订阅值都关联了当前的方法,可以通过key和monitoredItem来区分
        MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
        if (notification != null)
        {
            textBox3.Text = notification.Value.WrappedValue.Value.ToString( );
        }
    }
}

移除订阅

1
OpcUaClient.RemoveSubscription( "A" );

  

批量订阅的方式,参照源代码或是 github的说明文件。

 

 

  

方法调


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
外观模式(facade)之c#发布时间:2022-07-14
下一篇:
C#中用ILMerge将所有引用的DLL和exe文件打成一个exe文件发布时间:2022-07-14
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap