OPCUA从入门到精通看这里就够了

news2024/11/24 21:01:41

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

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给加载configuration
                m_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 attributes
                    DataValueCollection resultsValues = null;
                    DiagnosticInfoCollection diagnosticInfos = null;
                    //Console.WriteLine("数据读取开始");
                    // Call Read Service
                    m_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 attributes
                    StatusCodeCollection resultsValues = null;
                    DiagnosticInfoCollection diagnosticInfos = null;
                    //ApplicationLog.operateLog("数据读取开始");
                    // Call Read Service
                    m_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 parameters
                subscription = 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 changes

                    MonitoredItem MonitoredItem = new MonitoredItem(subscription.DefaultItem);
                    // Int32 Node - Objects\CTT\Scalar\Simulation\Int32
                    MonitoredItem.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 side
                subscription.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 event
                MonitoredItemNotification 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);
            }
        }

完成代码

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给加载configuration
                m_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 Constructors
        private ApplicationInstance m_application = new ApplicationInstance();

        #endregion

        #region Private Fields
        private 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 hello
                endpointDescription = 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 parameters
                subscription = 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 changes

                    MonitoredItem MonitoredItem = new MonitoredItem(subscription.DefaultItem);
                    // Int32 Node - Objects\CTT\Scalar\Simulation\Int32
                    MonitoredItem.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 side
                subscription.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 event
                MonitoredItemNotification 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 attributes
                    DataValueCollection resultsValues = null;
                    DiagnosticInfoCollection diagnosticInfos = null;
                    //Console.WriteLine("数据读取开始");
                    // Call Read Service
                    m_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 attributes
                    StatusCodeCollection resultsValues = null;
                    DiagnosticInfoCollection diagnosticInfos = null;
                    //ApplicationLog.operateLog("数据读取开始");
                    // Call Read Service
                    m_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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/571575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android平台音视频推送选RTMP还是GB28181?

技术背景 早在2015年&#xff0c;我们发布了RTMP直播推送模块&#xff0c;那时候音视频直播这块场景需求&#xff0c;还不像现在这么普遍&#xff0c;我们做这块的初衷&#xff0c;主要是为了实现移动单兵应急指挥系统的低延迟音视频数据传输。好多开发者可能会疑惑&#xff0…

信息系统建设和服务能力评估证书CS

信息系统建设和服务能力评估体系CS简介 简介&#xff1a;本标准&#xff08;团标T/CITIF 001-2019&#xff09;是信息系统建设和服务能力评估体系系列标准的第一个&#xff0c;提出了对信息系统建设和服务提供者的综合能力要求。 发证单位&#xff1a;中国电子信息行业联合会。…

整型,浮点型,大小字节知识细节一网打尽!

目录 一. 整型 2.11 C语言内置整型 2.12整型在内存如何存储&#xff1f; 2.12 原码&#xff0c;反码&#xff0c; 补码 2.13 当 整型遇上unsigned 2.1 unsigned 与 signed 解析 2.2 printf 输出 有无符号数解析 2.3 有关练习 二. 浮点型 2.51 浮点型与整型在存储上的…

视频会议产品对比分析

内网视频会议系统如何选择&#xff1f;有很多单位为了保密&#xff0c;只能使用内部网络&#xff0c;无法连接互联网&#xff0c;那些SaaS视频会议就无法使用。在内网的优秀视频会议也有很多可供选择&#xff0c;以下是几个常用的&#xff1a; 1. 宝利通&#xff1a;它支持多种…

港联证券|新产品“内卷”,史上最火光伏展能否带动光伏板块行情

在光伏成为“能源老大”之前&#xff0c;指数何时能够跑赢A股股主要股指&#xff0c;或是散户关注的重点。 时隔两年&#xff0c;SNEC上海光伏展如约举办。本届展会吸引了3000余家光伏公司参展&#xff0c;累计50万人涌进场馆&#xff0c;现场人群拥挤度丝毫不逊于过去两年资金…

Navicat连接Oracle时报错ORA-28547

错误信息 这是因为Navicat自带的oci.dll并不支持oracle11g&#xff0c;网上这么说的&#xff0c;需要去官网下载支持的版本。 直接动手。 1. 先DBeaver的连接到oracle数据库&#xff08;为了查询版本&#xff09; 1.1 查询版本 SQL&#xff1a;select * from v$version;1.2 …

如何利用Linkage Mapper工具包评估栖息地碎片化程度详解(含实例分析)

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Linkage Mapper解密数字世界链接 文章目录 Linkage Mapper工具包评估栖息地碎片化程度详解1. 介绍1.1 简介1.2 ArcGIS概述1.3 Linkage Mapper工具包简介

生态伙伴 | 携手深圳科创学院,持续推动项目落地与成长

01 大赛介绍 中国硬件创新创客大赛始于2015年&#xff0c;由深圳华秋电子有限公司主办&#xff0c;至今已经成功举办八届&#xff0c;赛事范围覆盖华南、华东、华北三大地区&#xff0c;超10个省市区域。 大赛影响了超过45万工程师群体&#xff0c;吸引了35000多名硬创先锋报…

分享Python采集66个焦点图,总有一款适合您

分享Python采集66个焦点图&#xff0c;总有一款适合您 Python采集的66个焦点图下载链接&#xff1a;https://pan.baidu.com/s/1nHoPaUp2Bqtrs5d7J-XdwA?pwd3yfb 提取码&#xff1a;3yfb jQuery SuperSlide插件自适应banner大图焦点图片轮播切换特效 jQuerySwiper仿魅族幻灯…

3.复制Centos虚拟机并且安装Ansible

在2.VirtualBox安装CentOS 7已经创建好了CentOS_template&#xff0c;按下面的步骤&#xff0c;从已经创建好的模版&#xff0c;分别复制出来3个虚拟机&#xff1a;ansible-controller和ansible-target1和ansible-target2。 注意这里的MAC地址设定&#xff0c;要选择&#xff…

用Tushare获取股票信息

Tushare数据Tushare为金融数据分析提供便捷、快速的接口&#xff0c;与投研和量化策略无缝对接https://tushare.pro/register?reg593541 o(≧口≦)o 先别看着链接就走了&#xff0c;敲黑板。 教大家如何基础使用日线行情获取接口数据&#xff0c;不会告诉你们我就是为了骗积分…

spring security oauth2.0-密码式password

密码式(password)授权模式 简介 如果你高度信任对方应用,密码式授权模式也是被允许的. 密码模式,顾名思义,就是使用账号密码的形式在授权服务器上获取授权令牌, 但是这种令牌是不支持刷新令牌的. 这种方式一般是不建议使用的,因为它的高风险性, 但是如果只是两家应用之间的…

算法工程师的主要职责(合集)

算法工程师的主要职责 算法工程师的主要职责1 1、环境建模 根据设计的机器人方案&#xff0c;构建机器人的运动学模型、观测模型等概率学模型; 2、slam算法研发 研究基于多线激光雷达的slam算法&#xff0c;包括特征提取、数据关联、闭环检测等相关算法的开发; 3、定位算法研发…

gd32f103vbt6 串口OTA升级-4-从rk3399的串口升级1

一、需求&#xff1a; 因客户需求&#xff0c;觉得升级单片机程序需要打开设备的盖子&#xff0c;&#xff08;抽出设备&#xff0c;拧螺丝&#xff0c;挺费事的&#xff09;。 那能不能把单片机也做到linux系统下升级呢&#xff1f; 答案当然是可行的。&#xff08;这里有个…

Shopee|Lazada电商平台api接口,接入获取商品评论|根据关键词取商品列表API说明示例

Shopee和Lazada是东南亚地区广受欢迎的电商平台。它们的模式非常类似&#xff0c;都是以C2C&#xff08;消费者到消费者&#xff09;、B2C&#xff08;企业到消费者&#xff09;和O2O&#xff08;线上到线下&#xff09;为主要销售模式。用户可以在平台上购买商品或者将自己的商…

如何在华为OD机试中获得满分?Java实现【组装新的数组】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

华为OD机试真题 Java 实现【查找重复代码】【2023Q1 100分】

一、题目描述 小明负责维护项目中的代码,需要查找出重复代码,用以支撑后续的代码优化,请你帮助小明找出重复的代码。 重复代码查找方法: 以字符串形式给定两行代码,找出两行代码中的最长公共子串。 如果不存在公共子串,返回空。 二、输入描述 输入两行代码。 三、…

深眸科技|机器视觉应用优势显著赋能生产,技术崛起获发展前景

近年来&#xff0c;机器视觉已经逐渐渗透进工业制造领域的各个方面&#xff0c;并已经成为工业自动化和智能化的核心&#xff0c;无论是“中国制造2025”还是“工业4.0”都离不开机器视觉&#xff0c;机器视觉将为智能制造打开一个新的世界。 随着人工智能、云计算、5G等技术的…

Android静态库/动态库相关知识

以下皆是在ubuntu下面执行 --------------------查看动态库/静态库是多少位的------------------------ 查看动态库.so文件 直接使用file查看&#xff0c;即 file xxx.so 例子如下&#xff1a; 这里的librtmp-arm.so是arm 64位的 查看静态库.a文件 objdump -a xxx.a 例子…

实训可视化项目小结 --- 开启Python初始之旅

Python初试感悟 语言之间是相通的&#xff0c;大多数时候&#xff0c;百分之八十的问题&#xff0c;都可以用常用的容器以及内置函数来辅助解决。之前从未认真接触过Python&#xff0c;但此次学校实训要求使用Python做一个可视化&#xff0c;东西不难&#xff0c;我个人负责爬…