diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 38f12af..e03a490 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -128,6 +128,26 @@ namespace S7.Net.UnitTest Assert.AreEqual(val3, result3); } + /// + /// Write/Read a large amount of data to test PDU max + /// + [TestMethod] + public async Task Test_Async_WriteLargeByteArray() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + var randomEngine = new Random(); + var data = new byte[8192]; + var db = 2; + randomEngine.NextBytes(data); + + await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data); + + var readData = await plc.ReadBytesAsync(DataType.DataBlock, db, 0, data.Length); + + CollectionAssert.AreEqual(data, readData); + } + /// /// Read/Write a class that has the same properties of a DB with the same field in the same order /// diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index 3b7589b..e2e563e 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -743,6 +743,26 @@ namespace S7.Net.UnitTest Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); } + /// + /// Write/Read a large amount of data to test PDU max + /// + [TestMethod] + public void T33_WriteLargeByteArray() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + var randomEngine = new Random(); + var data = new byte[8192]; + var db = 2; + randomEngine.NextBytes(data); + + plc.WriteBytes(DataType.DataBlock, db, 0, data); + + var readData = plc.ReadBytes(DataType.DataBlock, db, 0, data.Length); + + CollectionAssert.AreEqual(data, readData); + } + [TestMethod, ExpectedException(typeof(PlcException))] public void T18_ReadStructThrowsIfPlcIsNotConnected() { diff --git a/S7.Net/COTP.cs b/S7.Net/COTP.cs index 8f60423..c4af73a 100644 --- a/S7.Net/COTP.cs +++ b/S7.Net/COTP.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Threading.Tasks; -using System.Linq; namespace S7.Net { @@ -27,17 +26,17 @@ namespace S7.Net { TPkt = tPKT; - var br = new BinaryReader(new MemoryStream(tPKT.Data)); - HeaderLength = br.ReadByte(); + HeaderLength = tPKT.Data[0]; // Header length excluding this length byte if (HeaderLength >= 2) { - PDUType = br.ReadByte(); + PDUType = tPKT.Data[1]; if (PDUType == 0xf0) //DT Data { - var flags = br.ReadByte(); + var flags = tPKT.Data[2]; TPDUNumber = flags & 0x7F; LastDataUnit = (flags & 0x80) > 0; - Data = br.ReadBytes(tPKT.Length - HeaderLength - 4); //4 = TPKT Size + Data = new byte[tPKT.Data.Length - HeaderLength - 1]; // substract header length byte + header length. + Array.Copy(tPKT.Data, HeaderLength + 1, Data, 0, Data.Length); return; } //TODO: Handle other PDUTypes @@ -105,20 +104,24 @@ namespace S7.Net { var segment = TPDU.Read(stream); + if (segment.LastDataUnit) + { + return segment.Data; + } + + // More segments are expected, prepare a buffer to store all data var buffer = new byte[segment.Data.Length]; - var output = new MemoryStream(buffer); - output.Write(segment.Data, 0, segment.Data.Length); + Array.Copy(segment.Data, buffer, segment.Data.Length); while (!segment.LastDataUnit) { segment = TPDU.Read(stream); + var previousLength = buffer.Length; Array.Resize(ref buffer, buffer.Length + segment.Data.Length); - var lastPosition = output.Position; - output = new MemoryStream(buffer); - output.Write(segment.Data, (int) lastPosition, segment.Data.Length); + Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length); } - return buffer.Take((int)output.Position).ToArray(); + return buffer; } /// @@ -131,19 +134,24 @@ namespace S7.Net { var segment = await TPDU.ReadAsync(stream); + if (segment.LastDataUnit) + { + return segment.Data; + } + + // More segments are expected, prepare a buffer to store all data var buffer = new byte[segment.Data.Length]; - var output = new MemoryStream(buffer); - output.Write(segment.Data, 0, segment.Data.Length); + Array.Copy(segment.Data, buffer, segment.Data.Length); while (!segment.LastDataUnit) { segment = await TPDU.ReadAsync(stream); + var previousLength = buffer.Length; Array.Resize(ref buffer, buffer.Length + segment.Data.Length); - var lastPosition = output.Position; - output = new MemoryStream(buffer); - output.Write(segment.Data, (int) lastPosition, segment.Data.Length); + Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length); } - return buffer.Take((int)output.Position).ToArray(); + + return buffer; } } } diff --git a/S7.Net/Helper/MemoryStreamExtension.cs b/S7.Net/Helper/MemoryStreamExtension.cs new file mode 100644 index 0000000..dd10fbc --- /dev/null +++ b/S7.Net/Helper/MemoryStreamExtension.cs @@ -0,0 +1,18 @@ + +namespace S7.Net.Helper +{ + internal static class MemoryStreamExtension + { + /// + /// Helper function to write to whole content of the given byte array to a memory stream. + /// + /// Writes all bytes in value from 0 to value.Length to the memory stream. + /// + /// + /// + public static void WriteByteArray(this System.IO.MemoryStream stream, byte[] value) + { + stream.Write(value, 0, value.Length); + } + } +} diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 2286264..9325f8a 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -1,4 +1,5 @@ -using S7.Net.Types; +using S7.Net.Helper; +using S7.Net.Types; using System; using System.Collections.Generic; using System.Linq; @@ -13,21 +14,18 @@ namespace S7.Net /// /// /// - private ByteArray ReadHeaderPackage(int amount = 1) + private void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1) { //header size = 19 bytes - var package = new Types.ByteArray(19); - package.Add(new byte[] { 0x03, 0x00 }); + stream.WriteByteArray(new byte[] { 0x03, 0x00 }); //complete package size - package.Add(Types.Int.ToByteArray((short)(19 + (12 * amount)))); - package.Add(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 }); + stream.WriteByteArray(Types.Int.ToByteArray((short)(19 + (12 * amount)))); + stream.WriteByteArray(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 }); //data part size - package.Add(Types.Word.ToByteArray((ushort)(2 + (amount * 12)))); - package.Add(new byte[] { 0x00, 0x00, 0x04 }); + stream.WriteByteArray(Types.Word.ToByteArray((ushort)(2 + (amount * 12)))); + stream.WriteByteArray(new byte[] { 0x00, 0x00, 0x04 }); //amount of requests - package.Add((byte)amount); - - return package; + stream.WriteByte((byte)amount); } /// @@ -39,39 +37,36 @@ namespace S7.Net /// Start address of the byte /// Number of bytes to be read /// - private ByteArray CreateReadDataRequestPackage(DataType dataType, int db, int startByteAdr, int count = 1) + private void BuildReadDataRequestPackage(System.IO.MemoryStream stream, DataType dataType, int db, int startByteAdr, int count = 1) { //single data req = 12 - var package = new Types.ByteArray(12); - package.Add(new byte[] { 0x12, 0x0a, 0x10 }); + stream.WriteByteArray(new byte[] { 0x12, 0x0a, 0x10 }); switch (dataType) { case DataType.Timer: case DataType.Counter: - package.Add((byte)dataType); + stream.WriteByte((byte)dataType); break; default: - package.Add(0x02); + stream.WriteByte(0x02); break; } - package.Add(Word.ToByteArray((ushort)(count))); - package.Add(Word.ToByteArray((ushort)(db))); - package.Add((byte)dataType); + stream.WriteByteArray(Word.ToByteArray((ushort)(count))); + stream.WriteByteArray(Word.ToByteArray((ushort)(db))); + stream.WriteByte((byte)dataType); var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 - package.Add((byte)overflow); + stream.WriteByte((byte)overflow); switch (dataType) { case DataType.Timer: case DataType.Counter: - package.Add(Types.Word.ToByteArray((ushort)(startByteAdr))); + stream.WriteByteArray(Types.Word.ToByteArray((ushort)(startByteAdr))); break; default: - package.Add(Types.Word.ToByteArray((ushort)((startByteAdr) * 8))); + stream.WriteByteArray(Types.Word.ToByteArray((ushort)((startByteAdr) * 8))); break; } - - return package; } /// diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 601a3a4..6a63ddd 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -39,11 +39,16 @@ namespace S7.Net var s7data = await COTP.TSDU.ReadAsync(stream); if (s7data == null) throw new WrongNumberOfBytesException("No data received in response to Communication Setup"); + if (s7data.Length < 2) + throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); //Check for S7 Ack Data if (s7data[1] != 0x03) throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03); + if (s7data.Length < 20) + throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); + MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]); } @@ -67,20 +72,17 @@ namespace S7.Net /// Returns the bytes in an array public async Task ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count) { - List resultBytes = new List(); - int index = startByteAdr; + var resultBytes = new byte[count]; + int index = 0; while (count > 0) { //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0. - var maxToRead = (int)Math.Min(count, MaxPDUSize - 18); - byte[] bytes = await ReadBytesWithSingleRequestAsync(dataType, db, index, maxToRead); - if (bytes == null) - return resultBytes.ToArray(); - resultBytes.AddRange(bytes); + var maxToRead = Math.Min(count, MaxPDUSize - 18); + await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead); count -= maxToRead; index += maxToRead; } - return resultBytes.ToArray(); + return resultBytes; } /// @@ -222,15 +224,16 @@ namespace S7.Net { // first create the header int packageSize = 19 + (dataItems.Count * 12); - ByteArray package = new ByteArray(packageSize); - package.Add(ReadHeaderPackage(dataItems.Count)); + var package = new System.IO.MemoryStream(packageSize); + BuildHeaderPackage(package, dataItems.Count); // package.Add(0x02); // datenart foreach (var dataItem in dataItems) { - package.Add(CreateReadDataRequestPackage(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count))); + BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count)); } - await stream.WriteAsync(package.Array, 0, package.Array.Length); + var dataToSend = package.ToArray(); + await stream.WriteAsync(dataToSend, 0, dataToSend.Length); var s7data = await COTP.TSDU.ReadAsync(stream); //TODO use Async if (s7data == null || s7data[14] != 0xff) @@ -264,11 +267,8 @@ namespace S7.Net 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); - await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray()); + var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35); + await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite); count -= maxToWrite; localIndex += maxToWrite; } @@ -383,28 +383,24 @@ namespace S7.Net await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes); } - private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count) + private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count) { var stream = GetStreamIfAvailable(); - byte[] bytes = new byte[count]; - // first create the header - int packageSize = 31; - ByteArray package = new ByteArray(packageSize); - package.Add(ReadHeaderPackage()); + int packageSize = 31; + var package = new System.IO.MemoryStream(packageSize); + BuildHeaderPackage(package); // package.Add(0x02); // datenart - package.Add(CreateReadDataRequestPackage(dataType, db, startByteAdr, count)); + BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count); - await stream.WriteAsync(package.Array, 0, package.Array.Length); + var dataToSend = package.ToArray(); + await stream.WriteAsync(dataToSend, 0, dataToSend.Length); var s7data = await COTP.TSDU.ReadAsync(stream); AssertReadResponse(s7data, count); - for (int cnt = 0; cnt < count; cnt++) - bytes[cnt] = s7data[cnt + 18]; - - return bytes; + Array.Copy(s7data, 18, buffer, offset, count); } /// @@ -435,40 +431,15 @@ namespace S7.Net /// Start byte address. If you want to read DB1.DBW200, this is 200. /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. /// A task that represents the asynchronous write operation. - private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value) + private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count) { - var stream = GetStreamIfAvailable(); - - byte[] bReceive = new byte[513]; - int varCount = 0; try { - varCount = value.Length; - // first create the header - int packageSize = 35 + value.Length; - ByteArray package = new ByteArray(packageSize); + var stream = GetStreamIfAvailable(); + var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); - package.Add(new byte[] { 3, 0, 0 }); - package.Add((byte)packageSize); - package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); - package.Add(Word.ToByteArray((ushort)(varCount - 1))); - package.Add(new byte[] { 0, 0x0e }); - package.Add(Word.ToByteArray((ushort)(varCount + 4))); - package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 }); - package.Add(Word.ToByteArray((ushort)varCount)); - package.Add(Word.ToByteArray((ushort)(db))); - package.Add((byte)dataType); - var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 - package.Add((byte)overflow); - package.Add(Word.ToByteArray((ushort)(startByteAdr * 8))); - package.Add(new byte[] { 0, 4 }); - package.Add(Word.ToByteArray((ushort)(varCount * 8))); - - // now join the header and the data - package.Add(value); - - await stream.WriteAsync(package.Array, 0, package.Array.Length); + await stream.WriteAsync(dataToSend, 0, dataToSend.Length); var s7data = await COTP.TSDU.ReadAsync(stream); if (s7data == null || s7data[14] != 0xff) @@ -486,41 +457,17 @@ namespace S7.Net { var stream = GetStreamIfAvailable(); - byte[] bReceive = new byte[513]; - int varCount = 0; - try { - var value = new[] {bitValue ? (byte) 1 : (byte) 0}; - varCount = value.Length; - // first create the header - int packageSize = 35 + value.Length; - ByteArray package = new Types.ByteArray(packageSize); + var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); - package.Add(new byte[] { 3, 0, 0 }); - package.Add((byte)packageSize); - package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); - package.Add(Word.ToByteArray((ushort)(varCount - 1))); - package.Add(new byte[] { 0, 0x0e }); - package.Add(Word.ToByteArray((ushort)(varCount + 4))); - package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit - package.Add(Word.ToByteArray((ushort)varCount)); - package.Add(Word.ToByteArray((ushort)(db))); - package.Add((byte)dataType); - int overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 - package.Add((byte)overflow); - package.Add(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr))); - package.Add(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit - package.Add(Word.ToByteArray((ushort)(varCount))); - - // now join the header and the data - package.Add(value); - - await stream.WriteAsync(package.Array, 0, package.Array.Length); + await stream.WriteAsync(dataToSend, 0, dataToSend.Length); var s7data = await COTP.TSDU.ReadAsync(stream); if (s7data == null || s7data[14] != 0xff) + { throw new PlcException(ErrorCode.WrongNumberReceivedBytes); + } } catch (Exception exc) { diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 74b02bc..9ae2cc0 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -1,9 +1,11 @@ using S7.Net.Types; using System; +using System.IO; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using S7.Net.Protocol; +using S7.Net.Helper; //Implement synchronous methods here namespace S7.Net @@ -32,11 +34,16 @@ namespace S7.Net var s7data = COTP.TSDU.Read(stream); if (s7data == null) throw new WrongNumberOfBytesException("No data received in response to Communication Setup"); + if (s7data.Length < 2) + throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); //Check for S7 Ack Data if (s7data[1] != 0x03) throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03); + if (s7data.Length < 20) + throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); + MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]); } catch (Exception exc) @@ -80,20 +87,17 @@ namespace S7.Net /// Returns the bytes in an array public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count) { - List resultBytes = new List(); - int index = startByteAdr; + var result = new byte[count]; + int index = 0; while (count > 0) { //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0. - var maxToRead = (int)Math.Min(count, MaxPDUSize - 18); - byte[] bytes = ReadBytesWithSingleRequest(dataType, db, index, maxToRead); - if (bytes == null) - return resultBytes.ToArray(); - resultBytes.AddRange(bytes); + var maxToRead = Math.Min(count, MaxPDUSize - 18); + ReadBytesWithSingleRequest(dataType, db, startByteAdr + index, result, index, maxToRead); count -= maxToRead; index += maxToRead; } - return resultBytes.ToArray(); + return result; } /// @@ -232,7 +236,7 @@ namespace S7.Net //Snap7 seems to choke on PDU sizes above 256 even if snap7 //replies with bigger PDU size in connection setup. var maxToWrite = Math.Min(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480 - WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray()); + WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite); count -= maxToWrite; localIndex += maxToWrite; } @@ -339,28 +343,25 @@ namespace S7.Net WriteClassAsync(classValue, db, startByteAdr).GetAwaiter().GetResult(); } - private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, int count) + private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count) { var stream = GetStreamIfAvailable(); - byte[] bytes = new byte[count]; try { // first create the header - int packageSize = 31; - ByteArray package = new ByteArray(packageSize); - package.Add(ReadHeaderPackage()); + int packageSize = 19 + 12; // 19 header + 12 for 1 request + var package = new System.IO.MemoryStream(packageSize); + BuildHeaderPackage(package); // package.Add(0x02); // datenart - package.Add(CreateReadDataRequestPackage(dataType, db, startByteAdr, count)); + BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count); - stream.Write(package.Array, 0, package.Array.Length); + var dataToSend = package.ToArray(); + stream.Write(dataToSend, 0, dataToSend.Length); var s7data = COTP.TSDU.Read(stream); AssertReadResponse(s7data, count); - for (int cnt = 0; cnt < count; cnt++) - bytes[cnt] = s7data[cnt + 18]; - - return bytes; + Array.Copy(s7data, 18, buffer, offset, count); } catch (Exception exc) { @@ -387,38 +388,14 @@ namespace S7.Net S7WriteMultiple.ParseResponse(response, response.Length, dataItems); } - private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value) + private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count) { - var stream = GetStreamIfAvailable(); - int varCount = 0; try { - varCount = value.Length; - // first create the header - int packageSize = 35 + value.Length; - ByteArray package = new ByteArray(packageSize); + var stream = GetStreamIfAvailable(); + var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); - package.Add(new byte[] { 3, 0 }); - //complete package size - package.Add(Int.ToByteArray((short)packageSize)); - package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); - package.Add(Word.ToByteArray((ushort)(varCount - 1))); - package.Add(new byte[] { 0, 0x0e }); - package.Add(Word.ToByteArray((ushort)(varCount + 4))); - package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 }); - package.Add(Word.ToByteArray((ushort)varCount)); - package.Add(Word.ToByteArray((ushort)(db))); - package.Add((byte)dataType); - var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 - package.Add((byte)overflow); - package.Add(Word.ToByteArray((ushort)(startByteAdr * 8))); - package.Add(new byte[] { 0, 4 }); - package.Add(Word.ToByteArray((ushort)(varCount * 8))); - - // now join the header and the data - package.Add(value); - - stream.Write(package.Array, 0, package.Array.Length); + stream.Write(dataToSend, 0, dataToSend.Length); var s7data = COTP.TSDU.Read(stream); if (s7data == null || s7data[14] != 0xff) @@ -432,43 +409,85 @@ namespace S7.Net } } + private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count) + { + int varCount = count; + // first create the header + int packageSize = 35 + varCount; + var package = new MemoryStream(new byte[packageSize]); + + package.WriteByte(3); + package.WriteByte(0); + //complete package size + package.WriteByteArray(Int.ToByteArray((short)packageSize)); + package.WriteByteArray(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); + package.WriteByteArray(Word.ToByteArray((ushort)(varCount - 1))); + package.WriteByteArray(new byte[] { 0, 0x0e }); + package.WriteByteArray(Word.ToByteArray((ushort)(varCount + 4))); + package.WriteByteArray(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 }); + package.WriteByteArray(Word.ToByteArray((ushort)varCount)); + package.WriteByteArray(Word.ToByteArray((ushort)(db))); + package.WriteByte((byte)dataType); + var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 + package.WriteByte((byte)overflow); + package.WriteByteArray(Word.ToByteArray((ushort)(startByteAdr * 8))); + package.WriteByteArray(new byte[] { 0, 4 }); + package.WriteByteArray(Word.ToByteArray((ushort)(varCount * 8))); + + // now join the header and the data + package.Write(value, dataOffset, count); + + return package.ToArray(); + } + + private byte[] BuildWriteBitPackage(DataType dataType, int db, int startByteAdr, bool bitValue, int bitAdr) + { + var stream = GetStreamIfAvailable(); + var value = new[] { bitValue ? (byte)1 : (byte)0 }; + int varCount = 1; + // first create the header + int packageSize = 35 + varCount; + var package = new MemoryStream(new byte[packageSize]); + + package.WriteByte(3); + package.WriteByte(0); + //complete package size + package.WriteByteArray(Int.ToByteArray((short)packageSize)); + package.WriteByteArray(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); + package.WriteByteArray(Word.ToByteArray((ushort)(varCount - 1))); + package.WriteByteArray(new byte[] { 0, 0x0e }); + package.WriteByteArray(Word.ToByteArray((ushort)(varCount + 4))); + package.WriteByteArray(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit + package.WriteByteArray(Word.ToByteArray((ushort)varCount)); + package.WriteByteArray(Word.ToByteArray((ushort)(db))); + package.WriteByte((byte)dataType); + var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 + package.WriteByte((byte)overflow); + package.WriteByteArray(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr))); + package.WriteByteArray(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit + package.WriteByteArray(Word.ToByteArray((ushort)(varCount))); + + // now join the header and the data + package.WriteByteArray(value); + + return package.ToArray(); + } + + private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue) { var stream = GetStreamIfAvailable(); - int varCount = 0; - try { - var value = new[] {bitValue ? (byte) 1 : (byte) 0}; - varCount = value.Length; - // first create the header - int packageSize = 35 + value.Length; - ByteArray package = new ByteArray(packageSize); + var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); - package.Add(new byte[] { 3, 0, 0 }); - package.Add((byte)packageSize); - package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); - package.Add(Word.ToByteArray((ushort)(varCount - 1))); - package.Add(new byte[] { 0, 0x0e }); - package.Add(Word.ToByteArray((ushort)(varCount + 4))); - package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit - package.Add(Word.ToByteArray((ushort)varCount)); - package.Add(Word.ToByteArray((ushort)(db))); - package.Add((byte)dataType); - int overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 - package.Add((byte)overflow); - package.Add(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr))); - package.Add(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit - package.Add(Word.ToByteArray((ushort)(varCount))); - - // now join the header and the data - package.Add(value); - - stream.Write(package.Array, 0, package.Array.Length); + stream.Write(dataToSend, 0, dataToSend.Length); var s7data = COTP.TSDU.Read(stream); if (s7data == null || s7data[14] != 0xff) + { throw new PlcException(ErrorCode.WrongNumberReceivedBytes); + } } catch (Exception exc) { @@ -496,15 +515,16 @@ namespace S7.Net { // first create the header int packageSize = 19 + (dataItems.Count * 12); - ByteArray package = new ByteArray(packageSize); - package.Add(ReadHeaderPackage(dataItems.Count)); + var package = new System.IO.MemoryStream(packageSize); + BuildHeaderPackage(package, dataItems.Count); // package.Add(0x02); // datenart foreach (var dataItem in dataItems) { - package.Add(CreateReadDataRequestPackage(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count))); + BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count)); } - stream.Write(package.Array, 0, package.Array.Length); + var dataToSend = package.ToArray(); + stream.Write(dataToSend, 0, dataToSend.Length); var s7data = COTP.TSDU.Read(stream); //TODO use Async if (s7data == null || s7data[14] != 0xff) diff --git a/S7.Net/Types/ByteArray.cs b/S7.Net/Types/ByteArray.cs index 322cee8..e80e4b5 100644 --- a/S7.Net/Types/ByteArray.cs +++ b/S7.Net/Types/ByteArray.cs @@ -50,6 +50,11 @@ namespace S7.Net.Types list.AddRange(items); } + public void Add(IEnumerable items) + { + list.AddRange(items); + } + public void Add(ByteArray byteArray) { list.AddRange(byteArray.Array);