本文将会从以下几个方面介绍

1.OPCUA是什么

2.OPCUA常用的工具有那些

3.OPCUA的官网

4.使用opcua常用的方法和功能介绍

5.根据官网自己封装了一个opcuaclient类,并说明每个方法的用处

6.根据4中的opcuaclient类自己写了demo

本文所有用到的资料在此下载包括UaExpert,kepserver,demo等

下载链接

1.OPCUA是什么

OPC UA(开放式产品通信统一架构,Open Platform Communications Unified Architecture)是一种通信协议和通信架构,用于实现工业自动化系统中设备之间的数据交换和通信。它提供了一种标准化的、安全的、可扩展的机制,使得不同设备和系统能够无缝地进行数据交换和通信。广泛应用于工业自动化、制造业、物联网等其他领域,用于实现设备之间的实时数据传输、监控和控制。

2.OPCUA常用的工具有那些

客户端我一般使用UaExpert

使用方法网上可以随便搜到。比如:使用UaExpert - 水滴大海 - 博客园 (cnblogs.com)

服务端我一般使用kepserver

使用方法比如:KepServer的下载安装与使用说明_牛奶咖啡13的博客-CSDN博客

3.OPCUA的官网

OPCUA github地址是:GitHub - OPCFoundation/UA-.NETStandard: OPC Unified Architecture .NET Standard

官网截图

下载后启动UA Reference.sln即可查看官方提供的一些例子

打开后截图如下,这里就是opcua官网提供的例子,大家可以自己研究一下。

4.使用opcua常用的方法和功能介绍

①连接和断开连接,当然还有断开重连

②认证,包括没有限制、需要用户名和密码、数字证书

③数据读取:先把数据读上来,然后根据数据类型转换即可。特别注意结构体读取需要特殊处理一下

④数据写入:OPC UA在数据写入时,对数据类型有严格要求,数据类型不会自动转换,会写入失败。所以写入数据时需要指定数据类型。特别注意结构体写入需要特殊处理一下

⑤订阅:数据订阅后服务器数据变化后会自动通知到客户端。相当于把压力放在了服务器。注意:服务器对与订阅资源是有限的,有的服务器必须显式的释放订阅资源,不然会造成订阅资源耗尽而订阅失败,必须重启服务端软件才能解决。

⑥结构体数据读取

⑦结构体数据写入

5.根据官网自己封装了一个opcuaclient类

①连接和断开连接,当然还有断开重连

        /// <summary>/// 连接opcua/// </summary>/// <param name="serverUrl">连接地址</param>/// <param name="useSecurity">是否启用身份验证,true启用,false不启用</param>/// <param name="sessionTimeout"></param>/// <returns></returns>public async Task<Session> ConnectServer(string serverUrl,bool useSecurity,uint sessionTimeout = 0){try{// disconnect from existing session.InternalDisconnect();// select the best endpoint.Console.WriteLine("获取远程节点:" + serverUrl);serverUrl = "opc.tcp://127.0.0.1:49320";var endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, useSecurity, 600000);var endpointConfiguration = EndpointConfiguration.Create(m_configuration);var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);// Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(endpoint));Console.WriteLine("开始连接:" + serverUrl);m_session = await Session.Create(m_configuration,endpoint,false,DisableDomainCheck,(String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,sessionTimeout == 0 ? DefaultSessionTimeout : sessionTimeout,UserIdentity,PreferredLocales);// set up keep alive callback. 监听连接状态,但连接失败时自动重连m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);Console.WriteLine("OPC连接成功:" + serverUrl + "m_session.Connected的连接状态是:" + m_session.Connected);}catch (Exception ex){Console.WriteLine("OPC连接异常:" + serverUrl + ex.ToString());if (m_session != null){m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);}else{//isCconnect = false;}}try{UpdateStatus(false, DateTime.Now, "Connected, loading complex type system.");}catch (Exception e){UpdateStatus(true, DateTime.Now, "Connected, failed to load complex type system.");Utils.Trace(e, "Failed to load complex type system.");}// return the new session.return m_session;}/// <summary>/// 断开连接/// Disconnects from the server./// </summary>private void InternalDisconnect(){// stop any reconnect operation.if (m_reconnectHandler != null){m_reconnectHandler.Dispose();m_reconnectHandler = null;}// disconnect any existing session.if (m_session != null){m_session.KeepAlive -= Session_KeepAlive;m_session.Close(10000);m_session = null;}}/// <summary>/// Handles a keep alive event from a session./// 监听连接状态,当意外连接失败时自动重连/// </summary>private void Session_KeepAlive(Session session, KeepAliveEventArgs e){try{// check for events from discarded sessions.if (!Object.ReferenceEquals(session, m_session)){return;}// start reconnect sequence on communication error.if (Opc.Ua.ServiceResult.IsBad(e.Status)){if (ReconnectPeriod <= 0){UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);return;}UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", ReconnectPeriod);if (m_reconnectHandler == null){if (m_ReconnectStarting != null){m_ReconnectStarting(this, e);}m_reconnectHandler = new SessionReconnectHandler();//重连m_reconnectHandler.BeginReconnect(m_session, ReconnectPeriod, Server_ReconnectComplete);}return;}// update status.UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);// raise any additional notifications.if (m_KeepAliveComplete != null){m_KeepAliveComplete(this, e);}}catch (Exception exception){throw new Exception("Session_KeepAlive", exception);}}/// <summary>/// Handles a reconnect event complete from the reconnect handler./// 连接断开重连成功后执行该方法/// </summary>private void Server_ReconnectComplete(object sender, EventArgs e){try{// ignore callbacks from discarded objects.if (!Object.ReferenceEquals(sender, m_reconnectHandler)){return;}m_session = m_reconnectHandler.Session;m_reconnectHandler.Dispose();m_reconnectHandler = null;// raise any additional notifications.if (m_ReconnectComplete != null){m_ReconnectComplete(this, e);}//isCconnect = true;//if (!isSub)//{//    ReadItems();//}}catch (Exception exception){throw new Exception("Server_ReconnectComplete", exception);}}

②认证,包括没有限制、需要用户名和密码、数字证书

1.先加载客户端配置(一般在初始化时候加载)

        /// <summary>/// /// </summary>/// <param name="username"></param>/// <param name="password"></param>/// <param name="m_IP"></param>/// <param name="m_Port"></param>public opcuahelper(string username,string password){m_endpoints = new Dictionary<Uri, EndpointDescription>();if (username != "" && password != ""){UserIdentity = new UserIdentity(username, password);}Console.WriteLine("加载客户端配置");#region 加载客户端配置m_application.ApplicationType = ApplicationType.Client;m_application.ConfigSectionName = "Treasure.OPCUAClient";//1.加载配置,如果证书不存在则生成证书//2.把生成的证书导入到服务端,让服务器信任证书//3.然后再次连接即可成功// load the application configuration. 读取XML给加载configurationm_application.LoadApplicationConfiguration("OPCUAClient.Config.xml", silent: false);// check the application certificate. 检查证书m_application.CheckApplicationInstanceCertificate(silent: false, minimumKeySize: 0);m_configuration = m_application.ApplicationConfiguration;//当证书验证失败时回调m_configuration.CertificateValidator.CertificateValidation += CertificateValidation;#endregion}/// <summary>/// Handles the certificate validation event.当证书验证失败时回调事件/// This event is triggered every time an untrusted certificate is received from the server./// </summary>private void CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e){bool certificateAccepted = true;// ****// Implement a custom logic to decide if the certificate should be// accepted or not and set certificateAccepted flag accordingly.// The certificate can be retrieved from the e.Certificate field// ***ServiceResult error = e.Error;while (error != null){Console.WriteLine(error);error = error.InnerResult;}if (certificateAccepted){Console.WriteLine("Untrusted Certificate accepted. SubjectName = {0}", e.Certificate.SubjectName);}e.AcceptAll = certificateAccepted;}

2.如果opcua服务器没有认证限制或者有用户名和密码。这样配置你直接使用连接方法就可以连上。但是如果是有数字证书那么你需要到服务端把客户端发来的证书添加信任,再次连接即可连上。 如下面视频我kepserver是只启动数字认证,那么客户端的连接我需要手动信任才可。 具体看如下视频

数字认证

③数据读取:先把数据读上来,然后根据数据类型转换即可。我是都转成string赋值。特别注意结构体读取需要特殊处理一下

        /// <summary>/// OPCUA批量Tag读取/// 把读取的数据值都转成string/// </summary>/// <param name="itemBases">需要读取的节点</param>/// <returns></returns>public void ReadNodes(List<itemModel> itemBases){try{if (m_session.Connected){//Console.WriteLine("数据节点转换");//节点格式转换ReadValueIdCollection nodesToRead = new ReadValueIdCollection();for (int i = 0; i < itemBases.Count; i++){nodesToRead.Add(new ReadValueId(){NodeId = new NodeId(itemBases[i].ItemAdress),AttributeId = Attributes.Value});}NodeId ss = itemBases[0].ItemAdress;//EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(QTI_Data));// Read the node attributesDataValueCollection resultsValues = null;DiagnosticInfoCollection diagnosticInfos = null;//Console.WriteLine("数据读取开始");// Call Read Servicem_session.Read(null,0,TimestampsToReturn.Both,nodesToRead,out resultsValues,out diagnosticInfos);//验证结果ClientBase.ValidateResponse(resultsValues, nodesToRead);for (int i = 0; i < resultsValues.Count; i++){//这里数组还有其他的标准数据类型都当转string赋值,当然你也可以根据resultsValues[i].WrappedValue.TypeInfo一个个处理if (resultsValues[i].Value == null){itemBases[i].ItemValue = "";}else{//若是字符串数据的话,则进行直接赋值if (resultsValues[i].WrappedValue.TypeInfo.BuiltInType == BuiltInType.String){itemBases[i].ItemValue = resultsValues[i].Value.ToString();}else{itemBases[i].ItemValue = Newtonsoft.Json.JsonConvert.SerializeObject(resultsValues[i].Value);}}//在这里进行结构体类型数据处理if (itemBases[i].ItemType == DataType.Struct){// itemBases[i].ItemValue = ReadStruct(itemBases[i].ItemAdress, resultsValues[i]);}}}}catch (Exception ex){throw new Exception("OPUAReadNodes:" + ex.Message + ex.StackTrace);}}

④数据写入:OPC UA在数据写入时,对数据类型有严格要求,数据类型不会自动转换,会写入失败。所以写入数据时需要指定数据类型。特别注意结构体写入需要特殊处理一下

        /// <summary>/// 单个写入节点,注意T的数据类型要与OPCUA服务器的数据类型相对应/// </summary>/// <typeparam name="T">写入的数据类型</typeparam>/// <param name="t">写入的值</param>/// <param name="nodeAddress">写入值的地址</param>public void WriteNodes<T>(T t, string nodeAddress){try{if (m_session.Connected){//ApplicationLog.operateLog("数据节点转换");//节点格式转换WriteValueCollection nodesToWrite = new WriteValueCollection();//if (itemBases.ItemType == DataType.Struct)//类型是结构体时//{//    List<WriteVar> WriteVarList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<WriteVar>>(itemBases.ItemValue);//    ExtensionObject[] ExtensionObjectArr = new ExtensionObject[100];//    for (int j = 0; j < ExtensionObjectArr.Length; j++)//    {//        ExtensionObjectArr[j] = new ExtensionObject { Body = getNewByte() };//    }//    foreach (var item in WriteVarList)//    {//        var result1 = GetValueByteFromObj(item);//        ExtensionObjectArr[Convert.ToInt32(item.index)] = result1;//    }//    nodesToWrite.Add(new WriteValue()//    {//        NodeId = new NodeId(itemBases.ItemAdress),//        AttributeId = Attributes.Value,//        Value = new DataValue()//        {//            Value = ExtensionObjectArr//        }//    });//}nodesToWrite.Add(new WriteValue(){NodeId = new NodeId(nodeAddress),AttributeId = Attributes.Value,Value = new DataValue(){Value = t}});//NodeId ss = itemBases.ItemAdress;//EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(QTI_Data));// Read the node attributesStatusCodeCollection resultsValues = null;DiagnosticInfoCollection diagnosticInfos = null;//ApplicationLog.operateLog("数据读取开始");// Call Read Servicem_session.Write(null,nodesToWrite,out resultsValues,out diagnosticInfos);//验证结果ClientBase.ValidateResponse(resultsValues, nodesToWrite);}else{throw new Exception("OPCUA连接断开");}}catch (Exception ex){Console.WriteLine("WriteNodes 写入异常" + ex.ToString());throw;}}

⑤订阅:数据订阅后服务器数据变化后会自动通知到客户端。相当于把压力放在了服务器。注意:服务器对与订阅资源是有限的,有的服务器必须显式的释放订阅资源,不然会造成订阅资源耗尽而订阅失败,必须重启服务端软件才能解决。 而且除非监听时去除的Subscription 对象,而非单个节点。

        List<itemModel> Items = new List<itemModel>();Subscription subscription;/// <summary>///Create Subscription and MonitoredItems for DataChanges/// 添加订阅/// </summary>/// <param name="interval">采样间隔 单位毫秒</param>/// <param name="itemBases"></param>public void Subscribe(int interval, List<itemModel> itemBases){try{// Create a subscription for receiving data change notifications// Define Subscription parameterssubscription = new Subscription(m_session.DefaultSubscription);subscription.DisplayName = "OPCUA Subscribe";subscription.PublishingEnabled = true;subscription.PublishingInterval = 200;for (int i = 0; i < itemBases.Count; i++){// Create MonitoredItems for data changesMonitoredItem MonitoredItem = new MonitoredItem(subscription.DefaultItem);// Int32 Node - Objects\CTT\Scalar\Simulation\Int32MonitoredItem.StartNodeId = new NodeId(itemBases[i].ItemAdress);MonitoredItem.AttributeId = Attributes.Value;MonitoredItem.DisplayName = itemBases[i].ItemID;MonitoredItem.SamplingInterval = interval;MonitoredItem.Notification += OnMonitoredItemNotification;subscription.AddItem(MonitoredItem);Items.Add(itemBases[i]);}m_session.AddSubscription(subscription);// Create the subscription on Server sidesubscription.Create();// Create the monitored items on Server side//subscription.ApplyChanges();}catch (Exception ex){Console.WriteLine("OPCUA订阅" + ex.ToString());}}/// <summary>/// Create Subscription and MonitoredItems for DataChanges/// 移除订阅/// </summary>public void RemoveSubscribe(){try{m_session.RemoveSubscription(subscription);}catch (Exception ex){Console.WriteLine("OPCUA订阅" + ex.ToString());}}/// <summary>/// Handle DataChange notifications from Server/// 订阅后有数据变化回调/// </summary>public void OnMonitoredItemNotification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e){try{// Log MonitoredItem Notification eventMonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;//if (monitoredItem.DisplayName=="8")//{//    var irr = notification;//}var item = Items.Find(o => o.ItemID == monitoredItem.DisplayName);if (notification.Value != null){//若是字符串数据的话,则进行直接赋值if (notification.Value.WrappedValue.TypeInfo.BuiltInType == BuiltInType.String){item.ItemValue = notification.Value.ToString();}//在这里进行结构体类型数据处理//else if (item.ItemType == DataType.Struct)//{//    item.ItemValue = ReadStruct(item.ItemAdress, notification.Value);//}else{item.ItemValue = Newtonsoft.Json.JsonConvert.SerializeObject(notification.Value.Value);}MessageBox.Show($"数据节点是:{item.ItemAdress}  值是:{item.ItemValue}");}//m_output.WriteLine("Notification Received for Variable \"{0}\" and Value = {1}.", monitoredItem.DisplayName, notification.Value);}catch (Exception ex){//m_output.WriteLine("OnMonitoredItemNotification error: {0}", ex.Message);}}

结构体读写参考我这篇文章C# OPCUA 读写结构体_@榴莲酥的博客-CSDN博客

完成代码

using Newtonsoft.Json;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace opcuaTest
{/// <summary>/// opcua工具类/// </summary>public class opcuahelper{/// <summary>/// /// </summary>/// <param name="username"></param>/// <param name="password"></param>/// <param name="m_IP"></param>/// <param name="m_Port"></param>public opcuahelper(string username,string password){m_endpoints = new Dictionary<Uri, EndpointDescription>();if (username != "" && password != ""){UserIdentity = new UserIdentity(username, password);}Console.WriteLine("加载客户端配置");#region 加载客户端配置m_application.ApplicationType = ApplicationType.Client;m_application.ConfigSectionName = "Treasure.OPCUAClient";//1.加载配置,如果证书不存在则生成证书//2.把生成的证书导入到服务端,让服务器信任证书//3.然后再次连接即可成功// load the application configuration. 读取XML给加载configurationm_application.LoadApplicationConfiguration("OPCUAClient.Config.xml", silent: false);// check the application certificate. 检查证书m_application.CheckApplicationInstanceCertificate(silent: false, minimumKeySize: 0);m_configuration = m_application.ApplicationConfiguration;//当证书验证失败时回调m_configuration.CertificateValidator.CertificateValidation += CertificateValidation;#endregion}#region Constructorsprivate ApplicationInstance m_application = new ApplicationInstance();#endregion#region Private Fieldsprivate string m_ConnectStr;private ApplicationConfiguration m_configuration;private Session m_session;private SessionReconnectHandler m_reconnectHandler;private EventHandler m_ReconnectComplete;private EventHandler m_ReconnectStarting;private EventHandler m_KeepAliveComplete;private EventHandler m_ConnectComplete;private Dictionary<Uri, EndpointDescription> m_endpoints;#endregion#region Public Members/// <summary>/// Default session values./// </summary>[JsonIgnore]public static readonly uint DefaultSessionTimeout = 60000;/// <summary>/// 默认发现超时/// </summary>[JsonIgnore]public static readonly int DefaultDiscoverTimeout = 15000;/// <summary>/// 默认重新连接时间/// </summary>[JsonIgnore]public static readonly int DefaultReconnectPeriod = 1000;/// <summary>/// The name of the session to create./// </summary>[JsonIgnore]public string SessionName { get; set; }/// <summary>/// Gets or sets a flag indicating that the domain checks should be ignored when connecting./// </summary>[JsonIgnore]public bool DisableDomainCheck { get; set; }/// <summary>/// Gets the cached EndpointDescription for a Url./// </summary>public EndpointDescription GetEndpointDescription(Uri url){EndpointDescription endpointDescription;if (m_endpoints.TryGetValue(url, out endpointDescription)){return endpointDescription;}return null;}/// <summary>/// The URL displayed in the control./// </summary>public string ServerUrl{get;set;}/// <summary>/// Whether to use security when connecting./// </summary>public bool UseSecurity{get;set;}/// <summary>/// Get the Client status./// </summary>public string ConnectStatus{get { return m_ConnectStatus; }}private string m_ConnectStatus;/// <summary>/// The locales to use when creating the session./// </summary>[JsonIgnore]public string[] PreferredLocales { get; set; }/// <summary>/// The user identity to use when creating the session./// 认证/// </summary>[JsonIgnore]public IUserIdentity UserIdentity { get; set; } = new UserIdentity();/// <summary>/// The client application configuration./// 客户端配置,一般用在证书验证中/// </summary>[JsonIgnore]public ApplicationConfiguration Configuration{get => m_configuration;set{if (!Object.ReferenceEquals(m_configuration, value)){m_configuration = value;}}}/// <summary>/// The currently active session. /// 连接会话/// </summary>[JsonIgnore]public Session Session => m_session;/// <summary>/// The number of seconds between reconnect attempts (0 means reconnect is disabled)./// </summary>public int ReconnectPeriod { get; set; } = DefaultReconnectPeriod;/// <summary>/// The discover timeout./// </summary>public int DiscoverTimeout { get; set; } = DefaultDiscoverTimeout;/// <summary>/// The session timeout./// </summary>public uint SessionTimeout { get; set; } = DefaultSessionTimeout;/// <summary>/// Raised when a good keep alive from the server arrives./// </summary>public event EventHandler KeepAliveComplete{add { m_KeepAliveComplete += value; }remove { m_KeepAliveComplete -= value; }}/// <summary>/// Raised when a reconnect operation starts./// </summary>public event EventHandler ReconnectStarting{add { m_ReconnectStarting += value; }remove { m_ReconnectStarting -= value; }}/// <summary>/// Raised when a reconnect operation completes./// </summary>public event EventHandler ReconnectComplete{add { m_ReconnectComplete += value; }remove { m_ReconnectComplete -= value; }}/// <summary>/// Raised after successfully connecting to or disconnecing from a server./// </summary>public event EventHandler ConnectComplete{add { m_ConnectComplete += value; }remove { m_ConnectComplete -= value; }}/// <summary>/// Creates a new session./// 连接opcua/// </summary>/// <returns>The new session object.</returns>private async Task<Session> Connect(ITransportWaitingConnection connection,EndpointDescription endpointDescription,bool useSecurity,uint sessionTimeout = 0){// disconnect from existing session.InternalDisconnect();// select the best endpoint.if (endpointDescription == null){endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, connection, useSecurity, DiscoverTimeout);}EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);m_session = await Session.Create(m_configuration,connection,endpoint,false,!DisableDomainCheck,(String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,sessionTimeout == 0 ? DefaultSessionTimeout : sessionTimeout,UserIdentity,PreferredLocales);// set up keep alive callback.m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);// return the new session.return m_session;}/// <summary>/// 连接opcua/// </summary>/// <param name="serverUrl">连接地址</param>/// <param name="useSecurity">是否启用身份验证,true启用,false不启用</param>/// <param name="sessionTimeout"></param>/// <returns></returns>public async Task<Session> ConnectServer(string serverUrl,bool useSecurity,uint sessionTimeout = 0){try{// disconnect from existing session.InternalDisconnect();// select the best endpoint.Console.WriteLine("获取远程节点:" + serverUrl);serverUrl = "opc.tcp://127.0.0.1:49320";var endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, useSecurity, 600000);var endpointConfiguration = EndpointConfiguration.Create(m_configuration);var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);// Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(endpoint));Console.WriteLine("开始连接:" + serverUrl);m_session = await Session.Create(m_configuration,endpoint,false,DisableDomainCheck,(String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,sessionTimeout == 0 ? DefaultSessionTimeout : sessionTimeout,UserIdentity,PreferredLocales);// set up keep alive callback. 监听连接状态,但连接失败时自动重连m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);Console.WriteLine("OPC连接成功:" + serverUrl + "m_session.Connected的连接状态是:" + m_session.Connected);}catch (Exception ex){Console.WriteLine("OPC连接异常:" + serverUrl + ex.ToString());if (m_session != null){m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);}else{//isCconnect = false;}}try{UpdateStatus(false, DateTime.Now, "Connected, loading complex type system.");}catch (Exception e){UpdateStatus(true, DateTime.Now, "Connected, failed to load complex type system.");Utils.Trace(e, "Failed to load complex type system.");}// return the new session.return m_session;}/// <summary>/// Creates a new session.///  连接opcua,异步线程/// </summary>/// <param name="serverUrl">The URL of a server endpoint.</param>/// <param name="useSecurity">Whether to use security.</param>/// <param name="sessionTimeout">会话超时时间.</param>/// <returns>The new session object.</returns>public async Task<Session> ConnectServerAsync(string serverUrl = null,bool useSecurity = false,uint sessionTimeout = 0){return await Task.Run(() => ConnectServer(serverUrl, useSecurity, sessionTimeout));}/// <summary>/// Create a new reverse connection./// </summary>/// <param name="connection"></param>/// <param name="useSecurity"></param>/// <param name="discoverTimeout"></param>/// <param name="sessionTimeout"></param>public async Task<Session> ConnectServerAsync(ITransportWaitingConnection connection,bool useSecurity,int discoverTimeout = -1,uint sessionTimeout = 0){if (connection.EndpointUrl == null){throw new ArgumentException("Endpoint URL is not valid.");}EndpointDescription endpointDescription = null;if (!m_endpoints.TryGetValue(connection.EndpointUrl, out endpointDescription)){// Discovery uses the reverse connection and closes it// return and wait for next reverse helloendpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, connection, useSecurity, discoverTimeout);m_endpoints[connection.EndpointUrl] = endpointDescription;return null;}return await Connect(connection, endpointDescription, useSecurity, sessionTimeout);}/// <summary>/// Disconnects from the server./// </summary>public Task DisconnectAsync(){UpdateStatus(false, DateTime.Now, "Disconnected");return Task.Run(() => InternalDisconnect());}/// <summary>/// 断开连接/// Disconnects from the server./// </summary>private void InternalDisconnect(){// stop any reconnect operation.if (m_reconnectHandler != null){m_reconnectHandler.Dispose();m_reconnectHandler = null;}// disconnect any existing session.if (m_session != null){m_session.KeepAlive -= Session_KeepAlive;m_session.Close(10000);m_session = null;}}/// <summary>/// Disconnects from the server./// </summary>public void Disconnect(){UpdateStatus(false, DateTime.Now, "Disconnected");// stop any reconnect operation.InternalDisconnect();}#endregion#region 订阅的添加、删除、监听List<itemModel> Items = new List<itemModel>();Subscription subscription;/// <summary>///Create Subscription and MonitoredItems for DataChanges/// 添加订阅/// </summary>/// <param name="interval">采样间隔 单位毫秒</param>/// <param name="itemBases"></param>public void Subscribe(int interval, List<itemModel> itemBases){try{// Create a subscription for receiving data change notifications// Define Subscription parameterssubscription = new Subscription(m_session.DefaultSubscription);subscription.DisplayName = "OPCUA Subscribe";subscription.PublishingEnabled = true;subscription.PublishingInterval = 200;for (int i = 0; i < itemBases.Count; i++){// Create MonitoredItems for data changesMonitoredItem MonitoredItem = new MonitoredItem(subscription.DefaultItem);// Int32 Node - Objects\CTT\Scalar\Simulation\Int32MonitoredItem.StartNodeId = new NodeId(itemBases[i].ItemAdress);MonitoredItem.AttributeId = Attributes.Value;MonitoredItem.DisplayName = itemBases[i].ItemID;MonitoredItem.SamplingInterval = interval;MonitoredItem.Notification += OnMonitoredItemNotification;subscription.AddItem(MonitoredItem);Items.Add(itemBases[i]);}m_session.AddSubscription(subscription);// Create the subscription on Server sidesubscription.Create();// Create the monitored items on Server side//subscription.ApplyChanges();}catch (Exception ex){Console.WriteLine("OPCUA订阅" + ex.ToString());}}/// <summary>/// Create Subscription and MonitoredItems for DataChanges/// 移除订阅/// </summary>public void RemoveSubscribe(){try{m_session.RemoveSubscription(subscription);}catch (Exception ex){Console.WriteLine("OPCUA订阅" + ex.ToString());}}/// <summary>/// Handle DataChange notifications from Server/// 订阅后有数据变化回调/// </summary>public void OnMonitoredItemNotification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e){try{// Log MonitoredItem Notification eventMonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;//if (monitoredItem.DisplayName=="8")//{//    var irr = notification;//}var item = Items.Find(o => o.ItemID == monitoredItem.DisplayName);if (notification.Value != null){//若是字符串数据的话,则进行直接赋值if (notification.Value.WrappedValue.TypeInfo.BuiltInType == BuiltInType.String){item.ItemValue = notification.Value.ToString();}//在这里进行结构体类型数据处理//else if (item.ItemType == DataType.Struct)//{//    item.ItemValue = ReadStruct(item.ItemAdress, notification.Value);//}else{item.ItemValue = Newtonsoft.Json.JsonConvert.SerializeObject(notification.Value.Value);}MessageBox.Show($"数据节点是:{item.ItemAdress}  值是:{item.ItemValue}");}//m_output.WriteLine("Notification Received for Variable \"{0}\" and Value = {1}.", monitoredItem.DisplayName, notification.Value);}catch (Exception ex){//m_output.WriteLine("OnMonitoredItemNotification error: {0}", ex.Message);}}#endregion#region Event Handlers/// <summary>/// Updates the status control./// </summary>/// <param name="error">Whether the status represents an error.</param>/// <param name="time">The time associated with the status.</param>/// <param name="status">The status message.</param>/// <param name="args">Arguments used to format the status message.</param>private void UpdateStatus(bool error, DateTime time, string status, params object[] args){m_ConnectStatus = String.Format(status, args);}/// <summary>/// Handles a keep alive event from a session./// 监听连接状态,当连接失败时自动重连/// </summary>private void Session_KeepAlive(Session session, KeepAliveEventArgs e){try{// check for events from discarded sessions.if (!Object.ReferenceEquals(session, m_session)){return;}// start reconnect sequence on communication error.if (Opc.Ua.ServiceResult.IsBad(e.Status)){if (ReconnectPeriod <= 0){UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);return;}UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", ReconnectPeriod);if (m_reconnectHandler == null){if (m_ReconnectStarting != null){m_ReconnectStarting(this, e);}m_reconnectHandler = new SessionReconnectHandler();//重连m_reconnectHandler.BeginReconnect(m_session, ReconnectPeriod, Server_ReconnectComplete);}return;}// update status.UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);// raise any additional notifications.if (m_KeepAliveComplete != null){m_KeepAliveComplete(this, e);}}catch (Exception exception){throw new Exception("Session_KeepAlive", exception);}}/// <summary>/// Handles a reconnect event complete from the reconnect handler./// 连接断开重连成功后执行该方法/// </summary>private void Server_ReconnectComplete(object sender, EventArgs e){try{// ignore callbacks from discarded objects.if (!Object.ReferenceEquals(sender, m_reconnectHandler)){return;}m_session = m_reconnectHandler.Session;m_reconnectHandler.Dispose();m_reconnectHandler = null;// raise any additional notifications.if (m_ReconnectComplete != null){m_ReconnectComplete(this, e);}//isCconnect = true;//if (!isSub)//{//    ReadItems();//}}catch (Exception exception){throw new Exception("Server_ReconnectComplete", exception);}}#endregion#region 读写数据/// <summary>/// OPCUA批量Tag读取/// 把读取的数据值都转成string/// </summary>/// <param name="itemBases">需要读取的节点</param>/// <returns></returns>public void ReadNodes(List<itemModel> itemBases){try{if (m_session.Connected){//Console.WriteLine("数据节点转换");//节点格式转换ReadValueIdCollection nodesToRead = new ReadValueIdCollection();for (int i = 0; i < itemBases.Count; i++){nodesToRead.Add(new ReadValueId(){NodeId = new NodeId(itemBases[i].ItemAdress),AttributeId = Attributes.Value});}NodeId ss = itemBases[0].ItemAdress;//EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(QTI_Data));// Read the node attributesDataValueCollection resultsValues = null;DiagnosticInfoCollection diagnosticInfos = null;//Console.WriteLine("数据读取开始");// Call Read Servicem_session.Read(null,0,TimestampsToReturn.Both,nodesToRead,out resultsValues,out diagnosticInfos);//验证结果ClientBase.ValidateResponse(resultsValues, nodesToRead);for (int i = 0; i < resultsValues.Count; i++){//这里数组还有其他的标准数据类型都当转string赋值,当然你也可以根据resultsValues[i].WrappedValue.TypeInfo一个个处理if (resultsValues[i].Value == null){itemBases[i].ItemValue = "";}else{//若是字符串数据的话,则进行直接赋值if (resultsValues[i].WrappedValue.TypeInfo.BuiltInType == BuiltInType.String){itemBases[i].ItemValue = resultsValues[i].Value.ToString();}else{itemBases[i].ItemValue = Newtonsoft.Json.JsonConvert.SerializeObject(resultsValues[i].Value);}}//在这里进行结构体类型数据处理if (itemBases[i].ItemType == DataType.Struct){// itemBases[i].ItemValue = ReadStruct(itemBases[i].ItemAdress, resultsValues[i]);}}}}catch (Exception ex){throw new Exception("OPUAReadNodes:" + ex.Message + ex.StackTrace);}}/// <summary>/// 单个写入节点,注意T的数据类型要与OPCUA服务器的数据类型相对应/// 返回true写入成功,false失败/// </summary>/// <typeparam name="T">写入的数据类型</typeparam>/// <param name="t">写入的值</param>/// <param name="nodeAddress">写入值的地址</param>public bool WriteNodes<T>(T t, string nodeAddress){try{if (m_session.Connected){//ApplicationLog.operateLog("数据节点转换");//节点格式转换WriteValueCollection nodesToWrite = new WriteValueCollection();//if (itemBases.ItemType == DataType.Struct)//类型是结构体时//{//    List<WriteVar> WriteVarList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<WriteVar>>(itemBases.ItemValue);//    ExtensionObject[] ExtensionObjectArr = new ExtensionObject[100];//    for (int j = 0; j < ExtensionObjectArr.Length; j++)//    {//        ExtensionObjectArr[j] = new ExtensionObject { Body = getNewByte() };//    }//    foreach (var item in WriteVarList)//    {//        var result1 = GetValueByteFromObj(item);//        ExtensionObjectArr[Convert.ToInt32(item.index)] = result1;//    }//    nodesToWrite.Add(new WriteValue()//    {//        NodeId = new NodeId(itemBases.ItemAdress),//        AttributeId = Attributes.Value,//        Value = new DataValue()//        {//            Value = ExtensionObjectArr//        }//    });//}nodesToWrite.Add(new WriteValue(){NodeId = new NodeId(nodeAddress),AttributeId = Attributes.Value,Value = new DataValue(){Value = t}});//NodeId ss = itemBases.ItemAdress;//EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(QTI_Data));// Read the node attributesStatusCodeCollection resultsValues = null;DiagnosticInfoCollection diagnosticInfos = null;//ApplicationLog.operateLog("数据读取开始");// Call Read Servicem_session.Write(null,nodesToWrite,out resultsValues,out diagnosticInfos);//验证结果ClientBase.ValidateResponse(resultsValues, nodesToWrite);bool result = true;foreach (var r in resultsValues){if (StatusCode.IsBad(r)){result = false;break;}}return result;}else{throw new Exception("OPCUA连接断开");}}catch (Exception ex){Console.WriteLine("WriteNodes 写入异常" + ex.ToString());throw;}}#endregion#region 认证/// <summary>/// Handles the certificate validation event.当证书验证失败时回调事件/// This event is triggered every time an untrusted certificate is received from the server./// </summary>private void CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e){bool certificateAccepted = true;// ****// Implement a custom logic to decide if the certificate should be// accepted or not and set certificateAccepted flag accordingly.// The certificate can be retrieved from the e.Certificate field// ***ServiceResult error = e.Error;while (error != null){Console.WriteLine(error);error = error.InnerResult;}if (certificateAccepted){Console.WriteLine("Untrusted Certificate accepted. SubjectName = {0}", e.Certificate.SubjectName);}e.AcceptAll = certificateAccepted;}#endregion}}

6.demo

所有demo和资料都打包放在一起,大家可以一起下载使用。下面截图时opcua运行所需DLL

OPCUA从入门到精通看这里就够了相关推荐

  1. python免费入门教程-python入门免费教程看这些就够了

    原标题:python入门免费教程看这些就够了 python入门免费教程看这些就够了 自从20世纪90年代初Python语言诞生至今,它逐渐被广泛应用于处理系统管理任务和Web编程.Python已经成为 ...

  2. python免费全套教程-python入门免费教程看这些就够了

    原标题:python入门免费教程看这些就够了 python入门免费教程看这些就够了 自从20世纪90年代初Python语言诞生至今,它逐渐被广泛应用于处理系统管理任务和Web编程.Python已经成为 ...

  3. java编程入门到精通看什么书,详细说明

    Java面试高频题:Spring Boot+Sentinel+Nacos高并发已撸完Java成长笔记1. Java基础复盘2. Web编程初探3. SSM从入门到精通4. SpringBoot快速上手 ...

  4. 神仙级python入门教程(非常详细)零基础入门到精通看这篇开始

    ▌▌ Python的应用 自动化工具:自动处理数据.Excel文件.发邮件.下载.上传数据 网络爬虫:代替人工自动从下载数据,例如:商品信息.股票数据.技术文章 Web网站:开发一个网站.APP.小程 ...

  5. Kafka消息队列 入门到精通 看这一篇就够了

    文章目录 第一章 概述 1.1 Kafka 的定义及特点 1.2 消息队列的介绍 1.3 Kafka 的基础架构 第二章 入门 2.1 Kafka 的安装部署 2.2 Kafka 命令行操作 第三章 ...

  6. Jmeter从入门到精通-看这一篇就够了

    安装与配置 Jmeter安装与配置:Windows https://blog.csdn.net/qq19970496/article/details/100781616 Jmeter设置默认语言为中文 ...

  7. Spring Boot 教学 学习 讲义笔记 【入门到精通一篇就够了】(上)

    目录 初识Spring Boot Spring boot 入门 系统要求 Maven配置文件 HelloWorld项目 创建maven工程 引入依赖 创建主程序 编写业务 运行&测试 设置配置 ...

  8. 小程序入门到精通一篇就够了!

    一.小程序介绍 1.1.小程序是什么 官方文档:微信开放文档 微信小程序,简称小程序,英文名 MiniProgram ,是一种不需要下载安装即可使用的应用,它实现了应用 " 触手可及 &qu ...

  9. 从入门到精通,看了这篇文章,你离老黑的路就不远了

    关于被入侵 简单说明: 经常有帖子说:"我中xx木马啦,怎么办?"."我的windows有问题,是不是被入侵啦?"等等.通用的做法是查看可疑进程(win98需要 ...

最新文章

  1. ubuntu sendmail安装和使用具体实现[转]
  2. gentoo hibernate
  3. 第一个PowerShell脚本——PowerShell三分钟(九)
  4. [SHOI2011]双倍回文 manacher
  5. 【Linux学习】linux源代码版本控制RCS
  6. 录屏软件电脑_电脑录屏用什么软件?电脑录屏软件集锦
  7. 封装自己的Flex工具_SocketTool
  8. W5500EVB从网络上获取标准时间
  9. 智慧物流园区供应链管理系统解决方案:数智化供应链赋能物流运输行业供应链新模式
  10. 洗衣机测试点 思维导图
  11. QQ被盗的自救、事故分析
  12. 微信 Windows 版本干了件大事!
  13. 开心消消乐java下载_开心消消乐原版下载安装
  14. 基于Springboot+mysql的闲置二手交易网站系统设计
  15. Tableau表计算(2):计算依据
  16. java环境安装及java编译
  17. Java 性能优化的七个方向
  18. 【论文阅读】D19-1435——GEC问题解决的一种方法:PIE架构
  19. 百度网盘、小米云盘算什么,这个开源免费的云盘它不香吗?
  20. 苹果8参数_苹果Apple MacBook Air 13.3 新款笔记本怎么样,配置好不好

热门文章

  1. Prestashop1.7 搬家
  2. python牛顿法解非线性方程组_科学网—求解多元非线性方程组F(x)=0的Newton-Raphson方法及其MATLAB实现 - 王福昌的博文...
  3. 如何测量示波器的底噪
  4. SpringMVC高级篇
  5. Polyglot开发人员:是还是不是?
  6. java 对象流判断文件末尾 ( end of file / eof异常处理 )
  7. uniapp小程序使用RSA加密解密
  8. Android纯代码实现九宫格解锁
  9. Python Docker 接口测试代码-立哥开发
  10. 劳动合同未约定试用期工资,实际履行能否突破法定标准?