From 4b04ed74a154197451c59e5250ac7e96f27a5e1f Mon Sep 17 00:00:00 2001 From: Buchter Date: Thu, 19 Apr 2018 12:09:30 +0200 Subject: [PATCH 1/5] Fixed bug regarding size calculation of small S7 Structs There was an Error when you had Structs conaining less than 8 Bits. The size calculation in this case returned 0 and the Plc.ReadClass() method throwed an excpetion. Structs in Step7 within da DataBlock always starts with adresses that can by devided by two. The extended code ensures the correct size even if there are a couple of structs in a DataBlock containing only a few bits. --- S7.Net/Types/Class.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 794b469..26f4742 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -99,6 +99,10 @@ namespace S7.Net.Types numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType); } } + // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; return (int)numBytes; } From 0d1bc472c8f7c718cced1717d8dc3b8621888d34 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 27 Apr 2018 16:00:41 +0200 Subject: [PATCH 2/5] Added BitAdr to DataItem and fixed bug in ReadMultipleVars on VarType.Bit --- S7.Net.UnitTest/S7NetTests.cs | 64 +++++++++++++++++++++++++++++------ S7.Net/PLC.cs | 18 ++++++++-- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/S7.Net.UnitTest/S7NetTests.cs b/S7.Net.UnitTest/S7NetTests.cs index 8cb75df..107a90f 100644 --- a/S7.Net.UnitTest/S7NetTests.cs +++ b/S7.Net.UnitTest/S7NetTests.cs @@ -386,19 +386,42 @@ namespace S7.Net.UnitTest { Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); - ushort val = 16384; - plc.Write("DB2.DBW16384", val); - ushort result = (ushort)plc.Read("DB2.DBW16384"); - Assert.AreEqual(val, result, "A ushort goes from 0 to 64512"); + bool val = true; + plc.Write("DB2.DBX0.5", val); + bool result = (bool)plc.Read("DB2.DBX0.5"); + Assert.AreEqual(val, result); - ushort val2 = 129; - plc.Write("DB2.DBW16", val2); - ushort result2 = (ushort)plc.Read("DB2.DBW16"); - Assert.AreEqual(val2, result2, "A ushort goes from 0 to 64512"); + ushort val1 = 16384; + plc.Write("DB2.DBW16384", val1); + ushort result1 = (ushort)plc.Read("DB2.DBW16384"); + Assert.AreEqual(val1, result1, "A ushort goes from 0 to 64512"); + + bool val2 = true; + plc.Write("DB2.DBX8192.7", val2); + bool result2 = (bool)plc.Read("DB2.DBX8192.7"); + Assert.AreEqual(val2, result2); + + ushort val3 = 129; + plc.Write("DB2.DBW16", val3); + ushort result3 = (ushort)plc.Read("DB2.DBW16"); + Assert.AreEqual(val3, result3, "A ushort goes from 0 to 64512"); + + bool val4 = true; + plc.Write("DB2.DBX16384.6", val4); + bool result4 = (bool)plc.Read("DB2.DBX16384.6"); + Assert.AreEqual(val4, result4); var dataItems = new List() { new DataItem + { + Count = 1, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 0, + BitAdr = 5, + VarType = VarType.Bit + },new DataItem { Count = 1, DataType = DataType.DataBlock, @@ -407,19 +430,40 @@ namespace S7.Net.UnitTest VarType = VarType.Word }, new DataItem + { + Count = 1, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 8192, + BitAdr = 7, + VarType = VarType.Bit + }, + new DataItem { Count = 1, DataType = DataType.DataBlock, DB = 2, StartByteAdr = 16, VarType = VarType.Word - } + }, + new DataItem + { + Count = 1, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 16384, + BitAdr = 6, + VarType = VarType.Bit + }, }; plc.ReadMultipleVars(dataItems); Assert.AreEqual(dataItems[0].Value, val); - Assert.AreEqual(dataItems[1].Value, val2); + Assert.AreEqual(dataItems[1].Value, val1); + Assert.AreEqual(dataItems[2].Value, val2); + Assert.AreEqual(dataItems[3].Value, val3); + Assert.AreEqual(dataItems[4].Value, val4); } /// diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index 67ad57a..de498cd 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -301,9 +301,16 @@ namespace S7.Net if (bReceive[21] != 0xff) throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - int offset = 25; + int offset = 21; foreach (var dataItem in dataItems) { + // check for Return Code = Success + if (bReceive[offset] != 0xff) + throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); + + // to Data bytes + offset += 4; + int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count); byte[] bytes = new byte[byteCnt]; @@ -312,9 +319,14 @@ namespace S7.Net bytes[i] = bReceive[i + offset]; } - offset += byteCnt + 4; + // next Item + offset += byteCnt; - dataItem.Value = ParseBytes(dataItem.VarType, bytes, dataItem.Count); + // Fill byte in response + if (dataItem.VarType == VarType.Bit) + offset++; + + dataItem.Value = ParseBytes(dataItem.VarType, bytes, dataItem.Count, dataItem.BitAdr); } } catch (SocketException socketException) From b3458a8304d0e0a6a1236e4de05be0d9cf27757e Mon Sep 17 00:00:00 2001 From: Michele Cattafesta Date: Sat, 5 May 2018 23:24:06 +0100 Subject: [PATCH 3/5] unit tests --- S7.Net.UnitTest/Helpers/TestSmallClass.cs | 12 ++++++++++++ S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net.UnitTest/S7NetTests.cs | 14 ++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 S7.Net.UnitTest/Helpers/TestSmallClass.cs diff --git a/S7.Net.UnitTest/Helpers/TestSmallClass.cs b/S7.Net.UnitTest/Helpers/TestSmallClass.cs new file mode 100644 index 0000000..de2a9d2 --- /dev/null +++ b/S7.Net.UnitTest/Helpers/TestSmallClass.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace S7.UnitTest.Helpers +{ + class TestSmallClass + { + public bool Bool1 { get; set; } + } +} diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index 59a3494..92c68ae 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -60,6 +60,7 @@ + diff --git a/S7.Net.UnitTest/S7NetTests.cs b/S7.Net.UnitTest/S7NetTests.cs index a995536..9ee8ed2 100644 --- a/S7.Net.UnitTest/S7NetTests.cs +++ b/S7.Net.UnitTest/S7NetTests.cs @@ -804,6 +804,20 @@ namespace S7.Net.UnitTest } } + [TestMethod] + public void T28_ReadClass_DoesntCrash_When_ReadingLessThan1Byte() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + var tc = new TestSmallClass(); + tc.Bool1 = true; + + plc.WriteClass(tc, DB2); + var tc2 = plc.ReadClass(DB2); + + Assert.AreEqual(tc.Bool1, tc2.Bool1); + } + #endregion #region Private methods From cbaa8921dfee5b02a0ff7d56567ddff8e755205e Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 7 May 2018 09:47:20 +0200 Subject: [PATCH 4/5] import killnine master --- S7.Net.UnitTest/Helpers/TestSmallClass.cs | 12 ++ S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net.UnitTest/S7NetTests.cs | 113 ++++++------- S7.Net/COTP.cs | 97 ++++++++++++ S7.Net/PLC.cs | 184 +++++++++++++--------- S7.Net/S7.Net.csproj | 2 + S7.Net/TPKT.cs | 49 ++++++ S7.Net/Types/Class.cs | 4 + S7.Net/Types/Timer.cs | 4 +- 9 files changed, 332 insertions(+), 134 deletions(-) create mode 100644 S7.Net.UnitTest/Helpers/TestSmallClass.cs create mode 100644 S7.Net/COTP.cs create mode 100644 S7.Net/TPKT.cs diff --git a/S7.Net.UnitTest/Helpers/TestSmallClass.cs b/S7.Net.UnitTest/Helpers/TestSmallClass.cs new file mode 100644 index 0000000..de2a9d2 --- /dev/null +++ b/S7.Net.UnitTest/Helpers/TestSmallClass.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace S7.UnitTest.Helpers +{ + class TestSmallClass + { + public bool Bool1 { get; set; } + } +} diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index 59a3494..92c68ae 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -60,6 +60,7 @@ + diff --git a/S7.Net.UnitTest/S7NetTests.cs b/S7.Net.UnitTest/S7NetTests.cs index 107a90f..9ee8ed2 100644 --- a/S7.Net.UnitTest/S7NetTests.cs +++ b/S7.Net.UnitTest/S7NetTests.cs @@ -386,42 +386,19 @@ namespace S7.Net.UnitTest { Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); - bool val = true; - plc.Write("DB2.DBX0.5", val); - bool result = (bool)plc.Read("DB2.DBX0.5"); - Assert.AreEqual(val, result); + ushort val = 16384; + plc.Write("DB2.DBW16384", val); + ushort result = (ushort)plc.Read("DB2.DBW16384"); + Assert.AreEqual(val, result, "A ushort goes from 0 to 64512"); - ushort val1 = 16384; - plc.Write("DB2.DBW16384", val1); - ushort result1 = (ushort)plc.Read("DB2.DBW16384"); - Assert.AreEqual(val1, result1, "A ushort goes from 0 to 64512"); - - bool val2 = true; - plc.Write("DB2.DBX8192.7", val2); - bool result2 = (bool)plc.Read("DB2.DBX8192.7"); - Assert.AreEqual(val2, result2); - - ushort val3 = 129; - plc.Write("DB2.DBW16", val3); - ushort result3 = (ushort)plc.Read("DB2.DBW16"); - Assert.AreEqual(val3, result3, "A ushort goes from 0 to 64512"); - - bool val4 = true; - plc.Write("DB2.DBX16384.6", val4); - bool result4 = (bool)plc.Read("DB2.DBX16384.6"); - Assert.AreEqual(val4, result4); + ushort val2 = 129; + plc.Write("DB2.DBW16", val2); + ushort result2 = (ushort)plc.Read("DB2.DBW16"); + Assert.AreEqual(val2, result2, "A ushort goes from 0 to 64512"); var dataItems = new List() { new DataItem - { - Count = 1, - DataType = DataType.DataBlock, - DB = 2, - StartByteAdr = 0, - BitAdr = 5, - VarType = VarType.Bit - },new DataItem { Count = 1, DataType = DataType.DataBlock, @@ -430,40 +407,19 @@ namespace S7.Net.UnitTest VarType = VarType.Word }, new DataItem - { - Count = 1, - DataType = DataType.DataBlock, - DB = 2, - StartByteAdr = 8192, - BitAdr = 7, - VarType = VarType.Bit - }, - new DataItem { Count = 1, DataType = DataType.DataBlock, DB = 2, StartByteAdr = 16, VarType = VarType.Word - }, - new DataItem - { - Count = 1, - DataType = DataType.DataBlock, - DB = 2, - StartByteAdr = 16384, - BitAdr = 6, - VarType = VarType.Bit - }, + } }; plc.ReadMultipleVars(dataItems); Assert.AreEqual(dataItems[0].Value, val); - Assert.AreEqual(dataItems[1].Value, val1); - Assert.AreEqual(dataItems[2].Value, val2); - Assert.AreEqual(dataItems[3].Value, val3); - Assert.AreEqual(dataItems[4].Value, val4); + Assert.AreEqual(dataItems[1].Value, val2); } /// @@ -813,6 +769,55 @@ namespace S7.Net.UnitTest Assert.IsTrue(reachablePlc.IsAvailable); } + [TestMethod] + public void T26_ReadWriteDouble() + { + double test_value = 55.66; + plc.Write("DB1.DBD0", test_value); + Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Double"); + var helper = plc.Read("DB1.DBD0"); + double test_value2 = Conversion.ConvertToDouble((uint)helper); + + Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Double"); + Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals + } + + [TestMethod] + public void T27_ReadWriteBytesMany() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + var count = 2000; + var dataItems = new List(); + for (int i = 0; i < count; i++) + { + dataItems.Add((byte)(i%256)); + } + + plc.WriteBytes(DataType.DataBlock, 2, 0, dataItems.ToArray()); + + var res = plc.ReadBytes(DataType.DataBlock, 2, 0, count); + + for (int x = 0; x < count; x++) + { + Assert.AreEqual(x % 256, res[x]); + } + } + + [TestMethod] + public void T28_ReadClass_DoesntCrash_When_ReadingLessThan1Byte() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + var tc = new TestSmallClass(); + tc.Bool1 = true; + + plc.WriteClass(tc, DB2); + var tc2 = plc.ReadClass(DB2); + + Assert.AreEqual(tc.Bool1, tc2.Bool1); + } + #endregion #region Private methods diff --git a/S7.Net/COTP.cs b/S7.Net/COTP.cs new file mode 100644 index 0000000..434bd06 --- /dev/null +++ b/S7.Net/COTP.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Net.Sockets; + +namespace S7.Net +{ + + /// + /// COTP Protocol functions and types + /// + internal class COTP + { + /// + /// Describes a COTP TPDU (Transport protocol data unit) + /// + public class TPDU + { + public byte HeaderLength; + public byte PDUType; + public int TPDUNumber; + public byte[] Data; + public bool LastDataUnit; + + public TPDU(TPKT tPKT) + { + var br = new BinaryReader(new MemoryStream(tPKT.Data)); + HeaderLength = br.ReadByte(); + if (HeaderLength >= 2) + { + PDUType = br.ReadByte(); + if (PDUType == 0xf0) //DT Data + { + var flags = br.ReadByte(); + TPDUNumber = flags & 0x7F; + LastDataUnit = (flags & 0x80) > 0; + Data = br.ReadBytes(tPKT.Length - HeaderLength - 4); //4 = TPKT Size + return; + } + //TODO: Handle other PDUTypes + } + Data = new byte[0]; + } + + /// + /// Reads COTP TPDU (Transport protocol data unit) from the network stream + /// See: https://tools.ietf.org/html/rfc905 + /// + /// The socket to read from + /// COTP DPDU instance + public static TPDU Read(Socket socket) + { + var tpkt = TPKT.Read(socket); + if (tpkt.Length > 0) return new TPDU(tpkt); + return null; + } + + public override string ToString() + { + return string.Format("Length: {0} PDUType: {1} TPDUNumber: {2} Last: {3} Segment Data: {4}", + HeaderLength, + PDUType, + TPDUNumber, + LastDataUnit, + BitConverter.ToString(Data) + ); + } + + } + + /// + /// Describes a COTP TSDU (Transport service data unit). One TSDU consist of 1 ore more TPDUs + /// + public class TSDU + { + /// + /// Reads the full COTP TSDU (Transport service data unit) + /// See: https://tools.ietf.org/html/rfc905 + /// + /// Data in TSDU + public static byte[] Read(Socket socket) + { + var segment = TPDU.Read(socket); + if (segment == null) return null; + + var output = new MemoryStream(segment.Data.Length); + output.Write(segment.Data, 0, segment.Data.Length); + + while (!segment.LastDataUnit) + { + segment = TPDU.Read(socket); + output.Write(segment.Data, (int)output.Position, segment.Data.Length); + } + return output.GetBuffer(); + } + } + } +} diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index de498cd..d7a10aa 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -4,11 +4,8 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Net; -using System.Net.NetworkInformation; using System.Net.Sockets; using S7.Net.Types; -using Double = System.Double; - namespace S7.Net { @@ -41,6 +38,11 @@ namespace S7.Net /// Slot of the CPU of the PLC /// public Int16 Slot { get; private set; } + + /// + /// Max PDU size this cpu supports + /// + public Int16 MaxPDUSize { get; set; } /// /// Returns true if a connection to the PLC can be established @@ -119,6 +121,7 @@ namespace S7.Net IP = ip; Rack = rack; Slot = slot; + MaxPDUSize = 240; } private ErrorCode Connect(Socket socket) @@ -160,7 +163,6 @@ namespace S7.Net /// Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode public ErrorCode Open() { - byte[] bReceive = new byte[256]; _mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _mSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000); @@ -171,9 +173,25 @@ namespace S7.Net return LastErrorCode; } - try + try { - byte[] bSend1 = { 3, 0, 0, 22, 17, 224, 0, 0, 0, 46, 0, 193, 2, 1, 0, 194, 2, 3, 0, 192, 1, 9 }; + byte[] bSend1 = { + 3, 0, 0, 22, //TPKT + 17, //COTP Header Length + 224, //Connect Request + 0, 0, //Destination Reference + 0, 46, //Source Reference + 0, //Flags + 193, //Parameter Code (src-tasp) + 2, //Parameter Length + 1, 0, //Source TASP + 194, //Parameter Code (dst-tasp) + 2, //Parameter Length + 3, 0, //Destination TASP + 192, //Parameter Code (tpdu-size) + 1, //Parameter Length + 9 //TPDU Size (2^9 = 512) + }; switch (CPU) { case CpuType.S7200: @@ -229,28 +247,29 @@ namespace S7.Net return ErrorCode.WrongCPU_Type; } + //COTP Setup _mSocket.Send(bSend1, 22, SocketFlags.None); - if (_mSocket.Receive(bReceive, 22, SocketFlags.None) != 22) - { - throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - } + byte[] bsend2 = { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3, + 7, 80 //Try 1920 PDU Size. Same as libnodave. + }; - byte[] bsend2 = { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3, 1, 0 }; _mSocket.Send(bsend2, 25, SocketFlags.None); - if (_mSocket.Receive(bReceive, 27, SocketFlags.None) != 27) + var s7data = COTP.TSDU.Read(_mSocket); + if (s7data == null || s7data[1] != 0x03) //Check for S7 Ack Data { throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - } + } + MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]); + + return ErrorCode.NoError; } - catch(Exception exc) + catch (Exception exc) { LastErrorCode = ErrorCode.ConnectionError; LastErrorString = string.Format("Couldn't establish the connection to {0}.\nMessage: {1}", IP, exc.Message); return ErrorCode.ConnectionError; } - - return ErrorCode.NoError; } /// @@ -276,12 +295,14 @@ namespace S7.Net public void ReadMultipleVars(List dataItems) { int cntBytes = dataItems.Sum(dataItem => VarTypeToByteLength(dataItem.VarType, dataItem.Count)); - + + //TODO: Figure out how to use MaxPDUSize here + //Snap7 seems to choke on PDU sizes above 256 even if snap7 + //replies with bigger PDU size in connection setup. if (dataItems.Count > 20) throw new Exception("Too many vars requested"); if (cntBytes > 222) throw new Exception("Too many bytes requested"); // TODO: proper TDU check + split in multiple requests - try { // first create the header @@ -296,37 +317,20 @@ namespace S7.Net _mSocket.Send(package.array, package.array.Length, SocketFlags.None); - byte[] bReceive = new byte[512]; - int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None); - if (bReceive[21] != 0xff) + var s7data = COTP.TSDU.Read(_mSocket); + if (s7data == null || s7data[14] != 0xff) throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - int offset = 21; + int offset = 18; foreach (var dataItem in dataItems) { - // check for Return Code = Success - if (bReceive[offset] != 0xff) - throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - - // to Data bytes - offset += 4; - int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count); - byte[] bytes = new byte[byteCnt]; - - for (int i = 0; i < byteCnt; i++) - { - bytes[i] = bReceive[i + offset]; - } - - // next Item - offset += byteCnt; - - // Fill byte in response - if (dataItem.VarType == VarType.Bit) - offset++; - - dataItem.Value = ParseBytes(dataItem.VarType, bytes, dataItem.Count, dataItem.BitAdr); + dataItem.Value = ParseBytes( + dataItem.VarType, + s7data.Skip(offset).Take(byteCnt).ToArray(), + dataItem.Count + ); + offset += byteCnt + 4; } } catch (SocketException socketException) @@ -356,7 +360,8 @@ namespace S7.Net int index = startByteAdr; while (count > 0) { - var maxToRead = (int)Math.Min(count, 200); + //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0. + var maxToRead = (int)Math.Min(count, MaxPDUSize-18); byte[] bytes = ReadBytesWithASingleRequest(dataType, db, index, maxToRead); if (bytes == null) return resultBytes.ToArray(); @@ -633,7 +638,10 @@ namespace S7.Net int localIndex = 0; int count = value.Length; while (count > 0) - { + { + //TODO: Figure out how to use MaxPDUSize here + //Snap7 seems to choke on PDU sizes above 256 even if snap7 + //replies with bigger PDU size in connection setup. var maxToWrite = (int)Math.Min(count, 200); ErrorCode lastError = WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray()); if (lastError != ErrorCode.NoError) @@ -743,7 +751,7 @@ namespace S7.Net package = Types.DWord.ToByteArray((UInt32)value); break; case "Double": - package = Types.Double.ToByteArray((Double)value); + package = Types.Double.ToByteArray((double)value); break; case "Byte[]": package = (byte[])value; @@ -827,10 +835,11 @@ namespace S7.Net { return Write(DataType.DataBlock, mDB, dbIndex, (Int32)value); } - else + else if (value is double) { - objValue = Convert.ChangeType(value, typeof(UInt32)); + return Write(DataType.DataBlock, mDB, dbIndex, value); } + objValue = Convert.ChangeType(value, typeof(UInt32)); return Write(DataType.DataBlock, mDB, dbIndex, (UInt32)objValue); case "DBX": mByte = dbIndex; @@ -1046,8 +1055,6 @@ namespace S7.Net private byte[] ReadBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, int count) { - byte[] bytes = new byte[count]; - try { // first create the header @@ -1059,15 +1066,11 @@ namespace S7.Net _mSocket.Send(package.array, package.array.Length, SocketFlags.None); - byte[] bReceive = new byte[512]; - int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None); - if (bReceive[21] != 0xff) + var s7data = COTP.TSDU.Read(_mSocket); + if (s7data == null || s7data[14] != 0xff) throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - for (int cnt = 0; cnt < count; cnt++) - bytes[cnt] = bReceive[cnt + 25]; - - return bytes; + return s7data.Skip(18).Take(count).ToArray(); } catch (SocketException socketException) { @@ -1094,9 +1097,7 @@ namespace S7.Net /// NoError if it was successful, or the error is specified private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value) { - byte[] bReceive = new byte[513]; int varCount = 0; - try { varCount = value.Length; @@ -1125,8 +1126,8 @@ namespace S7.Net _mSocket.Send(package.array, package.array.Length, SocketFlags.None); - int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None); - if (bReceive[21] != 0xff) + var s7data = COTP.TSDU.Read(_mSocket); + if (s7data == null || s7data[14] != 0xff) { throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); } @@ -1143,7 +1144,6 @@ namespace S7.Net private ErrorCode WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue) { - byte[] bReceive = new byte[513]; int varCount = 0; try @@ -1175,11 +1175,9 @@ namespace S7.Net _mSocket.Send(package.array, package.array.Length, SocketFlags.None); - int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None); - if (bReceive[21] != 0xff) - { - throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - } + var s7data = COTP.TSDU.Read(_mSocket); + if (s7data == null || s7data[14] != 0xff) + throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); return ErrorCode.NoError; } @@ -1301,23 +1299,53 @@ namespace S7.Net } } - #region IDisposable members + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + /// + /// Releases all resources, disonnects from the PLC and closes the + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + if (_mSocket != null) + { + if (_mSocket.Connected) + { + _mSocket.Shutdown(SocketShutdown.Both); + _mSocket.Close(); + } + } + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~Plc() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. /// /// Releases all resources, disonnects from the PLC and closes the /// public void Dispose() { - if (_mSocket != null) - { - if (_mSocket.Connected) - { - _mSocket.Shutdown(SocketShutdown.Both); - _mSocket.Close(); - } - } + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); } - #endregion } } diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj index ac2ac19..bb2abab 100644 --- a/S7.Net/S7.Net.csproj +++ b/S7.Net/S7.Net.csproj @@ -77,9 +77,11 @@ + + diff --git a/S7.Net/TPKT.cs b/S7.Net/TPKT.cs new file mode 100644 index 0000000..3ca9fd5 --- /dev/null +++ b/S7.Net/TPKT.cs @@ -0,0 +1,49 @@ +using System; +using System.Net.Sockets; + +namespace S7.Net +{ + + /// + /// Describes a TPKT Packet + /// + internal class TPKT + { + public byte Version; + public byte Reserved1; + public int Length; + public byte[] Data; + + /// + /// Reds a TPKT from the socket + /// + /// The socket to read from + /// TPKT Instace + public static TPKT Read(Socket socket) + { + var buf = new byte[4]; + socket.Receive(buf, 4, SocketFlags.None); + var pkt = new TPKT + { + Version = buf[0], + Reserved1 = buf[1], + Length = buf[2] * 256 + buf[3] //BigEndian + }; + if (pkt.Length > 0) + { + pkt.Data = new byte[pkt.Length - 4]; + socket.Receive(pkt.Data, pkt.Length - 4, SocketFlags.None); + } + return pkt; + } + + public override string ToString() + { + return string.Format("Version: {0} Length: {1} Data: {2}", + Version, + Length, + BitConverter.ToString(Data) + ); + } + } +} diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 794b469..26f4742 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -99,6 +99,10 @@ namespace S7.Net.Types numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType); } } + // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; return (int)numBytes; } diff --git a/S7.Net/Types/Timer.cs b/S7.Net/Types/Timer.cs index a95253b..ede6af8 100644 --- a/S7.Net/Types/Timer.cs +++ b/S7.Net/Types/Timer.cs @@ -18,8 +18,8 @@ namespace S7.Net.Types wert += ((bytes[1] >> 4) & 0x0F) * 10.0; wert += ((bytes[1]) & 0x0F) * 1.0; - /// this value is not used... may for a nother exponation - ///int unknown = (bytes[0] >> 6) & 0x03; + // this value is not used... may for a nother exponation + //int unknown = (bytes[0] >> 6) & 0x03; switch ((bytes[0] >> 4) & 0x03) { From 642cf8169e15450f690ab306dde877748d5b32bc Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 7 May 2018 11:47:52 +0200 Subject: [PATCH 5/5] Fixed bug for reading VarType.Bit and VarType.Byte on odd number of bytes in ReadMultipleVars() --- S7.Net.UnitTest/S7NetTests.cs | 94 +++++++++++++++++++++++++++++++---- S7.Net/PLC.cs | 22 ++++++-- S7.Net/Types/DataItem.cs | 5 ++ 3 files changed, 107 insertions(+), 14 deletions(-) diff --git a/S7.Net.UnitTest/S7NetTests.cs b/S7.Net.UnitTest/S7NetTests.cs index 9ee8ed2..85bd7ca 100644 --- a/S7.Net.UnitTest/S7NetTests.cs +++ b/S7.Net.UnitTest/S7NetTests.cs @@ -386,19 +386,51 @@ namespace S7.Net.UnitTest { Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); - ushort val = 16384; - plc.Write("DB2.DBW16384", val); - ushort result = (ushort)plc.Read("DB2.DBW16384"); - Assert.AreEqual(val, result, "A ushort goes from 0 to 64512"); + bool val = true; + plc.Write("DB2.DBX0.5", val); + bool result = (bool)plc.Read("DB2.DBX0.5"); + Assert.AreEqual(val, result); - ushort val2 = 129; - plc.Write("DB2.DBW16", val2); - ushort result2 = (ushort)plc.Read("DB2.DBW16"); - Assert.AreEqual(val2, result2, "A ushort goes from 0 to 64512"); + ushort val1 = 16384; + plc.Write("DB2.DBW16384", val1); + ushort result1 = (ushort)plc.Read("DB2.DBW16384"); + Assert.AreEqual(val1, result1, "A ushort goes from 0 to 64512"); + + bool val2 = true; + plc.Write("DB2.DBX8192.7", val2); + bool result2 = (bool)plc.Read("DB2.DBX8192.7"); + Assert.AreEqual(val2, result2); + + ushort val3 = 129; + plc.Write("DB2.DBW16", val3); + ushort result3 = (ushort)plc.Read("DB2.DBW16"); + Assert.AreEqual(val3, result3, "A ushort goes from 0 to 64512"); + + byte[] val4 = new byte[] { 0x12, 0x34 }; + plc.Write("DB2.DBB2048", val4[0]); + plc.Write("DB2.DBB2049", val4[1]); + byte result4b0 = (byte)plc.Read("DB2.DBB2048"); + byte result4b1 = (byte)plc.Read("DB2.DBB2049"); + Assert.AreEqual(val4[0], result4b0); + Assert.AreEqual(val4[1], result4b1); + + bool val6 = true; + plc.Write("DB2.DBX16384.6", val6); + bool result6 = (bool)plc.Read("DB2.DBX16384.6"); + Assert.AreEqual(val6, result6); var dataItems = new List() { new DataItem + { + Count = 1, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 0, + BitAdr = 5, + VarType = VarType.Bit + } + ,new DataItem { Count = 1, DataType = DataType.DataBlock, @@ -407,19 +439,61 @@ namespace S7.Net.UnitTest VarType = VarType.Word }, new DataItem + { + Count = 1, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 8192, + BitAdr = 7, + VarType = VarType.Bit + }, + new DataItem { Count = 1, DataType = DataType.DataBlock, DB = 2, StartByteAdr = 16, VarType = VarType.Word - } + }, + // single byte + new DataItem + { + Count = 1, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 2048, + VarType = VarType.Byte + }, + // multiple bytes + new DataItem + { + Count = 2, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 2048, + VarType = VarType.Byte + }, + new DataItem + { + Count = 1, + DataType = DataType.DataBlock, + DB = 2, + StartByteAdr = 16384, + BitAdr = 6, + VarType = VarType.Bit + }, }; plc.ReadMultipleVars(dataItems); Assert.AreEqual(dataItems[0].Value, val); - Assert.AreEqual(dataItems[1].Value, val2); + Assert.AreEqual(dataItems[1].Value, val1); + Assert.AreEqual(dataItems[2].Value, val2); + Assert.AreEqual(dataItems[3].Value, val3); + Assert.AreEqual(dataItems[4].Value, val4[0]); + Assert.AreEqual(((byte[])dataItems[5].Value)[0], val4[0]); //dataItem[5].Value should be byte[2] + Assert.AreEqual(((byte[])dataItems[5].Value)[1], val4[1]); + Assert.AreEqual(dataItems[6].Value, val6); } /// diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index d7a10aa..82d1a28 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -321,16 +321,30 @@ namespace S7.Net if (s7data == null || s7data[14] != 0xff) throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); - int offset = 18; + int offset = 14; foreach (var dataItem in dataItems) { + // check for Return Code = Success + if (s7data[offset] != 0xff) + throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString()); + + // to Data bytes + offset += 4; + int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count); dataItem.Value = ParseBytes( - dataItem.VarType, + dataItem.VarType, s7data.Skip(offset).Take(byteCnt).ToArray(), - dataItem.Count + dataItem.Count, + dataItem.BitAdr ); - offset += byteCnt + 4; + + // next Item + offset += byteCnt; + + // Fill byte in response when bytecount is odd + if (dataItem.Count % 2 != 0 && (dataItem.VarType == VarType.Byte || dataItem.VarType == VarType.Bit)) + offset++; } } catch (SocketException socketException) diff --git a/S7.Net/Types/DataItem.cs b/S7.Net/Types/DataItem.cs index 454497f..5f48586 100644 --- a/S7.Net/Types/DataItem.cs +++ b/S7.Net/Types/DataItem.cs @@ -25,6 +25,11 @@ /// public int StartByteAdr { get; set; } + /// + /// Addess of bit to read from StartByteAdr + /// + public byte BitAdr { get; set; } + /// /// Number of variables to read ///