From 79e5b1aee6b808486ca433648c5ca2df3f2eb1d9 Mon Sep 17 00:00:00 2001 From: BennyZhao Date: Thu, 6 May 2021 08:44:30 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81https://github.com/zhaopeiym/IoTClien?= =?UTF-8?q?t/issues/4=202=E3=80=81=E6=96=B0=E5=A2=9EAllenBradleyClient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modbus_Tests/ModBusRtuClient_tests.cs | 133 +- .../Modbus_Tests/ModBusTcpClient_tests.cs | 73 +- .../ModbusRtuOverTcpClient_tests.cs | 219 ++++ .../PLC_Tests/AllenBradleyClient_Tests.cs | 65 + .../PLC_Tests/SiemensClient_Tests.cs | 165 +-- .../Server_Tests/AllenBradleyServer_Tests.cs | 19 + IoTClient/Clients/ModBus/ModBusTcpClient.cs | 8 +- .../Clients/Modbus/ModbusRtuOverTcpClient.cs | 12 +- IoTClient/Clients/PLC/AllenBradleyClient.cs | 734 +++++++++++ .../Clients/PLC/Constants/SiemensConstant.cs | 8 +- IoTClient/Clients/PLC/Enums/SiemensVersion.cs | 2 - IoTClient/Clients/PLC/MitsubishiClient.cs | 323 +++-- .../Clients/PLC/Models/AllenBradleyAddress.cs | 6 + .../Clients/PLC/Models/IMitsubishiMCType.cs | 23 - .../Clients/PLC/Models/MitsubishiA1Type.cs | 49 - .../Clients/PLC/Models/MitsubishiMCAddress.cs | 28 + .../Clients/PLC/Models/MitsubishiMCData.cs | 17 - .../Clients/PLC/Models/MitsubishiMCType.cs | 146 --- .../Clients/PLC/Models/MitsubishiMCWrite.cs | 29 - .../Clients/PLC/Models/OmronFinsAddress.cs | 28 + IoTClient/Clients/PLC/Models/OmronFinsData.cs | 8 - IoTClient/Clients/PLC/Models/OmronFinsType.cs | 32 - .../{SiemensData.cs => SiemensAddress.cs} | 5 +- ...SiemensWrite.cs => SiemensWriteAddress.cs} | 18 +- IoTClient/Clients/PLC/OmronFinsClient.cs | 202 ++- IoTClient/Clients/PLC/SiemensClient.cs | 120 +- IoTClient/IoTClient.csproj | 6 +- IoTClient/IoTClient/IoTClient.xml | 1145 ++++++++++------- IoTClient/Models/MitsubishiA1Data.cs | 14 - IoTClient/{Models => }/Result.cs | 38 +- IoTClient/SerialPortBase.cs | 8 +- IoTClient/SocketBase.cs | 15 +- IoTServer/IoTServer/IoTServer.xml | 29 + IoTServer/Servers/PLC/AllenBradleyServer.cs | 196 +++ README.md | 139 +- 35 files changed, 2848 insertions(+), 1214 deletions(-) create mode 100644 IoTClient.Tests/Modbus_Tests/ModbusRtuOverTcpClient_tests.cs create mode 100644 IoTClient.Tests/PLC_Tests/AllenBradleyClient_Tests.cs create mode 100644 IoTClient.Tests/Server_Tests/AllenBradleyServer_Tests.cs create mode 100644 IoTClient/Clients/PLC/AllenBradleyClient.cs create mode 100644 IoTClient/Clients/PLC/Models/AllenBradleyAddress.cs delete mode 100644 IoTClient/Clients/PLC/Models/IMitsubishiMCType.cs delete mode 100644 IoTClient/Clients/PLC/Models/MitsubishiA1Type.cs create mode 100644 IoTClient/Clients/PLC/Models/MitsubishiMCAddress.cs delete mode 100644 IoTClient/Clients/PLC/Models/MitsubishiMCData.cs delete mode 100644 IoTClient/Clients/PLC/Models/MitsubishiMCType.cs delete mode 100644 IoTClient/Clients/PLC/Models/MitsubishiMCWrite.cs create mode 100644 IoTClient/Clients/PLC/Models/OmronFinsAddress.cs delete mode 100644 IoTClient/Clients/PLC/Models/OmronFinsData.cs delete mode 100644 IoTClient/Clients/PLC/Models/OmronFinsType.cs rename IoTClient/Clients/PLC/Models/{SiemensData.cs => SiemensAddress.cs} (90%) rename IoTClient/Clients/PLC/Models/{SiemensWrite.cs => SiemensWriteAddress.cs} (70%) delete mode 100644 IoTClient/Models/MitsubishiA1Data.cs rename IoTClient/{Models => }/Result.cs (73%) create mode 100644 IoTServer/Servers/PLC/AllenBradleyServer.cs diff --git a/IoTClient.Tests/Modbus_Tests/ModBusRtuClient_tests.cs b/IoTClient.Tests/Modbus_Tests/ModBusRtuClient_tests.cs index e25fbed..e462315 100644 --- a/IoTClient.Tests/Modbus_Tests/ModBusRtuClient_tests.cs +++ b/IoTClient.Tests/Modbus_Tests/ModBusRtuClient_tests.cs @@ -17,41 +17,6 @@ public ModbusRtuClient_tests() client = new ModbusRtuClient("COM3", 9600, 8, StopBits.One, Parity.None); } - [Fact] - public void 批量读取() - { - var list = new List(); - list.Add(new ModbusInput() - { - Address = "2", - DataType = DataTypeEnum.Int16, - FunctionCode = 3, - StationNumber = 1 - }); - list.Add(new ModbusInput() - { - Address = "2", - DataType = DataTypeEnum.Int16, - FunctionCode = 4, - StationNumber = 1 - }); - list.Add(new ModbusInput() - { - Address = "5", - DataType = DataTypeEnum.Int16, - FunctionCode = 3, - StationNumber = 1 - }); - list.Add(new ModbusInput() - { - Address = "199", - DataType = DataTypeEnum.Int16, - FunctionCode = 3, - StationNumber = 1 - }); - var result = client.BatchRead(list); - } - [Fact] public async Task 短连接自动开关() { @@ -119,5 +84,103 @@ public async Task 长连接主动开关() client.Close(); } + + [Fact] + public void 批量读取() + { + var list = new List(); + list.Add(new ModbusInput() + { + Address = "2", + DataType = DataTypeEnum.Int16, + FunctionCode = 3, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "2", + DataType = DataTypeEnum.Int16, + FunctionCode = 4, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "5", + DataType = DataTypeEnum.Int16, + FunctionCode = 3, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "199", + DataType = DataTypeEnum.Int16, + FunctionCode = 3, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "200", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "201", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "202", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "203", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "204", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "205", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "206", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "207", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "208", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + var result = client.BatchRead(list); + } } } diff --git a/IoTClient.Tests/Modbus_Tests/ModBusTcpClient_tests.cs b/IoTClient.Tests/Modbus_Tests/ModBusTcpClient_tests.cs index 4d7d01b..95d33b1 100644 --- a/IoTClient.Tests/Modbus_Tests/ModBusTcpClient_tests.cs +++ b/IoTClient.Tests/Modbus_Tests/ModBusTcpClient_tests.cs @@ -36,7 +36,7 @@ public async Task 短连接自动开关() ulong ulong_number = (ulong)Math.Abs(rnd.Next(int.MinValue, int.MaxValue)); float float_number = rnd.Next(int.MinValue, int.MaxValue) / 100; double double_number = (double)rnd.Next(int.MinValue, int.MaxValue) / 100; - bool coil = int_number % 2 == 0; + bool coil = int_number % 2 == 0; #endregion //写入地址:0 值为:short_number 站号:stationNumber 功能码:默认16(也可以自己传入对应的功能码) @@ -124,14 +124,6 @@ public async Task 长连接主动开关() [Fact] public void 批量读取() { - Dictionary addresses = new Dictionary(); - addresses.Add("2", DataTypeEnum.Int16); - addresses.Add("5", DataTypeEnum.Int16); - addresses.Add("13", DataTypeEnum.Int16); - addresses.Add("19", DataTypeEnum.Int16); - addresses.Add("198", DataTypeEnum.Int16); - addresses.Add("199", DataTypeEnum.Int16); - var list = new List(); list.Add(new ModbusInput() { @@ -161,6 +153,69 @@ public void 批量读取() FunctionCode = 3, StationNumber = 1 }); + list.Add(new ModbusInput() + { + Address = "200", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "201", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "202", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "203", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "204", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "205", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "206", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "207", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "208", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); var result = client.BatchRead(list); } } diff --git a/IoTClient.Tests/Modbus_Tests/ModbusRtuOverTcpClient_tests.cs b/IoTClient.Tests/Modbus_Tests/ModbusRtuOverTcpClient_tests.cs new file mode 100644 index 0000000..fd779d5 --- /dev/null +++ b/IoTClient.Tests/Modbus_Tests/ModbusRtuOverTcpClient_tests.cs @@ -0,0 +1,219 @@ +using IoTClient.Clients.Modbus; +using IoTClient.Enums; +using IoTClient.Models; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using Xunit; + +namespace IoTClient.Tests.Modbus_Tests +{ + public class ModbusRtuOverTcpClient_tests + { + ModbusRtuOverTcpClient client; + byte stationNumber = 2;//站号 + public ModbusRtuOverTcpClient_tests() + { + client = new ModbusRtuOverTcpClient("127.0.0.1", 502); + } + + [Fact] + public async Task 短连接自动开关() + { + Random rnd = new Random((int)Stopwatch.GetTimestamp()); + for (int i = 0; i < 10; i++) + { + #region 生产随机数 + short short_number = (short)rnd.Next(short.MinValue, short.MaxValue); + ushort ushort_number = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + int int_number = rnd.Next(int.MinValue, int.MaxValue); + uint uint_number = (uint)Math.Abs(rnd.Next(int.MinValue, int.MaxValue)); + long long_number = rnd.Next(int.MinValue, int.MaxValue); + ulong ulong_number = (ulong)Math.Abs(rnd.Next(int.MinValue, int.MaxValue)); + float float_number = rnd.Next(int.MinValue, int.MaxValue) / 100; + double double_number = (double)rnd.Next(int.MinValue, int.MaxValue) / 100; + bool coil = int_number % 2 == 0; + #endregion + + //写入地址:0 值为:short_number 站号:stationNumber 功能码:默认16(也可以自己传入对应的功能码) + client.Write("0", short_number, stationNumber, 16); + client.Write("4", ushort_number, stationNumber, 16); + client.Write("8", int_number, stationNumber, 16); + client.Write("12", uint_number, stationNumber, 16); + client.Write("16", long_number, stationNumber, 16); + client.Write("20", ulong_number, stationNumber, 16); + client.Write("24", float_number, stationNumber, 16); + client.Write("28", double_number, stationNumber, 16); + + client.Write("32", coil, stationNumber, 5); + + //写入可能有一定的延时,500毫秒后检验 + await Task.Delay(500); + + //读取地址:0 站号:stationNumber 功能码:默认16(也可以自己传入对应的功能码) + var read_short_number = client.ReadInt16("0", stationNumber, 3).Value; + Assert.True(read_short_number == short_number); + Assert.True(client.ReadUInt16("4", stationNumber, 3).Value == ushort_number); + Assert.True(client.ReadInt32("8", stationNumber, 3).Value == int_number); + Assert.True(client.ReadUInt32("12", stationNumber, 3).Value == uint_number); + Assert.True(client.ReadInt64("16", stationNumber, 3).Value == long_number); + Assert.True(client.ReadUInt64("20", stationNumber, 3).Value == ulong_number); + Assert.True(client.ReadFloat("24", stationNumber, 3).Value == float_number); + Assert.True(client.ReadDouble("28", stationNumber, 3).Value == double_number); + + Assert.True(client.ReadCoil("32", stationNumber, 1).Value == coil); + } + } + + [Fact] + public async Task 长连接主动开关() + { + client.Open(); + + Random rnd = new Random((int)Stopwatch.GetTimestamp()); + for (int i = 0; i < 10; i++) + { + #region 生产随机数 + short short_number = (short)rnd.Next(short.MinValue, short.MaxValue); + ushort ushort_number = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + int int_number = rnd.Next(int.MinValue, int.MaxValue); + uint uint_number = (uint)Math.Abs(rnd.Next(int.MinValue, int.MaxValue)); + long long_number = rnd.Next(int.MinValue, int.MaxValue); + ulong ulong_number = (ulong)Math.Abs(rnd.Next(int.MinValue, int.MaxValue)); + float float_number = rnd.Next(int.MinValue, int.MaxValue) / 100; + double double_number = (double)rnd.Next(int.MinValue, int.MaxValue) / 100; + bool coil = int_number % 2 == 0; + #endregion + + //写入地址:0 值为:short_number 站号:stationNumber 功能码:默认16(也可以自己传入对应的功能码) + client.Write("0", short_number, stationNumber, 16); + client.Write("4", ushort_number, stationNumber, 16); + client.Write("8", int_number, stationNumber, 16); + client.Write("12", uint_number, stationNumber, 16); + client.Write("16", long_number, stationNumber, 16); + client.Write("20", ulong_number, stationNumber, 16); + client.Write("24", float_number, stationNumber, 16); + client.Write("28", double_number, stationNumber, 16); + + client.Write("32", coil, stationNumber, 5); + + //写入可能有一定的延时,500毫秒后检验 + await Task.Delay(500); + + //读取地址:0 站号:stationNumber 功能码:默认16(也可以自己传入对应的功能码) + var read_short_number = client.ReadInt16("0", stationNumber, 3).Value; + Assert.True(read_short_number == short_number); + Assert.True(client.ReadUInt16("4", stationNumber, 3).Value == ushort_number); + Assert.True(client.ReadInt32("8", stationNumber, 3).Value == int_number); + Assert.True(client.ReadUInt32("12", stationNumber, 3).Value == uint_number); + Assert.True(client.ReadInt64("16", stationNumber, 3).Value == long_number); + Assert.True(client.ReadUInt64("20", stationNumber, 3).Value == ulong_number); + Assert.True(client.ReadFloat("24", stationNumber, 3).Value == float_number); + Assert.True(client.ReadDouble("28", stationNumber, 3).Value == double_number); + + Assert.True(client.ReadCoil("32", stationNumber, 1).Value == coil); + } + + client.Close(); + } + + [Fact] + public void 批量读取() + { + var list = new List(); + list.Add(new ModbusInput() + { + Address = "2", + DataType = DataTypeEnum.Int16, + FunctionCode = 3, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "2", + DataType = DataTypeEnum.Int16, + FunctionCode = 4, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "5", + DataType = DataTypeEnum.Int16, + FunctionCode = 3, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "199", + DataType = DataTypeEnum.Int16, + FunctionCode = 3, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "200", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "201", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "202", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "203", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "204", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "205", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "206", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "207", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + list.Add(new ModbusInput() + { + Address = "208", + DataType = DataTypeEnum.Bool, + FunctionCode = 2, + StationNumber = 1 + }); + var result = client.BatchRead(list); + } + } +} diff --git a/IoTClient.Tests/PLC_Tests/AllenBradleyClient_Tests.cs b/IoTClient.Tests/PLC_Tests/AllenBradleyClient_Tests.cs new file mode 100644 index 0000000..d626364 --- /dev/null +++ b/IoTClient.Tests/PLC_Tests/AllenBradleyClient_Tests.cs @@ -0,0 +1,65 @@ +using IoTClient.Clients.PLC; +using System; +using System.Diagnostics; +using Xunit; + +namespace IoTClient.Tests.PLC_Tests +{ + public class AllenBradleyClient_Tests + { + private AllenBradleyClient client; + string ip = string.Empty; + + public AllenBradleyClient_Tests() + { + ip = "127.0.0.1"; + } + + [Theory] + [InlineData(44818)] + public void 长连接主动开关(int port) + { + client = new AllenBradleyClient(ip, port); + + client.Open(); + + #region MyRegion + + Random rnd = new Random((int)Stopwatch.GetTimestamp()); + for (int i = 0; i < 100; i++) + { + short short_number = (short)rnd.Next(short.MinValue, short.MaxValue); + ushort short_number_1 = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + + int int_number = rnd.Next(int.MinValue, int.MaxValue); + uint int_number_1 = (uint)rnd.Next(0, int.MaxValue); + + float float_number = rnd.Next(-100000, 100000) / 100; + + var bool_value = short_number % 2 == 1; + + client.Write("A1", bool_value); + Assert.True(client.ReadBoolean("A1").Value == bool_value); + client.Write("A1", !bool_value); + Assert.True(client.ReadBoolean("A1").Value == !bool_value); + + client.Write("A1", short_number); + Assert.True(client.ReadInt16("A1").Value == short_number); + client.Write("A1", short_number_1); + Assert.True(client.ReadUInt16("A1").Value == short_number_1); + + client.Write("A1", int_number); + Assert.True(client.ReadInt32("A1").Value == int_number); + client.Write("A1", int_number_1); + Assert.True(client.ReadUInt32("A1").Value == int_number_1); + + client.Write("A1", Convert.ToSingle(float_number)); + Assert.True(client.ReadFloat("A1").Value == Convert.ToSingle(float_number)); + + } + #endregion + + client.Close(); + } + } +} diff --git a/IoTClient.Tests/PLC_Tests/SiemensClient_Tests.cs b/IoTClient.Tests/PLC_Tests/SiemensClient_Tests.cs index ca252aa..0dc80bc 100644 --- a/IoTClient.Tests/PLC_Tests/SiemensClient_Tests.cs +++ b/IoTClient.Tests/PLC_Tests/SiemensClient_Tests.cs @@ -1,11 +1,11 @@ using IoTClient.Clients.PLC; using IoTClient.Common.Enums; -using System.Net; -using Xunit; -using IoTServer.Common; -using System.Collections.Generic; using IoTClient.Enums; using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using Xunit; namespace IoTClient.Tests.PLCTests { @@ -24,80 +24,101 @@ public SiemensClient_Tests() [Fact] public void 短连接自动开关() { - var value = true; - var result = client.Write("Q1.3", value); - Assert.True(client.ReadBoolean("Q1.3").Value); - value = false; - client.Write("Q1.3", value); - Assert.False(client.ReadBoolean("Q1.3").Value); - - short value_short = 11; - client.Write("V2205", value_short); - Assert.True(client.ReadInt16("V2205").Value == value_short); - - short value_short_1 = -11; - client.Write("V2205", value_short_1); - Assert.True(client.ReadInt16("V2205").Value == value_short_1); - - int value_int = 33; - client.Write("V2205", value_int); - Assert.True(client.ReadInt32("V2205").Value == value_int); - - long value_long = 44; - client.Write("V2205", value_long); - Assert.True(client.ReadInt64("V2205").Value == value_long); - - float value_float = 44.5f; - client.Write("V2205", value_float); - Assert.True(client.ReadFloat("V2205").Value == value_float); - - double value_double = 44.5d; - client.Write("V2205", value_double); - Assert.True(client.ReadDouble("V2205").Value == value_double); - - string value_string = "BennyZhao"; - client.Write("V2205", value_string); - Assert.True(client.ReadString("V2205").Value == value_string); + Random rnd = new Random((int)Stopwatch.GetTimestamp()); + for (int i = 0; i < 100; i++) + { + short short_number = (short)rnd.Next(short.MinValue, short.MaxValue); + ushort short_number_1 = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + + int int_number = rnd.Next(int.MinValue, int.MaxValue); + uint int_number_1 = (uint)rnd.Next(0, int.MaxValue); + + float float_number = int_number / 100; + var bool_value = short_number % 2 == 1; + + string value_string = "BennyZhao"+ float_number; + + client.Write("Q1.3", bool_value); + Assert.True(client.ReadBoolean("Q1.3").Value == bool_value); + client.Write("Q1.4", bool_value); + Assert.True(client.ReadBoolean("Q1.4").Value == bool_value); + client.Write("Q1.5", !bool_value); + Assert.True(client.ReadBoolean("Q1.5").Value == !bool_value); + + client.Write("V100", short_number); + Assert.True(client.ReadInt16("V100").Value == short_number); + client.Write("V100", short_number_1); + Assert.True(client.ReadUInt16("V100").Value == short_number_1); + + client.Write("V100", int_number); + Assert.True(client.ReadInt32("V100").Value == int_number); + client.Write("V100", int_number_1); + Assert.True(client.ReadUInt32("V100").Value == int_number_1); + + client.Write("V100", Convert.ToInt64(int_number)); + Assert.True(client.ReadInt64("V100").Value == Convert.ToInt64(int_number)); + client.Write("V100", Convert.ToUInt64(int_number_1)); + Assert.True(client.ReadUInt64("V100").Value == Convert.ToUInt64(int_number_1)); + + client.Write("V200", float_number); + Assert.True(client.ReadFloat("V200").Value == float_number); + client.Write("V300", Convert.ToDouble(float_number)); + Assert.True(client.ReadDouble("V300").Value == Convert.ToDouble(float_number)); + + client.Write("V2205", value_string); + Assert.True(client.ReadString("V2205").Value == value_string); + } } [Fact] public void 长连接主动开关() { client.Open(); - var value = true; - client.Write("Q1.3", value); - Assert.True(client.ReadBoolean("Q1.3").Value); - value = false; - client.Write("Q1.3", value); - Assert.False(client.ReadBoolean("Q1.3").Value); - - short value_short = 11; - client.Write("V2205", value_short); - Assert.True(client.ReadInt16("V2205").Value == value_short); - - short value_short_1 = -11; - client.Write("V2205", value_short_1); - Assert.True(client.ReadInt16("V2205").Value == value_short_1); - - int value_int = 33; - client.Write("V2205", value_int); - Assert.True(client.ReadInt32("V2205").Value == value_int); - - long value_long = 44; - client.Write("V2205", value_long); - Assert.True(client.ReadInt64("V2205").Value == value_long); - - float value_float = 44.5f; - client.Write("V2205", value_float); - Assert.True(client.ReadFloat("V2205").Value == value_float); - - double value_double = 44.5d; - client.Write("V2205", value_double); - Assert.True(client.ReadDouble("V2205").Value == value_double); - - string value_string = "BennyZhao"; - client.Write("V2205", value_string); - Assert.True(client.ReadString("V2205").Value == value_string); + + Random rnd = new Random((int)Stopwatch.GetTimestamp()); + for (int i = 0; i < 100; i++) + { + short short_number = (short)rnd.Next(short.MinValue, short.MaxValue); + ushort short_number_1 = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + + int int_number = rnd.Next(int.MinValue, int.MaxValue); + uint int_number_1 = (uint)rnd.Next(0, int.MaxValue); + + float float_number = int_number / 100; + var bool_value = short_number % 2 == 1; + + string value_string = "BennyZhao" + float_number; + + client.Write("Q1.3", bool_value); + Assert.True(client.ReadBoolean("Q1.3").Value == bool_value); + client.Write("Q1.4", bool_value); + Assert.True(client.ReadBoolean("Q1.4").Value == bool_value); + client.Write("Q1.5", !bool_value); + Assert.True(client.ReadBoolean("Q1.5").Value == !bool_value); + + client.Write("V100", short_number); + Assert.True(client.ReadInt16("V100").Value == short_number); + client.Write("V100", short_number_1); + Assert.True(client.ReadUInt16("V100").Value == short_number_1); + + client.Write("V100", int_number); + Assert.True(client.ReadInt32("V100").Value == int_number); + client.Write("V100", int_number_1); + Assert.True(client.ReadUInt32("V100").Value == int_number_1); + + client.Write("V100", Convert.ToInt64(int_number)); + Assert.True(client.ReadInt64("V100").Value == Convert.ToInt64(int_number)); + client.Write("V100", Convert.ToUInt64(int_number_1)); + Assert.True(client.ReadUInt64("V100").Value == Convert.ToUInt64(int_number_1)); + + client.Write("V200", float_number); + Assert.True(client.ReadFloat("V200").Value == float_number); + client.Write("V300", Convert.ToDouble(float_number)); + Assert.True(client.ReadDouble("V300").Value == Convert.ToDouble(float_number)); + + client.Write("V2205", value_string); + Assert.True(client.ReadString("V2205").Value == value_string); + } client?.Close(); } diff --git a/IoTClient.Tests/Server_Tests/AllenBradleyServer_Tests.cs b/IoTClient.Tests/Server_Tests/AllenBradleyServer_Tests.cs new file mode 100644 index 0000000..9638f48 --- /dev/null +++ b/IoTClient.Tests/Server_Tests/AllenBradleyServer_Tests.cs @@ -0,0 +1,19 @@ +using IoTServer.Servers.PLC; +using System.Threading.Tasks; +using Xunit; + +namespace IoTClient.Tests.Server_Tests +{ + public class AllenBradleyServer_Tests + { + [Fact] + public async Task StartAsync() + { + AllenBradleyServer server = new AllenBradleyServer(44818); + + server.Start(); + + await Task.Delay(1000 * 1000); + } + } +} diff --git a/IoTClient/Clients/ModBus/ModBusTcpClient.cs b/IoTClient/Clients/ModBus/ModBusTcpClient.cs index 6dd2f4a..70d993e 100644 --- a/IoTClient/Clients/ModBus/ModBusTcpClient.cs +++ b/IoTClient/Clients/ModBus/ModBusTcpClient.cs @@ -58,7 +58,7 @@ public ModbusTcpClient(string ip, int port, int timeout = 1500, EndianFormat for protected override Result Connect() { var result = new Result(); - socket?.Close(); + socket?.SafeClose(); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { @@ -72,15 +72,17 @@ protected override Result Connect() //连接 socket.Connect(ipAndPoint); - return result.EndTime(); } catch (Exception ex) { socket?.SafeClose(); result.IsSucceed = false; result.Err = ex.Message; - return result.EndTime(); + result.ErrCode = 408; + result.Exception = ex; + result.ErrList.Add(ex.Message); } + return result.EndTime(); } #region 发送报文,并获取响应报文 diff --git a/IoTClient/Clients/Modbus/ModbusRtuOverTcpClient.cs b/IoTClient/Clients/Modbus/ModbusRtuOverTcpClient.cs index 4082dde..81f8b82 100644 --- a/IoTClient/Clients/Modbus/ModbusRtuOverTcpClient.cs +++ b/IoTClient/Clients/Modbus/ModbusRtuOverTcpClient.cs @@ -10,7 +10,7 @@ namespace IoTClient.Clients.Modbus { /// - /// Tcp的方式发送ModbusRtu协议报文 - 客户端 - Beta + /// Tcp的方式发送ModbusRtu协议报文 - 客户端 /// public class ModbusRtuOverTcpClient : SocketBase, IModbusClient { @@ -40,7 +40,7 @@ public ModbusRtuOverTcpClient(string ip, int port, int timeout = 1500, EndianFor protected override Result Connect() { var result = new Result(); - socket?.Close(); + socket?.SafeClose(); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { @@ -54,15 +54,17 @@ protected override Result Connect() //连接 socket.Connect(ipAndPoint); - return result.EndTime(); } catch (Exception ex) { socket?.SafeClose(); result.IsSucceed = false; result.Err = ex.Message; - return result.EndTime(); + result.ErrCode = 408; + result.Exception = ex; + result.ErrList.Add(ex.Message); } + return result.EndTime(); } #region 发送报文,并获取响应报文 @@ -133,7 +135,7 @@ public Result Read(string address, byte stationNumber = 1, byte function //发送命令并获取响应报文 int readLenght; if (functionCode == 1 || functionCode == 2) - readLenght = 5 + readLength; + readLenght = 5 + (int)Math.Ceiling((float)readLength / 8); else readLenght = 5 + readLength * 2; var sendResult = SendPackage(commandCRC16, readLenght); diff --git a/IoTClient/Clients/PLC/AllenBradleyClient.cs b/IoTClient/Clients/PLC/AllenBradleyClient.cs new file mode 100644 index 0000000..4747012 --- /dev/null +++ b/IoTClient/Clients/PLC/AllenBradleyClient.cs @@ -0,0 +1,734 @@ +using IoTClient.Clients.PLC.Models; +using IoTClient.Common.Helpers; +using IoTClient.Enums; +using IoTClient.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace IoTClient.Clients.PLC +{ + /// + /// (AB)罗克韦尔客户端 Beta + /// https://blog.csdn.net/lishiming0308/article/details/85243041 + /// + public class AllenBradleyClient : SocketBase, IEthernetClient + { + public string Version => "AllenBradley"; + + /// + /// 连接地址 + /// + public IPEndPoint IpAndPoint { get; } + + /// + /// 是否是连接的 + /// + public bool Connected => socket?.Connected ?? false; + + /// + /// 超时时间 + /// + private readonly int timeout; + /// + /// 插槽 + /// + private readonly byte slot; + + /// + /// 警告日志委托 + /// 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 + /// + public LoggerDelegate WarningLog { get; set; } + + public AllenBradleyClient(string ip, int port, byte slot = 0, int timeout = 1500) + { + IpAndPoint = new IPEndPoint(IPAddress.Parse(ip), port); + this.timeout = timeout; + this.slot = slot; + } + + /// + /// 会话句柄(由AB PLC生成) + /// + public uint Session { get; private set; } + + /// + /// 注册命令 + /// + 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 + }; + + /// + /// 打开连接(如果已经是连接状态会先关闭再打开) + /// + /// + protected override Result Connect() + { + var result = new Result(); + socket?.SafeClose(); + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + try + { + #region 超时时间设置 +#if !DEBUG + socket.ReceiveTimeout = timeout; + socket.SendTimeout = timeout; +#endif + #endregion + + //连接 + socket.Connect(IpAndPoint); + + result.Requst = string.Join(" ", RegisteredCommand.Select(t => t.ToString("X2"))); + socket.Send(RegisteredCommand); + + var head = SocketRead(socket, 24); + var content = SocketRead(socket, GetContentLength(head)); + var response = head.Concat(content).ToArray(); + result.Response = string.Join(" ", response.Select(t => t.ToString("X2"))); + + byte[] buffer = new byte[4]; + buffer[0] = response[4]; + buffer[1] = response[5]; + buffer[2] = response[6]; + buffer[3] = response[7]; + //会话句柄 + Session = BitConverter.ToUInt32(buffer, 0); + } + catch (Exception ex) + { + socket?.SafeClose(); + result.IsSucceed = false; + result.Err = ex.Message; + result.ErrCode = 408; + result.Exception = ex; + result.ErrList.Add(ex.Message); + } + return result.EndTime(); + } + + #region Read + public Result Read(string address, ushort length, bool isBit = false, bool setEndian = true) + { + if (!socket?.Connected ?? true) + { + var connectResult = Connect(); + if (!connectResult.IsSucceed) + { + return new Result(connectResult); + } + } + var result = new Result(); + try + { + var command = GetReadCommand(address, 1); + result.Requst = string.Join(" ", command.Select(t => t.ToString("X2"))); + //发送命令 并获取响应报文 + var sendResult = SendPackage(command); + if (!sendResult.IsSucceed) + return sendResult; + var dataPackage = sendResult.Value; + result.Response = string.Join(" ", dataPackage.Select(t => t.ToString("X2"))); + + ushort count = BitConverter.ToUInt16(dataPackage, 38); + byte[] data = new byte[count - 6]; + Buffer.BlockCopy(dataPackage, 46, data, 0, data.Length); + + result.Value = data; + } + catch (SocketException ex) + { + result.IsSucceed = false; + if (ex.SocketErrorCode == SocketError.TimedOut) + { + result.Err = "连接超时"; + result.ErrList.Add("连接超时"); + } + else + { + result.Err = ex.Message; + result.Exception = ex; + result.ErrList.Add(ex.Message); + } + socket?.SafeClose(); + } + catch (Exception ex) + { + result.IsSucceed = false; + result.Err = ex.Message; + result.Exception = ex; + result.ErrList.Add(ex.Message); + socket?.SafeClose(); + } + finally + { + if (isAutoOpen) Dispose(); + } + return result.EndTime(); + } + + /// + /// 读取Boolean + /// + /// 地址 + /// + public Result ReadBoolean(string address) + { + var readResut = Read(address, 1, isBit: true); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToBoolean(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取byte + /// + /// + /// + public Result ReadByte(string address) + { + throw new NotImplementedException(); + } + + /// + /// 读取Int16 + /// + /// + /// + public Result ReadInt16(string address) + { + var readResut = Read(address, 2); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToInt16(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取UInt16 + /// + /// 地址 + /// + public Result ReadUInt16(string address) + { + var readResut = Read(address, 2); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToUInt16(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取Int32 + /// + /// 地址 + /// + public Result ReadInt32(string address) + { + var readResut = Read(address, 4); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToInt32(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取UInt32 + /// + /// 地址 + /// + public Result ReadUInt32(string address) + { + var readResut = Read(address, 4); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToUInt32(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取Int64 + /// + /// 地址 + /// + public Result ReadInt64(string address) + { + var readResut = Read(address, 8); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToInt64(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取UInt64 + /// + /// 地址 + /// + public Result ReadUInt64(string address) + { + var readResut = Read(address, 8); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToUInt64(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取Float + /// + /// 地址 + /// + public Result ReadFloat(string address) + { + var readResut = Read(address, 4); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToSingle(readResut.Value, 0); + return result.EndTime(); + } + + /// + /// 读取Double + /// + /// 地址 + /// + public Result ReadDouble(string address) + { + var readResut = Read(address, 8); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToDouble(readResut.Value, 0); + return result.EndTime(); + } + #endregion + + #region Write + public Result Write(string address, ushort typeCode, byte[] data, bool isBit = false) + { + if (!socket?.Connected ?? true) + { + var connectResult = Connect(); + if (!connectResult.IsSucceed) + { + return connectResult; + } + } + Result result = new Result(); + try + { + //Array.Reverse(data); + //发送写入信息 + //var arg = ConvertWriteArg(address, data, false); + byte[] command = GetWriteCommand(address, typeCode, data, 1); + result.Requst = string.Join(" ", command.Select(t => t.ToString("X2"))); + var sendResult = SendPackage(command); + if (!sendResult.IsSucceed) + return sendResult; + + var dataPackage = sendResult.Value; + result.Response = string.Join(" ", dataPackage.Select(t => t.ToString("X2"))); + } + catch (SocketException ex) + { + result.IsSucceed = false; + if (ex.SocketErrorCode == SocketError.TimedOut) + { + result.Err = "连接超时"; + result.ErrList.Add("连接超时"); + } + else + { + result.Err = ex.Message; + result.Exception = ex; + result.ErrList.Add(ex.Message); + } + socket?.SafeClose(); + } + catch (Exception ex) + { + result.IsSucceed = false; + result.Err = ex.Message; + result.Exception = ex; + result.ErrList.Add(ex.Message); + socket?.SafeClose(); + } + finally + { + if (isAutoOpen) Dispose(); + } + return result.EndTime(); + } + + public Result Write(string address, bool value) + { + return Write(address, 0xC1, value ? new byte[] { 0xFF, 0xFF } : new byte[] { 0x00, 0x00 }); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, byte value) + { + return Write(address, 0xC2, new byte[] { value, 0x00 });// new byte[1] { value } + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, sbyte value) + { + return Write(address, 0xC2, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, short value) + { + return Write(address, 0xC3, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, ushort value) + { + return Write(address, 0xC3, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, int value) + { + return Write(address, 0xC4, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, uint value) + { + return Write(address, 0xC4, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, long value) + { + return Write(address, 0xC5, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, ulong value) + { + return Write(address, 0xC5, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, float value) + { + return Write(address, 0xCA, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, double value) + { + return Write(address, 0xCB, BitConverter.GetBytes(value)); + } + + /// + /// 写入数据 + /// + /// 地址 + /// 值 + /// + public Result Write(string address, string value) + { + var valueBytes = Encoding.ASCII.GetBytes(value); + var bytes = new byte[valueBytes.Length + 1]; + bytes[0] = (byte)valueBytes.Length; + valueBytes.CopyTo(bytes, 1); + Array.Reverse(bytes); + return Write(address, 0xC4, bytes); + } + #endregion + + /// + /// 地址信息解析 + /// + /// + /// + /// + private AllenBradleyAddress ConvertArg(string address, bool isBit) + { + return new AllenBradleyAddress(); + } + + /// + /// 获取Read命令 + /// + /// + /// + /// + /// + protected byte[] GetReadCommand(string address, ushort length) + { + //if (!isBit) + //length = (ushort)(length / 2); + + 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] = slot; + + return command; + } + + /// + /// 获取Write命令 + /// + /// + /// + /// + /// + /// + protected byte[] GetWriteCommand(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] = slot; + return command; + + } + + /// + /// 发送报文,并获取响应报文 + /// + /// + /// + public Result SendPackage(byte[] command) + { + //从发送命令到读取响应为最小单元,避免多线程执行串数据(可线程安全执行) + lock (this) + { + Result result = new Result(); + + void _sendPackage() + { + + socket.Send(command); + var head = SocketRead(socket, 24); + var content = SocketRead(socket, GetContentLength(head)); + result.Value = head.Concat(content).ToArray(); + } + + try + { + _sendPackage(); + } + catch (Exception ex) + { + WarningLog?.Invoke(ex.Message, ex); + //如果出现异常,则进行一次重试 + //重新打开连接 + var conentResult = Connect(); + if (!conentResult.IsSucceed) + return new Result(conentResult); + + _sendPackage(); + } + return result.EndTime(); + } + } + + public Result> BatchRead(Dictionary addresses, int batchNumber) + { + throw new System.NotImplementedException(); + } + + public Result BatchWrite(Dictionary addresses, int batchNumber) + { + throw new System.NotImplementedException(); + } + + public Result ReadString(string address) + { + throw new NotImplementedException(); + } + + public Result Write(string address, byte[] data, bool isBit = false) + { + throw new NotImplementedException(); + } + + /// + /// 后面内容长度 + /// + /// + /// + private ushort GetContentLength(byte[] head) + { + return BitConverter.ToUInt16(head, 2); + } + } +} diff --git a/IoTClient/Clients/PLC/Constants/SiemensConstant.cs b/IoTClient/Clients/PLC/Constants/SiemensConstant.cs index a3b29d5..a958cb8 100644 --- a/IoTClient/Clients/PLC/Constants/SiemensConstant.cs +++ b/IoTClient/Clients/PLC/Constants/SiemensConstant.cs @@ -1,9 +1,7 @@ -using IoTClient.Core.Models; - -namespace IoTClient.Common.Constants +namespace IoTClient.Common.Constants { /// - /// + /// Siemens命令常量 /// public class SiemensConstant { @@ -20,7 +18,7 @@ public class SiemensConstant 0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00, 0x00,0x01,0x00,0xC0,0x01,0x0A,0xC1,0x02, 0x01,0x02,0xC2,0x02,0x01,0x00 - }; + }; /// /// 第二次初始化指令交互报文 diff --git a/IoTClient/Clients/PLC/Enums/SiemensVersion.cs b/IoTClient/Clients/PLC/Enums/SiemensVersion.cs index 79829a9..7adbb3a 100644 --- a/IoTClient/Clients/PLC/Enums/SiemensVersion.cs +++ b/IoTClient/Clients/PLC/Enums/SiemensVersion.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Text; namespace IoTClient.Common.Enums { diff --git a/IoTClient/Clients/PLC/MitsubishiClient.cs b/IoTClient/Clients/PLC/MitsubishiClient.cs index 7004e6a..12d2350 100644 --- a/IoTClient/Clients/PLC/MitsubishiClient.cs +++ b/IoTClient/Clients/PLC/MitsubishiClient.cs @@ -12,27 +12,39 @@ namespace IoTClient.Clients.PLC { /// - /// 三菱plc客户端 - Beta + /// 三菱plc客户端 /// public class MitsubishiClient : SocketBase, IEthernetClient { private int timeout; - + /// + /// 版本 + /// public string Version => version.ToString(); private MitsubishiVersion version; - + /// + /// 连接地址 + /// public IPEndPoint IpAndPoint { get; } + /// + /// 是否是连接的 + /// public bool Connected => socket?.Connected ?? false; + /// + /// 警告日志委托 + /// 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 + /// public LoggerDelegate WarningLog { get; set; } /// - /// + /// 构造函数 /// - /// - /// - /// + /// 三菱型号版本 + /// ip地址 + /// 端口 + /// 超时时间 public MitsubishiClient(MitsubishiVersion version, string ip, int port, int timeout = 1500) { this.version = version; @@ -41,13 +53,13 @@ public MitsubishiClient(MitsubishiVersion version, string ip, int port, int time } /// - /// 打开长连接 + /// 打开连接(如果已经是连接状态会先关闭再打开) /// /// protected override Result Connect() { var result = new Result(); - socket?.Close(); + socket?.SafeClose(); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { @@ -62,9 +74,12 @@ protected override Result Connect() } catch (Exception ex) { + socket?.SafeClose(); result.IsSucceed = false; result.Err = ex.Message; - return result.EndTime(); + result.ErrCode = 408; + result.Exception = ex; + result.ErrList.Add(ex.Message); } return result.EndTime(); } @@ -86,7 +101,9 @@ void _sendPackage() { socket.Send(command); var headPackage = SocketRead(socket, 9); - var dataPackage = SocketRead(socket, GetContentLength(headPackage)); + //其后内容的总长度 + var contentLength = BitConverter.ToUInt16(headPackage, 7); + var dataPackage = SocketRead(socket, contentLength); result.Value = headPackage.Concat(dataPackage).ToArray(); } @@ -105,11 +122,17 @@ void _sendPackage() _sendPackage(); } - + return result.EndTime(); } } + /// + /// 发送报文,并获取响应报文 + /// + /// + /// + /// public Result SendPackage(byte[] command, int receiveCount) { //从发送命令到读取响应为最小单元,避免多线程执行串数据(可线程安全执行) @@ -117,7 +140,8 @@ public Result SendPackage(byte[] command, int receiveCount) { Result result = new Result(); - void _sendPackage() { + void _sendPackage() + { socket.Send(command); var dataPackage = SocketRead(socket, receiveCount); result.Value = dataPackage.ToArray(); @@ -160,18 +184,18 @@ public Result Read(string address, ushort length, bool isBit = false, bo try { //发送读取信息 - MitsubishiMCData arg = null; + MitsubishiMCAddress arg = null; byte[] command = null; switch (version) { case MitsubishiVersion.A_1E: - arg = ConvertAddress_A_1E(address); - command = GetReadCommand_A_1E(arg.BeginAddress, arg.MitsubishiMCType.TypeCode, length, isBit); + arg = ConvertArg_A_1E(address); + command = GetReadCommand_A_1E(arg.BeginAddress, arg.TypeCode, length, isBit); break; case MitsubishiVersion.Qna_3E: - arg = ConvertAddress_Qna_3E(address); - command = GetReadCommand_Qna_3E(arg.BeginAddress, arg.MitsubishiMCType.TypeCode, length, isBit); + arg = ConvertArg_Qna_3E(address); + command = GetReadCommand_Qna_3E(arg.BeginAddress, arg.TypeCode, length, isBit); break; } result.Requst = string.Join(" ", command.Select(t => t.ToString("X2"))); @@ -523,17 +547,17 @@ public Result Write(string address, byte[] data, bool isBit = false) Array.Reverse(data); //发送写入信息 - MitsubishiMCData arg = null; + MitsubishiMCAddress arg = null; byte[] command = null; switch (version) { case MitsubishiVersion.A_1E: - arg = ConvertAddress_A_1E(address); - command = GetWriteCommand_A_1E(arg.BeginAddress, arg.MitsubishiMCType.TypeCode, data, isBit); + arg = ConvertArg_A_1E(address); + command = GetWriteCommand_A_1E(arg.BeginAddress, arg.TypeCode, data, isBit); break; case MitsubishiVersion.Qna_3E: - arg = ConvertAddress_Qna_3E(address); - command = GetWriteCommand_Qna_3E(arg.BeginAddress, arg.MitsubishiMCType.TypeCode, data, isBit); + arg = ConvertArg_Qna_3E(address); + command = GetWriteCommand_Qna_3E(arg.BeginAddress, arg.TypeCode, data, isBit); break; } result.Requst = string.Join(" ", command.Select(t => t.ToString("X2"))); @@ -847,221 +871,279 @@ protected byte[] GetWriteCommand_A_1E(int beginAddress, byte[] typeCode, byte[] } #endregion - #region private - /// - /// 获取内容长度 - /// - /// - /// - private int GetContentLength(byte[] head) - { - return BitConverter.ToUInt16(head, 7); - } + #region private + #region 地址解析 /// - /// 地址转换 + /// Qna_3E地址解析 /// /// /// - private MitsubishiMCData ConvertAddress_Qna_3E(string address) + private MitsubishiMCAddress ConvertArg_Qna_3E(string address) { address = address.ToUpper(); - var addressData = new MitsubishiMCData(); + var addressInfo = new MitsubishiMCAddress(); switch (address[0]) { - case 'M': + case 'M'://M中间继电器 { - addressData.MitsubishiMCType = MitsubishiMCType.M; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.M.Format); + addressInfo.TypeCode = new byte[] { 0x90 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'X': + case 'X':// X输入继电器 { - addressData.MitsubishiMCType = MitsubishiMCType.X; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.X.Format); + addressInfo.TypeCode = new byte[] { 0x9C }; + addressInfo.DataType = 0x01; + addressInfo.Format = 16; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'Y': + case 'Y'://Y输出继电器 { - addressData.MitsubishiMCType = MitsubishiMCType.Y; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.Y.Format); + addressInfo.TypeCode = new byte[] { 0x9D }; + addressInfo.DataType = 0x01; + addressInfo.Format = 16; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'D': + case 'D'://D数据寄存器 { - addressData.MitsubishiMCType = MitsubishiMCType.D; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.D.Format); + addressInfo.TypeCode = new byte[] { 0xA8 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'W': + case 'W'://W链接寄存器 { - addressData.MitsubishiMCType = MitsubishiMCType.W; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.W.Format); + addressInfo.TypeCode = new byte[] { 0xB4 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 16; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'L': + case 'L'://L锁存继电器 { - addressData.MitsubishiMCType = MitsubishiMCType.L; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.L.Format); + addressInfo.TypeCode = new byte[] { 0x92 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'F': + case 'F'://F报警器 { - addressData.MitsubishiMCType = MitsubishiMCType.F; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.F.Format); + addressInfo.TypeCode = new byte[] { 0x93 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'V': + case 'V'://V边沿继电器 { - addressData.MitsubishiMCType = MitsubishiMCType.V; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.V.Format); + addressInfo.TypeCode = new byte[] { 0x94 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'B': + case 'B'://B链接继电器 { - addressData.MitsubishiMCType = MitsubishiMCType.B; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.B.Format); + addressInfo.TypeCode = new byte[] { 0xA0 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 16; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'R': + case 'R'://R文件寄存器 { - addressData.MitsubishiMCType = MitsubishiMCType.R; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.R.Format); + addressInfo.TypeCode = new byte[] { 0xAF }; + addressInfo.DataType = 0x00; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; case 'S': { + //累计定时器的线圈 if (address[1] == 'C') { - addressData.MitsubishiMCType = MitsubishiMCType.SC; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.SC.Format); + addressInfo.TypeCode = new byte[] { 0xC6 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + //累计定时器的触点 else if (address[1] == 'S') { - addressData.MitsubishiMCType = MitsubishiMCType.SS; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.SS.Format); + addressInfo.TypeCode = new byte[] { 0xC7 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + //累计定时器的当前值 else if (address[1] == 'N') { - addressData.MitsubishiMCType = MitsubishiMCType.SN; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.SN.Format); + addressInfo.TypeCode = new byte[] { 0xC8 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 100; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + // S步进继电器 else { - addressData.MitsubishiMCType = MitsubishiMCType.S; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.S.Format); + addressInfo.TypeCode = new byte[] { 0x98 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; } case 'Z': { + //文件寄存器ZR区 if (address[1] == 'R') { - addressData.MitsubishiMCType = MitsubishiMCType.ZR; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.ZR.Format); + addressInfo.TypeCode = new byte[] { 0xB0 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 16; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + //变址寄存器 else { - addressData.MitsubishiMCType = MitsubishiMCType.Z; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.Z.Format); + addressInfo.TypeCode = new byte[] { 0xCC }; + addressInfo.DataType = 0x00; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; } case 'T': { + // 定时器的当前值 if (address[1] == 'N') { - addressData.MitsubishiMCType = MitsubishiMCType.TN; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.TN.Format); + addressInfo.TypeCode = new byte[] { 0xC2 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + //定时器的触点 else if (address[1] == 'S') { - addressData.MitsubishiMCType = MitsubishiMCType.TS; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.TS.Format); + addressInfo.TypeCode = new byte[] { 0xC1 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + //定时器的线圈 else if (address[1] == 'C') { - addressData.MitsubishiMCType = MitsubishiMCType.TC; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.TC.Format); + addressInfo.TypeCode = new byte[] { 0xC0 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } break; } case 'C': { + //计数器的当前值 if (address[1] == 'N') { - addressData.MitsubishiMCType = MitsubishiMCType.CN; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.CN.Format); + addressInfo.TypeCode = new byte[] { 0xC5 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + //计数器的触点 else if (address[1] == 'S') { - addressData.MitsubishiMCType = MitsubishiMCType.CS; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.CS.Format); + addressInfo.TypeCode = new byte[] { 0xC4 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } + //计数器的线圈 else if (address[1] == 'C') { - addressData.MitsubishiMCType = MitsubishiMCType.CC; - addressData.BeginAddress = Convert.ToInt32(address.Substring(2), MitsubishiMCType.CC.Format); + addressInfo.TypeCode = new byte[] { 0xC3 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(2), addressInfo.Format); } break; } } - return addressData; + return addressInfo; } - private MitsubishiMCData ConvertAddress_A_1E(string address) + /// + /// A_1E地址解析 + /// + /// + /// + private MitsubishiMCAddress ConvertArg_A_1E(string address) { address = address.ToUpper(); - var addressData = new MitsubishiMCData(); + var addressInfo = new MitsubishiMCAddress(); switch (address[0]) { - case 'X': + case 'X'://X输入寄存器 { - addressData.MitsubishiMCType = MitsubishiA1Type.X; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.X.Format); + addressInfo.TypeCode = new byte[] { 0x58, 0x20 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 8; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'Y': + case 'Y'://Y输出寄存器 { - addressData.MitsubishiMCType = MitsubishiA1Type.Y; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.Y.Format); + addressInfo.TypeCode = new byte[] { 0x59, 0x20 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 8; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'M': + case 'M'://M中间寄存器 { - addressData.MitsubishiMCType = MitsubishiA1Type.M; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.M.Format); + addressInfo.TypeCode = new byte[] { 0x4D, 0x20 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'S': + case 'S'://S状态寄存器 { - addressData.MitsubishiMCType = MitsubishiA1Type.S; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.S.Format); + addressInfo.TypeCode = new byte[] { 0x53, 0x20 }; + addressInfo.DataType = 0x01; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'D': + case 'D'://D数据寄存器 { - addressData.MitsubishiMCType = MitsubishiA1Type.D; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.D.Format); + addressInfo.TypeCode = new byte[] { 0x44, 0x20 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; - case 'R': + case 'R'://R文件寄存器 { - addressData.MitsubishiMCType = MitsubishiA1Type.R; - addressData.BeginAddress = Convert.ToInt32(address.Substring(1), MitsubishiMCType.R.Format); + addressInfo.TypeCode = new byte[] { 0x52, 0x20 }; + addressInfo.DataType = 0x00; + addressInfo.Format = 10; + addressInfo.BeginAddress = Convert.ToInt32(address.Substring(1), addressInfo.Format); } break; } - return addressData; - } - - private MitsubishiMCWrite ConvertWriteArg_Qna_3E(string address, byte[] writeData) - { - MitsubishiMCWrite arg = new MitsubishiMCWrite(ConvertAddress_Qna_3E(address)); - arg.WriteData = writeData; - //arg.ReadWriteBit = bit; - return arg; + return addressInfo; } + #endregion #region TODO public Result> BatchRead(Dictionary addresses, int batchNumber) @@ -1085,6 +1167,7 @@ public Result BatchWrite(Dictionary addresses, int batchNumber) } #endregion + #endregion } } diff --git a/IoTClient/Clients/PLC/Models/AllenBradleyAddress.cs b/IoTClient/Clients/PLC/Models/AllenBradleyAddress.cs new file mode 100644 index 0000000..8a26362 --- /dev/null +++ b/IoTClient/Clients/PLC/Models/AllenBradleyAddress.cs @@ -0,0 +1,6 @@ +namespace IoTClient.Clients.PLC.Models +{ + public class AllenBradleyAddress + { + } +} diff --git a/IoTClient/Clients/PLC/Models/IMitsubishiMCType.cs b/IoTClient/Clients/PLC/Models/IMitsubishiMCType.cs deleted file mode 100644 index 53fc37e..0000000 --- a/IoTClient/Clients/PLC/Models/IMitsubishiMCType.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace IoTClient.Models -{ - /// - /// - /// - public interface IMitsubishiMCType - { - /// - /// 类型的代号值 - /// - byte[] TypeCode { get; } - - /// - /// 数据的类型,0代表按字,1代表按位 - /// - byte DataType { get; } - - /// - /// 指示地址是10进制,还是16进制的 - /// - int Format { get; } - } -} diff --git a/IoTClient/Clients/PLC/Models/MitsubishiA1Type.cs b/IoTClient/Clients/PLC/Models/MitsubishiA1Type.cs deleted file mode 100644 index 32e5640..0000000 --- a/IoTClient/Clients/PLC/Models/MitsubishiA1Type.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace IoTClient.Models -{ - - public class MitsubishiA1Type : IMitsubishiMCType - { - public MitsubishiA1Type(byte[] code, byte type, int fromBase) - { - TypeCode = code; - Format = fromBase; - if (type < 2) DataType = type; - } - - public byte[] TypeCode { get; private set; } = { 0x00, 0x00 }; - /// - /// 数据的类型,0代表按字,1代表按位 - /// - public byte DataType { get; private set; } = 0x00; - - /// - /// 指示地址是10进制,还是16进制的 - /// - public int Format { get; private set; } - - /// - /// X输入寄存器 - /// - public readonly static MitsubishiA1Type X = new MitsubishiA1Type(new byte[] { 0x58, 0x20 }, 0x01, 8); - /// - /// Y输出寄存器 - /// - public readonly static MitsubishiA1Type Y = new MitsubishiA1Type(new byte[] { 0x59, 0x20 }, 0x01, 8); - /// - /// M中间寄存器 - /// - public readonly static MitsubishiA1Type M = new MitsubishiA1Type(new byte[] { 0x4D, 0x20 }, 0x01, 10); - /// - /// S状态寄存器 - /// - public readonly static MitsubishiA1Type S = new MitsubishiA1Type(new byte[] { 0x53, 0x20 }, 0x01, 10); - /// - /// D数据寄存器 - /// - public readonly static MitsubishiA1Type D = new MitsubishiA1Type(new byte[] { 0x44, 0x20 }, 0x00, 10); - /// - /// R文件寄存器 - /// - public readonly static MitsubishiA1Type R = new MitsubishiA1Type(new byte[] { 0x52, 0x20 }, 0x00, 10); - } -} diff --git a/IoTClient/Clients/PLC/Models/MitsubishiMCAddress.cs b/IoTClient/Clients/PLC/Models/MitsubishiMCAddress.cs new file mode 100644 index 0000000..7fbd600 --- /dev/null +++ b/IoTClient/Clients/PLC/Models/MitsubishiMCAddress.cs @@ -0,0 +1,28 @@ +namespace IoTClient.Models +{ + /// + /// 三菱解析后的地址信息 + /// + public class MitsubishiMCAddress + { + /// + /// 开始地址 + /// + public int BeginAddress { get; set; } + + /// + /// 类型的代号 + /// + public byte[] TypeCode { get; set; } + + /// + /// 数据的类型,0代表按字,1代表按位 + /// + public byte DataType { get; set; } + + /// + /// 指示地址是10进制,还是16进制的 + /// + public int Format { get; set; } + } +} diff --git a/IoTClient/Clients/PLC/Models/MitsubishiMCData.cs b/IoTClient/Clients/PLC/Models/MitsubishiMCData.cs deleted file mode 100644 index 7bfd9b9..0000000 --- a/IoTClient/Clients/PLC/Models/MitsubishiMCData.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace IoTClient.Models -{ - /// - /// - /// - public class MitsubishiMCData - { - /// - /// 开始地址 - /// - public int BeginAddress { get; set; } - /// - /// 数据类型 - /// - public IMitsubishiMCType MitsubishiMCType { get; set; } - } -} diff --git a/IoTClient/Clients/PLC/Models/MitsubishiMCType.cs b/IoTClient/Clients/PLC/Models/MitsubishiMCType.cs deleted file mode 100644 index 04358a3..0000000 --- a/IoTClient/Clients/PLC/Models/MitsubishiMCType.cs +++ /dev/null @@ -1,146 +0,0 @@ -namespace IoTClient.Models -{ - /// - /// 三菱PLC数据类型 - /// - public class MitsubishiMCType : IMitsubishiMCType - { - /// - /// - /// - /// 数据类型的代号 - /// 0或1,默认为0 - /// 十进制或十六进制 - public MitsubishiMCType(byte[] code, byte type, int format) - { - TypeCode = code; - Format = format; - DataType = type; - } - - /// - /// 类型的代号值 - /// - public byte[] TypeCode { get; private set; } = { 0x00 }; - - /// - /// 0代表按字,1代表按位 - /// - public byte DataType { get; private set; } = 0x00; - - /// - /// 10进制或16进制 - /// - public int Format { get; private set; } - - /// - /// X输入继电器 - /// - public static MitsubishiMCType X = new MitsubishiMCType(new byte[] { 0x9C }, 0x01, 16); - - /// - /// Y输出继电器 - /// - public static MitsubishiMCType Y = new MitsubishiMCType(new byte[] { 0x9D }, 0x01, 16); - - /// - /// M中间继电器 - /// - public static MitsubishiMCType M = new MitsubishiMCType(new byte[] { 0x90 }, 0x01, 10); - - /// - /// D数据寄存器 - /// - public static MitsubishiMCType D = new MitsubishiMCType(new byte[] { 0xA8 }, 0x00, 10); - - /// - /// W链接寄存器 - /// - public static MitsubishiMCType W = new MitsubishiMCType(new byte[] { 0xB4 }, 0x00, 16); - - /// - /// L锁存继电器 - /// - public static MitsubishiMCType L = new MitsubishiMCType(new byte[] { 0x92 }, 0x01, 10); - - /// - /// F报警器 - /// - public static MitsubishiMCType F = new MitsubishiMCType(new byte[] { 0x93 }, 0x01, 10); - - /// - /// V边沿继电器 - /// - public static MitsubishiMCType V = new MitsubishiMCType(new byte[] { 0x94 }, 0x01, 10); - - /// - /// B链接继电器 - /// - public static MitsubishiMCType B = new MitsubishiMCType(new byte[] { 0xA0 }, 0x01, 16); - - /// - /// R文件寄存器 - /// - public static MitsubishiMCType R = new MitsubishiMCType(new byte[] { 0xAF }, 0x00, 10); - - /// - /// S步进继电器 - /// - public static MitsubishiMCType S = new MitsubishiMCType(new byte[] { 0x98 }, 0x01, 10); - - /// - /// 变址寄存器 - /// - public static MitsubishiMCType Z = new MitsubishiMCType(new byte[] { 0xCC }, 0x00, 10); - - /// - /// 定时器的当前值 - /// - public static MitsubishiMCType TN = new MitsubishiMCType(new byte[] { 0xC2 }, 0x00, 10); - - /// - /// 定时器的触点 - /// - public static MitsubishiMCType TS = new MitsubishiMCType(new byte[] { 0xC1 }, 0x01, 10); - - /// - /// 定时器的线圈 - /// - public static MitsubishiMCType TC = new MitsubishiMCType(new byte[] { 0xC0 }, 0x01, 10); - - /// - /// 累计定时器的触点 - /// - public static MitsubishiMCType SS = new MitsubishiMCType(new byte[] { 0xC7 }, 0x01, 10); - - /// - /// 累计定时器的线圈 - /// - public static MitsubishiMCType SC = new MitsubishiMCType(new byte[] { 0xC6 }, 0x01, 10); - - /// - /// 累计定时器的当前值 - /// - public static MitsubishiMCType SN = new MitsubishiMCType(new byte[] { 0xC8 }, 0x00, 100); - - /// - /// 计数器的当前值 - /// - public static MitsubishiMCType CN = new MitsubishiMCType(new byte[] { 0xC5 }, 0x00, 10); - - /// - /// 计数器的触点 - /// - public static MitsubishiMCType CS = new MitsubishiMCType(new byte[] { 0xC4 }, 0x01, 10); - - /// - /// 计数器的线圈 - /// - public static MitsubishiMCType CC = new MitsubishiMCType(new byte[] { 0xC3 }, 0x01, 10); - - /// - /// 文件寄存器ZR区 - /// - public static MitsubishiMCType ZR = new MitsubishiMCType(new byte[] { 0xB0 }, 0x00, 16); - } -} diff --git a/IoTClient/Clients/PLC/Models/MitsubishiMCWrite.cs b/IoTClient/Clients/PLC/Models/MitsubishiMCWrite.cs deleted file mode 100644 index 1201c54..0000000 --- a/IoTClient/Clients/PLC/Models/MitsubishiMCWrite.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace IoTClient.Models -{ - public class MitsubishiMCWrite : MitsubishiMCData - { - public MitsubishiMCWrite() - { - - } - - public MitsubishiMCWrite(MitsubishiMCData data) - { - Assignment(data); - } - - /// - /// 要写入的数据 - /// - public byte[] WriteData { get; set; } - - /// - /// 赋值 - /// - private void Assignment(MitsubishiMCData data) - { - BeginAddress = data.BeginAddress; - MitsubishiMCType = data.MitsubishiMCType; - } - } -} diff --git a/IoTClient/Clients/PLC/Models/OmronFinsAddress.cs b/IoTClient/Clients/PLC/Models/OmronFinsAddress.cs new file mode 100644 index 0000000..05d0f2b --- /dev/null +++ b/IoTClient/Clients/PLC/Models/OmronFinsAddress.cs @@ -0,0 +1,28 @@ +namespace IoTClient.Clients.PLC.Models +{ + /// + /// Omron解析后的地址信息 + /// + public class OmronFinsAddress + { + /// + /// 位操作 + /// + public byte BitCode { get; set; } + + /// + /// 字操作 + /// + public byte WordCode { get; set; } + + /// + /// 位操作 解析地址 + /// + public byte[] BitAddress { get; set; } + + /// + /// 是否是bit + /// + public bool IsBit { set; get; } + } +} diff --git a/IoTClient/Clients/PLC/Models/OmronFinsData.cs b/IoTClient/Clients/PLC/Models/OmronFinsData.cs deleted file mode 100644 index 5c4c971..0000000 --- a/IoTClient/Clients/PLC/Models/OmronFinsData.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace IoTClient.Clients.PLC.Models -{ - public class OmronFinsData - { - public OmronFinsType OmronFinsType { get; set; } - public byte[] Content { get; set; } - } -} diff --git a/IoTClient/Clients/PLC/Models/OmronFinsType.cs b/IoTClient/Clients/PLC/Models/OmronFinsType.cs deleted file mode 100644 index eb26e02..0000000 --- a/IoTClient/Clients/PLC/Models/OmronFinsType.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace IoTClient.Clients.PLC.Models -{ - public class OmronFinsType - { - - public OmronFinsType(byte bitCode, byte wordCode) - { - BitCode = bitCode; - WordCode = wordCode; - } - - /// - /// 位操作 - /// - public byte BitCode { get; private set; } - - /// - /// 字操作 - /// - public byte WordCode { get; private set; } - - public static readonly OmronFinsType DM = new OmronFinsType(0x02, 0x82); - - public static readonly OmronFinsType CIO = new OmronFinsType(0x30, 0xB0); - - public static readonly OmronFinsType WR = new OmronFinsType(0x31, 0xB1); - - public static readonly OmronFinsType HR = new OmronFinsType(0x32, 0xB2); - - public static readonly OmronFinsType AR = new OmronFinsType(0x33, 0xB3); - } -} diff --git a/IoTClient/Clients/PLC/Models/SiemensData.cs b/IoTClient/Clients/PLC/Models/SiemensAddress.cs similarity index 90% rename from IoTClient/Clients/PLC/Models/SiemensData.cs rename to IoTClient/Clients/PLC/Models/SiemensAddress.cs index 3e2360b..77d47d9 100644 --- a/IoTClient/Clients/PLC/Models/SiemensData.cs +++ b/IoTClient/Clients/PLC/Models/SiemensAddress.cs @@ -2,7 +2,10 @@ namespace IoTClient.Core.Models { - public class SiemensData + /// + /// 西门子解析后的地址信息 + /// + public class SiemensAddress { /// /// 原地址 diff --git a/IoTClient/Clients/PLC/Models/SiemensWrite.cs b/IoTClient/Clients/PLC/Models/SiemensWriteAddress.cs similarity index 70% rename from IoTClient/Clients/PLC/Models/SiemensWrite.cs rename to IoTClient/Clients/PLC/Models/SiemensWriteAddress.cs index 2b657d5..c555ef9 100644 --- a/IoTClient/Clients/PLC/Models/SiemensWrite.cs +++ b/IoTClient/Clients/PLC/Models/SiemensWriteAddress.cs @@ -1,19 +1,13 @@ using IoTClient.Core.Models; -using System; -using System.Collections.Generic; -using System.Text; namespace IoTClient.Models { - public class SiemensWrite : SiemensData + /// + /// 西门子[写]解析后的地址信息 + /// + public class SiemensWriteAddress : SiemensAddress { - - public SiemensWrite() - { - - } - - public SiemensWrite(SiemensData data) + public SiemensWriteAddress(SiemensAddress data) { Assignment(data); } @@ -26,7 +20,7 @@ public SiemensWrite(SiemensData data) /// /// 赋值 /// - private void Assignment(SiemensData data) + private void Assignment(SiemensAddress data) { Address = data.Address; DataType = data.DataType; diff --git a/IoTClient/Clients/PLC/OmronFinsClient.cs b/IoTClient/Clients/PLC/OmronFinsClient.cs index 77d0a96..d921b15 100644 --- a/IoTClient/Clients/PLC/OmronFinsClient.cs +++ b/IoTClient/Clients/PLC/OmronFinsClient.cs @@ -2,7 +2,6 @@ using IoTClient.Common.Helpers; using IoTClient.Enums; using IoTClient.Interfaces; -using IoTClient.Models; using System; using System.Collections.Generic; using System.Linq; @@ -12,7 +11,7 @@ namespace IoTClient.Clients.PLC { /// - /// 欧姆龙PLC 客户端 - Beta + /// 欧姆龙PLC 客户端 /// https://flat2010.github.io/2020/02/23/Omron-Fins%E5%8D%8F%E8%AE%AE/ /// public class OmronFinsClient : SocketBase, IEthernetClient @@ -48,7 +47,8 @@ public class OmronFinsClient : SocketBase, IEthernetClient public bool Connected => socket?.Connected ?? false; /// - /// + /// 警告日志委托 + /// 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 /// public LoggerDelegate WarningLog { get; set; } @@ -75,13 +75,13 @@ public OmronFinsClient(string ip, int port = 9600, int timeout = 1500, EndianFor } /// - /// 打开长连接 + /// 打开连接(如果已经是连接状态会先关闭再打开) /// /// protected override Result Connect() { var result = new Result(); - socket?.Close(); + socket?.SafeClose(); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { @@ -94,6 +94,7 @@ protected override Result Connect() socket.Connect(IpAndPoint); + result.Requst = string.Join(" ", BasicCommand.Select(t => t.ToString("X2"))); socket.Send(BasicCommand); var head = SocketRead(socket, 8); @@ -106,13 +107,16 @@ protected override Result Connect() var length = BitConverter.ToInt32(buffer, 0); var content = SocketRead(socket, length); - - var requst = string.Join(" ", head.Concat(content).Select(t => t.ToString("X2"))); + result.Response = string.Join(" ", head.Concat(content).Select(t => t.ToString("X2"))); } catch (Exception ex) - { + { + socket?.SafeClose(); result.IsSucceed = false; result.Err = ex.Message; + result.ErrCode = 408; + result.Exception = ex; + result.ErrList.Add(ex.Message); } return result.EndTime(); ; } @@ -124,7 +128,7 @@ protected override Result Connect() /// 地址 /// /// - /// + /// 返回值是否设置大小端 /// public Result Read(string address, ushort length, bool isBit = false, bool setEndian = true) { @@ -141,7 +145,7 @@ public Result Read(string address, ushort length, bool isBit = false, bo { //发送读取信息 var arg = ConvertArg(address, isBit); - byte[] command = GetReadCommand(arg, length, isBit); + byte[] command = GetReadCommand(arg, length); result.Requst = string.Join(" ", command.Select(t => t.ToString("X2"))); //发送命令 并获取响应报文 var sendResult = SendPackage(command); @@ -309,6 +313,20 @@ public Result ReadFloat(string address) result.Value = BitConverter.ToSingle(readResut.Value, 0); return result.EndTime(); } + + /// + /// 读取Double + /// + /// 地址 + /// + public Result ReadDouble(string address) + { + var readResut = Read(address, 8); + var result = new Result(readResut); + if (result.IsSucceed) + result.Value = BitConverter.ToDouble(readResut.Value, 0); + return result.EndTime(); + } #endregion #region Write @@ -336,7 +354,7 @@ public Result Write(string address, byte[] data, bool isBit = false) data = data.Reverse().ToArray().ByteFormatting(endianFormat); //发送写入信息 var arg = ConvertArg(address, isBit); - byte[] command = GetWriteCommand(arg, data, isBit); + byte[] command = GetWriteCommand(arg, data); result.Requst = string.Join(" ", command.Select(t => t.ToString("X2"))); var sendResult = SendPackage(command); if (!sendResult.IsSucceed) @@ -505,98 +523,138 @@ public Result Write(string address, double value) #endregion /// - /// 读取Double + /// 地址信息解析 /// - /// 地址 + /// + /// /// - public Result ReadDouble(string address) - { - var readResut = Read(address, 8); - var result = new Result(readResut); - if (result.IsSucceed) - result.Value = BitConverter.ToDouble(readResut.Value, 0); - return result.EndTime(); - } - - private OmronFinsData ConvertArg(string address, bool isBit) + private OmronFinsAddress ConvertArg(string address, bool isBit) { address = address.ToUpper(); - var data = new OmronFinsData(); + var addressInfo = new OmronFinsAddress() + { + IsBit = isBit + }; switch (address[0]) { - case 'D': + case 'D'://DM区 { - data.OmronFinsType = OmronFinsType.DM; + addressInfo.BitCode = 0x02; + addressInfo.WordCode = 0x82; break; } - case 'C': + case 'C'://CIO区 { - data.OmronFinsType = OmronFinsType.CIO; + addressInfo.BitCode = 0x30; + addressInfo.WordCode = 0xB0; break; } - case 'W': + case 'W'://WR区 { - data.OmronFinsType = OmronFinsType.WR; + addressInfo.BitCode = 0x31; + addressInfo.WordCode = 0xB1; break; } - case 'H': + case 'H'://HR区 { - data.OmronFinsType = OmronFinsType.HR; + addressInfo.BitCode = 0x32; + addressInfo.WordCode = 0xB2; break; } - case 'A': + case 'A'://AR区 { - data.OmronFinsType = OmronFinsType.AR; + addressInfo.BitCode = 0x33; + addressInfo.WordCode = 0xB3; break; } case 'E': { - //TODO + string[] address_split = address.Split('.'); + int block_length = Convert.ToInt32(address_split[0].Substring(1), 16); + if (block_length < 16) + { + addressInfo.BitCode = (byte)(0x20 + block_length); + addressInfo.WordCode = (byte)(0xA0 + block_length); + } + else + { + addressInfo.BitCode = (byte)(0xE0 + block_length - 16); + addressInfo.WordCode = (byte)(0x60 + block_length - 16); + } + + if (isBit) + { + // 位操作 + ushort address_location = ushort.Parse(address_split[1]); + addressInfo.BitAddress = new byte[3]; + addressInfo.BitAddress[0] = BitConverter.GetBytes(address_location)[1]; + addressInfo.BitAddress[1] = BitConverter.GetBytes(address_location)[0]; + + if (address_split.Length > 2) + { + addressInfo.BitAddress[2] = byte.Parse(address_split[2]); + if (addressInfo.BitAddress[2] > 15) + //输入的位地址只能在0-15之间 + throw new Exception("位地址数据异常"); + } + } + else + { + // 字操作 + ushort address_location = ushort.Parse(address_split[1]); + addressInfo.BitAddress = new byte[3]; + addressInfo.BitAddress[0] = BitConverter.GetBytes(address_location)[1]; + addressInfo.BitAddress[1] = BitConverter.GetBytes(address_location)[0]; + } break; } - default: throw new Exception("Address解析异常"); + default: + //类型不支持 + throw new Exception("Address解析异常"); } - if (address[0] == 'E') - { - //TODO - } - else + if (address[0] != 'E') { if (isBit) { // 位操作 - string[] splits = address.Substring(1).Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); - ushort addr = ushort.Parse(splits[0]); - data.Content = new byte[3]; - data.Content[0] = BitConverter.GetBytes(addr)[1]; - data.Content[1] = BitConverter.GetBytes(addr)[0]; + string[] address_split = address.Substring(1).Split('.'); + ushort address_location = ushort.Parse(address_split[0]); + addressInfo.BitAddress = new byte[3]; + addressInfo.BitAddress[0] = BitConverter.GetBytes(address_location)[1]; + addressInfo.BitAddress[1] = BitConverter.GetBytes(address_location)[0]; - if (splits.Length > 1) + if (address_split.Length > 1) { - data.Content[2] = byte.Parse(splits[1]); - if (data.Content[2] > 15) - { + addressInfo.BitAddress[2] = byte.Parse(address_split[1]); + if (addressInfo.BitAddress[2] > 15) //输入的位地址只能在0-15之间 throw new Exception("位地址数据异常"); - } } } else { // 字操作 - ushort addr = ushort.Parse(address.Substring(1)); - data.Content = new byte[3]; - data.Content[0] = BitConverter.GetBytes(addr)[1]; - data.Content[1] = BitConverter.GetBytes(addr)[0]; + ushort address_location = ushort.Parse(address.Substring(1)); + addressInfo.BitAddress = new byte[3]; + addressInfo.BitAddress[0] = BitConverter.GetBytes(address_location)[1]; + addressInfo.BitAddress[1] = BitConverter.GetBytes(address_location)[0]; } } - return data; + return addressInfo; } - protected byte[] GetReadCommand(OmronFinsData arg, ushort length, bool isBit) + /// + /// 获取Read命令 + /// + /// + /// + /// + protected byte[] GetReadCommand(OmronFinsAddress arg, ushort length) { + bool isBit = arg.IsBit; + if (!isBit) length = (ushort)(length / 2); byte[] command = new byte[26 + 8]; @@ -620,16 +678,23 @@ protected byte[] GetReadCommand(OmronFinsData arg, ushort length, bool isBit) command[26] = 0x01; command[27] = 0x01; //Command Code 内存区域读取 - command[28] = isBit ? arg.OmronFinsType.BitCode : arg.OmronFinsType.WordCode; - arg.Content.CopyTo(command, 29); + command[28] = isBit ? arg.BitCode : arg.WordCode; + arg.BitAddress.CopyTo(command, 29); command[32] = (byte)(length / 256); command[33] = (byte)(length % 256); return command; } - protected byte[] GetWriteCommand(OmronFinsData arg, byte[] value, bool isBit) + /// + /// 获取Write命令 + /// + /// + /// + /// + protected byte[] GetWriteCommand(OmronFinsAddress arg, byte[] value) { + bool isBit = arg.IsBit; byte[] command = new byte[26 + 8 + value.Length]; Array.Copy(BasicCommand, 0, command, 0, 4); @@ -651,8 +716,8 @@ protected byte[] GetWriteCommand(OmronFinsData arg, byte[] value, bool isBit) command[26] = 0x01; command[27] = 0x02; //Command Code 内存区域写入 - command[28] = isBit ? arg.OmronFinsType.BitCode : arg.OmronFinsType.WordCode; - arg.Content.CopyTo(command, 29); + command[28] = isBit ? arg.BitCode : arg.WordCode; + arg.BitAddress.CopyTo(command, 29); command[32] = isBit ? (byte)(value.Length / 256) : (byte)(value.Length / 2 / 256); command[33] = isBit ? (byte)(value.Length % 256) : (byte)(value.Length / 2 % 256); value.CopyTo(command, 34); @@ -660,7 +725,11 @@ protected byte[] GetWriteCommand(OmronFinsData arg, byte[] value, bool isBit) return command; } - #region 发送报文,并获取响应报文 + /// + /// 发送报文,并获取响应报文 + /// + /// + /// public Result SendPackage(byte[] command) { //从发送命令到读取响应为最小单元,避免多线程执行串数据(可线程安全执行) @@ -677,8 +746,9 @@ void _sendPackage() buffer[1] = head[6]; buffer[2] = head[5]; buffer[3] = head[4]; - var length = BitConverter.ToInt32(buffer, 0); - var dataPackage = SocketRead(socket, length); + //4-7是Length字段 表示其后所有字段的总长度 + var contentLength = BitConverter.ToInt32(buffer, 0); + var dataPackage = SocketRead(socket, contentLength); result.Value = head.Concat(dataPackage).ToArray(); } @@ -700,8 +770,6 @@ void _sendPackage() return result.EndTime(); } } - #endregion - public Result> BatchRead(Dictionary addresses, int batchNumber) { diff --git a/IoTClient/Clients/PLC/SiemensClient.cs b/IoTClient/Clients/PLC/SiemensClient.cs index 80adcc1..eec570e 100644 --- a/IoTClient/Clients/PLC/SiemensClient.cs +++ b/IoTClient/Clients/PLC/SiemensClient.cs @@ -1,14 +1,12 @@ using IoTClient.Common.Constants; using IoTClient.Common.Enums; using IoTClient.Common.Helpers; -using IoTClient.Core; using IoTClient.Core.Models; using IoTClient.Enums; using IoTClient.Interfaces; using IoTClient.Models; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; @@ -18,18 +16,18 @@ namespace IoTClient.Clients.PLC { /// /// 西门子客户端 + /// http://www.360doc.cn/mip/763580999.html /// public class SiemensClient : SocketBase, IEthernetClient { /// - /// 版本 + /// CPU版本 /// private readonly SiemensVersion version; /// /// 超时时间 /// private readonly int timeout; - /// /// 是否是连接的 /// @@ -45,10 +43,16 @@ public class SiemensClient : SocketBase, IEthernetClient /// /// 警告日志委托 - /// 为了可用性,会对异常网络已经进行重试。此类日志通过委托接口给出去。 + /// 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 /// public LoggerDelegate WarningLog { get; set; } + /// + /// 构造函数 + /// + /// CPU版本 + /// IP地址和端口号 + /// 超时时间 public SiemensClient(SiemensVersion version, IPEndPoint ipAndPoint, int timeout = 1500) { this.version = version; @@ -56,6 +60,13 @@ public SiemensClient(SiemensVersion version, IPEndPoint ipAndPoint, int timeout this.timeout = timeout; } + /// + /// 构造函数 + /// + /// CPU版本 + /// IP地址 + /// 端口号 + /// 超时时间 public SiemensClient(SiemensVersion version, string ip, int port, int timeout = 1500) { this.version = version; @@ -64,7 +75,7 @@ public SiemensClient(SiemensVersion version, string ip, int port, int timeout = } /// - /// 打开长连接(如果已经是连接状态会先关闭再打开) + /// 打开连接(如果已经是连接状态会先关闭再打开) /// /// protected override Result Connect() @@ -115,28 +126,30 @@ protected override Result Connect() break; } + result.Requst = string.Join(" ", Command1.Select(t => t.ToString("X2"))); //第一次初始化指令交互 socket.Send(Command1); - var head1 = SocketRead(socket, SiemensConstant.InitHeadLength); - SocketRead(socket, GetContentLength(head1)); + var head1 = SocketRead(socket, SiemensConstant.InitHeadLength, WarningLog); + var content1 = SocketRead(socket, GetContentLength(head1), WarningLog); + result.Response = string.Join(" ", head1.Concat(content1).Select(t => t.ToString("X2"))); + result.Requst2 = string.Join(" ", Command2.Select(t => t.ToString("X2"))); //第二次初始化指令交互 socket.Send(Command2); - var head2 = SocketRead(socket, SiemensConstant.InitHeadLength); - SocketRead(socket, GetContentLength(head2)); - return result.EndTime(); + var head2 = SocketRead(socket, SiemensConstant.InitHeadLength, WarningLog); + var content2 = SocketRead(socket, GetContentLength(head2), WarningLog); + result.Response2 = string.Join(" ", head2.Concat(content2).Select(t => t.ToString("X2"))); } catch (Exception ex) { - //TODO socket?.SafeClose(); result.IsSucceed = false; result.Err = ex.Message; result.ErrCode = 408; result.Exception = ex; result.ErrList.Add(ex.Message); - return result.EndTime(); } + return result.EndTime(); } #region 发送报文,并获取响应报文 @@ -162,7 +175,7 @@ public Result SendPackage(byte[] command) //重新打开连接 var conentResult = Connect(); if (!conentResult.IsSucceed) - return new Result(conentResult); + return result.SetErrInfo(conentResult); socket.Send(command); } @@ -173,12 +186,12 @@ public Result SendPackage(byte[] command) //重新打开连接 var conentResult = Connect(); if (!conentResult.IsSucceed) - return new Result(conentResult); + return result.SetErrInfo(conentResult); socket.Send(command); - headPackage = SocketRead(socket, SiemensConstant.InitHeadLength); + headPackage = SocketRead(socket, SiemensConstant.InitHeadLength, WarningLog); } - var dataPackage = SocketRead(socket, GetContentLength(headPackage)); + var dataPackage = SocketRead(socket, GetContentLength(headPackage), WarningLog); result.Value = headPackage.Concat(dataPackage).ToArray(); return result.EndTime(); } @@ -187,12 +200,12 @@ public Result SendPackage(byte[] command) #region Read /// - /// 读取数据 + /// 读取字节数组 /// /// 地址 - /// - /// - /// + /// 读取长度 + /// 是否Bit类型 + /// 暂未使用 /// public Result Read(string address, ushort length, bool isBit = false, bool setEndian = true) { @@ -216,7 +229,7 @@ public Result Read(string address, ushort length, bool isBit = false, bo //发送命令 并获取响应报文 var sendResult = SendPackage(command); if (!sendResult.IsSucceed) - return sendResult; + return result.SetErrInfo(sendResult).EndTime(); var dataPackage = sendResult.Value; byte[] responseData = new byte[length]; @@ -256,10 +269,10 @@ public Result Read(string address, ushort length, bool isBit = false, bo } /// - /// summary + /// 读取字符串 /// - /// - /// + /// 地址 + /// 读取长度 /// public Result ReadString(string address, ushort length) { @@ -277,17 +290,15 @@ public Result ReadString(string address, ushort length) //发送读取信息 var arg = ConvertArg(address); arg.ReadWriteLength = length; - //arg.TypeCode, arg.BeginAddress, arg.DbBlock, length, false byte[] command = GetReadCommand(arg); result.Requst = string.Join(" ", command.Select(t => t.ToString("X2"))); var sendResult = SendPackage(command); if (!sendResult.IsSucceed) - return sendResult; + return result.SetErrInfo(sendResult).EndTime(); var dataPackage = sendResult.Value; byte[] requst = new byte[length]; Array.Copy(dataPackage, 25, requst, 0, length); - //Array.Copy(dataPackage, dataPackage.Length - length, requst, 0, length); result.Response = string.Join(" ", dataPackage.Select(t => t.ToString("X2"))); result.Value = requst; } @@ -1285,13 +1296,13 @@ public Result Write(string address, string value) /// /// /// - private SiemensData ConvertArg(string address) + private SiemensAddress ConvertArg(string address) { try { //转换成大写 address = address.ToUpper(); - var data = new SiemensData() + var addressInfo = new SiemensAddress() { Address = address, DbBlock = 0, @@ -1299,40 +1310,39 @@ private SiemensData ConvertArg(string address) switch (address[0]) { case 'I': - data.TypeCode = 0x81; + addressInfo.TypeCode = 0x81; break; case 'Q': - data.TypeCode = 0x82; + addressInfo.TypeCode = 0x82; break; case 'M': - data.TypeCode = 0x83; + addressInfo.TypeCode = 0x83; break; case 'D': - data.TypeCode = 0x84; + addressInfo.TypeCode = 0x84; string[] adds = address.Split('.'); if (address[1] == 'B') - data.DbBlock = Convert.ToUInt16(adds[0].Substring(2)); + addressInfo.DbBlock = Convert.ToUInt16(adds[0].Substring(2)); else - data.DbBlock = Convert.ToUInt16(adds[0].Substring(1)); + addressInfo.DbBlock = Convert.ToUInt16(adds[0].Substring(1)); //TODO - data.BeginAddress = GetBeingAddress(address.Substring(address.IndexOf('.') + 1)); + addressInfo.BeginAddress = GetBeingAddress(address.Substring(address.IndexOf('.') + 1)); break; case 'T': - data.TypeCode = 0x1D; + addressInfo.TypeCode = 0x1D; break; case 'C': - data.TypeCode = 0x1C; + addressInfo.TypeCode = 0x1C; break; case 'V': - data.TypeCode = 0x84; - data.DbBlock = 1; + addressInfo.TypeCode = 0x84; + addressInfo.DbBlock = 1; break; } - //去掉V1025 前面的V if (address[0] != 'D' && address[1] != 'B') - data.BeginAddress = GetBeingAddress(address.Substring(1)); - return data; + addressInfo.BeginAddress = GetBeingAddress(address.Substring(1)); + return addressInfo; } catch (Exception ex) { @@ -1340,7 +1350,7 @@ private SiemensData ConvertArg(string address) } } - private SiemensData[] ConvertArg(Dictionary addresses) + private SiemensAddress[] ConvertArg(Dictionary addresses) { return addresses.Select(t => { @@ -1392,19 +1402,19 @@ private SiemensData[] ConvertArg(Dictionary addresses) /// /// /// - private SiemensWrite ConvertWriteArg(string address, byte[] writeData, bool bit) + private SiemensWriteAddress ConvertWriteArg(string address, byte[] writeData, bool bit) { - SiemensWrite arg = new SiemensWrite(ConvertArg(address)); + SiemensWriteAddress arg = new SiemensWriteAddress(ConvertArg(address)); arg.WriteData = writeData; arg.ReadWriteBit = bit; return arg; } - private SiemensWrite[] ConvertWriteArg(Dictionary> addresses) + private SiemensWriteAddress[] ConvertWriteArg(Dictionary> addresses) { return addresses.Select(t => { - var item = new SiemensWrite(ConvertArg(t.Key)); + var item = new SiemensWriteAddress(ConvertArg(t.Key)); item.WriteData = t.Value.Key; item.ReadWriteBit = t.Value.Value; return item; @@ -1417,7 +1427,7 @@ private SiemensWrite[] ConvertWriteArg(Dictionary /// - protected byte[] GetReadCommand(SiemensData[] datas) + protected byte[] GetReadCommand(SiemensAddress[] datas) { //byte type, int beginAddress, ushort dbAddress, ushort length, bool isBit byte[] command = new byte[19 + datas.Length * 12]; @@ -1464,9 +1474,9 @@ protected byte[] GetReadCommand(SiemensData[] datas) /// /// /// - protected byte[] GetReadCommand(SiemensData data) + protected byte[] GetReadCommand(SiemensAddress data) { - return GetReadCommand(new SiemensData[] { data }); + return GetReadCommand(new SiemensAddress[] { data }); } /// @@ -1474,7 +1484,7 @@ protected byte[] GetReadCommand(SiemensData data) /// /// /// - protected byte[] GetWriteCommand(SiemensWrite[] writes) + protected byte[] GetWriteCommand(SiemensWriteAddress[] writes) { //(如果不是最后一个 WriteData.Length == 1 ,则需要填充一个空数据) var writeDataLength = writes.Sum(t => t.WriteData.Length == 1 ? 2 : t.WriteData.Length); @@ -1564,9 +1574,9 @@ protected byte[] GetWriteCommand(SiemensWrite[] writes) /// /// /// - protected byte[] GetWriteCommand(SiemensWrite write) + protected byte[] GetWriteCommand(SiemensWriteAddress write) { - return GetWriteCommand(new SiemensWrite[] { write }); + return GetWriteCommand(new SiemensWriteAddress[] { write }); } #endregion diff --git a/IoTClient/IoTClient.csproj b/IoTClient/IoTClient.csproj index 0769ab7..c091198 100644 --- a/IoTClient/IoTClient.csproj +++ b/IoTClient/IoTClient.csproj @@ -31,12 +31,14 @@ - + + + + - diff --git a/IoTClient/IoTClient/IoTClient.xml b/IoTClient/IoTClient/IoTClient.xml index beea9d8..ffd58f1 100644 --- a/IoTClient/IoTClient/IoTClient.xml +++ b/IoTClient/IoTClient/IoTClient.xml @@ -760,7 +760,7 @@ - Tcp的方式发送ModbusRtu协议报文 - 客户端 - Beta + Tcp的方式发送ModbusRtu协议报文 - 客户端 @@ -1641,22 +1641,291 @@ 功能码 + + + (AB)罗克韦尔客户端 Beta + https://blog.csdn.net/lishiming0308/article/details/85243041 + + + + + 连接地址 + + + + + 是否是连接的 + + + + + 超时时间 + + + + + 插槽 + + + + + 警告日志委托 + 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 + + + + + 会话句柄(由AB PLC生成) + + + + + 注册命令 + + + + + 打开连接(如果已经是连接状态会先关闭再打开) + + + + + + 读取Boolean + + 地址 + + + + + 读取byte + + + + + + + 读取Int16 + + + + + + + 读取UInt16 + + 地址 + + + + + 读取Int32 + + 地址 + + + + + 读取UInt32 + + 地址 + + + + + 读取Int64 + + 地址 + + + + + 读取UInt64 + + 地址 + + + + + 读取Float + + 地址 + + + + + 读取Double + + 地址 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 写入数据 + + 地址 + 值 + + + + + 地址信息解析 + + + + + + + + 获取Read命令 + + + + + + + + + 获取Write命令 + + + + + + + + + + 发送报文,并获取响应报文 + + + + + + + 后面内容长度 + + + + - 三菱plc客户端 - Beta + 三菱plc客户端 + + + + + 版本 + + + + + 连接地址 + + + + + 是否是连接的 + + + + + 警告日志委托 + 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 - + 构造函数 - - - + 三菱型号版本 + ip地址 + 端口 + 超时时间 - 打开长连接 + 打开连接(如果已经是连接状态会先关闭再打开) @@ -1667,6 +1936,14 @@ + + + 发送报文,并获取响应报文 + + + + + 读取数据 @@ -1901,33 +2178,48 @@ - + - 获取内容长度 + Qna_3E地址解析 - + - + - 地址转换 + A_1E地址解析 - + + + Omron解析后的地址信息 + + + 位操作 - + 字操作 + + + 位操作 解析地址 + + + + + 是否是bit + + - 欧姆龙PLC 客户端 - Beta + 欧姆龙PLC 客户端 https://flat2010.github.io/2020/02/23/Omron-Fins%E5%8D%8F%E8%AE%AE/ @@ -1953,7 +2245,8 @@ - + 警告日志委托 + 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 @@ -1975,7 +2268,7 @@ - 打开长连接 + 打开连接(如果已经是连接状态会先关闭再打开) @@ -1986,7 +2279,7 @@ 地址 - + 返回值是否设置大小端 @@ -2052,6 +2345,13 @@ 地址 + + + 读取Double + + 地址 + + 写入数据 @@ -2149,21 +2449,46 @@ - + - 读取Double + 地址信息解析 - 地址 + + - + - 西门子客户端 + 获取Read命令 - + + + + + + + 获取Write命令 + + + + + + + + 发送报文,并获取响应报文 + + + + + + + 西门子客户端 + http://www.360doc.cn/mip/763580999.html + + - 版本 + CPU版本 @@ -2189,12 +2514,29 @@ 警告日志委托 - 为了可用性,会对异常网络已经进行重试。此类日志通过委托接口给出去。 + 为了可用性,会对异常网络进行重试。此类日志通过委托接口给出去。 + + + 构造函数 + + CPU版本 + IP地址和端口号 + 超时时间 + + + + 构造函数 + + CPU版本 + IP地址 + 端口号 + 超时时间 + - 打开长连接(如果已经是连接状态会先关闭再打开) + 打开连接(如果已经是连接状态会先关闭再打开) @@ -2207,20 +2549,20 @@ - 读取数据 + 读取字节数组 地址 - - - + 读取长度 + 是否Bit类型 + 暂未使用 - summary + 读取字符串 - - + 地址 + 读取长度 @@ -2348,577 +2690,308 @@ 读取UInt64 地址 - - - - - 读取Int32 - - 地址 - 读取数量 - - - - - 读取Float - - 地址 - - - - - 读取Float - - 地址 - 读取数量 - - - - - 读取Double - - 地址 - - - - - 读取Double - - 地址 - 读取数量 - - - - - 读取String - - 地址 - - - - - 批量写入 - TODO 可以重构后面的Write 都走BatchWrite - - - - - - - 分批写入,默认按12个地址打包读取 - - 地址集合 - 批量读取数量 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 写入数据 - - 地址 - 值 - - - - - 获取区域类型代码 - - - - - - - 转换成写入需要的通讯信息 - - - - - - - - 获取读指令 - - - - - - 获取读指令 - - - - - - - 获取写指令 - - - - - - - 获取写指令 - - - - - - - 获取需要读取的长度 - - - - - - - 获取读取PLC地址的开始位置 - - - - - - - 地址 - - - - - 数据类型 - - - - - 站号 - - - - - 功能码 - - - - - 地址 - - - - - 站号 - - - - - 功能码 - - - - - - - - - - 类型的代号值 - - - - - 数据的类型,0代表按字,1代表按位 - - - - - 指示地址是10进制,还是16进制的 - - - - - 数据的类型,0代表按字,1代表按位 - - - - - 指示地址是10进制,还是16进制的 - - - - - X输入寄存器 - - - - - Y输出寄存器 - - - - - M中间寄存器 - - - - - S状态寄存器 - - - - - D数据寄存器 - - - - - R文件寄存器 - - - - - - - - - - 开始地址 - - - - - 数据类型 - - - - - 三菱PLC数据类型 - - - - - - - 数据类型的代号 - 0或1,默认为0 - 十进制或十六进制 + - + - 类型的代号值 + 读取Int32 + 地址 + 读取数量 + - + - 0代表按字,1代表按位 + 读取Float + 地址 + - + - 10进制或16进制 + 读取Float + 地址 + 读取数量 + - + - X输入继电器 + 读取Double + 地址 + - + - Y输出继电器 + 读取Double + 地址 + 读取数量 + - + - M中间继电器 + 读取String + 地址 + - + - D数据寄存器 + 批量写入 + TODO 可以重构后面的Write 都走BatchWrite + + - + - W链接寄存器 + 分批写入,默认按12个地址打包读取 + 地址集合 + 批量读取数量 + - + - L锁存继电器 + 写入数据 + 地址 + 值 + - + - F报警器 + 写入数据 + 地址 + 值 + 值 + - + - V边沿继电器 + 写入数据 + 地址 + 值 + - + - B链接继电器 + 写入数据 + 地址 + 值 + - + - R文件寄存器 + 写入数据 + 地址 + 值 + - + - S步进继电器 + 写入数据 + 地址 + 值 + - + - 变址寄存器 + 写入数据 + 地址 + 值 + - + - 定时器的当前值 + 写入数据 + 地址 + 值 + - + - 定时器的触点 + 写入数据 + 地址 + 值 + - + - 定时器的线圈 + 写入数据 + 地址 + 值 + - + - 累计定时器的触点 + 写入数据 + 地址 + 值 + - + - 累计定时器的线圈 + 写入数据 + 地址 + 值 + - + - 累计定时器的当前值 + 写入数据 + 地址 + 值 + - + - 计数器的当前值 + 获取区域类型代码 + + - + - 计数器的触点 + 转换成写入需要的通讯信息 + + + - + - 计数器的线圈 - + 获取读指令 + + - + - 文件寄存器ZR区 + 获取读指令 + + - + - 要写入的数据 + 获取写指令 + + - + - 赋值 + 获取写指令 + + - + - 要写入的数据 + 获取需要读取的长度 + + - + - 赋值 + 获取读取PLC地址的开始位置 + + - + - 请求结果 + 地址 - + - 是否成功 + 数据类型 - + - 异常消息 + 站号 - + - 异常Code - 408 连接失败 + 功能码 - + - 详细异常 + 地址 - + - 异常集合 + 站号 - + - 请求报文 + 功能码 - + - 响应报文 + 三菱解析后的地址信息 - + - 耗时(毫秒) + 开始地址 - + - 结束时间统计 + 类型的代号 - + - 开始时间 + 数据的类型,0代表按字,1代表按位 - + - 请求结果 + 指示地址是10进制,还是16进制的 - + - 数据结果 + 西门子[写]解析后的地址信息 - + - 结束时间统计 + 要写入的数据 - + 赋值 - + Siemens命令常量 @@ -3246,37 +3319,42 @@ 小端序 DCBA - + + + 西门子解析后的地址信息 + + + 原地址 - + 数据类型 - + 区域类型 - + DB块编号 - + 开始地址(西门子plc地址为8个位的长度,这里展开实际的开始地址。) - + 读取或写入长度 - + 是否读取或写入bit类型 @@ -3526,6 +3604,106 @@ + + + 请求结果 + + + + + 是否成功 + + + + + 异常消息 + + + + + 异常Code + 408 连接失败 + + + + + 详细异常 + + + + + 异常集合 + + + + + 请求报文 + + + + + 响应报文 + + + + + 请求报文2 + + + + + 响应报文2 + + + + + 耗时(毫秒) + + + + + 结束时间统计 + + + + + 开始时间 + + + + + 设置异常信息和Succeed状态 + + + + + + + 请求结果 + + + + + 数据结果 + + + + + 结束时间统计 + + + + + 赋值 + + + + + 设置异常信息和Succeed状态 + + + + SerialPort基类 @@ -3629,21 +3807,22 @@ - + - 读取 + Socket读取 socket 读取长度 + 日记委托记录 - 读取 + Socket读取 - - - + socket + 读取长度 + 日记委托记录 读到的数据,如果内部出现异常则返回null diff --git a/IoTClient/Models/MitsubishiA1Data.cs b/IoTClient/Models/MitsubishiA1Data.cs deleted file mode 100644 index 7dbaefd..0000000 --- a/IoTClient/Models/MitsubishiA1Data.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace IoTClient.Models -{ - public class MitsubishiA1Data - { - /// - /// 开始地址 - /// - public int BeginAddress { get; set; } - /// - /// 数据类型 - /// - public MitsubishiA1Type MitsubishiA1Type { get; set; } - } -} diff --git a/IoTClient/Models/Result.cs b/IoTClient/Result.cs similarity index 73% rename from IoTClient/Models/Result.cs rename to IoTClient/Result.cs index c55b2f8..c54e896 100644 --- a/IoTClient/Models/Result.cs +++ b/IoTClient/Result.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace IoTClient.Models +namespace IoTClient { /// /// 请求结果 @@ -48,6 +48,16 @@ public Result() /// public string Response { get; set; } + /// + /// 请求报文2 + /// + public string Requst2 { get; set; } + + /// + /// 响应报文2 + /// + public string Response2 { get; set; } + /// /// 耗时(毫秒) /// @@ -66,6 +76,21 @@ internal Result EndTime() /// 开始时间 /// public DateTime InitialTime { get; protected set; } = DateTime.Now; + + /// + /// 设置异常信息和Succeed状态 + /// + /// + /// + public Result SetErrInfo(Result result) + { + IsSucceed = result.IsSucceed; + Err = result.Err; + ErrList = result.ErrList; + ErrCode = result.ErrCode; + Exception = result.Exception; + return this; + } } /// @@ -121,5 +146,16 @@ private void Assignment(Result result) Exception = result.Exception; ErrCode = result.ErrCode; } + + /// + /// 设置异常信息和Succeed状态 + /// + /// + /// + public new Result SetErrInfo(Result result) + { + base.SetErrInfo(result); + return this; + } } } diff --git a/IoTClient/SerialPortBase.cs b/IoTClient/SerialPortBase.cs index 4654dc2..ff26253 100644 --- a/IoTClient/SerialPortBase.cs +++ b/IoTClient/SerialPortBase.cs @@ -45,10 +45,14 @@ protected Result Connect() } catch (Exception ex) { + if (serialPort?.IsOpen ?? false) serialPort?.Close(); result.IsSucceed = false; result.Err = ex.Message; + result.ErrCode = 408; + result.Exception = ex; + result.ErrList.Add(ex.Message); } - return result; + return result.EndTime(); } /// @@ -103,7 +107,7 @@ protected byte[] SerialPortRead(SerialPort serialPort) { //延时处理 Thread.Sleep(20); - } + } byte[] buffer = new byte[serialPort.BytesToRead]; var length = serialPort.Read(buffer, 0, buffer.Length); //TODO 是否 length 可能 不等于 buffer.Length ?? diff --git a/IoTClient/SocketBase.cs b/IoTClient/SocketBase.cs index 86e248a..37dae8c 100644 --- a/IoTClient/SocketBase.cs +++ b/IoTClient/SocketBase.cs @@ -81,14 +81,15 @@ public Result Close() } /// - /// 读取 + /// Socket读取 /// /// socket /// 读取长度 + /// 日记委托记录 /// - protected byte[] SocketRead(Socket socket, int receiveCount) + protected byte[] SocketRead(Socket socket, int receiveCount, LoggerDelegate warningLog = null) { - byte[] receiveBytes = SocketTryRead(socket, receiveCount); + byte[] receiveBytes = SocketTryRead(socket, receiveCount, warningLog); if (receiveBytes == null) { socket?.SafeClose(); @@ -98,11 +99,11 @@ protected byte[] SocketRead(Socket socket, int receiveCount) } /// - /// 读取 + /// Socket读取 /// - /// - /// - /// + /// socket + /// 读取长度 + /// 日记委托记录 /// 读到的数据,如果内部出现异常则返回null protected byte[] SocketTryRead(Socket socket, int receiveCount, LoggerDelegate warningLog = null) { diff --git a/IoTServer/IoTServer/IoTServer.xml b/IoTServer/IoTServer/IoTServer.xml index a0ad4b6..9c3ac17 100644 --- a/IoTServer/IoTServer/IoTServer.xml +++ b/IoTServer/IoTServer/IoTServer.xml @@ -120,6 +120,35 @@ + + + + + + + + + + 启动服务 + + + + + 停止服务 + + + + + 客户端连接到服务端 + + + + + + 接收客户端发送的消息 + + + 启动服务 diff --git a/IoTServer/Servers/PLC/AllenBradleyServer.cs b/IoTServer/Servers/PLC/AllenBradleyServer.cs new file mode 100644 index 0000000..3335d69 --- /dev/null +++ b/IoTServer/Servers/PLC/AllenBradleyServer.cs @@ -0,0 +1,196 @@ +using IoTClient.Common.Helpers; +using IoTServer.Common; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace IoTServer.Servers.PLC +{ + public class AllenBradleyServer : ServerSocketBase, IIoTServer + { + private Socket socketServer; + private string ip; + private int port; + List sockets = new List(); + DataPersist dataPersist; + + /// + /// + /// + /// + /// + public AllenBradleyServer(int port, string ip = null) + { + this.ip = ip; + this.port = port; + dataPersist = new DataPersist("AllenBradley"); + } + + /// + /// 启动服务 + /// + 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); }); + } + + /// + /// 停止服务 + /// + public void Stop() + { + if (socketServer?.Connected ?? false) socketServer.Shutdown(SocketShutdown.Both); + socketServer?.Close(); + } + + /// + /// 客户端连接到服务端 + /// + /// + 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; + } + + /// + /// 接收客户端发送的消息 + /// + /// + 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(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) + { + } + } + } + } +} diff --git a/README.md b/README.md index fb8268d..802accb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ - 本组件终身开源免费,采用最宽松MIT协议,您也可以随意修改和商业使用(商业使用请做好评估和测试)。 - 开发工具:Visual Studio 2019 - QQ交流群:[995475200](https://jq.qq.com/?_wv=1027&k=5bz0ne5) -- IoTClient Tool [下载1](https://github.com/zhaopeiym/IoTClient/releases/download/0.4.0/IoTClient.0.4.0.exe) [下载2](https://download.haojima.net/api/IoTClient/Download) # 使用说明 ## 引用组件 @@ -14,8 +13,10 @@ 或图形化安装 ![image](https://user-images.githubusercontent.com/5820324/68722366-2fc5bf00-05f0-11ea-8282-f2b0a58a9f9d.png) -## ModBusTcp读写操作 +
+ModBusTcp读写操作(点击展开) +## ModBusTcp读写操作 ``` //1、实例化客户端 - 输入正确的IP和端口 ModBusTcpClient client = new ModBusTcpClient("127.0.0.1", 502); @@ -24,15 +25,15 @@ ModBusTcpClient client = new ModBusTcpClient("127.0.0.1", 502); client.Write("4", (short)33, 2, 16); //2.1、【注意】写入数据的时候需要明确数据类型 -client.Write("0", (short)33, 2, 16); //写入的是short类型数值 -client.Write("4", (ushort)33, 2, 16); //写入的是ushort类型数值 -client.Write("8", (int)33, 2, 16); //写入的是int类型数值 -client.Write("12", (uint)33, 2, 16); //写入的是uint类型数值 -client.Write("16", (long)33, 2, 16); //写入的是long类型数值 -client.Write("20", (ulong)33, 2, 16); //写入的是ulong类型数值 -client.Write("24", (float)33, 2, 16); //写入的是float类型数值 -client.Write("28", (double)33, 2, 16); //写入的是double类型数值 -client.Write("32", true, 2, 5); //写入的是线圈类型数值 +client.Write("0", (short)33, 2, 16); //写入short类型数值 +client.Write("4", (ushort)33, 2, 16); //写入ushort类型数值 +client.Write("8", (int)33, 2, 16); //写入int类型数值 +client.Write("12", (uint)33, 2, 16); //写入uint类型数值 +client.Write("16", (long)33, 2, 16); //写入long类型数值 +client.Write("20", (ulong)33, 2, 16); //写入ulong类型数值 +client.Write("24", (float)33, 2, 16); //写入float类型数值 +client.Write("28", (double)33, 2, 16); //写入double类型数值 +client.Write("32", true, 2, 5); //写入线圈类型值 //3、读操作 - 参数依次是:地址 、站号 、功能码 var value = client.ReadInt16("4", 2, 3).Value; @@ -94,8 +95,12 @@ var result = client.BatchRead(list); //IP、端口、超时时间、大小端设置 ModBusTcpClient client = new ModBusTcpClient("127.0.0.1", 502, 1500, EndianFormat.ABCD); ``` +ModBusTcp更多使用方式,请参考[单元测试](https://github.com/zhaopeiym/IoTClient/blob/master/IoTClient.Tests/Modbus_Tests/ModBusTcpClient_tests.cs) + +
-### ModBusTcp更多使用方式,请参考[单元测试](https://github.com/zhaopeiym/IoTClient/blob/master/IoTClient.Tests/Modbus_Tests/ModBusTcpClient_tests.cs) +
+ModBusRtu读写操作 ## ModBusRtu读写操作 ``` @@ -104,6 +109,10 @@ ModBusRtuClient client = new ModBusRtuClient("COM3", 9600, 8, StopBits.One, Pari //其他读写操作和ModBusTcpClient的读写操作一致 ``` +
+ +
+ModBusAscii读写操作 ## ModBusAscii读写操作 ``` @@ -112,6 +121,10 @@ ModbusAsciiClient client = new ModbusAsciiClient("COM3", 9600, 8, StopBits.One, //其他读写操作和ModBusTcpClient的读写操作一致 ``` +
+ +
+ModbusRtuOverTcp读写操作 ## ModbusRtuOverTcp读写操作 ``` @@ -122,10 +135,15 @@ ModbusRtuOverTcpClient client = new ModbusRtuOverTcpClient("127.0.0.1", 502, 150 //其他读写操作和ModBusTcpClient的读写操作一致 ``` +
+ +
+SiemensClient(西门子)读写操作 ## SiemensClient(西门子)读写操作 ``` -//1、实例化客户端 - 输入正确的IP和端口 +//1、实例化客户端 - 输入型号、IP和端口 +//其他型号:SiemensVersion.S7_200、SiemensVersion.S7_300、SiemensVersion.S7_400、SiemensVersion.S7_1200、SiemensVersion.S7_1500 SiemensClient client = new SiemensClient(SiemensVersion.S7_200Smart, "127.0.0.1",102); //2、写操作 @@ -155,6 +173,11 @@ var response = result.Response; var value4 = result.Value; ``` +
+ +
+注意:关于Siemens的PLC地址 + ## 注意:关于Siemens的PLC地址 ``` VB263、VW263、VD263中的B、W、D分别表示:byte型(8位)、word型(16位)、doubleword型(32位)。 @@ -173,6 +196,10 @@ DB1.DBD0 - client.ReadFloat("DB1.0") |byte | VB1 | DB1.DBB1 |shor
ushort | VW2 | DB1.DBW2 |int
uint
float | VD4 | DB1.DBD4 +
+ +
+SiemensClient最佳实践 ## SiemensClient最佳实践 ``` @@ -206,6 +233,10 @@ client.Write("DB4.12", (float)9); //写入的是float类型 5、SiemensClient是线程安全类 由于plc长连接有限,SiemensClient被设计成线程安全类。可以把SiemensClient设置成单例,在多个线程之间使用SiemensClient的实例读写操作plc。 ``` +
+ +
+MitsubishiClient(三菱)读写操作 ## MitsubishiClient(三菱)读写操作 ``` @@ -238,6 +269,10 @@ var response = result.Response; //5.5 读取到的值 var value4 = result.Value; ``` +
+ +
+OmronFinsClient(欧姆龙)读写操作 ## OmronFinsClient(欧姆龙)读写操作 ``` @@ -270,10 +305,49 @@ var response = result.Response; //5.5 读取到的值 var value4 = result.Value; ``` +
+ +
+AllenBradleyClient(罗克韦尔)读写操作 + +## AllenBradleyClient(罗克韦尔)读写操作 +``` +//1、实例化客户端 - 输入正确的IP和端口 +AllenBradleyClient client = new AllenBradleyClient("127.0.0.1",44818); + +//2、写操作 +client.Write("A1", (short)11); + +//3、读操作 +var value = client.ReadInt16("A1").Value; + +//4、如果没有主动Open,则会每次读写操作的时候自动打开自动和关闭连接,这样会使读写效率大大减低。所以建议手动Open和Close。 +client.Open(); + +//5、读写操作都会返回操作结果对象Result +var result = client.ReadInt16("A1"); +//5.1 读取是否成功(true或false) +var isSucceed = result.IsSucceed; +//5.2 读取失败的异常信息 +var errMsg = result.Err; +//5.3 读取操作实际发送的请求报文 +var requst = result.Requst; +//5.4 读取操作服务端响应的报文 +var response = result.Response; +//5.5 读取到的值 +var value4 = result.Value; +``` +
-## 其他更多详细使用请[参考](https://github.com/zhaopeiym/IoTClient.Examples) +# 基于IoTClient库的一些项目 +
+IoTClient Tool 桌面程序工具(开源) + +### [IoTClient Tool](https://github.com/zhaopeiym/IoTClient/releases/download/0.4.0/IoTClient.0.4.0.exe) 桌面程序工具,[开源地址](https://github.com/zhaopeiym/IoTClient.Examples)。 + +- 1、可用来测试PLC和相关协议的通信 +- 2、可作为IoTClient库使用例子。 -# [IoTClient Tool](https://github.com/zhaopeiym/IoTClient.Examples)效果图 ![image](https://user-images.githubusercontent.com/5820324/115138587-b7bebc80-a05f-11eb-9f7c-720a88bdca6e.png) ![image](https://user-images.githubusercontent.com/5820324/115138592-bbeada00-a05f-11eb-9fc4-4b15a426cdb3.png) @@ -293,3 +367,38 @@ var value4 = result.Value; ![image](https://user-images.githubusercontent.com/5820324/115138606-c73e0580-a05f-11eb-9ca1-5ece1bae8e71.png) ![image](https://user-images.githubusercontent.com/5820324/115138607-c86f3280-a05f-11eb-83f1-d1706331406a.png) +
+ +
+能源管理系统(商用) + +### 能源管理(现场-单项目) +![image](https://user-images.githubusercontent.com/5820324/117001443-f10c5300-ad14-11eb-8597-bcc6e573c542.png) +![image](https://user-images.githubusercontent.com/5820324/117001444-f1a4e980-ad14-11eb-80ea-0972211e46a1.png) + +### 能源管理(云端-多项目) +![image](https://user-images.githubusercontent.com/5820324/117001447-f23d8000-ad14-11eb-9771-1854b13bef4b.png) +![image](https://user-images.githubusercontent.com/5820324/117001451-f2d61680-ad14-11eb-9507-bf4123e5cbe8.png) +![image](https://user-images.githubusercontent.com/5820324/117001454-f36ead00-ad14-11eb-8ea1-e993298eca9b.png) +![image](https://user-images.githubusercontent.com/5820324/117001460-f49fda00-ad14-11eb-8c75-eb88a24983b6.png) +![image](https://user-images.githubusercontent.com/5820324/117001461-f5d10700-ad14-11eb-9d82-d73a7347ad32.png) +![image](https://user-images.githubusercontent.com/5820324/117001464-f6699d80-ad14-11eb-8810-50b20f8954ae.png) + +### 能源管理(移动端) +![image](https://user-images.githubusercontent.com/5820324/116964170-796f0180-acdd-11eb-9514-fd9a05c15eae.png)![image](https://user-images.githubusercontent.com/5820324/116964172-7a079800-acdd-11eb-91ac-13c1a321145d.png)![image](https://user-images.githubusercontent.com/5820324/116964174-7aa02e80-acdd-11eb-8051-158f13ed2993.png)![image](https://user-images.githubusercontent.com/5820324/116964175-7b38c500-acdd-11eb-80b4-97827ee03374.png)![image](https://user-images.githubusercontent.com/5820324/116964177-7c69f200-acdd-11eb-94b8-ddbf5081ddaf.png)![image](https://user-images.githubusercontent.com/5820324/116964179-7d028880-acdd-11eb-95c6-601e235e3b6b.png)![image](https://user-images.githubusercontent.com/5820324/116964181-7d9b1f00-acdd-11eb-9914-911167e0af05.png) + +
+ +
+海底捞末端控制(商用) + +### 海底捞末端控制-web +![image](https://user-images.githubusercontent.com/5820324/117001939-87d90f80-ad15-11eb-8848-7a4956ba1ce9.png) +![image](https://user-images.githubusercontent.com/5820324/117001942-87d90f80-ad15-11eb-85b2-778cadaf85ad.png) +![image](https://user-images.githubusercontent.com/5820324/117001947-890a3c80-ad15-11eb-9e28-57e8b05cd04c.png) +![image](https://user-images.githubusercontent.com/5820324/117001949-89a2d300-ad15-11eb-9226-2e2683e2cc7f.png) + +### 海底捞末端控制-移动端 +![image](https://user-images.githubusercontent.com/5820324/116964517-5002a580-acde-11eb-9bfb-c859a57307c7.png)![image](https://user-images.githubusercontent.com/5820324/116964519-509b3c00-acde-11eb-8245-573ac3fa7f16.png)![image](https://user-images.githubusercontent.com/5820324/116964521-5133d280-acde-11eb-85de-b09dde1ca41e.png)![image](https://user-images.githubusercontent.com/5820324/116964525-51cc6900-acde-11eb-924f-f3320e4a179c.png) + +
\ No newline at end of file