diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs
index 5948171..6daa9f7 100644
--- a/S7.Net.UnitTest/S7NetTestsSync.cs
+++ b/S7.Net.UnitTest/S7NetTestsSync.cs
@@ -897,6 +897,21 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc.Bool1, tc2.Bool1);
}
+ [TestMethod]
+ public void T29_Read_Write_ExceptionHandlingWhenPlcIsNotReachable()
+ {
+ // leave plc Open
+ S7TestServer.Stop();
+
+ double test_value = 55.66;
+ plc.Write("DB1.DBD0", test_value);
+ Assert.AreEqual(plc.LastErrorCode, ErrorCode.WriteData, "No Write Error.");
+
+ var helper = plc.Read("DB1.DBD0");
+ Assert.AreEqual(helper, null, "Value in Read.");
+ Assert.AreEqual(plc.LastErrorCode, ErrorCode.ReadData, "No Read Error.");
+ }
+
#endregion
#region Private methods
diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs
index 7525ff2..cb004e2 100644
--- a/S7.Net/PLCHelpers.cs
+++ b/S7.Net/PLCHelpers.cs
@@ -154,7 +154,7 @@ namespace S7.Net
varType = VarType.Counter;
return;
default:
- throw new InvalidAddressException(string.Format("{0} is not av valid address", address.Substring(0, 1)));
+ throw new InvalidAddressException(string.Format("{0} is not a valid address", address.Substring(0, 1)));
}
string txt2 = address.Substring(1);
@@ -248,7 +248,7 @@ namespace S7.Net
///
private object ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0)
{
- if (bytes == null)
+ if (bytes == null || bytes.Length == 0)
return null;
switch (varType)
@@ -316,46 +316,10 @@ namespace S7.Net
}
}
- public byte[] GetPackage(object value)
- {
- switch (value.GetType().Name)
- {
- case "Byte":
- return Types.Byte.ToByteArray((byte)value);
- case "Int16":
- return Types.Int.ToByteArray((Int16)value);
- case "UInt16":
- return Types.Word.ToByteArray((UInt16)value);
- case "Int32":
- return Types.DInt.ToByteArray((Int32)value);
- case "UInt32":
- return Types.DWord.ToByteArray((UInt32)value);
- case "Double":
- return Types.Double.ToByteArray((double)value);
- case "Byte[]":
- return (byte[])value;
- case "Int16[]":
- return Types.Int.ToByteArray((Int16[])value);
- case "UInt16[]":
- return Types.Word.ToByteArray((UInt16[])value);
- case "Int32[]":
- return Types.DInt.ToByteArray((Int32[])value);
- case "UInt32[]":
- return Types.DWord.ToByteArray((UInt32[])value);
- case "Double[]":
- return Types.Double.ToByteArray((double[])value);
- case "String":
- return Types.String.ToByteArray(value as string);
- default:
- throw new InvalidVariableTypeException();
- }
- }
-
-
- ///
- /// Sets the to and to .
- ///
- public void ClearLastError()
+ ///
+ /// Sets the to and to .
+ ///
+ public void ClearLastError()
{
LastErrorCode = ErrorCode.NoError;
LastErrorString = string.Empty;
diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs
index efaab86..d6c639e 100644
--- a/S7.Net/PlcAsynchronous.cs
+++ b/S7.Net/PlcAsynchronous.cs
@@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
+using S7.Net.Protocol;
namespace S7.Net
{
@@ -234,12 +235,12 @@ namespace S7.Net
}
catch (SocketException socketException)
{
- LastErrorCode = ErrorCode.WriteData;
+ LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
}
catch (Exception exc)
{
- LastErrorCode = ErrorCode.WriteData;
+ LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
}
return dataItems;
@@ -344,7 +345,7 @@ namespace S7.Net
}
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
}
- return await WriteBytesAsync(dataType, db, startByteAdr, GetPackage(value));
+ return await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value));
}
///
@@ -411,6 +412,22 @@ namespace S7.Net
return bytes;
}
+ ///
+ /// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
+ /// or when the PLC reports errors for item(s) written.
+ ///
+ /// The DataItem(s) to write to the PLC.
+ /// Task that completes when response from PLC is parsed.
+ public async Task WriteAsync(params DataItem[] dataItems)
+ {
+ var message = new ByteArray();
+ var length = S7WriteMultiple.CreateRequest(message, dataItems);
+ await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false);
+
+ var response = await COTP.TSDU.ReadAsync(stream).ConfigureAwait(false);
+ S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
+ }
+
///
/// Writes up to 200 bytes to the PLC and returns NoError if successful. You must specify the memory area type, memory are address, byte start address and bytes count.
/// If the write was not successful, check LastErrorCode or LastErrorString.
diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs
index b0ddcbd..ccd4279 100644
--- a/S7.Net/PlcSynchronous.cs
+++ b/S7.Net/PlcSynchronous.cs
@@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
+using S7.Net.Protocol;
//Implement obsolete synchronous methods here
namespace S7.Net
@@ -272,7 +273,8 @@ namespace S7.Net
ErrorCode lastError = WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value);
if (lastError != ErrorCode.NoError)
{
- return lastError; }
+ return lastError;
+ }
return ErrorCode.NoError;
}
@@ -323,7 +325,7 @@ namespace S7.Net
}
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
}
- return WriteBytes(dataType, db, startByteAdr, GetPackage(value));
+ return WriteBytes(dataType, db, startByteAdr, Serialization.SerializeValue(value));
}
///
@@ -388,18 +390,33 @@ namespace S7.Net
}
catch (SocketException socketException)
{
- LastErrorCode = ErrorCode.WriteData;
+ LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
return null;
}
catch (Exception exc)
{
- LastErrorCode = ErrorCode.WriteData;
+ LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
return null;
}
}
+ ///
+ /// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
+ /// or when the PLC reports errors for item(s) written.
+ ///
+ /// The DataItem(s) to write to the PLC.
+ public void Write(params DataItem[] dataItems)
+ {
+ var message = new ByteArray();
+ var length = S7WriteMultiple.CreateRequest(message, dataItems);
+ stream.Write(message.Array, 0, length);
+
+ var response = COTP.TSDU.Read(stream);
+ S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
+ }
+
private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
{
int varCount = 0;
@@ -525,7 +542,7 @@ namespace S7.Net
{
package.Add(CreateReadDataRequestPackage(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count)));
}
-
+
stream.Write(package.Array, 0, package.Array.Length);
var s7data = COTP.TSDU.Read(stream); //TODO use Async
@@ -536,12 +553,12 @@ namespace S7.Net
}
catch (SocketException socketException)
{
- LastErrorCode = ErrorCode.WriteData;
+ LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
}
catch (Exception exc)
{
- LastErrorCode = ErrorCode.WriteData;
+ LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
}
}
diff --git a/S7.Net/Protocol/S7WriteMultiple.cs b/S7.Net/Protocol/S7WriteMultiple.cs
new file mode 100644
index 0000000..f4b2457
--- /dev/null
+++ b/S7.Net/Protocol/S7WriteMultiple.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using S7.Net.Types;
+
+namespace S7.Net.Protocol
+{
+ internal static class S7WriteMultiple
+ {
+ public static int CreateRequest(ByteArray message, DataItem[] dataItems)
+ {
+ message.Add(Header.Template);
+
+ message[Header.Offsets.ParameterCount] = (byte) dataItems.Length;
+ var paramSize = dataItems.Length * Parameter.Template.Length;
+
+ Serialization.SetWordAt(message, Header.Offsets.ParameterSize,
+ (ushort) (2 + paramSize));
+
+ var paramOffset = Header.Template.Length;
+ var dataOffset = paramOffset + paramSize;
+ var data = new ByteArray();
+
+ foreach (var item in dataItems)
+ {
+ message.Add(Parameter.Template);
+ var value = Serialization.SerializeValue(item.Value);
+ var wordLen = item.Value is bool ? 1 : 2;
+
+ message[paramOffset + Parameter.Offsets.WordLength] = (byte) wordLen;
+ Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.Amount, (ushort) value.Length);
+ Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.DbNumber, (ushort) item.DB);
+ message[paramOffset + Parameter.Offsets.Area] = (byte) item.DataType;
+ Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr, item.BitAdr);
+
+ paramOffset += Parameter.Template.Length;
+
+ data.Add(0x00);
+ if (item.Value is bool b)
+ {
+ data.Add(0x03);
+ data.AddWord(1);
+
+ data.Add(b ? (byte) 1 : (byte) 0);
+ data.Add(0);
+ }
+ else
+ {
+ var len = value.Length;
+ data.Add(0x04);
+ data.AddWord((ushort) (len << 3));
+ data.Add(value);
+
+ if ((len & 0b1) == 1)
+ {
+ data.Add(0);
+ }
+ }
+ }
+
+
+ message.Add(data.Array);
+
+ Serialization.SetWordAt(message, Header.Offsets.MessageLength, (ushort) message.Length);
+ Serialization.SetWordAt(message, Header.Offsets.DataLength, (ushort) (message.Length - paramOffset));
+
+ return message.Length;
+ }
+
+ public static void ParseResponse(byte[] message, int length, DataItem[] dataItems)
+ {
+ if (length < 12) throw new Exception("Not enough data received to parse write response.");
+
+ var messageError = Serialization.GetWordAt(message, 10);
+ if (messageError != 0)
+ throw new Exception($"Write failed with error {messageError}.");
+
+ if (length < 14 + dataItems.Length)
+ throw new Exception("Not enough data received to parse individual item responses.");
+
+ IList itemResults = new ArraySegment(message, 14, dataItems.Length);
+
+ List errors = null;
+
+ for (int i = 0; i < dataItems.Length; i++)
+ {
+ var result = itemResults[i];
+ if (result == 0xff) continue;
+
+ if (errors == null) errors = new List();
+ errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed with error code {result}."));
+ }
+
+ if (errors != null)
+ throw new AggregateException(
+ $"Write failed for {errors.Count} items. See the innerExceptions for details.", errors);
+ }
+
+ private static class Header
+ {
+ public static byte[] Template { get; } =
+ {
+ 0x03, 0x00, 0x00, 0x00, // TPKT
+ 0x02, 0xf0, 0x80, // ISO DT
+ 0x32, // S7 protocol ID
+ 0x01, // JobRequest
+ 0x00, 0x00, // Reserved
+ 0x05, 0x00, // PDU reference
+ 0x00, 0x0e, // Parameters length
+ 0x00, 0x00, // Data length
+ 0x05, // Function: Write var
+ 0x00, // Number of items to write
+ };
+
+ public static class Offsets
+ {
+ public const int MessageLength = 2;
+ public const int ParameterSize = 13;
+ public const int DataLength = 15;
+ public const int ParameterCount = 18;
+ }
+ }
+
+ private static class Parameter
+ {
+ public static byte[] Template { get; } =
+ {
+ 0x12, // Spec
+ 0x0a, // Length of remaining bytes
+ 0x10, // Addressing mode
+ 0x02, // Transport size
+ 0x00, 0x00, // Number of elements
+ 0x00, 0x00, // DB number
+ 0x84, // Area type
+ 0x00, 0x00, 0x00 // Area offset
+ };
+
+ public static class Offsets
+ {
+ public const int WordLength = 3;
+ public const int Amount = 4;
+ public const int DbNumber = 6;
+ public const int Area = 8;
+ public const int Address = 9;
+ }
+ }
+ }
+}
diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs
new file mode 100644
index 0000000..f586731
--- /dev/null
+++ b/S7.Net/Protocol/Serialization.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using S7.Net.Types;
+
+namespace S7.Net.Protocol
+{
+ internal static class Serialization
+ {
+ public static ushort GetWordAt(IList buf, int index)
+ {
+ return (ushort) ((buf[index] << 8) + buf[index]);
+ }
+
+ public static byte[] SerializeValue(object value)
+ {
+ switch (value.GetType().Name)
+ {
+ case "Boolean":
+ return new[] {(byte) ((bool) value ? 1 : 0)};
+ case "Byte":
+ return Types.Byte.ToByteArray((byte) value);
+ case "Int16":
+ return Types.Int.ToByteArray((Int16) value);
+ case "UInt16":
+ return Types.Word.ToByteArray((UInt16) value);
+ case "Int32":
+ return Types.DInt.ToByteArray((Int32) value);
+ case "UInt32":
+ return Types.DWord.ToByteArray((UInt32) value);
+ case "Double":
+ return Types.Double.ToByteArray((double) value);
+ case "Byte[]":
+ return (byte[]) value;
+ case "Int16[]":
+ return Types.Int.ToByteArray((Int16[]) value);
+ case "UInt16[]":
+ return Types.Word.ToByteArray((UInt16[]) value);
+ case "Int32[]":
+ return Types.DInt.ToByteArray((Int32[]) value);
+ case "UInt32[]":
+ return Types.DWord.ToByteArray((UInt32[]) value);
+ case "Double[]":
+ return Types.Double.ToByteArray((double[]) value);
+ case "String":
+ return Types.String.ToByteArray(value as string);
+ default:
+ throw new InvalidVariableTypeException();
+ }
+ }
+
+ public static void SetAddressAt(ByteArray buffer, int index, int startByte, byte bitNumber)
+ {
+ var start = startByte * 8 + bitNumber;
+ buffer[index + 2] = (byte) start;
+ start = start >> 8;
+ buffer[index + 1] = (byte) start;
+ start = start >> 8;
+ buffer[index] = (byte) start;
+ }
+
+ public static void SetWordAt(ByteArray buffer, int index, ushort value)
+ {
+ buffer[index] = (byte) (value >> 8);
+ buffer[index + 1] = (byte) value;
+ }
+ }
+}
diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj
index 6b576f1..1bcf18e 100644
--- a/S7.Net/S7.Net.csproj
+++ b/S7.Net/S7.Net.csproj
@@ -1,4 +1,4 @@
-
+
@@ -7,5 +7,5 @@
Properties\S7.Net.snk
S7.Net.UnitTest
-
+
\ No newline at end of file
diff --git a/S7.Net/Types/ByteArray.cs b/S7.Net/Types/ByteArray.cs
index ca8456a..322cee8 100644
--- a/S7.Net/Types/ByteArray.cs
+++ b/S7.Net/Types/ByteArray.cs
@@ -6,11 +6,19 @@ namespace S7.Net.Types
{
List list = new List();
+ public byte this[int index]
+ {
+ get => list[index];
+ set => list[index] = value;
+ }
+
public byte[] Array
{
get { return list.ToArray(); }
}
+ public int Length => list.Count;
+
public ByteArray()
{
list = new List();
@@ -31,6 +39,12 @@ namespace S7.Net.Types
list.Add(item);
}
+ public void AddWord(ushort value)
+ {
+ list.Add((byte) (value >> 8));
+ list.Add((byte) value);
+ }
+
public void Add(byte[] items)
{
list.AddRange(items);
diff --git a/S7.Net/Types/Int.cs b/S7.Net/Types/Int.cs
index 468e8b3..5489691 100644
--- a/S7.Net/Types/Int.cs
+++ b/S7.Net/Types/Int.cs
@@ -29,8 +29,8 @@ namespace S7.Net.Types
{
byte[] bytes = new byte[2];
- bytes[1] = (byte)((int)value & 0xFF);
- bytes[0] = (byte)((int)value >> 8 & 0xFF);
+ bytes[0] = (byte) (value >> 8 & 0xFF);
+ bytes[1] = (byte)(value & 0xFF);
return bytes;
}
@@ -45,8 +45,8 @@ namespace S7.Net.Types
for(int i=0; i< value.Length; i++)
{
- bytes[bytesPos++] = (byte)((int)value[i] & 0xFF);
- bytes[bytesPos++] = (byte)(((int)value[i] >> 8) & 0xFF);
+ bytes[bytesPos++] = (byte)((value[i] >> 8) & 0xFF);
+ bytes[bytesPos++] = (byte) (value[i] & 0xFF);
}
return bytes;
}