From 246aac8ee495ac31a29be9689c19fd0253683356 Mon Sep 17 00:00:00 2001 From: "2248356998 qq.com" <2248356998@qq.com> Date: Tue, 21 Mar 2023 13:37:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A5=BF=E9=97=A8=E5=AD=90S7?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GlobalUsings.cs | 1 + .../Siemens/Address/SiemensAddress.cs | 226 +++++++++++ .../Siemens/DateTime.cs | 157 ++++++++ .../Siemens/Helper/SiemensHelper.cs | 369 ++++++++++++++++++ .../Siemens/Helper/SiemensHelperCommand.cs | 142 +++++++ .../Siemens/Message/SiemensMessage.cs | 26 ++ .../Siemens/SiemensEnum.cs | 15 + .../Siemens/SiemensS7PLC.cs | 352 +++++++++++++++++ .../Siemens/SiemensS7PLCDataHandleAdapter.cs | 41 ++ .../Siemens/SiemensS7PLCNetMed.cs | 94 +++++ ...sGateway.Foundation.Adapter.Siemens.csproj | 12 + .../ThingsGateway.Foundation.Tests/Modbus.cs | 92 ++--- .../ThingsGateway.Foundation.Tests/Siemens.cs | 67 ++++ .../ThingsGateway.Foundation.Tests.csproj | 1 + .../ThingsGateway.Modbus.csproj | 7 +- .../ThingsGateway.Siemens/GlobalUsings.cs | 11 + src/Plugins/ThingsGateway.Siemens/S7.cs | 87 +++++ src/Plugins/ThingsGateway.Siemens/S7Helper.cs | 211 ++++++++++ src/Plugins/ThingsGateway.Siemens/S7_1200.cs | 28 ++ src/Plugins/ThingsGateway.Siemens/S7_1500.cs | 28 ++ src/Plugins/ThingsGateway.Siemens/S7_200.cs | 28 ++ .../ThingsGateway.Siemens/S7_200SMART.cs | 42 ++ src/Plugins/ThingsGateway.Siemens/S7_300.cs | 28 ++ src/Plugins/ThingsGateway.Siemens/S7_400.cs | 28 ++ .../ThingsGateway.Siemens.csproj | 27 ++ .../SeedData/Json/driver_plugin.json | 66 ++++ .../Page/CollectVariablePage.razor | 31 +- .../Page/DeviceStatusPage.razor | 16 +- src/ThingsGateway.sln | 25 ++ 29 files changed, 2196 insertions(+), 62 deletions(-) create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/GlobalUsings.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Address/SiemensAddress.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/DateTime.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelper.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelperCommand.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Message/SiemensMessage.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensEnum.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLC.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCDataHandleAdapter.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCNetMed.cs create mode 100644 src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/ThingsGateway.Foundation.Adapter.Siemens.csproj create mode 100644 src/Plugins/ThingsGateway.Foundation.Tests/Siemens.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/GlobalUsings.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7Helper.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7_1200.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7_1500.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7_200.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7_200SMART.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7_300.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/S7_400.cs create mode 100644 src/Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.csproj diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/GlobalUsings.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/GlobalUsings.cs new file mode 100644 index 000000000..062a3f981 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/GlobalUsings.cs @@ -0,0 +1 @@ +global using System; diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Address/SiemensAddress.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Address/SiemensAddress.cs new file mode 100644 index 000000000..c8bba47c5 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Address/SiemensAddress.cs @@ -0,0 +1,226 @@ +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + public enum S7Area : byte + { + PE = 0x81, + PA = 0x82, + MK = 0x83, + DB = 0x84, + CT = 0x1C, + TM = 0x1D, + AI = 0X06, + AQ = 0x07, + } + + /// + /// 西门子PLC地址数据信息 + /// + public class SiemensAddress : DeviceAddressBase + { + /// + /// 数据块代码 + /// + public byte DataCode { get; set; } + + /// + /// DB块数据信息 + /// + public ushort DbBlock { get; set; } + + + public static int CalculateAddressStarted(string address, bool isCounterOrTimer = false) + { + if (address.IndexOf('.') < 0) + { + return isCounterOrTimer ? Convert.ToInt32(address) : Convert.ToInt32(address) * 8; + } + + string[] strArray = address.Split('.'); + return (Convert.ToInt32(strArray[0]) * 8) + Convert.ToInt32(strArray[1]); + } + + + public static OperResult ParseFrom(string address) + { + return ParseFrom(address, 0); + } + + public static OperResult ParseFrom(string address, int length) + { + SiemensAddress s7AddressData = new SiemensAddress(); + try + { + address = address.ToUpper(); + s7AddressData.Length = length; + s7AddressData.DbBlock = 0; + if (address.StartsWith("AI")) + { + s7AddressData.DataCode = (byte)S7Area.AI; + if (address.StartsWith("AIX") || address.StartsWith("AIB") || address.StartsWith("AIW") || address.StartsWith("AID")) + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(3)); + } + else + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2)); + } + } + else if (address.StartsWith("AQ")) + { + s7AddressData.DataCode = (byte)S7Area.AQ; + if (address.StartsWith("AQX") || address.StartsWith("AQB") || address.StartsWith("AQW") || address.StartsWith("AQD")) + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(3)); + } + else + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2)); + } + } + else if (address[0] == 'I') + { + s7AddressData.DataCode = (byte)S7Area.PE; + if (address.StartsWith("IX") || address.StartsWith("IB") || address.StartsWith("IW") || address.StartsWith("ID")) + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2)); + } + else + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1)); + } + } + else if (address[0] == 'Q') + { + s7AddressData.DataCode = (byte)S7Area.PA; + if (address.StartsWith("QX") || address.StartsWith("QB") || address.StartsWith("QW") || address.StartsWith("QD")) + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2)); + } + else + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1)); + } + } + else if (address[0] == 'M') + { + s7AddressData.DataCode = (byte)S7Area.MK; + if (address.StartsWith("MX") || address.StartsWith("MB") || address.StartsWith("MW") || address.StartsWith("MD")) + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2)); + } + else + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1)); + } + } + else if (address[0] == 'D' || address.Substring(0, 2) == "DB") + { + s7AddressData.DataCode = (byte)S7Area.DB; + string[] strArray = address.Split('.'); + s7AddressData.DbBlock = address[1] != 'B' ? Convert.ToUInt16(strArray[0].Substring(1)) : Convert.ToUInt16(strArray[0].Substring(2)); + string address1 = address.Substring(address.IndexOf('.') + 1); + if (address1.StartsWith("DBX") || address1.StartsWith("DBB") || address1.StartsWith("DBW") || address1.StartsWith("DBD")) + { + address1 = address1.Substring(3); + } + + s7AddressData.AddressStart = CalculateAddressStarted(address1); + } + else if (address[0] == 'T') + { + s7AddressData.DataCode = (byte)S7Area.TM; + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1), true); + } + else if (address[0] == 'C') + { + s7AddressData.DataCode = (byte)S7Area.CT; + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1), true); + } + else + { + if (address[0] != 'V') + { + return new OperResult("不支持的类型"); + } + + s7AddressData.DataCode = (byte)S7Area.DB; + s7AddressData.DbBlock = 1; + if (address.StartsWith("VB") || address.StartsWith("VW") || address.StartsWith("VD") || address.StartsWith("VX")) + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2)); + } + else + { + s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1)); + } + } + } + catch (Exception ex) + { + return new OperResult(ex.Message); + } + return OperResult.CreateSuccessResult(s7AddressData); + } + + public override void Parse(string address, int length) + { + OperResult from = ParseFrom(address, length); + if (!from.IsSuccess) + { + return; + } + + AddressStart = from.Content.AddressStart; + Length = from.Content.Length; + DataCode = from.Content.DataCode; + DbBlock = from.Content.DbBlock; + } + + /// + public override string ToString() + { + if (DataCode == (byte)S7Area.TM) + { + return "T" + AddressStart.ToString(); + } + + if (DataCode == (byte)S7Area.CT) + { + return "C" + AddressStart.ToString(); + } + + if (DataCode == (byte)S7Area.AI) + { + return "AI" + GetActualStringAddress(AddressStart); + } + + if (DataCode == (byte)S7Area.AQ) + { + return "AQ" + GetActualStringAddress(AddressStart); + } + + if (DataCode == (byte)S7Area.PE) + { + return "I" + GetActualStringAddress(AddressStart); + } + + if (DataCode == (byte)S7Area.PA) + { + return "Q" + GetActualStringAddress(AddressStart); + } + + if (DataCode == (byte)S7Area.MK) + { + return "M" + GetActualStringAddress(AddressStart); + } + + return DataCode == (byte)S7Area.DB ? "DB" + DbBlock.ToString() + "." + GetActualStringAddress(AddressStart) : AddressStart.ToString(); + } + + private static string GetActualStringAddress(int addressStart) + { + return addressStart % 8 == 0 ? (addressStart / 8).ToString() : string.Format("{0}.{1}", addressStart / 8, addressStart % 8); + } + + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/DateTime.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/DateTime.cs new file mode 100644 index 000000000..5a4cd63e0 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/DateTime.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; + + +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + /// + /// https://github.com/S7NetPlus/s7netplus/blob/develop/S7.Net/Types/DateTime.cs + /// Contains the methods to convert between and S7 representation of datetime values. + /// + public static class DateTime + { + /// + /// The minimum value supported by the specification. + /// + public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1); + + /// + /// The maximum value supported by the specification. + /// + public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999); + + /// + /// Parses a value from bytes. + /// + /// Input bytes read from PLC. + /// A object representing the value read from PLC. + /// Thrown when the length of + /// is not 8 or any value in + /// is outside the valid range of values. + public static System.DateTime FromByteArray(byte[] bytes) + { + return FromByteArrayImpl(bytes); + } + + /// + /// Parses an array of values from bytes. + /// + /// Input bytes read from PLC. + /// An array of objects representing the values read from PLC. + /// Thrown when the length of + /// is not a multiple of 8 or any value in + /// is outside the valid range of values. + public static System.DateTime[] ToArray(byte[] bytes) + { + if (bytes.Length % 8 != 0) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long."); + + var cnt = bytes.Length / 8; + var result = new System.DateTime[bytes.Length / 8]; + + for (var i = 0; i < cnt; i++) + result[i] = FromByteArrayImpl(new ArraySegment(bytes, i * 8, 8)); + + return result; + } + + private static System.DateTime FromByteArrayImpl(IList bytes) + { + if (bytes.Count != 8) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count, + $"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long."); + + int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111); + + int ByteToYear(byte bcdYear) + { + var input = DecodeBcd(bcdYear); + if (input < 90) return input + 2000; + if (input < 100) return input + 1900; + + throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear, + $"Value '{input}' is higher than the maximum '99' of S7 date and time representation."); + } + + int AssertRangeInclusive(int input, byte min, byte max, string field) + { + if (input < min) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is lower than the minimum '{min}' allowed for {field}."); + if (input > max) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is higher than the maximum '{max}' allowed for {field}."); + + return input; + } + + var year = ByteToYear(bytes[0]); + var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month"); + var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month"); + var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour"); + var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute"); + var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second"); + var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits"); + var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit"); + var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week"); + + return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec); + } + + /// + /// Converts a value to a byte array. + /// + /// The DateTime value to convert. + /// A byte array containing the S7 date time representation of . + /// Thrown when the value of + /// is before + /// or after . + public static byte[] ToByteArray(System.DateTime dateTime) + { + byte EncodeBcd(int value) + { + return (byte)((value / 10 << 4) | value % 10); + } + + if (dateTime < SpecMinimumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation."); + + if (dateTime > SpecMaximumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation."); + + byte MapYear(int year) => (byte)(year < 2000 ? year - 1900 : year - 2000); + + int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int)dayOfWeek + 1; + + return new[] + { + EncodeBcd(MapYear(dateTime.Year)), + EncodeBcd(dateTime.Month), + EncodeBcd(dateTime.Day), + EncodeBcd(dateTime.Hour), + EncodeBcd(dateTime.Minute), + EncodeBcd(dateTime.Second), + EncodeBcd(dateTime.Millisecond / 10), + (byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek)) + }; + } + + /// + /// Converts an array of values to a byte array. + /// + /// The DateTime values to convert. + /// A byte array containing the S7 date time representations of . + /// Thrown when any value of + /// is before + /// or after . + public static byte[] ToByteArray(System.DateTime[] dateTimes) + { + var bytes = new List(dateTimes.Length * 8); + foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime)); + + return bytes.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelper.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelper.cs new file mode 100644 index 000000000..6ed0daa9c --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelper.cs @@ -0,0 +1,369 @@ +using System.Text; +using System.Threading.Tasks; + +using ThingsGateway.Foundation.Extension; + +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + internal partial class SiemensHelper + { + #region 验证 + //internal static OperResult AnalysisReadBit(byte[] content) + //{ + // int length = 1; + // if (content.Length < 21 || content[20] != 1) + // return new OperResult("数据块长度校验失败"); + // byte[] numArray = new byte[length]; + // if (content[21] == byte.MaxValue && content[22] == 3)//Bit:3;Byte:4;Counter或者Timer:9 + // { + // numArray[0] = content[25];//+4 + // } + // else + // { + // return new OperResult((int)content[21] + GetCpuError(content[21])); + // } + // return OperResult.CreateSuccessResult(numArray); + //} + + internal static OperResult AnalysisReadByte(byte[] sends, byte[] content) + { + int length = 0; + int itemLen = (sends.Length - 19) / 12; + + for (int index = 0; index < itemLen; index++) + { + if (sends[22 + (index * 12)] >= (byte)S7WordLength.Word) + { + length += ((sends[23 + (index * 12)] * 256) + sends[24 + (index * 12)]) * 2; + } + else + { + length += (sends[23 + (index * 12)] * 256) + sends[24 + (index * 12)]; + } + } + + if (content.Length < 21 || content[20] != itemLen) + { + return new OperResult("数据块长度校验失败"); + } + + byte[] dataArray = new byte[length]; + int index1 = 0; + int dataIndex = 0; + for (int index2 = 21; index2 < content.Length; index2++) + { + if (index2 + 1 < content.Length) + { + int s7len = 0; + if (sends[22 + (index1 * 12)] >= (byte)S7WordLength.Word) + { + s7len = ((sends[23 + (index1 * 12)] * 256) + sends[24 + (index1 * 12)]) * 2; + } + else + { + s7len = (sends[23 + (index1 * 12)] * 256) + sends[24 + (index1 * 12)]; + } + if (content[index2] == byte.MaxValue && content[index2 + 1] == 4)//Bit:3;Byte:4;Counter或者Timer:9 + { + Array.Copy(content, index2 + 4, dataArray, dataIndex, s7len); + index2 += s7len + 3; + dataIndex += s7len; + index1++; + } + else if (content[index2] == byte.MaxValue && content[index2 + 1] == 9)//Counter或者Timer:9 + { + int num = (content[index2 + 2] * 256) + content[index2 + 3]; + if (num % 3 == 0) + { + for (int index3 = 0; index3 < num / 3; index3++) + { + Array.Copy(content, index2 + 5 + (3 * index3), dataArray, dataIndex, 2); + dataIndex += 2; + } + } + else + { + for (int index4 = 0; index4 < num / 5; index4++) + { + Array.Copy(content, index2 + 7 + (5 * index4), dataArray, dataIndex, 2); + dataIndex += 2; + } + } + index2 += num + 4; + index1++; + } + else + { + return new OperResult((int)content[index2] + GetCpuError(content[index2])); + } + } + } + return OperResult.CreateSuccessResult(dataArray); + + } + + internal static OperResult AnalysisWrite(byte[] content) + { + if (content.Length < 22) + { + return new OperResult(content) { Message = "未知错误" }; + } + + byte err = content[21]; + if (err == byte.MaxValue) + { + return new OperResult((int)content[21] + GetCpuError(content[21])); + } + else + { + return OperResult.CreateSuccessResult(content); + } + + } + private static string GetCpuError(ushort Error) + { + switch (Error) + { + case 0x05: return "地址超限"; + case 0x06: return "返回长度无效"; + case 0x07: return "数据大小不匹配"; + case 0x0a: + case 0xd209: return "数据块不存在"; + case 0x8500: return "超出PDU大小"; + case 0xdc01: return "无效的值"; + case 0x8104: return "功能不可用"; + case 0xd241: return "需要密码"; + case 0xd602: return "无效密码"; + case 0xd604: + case 0xd605: return "没有设置密码或已清除"; + default: + return "未知错误"; + }; + } + + #endregion + + + + + + #region 获取报文 + internal static OperResult GetReadCommand(SiemensAddress[] siemensAddress) + { + if (siemensAddress == null) + { + return new OperResult("地址为null"); + } + int len = siemensAddress.Length <= 19 ? siemensAddress.Length : throw new Exception("读取数量大于19!"); + int telegramLen = len * 12 + 19; + int parameterLen = len * 12 + 2; + + byte[] numArray = new byte[telegramLen]; + + Array.Copy(S7_MULRW_HEADER, 0, numArray, 0, S7_MULRW_HEADER.Length); + numArray[2] = (byte)(telegramLen / 256); + numArray[3] = (byte)(telegramLen % 256); + numArray[13] = (byte)(parameterLen / 256); + numArray[14] = (byte)(parameterLen % 256); + numArray[18] = (byte)len; + + for (int index = 0; index < len; index++) + { + Array.Copy(S7_MULRD_ITEM, 0, numArray, 19 + (index * 12), S7_MULRD_ITEM.Length); + if (siemensAddress[index].DataCode == (byte)S7WordLength.Counter || siemensAddress[index].DataCode == (byte)S7WordLength.Timer) + { + numArray[22 + (index * 12)] = siemensAddress[index].DataCode; + numArray[23 + (index * 12)] = (byte)(siemensAddress[index].Length / 256); + numArray[24 + (index * 12)] = (byte)(siemensAddress[index].Length % 256); + } + else + { + numArray[22 + (index * 12)] = (byte)S7WordLength.Byte; + numArray[23 + (index * 12)] = (byte)(siemensAddress[index].Length / 256); + numArray[24 + (index * 12)] = (byte)(siemensAddress[index].Length % 256); + } + numArray[25 + (index * 12)] = (byte)(siemensAddress[index].DbBlock / 256U); + numArray[26 + (index * 12)] = (byte)(siemensAddress[index].DbBlock % 256U); + numArray[27 + (index * 12)] = siemensAddress[index].DataCode; + numArray[28 + (index * 12)] = (byte)(siemensAddress[index].AddressStart / 256 / 256 % 256); + numArray[29 + (index * 12)] = (byte)(siemensAddress[index].AddressStart / 256 % 256); + numArray[30 + (index * 12)] = (byte)(siemensAddress[index].AddressStart % 256); + } + return OperResult.CreateSuccessResult(numArray); + } + + internal static OperResult GetWriteBitCommand(SiemensAddress address, bool data) + { + int len = 1; + int telegramLen = 16 + 19 + len;//最后的1是写入值的byte数量 + int parameterLen = 12 + 2; + + byte[] numArray = new byte[telegramLen]; + + Array.Copy(S7_MULRW_HEADER, 0, numArray, 0, S7_MULRW_HEADER.Length); + numArray[2] = (byte)(telegramLen / 256); + numArray[3] = (byte)(telegramLen % 256); + numArray[13] = (byte)(parameterLen / 256); + numArray[14] = (byte)(parameterLen % 256); + numArray[15] = (byte)((4 + len) / 256); + numArray[16] = (byte)((4 + len) % 256); + numArray[17] = 5; + numArray[18] = (byte)1; + //写入Item与读取大致相同 + numArray[19] = (byte)18; + numArray[20] = (byte)10; + numArray[21] = (byte)16; + numArray[22] = (byte)S7WordLength.Bit; + numArray[23] = (byte)(len / 256); + numArray[24] = (byte)(len % 256); + numArray[25] = (byte)(address.DbBlock / 256U); + numArray[26] = (byte)(address.DbBlock % 256U); + numArray[27] = (byte)address.DataCode; + numArray[28] = (byte)(address.AddressStart / 256 / 256); + numArray[29] = (byte)(address.AddressStart / 256); + numArray[30] = (byte)(address.AddressStart % 256); + //后面跟的是写入的数据信息 + numArray[31] = 0; + numArray[32] = 3;//Bit:3;Byte:4;Counter或者Timer:9 + numArray[33] = (byte)(len / 256); + numArray[34] = (byte)(len % 256); + numArray[35] = (byte)(data ? 1 : 0); + + return OperResult.CreateSuccessResult(numArray); + + } + + internal static OperResult GetWriteByteCommand(SiemensAddress address, byte[] data) + { + int len = data.Length; + int telegramLen = 16 + 19 + len;//最后的1是写入值的byte数量 + int parameterLen = 12 + 2; + + byte[] numArray = new byte[telegramLen]; + + Array.Copy(S7_MULRW_HEADER, 0, numArray, 0, S7_MULRW_HEADER.Length); + numArray[2] = (byte)(telegramLen / 256); + numArray[3] = (byte)(telegramLen % 256); + numArray[13] = (byte)(parameterLen / 256); + numArray[14] = (byte)(parameterLen % 256); + numArray[15] = (byte)((4 + len) / 256); + numArray[16] = (byte)((4 + len) % 256); + numArray[17] = 5; + numArray[18] = (byte)1; + //写入Item与读取大致相同 + numArray[19] = (byte)18; + numArray[20] = (byte)10; + numArray[21] = (byte)16; + numArray[22] = (byte)S7WordLength.Byte; + numArray[23] = (byte)(len / 256); + numArray[24] = (byte)(len % 256); + + numArray[25] = (byte)(address.DbBlock / 256U); + numArray[26] = (byte)(address.DbBlock % 256U); + numArray[27] = (byte)address.DataCode; + numArray[28] = (byte)(address.AddressStart / 256 / 256); + numArray[29] = (byte)(address.AddressStart / 256); + numArray[30] = (byte)(address.AddressStart % 256); + //后面跟的是写入的数据信息 + numArray[31] = 0; + numArray[32] = 4;//Bit:3;Byte:4;Counter或者Timer:9 + numArray[33] = (byte)(len * 8 / 256); + numArray[34] = (byte)(len * 8 % 256); + data.CopyTo(numArray, 35); + + return OperResult.CreateSuccessResult(numArray); + + } + + #endregion + + #region 字符串 + + internal static async Task> ReadString( + SiemensS7PLC plc, string address, Encoding encoding) + { + //先读取一次获取长度,再读取实际值 + if (plc.CurrentPlc != SiemensEnum.S200Smart) + { + var result1 = await plc.ReadAsync(address, 2); + if (!result1.IsSuccess) + { + return result1.Copy(); + } + if (result1.Content[0] == (byte)0 || result1.Content[0] == byte.MaxValue) + { + return new OperResult("在PLC中不是字符串类型"); + } + var result2 = await plc.ReadAsync(address, 2 + result1.Content[1]); + if (!result2.IsSuccess) + { + return result2.Copy(); + } + else + { + return OperResult.CreateSuccessResult(encoding.GetString(result2.Content, 2, result2.Content.Length - 2)); + } + } + else + { + var result1 = await plc.ReadAsync(address, 1); + if (!result1.IsSuccess) + return result1.Copy(); + var result2 = await plc.ReadAsync(address, 1 + result1.Content[0]); + if (!result2.IsSuccess) + { + return result2.Copy(); + } + else + { + return OperResult.CreateSuccessResult(encoding.GetString(result2.Content, 1, result2.Content.Length - 1)); + } + + } + + } + + internal static async Task Write(SiemensS7PLC plc, string address, string value, Encoding encoding) + { + if (value == null) + value = string.Empty; + byte[] inBytes = encoding.GetBytes(value); + //if (encoding == Encoding.Unicode) + // inBytes = inBytes.BytesReverseByWord(); + if (plc.CurrentPlc != SiemensEnum.S200Smart) + { + OperResult result = await plc.ReadAsync(address, 2); + if (!result.IsSuccess) return result; + if (result.Content[0] == byte.MaxValue) return new OperResult("在PLC中不是字符串类型"); + if (result.Content[0] == 0) result.Content[0] = 254; + if (value.Length > result.Content[0]) return new OperResult("写入值长度超限"); + return await plc.WriteAsync( + address, + DataHelper.SpliceArray(new byte[2] { result.Content[0], (byte)value.Length }, + inBytes + )); + } + return await plc.WriteAsync(address, DataHelper.SpliceArray(new byte[1] + { + (byte) value.Length + }, inBytes)); + } + + #endregion + } + + + public enum S7WordLength : byte + { + Bit = 0x01, + Byte = 0x02, + Char = 0x03, + Word = 0x04, + Int = 0x05, + DWord = 0x06, + DInt = 0x07, + Real = 0x08, + Counter = 0x1C, + Timer = 0x1D, + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelperCommand.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelperCommand.cs new file mode 100644 index 000000000..7d124e373 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Helper/SiemensHelperCommand.cs @@ -0,0 +1,142 @@ +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + internal partial class SiemensHelper + { + /// + /// S7连读写请求头(包含ISO头和COTP头) + /// + static byte[] S7_MULRW_HEADER = { + 0x03,0x00, + 0x00,0x1f, // 报文长度(item.len*12+19,注意:根据传入读取item数量更改) + 0x02,0xf0, 0x80, //COTP信息 + 0x32, // S7协议ID + 0x01, // 类型,请求命令 + 0x00,0x00, // 冗余识别 + 0x00,0x01, // 序列号 + 0x00,0x0e, // parameter长度(item.len*12+2,注意:根据传入读取item数量更改) + 0x00,0x00, // Data Length+4 ,写入时填写,读取时为0 + 0x04, // 4 Read Var, 5 Write Var ,注意更改 + 0x01, // Item数量(item.len,注意:根据传入读取item数量更改) + }; + + // S7变量多读Item + static byte[] S7_MULRD_ITEM = { + 0x12, // Var 规范. + 0x0a, // 剩余的字节长度 + 0x10, // Syntax ID + (byte)S7WordLength.Byte, // 相关的数据长度代码(注意:根据传入的变量更改) + 0x00,0x01, // 数据长度 (注意:根据传入的变量更改) + 0x00,0x00, // DB编号 (注意:根据传入的变量更改) + 0x84, // 数据块类型 (注意:根据传入的变量更改) + 0x00,0x00,0x00 // 数据块偏移量 (注意:根据传入的变量更改) + }; + + + // ISO连接请求报文(也包含ISO头和COTP头) + internal static byte[] ISO_CR = { + // TPKT (RFC1006 Header) + 0x03, // RFC 1006 ID (3) + 0x00, // 保留 0 + 0x00, // 数据包长度 (整个框架、有效载荷和TPDU包括在内) + 0x16, // 数据包长度 (整个框架、有效载荷和TPDU包括在内) + // COTP (ISO 8073 Header) + 0x11, // PDU Size Length + 0xE0, // CR -连接请求ID + 0x00, 0x00, // Dst Reference + 0x00, 0x01, // Src Reference + 0x00, // Class + Options Flags + //对于S7200/Smart ,下面参数也需要重写 + 0xC0, // PDU Max Length ID + 0x01, 0x0A, // PDU Max Length + 0xC1, // Src TSAP Identifier + 0x02, // Src TSAP Length (2 bytes) + 0x01, 0x00, // Src TSAP (需重写) + 0xC2, // Dst TSAP Identifier + 0x02, // Dst TSAP Length (2 bytes) + 0x01, 0x02 // Dst TSAP (需重写) + }; + // ISO连接请求报文(也包含ISO头和COTP头) + internal static byte[] ISO_CR200 = { + // TPKT (RFC1006 Header) + 0x03, // RFC 1006 ID (3) + 0x00, // 保留 0 + 0x00, // 数据包长度 (整个框架、有效载荷和TPDU包括在内) + 0x16, // 数据包长度 (整个框架、有效载荷和TPDU包括在内) + // COTP (ISO 8073 Header) + 0x11, // PDU Size Length + 0xE0, // CR -连接请求ID + 0x00, 0x00, // Dst Reference + 0x00, 0x01, // Src Reference + 0x00, // Class + Options Flags + + //对于S7200/Smart + 0xC1,0x02, + 0x4D,0x57, //LOCALTASP + 0xC2, + 0x02, + 0x4D,0x57, //DESTTASP + 0xC0, + 0x01,0x09 + + }; + // ISO连接请求报文(也包含ISO头和COTP头) + internal static byte[] ISO_CR200SMART = { + // TPKT (RFC1006 Header) + 0x03, // RFC 1006 ID (3) + 0x00, // 保留 0 + 0x00, // 数据包长度 (整个框架、有效载荷和TPDU包括在内) + 0x16, // 数据包长度 (整个框架、有效载荷和TPDU包括在内) + // COTP (ISO 8073 Header) + 0x11, // PDU Size Length + 0xE0, // CR -连接请求ID + 0x00, 0x00, // Dst Reference + 0x00, 0x01, // Src Reference + 0x00, // Class + Options Flags + + //对于S7200/Smart + 0xC1,0x02, + 0x10,0x00,//LOCALTASP + 0xC2, + 0x02, + 0x03,0x00,//DESTTASP + 0xC0, + 0x01,0x0A + }; + // PDU获取报文(也包含ISO头和COTP头) + internal static byte[] S7_PN = { + 0x03, 0x00, 0x00, 0x19, + 0x02, 0xf0, 0x80, // TPKT + COTP + 0x32, 0x01, 0x00, 0x00, + + //这里对于S7200/Smart需要重写 + 0x04, 0x00, 0x00, 0x08, + + 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x01, 0x00, 0x01, + 0x01,0xE0 // PDU Length Requested 这里默认480字节,对于S7200/Smart 960字节 + }; + // PDU获取报文(也包含ISO头和COTP头) + internal static byte[] S7200_PN = { + 0x03, 0x00, 0x00, 0x19, + 0x02, 0xf0, 0x80, // TPKT + COTP + 0x32, 0x01, 0x00, 0x00, + + 0x00,0x00,0x00,0x08, + + 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x01, 0x00, 0x01, + 0x01,0xE0 // PDU Length Requested 这里默认960字节 + }; + internal static byte[] S7200SMART_PN = { + 0x03, 0x00, 0x00, 0x19, + 0x02, 0xf0, 0x80, // TPKT + COTP + 0x32, 0x01, 0x00, 0x00, + + 0xCC,0xC1,0x00,0x08, + + 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x01, 0x00, 0x01, + 0x01,0xE0 // PDU Length Requested 这里默认960字节 + }; + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Message/SiemensMessage.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Message/SiemensMessage.cs new file mode 100644 index 000000000..8442942ac --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/Message/SiemensMessage.cs @@ -0,0 +1,26 @@ +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + public class SiemensMessage : MessageBase, IMessage + { + public override int HeadBytesLength => 4; + + public override bool CheckHeadBytes(byte[] token) + { + HeadBytes = token; + byte[] headBytes = HeadBytes; + if (headBytes == null || headBytes.Length < 4) + BodyLength = 0; + int length = (HeadBytes[2] * 256) + HeadBytes[3] - 4; + if (length < 0) + length = 0; + BodyLength = length; + return HeadBytes != null && HeadBytes[0] == 3 && HeadBytes[1] == 0; + } + + protected override void SendBytesThen() + { + + } + + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensEnum.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensEnum.cs new file mode 100644 index 000000000..4f8396fb4 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensEnum.cs @@ -0,0 +1,15 @@ +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + public enum SiemensEnum + { + S200, + S200Smart, + + S300, + S400, + + S1200, + S1500, + + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLC.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLC.cs new file mode 100644 index 000000000..3aa486a92 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLC.cs @@ -0,0 +1,352 @@ +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using ThingsGateway.Foundation.Extension; + +using TouchSocket.Core; +using TouchSocket.Resources; +using TouchSocket.Sockets; + +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + /// + /// 相关命令含义源自网络资料/Shrap7/s7netplus + /// + public partial class SiemensS7PLC : ReadWriteDevicesTcpClientBase + { + + public SiemensS7PLCDataHandleAdapter DataHandleAdapter = new(); + private SiemensEnum _currentPlc = SiemensEnum.S1200; + private int pdu_length = 0; + private byte plc_rack = 0; + private byte plc_slot = 0; + private byte[] ISO_CR; + private byte[] S7_PN; + public SiemensEnum CurrentPlc => _currentPlc; + /// + /// 传入PLC类型,程序内会改变相应PLC类型的S7协议LocalTSAP, RemoteTSAP等 + /// + /// + /// + public SiemensS7PLC(TcpClient tcpClient, SiemensEnum siemensPLCEnum) : base(tcpClient) + { + _currentPlc = siemensPLCEnum; + RegisterByteLength = 1; + ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big); + ISO_CR = new byte[22]; + S7_PN = new byte[25]; + Array.Copy(SiemensHelper.ISO_CR, ISO_CR, ISO_CR.Length); + Array.Copy(SiemensHelper.S7_PN, S7_PN, S7_PN.Length); + switch (siemensPLCEnum) + { + case SiemensEnum.S1200: + ISO_CR[21] = 0x00; + break; + case SiemensEnum.S300: + ISO_CR[21] = 0x02; + break; + case SiemensEnum.S400: + ISO_CR[21] = 0x03; + ISO_CR[17] = 0x00; + break; + case SiemensEnum.S1500: + ISO_CR[21] = 0x00; + break; + case SiemensEnum.S200Smart: + ISO_CR = SiemensHelper.ISO_CR200SMART; + S7_PN = SiemensHelper.S7200SMART_PN; + break; + case SiemensEnum.S200: + ISO_CR = SiemensHelper.ISO_CR200; + S7_PN = SiemensHelper.S7200_PN; + break; + + } + tcpClient.Connected += Connected; + } + + + #region 设置 + /// + /// 获取或设置DstTSAP,在非200系列时有效 + /// + public byte ConnectionType + { + get => ISO_CR[20]; + set + { + if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart) + { + return; + } + ISO_CR[20] = value; + } + } + + /// + /// 远程TSAP,需重新连接 + /// + public int DestTSAP + { + get + { + return + _currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart ? + (ISO_CR[17] * 256) + ISO_CR[18] : + (ISO_CR[20] * 256) + ISO_CR[21]; + } + set + { + //TODO:这里优化为ThingsGatewayBitConverter + if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart) + { + ISO_CR[17] = BitConverter.GetBytes(value)[1]; + ISO_CR[18] = BitConverter.GetBytes(value)[0]; + } + else + { + ISO_CR[20] = BitConverter.GetBytes(value)[1]; + ISO_CR[21] = BitConverter.GetBytes(value)[0]; + } + } + } + + /// + /// 本地TSAP,需重新连接 + /// + public int LocalTSAP + { + get + { + return + _currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart ? + (ISO_CR[13] * 256) + ISO_CR[14] : + (ISO_CR[16] * 256) + ISO_CR[17]; + } + set + { + //TODO:这里优化为ThingsGatewayBitConverter + if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart) + { + ISO_CR[13] = BitConverter.GetBytes(value)[1]; + ISO_CR[14] = BitConverter.GetBytes(value)[0]; + } + else + { + ISO_CR[16] = BitConverter.GetBytes(value)[1]; + ISO_CR[17] = BitConverter.GetBytes(value)[0]; + } + } + } + + public int PDULength => pdu_length; + + /// + /// 机架号,需重新连接 + /// + public byte Rack + { + get => plc_rack; + set + { + plc_rack = value; + if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart) + { + return; + } + + ISO_CR[21] = (byte)((plc_rack * 0x20) + plc_slot); + } + } + + /// + /// 槽号,需重新连接 + /// + public byte Slot + { + get => plc_slot; + set + { + plc_slot = value; + if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart) + { + return; + } + ISO_CR[21] = (byte)((plc_rack * 0x20) + plc_slot); + } + } + + #endregion + + public override async Task> ReadAsync(string address, int length, CancellationToken token = default) + { + try + { + await ConnectAsync(); + var commandResult = GetReadByteCommand(address, length); + if (commandResult.IsSuccess) + { + List bytes = new(); + foreach (var item in commandResult.Content) + { + var result = TcpClient.GetWaitingClient(new()).SendThenResponse(item, TimeOut, token); + if (result.RequestInfo is MessageBase collectMessage) + { + if (collectMessage.IsSuccess) + bytes.AddRange(collectMessage.Content); + else + return OperResult.CreateFailedResult(collectMessage); + } + } + return bytes.Count > 0 ? OperResult.CreateSuccessResult(bytes.ToArray()) : new OperResult(TouchSocketStatus.UnknownError.GetDescription()); + } + + else + { + return OperResult.CreateFailedResult(commandResult); + } + } + catch (Exception ex) + { + return new OperResult(ex); + } + finally + { + } + + } + + /// + /// 读取变长字符串 + /// + public async Task> ReadString(string address, Encoding encoding) + { + return await SiemensHelper.ReadString(this, address, encoding); + } + + public async Task> ReadDateTime(string address) + { + return ByteTransformHelper.GetResultFromBytes(await ReadAsync(address, 8), ThingsGateway.Foundation.Adapter.Siemens.DateTime.FromByteArray); + } + + public async Task> ReadDate(string address) + { + return (await this.ReadAsync(address, 2)). + Then(m => OperResult.CreateSuccessResult(ThingsGateway.Foundation.Adapter.Siemens.DateTime.SpecMinimumDateTime.AddDays( + ThingsGatewayBitConverter.ToUInt16(m, 0))) + ); + } + + public override void SetDataAdapter() + { + DataHandleAdapter = new(); + TcpClient.SetDataHandlingAdapter(DataHandleAdapter); + } + + + public override Task WriteAsync(string address, string value, Encoding encoding, CancellationToken token = default) + { + return SiemensHelper.Write(this, address, value, encoding); + } + + public override async Task WriteAsync(string address, byte[] value, CancellationToken token = default) + { + try + { + await ConnectAsync(); + var commandResult = GetWriteByteCommand(address, value); + if (commandResult.IsSuccess) + { + List bytes = new(); + foreach (var item in commandResult.Content) + { + ResponsedData result = TcpClient.GetWaitingClient(new()).SendThenResponse(item, TimeOut, token); + bytes.Add(result); + } + return OperResult.CreateSuccessResult(bytes.ToArray()); + } + else + { + return OperResult.CreateFailedResult(commandResult); + } + } + catch (Exception ex) + { + return new OperResult(ex); + } + finally + { + } + } + + public override async Task WriteAsync(string address, bool[] value, CancellationToken token = default) + { + if (value.Length > 1) + { + return new OperResult("不支持多写"); + } + try + { + await ConnectAsync(); + + var commandResult = GetWriteBitCommand(address, value[0]); + if (commandResult.IsSuccess) + { + var result = TcpClient.GetWaitingClient(new()).SendThenResponse(commandResult.Content, TimeOut, token); + return OperResult.CreateSuccessResult(result); + } + else + { + return OperResult.CreateFailedResult(commandResult); + } + } + catch (Exception ex) + { + return new OperResult(ex); + } + finally + { + } + } + + private void Connected(ITcpClient client, MsgEventArgs e) + { + try + { + var result1 = SendThenResponse(ISO_CR); + if (!result1.IsSuccess) + { + Logger?.Error(client.GetIPPort() + "ISO初始化失败"); + return; + } + var result2 = SendThenResponse(S7_PN); + if (!result2.IsSuccess) + { + Logger?.Error(client.GetIPPort() + "初始化失败"); + return; + } + pdu_length = ThingsGatewayBitConverter.ToUInt16(result2.Content.SelectLast(2), 0); + pdu_length = pdu_length < 200 ? 200 : pdu_length; + } + catch (Exception ex) + { + Logger.Exception(ex); + } + + } + + public async Task WriteDateTime(string address, System.DateTime dateTime) + { + return await WriteAsync(address, ThingsGateway.Foundation.Adapter.Siemens.DateTime.ToByteArray(dateTime)); + } + + public async Task WriteDate(string address, System.DateTime dateTime) + { + return await base.WriteAsync(address, Convert.ToUInt16((dateTime - ThingsGateway.Foundation.Adapter.Siemens.DateTime.SpecMinimumDateTime).TotalDays)); + } + + + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCDataHandleAdapter.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCDataHandleAdapter.cs new file mode 100644 index 000000000..74a3b0dc3 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCDataHandleAdapter.cs @@ -0,0 +1,41 @@ +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + public class SiemensS7PLCDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter + { + public override byte[] PackCommand(byte[] command) + { + return command; + } + protected override void Reset() + { + base.Reset(); + } + + protected override OperResult UnpackResponse( + byte[] send, + byte[] response) + { + if (response[2] * 256 + response[3] == 7) + { + return new OperResult(response); + } + else + { + //已请求方为准,分开返回类型校验 + switch (send[17]) + { + case 0x04: + return SiemensHelper.AnalysisReadByte(send, response); + case 0x05: + return SiemensHelper.AnalysisWrite(response); + } + return OperResult.CreateSuccessResult(response); + } + } + + protected override SiemensMessage GetInstance() + { + return new SiemensMessage(); + } + } +} diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCNetMed.cs b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCNetMed.cs new file mode 100644 index 000000000..8e179fd55 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/Siemens/SiemensS7PLCNetMed.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; + +using ThingsGateway.Foundation.Extension; + +namespace ThingsGateway.Foundation.Adapter.Siemens +{ + public partial class SiemensS7PLC : ReadWriteDevicesTcpClientBase + { + private static OperResult GetWriteBitCommand(string address, bool data) + { + OperResult result = SiemensAddress.ParseFrom(address); + if (!result.IsSuccess) + { + return result.Copy(); + } + return SiemensHelper.GetWriteBitCommand(result.Content, data); + } + private OperResult> GetReadByteCommand(string address, int length) + { + OperResult from = SiemensAddress.ParseFrom(address, length); + if (!from.IsSuccess) return from.Copy>(); + ushort num1 = 0; + var listBytes = new List(); + while (num1 < length) + { + //pdu长度,重复生成报文,直至全部生成 + ushort num2 = (ushort)Math.Min(length - num1, pdu_length); + from.Content.Length = num2; + var result = GetReadByteCommand(new SiemensAddress[1] { from.Content }); + if (!result.IsSuccess) return result.Copy>(); + listBytes.AddRange(result.Content); + num1 += num2; + if (from.Content.DataCode == (byte)S7WordLength.Timer || from.Content.DataCode == (byte)S7WordLength.Counter) + { + from.Content.AddressStart += num2 / 2; + } + else + { + from.Content.AddressStart += num2 * 8; + } + } + return OperResult.CreateSuccessResult(listBytes); + } + + private OperResult> GetReadByteCommand(SiemensAddress[] siemensAddress) + { + if (siemensAddress.Length <= 19) + { + return ByteTransformHelper.GetResultFromBytes(SiemensHelper.GetReadCommand(siemensAddress), m => new List() { m }); + } + + List byteList = new List(); + List s7AddressDataArrayList = siemensAddress.ArraySplitByLength(19); + for (int index = 0; index < s7AddressDataArrayList.Count; ++index) + { + var result = GetReadByteCommand(s7AddressDataArrayList[index]); + if (!result.IsSuccess) + { + return result; + } + byteList.AddRange(result.Content); + } + return OperResult.CreateSuccessResult(byteList); + } + + private OperResult> GetWriteByteCommand(string address, byte[] value) + { + return SiemensAddress.ParseFrom(address).Then(m => GetWriteByteCommand(m, value)); + } + + private OperResult> GetWriteByteCommand(SiemensAddress address, byte[] value) + { + int length1 = value.Length; + ushort index = 0; + List bytes = new List(); + while (index < length1) + { + //pdu长度,重复生成报文,直至全部生成 + ushort length2 = (ushort)Math.Min(length1 - index, pdu_length); + byte[] data = ThingsGatewayBitConverter.ToByte(value, index, length2); + OperResult result1 = SiemensHelper.GetWriteByteCommand(address, data); + if (!result1.IsSuccess) + { + return result1.Copy>(); + } + bytes.Add(result1.Content); + index += length2; + address.AddressStart += length2 * 8; + } + return OperResult.CreateSuccessResult(bytes); + } + + } +} diff --git a/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/ThingsGateway.Foundation.Adapter.Siemens.csproj b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/ThingsGateway.Foundation.Adapter.Siemens.csproj new file mode 100644 index 000000000..9638c8ebf --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Adapter.Siemens/ThingsGateway.Foundation.Adapter.Siemens.csproj @@ -0,0 +1,12 @@ + + + + latestMajor + net462;net6.0;net7.0 + 1.2.0 + + + + + + diff --git a/src/Plugins/ThingsGateway.Foundation.Tests/Modbus.cs b/src/Plugins/ThingsGateway.Foundation.Tests/Modbus.cs index 5c2245642..3553d23f7 100644 --- a/src/Plugins/ThingsGateway.Foundation.Tests/Modbus.cs +++ b/src/Plugins/ThingsGateway.Foundation.Tests/Modbus.cs @@ -10,49 +10,20 @@ namespace ThingsGateway.Foundation.Tests { public class Modbus { + private ITestOutputHelper _output; TcpClient client; - SerialClient serialClient; private TouchSocketConfig config; - private ModbusRtuOverTcp RtuTcpClient; private ModbusRtu RtuClient; + private ModbusRtuOverTcp RtuTcpClient; private ModbusRtuOverUdp RtuUdpClient; + SerialClient serialClient; private ModbusTcp TcpClient; private ModbusUdp UdpClient; - private ITestOutputHelper _output; public Modbus(ITestOutputHelper output) { _output = output; } [Fact] - public async Task RtuTcpReadTest() - { - ModbusRtuTcpClient(); - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - - for (int i = 0; i < 10; i++) - { - string address = $"4{i * 2 + 1};DATA=ABCD;"; - var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat); - var test = await RtuTcpClient.ReadAsync(address, 1); - var data = byteConverter.ToInt16(test.Content, 0); - _output.WriteLine(data.ToJson()); - } - - for (int i = 100; i < 110; i++) - { - string address = $"4{i * 2 + 1};TEXT=UTF8;LEN=4"; - var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat); - var test = await RtuTcpClient.ReadAsync(address, length / RtuTcpClient.RegisterByteLength); - var data = byteConverter.ToString(test.Content, 0, length); - _output.WriteLine(data.ToJson()); - } - - stopwatch.Stop(); - _output.WriteLine("×ܺÄʱ£º" + stopwatch.Elapsed.TotalSeconds); - - } - [Fact] public async Task RtuReadTest() { ModbusRtuClient(); @@ -80,6 +51,36 @@ namespace ThingsGateway.Foundation.Tests stopwatch.Stop(); _output.WriteLine("×ܺÄʱ£º" + stopwatch.Elapsed.TotalSeconds); + } + + [Fact] + public async Task RtuTcpReadTest() + { + ModbusRtuTcpClient(); + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + for (int i = 0; i < 10; i++) + { + string address = $"4{i * 2 + 1};DATA=ABCD;"; + var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat); + var test = await RtuTcpClient.ReadAsync(address, 1); + var data = byteConverter.ToInt16(test.Content, 0); + _output.WriteLine(data.ToJson()); + } + + for (int i = 100; i < 110; i++) + { + string address = $"4{i * 2 + 1};TEXT=UTF8;LEN=4"; + var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat); + var test = await RtuTcpClient.ReadAsync(address, length / RtuTcpClient.RegisterByteLength); + var data = byteConverter.ToString(test.Content, 0, length); + _output.WriteLine(data.ToJson()); + } + + stopwatch.Stop(); + _output.WriteLine("×ܺÄʱ£º" + stopwatch.Elapsed.TotalSeconds); + } [Fact] public async Task TcpReadTest() @@ -102,6 +103,19 @@ namespace ThingsGateway.Foundation.Tests } + private void ModbusRtuClient() + { + config = new TouchSocketConfig(); + config.SetSerialProperty(new() { PortName = "COM6" }) + .SetBufferLength(300); + //ÔØÈëÅäÖà + serialClient = config.Container.Resolve(); + serialClient.Setup(config); + RtuClient = new(serialClient); + RtuClient.Station = 1; + RtuClient.TimeOut = 5000; + } + private void ModbusRtuTcpClient() { config = new TouchSocketConfig(); @@ -154,19 +168,5 @@ namespace ThingsGateway.Foundation.Tests UdpClient.Station = 1; UdpClient.TimeOut = 5000; } - - private void ModbusRtuClient() - { - config = new TouchSocketConfig(); - config.SetSerialProperty(new() { PortName = "COM6" }) - .SetBufferLength(300); - //ÔØÈëÅäÖà - serialClient = config.Container.Resolve(); - serialClient.Setup(config); - RtuClient = new(serialClient); - RtuClient.Station = 1; - RtuClient.TimeOut = 5000; - } - } } \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Tests/Siemens.cs b/src/Plugins/ThingsGateway.Foundation.Tests/Siemens.cs new file mode 100644 index 000000000..8cec6b900 --- /dev/null +++ b/src/Plugins/ThingsGateway.Foundation.Tests/Siemens.cs @@ -0,0 +1,67 @@ +using System.Diagnostics; + +using ThingsGateway.Foundation.Adapter.Siemens; + +using Xunit; +using Xunit.Abstractions; + +namespace ThingsGateway.Foundation.Tests +{ + public class Siemens + { + private ITestOutputHelper _output; + TcpClient client; + private TouchSocketConfig config; + private SiemensS7PLC TcpClient; + public Siemens(ITestOutputHelper output) + { + _output = output; + } + + + [Fact] + public async Task TcpReadTest() + { + S7TcpClient(); + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + for (int i = 0; i < 20; i++) + { + string address = $"M1.{i + 1};"; + var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, TcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat); + var test = await TcpClient.ReadAsync(address, 2); + if (test .IsSuccess) + { + var data = byteConverter.ToBoolean(test.Content, 0); + _output.WriteLine(data.ToJson()); + } + else + { + _output.WriteLine(test.ToJson()); + } + } + + stopwatch.Stop(); + _output.WriteLine("×ܺÄʱ£º" + stopwatch.Elapsed.TotalSeconds); + + } + + + + private void S7TcpClient() + { + config = new TouchSocketConfig(); + config.SetRemoteIPHost(new IPHost("127.0.0.1:102")) + .SetBufferLength(300); + //ÔØÈëÅäÖà + client = config.Container.Resolve(); + client.Setup(config); + TcpClient = new(client,SiemensEnum.S1500); + TcpClient.ConnectTimeOut = 5000; + TcpClient.TimeOut = 5000; + } + + + } +} \ No newline at end of file diff --git a/src/Plugins/ThingsGateway.Foundation.Tests/ThingsGateway.Foundation.Tests.csproj b/src/Plugins/ThingsGateway.Foundation.Tests/ThingsGateway.Foundation.Tests.csproj index 6adf1bb40..607438b71 100644 --- a/src/Plugins/ThingsGateway.Foundation.Tests/ThingsGateway.Foundation.Tests.csproj +++ b/src/Plugins/ThingsGateway.Foundation.Tests/ThingsGateway.Foundation.Tests.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.csproj b/src/Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.csproj index 385a47013..e676de0e4 100644 --- a/src/Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.csproj +++ b/src/Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.csproj @@ -18,8 +18,11 @@ - - + + Compile + + + diff --git a/src/Plugins/ThingsGateway.Siemens/GlobalUsings.cs b/src/Plugins/ThingsGateway.Siemens/GlobalUsings.cs new file mode 100644 index 000000000..fc2ab9e84 --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/GlobalUsings.cs @@ -0,0 +1,11 @@ +global using Microsoft.Extensions.DependencyInjection; + +global using System.Net; + +global using ThingsGateway.Foundation.Adapter.Siemens; +global using ThingsGateway.Web.Foundation; + +global using TouchSocket.Core; +global using TouchSocket.Sockets; + + diff --git a/src/Plugins/ThingsGateway.Siemens/S7.cs b/src/Plugins/ThingsGateway.Siemens/S7.cs new file mode 100644 index 000000000..8e81c8035 --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7.cs @@ -0,0 +1,87 @@ +using System.Text; + +using ThingsGateway.Foundation; + +namespace ThingsGateway.Siemens +{ + public abstract class S7 : DriverBase + { + + protected SiemensS7PLC _plc; + + protected S7(IServiceScopeFactory scopeFactory) : base(scopeFactory) + { + } + + [DeviceProperty("连接超时时间", "")] public ushort ConnectTimeOut { get; set; } = 3000; + [DeviceProperty("默认解析顺序", "")] public DataFormat DataFormat { get; set; } + [DeviceProperty("IP", "")] public string IP { get; set; } = "127.0.0.1"; + [DeviceProperty("端口", "")] public int Port { get; set; } = 102; + public override IThingsGatewayBitConverter ThingsGatewayBitConverter { get => _plc?.ThingsGatewayBitConverter; } + [DeviceProperty("读写超时时间", "")] public ushort TimeOut { get; set; } = 3000; + + + [DeviceMethod("ReadDate", "")] + public Task> ReadDate(string address) + { + return _plc?.ReadDate(address); + } + + [DeviceMethod("ReadString", "")] + public Task> ReadString(string address, Encoding encoding) + { + return _plc?.ReadString(address, encoding); + } + + + [DeviceMethod("WriteDate", "")] + public Task WriteDate(string address, System.DateTime dateTime) + { + return _plc?.WriteDate(address, dateTime); + } + [DeviceMethod("WriteDateTime", "")] + public Task WriteDateTime(string address, System.DateTime dateTime) + { + return _plc?.WriteDateTime(address, dateTime); + } + + public override void AfterStop() + { + _plc?.Disconnect(); + } + + public override async Task BeforStart() + { + await _plc.ConnectAsync(); + } + + public override void Dispose() + { + _plc.Disconnect(); + } + + public override bool IsSupportAddressRequest() + { + return true; + } + public override OperResult> LoadSourceRead(List deviceVariables) + { + Init(null); + _plc.Connect(); + var data= deviceVariables.LoadSourceRead(_logger, ThingsGatewayBitConverter, _plc); + _plc?.Disconnect(); + return data; + } + + public override async Task WriteValueAsync(CollectVariableRunTime deviceVariable, string value) + { + return await _plc.WriteAsync(deviceVariable.DataType, deviceVariable.VariableAddress, value); + } + + protected override async Task> ReadAsync(string address, int length, CancellationToken cancellationToken) + { + return await _plc.ReadAsync(address, length); + } + + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/S7Helper.cs b/src/Plugins/ThingsGateway.Siemens/S7Helper.cs new file mode 100644 index 000000000..348fb5d3e --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7Helper.cs @@ -0,0 +1,211 @@ +using Microsoft.Extensions.Logging; + +using ThingsGateway.Foundation; +using ThingsGateway.Foundation.Extension; + +namespace ThingsGateway.Siemens +{ + internal static class S7Helper + { + internal static OperResult> LoadSourceRead(this List deviceVariables, ILogger _logger, IThingsGatewayBitConverter byteConverter, SiemensS7PLC siemensS7Net) + { + var result = new List(); + try + { + //需要先剔除额外信息,比如dataformat等 + foreach (var item in deviceVariables) + { + var address = item.VariableAddress; + + IThingsGatewayBitConverter transformParameter = ByteConverterHelper.GetTransByAddress( + ref address, byteConverter, out int length, out BcdFormat bCDFormat); + item.ThingsGatewayBitConverter = transformParameter; + item.StringLength = length; + item.StringBcdFormat = bCDFormat; + item.VariableAddress = address; + + int bitIndex = 0; + string[] addressSplits = new string[] { address }; + if (address.IndexOf('.') > 0) + { + addressSplits = address.SplitDot(); + try + { + if ((addressSplits.Length == 2 && !address.ToUpper().Contains("DB")) || (addressSplits.Length >= 3 && address.ToUpper().Contains("DB"))) + bitIndex = Convert.ToInt32(addressSplits.Last()); + + } + catch (Exception ex) + { + _logger?.LogError(ex, "自动分包方法获取Bit失败"); + } + } + item.Index = bitIndex; + } + //按读取间隔分组 + var tags = deviceVariables.GroupBy(it => it.IntervalTime); + foreach (var item in tags) + { + Dictionary map = item.ToDictionary(it => + { + + var lastLen = it.DataTypeEnum.GetByteLength(); ; + if (lastLen <= 0) + { + if (it.DataTypeEnum.GetNetType() == typeof(bool)) + { + lastLen = 2; + } + else if (it.DataTypeEnum.GetNetType() == typeof(string)) + { + lastLen = it.StringLength; + } + else if (it.DataTypeEnum.GetNetType() == typeof(object)) + { + lastLen = 1; + } + } + + var s7Address = SiemensAddress.ParseFrom(it.VariableAddress); + if (s7Address.IsSuccess) + { + if ((s7Address.Content.DataCode == (byte)S7WordLength.Counter || s7Address.Content.DataCode == (byte)S7WordLength.Timer) && lastLen == 1) + { + lastLen = 2; + } + } + //这里把每个变量的应读取长度都写入变量地址实体中 + return SiemensAddress.ParseFrom(it.VariableAddress, (ushort)lastLen).Content; + + }); + + //获取变量的地址 + var modbusAddressList = map.Keys.ToList(); + + //获取S7数据代码 + var functionCodes = modbusAddressList.Select(t => t.DataCode).Distinct(); + foreach (var functionCode in functionCodes) + { + //相同数据代码的变量集合 + var modbusAddressSameFunList = modbusAddressList + .Where(t => t.DataCode == functionCode); + //相同数据代码的变量集合中的不同DB块 + var stationNumbers = modbusAddressSameFunList + .Select(t => t.DbBlock).Distinct(); + foreach (var stationNumber in stationNumbers) + { + var addressList = modbusAddressSameFunList.Where(t => t.DbBlock == stationNumber) + .ToDictionary(t => t, t => map[t]); + //循环对数据代码,站号都一样的变量进行分配连读包 + var tempResult = LoadSourceRead(addressList, functionCode, item.Key, siemensS7Net); + //添加到总连读包 + result.AddRange(tempResult.Content); + } + } + } + } + catch (Exception ex) + { + _logger?.LogError(ex, "自动分包失败"); + } + return OperResult.CreateSuccessResult(result); + } + + private static OperResult> LoadSourceRead(Dictionary addressList, int functionCode, int timeInterval, SiemensS7PLC siemensS7Net) + { + + List sourceReads = new List(); + //实际地址与长度排序 + var addresss = addressList.Keys.OrderBy(it => + { + int address = 0; + if (it.DataCode == (byte)S7WordLength.Counter || it.DataCode == (byte)S7WordLength.Timer) + { + address = it.AddressStart * 2; + } + else + { + address = it.AddressStart / 8; + } + return address + it.Length; + }).ToList(); + var minAddress = addresss.First().AddressStart; + var maxAddress = addresss.Last().AddressStart; + while (maxAddress >= minAddress) + { + //这里直接避免末位变量长度超限的情况,pdu长度-8 + int readLength = siemensS7Net.PDULength == 0 ? 200 : siemensS7Net.PDULength - 8; + List tempAddress = new(); + if (functionCode == (byte)S7WordLength.Counter || functionCode == (byte)S7WordLength.Timer) + { + tempAddress = addresss.Where(t => t.AddressStart >= minAddress && ((t.AddressStart) + t.Length) <= ((minAddress) + readLength)).ToList(); + while ((tempAddress.Last().AddressStart * 2) + tempAddress.Last().Length - (tempAddress.First().AddressStart * 2) > readLength) + { + tempAddress.Remove(tempAddress.Last()); + } + } + else + { + tempAddress = addresss.Where(t => t.AddressStart >= minAddress && ((t.AddressStart) + t.Length) <= ((minAddress) + readLength)).ToList(); + while ((tempAddress.Last().AddressStart / 8) + tempAddress.Last().Length - (tempAddress.First().AddressStart / 8) > readLength) + { + tempAddress.Remove(tempAddress.Last()); + } + } + + //读取寄存器长度 + int lastAddress = 0; + int firstAddress = 0; + if (functionCode == (byte)S7WordLength.Counter || functionCode == (byte)S7WordLength.Timer) + { + lastAddress = tempAddress.Last().AddressStart * 2; + firstAddress = tempAddress.First().AddressStart * 2; + } + else + { + lastAddress = tempAddress.Last().AddressStart / 8; + firstAddress = tempAddress.First().AddressStart / 8; + } + var sourceLen = lastAddress + tempAddress.Last().Length - firstAddress; + DeviceVariableSourceRead sourceRead = new DeviceVariableSourceRead(timeInterval); + sourceRead.Address = tempAddress.OrderBy(it => it.AddressStart).First().ToString(); + sourceRead.Length = sourceLen.ToString(); + foreach (var item in tempAddress) + { + var readNode = addressList[item]; + if (functionCode == (byte)S7WordLength.Counter || functionCode == (byte)S7WordLength.Timer) + { + if (readNode.DataTypeEnum == DataTypeEnum.Bool) + { + readNode.Index = (((item.AddressStart * 2) - (tempAddress.First().AddressStart * 2)) * 8) + readNode.Index; + } + else + { + readNode.Index = (item.AddressStart * 2) - (tempAddress.First().AddressStart * 2) + readNode.Index; + } + } + else + { + if (readNode.DataTypeEnum == DataTypeEnum.Bool) + { + readNode.Index = (((item.AddressStart / 8) - (tempAddress.First().AddressStart / 8)) * 8) + readNode.Index; + } + else + { + readNode.Index = (item.AddressStart / 8) - (tempAddress.First().AddressStart / 8) + readNode.Index; + } + } + sourceRead.DeviceVariables.Add(readNode); + addresss.Remove(item); + } + sourceReads.Add(sourceRead); + if (addresss.Count > 0) + minAddress = addresss.First().AddressStart; + else + break; + } + return OperResult.CreateSuccessResult(sourceReads); + } + + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/S7_1200.cs b/src/Plugins/ThingsGateway.Siemens/S7_1200.cs new file mode 100644 index 000000000..72707886d --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7_1200.cs @@ -0,0 +1,28 @@ +namespace ThingsGateway.Siemens +{ + public class S7_1200 : S7 + { + public S7_1200(IServiceScopeFactory scopeFactory) : base(scopeFactory) + { + } + + protected override void Init(CollectDeviceRunTime device, object client = null) + { + if (client == null) + { + TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port)) + .SetBufferLength(1024); + client = TouchSocketConfig.Container.Resolve(); + ((TcpClient)client).Setup(TouchSocketConfig); + } + //载入配置 + _plc = new((TcpClient)client, SiemensEnum.S1200); + _plc.DataFormat = DataFormat; + _plc.ConnectTimeOut = ConnectTimeOut; + _plc.TimeOut = TimeOut; + + } + + + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/S7_1500.cs b/src/Plugins/ThingsGateway.Siemens/S7_1500.cs new file mode 100644 index 000000000..37adb4c4d --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7_1500.cs @@ -0,0 +1,28 @@ +namespace ThingsGateway.Siemens +{ + public class S7_1500 : S7 + { + public S7_1500(IServiceScopeFactory scopeFactory) : base(scopeFactory) + { + } + + protected override void Init(CollectDeviceRunTime device, object client = null) + { + if (client == null) + { + TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port)) + .SetBufferLength(1024); + client = TouchSocketConfig.Container.Resolve(); + ((TcpClient)client).Setup(TouchSocketConfig); + } + //载入配置 + _plc = new((TcpClient)client, SiemensEnum.S1500); + _plc.DataFormat = DataFormat; + _plc.ConnectTimeOut = ConnectTimeOut; + _plc.TimeOut = TimeOut; + + } + + + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/S7_200.cs b/src/Plugins/ThingsGateway.Siemens/S7_200.cs new file mode 100644 index 000000000..5569c6ba3 --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7_200.cs @@ -0,0 +1,28 @@ +namespace ThingsGateway.Siemens +{ + public class S7_200 : S7 + { + public S7_200(IServiceScopeFactory scopeFactory) : base(scopeFactory) + { + } + + protected override void Init(CollectDeviceRunTime device, object client = null) + { + if (client == null) + { + TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port)) + .SetBufferLength(1024); + client = TouchSocketConfig.Container.Resolve(); + ((TcpClient)client).Setup(TouchSocketConfig); + } + //载入配置 + _plc = new((TcpClient)client, SiemensEnum.S200); + _plc.DataFormat = DataFormat; + _plc.ConnectTimeOut = ConnectTimeOut; + _plc.TimeOut = TimeOut; + + } + + + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/S7_200SMART.cs b/src/Plugins/ThingsGateway.Siemens/S7_200SMART.cs new file mode 100644 index 000000000..84661121c --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7_200SMART.cs @@ -0,0 +1,42 @@ +namespace ThingsGateway.Siemens +{ + public class S7_200SMART : S7 + { + public S7_200SMART(IServiceScopeFactory scopeFactory) : base(scopeFactory) + { + } + + protected override void Init(CollectDeviceRunTime device, object client = null) + { + if (client == null) + { + TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port)) + .SetBufferLength(1024); + client = TouchSocketConfig.Container.Resolve(); + ((TcpClient)client).Setup(TouchSocketConfig); + } + //载入配置 + _plc = new((TcpClient)client, SiemensEnum.S200Smart); + _plc.DataFormat = DataFormat; + _plc.ConnectTimeOut = ConnectTimeOut; + _plc.TimeOut = TimeOut; + + } + + //[Method("HotStart", "热启动")] + //public OperResult HotStart() + //{ + // return _plc?.HotStart(); + //} + //[Method("ColdStart", "冷启动")] + //public OperResult ColdStart() + //{ + // return _plc?.ColdStart(); + //} + //[Method("Stop", "停止")] + //public OperResult Stop() + //{ + // return _plc?.Stop(); + //} + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/S7_300.cs b/src/Plugins/ThingsGateway.Siemens/S7_300.cs new file mode 100644 index 000000000..d8d29d0ba --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7_300.cs @@ -0,0 +1,28 @@ +namespace ThingsGateway.Siemens +{ + public class S7_300 : S7 + { + public S7_300(IServiceScopeFactory scopeFactory) : base(scopeFactory) + { + } + + protected override void Init(CollectDeviceRunTime device, object client = null) + { + if (client == null) + { + TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port)) + .SetBufferLength(1024); + client = TouchSocketConfig.Container.Resolve(); + ((TcpClient)client).Setup(TouchSocketConfig); + } + //载入配置 + _plc = new((TcpClient)client, SiemensEnum.S300); + _plc.DataFormat = DataFormat; + _plc.ConnectTimeOut = ConnectTimeOut; + _plc.TimeOut = TimeOut; + + } + + + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/S7_400.cs b/src/Plugins/ThingsGateway.Siemens/S7_400.cs new file mode 100644 index 000000000..0cdf54d99 --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/S7_400.cs @@ -0,0 +1,28 @@ +namespace ThingsGateway.Siemens +{ + public class S7_400 : S7 + { + public S7_400(IServiceScopeFactory scopeFactory) : base(scopeFactory) + { + } + + protected override void Init(CollectDeviceRunTime device, object client = null) + { + if (client == null) + { + TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port)) + .SetBufferLength(1024); + client = TouchSocketConfig.Container.Resolve(); + ((TcpClient)client).Setup(TouchSocketConfig); + } + //载入配置 + _plc = new((TcpClient)client, SiemensEnum.S400); + _plc.DataFormat = DataFormat; + _plc.ConnectTimeOut = ConnectTimeOut; + _plc.TimeOut = TimeOut; + + } + + + } +} diff --git a/src/Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.csproj b/src/Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.csproj new file mode 100644 index 000000000..c313404bd --- /dev/null +++ b/src/Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.csproj @@ -0,0 +1,27 @@ + + + + latestMajor + net6.0;net7.0 + enable + 1.2.0 + OnBuildSuccess + true + false + + + + + + + + + + + + + Compile + + + + diff --git a/src/ThingsGateway.Web.Foundation/SeedData/Json/driver_plugin.json b/src/ThingsGateway.Web.Foundation/SeedData/Json/driver_plugin.json index 5cb8bd122..bc5d3185f 100644 --- a/src/ThingsGateway.Web.Foundation/SeedData/Json/driver_plugin.json +++ b/src/ThingsGateway.Web.Foundation/SeedData/Json/driver_plugin.json @@ -1,5 +1,71 @@ { "RECORDS": [ + { + "Id": "395093826302213", + "AssembleName": "ThingsGateway.Siemens.S7_1200", + "FileName": "ThingsGateway.Siemens", + "DriverTypeEnum": "Collect", + "FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll", + "CreateTime": "2023-03-12 20:20:55.726", + "CreateUser": "superAdmin", + "CreateUserId": "212725263002001", + "IsDelete": "0" + }, + { + "Id": "395093826302214", + "AssembleName": "ThingsGateway.Siemens.S7_1500", + "FileName": "ThingsGateway.Siemens", + "DriverTypeEnum": "Collect", + "FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll", + "CreateTime": "2023-03-12 20:20:55.728", + "CreateUser": "superAdmin", + "CreateUserId": "212725263002001", + "IsDelete": "0" + }, + { + "Id": "395093826302215", + "AssembleName": "ThingsGateway.Siemens.S7_200", + "FileName": "ThingsGateway.Siemens", + "DriverTypeEnum": "Collect", + "FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll", + "CreateTime": "2023-03-12 20:20:55.728", + "CreateUser": "superAdmin", + "CreateUserId": "212725263002001", + "IsDelete": "0" + }, + { + "Id": "395093826302216", + "AssembleName": "ThingsGateway.Siemens.S7_200SMART", + "FileName": "ThingsGateway.Siemens", + "DriverTypeEnum": "Collect", + "FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll", + "CreateTime": "2023-03-12 20:20:55.728", + "CreateUser": "superAdmin", + "CreateUserId": "212725263002001", + "IsDelete": "0" + }, + { + "Id": "395093826302217", + "AssembleName": "ThingsGateway.Siemens.S7_300", + "FileName": "ThingsGateway.Siemens", + "DriverTypeEnum": "Collect", + "FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll", + "CreateTime": "2023-03-12 20:20:55.728", + "CreateUser": "superAdmin", + "CreateUserId": "212725263002001", + "IsDelete": "0" + }, + { + "Id": "395093826302218", + "AssembleName": "ThingsGateway.Siemens.S7_400", + "FileName": "ThingsGateway.Siemens", + "DriverTypeEnum": "Collect", + "FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll", + "CreateTime": "2023-03-12 20:20:55.728", + "CreateUser": "superAdmin", + "CreateUserId": "212725263002001", + "IsDelete": "0" + }, { "Id": "389003388313861", "AssembleName": "ThingsGateway.Modbus.ModbusRtuOverTcp", diff --git a/src/ThingsGateway.Web.Page/Page/CollectVariablePage.razor b/src/ThingsGateway.Web.Page/Page/CollectVariablePage.razor index 61c21bba3..4924ab7b5 100644 --- a/src/ThingsGateway.Web.Page/Page/CollectVariablePage.razor +++ b/src/ThingsGateway.Web.Page/Page/CollectVariablePage.razor @@ -148,17 +148,25 @@ StringNumber tab; List CollectDevices = new(); List UploadDevices = new(); - List OtherMethods = new(); + Dictionary> OtherMethods = new(); void DeviceChanged(long devId) { if (devId > 0) - OtherMethods = this.GetBackgroundService().GetDeviceMethods(devId); + { + var data = this.GetBackgroundService().GetDeviceMethods(devId); + OtherMethods.AddOrUpdate(devId,data); + } else OtherMethods = new(); } RenderFragment GetRenderFragment(VariableEditInput context) { + if(!OtherMethods.ContainsKey(context.DeviceId)) + { + DeviceChanged(context.DeviceId); + } + RenderFragment renderFragment = null; renderFragment += @
@@ -247,17 +255,24 @@ @(context.Description(x => x.WriteExpressions)) + + @if(OtherMethods.ContainsKey(context.DeviceId)) + { + - @(context.Description(x => x.OtherMethod)) - @(context.Description(x => x.OtherMethod)) + u) ItemValue=@(u =>u) HideDetails=@("auto") Height="30" - Dense> - - + Dense> + + + + } + diff --git a/src/ThingsGateway.Web.Page/Page/DeviceStatusPage.razor b/src/ThingsGateway.Web.Page/Page/DeviceStatusPage.razor index 75ac27c54..ee808ff53 100644 --- a/src/ThingsGateway.Web.Page/Page/DeviceStatusPage.razor +++ b/src/ThingsGateway.Web.Page/Page/DeviceStatusPage.razor @@ -357,7 +357,6 @@ - @code { private string _collectDeviceGroup; private string _uploadDeviceGroup; @@ -369,19 +368,21 @@ List _uploadDeviceCores = new(); void collectDeviceQuery() { - _collectDeviceCores = CollectDeviceHostService.CollectDeviceCores.WhereIf(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _collectDeviceGroup).ToList(); + _collectDeviceCores = CollectDeviceHostService?.CollectDeviceCores?.WhereIf(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _collectDeviceGroup).ToList(); } void uploadDeviceQuery() { - _uploadDeviceCores = UploadDeviceHostService.UploadDeviceCores.WhereIf(!_uploadDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _uploadDeviceGroup).ToList(); + _uploadDeviceCores = UploadDeviceHostService?.UploadDeviceCores?.WhereIf(!_uploadDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _uploadDeviceGroup).ToList(); } protected override async Task OnParametersSetAsync() { _collectDeviceGroups = CollectDeviceService.GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList(); _uploadDeviceGroups = UploadDeviceService.GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList(); + collectDeviceQuery(); + uploadDeviceQuery(); await base.OnParametersSetAsync(); } - + private bool isDownExport; [Inject] IJSRuntime JS { get; set; } @@ -424,8 +425,7 @@ DelayTimer.Elapsed += timer_Elapsed; DelayTimer.AutoReset = true; DelayTimer.Start(); - collectDeviceQuery(); - uploadDeviceQuery(); + return base.OnInitializedAsync(); } private BootstrapDynamicComponent _importComponent; @@ -459,6 +459,7 @@ isRestart = true; StateHasChanged(); await Task.Run(async () => await CollectDeviceHostService.UpDeviceThread(devId)); + collectDeviceQuery(); } } catch (Exception ex) @@ -480,6 +481,7 @@ isRestart = true; StateHasChanged(); await Task.Run(() => UploadDeviceHostService.UpDeviceThread(devId)); + uploadDeviceQuery(); } } catch (Exception ex) @@ -512,6 +514,8 @@ StateHasChanged(); await Task.Run(async () => await CollectDeviceHostService.RestartDeviceThread()); + collectDeviceQuery(); + uploadDeviceQuery(); } } finally diff --git a/src/ThingsGateway.sln b/src/ThingsGateway.sln index 716447bd2..a32e858bf 100644 --- a/src/ThingsGateway.sln +++ b/src/ThingsGateway.sln @@ -66,6 +66,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventSource", "EventSource" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.RabbitMQ", "Plugins\ThingsGateway.RabbitMQ\ThingsGateway.RabbitMQ.csproj", "{7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation.Adapter.Siemens", "Plugins\ThingsGateway.Foundation.Adapter.Siemens\ThingsGateway.Foundation.Adapter.Siemens.csproj", "{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Siemens", "Plugins\ThingsGateway.Siemens\ThingsGateway.Siemens.csproj", "{12D7E735-8DA0-47FB-849F-84C965E4C746}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Siemens", "Siemens", "{98BB0E39-0629-4619-A01B-4BEB0F8DE879}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -218,6 +224,22 @@ Global {7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}.Release|Any CPU.Build.0 = Release|Any CPU {7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}.Release|x86.ActiveCfg = Release|Any CPU {7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}.Release|x86.Build.0 = Release|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|x86.ActiveCfg = Debug|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|x86.Build.0 = Debug|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|Any CPU.Build.0 = Release|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|x86.ActiveCfg = Release|Any CPU + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|x86.Build.0 = Release|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|x86.ActiveCfg = Debug|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|x86.Build.0 = Debug|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|Any CPU.Build.0 = Release|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|x86.ActiveCfg = Release|Any CPU + {12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -246,6 +268,9 @@ Global {4141C657-56B0-40D9-BA52-9F53A6D9E338} = {D64E48AD-2937-4227-8270-63AEF3008374} {968A381B-E354-4089-984D-7B32DF2A470D} = {D1DFBF11-65DE-49C8-9AE9-BFDFB66E129A} {7D38A1C3-56FA-41CE-B541-9FF1B5A632F7} = {968A381B-E354-4089-984D-7B32DF2A470D} + {A0C38DE3-56FD-4ECC-9599-372A4716A4C8} = {98BB0E39-0629-4619-A01B-4BEB0F8DE879} + {12D7E735-8DA0-47FB-849F-84C965E4C746} = {98BB0E39-0629-4619-A01B-4BEB0F8DE879} + {98BB0E39-0629-4619-A01B-4BEB0F8DE879} = {D1DFBF11-65DE-49C8-9AE9-BFDFB66E129A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C44BAB06-0E6E-46A5-84A1-C9C94533DD4D}