فهرست منبع

优化OPCUA订阅流程

Shun 2 سال پیش
والد
کامیت
0c04af933c

+ 4 - 0
src/YSAI.DAQ/YSAI.Core/communication/net/tcp/service/TcpServiceOperate.cs

@@ -232,6 +232,10 @@ namespace YSAI.Core.communication.net.tcp.service
                     foreach (var item in ClientIoc)
                     {
                         item.Value.Switch.Cancel();
+                    }
+                    //关闭客户端消息线程
+                    foreach (var item in ClientIoc)
+                    {
                         item.Value.TaskObj.Wait();
                         item.Value.SocketObj.Close();
                         item.Value.SocketObj.Dispose();

+ 4 - 1
src/YSAI.DAQ/YSAI.Core/communication/net/ws/service/WsServiceOperate.cs

@@ -245,11 +245,14 @@ namespace YSAI.Core.communication.net.ws.service
                         //任务对象清空
                         MonitorConnectTask = null;
                     }
-
                     //关闭客户端消息线程
                     foreach (var item in ClientIoc)
                     {
                         item.Value.Switch.Cancel();
+                    }
+                    //关闭客户端消息线程
+                    foreach (var item in ClientIoc)
+                    {
                         item.Value.TaskObj.Wait();
                         item.Value.WebSocketObj.WebSocket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None);
                         item.Value.WebSocketObj.WebSocket.Abort();

BIN
src/YSAI.DAQ/YSAI.Lib/YSAI.Opc.Pack/YSAI.Opc.dll


BIN
src/YSAI.DAQ/YSAI.Lib/YSAI.Opc.Pack/YSAI.Opc.pdb


+ 12 - 0
src/YSAI.DAQ/YSAI.Lib/YSAI.Opc.Pack/YSAI.Opc.xml

@@ -1426,6 +1426,18 @@
             <param name="param">参数</param>
             <returns>只要有一个不成功则直接返回</returns>
         </member>
+        <member name="F:YSAI.Opc.ua.client.OpcUaClientOperate.SubscribeStatus">
+            <summary>
+            OPCUA 订阅状态集合
+            </summary>
+        </member>
+        <member name="M:YSAI.Opc.ua.client.OpcUaClientOperate.EnumParse(System.String)">
+            <summary>
+            订阅状态枚举解析
+            </summary>
+            <param name=""></param>
+            <returns></returns>
+        </member>
         <member name="M:YSAI.Opc.ua.client.OpcUaClientOperate.RemoveSubscribeAsync(YSAI.Core.data.Address)">
             <summary>
              移除订阅 通过节点来移除订阅,移除指定节点的订阅,没有也返回成功

+ 3 - 3
src/YSAI.DAQ/YSAI.Opc/da/client/OpcDaClientOperate.cs

@@ -743,11 +743,12 @@ namespace YSAI.Opc.da.client
                         foreach (var item in TaskArray)
                         {
                             item.Key.Cancel();
+                        }
+                        foreach (var item in TaskArray)
+                        {
                             item.Value.Wait();
                             item.Value.Dispose();
                         }
-                        TaskArray.Clear();
-                        TaskArray = null;
                     }
                     //队列数据清空
                     if (DataQueue != null)
@@ -756,7 +757,6 @@ namespace YSAI.Opc.da.client
                         DataQueue = null;
                     }
 
-
                     opcDaClient.Dispose();
                     opcDaClient = null;
                 }

+ 3 - 2
src/YSAI.DAQ/YSAI.Opc/ua/client/OpcUaClientData.cs

@@ -57,7 +57,7 @@ namespace YSAI.Opc.ua.client
             /// 超时时间
             /// </summary>
             [Description("超时时间")]
-            public int Timeout { get; set; } = 1000;
+            public int Timeout { get; set; } = 5000;
 
             /// <summary>
             /// 取样时间间隔
@@ -104,7 +104,8 @@ namespace YSAI.Opc.ua.client
                     ServerUrl == Obj.ServerUrl &&
                     CustomName == Obj.CustomName &&
                     SN == Obj.SN &&
-                    SamplingInterval == Obj.SamplingInterval)
+                    SamplingInterval == Obj.SamplingInterval&&
+                    TaskNumber==Obj.TaskNumber&& TaskHandleInterval==Obj.TaskHandleInterval&& PublishingInterval== Obj.PublishingInterval&& Timeout == Obj.Timeout&& Cer == Obj.Cer&& SecreKey == Obj.SecreKey)
                     {
                         return true;
                     }

+ 103 - 24
src/YSAI.DAQ/YSAI.Opc/ua/client/OpcUaClientOperate.cs

@@ -9,6 +9,7 @@ using YSAI.Core.virtualAddress;
 using YSAI.Log;
 using YSAI.Opc.ua.client.unility;
 using YSAI.Unility;
+using static System.Net.Mime.MediaTypeNames;
 using static YSAI.Opc.ua.client.OpcUaClientData;
 
 namespace YSAI.Opc.ua.client
@@ -230,6 +231,8 @@ namespace YSAI.Opc.ua.client
 
         #endregion
 
+        #region 公共函数
+
         /// <summary>
         /// 获取当前服务ID
         /// </summary>
@@ -667,7 +670,8 @@ namespace YSAI.Opc.ua.client
                 return dataValues[1].WrappedValue.TypeInfo.BuiltInType;
             }
             return BuiltInType.Null;
-        }
+        } 
+        #endregion
 
         /// <summary>
         /// 释放
@@ -759,6 +763,13 @@ namespace YSAI.Opc.ua.client
                             {
                                 return Break("AddSubscribe", false, "节点已存在标签中,无须重复订阅");
                             }
+                            //订阅节点集合
+                            List<MonitoredItem> monitoredItems = null;
+                            if (Nodes.Count > 0)
+                            {
+                                //实例化对象
+                                monitoredItems = new List<MonitoredItem>();
+                            }
                             //循环添加
                             foreach (var item in Nodes)
                             {
@@ -773,35 +784,36 @@ namespace YSAI.Opc.ua.client
                                 monitoredItem.QueueSize = uint.MaxValue;  //队列大小
                                 monitoredItem.DiscardOldest = true;  //当队列满了是否丢弃早期数据
                                 monitoredItem.Handle = this;  //本地监听地址
-                                allSubscriptions[Tag].AddItem(monitoredItem);  //添加订阅
                                 monitoredItem.Notification += delegate (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { OnMonitoredItemNotification(monitoredItem, e, item); };  //重写事件添加一个参数
+                                monitoredItems?.Add(monitoredItem);  //添加至集合
                             }
 
                             if (Nodes.Count > 0)
                             {
+                                //添加监控项
+                                allSubscriptions[Tag].AddItems(monitoredItems);  
                                 //添加订阅
                                 clientSession?.AddSubscription(allSubscriptions[Tag]);
-                                //在服务器端创建订阅
+                                //在服务器上创建订阅并添加所有监视项
                                 if (allSubscriptions[Tag].CreateAsync().Wait(basics.Timeout))
                                 {
-                                    // 在服务器端创建监控
-                                    if (allSubscriptions[Tag].ApplyChangesAsync().Wait(basics.Timeout))
+                                    // 将任何更改应用于订阅
+                                    if (!allSubscriptions[Tag].ApplyChangesAsync().Wait(basics.Timeout))
                                     {
-                                        return Break("AddSubscribe", true);
-                                    }
-                                    else
-                                    {
-                                        return Break("AddSubscribe", false, "在服务器端创建监控项超时");
+                                        allSubscriptions?.Remove(Tag, out _);
+                                        return Break("AddSubscribe", false, "将任何更改应用于订阅项超时");
                                     }
                                 }
                                 else
                                 {
-                                    return Break("AddSubscribe", false, "在服务器端创建订阅超时");
+                                    allSubscriptions?.Remove(Tag, out _);
+                                    return Break("AddSubscribe", false, "在服务器上创建订阅并添加所有监视项超时");
                                 }
                             }
                         }
                         else
                         {
+                           
                             //此标签不存在,直接创建
                             //订阅
                             Subscription subscription = new Subscription(clientSession?.DefaultSubscription);
@@ -813,6 +825,9 @@ namespace YSAI.Opc.ua.client
                             subscription.Priority = 1;  //分配给订阅的优先级
                             subscription.DisplayName = Tag;  //订阅的显示名称
                             subscription.TimestampsToReturn = TimestampsToReturn.Both;  //与通知消息一起返回的时间戳
+                            subscription.StateChanged +=delegate(Subscription subscription, SubscriptionStateChangedEventArgs e) { Subscription_StateChanged(subscription, e, Tag); };  //订阅状态
+                            //监控项集合                                                                                                                                                    //订阅节点集合
+                            List<MonitoredItem> monitoredItems = new List<MonitoredItem>();
                             //循环添加
                             foreach (var item in Nodes)
                             {
@@ -827,29 +842,29 @@ namespace YSAI.Opc.ua.client
                                 monitoredItem.QueueSize = uint.MaxValue;  //队列大小
                                 monitoredItem.DiscardOldest = true;  //当队列满了是否丢弃早期数据
                                 monitoredItem.Handle = this;  //本地监听地址
-                                subscription.AddItem(monitoredItem);   //添加订阅通知项
                                 monitoredItem.Notification += delegate (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { OnMonitoredItemNotification(monitoredItem, e, item); };  //重写事件添加一个参数
+                                monitoredItems?.Add(monitoredItem);  //添加至集合
                             }
+                            //添加监控项
+                            subscription.AddItems(monitoredItems);
                             //把此订阅添加到集合,方便后续移除订阅(当存在此键则更新值,不存在则添加)
                             allSubscriptions?.AddOrUpdate(Tag, subscription, (k, v) => subscription);
                             //添加订阅
                             clientSession?.AddSubscription(allSubscriptions[Tag]);
-                            //在服务器端创建订阅
+                            //在服务器上创建订阅并添加所有监视项
                             if (allSubscriptions[Tag].CreateAsync().Wait(basics.Timeout))
                             {
-                                // 在服务器端创建监控项
-                                if (allSubscriptions[Tag].ApplyChangesAsync().Wait(basics.Timeout))
-                                {
-                                    return Break("AddSubscribe", true);
-                                }
-                                else
+                                // 将任何更改应用于订阅项
+                                if (!allSubscriptions[Tag].ApplyChangesAsync().Wait(basics.Timeout))
                                 {
-                                    return Break("AddSubscribe", false, "在服务器端创建监控项超时");
+                                    allSubscriptions?.Remove(Tag, out _);
+                                    return Break("AddSubscribe", false, "将任何更改应用于订阅项超时");
                                 }
                             }
                             else
                             {
-                                return Break("AddSubscribe", false, "在服务器端创建订阅超时");
+                                allSubscriptions?.Remove(Tag, out _);
+                                return Break("AddSubscribe", false, "在服务器上创建订阅并添加所有监视项超时");
                             }
 
                         }
@@ -868,6 +883,55 @@ namespace YSAI.Opc.ua.client
                 return Break("AddSubscribe", false, ex.Message, Exc: ex);
             }
         }
+
+        /// <summary>
+        /// OPCUA 订阅状态集合
+        /// </summary>
+        private Dictionary<SubscriptionChangeMask, string> SubscribeStatus = new Dictionary<SubscriptionChangeMask, string>
+        {
+           { SubscriptionChangeMask.None, "订阅没有改变" },
+           { SubscriptionChangeMask.Created,"在服务器上创建订阅" },
+           { SubscriptionChangeMask.Deleted,"在服务器上删除订阅" },
+           { SubscriptionChangeMask.Modified,"在服务器上修改订阅" },
+           { SubscriptionChangeMask.ItemsAdded,"被监视的项目被添加到订阅中(但没有在服务器上创建)" },
+           { SubscriptionChangeMask.ItemsRemoved,"被监控的项目被删除到订阅(但没有在服务器上删除)" },
+           { SubscriptionChangeMask.ItemsCreated,"在服务器上创建监视项" },
+           { SubscriptionChangeMask.ItemsDeleted,"在服务器上删除监控项" },
+           { SubscriptionChangeMask.ItemsModified,"在服务器上修改监控项" },
+           { SubscriptionChangeMask.Transferred,"订阅在服务器上被转移" }
+        };
+        /// <summary>
+        /// 订阅状态枚举解析
+        /// </summary>
+        /// <param name=""></param>
+        /// <returns></returns>
+        private string EnumParse(string enumStr)
+        {
+            if (enumStr.Contains(","))
+            {
+                string[] Es = enumStr.Split(",");
+                string Info = string.Empty;
+                foreach (var item in Es)
+                {
+                    Info += $"( {SubscribeStatus[(SubscriptionChangeMask)Enum.Parse(typeof(SubscriptionChangeMask), item)]} )";
+                }
+                return Info.Replace(")(", ") (");
+            }
+            else
+            {
+                return SubscribeStatus[(SubscriptionChangeMask)Enum.Parse(typeof(SubscriptionChangeMask), enumStr)];
+            }
+        }
+
+        //订阅状态
+        private void Subscription_StateChanged(Subscription subscription, SubscriptionStateChangedEventArgs e, string tag)
+        {
+            string Message = EnumParse(e.Status.ToString());
+            string MessageJson = "{" + $"\"Uri\":\"{basics.ServerUrl}\",\"CustomName\":\"{basics.CustomName}\",\"StatusCode\":\"{e.Status}\",\"StatusMessage\":\"{Message}\"" + "}";
+            LogHelper.Info(MessageJson.JsonFormatting(), ClassName + ".log");
+            OnEventHandler(this, new EventResult(true, Message, MessageJson));
+        }
+
         /// <summary>
         ///  移除订阅 通过节点来移除订阅,移除指定节点的订阅,没有也返回成功
         /// </summary>
@@ -1051,6 +1115,7 @@ namespace YSAI.Opc.ua.client
                             catch (Exception ex)
                             {
                                 OnEventHandler(this, new EventResult(false, $"[ {Steps.订阅通知} ]异常:{ex.Message}"));
+
                             }
                         }
                     }
@@ -1207,6 +1272,17 @@ namespace YSAI.Opc.ua.client
                     return Break("Off", false, "未连接");
                 }
 
+                if (allSubscriptions != null)
+                {
+                    foreach (var item in allSubscriptions)
+                    {
+                        item.Value.Session.Close();
+                        item.Value.Session.Dispose();
+                        item.Value.Dispose();
+                    }
+                    allSubscriptions.Clear();
+                }
+
                 if (sessionReconnectHandler != null)
                 {
                     sessionReconnectHandler.Dispose();
@@ -1215,7 +1291,7 @@ namespace YSAI.Opc.ua.client
 
                 if (clientSession != null)
                 {
-                    if (StatusCode.IsGood(clientSession.Close(true)))
+                    if (StatusCode.IsGood(clientSession.Close()))
                     {
                         clientSession.Dispose();
                         clientSession = null;
@@ -1232,11 +1308,12 @@ namespace YSAI.Opc.ua.client
                     foreach (var item in TaskArray)
                     {
                         item.Key.Cancel();
+                    }
+                    foreach (var item in TaskArray)
+                    {
                         item.Value.Wait();
                         item.Value.Dispose();
                     }
-                    TaskArray.Clear();
-                    TaskArray = null;
                 }
                 //队列数据清空
                 if (DataQueue != null)
@@ -1245,6 +1322,8 @@ namespace YSAI.Opc.ua.client
                     DataQueue = null;
                 }
 
+              
+
                 IsConnected = false;
 
                 return Break("Off", true);

+ 32 - 33
src/YSAI.DAQ/YSAI.Tool.Core/opc/ua/client/OpcUaClientToolController.cs

@@ -41,7 +41,6 @@ namespace YSAI.Tool.Core.opc.ua.client
         private void WindowHelper_OnSkinSwitchEvent(object? sender, EventArgs e)
         {
             Node?.Clear();
-            GetAllNode();
         }
 
         /// <summary>
@@ -553,7 +552,6 @@ namespace YSAI.Tool.Core.opc.ua.client
             //事件
             opcUaOperate.OnEvent -= OpcUaOperate_OnEvent;
             opcUaOperate.OnEvent += OpcUaOperate_OnEvent;
-
             //打开连接
             reverseBack = opcUaOperate.On();
             //信息输出
@@ -759,7 +757,6 @@ namespace YSAI.Tool.Core.opc.ua.client
         /// </summary>
         private void OpcUaOperate_OnEvent(object? sender, EventResult e)
         {
-            MessageOut(e.Message);
             if (e.State)
             {
                 if (e.RData != null)
@@ -769,11 +766,15 @@ namespace YSAI.Tool.Core.opc.ua.client
                     {
                         foreach (var itemc in pairs)
                         {
-                            MessageOut($"{e.Message}  -  Key:{itemc.Key}  -  Value:{itemc.Value}");
+                            MessageOut($"{e.Message}  -  Key:{itemc.Key}  -  Value:{itemc.Value.Value}");
                         }
                     }
                 }
             }
+            else
+            {
+                MessageOut(e.Message);
+            }
         }
 
 
@@ -805,44 +806,42 @@ namespace YSAI.Tool.Core.opc.ua.client
         /// <summary>
         /// 获取所有节点
         /// </summary>
-        private async Task GetAllNode()
+        private Task GetAllNode()
         {
-            if (opcUaOperate == null) return;
-            //获取所有节点
-            ReferenceDescriptionCollection references = await opcUaOperate.GetAllNode(opcUaOperate.GetNodeID());
-            //获取成功
-            if (references != null)
+            return Task.Run(() =>
             {
-                for (int ii = 0; ii < references.Count; ii++)
+                //获取所有节点
+                ReferenceDescriptionCollection references = opcUaOperate.GetAllNode(opcUaOperate.GetNodeID()).Result;
+                //获取成功
+                if (references != null)
                 {
-                    //赋值
-                    ReferenceDescription target = references[ii];
-                    //得到这个节点下有多少个节点
-                    ReferenceDescriptionCollection descriptions = await opcUaOperate.GetAllNode((NodeId)target.NodeId);
-                    if (descriptions != null)
+                    for (int ii = 0; ii < references.Count; ii++)
                     {
-                        int references1 = descriptions.Count;
-
-                        DrawingImage drawingImage = null;
-                        string imagename = opcUaOperate.GetNodeIconType(target, opcUaOperate.GetNodeID());
-                        System.Windows.Application.Current?.Dispatcher.Invoke(new Action(async () => drawingImage = (DrawingImage)System.Windows.Application.Current.FindResource(imagename)));
                         //赋值
-                        NodeStructuralBody structuralBody = new NodeStructuralBody() { Name = target.ToString(), Icon = drawingImage, NodeID = target, Count = references1 > 0 ? $"( {references1} )" : string.Empty };
-
-                        //看此节点下是否存在节点
-                        if (references1 > 0)
+                        ReferenceDescription target = references[ii];
+                        //得到这个节点下有多少个节点
+                        ReferenceDescriptionCollection descriptions = opcUaOperate.GetAllNode((NodeId)target.NodeId).Result;
+                        if (descriptions != null)
                         {
-                            GetBranchNode((NodeId)target.NodeId, structuralBody);
-                        }
+                            int references1 = descriptions.Count;
 
-                        System.Windows.Application.Current?.Dispatcher.Invoke(new Action(() => Node.Add(structuralBody)));
-                    }
-                    else
-                    {
-                        Console.WriteLine();
+                            DrawingImage drawingImage = null;
+                            string imagename = opcUaOperate.GetNodeIconType(target, opcUaOperate.GetNodeID());
+                            System.Windows.Application.Current?.Dispatcher.Invoke(new Action(async () => drawingImage = (DrawingImage)System.Windows.Application.Current.FindResource(imagename)));
+                            //赋值
+                            NodeStructuralBody structuralBody = new NodeStructuralBody() { Name = target.ToString(), Icon = drawingImage, NodeID = target, Count = references1 > 0 ? $"( {references1} )" : string.Empty };
+
+                            //看此节点下是否存在节点
+                            if (references1 > 0)
+                            {
+                                GetBranchNode((NodeId)target.NodeId, structuralBody);
+                            }
+
+                            System.Windows.Application.Current?.Dispatcher.Invoke(new Action(() => Node.Add(structuralBody)));
+                        }
                     }
                 }
-            }
+            });
         }
         /// <summary>
         /// 获取分支