原文转自:https://www.cnblogs.com/dathlin/p/7724834.html

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服务器引擎。

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):

Install-Package OpcUaHelper

或者:

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

using OpcUaHelper;

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

技术支持QQ群:592132877 (组件的版本更新细节也将第一时间在群里发布)

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

private void button1_Click(object sender, EventArgs e)
{
using (FormBrowseServer form = new FormBrowseServer())
{
form.ShowDialog();
}
}

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

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

客户端实例化


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中的代码如下:

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

节点读取操作


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

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

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

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的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}

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

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的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}

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

类型未知节点读取操作


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

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

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

  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 == -)
{
int temp = (int)value.WrappedValue.Value; // 最终值
}
else if (value.WrappedValue.TypeInfo.ValueRank == )
{
int[] temp = (int[])value.WrappedValue.Value; // 最终值
}
else if (value.WrappedValue.TypeInfo.ValueRank == )
{
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的读取,读取到数据后需要自己做一些转换,根据类型来自己转,参照上面类型未知的节点操作代码。

private void button4_Click(object sender, EventArgs e)
{
string[] nodes = new string[]
{
"ns=2;s=Robots/RobotA/RobotMode",
"ns=2;s=Robots/RobotA/UserFloat"
}; // 因为不能保证读取的节点类型一致,所以只提供统一的DataValue读取,每个节点需要单独解析
foreach(Opc.Ua.DataValue value in opcUaClient.ReadNodes(nodes))
{
// 获取到了值,具体的每个变量的解析参照上面类型不确定的解析
object data = value.WrappedValue.Value;
// 下面写你自己的操作 }
}

节点写入操作


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

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的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}

再写个例子,写入Float数据

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的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}

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

批量节点写入操作


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

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[]
{
,
new float[]{,,,,,}
}; // 都成功返回True,否则返回False
bool result = opcUaClient.WriteNodes(nodes, data);
}

数据订阅


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

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( );
}
}
}

移除订阅

OpcUaClient.RemoveSubscription( "A" );

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

方法调用


有些OPC 服务器会提供方法调用,测试服务器提供了一个方法,它支持两个int参数输入,string参数输出,方法节点为:ns=2;s=Machines/Machine B/Calculate

我们接下来看看调用服务器的方法到底返回了什么?

private void button6_Click(object sender, EventArgs e)
{
try
{
string value = opcUaClient.CallMethodByNodeId("ns=2;s=Machines/Machine B",
"ns=2;s=Machines/Machine B/Calculate", , )[].ToString();
MessageBox.Show(value);// 显示:我也不知道刚刚发生了什么,调用设备为:Machine B
}
catch(Exception ex)
{
// 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}

我们在调用方法的时候需要传入方法的父节点 ID,以及方法的ID,必须先清楚方法的传入参数和传出参数才能对应的代码。

日志输出


OPC UA客户端在运行时会输出一大堆的日志,容量会增加的比较快,是否需要配置,请谨慎处理,如果真的有需要,按照下面的配置方式来完成

private void button5_Click(object sender, EventArgs e)
{
// False 代表每次启动清空日志,True代码不清空,注意,该日志大小增加非常快
opcUaClient.SetLogPathName(Application.StartupPath + "\\Logs\\opc.ua.client.txt", false);
}

上述的都是一些最常用的方法了,已经可以应付大多数的需求,该客户端类还提供了一些连接启动事件,断开事件等等,可以满足额外的需求。

引用读取


这种情况比较少,比如服务器端有个MachineB节点,下面放了一些数据,如果客户端把读取的节点写死一般问题也不大,应该服务器很少会改变,但是服务器真的改变了呢。。。。比如在MachineB下追加了一个数据,这种情况确实很少,但是对于我们写成相对动态的情况来说,就很有必要,但是中间问题很多,因为新增的节点类型你是不知道的,ID也是不知道的,所以还先要读取引用,然后在读取数据,然后在判断类型,进行相应的转化。

private void button6_Click(object sender, EventArgs e)
{
try
{
Opc.Ua.ReferenceDescription[] reference = opcUaClient.BrowseNodeReference("ns=2;s=Machines/Machine B"); foreach (var refer in reference)
{
// 如果不是值节点,就不要了,否则下面读取了也是没有意义的
if (refer.NodeClass != NodeClass.Variable)
{
continue;
} // 分别读取数据
Opc.Ua.DataValue dataValue = opcUaClient.ReadNode((Opc.Ua.NodeId)refer.NodeId);
if (dataValue.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Boolean)
{
// 读取到的是bool数据,在这里做处理
bool value = (bool)dataValue.WrappedValue.Value;
}
else if (dataValue.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.String)
{
// 读取到的是string字符串,在这里做处理
string value = dataValue.WrappedValue.Value.ToString();
}
}
}
catch (Exception ex)
{
// 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}

异步操作


在读取写入单个节点的功能中,提供了一个异步版本,用来方便的进行异步操作

private async void button2_Click(object sender, EventArgs e)
{
try
{
float value = await opcUaClient.ReadNodeAsync<float>("ns=2;s=Machines/Machine B/TestValueFloat");
MessageBox.Show(value.ToString()); // 显示100.5
}
catch(Exception ex)
{
// 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}
private async void button3_Click(object sender, EventArgs e)
{
try
{
bool IsSuccess = await opcUaClient.WriteNodeAsync("ns=2;s=Machines/Machine B/TestValueFloat",123.456f);
MessageBox.Show(IsSuccess.ToString()); // 显示True,如果成功的话
}
catch(Exception ex)
{
// 使用了opc ua的错误处理机制来处理错误,网络不通或是读取拒绝
Opc.Ua.Client.Controls.ClientUtils.HandleException(Text, ex);
}
}

  

查看本地以注册的服务器


利用官方的控件库来实现的一个操作,允许查看本地的已经注册的服务器。

private void button6_Click( object sender, EventArgs e )
{
// 获取本机已经注册的服务器地址
string endpointUrl = new Opc.Ua.Client.Controls.DiscoverServerDlg( ).ShowDialog( opcUaClient.AppConfig, null );
// 获取其他服务器注册的地址,注意,需要该IP的安全策略配置正确
// string endpointUrl = new Opc.Ua.Client.Controls.DiscoverServerDlg( ).ShowDialog( opcUaClient.AppConfig, "192.168.0.100" ); if (!string.IsNullOrEmpty( endpointUrl ))
{
// 获取到的需要操作的服务器地址
}
}

触发事件


本opc ua客户端类,包含了几个常用的事件,现在进行说明:

  • ConnectComplete 事件:在第一次连接到服务器完成的时候触发
  • ReconnectStarting 事件:开始重新连接到服务器的时候触发
  • ReconnectComplete 事件:重新连接到服务器的时候触发
  • KeepAliveComplete 事件:因为opc ua客户端每隔5秒会与服务器进行通讯验证,每次验证都会触发该方法
  • OpcStatusChange 事件:本OPC UA客户端的终极事件,当客户端的状态变更都会触发,包括了连接,重连,断开,状态激活,opc ua的状态等等

事件类的完整代码如下:

/// <summary>
/// 状态通知的消息类
/// </summary>
public class OpcUaStatusEventArgs : EventArgs
{
/// <summary>
/// 是否异常
/// </summary>
public bool Error { get; set; }
/// <summary>
/// 时间
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// 文本
/// </summary>
public string Text { get; set; } /// <summary>
/// 转化为字符串
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Error ? "[异常]" : "[正常]" + Time.ToString(" yyyy-MM-dd HH:mm:ss ") + Text;
}
}

获取客户端网络是否正常有个属性

/// <summary>
/// Indicate the connect status
/// </summary>
public bool Connected
{
get { return m_IsConnected; }
}

特别说明


虽然提供了删除一个节点和新增一个节点的方法,但是在客户端是不允许操作的,调用无效。

原文转自:https://www.cnblogs.com/dathlin/p/7724834.html

最新文章

  1. javaWeb中 servlet 、request 、response
  2. ubuntu 13.10 monodevelop3 安装
  3. 【Alpha版本】冲刺阶段——Day 3
  4. Spring系列之beanFactory与ApplicationContext
  5. Centos 部署Keepalive高可用软件
  6. PHP初学者必须掌握的10个知识点
  7. comet
  8. Dynamic Web Project创建及版本修改的问题
  9. 3.2html学习笔记之图片
  10. 如何通过jquery隐藏和显示元素
  11. hud 3336 count the string (KMP)
  12. I2C转UART
  13. 【Heritrix基础教程之4】开始一个爬虫抓取的全流程代码分析
  14. SQL点滴12—SQL Server备份还原数据库中的小把戏
  15. WPF使用RoutedCommand自己定义命令
  16. Windows 下python 环境安装
  17. SpringBoot+Dubbo+Zookeeper整合搭建简单的分布式应用
  18. Git安装教程(windows)
  19. 可以用py库: pyautogui (自动测试模块,模拟鼠标、键盘动作)来代替pyuserinput
  20. kubernetes常用命令

热门文章

  1. JVM 字节码指令手册 - 查看 Java 字节码
  2. 【原】关于executeQuery与ResultSet
  3. 35. ClustrixDB 减少device1大小
  4. Vue(js框架)
  5. less命令:查看文件内容
  6. Java集合框架之接口Collection源码分析
  7. $\LaTeX$数学公式大全10
  8. springboot中如何启动tomcat
  9. Java Jsch SFTP 递归下载文件夹
  10. 微信小程序&lt;web-view&gt;出现{&quot;base_resp&quot;:{&quot;ret&quot;:-1}}