C# 实现opc ua服务器的远程连接(转)
原文转自: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
C# 实现opc ua服务器的远程连接(转)的更多相关文章
- C# 读写opc ua服务器,浏览所有节点,读写节点,读历史数据,调用方法,订阅,批量订阅操作
OPC UA简介 OPC是应用于工业通信的,在windows环境的下一种通讯技术,原有的通信技术难以满足日益复杂的环境,在可扩展性,安全性,跨平台性方面的不足日益明显,所以OPC基金会在几年前提出了面 ...
- 分享一款免费OPC UA服务器
OPC UA基于OPC基金会提供的新一代技术,提供安全,可靠和独立于厂商的,实现原始数据和预处理的信息从制造层级到生产计划或ERP层级的传输.通过OPC UA,所有需要的信息在任何时间,任何地点对每个 ...
- C#实现访问OPC UA服务器
OPC UA服务器支持三种认证方式,分别是匿名认证.用户认证和证书认证.其中匿名认证安全等级最低,访问不做任何校验.用户认证访问时,OPC UA客户端需要提供用户名及密码认证,只有用户名和密码正确才允 ...
- 服务器能远程连接,网络连接正常,但是外网域名Ping不通,浏览器中打不开网站
服务器能远程连接成功,但在浏览器中打不开任何网站,出现这个问题一般是安装什么软件引起IE的相关设置做了变动或者是服务器中了病毒引起的,或是服务器的DNS设置是错误的. 一.先检查服务器DNS是否正确 ...
- SharpNodeSettings项目,可配置的数据采集,统一的工业数据网关,OPC UA服务器开发,PLC数据发布到自身内存,redis,opc ua,以及数据可视化开发
本项目隶属于 HslCommunication 项目的SDK套件,如果不清楚HslCommunication组件的话,可以先了解那个项目,源代码地址:https://github.com/dathli ...
- windows远程连接老是出问题?如何使用Radmin进行云服务器的远程连接与文件传输?
(windows远程连接老是出错怎么办?云服务器远程连接一直有问题怎么办?如何用对多台windows电脑远程连接怎么办? 最近发现win的mstsc不好用,偶然想起Radmin这款老牌软件,利用Rad ...
- 基于open62541的opc ua 服务器开发实现(1)
关于opcua的介绍这里就不多说了,相信大家大都有了一些了解,open62541是一个开源C(C99)的opc-ua实现,开源代码可在官网或github上下载. 话不多说,首先搭建一个opcua服务器 ...
- C# OPC UA服务器 OPC UA网关 三菱 西门子 欧姆龙 Modbus转OPC UA 服务器 可配置的OPC UA服务器网关 HslSharp软件文档
前言 本文将使用一个基于开源项目HslCommunication创建的OPC UA网关,方便通过配置创建一个OPC UA的网关中心.具体的操作及支持的设备信息项目参照下面: 开源项目HslCommun ...
- 配置redis服务器允许远程连接
说明 默认情况下,redis只允许本机访问.如果需要外部访问,需要修改下配置文件. 配置修改 redis.windows.conf 将bind 127.0.0.1 注释 将protected-mode ...
随机推荐
- Linux之more命令
命令解释 more命令类似与cat命令,却比cat命令强大,它以全屏幕的方式按页显示文本文件的内容,支持vi中的关键字定位操作. 命令说明 more [选项] 文件.. 命令选项 -d 显示帮助 ...
- Netty TCP 通信失败
前段时间,在搞Netty TCP 通信,踩了一些坑,今天就在这篇总结一下 Netty通信失败原因 Netty TCP 通信失败的可能原因: 1.服务端或客户端,其中一端没有正常启动 2.是否在正确的位 ...
- web添加学生信息(首发web)
程序思路,先在JSP上画好页面,然后再创建一Servlet文件用于判断在网页上操作是否正确,还需要与数据库相连接,用DBUtile文件连接数据库,用Dao层来实现数据的增加,用Service来服务于D ...
- ajax请求自动刷新页面
ajax是异步请求技术,可以实现页面的局部刷新.但是今天写代码的时候发现每次ajax之后都会发生整个页面的刷新,最后发现这是因为触发ajax事件的input标签的type设置为了submit,所以会产 ...
- Oracle之:Function :getcurrdate()
getdate()函数连接请戳这里 create or replace function getcurrdate(i_date date) return date is v_date date; v_ ...
- Java-五种线程池,四种拒绝策略,三种阻塞队列(转)
Java-五种线程池,四种拒绝策略,三种阻塞队列 三种阻塞队列: BlockingQueue<Runnable> workQueue = null; workQueue = n ...
- [Algorithm] Finding all factors of a number
12's factors are: {1,2,3,4,6,12} function factors (n) { let list = []; for (let i = 1; i < Math.s ...
- [Python之路] 使用epoll实现高并发HTTP服务器
什么是epoll 我们在 Python多种方式实现并发的Web Server 的最后使用单进程+单线程+非阻塞+长连接实现了一个可并发处理客户端连接的服务器.他的原理可以用以下的图来描述: 解释: ...
- Monkey初步使用
版权声明: 本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有. 允许有条件转载,转载请附带底部二维码. 一.什么是Monkey Monkey是Android自身提供的,可以通 ...
- B - Problem Arrangement ZOJ - 3777
Problem Arrangement ZOJ - 3777 题目大意:有n道题,第i道题第j个做可以获得Pij的兴趣值,问至少得到m兴趣值的数学期望是多少,如果没有的话就输出No solution. ...