OPC UA (Unified Architecture),是工业4.0的标准通信规范,大家现在都不陌生。
目前大部分工控行业的应用系统都逐渐的在向OPC UA靠拢,所以随着iot的发展,OPC UA势必会有更加广阔的应用前景,
所以我们应该投入研发力量来研究OPC UA的相关技术规范,并开始应用到业务系统。
基于此行业形势,为了满足公司的需要,用C#打造了一套OPC UA客户端封装组件,与大家共勉。
组件源码参见下面代码,先简单罗列下OPC UA的相关技术的核心概念,加深大家对OPC UA的理解。
OPC UA是什么?
OPC通信标准的核心是互通性 (Interoperability) 和标准化 (Standardization) 问题。传统的OPC技术在控制级别很好地 解决了硬件设备间的互通性问题, 在企业层面的通信标准化是同样需要的。OPC UA之前的访问规范都是基于微软的COM/DCOM技术, 这会给新增层面的通信带来不可根除的弱点。加上传统OPC技术不够灵活、平台局限等问题的逐渐凸显, OPC基金会 (OPC Foundation) 发布了最新的数据通讯统一方法 — OPC统一架构 (OPC UA), 涵盖了OPC 实时数据访问规范 (OPC DA)、OPC历史数据访问规范 (OPC HDA)、 OPC 报警事件访问规范 (OPC A&E) 和OPC安全协议 (OPC Security) 的不同方面, 但在其基础之上进行了功能扩展。
OPC UA,是在传统OPC技术取得很大成功之后的又一个突破,让数据采集、信息模型化以及工厂底层与企业层面之间的通讯更加安全、可靠。
OPC UA的几大优势:
1、与平台无关,可在任何操作系统上运行
2、为未来的先进系统做好准备,与保留系统继续兼容
3、配置和维护更加方便
4、基于服务的技术
5、可见性增加
6、通信范围更广
7、通信性能提高
OPC UA的主要特点:
1、访问统一性
OPC UA有效地将现有的OPC规范 (DA、A&E、HDA、命令、复杂数据和对象类型) 集成进来,成为现在的新的OPC UA规范。 OPC UA提供了一致、完整的地址空间和服务模型,解决了过去同一系统的信息不能以统一方式被访问的问题。
2、通信性能
OPC UA 规范可以通过任何单一端口 (经管理员开放后)进行通信。这让穿越防火墙不再是OPC通信的路障,并且为提高传输性能, OPC UA消息的编码格式可以是XML文本格式或二进制格式,也可使用多种传输协议进行传输,比如:TCP和通过HTTP的网络服务。
3、可靠性、冗余性
OPC UA的开发含有高度可靠性和冗余性的设计。可调试的逾时设置,错误发现和自动纠正等新特征, 都使得符合OPC UA规范的软件产品可以很自如地处理通信错误和失败。 OPC UA的标准冗余模型也使得来自不同厂商的软件应用可以同时被采纳并彼此兼容。
4、标准安全模型
OPC UA 访问规范明确提出了标准安全模型, 每个OPC UA应用都必须执行OPC UA安全协议, 这在提高互通性的同时降低了维护和额外配置费用。 用于OPC UA应用程序之间传递消息的底层通信技术提供了加密功能和标记技术, 保证了消息的完整性,也防止信息的泄漏。
5、平台无关
OPC UA软件的开发不再依靠和局限于任何特定的操作平台。过去只局限于Windows平台的OPC技术拓展到了Linux、Unix、Mac等各种其它平台。 基于Internet的WebService服务架构 (SOA) 和非常灵活的数据交换系统, OPC UA的发展不仅立足于现在,更加面向未来。
下面给大家分享一下项目中用到的应用组件(完全干货),核心源代码(部分)如下:(组件应用交流QQ群:706224870,群文件里有完整的组件源代码及测试DEMO,供大家参考)
其他相关应用请参考:https://blog.csdn.net/mql007007
主要有两个核心类OpcUaClientAPI和PLCMgt。
OpcUaClientAPI:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using Opc.Ua;
using Opc.Ua.Client;
namespace OpcUaClientAPI
{
public class OpcUaClientAPI
{
#region Construction
public OpcUaClientAPI()
{
// Creats the application configuration (containing the certificate) on construction
mApplicationConfig = CreateClientConfiguration("demo", "LocalMachine\\My", "localhost");
}
public OpcUaClientAPI(string applicationName, string storePath, string storeIP)
{
// Creats the application configuration (containing the certificate) on construction
mApplicationConfig = CreateClientConfiguration(applicationName, storePath, storeIP);
}
#endregion
#region Properties
/// <summary>
/// Keeps a session with an UA server.
/// </summary>
private Session mSession = null;
/// <summary>
/// Specifies this application
/// </summary>
private ApplicationConfiguration mApplicationConfig = null;
/// <summary>
/// Provides the session being established with an OPC UA server.
/// </summary>
public Session Session
{
get { return mSession; }
}
/// <summary>
/// Provides the event for value changes of a monitored item.
/// </summary>
public MonitoredItemNotificationEventHandler ItemChangedNotification = null;
/// <summary>
/// Provides the event for KeepAliveNotifications.
/// </summary>
public KeepAliveEventHandler KeepAliveNotification = null;
#endregion
#region Discovery
/// <summary>Finds Servers based on a discovery url</summary>
/// <param name="discoveryUrl">The discovery url</param>
/// <returns>ApplicationDescriptionCollection containing found servers</returns>
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
public ApplicationDescriptionCollection FindServers(string discoveryUrl)
{
//Create a URI using the discovery URL
Uri uri = new Uri(discoveryUrl);
try
{
//Ceate a DiscoveryClient
DiscoveryClient client = DiscoveryClient.Create(uri);
//Find servers
ApplicationDescriptionCollection servers = client.FindServers(null);
return servers;
}
catch (Exception e)
{
//handle Exception here
throw e;
}
}
/// <summary>Finds Endpoints based on a server‘s url</summary>
/// <param name="discoveryUrl">The server‘s url</param>
/// <returns>EndpointDescriptionCollection containing found Endpoints</returns>
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
public EndpointDescriptionCollection GetEndpoints(string serverUrl)
{
//Create a URI using the server‘s URL
Uri uri = new Uri(serverUrl);
try
{
//Create a DiscoveryClient
DiscoveryClient client = DiscoveryClient.Create(uri);
//Search for available endpoints
EndpointDescriptionCollection endpoints = client.GetEndpoints(null);
return endpoints;
}
catch (Exception e)
{
//handle Exception here
throw e;
}
}
#endregion
#region Connect/Disconnect
/// <summary>
/// 连接
/// </summary>
/// <param name="url"></param>
/// <param name="security"></param>
/// <param name="userIdentity"></param>
public void Connect(string url, bool security, UserIdentity userIdentity)
{
try
{
//Secify application configuration
ApplicationConfiguration ApplicationConfig = mApplicationConfig;
//Hook up a validator function for a CertificateValidation event
mApplicationConfig.CertificateValidator.CertificateValidation += Validator_CertificateValidation;
//Create EndPoint description
EndpointDescription EndpointDescription = CreateEndpointDescription(url, security);
//Create EndPoint configuration
EndpointConfiguration EndpointConfiguration = EndpointConfiguration.Create(ApplicationConfig);
//Create an Endpoint object to connect to server
ConfiguredEndpoint Endpoint = new ConfiguredEndpoint(null, EndpointDescription, EndpointConfiguration);
//Create anonymous user identity
//UserIdentity UserIdentity = new UserIdentity();
//Create and connect session
mSession = Session.Create(
ApplicationConfig,
Endpoint,
true,
"MySession",
60000,
userIdentity,
null
);
mSession.KeepAlive += new KeepAliveEventHandler(Notification_KeepAlive);
}
catch (Exception e)
{
//handle Exception here
throw e;
}
}
/// <summary>
/// 连接
/// </summary>
/// <param name="endpointDescription"></param>
/// <param name="userIdentity"></param>
public void Connect(EndpointDescription endpointDescription, UserIdentity userIdentity)
{
try
{
//Secify application configuration
ApplicationConfiguration ApplicationConfig = mApplicationConfig;
//ApplicationConfig.SecurityConfiguration.AutoAcceptUntrustedCertificates = true;
//Hook up a validator function for a CertificateValidation event
ApplicationConfig.CertificateValidator.CertificateValidation += Validator_CertificateValidation;
//Create EndPoint configuration
EndpointConfiguration EndpointConfiguration = EndpointConfiguration.Create(ApplicationConfig);
//Connect to server and get endpoints
ConfiguredEndpoint mEndpoint = new ConfiguredEndpoint(null, endpointDescription, EndpointConfiguration);
//Create the binding factory.
BindingFactory bindingFactory = BindingFactory.Create(mApplicationConfig, ServiceMessageContext.GlobalContext);
//Create anonymous user identity
//UserIdentity UserIdentity = new UserIdentity();
//Create and connect session
mSession = Session.Create(
ApplicationConfig,
mEndpoint,
true,
"MySession",
60000,
userIdentity,
null
);
mSession.KeepAlive += new KeepAliveEventHandler(Notification_KeepAlive);
}
catch (Exception e)
{
//handle Exception here
throw e;
}
}
/// <summary>
/// 连接,mqg于20180914增加,增加PLC会话超时时间
/// </summary>
/// <param name="url"></param>
/// <param name="security"></param>
/// <param name="userIdentity"></param>
/// <param name="timeOverMinutes">会话超时分钟数,单位分钟</param>
public void Connect(string url, bool security, UserIdentity userIdentity, int timeOverMinutes)
{
try
{
//Secify application configuration
ApplicationConfiguration ApplicationConfig = mApplicationConfig;
//Hook up a validator function for a CertificateValidation event
mApplicationConfig.CertificateValidator.CertificateValidation += Validator_CertificateValidation;
//Create EndPoint description
EndpointDescription EndpointDescription = CreateEndpointDescription(url, security);
//Create EndPoint configuration
EndpointConfiguration EndpointConfiguration = EndpointConfiguration.Create(ApplicationConfig);
//Create an Endpoint object to connect to server
ConfiguredEndpoint Endpoint = new ConfiguredEndpoint(null, EndpointDescription, EndpointConfiguration);
//Create anonymous user identity
//UserIdentity UserIdentity = new UserIdentity();
//Create and connect session
mSession = Session.Create(
ApplicationConfig,
Endpoint,
true,
"MySession",
Convert.ToUInt32(timeOverMinutes * 60000),
userIdentity,
null
);
mSession.KeepAlive += new KeepAliveEventHandler(Notification_KeepAlive);
}
catch (Exception e)
{
//handle Exception here
throw e;
}
}
/// <summary>
/// 连接, mqg于20180914增加,增加PLC会话超时时间
/// </summary>
/// <param name="endpointDescription"></param>
/// <param name="userIdentity"></param>
/// <param name="timeOverMinutes"></param>
public void Connect(EndpointDescription endpointDescription, UserIdentity userIdentity, int timeOverMinutes)
{
try
{
//Secify application configuration
ApplicationConfiguration ApplicationConfig = mApplicationConfig;
//ApplicationConfig.SecurityConfiguration.AutoAcceptUntrustedCertificates = true;
//Hook up a validator function for a CertificateValidation event
ApplicationConfig.CertificateValidator.CertificateValidation += Validator_CertificateValidation;
//Create EndPoint configuration
EndpointConfiguration EndpointConfiguration = EndpointConfiguration.Create(ApplicationConfig);
//Connect to server and get endpoints
ConfiguredEndpoint mEndpoint = new ConfiguredEndpoint(null, endpointDescription, EndpointConfiguration);
//Create the binding factory.
BindingFactory bindingFactory = BindingFactory.Create(mApplicationConfig, ServiceMessageContext.GlobalContext);
//Create anonymous user identity
//UserIdentity UserIdentity = new UserIdentity();
//Create and connect session
mSession = Session.Create(
ApplicationConfig,
mEndpoint,
true,
"MySession",
Convert.ToUInt32(timeOverMinutes * 60000),
userIdentity,
null
);
mSession.KeepAlive += new KeepAliveEventHandler(Notification_KeepAlive);
}
catch (Exception e)
{
//handle Exception here
throw e;
}
}
/// <summary>Closes an existing session and disconnects from the server.</summary>
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
public void Disconnect()
{
// Close the session.
try
{
mSession.Close(10000);
mSession.Dispose();
}
catch (Exception e)
{
//handle Exception here
throw e;
}
}
#endregion
PLCMgt:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections;
using System.Threading;
using Opc.Ua;
using Opc.Ua.Client;
using BaseAPI;
using Common;
using OpcUaClientAPI;
namespace PLC_OPCUA_Mgt
{
public class PLCMgt
{
#region 定义
ConfigParams configParams = null;
OpcUaClientAPI.OpcUaClientAPI opcuaClientAPI = null;
Session PLCSession = null;
public Queue PLCBaseState = new Queue(); //PLC基本状态队列
private string last_PLCBaseState = string.Empty; //上次订阅值
List<SubscribeQueue> _subscribeQueues = new List<SubscribeQueue>();
/// <summary>
/// 订阅监控项结果字典,不同的监控项存入不同的队列
/// </summary>
private List<SubscribeQueue> SubscribeQueues
{
get
{
return this._subscribeQueues;
}
set
{
this._subscribeQueues = value;
}
}
private bool UseOPCUA = false; //OPCUA通道是否可用,mqg于20181126增加
#endregion
public PLCMgt(ConfigParams configParamsP)
{
this.configParams = configParamsP;
opcuaClientAPI = new OpcUaClientAPI.OpcUaClientAPI(this.configParams.ApplicationName, this.configParams.StorePath, this.configParams.StoreIP);
//启动连接
Session tmpSession = this.ConnectPLC();
//mqg于20180914增加,处理OPCUA通信连接状态
if (tmpSession.Connected)
{
this.UseOPCUA = true;
ThreadStart AutoOPCUA = new ThreadStart(MonitorOPCUAConnection);
Thread AutoOPCUAThread = new Thread(AutoOPCUA);
AutoOPCUAThread.Start();
}
}
/// <summary>
/// 连接PLC
/// </summary>
/// <returns></returns>
public Session ConnectPLC()
{
UserIdentity UserIdentity = new UserIdentity(this.configParams.PLCConnectedUser, this.configParams.PLCConnectedPwd);
EndpointDescription tmpEndpointDesc = null;
try
{
ApplicationDescriptionCollection servers = this.opcuaClientAPI.FindServers(this.configParams.PLCAddr);
foreach (ApplicationDescription ad in servers)
{
foreach (string url in ad.DiscoveryUrls)
{
EndpointDescriptionCollection endpoints = this.opcuaClientAPI.GetEndpoints(url);
foreach (EndpointDescription ep in endpoints)
{
tmpEndpointDesc = ep;
break;
}
}
}
for (int i = 0; i < int.Parse(this.configParams.ReStartMaxCnt); i++)
{
//mqg于20180914增加,增加PLC会话超时时间到10分钟
//this.opcuaClientAPI.Connect(tmpEndpointDesc, UserIdentity);
this.opcuaClientAPI.Connect(tmpEndpointDesc, UserIdentity, 10);
this.PLCSession = this.opcuaClientAPI.Session;
if (this.PLCSession != null && !this.PLCSession.Disposed)
{
this.opcuaClientAPI.KeepAliveNotification += new KeepAliveEventHandler(Notification_KeepAlive);
}
if (i == 4 && this.PLCSession == null)
{
string logcontent = DateTime.Now.ToString() + " 连接PLC失败!原因:超过最大连接次数(" + this.configParams.ReStartMaxCnt + ")。";
BaseApiHandler.RecordLog(logcontent, this.configParams.Logpath, "ConnecPLC", this.configParams.RecordBizDataLog);
}
if (this.PLCSession.Connected)
{
string logcontent = DateTime.Now.ToString() + " 连接PLC成功!";
BaseApiHandler.RecordLog(logcontent, this.configParams.Logpath, "连接PLC成功", this.configParams.RecordBizDataLog);
break; //连上就终止再连
}
}
}
catch (Exception ex)
{
string logcontent = DateTime.Now.ToString() + " 连接PLC发生异常。原因:" + ex.Message;
BaseApiHandler.RecordLog(logcontent, this.configParams.Logpath, "ConnecPLC", this.configParams.RecordBizDataLog);
}
return this.PLCSession;
}
/// <summary>
/// 处理保持连接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void Notification_KeepAlive(Session sender, KeepAliveEventArgs e)
{
//Handle KeepAlive here
}
/// <summary>
/// 监控OPCUA通信连接,mqg于20180914增加
/// </summary>
private void MonitorOPCUAConnection()
{
while (true)
{
try
{
if (!this.UseOPCUA)
{
this.opcuaClientAPI = null;
this.opcuaClientAPI = new OpcUaClientAPI.OpcUaClientAPI(this.configParams.ApplicationName, this.configParams.StorePath, this.configParams.StoreIP);
//启动连接
Session tmpSession = this.ConnectPLC();
if (tmpSession.Connected)
{
this.UseOPCUA = true;
}
}
}
catch (Exception ex)
{
//记录结果
string logcontent = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 监控OPCUA通信连接处理发生错误!原因: " + ex.Message;
BaseApiHandler.RecordLog(logcontent, this.configParams.Logpath, "ConnetOPCUA", this.configParams.RecordBizDataLog);
}
Thread.Sleep(5 * 1000);//延迟秒级别
}
}