Browse Source

1. 新增 AllenBradley PLC采集
2. 新增S7类型读取流程
3. 优化CORE内部结构代码
4. 优化所有采集协议的读取代码
5. 版本更新

Shun 2 years ago
parent
commit
cfa2eb4bf4
36 changed files with 4145 additions and 547 deletions
  1. 8 1
      README.md
  2. 27 0
      src/YSAI.DAQ/YSAI.AllenBradley.Pack/Program.cs
  3. 18 0
      src/YSAI.DAQ/YSAI.AllenBradley.Pack/YSAI.AllenBradley.Pack.csproj
  4. 67 0
      src/YSAI.DAQ/YSAI.AllenBradley/AllenBradleyData.cs
  5. 736 0
      src/YSAI.DAQ/YSAI.AllenBradley/AllenBradleyOperate.cs
  6. 13 0
      src/YSAI.DAQ/YSAI.AllenBradley/Properties/PublishProfiles/FolderProfile.pubxml
  7. 18 0
      src/YSAI.DAQ/YSAI.AllenBradley/YSAI.AllenBradley.csproj
  8. 56 49
      src/YSAI.DAQ/YSAI.Beckhoff/BeckhoffOperate.cs
  9. 1 1
      src/YSAI.DAQ/YSAI.Core/YSAI.Core.csproj
  10. 1 1
      src/YSAI.DAQ/YSAI.Core/data/AddressValue.cs
  11. 2 2
      src/YSAI.DAQ/YSAI.Core/data/Result.cs
  12. 0 1
      src/YSAI.DAQ/YSAI.Core/handler/AddressHandler.cs
  13. 21 7
      src/YSAI.DAQ/YSAI.DAQ.sln
  14. 89 74
      src/YSAI.DAQ/YSAI.Mitsubishi/MitsubishiOperate.cs
  15. 5 5
      src/YSAI.DAQ/YSAI.Omron/OmronData.cs
  16. 50 42
      src/YSAI.DAQ/YSAI.Omron/OmronOperate.cs
  17. 77 124
      src/YSAI.DAQ/YSAI.S7/S7Operate.cs
  18. 111 104
      src/YSAI.DAQ/YSAI.Test.Console/Program.cs
  19. 0 134
      src/YSAI.DAQ/YSAI.Test.Consoles/Program.cs
  20. 111 0
      src/YSAI.DAQ/YSAI.Test.Service/Common/DataPersist.cs
  21. 58 0
      src/YSAI.DAQ/YSAI.Test.Service/Common/Helpers/CRC16.cs
  22. 134 0
      src/YSAI.DAQ/YSAI.Test.Service/Common/Helpers/DataConvert.cs
  23. 51 0
      src/YSAI.DAQ/YSAI.Test.Service/Common/Helpers/LRC.cs
  24. 10 0
      src/YSAI.DAQ/YSAI.Test.Service/Program.cs
  25. 702 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/BACnet/BACnetServer.cs
  26. 172 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/Modbus/ModBusAsciiServer.cs
  27. 134 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/Modbus/ModBusRtuServer.cs
  28. 206 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/Modbus/ModBusTcpServer.cs
  29. 192 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/AllenBradleyServer.cs
  30. 15 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/IIoTServer.cs
  31. 245 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/MitsubishiA1EServer.cs
  32. 218 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/MitsubishiQna3EServer.cs
  33. 229 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/OmronFinsServer.cs
  34. 322 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/SiemensServer.cs
  35. 44 0
      src/YSAI.DAQ/YSAI.Test.Service/Servers/ServerSocketBase.cs
  36. 2 2
      src/YSAI.DAQ/YSAI.Test.Consoles/YSAI.Test.Consoles.csproj

+ 8 - 1
README.md

@@ -437,4 +437,11 @@ while(true)
 1. 新增 NETMQ 传输协议(不保证消息质量模式)
 2. 优化函数运行时间,使用 struct 直接操作底层,性能提升
 3. 细节优化,版本更新
-4. 修改所有协议获取参数重复代码,移植至Core
+4. 修改所有协议获取参数重复代码,移植至Core
+
+#### 2023-11-09
+1. 新增 AllenBradley PLC采集
+2. 新增S7类型读取流程
+3. 优化CORE内部结构代码
+4. 优化所有采集协议的读取代码
+5. 版本更新

+ 27 - 0
src/YSAI.DAQ/YSAI.AllenBradley.Pack/Program.cs

@@ -0,0 +1,27 @@
+using YSAI.Unility;
+
+namespace YSAI.AllenBradley.Pack
+{
+    internal class Program
+    {
+        static void Main(string[] args)
+        {
+            string FileDirectory = string.Empty;
+            string[] paths = AppDomain.CurrentDomain.BaseDirectory.Split('\\');
+            for (int i = 0; i < paths.Length - 5; i++)
+            {
+                FileDirectory += $"{paths[i]}//";
+            }
+            FileDirectory += "YSAI.Manage//param";
+            string Name = "YSAI.AllenBradley";
+            string ParamFile = $"{FileDirectory}//{Name}.Param.json";
+            string ParamDetailsFile = $"{FileDirectory}//{Name}.ParamDetails.json";
+            if (!Directory.Exists(FileDirectory))
+            {
+                Directory.CreateDirectory(FileDirectory);
+            }
+            FileTool.StringToFile(ParamFile, new AllenBradleyData.Basics().ToJson().JsonFormatting());
+            FileTool.StringToFile(ParamDetailsFile, ReflexTool.GetClassAllPropertyData<AllenBradleyData.Basics>().ToJson().JsonFormatting());
+        }
+    }
+}

File diff suppressed because it is too large
+ 18 - 0
src/YSAI.DAQ/YSAI.AllenBradley.Pack/YSAI.AllenBradley.Pack.csproj


+ 67 - 0
src/YSAI.DAQ/YSAI.AllenBradley/AllenBradleyData.cs

@@ -0,0 +1,67 @@
+using System.ComponentModel;
+using YSAI.Core.attribute;
+using YSAI.Core.subscription;
+using YSAI.Unility;
+
+namespace YSAI.AllenBradley
+{
+    public class AllenBradleyData
+    {
+        /// <summary>
+        /// 基础数据
+        /// </summary>
+        public class Basics : SubscribeData.SCData
+        {
+            /// <summary>
+            /// 唯一标识符
+            /// </summary>
+            [Description("唯一标识符")]
+            public string? SN { get; set; } = Guid.NewGuid().ToUpperNString();
+
+            /// <summary>
+            /// ip地址
+            /// </summary>
+            [Description("IP")]
+            [Verify(@"^(25[0-4]|2[0-4]\\d]|[01]?\\d{2}|[1-9])\\.(25[0-5]|2[0-4]\\d]|[01]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d]|[01]?\\d?\\d)\\.(25[0-4]|2[0-4]\\d]|[01]?\\d{2}|[1-9])$", "输入有误")]
+            [Display(true, true, true, Core.data.ParamStructure.dataCate.text)]
+            public string? Ip { get; set; } = "127.0.0.1";
+
+            /// <summary>
+            /// 端口
+            /// </summary>
+            [Description("端口")]
+            [Display(true, true, true, Core.data.ParamStructure.dataCate.unmber)]
+            public int Port { get; set; } = 6688;
+
+            /// <summary>
+            /// 插槽
+            /// </summary>
+            [Description("插槽")]
+            [Display(true, true, true, Core.data.ParamStructure.dataCate.unmber)]
+            public int Slot { get; set; } = 0;
+
+            /// <summary>
+            /// 是否需要断开重新连接
+            /// </summary>
+            [Description("是否需要断开重新连接")]
+            [Display(true, true, true, Core.data.ParamStructure.dataCate.radio)]
+            public bool InterruptReconnection { get; set; } = true;
+
+            /// <summary>
+            /// 重连间隔(毫秒)
+            /// </summary>
+            [Description("重连间隔")]
+            [Unit("ms")]
+            [Display(true, true, true, Core.data.ParamStructure.dataCate.unmber)]
+            public int ReconnectionInterval { get; set; } = 2000;
+
+            /// <summary>
+            /// 超时时间
+            /// </summary>
+            [Description("超时时间")]
+            [Unit("ms")]
+            [Display(true, true, true, Core.data.ParamStructure.dataCate.unmber)]
+            public int Timeout { get; set; } = 1000;
+        }
+    }
+}

+ 736 - 0
src/YSAI.DAQ/YSAI.AllenBradley/AllenBradleyOperate.cs

@@ -0,0 +1,736 @@
+using System.Collections.Concurrent;
+using System.Text;
+using YSAI.Core.communication.net.tcp.client;
+using YSAI.Core.data;
+using YSAI.Core.handler;
+using YSAI.Core.@interface;
+using YSAI.Core.subscription;
+using YSAI.Core.virtualAddress;
+using YSAI.Log;
+using YSAI.Unility;
+
+namespace YSAI.AllenBradley
+{
+    /// <summary>
+    /// 艾伦·布拉德利 PLC
+    /// </summary>
+    public class AllenBradleyOperate : IBaseAbstract, IDaq
+    {
+        protected override string TAG => "AllenBradleyOperate";
+
+        /// <summary>
+        /// 锁
+        /// </summary>
+        private static readonly object Lock = new object();
+
+        /// <summary>
+        /// 自身对象集合
+        /// </summary>
+        private static List<AllenBradleyOperate> ThisObjList = new List<AllenBradleyOperate>();
+
+        /// <summary>
+        /// 单例模式
+        /// </summary>
+        /// <returns></returns>
+        public static AllenBradleyOperate Instance(AllenBradleyData.Basics basics)
+        {
+            if (ThisObjList.Count >= MaxInstanceCount)
+            {
+                throw new Exception(ExceedMaxInstanceCountTips);
+            }
+            AllenBradleyOperate? exp = ThisObjList.FirstOrDefault(c => c.basics.Comparer(basics).result);
+            if (exp == null)
+            {
+                lock (Lock)
+                {
+                    if (ThisObjList.Count(c => c.basics.Comparer(basics).result) > 0)
+                    {
+                        return ThisObjList.First(c => c.basics.Comparer(basics).result);
+                    }
+                    else
+                    {
+                        AllenBradleyOperate exp2 = new AllenBradleyOperate(basics);
+                        ThisObjList.Add(exp2);
+                        return exp2;
+                    }
+                }
+            }
+            return exp;
+        }
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="param">参数</param>
+        public AllenBradleyOperate(AllenBradleyData.Basics basics)
+        {
+            this.basics = basics;
+        }
+        public AllenBradleyOperate()
+        { }
+
+        /// <summary>
+        /// 基础数据
+        /// </summary>
+        private AllenBradleyData.Basics basics;
+
+        /// <summary>
+        ///SOCKET TCP Client
+        /// </summary>
+        private TcpClientOperate tcpClientOperate;
+
+        /// <summary>
+        /// 虚拟地址
+        /// </summary>
+        private VirtualAddressManage VAM = new VirtualAddressManage();
+
+        #region 基础
+        /// <summary>
+        /// 会话句柄(由AB PLC生成)
+        /// </summary>
+        private uint Session { get; set; }
+        /// <summary>
+        /// 注册命令
+        /// </summary>
+        private byte[] RegisteredCommand = new byte[28] {
+            0x65,0x00,                              //注册请求
+            0x04,0x00,                              //命令数据长度(单位字节)
+            0x00,0x00,0x00,0x00,                    //会话句柄,初始值为0x00000000
+            0x00,0x00,0x00,0x00,                    //状态,初始值为0x00000000(状态好)
+            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//请求通信一方的说明
+            0x00,0x00,0x00,0x00,                    //选项,默认为0x00000000
+            0x01,0x00,                              //协议版本(0x0001)
+            0x00,0x00                               //选项标记(0x0000
+        };
+        /// <summary>
+        /// 读取数据
+        /// </summary>
+        /// <param name="address">地址</param>
+        /// <param name="length">长度</param>
+        /// <returns>统一返回(byte[])</returns>
+        private byte[] ReadCommand(string address, ushort length)
+        {
+            var address_ASCII = Encoding.ASCII.GetBytes(address);
+            if (address_ASCII.Length % 2 == 1)
+            {
+                address_ASCII = new byte[address_ASCII.Length + 1];
+                Encoding.ASCII.GetBytes(address).CopyTo(address_ASCII, 0);
+            }
+
+            byte[] command = new byte[9 + 26 + address_ASCII.Length + 1 + 24];
+
+            command[0] = 0x6F;//命令
+            command[2] = BitConverter.GetBytes((ushort)(command.Length - 24))[0];
+            command[3] = BitConverter.GetBytes((ushort)(command.Length - 24))[1];//长度
+            command[4] = BitConverter.GetBytes(Session)[0];
+            command[5] = BitConverter.GetBytes(Session)[1];
+            command[6] = BitConverter.GetBytes(Session)[2];
+            command[7] = BitConverter.GetBytes(Session)[3];//会话句柄
+
+            command[0 + 24] = 0x00;
+            command[1 + 24] = 0x00;
+            command[2 + 24] = 0x00;
+            command[3 + 24] = 0x00;//接口句柄,默认为0x00000000(CIP)
+            command[4 + 24] = 0x01;
+            command[5 + 24] = 0x00;//超时(0x0001)
+            command[6 + 24] = 0x02;
+            command[7 + 24] = 0x00;//项数(0x0002)
+            command[8 + 24] = 0x00;
+            command[9 + 24] = 0x00;//空地址项(0x0000)
+            command[10 + 24] = 0x00;
+            command[11 + 24] = 0x00;//长度(0x0000)
+            command[12 + 24] = 0xB2;
+            command[13 + 24] = 0x00;//未连接数据项(0x00b2)
+            command[14 + 24] = BitConverter.GetBytes((short)(command.Length - 16 - 24))[0]; // 后面数据包的长度,等全部生成后在赋值
+            command[15 + 24] = BitConverter.GetBytes((short)(command.Length - 16 - 24))[1];
+            command[16 + 24] = 0x52;//服务类型(0x03请求服务列表,0x52请求标签数据)
+            command[17 + 24] = 0x02;//请求路径大小
+            command[18 + 24] = 0x20;
+            command[19 + 24] = 0x06;//请求路径(0x0620)
+            command[20 + 24] = 0x24;
+            command[21 + 24] = 0x01;//请求路径(0x0124)
+            command[22 + 24] = 0x0A;
+            command[23 + 24] = 0xF0;
+            command[24 + 24] = BitConverter.GetBytes((short)(6 + address_ASCII.Length))[0];     // CIP指令长度
+            command[25 + 24] = BitConverter.GetBytes((short)(6 + address_ASCII.Length))[1];
+
+            command[0 + 24 + 26] = 0x4C;//读取数据
+            command[1 + 24 + 26] = (byte)((address_ASCII.Length + 2) / 2);
+            command[2 + 24 + 26] = 0x91;
+            command[3 + 24 + 26] = (byte)address.Length;
+            address_ASCII.CopyTo(command, 4 + 24 + 26);
+            command[4 + 24 + 26 + address_ASCII.Length] = BitConverter.GetBytes(length)[0];
+            command[5 + 24 + 26 + address_ASCII.Length] = BitConverter.GetBytes(length)[1];
+
+            command[6 + 24 + 26 + address_ASCII.Length] = 0x01;
+            command[7 + 24 + 26 + address_ASCII.Length] = 0x00;
+            command[8 + 24 + 26 + address_ASCII.Length] = 0x01;
+            command[9 + 24 + 26 + address_ASCII.Length] = (byte)basics.Slot;
+
+            return command;
+        }
+
+        /// <summary>
+        /// 写入数据
+        /// </summary>
+        /// <param name="address">地址</param>
+        /// <param name="typeCode">类型代码</param>
+        /// <param name="value">数据</param>
+        /// <param name="length">长度</param>
+        /// <returns>统一返回(byte[])</returns>
+        private byte[] WriteCommand(string address, ushort typeCode, byte[] value, int length)
+        {
+            var address_ASCII = Encoding.ASCII.GetBytes(address);
+            if (address_ASCII.Length % 2 == 1)
+            {
+                address_ASCII = new byte[address_ASCII.Length + 1];
+                Encoding.ASCII.GetBytes(address).CopyTo(address_ASCII, 0);
+            }
+            byte[] command = new byte[8 + 26 + address_ASCII.Length + value.Length + 4 + 24];
+
+            command[0] = 0x6F;//命令
+            command[2] = BitConverter.GetBytes((ushort)(command.Length - 24))[0];
+            command[3] = BitConverter.GetBytes((ushort)(command.Length - 24))[1];//长度
+            command[4] = BitConverter.GetBytes(Session)[0];
+            command[5] = BitConverter.GetBytes(Session)[1];
+            command[6] = BitConverter.GetBytes(Session)[2];
+            command[7] = BitConverter.GetBytes(Session)[3];//会话句柄
+
+            command[0 + 24] = 0x00;
+            command[1 + 24] = 0x00;
+            command[2 + 24] = 0x00;
+            command[3 + 24] = 0x00;//接口句柄,默认为0x00000000(CIP)
+            command[4 + 24] = 0x01;
+            command[5 + 24] = 0x00;//超时(0x0001)
+            command[6 + 24] = 0x02;
+            command[7 + 24] = 0x00;//项数(0x0002)
+            command[8 + 24] = 0x00;
+            command[9 + 24] = 0x00;
+            command[10 + 24] = 0x00;
+            command[11 + 24] = 0x00;//空地址项(0x0000)
+            command[12 + 24] = 0xB2;
+            command[13 + 24] = 0x00;//未连接数据项(0x00b2)
+            command[14 + 24] = BitConverter.GetBytes((short)(command.Length - 16 - 24))[0]; // 后面数据包的长度,等全部生成后在赋值
+            command[15 + 24] = BitConverter.GetBytes((short)(command.Length - 16 - 24))[1];
+            command[16 + 24] = 0x52;//服务类型(0x03请求服务列表,0x52请求标签数据)
+            command[17 + 24] = 0x02;//请求路径大小
+            command[18 + 24] = 0x20;
+            command[19 + 24] = 0x06;//请求路径(0x0620)
+            command[20 + 24] = 0x24;
+            command[21 + 24] = 0x01;//请求路径(0x0124)
+            command[22 + 24] = 0x0A;
+            command[23 + 24] = 0xF0;
+            command[24 + 24] = BitConverter.GetBytes((short)(8 + value.Length + address_ASCII.Length))[0];     // CIP指令长度
+            command[25 + 24] = BitConverter.GetBytes((short)(8 + value.Length + address_ASCII.Length))[1];
+
+            command[0 + 26 + 24] = 0x4D;//写数据
+            command[1 + 26 + 24] = (byte)((address_ASCII.Length + 2) / 2);
+            command[2 + 26 + 24] = 0x91;
+            command[3 + 26 + 24] = (byte)address.Length;
+            address_ASCII.CopyTo(command, 4 + 26 + 24);
+            command[4 + 26 + 24 + address_ASCII.Length] = BitConverter.GetBytes(typeCode)[0];
+            command[5 + 26 + 24 + address_ASCII.Length] = BitConverter.GetBytes(typeCode)[1];
+            command[6 + 26 + 24 + address_ASCII.Length] = BitConverter.GetBytes(length)[0];//TODO length ??
+            command[7 + 26 + 24 + address_ASCII.Length] = BitConverter.GetBytes(length)[1];
+            value.CopyTo(command, 8 + 26 + 24 + address_ASCII.Length);
+
+            command[8 + 26 + 24 + address_ASCII.Length + value.Length] = 0x01;
+            command[9 + 26 + 24 + address_ASCII.Length + value.Length] = 0x00;
+            command[10 + 26 + 24 + address_ASCII.Length + value.Length] = 0x01;
+            command[11 + 26 + 24 + address_ASCII.Length + value.Length] = (byte)basics.Slot;
+            return command;
+        }
+
+
+        /// <summary>
+        /// 读取数据
+        /// </summary>
+        /// <param name="address">地址</param>
+        /// <param name="length">长度</param>
+        /// <returns>统一返回(byte[])</returns>
+        private byte[] R(string address, ushort length)
+        {
+            //获取命令
+            byte[] command = ReadCommand(address, length);
+            //发送等待结果
+            OperateResult operateResult = tcpClientOperate.SendWait(command);
+            if (operateResult.State)
+            {
+                byte[]? bytes = operateResult.GetRData<byte[]>();
+                ushort count = BitConverter.ToUInt16(bytes, 38);
+                byte[] data = new byte[count - 6];
+                Buffer.BlockCopy(bytes, 46, data, 0, data.Length);
+                return data;
+            }
+            //读取失败返回
+            return new byte[] { };
+        }
+
+        /// <summary>
+        /// 写入数据
+        /// </summary>
+        /// <param name="address">地址</param>
+        /// <param name="typeCode">类型代码</param>
+        /// <param name="data">数据</param>
+        /// <returns>统一返回(写入状态)</returns>
+        private bool W(string address, ushort typeCode, byte[] data)
+        {
+            byte[] command = WriteCommand(address, typeCode, data, 1);
+            //发送等待结果
+            return tcpClientOperate.SendWait(command).State;
+        }
+        #endregion
+
+        public OperateResult On()
+        {
+            string SN = Depart("On");
+            try
+            {
+                if (tcpClientOperate != null && tcpClientOperate.GetStatus().State)
+                {
+                    return Break(SN, false, "已连接");
+                }
+                if (tcpClientOperate != null && !tcpClientOperate.GetStatus().State)
+                {
+                    tcpClientOperate.Dispose();
+                }
+                //先实例化底层 socket 通信
+                tcpClientOperate = TcpClientOperate.Instance(new TcpClientData.Basics
+                {
+                    InterruptReconnection = basics.InterruptReconnection,
+                    Ip = basics.Ip,
+                    Port = basics.Port,
+                    ReconnectionInterval = basics.ReconnectionInterval,
+                    SN = basics.SN,
+                    Timeout = basics.Timeout,
+                    SendWait = true,
+                    SendWaitInterval = basics.Timeout
+                });
+                OperateResult operateResult = tcpClientOperate.On();
+                if (operateResult.State)
+                {
+                    //发送基础命令
+                    operateResult = tcpClientOperate.SendWait(RegisteredCommand);
+                    if (operateResult.State)
+                    {
+                        byte[]? bytes = operateResult.GetRData<byte[]>();
+                        if (bytes != null)
+                        {
+                            byte[] buffer = new byte[4];
+                            buffer[0] = bytes[7];
+                            buffer[1] = bytes[6];
+                            buffer[2] = bytes[5];
+                            buffer[3] = bytes[4];
+                            Session = BitConverter.ToUInt32(buffer, 0);
+                            return Break(SN, true);
+                        }
+                    }
+                }
+                return Break(SN, false, operateResult.Message);
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> OnAsync()
+        {
+            return Task.Run(() => On());
+        }
+
+        public OperateResult GetStatus()
+        {
+            string SN = Depart("GetStatus");
+            if (tcpClientOperate == null)
+            {
+                return Break(SN, false, "未连接");
+            }
+            else
+            {
+                return Break(SN, tcpClientOperate.GetStatus().State, tcpClientOperate.GetStatus().State ? "已连接" : "未连接");
+            }
+        }
+
+        public Task<OperateResult> GetStatusAsync()
+        {
+            return Task.Run(() => GetStatus());
+        }
+
+        public OperateResult Off()
+        {
+            string SN = Depart("Off");
+            try
+            {
+                if (tcpClientOperate == null || !tcpClientOperate.GetStatus().State)
+                {
+                    return Break(SN, false, "未连接");
+                }
+                tcpClientOperate.Dispose();
+                tcpClientOperate = null;
+                return Break(SN, true);
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> OffAsync()
+        {
+            return Task.Run(() => Off());
+        }
+
+        public OperateResult Read(Address address)
+        {
+            string SN = Depart("Read");
+            try
+            {
+                if (tcpClientOperate == null || !tcpClientOperate.GetStatus().State)
+                {
+                    return Break(SN, false, "未连接");
+                }
+                //节点数据
+                ConcurrentDictionary<string, AddressValue> param = new ConcurrentDictionary<string, AddressValue>();
+                //循环添加项集合
+                foreach (var item in address.AddressArray)
+                {
+                    if (!item.IsEnable) continue;
+                    //是不是虚拟地址
+                    bool IsVA = false;
+                    //初始化虚拟地址
+                    VAM.InitVirtualAddress(item, out IsVA);
+                    //值
+                    string? Value = string.Empty;
+
+                    if (IsVA)
+                    {
+                        Value = VAM.Read(item);
+                    }
+                    else
+                    {
+                        try
+                        {
+                            switch (item.AddressDataType)
+                            {
+                                case Core.@enum.DataType.Bool:
+                                    Value = BitConverter.ToBoolean(R(item.AddressName, 1), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Short:
+                                case Core.@enum.DataType.Int16:
+                                    Value = BitConverter.ToInt16(R(item.AddressName, 2), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Ushort:
+                                case Core.@enum.DataType.UInt16:
+                                    Value = BitConverter.ToUInt16(R(item.AddressName, 2), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Int:
+                                case Core.@enum.DataType.Int32:
+                                    Value = BitConverter.ToInt32(R(item.AddressName, 4), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Uint:
+                                case Core.@enum.DataType.UInt32:
+                                    Value = BitConverter.ToUInt32(R(item.AddressName, 4), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Long:
+                                case Core.@enum.DataType.Int64:
+                                    Value = BitConverter.ToInt64(R(item.AddressName, 8), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Ulong:
+                                case Core.@enum.DataType.UInt64:
+                                    Value = BitConverter.ToUInt64(R(item.AddressName, 8), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Double:
+                                    Value = BitConverter.ToDouble(R(item.AddressName, 8), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Float:
+                                    Value = BitConverter.ToSingle(R(item.AddressName, 4), 0).ToString();
+                                    break;
+                                default:
+                                    LogHelper.Error($"[ {item.AddressName} ]读取失败:不支持{item.AddressDataType}类型读取");
+                                    break;
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            LogHelper.Error($"[ {item.AddressName} ]读取异常:{ex.Message}");
+                        }
+                    }
+                    //数据处理
+                    AddressValue addressValue = Core.handler.AddressHandler.ExecuteDispose(item, Value);
+
+                    //数据添加
+                    param.AddOrUpdate(item.AddressName, addressValue, (k, v) => addressValue);
+                }
+
+                if (param.Count > 0)
+                {
+                    //返回读取的数据
+                    return Break(SN, true, RData: param, RType: Core.@enum.ResultType.KeyValue);
+                }
+                else
+                {
+                    return Break(SN, false, "读取失败");
+                }
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> ReadAsync(Address address)
+        {
+            return Task.Run(() => Read(address));
+        }
+
+        public OperateResult Write<V>(ConcurrentDictionary<string, V> Values)
+        {
+            string SN = Depart("Write");
+            try
+            {
+                if (tcpClientOperate == null || !tcpClientOperate.GetStatus().State)
+                {
+                    return Break(SN, false, "未连接");
+                }
+                //失败消息
+                List<string> FailMessage = new List<string>();
+
+                foreach (var item in Values)
+                {
+                    KeyValuePair<string, V> Param = item;
+                    //判断是不是虚拟点
+                    if (VAM.IsVirtualAddress(Param.Key))
+                    {
+                        if (!VAM.Write(Param.Key, Param.Value.ToString()))
+                        {
+                            FailMessage.Add($"{Param.Key},写入失败");
+                        }
+                    }
+                    else
+                    {
+                        bool RState = false;
+                        object obj = Param.Value;
+                        if (obj is byte)
+                        {
+                            RState = W(Param.Key, 0xC2, new byte[] { (byte)obj, 0x00 });
+                        }
+                        else if (obj is bool)
+                        {
+                            RState = W(item.Key, 0xC1, bool.Parse(Param.Value.ToString()) ? new byte[] { 0xFF, 0xFF } : new byte[] { 0x00, 0x00 });
+                        }
+                        else if (obj is string)
+                        {
+                            var valueBytes = Encoding.ASCII.GetBytes(Param.Value.ToString());
+                            var bytes = new byte[valueBytes.Length + 1];
+                            bytes[0] = (byte)valueBytes.Length;
+                            valueBytes.CopyTo(bytes, 1);
+                            Array.Reverse(bytes);
+                            RState = W(item.Key, 0xC4, bytes);
+                        }
+                        else if (obj is Int16)
+                        {
+                            RState = W(Param.Key, 0xC3, BitConverter.GetBytes((Int16)obj));
+                        }
+                        else if (obj is UInt16)
+                        {
+                            RState = W(Param.Key, 0xC3, BitConverter.GetBytes((UInt16)obj));
+                        }
+                        else if (obj is Int32)
+                        {
+                            RState = W(Param.Key, 0xC4, BitConverter.GetBytes((Int32)obj));
+                        }
+                        else if (obj is UInt32)
+                        {
+                            RState = W(Param.Key, 0xC4, BitConverter.GetBytes((UInt32)obj));
+                        }
+                        else if (obj is Int64)
+                        {
+                            RState = W(Param.Key, 0xC5, BitConverter.GetBytes((Int64)obj));
+                        }
+                        else if (obj is UInt64)
+                        {
+                            RState = W(Param.Key, 0xC5, BitConverter.GetBytes((UInt64)obj));
+                        }
+                        else if (obj is float)
+                        {
+                            RState = W(Param.Key, 0xCA, BitConverter.GetBytes((float)obj));
+                        }
+                        else if (obj is double)
+                        {
+                            RState = W(Param.Key, 0xCB, BitConverter.GetBytes((double)obj));
+                        }
+                        if (!RState)
+                        {
+                            FailMessage.Add($"{Param.Key},写入失败");
+                        }
+                    }
+                }
+                if (FailMessage.Count > 0)
+                {
+                    return Break("Write", false, FailMessage.ToJson());
+                }
+                return Break("Write", true, "写入成功");
+
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> WriteAsync<V>(ConcurrentDictionary<string, V> Values)
+        {
+            return Task.Run(() => Write(Values));
+        }
+
+        public void Dispose()
+        {
+            Off();
+            GC.Collect();
+            GC.SuppressFinalize(this);
+            ThisObjList.Remove(this);
+        }
+
+        /// <summary>
+        /// 实现订阅功能
+        /// </summary>
+        private SubscribeOperate subscribeOperate;
+
+        /// <summary>
+        /// 事件抛出
+        /// </summary>
+        /// <param name="sender">自定义订阅对象</param>
+        /// <param name="e">返回的参数</param>
+        private void SubscribeOperate_OnEvent(object? sender, EventResult e)
+        {
+            OnEventHandler(this, e);
+        }
+
+        public OperateResult Subscribe(Address address)
+        {
+            string SN = Depart("Subscribe");
+            try
+            {
+                if (subscribeOperate == null)
+                {
+                    subscribeOperate = SubscribeOperate.Instance(new SubscribeData.Basics()
+                    {
+                        Address = address,
+                        ChangeOut = basics.ChangeOut,
+                        Function = Read,
+                        AllOut = basics.AllOut,
+                        HandleInterval = basics.HandleInterval,
+                        SN = basics.SN,
+                        TaskHandleInterval = basics.TaskHandleInterval,
+                        TaskNumber = basics.TaskNumber
+                    });
+                    subscribeOperate.OnEvent += SubscribeOperate_OnEvent;
+                    OperateResult operateResult = subscribeOperate.On();
+                    return Break(SN, operateResult.State, operateResult.Message);
+                }
+                else
+                {
+                    OperateResult operateResult = subscribeOperate.Subscribe(address);
+                    return Break(SN, operateResult.State, operateResult.Message);
+                }
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> SubscribeAsync(Address address)
+        {
+            return Task.Run(() => Subscribe(address));
+        }
+
+        public OperateResult UnSubscribe(Address address)
+        {
+            string SN = Depart("UnSubscribe");
+            try
+            {
+                if (subscribeOperate != null)
+                {
+                    OperateResult operateResult = subscribeOperate.UnSubscribe(address);
+                    return Break(SN, operateResult.State, operateResult.Message);
+                }
+                else
+                {
+                    return Break(SN, false, "当前尚未订阅");
+                }
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> UnSubscribeAsync(Address address)
+        {
+            return Task.Run(() => UnSubscribe(address));
+        }
+
+        public OperateResult GetParam()
+        {
+            string SN = Depart("GetParam");
+            try
+            {
+                //名称
+                string name = TAG.Replace("Operate", string.Empty).Replace("Client", string.Empty);
+                //命名空间
+                string nameSpace = "YSAI.AllenBradley.AllenBradleyOperate";
+                //参数处理
+                OperateResult operateResult = ParamHandler.Get(new AllenBradleyData.Basics(), name, Properties: new List<ParamStructure.propertie>
+                {
+                    new ParamStructure.propertie
+                    {
+                        PropertyName = "ServiceName",
+                        Description = "命名空间",
+                        Show = false,
+                        Use = false,
+                        Default = nameSpace,
+                        DataCate = null
+                    }
+                });
+                return Break(SN, operateResult);
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> GetParamAsync()
+        {
+            return Task.Run(() => GetParam());
+        }
+
+        public OperateResult CreateInstance<T>(T Basics)
+        {
+            string SN = Depart("CreateInstance");
+            try
+            {
+                //先判断对象类型是否一致
+                if (typeof(T).FullName.Equals(typeof(AllenBradleyData.Basics).FullName))
+                {
+                    return Break(SN, true, RData: Instance(Basics as AllenBradleyData.Basics));
+                }
+                else
+                {
+                    return Break(SN, false, "对象类型错误,无法创建实例");
+                }
+            }
+            catch (Exception ex)
+            {
+                return Break(SN, false, ex.Message, Exception: ex);
+            }
+        }
+
+        public Task<OperateResult> CreateInstanceAsync<T>(T Basics)
+        {
+            return Task.Run(() => CreateInstance(Basics));
+        }
+    }
+}

+ 13 - 0
src/YSAI.DAQ/YSAI.AllenBradley/Properties/PublishProfiles/FolderProfile.pubxml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <Configuration>Release</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>..\YSAI.Publish\Release\net6.0\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+  </PropertyGroup>
+</Project>

+ 18 - 0
src/YSAI.DAQ/YSAI.AllenBradley/YSAI.AllenBradley.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<TargetFramework>net6.0</TargetFramework>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<Nullable>enable</Nullable>
+		<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
+		<Version>1.0.0.1</Version>
+		<Authors>Shun</Authors>
+		<Company>YSAI</Company>
+		<Product>SCADA</Product>
+		<GenerateDocumentationFile>True</GenerateDocumentationFile>
+	</PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="YSAI.Core" Version="1.0.0.87" />
+	</ItemGroup>
+</Project>

+ 56 - 49
src/YSAI.DAQ/YSAI.Beckhoff/BeckhoffOperate.cs

@@ -7,6 +7,7 @@ using YSAI.Core.data;
 using YSAI.Core.handler;
 using YSAI.Core.@interface;
 using YSAI.Core.virtualAddress;
+using YSAI.Log;
 using YSAI.Unility;
 
 namespace YSAI.Beckhoff
@@ -392,67 +393,73 @@ namespace YSAI.Beckhoff
                         //判断是否正常
                         if (handle.Succeeded)
                         {
-                            //获取到了句柄
-                            switch (item.AddressDataType)
+                            try
                             {
-                                case Core.@enum.DataType.Bool:
-                                    resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(bool), Cts.Token).Result;
-                                    break;
-
-                                case Core.@enum.DataType.String:
-                                case Core.@enum.DataType.Char:
-                                    resultAnyValue = adsClient.ReadAnyStringAsync(handle.Handle, byte.MaxValue, StringMarshaler.DefaultEncoding, Cts.Token).Result;
-                                    break;
-
-                                case Core.@enum.DataType.Double:
-                                case Core.@enum.DataType.Float:
-                                    resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(double), Cts.Token).Result;
-                                    break;
-
-                                case Core.@enum.DataType.Int:
-                                    resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(uint), Cts.Token).Result;
-                                    break;
-
-                                case Core.@enum.DataType.DateTime:
-                                    resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(DateTime), Cts.Token).Result;
-                                    break;
-
-                                case Core.@enum.DataType.Time:
-                                    resultRead = adsClient.ReadAsync(handle.Handle, readBuffer.AsMemory(0, TimeBase.MarshalSize), Cts.Token).Result;
-                                    if (resultRead.Succeeded)
-                                    {
-                                        TIME plcTime = null;
-                                        PrimitiveTypeMarshaler.Default.Unmarshal(readBuffer.AsSpan(0, TimeBase.MarshalSize), out plcTime);
-                                        if (plcTime != null)
+                                //获取到了句柄
+                                switch (item.AddressDataType)
+                                {
+                                    case Core.@enum.DataType.Bool:
+                                        resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(bool), Cts.Token).Result;
+                                        break;
+
+                                    case Core.@enum.DataType.String:
+                                    case Core.@enum.DataType.Char:
+                                        resultAnyValue = adsClient.ReadAnyStringAsync(handle.Handle, byte.MaxValue, StringMarshaler.DefaultEncoding, Cts.Token).Result;
+                                        break;
+
+                                    case Core.@enum.DataType.Double:
+                                    case Core.@enum.DataType.Float:
+                                        resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(double), Cts.Token).Result;
+                                        break;
+
+                                    case Core.@enum.DataType.Int:
+                                        resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(uint), Cts.Token).Result;
+                                        break;
+
+                                    case Core.@enum.DataType.DateTime:
+                                        resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(DateTime), Cts.Token).Result;
+                                        break;
+
+                                    case Core.@enum.DataType.Time:
+                                        resultRead = adsClient.ReadAsync(handle.Handle, readBuffer.AsMemory(0, TimeBase.MarshalSize), Cts.Token).Result;
+                                        if (resultRead.Succeeded)
                                         {
-                                            Value = plcTime.Time.ToString();
+                                            TIME plcTime = null;
+                                            PrimitiveTypeMarshaler.Default.Unmarshal(readBuffer.AsSpan(0, TimeBase.MarshalSize), out plcTime);
+                                            if (plcTime != null)
+                                            {
+                                                Value = plcTime.Time.ToString();
+                                            }
                                         }
-                                    }
-                                    break;
+                                        break;
 
-                                case Core.@enum.DataType.Date:
-                                    resultRead = adsClient.ReadAsync(handle.Handle, readBuffer.AsMemory(0, TimeBase.MarshalSize), Cts.Token).Result;
-                                    if (resultRead.Succeeded)
-                                    {
-                                        DATE plcDate = null;
-                                        PrimitiveTypeMarshaler.Default.Unmarshal(readBuffer.AsSpan(0, TimeBase.MarshalSize), out plcDate);
-                                        if (plcDate != null)
+                                    case Core.@enum.DataType.Date:
+                                        resultRead = adsClient.ReadAsync(handle.Handle, readBuffer.AsMemory(0, TimeBase.MarshalSize), Cts.Token).Result;
+                                        if (resultRead.Succeeded)
                                         {
-                                            Value = plcDate.ToString();
+                                            DATE plcDate = null;
+                                            PrimitiveTypeMarshaler.Default.Unmarshal(readBuffer.AsSpan(0, TimeBase.MarshalSize), out plcDate);
+                                            if (plcDate != null)
+                                            {
+                                                Value = plcDate.ToString();
+                                            }
                                         }
-                                    }
-                                    break;
+                                        break;
 
-                                default:
-                                    Value = $"不支持{item.AddressDataType}类型读取";
-                                    break;
+                                    default:
+                                        LogHelper.Error($"[ {item.AddressName} ]读取失败:不支持{item.AddressDataType}类型读取");
+                                        break;
+                                }
+                            }
+                            catch (Exception ex)
+                            {
+                                LogHelper.Error($"[ {item.AddressName} ]读取异常:{ex.Message}");
                             }
                         }
                         else
                         {
-                            Value = $"未获取到句柄ID";
+                            LogHelper.Error($"[ {item.AddressName} ]读取失败:未获取到句柄ID");
                         }
-
                         if (resultAnyValue != null)
                         {
                             if (resultAnyValue.Succeeded)

+ 1 - 1
src/YSAI.DAQ/YSAI.Core/YSAI.Core.csproj

@@ -5,7 +5,7 @@
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-    <Version>1.0.0.87</Version>
+    <Version>1.0.0.88</Version>
     <Authors>Shun</Authors>
     <Company>YSAI</Company>
     <Product>SCADA</Product>

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

@@ -27,7 +27,7 @@ namespace YSAI.Core.data
         /// <summary>
         /// 获取或更新的时间
         /// </summary>
-        public DateTime Time { get; set; } = DateTime.Now;
+        public string Time { get; set; } = DateTime.Now.ToDateTimeString();
 
         /// <summary>
         /// 设置父类的参数

+ 2 - 2
src/YSAI.DAQ/YSAI.Core/data/Result.cs

@@ -30,9 +30,9 @@ namespace YSAI.Core.data
         public bool State { get; set; }
 
         /// <summary>
-        /// 创建时间
+        /// 时间
         /// </summary>
-        public string CreatedTime { get; set; } = DateTime.Now.ToDateTimeString();
+        public string Time { get; set; } = DateTime.Now.ToDateTimeString();
 
         /// <summary>
         /// 结果数据

+ 0 - 1
src/YSAI.DAQ/YSAI.Core/handler/AddressHandler.cs

@@ -56,7 +56,6 @@ namespace YSAI.Core.handler
                 addressValue.AddressName = addressDetails.AddressName;
                 addressValue.Value = value;
                 addressValue.OriginalValue = originalValue;
-                addressValue.Time = DateTime.Now.ToLocalTime();
                 addressValue.Quality = Quality;
 
                 //反射状态为true才进行操作

+ 21 - 7
src/YSAI.DAQ/YSAI.DAQ.sln

@@ -109,9 +109,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YSAI.Mitsubishi.Pack", "YSA
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YSAI.NetMQ", "YSAI.NetMQ\YSAI.NetMQ.csproj", "{B703E19E-A1A7-4D7B-908A-C28B66512A8B}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YSAI.Test.Consoles", "YSAI.Test.Consoles\YSAI.Test.Consoles.csproj", "{11411FF2-2768-4216-8062-8B56461A010B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YSAI.NetMQ.Pack", "YSAI.NetMQ.Pack\YSAI.NetMQ.Pack.csproj", "{F41F510E-5D32-44A1-97A0-228B507DDA90}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YSAI.NetMQ.Pack", "YSAI.NetMQ.Pack\YSAI.NetMQ.Pack.csproj", "{F41F510E-5D32-44A1-97A0-228B507DDA90}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YSAI.Test.Service", "YSAI.Test.Service\YSAI.Test.Service.csproj", "{6B3E64C2-34D4-4086-878D-D736431F0004}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YSAI.AllenBradley", "YSAI.AllenBradley\YSAI.AllenBradley.csproj", "{5D263A57-A36F-44DF-9AC8-9F4AA928B221}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YSAI.AllenBradley.Pack", "YSAI.AllenBradley.Pack\YSAI.AllenBradley.Pack.csproj", "{3263AB0B-19E7-456B-9DCF-C7DC3BEED337}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -295,14 +299,22 @@ Global
 		{B703E19E-A1A7-4D7B-908A-C28B66512A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{B703E19E-A1A7-4D7B-908A-C28B66512A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{B703E19E-A1A7-4D7B-908A-C28B66512A8B}.Release|Any CPU.Build.0 = Release|Any CPU
-		{11411FF2-2768-4216-8062-8B56461A010B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{11411FF2-2768-4216-8062-8B56461A010B}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{11411FF2-2768-4216-8062-8B56461A010B}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{11411FF2-2768-4216-8062-8B56461A010B}.Release|Any CPU.Build.0 = Release|Any CPU
 		{F41F510E-5D32-44A1-97A0-228B507DDA90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{F41F510E-5D32-44A1-97A0-228B507DDA90}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F41F510E-5D32-44A1-97A0-228B507DDA90}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F41F510E-5D32-44A1-97A0-228B507DDA90}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6B3E64C2-34D4-4086-878D-D736431F0004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6B3E64C2-34D4-4086-878D-D736431F0004}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6B3E64C2-34D4-4086-878D-D736431F0004}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6B3E64C2-34D4-4086-878D-D736431F0004}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5D263A57-A36F-44DF-9AC8-9F4AA928B221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5D263A57-A36F-44DF-9AC8-9F4AA928B221}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5D263A57-A36F-44DF-9AC8-9F4AA928B221}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5D263A57-A36F-44DF-9AC8-9F4AA928B221}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3263AB0B-19E7-456B-9DCF-C7DC3BEED337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3263AB0B-19E7-456B-9DCF-C7DC3BEED337}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3263AB0B-19E7-456B-9DCF-C7DC3BEED337}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3263AB0B-19E7-456B-9DCF-C7DC3BEED337}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -356,8 +368,10 @@ Global
 		{5EB13A98-97E5-4848-A5F3-D90974AA76A8} = {1EBA4FD7-DF52-49A0-8AA4-9E61ABC614F5}
 		{3573D496-3EE5-48B2-9060-E5306B5E3A94} = {1EBA4FD7-DF52-49A0-8AA4-9E61ABC614F5}
 		{B703E19E-A1A7-4D7B-908A-C28B66512A8B} = {9D8EDBBA-7A97-4D84-9B12-7FCC2F834046}
-		{11411FF2-2768-4216-8062-8B56461A010B} = {12CB0510-7B1E-4518-AA3B-412A4D323D42}
 		{F41F510E-5D32-44A1-97A0-228B507DDA90} = {1EBA4FD7-DF52-49A0-8AA4-9E61ABC614F5}
+		{6B3E64C2-34D4-4086-878D-D736431F0004} = {12CB0510-7B1E-4518-AA3B-412A4D323D42}
+		{5D263A57-A36F-44DF-9AC8-9F4AA928B221} = {0A264424-1AD7-49FA-B813-D96498066479}
+		{3263AB0B-19E7-456B-9DCF-C7DC3BEED337} = {1EBA4FD7-DF52-49A0-8AA4-9E61ABC614F5}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {5D5D3927-6714-40C0-84EA-44C5BA4C5E87}

+ 89 - 74
src/YSAI.DAQ/YSAI.Mitsubishi/MitsubishiOperate.cs

@@ -7,6 +7,7 @@ using YSAI.Core.handler;
 using YSAI.Core.@interface;
 using YSAI.Core.subscription;
 using YSAI.Core.virtualAddress;
+using YSAI.Log;
 using YSAI.Unility;
 using static YSAI.Mitsubishi.MitsubishiData;
 
@@ -771,44 +772,51 @@ namespace YSAI.Mitsubishi
                         }
                         if (addressData.count.Equals(0))
                         {
-                            switch (item.AddressDataType)
+                            try
                             {
-                                case Core.@enum.DataType.Short:
-                                case Core.@enum.DataType.Int16:
-                                    Value = BitConverter.ToInt16(R(addressData.addressName, 2), 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Bool:
-                                    Value = ((R(addressData.addressName, 1)[0] & 0b00010000) != 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Ushort:
-                                case Core.@enum.DataType.UInt16:
-                                    Value = BitConverter.ToUInt16(R(addressData.addressName, 2), 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Int:
-                                case Core.@enum.DataType.Int32:
-                                    Value = BitConverter.ToInt32(R(addressData.addressName, 4), 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Uint:
-                                case Core.@enum.DataType.UInt32:
-                                    Value = BitConverter.ToUInt32(R(addressData.addressName, 4), 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Long:
-                                case Core.@enum.DataType.Int64:
-                                    Value = BitConverter.ToInt64(R(addressData.addressName, 8), 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Ulong:
-                                case Core.@enum.DataType.UInt64:
-                                    Value = BitConverter.ToUInt64(R(addressData.addressName, 8), 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Double:
-                                    Value = BitConverter.ToDouble(R(addressData.addressName, 8), 0).ToString();
-                                    break;
-                                case Core.@enum.DataType.Float:
-                                    Value = BitConverter.ToSingle(R(addressData.addressName, 4), 0).ToString();
-                                    break;
-                                default:
-                                    Value = $"不支持{item.AddressDataType}类型读取";
-                                    break;
+                                switch (item.AddressDataType)
+                                {
+                                    case Core.@enum.DataType.Short:
+                                    case Core.@enum.DataType.Int16:
+                                        Value = BitConverter.ToInt16(R(addressData.addressName, 2), 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Bool:
+                                        Value = ((R(addressData.addressName, 1)[0] & 0b00010000) != 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Ushort:
+                                    case Core.@enum.DataType.UInt16:
+                                        Value = BitConverter.ToUInt16(R(addressData.addressName, 2), 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Int:
+                                    case Core.@enum.DataType.Int32:
+                                        Value = BitConverter.ToInt32(R(addressData.addressName, 4), 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Uint:
+                                    case Core.@enum.DataType.UInt32:
+                                        Value = BitConverter.ToUInt32(R(addressData.addressName, 4), 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Long:
+                                    case Core.@enum.DataType.Int64:
+                                        Value = BitConverter.ToInt64(R(addressData.addressName, 8), 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Ulong:
+                                    case Core.@enum.DataType.UInt64:
+                                        Value = BitConverter.ToUInt64(R(addressData.addressName, 8), 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Double:
+                                        Value = BitConverter.ToDouble(R(addressData.addressName, 8), 0).ToString();
+                                        break;
+                                    case Core.@enum.DataType.Float:
+                                        Value = BitConverter.ToSingle(R(addressData.addressName, 4), 0).ToString();
+                                        break;
+                                    default:
+                                        LogHelper.Error($"[ {item.AddressName} ]读取失败:不支持{item.AddressDataType}类型读取");
+                                        break;
+                                }
+                            }
+                            catch (Exception ex)
+                            {
+                                LogHelper.Error($"[ {item.AddressName} ]读取异常:{ex.Message}");
                             }
                         }
                         else
@@ -824,48 +832,55 @@ namespace YSAI.Mitsubishi
                             //数据块地址
                             int dbAddress = int.Parse(addressData.addressName.Substring(1));
 
-                            switch (item.AddressDataType)
+                            try
                             {
-                                case Core.@enum.DataType.Short:
-                                    len = 2;
-                                    bytes = R(addressData.addressName, Convert.ToUInt16(len * addressData.count));
-                                    for (int i = 0; i < addressData.count; i++)
-                                    {
-                                        DValue.Add(new RetValue
-                                        {
-                                            Address = $"{dbType}{dbAddress + i * len}",
-                                            Value = BitConverter.ToInt16(bytes, (addressData.count - 1 - i) * len).ToString()
-                                        });
-                                    }
-                                    Value = DValue.ToJson();
-                                    break;
-                                case Core.@enum.DataType.Bool:
-                                    len = 1;
-                                    bytes = R(addressData.addressName, Convert.ToUInt16(len * addressData.count));
-                                    for (int i = 0; i < addressData.count; i++)
-                                    {
-                                        var index = i / 2;
-                                        var isoffset = i % 2 == 0;
-                                        bool value;
-                                        if (isoffset)
+                                switch (item.AddressDataType)
+                                {
+                                    case Core.@enum.DataType.Short:
+                                        len = 2;
+                                        bytes = R(addressData.addressName, Convert.ToUInt16(len * addressData.count));
+                                        for (int i = 0; i < addressData.count; i++)
                                         {
-                                            value = (bytes[index] & 0b00010000) != 0;
+                                            DValue.Add(new RetValue
+                                            {
+                                                Address = $"{dbType}{dbAddress + i * len}",
+                                                Value = BitConverter.ToInt16(bytes, (addressData.count - 1 - i) * len).ToString()
+                                            });
                                         }
-                                        else
+                                        Value = DValue.ToJson();
+                                        break;
+                                    case Core.@enum.DataType.Bool:
+                                        len = 1;
+                                        bytes = R(addressData.addressName, Convert.ToUInt16(len * addressData.count));
+                                        for (int i = 0; i < addressData.count; i++)
                                         {
-                                            value = (bytes[index] & 0b00000001) != 0;
+                                            var index = i / 2;
+                                            var isoffset = i % 2 == 0;
+                                            bool value;
+                                            if (isoffset)
+                                            {
+                                                value = (bytes[index] & 0b00010000) != 0;
+                                            }
+                                            else
+                                            {
+                                                value = (bytes[index] & 0b00000001) != 0;
+                                            }
+                                            DValue.Add(new RetValue
+                                            {
+                                                Address = $"{dbType}{dbAddress + i * len}",
+                                                Value = value.ToString()
+                                            });
                                         }
-                                        DValue.Add(new RetValue
-                                        {
-                                            Address = $"{dbType}{dbAddress + i * len}",
-                                            Value = value.ToString()
-                                        });
-                                    }
-                                    Value = DValue.ToJson();
-                                    break;
-                                default:
-                                    Value = $"不支持{item.AddressDataType}类型批量读取";
-                                    break;
+                                        Value = DValue.ToJson();
+                                        break;
+                                    default:
+                                        LogHelper.Error($"[ {item.AddressName} ]读取失败:不支持{item.AddressDataType}类型批量读取");
+                                        break;
+                                }
+                            }
+                            catch (Exception ex)
+                            {
+                                LogHelper.Error($"[ {item.AddressName} ]读取异常:{ex.Message}");
                             }
                         }
                     }

+ 5 - 5
src/YSAI.DAQ/YSAI.Omron/OmronData.cs

@@ -64,13 +64,13 @@ namespace YSAI.Omron
 
             /// <summary>
             /// DA2(即Destination unit address,目标单元地址)
-            /// 0x00:PC(CPU),
-            /// 0xFE:SYSMAC NET Link Unit or SYSMAC LINK Unit connected to network,
-            /// 0x10~0x1F:CPU总线单元 ,其值等于10 + 单元号(前端面板中配置的单元号)
+            /// 0:PC(CPU),
+            /// 254:SYSMAC NET Link Unit or SYSMAC LINK Unit connected to network,
+            /// 16~31:CPU总线单元 ,其值等于10 + 单元号(前端面板中配置的单元号)
             /// </summary>
             [Description("目标单元地址")]
             [Display(true, true, true, Core.data.ParamStructure.dataCate.unmber)]
-            public byte UnitAddress { get; set; } = 0x00;
+            public int UnitAddress { get; set; } = 0;
 
             /// <summary>
             /// SA1客户端节点编号,
@@ -78,7 +78,7 @@ namespace YSAI.Omron
             /// </summary>
             [Description("SA1客户端节点编号")]
             [Display(true, true, true, Core.data.ParamStructure.dataCate.unmber)]
-            public byte SA1 { get; set; } = 0x0B;
+            public int SA1 { get; set; } = 11;
 
             /// <summary>
             /// 协议类型

+ 50 - 42
src/YSAI.DAQ/YSAI.Omron/OmronOperate.cs

@@ -6,6 +6,7 @@ using YSAI.Core.handler;
 using YSAI.Core.@interface;
 using YSAI.Core.subscription;
 using YSAI.Core.virtualAddress;
+using YSAI.Log;
 using YSAI.Unility;
 using static YSAI.Omron.OmronData;
 
@@ -501,9 +502,9 @@ namespace YSAI.Omron
             command[18] = 0x02; //GCT 网关计数
             command[19] = 0x00; //DNA 目标网络地址 00:表示本地网络  0x01~0x7F:表示远程网络
             command[20] = DA1; //DA1 目标节点编号 0x01~0x3E:SYSMAC LINK网络中的节点号 0x01~0x7E:YSMAC NET网络中的节点号 0xFF:广播传输
-            command[21] = basics.UnitAddress; //DA2 目标单元地址
+            command[21] = (byte)basics.UnitAddress; //DA2 目标单元地址
             command[22] = 0x00; //SNA 源网络地址 取值及含义同DNA字段
-            command[23] = basics.SA1; //SA1 源节点编号 取值及含义同DA1字段
+            command[23] = (byte)basics.SA1; //SA1 源节点编号 取值及含义同DA1字段
             command[24] = 0x00; //SA2 源单元地址 取值及含义同DA2字段
             command[25] = 0x00; //SID Service ID 取值0x00~0xFF,产生会话的进程的唯一标识
 
@@ -539,9 +540,9 @@ namespace YSAI.Omron
             command[18] = 0x02; //GCT 网关计数
             command[19] = 0x00; //DNA 目标网络地址 00:表示本地网络  0x01~0x7F:表示远程网络
             command[20] = DA1; //DA1 目标节点编号 0x01~0x3E:SYSMAC LINK网络中的节点号 0x01~0x7E:YSMAC NET网络中的节点号 0xFF:广播传输
-            command[21] = basics.UnitAddress; //DA2 目标单元地址
+            command[21] = (byte)basics.UnitAddress; //DA2 目标单元地址
             command[22] = 0x00; //SNA 源网络地址 取值及含义同DNA字段
-            command[23] = basics.SA1; //SA1 源节点编号 取值及含义同DA1字段
+            command[23] = (byte)basics.SA1; //SA1 源节点编号 取值及含义同DA1字段
             command[24] = 0x00; //SA2 源单元地址 取值及含义同DA2字段
             command[25] = 0x00; //SID Service ID 取值0x00~0xFF,产生会话的进程的唯一标识
 
@@ -636,7 +637,7 @@ namespace YSAI.Omron
                 if (operateResult.State)
                 {
                     //发送基础命令
-                    BasicCommand[19] = basics.SA1;
+                    BasicCommand[19] = (byte)basics.SA1;
                     operateResult = tcpClientOperate.SendWait(BasicCommand);
                     if (operateResult.State)
                     {
@@ -702,44 +703,51 @@ namespace YSAI.Omron
                     }
                     else
                     {
-                        switch (item.AddressDataType)
+                        try
                         {
-                            case Core.@enum.DataType.Bool:
-                                Value = BitConverter.ToBoolean(R(item.AddressName, 1, true)).ToString();
-                                break;
-                            case Core.@enum.DataType.Short:
-                            case Core.@enum.DataType.Int16:
-                                Value = BitConverter.ToInt16(R(item.AddressName, 2), 0).ToString();
-                                break;
-                            case Core.@enum.DataType.Ushort:
-                            case Core.@enum.DataType.UInt16:
-                                Value = BitConverter.ToUInt16(R(item.AddressName, 2), 0).ToString();
-                                break;
-                            case Core.@enum.DataType.Int:
-                            case Core.@enum.DataType.Int32:
-                                Value = BitConverter.ToInt32(R(item.AddressName, 4), 0).ToString();
-                                break;
-                            case Core.@enum.DataType.Uint:
-                            case Core.@enum.DataType.UInt32:
-                                Value = BitConverter.ToUInt32(R(item.AddressName, 4), 0).ToString();
-                                break;
-                            case Core.@enum.DataType.Long:
-                            case Core.@enum.DataType.Int64:
-                                Value = BitConverter.ToInt64(R(item.AddressName, 8), 0).ToString();
-                                break;
-                            case Core.@enum.DataType.Ulong:
-                            case Core.@enum.DataType.UInt64:
-                                Value = BitConverter.ToUInt64(R(item.AddressName, 8), 0).ToString();
-                                break;
-                            case Core.@enum.DataType.Double:
-                                Value = BitConverter.ToDouble(R(item.AddressName, 8), 0).ToString();
-                                break;
-                            case Core.@enum.DataType.Float:
-                                Value = BitConverter.ToSingle(R(item.AddressName, 4), 0).ToString();
-                                break;
-                            default:
-                                Value = $"不支持{item.AddressDataType}类型读取";
-                                break;
+                            switch (item.AddressDataType)
+                            {
+                                case Core.@enum.DataType.Bool:
+                                    Value = BitConverter.ToBoolean(R(item.AddressName, 1, true)).ToString();
+                                    break;
+                                case Core.@enum.DataType.Short:
+                                case Core.@enum.DataType.Int16:
+                                    Value = BitConverter.ToInt16(R(item.AddressName, 2), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Ushort:
+                                case Core.@enum.DataType.UInt16:
+                                    Value = BitConverter.ToUInt16(R(item.AddressName, 2), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Int:
+                                case Core.@enum.DataType.Int32:
+                                    Value = BitConverter.ToInt32(R(item.AddressName, 4), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Uint:
+                                case Core.@enum.DataType.UInt32:
+                                    Value = BitConverter.ToUInt32(R(item.AddressName, 4), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Long:
+                                case Core.@enum.DataType.Int64:
+                                    Value = BitConverter.ToInt64(R(item.AddressName, 8), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Ulong:
+                                case Core.@enum.DataType.UInt64:
+                                    Value = BitConverter.ToUInt64(R(item.AddressName, 8), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Double:
+                                    Value = BitConverter.ToDouble(R(item.AddressName, 8), 0).ToString();
+                                    break;
+                                case Core.@enum.DataType.Float:
+                                    Value = BitConverter.ToSingle(R(item.AddressName, 4), 0).ToString();
+                                    break;
+                                default:
+                                    LogHelper.Error($"[ {item.AddressName} ]读取失败:不支持{item.AddressDataType}类型读取");
+                                    break;
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            LogHelper.Error($"[ {item.AddressName} ]读取异常:{ex.Message}");
                         }
                     }
                     //数据处理

+ 77 - 124
src/YSAI.DAQ/YSAI.S7/S7Operate.cs

@@ -7,6 +7,7 @@ using YSAI.Core.handler;
 using YSAI.Core.@interface;
 using YSAI.Core.subscription;
 using YSAI.Core.virtualAddress;
+using YSAI.Log;
 using YSAI.Unility;
 
 namespace YSAI.S7
@@ -80,6 +81,11 @@ namespace YSAI.S7
         /// </summary>
         private VirtualAddressManage VAM = new VirtualAddressManage();
 
+        /// <summary>
+        /// 令牌
+        /// </summary>
+        private CancellationTokenSource tokenSource;
+
         /// <summary>
         /// S7PLC
         /// </summary>
@@ -104,29 +110,22 @@ namespace YSAI.S7
             Depart("On");
             try
             {
-                if (PlcS7 == null)
+
+                if (PlcS7 != null && PlcS7.IsConnected)
                 {
-                    PlcS7 = new Plc(basics.S7CpuType, basics.Ip, basics.Port, (short)basics.Rack, (short)basics.Slot);
-                    PlcS7.Open();
-                    if (!PlcS7.IsConnected)
-                    {
-                        return Break("On", false, "S7连接失败");
-                    }
-                    return Break("On", true, "S7连接成功");
+                    return Break("On", false, "S7已连接");
                 }
-                else
+                if (tokenSource == null)
                 {
-                    if (PlcS7.IsConnected)
-                    {
-                        return Break("On", false, "S7已连接");
-                    }
-                    else
-                    {
-                        PlcS7.Close();
-                        PlcS7 = null;
-                        return Break("On", false, "已重置S7对象,请重新连接");
-                    }
+                    tokenSource = new CancellationTokenSource();
+                }
+                PlcS7 = new Plc(basics.S7CpuType, basics.Ip, basics.Port, (short)basics.Rack, (short)basics.Slot);
+                PlcS7.Open();
+                if (!PlcS7.IsConnected)
+                {
+                    return Break("On", false, "连接失败");
                 }
+                return Break("On", true, "连接成功");
             }
             catch (Exception ex)
             {
@@ -147,13 +146,21 @@ namespace YSAI.S7
             {
                 if (PlcS7 != null)
                 {
+                    if (tokenSource != null)
+                    {
+                        tokenSource.Cancel();
+                        tokenSource = null;
+                    }
                     PlcS7.Close();
                     PlcS7 = null;
-                    return Break("Off", false, "S7关闭成功");
+
+
+
+                    return Break("Off", true);
                 }
                 else
                 {
-                    return Break("Off", false, "S7未连接");
+                    return Break("Off", false, "未连接");
                 }
             }
             catch (Exception ex)
@@ -196,111 +203,57 @@ namespace YSAI.S7
                         {
                             //获取地址数据项
                             DataItem dataItem = DataItem.FromAddress(item.AddressName);
-                            //switch (dataItem.VarType)
-                            //{
-                            //    case VarType.Bit:   //bool 类型
-                            //        Value = ((bool)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.Byte:  //字节数据
-                            //        Value = ((byte)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.Word:  //二进制
-                            //        Value = ((ushort)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.DWord:  //二进制
-                            //        Value = ((uint)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.Int:  //16位 int 在C# 中指 uint
-                            //        Value = ((uint)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.DInt:  //32位 int 在C# 中指 int
-                            //        Value = ((int)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.Real:  //浮点型  Float
-                            //        Value = ((uint)PlcS7.ReadAsync(item.AddressName).Result).ConvertToFloat().ToString();
-                            //        break;
-                            //    case VarType.LReal:  // 浮点型  double
-                            //        Value = ((double)PlcS7.ReadAsync(item.AddressName).Result).ToDouble().ToString();
-                            //        break;
-                            //    case VarType.String:   //字符串
-                            //    case VarType.S7String:  //字符串
-                            //    case VarType.S7WString:  //字符串
-                            //        byte[]? head = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, 2);
-                            //        byte[]? bytes = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr + 2, head[1]);
-                            //        Value = Encoding.ASCII.GetString(bytes).TrimEnd(new char[] { '\0' });
-                            //        break;
-                            //    case VarType.Timer:  //时间
-                            //        Value = ((double)PlcS7.ReadAsync(item.AddressName).Result).ToDouble().ToString();
-                            //        break;
-                            //    case VarType.Counter:  //计算器?
-                            //        Value = ((ushort)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.DateTime:  //时间类型
-                            //        Value = ((System.DateTime)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //    case VarType.DateTimeLong:   //时间类型
-                            //        Value = ((System.DateTime)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                            //        break;
-                            //}
-
-                            switch (item.AddressDataType)
+
+                            try
+                            {
+                                switch (item.AddressDataType)
+                                {
+                                    case Core.@enum.DataType.Bool:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.Bit, dataItem.Count).Result.ToString();
+                                        break;
+
+                                    case Core.@enum.DataType.String:
+                                        byte[]? head = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, 2);
+                                        byte[]? bytes = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr + 2, head[1]);
+                                        Value = Encoding.ASCII.GetString(bytes).TrimEnd(new char[] { '\0' });
+                                        break;
+
+                                    case Core.@enum.DataType.Byte:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.Byte, dataItem.Count).Result.ToString();
+                                        break;
+
+                                    case Core.@enum.DataType.Double:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.LReal, dataItem.Count).Result.ToString();
+                                        break;
+
+                                    case Core.@enum.DataType.Float:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.Real, dataItem.Count).Result.ToString();
+                                        break;
+                                    case Core.@enum.DataType.Short:
+                                    case Core.@enum.DataType.Int16:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.Int, dataItem.Count).Result.ToString();
+                                        break;
+                                    case Core.@enum.DataType.Ushort:
+                                    case Core.@enum.DataType.UInt16:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.Word, dataItem.Count).Result.ToString();
+                                        break;
+                                    case Core.@enum.DataType.Int:
+                                    case Core.@enum.DataType.Int32:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.DInt, dataItem.Count).Result.ToString();
+                                        break;
+                                    case Core.@enum.DataType.Uint:
+                                    case Core.@enum.DataType.UInt32:
+                                        Value = PlcS7.ReadAsync(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarType.DWord, dataItem.Count).Result.ToString();
+                                        break;
+                                    default:
+                                        LogHelper.Error($"[ {item.AddressName} ]读取失败:不支持{item.AddressDataType}类型读取");
+                                        break;
+                                }
+                            }
+                            catch (Exception ex)
                             {
-                                case Core.@enum.DataType.Bool:
-                                    Value = ((bool)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                                    break;
-
-                                case Core.@enum.DataType.String:
-                                    byte[]? head = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, 2);
-                                    byte[]? bytes = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr + 2, head[1]);
-                                    Value = Encoding.ASCII.GetString(bytes).TrimEnd(new char[] { '\0' });
-                                    break;
-
-                                case Core.@enum.DataType.Byte:
-                                    Value = ((byte)PlcS7.ReadAsync(item.AddressName).Result).ToString();
-                                    break;
-
-                                case Core.@enum.DataType.Double:
-                                    Value = ((ushort)PlcS7.ReadAsync(item.AddressName).Result).ToDouble().ToString();
-                                    break;
-
-                                case Core.@enum.DataType.Float:
-                                    Value = ((uint)PlcS7.ReadAsync(item.AddressName).Result).ConvertToFloat().ToString();
-                                    break;
-
-                                case Core.@enum.DataType.Int:
-                                    Value = ((uint)PlcS7.ReadAsync(item.AddressName).Result).ConvertToInt().ToString();
-                                    break;
-
-                                case Core.@enum.DataType.Uint:
-                                    Value = ((int)PlcS7.ReadAsync(item.AddressName).Result).ConvertToUInt().ToString();
-                                    break;
-
-                                default:
-                                    Value = $"不支持{item.AddressDataType}类型读取";
-                                    break;
+                                LogHelper.Error($"[ {item.AddressName} ]读取异常:{ex.Message}");
                             }
-                            //switch (item.AddressDataType)
-                            //{
-                            //    case Core.@enum.DataType.Bool:
-                            //        Value = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, dataItem.Count).ValToBinString().BinStringToByte().Value.SelectBit(0).ToString();
-                            //        break;
-                            //    case Core.@enum.DataType.String:
-                            //        byte[]? head = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, 2);
-                            //        byte[]? bytes = PlcS7?.ReadBytes(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr + 2, head[1]);
-                            //        Value = Encoding.ASCII.GetString(bytes).TrimEnd(new char[] { '\0' });
-                            //        break;
-                            //    case Core.@enum.DataType.Byte:
-                            //        break;
-                            //    case Core.@enum.DataType.Float:
-                            //        Value = ((uint)PlcS7?.Read(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, dataItem.VarType, dataItem.Count)).ConvertToFloat().ToString();
-                            //        break;
-                            //    case Core.@enum.DataType.Int:
-                            //        Value = ((uint)PlcS7?.Read(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, dataItem.VarType, dataItem.Count)).ConvertToInt().ToString();
-                            //        break;
-                            //    default:
-                            //        Value = $"不支持{item.AddressDataType}类型读取";
-                            //        break;
-                            //}
                         }
 
                         //数据处理

+ 111 - 104
src/YSAI.DAQ/YSAI.Test.Console/Program.cs

@@ -1,67 +1,67 @@
 
-using YSAI.Beckhoff;
-using YSAI.Can;
-using YSAI.Core.data;
-using YSAI.DB;
-using YSAI.Kafka;
-using YSAI.Log;
-using YSAI.Mitsubishi;
-using YSAI.Modbus;
-using YSAI.Mqtt.client;
-using YSAI.NetMQ;
-using YSAI.Netty.client;
-using YSAI.Omron;
-using YSAI.Opc.da.client;
-using YSAI.Opc.da.http;
-using YSAI.Opc.ua.client;
-using YSAI.RabbitMQ;
-using YSAI.S7;
-using YSAI.Unility;
+//using YSAI.Beckhoff;
+//using YSAI.Can;
+//using YSAI.Core.data;
+//using YSAI.DB;
+//using YSAI.Kafka;
+//using YSAI.Log;
+//using YSAI.Mitsubishi;
+//using YSAI.Modbus;
+//using YSAI.Mqtt.client;
+//using YSAI.NetMQ;
+//using YSAI.Netty.client;
+//using YSAI.Omron;
+//using YSAI.Opc.da.client;
+//using YSAI.Opc.da.http;
+//using YSAI.Opc.ua.client;
+//using YSAI.RabbitMQ;
+//using YSAI.S7;
+//using YSAI.Unility;
 
-OperateResult operateResult = new BeckhoffOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"BeckhoffOperate.log");
+//OperateResult operateResult = new BeckhoffOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"BeckhoffOperate.log");
 
-operateResult = new CanOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"CanOperate.log");
+//operateResult = new CanOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"CanOperate.log");
 
-operateResult = new DBOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"DBOperate.log");
+//operateResult = new DBOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"DBOperate.log");
 
-operateResult = new ModbusOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"ModbusOperate.log");
+//operateResult = new ModbusOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"ModbusOperate.log");
 
-operateResult = new OpcUaClientOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OpcUaClientOperate.log");
+//operateResult = new OpcUaClientOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OpcUaClientOperate.log");
 
-operateResult = new OpcDaClientOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OpcDaClientOperate.log");
+//operateResult = new OpcDaClientOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OpcDaClientOperate.log");
 
-operateResult = new OpcDaHttpOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OpcDaHttpOperate.log");
+//operateResult = new OpcDaHttpOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OpcDaHttpOperate.log");
 
-operateResult = new S7Operate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"S7Operate.log");
+//operateResult = new S7Operate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"S7Operate.log");
 
-operateResult = new KafkaOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"KafkaOperate.log");
+//operateResult = new KafkaOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"KafkaOperate.log");
 
-operateResult = new MqttClientOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"MqttClientOperate.log");
+//operateResult = new MqttClientOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"MqttClientOperate.log");
 
-operateResult = new NettyClientOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"NettyClientOperate.log");
+//operateResult = new NettyClientOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"NettyClientOperate.log");
 
-operateResult = new RabbitMQOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"RabbitMQOperate.log");
+//operateResult = new RabbitMQOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"RabbitMQOperate.log");
 
-operateResult = new MitsubishiOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"MitsubishiOperate.log");
+//operateResult = new MitsubishiOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"MitsubishiOperate.log");
 
-operateResult = new OmronOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OmronOperate.log");
+//operateResult = new OmronOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"OmronOperate.log");
 
-operateResult = new NetMQOperate().GetParam();
-LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"NetMQOperate.log");
+//operateResult = new NetMQOperate().GetParam();
+//LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"NetMQOperate.log");
 
 //using System.Collections.Concurrent;
 //using YSAI.Beckhoff;
@@ -355,64 +355,71 @@ LogHelper.Verbose(operateResult.RData.ToJson().JsonFormatting(), $"NetMQOperate.
 //    LogHelper.Info(result.ToJson().JsonFormatting());
 //}
 
-//    S7ClientOperate s7ClientOperate = S7ClientOperate.Instance(new S7ClientData.Basics()
-//    {
-//        Ip = "192.168.2.20",
-//        Port = 102,
-//        Rack = 0,
-//        Slot = 1,
-//        S7CpuType = CpuType.S71200,
-//        ChangeOut = false,
-//        HandleInterval = 1,
-//        SN = Guid.NewGuid().ToString()
-//    }); ;
-
-////打开
-//OperateResult operateResult = s7ClientOperate.On();
-//Console.WriteLine(operateResult.Message);
-
-////点位地址
-//Address address = new Address();
-//address.SN = Guid.NewGuid().ToString();
-//address.CreationTime = DateTime.Now.ToLocalTime();
-//address.AddressArray = new List<AddressDetails>();
-//address.AddressArray.Add(new AddressDetails()
-//{
-//    AddressName = "DB71.DBD4",
-//    AddressDataType = YSAI.Core.@enum.DataType.String,
-//    AddressType = AddressType.Reality
-//});
-
-////读取
-//operateResult = s7ClientOperate.Read(address);
-//Console.WriteLine(operateResult.Message);
-//if (operateResult.State)
-//{
-//    foreach (var item in operateResult.RData as ConcurrentDictionary<string, AddressValue>)
-//    {
-//        Console.WriteLine(item.Key);
-//        Console.WriteLine(item.Value.Value);
-//    }
-//}
+using S7.Net;
+using System.Collections.Concurrent;
+using YSAI.Core.data;
+using YSAI.Core.@enum;
+using YSAI.S7;
+using YSAI.Unility;
 
-////订阅
-//s7ClientOperate.OnEvent += S7ClientOperate_OnEvent;
-//s7ClientOperate.Subscribe(address);
+S7Operate s7ClientOperate = S7Operate.Instance(new S7Data.Basics()
+{
+    Ip = "192.168.2.20",
+    Port = 102,
+    Rack = 0,
+    Slot = 1,
+    S7CpuType = CpuType.S71200,
+    ChangeOut = true,
+    HandleInterval = 1000,
+    SN = Guid.NewGuid().ToString()
+}); ;
 
-//while (true)
-//{
-//   string value= Console.ReadLine();
-//    //写入
-//    ConcurrentDictionary<string, string> keyValuePairs = new ConcurrentDictionary<string, string>();
-//    keyValuePairs.TryAdd("DB71.DBD4", value);
-//    operateResult = s7ClientOperate.Write(keyValuePairs);
-//    Console.WriteLine(operateResult.Message);
-//}
-
-//void S7ClientOperate_OnEvent(object? sender, EventResult e)
-//{
-//    Console.WriteLine(e.ToJson());
-//}
+//打开
+OperateResult operateResult = s7ClientOperate.On();
+Console.WriteLine(operateResult.Message);
+
+//点位地址
+Address address = new Address();
+address.SN = Guid.NewGuid().ToString();
+address.CreationTime = DateTime.Now.ToLocalTime();
+address.AddressArray = new List<AddressDetails>();
+address.AddressArray.Add(new AddressDetails()
+{
+    AddressName = "DB71.DBD4",
+    AddressDataType = YSAI.Core.@enum.DataType.Uint,
+    AddressType = AddressType.Reality
+});
+
+//读取
+operateResult = s7ClientOperate.Read(address);
+Console.WriteLine(operateResult.Message);
+if (operateResult.State)
+{
+    foreach (var item in operateResult.RData as ConcurrentDictionary<string, AddressValue>)
+    {
+        Console.WriteLine(item.Key);
+        Console.WriteLine(item.Value.Value);
+    }
+}
+
+//订阅
+s7ClientOperate.OnEvent += S7ClientOperate_OnEvent;
+s7ClientOperate.Subscribe(address);
+
+while (true)
+{
+    string value = Console.ReadLine();
+    //写入
+    ConcurrentDictionary<string, uint> keyValuePairs = new ConcurrentDictionary<string, uint>();
+    keyValuePairs.TryAdd("DB71.DBD4", string.IsNullOrWhiteSpace(value) ? uint.MaxValue : uint.Parse(value));
+    operateResult = s7ClientOperate.Write(keyValuePairs);
+    Console.WriteLine(operateResult.Message);
+}
+
+void S7ClientOperate_OnEvent(object? sender, EventResult e)
+{
+    Console.WriteLine(e.ToJson());
+}
 
 //WsServiceOperate wsServiceOperate = WsServiceOperate.Instance(new WsServiceData.Basics
 //{

+ 0 - 134
src/YSAI.DAQ/YSAI.Test.Consoles/Program.cs

@@ -1,134 +0,0 @@
-namespace YSAI.Test.Consoles
-{
-    internal class Program
-    {
-        static void Main(string[] args)
-        {
-            ////名称
-            //string name = "Modbus";
-            ////命名空间
-            //string nameSpace = "YSAI.Modbus.ModbusOperate";
-
-            //OperateResult operateResult = ParamHandler.Get(new ModbusData.Basics(), name, nameSpace, subclass: new List<ParamHandler.SubClass>
-            //{
-            //    new ParamHandler.SubClass
-            //    {
-            //        Name= "Modbus-Tcp",
-            //        Properties=new List<ParamStructure.propertie>
-            //        {
-            //            new ParamStructure.propertie
-            //            {
-            //                PropertyName = "ServiceName",
-            //                Description = "命名空间",
-            //                Show = false,
-            //                Use = false,
-            //                Default = nameSpace,
-            //                DataCate = null
-            //            }
-            //        },
-            //        RGroups=new List<ParamHandler.RGroup>
-            //        {
-            //            new ParamHandler.RGroup
-            //            {
-            //                RIndex=2,
-            //                RCount=1
-            //            },
-            //             new ParamHandler.RGroup
-            //            {
-            //                RIndex=10,
-            //                RCount=5
-            //            }
-            //        }
-            //    },new ParamHandler.SubClass
-            //    {
-            //        Name= "Modbus-Udp",
-            //        Properties=new List<ParamStructure.propertie>
-            //        {
-            //            new ParamStructure.propertie
-            //            {
-            //                PropertyName = "ServiceName",
-            //                Description = "命名空间",
-            //                Show = false,
-            //                Use = false,
-            //                Default = nameSpace,
-            //                DataCate = null
-            //            }
-            //        },
-            //        RGroups=new List<ParamHandler.RGroup>
-            //        {
-            //            new ParamHandler.RGroup
-            //            {
-            //                RIndex=2,
-            //                RCount=1
-            //            },
-            //             new ParamHandler.RGroup
-            //            {
-            //                RIndex=10,
-            //                RCount=5
-            //            }
-            //        }
-            //    },
-            //    new ParamHandler.SubClass
-            //    {
-            //        Name= "Modbus-Rtu",
-            //        Properties=new List<ParamStructure.propertie>
-            //        {
-            //            new ParamStructure.propertie
-            //            {
-            //                PropertyName = "ServiceName",
-            //                Description = "命名空间",
-            //                Show = false,
-            //                Use = false,
-            //                Default = nameSpace,
-            //                DataCate = null
-            //            }
-            //        },
-            //        RGroups=new List<ParamHandler.RGroup>
-            //        {
-            //            new ParamHandler.RGroup
-            //            {
-            //                RIndex=2,
-            //                RCount=1
-            //            },
-            //             new ParamHandler.RGroup
-            //            {
-            //                RIndex=7,
-            //                RCount=3
-            //            }
-            //        }
-            //    },
-            //    new ParamHandler.SubClass
-            //    {
-            //        Name= "Modbus-Ascii",
-            //        Properties=new List<ParamStructure.propertie>
-            //        {
-            //            new ParamStructure.propertie
-            //            {
-            //                PropertyName = "ServiceName",
-            //                Description = "命名空间",
-            //                Show = false,
-            //                Use = false,
-            //                Default = nameSpace,
-            //                DataCate = null
-            //            }
-            //        },
-            //        RGroups=new List<ParamHandler.RGroup>
-            //        {
-            //            new ParamHandler.RGroup
-            //            {
-            //                RIndex=2,
-            //                RCount=1
-            //            },
-            //             new ParamHandler.RGroup
-            //            {
-            //                RIndex=7,
-            //                RCount=3
-            //            }
-            //        }
-            //    },
-            //});
-
-            //Console.WriteLine(operateResult.Message);
-        }
-    }
-}

+ 111 - 0
src/YSAI.DAQ/YSAI.Test.Service/Common/DataPersist.cs

@@ -0,0 +1,111 @@
+using Newtonsoft.Json;
+using System.Collections.Concurrent;
+
+namespace YSAI.Test.Service.Common
+{
+    public class DataPersist
+    {
+        string prefix;
+        static ConcurrentDictionary<string, string> data = new ConcurrentDictionary<string, string>();
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="prefix">前缀</param> 
+        public DataPersist(string prefix)
+        {
+            this.prefix = $"iot_{prefix}_";
+        }
+
+        /// <summary>
+        /// 读
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public string Read(string key)
+        {
+            key = prefix + key;
+            if (data.ContainsKey(key))
+            {
+                return data[key];
+            }
+            return string.Empty;
+        }
+
+        public string Read(int key)
+        {
+            return Read(key.ToString());
+        }
+
+        /// <summary>
+        /// 写
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="value"></param>
+        public void Write(string key, string value)
+        {
+            key = prefix + key;
+            if (data.ContainsKey(key))
+            {
+                data[key] = value;
+            }
+            else
+            {
+                data.TryAdd(key, value);
+            }
+        }
+
+        public void Write(int key, string value)
+        {
+            Write(key.ToString(), value);
+        }
+
+        /// <summary>
+        /// 清空
+        /// </summary>
+        public static void Clear()
+        {
+            data = new ConcurrentDictionary<string, string>();
+
+            var filePath = @"C:\IoTClient\ConnectionConfig.Data";
+            if (File.Exists(filePath)) File.Delete(filePath);
+        }
+
+        /// <summary>
+        /// 保存数据
+        /// </summary>
+        public static void SaveData()
+        {
+            var path = @"C:\IoTClient";
+            var filePath = path + @"\IoTClient.Data";
+            var dataString = JsonConvert.SerializeObject(data);
+            using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
+            {
+                using (StreamWriter sw = new StreamWriter(fileStream))
+                {
+                    sw.Write(dataString);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 初始化加载数据
+        /// </summary>
+        public static void LoadData()
+        {
+            var dataString = string.Empty;
+            var path = @"C:\IoTClient";
+            var filePath = path + @"\IoTClient.Data";
+            if (File.Exists(filePath))
+                dataString = File.ReadAllText(filePath);
+            else
+            {
+                if (!Directory.Exists(path))
+                    Directory.CreateDirectory(path);
+                File.SetAttributes(path, FileAttributes.Hidden);
+            }
+            if (!string.IsNullOrWhiteSpace(dataString))
+                data = JsonConvert.DeserializeObject<ConcurrentDictionary<string, string>>(dataString);
+        }
+    }
+}

+ 58 - 0
src/YSAI.DAQ/YSAI.Test.Service/Common/Helpers/CRC16.cs

@@ -0,0 +1,58 @@
+namespace YSAI.Test.Service.Common.Helpers
+{
+    /// <summary>
+    /// CRC16验证
+    /// </summary>
+    public class CRC16
+    {
+        /// <summary>
+        /// 验证CRC16校验码
+        /// </summary>
+        /// <param name="value">校验数据</param>
+        /// <param name="poly">多项式码</param>
+        /// <param name="crcInit">校验码初始值</param>
+        /// <returns></returns>
+        public static bool CheckCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
+        {
+            if (value == null || !value.Any())
+                throw new ArgumentException("生成CRC16的入参有误");
+
+            var crc16 = GetCRC16(value, poly, crcInit);
+            if (crc16[crc16.Length - 2] == crc16[crc16.Length - 1] && crc16[crc16.Length - 1] == 0)
+                return true;
+            return false;
+        }
+
+        /// <summary>
+        /// 计算CRC16校验码
+        /// </summary>
+        /// <param name="value">校验数据</param>
+        /// <param name="poly">多项式码</param>
+        /// <param name="crcInit">校验码初始值</param>
+        /// <returns></returns>
+        public static byte[] GetCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
+        {
+            if (value == null || !value.Any())
+                throw new ArgumentException("生成CRC16的入参有误");
+
+            //运算
+            ushort crc = crcInit;
+            for (int i = 0; i < value.Length; i++)
+            {
+                crc = (ushort)(crc ^ (value[i]));
+                for (int j = 0; j < 8; j++)
+                {
+                    crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
+                }
+            }
+            byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
+            byte lo = (byte)(crc & 0x00FF);         //低位置
+
+            byte[] buffer = new byte[value.Length + 2];
+            value.CopyTo(buffer, 0);
+            buffer[buffer.Length - 1] = hi;
+            buffer[buffer.Length - 2] = lo;
+            return buffer;
+        }
+    }
+}

+ 134 - 0
src/YSAI.DAQ/YSAI.Test.Service/Common/Helpers/DataConvert.cs

@@ -0,0 +1,134 @@
+using System.Text;
+
+namespace YSAI.Test.Service.Common.Helpers
+{
+    /// <summary>
+    /// 数据转换
+    /// </summary>
+    public static class DataConvert
+    {
+        /// <summary>
+        /// 字节数组转16进制字符
+        /// </summary>
+        /// <param name="byteArray"></param>
+        /// <returns></returns>
+        public static string ByteArrayToString(this byte[] byteArray)
+        {
+            return string.Join(" ", byteArray.Select(t => t.ToString("X2")));
+        }
+
+        /// <summary>
+        /// 16进制字符串转字节数组
+        /// </summary>
+        /// <param name="str"></param>
+        /// <param name="strict">严格模式(严格按两个字母间隔一个空格)</param>
+        /// <returns></returns>
+        public static byte[] StringToByteArray(this string str, bool strict = true)
+        {
+            if (string.IsNullOrWhiteSpace(str) || str.Trim().Replace(" ", "").Length % 2 != 0)
+                throw new ArgumentException("请传入有效的参数");
+
+            if (strict)
+            {
+                return str.Split(' ').Where(t => t?.Length == 2).Select(t => Convert.ToByte(t, 16)).ToArray();
+            }
+            else
+            {
+                str = str.Trim().Replace(" ", "");
+                var list = new List<byte>();
+                for (int i = 0; i < str.Length; i++)
+                {
+                    var string16 = str[i].ToString() + str[++i].ToString();
+                    list.Add(Convert.ToByte(string16, 16));
+                }
+                return list.ToArray();
+            }
+        }
+
+        /// <summary>
+        /// Asciis字符串数组字符串装字节数组
+        /// </summary>
+        /// <param name="str"></param>
+        /// <param name="strict"></param>
+        /// <returns></returns>
+        public static byte[] AsciiStringToByteArray(this string str, bool strict = true)
+        {
+            if (string.IsNullOrWhiteSpace(str) || str.Trim().Replace(" ", "").Length % 2 != 0)
+                throw new ArgumentException("请传入有效的参数");
+
+            if (strict)
+            {
+                List<string> stringList = new List<string>();
+                foreach (var item in str.Split(' '))
+                {
+                    stringList.Add(((char)(Convert.ToByte(item, 16))).ToString());
+                }
+                return StringToByteArray(string.Join("", stringList), false);
+            }
+            else
+            {
+                str = str.Trim().Replace(" ", "");
+                var stringList = new List<string>();
+                for (int i = 0; i < str.Length; i++)
+                {
+                    var stringAscii = str[i].ToString() + str[++i].ToString();
+                    stringList.Add(((char)Convert.ToByte(stringAscii, 16)).ToString());
+                }
+                return StringToByteArray(string.Join("", stringList), false);
+            }
+        }
+
+        /// <summary>
+        /// Asciis数组字符串装字节数组
+        /// 如:30 31 =》 00 01
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        public static byte[] AsciiArrayToByteArray(this byte[] str)
+        {
+            if (!str?.Any() ?? true)
+                throw new ArgumentException("请传入有效的参数");
+
+            List<string> stringList = new List<string>();
+            foreach (var item in str)
+            {
+                stringList.Add(((char)item).ToString());
+            }
+            return StringToByteArray(string.Join("", stringList), false);
+        }
+
+        /// <summary>
+        /// 字节数组转换成Ascii字节数组
+        /// 如:00 01 => 30 31
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        public static byte[] ByteArrayToAsciiArray(this byte[] str)
+        {
+            return Encoding.ASCII.GetBytes(string.Join("", str.Select(t => t.ToString("X2"))));
+        }
+
+        /// <summary>
+        /// Int转二进制
+        /// </summary>
+        /// <param name="value"></param>
+        /// <param name="minLength">补0长度</param>
+        /// <returns></returns>
+        public static string IntToBinaryArray(this int value, int minLength = 0)
+        {
+            //Convert.ToString(12,2); // 将12转为2进制字符串,结果 “1100”
+            return Convert.ToString(value, 2).PadLeft(minLength, '0');
+        }
+
+        /// <summary>
+        /// 二进制转Int
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static int BinaryArrayToInt(this string value)
+        {
+            //Convert.ToInt("1100",2); // 将2进制字符串转为整数,结果 12
+            return Convert.ToInt32(value, 2);
+        }
+    }
+}

+ 51 - 0
src/YSAI.DAQ/YSAI.Test.Service/Common/Helpers/LRC.cs

@@ -0,0 +1,51 @@
+namespace YSAI.Test.Service.Common.Helpers
+{
+    /// <summary>
+    /// LRC验证
+    /// </summary>
+    public class LRC
+    {
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static byte[] GetLRC(byte[] value)
+        {
+            if (value == null) return null;
+
+            int sum = 0;
+            for (int i = 0; i < value.Length; i++)
+            {
+                sum += value[i];
+            }
+
+            sum = sum % 256;
+            sum = 256 - sum;
+
+            byte[] LRC = new byte[] { (byte)sum };
+            return value.Concat(LRC).ToArray();
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static bool CheckLRC(byte[] value)
+        {
+            if (value == null) throw new ArgumentNullException("参数为null");
+
+            int length = value.Length;
+            byte[] buffer = new byte[length - 1];
+            Array.Copy(value, 0, buffer, 0, buffer.Length);
+
+            byte[] LRCbuf = GetLRC(buffer);
+            if (LRCbuf[length - 1] == value[length - 1])
+            {
+                return true;
+            }
+            return false;
+        }
+    }
+}

+ 10 - 0
src/YSAI.DAQ/YSAI.Test.Service/Program.cs

@@ -0,0 +1,10 @@
+namespace YSAI.Test.Service
+{
+    internal class Program
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Hello, World!");
+        }
+    }
+}

+ 702 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/BACnet/BACnetServer.cs

@@ -0,0 +1,702 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO.BACnet;
+using System.IO.BACnet.Storage;
+using System.Linq;
+using System.Net.NetworkInformation;
+
+namespace YSAI.Test.Service.Servers.BACnet
+{
+    public class BACnetServer
+    {
+        private DeviceStorage m_storage;
+        private BacnetClient m_ip_server;
+        private uint deviceId = 1234;
+        private static uint increaseId = 1;
+
+        private Dictionary<BacnetObjectId, List<Subscription>> m_subscriptions = new Dictionary<BacnetObjectId, List<Subscription>>();
+        private object m_lockObject = new object();
+        private BacnetSegmentations m_supported_segmentation = BacnetSegmentations.SEGMENTATION_BOTH;
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start(string localEndpointIp = "")
+        {
+            try
+            {
+                //init
+
+                PhysicalAddress macAddr =
+                (
+                    from netiface in NetworkInterface.GetAllNetworkInterfaces()
+                    where ((netiface.OperationalStatus == OperationalStatus.Up) && ((netiface.NetworkInterfaceType == NetworkInterfaceType.Ethernet) || (netiface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)))
+                    select netiface.GetPhysicalAddress()
+                ).FirstOrDefault();
+
+                if (macAddr != null)
+                {
+                    byte[] mac = macAddr.GetAddressBytes();
+                    deviceId = ((uint)mac[5] + (uint)((mac[4] << 8) & 0x3F)) << 6;
+                }
+                // Un bricollage du dimanche vite fait ici !
+                deviceId = deviceId + increaseId++;
+
+                m_storage = DeviceStorage.Load("YSAI.Test.Service.DeviceStorage.xml", deviceId);
+
+                var str = JsonConvert.SerializeObject(m_storage);
+
+                m_storage.ChangeOfValue += new DeviceStorage.ChangeOfValueHandler(m_storage_ChangeOfValue);
+                m_storage.ReadOverride += new DeviceStorage.ReadOverrideHandler(m_storage_ReadOverride);
+
+                //create udp service point
+                BacnetIpUdpProtocolTransport udp_transport = new BacnetIpUdpProtocolTransport(47808, false, localEndpointIp: localEndpointIp);       //set to true to force "single socket" usage
+                m_ip_server = new BacnetClient(udp_transport);
+
+                m_ip_server.OnWhoIs += new BacnetClient.WhoIsHandler(OnWhoIs);
+                m_ip_server.OnReadPropertyRequest += new BacnetClient.ReadPropertyRequestHandler(OnReadPropertyRequest);
+                m_ip_server.OnWritePropertyRequest += new BacnetClient.WritePropertyRequestHandler(OnWritePropertyRequest);
+                m_ip_server.OnReadPropertyMultipleRequest += new BacnetClient.ReadPropertyMultipleRequestHandler(OnReadPropertyMultipleRequest);
+                m_ip_server.OnSubscribeCOV += new BacnetClient.SubscribeCOVRequestHandler(OnSubscribeCOV);
+                m_ip_server.OnSubscribeCOVProperty += new BacnetClient.SubscribeCOVPropertyRequestHandler(OnSubscribeCOVProperty);
+                m_ip_server.OnTimeSynchronize += new BacnetClient.TimeSynchronizeHandler(OnTimeSynchronize);
+                m_ip_server.OnDeviceCommunicationControl += new BacnetClient.DeviceCommunicationControlRequestHandler(OnDeviceCommunicationControl);
+                m_ip_server.OnReinitializedDevice += new BacnetClient.ReinitializedRequestHandler(OnReinitializedDevice);
+                m_ip_server.Start();
+
+                //send greeting
+                m_ip_server.Iam(m_storage.DeviceId, m_supported_segmentation);
+
+            }
+            catch (Exception e)
+            {
+
+            }
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+
+        }
+        public BacnetValue GetBacObjectPresentValue(BacnetObjectId id)
+        {
+            // L'index 0 c'est le nombre de valeurs associées à la propriété
+            // L'index 1 pour la première valeur
+            // L'index System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL pour tout le tableau
+            IList<BacnetValue> val = null;
+            m_storage.ReadProperty(id, BacnetPropertyIds.PROP_PRESENT_VALUE, 1, out val);
+            return val[0];
+        }
+
+        public void SetBacObjectPresentValue(BacnetObjectId id, BacnetValue bv)
+        {
+            // On est sur des valeurs simples, la comparaison est possible ici sans problème
+            if (GetBacObjectPresentValue(id).Value.ToString() == bv.Value.ToString())
+                return;
+
+            // L'index 0 c'est le nombre de valeurs associées à la propriété
+            // L'index 1 pour la première valeur
+            // L'index System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL pour tout le tableau
+            IList<BacnetValue> val = new BacnetValue[1] { bv };
+            m_storage.WriteProperty(id, BacnetPropertyIds.PROP_PRESENT_VALUE, 1, val, true);
+        }
+
+        // Ici les remplacement de la lecture de quelques élements
+        private void m_storage_ReadOverride(BacnetObjectId object_id, BacnetPropertyIds property_id, uint array_index, out IList<BacnetValue> value, out DeviceStorage.ErrorCodes status, out bool handled)
+        {
+            handled = true;
+            value = new BacnetValue[0];
+            status = DeviceStorage.ErrorCodes.Good;
+
+
+            if (object_id.Type == BacnetObjectTypes.OBJECT_DEVICE && property_id == BacnetPropertyIds.PROP_OBJECT_LIST)
+            {
+                if (array_index == 0)
+                {
+                    //object list count 
+                    value = new BacnetValue[] { new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT, (uint)m_storage.Objects.Length) };
+                }
+                else if (array_index != System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL)
+                {
+                    //object list index 
+                    value = new BacnetValue[] { new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID, new BacnetObjectId(m_storage.Objects[array_index - 1].Type, m_storage.Objects[array_index - 1].Instance)) };
+                }
+                else
+                {
+                    //object list whole
+                    BacnetValue[] list = new BacnetValue[m_storage.Objects.Length];
+                    for (int i = 0; i < list.Length; i++)
+                    {
+                        list[i].Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID;
+                        list[i].Value = new BacnetObjectId(m_storage.Objects[i].Type, m_storage.Objects[i].Instance);
+                    }
+                    value = list;
+                }
+            }
+            else if (object_id.Type == BacnetObjectTypes.OBJECT_DEVICE && object_id.Instance == m_storage.DeviceId && property_id == BacnetPropertyIds.PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED)
+            {
+                BacnetValue v = new BacnetValue();
+                v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING;
+                BacnetBitString b = new BacnetBitString();
+                b.SetBit((byte)BacnetObjectTypes.MAX_ASHRAE_OBJECT_TYPE, false); //set all false
+                b.SetBit((byte)BacnetObjectTypes.OBJECT_ANALOG_INPUT, true);
+                b.SetBit((byte)BacnetObjectTypes.OBJECT_DEVICE, true);
+                b.SetBit((byte)BacnetObjectTypes.OBJECT_ANALOG_VALUE, true);
+                b.SetBit((byte)BacnetObjectTypes.OBJECT_CHARACTERSTRING_VALUE, true);
+                b.SetBit((byte)BacnetObjectTypes.OBJECT_MULTI_STATE_VALUE, true);
+                b.SetBit((byte)BacnetObjectTypes.OBJECT_BINARY_VALUE, true);
+                v.Value = b;
+                value = new BacnetValue[] { v };
+            }
+            else if (object_id.Type == BacnetObjectTypes.OBJECT_DEVICE && object_id.Instance == m_storage.DeviceId && property_id == BacnetPropertyIds.PROP_PROTOCOL_SERVICES_SUPPORTED)
+            {
+                BacnetValue v = new BacnetValue();
+                v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING;
+                BacnetBitString b = new BacnetBitString();
+                b.SetBit((byte)BacnetServicesSupported.MAX_BACNET_SERVICES_SUPPORTED, false); //set all false
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_SUBSCRIBE_COV, true);
+
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_I_AM, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_WHO_IS, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_READ_PROP_MULTIPLE, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_READ_PROPERTY, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_WRITE_PROPERTY, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_CONFIRMED_COV_NOTIFICATION, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_UNCONFIRMED_COV_NOTIFICATION, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_REINITIALIZE_DEVICE, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_TIME_SYNCHRONIZATION, true);
+                b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_UTC_TIME_SYNCHRONIZATION, true);
+                v.Value = b;
+                value = new BacnetValue[] { v };
+            }
+            else if (object_id.Type == BacnetObjectTypes.OBJECT_DEVICE && object_id.Instance == m_storage.DeviceId && property_id == BacnetPropertyIds.PROP_SEGMENTATION_SUPPORTED)
+            {
+                BacnetValue v = new BacnetValue();
+                v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED;
+                v.Value = (uint)BacnetSegmentations.SEGMENTATION_BOTH;
+                value = new BacnetValue[] { v };
+            }
+            else if (object_id.Type == BacnetObjectTypes.OBJECT_DEVICE && object_id.Instance == m_storage.DeviceId && property_id == BacnetPropertyIds.PROP_SYSTEM_STATUS)
+            {
+                BacnetValue v = new BacnetValue();
+                v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED;
+                v.Value = (uint)BacnetDeviceStatus.OPERATIONAL;      //can we be in any other mode I wonder?
+                value = new BacnetValue[] { v };
+            }
+            else if (object_id.Type == BacnetObjectTypes.OBJECT_DEVICE && object_id.Instance == m_storage.DeviceId && property_id == BacnetPropertyIds.PROP_ACTIVE_COV_SUBSCRIPTIONS)
+            {
+                List<BacnetValue> list = new List<BacnetValue>();
+                foreach (KeyValuePair<BacnetObjectId, List<Subscription>> entry in m_subscriptions)
+                {
+                    foreach (Subscription sub in entry.Value)
+                    {
+                        //encode
+                        System.IO.BACnet.Serialize.EncodeBuffer buffer = new System.IO.BACnet.Serialize.EncodeBuffer();
+                        BacnetCOVSubscription cov = new BacnetCOVSubscription();
+                        cov.Recipient = sub.reciever_address;
+                        cov.subscriptionProcessIdentifier = sub.subscriberProcessIdentifier;
+                        cov.monitoredObjectIdentifier = sub.monitoredObjectIdentifier;
+                        cov.monitoredProperty = sub.monitoredProperty;
+                        cov.IssueConfirmedNotifications = sub.issueConfirmedNotifications;
+                        cov.TimeRemaining = (uint)sub.lifetime - (uint)(DateTime.Now - sub.start).TotalMinutes;
+                        cov.COVIncrement = sub.covIncrement;
+                        System.IO.BACnet.Serialize.ASN1.encode_cov_subscription(buffer, cov);
+
+                        //add
+                        BacnetValue v = new BacnetValue();
+                        v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_COV_SUBSCRIPTION;
+                        v.Value = buffer.ToArray();
+                        list.Add(v);
+                    }
+                }
+                value = list;
+            }
+            else if (object_id.Type == BacnetObjectTypes.OBJECT_OCTETSTRING_VALUE && object_id.Instance == 0 && property_id == BacnetPropertyIds.PROP_PRESENT_VALUE)
+            {
+                //this is our huge blob
+                BacnetValue v = new BacnetValue();
+                v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING;
+                byte[] blob = new byte[2000];
+                for (int i = 0; i < blob.Length; i++)
+                    blob[i] = (i % 2 == 0) ? (byte)'A' : (byte)'B';
+                v.Value = blob;
+                value = new BacnetValue[] { v };
+            }
+            else if (object_id.Type == BacnetObjectTypes.OBJECT_GROUP && property_id == BacnetPropertyIds.PROP_PRESENT_VALUE)
+            {
+                //get property list
+                IList<BacnetValue> properties;
+                if (m_storage.ReadProperty(object_id, BacnetPropertyIds.PROP_LIST_OF_GROUP_MEMBERS, System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL, out properties) != DeviceStorage.ErrorCodes.Good)
+                {
+                    value = new BacnetValue[] { new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_ERROR, new BacnetError(BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_INTERNAL_ERROR)) };
+                }
+                else
+                {
+                    List<BacnetValue> _value = new List<BacnetValue>();
+                    foreach (BacnetValue p in properties)
+                    {
+                        if (p.Value is BacnetReadAccessSpecification)
+                        {
+                            BacnetReadAccessSpecification prop = (BacnetReadAccessSpecification)p.Value;
+                            BacnetReadAccessResult result = new BacnetReadAccessResult();
+                            result.objectIdentifier = prop.objectIdentifier;
+                            List<BacnetPropertyValue> result_values = new List<BacnetPropertyValue>();
+                            foreach (BacnetPropertyReference r in prop.propertyReferences)
+                            {
+                                BacnetPropertyValue prop_value = new BacnetPropertyValue();
+                                prop_value.property = r;
+                                if (m_storage.ReadProperty(prop.objectIdentifier, (BacnetPropertyIds)r.propertyIdentifier, r.propertyArrayIndex, out prop_value.value) != DeviceStorage.ErrorCodes.Good)
+                                {
+                                    prop_value.value = new BacnetValue[] { new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_ERROR, new BacnetError(BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_INTERNAL_ERROR)) };
+                                }
+                                result_values.Add(prop_value);
+                            }
+                            result.values = result_values;
+                            _value.Add(new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_READ_ACCESS_RESULT, result));
+                        }
+                    }
+                    value = _value;
+                }
+            }
+            else
+            {
+                handled = false;
+            }
+        }
+
+        private class Subscription
+        {
+            public BacnetClient reciever;
+            public BacnetAddress reciever_address;
+            public uint subscriberProcessIdentifier;
+            public BacnetObjectId monitoredObjectIdentifier;
+            public BacnetPropertyReference monitoredProperty;
+            public bool issueConfirmedNotifications;
+            public uint lifetime;
+            public DateTime start;
+            public float covIncrement;
+            public Subscription(BacnetClient reciever, BacnetAddress reciever_address, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, BacnetPropertyReference property, bool issueConfirmedNotifications, uint lifetime, float covIncrement)
+            {
+                this.reciever = reciever;
+                this.reciever_address = reciever_address;
+                this.subscriberProcessIdentifier = subscriberProcessIdentifier;
+                this.monitoredObjectIdentifier = monitoredObjectIdentifier;
+                this.monitoredProperty = property;
+                this.issueConfirmedNotifications = issueConfirmedNotifications;
+                this.lifetime = lifetime;
+                this.start = DateTime.Now;
+                this.covIncrement = covIncrement;
+            }
+            public int GetTimeRemaining()
+            {
+
+                if (lifetime == 0) return 0;
+
+                uint elapse = (uint)(DateTime.Now - start).TotalSeconds;
+
+                if (lifetime > elapse)
+                    return (int)(lifetime - elapse);
+                else
+
+                    return -1;
+
+            }
+        }
+
+        private void RemoveOldSubscriptions()
+        {
+            LinkedList<BacnetObjectId> to_be_deleted = new LinkedList<BacnetObjectId>();
+            foreach (KeyValuePair<BacnetObjectId, List<Subscription>> entry in m_subscriptions)
+            {
+                for (int i = 0; i < entry.Value.Count; i++)
+                {
+                    // Modif F. Chaxel <0 modifié == 0
+                    if (entry.Value[i].GetTimeRemaining() < 0)
+                    {
+                        entry.Value.RemoveAt(i);
+                        i--;
+                    }
+                }
+                if (entry.Value.Count == 0)
+                    to_be_deleted.AddLast(entry.Key);
+            }
+            foreach (BacnetObjectId obj_id in to_be_deleted)
+                m_subscriptions.Remove(obj_id);
+        }
+
+        // C'est que la souscription est ajoutée à la liste
+        // ou retrouvée est remise en état
+        private Subscription HandleSubscriptionRequest(BacnetClient sender, BacnetAddress adr, byte invoke_id, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, uint property_id, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, float covIncrement)
+        {
+            //remove old leftovers
+            RemoveOldSubscriptions();
+
+            //find existing
+            List<Subscription> subs = null;
+            Subscription sub = null;
+            if (m_subscriptions.ContainsKey(monitoredObjectIdentifier))
+            {
+                subs = m_subscriptions[monitoredObjectIdentifier];
+                foreach (Subscription s in subs)
+                {
+                    // Modif FC
+                    if (s.reciever.Equals(sender) && s.reciever_address.Equals(adr) && s.monitoredObjectIdentifier.Equals(monitoredObjectIdentifier) && s.monitoredProperty.propertyIdentifier == property_id)
+                    {
+                        sub = s;
+                        break;
+                    }
+                }
+            }
+
+            //cancel
+            if (cancellationRequest && sub != null)
+            {
+                subs.Remove(sub);
+                if (subs.Count == 0)
+                    m_subscriptions.Remove(sub.monitoredObjectIdentifier);
+
+                //send confirm
+                // F. Chaxel : a supprimer, c'est fait par l'appellant
+                sender.SimpleAckResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, invoke_id);
+
+                return null;
+            }
+
+            //create if needed
+            if (sub == null)
+            {
+                sub = new Subscription(sender, adr, subscriberProcessIdentifier, monitoredObjectIdentifier, new BacnetPropertyReference((uint)BacnetPropertyIds.PROP_ALL, System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL), issueConfirmedNotifications, lifetime, covIncrement);
+
+                if (subs == null)
+                {
+                    subs = new List<Subscription>();
+                    m_subscriptions.Add(sub.monitoredObjectIdentifier, subs);
+                }
+                subs.Add(sub);
+            }
+
+            //update perhaps
+            sub.issueConfirmedNotifications = issueConfirmedNotifications;
+            sub.lifetime = lifetime;
+            sub.start = DateTime.Now;
+
+            return sub;
+        }
+
+        private void OnSubscribeCOV(BacnetClient sender, BacnetAddress adr, byte invoke_id, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetMaxSegments max_segments)
+        {
+
+            lock (m_lockObject)
+            {
+                try
+                {
+                    //create 
+                    Subscription sub = HandleSubscriptionRequest(sender, adr, invoke_id, subscriberProcessIdentifier, monitoredObjectIdentifier, (uint)BacnetPropertyIds.PROP_ALL, cancellationRequest, issueConfirmedNotifications, lifetime, 0);
+
+                    //send confirm
+                    sender.SimpleAckResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, invoke_id);
+
+                    //also send first values
+                    if (!cancellationRequest)
+                    {
+                        System.Threading.ThreadPool.QueueUserWorkItem((o) =>
+                        {
+
+                            IList<BacnetPropertyValue> values;
+                            if (m_storage.ReadPropertyAll(sub.monitoredObjectIdentifier, out values))
+                                if (!sender.Notify(adr, sub.subscriberProcessIdentifier, m_storage.DeviceId, sub.monitoredObjectIdentifier, (uint)sub.GetTimeRemaining(), sub.issueConfirmedNotifications, values))
+                                    Trace.TraceError("Couldn't send notify");
+                        }, null);
+                    }
+                }
+                catch (Exception)
+                {
+                    sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                }
+            }
+        }
+
+        private void OnSubscribeCOVProperty(BacnetClient sender, BacnetAddress adr, byte invoke_id, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, BacnetPropertyReference monitoredProperty, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, float covIncrement, BacnetMaxSegments max_segments)
+        {
+            lock (m_lockObject)
+            {
+                try
+                {
+                    //create 
+                    Subscription sub = HandleSubscriptionRequest(sender, adr, invoke_id, subscriberProcessIdentifier, monitoredObjectIdentifier, (uint)BacnetPropertyIds.PROP_ALL, cancellationRequest, issueConfirmedNotifications, lifetime, covIncrement);
+
+                    //send confirm
+                    sender.SimpleAckResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, invoke_id);
+
+                    //also send first values
+                    if (!cancellationRequest)
+                    {
+                        System.Threading.ThreadPool.QueueUserWorkItem((o) =>
+                        {
+                            IList<BacnetValue> _values;
+                            m_storage.ReadProperty(sub.monitoredObjectIdentifier, (BacnetPropertyIds)sub.monitoredProperty.propertyIdentifier, sub.monitoredProperty.propertyArrayIndex, out _values);
+                            List<BacnetPropertyValue> values = new List<BacnetPropertyValue>();
+                            BacnetPropertyValue tmp = new BacnetPropertyValue();
+                            tmp.property = sub.monitoredProperty;
+                            tmp.value = _values;
+                            values.Add(tmp);
+                            if (!sender.Notify(adr, sub.subscriberProcessIdentifier, m_storage.DeviceId, sub.monitoredObjectIdentifier, (uint)sub.GetTimeRemaining(), sub.issueConfirmedNotifications, values))
+                                Trace.TraceError("Couldn't send notify");
+                        }, null);
+                    }
+                }
+                catch (Exception)
+                {
+                    sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                }
+            }
+        }
+
+        private void m_storage_ChangeOfValue(DeviceStorage sender, BacnetObjectId object_id, BacnetPropertyIds property_id, uint array_index, IList<BacnetValue> value)
+        {
+            System.Threading.ThreadPool.QueueUserWorkItem((o) =>
+            {
+                lock (m_lockObject)
+                {
+                    //remove old leftovers
+                    RemoveOldSubscriptions();
+
+                    //find subscription
+                    if (!m_subscriptions.ContainsKey(object_id)) return;
+                    List<Subscription> subs = m_subscriptions[object_id];
+
+                    //convert
+                    List<BacnetPropertyValue> values = new List<BacnetPropertyValue>();
+                    BacnetPropertyValue tmp = new BacnetPropertyValue();
+                    tmp.property = new BacnetPropertyReference((uint)property_id, array_index);
+                    tmp.value = value;
+                    values.Add(tmp);
+
+                    //send to all
+                    foreach (Subscription sub in subs)
+                    {
+                        if (sub.monitoredProperty.propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL || sub.monitoredProperty.propertyIdentifier == (uint)property_id)
+                        {
+                            //send notify
+                            if (!sub.reciever.Notify(sub.reciever_address, sub.subscriberProcessIdentifier, m_storage.DeviceId, sub.monitoredObjectIdentifier, (uint)sub.GetTimeRemaining(), sub.issueConfirmedNotifications, values))
+                                Trace.TraceError("Couldn't send notify");
+                        }
+                    }
+                }
+            }, null);
+        }
+
+        private void HandleSegmentationResponse(BacnetClient sender, BacnetAddress adr, byte invoke_id, BacnetMaxSegments max_segments, Action<BacnetClient.Segmentation> transmit)
+        {
+            BacnetClient.Segmentation segmentation = sender.GetSegmentBuffer(max_segments);
+
+            //send first
+            transmit(segmentation);
+
+            if (segmentation == null || segmentation.buffer.result == System.IO.BACnet.Serialize.EncodeResult.Good) return;
+
+            //start new thread to handle the segment sequence
+            System.Threading.ThreadPool.QueueUserWorkItem((o) =>
+            {
+                byte old_max_info_frames = sender.Transport.MaxInfoFrames;
+                sender.Transport.MaxInfoFrames = segmentation.window_size;      //increase max_info_frames, to increase throughput. This might be against 'standard'
+                while (true)
+                {
+                    bool more_follows = (segmentation.buffer.result & System.IO.BACnet.Serialize.EncodeResult.NotEnoughBuffer) > 0;
+
+                    //wait for segmentACK
+                    if ((segmentation.sequence_number - 1) % segmentation.window_size == 0 || !more_follows)
+                    {
+                        if (!sender.WaitForAllTransmits(sender.TransmitTimeout))
+                        {
+                            Trace.TraceWarning("Transmit timeout");
+                            break;
+                        }
+                        byte current_number = segmentation.sequence_number;
+                        if (!sender.WaitForSegmentAck(adr, invoke_id, segmentation, sender.Timeout))
+                        {
+                            Trace.TraceWarning("Didn't get segmentACK");
+                            break;
+                        }
+                        if (segmentation.sequence_number != current_number)
+                        {
+                            Trace.WriteLine("Oh, a retransmit", null);
+                            more_follows = true;
+                        }
+                    }
+                    else
+                    {
+                        //a negative segmentACK perhaps
+                        byte current_number = segmentation.sequence_number;
+                        sender.WaitForSegmentAck(adr, invoke_id, segmentation, TimeSpan.FromSeconds(0));      //don't wait
+                        if (segmentation.sequence_number != current_number)
+                        {
+                            Trace.WriteLine("Oh, a retransmit", null);
+                            more_follows = true;
+                        }
+                    }
+
+                    if (more_follows)
+                        lock (m_lockObject) transmit(segmentation);
+                    else
+                        break;
+                }
+                sender.Transport.MaxInfoFrames = old_max_info_frames;
+            });
+        }
+
+        private void OnDeviceCommunicationControl(BacnetClient sender, BacnetAddress adr, byte invoke_id, uint time_duration, uint enable_disable, string password, BacnetMaxSegments max_segments)
+        {
+            switch (enable_disable)
+            {
+                case 0:
+                    Trace.TraceInformation("Enable communication? Sure!");
+                    sender.SimpleAckResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, invoke_id);
+                    break;
+                case 1:
+                    Trace.TraceInformation("Disable communication? ... smile and wave (ignored)");
+                    sender.SimpleAckResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, invoke_id);
+                    break;
+                case 2:
+                    Trace.TraceWarning("Disable initiation? I don't think so!");
+                    sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                    break;
+                default:
+                    Trace.TraceError("Now, what is this device_communication code: " + enable_disable + "!!!!");
+                    sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                    break;
+            }
+        }
+
+        private void OnReinitializedDevice(BacnetClient sender, BacnetAddress adr, byte invoke_id, BacnetReinitializedStates state, string password, BacnetMaxSegments max_segments)
+        {
+            Trace.TraceInformation("So you wanna reboot me, eh? Pfff! (" + state.ToString() + ")");
+            sender.SimpleAckResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE, invoke_id);
+        }
+
+        private void OnTimeSynchronize(BacnetClient sender, BacnetAddress adr, DateTime dateTime, bool utc)
+        {
+            Trace.TraceInformation("Uh, a new date: " + dateTime.ToString());
+        }
+
+        private void OnReadPropertyMultipleRequest(BacnetClient sender, BacnetAddress adr, byte invoke_id, IList<BacnetReadAccessSpecification> properties, BacnetMaxSegments max_segments)
+        {
+            lock (m_lockObject)
+            {
+                try
+                {
+                    IList<BacnetPropertyValue> value;
+                    List<BacnetReadAccessResult> values = new List<BacnetReadAccessResult>();
+                    foreach (BacnetReadAccessSpecification p in properties)
+                    {
+                        if (p.propertyReferences.Count == 1 && p.propertyReferences[0].propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL)
+                        {
+                            if (!m_storage.ReadPropertyAll(p.objectIdentifier, out value))
+                            {
+                                sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, invoke_id, BacnetErrorClasses.ERROR_CLASS_OBJECT, BacnetErrorCodes.ERROR_CODE_UNKNOWN_OBJECT);
+                                return;
+                            }
+                        }
+                        else
+                            m_storage.ReadPropertyMultiple(p.objectIdentifier, p.propertyReferences, out value);
+                        values.Add(new BacnetReadAccessResult(p.objectIdentifier, value));
+                    }
+
+                    HandleSegmentationResponse(sender, adr, invoke_id, max_segments, (seg) =>
+                    {
+                        sender.ReadPropertyMultipleResponse(adr, invoke_id, seg, values);
+                    });
+                }
+                catch (Exception)
+                {
+                    sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                }
+            }
+        }
+
+        private void OnWritePropertyRequest(BacnetClient sender, BacnetAddress adr, byte invoke_id, BacnetObjectId object_id, BacnetPropertyValue value, BacnetMaxSegments max_segments)
+        {
+            BacnetPropertyIds PropId = (BacnetPropertyIds)value.property.propertyIdentifier;
+
+            bool AllowWrite =
+                (object_id.Equals("OBJECT_ANALOG_VALUE:0") && (PropId == BacnetPropertyIds.PROP_OUT_OF_SERVICE)) ||
+                (object_id.Equals("OBJECT_ANALOG_VALUE:0") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||
+                (object_id.Equals("OBJECT_ANALOG_VALUE:1") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||
+                (object_id.Equals("OBJECT_ANALOG_VALUE:2") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||
+                (object_id.Equals("OBJECT_ANALOG_VALUE:3") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||
+                (object_id.Equals("OBJECT_CHARACTERSTRING_VALUE:1") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||
+                (object_id.Equals("OBJECT_CHARACTERSTRING_VALUE:2") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||
+                (object_id.Equals("OBJECT_CHARACTERSTRING_VALUE:3") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||
+                (object_id.Equals("OBJECT_MULTI_STATE_VALUE:0") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE));
+
+            if (AllowWrite == false)
+            {
+                sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_WRITE_ACCESS_DENIED);
+                return;
+            }
+
+            lock (m_lockObject)
+            {
+                try
+                {
+                    // Modif FC
+
+                    DeviceStorage.ErrorCodes code = m_storage.WriteCommandableProperty(object_id, (BacnetPropertyIds)value.property.propertyIdentifier, value.value[0], value.priority);
+
+                    if (code == DeviceStorage.ErrorCodes.NotForMe)
+                        code = m_storage.WriteProperty(object_id, (BacnetPropertyIds)value.property.propertyIdentifier, value.property.propertyArrayIndex, value.value);
+
+                    if (code == DeviceStorage.ErrorCodes.Good)
+                        sender.SimpleAckResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, invoke_id);
+                    else
+                        if (code == DeviceStorage.ErrorCodes.WriteAccessDenied)
+                        sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_WRITE_ACCESS_DENIED);
+                    else
+                        sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                }
+                catch (Exception)
+                {
+                    sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                }
+            }
+        }
+
+        private void OnReadPropertyRequest(BacnetClient sender, BacnetAddress adr, byte invoke_id, BacnetObjectId object_id, BacnetPropertyReference property, BacnetMaxSegments max_segments)
+        {
+            lock (m_lockObject)
+            {
+                try
+                {
+                    IList<BacnetValue> value;
+                    DeviceStorage.ErrorCodes code = m_storage.ReadProperty(object_id, (BacnetPropertyIds)property.propertyIdentifier, property.propertyArrayIndex, out value);
+                    if (code == DeviceStorage.ErrorCodes.Good)
+                        sender.ReadPropertyResponse(adr, invoke_id, sender.GetSegmentBuffer(max_segments), object_id, property, value);
+                    else
+                        sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                }
+                catch (Exception)
+                {
+                    sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, invoke_id, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_OTHER);
+                }
+            }
+        }
+
+        private void OnWhoIs(BacnetClient sender, BacnetAddress adr, int low_limit, int high_limit)
+        {
+            lock (m_lockObject)
+            {
+                if (low_limit != -1 && m_storage.DeviceId < low_limit) return;
+                else if (high_limit != -1 && m_storage.DeviceId > high_limit) return;
+                else sender.Iam(m_storage.DeviceId, m_supported_segmentation);
+            }
+        }
+    }
+
+}

+ 172 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/Modbus/ModBusAsciiServer.cs

@@ -0,0 +1,172 @@
+using Newtonsoft.Json;
+using System.IO.Ports;
+using System.Text;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.Modbus
+{
+    /// <summary>
+    /// ModbusAscii 服务端模拟
+    /// </summary>
+    public class ModbusAsciiServer
+    {
+        private SerialPort serialPort;
+        DataPersist dataPersist;
+        public ModbusAsciiServer(string portName, int baudRate, int dataBits, StopBits stopBits, Parity parity, int timeout = 1500)
+        {
+            if (serialPort == null) serialPort = new SerialPort();
+            serialPort.PortName = portName;
+            serialPort.BaudRate = baudRate;
+            serialPort.DataBits = dataBits;
+            serialPort.StopBits = stopBits;
+            serialPort.Encoding = Encoding.ASCII;
+            serialPort.Parity = parity;
+
+            serialPort.ReadTimeout = timeout;
+            serialPort.WriteTimeout = timeout;
+            serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceived);
+            dataPersist = new DataPersist("ModbusTcpServer");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            serialPort.Open();
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            serialPort.Close();
+            serialPort.Dispose();
+        }
+
+        /// <summary>
+        /// 接收数据回调用
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void DataReceived(object sender, SerialDataReceivedEventArgs e)
+        {
+            try
+            {
+                byte[] requetData = new byte[serialPort.BytesToRead];
+                serialPort.Read(requetData, 0, requetData.Length);
+                var address = $"{requetData[5]}-{requetData[6]}-{requetData[7]}-{requetData[8]}";
+
+                byte[] tempData = new byte[requetData.Length - 3];
+                Buffer.BlockCopy(requetData, 1, tempData, 0, tempData.Length);
+                var requetAsciiData = DataConvert.AsciiArrayToByteArray(tempData);
+
+                var type = $"{requetData[3].ToString("X2")}{requetData[4].ToString("X2")}";
+                switch (type)
+                {
+                    //读取线圈
+                    case "3031":
+                        {
+                            var value = dataPersist.Read(address);
+                            var bytes = JsonConvert.DeserializeObject<byte[]>(value);
+                            byte[] data = null;
+                            //没有存储过的数据的,默认响应false
+                            if (bytes == null)
+                            {
+                                data = DataConvert.StringToByteArray("01 01 01 00");
+                            }
+                            else
+                            {
+                                if (bytes[0].ToString("X2") != "3A" ||
+                                    bytes[bytes.Length - 2].ToString("X2") != "0D" ||
+                                    bytes[bytes.Length - 1].ToString("X2") != "0A")
+                                {
+                                    throw new Exception("标记验证失败");
+                                }
+
+                                byte[] asciiData = new byte[bytes.Length - 3];
+                                Buffer.BlockCopy(bytes, 1, asciiData, 0, asciiData.Length);
+                                var byteDataArray = DataConvert.AsciiArrayToByteArray(asciiData);
+
+                                //true
+                                if (byteDataArray[4].ToString("X2") == "FF" && byteDataArray[5].ToString("X2") == "00")
+                                    data = DataConvert.StringToByteArray("01 01 01 01");
+                                else//false
+                                    data = DataConvert.StringToByteArray("01 01 01 00");
+                            }
+
+                            var dataString = string.Join("", LRC.GetLRC(data).Select(t => t.ToString("X2")));
+                            var databyte = Encoding.ASCII.GetBytes(dataString);
+                            var responseData = new byte[databyte.Length + 3];
+                            Buffer.BlockCopy(databyte, 0, responseData, 1, databyte.Length);
+                            responseData[0] = 0x3A;
+                            responseData[responseData.Length - 2] = 0x0D;
+                            responseData[responseData.Length - 1] = 0x0A;
+
+                            serialPort.Write(responseData, 0, responseData.Length);
+                        }
+                        break;
+                    //写入线圈
+                    case "3035":
+                        {
+                            dataPersist.Write(address, JsonConvert.SerializeObject(requetData));
+                            serialPort.Write(requetData, 0, requetData.Length);
+                        }
+                        break;
+                    //读取
+                    case "3033":
+                        {
+                            var value = dataPersist.Read(address);
+                            var bytes = JsonConvert.DeserializeObject<byte[]>(value);
+                            byte[] data = null;
+                            if (bytes == null)
+                            {
+                                data = new byte[2 + 3];
+                                data[2] = 2;//数据长度
+                            }
+                            else
+                            {
+                                if (bytes[0].ToString("X2") != "3A" ||
+                                    bytes[bytes.Length - 2].ToString("X2") != "0D" ||
+                                    bytes[bytes.Length - 1].ToString("X2") != "0A")
+                                {
+                                    throw new Exception("标记验证失败");
+                                }
+
+                                byte[] asciiData = new byte[bytes.Length - 3];
+                                Buffer.BlockCopy(bytes, 1, asciiData, 0, asciiData.Length);
+                                var byteDataArray = DataConvert.AsciiArrayToByteArray(asciiData);
+                                data = new byte[byteDataArray[6] + 3];
+                                data[2] = byteDataArray[6];//数据长度
+                                Buffer.BlockCopy(byteDataArray, 7, data, 3, data.Length - 3);
+                            }
+                            data[0] = requetAsciiData[0];//站号
+                            data[1] = 0x03;//功能码                           
+
+                            var dataString = string.Join("", LRC.GetLRC(data).Select(t => t.ToString("X2")));
+                            var databyte = Encoding.ASCII.GetBytes(dataString);
+                            var responseData = new byte[databyte.Length + 3];
+                            Buffer.BlockCopy(databyte, 0, responseData, 1, databyte.Length);
+                            responseData[0] = 0x3A;
+                            responseData[responseData.Length - 2] = 0x0D;
+                            responseData[responseData.Length - 1] = 0x0A;
+
+                            serialPort.Write(responseData, 0, responseData.Length);
+                        }
+                        break;
+                    //写入3130 对应十进制16 十六进制 10
+                    case "3130":
+                        {
+                            dataPersist.Write(address, JsonConvert.SerializeObject(requetData));
+                            serialPort.Write(requetData, 0, requetData.Length);
+                        }
+                        break;
+                }
+            }
+            catch (Exception ex)
+            { }
+        }
+    }
+}

+ 134 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/Modbus/ModBusRtuServer.cs

@@ -0,0 +1,134 @@
+using Newtonsoft.Json;
+using System.IO.Ports;
+using System.Text;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.Modbus
+{
+    /// <summary>
+    /// ModbusRtu 服务端模拟
+    /// </summary>
+    public class ModbusRtuServer
+    {
+        private SerialPort serialPort;
+        DataPersist dataPersist;
+        public ModbusRtuServer(string portName, int baudRate, int dataBits, StopBits stopBits, Parity parity, int timeout = 1500)
+        {
+            if (serialPort == null) serialPort = new SerialPort();
+            serialPort.PortName = portName;
+            serialPort.BaudRate = baudRate;
+            serialPort.DataBits = dataBits;
+            serialPort.StopBits = stopBits;
+            serialPort.Encoding = Encoding.ASCII;
+            serialPort.Parity = parity;
+
+            serialPort.ReadTimeout = timeout;
+            serialPort.WriteTimeout = timeout;
+            serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceived);
+            dataPersist = new DataPersist("ModbusTcpServer");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            serialPort.Open();
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            serialPort.Close();
+            serialPort.Dispose();
+        }
+
+        /// <summary>
+        /// 接收数据回调用
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void DataReceived(object sender, SerialDataReceivedEventArgs e)
+        {
+            try
+            {
+                byte[] requetData = new byte[serialPort.BytesToRead];
+                serialPort.Read(requetData, 0, requetData.Length);
+                var address = requetData[2] * 256 + requetData[3];
+
+                var stationNumberKey = $"{requetData[0]}-key";//站号
+                switch (requetData[1])
+                {
+                    //读取线圈
+                    case 1:
+                        {
+                            var value = dataPersist.Read(stationNumberKey + "-Coil");
+                            var byteArray = JsonConvert.DeserializeObject<byte[]>(value) ?? new byte[65536];
+                            var registerLenght = requetData[4] * 256 + requetData[5];
+                            var blenght = (byte)Math.Ceiling(registerLenght / 8f);
+                            var tempData = new byte[registerLenght];
+                            Buffer.BlockCopy(byteArray, address, tempData, 0, tempData.Length);
+                            var rData = new byte[3 + registerLenght];
+                            rData[0] = 1;
+                            rData[1] = 1;
+                            rData[2] = (byte)registerLenght;
+                            for (int i = 0; i < blenght; i++)
+                            {
+                                rData[3 + i] = (byte)DataConvert.BinaryArrayToInt(string.Join("", tempData.Skip(i * 8).Take(8).Reverse()));
+                            }
+                            var responseData = CRC16.GetCRC16(rData);
+                            serialPort.Write(responseData, 0, responseData.Length);
+                        }
+                        break;
+                    //写入线圈
+                    case 5:
+                        {
+                            var value = new byte[2];
+                            Buffer.BlockCopy(requetData, requetData.Length - 4, value, 0, value.Length);
+                            var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(stationNumberKey + "-Coil")) ?? new byte[65536];
+                            if (value[0] == 0 && value[1] == 0)
+                                byteArray[address] = 0;
+                            else
+                                byteArray[address] = 1;
+                            dataPersist.Write(stationNumberKey + "-Coil", JsonConvert.SerializeObject(byteArray));
+                            serialPort.Write(requetData, 0, requetData.Length);
+                        }
+                        break;
+                    //读取
+                    case 3:
+                        {
+                            var value = dataPersist.Read(stationNumberKey);
+                            var byteArray = JsonConvert.DeserializeObject<byte[]>(value) ?? new byte[65536];
+                            var dlength = requetData[4] * 256 + requetData[5];
+                            var dataHead = new byte[] { 1, 3, (byte)(dlength * 2) };
+                            var dataContent = new byte[dlength * 2];
+                            Buffer.BlockCopy(byteArray, address * 2, dataContent, 0, dataContent.Length);
+                            var tempData = dataHead.Concat(dataContent).ToArray();
+                            var responseData = CRC16.GetCRC16(tempData);
+                            serialPort.Write(responseData, 0, responseData.Length);
+                        }
+                        break;
+                    //写入
+                    case 16:
+                        {
+                            var value = new byte[requetData[6]];
+                            Buffer.BlockCopy(requetData, 7, value, 0, value.Length);
+                            var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(stationNumberKey)) ?? new byte[65536];
+                            value.CopyTo(byteArray, address * 2);
+                            dataPersist.Write(stationNumberKey, JsonConvert.SerializeObject(byteArray));
+                            var tempData = new byte[6];
+                            Buffer.BlockCopy(requetData, 0, tempData, 0, tempData.Length);
+                            var responseData = CRC16.GetCRC16(tempData);
+                            serialPort.Write(responseData, 0, responseData.Length);
+                        }
+                        break;
+                }
+            }
+            catch (Exception ex)
+            { }
+        }
+    }
+}

+ 206 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/Modbus/ModBusTcpServer.cs

@@ -0,0 +1,206 @@
+using Newtonsoft.Json;
+using System.Net;
+using System.Net.Sockets;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.Modbus
+{
+    /// <summary>
+    /// ModbusTcp 服务端模拟
+    /// </summary>
+    public class ModbusTcpServer : ServerSocketBase
+    {
+        private Socket socketServer;
+        private string ip;
+        private int port;
+        DataPersist dataPersist;
+        List<Socket> sockets = new List<Socket>();
+        public ModbusTcpServer(int port, string ip = null)
+        {
+            this.ip = ip;
+            this.port = port;
+            dataPersist = new DataPersist("ModbusTcpServer");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            //1 创建Socket对象
+            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            //2 绑定ip和端口 
+            var ipaddress = string.IsNullOrWhiteSpace(ip) ? IPAddress.Any : IPAddress.Parse(ip);
+            IPEndPoint ipEndPoint = new IPEndPoint(ipaddress, port);
+            socketServer.Bind(ipEndPoint);
+
+            //3、开启侦听(等待客户机发出的连接),并设置最大客户k端连接数为10
+            socketServer.Listen(10);
+
+            Task.Run(() => { Accept(socketServer); });
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            if (socketServer?.Connected ?? false) socketServer.Shutdown(SocketShutdown.Both);
+            socketServer?.Close();
+        }
+
+        /// <summary>
+        /// 客户端连接到服务端
+        /// </summary>
+        /// <param name="socket"></param>
+        void Accept(Socket socket)
+        {
+            while (true)
+            {
+                try
+                {
+                    Socket newSocket = null;
+                    try
+                    {
+                        //阻塞等待客户端连接
+                        newSocket = socket.Accept();
+                        sockets.Add(newSocket);
+                    }
+                    catch (SocketException ex)
+                    {
+                        foreach (var item in sockets)
+                        {
+                            if (item.Connected) item.Shutdown(SocketShutdown.Both);
+                            item.Close();
+                        }
+                    }
+                    Task.Run(() => { Receive(newSocket); });
+                }
+                catch (SocketException ex)
+                {
+                    if (ex.SocketErrorCode != SocketError.Interrupted)
+                        throw ex;
+                }
+
+            }
+        }
+
+        /// <summary>
+        /// 接收客户端发送的消息
+        /// </summary>
+        /// <param name="newSocket"></param>
+        void Receive(Socket newSocket)
+        {
+            while (newSocket.Connected)
+            {
+                try
+                {
+                    byte[] requetData1 = new byte[8];
+                    //读取客户端发送过来的数据                  
+                    requetData1 = SocketRead(newSocket, requetData1.Length);
+                    byte[] requetData2 = new byte[requetData1[5] - 2];
+                    requetData2 = SocketRead(newSocket, requetData2.Length);
+                    var requetData = requetData1.Concat(requetData2).ToArray();
+                    byte[] responseData1 = new byte[8];
+                    //复制请求报文中的报文头
+                    Buffer.BlockCopy(requetData, 0, responseData1, 0, responseData1.Length);
+                    var addressKey = $"{requetData[6]}-{requetData[8] * 256 + requetData[9]}";//站号-寄存器地址
+                    var stationNumberKey = $"{requetData[6]}-key";//站号
+                    var address = requetData[8] * 256 + requetData[9];//寄存器地址
+                    var registerLenght = requetData[10] * 256 + requetData[11];//寄存器的长度
+                    switch (requetData[7])
+                    {
+                        //读取线圈
+                        case 1:
+                            {
+                                var value = dataPersist.Read(stationNumberKey + "-Coil");// 数据存在 8、9                            
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(value) ?? new byte[65536];
+
+                                var blenght = (byte)Math.Ceiling(registerLenght / 8f);
+                                //当前位置到最后的长度
+                                responseData1[5] = (byte)(3 + blenght);
+                                byte[] responseData2 = new byte[1 + blenght];
+
+                                var tempData = new byte[registerLenght];
+                                Buffer.BlockCopy(byteArray, address, tempData, 0, tempData.Length);
+                                responseData2[0] = blenght;//显示数据所占长度
+                                for (int i = 0; i < blenght; i++)
+                                {
+                                    responseData2[1 + i] = (byte)DataConvert.BinaryArrayToInt(string.Join("", tempData.Skip(i * 8).Take(8).Reverse()));
+                                }
+                                var responseData = responseData1.Concat(responseData2).ToArray();
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //写入线圈
+                        case 5:
+                            {
+                                var value = new byte[2];
+                                Buffer.BlockCopy(requetData, 10, value, 0, value.Length);
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(stationNumberKey + "-Coil")) ?? new byte[65536];
+                                if (value[0] == 0 && value[1] == 0)
+                                    byteArray[address] = 0;
+                                else
+                                    byteArray[address] = 1;
+                                dataPersist.Write(stationNumberKey + "-Coil", JsonConvert.SerializeObject(byteArray));
+                                var responseData = new byte[10];
+                                Buffer.BlockCopy(requetData, 0, responseData, 0, responseData.Length);
+                                responseData[5] = 4;//后面的长度
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //读取
+                        case 3:
+                            {
+                                var value = dataPersist.Read(stationNumberKey);// 数据存在 8、9                            
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(value) ?? new byte[65536];
+                                //当前位置到最后的长度
+                                responseData1[4] = (byte)((3 + registerLenght * 2) / 256);
+                                responseData1[5] = (byte)((3 + registerLenght * 2) % 256);
+                                byte[] responseData2 = new byte[1 + registerLenght * 2];
+                                responseData2[0] = (byte)(registerLenght * 2);
+                                Buffer.BlockCopy(byteArray, address * 2, responseData2, 1, registerLenght * 2);
+                                var responseData = responseData1.Concat(responseData2).ToArray();
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //写入
+                        case 16:
+                            {
+                                var value = new byte[requetData[12]];
+                                Buffer.BlockCopy(requetData, 13, value, 0, value.Length);
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(stationNumberKey)) ?? new byte[65536];
+                                value.CopyTo(byteArray, address * 2);
+                                dataPersist.Write(stationNumberKey, JsonConvert.SerializeObject(byteArray));
+                                var responseData = new byte[12];
+                                Buffer.BlockCopy(requetData, 0, responseData, 0, responseData.Length);
+                                responseData[5] = 6;//后面的长度
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //读离散
+                        case 2:
+                            {
+                                //只返回false
+                                var bytes = new byte[2];
+                                //当前位置到最后的长度
+                                responseData1[5] = (byte)(2 + bytes.Length);
+                                byte[] responseData2 = new byte[2] { bytes[1], bytes[0] };
+                                var responseData = responseData1.Concat(responseData2).ToArray();
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    //if (newSocket?.Connected ?? false) newSocket?.Shutdown(SocketShutdown.Both);
+                    //newSocket?.Close();
+                    //throw ex;
+                }
+            }
+        }
+    }
+}

+ 192 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/AllenBradleyServer.cs

@@ -0,0 +1,192 @@
+using Newtonsoft.Json;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.PLC
+{
+    public class AllenBradleyServer : ServerSocketBase, IIoTServer
+    {
+        private Socket socketServer;
+        private string ip;
+        private int port;
+        List<Socket> sockets = new List<Socket>();
+        DataPersist dataPersist;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="port"></param>
+        /// <param name="ip"></param>
+        public AllenBradleyServer(int port, string ip = null)
+        {
+            this.ip = ip;
+            this.port = port;
+            dataPersist = new DataPersist("AllenBradley");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            //1 创建Socket对象
+            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            //2 绑定ip和端口 
+            var ipaddress = string.IsNullOrWhiteSpace(ip) ? IPAddress.Any : IPAddress.Parse(ip);
+            IPEndPoint ipEndPoint = new IPEndPoint(ipaddress, port);
+            socketServer.Bind(ipEndPoint);
+
+            //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
+            socketServer.Listen(10);
+
+            Task.Run(() => { Accept(socketServer); });
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            if (socketServer?.Connected ?? false) socketServer.Shutdown(SocketShutdown.Both);
+            socketServer?.Close();
+        }
+
+        /// <summary>
+        /// 客户端连接到服务端
+        /// </summary>
+        /// <param name="socket"></param>
+        void Accept(Socket socket)
+        {
+            while (true)
+            {
+                try
+                {
+                    Socket newSocket = null;
+                    try
+                    {
+                        //阻塞等待客户端连接
+                        newSocket = socket.Accept();
+                        sockets.Add(newSocket);
+                        Task.Run(() => { Receive(newSocket); });
+                    }
+                    catch (SocketException)
+                    {
+                        foreach (var item in sockets)
+                        {
+                            if (item?.Connected ?? false) item.Shutdown(SocketShutdown.Both);
+                            item?.Close();
+                        }
+                    }
+                }
+                catch (SocketException ex)
+                {
+                    if (ex.SocketErrorCode != SocketError.Interrupted)
+                        throw ex;
+                }
+
+            }
+        }
+
+        private byte[] GetBasicCommand()
+        {
+            byte[] command = new byte[28];
+            command[0] = 0x65;
+            command[2] = 0x04;
+            command[24] = 0x01;
+            return command;
+        }
+
+        private bool ByteArryThan(byte[] items1, byte[] items2)
+        {
+            if (items1.Length != items2.Length)
+                return false;
+            else
+            {
+                if (items1[0] != items2[0])
+                    return false;
+                if (items1[2] != items2[2])
+                    return false;
+                if (items1[24] != items2[24])
+                    return false;
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// 接收客户端发送的消息
+        /// </summary>
+        /// <param name="newSocket"></param>
+        void Receive(Socket newSocket)
+        {
+            while (newSocket.Connected)
+            {
+                try
+                {
+                    byte[] completeRequetData = null;
+
+                    byte[] requetData1 = new byte[28];
+                    //读取客户端发送过来的数据                   
+                    requetData1 = SocketRead(newSocket, requetData1.Length);
+                    if (ByteArryThan(requetData1, GetBasicCommand()))
+                    {
+                        //连接握手
+                        var response = DataConvert.StringToByteArray("65 00 04 00 57 01 56 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00");
+                        newSocket.Send(response);
+                        continue;
+                    }
+
+                    byte[] requetData2 = new byte[40 - 28];
+                    requetData2 = SocketRead(newSocket, requetData2.Length);
+
+                    var requetLength = requetData2[requetData2.Length - 1] * 256 + requetData2[requetData2.Length - 2];
+                    byte[] requetData3 = SocketRead(newSocket, requetLength);
+
+                    completeRequetData = requetData1.Concat(requetData2).Concat(requetData3).ToArray();
+
+                    var requst = string.Join(" ", completeRequetData.Select(t => t.ToString("X2")));
+
+                    var address_ASCII_Length = completeRequetData[1 + 26 + 24] * 2 - 2;
+                    var address_ASCII = new byte[address_ASCII_Length];
+                    Buffer.BlockCopy(completeRequetData, 4 + 26 + 24, address_ASCII, 0, address_ASCII_Length);
+                    var address = Encoding.ASCII.GetString(address_ASCII);
+
+                    var CIP_Length = completeRequetData[24 + 24] + completeRequetData[25 + 24] * 256;
+
+                    switch (completeRequetData[26 + 24])
+                    {
+                        //读
+                        case 0x4C:
+                            {
+                                var value_byte = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(address)) ?? new byte[4];
+                                var response = DataConvert.StringToByteArray("66 00 1A 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 02 00 00 00 00 00 B2 00 0A 00 CC 00 00 00 00 00 7B 00 00 00");
+                                BitConverter.GetBytes((ushort)(value_byte.Length + 6)).CopyTo(response, 38);
+                                value_byte.CopyTo(response, 46);
+                                var response_str = string.Join(" ", response.Select(t => t.ToString("X2")));
+                                newSocket.Send(response);
+                            }
+                            break;
+                        //写
+                        case 0x4D:
+                            {
+                                var value_Length = CIP_Length - address_ASCII_Length - 8;
+                                var value_byte = new byte[value_Length];
+                                Buffer.BlockCopy(completeRequetData, 8 + 26 + 24 + address_ASCII.Length, value_byte, 0, value_Length);
+                                //var value = BitConverter.ToInt16(value_byte, 0);
+                                dataPersist.Write(address, JsonConvert.SerializeObject(value_byte));
+                                var response = DataConvert.StringToByteArray("66 00 16 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 02 00 00 00 00 00 B2 00 06 00 CD 00 00 00 00 00");
+                                newSocket.Send(response);
+                            }
+                            break;
+                    }
+                }
+                catch (Exception ex)
+                {
+                }
+            }
+        }
+    }
+}

+ 15 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/IIoTServer.cs

@@ -0,0 +1,15 @@
+namespace YSAI.Test.Service.Servers.PLC
+{
+    public interface IIoTServer
+    {
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        void Start();
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        void Stop();
+    }
+}

+ 245 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/MitsubishiA1EServer.cs

@@ -0,0 +1,245 @@
+using Newtonsoft.Json;
+using System.Net;
+using System.Net.Sockets;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.PLC
+{
+    /// <summary>
+    /// 三菱MC A-1E 服务端模拟
+    /// </summary>
+    public class MitsubishiA1EServer : ServerSocketBase, IIoTServer
+    {
+        private Socket socketServer;
+        private string ip;
+        private int port;
+        List<Socket> sockets = new List<Socket>();
+        DataPersist dataPersist;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="port"></param>
+        /// <param name="ip"></param>
+        public MitsubishiA1EServer(int port, string ip = null)
+        {
+            this.ip = ip;
+            this.port = port;
+            dataPersist = new DataPersist("MitsubishiA1EServer");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            //1 创建Socket对象
+            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            //2 绑定ip和端口 
+            var ipaddress = string.IsNullOrWhiteSpace(ip) ? IPAddress.Any : IPAddress.Parse(ip);
+            IPEndPoint ipEndPoint = new IPEndPoint(ipaddress, port);
+            socketServer.Bind(ipEndPoint);
+
+            //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
+            socketServer.Listen(10);
+
+            Task.Run(() => { Accept(socketServer); });
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            if (socketServer?.Connected ?? false) socketServer.Shutdown(SocketShutdown.Both);
+            socketServer?.Close();
+        }
+
+        /// <summary>
+        /// 客户端连接到服务端
+        /// </summary>
+        /// <param name="socket"></param>
+        void Accept(Socket socket)
+        {
+            while (true)
+            {
+                try
+                {
+                    Socket newSocket = null;
+                    try
+                    {
+                        //阻塞等待客户端连接
+                        newSocket = socket.Accept();
+                        sockets.Add(newSocket);
+                        Task.Run(() => { Receive(newSocket); });
+                    }
+                    catch (SocketException)
+                    {
+                        foreach (var item in sockets)
+                        {
+                            if (item?.Connected ?? false) item.Shutdown(SocketShutdown.Both);
+                            item?.Close();
+                        }
+                    }
+                }
+                catch (SocketException ex)
+                {
+                    if (ex.SocketErrorCode != SocketError.Interrupted)
+                        throw ex;
+                }
+
+            }
+        }
+
+        /// <summary>
+        /// 接收客户端发送的消息
+        /// </summary>
+        /// <param name="newSocket"></param>
+        void Receive(Socket newSocket)
+        {
+            while (newSocket.Connected)
+            {
+                try
+                {
+                    byte[] temp_requetData1 = new byte[12];
+                    //读取客户端发送过来的数据                   
+                    temp_requetData1 = SocketRead(newSocket, temp_requetData1.Length);
+                    byte[] requetData = null;
+                    //0x03写字 - 0x00 读位 0x01 读字  0x02写位 0x03写字
+                    if (temp_requetData1[0] == 0x03)
+                    {
+                        var lenght = temp_requetData1[11] * 2 * 256 + temp_requetData1[10] * 2;
+                        byte[] temp_requetData2 = new byte[lenght];
+                        temp_requetData2 = SocketRead(newSocket, temp_requetData2.Length);
+                        requetData = temp_requetData1.Concat(temp_requetData2).ToArray();
+                    }
+                    //0x02写位
+                    else if (temp_requetData1[0] == 0x02)
+                    {
+                        var lenght = temp_requetData1[11] * 256 + temp_requetData1[10];
+                        byte[] temp_requetData2 = new byte[lenght];
+                        temp_requetData2 = SocketRead(newSocket, temp_requetData2.Length);
+                        requetData = temp_requetData1.Concat(temp_requetData2).ToArray();
+                    }
+                    else
+                        requetData = temp_requetData1;
+                    //地址
+                    var address = $"{requetData[8]}{requetData[9]}-{requetData[5] * 256 + requetData[4]}";
+                    var dataKey = $"{requetData[8]}{requetData[9]}";
+                    var beginAddress = requetData[5] * 256 + requetData[4];
+                    var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(dataKey)) ?? new byte[65536];
+
+                    switch (requetData[0])
+                    {
+                        //读位 0x01 位  0x00 字节                      
+                        case 0x00:
+                            {
+                                //数据存储长度
+                                var lenght = requetData[10] + requetData[11] * 256;
+                                var value = dataPersist.Read(address);
+                                //把存储的数据转为字节数组
+                                var bytes = JsonConvert.DeserializeObject<byte[]>(value);
+                                if (string.IsNullOrWhiteSpace(value))
+                                    bytes = BitConverter.GetBytes(0);
+                                //数据的字节长度
+                                var dataLenght = (int)Math.Ceiling(lenght * 0.5);
+                                // 响应报文总长度
+                                byte[] responseData = new byte[2 + dataLenght];
+                                DataConvert.StringToByteArray("80 00").CopyTo(responseData, 0);
+
+                                if (lenght == 1)
+                                {
+                                    var oldBitValue = byteArray[beginAddress / 2];
+                                    //一个字节存储两个点位数据
+                                    var bitOffset = beginAddress % 2;
+                                    if (bitOffset == 0)
+                                        responseData[2] = (oldBitValue & 0b00010000) != 0 ? (byte)0b00010000 : (byte)0b00000000;
+                                    else
+                                        responseData[2] = (oldBitValue & 0b00000001) != 0 ? (byte)0b00010000 : (byte)0b00000000;
+                                }
+                                else
+                                    Buffer.BlockCopy(byteArray, beginAddress / 2, responseData, 2, dataLenght);
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //读字
+                        case 0x01:
+                            {
+                                var addressLenght = 2;
+
+                                //数据存储长度
+                                var lenght = requetData[10] + requetData[11] * 256;
+                                var value = dataPersist.Read(address);
+                                //数据的字节长度
+                                var dataLenght = lenght * 2;
+                                // 响应报文总长度
+                                byte[] responseData = new byte[2 + dataLenght];
+                                DataConvert.StringToByteArray("81 00").CopyTo(responseData, 0);
+                                Buffer.BlockCopy(byteArray, beginAddress * addressLenght, responseData, 2, dataLenght);
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //写位 0x02写位 0x03写字
+                        case 0x02:
+                            {
+                                ////[12]后面存的是数据
+                                var valueByte = new byte[requetData.Length - 12];
+                                Buffer.BlockCopy(requetData, 12, valueByte, 0, valueByte.Length);
+                                ////一个字节存储两个点位数据
+                                //valueByte.CopyTo(byteArray, beginAddress / 2);
+
+                                var oldBitValue = byteArray[beginAddress / 2];
+                                var bitOffset = beginAddress % 2;//一个字节存储两个点位数据
+                                if (valueByte[0] == 0x10)//true
+                                {
+                                    if (bitOffset == 0)
+                                        oldBitValue = (byte)(oldBitValue | 0b00010000);
+                                    else
+                                        oldBitValue = (byte)(oldBitValue | 0b00000001);
+                                }
+                                else//false
+                                {
+                                    if (bitOffset == 0)
+                                        oldBitValue = (byte)(oldBitValue & ~0b11111110);//修改成0b00000001或0b00000000
+                                    else
+                                        oldBitValue = (byte)(oldBitValue & ~0b11101111);//修改成0b00010000或0b00000000
+                                }
+                                new byte[] { oldBitValue }.CopyTo(byteArray, beginAddress / 2);
+
+                                //存储字节数据到内存
+                                dataPersist.Write(dataKey, JsonConvert.SerializeObject(byteArray));
+                                byte[] responseData1 = DataConvert.StringToByteArray("82 00");
+                                newSocket.Send(responseData1);
+                            }
+                            break;
+                        //写字
+                        case 0x03:
+                            {
+                                var addressLenght = 2;
+
+                                //[12]后面存的是数据
+                                var valueByte = new byte[requetData.Length - 12];
+                                Buffer.BlockCopy(requetData, 12, valueByte, 0, valueByte.Length);
+                                valueByte.CopyTo(byteArray, beginAddress * addressLenght);
+
+                                //存储字节数据到内存
+                                dataPersist.Write(dataKey, JsonConvert.SerializeObject(byteArray));
+
+                                byte[] responseData1 = DataConvert.StringToByteArray("83 00");
+                                newSocket.Send(responseData1);
+                            }
+                            break;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    //if (newSocket?.Connected ?? false) newSocket?.Shutdown(SocketShutdown.Both);
+                    //newSocket?.Close();
+                    //throw ex;
+                }
+            }
+        }
+    }
+}

+ 218 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/MitsubishiQna3EServer.cs

@@ -0,0 +1,218 @@
+using Newtonsoft.Json;
+using System.Net;
+using System.Net.Sockets;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.PLC
+{
+    /// <summary>
+    /// 三菱MC Qna-3E  服务端模拟
+    /// </summary>
+    public class MitsubishiQna3EServer : ServerSocketBase, IIoTServer
+    {
+        private Socket socketServer;
+        private string ip;
+        private int port;
+        List<Socket> sockets = new List<Socket>();
+        DataPersist dataPersist;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="port"></param>
+        /// <param name="ip"></param>
+        public MitsubishiQna3EServer(int port, string ip = null)
+        {
+            this.ip = ip;
+            this.port = port;
+            dataPersist = new DataPersist("MitsubishiQna3EServer");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            //1 创建Socket对象
+            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            //2 绑定ip和端口 
+            var ipaddress = string.IsNullOrWhiteSpace(ip) ? IPAddress.Any : IPAddress.Parse(ip);
+            IPEndPoint ipEndPoint = new IPEndPoint(ipaddress, port);
+            socketServer.Bind(ipEndPoint);
+
+            //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
+            socketServer.Listen(10);
+
+            Task.Run(() => { Accept(socketServer); });
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            if (socketServer?.Connected ?? false) socketServer.Shutdown(SocketShutdown.Both);
+            socketServer?.Close();
+        }
+
+        /// <summary>
+        /// 客户端连接到服务端
+        /// </summary>
+        /// <param name="socket"></param>
+        void Accept(Socket socket)
+        {
+            while (true)
+            {
+                try
+                {
+                    Socket newSocket = null;
+                    try
+                    {
+                        //阻塞等待客户端连接
+                        newSocket = socket.Accept();
+                        sockets.Add(newSocket);
+                        Task.Run(() => { Receive(newSocket); });
+                    }
+                    catch (SocketException)
+                    {
+                        foreach (var item in sockets)
+                        {
+                            if (item?.Connected ?? false) item.Shutdown(SocketShutdown.Both);
+                            item?.Close();
+                        }
+                    }
+                }
+                catch (SocketException ex)
+                {
+                    if (ex.SocketErrorCode != SocketError.Interrupted)
+                        throw ex;
+                }
+
+            }
+        }
+
+        /// <summary>
+        /// 接收客户端发送的消息
+        /// </summary>
+        /// <param name="newSocket"></param>
+        void Receive(Socket newSocket)
+        {
+            while (newSocket.Connected)
+            {
+                try
+                {
+                    byte[] requetData1 = new byte[21];
+                    //读取客户端发送过来的数据                   
+                    requetData1 = SocketRead(newSocket, requetData1.Length);
+                    byte[] requetData = null;
+                    //[12] 0x14 写 0x04 读
+                    if (requetData1[12] == 0x14)
+                    {
+                        var lenght = requetData1[20] * 2 * 256 + requetData1[19] * 2;
+                        //如果是按Bit存储
+                        if (requetData1[13] == 0x01) lenght = 1;
+                        byte[] requetData2 = new byte[lenght];
+                        requetData2 = SocketRead(newSocket, requetData2.Length);
+                        requetData = requetData1.Concat(requetData2).ToArray();
+                    }
+                    else
+                        requetData = requetData1;
+                    //地址
+                    //var address = $"{requetData[18]}-{requetData[17] * 256 * 256 + requetData[16] * 256 + requetData[15]}";
+
+                    var dataKey = $"{requetData[18]}";
+                    var beginAddress = requetData[17] * 256 * 256 + requetData[16] * 256 + requetData[15];
+                    //[13]0x01 位   0x00 字节
+                    var isBit = requetData1[13] == 0x01;
+                    var addressLenght = isBit ? 0.5 : 2;
+
+                    switch (requetData[12])
+                    {
+                        //读
+                        case 0x04:
+                            {
+                                //数据存储长度
+                                var lenght = requetData[19] + requetData[20] * 256;
+                                //var value = dataPersist.Read(address);
+                                //把存储的数据转为字节数组                              
+                                var dataValue = dataPersist.Read(dataKey);
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(dataValue) ?? new byte[65536];
+
+                                //数据的字节长度
+                                var dataLenght = isBit ? (int)Math.Ceiling(lenght * addressLenght) : lenght * 2;
+
+                                // 响应报文总长度
+                                byte[] responseData = new byte[11 + dataLenght];
+
+                                DataConvert.StringToByteArray("D0 00 00 FF FF 03 00 06 00 00 00").CopyTo(responseData, 0);
+                                //responseData1[7][8]存储的是后面还有多少长度
+                                responseData[7] = BitConverter.GetBytes(2 + dataLenght)[0];
+                                responseData[8] = BitConverter.GetBytes(2 + dataLenght)[1];
+                                if (isBit && lenght == 1)
+                                {
+                                    var oldBitValue = byteArray[beginAddress / 2];
+                                    //一个字节存储两个点位数据
+                                    var bitOffset = beginAddress % 2;
+                                    if (bitOffset == 0)
+                                        responseData[11] = (oldBitValue & 0b00010000) != 0 ? (byte)0b00010000 : (byte)0b00000000;
+                                    else
+                                        responseData[11] = (oldBitValue & 0b00000001) != 0 ? (byte)0b00010000 : (byte)0b00000000;
+                                }
+                                else
+                                    Buffer.BlockCopy(byteArray, (int)Math.Floor(beginAddress * addressLenght), responseData, 11, dataLenght);
+
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //写
+                        case 0x14:
+                            {
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(dataKey)) ?? new byte[65536];
+                                //[21]后面存的是数据
+                                var valueByte = new byte[requetData.Length - 21];
+                                Buffer.BlockCopy(requetData, 21, valueByte, 0, valueByte.Length);
+                                //存储字节数据到内存
+                                //dataPersist.Write(address, JsonConvert.SerializeObject(valueByte));
+
+                                if (isBit)
+                                {
+                                    var oldBitValue = byteArray[beginAddress / 2];
+                                    var bitOffset = beginAddress % 2;//一个字节存储两个点位数据
+                                    if (valueByte[0] == 0x10)//true
+                                    {
+                                        if (bitOffset == 0)
+                                            oldBitValue = (byte)(oldBitValue | 0b00010000);
+                                        else
+                                            oldBitValue = (byte)(oldBitValue | 0b00000001);
+                                    }
+                                    else//false
+                                    {
+                                        if (bitOffset == 0)
+                                            oldBitValue = (byte)(oldBitValue & ~0b11111110);//修改成0b00000001或0b00000000
+                                        else
+                                            oldBitValue = (byte)(oldBitValue & ~0b11101111);//修改成0b00010000或0b00000000
+                                    }
+                                    new byte[] { oldBitValue }.CopyTo(byteArray, (int)Math.Floor(beginAddress * addressLenght));
+                                }
+                                else
+                                    valueByte.CopyTo(byteArray, (int)Math.Floor(beginAddress * addressLenght));
+                                dataPersist.Write(dataKey, JsonConvert.SerializeObject(byteArray));
+
+                                byte[] responseData1 = DataConvert.StringToByteArray("D0 00 00 FF FF 03 00 02 00 00 00");
+                                newSocket.Send(responseData1);
+                            }
+                            break;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    //if (newSocket?.Connected ?? false) newSocket?.Shutdown(SocketShutdown.Both);
+                    //newSocket?.Close();
+                    //throw ex;
+                }
+            }
+        }
+    }
+}

+ 229 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/OmronFinsServer.cs

@@ -0,0 +1,229 @@
+using Newtonsoft.Json;
+using System.Net;
+using System.Net.Sockets;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.PLC
+{
+    /// <summary>
+    /// 欧姆龙Fins 服务端模拟
+    /// </summary>
+    public class OmronFinsServer : ServerSocketBase, IIoTServer
+    {
+        private Socket socketServer;
+        private string ip;
+        private int port;
+        List<Socket> sockets = new List<Socket>();
+        DataPersist dataPersist;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="port"></param>
+        /// <param name="ip"></param>
+        public OmronFinsServer(int port, string ip = null)
+        {
+            this.ip = ip;
+            this.port = port;
+            dataPersist = new DataPersist("OmronFinsServer");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            //1 创建Socket对象
+            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            //2 绑定ip和端口 
+            var ipaddress = string.IsNullOrWhiteSpace(ip) ? IPAddress.Any : IPAddress.Parse(ip);
+            IPEndPoint ipEndPoint = new IPEndPoint(ipaddress, port);
+            socketServer.Bind(ipEndPoint);
+
+            //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
+            socketServer.Listen(10);
+
+            Task.Run(() => { Accept(socketServer); });
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            if (socketServer?.Connected ?? false) socketServer.Shutdown(SocketShutdown.Both);
+            socketServer?.Close();
+        }
+
+        /// <summary>
+        /// 客户端连接到服务端
+        /// </summary>
+        /// <param name="socket"></param>
+        void Accept(Socket socket)
+        {
+            while (true)
+            {
+                try
+                {
+                    Socket newSocket = null;
+                    try
+                    {
+                        //阻塞等待客户端连接
+                        newSocket = socket.Accept();
+                        sockets.Add(newSocket);
+                        Task.Run(() => { Receive(newSocket); });
+                    }
+                    catch (SocketException)
+                    {
+                        foreach (var item in sockets)
+                        {
+                            if (item?.Connected ?? false) item.Shutdown(SocketShutdown.Both);
+                            item?.Close();
+                        }
+                    }
+                }
+                catch (SocketException ex)
+                {
+                    if (ex.SocketErrorCode != SocketError.Interrupted)
+                        throw ex;
+                }
+
+            }
+        }
+
+        private byte[] BasicCommand = new byte[]
+        {
+            0x46, 0x49, 0x4E, 0x53,
+            0x00, 0x00, 0x00, 0x0C,// 后面长度
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01
+        };
+
+        private bool ByteArryThan(byte[] items1, byte[] items2)
+        {
+            if (items1.Length != items2.Length)
+                return false;
+            else
+            {
+                //只判断前16个字节
+                for (int i = 0; i < 16; i++)
+                {
+                    if (items1[i] != items2[i])
+                        return false;
+                }
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// 接收客户端发送的消息
+        /// </summary>
+        /// <param name="newSocket"></param>
+        void Receive(Socket newSocket)
+        {
+            while (newSocket.Connected)
+            {
+                try
+                {
+                    byte[] completeRequetData = null;
+
+                    byte[] requetData1 = new byte[20];
+                    //读取客户端发送过来的数据                   
+                    requetData1 = SocketRead(newSocket, requetData1.Length);
+                    if (ByteArryThan(requetData1, BasicCommand))
+                    {
+                        var response = DataConvert.StringToByteArray("46 49 4E 53 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01");
+                        newSocket.Send(response);
+                        continue;
+                    }
+
+                    byte[] requetData2 = new byte[34 - 20];
+                    requetData2 = SocketRead(newSocket, requetData2.Length);
+
+                    completeRequetData = requetData1.Concat(requetData2).ToArray();
+
+                    var isBit = completeRequetData[28] == 0x02 ||
+                      completeRequetData[28] == 0x30 ||
+                      completeRequetData[28] == 0x31 ||
+                      completeRequetData[28] == 0x32 ||
+                      completeRequetData[28] == 0x33;
+
+                    var addressLenght = isBit ? 1 : 2;
+
+                    //1 读 2 写
+                    var readWriteCode = requetData2[27 - 20];
+                    //读写数据长度
+                    var readWriteLength = requetData2[32 - 20] * 256 + requetData2[33 - 20];
+                    if (readWriteCode == 0x02)//写
+                    {
+                        byte[] requetData3 = SocketRead(newSocket, readWriteLength * addressLenght);
+                        completeRequetData = completeRequetData.Concat(requetData3).ToArray();
+                    }
+
+                    var dataKey = $"{completeRequetData[28]}";
+                    var beginAddress = completeRequetData[30] + completeRequetData[29] * 256;
+                    var bitAddress = completeRequetData[31];
+
+                    switch (completeRequetData[27])
+                    {
+                        //读
+                        case 0x01:
+                            {
+                                //把存储的数据转为字节数组                              
+                                var dataValue = dataPersist.Read(dataKey);
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(dataValue) ?? new byte[65536];
+
+                                //数据的字节长度
+                                var dataLenght = readWriteLength * addressLenght;
+
+                                // 响应报文总长度
+                                byte[] responseData = new byte[30 + dataLenght];
+
+                                DataConvert.StringToByteArray("46 49 4E 53 00 00 00 1A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00").CopyTo(responseData, 0);
+                                //存储的是后面还有多少长度
+                                responseData[4] = BitConverter.GetBytes(22 + dataLenght)[3];
+                                responseData[5] = BitConverter.GetBytes(22 + dataLenght)[2];
+                                responseData[6] = BitConverter.GetBytes(22 + dataLenght)[1];
+                                responseData[7] = BitConverter.GetBytes(22 + dataLenght)[0];
+                                if (isBit)
+                                    Buffer.BlockCopy(byteArray, beginAddress * 16 + bitAddress, responseData, 30, dataLenght);
+                                else
+                                    Buffer.BlockCopy(byteArray, beginAddress * addressLenght, responseData, 30, dataLenght);
+
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //写
+                        case 0x02:
+                            {
+                                var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(dataKey)) ?? new byte[65536];
+                                //[32]后面存的是数据
+                                var valueByte = new byte[completeRequetData.Length - 34];
+                                Buffer.BlockCopy(completeRequetData, 34, valueByte, 0, valueByte.Length);
+
+                                //var tempValue1 = BitConverter.ToSingle(valueByte.Reverse().ToArray().ByteFormatting(EndianFormat.CDAB), 0);
+
+                                if (isBit)
+                                    valueByte.CopyTo(byteArray, beginAddress * 16 + bitAddress);
+                                else
+                                    valueByte.CopyTo(byteArray, beginAddress * addressLenght);
+                                dataPersist.Write(dataKey, JsonConvert.SerializeObject(byteArray));
+
+                                newSocket.Send(DataConvert.StringToByteArray("46 49 4E 53 00 00 00 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"));
+                            }
+                            break;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    //if (newSocket?.Connected ?? false) newSocket?.Shutdown(SocketShutdown.Both);
+                    //newSocket?.Close();
+                    //throw ex;
+                }
+            }
+        }
+    }
+}

+ 322 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/PLC/SiemensServer.cs

@@ -0,0 +1,322 @@
+using Newtonsoft.Json;
+using System.Net;
+using System.Net.Sockets;
+using YSAI.Test.Service.Common;
+using YSAI.Test.Service.Common.Helpers;
+
+namespace YSAI.Test.Service.Servers.PLC
+{
+    /// <summary>
+    /// Siemens命令常量
+    /// </summary>
+    public class SiemensConstant
+    {
+        /// <summary>
+        /// Head头读取长度
+        /// </summary>
+        public static readonly ushort InitHeadLength = 4;
+
+        /// <summary>
+        /// 第一次初始化指令交互报文
+        /// </summary>
+        public static readonly byte[] Command1 = new byte[22]
+        {
+            0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,
+            0x00,0x01,0x00,0xC0,0x01,0x0A,0xC1,0x02,
+            0x01,0x02,0xC2,0x02,0x01,0x00
+        };
+
+        /// <summary>
+        /// 第二次初始化指令交互报文
+        /// </summary>
+        public static readonly byte[] Command2 = new byte[25]
+        {
+            0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,
+            0x01,0x00,0x00,0x04,0x00,0x00,0x08,0x00,
+            0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x01,0xE0
+        };
+
+        /// <summary>
+        /// 第一次初始化指令交互报文
+        /// </summary>
+        public static readonly byte[] Command1_200Smart = new byte[22]
+        {
+            0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,
+            0x00,0x01,0x00,0xC1,0x02,0x10,0x00,0xC2,
+            0x02,0x03,0x00,0xC0,0x01,0x0A
+        };
+
+        /// <summary>
+        /// 第二次初始化指令交互报文
+        /// </summary>
+        public static readonly byte[] Command2_200Smart = new byte[25]
+        {
+            0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,
+            0x01,0x00,0x00,0xCC,0xC1,0x00,0x08,0x00,
+            0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x03,0xC0
+        };
+
+        /// <summary>
+        /// 第一次初始化指令交互报文
+        /// </summary>
+        public static readonly byte[] Command1_200 = new byte[]
+        {
+            0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,
+            0x00,0x01,0x00,0xC1,0x02,0x4D,0x57,0xC2,
+            0x02,0x4D,0x57,0xC0,0x01,0x09
+        };
+
+        /// <summary>
+        /// 第二次初始化指令交互报文
+        /// </summary>
+        public static readonly byte[] Command2_200 = new byte[]
+        {
+            0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,
+            0x01,0x00,0x00,0x00,0x00,0x00,0x08,0x00,
+            0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x03,0xC0
+        };
+    }
+    /// <summary>
+    /// 西门子PLC 服务端模拟
+    /// </summary>
+    public class SiemensServer : ServerSocketBase
+    {
+        private Socket socketServer;
+        private string ip;
+        private int port;
+        List<Socket> sockets = new List<Socket>();
+        DataPersist dataPersist;
+        public SiemensServer(int port, string ip = null)
+        {
+            this.ip = ip;
+            this.port = port;
+            dataPersist = new DataPersist("SiemensServer");
+        }
+
+        /// <summary>
+        /// 启动服务
+        /// </summary>
+        public void Start()
+        {
+            //1 创建Socket对象
+            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            //2 绑定ip和端口 
+            var ipaddress = string.IsNullOrWhiteSpace(ip) ? IPAddress.Any : IPAddress.Parse(ip);
+            IPEndPoint ipEndPoint = new IPEndPoint(ipaddress, port);
+            socketServer.Bind(ipEndPoint);
+
+            //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
+            socketServer.Listen(100);
+
+            Task.Run(() => { Accept(socketServer); });
+        }
+
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            if (socketServer?.Connected ?? false) socketServer.Shutdown(SocketShutdown.Both);
+            socketServer?.Close();
+        }
+
+        /// <summary>
+        /// 客户端连接到服务端
+        /// </summary>
+        /// <param name="socket"></param>
+        void Accept(Socket socket)
+        {
+            while (true)
+            {
+                try
+                {
+                    Socket newSocket = null;
+                    try
+                    {
+                        //阻塞等待客户端连接
+                        newSocket = socket.Accept();
+                        sockets.Add(newSocket);
+                        Task.Run(() => { Receive(newSocket); });
+                    }
+                    catch (SocketException)
+                    {
+                        foreach (var item in sockets)
+                        {
+                            if (item?.Connected ?? false) item.Shutdown(SocketShutdown.Both);
+                            item?.Close();
+                        }
+                    }
+                }
+                catch (SocketException ex)
+                {
+                    if (ex.SocketErrorCode != SocketError.Interrupted)
+                        throw ex;
+                }
+
+            }
+        }
+
+        /// <summary>
+        /// 接收客户端发送的消息
+        /// </summary>
+        /// <param name="newSocket"></param>
+        void Receive(Socket newSocket)
+        {
+            while (newSocket.Connected)
+            {
+                try
+                {
+                    byte[] requetData1 = new byte[4];
+                    //读取客户端发送过来的数据                   
+                    requetData1 = SocketRead(newSocket, requetData1.Length);
+                    byte[] requetData2 = new byte[requetData1[2] * 256 + requetData1[3] - 4];
+                    requetData2 = SocketRead(newSocket, requetData2.Length);
+                    var requetData = requetData1.Concat(requetData2).ToArray();
+
+                    //如果是连接的时候发送的两次初始化指令 则直接跳过
+                    if (requetData[3] == SiemensConstant.Command1_200Smart.Length || requetData[3] == SiemensConstant.Command2_200Smart.Length)
+                    {
+                        newSocket.Send(requetData);
+                        continue;
+                    }
+
+                    switch (requetData[17])
+                    {
+                        //读
+                        case 4:
+                            {
+                                //数据块个数
+                                var dbNumber = requetData[18];
+                                //读取数据总长度
+                                var readLength = 0;
+
+                                for (int i = 0; i < dbNumber; i++)
+                                {
+                                    //访问数据的个数,以byte为单位
+                                    var byteLength = requetData[23 + i * 12] * 256 + requetData[24 + i * 12];
+                                    if (byteLength == 1 && i < dbNumber - 1)//非最后一个bit/byte,需要补全。
+                                        byteLength += 1;
+                                    readLength += byteLength;
+                                }
+
+                                var dataContent = new byte[4 * dbNumber + readLength];//数据报文总长度
+                                var cursor = 0;
+                                for (int i = 0; i < dbNumber; i++)
+                                {
+                                    //DB块的偏移量
+                                    var beginAddress = requetData[28 + i * 12] * 256 * 256 + requetData[29 + i * 12] * 256 + requetData[30 + i * 12];
+                                    //访问数据的个数
+                                    var byteLength = requetData[23 + i * 12] * 256 + requetData[24 + i * 12];
+                                    if (byteLength == 1 && i < dbNumber - 1)//非最后一个bit/byte,需要补全。
+                                        byteLength += 1;
+                                    //是否是bit类型
+                                    var isBit = requetData[22 + i * 12] == 0x01;
+                                    //访问数据块的类型(V、I、DB...)
+                                    var dbType = requetData[27 + i * 12];
+                                    var dataKey = $"s200-{dbType}";
+                                    var dataValue = dataPersist.Read(dataKey);
+                                    var byteArray = JsonConvert.DeserializeObject<byte[]>(dataValue) ?? new byte[65536];
+
+                                    //DataConvert.StringToByteArray("FF 09 00 04").CopyTo(dataContent, cursor);
+                                    dataContent[0 + cursor] = 0xFF;
+                                    //dataContent[1 + cursor] = readLength == 1 ? (byte)0x03 : (byte)0x04;//04 byte(字节) 03bit(位)
+                                    dataContent[1 + cursor] = isBit ? (byte)0x03 : (byte)0x04;//04 byte(字节) 03bit(位)
+                                    dataContent[2 + cursor] = (byte)(byteLength / 256);//后半截数据数的Length
+                                    dataContent[3 + cursor] = (byte)(byteLength % 256);//后半截数据数的Length
+                                    if (isBit)//按bit
+                                    {
+                                        var bitOffset = beginAddress % 8;
+                                        byte bitOffsetValue = (byte)Math.Pow(2, bitOffset);
+                                        var oldBitValue = byteArray[beginAddress / 8];
+                                        //转成bit的形式所需的返回值
+                                        var bitValue = (oldBitValue & bitOffsetValue) != 0 ? 0x01 : 0x00;
+                                        //[4 + cursor]返回给客户端的数据
+                                        new byte[] { (byte)bitValue }.CopyTo(dataContent, 4 + cursor);
+                                    }
+                                    else
+                                        //[4 + cursor]返回给客户端的数据
+                                        Buffer.BlockCopy(byteArray, beginAddress / 8, dataContent, 4 + cursor, byteLength);
+
+                                    cursor += 4 + byteLength;
+                                }
+
+                                byte[] responseData = new byte[21 + dataContent.Length];
+                                DataConvert.StringToByteArray("03 00 00 1A 02 F0 80 32 03 00 00 00 01 00 02 00 00 00 00 04 01").CopyTo(responseData, 0);
+                                responseData[8] = 0x03;//1  客户端发送命令 3 服务器回复命令 
+                                responseData[2] = (byte)(responseData.Length / 256);//返回数据长度
+                                responseData[3] = (byte)(responseData.Length % 256);
+                                responseData[15] = (byte)(requetData.Length / 256);//读取数据长度
+                                responseData[16] = (byte)(requetData.Length % 256);
+                                responseData[20] = requetData[18];//读取数据块个数
+                                dataContent.CopyTo(responseData, 21);
+                                newSocket.Send(responseData);
+                            }
+                            break;
+                        //写
+                        case 5:
+                            {
+                                var writesLength = requetData[18];//写如数据的个数 Item count
+                                int cursor = 0;
+                                for (int i = 0; i < writesLength; i++)
+                                {
+                                    //DB块的偏移量(存储数据的地址)
+                                    var beginAddress = requetData[28 + i * 12] * 256 * 256 + requetData[29 + i * 12] * 256 + requetData[30 + i * 12];
+                                    //访问数据块的类型(V、I、DB...)
+                                    var dbType = requetData[27 + i * 12];
+                                    var dataKey = $"s200-{dbType}";
+
+                                    //Data之前的下标
+                                    var dataBeforeIndex = 18 + writesLength * 12;
+                                    //[2 + index]是否是bit类型
+                                    var isBit = requetData[dataBeforeIndex + 4 * (i + 1) - 2 + cursor] == 0x03;
+                                    var coefficient = isBit ? 1 : 8;
+                                    //初始化要写入的字节数组长度
+                                    var writeValue = new byte[requetData[dataBeforeIndex + 4 * (i + 1) + cursor] / coefficient];
+                                    //开始写入的地址(报文中的数据的位置)
+                                    var requetBeginLocation = dataBeforeIndex + 4 * (i + 1) + cursor + 1;
+
+                                    //非最后一个bit,需要补全。
+                                    if (writeValue.Length == 1 && i < writesLength - 1)
+                                        cursor++;
+                                    cursor += writeValue.Length;
+                                    //数据赋值到writeValue中
+                                    Buffer.BlockCopy(requetData, requetBeginLocation, writeValue, 0, writeValue.Length);
+
+                                    var byteArray = JsonConvert.DeserializeObject<byte[]>(dataPersist.Read(dataKey)) ?? new byte[65536];
+                                    if (isBit)
+                                    {
+                                        var oldBitValue = byteArray[beginAddress / 8];
+                                        var bitOffset = beginAddress % 8;
+                                        byte bitOffsetValue = (byte)Math.Pow(2, bitOffset);
+                                        if (writeValue[0] == 0x01)//true
+                                            oldBitValue = (byte)(oldBitValue | bitOffsetValue);//组合bitOffsetValue
+                                        else//false
+                                            oldBitValue = (byte)(oldBitValue & ~bitOffsetValue);//去掉bitOffsetValue
+                                        new byte[] { oldBitValue }.CopyTo(byteArray, beginAddress / 8);
+                                    }
+                                    else
+                                        writeValue.CopyTo(byteArray, beginAddress / 8);
+
+                                    //存储字节数据到内存
+                                    dataPersist.Write(dataKey, JsonConvert.SerializeObject(byteArray));
+                                }
+
+                                byte[] responseData1 = new byte[22];
+                                DataConvert.StringToByteArray("03 00 00 16 02 F0 80 32 03 00 00 00 01 00 02 00 01 00 00 05 01 FF").CopyTo(responseData1, 0);
+                                newSocket.Send(responseData1);
+                            }
+                            break;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    //if (newSocket?.Connected ?? false) newSocket?.Shutdown(SocketShutdown.Both);
+                    //newSocket?.Close();
+                    //throw ex;
+                }
+            }
+        }
+    }
+}
+

+ 44 - 0
src/YSAI.DAQ/YSAI.Test.Service/Servers/ServerSocketBase.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Text;
+
+namespace YSAI.Test.Service.Servers
+{
+    /// <summary>
+    /// ServerSocket基类
+    /// </summary>
+    public class ServerSocketBase
+    {
+        /// <summary>
+        /// 分批缓冲区大小
+        /// </summary>
+        protected const int BufferSize = 4096;
+
+        /// <summary>
+        /// 读取
+        /// </summary>
+        /// <param name="socket">socket</param>
+        /// <param name="receiveCount">读取长度</param>
+        /// <returns></returns>
+        protected byte[] SocketRead(Socket socket, int receiveCount)
+        {
+            byte[] receiveBytes = new byte[receiveCount];
+            int receiveFinish = 0;
+            while (receiveFinish < receiveCount)
+            {
+                // 分批读取
+                int receiveLength = (receiveCount - receiveFinish) >= BufferSize ? BufferSize : (receiveCount - receiveFinish);
+                var readLeng = socket.Receive(receiveBytes, receiveFinish, receiveLength, SocketFlags.None);
+                if (readLeng == 0)
+                {
+                    if (socket.Connected) socket.Shutdown(SocketShutdown.Both);
+                    socket.Close();
+                    throw new Exception("连接已断开");
+                }
+                receiveFinish += readLeng;
+            }
+            return receiveBytes;
+        }
+    }
+}

+ 2 - 2
src/YSAI.DAQ/YSAI.Test.Consoles/YSAI.Test.Consoles.csproj

@@ -8,8 +8,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\YSAI.Modbus\YSAI.Modbus.csproj" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+	<PackageReference Include="Talk.BACnet" Version="1.0.2" />
   </ItemGroup>
 
-
 </Project>