Selaa lähdekoodia

1. OpcUa新增根据设置值自动分配订阅组
2. OpcDa新增根据设置值自动分配订阅组
3. 细节优化

Shun 2 vuotta sitten
vanhempi
commit
21ff76fe99
30 muutettua tiedostoa jossa 402 lisäystä ja 173 poistoa
  1. 6 1
      README.md
  2. 1 1
      src/YSAI.DAQ/YSAI.Beckhoff/BeckhoffData.cs
  3. 1 1
      src/YSAI.DAQ/YSAI.Can/CanData.cs
  4. 1 1
      src/YSAI.DAQ/YSAI.Core/communication/BasicsData.cs
  5. 1 1
      src/YSAI.DAQ/YSAI.Core/communication/net/http/HttpData.cs
  6. 1 1
      src/YSAI.DAQ/YSAI.Core/communication/net/tcp/service/TcpServiceData.cs
  7. 1 1
      src/YSAI.DAQ/YSAI.Core/communication/net/ws/service/WsServiceData.cs
  8. 1 1
      src/YSAI.DAQ/YSAI.Core/data/Address.cs
  9. 1 1
      src/YSAI.DAQ/YSAI.Core/subscribe/SubscribeData.cs
  10. 1 1
      src/YSAI.DAQ/YSAI.DB/DBData.cs
  11. 1 1
      src/YSAI.DAQ/YSAI.Kafka/KafkaData.cs
  12. 2 2
      src/YSAI.DAQ/YSAI.Kafka/KafkaOperate.cs
  13. 2 1
      src/YSAI.DAQ/YSAI.Log/LogBase.cs
  14. 66 0
      src/YSAI.DAQ/YSAI.Log/LogHelper.cs
  15. 1 1
      src/YSAI.DAQ/YSAI.Modbus/ModbusData.cs
  16. 1 1
      src/YSAI.DAQ/YSAI.Mqtt/client/MqttClientData.cs
  17. 1 1
      src/YSAI.DAQ/YSAI.Mqtt/client/MqttClientOperate.cs
  18. 1 1
      src/YSAI.DAQ/YSAI.Netty/client/NettyClientData.cs
  19. 1 1
      src/YSAI.DAQ/YSAI.Netty/service/NettyServiceData.cs
  20. 10 1
      src/YSAI.DAQ/YSAI.Opc/da/client/OpcDaClientData.cs
  21. 24 3
      src/YSAI.DAQ/YSAI.Opc/da/client/OpcDaClientOperate.cs
  22. 1 1
      src/YSAI.DAQ/YSAI.Opc/da/http/OpcDaHttpData.cs
  23. 32 2
      src/YSAI.DAQ/YSAI.Opc/ua/client/OpcUaClientData.cs
  24. 59 16
      src/YSAI.DAQ/YSAI.Opc/ua/client/OpcUaClientOperate.cs
  25. 5 32
      src/YSAI.DAQ/YSAI.Opc/ua/client/OpcUaClientReadController.cs
  26. 78 0
      src/YSAI.DAQ/YSAI.Opc/unility/OpcHelper.cs
  27. 1 1
      src/YSAI.DAQ/YSAI.RabbitMQ/RabbitMQData.cs
  28. 1 1
      src/YSAI.DAQ/YSAI.S7/S7Data.cs
  29. 99 96
      src/YSAI.DAQ/YSAI.Test.All/Program.cs
  30. 1 1
      src/YSAI.DAQ/YSAI.Test/UnitTest1.cs

+ 6 - 1
README.md

@@ -346,4 +346,9 @@ while(true)
 2. 移除Modbus 非标服务端
 3. 底层通信新增字节数从末尾从前移除0x00
 4. 多余代码清理
-5. 版本更新
+5. 版本更新
+
+#### 2023-10-31
+1. OpcUa新增根据设置值自动分配订阅组
+2. OpcDa新增根据设置值自动分配订阅组
+3. 细节优化

+ 1 - 1
src/YSAI.DAQ/YSAI.Beckhoff/BeckhoffData.cs

@@ -17,7 +17,7 @@ namespace YSAI.Beckhoff
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 设备的网络ID

+ 1 - 1
src/YSAI.DAQ/YSAI.Can/CanData.cs

@@ -17,7 +17,7 @@ namespace YSAI.Can
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 波特率

+ 1 - 1
src/YSAI.DAQ/YSAI.Core/communication/BasicsData.cs

@@ -12,7 +12,7 @@ namespace YSAI.Core.communication
         /// 唯一标识符
         /// </summary>
         [Description("唯一标识符")]
-        public string? SN { get; set; } = Guid.NewGuid().ToNString();
+        public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
         /// <summary>
         /// 发送等待结果(不启用监控)

+ 1 - 1
src/YSAI.DAQ/YSAI.Core/communication/net/http/HttpData.cs

@@ -17,7 +17,7 @@ namespace YSAI.Core.communication.net.http
             /// <summary>
             /// 唯一标识符
             /// </summary>
-            public string SN { get; set; } = Guid.NewGuid().ToNString();
+            public string SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 最大连接数

+ 1 - 1
src/YSAI.DAQ/YSAI.Core/communication/net/tcp/service/TcpServiceData.cs

@@ -12,7 +12,7 @@ namespace YSAI.Core.communication.net.tcp.service
             /// <summary>
             /// 唯一标识符
             /// </summary>
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// IP

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

@@ -12,7 +12,7 @@ namespace YSAI.Core.communication.net.ws.service
             /// <summary>
             /// 唯一标识符
             /// </summary>
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 支持监控多个,注意必须带 / 结尾

+ 1 - 1
src/YSAI.DAQ/YSAI.Core/data/Address.cs

@@ -10,7 +10,7 @@ namespace YSAI.Core.data
         /// <summary>
         /// 可以理解成唯一标识符(可以存机台号、组名、车间、厂)
         /// </summary>
-        public string SN { get; set; } = Guid.NewGuid().ToNString();
+        public string SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
         /// <summary>
         /// 所有地址 也可以是一个地址  此数组里面的地址应是唯一的

+ 1 - 1
src/YSAI.DAQ/YSAI.Core/subscribe/SubscribeData.cs

@@ -17,7 +17,7 @@ namespace YSAI.Core.subscription
             /// <summary>
             /// 唯一标识符
             /// </summary>
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 点位 可为空,可后期赋值

+ 1 - 1
src/YSAI.DAQ/YSAI.DB/DBData.cs

@@ -17,7 +17,7 @@ namespace YSAI.DB
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 数据库连接字符串

+ 1 - 1
src/YSAI.DAQ/YSAI.Kafka/KafkaData.cs

@@ -23,7 +23,7 @@ namespace YSAI.Kafka
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 服务器地址

+ 2 - 2
src/YSAI.DAQ/YSAI.Kafka/KafkaOperate.cs

@@ -357,7 +357,7 @@ namespace YSAI.Kafka
                     //创建配置
                     producerConfig = new ProducerConfig()
                     {
-                        ClientId = Guid.NewGuid().ToString(),
+                        ClientId = Guid.NewGuid().ToUpperNString(),
                         BootstrapServers = basics.BootstrapServers,
                         EnableIdempotence = true,
                         SecurityProtocol = basics.SecurityProtocol
@@ -370,7 +370,7 @@ namespace YSAI.Kafka
                     consumerConfig = new ConsumerConfig()
                     {
                         BootstrapServers = basics.BootstrapServers,
-                        GroupId = Guid.NewGuid().ToString(),
+                        GroupId = Guid.NewGuid().ToUpperNString(),
                         AutoOffsetReset = basics.AutoOffsetReset,
                         SecurityProtocol = basics.SecurityProtocol
                     };

+ 2 - 1
src/YSAI.DAQ/YSAI.Log/LogBase.cs

@@ -232,7 +232,8 @@ namespace YSAI.Log
             }
             catch (Exception ex)
             {
-                Out("历史日志删除异常:" + ex.Message, Serilog.Events.LogEventLevel.Error, exception: ex);
+                //删除历史日志异常
+                Out("Deleting historical logs is abnormal :" + ex.Message, Serilog.Events.LogEventLevel.Error, exception: ex);
             }
         }
     }

+ 66 - 0
src/YSAI.DAQ/YSAI.Log/LogHelper.cs

@@ -18,6 +18,17 @@
             logBase.Out(info, Serilog.Events.LogEventLevel.Verbose, filename, exception);
         }
 
+        /// <summary>
+        /// 异步详细信息
+        /// </summary>
+        /// <param name="info">信息</param>
+        /// <param name="filename">文件名</param>
+        /// <param name="exception">异常对象</param>
+        public static Task VerboseAsync(string info, string? filename = null, Exception? exception = null, CancellationToken? token = null)
+        {
+            return Task.Run(() => Verbose(info, filename, exception), token ?? CancellationToken.None);
+        }
+
         /// <summary>
         /// 调试
         /// </summary>
@@ -29,6 +40,17 @@
             logBase.Out(info, Serilog.Events.LogEventLevel.Debug, filename, exception);
         }
 
+        /// <summary>
+        /// 异步调试
+        /// </summary>
+        /// <param name="info">信息</param>
+        /// <param name="filename">文件名</param>
+        /// <param name="exception">异常对象</param>
+        public static Task DebugAsync(string info, string? filename = null, Exception? exception = null, CancellationToken? token = null)
+        {
+            return Task.Run(() => Debug(info, filename, exception), token ?? CancellationToken.None);
+        }
+
         /// <summary>
         /// 信息
         /// </summary>
@@ -40,6 +62,17 @@
             logBase.Out(info, Serilog.Events.LogEventLevel.Information, filename, exception);
         }
 
+        /// <summary>
+        /// 异步信息
+        /// </summary>
+        /// <param name="info">信息</param>
+        /// <param name="filename">文件名</param>
+        /// <param name="exception">异常对象</param>
+        public static Task InfoAsync(string info, string? filename = null, Exception? exception = null, CancellationToken? token = null)
+        {
+            return Task.Run(() => Info(info, filename, exception), token ?? CancellationToken.None);
+        }
+
         /// <summary>
         /// 警告
         /// </summary>
@@ -51,6 +84,17 @@
             logBase.Out(info, Serilog.Events.LogEventLevel.Warning, filename, exception);
         }
 
+        /// <summary>
+        /// 异步警告
+        /// </summary>
+        /// <param name="info">信息</param>
+        /// <param name="filename">文件名</param>
+        /// <param name="exception">异常对象</param>
+        public static Task WarningAsync(string info, string? filename = null, Exception? exception = null, CancellationToken? token = null)
+        {
+            return Task.Run(() => Warning(info, filename, exception), token ?? CancellationToken.None);
+        }
+
         /// <summary>
         /// 异常或错误
         /// </summary>
@@ -62,6 +106,17 @@
             logBase.Out(info, Serilog.Events.LogEventLevel.Error, filename, exception);
         }
 
+        /// <summary>
+        /// 异步异常或错误
+        /// </summary>
+        /// <param name="info">信息</param>
+        /// <param name="filename">文件名</param>
+        /// <param name="exception">异常对象</param>
+        public static Task ErrorAsync(string info, string? filename = null, Exception? exception = null, CancellationToken? token = null)
+        {
+            return Task.Run(() => Error(info, filename, exception), token ?? CancellationToken.None);
+        }
+
         /// <summary>
         /// 致命错误或异常
         /// </summary>
@@ -72,5 +127,16 @@
         {
             logBase.Out(info, Serilog.Events.LogEventLevel.Fatal, filename, exception);
         }
+
+        /// <summary>
+        /// 异步致命错误或异常
+        /// </summary>
+        /// <param name="info">信息</param>
+        /// <param name="filename">文件名</param>
+        /// <param name="exception">异常对象</param>
+        public static Task FatalAsync(string info, string? filename = null, Exception? exception = null, CancellationToken? token = null)
+        {
+            return Task.Run(() => Fatal(info, filename, exception), token ?? CancellationToken.None);
+        }
     }
 }

+ 1 - 1
src/YSAI.DAQ/YSAI.Modbus/ModbusData.cs

@@ -78,7 +78,7 @@ namespace YSAI.Modbus
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 寄存器地址

+ 1 - 1
src/YSAI.DAQ/YSAI.Mqtt/client/MqttClientData.cs

@@ -20,7 +20,7 @@ namespace YSAI.Mqtt.client
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// IP地址

+ 1 - 1
src/YSAI.DAQ/YSAI.Mqtt/client/MqttClientOperate.cs

@@ -295,7 +295,7 @@ namespace YSAI.Mqtt.client
                 //服务质量
                 mqttClientOptionsBuilder.WithWillQualityOfServiceLevel(mqttClientData.QualityOfServiceLevel);
                 //传递ClientID
-                mqttClientOptionsBuilder.WithClientId(string.IsNullOrEmpty(mqttClientData.ClientID) ? Guid.NewGuid().ToString() : mqttClientData.ClientID);
+                mqttClientOptionsBuilder.WithClientId(string.IsNullOrEmpty(mqttClientData.ClientID) ? Guid.NewGuid().ToUpperNString() : mqttClientData.ClientID);
                 //MQTT服务的地址
                 mqttClientOptionsBuilder.WithTcpServer(mqttClientData.Ip, mqttClientData.Port);
                 //传递账号密码

+ 1 - 1
src/YSAI.DAQ/YSAI.Netty/client/NettyClientData.cs

@@ -14,7 +14,7 @@ namespace YSAI.Netty.client
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 主机

+ 1 - 1
src/YSAI.DAQ/YSAI.Netty/service/NettyServiceData.cs

@@ -14,7 +14,7 @@ namespace YSAI.Netty.service
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 端口

+ 10 - 1
src/YSAI.DAQ/YSAI.Opc/da/client/OpcDaClientData.cs

@@ -19,7 +19,7 @@ namespace YSAI.Opc.da.client
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 服务名
@@ -40,6 +40,15 @@ namespace YSAI.Opc.da.client
             [Description("更新频率")]
             public int UpdateRate { get; set; } = 100;
 
+            /// <summary>
+            /// 订阅单组最大数,
+            /// -1:不执行分组,数据少时使用,
+            /// 100:每组订阅100个数据点,自动根据传入的值分组,你有1000个点位,每组最大数是100,这时就会自动分配10个组来执行订阅
+            /// 数据是根据实际情况来定
+            /// </summary>
+            [Description("订阅单组最大数")]
+            public int SubscribeSingleGroupMaxCount = -1;
+
             /// <summary>
             /// 任务数量
             /// </summary>

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

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
 using YSAI.Core.data;
 using YSAI.Core.@interface;
 using YSAI.Core.virtualAddress;
+using YSAI.Opc.unility;
 using YSAI.Unility;
 
 namespace YSAI.Opc.da.client
@@ -237,7 +238,7 @@ namespace YSAI.Opc.da.client
                         subscriptionState.Deadband = 0;
                         subscriptionState.Active = true;
                         subscriptionState.UpdateRate = basics.UpdateRate;
-                        subscriptionState.ClientHandle = Guid.NewGuid().ToString();
+                        subscriptionState.ClientHandle = this;
                         subscriptionState.Name = GroupName;
                         ISubscription subscription = opcDaClient.CreateSubscription(subscriptionState);
                         if (IsSubscribed)
@@ -948,8 +949,28 @@ namespace YSAI.Opc.da.client
             Depart("Subscribe");
             try
             {
-                OperateResult operateResult = AddNode(address, string.Empty, true);
-                return Break("Subscribe", operateResult.State, operateResult.Message);
+                if (basics.SubscribeSingleGroupMaxCount != -1)
+                {
+                    List<string> FailMessage = new List<string>();
+                    foreach (var item in OpcHelper.SubscribeAddressHandle(address, basics.SubscribeSingleGroupMaxCount))
+                    {
+                        OperateResult operateResult = AddNode(item.Value, item.Key, true);
+                        if (!operateResult.State)
+                        {
+                            FailMessage.Add(operateResult.Message);
+                        }
+                    }
+                    if (FailMessage.Count > 0)
+                    {
+                        return Break("Subscribe", false, $"存在{FailMessage.Count}异常", FailMessage);
+                    }
+                    return Break("Subscribe", true);
+                }
+                else
+                {
+                    OperateResult operateResult = AddNode(address, string.Empty, true);
+                    return Break("Subscribe", operateResult.State, operateResult.Message);
+                }
             }
             catch (Exception ex)
             {

+ 1 - 1
src/YSAI.DAQ/YSAI.Opc/da/http/OpcDaHttpData.cs

@@ -20,7 +20,7 @@ namespace YSAI.Opc.da.http
             /// 唯一标识
             /// </summary>
             [Description("唯一标识")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 服务IP

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

@@ -17,7 +17,7 @@ namespace YSAI.Opc.ua.client
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 用户名
@@ -53,7 +53,7 @@ namespace YSAI.Opc.ua.client
             /// 自定义名称(唯一)
             /// </summary>
             [Description("自定义名称(唯一)")]
-            public string? CustomName { get; set; } = Guid.NewGuid().ToNString().ToUpper();
+            public string? CustomName { get; set; } = Guid.NewGuid().ToLowerNString();
 
             /// <summary>
             /// 超时时间
@@ -73,6 +73,36 @@ namespace YSAI.Opc.ua.client
             [Description("发布时间间隔")]
             public int PublishingInterval { get; set; } = 1000;
 
+            /// <summary>
+            /// 保活计数,
+            /// 如果到发布周期但没有新数据,服务端会跳过但保活次数定义了最多可以跳过多少次,之后哪怕是空消息也要发给客户端,告诉客户的订阅仍然有效,但没数据
+            /// </summary>
+            [Description("保活计数")]
+            public int KeepAliveCount { get; set; } = byte.MaxValue * 1024;
+
+            /// <summary>
+            /// 寿命计数,
+            /// 客户端有责任向服务端发送订阅请求,然后服务端才能发送数据变更通知。服务端通过判断是否有新的请求来判断客户端是否活着。LifeTimeCount定义新请求之前最多允许多少个发布周期,超过后服务端判定客户端失活,清除订阅,
+            /// 必须是【保活计数】的三倍以“上”
+            /// </summary>
+            [Description("寿命计数")]
+            public int LifetimeCount { get; set; } = byte.MaxValue * 1024 * 3;
+
+            /// <summary>
+            /// 队列大小
+            /// </summary>
+            [Description("队列大小")]
+            public int QueueSize { get; set; } = 1024 * 64;
+
+            /// <summary>
+            /// 订阅单组最大数,
+            /// -1:不执行分组,数据少时使用,
+            /// 100:每组订阅100个数据点,自动根据传入的值分组,你有1000个点位,每组最大数是100,这时就会自动分配10个组来执行订阅
+            /// 数据是根据实际情况来定
+            /// </summary>
+            [Description("订阅单组最大数")]
+            public int SubscribeSingleGroupMaxCount = -1;
+
             /// <summary>
             /// 任务数量
             /// </summary>

+ 59 - 16
src/YSAI.DAQ/YSAI.Opc/ua/client/OpcUaClientOperate.cs

@@ -2,6 +2,7 @@
 using Opc.Ua.Client;
 using Opc.Ua.Configuration;
 using System.Collections.Concurrent;
+using System.Dynamic;
 using System.Security.Cryptography.X509Certificates;
 using YSAI.Core.data;
 using YSAI.Core.@enum;
@@ -9,6 +10,7 @@ using YSAI.Core.@interface;
 using YSAI.Core.virtualAddress;
 using YSAI.Log;
 using YSAI.Opc.ua.client.unility;
+using YSAI.Opc.unility;
 using YSAI.Unility;
 using static YSAI.Opc.ua.client.OpcUaClientData;
 
@@ -70,6 +72,8 @@ namespace YSAI.Opc.ua.client
         {
             //设置参数
             this.basics = basics;
+
+            SubscribeStateCheck();
         }
 
         #region 公共属性
@@ -83,11 +87,6 @@ namespace YSAI.Opc.ua.client
 
         #region 私有属性
 
-        /// <summary>
-        /// 默认标签
-        /// </summary>
-        private string DefaultTag = "YSAI";
-
         /// <summary>
         /// 基础数据
         /// </summary>
@@ -461,6 +460,41 @@ namespace YSAI.Opc.ua.client
             return readResponse.Results[0];
         }
 
+        /// <summary>
+        /// 订阅状态监测
+        /// </summary>
+        /// <param name="token">生命周期</param>
+        /// <returns></returns>
+        private Task SubscribeStateCheck(CancellationToken? token = null)
+        {
+            return Task.Run(() =>
+            {
+                while (true)
+                {
+                    //订阅数据为空则直接跳过
+                    if (allSubscriptions == null)
+                    {
+                        Thread.Sleep(30000);
+                        continue;
+                    }
+
+                    //需要重新订阅的地址
+                    foreach (var item in allSubscriptions)
+                    {
+                        foreach (var monitoredItem in item.Value.MonitoredItems)
+                        {
+                            if (!monitoredItem.Status.Created)
+                            {
+                                LogHelper.Error($"{monitoredItem.DisplayName} 地址未能在服务器上创建订阅,请检查地址是否正常", $"{TAG}/SubscribeCheckStatus.log");
+                            }
+                        }
+                    }
+
+                    Thread.Sleep(30000);
+                }
+            }, token ?? CancellationToken.None);
+        }
+
         #endregion 私有函数
 
         #region 公共函数
@@ -1088,7 +1122,8 @@ namespace YSAI.Opc.ua.client
                             monitoredItem.AttributeId = Attributes.Value;  //要监视的属性
                             monitoredItem.SamplingInterval = basics.SamplingInterval;  //多少秒采集一次数据
                             monitoredItem.DiscardOldest = true;  //当队列满时清空较早的队列数据
-                            monitoredItem.QueueSize = 1000;  //队列大小
+                            monitoredItem.QueueSize = (uint)basics.QueueSize;  //队列大小
+                            monitoredItem.DisplayName = item.AddressName;  //被监视项目的显示名称
                             monitoredItem.Notification += delegate (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { OnMonitoredItemNotification(monitoredItem, e, item); };  //重写事件添加一个参数
                             monitoredItems?.Add(monitoredItem);  //添加至集合
                         }
@@ -1119,15 +1154,14 @@ namespace YSAI.Opc.ua.client
                         subscription.Handle = this;  //分配给订阅的本地句柄
                         subscription.PublishingEnabled = true;//是否启用发布
                         subscription.PublishingInterval = basics.PublishingInterval; //出版间隔
-                        subscription.KeepAliveCount = 10;  //保活计数
-                        subscription.LifetimeCount = 100;  //寿命计数
+                        subscription.KeepAliveCount = (uint)basics.KeepAliveCount;  //保活计数
+                        subscription.LifetimeCount = (uint)basics.LifetimeCount;  //寿命计数
                         subscription.MaxNotificationsPerPublish = (uint)basics.PublishingInterval;  //每个发布请求的最大通知数
                         subscription.DisplayName = Tag;  //订阅的显示名称
                         subscription.TimestampsToReturn = TimestampsToReturn.Both;  //与通知消息一起返回的时间戳
                         subscription.StateChanged += delegate (Subscription subscription, SubscriptionStateChangedEventArgs e) { Subscription_StateChanged(subscription, e, Tag); };  //订阅状态
                         subscription.PublishStatusChanged += Subscription_PublishStatusChanged;   //指示订阅的发布状态已停止或已恢复
-                        //监控项集合
-                        List<MonitoredItem> monitoredItems = new List<MonitoredItem>();
+                        List<MonitoredItem> monitoredItems = new List<MonitoredItem>();//监控项集合
 
                         //循环添加
                         foreach (var item in Nodes)
@@ -1139,7 +1173,8 @@ namespace YSAI.Opc.ua.client
                             monitoredItem.AttributeId = Attributes.Value;  //要监视的属性
                             monitoredItem.SamplingInterval = basics.SamplingInterval;  //多少秒采集一次数据
                             monitoredItem.DiscardOldest = true;  //当队列满时清空较早的队列数据
-                            monitoredItem.QueueSize = 1000;  //队列大小
+                            monitoredItem.QueueSize = (uint)basics.QueueSize;  //队列大小
+                            monitoredItem.DisplayName = item.AddressName;  //被监视项目的显示名称
                             monitoredItem.Notification += delegate (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { OnMonitoredItemNotification(monitoredItem, e, item); };  //重写事件添加一个参数
                             monitoredItems?.Add(monitoredItem);  //添加至集合
                         }
@@ -1230,7 +1265,7 @@ namespace YSAI.Opc.ua.client
                                     }
                                 }
                                 //在数组中移除此项
-                                allSubscriptions.Remove(alls.Key, out Subscription? subscription);
+                                allSubscriptions.Remove(alls.Key, out _);
                             }
                             //移除后立马跳到上一级,防止性能消耗
                             break;
@@ -1368,7 +1403,7 @@ namespace YSAI.Opc.ua.client
                                 ConcurrentDictionary<string, AddressValue> param = new ConcurrentDictionary<string, AddressValue>();
                                 //处理数据
                                 AddressValue addressValue = YSAI.Core.handler.AddressHandler.ExecuteDispose(queueData.addressDetails, value);  //数据
-                                //添加或更新
+                                                                                                                                               //添加或更新
                                 param.AddOrUpdate(queueData.addressDetails.AddressName, addressValue, (k, v) => addressValue);
 
                                 //响应
@@ -1489,7 +1524,7 @@ namespace YSAI.Opc.ua.client
                 endpoint.Update(endpointDescription);
 
                 //通过调用CreateSession服务创建与服务器的新通信会话
-                clientSession = Session.Create(AC, endpoint, true, false, $"{AC.ApplicationName}.{Guid.NewGuid().ToNString()}", (uint)basics.Timeout, UserIdentity, new string[] { }).WaitAsync(new TimeSpan(0, 0, 0, 0, basics.Timeout)).Result;
+                clientSession = Session.Create(AC, endpoint, true, false, $"{AC.ApplicationName}.{Guid.NewGuid().ToUpperNString()}", (uint)basics.Timeout, UserIdentity, new string[] { }).WaitAsync(new TimeSpan(0, 0, 0, 0, basics.Timeout)).Result;
 
                 //当会话关闭,则关闭订阅
                 clientSession.DeleteSubscriptionsOnClose = true;
@@ -1769,8 +1804,16 @@ namespace YSAI.Opc.ua.client
             Depart("Subscribe");
             try
             {
-                ConcurrentDictionary<string, Address> PARAM = new ConcurrentDictionary<string, Address>();
-                PARAM.TryAdd(DefaultTag, address);
+                ConcurrentDictionary<string, Address> PARAM = null;
+                if (basics.SubscribeSingleGroupMaxCount != -1)
+                {
+                    PARAM = OpcHelper.SubscribeAddressHandle(address, basics.SubscribeSingleGroupMaxCount);
+                }
+                else
+                {
+                    PARAM = new ConcurrentDictionary<string, Address>();
+                    PARAM.TryAdd(Guid.NewGuid().ToUpperNString(), address);
+                }
                 OperateResult operateResult = AddSubscribe(PARAM);
                 return Break("Subscribe", operateResult.State, operateResult.Message);
             }

+ 5 - 32
src/YSAI.DAQ/YSAI.Opc/ua/client/OpcUaClientReadController.cs

@@ -1,5 +1,6 @@
 using System.Collections.Concurrent;
 using YSAI.Core.data;
+using YSAI.Opc.unility;
 using YSAI.Unility;
 using static YSAI.Opc.ua.client.OpcUaClientData;
 
@@ -35,7 +36,7 @@ namespace YSAI.Opc.ua.client
         /// </summary>
         /// <param name="Nodes">节点集合</param>
         /// <param name="SubscriptionNodes">订阅节点集合</param>
-        /// <param name="ClientBasics">OPCUA服务器基本参数</param>
+        /// <param name="ServerBasics">OPCUA服务器基本参数</param>
         /// <param name="ServerMaxSession">UPCUA服务器支持最大会话数量</param>
         public OpcUaClientReadController(Basics ServerBasics, int ServerMaxSession, Address Nodes, Address SubscriptionNodes = null)
         {
@@ -57,7 +58,7 @@ namespace YSAI.Opc.ua.client
                 //得到一个会话处理多少个节点
                 int NodesIndex = Math.Ceiling((float)Nodes.AddressArray.Count / ServerMaxSession).ToInt();
                 //节点数据分隔
-                Dictionary<int, Address> NodesSplit = SplitList(Nodes, NodesIndex);
+                Dictionary<int, Address> NodesSplit = OpcHelper.SplitList(Nodes, NodesIndex);
 
                 //循环启动OPCUA客户端
                 for (int i = 0; i < NodesSplit.Count; i++)
@@ -74,12 +75,12 @@ namespace YSAI.Opc.ua.client
                 //得到一个会话处理多少个节点
                 int NodesIndex = Math.Ceiling((float)Nodes.AddressArray.Count / ServerMaxSession).ToInt();
                 //节点数据分隔
-                Dictionary<int, Address> NodesSplit = SplitList(Nodes, NodesIndex);
+                Dictionary<int, Address> NodesSplit = OpcHelper.SplitList(Nodes, NodesIndex);
 
                 //得到一个会话处理多少个节点
                 int SubscriptionNodesIndex = Math.Ceiling((float)SubscriptionNodes.AddressArray.Count / ServerMaxSession).ToInt();
                 //订阅节点分割
-                Dictionary<int, Address> SubscriptionNodesSplit = SplitList(SubscriptionNodes, SubscriptionNodesIndex);
+                Dictionary<int, Address> SubscriptionNodesSplit = OpcHelper.SplitList(SubscriptionNodes, SubscriptionNodesIndex);
 
                 //循环启动OPCUA客户端
                 for (int i = 0; i < (NodesSplit.Count + SubscriptionNodesSplit.Count) / 2; i++)
@@ -93,33 +94,5 @@ namespace YSAI.Opc.ua.client
                 return Data;
             }
         }
-
-        /// <summary>
-        /// List<string> 分隔
-        /// </summary>
-        /// <param name="list">要拆分的集合</param>
-        /// <param name="num">拆分数</param>
-        /// <returns></returns>
-        public Dictionary<int, Address> SplitList(Address list, int num)
-        {
-            int listSize = list.AddressArray.Count; // 长度
-
-            Dictionary<int, Address> contractItemDic = new Dictionary<int, Address>(); //用户封装返回的多个list
-            Address contractItemList = new Address();
-            ; //用于承装每个等分list
-            int page = 0;
-            for (int i = 0; i < listSize; i++)
-            {
-                //for循环依次放入每个list中
-                contractItemList.AddressArray.Add(list.AddressArray[i]); //先将对象放入list,以防止最后一个没有放入
-                if (((i + 1) % num == 0) || (i + 1 == listSize))
-                {
-                    //如果l+1 除以 要分的份数 为整除,或者是最后一份,为结束循环.那就算作一份list,
-                    contractItemDic.Add(page++, contractItemList); //将这一份放入Map中.
-                    contractItemList = new Address(); //新建一个list,用于继续存储对象
-                }
-            }
-            return contractItemDic;
-        }
     }
 }

+ 78 - 0
src/YSAI.DAQ/YSAI.Opc/unility/OpcHelper.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using YSAI.Core.data;
+using YSAI.Unility;
+
+namespace YSAI.Opc.unility
+{
+    /// <summary>
+    /// OPCUA 帮助类
+    /// </summary>
+    public class OpcHelper
+    {
+        /// <summary>
+        /// 地址处理
+        /// </summary>
+        /// <param name="address">地址数据</param>
+        /// <param name="GroupMaxNumber">每组最大订阅数</param>
+        /// <returns></returns>
+        public static ConcurrentDictionary<string, Address>? SubscribeAddressHandle(Address address, int GroupMaxNumber)
+        {
+            //返回数据
+            ConcurrentDictionary<string, Address>? addressArray = null;
+            //地址不能为空
+            if (address != null && address.AddressArray != null && address.AddressArray.Count > 0)
+            {
+                //实例化返回数据
+                addressArray = new ConcurrentDictionary<string, Address>();
+                //数据分割
+                Dictionary<int, Address> slData = SplitList(address, GroupMaxNumber);
+                //组织数据
+                foreach (var item in slData)
+                {
+                    //组名
+                    string TAG = $"{item.Key}_{Guid.NewGuid().ToUpperNString()}";
+                    //地址
+                    Address Address = item.Value;
+                    //添加至线程安全字典
+                    addressArray.AddOrUpdate(TAG, Address, (k, v) => Address);
+                }
+            }
+            return addressArray;
+        }
+
+        /// <summary>
+        /// 地址分隔
+        /// </summary>
+        /// <param name="list">要拆分的集合</param>
+        /// <param name="num">拆分数</param>
+        /// <returns></returns>
+        public static Dictionary<int, Address> SplitList(Address list, int num)
+        {
+            int listSize = list.AddressArray.Count; // 长度
+
+            Dictionary<int, Address> contractItemDic = new Dictionary<int, Address>(); //用户封装返回的多个list
+            Address contractItemList = new Address(); ;  //实例化数据
+            contractItemList.AddressArray = new List<AddressDetails>();  //实例化集合
+            //用于承装每个等分list
+            int page = 0;
+            for (int i = 0; i < listSize; i++)
+            {
+                //for循环依次放入每个list中
+                contractItemList.AddressArray.Add(list.AddressArray[i]); //先将对象放入list,以防止最后一个没有放入
+                if ((i + 1) % num == 0 || i + 1 == listSize)
+                {
+                    //如果l+1 除以 要分的份数 为整除,或者是最后一份,为结束循环.那就算作一份list,
+                    contractItemDic.Add(page++, contractItemList); //将这一份放入Map中.
+                    contractItemList = new Address(); //新建一个list,用于继续存储对象
+                    contractItemList.AddressArray = new List<AddressDetails>();  //实例化集合
+                }
+            }
+            return contractItemDic;
+        }
+    }
+}

+ 1 - 1
src/YSAI.DAQ/YSAI.RabbitMQ/RabbitMQData.cs

@@ -17,7 +17,7 @@ namespace YSAI.RabbitMQ
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// 交换机名称

+ 1 - 1
src/YSAI.DAQ/YSAI.S7/S7Data.cs

@@ -18,7 +18,7 @@ namespace YSAI.S7
             /// 唯一标识符
             /// </summary>
             [Description("唯一标识符")]
-            public string? SN { get; set; } = Guid.NewGuid().ToNString();
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
 
             /// <summary>
             /// ip地址

+ 99 - 96
src/YSAI.DAQ/YSAI.Test.All/Program.cs

@@ -1,16 +1,16 @@
-using System.Text;
-using YSAI.Core.communication.net.tcp.client;
-using YSAI.Core.data;
+//using System.Text;
+//using YSAI.Core.communication.net.tcp.client;
+//using YSAI.Core.data;
 
-TcpClientOperate tcpClientOperate = TcpClientOperate.Instance(new TcpClientData.Basics
-{
-    InterruptReconnection = false
-});
+//TcpClientOperate tcpClientOperate = TcpClientOperate.Instance(new TcpClientData.Basics
+//{
+//    InterruptReconnection = false
+//});
 
-tcpClientOperate.On();
+//tcpClientOperate.On();
 
-OperateResult operateResult = tcpClientOperate.SendWait(Encoding.ASCII.GetBytes("S"));
-Console.WriteLine();
+//OperateResult operateResult = tcpClientOperate.SendWait(Encoding.ASCII.GetBytes("S"));
+//Console.WriteLine();
 
 //using System.Diagnostics;
 //using YSAI.Unility;
@@ -458,100 +458,103 @@ Console.WriteLine();
 //    public string Description { get; set; }
 //}
 
-//using System.Collections.Concurrent;
-//using YSAI.Core.data;
-//using YSAI.Log;
-//using YSAI.Opc.ua.client;
-//using YSAI.Unility;
-
-///// <summary>
-///// 读取Csv,返回行集合
-///// </summary>
-///// <param name="path"></param>
-///// <param name="hasTitle"></param>
-///// <returns></returns>
-//List<string> ReadCsv(string path, bool hasTitle)
-//{
-//    if (!File.Exists(path))
-//        return new List<string>();
-
-//    var lines = File.ReadAllLines(path).ToList();
-//    if (hasTitle)
-//    {
-//        lines.RemoveAt(0);
-//    }
-//    return lines;
-//}
-
-////Address address = JsonTool.StringToJsonEntity<Address>(FileTool.FileToString("C:\\Users\\Shun\\Desktop\\[6032]Node_Address 202310120854271486.json"));
-
-//Address address = new Address();
-//address.SN = Guid.NewGuid().ToString();
-//address.CreationTime = DateTime.Now;
-//address.AddressArray = new List<AddressDetails>();
-//List<string> strings = ReadCsv("C:\\Users\\Shun\\Desktop\\6022.csv", true);
-//foreach (var item in strings)
-//{
-//    string[] str = item.Split(",");
-//    string addressD = str[0].Replace("\"", "");
-//    address.AddressArray.Add(new AddressDetails()
-//    {
-//        AddressName = $"ns=2;s=6022.6022.{addressD}",
-//        SN = Guid.NewGuid().ToString()
-//    });
-//}
-
-////address.AddressArray.Add(new AddressDetails()
-////{
-////    AddressName = $"ns=2;s=6022.6022.LAP5_DP2113_AUTO",
-////    SN = Guid.NewGuid().ToString()
-////});
-
-////address.AddressArray.Add(new AddressDetails()
-////{
-////    AddressName = $"ns=2;s=6022.6022.LAP5_DP2112_STATE_RUN",
-////    SN = Guid.NewGuid().ToString()
-////});
+using System.Collections.Concurrent;
+using YSAI.Core.data;
+using YSAI.Log;
+using YSAI.Opc.ua.client;
+using YSAI.Opc.ua.client.unility;
+using YSAI.Unility;
+
+/// <summary>
+/// 读取Csv,返回行集合
+/// </summary>
+/// <param name="path"></param>
+/// <param name="hasTitle"></param>
+/// <returns></returns>
+List<string> ReadCsv(string path, bool hasTitle)
+{
+    if (!File.Exists(path))
+        return new List<string>();
+
+    var lines = File.ReadAllLines(path).ToList();
+    if (hasTitle)
+    {
+        lines.RemoveAt(0);
+    }
+    return lines;
+}
+
+//Address address = JsonTool.StringToJsonEntity<Address>(FileTool.FileToString("C:\\Users\\Shun\\Desktop\\[6032]Node_Address 202310120854271486.json"));
+
+Address address = new Address();
+address.SN = Guid.NewGuid().ToString();
+address.CreationTime = DateTime.Now;
+address.AddressArray = new List<AddressDetails>();
+List<string> strings = ReadCsv("C:\\Users\\Shun\\Desktop\\6022.csv", true);
+foreach (var item in strings)
+{
+    string[] str = item.Split(",");
+    string addressD = str[0].Replace("\"", "");
+    address.AddressArray.Add(new AddressDetails()
+    {
+        AddressName = $"ns=2;s=6022.6022.{addressD}",
+        SN = Guid.NewGuid().ToString()
+    });
+}
 
-//OpcUaClientOperate opcUaClientOperate = OpcUaClientOperate.Instance(new OpcUaClientData.Basics
+//address.AddressArray.Add(new AddressDetails()
 //{
-//    ServerUrl = "opc.tcp://192.168.2.220:49320",
-//    CustomName = "YSAI 性能测试",
-//    TaskNumber = 10,
-//    TaskHandleInterval = 1
+//    AddressName = $"ns=2;s=6022.6022.LAP5_DP2113_AUTO",
+//    SN = Guid.NewGuid().ToString()
 //});
-//Console.WriteLine(opcUaClientOperate.On().ToJson().JsonFormatting());
-//opcUaClientOperate.OnEvent += OpcUaClientOperate_OnEvent;
 
-//while (true)
+//address.AddressArray.Add(new AddressDetails()
 //{
-//    Console.ReadLine();
-//    OperateResult operateResult = opcUaClientOperate.Subscribe(address);
-//    Console.WriteLine(operateResult.ToJson().JsonFormatting());
-//}
+//    AddressName = $"ns=2;s=6022.6022.LAP5_DP2112_STATE_RUN",
+//    SN = Guid.NewGuid().ToString()
+//});
 
-//void OpcUaClientOperate_OnEvent(object? sender, EventResult e)
-//{
-//    switch (e.RType)
-//    {
-//        case YSAI.Core.@enum.ResultType.KeyValue:
+OpcUaClientOperate opcUaClientOperate = OpcUaClientOperate.Instance(new OpcUaClientData.Basics
+{
+    ServerUrl = "opc.tcp://192.168.2.220:49320",
+    CustomName = "YSAI 性能测试",
+    TaskNumber = 10,
+    TaskHandleInterval = 1,
+    SubscribeSingleGroupMaxCount = 100,
+});
+Console.WriteLine(opcUaClientOperate.On().ToJson().JsonFormatting());
+opcUaClientOperate.OnEvent += OpcUaClientOperate_OnEvent;
 
-//            ConcurrentDictionary<string, AddressValue> pairs = e.GetRData<ConcurrentDictionary<string, AddressValue>>();
-//            foreach (var item in pairs)
-//            {
-//                if (item.Value.AddressName.Equals("ns=2;s=6022.6022.LAP5_DP2112_STATE_RUN")|| item.Value.AddressName.Equals("ns=2;s=6022.6022.LAP5_DP2113_AUTO"))
-//                {
-//                    String str = String.Format("{0,-100}{1,-100}", item.Key, item.Value.Value);
+while (true)
+{
+    Console.ReadLine();
+    OperateResult operateResult = opcUaClientOperate.Subscribe(address);
+    Console.WriteLine(operateResult.ToJson().JsonFormatting());
+}
 
-//                    LogHelper.Verbose(str);
-//                }
-//            }
-//            break;
-//        default:
-//            Console.WriteLine(e.Message);
-//            break;
-//    }
-//}
+void OpcUaClientOperate_OnEvent(object? sender, EventResult e)
+{
+    switch (e.RType)
+    {
+        case YSAI.Core.@enum.ResultType.KeyValue:
+
+            ConcurrentDictionary<string, AddressValue> pairs = e.GetRData<ConcurrentDictionary<string, AddressValue>>();
+            foreach (var item in pairs)
+            {
+                if (item.Value.AddressName.Equals("ns=2;s=6022.6022.LAP5_DP2112_STATE_RUN") || item.Value.AddressName.Equals("ns=2;s=6022.6022.LAP5_DP2113_AUTO"))
+                {
+                    String str = String.Format("{0,-100}{1,-100}", item.Key, item.Value.Value);
+
+                    LogHelper.Verbose(str);
+                }
+            }
+            break;
+
+        default:
+            Console.WriteLine(e.Message);
+            break;
+    }
+}
 
 //using Opc.Ua;
 //using YSAI.Core.data;

+ 1 - 1
src/YSAI.DAQ/YSAI.Test/UnitTest1.cs

@@ -171,7 +171,7 @@ namespace YSAI.Test
             servers = OpcDaClientOperate.GetServersArray(Specification.COM_DA_30);
             Console.WriteLine();
 
-            using (OpcDaClientOperate opcDaClientOperate = OpcDaClientOperate.Instance(new OpcDaClientData.Basics() { SN = Guid.NewGuid().ToString(), SName = "Knight.OPC.Server.Demo", ApiVerType = OpcDaClientData.ApiVerType.COM_DA_20 }))
+            using (OpcDaClientOperate opcDaClientOperate = OpcDaClientOperate.Instance(new OpcDaClientData.Basics() { SN = Guid.NewGuid().ToUpperNString(), SName = "Knight.OPC.Server.Demo", ApiVerType = OpcDaClientData.ApiVerType.COM_DA_20 }))
             {
                 OperateResult operateResult = opcDaClientOperate.On();
                 Console.WriteLine(operateResult.Message);