From 5330593256047be8f6aa6b41ad2a35362ab8157e Mon Sep 17 00:00:00 2001 From: SigiRab Date: Fri, 14 Nov 2025 11:15:37 +0100 Subject: [PATCH 01/15] =?UTF-8?q?bitte=20die=20neue=20S7=201200=20G2=20ein?= =?UTF-8?q?f=C3=BCgen,=20diese=20Serie=20kann=20ab=20Tia=20V20=20programmi?= =?UTF-8?q?ert=20werden=20und=20unterst=C3=BCtz=20keine=20Legacy-Verbindun?= =?UTF-8?q?gen=20mehr.=20Der=20Treiber=20funktioniert=20aber=20ansonsten?= =?UTF-8?q?=20einwandfrei.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/S7CommPlusDriver/Legitimation/Legitimation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/S7CommPlusDriver/Legitimation/Legitimation.cs b/src/S7CommPlusDriver/Legitimation/Legitimation.cs index 41e97c7..5645459 100644 --- a/src/S7CommPlusDriver/Legitimation/Legitimation.cs +++ b/src/S7CommPlusDriver/Legitimation/Legitimation.cs @@ -51,6 +51,10 @@ private int legitimate(ValueStruct serverSession, string password, string userna legacyLegitimation = true; } } + else if (sessionVersionPAOMString.Contains("50-0XB0") && deviceVersion.StartsWith('2')) //New S7-1200 G2 (example: "1;6ES7 212-1HG50-0XB0;V1.0") + { + legacyLegitimation = false; + } else if (deviceVersion.StartsWith("2")) { if (fwVerNo < 403) From 38b00f2bd252c14e692af91b9dde6fc930d1f288 Mon Sep 17 00:00:00 2001 From: Thomas Wiens Date: Thu, 27 Nov 2025 20:07:58 +0100 Subject: [PATCH 02/15] Use double quote for string --- src/S7CommPlusDriver/Legitimation/Legitimation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/S7CommPlusDriver/Legitimation/Legitimation.cs b/src/S7CommPlusDriver/Legitimation/Legitimation.cs index 5645459..8050fd5 100644 --- a/src/S7CommPlusDriver/Legitimation/Legitimation.cs +++ b/src/S7CommPlusDriver/Legitimation/Legitimation.cs @@ -51,7 +51,7 @@ private int legitimate(ValueStruct serverSession, string password, string userna legacyLegitimation = true; } } - else if (sessionVersionPAOMString.Contains("50-0XB0") && deviceVersion.StartsWith('2')) //New S7-1200 G2 (example: "1;6ES7 212-1HG50-0XB0;V1.0") + else if (sessionVersionPAOMString.Contains("50-0XB0") && deviceVersion.StartsWith("2")) //New S7-1200 G2 (example: "1;6ES7 212-1HG50-0XB0;V1.0") { legacyLegitimation = false; } From 9f1864f7a2b879623241d94ce14dd22796dc6929 Mon Sep 17 00:00:00 2001 From: Thomas Wiens Date: Thu, 27 Nov 2025 21:08:51 +0100 Subject: [PATCH 03/15] Alarms: Add method to read the active alarms without notification --- src/S7CommPlusDriver/Alarming/BrowseAlarms.cs | 68 +++++++++++++++++++ src/S7CommPlusDriver/Core/Ids.cs | 2 + 2 files changed, 70 insertions(+) diff --git a/src/S7CommPlusDriver/Alarming/BrowseAlarms.cs b/src/S7CommPlusDriver/Alarming/BrowseAlarms.cs index 6f31c4f..a7f96b5 100644 --- a/src/S7CommPlusDriver/Alarming/BrowseAlarms.cs +++ b/src/S7CommPlusDriver/Alarming/BrowseAlarms.cs @@ -332,6 +332,74 @@ private void GetTexts(byte[] tloa_1, byte[] tloa_2, byte[] tloa_3, byte[] tlsa, } } } + + /// + /// Reads the active program alarms from the Plc (single poll). + /// + /// Call example: + /// CultureInfo ci = new CultureInfo("de-DE"); + /// var alarmList = new List(); + /// conn.GetActiveAlarms(out alarmList, ci.LCID); + /// foreach (var a in alarmList) + /// { + /// Console.WriteLine(a.ToString()); + /// } + /// + /// Contains the alarms, empty if there is no active alarm + /// Language id for retrieving the text entries, use language code e.g. 1031 for german + /// 0 on success + public int GetActiveAlarms(out List alarmList, int languageId) + { + int res; + + alarmList = new List(); + + var exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = Ids.NativeObjects_theAlarmSubsystem_Rid; + exploreReq.ExploreRequestId = Ids.AlarmSubsystem_itsUpdateRelevantDAI; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 0; + + // Add the requestes attributes. + // Request the same attributes we get from an alarm notification, so we can reuse other methods. + exploreReq.AddressList.Add(Ids.DAI_CPUAlarmID); + exploreReq.AddressList.Add(Ids.DAI_AllStatesInfo); + exploreReq.AddressList.Add(Ids.DAI_AlarmDomain); + exploreReq.AddressList.Add(Ids.DAI_Coming); + exploreReq.AddressList.Add(Ids.DAI_Going); + exploreReq.AddressList.Add(Ids.DAI_MessageType); + exploreReq.AddressList.Add(Ids.DAI_HmiInfo); + // Extra ones which we only need for compatibility with notification. + exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); + exploreReq.AddressList.Add(Ids.DAI_SequenceCounter); + exploreReq.AddressList.Add(Ids.DAI_AlarmTexts_Rid); + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + + foreach (var obj in exploreRes.Objects) + { + alarmList.Add(AlarmsDai.FromNotificationObject(obj, languageId)); + } + + return 0; + } } public class AlarmData diff --git a/src/S7CommPlusDriver/Core/Ids.cs b/src/S7CommPlusDriver/Core/Ids.cs index f543bf7..12b330e 100644 --- a/src/S7CommPlusDriver/Core/Ids.cs +++ b/src/S7CommPlusDriver/Core/Ids.cs @@ -19,6 +19,7 @@ public static class Ids { public const int None = 0; public const int NativeObjects_thePLCProgram_Rid = 3; + public const int NativeObjects_theAlarmSubsystem_Rid = 8; public const int NativeObjects_theCPUexecUnit_Rid = 52; public const int NativeObjects_theIArea_Rid = 80; public const int NativeObjects_theQArea_Rid = 81; @@ -86,6 +87,7 @@ public static class Ids public const int AlarmSubscriptionRef_AlarmDomain = 2659; public const int AlarmSubscriptionRef_itsAlarmSubsystem = 2660; public const int AlarmSubscriptionRef_Class_Rid = 2662; + public const int AlarmSubsystem_itsUpdateRelevantDAI = 2667; public const int DAI_CPUAlarmID = 2670; public const int DAI_AllStatesInfo = 2671; public const int DAI_AlarmDomain = 2672; From ce4c92cc989ea1028e29254a9313b1aa67813806 Mon Sep 17 00:00:00 2001 From: SigiRab Date: Fri, 28 Nov 2025 15:31:44 +0100 Subject: [PATCH 04/15] Add files via upload --- .../ClientApi/PlcTagExtend.cs | 710 ++++++++++++++++++ 1 file changed, 710 insertions(+) create mode 100644 src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs diff --git a/src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs b/src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs new file mode 100644 index 0000000..7bae1bf --- /dev/null +++ b/src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs @@ -0,0 +1,710 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace S7CommPlusDriver.ClientApi; + +public class PlcTagBoolArray : PlcTag +{ + private bool[] m_Value; + + public bool[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagBoolArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new bool[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueBoolArray)) == 0) + { + Value = ((ValueBoolArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueBoolArray(Value); + } + + public override string ToString() + { + var val = new ValueBoolArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagByteArray : PlcTag +{ + private byte[] m_Value; + + public byte[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagByteArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new byte[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueByteArray)) == 0) + { + Value = ((ValueByteArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueByteArray(Value); + } + + public override string ToString() + { + var val = new ValueByteArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagWordArray : PlcTag +{ + private ushort[] m_Value; + + public ushort[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagWordArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new ushort[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueWordArray)) == 0) + { + Value = ((ValueWordArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueWordArray(Value); + } + + public override string ToString() + { + var val = new ValueWordArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagIntArray : PlcTag +{ + private short[] m_Value; + + public short[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new short[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueIntArray)) == 0) + { + Value = ((ValueIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueIntArray(Value); + } + + public override string ToString() + { + var val = new ValueIntArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagDWordArray : PlcTag +{ + private uint[] m_Value; + + public uint[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagDWordArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new uint[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueDWordArray)) == 0) + { + Value = ((ValueDWordArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueDWordArray(Value); + } + + public override string ToString() + { + var val = new ValueDWordArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagDIntArray : PlcTag +{ + private int[] m_Value; + + public int[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagDIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new int[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueDIntArray)) == 0) + { + Value = ((ValueDIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueDIntArray(Value); + } + + public override string ToString() + { + var val = new ValueDIntArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagRealArray : PlcTag +{ + private float[] m_Value; + + public float[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagRealArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new float[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueRealArray)) == 0) + { + Value = ((ValueRealArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueRealArray(Value); + } + + public override string ToString() + { + var val = new ValueRealArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagUSIntArray : PlcTag +{ + private byte[] m_Value; + + public byte[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagUSIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new byte[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) + { + Value = ((ValueUSIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueUSIntArray(Value); + } + + public override string ToString() + { + var val = new ValueUSIntArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagUIntArray : PlcTag +{ + private ushort[] m_Value; + + public ushort[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagUIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new ushort[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUIntArray)) == 0) + { + Value = ((ValueUIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueUIntArray(Value); + } + + public override string ToString() + { + var val = new ValueUIntArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagUDIntArray : PlcTag +{ + private uint[] m_Value; + + public uint[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagUDIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new uint[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUDIntArray)) == 0) + { + Value = ((ValueUDIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueUDIntArray(Value); + } + + public override string ToString() + { + var val = new ValueUDIntArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagSIntArray : PlcTag +{ + private sbyte[] m_Value; + + public sbyte[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagSIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new sbyte[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueSIntArray)) == 0) + { + Value = ((ValueSIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueSIntArray(Value); + } + + public override string ToString() + { + var val = new ValueSIntArray(Value); + return ResultString(this, val.ToString()); + } +} + +public class PlcTagDateAndTimeArray : PlcTag +{ + /* BCD coded: + * YYMMDDhhmmssuuuQ + * uuu = milliseconds + * Q = Weekday 1=Su, 2=Mo, 3=Tu, 4=We, 5=Th, 6=Fr, 7=Sa + */ + private DateTime[] m_Value; + + public DateTime[] Value + { + get + { + return m_Value; + } + + set + { + bool dataOk = true; + foreach (var item in value) + { + if (item < new DateTime(1990, 1, 1) && item >= new DateTime(2090, 1, 1)) + { + dataOk = false; + break; + } + } + if (dataOk) + { + m_Value = value; + } + else + { + throw new ArgumentOutOfRangeException("Value", "DateTime must be >= 1990-01-01 and < 2090-01-01"); + } + } + } + + public PlcTagDateAndTimeArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + Value = new DateTime[0]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) + { + List dateTimes = new List(); + var v = ((ValueUSIntArray)valueObj).GetValue(); + int pos = 0; + do + { + int[] ts = new int[8]; + for (int i = 0; i < 7; i++) + { + ts[i] = BcdByteToInt(v[pos + i]); + } + // The left nibble of the last byte contains the LSD of milliseconds, + // the right nibble the weekday (which we don't process here). + ts[7] = v[7] >> 4; + + int year; + if (ts[0] >= 90) + { + year = 1900 + ts[0]; + } + else + { + year = 2000 + ts[0]; + } + var value = new DateTime(year, ts[1], ts[2], ts[3], ts[4], ts[5]); + value = value.AddMilliseconds(ts[6] * 10 + ts[7]); + dateTimes.Add(value); + pos += 8; + } while (pos < v.Length); + Value = dateTimes.ToArray(); + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + var byteStrings = new List(); + foreach (var item in Value) + { + int[] ts = new int[8]; + byte[] b = new byte[8]; + if (item.Year < 2000) + { + // 90-99 = 1990-1999 + ts[0] = item.Year - 1900; + } + else + { + // 00-89 = 2000-2089 + ts[0] = item.Year - 2000; + } + ts[1] = item.Month; + ts[2] = item.Day; + ts[3] = item.Hour; + ts[4] = item.Minute; + ts[5] = item.Second; + ts[6] = item.Millisecond / 10; + ts[7] = (item.Millisecond % 10) << 4; // Don't set the weekday + for (int i = 0; i < 7; i++) + { + b[i] = IntToBcdByte(ts[i]); + } + b[7] = (byte)ts[7]; + byteStrings.AddRange(b); + } + return new ValueUSIntArray(byteStrings.ToArray()); + } + + public override string ToString() + { + string s = ""; + for (int i = 0; i < Value.Length; i++) + { + string ts = Value[i].ToString(); + if (Value[i].Millisecond > 0) + { + ts += String.Format(".{0:D03}", Value[i].Millisecond); + } + s += String.Format("{0}", ts); + } + s += ""; + return ResultString(this, s); + } +} + + +public class PlcTagStringArray : PlcTag +{ + private string[] m_Value; + private byte m_MaxLength = 254; + private string m_Encoding = "ISO-8859-1"; + + public string[] Value + { + get + { + return m_Value; + } + + set + { + bool lengthOk = true; + foreach (var item in value) + { + if (item.Length > m_MaxLength) + { + lengthOk = false; + break; + } + } + if (lengthOk) + { + m_Value = value; + } + else + { + throw new ArgumentOutOfRangeException("Value", "String is longer than the allowed max. length of " + m_MaxLength); + } + } + } + + public PlcTagStringArray(string name, ItemAddress address, uint softdatatype, byte maxlength = 254) : base(name, address, softdatatype) + { + m_MaxLength = maxlength; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) + { + List strings = new List(); + var v = ((ValueUSIntArray)valueObj).GetValue(); + int pos = 0; + do + { + int max_len = v[pos]; + int act_len = v[pos + 1]; + // IEC 61131-3 states ISO-646 IRV, with optional extensions like "Latin-1 Supplement". + // Siemens TIA-Portal gives warnings using other than 7 Bit ASCII characters. + // Let the user define his local encoding via SetStringEncoding(). + var str = Encoding.GetEncoding(m_Encoding).GetString(v, pos + 2, act_len); + strings.Add(str); + pos += max_len + 2; + + } while (pos < v.Length); + Value = strings.ToArray(); + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + var byteStrings = new List(); + foreach (var item in Value) + { + // Must write the complete array of MaxLength of the string (plus two bytes header). + byte[] sb = Encoding.GetEncoding(m_Encoding).GetBytes(item); + var b = new byte[m_MaxLength + 2]; + b[0] = m_MaxLength; + b[1] = (byte)sb.Length; + for (int i = 0; i < sb.Length; i++) + { + b[i + 2] = sb[i]; + } + byteStrings.AddRange(b); + } + return new ValueUSIntArray(byteStrings.ToArray()); + } + + public void SetStringEncoding(string encoding) + { + m_Encoding = encoding; + } + + public override string ToString() + { + string s = ""; + for (int i = 0; i < Value.Length; i++) + { + s += String.Format("{0}", Value[i]); + } + s += ""; + return ResultString(this, s); + } +} From 9cfa84b34a8499f7af69a063f205f7f2f95ef48d Mon Sep 17 00:00:00 2001 From: SigiRab Date: Fri, 28 Nov 2025 15:39:09 +0100 Subject: [PATCH 05/15] Update PlcTags.cs extended for array-types --- src/S7CommPlusDriver/ClientApi/PlcTags.cs | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/S7CommPlusDriver/ClientApi/PlcTags.cs b/src/S7CommPlusDriver/ClientApi/PlcTags.cs index 368b9d9..6a43eb0 100644 --- a/src/S7CommPlusDriver/ClientApi/PlcTags.cs +++ b/src/S7CommPlusDriver/ClientApi/PlcTags.cs @@ -69,25 +69,39 @@ public static int WriteTags(this S7CommPlusConnection conn, IEnumerable return res; } - public static PlcTag TagFactory(string name, ItemAddress address, uint softdatatype) + public static PlcTag TagFactory(string name, ItemAddress address, uint softdatatype, bool Is1Dim = false) { switch (softdatatype) { case Softdatatype.S7COMMP_SOFTDATATYPE_BOOL: + if (Is1Dim) + return new PlcTagBoolArray(name, address, softdatatype); return new PlcTagBool(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_BYTE: + if (Is1Dim) + return new PlcTagByteArray(name, address, softdatatype); return new PlcTagByte(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_CHAR: return new PlcTagChar(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_WORD: + if (Is1Dim) + return new PlcTagWordArray(name, address, softdatatype); return new PlcTagWord(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_INT: + if (Is1Dim) + return new PlcTagIntArray(name, address, softdatatype); return new PlcTagInt(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_DWORD: + if (Is1Dim) + return new PlcTagDWordArray(name, address, softdatatype); return new PlcTagDWord(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_DINT: + if (Is1Dim) + return new PlcTagDIntArray(name, address, softdatatype); return new PlcTagDInt(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_REAL: + if (Is1Dim) + return new PlcTagRealArray(name, address, softdatatype); return new PlcTagReal(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_DATE: return new PlcTagDate(name, address, softdatatype); @@ -98,9 +112,13 @@ public static PlcTag TagFactory(string name, ItemAddress address, uint softdatat case Softdatatype.S7COMMP_SOFTDATATYPE_S5TIME: return new PlcTagS5Time(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_DATEANDTIME: + if (Is1Dim) + return new PlcTagDateAndTimeArray(name, address, softdatatype); return new PlcTagDateAndTime(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_STRING: + if (Is1Dim) + return new PlcTagStringArray(name, address, softdatatype); return new PlcTagString(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_POINTER: return new PlcTagPointer(name, address, softdatatype); @@ -118,6 +136,8 @@ public static PlcTag TagFactory(string name, ItemAddress address, uint softdatat return new PlcTagUInt(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_BBOOL: + if (Is1Dim) + return new PlcTagBoolArray(name, address, softdatatype); return new PlcTagBool(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_LREAL: @@ -129,12 +149,20 @@ public static PlcTag TagFactory(string name, ItemAddress address, uint softdatat case Softdatatype.S7COMMP_SOFTDATATYPE_LWORD: return new PlcTagLWord(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_USINT: + if (Is1Dim) + return new PlcTagUSIntArray(name, address, softdatatype); return new PlcTagUSInt(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_UINT: + if (Is1Dim) + return new PlcTagUIntArray(name, address, softdatatype); return new PlcTagUInt(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_UDINT: + if (Is1Dim) + return new PlcTagUDIntArray(name, address, softdatatype); return new PlcTagUDInt(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_SINT: + if (Is1Dim) + return new PlcTagSIntArray(name, address, softdatatype); return new PlcTagSInt(name, address, softdatatype); case Softdatatype.S7COMMP_SOFTDATATYPE_WCHAR: From b9acd44f2abbfb2c684b0349937a5b5d89328521 Mon Sep 17 00:00:00 2001 From: SigiRab Date: Fri, 28 Nov 2025 15:42:26 +0100 Subject: [PATCH 06/15] Update S7CommPlusConnection.cs --- src/S7CommPlusDriver/S7CommPlusConnection.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/S7CommPlusDriver/S7CommPlusConnection.cs b/src/S7CommPlusDriver/S7CommPlusConnection.cs index 2bcb801..8a16b7e 100644 --- a/src/S7CommPlusDriver/S7CommPlusConnection.cs +++ b/src/S7CommPlusDriver/S7CommPlusConnection.cs @@ -1080,9 +1080,17 @@ private PlcTag browsePlcTagBySymbol(uint ti_relid, ref string symbol, VarInfo va if (idx < 0) return null; PVartypeListElement varType = pObj.VartypeList.Elements[idx]; varInfo.AccessSequence += "." + String.Format("{0:X}", varType.LID); + bool is1Dim = false; if (varType.OffsetInfoType.Is1Dim()) { - calcAccessSeqFor1DimArray(ref symbol, varType, varInfo); + if (symbol == "") + { + is1Dim = true; + } + else + { + calcAccessSeqFor1DimArray(ref symbol, varType, varInfo); + } } if (varType.OffsetInfoType.IsMDim()) { @@ -1090,6 +1098,10 @@ private PlcTag browsePlcTagBySymbol(uint ti_relid, ref string symbol, VarInfo va } if (varType.OffsetInfoType.HasRelation()) { + if (symbol.Length <= 0 && varType.Softdatatype == Softdatatype.S7COMMP_SOFTDATATYPE_DTL) + { + return PlcTags.TagFactory(varInfo.Name, new ItemAddress(varInfo.AccessSequence), varType.Softdatatype, is1Dim); + } if (symbol.Length <= 0) { return null; @@ -1102,7 +1114,7 @@ private PlcTag browsePlcTagBySymbol(uint ti_relid, ref string symbol, VarInfo va } else { - return PlcTags.TagFactory(varInfo.Name, new ItemAddress(varInfo.AccessSequence), varType.Softdatatype); + return PlcTags.TagFactory(varInfo.Name, new ItemAddress(varInfo.AccessSequence), varType.Softdatatype, is1Dim); } } From b61c12637e6f0c59681afe29953377af199662cf Mon Sep 17 00:00:00 2001 From: SigiRab Date: Fri, 28 Nov 2025 15:43:12 +0100 Subject: [PATCH 07/15] Update S7CommPlusConnection.cs extended for array-types From b84fb52b4c9d9f66de23b74fa45b2e0ed8e14f98 Mon Sep 17 00:00:00 2001 From: Thomas Wiens Date: Wed, 3 Dec 2025 18:35:44 +0100 Subject: [PATCH 08/15] Move Array-Tag classes into single PlcTags file --- src/S7CommPlusDriver/ClientApi/PlcTag.cs | 709 +++++++++++++++++ .../ClientApi/PlcTagExtend.cs | 710 ------------------ 2 files changed, 709 insertions(+), 710 deletions(-) delete mode 100644 src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs diff --git a/src/S7CommPlusDriver/ClientApi/PlcTag.cs b/src/S7CommPlusDriver/ClientApi/PlcTag.cs index 18cc628..4e83546 100644 --- a/src/S7CommPlusDriver/ClientApi/PlcTag.cs +++ b/src/S7CommPlusDriver/ClientApi/PlcTag.cs @@ -1,5 +1,7 @@ using System; using System.Text; +using System.Collections; +using System.Collections.Generic; namespace S7CommPlusDriver.ClientApi { @@ -1701,4 +1703,711 @@ public override string ToString() return String.Format(fmt, Value.ToString(), ns); } } + + #region Arrays + + public class PlcTagBoolArray : PlcTag + { + private bool[] m_Value; + + public bool[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagBoolArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new bool[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueBoolArray)) == 0) + { + Value = ((ValueBoolArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueBoolArray(Value); + } + + public override string ToString() + { + var val = new ValueBoolArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagByteArray : PlcTag + { + private byte[] m_Value; + + public byte[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagByteArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new byte[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueByteArray)) == 0) + { + Value = ((ValueByteArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueByteArray(Value); + } + + public override string ToString() + { + var val = new ValueByteArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagWordArray : PlcTag + { + private ushort[] m_Value; + + public ushort[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagWordArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new ushort[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueWordArray)) == 0) + { + Value = ((ValueWordArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueWordArray(Value); + } + + public override string ToString() + { + var val = new ValueWordArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagIntArray : PlcTag + { + private short[] m_Value; + + public short[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new short[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueIntArray)) == 0) + { + Value = ((ValueIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueIntArray(Value); + } + + public override string ToString() + { + var val = new ValueIntArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagDWordArray : PlcTag + { + private uint[] m_Value; + + public uint[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagDWordArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new uint[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueDWordArray)) == 0) + { + Value = ((ValueDWordArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueDWordArray(Value); + } + + public override string ToString() + { + var val = new ValueDWordArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagDIntArray : PlcTag + { + private int[] m_Value; + + public int[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagDIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new int[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueDIntArray)) == 0) + { + Value = ((ValueDIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueDIntArray(Value); + } + + public override string ToString() + { + var val = new ValueDIntArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagRealArray : PlcTag + { + private float[] m_Value; + + public float[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagRealArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new float[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueRealArray)) == 0) + { + Value = ((ValueRealArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueRealArray(Value); + } + + public override string ToString() + { + var val = new ValueRealArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagUSIntArray : PlcTag + { + private byte[] m_Value; + + public byte[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagUSIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new byte[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) + { + Value = ((ValueUSIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueUSIntArray(Value); + } + + public override string ToString() + { + var val = new ValueUSIntArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagUIntArray : PlcTag + { + private ushort[] m_Value; + + public ushort[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagUIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new ushort[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUIntArray)) == 0) + { + Value = ((ValueUIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueUIntArray(Value); + } + + public override string ToString() + { + var val = new ValueUIntArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagUDIntArray : PlcTag + { + private uint[] m_Value; + + public uint[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagUDIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new uint[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUDIntArray)) == 0) + { + Value = ((ValueUDIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueUDIntArray(Value); + } + + public override string ToString() + { + var val = new ValueUDIntArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagSIntArray : PlcTag + { + private sbyte[] m_Value; + + public sbyte[] Value + { + get { return m_Value; } + set { m_Value = value; } + } + + public PlcTagSIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + m_Value = new sbyte[1]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueSIntArray)) == 0) + { + Value = ((ValueSIntArray)valueObj).GetValue(); + + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + return new ValueSIntArray(Value); + } + + public override string ToString() + { + var val = new ValueSIntArray(Value); + return ResultString(this, val.ToString()); + } + } + + public class PlcTagDateAndTimeArray : PlcTag + { + /* BCD coded: + * YYMMDDhhmmssuuuQ + * uuu = milliseconds + * Q = Weekday 1=Su, 2=Mo, 3=Tu, 4=We, 5=Th, 6=Fr, 7=Sa + */ + private DateTime[] m_Value; + + public DateTime[] Value + { + get + { + return m_Value; + } + + set + { + bool dataOk = true; + foreach (var item in value) + { + if (item < new DateTime(1990, 1, 1) && item >= new DateTime(2090, 1, 1)) + { + dataOk = false; + break; + } + } + if (dataOk) + { + m_Value = value; + } + else + { + throw new ArgumentOutOfRangeException("Value", "DateTime must be >= 1990-01-01 and < 2090-01-01"); + } + } + } + + public PlcTagDateAndTimeArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) + { + Value = new DateTime[0]; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) + { + List dateTimes = new List(); + var v = ((ValueUSIntArray)valueObj).GetValue(); + int pos = 0; + do + { + int[] ts = new int[8]; + for (int i = 0; i < 7; i++) + { + ts[i] = BcdByteToInt(v[pos + i]); + } + // The left nibble of the last byte contains the LSD of milliseconds, + // the right nibble the weekday (which we don't process here). + ts[7] = v[7] >> 4; + + int year; + if (ts[0] >= 90) + { + year = 1900 + ts[0]; + } + else + { + year = 2000 + ts[0]; + } + var value = new DateTime(year, ts[1], ts[2], ts[3], ts[4], ts[5]); + value = value.AddMilliseconds(ts[6] * 10 + ts[7]); + dateTimes.Add(value); + pos += 8; + } while (pos < v.Length); + Value = dateTimes.ToArray(); + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + var byteStrings = new List(); + foreach (var item in Value) + { + int[] ts = new int[8]; + byte[] b = new byte[8]; + if (item.Year < 2000) + { + // 90-99 = 1990-1999 + ts[0] = item.Year - 1900; + } + else + { + // 00-89 = 2000-2089 + ts[0] = item.Year - 2000; + } + ts[1] = item.Month; + ts[2] = item.Day; + ts[3] = item.Hour; + ts[4] = item.Minute; + ts[5] = item.Second; + ts[6] = item.Millisecond / 10; + ts[7] = (item.Millisecond % 10) << 4; // Don't set the weekday + for (int i = 0; i < 7; i++) + { + b[i] = IntToBcdByte(ts[i]); + } + b[7] = (byte)ts[7]; + byteStrings.AddRange(b); + } + return new ValueUSIntArray(byteStrings.ToArray()); + } + + public override string ToString() + { + string s = ""; + for (int i = 0; i < Value.Length; i++) + { + string ts = Value[i].ToString(); + if (Value[i].Millisecond > 0) + { + ts += String.Format(".{0:D03}", Value[i].Millisecond); + } + s += String.Format("{0}", ts); + } + s += ""; + return ResultString(this, s); + } + } + + + public class PlcTagStringArray : PlcTag + { + private string[] m_Value; + private byte m_MaxLength = 254; + private string m_Encoding = "ISO-8859-1"; + + public string[] Value + { + get + { + return m_Value; + } + + set + { + bool lengthOk = true; + foreach (var item in value) + { + if (item.Length > m_MaxLength) + { + lengthOk = false; + break; + } + } + if (lengthOk) + { + m_Value = value; + } + else + { + throw new ArgumentOutOfRangeException("Value", "String is longer than the allowed max. length of " + m_MaxLength); + } + } + } + + public PlcTagStringArray(string name, ItemAddress address, uint softdatatype, byte maxlength = 254) : base(name, address, softdatatype) + { + m_MaxLength = maxlength; + } + + public override void ProcessReadResult(object valueObj, ulong error) + { + LastReadError = error; + if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) + { + List strings = new List(); + var v = ((ValueUSIntArray)valueObj).GetValue(); + int pos = 0; + do + { + int max_len = v[pos]; + int act_len = v[pos + 1]; + // IEC 61131-3 states ISO-646 IRV, with optional extensions like "Latin-1 Supplement". + // Siemens TIA-Portal gives warnings using other than 7 Bit ASCII characters. + // Let the user define his local encoding via SetStringEncoding(). + var str = Encoding.GetEncoding(m_Encoding).GetString(v, pos + 2, act_len); + strings.Add(str); + pos += max_len + 2; + + } while (pos < v.Length); + Value = strings.ToArray(); + Quality = PlcTagQC.TAG_QUALITY_GOOD; + } + else + { + Quality = PlcTagQC.TAG_QUALITY_BAD; + } + } + + public override PValue GetWriteValue() + { + var byteStrings = new List(); + foreach (var item in Value) + { + // Must write the complete array of MaxLength of the string (plus two bytes header). + byte[] sb = Encoding.GetEncoding(m_Encoding).GetBytes(item); + var b = new byte[m_MaxLength + 2]; + b[0] = m_MaxLength; + b[1] = (byte)sb.Length; + for (int i = 0; i < sb.Length; i++) + { + b[i + 2] = sb[i]; + } + byteStrings.AddRange(b); + } + return new ValueUSIntArray(byteStrings.ToArray()); + } + + public void SetStringEncoding(string encoding) + { + m_Encoding = encoding; + } + + public override string ToString() + { + string s = ""; + for (int i = 0; i < Value.Length; i++) + { + s += String.Format("{0}", Value[i]); + } + s += ""; + return ResultString(this, s); + } + } + #endregion } \ No newline at end of file diff --git a/src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs b/src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs deleted file mode 100644 index 7bae1bf..0000000 --- a/src/S7CommPlusDriver/ClientApi/PlcTagExtend.cs +++ /dev/null @@ -1,710 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace S7CommPlusDriver.ClientApi; - -public class PlcTagBoolArray : PlcTag -{ - private bool[] m_Value; - - public bool[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagBoolArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new bool[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueBoolArray)) == 0) - { - Value = ((ValueBoolArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueBoolArray(Value); - } - - public override string ToString() - { - var val = new ValueBoolArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagByteArray : PlcTag -{ - private byte[] m_Value; - - public byte[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagByteArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new byte[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueByteArray)) == 0) - { - Value = ((ValueByteArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueByteArray(Value); - } - - public override string ToString() - { - var val = new ValueByteArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagWordArray : PlcTag -{ - private ushort[] m_Value; - - public ushort[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagWordArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new ushort[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueWordArray)) == 0) - { - Value = ((ValueWordArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueWordArray(Value); - } - - public override string ToString() - { - var val = new ValueWordArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagIntArray : PlcTag -{ - private short[] m_Value; - - public short[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new short[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueIntArray)) == 0) - { - Value = ((ValueIntArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueIntArray(Value); - } - - public override string ToString() - { - var val = new ValueIntArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagDWordArray : PlcTag -{ - private uint[] m_Value; - - public uint[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagDWordArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new uint[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueDWordArray)) == 0) - { - Value = ((ValueDWordArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueDWordArray(Value); - } - - public override string ToString() - { - var val = new ValueDWordArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagDIntArray : PlcTag -{ - private int[] m_Value; - - public int[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagDIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new int[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueDIntArray)) == 0) - { - Value = ((ValueDIntArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueDIntArray(Value); - } - - public override string ToString() - { - var val = new ValueDIntArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagRealArray : PlcTag -{ - private float[] m_Value; - - public float[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagRealArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new float[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueRealArray)) == 0) - { - Value = ((ValueRealArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueRealArray(Value); - } - - public override string ToString() - { - var val = new ValueRealArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagUSIntArray : PlcTag -{ - private byte[] m_Value; - - public byte[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagUSIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new byte[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) - { - Value = ((ValueUSIntArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueUSIntArray(Value); - } - - public override string ToString() - { - var val = new ValueUSIntArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagUIntArray : PlcTag -{ - private ushort[] m_Value; - - public ushort[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagUIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new ushort[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueUIntArray)) == 0) - { - Value = ((ValueUIntArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueUIntArray(Value); - } - - public override string ToString() - { - var val = new ValueUIntArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagUDIntArray : PlcTag -{ - private uint[] m_Value; - - public uint[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagUDIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new uint[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueUDIntArray)) == 0) - { - Value = ((ValueUDIntArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueUDIntArray(Value); - } - - public override string ToString() - { - var val = new ValueUDIntArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagSIntArray : PlcTag -{ - private sbyte[] m_Value; - - public sbyte[] Value - { - get { return m_Value; } - set { m_Value = value; } - } - - public PlcTagSIntArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - m_Value = new sbyte[1]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueSIntArray)) == 0) - { - Value = ((ValueSIntArray)valueObj).GetValue(); - - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - return new ValueSIntArray(Value); - } - - public override string ToString() - { - var val = new ValueSIntArray(Value); - return ResultString(this, val.ToString()); - } -} - -public class PlcTagDateAndTimeArray : PlcTag -{ - /* BCD coded: - * YYMMDDhhmmssuuuQ - * uuu = milliseconds - * Q = Weekday 1=Su, 2=Mo, 3=Tu, 4=We, 5=Th, 6=Fr, 7=Sa - */ - private DateTime[] m_Value; - - public DateTime[] Value - { - get - { - return m_Value; - } - - set - { - bool dataOk = true; - foreach (var item in value) - { - if (item < new DateTime(1990, 1, 1) && item >= new DateTime(2090, 1, 1)) - { - dataOk = false; - break; - } - } - if (dataOk) - { - m_Value = value; - } - else - { - throw new ArgumentOutOfRangeException("Value", "DateTime must be >= 1990-01-01 and < 2090-01-01"); - } - } - } - - public PlcTagDateAndTimeArray(string name, ItemAddress address, uint softdatatype) : base(name, address, softdatatype) - { - Value = new DateTime[0]; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) - { - List dateTimes = new List(); - var v = ((ValueUSIntArray)valueObj).GetValue(); - int pos = 0; - do - { - int[] ts = new int[8]; - for (int i = 0; i < 7; i++) - { - ts[i] = BcdByteToInt(v[pos + i]); - } - // The left nibble of the last byte contains the LSD of milliseconds, - // the right nibble the weekday (which we don't process here). - ts[7] = v[7] >> 4; - - int year; - if (ts[0] >= 90) - { - year = 1900 + ts[0]; - } - else - { - year = 2000 + ts[0]; - } - var value = new DateTime(year, ts[1], ts[2], ts[3], ts[4], ts[5]); - value = value.AddMilliseconds(ts[6] * 10 + ts[7]); - dateTimes.Add(value); - pos += 8; - } while (pos < v.Length); - Value = dateTimes.ToArray(); - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - var byteStrings = new List(); - foreach (var item in Value) - { - int[] ts = new int[8]; - byte[] b = new byte[8]; - if (item.Year < 2000) - { - // 90-99 = 1990-1999 - ts[0] = item.Year - 1900; - } - else - { - // 00-89 = 2000-2089 - ts[0] = item.Year - 2000; - } - ts[1] = item.Month; - ts[2] = item.Day; - ts[3] = item.Hour; - ts[4] = item.Minute; - ts[5] = item.Second; - ts[6] = item.Millisecond / 10; - ts[7] = (item.Millisecond % 10) << 4; // Don't set the weekday - for (int i = 0; i < 7; i++) - { - b[i] = IntToBcdByte(ts[i]); - } - b[7] = (byte)ts[7]; - byteStrings.AddRange(b); - } - return new ValueUSIntArray(byteStrings.ToArray()); - } - - public override string ToString() - { - string s = ""; - for (int i = 0; i < Value.Length; i++) - { - string ts = Value[i].ToString(); - if (Value[i].Millisecond > 0) - { - ts += String.Format(".{0:D03}", Value[i].Millisecond); - } - s += String.Format("{0}", ts); - } - s += ""; - return ResultString(this, s); - } -} - - -public class PlcTagStringArray : PlcTag -{ - private string[] m_Value; - private byte m_MaxLength = 254; - private string m_Encoding = "ISO-8859-1"; - - public string[] Value - { - get - { - return m_Value; - } - - set - { - bool lengthOk = true; - foreach (var item in value) - { - if (item.Length > m_MaxLength) - { - lengthOk = false; - break; - } - } - if (lengthOk) - { - m_Value = value; - } - else - { - throw new ArgumentOutOfRangeException("Value", "String is longer than the allowed max. length of " + m_MaxLength); - } - } - } - - public PlcTagStringArray(string name, ItemAddress address, uint softdatatype, byte maxlength = 254) : base(name, address, softdatatype) - { - m_MaxLength = maxlength; - } - - public override void ProcessReadResult(object valueObj, ulong error) - { - LastReadError = error; - if (CheckErrorAndType(error, valueObj, typeof(ValueUSIntArray)) == 0) - { - List strings = new List(); - var v = ((ValueUSIntArray)valueObj).GetValue(); - int pos = 0; - do - { - int max_len = v[pos]; - int act_len = v[pos + 1]; - // IEC 61131-3 states ISO-646 IRV, with optional extensions like "Latin-1 Supplement". - // Siemens TIA-Portal gives warnings using other than 7 Bit ASCII characters. - // Let the user define his local encoding via SetStringEncoding(). - var str = Encoding.GetEncoding(m_Encoding).GetString(v, pos + 2, act_len); - strings.Add(str); - pos += max_len + 2; - - } while (pos < v.Length); - Value = strings.ToArray(); - Quality = PlcTagQC.TAG_QUALITY_GOOD; - } - else - { - Quality = PlcTagQC.TAG_QUALITY_BAD; - } - } - - public override PValue GetWriteValue() - { - var byteStrings = new List(); - foreach (var item in Value) - { - // Must write the complete array of MaxLength of the string (plus two bytes header). - byte[] sb = Encoding.GetEncoding(m_Encoding).GetBytes(item); - var b = new byte[m_MaxLength + 2]; - b[0] = m_MaxLength; - b[1] = (byte)sb.Length; - for (int i = 0; i < sb.Length; i++) - { - b[i + 2] = sb[i]; - } - byteStrings.AddRange(b); - } - return new ValueUSIntArray(byteStrings.ToArray()); - } - - public void SetStringEncoding(string encoding) - { - m_Encoding = encoding; - } - - public override string ToString() - { - string s = ""; - for (int i = 0; i < Value.Length; i++) - { - s += String.Format("{0}", Value[i]); - } - s += ""; - return ResultString(this, s); - } -} From ef73ef30b2a27d3a45da0a7c63a47e24133b520c Mon Sep 17 00:00:00 2001 From: z Date: Mon, 23 Mar 2026 11:22:01 +0800 Subject: [PATCH 09/15] add function: support for S7-1507S F software controller(min FW:21.9) --- .vs/S7CommPlusDriver/v17/.wsuo | Bin 0 -> 13824 bytes .vs/S7CommPlusDriver/v17/DocumentLayout.json | 97 +++++ .../Legitimation/Legitimation.cs | 55 ++- .../Legitimation/Legitimation.cs.bak | 392 ++++++++++++++++++ 4 files changed, 537 insertions(+), 7 deletions(-) create mode 100644 .vs/S7CommPlusDriver/v17/.wsuo create mode 100644 .vs/S7CommPlusDriver/v17/DocumentLayout.json create mode 100644 src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak diff --git a/.vs/S7CommPlusDriver/v17/.wsuo b/.vs/S7CommPlusDriver/v17/.wsuo new file mode 100644 index 0000000000000000000000000000000000000000..6bfc261eedb41bff1b4e15028361ec8b8400fa38 GIT binary patch literal 13824 zcmeHOO^h5z6|UJ3AUFhK2L}R}B`hJ3)TX_5EpYmLP8wEg)4WY*nD60)J*T}PP=C&BpGRI^t!96 ztLoLO_fxNa=C5}ied`x*KJhOx5Kf5)#NB%ji4PX?YlXP@xDX#fnt6Bc-Mhz6j3as& zrf39KL<_$iF)PlBExg;}23c7-92XDvc>dshYW?WP5C8Fdzx!wH2+JMM3tt4rmUvY> z59}FYXT|IHX=U%{<6@?VpToZQ>%pltWJ7#a(D;dWTWp{QZIJ>R7>f{)cFPTW&54;> zk=?_}74yH2#Ii@#O zkM#8a76lJM>)*Sn|Es`zsQ+sCd$yDIfOdd-`vl@zhd+ck^BAh=KW$N7dhheo$fqsX zn|qjguSbBqJqR@4$xr%uoU^O&pK8rHgL4S&Kj#C=6Z!ucz-Iv`0G|WQ0zMCT67U7U z9N>$9rvP69JPr6V;46S<0M7y>U>@)szyO#43s48x00-a#iZq=ZNS`XCPb0nvI0IM$ zoCTZ%d=qdU@I2rJz_$P|0+sHOJhE_+R9eZf^-VBluuf z@T1?iro6sbTt%v*3zfWQ$M8SDhW6+yF%05gL?2T0;TpWrmhuR9AdB>ocagKdM^@MW z)P7ptYWef4_)MNLETZKWux$co8}F)qZ(f@^JyWY8p>4a)FMF@xBXyhMlH&d~=vEWd z?EwE(^rwwjtUTlW6zBEyllXyUHoNejKxW5m7`s&=ECg#L;KNnb6O4b2{>3QtQ}ng{EyKU2W`Qlt_1ox4J`RKQ!T$Devk!yc4@z^b zuGb%B_>212p95Zk^!51w`LEH>qTf{&Kkc<1e?%t=uiViy$#~7o?VEu*Vis1}z|VxI z$7fj}E$>8l<*om2owr{7>8NdOhMk?X{ld9%>#zLn=*;7fj9-7${tlKu$Zuudq5nU* z|IcxJzX#fZNpxt^`m}?S;HMpD{eBzU3rJQDllH$dqLPA~4+g^|d4p|LNz;e?E#Qne zDb~CG7}tcHYi2R~H{p$O?qA1Rk)APEBppD8nV9yP-uo^}Zy?1r#4hG&dT#4jof`lP zF|IE+@uT-ful5G=R*}2j^DguD8{J$JEBbi=pLMM4*^-CS^h#po9nxD|N3U;BY0oz? zYNVvCsQCSqQqch?$+_u~Aj-+V!=U@vb; zNg+=)=z&p}c<#Y757up|oX!LHQNJU;tCJ(Y+5brbcfazz zyy8ldJG^Z9Jh!omKiNx_$BK0r9~v5B?#SDxpZUoj&cFGOU*9?Qt2Hd-8p6*WyM6ce zi5GwQM)I4Vo%l~Mw7NP6;2L@^4m{IM>XG!)dM53_w56ZAzVwnTHC@+r5-(Cj)w)a5 zg{uoBeNi?poiy4c_4OO~`9FOq>_doqb4&x}JWP7>?p;&Ui&)-6^rfzYx7 zOQv-%k+vBpGBi9-rcoG&u^szyY-?F0*3mi80=9is9_yplgMjV)?VL0G&p>@}ef|yd|KClVM;w()0o=G#oRt+^A4Ibzn2sdd!CkS+N~yZV@51G5ca_GJyB*(YQQ}+tGi|#LocbEUTny~LM*c!&xqX2cTInP zFY>;WKM&7;56^$Muvf?P=EL*f^7H5a$NBFvyrIi#&vfs3cX7|POoN{q+4wLSe0B`}bIrgzAPj@_ z=Qz3ZlK&InG$e5wmXSMrJhNbKF_8bHTZiJeVR7EA)$17rt wFGC8rqT=1SPVWvxzh!?6{V$GxzoqeFhv)y3o&QsBdG43n_y7O^ literal 0 HcmV?d00001 diff --git a/.vs/S7CommPlusDriver/v17/DocumentLayout.json b/.vs/S7CommPlusDriver/v17/DocumentLayout.json new file mode 100644 index 0000000..3b2eef4 --- /dev/null +++ b/.vs/S7CommPlusDriver/v17/DocumentLayout.json @@ -0,0 +1,97 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\zhangf7.ZF-WORLD\\source\\repos\\S7CommPlusDriver\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 194, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" + }, + { + "$type": "Bookmark", + "Name": "ST:132:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:135:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:132:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:133:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:134:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:137:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:142:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:143:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{1c64b9c2-e352-428e-a56d-0ace190b99a6}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{de1fc918-f32e-4dd7-a915-1792a051f26b}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{e5c86464-96be-4d7c-9a8b-abcb3bbf5f92}" + } + ] + }, + { + "DockedWidth": 206, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:928770227:0:{81164725-9a96-4ece-a4cb-440d8fd285e5}" + }, + { + "$type": "Bookmark", + "Name": "ST:254354193:0:{71f361cc-493f-47c0-923f-f2570b6f8618}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/S7CommPlusDriver/Legitimation/Legitimation.cs b/src/S7CommPlusDriver/Legitimation/Legitimation.cs index 8050fd5..7901ced 100644 --- a/src/S7CommPlusDriver/Legitimation/Legitimation.cs +++ b/src/S7CommPlusDriver/Legitimation/Legitimation.cs @@ -23,23 +23,55 @@ public partial class S7CommPlusConnection /// error code (0 = ok) private int legitimate(ValueStruct serverSession, string password, string username = "") { + // S7-1214C (6ES7 214-1AG40-0XB0) 1;6ES7 214-1AG40-0XB0 ;V4.5 + // S7-1510SP (6ES7 510-1DJ01-0AB0) 1;6ES7 510-1DJ01-0AB0;V2.9 + // S7-1507SF (6ES7 672-7FC01-0YA0) 1;6ES7 672-7FC01-0YA0;V21.9 + // Parse device and firmware version + // doc: https://cache.industry.siemens.com/dl/files/068/109769068/att_1329908/v4/109769068_UsingCertificatesWithTIAPortal_DOC_V2_1_en.pdf + // Certificates in the scope of PG/PC and HMI communication + // Starting with TIA Portal V17, PG / PC and HMI communication is secured with TLS, protecting the data exchanged between + // Field PGs and HMIs with SIMATIC CPUs. + // The CPU families that support Secure PG / HMI communication are: + // • S7 - 1500 controllers as of firmware version V2.9. + // • S7 - 1200 controllers as of firmware version V4.5. + // • Software controllers as of firmware version V21.9. + // • SIMATIC Drive controllers as of firmware version V2.9. + // • PLCSim and PLCSim Advanced Version V4.0. + // HMI components that support Secure PG/ HMI communication, as of image version V17, are: + // • Panels or PCs configured with WinCC Basic, Comfort and Advanced. + // • PCs with WinCC RT Professional. + // • WinCC Unified PCs and Comfort Panels. + // In addition, SINAMICS RT SW, as of version V6.1, and STARTDRIVE, as of version V17, support secure communication string sessionVersionPAOMString = ((ValueWString)serverSession.GetStructElement((uint)Ids.LID_SessionVersionSystemPAOMString)).GetValue(); - Regex reVersions = new Regex("^.*;.*[17]\\s?([52]\\d\\d).+;[VS](\\d\\.\\d)$"); + var reVersions = new Regex( + @"^[^;]*;[^;]*[17]\s?(\d{3}).*;[VS](\d{1,2}\.\d+)$", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + ); Match m = reVersions.Match(sessionVersionPAOMString); if (!m.Success) { Console.WriteLine("S7CommPlusConnection - Legitimate: Could not extract firmware version!"); return S7Consts.errCliFirmwareNotSupported; } - string deviceVersion = m.Groups[1].Value; - string firmwareVersion = m.Groups[2].Value; - int fwVerNo = int.Parse(firmwareVersion.Split('.')[0]) * 100; - fwVerNo += int.Parse(firmwareVersion.Split('.')[1]); + string deviceVersion = m.Groups[1].Value; // e.g., "672" + string firmwareVersion = m.Groups[2].Value; // e.g., "21.9" + + // Compute fwVerNo = major*100 + minor (e.g., "21.9" -> 2109) + int fwVerNo; + { + var parts = firmwareVersion.Split('.'); + if (parts.Length < 2 || !int.TryParse(parts[0], out var major) || !int.TryParse(parts[1], out var minor)) + { + Console.WriteLine($"S7CommPlusConnection - Legitimate: Invalid firmware format: {firmwareVersion}"); + return S7Consts.errCliFirmwareNotSupported; + } + fwVerNo = (major * 100) + minor; + } // Check if we have to use legacy legitimation via the firmware version bool legacyLegitimation = false; - if (deviceVersion.StartsWith("5")) + if (deviceVersion.StartsWith("5")) // S7-1500 (5xx) { if (fwVerNo < 209) { @@ -55,7 +87,7 @@ private int legitimate(ValueStruct serverSession, string password, string userna { legacyLegitimation = false; } - else if (deviceVersion.StartsWith("2")) + else if (deviceVersion.StartsWith("2")) // S7-1200 (2xx) { if (fwVerNo < 403) { @@ -67,6 +99,15 @@ private int legitimate(ValueStruct serverSession, string password, string userna legacyLegitimation = true; } } + else if (deviceVersion.StartsWith("6")) // S7-1507S (6xx) + { + if (fwVerNo < 2109) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); + return S7Consts.errCliFirmwareNotSupported; + } + legacyLegitimation = true; + } else { Console.WriteLine("S7CommPlusConnection - Legitimate: Device version is not supported!"); diff --git a/src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak b/src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak new file mode 100644 index 0000000..d04e921 --- /dev/null +++ b/src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak @@ -0,0 +1,392 @@ +using S7CommPlusDriver.Legitimation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace S7CommPlusDriver { + public partial class S7CommPlusConnection + { + + private byte[] omsSecret; + + /// + /// Legitimation stage of the connect routine + /// + /// Server sesstion information containing the firmware version + /// PLC password + /// PLC username (leave empty for legacy login) + /// error code (0 = ok) + private int legitimate(ValueStruct serverSession, string password, string username = "") + { + // S7-1214C (6ES7 214-1AG40-0XB0) 1;6ES7 214-1AG40-0XB0 ;V4.5 + // S7-1510SP (6ES7 510-1DJ01-0AB0) 1;6ES7 510-1DJ01-0AB0;V2.9 + // S7-1507SF (6ES7 672-7FC01-0YA0) 1;6ES7 672-7FC01-0YA0;V21.9 + + // Parse device and firmware version + // doc: https://cache.industry.siemens.com/dl/files/068/109769068/att_1329908/v4/109769068_UsingCertificatesWithTIAPortal_DOC_V2_1_en.pdf + // Certificates in the scope of PG/PC and HMI communication + // Starting with TIA Portal V17, PG / PC and HMI communication is secured with TLS, protecting the data exchanged between + // Field PGs and HMIs with SIMATIC CPUs. + // The CPU families that support Secure PG / HMI communication are: + // • S7 - 1500 controllers as of firmware version V2.9. + // • S7 - 1200 controllers as of firmware version V4.5. + // • Software controllers as of firmware version V21.9. + // • SIMATIC Drive controllers as of firmware version V2.9. + // • PLCSim and PLCSim Advanced Version V4.0. + // HMI components that support Secure PG/ HMI communication, as of image version V17, are: + // • Panels or PCs configured with WinCC Basic, Comfort and Advanced. + // • PCs with WinCC RT Professional. + // • WinCC Unified PCs and Comfort Panels. + // In addition, SINAMICS RT SW, as of version V6.1, and STARTDRIVE, as of version V17, support secure communication + string sessionVersionPAOMString = ((ValueWString)serverSession.GetStructElement((uint)Ids.LID_SessionVersionSystemPAOMString)).GetValue(); + var reVersions = new Regex( + @"^[^;]*;[^;]*[17]\s?(\d{3}).*;[VS](\d{1,2}\.\d+)$", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + ); + Match m = reVersions.Match(sessionVersionPAOMString); + if (!m.Success) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Could not extract firmware version!"); + return S7Consts.errCliFirmwareNotSupported; + } + string deviceVersion = m.Groups[1].Value; + string firmwareVersion = m.Groups[2].Value; + + // Compute fwVerNo = major*100 + minor (e.g., "21.9" -> 2109) + int fwVerNo; + { + var parts = firmwareVersion.Split('.'); + if (parts.Length < 2 || !int.TryParse(parts[0], out var major) || !int.TryParse(parts[1], out var minor)) + { + Console.WriteLine($"S7CommPlusConnection - Legitimate: Invalid firmware format: {firmwareVersion}"); + return S7Consts.errCliFirmwareNotSupported; + } + fwVerNo = (major * 100) + minor; + } + + // Check if we have to use legacy legitimation via the firmware version + bool legacyLegitimation = false; + if (deviceVersion.StartsWith("5")) + { + if (fwVerNo < 209) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); + return S7Consts.errCliFirmwareNotSupported; + } + if (fwVerNo < 301) + { + legacyLegitimation = true; + } + } + else if (sessionVersionPAOMString.Contains("50-0XB0") && deviceVersion.StartsWith("2")) //New S7-1200 G2 (example: "1;6ES7 212-1HG50-0XB0;V1.0") + { + legacyLegitimation = false; + } + else if (deviceVersion.StartsWith("2")) + { + if (fwVerNo < 403) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); + return S7Consts.errCliFirmwareNotSupported; + } + if (fwVerNo < 407) + { + legacyLegitimation = true; + } + } + else if (deviceVersion.StartsWith("6")) // S7-1507S (6xx) + { + if (fwVerNo < 2109) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); + return S7Consts.errCliFirmwareNotSupported; + } + legacyLegitimation = true; + } + else + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Device version is not supported!"); + return S7Consts.errCliDeviceNotSupported; + } + + // Get current protection level + var getVarSubstreamedReq = new GetVarSubstreamedRequest(ProtocolVersion.V2); + getVarSubstreamedReq.InObjectId = m_SessionId; + getVarSubstreamedReq.SessionId = m_SessionId; + getVarSubstreamedReq.Address = Ids.EffectiveProtectionLevel; + int res = SendS7plusFunctionObject(getVarSubstreamedReq); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var getVarSubstreamedRes = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); + if (getVarSubstreamedRes == null) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: GetVarSubstreamedResponse with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU; + } + + // Check access level + UInt32 accessLevel = (getVarSubstreamedRes.Value as ValueUDInt).GetValue(); + if (accessLevel > AccessLevel.FullAccess && password != "") + { + // Legitimate + if (legacyLegitimation) + { + return legitimateLegacy(password); + } + else + { + return legitimateNew(password, username); + } + + } + else if (accessLevel > AccessLevel.FullAccess) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Warning: Access level is not fullaccess but no password set!"); + } + + return 0; + } + + /// + /// Legitimate using the new login method (firmware >= 3.1) + /// + /// PLC password + /// PLC username (leave empy for legacy login) + /// error code (0 = ok) + private int legitimateNew(string password, string username = "") + { + // Get challenge + var getVarSubstreamedReq_challange = new GetVarSubstreamedRequest(ProtocolVersion.V2); + getVarSubstreamedReq_challange.InObjectId = m_SessionId; + getVarSubstreamedReq_challange.SessionId = m_SessionId; + getVarSubstreamedReq_challange.Address = Ids.ServerSessionRequest; + int res = SendS7plusFunctionObject(getVarSubstreamedReq_challange); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var getVarSubstreamedRes_challenge = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); + if (getVarSubstreamedRes_challenge == null) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: getVarSubstreamedRes_challenge with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU; + } + + byte[] challenge = (getVarSubstreamedRes_challenge.Value as ValueUSIntArray).GetValue(); + + // Encrypt challengeResponse + byte[] challengeResponse; + if (omsSecret == null || omsSecret.Length != 32) + { + // Create oms exporter secret + omsSecret = m_client.getOMSExporterSecret(); + } + // Roll key + byte[] key = LegitimationCrypto.sha256(omsSecret); + omsSecret = key; + + // Use the first 16 bytes of the challenge as iv + byte[] iv = new ArraySegment(challenge, 0, 16).ToArray(); + // Encrypt + challengeResponse = LegitimationCrypto.EncryptAesCbc(buildLegitimationPayload(password, username), key, iv); + + // Send challengeResponse + var setVariableReq = new SetVariableRequest(ProtocolVersion.V2); + setVariableReq.InObjectId = m_SessionId; + setVariableReq.SessionId = m_SessionId; + setVariableReq.Address = Ids.Legitimate; + setVariableReq.Value = new ValueBlob(0, challengeResponse); + res = SendS7plusFunctionObject(setVariableReq); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var setVariableResponse = SetVariableResponse.DeserializeFromPdu(m_ReceivedPDU); + if (setVariableResponse == null) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: setVariableResponse with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU; + } + // Check if the legitimation attempt was successful + Int16 errorCode = (Int16)setVariableResponse.ReturnValue; + if (errorCode < 0) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: access denied"); + m_client.Disconnect(); + return S7Consts.errCliAccessDenied; + } + + return 0; + } + + /// + /// Builds the legitimation payload from given username and password. + /// If username is empty the payload for a legacy login will be build. + /// If username is not empty the payload for the new login is build. + /// + /// PLC password + /// PLC username (optional) + /// Build payload + private static byte[] buildLegitimationPayload(string password, string username = "") + { + ValueStruct payload = new ValueStruct(Ids.LID_LegitimationPayloadStruct); + if (username != "") + { + // Login with username and password = new login + payload.AddStructElement(Ids.LID_LegitimationPayloadType, new ValueUDInt(LegitimationType.New)); + payload.AddStructElement(Ids.LID_LegitimationPayloadUsername, new ValueBlob(0, Encoding.UTF8.GetBytes(username))); + payload.AddStructElement(Ids.LID_LegitimationPayloadPassword, new ValueBlob(0, Encoding.UTF8.GetBytes(password))); + + } + else + { + // Login with only password = legacy login + // Hash password + byte[] hashedPw; + using (SHA1Managed sha1 = new SHA1Managed()) + { + hashedPw = sha1.ComputeHash(Encoding.UTF8.GetBytes(password)); + } + + payload.AddStructElement(Ids.LID_LegitimationPayloadType, new ValueUDInt(LegitimationType.Legacy)); + payload.AddStructElement(Ids.LID_LegitimationPayloadUsername, new ValueBlob(0, Encoding.UTF8.GetBytes(username))); + payload.AddStructElement(Ids.LID_LegitimationPayloadPassword, new ValueBlob(0, hashedPw)); + } + using (var memStr = new MemoryStream()) + { + payload.Serialize(memStr); + return memStr.ToArray(); + } + } + + /// + /// Legitimate using the old legacy login (firmware version < 3.1) + /// + /// PLC password + /// error code (0 = OK) + private int legitimateLegacy(string password) + { + + // Get challenge + var getVarSubstreamedReq_challange = new GetVarSubstreamedRequest(ProtocolVersion.V2); + getVarSubstreamedReq_challange.InObjectId = m_SessionId; + getVarSubstreamedReq_challange.SessionId = m_SessionId; + getVarSubstreamedReq_challange.Address = Ids.ServerSessionRequest; + int res = SendS7plusFunctionObject(getVarSubstreamedReq_challange); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var getVarSubstreamedRes_challenge = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); + if (getVarSubstreamedRes_challenge == null) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: getVarSubstreamedRes_challenge with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU; + } + + byte[] challenge = (getVarSubstreamedRes_challenge.Value as ValueUSIntArray).GetValue(); + + // Calculate challengeResponse [sha1(password) xor challenge] + byte[] challengeResponse; + using (SHA1Managed sha1 = new SHA1Managed()) + { + challengeResponse = sha1.ComputeHash(Encoding.UTF8.GetBytes(password)); + } + if (challengeResponse.Length != challenge.Length) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: challengeResponse.Length != challenge.Length"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU; + } + for (int i = 0; i < challengeResponse.Length; ++i) + { + challengeResponse[i] = (byte)(challengeResponse[i] ^ challenge[i]); + } + + // Send challengeResponse + var setVariableReq = new SetVariableRequest(ProtocolVersion.V2); + setVariableReq.InObjectId = m_SessionId; + setVariableReq.SessionId = m_SessionId; + setVariableReq.Address = Ids.ServerSessionResponse; + setVariableReq.Value = new ValueUSIntArray(challengeResponse); + res = SendS7plusFunctionObject(setVariableReq); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var setVariableResponse = SetVariableResponse.DeserializeFromPdu(m_ReceivedPDU); + if (setVariableResponse == null) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: setVariableResponse with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU; + } + // Check if the legitimation attempt was successful + Int16 errorCode = (Int16)setVariableResponse.ReturnValue; + if (errorCode < 0) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: access denied"); + m_client.Disconnect(); + return S7Consts.errCliAccessDenied; + } + + return 0; + } + } +} From 1a91c494e24ce6fc9e899623df4b7d04cd0f3484 Mon Sep 17 00:00:00 2001 From: z Date: Mon, 23 Mar 2026 12:03:08 +0800 Subject: [PATCH 10/15] Revert "add function: support for S7-1507S F software controller(min FW:21.9)" This reverts commit ef73ef30b2a27d3a45da0a7c63a47e24133b520c. --- .vs/S7CommPlusDriver/v17/.wsuo | Bin 13824 -> 0 bytes .vs/S7CommPlusDriver/v17/DocumentLayout.json | 97 ----- .../Legitimation/Legitimation.cs | 55 +-- .../Legitimation/Legitimation.cs.bak | 392 ------------------ 4 files changed, 7 insertions(+), 537 deletions(-) delete mode 100644 .vs/S7CommPlusDriver/v17/.wsuo delete mode 100644 .vs/S7CommPlusDriver/v17/DocumentLayout.json delete mode 100644 src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak diff --git a/.vs/S7CommPlusDriver/v17/.wsuo b/.vs/S7CommPlusDriver/v17/.wsuo deleted file mode 100644 index 6bfc261eedb41bff1b4e15028361ec8b8400fa38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13824 zcmeHOO^h5z6|UJ3AUFhK2L}R}B`hJ3)TX_5EpYmLP8wEg)4WY*nD60)J*T}PP=C&BpGRI^t!96 ztLoLO_fxNa=C5}ied`x*KJhOx5Kf5)#NB%ji4PX?YlXP@xDX#fnt6Bc-Mhz6j3as& zrf39KL<_$iF)PlBExg;}23c7-92XDvc>dshYW?WP5C8Fdzx!wH2+JMM3tt4rmUvY> z59}FYXT|IHX=U%{<6@?VpToZQ>%pltWJ7#a(D;dWTWp{QZIJ>R7>f{)cFPTW&54;> zk=?_}74yH2#Ii@#O zkM#8a76lJM>)*Sn|Es`zsQ+sCd$yDIfOdd-`vl@zhd+ck^BAh=KW$N7dhheo$fqsX zn|qjguSbBqJqR@4$xr%uoU^O&pK8rHgL4S&Kj#C=6Z!ucz-Iv`0G|WQ0zMCT67U7U z9N>$9rvP69JPr6V;46S<0M7y>U>@)szyO#43s48x00-a#iZq=ZNS`XCPb0nvI0IM$ zoCTZ%d=qdU@I2rJz_$P|0+sHOJhE_+R9eZf^-VBluuf z@T1?iro6sbTt%v*3zfWQ$M8SDhW6+yF%05gL?2T0;TpWrmhuR9AdB>ocagKdM^@MW z)P7ptYWef4_)MNLETZKWux$co8}F)qZ(f@^JyWY8p>4a)FMF@xBXyhMlH&d~=vEWd z?EwE(^rwwjtUTlW6zBEyllXyUHoNejKxW5m7`s&=ECg#L;KNnb6O4b2{>3QtQ}ng{EyKU2W`Qlt_1ox4J`RKQ!T$Devk!yc4@z^b zuGb%B_>212p95Zk^!51w`LEH>qTf{&Kkc<1e?%t=uiViy$#~7o?VEu*Vis1}z|VxI z$7fj}E$>8l<*om2owr{7>8NdOhMk?X{ld9%>#zLn=*;7fj9-7${tlKu$Zuudq5nU* z|IcxJzX#fZNpxt^`m}?S;HMpD{eBzU3rJQDllH$dqLPA~4+g^|d4p|LNz;e?E#Qne zDb~CG7}tcHYi2R~H{p$O?qA1Rk)APEBppD8nV9yP-uo^}Zy?1r#4hG&dT#4jof`lP zF|IE+@uT-ful5G=R*}2j^DguD8{J$JEBbi=pLMM4*^-CS^h#po9nxD|N3U;BY0oz? zYNVvCsQCSqQqch?$+_u~Aj-+V!=U@vb; zNg+=)=z&p}c<#Y757up|oX!LHQNJU;tCJ(Y+5brbcfazz zyy8ldJG^Z9Jh!omKiNx_$BK0r9~v5B?#SDxpZUoj&cFGOU*9?Qt2Hd-8p6*WyM6ce zi5GwQM)I4Vo%l~Mw7NP6;2L@^4m{IM>XG!)dM53_w56ZAzVwnTHC@+r5-(Cj)w)a5 zg{uoBeNi?poiy4c_4OO~`9FOq>_doqb4&x}JWP7>?p;&Ui&)-6^rfzYx7 zOQv-%k+vBpGBi9-rcoG&u^szyY-?F0*3mi80=9is9_yplgMjV)?VL0G&p>@}ef|yd|KClVM;w()0o=G#oRt+^A4Ibzn2sdd!CkS+N~yZV@51G5ca_GJyB*(YQQ}+tGi|#LocbEUTny~LM*c!&xqX2cTInP zFY>;WKM&7;56^$Muvf?P=EL*f^7H5a$NBFvyrIi#&vfs3cX7|POoN{q+4wLSe0B`}bIrgzAPj@_ z=Qz3ZlK&InG$e5wmXSMrJhNbKF_8bHTZiJeVR7EA)$17rt wFGC8rqT=1SPVWvxzh!?6{V$GxzoqeFhv)y3o&QsBdG43n_y7O^ diff --git a/.vs/S7CommPlusDriver/v17/DocumentLayout.json b/.vs/S7CommPlusDriver/v17/DocumentLayout.json deleted file mode 100644 index 3b2eef4..0000000 --- a/.vs/S7CommPlusDriver/v17/DocumentLayout.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "Version": 1, - "WorkspaceRootPath": "C:\\Users\\zhangf7.ZF-WORLD\\source\\repos\\S7CommPlusDriver\\", - "Documents": [], - "DocumentGroupContainers": [ - { - "Orientation": 0, - "VerticalTabListWidth": 256, - "DocumentGroups": [ - { - "DockedWidth": 194, - "SelectedChildIndex": -1, - "Children": [ - { - "$type": "Bookmark", - "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" - }, - { - "$type": "Bookmark", - "Name": "ST:132:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" - }, - { - "$type": "Bookmark", - "Name": "ST:128:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:135:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:132:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:133:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:134:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" - }, - { - "$type": "Bookmark", - "Name": "ST:137:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:142:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:143:0:{1fc202d4-d401-403c-9834-5b218574bb67}" - }, - { - "$type": "Bookmark", - "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" - }, - { - "$type": "Bookmark", - "Name": "ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}" - }, - { - "$type": "Bookmark", - "Name": "ST:0:0:{1c64b9c2-e352-428e-a56d-0ace190b99a6}" - }, - { - "$type": "Bookmark", - "Name": "ST:0:0:{de1fc918-f32e-4dd7-a915-1792a051f26b}" - }, - { - "$type": "Bookmark", - "Name": "ST:0:0:{e5c86464-96be-4d7c-9a8b-abcb3bbf5f92}" - } - ] - }, - { - "DockedWidth": 206, - "SelectedChildIndex": -1, - "Children": [ - { - "$type": "Bookmark", - "Name": "ST:928770227:0:{81164725-9a96-4ece-a4cb-440d8fd285e5}" - }, - { - "$type": "Bookmark", - "Name": "ST:254354193:0:{71f361cc-493f-47c0-923f-f2570b6f8618}" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/src/S7CommPlusDriver/Legitimation/Legitimation.cs b/src/S7CommPlusDriver/Legitimation/Legitimation.cs index 7901ced..8050fd5 100644 --- a/src/S7CommPlusDriver/Legitimation/Legitimation.cs +++ b/src/S7CommPlusDriver/Legitimation/Legitimation.cs @@ -23,55 +23,23 @@ public partial class S7CommPlusConnection /// error code (0 = ok) private int legitimate(ValueStruct serverSession, string password, string username = "") { - // S7-1214C (6ES7 214-1AG40-0XB0) 1;6ES7 214-1AG40-0XB0 ;V4.5 - // S7-1510SP (6ES7 510-1DJ01-0AB0) 1;6ES7 510-1DJ01-0AB0;V2.9 - // S7-1507SF (6ES7 672-7FC01-0YA0) 1;6ES7 672-7FC01-0YA0;V21.9 - // Parse device and firmware version - // doc: https://cache.industry.siemens.com/dl/files/068/109769068/att_1329908/v4/109769068_UsingCertificatesWithTIAPortal_DOC_V2_1_en.pdf - // Certificates in the scope of PG/PC and HMI communication - // Starting with TIA Portal V17, PG / PC and HMI communication is secured with TLS, protecting the data exchanged between - // Field PGs and HMIs with SIMATIC CPUs. - // The CPU families that support Secure PG / HMI communication are: - // • S7 - 1500 controllers as of firmware version V2.9. - // • S7 - 1200 controllers as of firmware version V4.5. - // • Software controllers as of firmware version V21.9. - // • SIMATIC Drive controllers as of firmware version V2.9. - // • PLCSim and PLCSim Advanced Version V4.0. - // HMI components that support Secure PG/ HMI communication, as of image version V17, are: - // • Panels or PCs configured with WinCC Basic, Comfort and Advanced. - // • PCs with WinCC RT Professional. - // • WinCC Unified PCs and Comfort Panels. - // In addition, SINAMICS RT SW, as of version V6.1, and STARTDRIVE, as of version V17, support secure communication string sessionVersionPAOMString = ((ValueWString)serverSession.GetStructElement((uint)Ids.LID_SessionVersionSystemPAOMString)).GetValue(); - var reVersions = new Regex( - @"^[^;]*;[^;]*[17]\s?(\d{3}).*;[VS](\d{1,2}\.\d+)$", - RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase - ); + Regex reVersions = new Regex("^.*;.*[17]\\s?([52]\\d\\d).+;[VS](\\d\\.\\d)$"); Match m = reVersions.Match(sessionVersionPAOMString); if (!m.Success) { Console.WriteLine("S7CommPlusConnection - Legitimate: Could not extract firmware version!"); return S7Consts.errCliFirmwareNotSupported; } - string deviceVersion = m.Groups[1].Value; // e.g., "672" - string firmwareVersion = m.Groups[2].Value; // e.g., "21.9" - - // Compute fwVerNo = major*100 + minor (e.g., "21.9" -> 2109) - int fwVerNo; - { - var parts = firmwareVersion.Split('.'); - if (parts.Length < 2 || !int.TryParse(parts[0], out var major) || !int.TryParse(parts[1], out var minor)) - { - Console.WriteLine($"S7CommPlusConnection - Legitimate: Invalid firmware format: {firmwareVersion}"); - return S7Consts.errCliFirmwareNotSupported; - } - fwVerNo = (major * 100) + minor; - } + string deviceVersion = m.Groups[1].Value; + string firmwareVersion = m.Groups[2].Value; + int fwVerNo = int.Parse(firmwareVersion.Split('.')[0]) * 100; + fwVerNo += int.Parse(firmwareVersion.Split('.')[1]); // Check if we have to use legacy legitimation via the firmware version bool legacyLegitimation = false; - if (deviceVersion.StartsWith("5")) // S7-1500 (5xx) + if (deviceVersion.StartsWith("5")) { if (fwVerNo < 209) { @@ -87,7 +55,7 @@ private int legitimate(ValueStruct serverSession, string password, string userna { legacyLegitimation = false; } - else if (deviceVersion.StartsWith("2")) // S7-1200 (2xx) + else if (deviceVersion.StartsWith("2")) { if (fwVerNo < 403) { @@ -99,15 +67,6 @@ private int legitimate(ValueStruct serverSession, string password, string userna legacyLegitimation = true; } } - else if (deviceVersion.StartsWith("6")) // S7-1507S (6xx) - { - if (fwVerNo < 2109) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); - return S7Consts.errCliFirmwareNotSupported; - } - legacyLegitimation = true; - } else { Console.WriteLine("S7CommPlusConnection - Legitimate: Device version is not supported!"); diff --git a/src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak b/src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak deleted file mode 100644 index d04e921..0000000 --- a/src/S7CommPlusDriver/Legitimation/Legitimation.cs.bak +++ /dev/null @@ -1,392 +0,0 @@ -using S7CommPlusDriver.Legitimation; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace S7CommPlusDriver { - public partial class S7CommPlusConnection - { - - private byte[] omsSecret; - - /// - /// Legitimation stage of the connect routine - /// - /// Server sesstion information containing the firmware version - /// PLC password - /// PLC username (leave empty for legacy login) - /// error code (0 = ok) - private int legitimate(ValueStruct serverSession, string password, string username = "") - { - // S7-1214C (6ES7 214-1AG40-0XB0) 1;6ES7 214-1AG40-0XB0 ;V4.5 - // S7-1510SP (6ES7 510-1DJ01-0AB0) 1;6ES7 510-1DJ01-0AB0;V2.9 - // S7-1507SF (6ES7 672-7FC01-0YA0) 1;6ES7 672-7FC01-0YA0;V21.9 - - // Parse device and firmware version - // doc: https://cache.industry.siemens.com/dl/files/068/109769068/att_1329908/v4/109769068_UsingCertificatesWithTIAPortal_DOC_V2_1_en.pdf - // Certificates in the scope of PG/PC and HMI communication - // Starting with TIA Portal V17, PG / PC and HMI communication is secured with TLS, protecting the data exchanged between - // Field PGs and HMIs with SIMATIC CPUs. - // The CPU families that support Secure PG / HMI communication are: - // • S7 - 1500 controllers as of firmware version V2.9. - // • S7 - 1200 controllers as of firmware version V4.5. - // • Software controllers as of firmware version V21.9. - // • SIMATIC Drive controllers as of firmware version V2.9. - // • PLCSim and PLCSim Advanced Version V4.0. - // HMI components that support Secure PG/ HMI communication, as of image version V17, are: - // • Panels or PCs configured with WinCC Basic, Comfort and Advanced. - // • PCs with WinCC RT Professional. - // • WinCC Unified PCs and Comfort Panels. - // In addition, SINAMICS RT SW, as of version V6.1, and STARTDRIVE, as of version V17, support secure communication - string sessionVersionPAOMString = ((ValueWString)serverSession.GetStructElement((uint)Ids.LID_SessionVersionSystemPAOMString)).GetValue(); - var reVersions = new Regex( - @"^[^;]*;[^;]*[17]\s?(\d{3}).*;[VS](\d{1,2}\.\d+)$", - RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase - ); - Match m = reVersions.Match(sessionVersionPAOMString); - if (!m.Success) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: Could not extract firmware version!"); - return S7Consts.errCliFirmwareNotSupported; - } - string deviceVersion = m.Groups[1].Value; - string firmwareVersion = m.Groups[2].Value; - - // Compute fwVerNo = major*100 + minor (e.g., "21.9" -> 2109) - int fwVerNo; - { - var parts = firmwareVersion.Split('.'); - if (parts.Length < 2 || !int.TryParse(parts[0], out var major) || !int.TryParse(parts[1], out var minor)) - { - Console.WriteLine($"S7CommPlusConnection - Legitimate: Invalid firmware format: {firmwareVersion}"); - return S7Consts.errCliFirmwareNotSupported; - } - fwVerNo = (major * 100) + minor; - } - - // Check if we have to use legacy legitimation via the firmware version - bool legacyLegitimation = false; - if (deviceVersion.StartsWith("5")) - { - if (fwVerNo < 209) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); - return S7Consts.errCliFirmwareNotSupported; - } - if (fwVerNo < 301) - { - legacyLegitimation = true; - } - } - else if (sessionVersionPAOMString.Contains("50-0XB0") && deviceVersion.StartsWith("2")) //New S7-1200 G2 (example: "1;6ES7 212-1HG50-0XB0;V1.0") - { - legacyLegitimation = false; - } - else if (deviceVersion.StartsWith("2")) - { - if (fwVerNo < 403) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); - return S7Consts.errCliFirmwareNotSupported; - } - if (fwVerNo < 407) - { - legacyLegitimation = true; - } - } - else if (deviceVersion.StartsWith("6")) // S7-1507S (6xx) - { - if (fwVerNo < 2109) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); - return S7Consts.errCliFirmwareNotSupported; - } - legacyLegitimation = true; - } - else - { - Console.WriteLine("S7CommPlusConnection - Legitimate: Device version is not supported!"); - return S7Consts.errCliDeviceNotSupported; - } - - // Get current protection level - var getVarSubstreamedReq = new GetVarSubstreamedRequest(ProtocolVersion.V2); - getVarSubstreamedReq.InObjectId = m_SessionId; - getVarSubstreamedReq.SessionId = m_SessionId; - getVarSubstreamedReq.Address = Ids.EffectiveProtectionLevel; - int res = SendS7plusFunctionObject(getVarSubstreamedReq); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var getVarSubstreamedRes = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); - if (getVarSubstreamedRes == null) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: GetVarSubstreamedResponse with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU; - } - - // Check access level - UInt32 accessLevel = (getVarSubstreamedRes.Value as ValueUDInt).GetValue(); - if (accessLevel > AccessLevel.FullAccess && password != "") - { - // Legitimate - if (legacyLegitimation) - { - return legitimateLegacy(password); - } - else - { - return legitimateNew(password, username); - } - - } - else if (accessLevel > AccessLevel.FullAccess) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: Warning: Access level is not fullaccess but no password set!"); - } - - return 0; - } - - /// - /// Legitimate using the new login method (firmware >= 3.1) - /// - /// PLC password - /// PLC username (leave empy for legacy login) - /// error code (0 = ok) - private int legitimateNew(string password, string username = "") - { - // Get challenge - var getVarSubstreamedReq_challange = new GetVarSubstreamedRequest(ProtocolVersion.V2); - getVarSubstreamedReq_challange.InObjectId = m_SessionId; - getVarSubstreamedReq_challange.SessionId = m_SessionId; - getVarSubstreamedReq_challange.Address = Ids.ServerSessionRequest; - int res = SendS7plusFunctionObject(getVarSubstreamedReq_challange); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var getVarSubstreamedRes_challenge = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); - if (getVarSubstreamedRes_challenge == null) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: getVarSubstreamedRes_challenge with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU; - } - - byte[] challenge = (getVarSubstreamedRes_challenge.Value as ValueUSIntArray).GetValue(); - - // Encrypt challengeResponse - byte[] challengeResponse; - if (omsSecret == null || omsSecret.Length != 32) - { - // Create oms exporter secret - omsSecret = m_client.getOMSExporterSecret(); - } - // Roll key - byte[] key = LegitimationCrypto.sha256(omsSecret); - omsSecret = key; - - // Use the first 16 bytes of the challenge as iv - byte[] iv = new ArraySegment(challenge, 0, 16).ToArray(); - // Encrypt - challengeResponse = LegitimationCrypto.EncryptAesCbc(buildLegitimationPayload(password, username), key, iv); - - // Send challengeResponse - var setVariableReq = new SetVariableRequest(ProtocolVersion.V2); - setVariableReq.InObjectId = m_SessionId; - setVariableReq.SessionId = m_SessionId; - setVariableReq.Address = Ids.Legitimate; - setVariableReq.Value = new ValueBlob(0, challengeResponse); - res = SendS7plusFunctionObject(setVariableReq); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var setVariableResponse = SetVariableResponse.DeserializeFromPdu(m_ReceivedPDU); - if (setVariableResponse == null) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: setVariableResponse with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU; - } - // Check if the legitimation attempt was successful - Int16 errorCode = (Int16)setVariableResponse.ReturnValue; - if (errorCode < 0) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: access denied"); - m_client.Disconnect(); - return S7Consts.errCliAccessDenied; - } - - return 0; - } - - /// - /// Builds the legitimation payload from given username and password. - /// If username is empty the payload for a legacy login will be build. - /// If username is not empty the payload for the new login is build. - /// - /// PLC password - /// PLC username (optional) - /// Build payload - private static byte[] buildLegitimationPayload(string password, string username = "") - { - ValueStruct payload = new ValueStruct(Ids.LID_LegitimationPayloadStruct); - if (username != "") - { - // Login with username and password = new login - payload.AddStructElement(Ids.LID_LegitimationPayloadType, new ValueUDInt(LegitimationType.New)); - payload.AddStructElement(Ids.LID_LegitimationPayloadUsername, new ValueBlob(0, Encoding.UTF8.GetBytes(username))); - payload.AddStructElement(Ids.LID_LegitimationPayloadPassword, new ValueBlob(0, Encoding.UTF8.GetBytes(password))); - - } - else - { - // Login with only password = legacy login - // Hash password - byte[] hashedPw; - using (SHA1Managed sha1 = new SHA1Managed()) - { - hashedPw = sha1.ComputeHash(Encoding.UTF8.GetBytes(password)); - } - - payload.AddStructElement(Ids.LID_LegitimationPayloadType, new ValueUDInt(LegitimationType.Legacy)); - payload.AddStructElement(Ids.LID_LegitimationPayloadUsername, new ValueBlob(0, Encoding.UTF8.GetBytes(username))); - payload.AddStructElement(Ids.LID_LegitimationPayloadPassword, new ValueBlob(0, hashedPw)); - } - using (var memStr = new MemoryStream()) - { - payload.Serialize(memStr); - return memStr.ToArray(); - } - } - - /// - /// Legitimate using the old legacy login (firmware version < 3.1) - /// - /// PLC password - /// error code (0 = OK) - private int legitimateLegacy(string password) - { - - // Get challenge - var getVarSubstreamedReq_challange = new GetVarSubstreamedRequest(ProtocolVersion.V2); - getVarSubstreamedReq_challange.InObjectId = m_SessionId; - getVarSubstreamedReq_challange.SessionId = m_SessionId; - getVarSubstreamedReq_challange.Address = Ids.ServerSessionRequest; - int res = SendS7plusFunctionObject(getVarSubstreamedReq_challange); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var getVarSubstreamedRes_challenge = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); - if (getVarSubstreamedRes_challenge == null) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: getVarSubstreamedRes_challenge with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU; - } - - byte[] challenge = (getVarSubstreamedRes_challenge.Value as ValueUSIntArray).GetValue(); - - // Calculate challengeResponse [sha1(password) xor challenge] - byte[] challengeResponse; - using (SHA1Managed sha1 = new SHA1Managed()) - { - challengeResponse = sha1.ComputeHash(Encoding.UTF8.GetBytes(password)); - } - if (challengeResponse.Length != challenge.Length) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: challengeResponse.Length != challenge.Length"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU; - } - for (int i = 0; i < challengeResponse.Length; ++i) - { - challengeResponse[i] = (byte)(challengeResponse[i] ^ challenge[i]); - } - - // Send challengeResponse - var setVariableReq = new SetVariableRequest(ProtocolVersion.V2); - setVariableReq.InObjectId = m_SessionId; - setVariableReq.SessionId = m_SessionId; - setVariableReq.Address = Ids.ServerSessionResponse; - setVariableReq.Value = new ValueUSIntArray(challengeResponse); - res = SendS7plusFunctionObject(setVariableReq); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var setVariableResponse = SetVariableResponse.DeserializeFromPdu(m_ReceivedPDU); - if (setVariableResponse == null) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: setVariableResponse with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU; - } - // Check if the legitimation attempt was successful - Int16 errorCode = (Int16)setVariableResponse.ReturnValue; - if (errorCode < 0) - { - Console.WriteLine("S7CommPlusConnection - Legitimate: access denied"); - m_client.Disconnect(); - return S7Consts.errCliAccessDenied; - } - - return 0; - } - } -} From c4eb09925ff6e20c7269b913484a79c39b9a8751 Mon Sep 17 00:00:00 2001 From: z Date: Mon, 23 Mar 2026 12:10:57 +0800 Subject: [PATCH 11/15] support for Software controllers as of firmware version V21.9. --- .../Legitimation/Legitimation.cs | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/src/S7CommPlusDriver/Legitimation/Legitimation.cs b/src/S7CommPlusDriver/Legitimation/Legitimation.cs index 8050fd5..2597d7c 100644 --- a/src/S7CommPlusDriver/Legitimation/Legitimation.cs +++ b/src/S7CommPlusDriver/Legitimation/Legitimation.cs @@ -8,8 +8,9 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace S7CommPlusDriver { - public partial class S7CommPlusConnection +namespace S7CommPlusDriver +{ + public partial class S7CommPlusConnection { private byte[] omsSecret; @@ -23,23 +24,55 @@ public partial class S7CommPlusConnection /// error code (0 = ok) private int legitimate(ValueStruct serverSession, string password, string username = "") { + // S7-1214C (6ES7 214-1AG40-0XB0) 1;6ES7 214-1AG40-0XB0 ;V4.5 + // S7-1510SP (6ES7 510-1DJ01-0AB0) 1;6ES7 510-1DJ01-0AB0;V2.9 + // S7-1507SF (6ES7 672-7FC01-0YA0) 1;6ES7 672-7FC01-0YA0;V21.9 + // Parse device and firmware version + // doc: https://cache.industry.siemens.com/dl/files/068/109769068/att_1329908/v4/109769068_UsingCertificatesWithTIAPortal_DOC_V2_1_en.pdf + // Certificates in the scope of PG/PC and HMI communication + // Starting with TIA Portal V17, PG / PC and HMI communication is secured with TLS, protecting the data exchanged between + // Field PGs and HMIs with SIMATIC CPUs. + // The CPU families that support Secure PG / HMI communication are: + // • S7 - 1500 controllers as of firmware version V2.9. + // • S7 - 1200 controllers as of firmware version V4.5. + // • Software controllers as of firmware version V21.9. + // • SIMATIC Drive controllers as of firmware version V2.9. + // • PLCSim and PLCSim Advanced Version V4.0. + // HMI components that support Secure PG/ HMI communication, as of image version V17, are: + // • Panels or PCs configured with WinCC Basic, Comfort and Advanced. + // • PCs with WinCC RT Professional. + // • WinCC Unified PCs and Comfort Panels. + // In addition, SINAMICS RT SW, as of version V6.1, and STARTDRIVE, as of version V17, support secure communication string sessionVersionPAOMString = ((ValueWString)serverSession.GetStructElement((uint)Ids.LID_SessionVersionSystemPAOMString)).GetValue(); - Regex reVersions = new Regex("^.*;.*[17]\\s?([52]\\d\\d).+;[VS](\\d\\.\\d)$"); + var reVersions = new Regex( + @"^[^;]*;[^;]*[17]\s?(\d{3}).*;[VS](\d{1,2}\.\d+)$", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + ); Match m = reVersions.Match(sessionVersionPAOMString); if (!m.Success) { Console.WriteLine("S7CommPlusConnection - Legitimate: Could not extract firmware version!"); return S7Consts.errCliFirmwareNotSupported; } - string deviceVersion = m.Groups[1].Value; - string firmwareVersion = m.Groups[2].Value; - int fwVerNo = int.Parse(firmwareVersion.Split('.')[0]) * 100; - fwVerNo += int.Parse(firmwareVersion.Split('.')[1]); + string deviceVersion = m.Groups[1].Value; // e.g., "672" + string firmwareVersion = m.Groups[2].Value; // e.g., "21.9" + + // Compute fwVerNo = major*100 + minor (e.g., "21.9" -> 2109) + int fwVerNo; + { + var parts = firmwareVersion.Split('.'); + if (parts.Length < 2 || !int.TryParse(parts[0], out var major) || !int.TryParse(parts[1], out var minor)) + { + Console.WriteLine($"S7CommPlusConnection - Legitimate: Invalid firmware format: {firmwareVersion}"); + return S7Consts.errCliFirmwareNotSupported; + } + fwVerNo = (major * 100) + minor; + } // Check if we have to use legacy legitimation via the firmware version bool legacyLegitimation = false; - if (deviceVersion.StartsWith("5")) + if (deviceVersion.StartsWith("5")) // S7-1500 (5xx) { if (fwVerNo < 209) { @@ -55,7 +88,7 @@ private int legitimate(ValueStruct serverSession, string password, string userna { legacyLegitimation = false; } - else if (deviceVersion.StartsWith("2")) + else if (deviceVersion.StartsWith("2")) // S7-1200 (2xx) { if (fwVerNo < 403) { @@ -67,6 +100,15 @@ private int legitimate(ValueStruct serverSession, string password, string userna legacyLegitimation = true; } } + else if (deviceVersion.StartsWith("6")) // S7-1507S (6xx) + { + if (fwVerNo < 2109) + { + Console.WriteLine("S7CommPlusConnection - Legitimate: Firmware version is not supported!"); + return S7Consts.errCliFirmwareNotSupported; + } + legacyLegitimation = true; + } else { Console.WriteLine("S7CommPlusConnection - Legitimate: Device version is not supported!"); From cc39500937a6e9439274e4ccc089a32e924a1c95 Mon Sep 17 00:00:00 2001 From: Michal Zukiewicz Date: Thu, 23 Apr 2026 14:50:06 +0200 Subject: [PATCH 12/15] Removed create socket function and some whitespace --- src/S7CommPlusDriver/Core/PValue.cs | 6 +- src/S7CommPlusDriver/Net/MsgSocket.cs | 17 +- src/S7CommPlusDriver/Net/S7Client.cs | 45 +- src/S7CommPlusDriver/S7CommPlusConnection.cs | 14 +- .../S7CommPlusConnectionHighLevel.cs | 700 +++++++++--------- 5 files changed, 393 insertions(+), 389 deletions(-) diff --git a/src/S7CommPlusDriver/Core/PValue.cs b/src/S7CommPlusDriver/Core/PValue.cs index 68a8625..bb61e86 100644 --- a/src/S7CommPlusDriver/Core/PValue.cs +++ b/src/S7CommPlusDriver/Core/PValue.cs @@ -2387,7 +2387,7 @@ public class ValueRID : PValue { UInt32 Value; - public ValueRID(UInt32 rid) : this (rid, 0) + public ValueRID(UInt32 rid) : this(rid, 0) { } @@ -3118,7 +3118,7 @@ public enum PackedStructTransportFlagBits Count2Present = 1 << 10 // If this bit is set, then there's a 2nd counter present. Which if for a rare case you can read an array of struct, if the complete size, the 1st for one element. } - public ValueStruct(UInt32 value) : this (value, 0) + public ValueStruct(UInt32 value) : this(value, 0) { } @@ -3134,7 +3134,7 @@ public UInt32 GetValue() return Value; } - public void AddStructElement(uint id, PValue elem) + public void AddStructElement(uint id, PValue elem) { Elements.Add(id, elem); } diff --git a/src/S7CommPlusDriver/Net/MsgSocket.cs b/src/S7CommPlusDriver/Net/MsgSocket.cs index 297bbde..fa081f1 100644 --- a/src/S7CommPlusDriver/Net/MsgSocket.cs +++ b/src/S7CommPlusDriver/Net/MsgSocket.cs @@ -13,8 +13,8 @@ namespace S7CommPlusDriver { - // - class MsgSocket + + class MsgSocket { private Socket TCPSocket; private int _ReadTimeout = 2000; @@ -40,11 +40,6 @@ public void Close() } } - private void CreateSocket() - { - TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - TCPSocket.NoDelay = true; - } private void TCPPing(string Host, int Port) { @@ -65,7 +60,8 @@ private void TCPPing(string Host, int Port) catch { LastError = S7Consts.errTCPConnectionFailed; - }; + } + PingSocket.Close(); } @@ -79,7 +75,10 @@ public int Connect(string Host, int Port) if (LastError == 0) try { - CreateSocket(); + TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) + { + NoDelay = true + }; TCPSocket.Connect(Host, Port); } catch diff --git a/src/S7CommPlusDriver/Net/S7Client.cs b/src/S7CommPlusDriver/Net/S7Client.cs index 28fa468..f429c24 100644 --- a/src/S7CommPlusDriver/Net/S7Client.cs +++ b/src/S7CommPlusDriver/Net/S7Client.cs @@ -22,12 +22,12 @@ namespace S7CommPlusDriver public class S7Client : OpenSSLConnector.IConnectorCallback { //TODO: better API, maybe a Callback - public static bool WriteSslKeyToFile; - public static string WriteSslKeyPath; - - #region [Constants and TypeDefs] - - public int _LastError = 0; + public static bool WriteSslKeyToFile; + public static string WriteSslKeyPath; + + #region [Constants and TypeDefs] + + public int _LastError = 0; #endregion @@ -111,7 +111,7 @@ public void OnDataAvailable() public void SSL_CTX_keylog_cb(IntPtr ssl, string line) { string filename = "key_" + m_DateTimeStarted.ToString("yyyyMMdd_HHmmss") + ".log"; - if (WriteSslKeyPath != null) + if (WriteSslKeyPath != null) filename = Path.Combine(WriteSslKeyPath, filename); StreamWriter file = new StreamWriter(filename, append: true); file.WriteLine(line); @@ -125,7 +125,7 @@ public int SslActivate() try { ret = Native.OPENSSL_init_ssl(0, IntPtr.Zero); // returns 1 on success or 0 on error - if (ret != 1) + if (ret != 1) { return S7Consts.errOpenSSL; } @@ -142,17 +142,17 @@ public int SslActivate() return S7Consts.errOpenSSL; } m_sslconn = new OpenSSLConnector(m_ptr_ctx, this); - m_sslconn.ExpectConnect(); - - // Keylog callback setzen - if (WriteSslKeyToFile) - { - m_keylog_cb = new Native.SSL_CTX_keylog_cb_func(SSL_CTX_keylog_cb); - Native.SSL_CTX_set_keylog_callback(m_ptr_ctx, m_keylog_cb); + m_sslconn.ExpectConnect(); + + // Keylog callback setzen + if (WriteSslKeyToFile) + { + m_keylog_cb = new Native.SSL_CTX_keylog_cb_func(SSL_CTX_keylog_cb); + Native.SSL_CTX_set_keylog_callback(m_ptr_ctx, m_keylog_cb); } m_SslActive = true; - } + } catch { return S7Consts.errOpenSSL; @@ -185,7 +185,8 @@ private void RunThread() _LastError = 0; Length = RecvIsoPacket(); // TODO: Hier nur den Payload zurückgeben - if (Length > 0) { + if (Length > 0) + { byte[] Buffer = new byte[Length - TPKT_ISO.Length]; Array.Copy(PDU, TPKT_ISO.Length, Buffer, 0, Length - TPKT_ISO.Length); int Size = Length - TPKT_ISO.Length; @@ -193,7 +194,9 @@ private void RunThread() { // Durch SSL eingelesene Daten an SSL weiterleiten m_sslconn.ReadCompleted(Buffer, Size); - } else { + } + else + { // Wenn etwas gelesen werden konnte, Client benachrichtigen OnDataReceived?.Invoke(Buffer, Size); } @@ -344,7 +347,7 @@ private int RecvIsoPacket() //if ((Size > _PduSizeRequested + IsoHSize) || (Size < MinPduSize)) // _LastError = S7Consts.errIsoInvalidPDU; //else - Done = true; // a valid Length !=7 && >16 && <247 + Done = true; // a valid Length !=7 && >16 && <247 } } } @@ -461,7 +464,7 @@ public int Disconnect() m_runThread?.Join(); Socket.Close(); - + return 0; } @@ -606,7 +609,7 @@ public static string ErrorText(int Error) case S7Consts.errCliFirmwareNotSupported: return "CLI : Firmware not supported"; case S7Consts.errCliDeviceNotSupported: return "CLI : Device type not supported"; default: return "CLI : Unknown error (0x" + Convert.ToString(Error, 16) + ")"; - }; + } } public int LastError() diff --git a/src/S7CommPlusDriver/S7CommPlusConnection.cs b/src/S7CommPlusDriver/S7CommPlusConnection.cs index 853bfc8..5614fed 100644 --- a/src/S7CommPlusDriver/S7CommPlusConnection.cs +++ b/src/S7CommPlusDriver/S7CommPlusConnection.cs @@ -392,7 +392,8 @@ private int checkResponseWithIntegrity(IS7pRequest request, IS7pResponse respons /// public int Connect(string address, string password = "", string username = "", int timeoutMs = 5000) { - if (timeoutMs > 0) { + if (timeoutMs > 0) + { m_ReadTimeout = timeoutMs; } @@ -409,7 +410,7 @@ public int Connect(string address, string password = "", string username = "", i #region Step 1: Unencrypted InitSSL Request / Response - InitSslRequest sslReq = new InitSslRequest(ProtocolVersion.V1, 0 , 0); + InitSslRequest sslReq = new InitSslRequest(ProtocolVersion.V1, 0, 0); res = SendS7plusFunctionObject(sslReq); if (res != 0) { @@ -520,7 +521,8 @@ public int Connect(string address, string password = "", string username = "", i #region Step 6: Password res = legitimate(serverSession, password, username); - if (res != 0) { + if (res != 0) + { m_client.Disconnect(); return res; } @@ -605,7 +607,7 @@ public int ReadValues(List addresslist, out List values, ou getMultiVarReq.AddressList.Clear(); count_perChunk = 0; - while (count_perChunk < m_CommRessources.TagsPerReadRequestMax && (chunk_startIndex + count_perChunk) < addresslist.Count) + while (count_perChunk < m_CommRessources.TagsPerReadRequestMax && (chunk_startIndex + count_perChunk) < addresslist.Count) { getMultiVarReq.AddressList.Add(addresslist[chunk_startIndex + count_perChunk]); count_perChunk++; @@ -1402,9 +1404,9 @@ public int GetCommentsXml(uint relid, out string xml_linecomment, out string xml return res; } - foreach(var obj in exploreRes.Objects) + foreach (var obj in exploreRes.Objects) { - foreach(var att in obj.Attributes) + foreach (var att in obj.Attributes) { switch (att.Key) { diff --git a/src/S7CommPlusDriver/S7CommPlusConnectionHighLevel.cs b/src/S7CommPlusDriver/S7CommPlusConnectionHighLevel.cs index 3cf52c8..6a1bb04 100644 --- a/src/S7CommPlusDriver/S7CommPlusConnectionHighLevel.cs +++ b/src/S7CommPlusDriver/S7CommPlusConnectionHighLevel.cs @@ -1,37 +1,37 @@ -#region License -/****************************************************************************** - * S7CommPlusDriver - * - * Copyright (C) 2023 Thomas Wiens, th.wiens@gmx.de - * - * This file is part of S7CommPlusDriver. - * - * S7CommPlusDriver is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - /****************************************************************************/ -#endregion - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace S7CommPlusDriver -{ - public partial class S7CommPlusConnection - { - public enum BlockType - { - unkown, - DB, - FB, - FC, - OB, - UDT, - } - +#region License +/****************************************************************************** + * S7CommPlusDriver + * + * Copyright (C) 2023 Thomas Wiens, th.wiens@gmx.de + * + * This file is part of S7CommPlusDriver. + * + * S7CommPlusDriver is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + /****************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace S7CommPlusDriver +{ + public partial class S7CommPlusConnection + { + public enum BlockType + { + unkown, + DB, + FB, + FC, + OB, + UDT, + } + public enum ProgrammingLanguage : int { Undef = 0, @@ -88,8 +88,8 @@ public enum ProgrammingLanguage : int ProDiag = 500, ProDiag_OB = 501, CEM = 600, - } - + } + public enum BinaryArtifacts : uint { Undefined = 0u, @@ -105,141 +105,141 @@ public enum BinaryArtifacts : uint VirtualPlcMc7plusDataKey = 2147483682u, VirtualPlcOptimizationInfoDataKey = 2147483683u, VirtualPlcClosedImmediateDataKey = 2147483684u - } - - public class BlockInfo - { - public string name; // Name of the datablock - public UInt32 number; // Number of the datablock - public BlockType type; + } + + public class BlockInfo + { + public string name; // Name of the datablock + public UInt32 number; // Number of the datablock + public BlockType type; public ProgrammingLanguage lang; - public UInt32 db_block_relid; // RID of the datablock - public UInt32 db_block_ti_relid; // Type-Info RID of the datablock - }; - - public int BrowseAllBlocks(out List exploreData) - { - int res; - Browser vars = new Browser(); - ExploreRequest exploreReq; - ExploreResponse exploreRes; - - #region Read all objects - - exploreData = new List(); - - exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = Ids.NativeObjects_thePLCProgram_Rid; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; - exploreReq.ExploreParents = 0; - - // We want to know the following attributes - exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); - exploreReq.AddressList.Add(Ids.Block_BlockNumber); - exploreReq.AddressList.Add(Ids.Block_BlockLanguage); - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - - #endregion - - #region Evaluate all data blocks that then need to be browsed - - var obj = exploreRes.Objects.First(o => o.ClassId == Ids.PLCProgram_Class_Rid); - foreach (var ob in obj.GetObjects()) - { - switch (ob.ClassId) - { - case Ids.DB_Class_Rid: - case Ids.FB_Class_Rid: + public UInt32 db_block_relid; // RID of the datablock + public UInt32 db_block_ti_relid; // Type-Info RID of the datablock + }; + + public int BrowseAllBlocks(out List exploreData) + { + int res; + Browser vars = new Browser(); + ExploreRequest exploreReq; + ExploreResponse exploreRes; + + #region Read all objects + + exploreData = new List(); + + exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = Ids.NativeObjects_thePLCProgram_Rid; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 0; + + // We want to know the following attributes + exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); + exploreReq.AddressList.Add(Ids.Block_BlockNumber); + exploreReq.AddressList.Add(Ids.Block_BlockLanguage); + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + + #endregion + + #region Evaluate all data blocks that then need to be browsed + + var obj = exploreRes.Objects.First(o => o.ClassId == Ids.PLCProgram_Class_Rid); + foreach (var ob in obj.GetObjects()) + { + switch (ob.ClassId) + { + case Ids.DB_Class_Rid: + case Ids.FB_Class_Rid: case Ids.FC_Class_Rid: - case Ids.OB_Class_Rid: - UInt32 relid = ob.RelationId; - UInt32 area = (relid >> 16); - UInt32 num = relid & 0xffff; - - var name = (ValueWString)(ob.GetAttribute(Ids.ObjectVariableTypeName)); - var data = new BlockInfo(); - data.db_block_relid = relid; - data.name = name.GetValue(); - data.number = num; - data.type = ob.ClassId switch - { - Ids.DB_Class_Rid => BlockType.DB, - Ids.FB_Class_Rid => BlockType.FB, - Ids.FC_Class_Rid => BlockType.FC, - Ids.OB_Class_Rid => BlockType.OB, - }; - - var lang = ((ValueUInt)ob.Attributes[Ids.Block_BlockLanguage]).GetValue(); - data.lang = (ProgrammingLanguage)lang; - exploreData.Add(data); - break; - } - } - - #endregion - - return 0; + case Ids.OB_Class_Rid: + UInt32 relid = ob.RelationId; + UInt32 area = (relid >> 16); + UInt32 num = relid & 0xffff; + + var name = (ValueWString)(ob.GetAttribute(Ids.ObjectVariableTypeName)); + var data = new BlockInfo(); + data.db_block_relid = relid; + data.name = name.GetValue(); + data.number = num; + data.type = ob.ClassId switch + { + Ids.DB_Class_Rid => BlockType.DB, + Ids.FB_Class_Rid => BlockType.FB, + Ids.FC_Class_Rid => BlockType.FC, + Ids.OB_Class_Rid => BlockType.OB, + }; + + var lang = ((ValueUInt)ob.Attributes[Ids.Block_BlockLanguage]).GetValue(); + data.lang = (ProgrammingLanguage)lang; + exploreData.Add(data); + break; + } + } + + #endregion + + return 0; } - public int GetPlcStructureXML(out string plcStrutureXml) - { - int res; - Browser vars = new Browser(); - ExploreRequest exploreReq; - ExploreResponse exploreRes; - plcStrutureXml = null; - - #region Read all objects - - exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = Ids.Constants | 0x0000ffff; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; - exploreReq.ExploreParents = 1; - - exploreReq.AddressList.Add(Ids.ConstantsGlobal_Symbolics); - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - - #endregion - - var attr = exploreRes?.Objects?[0]?.Objects?.First().Value?.Objects?.First().Value?.Attributes?[Ids.ConstantsGlobal_Symbolics] as ValueBlob; + public int GetPlcStructureXML(out string plcStrutureXml) + { + int res; + Browser vars = new Browser(); + ExploreRequest exploreReq; + ExploreResponse exploreRes; + plcStrutureXml = null; + + #region Read all objects + + exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = Ids.Constants | 0x0000ffff; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 1; + + exploreReq.AddressList.Add(Ids.ConstantsGlobal_Symbolics); + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + + #endregion + + var attr = exploreRes?.Objects?[0]?.Objects?.First().Value?.Objects?.First().Value?.Attributes?[Ids.ConstantsGlobal_Symbolics] as ValueBlob; if (attr != null) { BlobDecompressor bd3 = new BlobDecompressor(); @@ -247,66 +247,66 @@ public int GetPlcStructureXML(out string plcStrutureXml) var xml = bd3.decompress(v, 0); plcStrutureXml = xml; } - - return 0; - } - - public int GetBlockXml(uint relid, out string blockName, out ProgrammingLanguage lang, out uint blockNumber, out BlockType blockType, out string xml_linecomment, out Dictionary xml_comment, out string interfaceDescription, out string[] blockBody, out string fuctionalObjectCode, out string[] intRef, out string[] extRef) - { - int res; - // With requesting DataInterface_InterfaceDescription, whe would be able to get all informations like the access ids and - // datatype informations, that we get from the other browsing method. Needs to be tested which one is more efficient on network traffic or plc load. - // If we keep use browsing for the comments, at least we would be able to read all information in one request. - xml_linecomment = String.Empty; - xml_comment = new(); - interfaceDescription = String.Empty; - blockBody = new string[0]; - fuctionalObjectCode = String.Empty; - intRef = new string[0]; - extRef = new string[0]; - blockName = null; - lang = ProgrammingLanguage.Undef; - blockNumber = relid & 0xffff; - blockType = BlockType.unkown; - - var exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = relid; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; + + return 0; + } + + public int GetBlockXml(uint relid, out string blockName, out ProgrammingLanguage lang, out uint blockNumber, out BlockType blockType, out string xml_linecomment, out Dictionary xml_comment, out string interfaceDescription, out string[] blockBody, out string fuctionalObjectCode, out string[] intRef, out string[] extRef) + { + int res; + // With requesting DataInterface_InterfaceDescription, whe would be able to get all informations like the access ids and + // datatype informations, that we get from the other browsing method. Needs to be tested which one is more efficient on network traffic or plc load. + // If we keep use browsing for the comments, at least we would be able to read all information in one request. + xml_linecomment = String.Empty; + xml_comment = new(); + interfaceDescription = String.Empty; + blockBody = new string[0]; + fuctionalObjectCode = String.Empty; + intRef = new string[0]; + extRef = new string[0]; + blockName = null; + lang = ProgrammingLanguage.Undef; + blockNumber = relid & 0xffff; + blockType = BlockType.unkown; + + var exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = relid; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; exploreReq.ExploreParents = 0; // We want to know the following attributes - exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); - //exploreReq.AddressList.Add(Ids.Block_BlockNumber); - exploreReq.AddressList.Add(Ids.Block_BlockLanguage); - - exploreReq.AddressList.Add(Ids.ASObjectES_Comment); - exploreReq.AddressList.Add(Ids.DataInterface_LineComments); - exploreReq.AddressList.Add(Ids.DataInterface_InterfaceDescription); - exploreReq.AddressList.Add(Ids.Block_BodyDescription); + exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); + //exploreReq.AddressList.Add(Ids.Block_BlockNumber); + exploreReq.AddressList.Add(Ids.Block_BlockLanguage); + + exploreReq.AddressList.Add(Ids.ASObjectES_Comment); + exploreReq.AddressList.Add(Ids.DataInterface_LineComments); + exploreReq.AddressList.Add(Ids.DataInterface_InterfaceDescription); + exploreReq.AddressList.Add(Ids.Block_BodyDescription); exploreReq.AddressList.Add(Ids.FunctionalObject_extRefData); exploreReq.AddressList.Add(Ids.FunctionalObject_intRefData); - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - - foreach (var obj in exploreRes.Objects) + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + + foreach (var obj in exploreRes.Objects) { blockType = obj.ClassId switch { @@ -337,151 +337,151 @@ public int GetBlockXml(uint relid, out string blockName, out ProgrammingLanguage 2657 => BlockType.OB, // TimeOfDayOB 2658 => BlockType.OB, // UpdateEventOB 8440 => BlockType.OB, // LookAheadOB - }; - - - foreach (var att in obj.Attributes) - { - switch (att.Key) + }; + + + foreach (var att in obj.Attributes) + { + switch (att.Key) { - case Ids.ObjectVariableTypeName: + case Ids.ObjectVariableTypeName: { blockName = ((ValueWString)att.Value).GetValue(); break; } - case Ids.Block_BlockNumber: + case Ids.Block_BlockNumber: { break; } - case Ids.Block_BlockLanguage: + case Ids.Block_BlockLanguage: { var l = ((ValueUInt)att.Value).GetValue(); lang = (ProgrammingLanguage)l; break; - } - case Ids.FunctionalObject_extRefData: - { - var xx = (ValueBlobSparseArray)att.Value; - BlobDecompressor bd3 = new BlobDecompressor(); - var blob_sp3 = xx.GetValue(); - extRef = new string[blob_sp3.Count]; - var i = 0; - foreach (var key in blob_sp3.Keys) - { - if (blob_sp3[key].value != null) - extRef[i++] = bd3.decompress(blob_sp3[key].value, 4); - } - break; } - case Ids.FunctionalObject_intRefData: - { - var xx = (ValueBlobSparseArray)att.Value; - BlobDecompressor bd3 = new BlobDecompressor(); - var blob_sp3 = xx.GetValue(); - intRef = new string[blob_sp3.Count]; - var i = 0; - foreach (var key in blob_sp3.Keys) - { - if (blob_sp3[key].value != null) - intRef[i++] = bd3.decompress(blob_sp3[key].value, 4); - } - break; - } - case Ids.ASObjectES_Comment: - { - var att_comment = (ValueWStringSparseArray)att.Value; - xml_comment = att_comment.GetValue(); - break; - } - case Ids.DataInterface_LineComments: - { - var att_linecomment = (ValueBlobSparseArray)att.Value; - BlobDecompressor bd = new BlobDecompressor(); - var blob_sp = att_linecomment.GetValue(); - // In DBs we get the data with Sparsearray key = 1, in M-Area with key = 2. - // For now, just take the first, don't know where the key ids are for. - foreach (var key in blob_sp.Keys) - { - xml_linecomment = bd.decompress(blob_sp[key].value, 4); // Offset of 4, as we have a header for the zlib dictionary version - break; - } - break; - } - case Ids.DataInterface_InterfaceDescription: - { - var att_ifsescr = (ValueBlob)att.Value; - BlobDecompressor bd2 = new BlobDecompressor(); - var blob_sp2 = att_ifsescr.GetValue(); - interfaceDescription = bd2.decompress(blob_sp2, 4); // Offset of 4, as we have a header for the zlib dictionary version - break; - } - case Ids.Block_BodyDescription: - { - var xx = (ValueBlobSparseArray)att.Value; - BlobDecompressor bd3 = new BlobDecompressor(); - var blob_sp3 = xx.GetValue(); - blockBody = new string[blob_sp3.Where(x => x.Key < (uint)BinaryArtifacts.PlcFamily).Count()]; - var i = 0; - foreach (var key in blob_sp3.Keys.OrderBy(x => x)) - { + case Ids.FunctionalObject_extRefData: + { + var xx = (ValueBlobSparseArray)att.Value; + BlobDecompressor bd3 = new BlobDecompressor(); + var blob_sp3 = xx.GetValue(); + extRef = new string[blob_sp3.Count]; + var i = 0; + foreach (var key in blob_sp3.Keys) + { + if (blob_sp3[key].value != null) + extRef[i++] = bd3.decompress(blob_sp3[key].value, 4); + } + break; + } + case Ids.FunctionalObject_intRefData: + { + var xx = (ValueBlobSparseArray)att.Value; + BlobDecompressor bd3 = new BlobDecompressor(); + var blob_sp3 = xx.GetValue(); + intRef = new string[blob_sp3.Count]; + var i = 0; + foreach (var key in blob_sp3.Keys) + { + if (blob_sp3[key].value != null) + intRef[i++] = bd3.decompress(blob_sp3[key].value, 4); + } + break; + } + case Ids.ASObjectES_Comment: + { + var att_comment = (ValueWStringSparseArray)att.Value; + xml_comment = att_comment.GetValue(); + break; + } + case Ids.DataInterface_LineComments: + { + var att_linecomment = (ValueBlobSparseArray)att.Value; + BlobDecompressor bd = new BlobDecompressor(); + var blob_sp = att_linecomment.GetValue(); + // In DBs we get the data with Sparsearray key = 1, in M-Area with key = 2. + // For now, just take the first, don't know where the key ids are for. + foreach (var key in blob_sp.Keys) + { + xml_linecomment = bd.decompress(blob_sp[key].value, 4); // Offset of 4, as we have a header for the zlib dictionary version + break; + } + break; + } + case Ids.DataInterface_InterfaceDescription: + { + var att_ifsescr = (ValueBlob)att.Value; + BlobDecompressor bd2 = new BlobDecompressor(); + var blob_sp2 = att_ifsescr.GetValue(); + interfaceDescription = bd2.decompress(blob_sp2, 4); // Offset of 4, as we have a header for the zlib dictionary version + break; + } + case Ids.Block_BodyDescription: + { + var xx = (ValueBlobSparseArray)att.Value; + BlobDecompressor bd3 = new BlobDecompressor(); + var blob_sp3 = xx.GetValue(); + blockBody = new string[blob_sp3.Where(x => x.Key < (uint)BinaryArtifacts.PlcFamily).Count()]; + var i = 0; + foreach (var key in blob_sp3.Keys.OrderBy(x => x)) + { if (!(key < (uint)BinaryArtifacts.PlcFamily)) { //TODO: what to do with binary artifacts? var binaryArtifactType = (BinaryArtifacts)key; continue; - } - + } + if (blob_sp3[key].value != null) { var code = bd3.decompress(blob_sp3[key].value, 4); blockBody[i++] = code; - } - } - break; - } - } - } - } - return 0; + } + } + break; + } + } + } + } + return 0; } - public int RunExploreRequest(uint relid, uint[] attributes, out List objects, byte exploreChildsRecursive = 1, byte exploreParents = 0) + public int RunExploreRequest(uint relid, uint[] attributes, out List objects, byte exploreChildsRecursive = 1, byte exploreParents = 0) { int res; - objects = null; - - var exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = relid; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = exploreChildsRecursive; - exploreReq.ExploreParents = exploreParents; - + objects = null; + + var exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = relid; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = exploreChildsRecursive; + exploreReq.ExploreParents = exploreParents; + if (attributes != null && attributes.Length > 0) { // We want to know the following attributes exploreReq.AddressList.AddRange(attributes); - } - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - + } + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + objects = exploreRes.Objects; return 0; @@ -491,17 +491,17 @@ public int RunGetVarSubstreamedRequest(uint objectId, ushort address, out PValue { int res; value = null; - + var getVarSubstreamedReq = new GetVarSubstreamedRequest(ProtocolVersion.V2); - getVarSubstreamedReq.InObjectId = objectId; - getVarSubstreamedReq.SessionId = m_SessionId; - getVarSubstreamedReq.Address = address; - res = SendS7plusFunctionObject(getVarSubstreamedReq); - if (res != 0) - { - return res; + getVarSubstreamedReq.InObjectId = objectId; + getVarSubstreamedReq.SessionId = m_SessionId; + getVarSubstreamedReq.Address = address; + res = SendS7plusFunctionObject(getVarSubstreamedReq); + if (res != 0) + { + return res; } - m_LastError = 0; + m_LastError = 0; WaitForNewS7plusReceived(m_ReadTimeout); if (m_LastError != 0) { @@ -509,10 +509,10 @@ public int RunGetVarSubstreamedRequest(uint objectId, ushort address, out PValue } - var getVarSubstreamedRes = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); - if (getVarSubstreamedRes == null) - { - return S7Consts.errIsoInvalidPDU8; + var getVarSubstreamedRes = GetVarSubstreamedResponse.DeserializeFromPdu(m_ReceivedPDU); + if (getVarSubstreamedRes == null) + { + return S7Consts.errIsoInvalidPDU8; } value = getVarSubstreamedRes.Value; @@ -579,5 +579,5 @@ public int GetCpuInfos(out CpuInfo cpuInfo) return 0; } - } -} + } +} From 09f6996804bf8ba20c2ea0e1a0b2a92b174d0ee8 Mon Sep 17 00:00:00 2001 From: Michal Zukiewicz Date: Wed, 6 May 2026 12:35:04 +0200 Subject: [PATCH 13/15] Added proper exceptions to MsgSocket --- src/S7CommPlusDriver/Exceptions.cs | 46 ++++++++++++ src/S7CommPlusDriver/Net/MsgSocket.cs | 104 +++++++++++++------------- src/S7CommPlusDriver/Net/S7Client.cs | 45 +++++++++-- 3 files changed, 135 insertions(+), 60 deletions(-) create mode 100644 src/S7CommPlusDriver/Exceptions.cs diff --git a/src/S7CommPlusDriver/Exceptions.cs b/src/S7CommPlusDriver/Exceptions.cs new file mode 100644 index 0000000..3e2e05b --- /dev/null +++ b/src/S7CommPlusDriver/Exceptions.cs @@ -0,0 +1,46 @@ +using System; + +namespace S7CommPlusDriver +{ + public class ErrTCPConnectionFailed : Exception + { + public ErrTCPConnectionFailed(string location, string message) : base("TCP: Connection Error - " + message + " - " + location) + { + } + } + + public class ErrTCPDataSend : Exception + { + public ErrTCPDataSend(string location, string message) : base("TCP: Error sending data - " + message + " - " + location) + { + } + } + + public class ErrTCPNotConnected : Exception + { + public ErrTCPNotConnected(string location, string message) : base("CLI: Client not connected - " + message + " - " + location) + { + } + } + + public class ErrTCPDataReceive : Exception + { + public ErrTCPDataReceive(string location, string message) : base("TCP: Error receiving data - " + message + " - " + location) + { + } + } + + public class ErrIsoConnect : Exception + { + public ErrIsoConnect(string location, string message) : base("ISO: Connection Error - " + message + " - " + location) + { + } + } + + public class ErrIsoInvalidPDU : Exception + { + public ErrIsoInvalidPDU(string location, string message) : base("ISO: Invalid PDU received - " + message + " - " + location) + { + } + } +} \ No newline at end of file diff --git a/src/S7CommPlusDriver/Net/MsgSocket.cs b/src/S7CommPlusDriver/Net/MsgSocket.cs index fa081f1..d5bed97 100644 --- a/src/S7CommPlusDriver/Net/MsgSocket.cs +++ b/src/S7CommPlusDriver/Net/MsgSocket.cs @@ -8,6 +8,7 @@ #endregion using System; +using System.Diagnostics; using System.Net.Sockets; using System.Threading; @@ -20,7 +21,6 @@ class MsgSocket private int _ReadTimeout = 2000; private int _WriteTimeout = 2000; private int _ConnectTimeout = 1000; - public int LastError = 0; public MsgSocket() { @@ -45,8 +45,7 @@ private void TCPPing(string Host, int Port) { // To Ping the PLC an Asynchronous socket is used rather then an ICMP packet. // This allows the use also across Internet and Firewalls (obviously the port must be opened) - LastError = 0; - Socket PingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + Socket PingSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { IAsyncResult result = PingSocket.BeginConnect(Host, Port, null, null); @@ -54,42 +53,48 @@ private void TCPPing(string Host, int Port) if (!success) { - LastError = S7Consts.errTCPConnectionFailed; + throw new ErrTCPConnectionFailed("MsgSocket->TCPPing", "Connection to ping socket failed"); } } - catch + catch (Exception e) when (e is not ErrTCPConnectionFailed) { - LastError = S7Consts.errTCPConnectionFailed; + PingSocket.Close(); + throw new ErrTCPConnectionFailed("MsgSocket->TCPPing", "Connection to ping socket failed: " + e.Message); } PingSocket.Close(); } - public int Connect(string Host, int Port) + public void Connect(string Host, int Port) { - LastError = 0; if (!Connected) { // TWI: TCPPing rausgenommen, stört bei Wireshark Analyse - //TCPPing(Host, Port); - if (LastError == 0) - try - { - TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) - { - NoDelay = true - }; - TCPSocket.Connect(Host, Port); - } - catch + // TCPPing(Host, Port); + try + { + TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { - LastError = S7Consts.errTCPConnectionFailed; - } + NoDelay = true + }; + } + catch (Exception e) + { + throw new ErrTCPConnectionFailed("MsgSocket->Connect", "Error creating socket: " + e.Message); + } + + try + { + TCPSocket.Connect(Host, Port); + } + catch (Exception e) + { + throw new ErrTCPConnectionFailed("MsgSocket->Connect", "Error connecting to socket: " + e.Message); + } } - return LastError; } - private int WaitForData(int Size, int Timeout) + private void WaitForData(int Size, int Timeout) { bool Expired = false; int SizeAvail; @@ -113,61 +118,54 @@ private int WaitForData(int Size, int Timeout) catch { } } } - catch + catch (Exception e) { - LastError = S7Consts.errTCPDataReceive; + throw new ErrTCPDataReceive("MsgSocket->WaitForData", "Problem while waiting for data: " + e.Message); } if (Expired) { - LastError = S7Consts.errTCPDataReceive; + throw new ErrTCPDataReceive("MsgSocket->WaitForData", "Data expired."); } - return LastError; } - public int Receive(byte[] Buffer, int Start, int Size) + public void Receive(byte[] Buffer, int Start, int Size) { - - int BytesRead = 0; - LastError = WaitForData(Size, _ReadTimeout); - if (LastError == 0) + int BytesRead; + WaitForData(Size, _ReadTimeout); + try { - try - { - BytesRead = TCPSocket.Receive(Buffer, Start, Size, SocketFlags.None); - } - catch - { - LastError = S7Consts.errTCPDataReceive; - } - if (BytesRead == 0) // Connection Reset by the peer - { - LastError = S7Consts.errTCPDataReceive; - Close(); - } + BytesRead = TCPSocket.Receive(Buffer, Start, Size, SocketFlags.None); + } + catch (Exception e) + { + throw new ErrTCPDataReceive("MsgSocket->Receive", "Problem receiving data: " + e.Message); + } + if (BytesRead == 0) // Connection Reset by the peer + { + Close(); + throw new ErrTCPDataReceive("MsgSocket->Receive", "Connection reset by the peer"); } - return LastError; } - public int Send(byte[] Buffer, int Size) + public void Send(byte[] Buffer, int Size) { - LastError = 0; try { - int BytesSent = TCPSocket.Send(Buffer, Size, SocketFlags.None); + // int BytesSent = + TCPSocket.Send(Buffer, Size, SocketFlags.None); } - catch + catch (Exception e) { - LastError = S7Consts.errTCPDataSend; Close(); + throw new ErrTCPDataSend("MsgSocket->Send", "Problem sending data: " + e.Message); } - return LastError; } public bool Connected { get { - return (TCPSocket != null) && (TCPSocket.Connected); + return (TCPSocket != null) && TCPSocket.Connected; } } diff --git a/src/S7CommPlusDriver/Net/S7Client.cs b/src/S7CommPlusDriver/Net/S7Client.cs index f429c24..b62e723 100644 --- a/src/S7CommPlusDriver/Net/S7Client.cs +++ b/src/S7CommPlusDriver/Net/S7Client.cs @@ -183,7 +183,14 @@ private void RunThread() { // Versuchen zu lesen _LastError = 0; - Length = RecvIsoPacket(); + try + { + Length = RecvIsoPacket(); + } + catch + { + Length = 0; + } // TODO: Hier nur den Payload zurückgeben if (Length > 0) { @@ -253,7 +260,7 @@ private int TCPConnect() if (_LastError == 0) try { - _LastError = Socket.Connect(IPAddress, _PLCPort); + Socket.Connect(IPAddress, _PLCPort); } catch { @@ -265,14 +272,30 @@ private int TCPConnect() private void RecvPacket(byte[] Buffer, int Start, int Size) { if (Connected) - _LastError = Socket.Receive(Buffer, Start, Size); + { + try + { + Socket.Receive(Buffer, Start, Size); + } + catch + { + _LastError = S7Consts.errTCPDataReceive; + } + } else _LastError = S7Consts.errTCPNotConnected; } private void SendPacket(byte[] Buffer, int Len) { - _LastError = Socket.Send(Buffer, Len); + try + { + Socket.Send(Buffer, Len); + } + catch + { + _LastError = S7Consts.errTCPDataSend; + } } private void SendPacket(byte[] Buffer) @@ -311,7 +334,15 @@ private int SendIsoPacket(byte[] Buffer) { return S7Consts.errIsoInvalidPDU; } - SendPacket(PDU, TPKT_ISO.Length + Size); + + try + { + SendPacket(PDU, TPKT_ISO.Length + Size); + } + catch (ErrTCPDataSend) + { + _LastError = S7Consts.errTCPDataSend; + } return _LastError; } @@ -329,7 +360,7 @@ private void SetWordAt(byte[] Buffer, int Pos, UInt16 Value) private int RecvIsoPacket() { - Boolean Done = false; + bool Done = false; int Size = 0; while ((_LastError == 0) && !Done) { @@ -716,7 +747,7 @@ public bool Connected { get { - return (Socket != null) && (Socket.Connected); + return (Socket != null) && Socket.Connected; } } #endregion From 2a9dbec37ccb60c581a6ac12bf04ed4dcf4d4325 Mon Sep 17 00:00:00 2001 From: Michal Zukiewicz Date: Wed, 6 May 2026 12:37:49 +0200 Subject: [PATCH 14/15] Replaced TickCount with Stopwatch in MsgSocket to avoid overflow --- src/S7CommPlusDriver/Net/MsgSocket.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/S7CommPlusDriver/Net/MsgSocket.cs b/src/S7CommPlusDriver/Net/MsgSocket.cs index d5bed97..bab09dc 100644 --- a/src/S7CommPlusDriver/Net/MsgSocket.cs +++ b/src/S7CommPlusDriver/Net/MsgSocket.cs @@ -98,8 +98,7 @@ private void WaitForData(int Size, int Timeout) { bool Expired = false; int SizeAvail; - int Elapsed = Environment.TickCount; - LastError = 0; + Stopwatch Elapsed = Stopwatch.StartNew(); try { SizeAvail = TCPSocket.Available; @@ -107,7 +106,7 @@ private void WaitForData(int Size, int Timeout) { Thread.Sleep(2); SizeAvail = TCPSocket.Available; - Expired = Environment.TickCount - Elapsed > Timeout; + Expired = Elapsed.ElapsedMilliseconds > Timeout; // If timeout we clean the buffer if (Expired && (SizeAvail > 0)) try From 422b5711ffc328dad19593cb0447f76d56b81417 Mon Sep 17 00:00:00 2001 From: Michal Zukiewicz Date: Fri, 8 May 2026 16:14:10 +0200 Subject: [PATCH 15/15] Replaced Environment.TickCount with Stopwatch Added exceptions to s7client --- src/DriverTest/Program.cs | 14 +- src/S7CommPlusDriver/Net/S7Client.cs | 186 +- src/S7CommPlusDriver/Net/S7Consts.cs | 56 +- .../OpenSSL/OpenSSLConnector.cs | 2 +- src/S7CommPlusDriver/S7CommPlusConnection.cs | 2877 +++++++++-------- 5 files changed, 1548 insertions(+), 1587 deletions(-) diff --git a/src/DriverTest/Program.cs b/src/DriverTest/Program.cs index aea7ba1..1cfe01a 100644 --- a/src/DriverTest/Program.cs +++ b/src/DriverTest/Program.cs @@ -38,9 +38,9 @@ static void Main(string[] args) Console.WriteLine("Main - Versuche Verbindungsaufbau zu: " + HostIp); S7CommPlusConnection conn = new S7CommPlusConnection(); - res = conn.Connect(HostIp, Password, Username); - if (res == 0) + try { + conn.Connect(HostIp, Password, Username); Console.WriteLine("Main - Connect fertig"); #region Variablenhaushalt browsen @@ -95,7 +95,7 @@ static void Main(string[] args) List values = new List(); List errors = new List(); - + // Fehlerhafte Variable setzen //readlist[2].LID[0] = 123; res = conn.ReadValues(readlist, out values, out errors); @@ -112,7 +112,7 @@ static void Main(string[] args) string formatstring = "{0,-80}{1,-30}{2,-20}{3,-20}"; Console.WriteLine(String.Format(formatstring, "SYMBOLIC-NAME", "ACCESS-SEQUENCE", "TYP", "VALUE")); for (int i = 0; i < vars.Count; i++) - { + { string s = String.Format(formatstring, vars[i].Name, vars[i].AccessSequence, Softdatatype.Types[vars[i].Softdatatype], values[i]); Console.WriteLine(s); } @@ -167,12 +167,12 @@ static void Main(string[] args) conn.SetPlcOperatingState(3); #endregion */ - conn.Disconnect(); } - else + catch (Exception e) { - Console.WriteLine("Main - Connect fehlgeschlagen!"); + Console.WriteLine("Main - " + e.ToString()); } + conn.Disconnect(); Console.WriteLine("Main - ENDE. Bitte Taste drücken."); Console.ReadKey(); } diff --git a/src/S7CommPlusDriver/Net/S7Client.cs b/src/S7CommPlusDriver/Net/S7Client.cs index b62e723..bd84cc4 100644 --- a/src/S7CommPlusDriver/Net/S7Client.cs +++ b/src/S7CommPlusDriver/Net/S7Client.cs @@ -1,4 +1,4 @@ -#region License +#region License /****************************************************************************** * S7CommPlusDriver * @@ -9,6 +9,7 @@ using OpenSsl; using System; +using System.Diagnostics; using System.IO; using System.Threading; @@ -25,12 +26,6 @@ public class S7Client : OpenSSLConnector.IConnectorCallback public static bool WriteSslKeyToFile; public static string WriteSslKeyPath; - #region [Constants and TypeDefs] - - public int _LastError = 0; - - #endregion - #region [S7 Telegrams] // ISO Connection Request telegram (contains also ISO Header and COTP Header) @@ -182,7 +177,6 @@ private void RunThread() while (!m_runThread_DoStop) { // Versuchen zu lesen - _LastError = 0; try { Length = RecvIsoPacket(); @@ -239,7 +233,7 @@ private void RunThread() private byte LastPDUType; private byte[] PDU = new byte[2048]; private MsgSocket Socket = null; - private int Time_ms = 0; + private long Time_ms = 0; private void CreateSocket() { @@ -255,18 +249,16 @@ private void CreateSocket() } } - private int TCPConnect() + private void TCPConnect() { - if (_LastError == 0) - try - { - Socket.Connect(IPAddress, _PLCPort); - } - catch - { - _LastError = S7Consts.errTCPConnectionFailed; - } - return _LastError; + try + { + Socket.Connect(IPAddress, _PLCPort); + } + catch (Exception e) when (e is not ErrTCPConnectionFailed) + { + throw new ErrTCPConnectionFailed("S7Client->TCPConnect", "Problem connecting to socket: " + e.Message); + } } private void RecvPacket(byte[] Buffer, int Start, int Size) @@ -277,13 +269,13 @@ private void RecvPacket(byte[] Buffer, int Start, int Size) { Socket.Receive(Buffer, Start, Size); } - catch + catch (Exception e) { - _LastError = S7Consts.errTCPDataReceive; + throw new ErrTCPDataReceive("S7Client->RecvPacket", "Problem while receiving packets: " + e.Message); } } else - _LastError = S7Consts.errTCPNotConnected; + throw new ErrTCPNotConnected("S7Client->RecvPacket", "S7Client is not connected"); } private void SendPacket(byte[] Buffer, int Len) @@ -292,9 +284,9 @@ private void SendPacket(byte[] Buffer, int Len) { Socket.Send(Buffer, Len); } - catch + catch (Exception e) { - _LastError = S7Consts.errTCPDataSend; + throw new ErrTCPDataSend("S7Client->SendPacket", "Problem while sending packets: " + e.Message); } } @@ -303,7 +295,7 @@ private void SendPacket(byte[] Buffer) if (Connected) SendPacket(Buffer, Buffer.Length); else - _LastError = S7Consts.errTCPNotConnected; + throw new ErrTCPNotConnected("S7Client->SendPacket", "S7Client is not connected"); } public void Send(byte[] Buffer) @@ -318,11 +310,10 @@ public void Send(byte[] Buffer) } } - private int SendIsoPacket(byte[] Buffer) + private void SendIsoPacket(byte[] Buffer) { // Packt die zu sendenden Daten in den Iso-Header ein. int Size = Buffer.Length; - _LastError = 0; Array.Copy(TPKT_ISO, 0, PDU, 0, TPKT_ISO.Length); SetWordAt(PDU, 2, (ushort)(Size + TPKT_ISO.Length)); @@ -330,21 +321,12 @@ private int SendIsoPacket(byte[] Buffer) { Array.Copy(Buffer, 0, PDU, TPKT_ISO.Length, Size); } - catch - { - return S7Consts.errIsoInvalidPDU; - } - - try - { - SendPacket(PDU, TPKT_ISO.Length + Size); - } - catch (ErrTCPDataSend) + catch (Exception e) { - _LastError = S7Consts.errTCPDataSend; + throw new ErrIsoInvalidPDU("S7Client->SendIsoPacket", "Problem copying buffer: " + e.Message); } - return _LastError; + SendPacket(PDU, TPKT_ISO.Length + Size); } private UInt16 GetWordAt(byte[] Buffer, int Pos) @@ -362,40 +344,31 @@ private int RecvIsoPacket() { bool Done = false; int Size = 0; - while ((_LastError == 0) && !Done) + while (!Done) { // Get TPKT (4 bytes) RecvPacket(PDU, 0, 4); - if (_LastError == 0) + Size = GetWordAt(PDU, 2); + // Check 0 bytes Data Packet (only TPKT+COTP = 7 bytes) + if (Size == IsoHSize) + RecvPacket(PDU, 4, 3); // Skip remaining 3 bytes and Done is still false + else { - Size = GetWordAt(PDU, 2); - // Check 0 bytes Data Packet (only TPKT+COTP = 7 bytes) - if (Size == IsoHSize) - RecvPacket(PDU, 4, 3); // Skip remaining 3 bytes and Done is still false - else - { - // TODO: Größe korrekt prüfen - //if ((Size > _PduSizeRequested + IsoHSize) || (Size < MinPduSize)) - // _LastError = S7Consts.errIsoInvalidPDU; - //else - Done = true; // a valid Length !=7 && >16 && <247 - } + // TODO: Größe korrekt prüfen + //if ((Size > _PduSizeRequested + IsoHSize) || (Size < MinPduSize)) + // _LastError = S7Consts.errIsoInvalidPDU; + //else + Done = true; // a valid Length !=7 && >16 && <247 } } - if (_LastError == 0) - { - RecvPacket(PDU, 4, 3); // Skip remaining 3 COTP bytes - LastPDUType = PDU[5]; // Stores PDU Type, we need it - // Receives the S7 Payload - RecvPacket(PDU, 7, Size - IsoHSize); - } - if (_LastError == 0) - return Size; - else - return 0; + RecvPacket(PDU, 4, 3); // Skip remaining 3 COTP bytes + LastPDUType = PDU[5]; // Stores PDU Type, we need it + // Receives the S7 Payload + RecvPacket(PDU, 7, Size - IsoHSize); + return Size; } - private int ISOConnect() + private void ISOConnect() { int Size; byte[] isocon = new byte[ISO_CR.Length + RemoteTSAP_S.Length]; @@ -411,22 +384,15 @@ private int ISOConnect() // Sends the connection request telegram SendPacket(isocon); - if (_LastError == 0) + // Gets the reply (if any) + Size = RecvIsoPacket(); + if (Size == 36) { - // Gets the reply (if any) - Size = RecvIsoPacket(); - if (_LastError == 0) - { - if (Size == 36) - { - if (LastPDUType != (byte)0xD0) // 0xD0 = CC Connection confirm - _LastError = S7Consts.errIsoConnect; - } - else - _LastError = S7Consts.errIsoInvalidPDU; - } + if (LastPDUType != (byte)0xD0) // 0xD0 = CC Connection confirm + throw new ErrIsoConnect("S7Client->ISOConnect", "Didn't receive CC byte"); } - return _LastError; + else + throw new ErrIsoInvalidPDU("S7Client->ISOConnect", "Wrong packet size"); } public byte[] getOMSExporterSecret() @@ -450,30 +416,27 @@ public S7Client() Disconnect(); } - public int Connect() + public void Connect() { - _LastError = 0; Time_ms = 0; - int Elapsed = Environment.TickCount; + Stopwatch Elapsed = Stopwatch.StartNew(); if (!Connected) { - TCPConnect(); // First stage : TCP Connection - if (_LastError == 0) + try { + TCPConnect(); // First stage : TCP Connection ISOConnect(); // Second stage : ISOTCP (ISO 8073) Connection - if (_LastError == 0) - { - // _LastError = S7P_InitSSLRequest(); // Third stage : Init SSL Request - StartThread(); - } + // _LastError = S7P_InitSSLRequest(); // Third stage : Init SSL Request + StartThread(); + } + catch + { + Disconnect(); + throw; } } - if (_LastError != 0) - Disconnect(); else - Time_ms = Environment.TickCount - Elapsed; - - return _LastError; + Time_ms = Elapsed.ElapsedMilliseconds; } public int SetConnectionParams(string Address, ushort LocalTSAP, byte[] RemoteTSAP) @@ -489,14 +452,12 @@ public int SetConnectionParams(string Address, ushort LocalTSAP, byte[] RemoteTS return 0; } - public int Disconnect() + public void Disconnect() { m_runThread_DoStop = true; m_runThread?.Join(); Socket.Close(); - - return 0; } public int GetParam(Int32 ParamNumber, ref int Value) @@ -587,17 +548,17 @@ public static string ErrorText(int Error) switch (Error) { case 0: return "OK"; - case S7Consts.errTCPSocketCreation: return "SYS : Error creating the Socket"; - case S7Consts.errTCPConnectionTimeout: return "TCP : Connection Timeout"; - case S7Consts.errTCPConnectionFailed: return "TCP : Connection Error"; - case S7Consts.errTCPReceiveTimeout: return "TCP : Data receive Timeout"; + // case S7Consts.errTCPSocketCreation: return "SYS : Error creating the Socket"; + // case S7Consts.errTCPConnectionTimeout: return "TCP : Connection Timeout"; + // case S7Consts.errTCPConnectionFailed: return "TCP : Connection Error"; + // case S7Consts.errTCPReceiveTimeout: return "TCP : Data receive Timeout"; case S7Consts.errTCPDataReceive: return "TCP : Error receiving Data"; - case S7Consts.errTCPSendTimeout: return "TCP : Data send Timeout"; - case S7Consts.errTCPDataSend: return "TCP : Error sending Data"; - case S7Consts.errTCPConnectionReset: return "TCP : Connection reset by the Peer"; - case S7Consts.errTCPNotConnected: return "CLI : Client not connected"; - case S7Consts.errTCPUnreachableHost: return "TCP : Unreachable host"; - case S7Consts.errIsoConnect: return "ISO : Connection Error"; + // case S7Consts.errTCPSendTimeout: return "TCP : Data send Timeout"; + // case S7Consts.errTCPDataSend: return "TCP : Error sending Data"; + // case S7Consts.errTCPConnectionReset: return "TCP : Connection reset by the Peer"; + // case S7Consts.errTCPNotConnected: return "CLI : Client not connected"; + // case S7Consts.errTCPUnreachableHost: return "TCP : Unreachable host"; + // case S7Consts.errIsoConnect: return "ISO : Connection Error"; case S7Consts.errIsoInvalidPDU: return "ISO : Invalid PDU received"; case S7Consts.errIsoInvalidDataSize: return "ISO : Invalid Buffer passed to Send/Receive"; case S7Consts.errCliNegotiatingPDU: return "CLI : Error in PDU negotiation"; @@ -643,11 +604,6 @@ public static string ErrorText(int Error) } } - public int LastError() - { - return _LastError; - } - public int RequestedPduLength() { return _PduSizeRequested; @@ -658,12 +614,12 @@ public int NegotiatedPduLength() return _PDULength; } - public int ExecTime() + public long ExecTime() { return Time_ms; } - public int ExecutionTime + public long ExecutionTime { get { diff --git a/src/S7CommPlusDriver/Net/S7Consts.cs b/src/S7CommPlusDriver/Net/S7Consts.cs index b8618d1..3871d40 100644 --- a/src/S7CommPlusDriver/Net/S7Consts.cs +++ b/src/S7CommPlusDriver/Net/S7Consts.cs @@ -22,31 +22,31 @@ public static class S7Consts //------------------------------------------------------------------------------ // ERRORS //------------------------------------------------------------------------------ - public const int errTCPSocketCreation = 0x00000001; - public const int errTCPConnectionTimeout = 0x00000002; - public const int errTCPConnectionFailed = 0x00000003; - public const int errTCPReceiveTimeout = 0x00000004; + // public const int errTCPSocketCreation = 0x00000001; + // public const int errTCPConnectionTimeout = 0x00000002; + // public const int errTCPConnectionFailed = 0x00000003; + // public const int errTCPReceiveTimeout = 0x00000004; public const int errTCPDataReceive = 0x00000005; - public const int errTCPSendTimeout = 0x00000006; - public const int errTCPDataSend = 0x00000007; - public const int errTCPConnectionReset = 0x00000008; - public const int errTCPNotConnected = 0x00000009; - public const int errTCPUnreachableHost = 0x00002751; + // public const int errTCPSendTimeout = 0x00000006; + // public const int errTCPDataSend = 0x00000007; + // public const int errTCPConnectionReset = 0x00000008; + // public const int errTCPNotConnected = 0x00000009; + // public const int errTCPUnreachableHost = 0x00002751; - public const int errIsoConnect = 0x00010000; // Connection error - public const int errIsoInvalidPDU = 0x00030000; // Bad format - public const int errIsoInvalidPDU1 = 0x00030001; // Bad format - public const int errIsoInvalidPDU2 = 0x00030002; // Bad format - public const int errIsoInvalidPDU3 = 0x00030003; // Bad format - public const int errIsoInvalidPDU4 = 0x00030004; // Bad format - public const int errIsoInvalidPDU5 = 0x00030005; // Bad format - public const int errIsoInvalidPDU6 = 0x00030006; // Bad format - public const int errIsoInvalidPDU7 = 0x00030007; // Bad format - public const int errIsoInvalidPDU8 = 0x00030008; // Bad format - public const int errIsoInvalidPDU9 = 0x00030009; // Bad format - public const int errIsoInvalidPDU10 = 0x00030010; // Bad format - public const int errIsoInvalidPDU11 = 0x00030011; // Bad format - public const int errIsoInvalidPDU12 = 0x00030012; // Bad format + // public const int errIsoConnect = 0x00010000; // Connection error + public const int errIsoInvalidPDU = 0x00030000; // Bad format + public const int errIsoInvalidPDU1 = 0x00030001; // Bad format + public const int errIsoInvalidPDU2 = 0x00030002; // Bad format + public const int errIsoInvalidPDU3 = 0x00030003; // Bad format + public const int errIsoInvalidPDU4 = 0x00030004; // Bad format + public const int errIsoInvalidPDU5 = 0x00030005; // Bad format + public const int errIsoInvalidPDU6 = 0x00030006; // Bad format + public const int errIsoInvalidPDU7 = 0x00030007; // Bad format + public const int errIsoInvalidPDU8 = 0x00030008; // Bad format + public const int errIsoInvalidPDU9 = 0x00030009; // Bad format + public const int errIsoInvalidPDU10 = 0x00030010; // Bad format + public const int errIsoInvalidPDU11 = 0x00030011; // Bad format + public const int errIsoInvalidPDU12 = 0x00030012; // Bad format public const int errIsoInvalidDataSize = 0x00040000; // Bad Datasize passed to send/recv : buffer is invalid public const int errCliNegotiatingPDU = 0x00100000; @@ -89,11 +89,11 @@ public static class S7Consts public const int errCliFirmwareNotSupported = 0x02800000; public const int errCliDeviceNotSupported = 0x02900000; - public const int errOpenSSL = 0x03100000; - public const int errInitSslResponse = 0x03100001; - //------------------------------------------------------------------------------ - // PARAMS LIST FOR COMPATIBILITY WITH Snap7.net.cs - //------------------------------------------------------------------------------ + public const int errOpenSSL = 0x03100000; + public const int errInitSslResponse = 0x03100001; + //------------------------------------------------------------------------------ + // PARAMS LIST FOR COMPATIBILITY WITH Snap7.net.cs + //------------------------------------------------------------------------------ public const Int32 p_u16_LocalPort = 1; // Not applicable here public const Int32 p_u16_RemotePort = 2; public const Int32 p_i32_PingTimeout = 3; diff --git a/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs b/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs index bf61865..1819e8c 100644 --- a/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs +++ b/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs @@ -218,7 +218,7 @@ protected void RunSSL() } } - public void Write(byte[] pData,int dataLen) + public void Write(byte[] pData, int dataLen) { DataBuffer pBuffer = new DataBuffer(pData, dataLen); AppendBuffer(m_pendingWriteList, pBuffer); diff --git a/src/S7CommPlusDriver/S7CommPlusConnection.cs b/src/S7CommPlusDriver/S7CommPlusConnection.cs index 5614fed..9c489df 100644 --- a/src/S7CommPlusDriver/S7CommPlusConnection.cs +++ b/src/S7CommPlusDriver/S7CommPlusConnection.cs @@ -1,1436 +1,1441 @@ -#region License -/****************************************************************************** - * S7CommPlusDriver - * - * Copyright (C) 2023 Thomas Wiens, th.wiens@gmx.de - * - * This file is part of S7CommPlusDriver. - * - * S7CommPlusDriver is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - /****************************************************************************/ -#endregion - -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.IO; -using System.Linq; -using System.Diagnostics; -using S7CommPlusDriver.ClientApi; -using System.Text.RegularExpressions; -using System.Security.Cryptography; - -namespace S7CommPlusDriver -{ - public partial class S7CommPlusConnection - { - #region Private Members - private S7Client m_client; - private MemoryStream m_ReceivedPDU; - private MemoryStream m_ReceivedTempPDU; - private Queue m_ReceivedPDUs = new Queue(); - private Mutex m_Mutex = new Mutex(); - - private bool m_ReceivedNeedMoreDataForCompletePDU; - private bool m_NewS7CommPlusReceived; - private UInt32 m_SessionId; - private UInt32 m_SessionId2; - public UInt32 SessionId2 - { - get { return m_SessionId2; } - private set { m_SessionId2 = value; } - } - - private int m_ReadTimeout = 5000; - private UInt16 m_SequenceNumber = 0; - private UInt32 m_IntegrityId = 0; - private UInt32 m_IntegrityId_Set = 0; - private CommRessources m_CommRessources = new CommRessources(); - - private List dbInfoList; - private List typeInfoList = new List(); - #endregion - - #region Public Members - public int m_LastError = 0; - - #endregion - - #region Private Methods - - private UInt16 GetNextSequenceNumber() - { - if (m_SequenceNumber == UInt16.MaxValue) - { - m_SequenceNumber = 1; - } - else - { - m_SequenceNumber++; - } - return m_SequenceNumber; - } - - // We must count the IntegrityId for different functions of the protocol. - // As a first guess functions for setting variables need separate counters. - // Use the functioncode to differ between the which sequence/integrity counter values. - private UInt32 GetNextIntegrityId(ushort functioncode) - { - UInt32 ret; - switch (functioncode) - { - case Functioncode.SetMultiVariables: - case Functioncode.SetVariable: - case Functioncode.SetVarSubStreamed: - case Functioncode.DeleteObject: - case Functioncode.CreateObject: - if (m_IntegrityId_Set == UInt32.MaxValue) - { - m_IntegrityId_Set = 0; - } - else - { - m_IntegrityId_Set++; - } - ret = m_IntegrityId_Set; - break; - default: - if (m_IntegrityId == UInt32.MaxValue) - { - m_IntegrityId = 0; - } - else - { - m_IntegrityId++; - } - ret = m_IntegrityId; - break; - } - return ret; - } - - private void WaitForNewS7plusReceived(int Timeout) - { - //TODO: Tickcount overflows!!!! - bool Expired = false; - int Elapsed = Environment.TickCount; - bool done = false; - - m_Mutex.WaitOne(); - if (m_ReceivedPDUs.Count > 0) - { - m_ReceivedPDU = m_ReceivedPDUs.Dequeue(); - done = true; - } - m_Mutex.ReleaseMutex(); - - while (!done && !Expired) - { - Thread.Sleep(2); - Expired = Environment.TickCount - Elapsed > Timeout; - m_Mutex.WaitOne(); - if (m_ReceivedPDUs.Count > 0) - { - m_ReceivedPDU = m_ReceivedPDUs.Dequeue(); - done = true; - } - m_Mutex.ReleaseMutex(); - } - - if (Expired) - { - Console.WriteLine("S7CommPlusConnection - WaitForNewS7plusReceived: ERROR: Timeout!"); - m_LastError = S7Consts.errTCPDataReceive; - } - } - - private int SendS7plusFunctionObject(IS7pRequest funcObj) - { - // If we don't have a SessionId, this must be the first CreateObjectRequest, where we use the Id for NullServerSession - if (m_SessionId == 0) - { - funcObj.SessionId = Ids.ObjectNullServerSession; - } - else - { - funcObj.SessionId = m_SessionId; - } - - // Insert SequenceNumber and IntegrityId, if neccessary for object type and state of communication - funcObj.SequenceNumber = GetNextSequenceNumber(); - if (funcObj.WithIntegrityId) - { - funcObj.IntegrityId = GetNextIntegrityId(funcObj.FunctionCode); - } - - MemoryStream stream = new MemoryStream(); - funcObj.Serialize(stream); - return SendS7plusPDUdata(stream.ToArray(), (int)stream.Length, funcObj.ProtocolVersion); - } - - private int SendS7plusPDUdata(byte[] sendPduData, int bytesToSend, byte protoVersion) - { - m_LastError = 0; - - int curSize; - int sourcePos = 0; - int sendLen; - int NegotiatedIsoPduSize = 1024;// TODO: Respect the negotiated TPDU size - - // 4 Byte TPKT Header - // 3 Byte ISO-Header - // 5 Byte TLS Header + 17 Bytes addition from TLS - // 4 Byte S7CommPlus Header - // 4 Byte S7CommPlus Trailer (must fit into last PDU) - int MaxSize = NegotiatedIsoPduSize - 4 - 3 - 5 - 17 - 4 - 4; - byte[] packet = new byte[MaxSize + 4]; //max packet size is always MaxSize + PDU Header - - while (bytesToSend > 0) - { - if (bytesToSend > MaxSize) - { - curSize = MaxSize; - bytesToSend -= MaxSize; - } - else - { - curSize = bytesToSend; - bytesToSend -= curSize; - } - // Header - packet[0] = 0x72; - packet[1] = protoVersion; - packet[2] = (byte)(curSize >> 8); - packet[3] = (byte)(curSize & 0x00FF); - // Data part - Array.Copy(sendPduData, sourcePos, packet, 4, curSize); - sourcePos += curSize; - sendLen = 4 + curSize; - - // Trailer only in last packet - if (bytesToSend == 0) - { - Array.Resize(ref packet, sendLen + 4); //resize only the last package to sendLen + TrailerLen - packet[sendLen] = 0x72; - sendLen++; - packet[sendLen] = protoVersion; - sendLen++; - packet[sendLen] = 0; - sendLen++; - packet[sendLen] = 0; - sendLen++; - } - m_client.Send(packet); - } - return m_LastError; - } - - private void OnDataReceived(byte[] PDU, int len) - { - // In this method, we've got always a complete TPDU (from protocol layer above) without fragmentation - // At this point, we can detect if we receive a fragmented S7CommPlus PDU. - // If not fragmented, then TPKT.Length - 15 is equal of the length in S7CommPlus.Header. - // 15 bytes because: 4 Bytes TPKT.Header.len + 3 Bytes ISO.Header.Len + 4 Bytes S7CommPlus.Header.len + 4 Bytes S7CommPlus.trailer.Len. - // Since the pure userdata of the TPDU comes in here, that is only minus 4 bytes header + 4 bytes trailer. - // - // Special handling for SystemEvents with ProtocolVersion = 0xfe: - // Here's only a header. - // Because of this, the first byte for the ProtocolVersion must be written in then stream at first. - // The datalength must not be written into the stream, because it's not valid on fragmented PDUs - // for the complete length, only for the single fragment. - - // This method is called from a different thread. - // If we use subscriptions or alarming, we may get new data before the last PDU was processed completely. - // First step we push the complete PDU to a queue. - // TODO: m_LastError handling would also not work as expected. This needs some more redesign. - - if (!m_ReceivedNeedMoreDataForCompletePDU) - { - m_ReceivedTempPDU = new MemoryStream(); - } - // S7comm-plus - byte protoVersion; - int pos = 0; - int s7HeaderDataLen = 0; - // Check header - if (PDU[pos] != 0x72) - { - m_ReceivedNeedMoreDataForCompletePDU = false; - m_LastError = S7Consts.errIsoInvalidPDU1; - return; - } - pos++; - protoVersion = PDU[pos]; - if (protoVersion != ProtocolVersion.V1 && protoVersion != ProtocolVersion.V2 && protoVersion != ProtocolVersion.V3 && protoVersion != ProtocolVersion.SystemEvent) - { - m_ReceivedNeedMoreDataForCompletePDU = false; - m_LastError = S7Consts.errIsoInvalidPDU2; - return; - } - // For the first fragment, write the ProtocolVersion into the stream in advance - if (!m_ReceivedNeedMoreDataForCompletePDU) - { - m_ReceivedTempPDU.Write(PDU, pos, 1); - } - pos++; - - // Read the length of the data-part from header - s7HeaderDataLen = GetWordAt(PDU, pos); - pos += 2; - if (s7HeaderDataLen > 0) - { - // Special handling for SystemEvent 0xfe PDUs: - // This only confirms a few data, but also reports major protocol errors (e.g. incorrect sequence numbers). - // The confirms can be discarded (for now), but the errors are relevant, because a connection termination is neccessary. - // As we don't have a trailer on this types, it's not possible that they are transmitted as fragments. - if (protoVersion == ProtocolVersion.SystemEvent) - { - Console.WriteLine("S7CommPlusConnection - OnDataReceived: ProtocolVersion 0xfe SystemEvent received"); - m_ReceivedTempPDU.Write(PDU, pos, s7HeaderDataLen); - pos += s7HeaderDataLen; - // Create SystemEventObject - m_ReceivedNeedMoreDataForCompletePDU = false; - m_ReceivedTempPDU.Position = 0; - m_NewS7CommPlusReceived = false; - - var sysevt = SystemEvent.DeserializeFromPdu(m_ReceivedTempPDU); - if (sysevt.IsFatalError()) - { - Console.WriteLine("S7CommPlusConnection - OnDataReceived: SystemEvent has fatal error"); - // Termination neccessary - m_LastError = S7Consts.errIsoInvalidPDU3; - } - else - { - Console.WriteLine("S7CommPlusConnection - OnDataReceived: SystemEvent with non fatal error, do nothing"); - } - } - else - { - // Copy data part to destination stream - m_ReceivedTempPDU.Write(PDU, pos, s7HeaderDataLen); - pos += s7HeaderDataLen; - // If this is a fragmented PDU, then at this point no trailer - if ((len - 4 - 4) == s7HeaderDataLen) - { - m_ReceivedNeedMoreDataForCompletePDU = false; - m_ReceivedTempPDU.Position = 0; // Set position back to zero, ready for readout - m_NewS7CommPlusReceived = true; - } - else - { - m_ReceivedNeedMoreDataForCompletePDU = true; - } - } - } - - // If a complete (usable) PDU is received, add to the queue (threadsafe) for readout - if (m_NewS7CommPlusReceived) - { - // Push complete PDU to the queue - m_Mutex.WaitOne(); - m_ReceivedPDUs.Enqueue(m_ReceivedTempPDU); - m_Mutex.ReleaseMutex(); - m_NewS7CommPlusReceived = false; - } - } - - private UInt16 GetWordAt(byte[] Buffer, int Pos) - { - return (UInt16)((Buffer[Pos] << 8) | Buffer[Pos + 1]); - } - - private void SetWordAt(byte[] Buffer, int Pos, UInt16 Value) - { - Buffer[Pos] = (byte)(Value >> 8); - Buffer[Pos + 1] = (byte)(Value & 0x00FF); - } - - private void printBuf(byte[] b) - { - for (int i = 0; i < b.Length; i++) - { - Console.Write("0x" + String.Format("{0:X02} ", b[i])); - } - Console.Write(Environment.NewLine); - } - - private int checkResponseWithIntegrity(IS7pRequest request, IS7pResponse response) - { - if (response == null) - { - //Console.WriteLine("checkResponseWithIntegrity: ERROR! response == null"); - return S7Consts.errIsoInvalidPDU4; - } - if (request.SequenceNumber != response.SequenceNumber) - { - //Console.WriteLine(String.Format("checkResponseWithIntegrity: ERROR! SeqenceNumber of Response ({0}) doesn't match Request ({1})", response.SequenceNumber, request.SequenceNumber)); - return S7Consts.errIsoInvalidPDU5; - } - // Overflow is possible and allowed - UInt32 reqIntegCheck = (UInt32)request.SequenceNumber + request.IntegrityId; - if (response.IntegrityId != reqIntegCheck) - { - Console.WriteLine(String.Format("checkResponseWithIntegrity: ERROR! IntegrityId of the Response ({0}) doesn't match Request ({1})", response.IntegrityId, reqIntegCheck)); - // Don't return this as error so far - } - return 0; - } - #endregion - - #region Public Methods - /// - /// Establishes a connection to the PLC. - /// - /// PLC IP address - /// PLC password (if set) - /// read timeout in milliseconds (default: 5000 ms) - /// - public int Connect(string address, string password = "", string username = "", int timeoutMs = 5000) - { - if (timeoutMs > 0) - { - m_ReadTimeout = timeoutMs; - } - - m_LastError = 0; - int res; - int Elapsed = Environment.TickCount; - m_client = new S7Client(); - m_client.OnDataReceived = this.OnDataReceived; - - m_client.SetConnectionParams(address, 0x0600, Encoding.ASCII.GetBytes("SIMATIC-ROOT-HMI")); - res = m_client.Connect(); - if (res != 0) - return res; - - #region Step 1: Unencrypted InitSSL Request / Response - - InitSslRequest sslReq = new InitSslRequest(ProtocolVersion.V1, 0, 0); - res = SendS7plusFunctionObject(sslReq); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - InitSslResponse sslRes; - sslRes = InitSslResponse.DeserializeFromPdu(m_ReceivedPDU); - if (sslRes == null) - { - m_client.Disconnect(); - return S7Consts.errInitSslResponse; - } - - #endregion - - #region Step 2: Activate TLS. Everything from here onwards is TLS encrypted. - - res = m_client.SslActivate(); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - - #endregion - - #region Step 3: CreateObjectRequest / Response (with TLS) - - var createObjReq = new CreateObjectRequest(ProtocolVersion.V1, 0, false); - createObjReq.SetNullServerSessionData(); - res = SendS7plusFunctionObject(createObjReq); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var createObjRes = CreateObjectResponse.DeserializeFromPdu(m_ReceivedPDU); - if (createObjRes == null) - { - //Console.WriteLine("S7CommPlusConnection - Connect: CreateObjectResponse with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU6; - } - // There are (always?) at least two IDs in the response. - // Usually the first is used for polling data, and the 2nd for jobs which use notifications, e.g. alarming, subscriptions. - m_SessionId = createObjRes.ObjectIds[0]; - m_SessionId2 = createObjRes.ObjectIds[1]; - //Console.WriteLine("S7CommPlusConnection - Connect: Using SessionId=0x" + String.Format("{0:X04}", m_SessionId)); - - // Evaluate Struct 314 - PValue sval = createObjRes.ResponseObject.GetAttribute(Ids.ServerSessionVersion); - ValueStruct serverSession = (ValueStruct)sval; - - #endregion - - #region Step 4: SetMultiVariablesRequest / Response - - var setMultiVarReq = new SetMultiVariablesRequest(ProtocolVersion.V2); - setMultiVarReq.SetSessionSetupData(m_SessionId, serverSession); - res = SendS7plusFunctionObject(setMultiVarReq); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var setMultiVarRes = SetMultiVariablesResponse.DeserializeFromPdu(m_ReceivedPDU); - if (setMultiVarRes == null) - { - //Console.WriteLine("S7CommPlusConnection - Connect: SetMultiVariablesResponse with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU7; - } - - #endregion - - #region Step 5: Read SystemLimits - res = m_CommRessources.ReadMax(this); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - #endregion - - #region Step 6: Password - res = legitimate(serverSession, password, username); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - #endregion - - // If everything has been error-free up to this point, then the connection has been established successfully. - Console.WriteLine("S7CommPlusConnection - Connect: Time for connection establishment: " + (Environment.TickCount - Elapsed) + " ms."); - return 0; - } - - public void Disconnect() - { - DeleteObject(m_SessionId); - m_client.Disconnect(); - } - - /// - /// Deletes the object with the given Id. - /// - /// The object Id to delete - /// 0 on success - private int DeleteObject(uint deleteObjectId) - { - int res; - var delObjReq = new DeleteObjectRequest(ProtocolVersion.V2); - delObjReq.DeleteObjectId = deleteObjectId; - res = SendS7plusFunctionObject(delObjReq); - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - // If we delete our own session id, then there's no IntegrityId in the response. - // And the error code gives an error, but not a fatal one. - // If we delete another object, there should be an IntegrityId in the response, and - // the response gives no error. - if (deleteObjectId == m_SessionId) - { - var delObjRes = DeleteObjectResponse.DeserializeFromPdu(m_ReceivedPDU, false); - Trace.WriteLine("S7CommPlusConnection - DeleteSession: Deleted our own Session Id object, not checking the response."); - m_SessionId = 0; // not valid anymore - m_SessionId2 = 0; - } - else - { - var delObjRes = DeleteObjectResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(delObjReq, delObjRes); - if (res != 0) - { - return res; - } - if (delObjRes.ReturnValue != 0) - { - Console.WriteLine("S7CommPlusConnection - DeleteSession: Executed with Error! ReturnValue=" + delObjRes.ReturnValue); - res = -1; - } - } - return res; - } - - public int ReadValues(List addresslist, out List values, out List errors) - { - // The requester must pass the internal type with the request, otherwise not all return values can be converted automatically. - // For example, strings are transmitted as UInt-Array. - values = new List(); - errors = new List(); - // Initialize error fields to error value - for (int i = 0; i < addresslist.Count; i++) - { - values.Add(null); - errors.Add(0xffffffffffffffff); - } - - // Split request into chunks, taking the MaxTags per request into account - int chunk_startIndex = 0; - int count_perChunk = 0; - do - { - int res; - var getMultiVarReq = new GetMultiVariablesRequest(ProtocolVersion.V2); - - getMultiVarReq.AddressList.Clear(); - count_perChunk = 0; - while (count_perChunk < m_CommRessources.TagsPerReadRequestMax && (chunk_startIndex + count_perChunk) < addresslist.Count) - { - getMultiVarReq.AddressList.Add(addresslist[chunk_startIndex + count_perChunk]); - count_perChunk++; - } - - res = SendS7plusFunctionObject(getMultiVarReq); - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - var getMultiVarRes = GetMultiVariablesResponse.DeserializeFromPdu(m_ReceivedPDU); - res = checkResponseWithIntegrity(getMultiVarReq, getMultiVarRes); - if (res != 0) - { - return res; - } - // ReturnValue shows also an error, if only one single variable could not be read - if (getMultiVarRes.ReturnValue != 0) - { - Console.WriteLine("S7CommPlusConnection - ReadValues: Executed with Error! ReturnValue=" + getMultiVarRes.ReturnValue); - } - - // TODO: If a variable could not be read, there is no value, but there is an ErrorValue. - // The user must therefore check whether Value != null. Maybe there's a more elegant solution. - foreach (var v in getMultiVarRes.Values) - { - values[chunk_startIndex + (int)v.Key - 1] = v.Value; - // Initialize error to 0, will be overwritten below if there was an error on an item. - errors[chunk_startIndex + (int)v.Key - 1] = 0; - } - - foreach (var ev in getMultiVarRes.ErrorValues) - { - errors[chunk_startIndex + (int)ev.Key - 1] = ev.Value; - } - chunk_startIndex += count_perChunk; - - } while (chunk_startIndex < addresslist.Count); - - return m_LastError; - } - - public int WriteValues(List addresslist, List values, out List errors) - { - int res; - errors = new List(); - for (int i = 0; i < addresslist.Count; i++) - { - // Initialize to no error value, as there's no explicit value for write success. - errors.Add(0); - } - - // Split request into chunks, taking the MaxTags per request into account - int chunk_startIndex = 0; - int count_perChunk = 0; - do - { - var setMultiVarReq = new SetMultiVariablesRequest(ProtocolVersion.V2); - setMultiVarReq.AddressListVar.Clear(); - setMultiVarReq.ValueList.Clear(); - count_perChunk = 0; - while (count_perChunk < m_CommRessources.TagsPerWriteRequestMax && (chunk_startIndex + count_perChunk) < addresslist.Count) - { - setMultiVarReq.AddressListVar.Add(addresslist[chunk_startIndex + count_perChunk]); - setMultiVarReq.ValueList.Add(values[chunk_startIndex + count_perChunk]); - count_perChunk++; - } - - res = SendS7plusFunctionObject(setMultiVarReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - var setMultiVarRes = SetMultiVariablesResponse.DeserializeFromPdu(m_ReceivedPDU); - res = checkResponseWithIntegrity(setMultiVarReq, setMultiVarRes); - if (res != 0) - { - return res; - } - // ReturnValue shows also an error, if only one single variable could not be written - if (setMultiVarRes.ReturnValue != 0) - { - Console.WriteLine("S7CommPlusConnection - WriteValues: Write with errors. ReturnValue=" + setMultiVarRes.ReturnValue); - } - - foreach (var ev in setMultiVarRes.ErrorValues) - { - errors[chunk_startIndex + (int)ev.Key - 1] = ev.Value; - } - chunk_startIndex += count_perChunk; - - } while (chunk_startIndex < addresslist.Count); - - return m_LastError; - } - - public int SetPlcOperatingState(Int32 state) - { - int res; - var setVarReq = new SetVariableRequest(ProtocolVersion.V2); - setVarReq.InObjectId = Ids.NativeObjects_theCPUexecUnit_Rid; - setVarReq.Address = Ids.CPUexecUnit_operatingStateReq; - setVarReq.Value = new ValueDInt(state); - - res = SendS7plusFunctionObject(setVarReq); - if (res != 0) - { - m_client.Disconnect(); - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - m_client.Disconnect(); - return m_LastError; - } - - var setVarRes = SetVariableResponse.DeserializeFromPdu(m_ReceivedPDU); - if (setVarRes == null) - { - //Console.WriteLine("S7CommPlusConnection - Connect: SetVariableResponse with Error!"); - m_client.Disconnect(); - return S7Consts.errIsoInvalidPDU12; - } - - return 0; - } - - public int Browse(out List varInfoList) - { - int res; - varInfoList = new List(); - Browser vars = new Browser(); - ExploreRequest exploreReq; - ExploreResponse exploreRes; - - #region Read all objects - - var exploreData = new List(); - - exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = Ids.NativeObjects_thePLCProgram_Rid; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; - exploreReq.ExploreParents = 0; - - // We want to know the following attributes - exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); - exploreReq.AddressList.Add(Ids.Block_BlockNumber); - exploreReq.AddressList.Add(Ids.ASObjectES_Comment); - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - - #endregion - - #region Evaluate all data blocks that then need to be browsed - - var obj = exploreRes.Objects.First(o => o.ClassId == Ids.PLCProgram_Class_Rid); - - foreach (var ob in obj.GetObjects()) - { - switch (ob.ClassId) - { - case Ids.DB_Class_Rid: - UInt32 relid = ob.RelationId; - UInt32 area = (relid >> 16); - UInt32 num = relid & 0xffff; - if (area == 0x8a0e) - { - var name = (ValueWString)(ob.GetAttribute(Ids.ObjectVariableTypeName)); - BrowseData data = new BrowseData(); - data.db_block_relid = relid; - data.db_name = name.GetValue(); - data.db_number = num; - exploreData.Add(data); - } - break; - } - } - - #endregion - - #region Determine the TypeInfo RID to the RelId from the first response - // By querying LID = 1 from all DBs you get the RID back with which the type information can be queried. - // This is neccessary because, for example, with instance DBs (e.g. TON), the type information must - // not be accessed via the RID of the DB, but of the RID of the TON. - var readlist = new List(); - var values = new List(); - var errors = new List(); - - foreach (var data in exploreData) - { - if (data.db_number > 0) // only process datablocks here, no marker, timer etc. - { - // Insert the variable address - var adr1 = new ItemAddress(); - adr1.AccessArea = data.db_block_relid; - adr1.AccessSubArea = Ids.DB_ValueActual; - adr1.LID.Add(1); - readlist.Add(adr1); - } - } - res = ReadValues(readlist, out values, out errors); - if (res != 0) - { - return res; - } - #endregion - - #region Pass the preliminary information for recombination to ExploreSymbols - - // Add the response information to the list - for (int i = 0; i < values.Count; i++) - { - if (errors[i] == 0) - { - ValueRID rid = (ValueRID)values[i]; - var data = exploreData[i]; - data.db_block_ti_relid = rid.GetValue(); - exploreData[i] = data; - } - else - { - // On error, set the relid to zero, will be removed from the list in the next step. - // TODO: Report this as an error? - var data = exploreData[i]; - data.db_block_ti_relid = 0; - exploreData[i] = data; - } - } - // Remove elements with db_block_ti_relid == 0. This occurs e.g. on datablocks only present in load memory. - // The informations can't be used any further (at least not for variable access). - exploreData.RemoveAll(item => item.db_block_ti_relid == 0); - - foreach (var ed in exploreData) - { - vars.AddBlockNode(eNodeType.Root, ed.db_name, ed.db_block_relid, ed.db_block_ti_relid); - } - - // Add IQMCT areas manually - vars.AddBlockNode(eNodeType.Root, "IArea", Ids.NativeObjects_theIArea_Rid, 0x90010000); - vars.AddBlockNode(eNodeType.Root, "QArea", Ids.NativeObjects_theQArea_Rid, 0x90020000); - vars.AddBlockNode(eNodeType.Root, "MArea", Ids.NativeObjects_theMArea_Rid, 0x90030000); - vars.AddBlockNode(eNodeType.Root, "S7Timers", Ids.NativeObjects_theS7Timers_Rid, 0x90050000); - vars.AddBlockNode(eNodeType.Root, "S7Counters", Ids.NativeObjects_theS7Counters_Rid, 0x90060000); - - #endregion - - #region Read the Type Info Container (as a single big PDU, must be proven to be the way to go in big programs) - exploreReq = new ExploreRequest(ProtocolVersion.V2); - // With ObjectOMSTypeInfoContainer we get all in a big PDU (with maybe hundreds of fragments) - exploreReq.ExploreId = Ids.ObjectOMSTypeInfoContainer; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; - exploreReq.ExploreParents = 0; - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - #endregion - - #region Process the response, and build the complete variables list - exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - var objs = exploreRes.Objects.First(o => o.ClassId == Ids.ClassOMSTypeInfoContainer); - - vars.SetTypeInfoContainerObjects(objs.GetObjects()); - vars.BuildTree(); - vars.BuildFlatList(); - varInfoList = vars.GetVarInfoList(); - #endregion - - return 0; - } - - /// - /// Gets the first level of a tag symbol string. Removes the " used to escape special chars. - /// - /// plc tag symbol - /// The first level of the symbol string - /// Symbol syntax error - private string parseSymbolLevel(ref string symbol) - { - if (symbol.StartsWith("\"")) - { - int idx = symbol.IndexOf('"', 1); - if (idx < 0) throw new Exception("Symbol syntax error"); - string lvl = symbol.Substring(1, idx - 1); - symbol = symbol.Remove(0, idx + 1); - if (symbol.StartsWith(".")) symbol = symbol.Remove(0, 1); - return lvl; - } - else - { - int idx = symbol.IndexOf('.'); - int idx2 = symbol.IndexOf('[', 1); - if (idx2 >= 0 && (idx2 < idx || idx < 0)) idx = idx2; - if (idx >= 0) - { - string lvl = symbol.Substring(0, idx); - symbol = symbol.Remove(0, idx); - if (symbol.StartsWith(".")) symbol = symbol.Remove(0, 1); - return lvl; - } - else - { - string lvl = symbol; - symbol = ""; - return lvl; - } - } - } - - /// - /// Gets the typeinfo by given ti relid from the internal buffer. If it's not found in the buffer - /// it's fetched from the PLC and stored in the buffer. - /// - /// type info relid - /// type info - /// Could not get type info - public PObject getTypeInfoByRelId(uint ti_relid) - { - PObject pObj = typeInfoList.Find(ti => ti.RelationId == ti_relid); - if (pObj == null) - { - // Type info not found in list, request it from plc - List newPObj = new List(); - if (GetTypeInformation(ti_relid, out newPObj) != 0) throw new Exception("Could not get type info"); - typeInfoList.AddRange(newPObj); - // Try again - pObj = typeInfoList.Find(ti => ti.RelationId == ti_relid); - } - return pObj; - } - - /// - /// Calculates the access sequence for 1 dimensional arrays. - /// - /// plc tag symbol - /// Var type that holds the dim info - /// used to build access sequence - /// Symbol syntax error - private void calcAccessSeqFor1DimArray(ref string symbol, PVartypeListElement varType, VarInfo varInfo) - { - Regex re = new Regex(@"^\[(-?\d+)\]"); - Match m = re.Match(symbol); - if (!m.Success) throw new Exception("Symbol syntax error"); - parseSymbolLevel(ref symbol); // remove index from symbol string - int arrayIndex = int.Parse(m.Groups[1].Value); - - var ioit = (IOffsetInfoType_1Dim)varType.OffsetInfoType; - uint arrayElementCount = ioit.GetArrayElementCount(); - int arrayLowerBounds = ioit.GetArrayLowerBounds(); - - if (arrayIndex - arrayLowerBounds > arrayElementCount) throw new Exception("Out of bounds"); - if (arrayIndex < arrayLowerBounds) throw new Exception("Out of bounds"); - varInfo.AccessSequence += "." + String.Format("{0:X}", arrayIndex - arrayLowerBounds); - if (varType.OffsetInfoType.HasRelation()) varInfo.AccessSequence += ".1"; // additional ".1" for array of struct - } - - /// - /// Calculates the access sequence for multi-dimensional arrays. - /// - /// plc tag symbol - /// Var type that holds the dim info - /// used to build access sequence - /// Symbol syntax error - private void calcAccessSeqForMDimArray(ref string symbol, PVartypeListElement varType, VarInfo varInfo) - { - Regex re = new Regex(@"^\[( ?-?\d+ ?(, ?-?\d+ ?)+)\]"); - Match m = re.Match(symbol); - if (!m.Success) throw new Exception("Symbol syntax error"); - parseSymbolLevel(ref symbol); // remove index from symbol string - string idxs = m.Groups[1].Value.Replace(" ", ""); - - int[] indexes = Array.ConvertAll(idxs.Split(','), e => int.Parse(e)); - var ioit = (IOffsetInfoType_MDim)varType.OffsetInfoType; - uint[] MdimArrayElementCount = (uint[])ioit.GetMdimArrayElementCount().Clone(); - int[] MdimArrayLowerBounds = ioit.GetMdimArrayLowerBounds(); - - // check dim count - int dimCount = MdimArrayElementCount.Aggregate(0, (acc, act) => acc += (act > 0) ? 1 : 0); - if (dimCount != indexes.Count()) throw new Exception("Out of bounds"); - // check bounds - for (int i = 0; i < dimCount; ++i) - { - indexes[i] = (indexes[i] - MdimArrayLowerBounds[dimCount - i - 1]); - if (indexes[i] > MdimArrayElementCount[dimCount - i - 1]) throw new Exception("Out of bounds"); - if (indexes[i] < 0) throw new Exception("Out of bounds"); - } - - // calc dim size - if (varType.Softdatatype == Softdatatype.S7COMMP_SOFTDATATYPE_BBOOL) - { - MdimArrayElementCount[0] += 8 - MdimArrayElementCount[0] % 8; // for bool must be a mutiple of 8! - } - uint[] dimSize = new uint[dimCount]; - uint g = 1; - for (int i = 0; i < dimCount - 1; ++i) - { - dimSize[i] = g; - g *= MdimArrayElementCount[i]; - } - dimSize[dimCount - 1] = g; - - // calc id - int arrayIndex = 0; - for (int i = 0; i < dimCount; ++i) - { - arrayIndex += indexes[i] * (int)dimSize[dimCount - i - 1]; - } - - varInfo.AccessSequence += "." + String.Format("{0:X}", arrayIndex); - if (varType.OffsetInfoType.HasRelation()) varInfo.AccessSequence += ".1"; // additional ".1" for array of struct - } - - /// - /// Browses the symbol level by level recursively. Fetches missing type info automatically from the plc. - /// - /// type info relid - /// plc tag symbol - /// used to build access sequence - /// plc tag or null if not found - /// Symbol syntax error, Out of bounds - private PlcTag browsePlcTagBySymbol(uint ti_relid, ref string symbol, VarInfo varInfo) - { - PObject pObj = getTypeInfoByRelId(ti_relid); - if (pObj == null) throw new Exception("Could not get type info"); - string levelName = parseSymbolLevel(ref symbol); - // find level name of symbol in var list - int idx = pObj.VarnameList?.Names?.IndexOf(levelName) ?? -1; - if (idx < 0) return null; - PVartypeListElement varType = pObj.VartypeList.Elements[idx]; - varInfo.AccessSequence += "." + String.Format("{0:X}", varType.LID); - bool is1Dim = false; - if (varType.OffsetInfoType.Is1Dim()) - { - if (symbol == "") - { - is1Dim = true; - } - else - { - calcAccessSeqFor1DimArray(ref symbol, varType, varInfo); - } - } - if (varType.OffsetInfoType.IsMDim()) - { - calcAccessSeqForMDimArray(ref symbol, varType, varInfo); - } - if (varType.OffsetInfoType.HasRelation()) - { - if (symbol.Length <= 0 && varType.Softdatatype == Softdatatype.S7COMMP_SOFTDATATYPE_DTL) - { - return PlcTags.TagFactory(varInfo.Name, new ItemAddress(varInfo.AccessSequence), varType.Softdatatype, is1Dim); - } - if (symbol.Length <= 0) - { - return null; - } - else - { - var ioit = (IOffsetInfoType_Relation)varType.OffsetInfoType; - return browsePlcTagBySymbol(ioit.GetRelationId(), ref symbol, varInfo); - } - } - else - { - return PlcTags.TagFactory(varInfo.Name, new ItemAddress(varInfo.AccessSequence), varType.Softdatatype, is1Dim); - } - } - - /// - /// Get the plc tag for the given plc tag symbol. - /// - /// plc tag symbol - /// plc tag, returns null if plc tag could not be found - public PlcTag getPlcTagBySymbol(string symbol) - { - VarInfo varInfo = new VarInfo(); - varInfo.Name = symbol; - // make sure we have the db list - if (dbInfoList == null) - { - if (GetListOfDatablocks(out dbInfoList) != 0) { return null; } - } - string levelName = parseSymbolLevel(ref symbol); - // find db by first level name of symbol - DatablockInfo dbInfo = dbInfoList.Find(dbi => dbi.db_name == levelName); - if (dbInfo != null) - { - varInfo.AccessSequence = String.Format("{0:X}", dbInfo.db_block_relid); - return browsePlcTagBySymbol(dbInfo.db_block_ti_relid, ref symbol, varInfo); - } - else - { - symbol = varInfo.Name; - // Merker - varInfo.AccessSequence = String.Format("{0:X}", Ids.NativeObjects_theMArea_Rid); - PlcTag tag = browsePlcTagBySymbol(0x90030000, ref symbol, varInfo); - if (tag != null) return tag; - symbol = varInfo.Name; - // Outputs - varInfo.AccessSequence = String.Format("{0:X}", Ids.NativeObjects_theQArea_Rid); - tag = browsePlcTagBySymbol(0x90020000, ref symbol, varInfo); - if (tag != null) return tag; - symbol = varInfo.Name; - // Inputs - varInfo.AccessSequence = String.Format("{0:X}", Ids.NativeObjects_theIArea_Rid); - tag = browsePlcTagBySymbol(0x90010000, ref symbol, varInfo); - if (tag != null) return tag; - // TODO: implement s5timers and counters... no one uses them anymore anyway - } - return null; - } - - public class BrowseEntry - { - public string Name; - public uint Softdatatype; - public UInt32 LID; - public UInt32 SymbolCrc; - public string AccessSequence; - }; - - public class BrowseData - { - public string db_name; // Name of the datablock - public UInt32 db_number; // Number of the datablock - public UInt32 db_block_relid; // RID of the datablock - public UInt32 db_block_ti_relid; // Type-Info RID of the datablock - public List variables = new List(); // Variables inside the datablock - }; - - public class DatablockInfo - { - public string db_name; // Name of the datablock - public UInt32 db_number; // Number of the datablock - public UInt32 db_block_relid; // RID of the datablock - public UInt32 db_block_ti_relid; // Type-Info RID of the datablock - }; - - public int GetListOfDatablocks(out List dbInfoList) - { - int res; - - dbInfoList = new List(); - - var exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = Ids.NativeObjects_thePLCProgram_Rid; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; - exploreReq.ExploreParents = 0; - - // Add the attributes we need in the response - exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); - - // Set filter on Id for Datablock Class RID. With this filter, we only - // get informations from datablocks, and not other blocks we don't need here. - var filter = new ValueStruct(Ids.Filter); - filter.AddStructElement(Ids.FilterOperation, new ValueDInt(8)); // 8 = InstanceIOf - filter.AddStructElement(Ids.AddressCount, new ValueUDInt(0)); - uint[] faddress = new uint[32]; // Unknown, possible dependant on FilterOperation - filter.AddStructElement(Ids.Address, new ValueUDIntArray(faddress)); - filter.AddStructElement(Ids.FilterValue, new ValueRID(Ids.DB_Class_Rid)); - - exploreReq.FilterData = filter; - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - - // Get the datablock information we want further informations from. - var objList = exploreRes.Objects; - - foreach (var ob in objList) - { - // May be this check can be removed, if setting the filter to the DB_Class_Rid is working 100%. - switch (ob.ClassId) - { - case Ids.DB_Class_Rid: - UInt32 relid = ob.RelationId; - UInt32 area = (relid >> 16); - UInt32 num = relid & 0xffff; - if (area == 0x8a0e) - { - var name = (ValueWString)(ob.GetAttribute(Ids.ObjectVariableTypeName)); - DatablockInfo data = new DatablockInfo(); - data.db_block_relid = relid; - data.db_name = name.GetValue(); - data.db_number = num; - dbInfoList.Add(data); - } - break; - } - } - - // Get the TypeInfo RID to RelId from the first response - - // With LID=1 we get the RID back. With this number we can explore further - // informations of this datablock. - // This is neccessary, because informations about instance DBs (e.g. TON) you - // don't get by the RID of the DB, instead of exploring the TON Type RID. - var readlist = new List(); - var values = new List(); - var errors = new List(); - - foreach (var data in dbInfoList) - { - if (data.db_number > 0) - { - // Insert the address - var adr1 = new ItemAddress(); - adr1.AccessArea = data.db_block_relid; - adr1.AccessSubArea = Ids.DB_ValueActual; - adr1.LID.Add(1); - readlist.Add(adr1); - } - } - res = ReadValues(readlist, out values, out errors); - if (res != 0) - { - return res; - } - - // Insert response data into the list - for (int i = 0; i < values.Count; i++) - { - if (errors[i] == 0) - { - var rid = (ValueRID)values[i]; - var data = dbInfoList[i]; - data.db_block_ti_relid = rid.GetValue(); - dbInfoList[i] = data; - } - else - { - // On error, set relid=0, which is then removed in the next step. - // Should we report this for the user? - var data = dbInfoList[i]; - data.db_block_ti_relid = 0; - dbInfoList[i] = data; - } - } - - // Remove elements with db_block_ti_relid == 0. - // This can occur on datablocks which are only in load memory and can't be explored. - dbInfoList.RemoveAll(item => item.db_block_ti_relid == 0); - - return 0; - } - - public int GetTypeInformation(uint exploreId, out List objList) - { - int res; - objList = new List(); - - var exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = exploreId; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; - exploreReq.ExploreParents = 0; - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - objList = exploreRes.Objects; - - return 0; - } - - /// - /// Requests the tag and block comments from the Plc, returned as XML strings. - /// xml_linecomment: - /// The returned XML format differs between between request of I/Q/M/C/T areas and datablocks: - /// I/Q/M/C/T: .... - /// Datablock: .... - /// As "ID" the number for the variable identification is used. - /// - /// xml_dbcomment: - /// The xml-value description generated from our own value xml-serialization for WStringSparseArray. The value key is the language id. - /// Example: - /// DB Kommentar in german de-DEDB comment in english en-US - /// - /// The relation ID for the area you want the comments for, e.g. 0x8a0e0000+db_number, or 0x52 for M-area - /// - /// - /// 0 if no error - public int GetCommentsXml(uint relid, out string xml_linecomment, out string xml_dbcomment) - { - int res; - // With requesting DataInterface_InterfaceDescription, whe would be able to get all informations like the access ids and - // datatype informations, that we get from the other browsing method. Needs to be tested which one is more efficient on network traffic or plc load. - // If we keep use browsing for the comments, at least we would be able to read all information in one request. - xml_linecomment = String.Empty; - xml_dbcomment = String.Empty; - - var exploreReq = new ExploreRequest(ProtocolVersion.V2); - exploreReq.ExploreId = relid; - exploreReq.ExploreRequestId = Ids.None; - exploreReq.ExploreChildsRecursive = 1; - exploreReq.ExploreParents = 0; - - // We want to know the following attributes - exploreReq.AddressList.Add(Ids.ASObjectES_Comment); - exploreReq.AddressList.Add(Ids.DataInterface_LineComments); - - res = SendS7plusFunctionObject(exploreReq); - if (res != 0) - { - return res; - } - m_LastError = 0; - WaitForNewS7plusReceived(m_ReadTimeout); - if (m_LastError != 0) - { - return m_LastError; - } - - var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); - res = checkResponseWithIntegrity(exploreReq, exploreRes); - if (res != 0) - { - return res; - } - - foreach (var obj in exploreRes.Objects) - { - foreach (var att in obj.Attributes) - { - switch (att.Key) - { - case Ids.ASObjectES_Comment: - var att_comment = (ValueWStringSparseArray)att.Value; - xml_dbcomment = att_comment.ToString(); - break; - case Ids.DataInterface_LineComments: - var att_linecomment = (ValueBlobSparseArray)att.Value; - BlobDecompressor bd = new BlobDecompressor(); - var blob_sp = att_linecomment.GetValue(); - // In DBs we get the data with Sparsearray key = 1, in M-Area with key = 2. - // For now, just take the first, don't know where the key ids are for. - foreach (var key in blob_sp.Keys) - { - xml_linecomment = bd.decompress(blob_sp[key].value, 4); // Offset of 4, as we have a header for the zlib dictionary version - break; - } - break; - } - } - } - return 0; - } - } - #endregion -} +#region License +/****************************************************************************** + * S7CommPlusDriver + * + * Copyright (C) 2023 Thomas Wiens, th.wiens@gmx.de + * + * This file is part of S7CommPlusDriver. + * + * S7CommPlusDriver is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + /****************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.IO; +using System.Linq; +using System.Diagnostics; +using S7CommPlusDriver.ClientApi; +using System.Text.RegularExpressions; +using System.Security.Cryptography; + +namespace S7CommPlusDriver +{ + public partial class S7CommPlusConnection + { + #region Private Members + private S7Client m_client; + private MemoryStream m_ReceivedPDU; + private MemoryStream m_ReceivedTempPDU; + private Queue m_ReceivedPDUs = new Queue(); + private Mutex m_Mutex = new Mutex(); + + private bool m_ReceivedNeedMoreDataForCompletePDU; + private bool m_NewS7CommPlusReceived; + private UInt32 m_SessionId; + private UInt32 m_SessionId2; + public UInt32 SessionId2 + { + get { return m_SessionId2; } + private set { m_SessionId2 = value; } + } + + private int m_ReadTimeout = 5000; + private UInt16 m_SequenceNumber = 0; + private UInt32 m_IntegrityId = 0; + private UInt32 m_IntegrityId_Set = 0; + private CommRessources m_CommRessources = new CommRessources(); + + private List dbInfoList; + private List typeInfoList = new List(); + #endregion + + #region Public Members + public int m_LastError = 0; + + #endregion + + #region Private Methods + + private UInt16 GetNextSequenceNumber() + { + if (m_SequenceNumber == UInt16.MaxValue) + { + m_SequenceNumber = 1; + } + else + { + m_SequenceNumber++; + } + return m_SequenceNumber; + } + + // We must count the IntegrityId for different functions of the protocol. + // As a first guess functions for setting variables need separate counters. + // Use the functioncode to differ between the which sequence/integrity counter values. + private UInt32 GetNextIntegrityId(ushort functioncode) + { + UInt32 ret; + switch (functioncode) + { + case Functioncode.SetMultiVariables: + case Functioncode.SetVariable: + case Functioncode.SetVarSubStreamed: + case Functioncode.DeleteObject: + case Functioncode.CreateObject: + if (m_IntegrityId_Set == UInt32.MaxValue) + { + m_IntegrityId_Set = 0; + } + else + { + m_IntegrityId_Set++; + } + ret = m_IntegrityId_Set; + break; + default: + if (m_IntegrityId == UInt32.MaxValue) + { + m_IntegrityId = 0; + } + else + { + m_IntegrityId++; + } + ret = m_IntegrityId; + break; + } + return ret; + } + + private void WaitForNewS7plusReceived(int Timeout) + { + bool Expired = false; + Stopwatch Elapsed = Stopwatch.StartNew(); + bool done = false; + + m_Mutex.WaitOne(); + if (m_ReceivedPDUs.Count > 0) + { + m_ReceivedPDU = m_ReceivedPDUs.Dequeue(); + done = true; + } + m_Mutex.ReleaseMutex(); + + while (!done && !Expired) + { + Thread.Sleep(2); + Expired = Elapsed.ElapsedMilliseconds > Timeout; + m_Mutex.WaitOne(); + if (m_ReceivedPDUs.Count > 0) + { + m_ReceivedPDU = m_ReceivedPDUs.Dequeue(); + done = true; + } + m_Mutex.ReleaseMutex(); + } + + if (Expired) + { + Console.WriteLine("S7CommPlusConnection - WaitForNewS7plusReceived: ERROR: Timeout!"); + m_LastError = S7Consts.errTCPDataReceive; + } + } + + private int SendS7plusFunctionObject(IS7pRequest funcObj) + { + // If we don't have a SessionId, this must be the first CreateObjectRequest, where we use the Id for NullServerSession + if (m_SessionId == 0) + { + funcObj.SessionId = Ids.ObjectNullServerSession; + } + else + { + funcObj.SessionId = m_SessionId; + } + + // Insert SequenceNumber and IntegrityId, if neccessary for object type and state of communication + funcObj.SequenceNumber = GetNextSequenceNumber(); + if (funcObj.WithIntegrityId) + { + funcObj.IntegrityId = GetNextIntegrityId(funcObj.FunctionCode); + } + + MemoryStream stream = new MemoryStream(); + funcObj.Serialize(stream); + return SendS7plusPDUdata(stream.ToArray(), (int)stream.Length, funcObj.ProtocolVersion); + } + + private int SendS7plusPDUdata(byte[] sendPduData, int bytesToSend, byte protoVersion) + { + m_LastError = 0; + + int curSize; + int sourcePos = 0; + int sendLen; + int NegotiatedIsoPduSize = 1024;// TODO: Respect the negotiated TPDU size + + // 4 Byte TPKT Header + // 3 Byte ISO-Header + // 5 Byte TLS Header + 17 Bytes addition from TLS + // 4 Byte S7CommPlus Header + // 4 Byte S7CommPlus Trailer (must fit into last PDU) + int MaxSize = NegotiatedIsoPduSize - 4 - 3 - 5 - 17 - 4 - 4; + byte[] packet = new byte[MaxSize + 4]; //max packet size is always MaxSize + PDU Header + + while (bytesToSend > 0) + { + if (bytesToSend > MaxSize) + { + curSize = MaxSize; + bytesToSend -= MaxSize; + } + else + { + curSize = bytesToSend; + bytesToSend -= curSize; + } + // Header + packet[0] = 0x72; + packet[1] = protoVersion; + packet[2] = (byte)(curSize >> 8); + packet[3] = (byte)(curSize & 0x00FF); + // Data part + Array.Copy(sendPduData, sourcePos, packet, 4, curSize); + sourcePos += curSize; + sendLen = 4 + curSize; + + // Trailer only in last packet + if (bytesToSend == 0) + { + Array.Resize(ref packet, sendLen + 4); //resize only the last package to sendLen + TrailerLen + packet[sendLen] = 0x72; + sendLen++; + packet[sendLen] = protoVersion; + sendLen++; + packet[sendLen] = 0; + sendLen++; + packet[sendLen] = 0; + sendLen++; + } + m_client.Send(packet); + } + return m_LastError; + } + + private void OnDataReceived(byte[] PDU, int len) + { + // In this method, we've got always a complete TPDU (from protocol layer above) without fragmentation + // At this point, we can detect if we receive a fragmented S7CommPlus PDU. + // If not fragmented, then TPKT.Length - 15 is equal of the length in S7CommPlus.Header. + // 15 bytes because: 4 Bytes TPKT.Header.len + 3 Bytes ISO.Header.Len + 4 Bytes S7CommPlus.Header.len + 4 Bytes S7CommPlus.trailer.Len. + // Since the pure userdata of the TPDU comes in here, that is only minus 4 bytes header + 4 bytes trailer. + // + // Special handling for SystemEvents with ProtocolVersion = 0xfe: + // Here's only a header. + // Because of this, the first byte for the ProtocolVersion must be written in then stream at first. + // The datalength must not be written into the stream, because it's not valid on fragmented PDUs + // for the complete length, only for the single fragment. + + // This method is called from a different thread. + // If we use subscriptions or alarming, we may get new data before the last PDU was processed completely. + // First step we push the complete PDU to a queue. + // TODO: m_LastError handling would also not work as expected. This needs some more redesign. + + if (!m_ReceivedNeedMoreDataForCompletePDU) + { + m_ReceivedTempPDU = new MemoryStream(); + } + // S7comm-plus + byte protoVersion; + int pos = 0; + int s7HeaderDataLen = 0; + // Check header + if (PDU[pos] != 0x72) + { + m_ReceivedNeedMoreDataForCompletePDU = false; + m_LastError = S7Consts.errIsoInvalidPDU1; + return; + } + pos++; + protoVersion = PDU[pos]; + if (protoVersion != ProtocolVersion.V1 && protoVersion != ProtocolVersion.V2 && protoVersion != ProtocolVersion.V3 && protoVersion != ProtocolVersion.SystemEvent) + { + m_ReceivedNeedMoreDataForCompletePDU = false; + m_LastError = S7Consts.errIsoInvalidPDU2; + return; + } + // For the first fragment, write the ProtocolVersion into the stream in advance + if (!m_ReceivedNeedMoreDataForCompletePDU) + { + m_ReceivedTempPDU.Write(PDU, pos, 1); + } + pos++; + + // Read the length of the data-part from header + s7HeaderDataLen = GetWordAt(PDU, pos); + pos += 2; + if (s7HeaderDataLen > 0) + { + // Special handling for SystemEvent 0xfe PDUs: + // This only confirms a few data, but also reports major protocol errors (e.g. incorrect sequence numbers). + // The confirms can be discarded (for now), but the errors are relevant, because a connection termination is neccessary. + // As we don't have a trailer on this types, it's not possible that they are transmitted as fragments. + if (protoVersion == ProtocolVersion.SystemEvent) + { + Console.WriteLine("S7CommPlusConnection - OnDataReceived: ProtocolVersion 0xfe SystemEvent received"); + m_ReceivedTempPDU.Write(PDU, pos, s7HeaderDataLen); + pos += s7HeaderDataLen; + // Create SystemEventObject + m_ReceivedNeedMoreDataForCompletePDU = false; + m_ReceivedTempPDU.Position = 0; + m_NewS7CommPlusReceived = false; + + var sysevt = SystemEvent.DeserializeFromPdu(m_ReceivedTempPDU); + if (sysevt.IsFatalError()) + { + Console.WriteLine("S7CommPlusConnection - OnDataReceived: SystemEvent has fatal error"); + // Termination neccessary + m_LastError = S7Consts.errIsoInvalidPDU3; + } + else + { + Console.WriteLine("S7CommPlusConnection - OnDataReceived: SystemEvent with non fatal error, do nothing"); + } + } + else + { + // Copy data part to destination stream + m_ReceivedTempPDU.Write(PDU, pos, s7HeaderDataLen); + pos += s7HeaderDataLen; + // If this is a fragmented PDU, then at this point no trailer + if ((len - 4 - 4) == s7HeaderDataLen) + { + m_ReceivedNeedMoreDataForCompletePDU = false; + m_ReceivedTempPDU.Position = 0; // Set position back to zero, ready for readout + m_NewS7CommPlusReceived = true; + } + else + { + m_ReceivedNeedMoreDataForCompletePDU = true; + } + } + } + + // If a complete (usable) PDU is received, add to the queue (threadsafe) for readout + if (m_NewS7CommPlusReceived) + { + // Push complete PDU to the queue + m_Mutex.WaitOne(); + m_ReceivedPDUs.Enqueue(m_ReceivedTempPDU); + m_Mutex.ReleaseMutex(); + m_NewS7CommPlusReceived = false; + } + } + + private UInt16 GetWordAt(byte[] Buffer, int Pos) + { + return (UInt16)((Buffer[Pos] << 8) | Buffer[Pos + 1]); + } + + private void SetWordAt(byte[] Buffer, int Pos, UInt16 Value) + { + Buffer[Pos] = (byte)(Value >> 8); + Buffer[Pos + 1] = (byte)(Value & 0x00FF); + } + + private void printBuf(byte[] b) + { + for (int i = 0; i < b.Length; i++) + { + Console.Write("0x" + String.Format("{0:X02} ", b[i])); + } + Console.Write(Environment.NewLine); + } + + private int checkResponseWithIntegrity(IS7pRequest request, IS7pResponse response) + { + if (response == null) + { + //Console.WriteLine("checkResponseWithIntegrity: ERROR! response == null"); + return S7Consts.errIsoInvalidPDU4; + } + if (request.SequenceNumber != response.SequenceNumber) + { + //Console.WriteLine(String.Format("checkResponseWithIntegrity: ERROR! SeqenceNumber of Response ({0}) doesn't match Request ({1})", response.SequenceNumber, request.SequenceNumber)); + return S7Consts.errIsoInvalidPDU5; + } + // Overflow is possible and allowed + UInt32 reqIntegCheck = (UInt32)request.SequenceNumber + request.IntegrityId; + if (response.IntegrityId != reqIntegCheck) + { + Console.WriteLine(String.Format("checkResponseWithIntegrity: ERROR! IntegrityId of the Response ({0}) doesn't match Request ({1})", response.IntegrityId, reqIntegCheck)); + // Don't return this as error so far + } + return 0; + } + #endregion + + #region Public Methods + /// + /// Establishes a connection to the PLC. + /// + /// PLC IP address + /// PLC password (if set) + /// read timeout in milliseconds (default: 5000 ms) + /// + public int Connect(string address, string password = "", string username = "", int timeoutMs = 5000) + { + if (timeoutMs > 0) + { + m_ReadTimeout = timeoutMs; + } + + m_LastError = 0; + int res; + Stopwatch Elapsed = Stopwatch.StartNew(); + m_client = new S7Client(); + m_client.OnDataReceived = this.OnDataReceived; + + m_client.SetConnectionParams(address, 0x0600, Encoding.ASCII.GetBytes("SIMATIC-ROOT-HMI")); + try + { + m_client.Connect(); + } + catch + { + m_client.Disconnect(); + throw; + } + + #region Step 1: Unencrypted InitSSL Request / Response + + InitSslRequest sslReq = new InitSslRequest(ProtocolVersion.V1, 0, 0); + res = SendS7plusFunctionObject(sslReq); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + InitSslResponse sslRes; + sslRes = InitSslResponse.DeserializeFromPdu(m_ReceivedPDU); + if (sslRes == null) + { + m_client.Disconnect(); + return S7Consts.errInitSslResponse; + } + + #endregion + + #region Step 2: Activate TLS. Everything from here onwards is TLS encrypted. + + res = m_client.SslActivate(); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + + #endregion + + #region Step 3: CreateObjectRequest / Response (with TLS) + + var createObjReq = new CreateObjectRequest(ProtocolVersion.V1, 0, false); + createObjReq.SetNullServerSessionData(); + res = SendS7plusFunctionObject(createObjReq); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var createObjRes = CreateObjectResponse.DeserializeFromPdu(m_ReceivedPDU); + if (createObjRes == null) + { + //Console.WriteLine("S7CommPlusConnection - Connect: CreateObjectResponse with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU6; + } + // There are (always?) at least two IDs in the response. + // Usually the first is used for polling data, and the 2nd for jobs which use notifications, e.g. alarming, subscriptions. + m_SessionId = createObjRes.ObjectIds[0]; + m_SessionId2 = createObjRes.ObjectIds[1]; + //Console.WriteLine("S7CommPlusConnection - Connect: Using SessionId=0x" + String.Format("{0:X04}", m_SessionId)); + + // Evaluate Struct 314 + PValue sval = createObjRes.ResponseObject.GetAttribute(Ids.ServerSessionVersion); + ValueStruct serverSession = (ValueStruct)sval; + + #endregion + + #region Step 4: SetMultiVariablesRequest / Response + + var setMultiVarReq = new SetMultiVariablesRequest(ProtocolVersion.V2); + setMultiVarReq.SetSessionSetupData(m_SessionId, serverSession); + res = SendS7plusFunctionObject(setMultiVarReq); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var setMultiVarRes = SetMultiVariablesResponse.DeserializeFromPdu(m_ReceivedPDU); + if (setMultiVarRes == null) + { + //Console.WriteLine("S7CommPlusConnection - Connect: SetMultiVariablesResponse with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU7; + } + + #endregion + + #region Step 5: Read SystemLimits + res = m_CommRessources.ReadMax(this); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + #endregion + + #region Step 6: Password + res = legitimate(serverSession, password, username); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + #endregion + + // If everything has been error-free up to this point, then the connection has been established successfully. + Console.WriteLine("S7CommPlusConnection - Connect: Time for connection establishment: " + Elapsed.ElapsedMilliseconds + " ms."); + return 0; + } + + public void Disconnect() + { + DeleteObject(m_SessionId); + m_client.Disconnect(); + } + + /// + /// Deletes the object with the given Id. + /// + /// The object Id to delete + /// 0 on success + private int DeleteObject(uint deleteObjectId) + { + int res; + var delObjReq = new DeleteObjectRequest(ProtocolVersion.V2); + delObjReq.DeleteObjectId = deleteObjectId; + res = SendS7plusFunctionObject(delObjReq); + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + // If we delete our own session id, then there's no IntegrityId in the response. + // And the error code gives an error, but not a fatal one. + // If we delete another object, there should be an IntegrityId in the response, and + // the response gives no error. + if (deleteObjectId == m_SessionId) + { + var delObjRes = DeleteObjectResponse.DeserializeFromPdu(m_ReceivedPDU, false); + Trace.WriteLine("S7CommPlusConnection - DeleteSession: Deleted our own Session Id object, not checking the response."); + m_SessionId = 0; // not valid anymore + m_SessionId2 = 0; + } + else + { + var delObjRes = DeleteObjectResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(delObjReq, delObjRes); + if (res != 0) + { + return res; + } + if (delObjRes.ReturnValue != 0) + { + Console.WriteLine("S7CommPlusConnection - DeleteSession: Executed with Error! ReturnValue=" + delObjRes.ReturnValue); + res = -1; + } + } + return res; + } + + public int ReadValues(List addresslist, out List values, out List errors) + { + // The requester must pass the internal type with the request, otherwise not all return values can be converted automatically. + // For example, strings are transmitted as UInt-Array. + values = new List(); + errors = new List(); + // Initialize error fields to error value + for (int i = 0; i < addresslist.Count; i++) + { + values.Add(null); + errors.Add(0xffffffffffffffff); + } + + // Split request into chunks, taking the MaxTags per request into account + int chunk_startIndex = 0; + int count_perChunk = 0; + do + { + int res; + var getMultiVarReq = new GetMultiVariablesRequest(ProtocolVersion.V2); + + getMultiVarReq.AddressList.Clear(); + count_perChunk = 0; + while (count_perChunk < m_CommRessources.TagsPerReadRequestMax && (chunk_startIndex + count_perChunk) < addresslist.Count) + { + getMultiVarReq.AddressList.Add(addresslist[chunk_startIndex + count_perChunk]); + count_perChunk++; + } + + res = SendS7plusFunctionObject(getMultiVarReq); + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var getMultiVarRes = GetMultiVariablesResponse.DeserializeFromPdu(m_ReceivedPDU); + res = checkResponseWithIntegrity(getMultiVarReq, getMultiVarRes); + if (res != 0) + { + return res; + } + // ReturnValue shows also an error, if only one single variable could not be read + if (getMultiVarRes.ReturnValue != 0) + { + Console.WriteLine("S7CommPlusConnection - ReadValues: Executed with Error! ReturnValue=" + getMultiVarRes.ReturnValue); + } + + // TODO: If a variable could not be read, there is no value, but there is an ErrorValue. + // The user must therefore check whether Value != null. Maybe there's a more elegant solution. + foreach (var v in getMultiVarRes.Values) + { + values[chunk_startIndex + (int)v.Key - 1] = v.Value; + // Initialize error to 0, will be overwritten below if there was an error on an item. + errors[chunk_startIndex + (int)v.Key - 1] = 0; + } + + foreach (var ev in getMultiVarRes.ErrorValues) + { + errors[chunk_startIndex + (int)ev.Key - 1] = ev.Value; + } + chunk_startIndex += count_perChunk; + + } while (chunk_startIndex < addresslist.Count); + + return m_LastError; + } + + public int WriteValues(List addresslist, List values, out List errors) + { + int res; + errors = new List(); + for (int i = 0; i < addresslist.Count; i++) + { + // Initialize to no error value, as there's no explicit value for write success. + errors.Add(0); + } + + // Split request into chunks, taking the MaxTags per request into account + int chunk_startIndex = 0; + int count_perChunk = 0; + do + { + var setMultiVarReq = new SetMultiVariablesRequest(ProtocolVersion.V2); + setMultiVarReq.AddressListVar.Clear(); + setMultiVarReq.ValueList.Clear(); + count_perChunk = 0; + while (count_perChunk < m_CommRessources.TagsPerWriteRequestMax && (chunk_startIndex + count_perChunk) < addresslist.Count) + { + setMultiVarReq.AddressListVar.Add(addresslist[chunk_startIndex + count_perChunk]); + setMultiVarReq.ValueList.Add(values[chunk_startIndex + count_perChunk]); + count_perChunk++; + } + + res = SendS7plusFunctionObject(setMultiVarReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var setMultiVarRes = SetMultiVariablesResponse.DeserializeFromPdu(m_ReceivedPDU); + res = checkResponseWithIntegrity(setMultiVarReq, setMultiVarRes); + if (res != 0) + { + return res; + } + // ReturnValue shows also an error, if only one single variable could not be written + if (setMultiVarRes.ReturnValue != 0) + { + Console.WriteLine("S7CommPlusConnection - WriteValues: Write with errors. ReturnValue=" + setMultiVarRes.ReturnValue); + } + + foreach (var ev in setMultiVarRes.ErrorValues) + { + errors[chunk_startIndex + (int)ev.Key - 1] = ev.Value; + } + chunk_startIndex += count_perChunk; + + } while (chunk_startIndex < addresslist.Count); + + return m_LastError; + } + + public int SetPlcOperatingState(Int32 state) + { + int res; + var setVarReq = new SetVariableRequest(ProtocolVersion.V2); + setVarReq.InObjectId = Ids.NativeObjects_theCPUexecUnit_Rid; + setVarReq.Address = Ids.CPUexecUnit_operatingStateReq; + setVarReq.Value = new ValueDInt(state); + + res = SendS7plusFunctionObject(setVarReq); + if (res != 0) + { + m_client.Disconnect(); + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + m_client.Disconnect(); + return m_LastError; + } + + var setVarRes = SetVariableResponse.DeserializeFromPdu(m_ReceivedPDU); + if (setVarRes == null) + { + //Console.WriteLine("S7CommPlusConnection - Connect: SetVariableResponse with Error!"); + m_client.Disconnect(); + return S7Consts.errIsoInvalidPDU12; + } + + return 0; + } + + public int Browse(out List varInfoList) + { + int res; + varInfoList = new List(); + Browser vars = new Browser(); + ExploreRequest exploreReq; + ExploreResponse exploreRes; + + #region Read all objects + + var exploreData = new List(); + + exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = Ids.NativeObjects_thePLCProgram_Rid; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 0; + + // We want to know the following attributes + exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); + exploreReq.AddressList.Add(Ids.Block_BlockNumber); + exploreReq.AddressList.Add(Ids.ASObjectES_Comment); + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + + #endregion + + #region Evaluate all data blocks that then need to be browsed + + var obj = exploreRes.Objects.First(o => o.ClassId == Ids.PLCProgram_Class_Rid); + + foreach (var ob in obj.GetObjects()) + { + switch (ob.ClassId) + { + case Ids.DB_Class_Rid: + UInt32 relid = ob.RelationId; + UInt32 area = (relid >> 16); + UInt32 num = relid & 0xffff; + if (area == 0x8a0e) + { + var name = (ValueWString)(ob.GetAttribute(Ids.ObjectVariableTypeName)); + BrowseData data = new BrowseData(); + data.db_block_relid = relid; + data.db_name = name.GetValue(); + data.db_number = num; + exploreData.Add(data); + } + break; + } + } + + #endregion + + #region Determine the TypeInfo RID to the RelId from the first response + // By querying LID = 1 from all DBs you get the RID back with which the type information can be queried. + // This is neccessary because, for example, with instance DBs (e.g. TON), the type information must + // not be accessed via the RID of the DB, but of the RID of the TON. + var readlist = new List(); + var values = new List(); + var errors = new List(); + + foreach (var data in exploreData) + { + if (data.db_number > 0) // only process datablocks here, no marker, timer etc. + { + // Insert the variable address + var adr1 = new ItemAddress(); + adr1.AccessArea = data.db_block_relid; + adr1.AccessSubArea = Ids.DB_ValueActual; + adr1.LID.Add(1); + readlist.Add(adr1); + } + } + res = ReadValues(readlist, out values, out errors); + if (res != 0) + { + return res; + } + #endregion + + #region Pass the preliminary information for recombination to ExploreSymbols + + // Add the response information to the list + for (int i = 0; i < values.Count; i++) + { + if (errors[i] == 0) + { + ValueRID rid = (ValueRID)values[i]; + var data = exploreData[i]; + data.db_block_ti_relid = rid.GetValue(); + exploreData[i] = data; + } + else + { + // On error, set the relid to zero, will be removed from the list in the next step. + // TODO: Report this as an error? + var data = exploreData[i]; + data.db_block_ti_relid = 0; + exploreData[i] = data; + } + } + // Remove elements with db_block_ti_relid == 0. This occurs e.g. on datablocks only present in load memory. + // The informations can't be used any further (at least not for variable access). + exploreData.RemoveAll(item => item.db_block_ti_relid == 0); + + foreach (var ed in exploreData) + { + vars.AddBlockNode(eNodeType.Root, ed.db_name, ed.db_block_relid, ed.db_block_ti_relid); + } + + // Add IQMCT areas manually + vars.AddBlockNode(eNodeType.Root, "IArea", Ids.NativeObjects_theIArea_Rid, 0x90010000); + vars.AddBlockNode(eNodeType.Root, "QArea", Ids.NativeObjects_theQArea_Rid, 0x90020000); + vars.AddBlockNode(eNodeType.Root, "MArea", Ids.NativeObjects_theMArea_Rid, 0x90030000); + vars.AddBlockNode(eNodeType.Root, "S7Timers", Ids.NativeObjects_theS7Timers_Rid, 0x90050000); + vars.AddBlockNode(eNodeType.Root, "S7Counters", Ids.NativeObjects_theS7Counters_Rid, 0x90060000); + + #endregion + + #region Read the Type Info Container (as a single big PDU, must be proven to be the way to go in big programs) + exploreReq = new ExploreRequest(ProtocolVersion.V2); + // With ObjectOMSTypeInfoContainer we get all in a big PDU (with maybe hundreds of fragments) + exploreReq.ExploreId = Ids.ObjectOMSTypeInfoContainer; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 0; + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + #endregion + + #region Process the response, and build the complete variables list + exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + var objs = exploreRes.Objects.First(o => o.ClassId == Ids.ClassOMSTypeInfoContainer); + + vars.SetTypeInfoContainerObjects(objs.GetObjects()); + vars.BuildTree(); + vars.BuildFlatList(); + varInfoList = vars.GetVarInfoList(); + #endregion + + return 0; + } + + /// + /// Gets the first level of a tag symbol string. Removes the " used to escape special chars. + /// + /// plc tag symbol + /// The first level of the symbol string + /// Symbol syntax error + private string parseSymbolLevel(ref string symbol) + { + if (symbol.StartsWith("\"")) + { + int idx = symbol.IndexOf('"', 1); + if (idx < 0) throw new Exception("Symbol syntax error"); + string lvl = symbol.Substring(1, idx - 1); + symbol = symbol.Remove(0, idx + 1); + if (symbol.StartsWith(".")) symbol = symbol.Remove(0, 1); + return lvl; + } + else + { + int idx = symbol.IndexOf('.'); + int idx2 = symbol.IndexOf('[', 1); + if (idx2 >= 0 && (idx2 < idx || idx < 0)) idx = idx2; + if (idx >= 0) + { + string lvl = symbol.Substring(0, idx); + symbol = symbol.Remove(0, idx); + if (symbol.StartsWith(".")) symbol = symbol.Remove(0, 1); + return lvl; + } + else + { + string lvl = symbol; + symbol = ""; + return lvl; + } + } + } + + /// + /// Gets the typeinfo by given ti relid from the internal buffer. If it's not found in the buffer + /// it's fetched from the PLC and stored in the buffer. + /// + /// type info relid + /// type info + /// Could not get type info + public PObject getTypeInfoByRelId(uint ti_relid) + { + PObject pObj = typeInfoList.Find(ti => ti.RelationId == ti_relid); + if (pObj == null) + { + // Type info not found in list, request it from plc + List newPObj = new List(); + if (GetTypeInformation(ti_relid, out newPObj) != 0) throw new Exception("Could not get type info"); + typeInfoList.AddRange(newPObj); + // Try again + pObj = typeInfoList.Find(ti => ti.RelationId == ti_relid); + } + return pObj; + } + + /// + /// Calculates the access sequence for 1 dimensional arrays. + /// + /// plc tag symbol + /// Var type that holds the dim info + /// used to build access sequence + /// Symbol syntax error + private void calcAccessSeqFor1DimArray(ref string symbol, PVartypeListElement varType, VarInfo varInfo) + { + Regex re = new Regex(@"^\[(-?\d+)\]"); + Match m = re.Match(symbol); + if (!m.Success) throw new Exception("Symbol syntax error"); + parseSymbolLevel(ref symbol); // remove index from symbol string + int arrayIndex = int.Parse(m.Groups[1].Value); + + var ioit = (IOffsetInfoType_1Dim)varType.OffsetInfoType; + uint arrayElementCount = ioit.GetArrayElementCount(); + int arrayLowerBounds = ioit.GetArrayLowerBounds(); + + if (arrayIndex - arrayLowerBounds > arrayElementCount) throw new Exception("Out of bounds"); + if (arrayIndex < arrayLowerBounds) throw new Exception("Out of bounds"); + varInfo.AccessSequence += "." + String.Format("{0:X}", arrayIndex - arrayLowerBounds); + if (varType.OffsetInfoType.HasRelation()) varInfo.AccessSequence += ".1"; // additional ".1" for array of struct + } + + /// + /// Calculates the access sequence for multi-dimensional arrays. + /// + /// plc tag symbol + /// Var type that holds the dim info + /// used to build access sequence + /// Symbol syntax error + private void calcAccessSeqForMDimArray(ref string symbol, PVartypeListElement varType, VarInfo varInfo) + { + Regex re = new Regex(@"^\[( ?-?\d+ ?(, ?-?\d+ ?)+)\]"); + Match m = re.Match(symbol); + if (!m.Success) throw new Exception("Symbol syntax error"); + parseSymbolLevel(ref symbol); // remove index from symbol string + string idxs = m.Groups[1].Value.Replace(" ", ""); + + int[] indexes = Array.ConvertAll(idxs.Split(','), e => int.Parse(e)); + var ioit = (IOffsetInfoType_MDim)varType.OffsetInfoType; + uint[] MdimArrayElementCount = (uint[])ioit.GetMdimArrayElementCount().Clone(); + int[] MdimArrayLowerBounds = ioit.GetMdimArrayLowerBounds(); + + // check dim count + int dimCount = MdimArrayElementCount.Aggregate(0, (acc, act) => acc += (act > 0) ? 1 : 0); + if (dimCount != indexes.Count()) throw new Exception("Out of bounds"); + // check bounds + for (int i = 0; i < dimCount; ++i) + { + indexes[i] = (indexes[i] - MdimArrayLowerBounds[dimCount - i - 1]); + if (indexes[i] > MdimArrayElementCount[dimCount - i - 1]) throw new Exception("Out of bounds"); + if (indexes[i] < 0) throw new Exception("Out of bounds"); + } + + // calc dim size + if (varType.Softdatatype == Softdatatype.S7COMMP_SOFTDATATYPE_BBOOL) + { + MdimArrayElementCount[0] += 8 - MdimArrayElementCount[0] % 8; // for bool must be a mutiple of 8! + } + uint[] dimSize = new uint[dimCount]; + uint g = 1; + for (int i = 0; i < dimCount - 1; ++i) + { + dimSize[i] = g; + g *= MdimArrayElementCount[i]; + } + dimSize[dimCount - 1] = g; + + // calc id + int arrayIndex = 0; + for (int i = 0; i < dimCount; ++i) + { + arrayIndex += indexes[i] * (int)dimSize[dimCount - i - 1]; + } + + varInfo.AccessSequence += "." + String.Format("{0:X}", arrayIndex); + if (varType.OffsetInfoType.HasRelation()) varInfo.AccessSequence += ".1"; // additional ".1" for array of struct + } + + /// + /// Browses the symbol level by level recursively. Fetches missing type info automatically from the plc. + /// + /// type info relid + /// plc tag symbol + /// used to build access sequence + /// plc tag or null if not found + /// Symbol syntax error, Out of bounds + private PlcTag browsePlcTagBySymbol(uint ti_relid, ref string symbol, VarInfo varInfo) + { + PObject pObj = getTypeInfoByRelId(ti_relid); + if (pObj == null) throw new Exception("Could not get type info"); + string levelName = parseSymbolLevel(ref symbol); + // find level name of symbol in var list + int idx = pObj.VarnameList?.Names?.IndexOf(levelName) ?? -1; + if (idx < 0) return null; + PVartypeListElement varType = pObj.VartypeList.Elements[idx]; + varInfo.AccessSequence += "." + String.Format("{0:X}", varType.LID); + bool is1Dim = false; + if (varType.OffsetInfoType.Is1Dim()) + { + if (symbol == "") + { + is1Dim = true; + } + else + { + calcAccessSeqFor1DimArray(ref symbol, varType, varInfo); + } + } + if (varType.OffsetInfoType.IsMDim()) + { + calcAccessSeqForMDimArray(ref symbol, varType, varInfo); + } + if (varType.OffsetInfoType.HasRelation()) + { + if (symbol.Length <= 0 && varType.Softdatatype == Softdatatype.S7COMMP_SOFTDATATYPE_DTL) + { + return PlcTags.TagFactory(varInfo.Name, new ItemAddress(varInfo.AccessSequence), varType.Softdatatype, is1Dim); + } + if (symbol.Length <= 0) + { + return null; + } + else + { + var ioit = (IOffsetInfoType_Relation)varType.OffsetInfoType; + return browsePlcTagBySymbol(ioit.GetRelationId(), ref symbol, varInfo); + } + } + else + { + return PlcTags.TagFactory(varInfo.Name, new ItemAddress(varInfo.AccessSequence), varType.Softdatatype, is1Dim); + } + } + + /// + /// Get the plc tag for the given plc tag symbol. + /// + /// plc tag symbol + /// plc tag, returns null if plc tag could not be found + public PlcTag getPlcTagBySymbol(string symbol) + { + VarInfo varInfo = new VarInfo(); + varInfo.Name = symbol; + // make sure we have the db list + if (dbInfoList == null) + { + if (GetListOfDatablocks(out dbInfoList) != 0) { return null; } + } + string levelName = parseSymbolLevel(ref symbol); + // find db by first level name of symbol + DatablockInfo dbInfo = dbInfoList.Find(dbi => dbi.db_name == levelName); + if (dbInfo != null) + { + varInfo.AccessSequence = String.Format("{0:X}", dbInfo.db_block_relid); + return browsePlcTagBySymbol(dbInfo.db_block_ti_relid, ref symbol, varInfo); + } + else + { + symbol = varInfo.Name; + // Merker + varInfo.AccessSequence = String.Format("{0:X}", Ids.NativeObjects_theMArea_Rid); + PlcTag tag = browsePlcTagBySymbol(0x90030000, ref symbol, varInfo); + if (tag != null) return tag; + symbol = varInfo.Name; + // Outputs + varInfo.AccessSequence = String.Format("{0:X}", Ids.NativeObjects_theQArea_Rid); + tag = browsePlcTagBySymbol(0x90020000, ref symbol, varInfo); + if (tag != null) return tag; + symbol = varInfo.Name; + // Inputs + varInfo.AccessSequence = String.Format("{0:X}", Ids.NativeObjects_theIArea_Rid); + tag = browsePlcTagBySymbol(0x90010000, ref symbol, varInfo); + if (tag != null) return tag; + // TODO: implement s5timers and counters... no one uses them anymore anyway + } + return null; + } + + public class BrowseEntry + { + public string Name; + public uint Softdatatype; + public UInt32 LID; + public UInt32 SymbolCrc; + public string AccessSequence; + }; + + public class BrowseData + { + public string db_name; // Name of the datablock + public UInt32 db_number; // Number of the datablock + public UInt32 db_block_relid; // RID of the datablock + public UInt32 db_block_ti_relid; // Type-Info RID of the datablock + public List variables = new List(); // Variables inside the datablock + }; + + public class DatablockInfo + { + public string db_name; // Name of the datablock + public UInt32 db_number; // Number of the datablock + public UInt32 db_block_relid; // RID of the datablock + public UInt32 db_block_ti_relid; // Type-Info RID of the datablock + }; + + public int GetListOfDatablocks(out List dbInfoList) + { + int res; + + dbInfoList = new List(); + + var exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = Ids.NativeObjects_thePLCProgram_Rid; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 0; + + // Add the attributes we need in the response + exploreReq.AddressList.Add(Ids.ObjectVariableTypeName); + + // Set filter on Id for Datablock Class RID. With this filter, we only + // get informations from datablocks, and not other blocks we don't need here. + var filter = new ValueStruct(Ids.Filter); + filter.AddStructElement(Ids.FilterOperation, new ValueDInt(8)); // 8 = InstanceIOf + filter.AddStructElement(Ids.AddressCount, new ValueUDInt(0)); + uint[] faddress = new uint[32]; // Unknown, possible dependant on FilterOperation + filter.AddStructElement(Ids.Address, new ValueUDIntArray(faddress)); + filter.AddStructElement(Ids.FilterValue, new ValueRID(Ids.DB_Class_Rid)); + + exploreReq.FilterData = filter; + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + + // Get the datablock information we want further informations from. + var objList = exploreRes.Objects; + + foreach (var ob in objList) + { + // May be this check can be removed, if setting the filter to the DB_Class_Rid is working 100%. + switch (ob.ClassId) + { + case Ids.DB_Class_Rid: + UInt32 relid = ob.RelationId; + UInt32 area = (relid >> 16); + UInt32 num = relid & 0xffff; + if (area == 0x8a0e) + { + var name = (ValueWString)(ob.GetAttribute(Ids.ObjectVariableTypeName)); + DatablockInfo data = new DatablockInfo(); + data.db_block_relid = relid; + data.db_name = name.GetValue(); + data.db_number = num; + dbInfoList.Add(data); + } + break; + } + } + + // Get the TypeInfo RID to RelId from the first response + + // With LID=1 we get the RID back. With this number we can explore further + // informations of this datablock. + // This is neccessary, because informations about instance DBs (e.g. TON) you + // don't get by the RID of the DB, instead of exploring the TON Type RID. + var readlist = new List(); + var values = new List(); + var errors = new List(); + + foreach (var data in dbInfoList) + { + if (data.db_number > 0) + { + // Insert the address + var adr1 = new ItemAddress(); + adr1.AccessArea = data.db_block_relid; + adr1.AccessSubArea = Ids.DB_ValueActual; + adr1.LID.Add(1); + readlist.Add(adr1); + } + } + res = ReadValues(readlist, out values, out errors); + if (res != 0) + { + return res; + } + + // Insert response data into the list + for (int i = 0; i < values.Count; i++) + { + if (errors[i] == 0) + { + var rid = (ValueRID)values[i]; + var data = dbInfoList[i]; + data.db_block_ti_relid = rid.GetValue(); + dbInfoList[i] = data; + } + else + { + // On error, set relid=0, which is then removed in the next step. + // Should we report this for the user? + var data = dbInfoList[i]; + data.db_block_ti_relid = 0; + dbInfoList[i] = data; + } + } + + // Remove elements with db_block_ti_relid == 0. + // This can occur on datablocks which are only in load memory and can't be explored. + dbInfoList.RemoveAll(item => item.db_block_ti_relid == 0); + + return 0; + } + + public int GetTypeInformation(uint exploreId, out List objList) + { + int res; + objList = new List(); + + var exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = exploreId; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 0; + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + objList = exploreRes.Objects; + + return 0; + } + + /// + /// Requests the tag and block comments from the Plc, returned as XML strings. + /// xml_linecomment: + /// The returned XML format differs between between request of I/Q/M/C/T areas and datablocks: + /// I/Q/M/C/T: .... + /// Datablock: .... + /// As "ID" the number for the variable identification is used. + /// + /// xml_dbcomment: + /// The xml-value description generated from our own value xml-serialization for WStringSparseArray. The value key is the language id. + /// Example: + /// DB Kommentar in german de-DEDB comment in english en-US + /// + /// The relation ID for the area you want the comments for, e.g. 0x8a0e0000+db_number, or 0x52 for M-area + /// + /// + /// 0 if no error + public int GetCommentsXml(uint relid, out string xml_linecomment, out string xml_dbcomment) + { + int res; + // With requesting DataInterface_InterfaceDescription, whe would be able to get all informations like the access ids and + // datatype informations, that we get from the other browsing method. Needs to be tested which one is more efficient on network traffic or plc load. + // If we keep use browsing for the comments, at least we would be able to read all information in one request. + xml_linecomment = String.Empty; + xml_dbcomment = String.Empty; + + var exploreReq = new ExploreRequest(ProtocolVersion.V2); + exploreReq.ExploreId = relid; + exploreReq.ExploreRequestId = Ids.None; + exploreReq.ExploreChildsRecursive = 1; + exploreReq.ExploreParents = 0; + + // We want to know the following attributes + exploreReq.AddressList.Add(Ids.ASObjectES_Comment); + exploreReq.AddressList.Add(Ids.DataInterface_LineComments); + + res = SendS7plusFunctionObject(exploreReq); + if (res != 0) + { + return res; + } + m_LastError = 0; + WaitForNewS7plusReceived(m_ReadTimeout); + if (m_LastError != 0) + { + return m_LastError; + } + + var exploreRes = ExploreResponse.DeserializeFromPdu(m_ReceivedPDU, true); + res = checkResponseWithIntegrity(exploreReq, exploreRes); + if (res != 0) + { + return res; + } + + foreach (var obj in exploreRes.Objects) + { + foreach (var att in obj.Attributes) + { + switch (att.Key) + { + case Ids.ASObjectES_Comment: + var att_comment = (ValueWStringSparseArray)att.Value; + xml_dbcomment = att_comment.ToString(); + break; + case Ids.DataInterface_LineComments: + var att_linecomment = (ValueBlobSparseArray)att.Value; + BlobDecompressor bd = new BlobDecompressor(); + var blob_sp = att_linecomment.GetValue(); + // In DBs we get the data with Sparsearray key = 1, in M-Area with key = 2. + // For now, just take the first, don't know where the key ids are for. + foreach (var key in blob_sp.Keys) + { + xml_linecomment = bd.decompress(blob_sp[key].value, 4); // Offset of 4, as we have a header for the zlib dictionary version + break; + } + break; + } + } + } + return 0; + } + } + #endregion +}