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>
/// 增加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>
/// 增加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通道是否可用
#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();
/處理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++)
{
//增加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通信連接
/// </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);//延遲秒級別
}
}