Merge pull request #121 from mycroes/master

Add multiple write support
This commit is contained in:
Michele Cattafesta
2018-06-21 13:31:39 +01:00
committed by GitHub
7 changed files with 265 additions and 38 deletions

View File

@@ -316,42 +316,6 @@ 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();
}
}
/// <summary>
/// Sets the <see cref="LastErrorCode"/> to <see cref="ErrorCode.NoError"/> and <see cref="LastErrorString"/> to <see cref="string.Empty"/>.
/// </summary>

View File

@@ -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
{
@@ -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));
}
/// <summary>
@@ -411,6 +412,22 @@ namespace S7.Net
return bytes;
}
/// <summary>
/// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
/// or when the PLC reports errors for item(s) written.
/// </summary>
/// <param name="dataItems">The DataItem(s) to write to the PLC.</param>
/// <returns>Task that completes when response from PLC is parsed.</returns>
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);
}
/// <summary>
/// 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.

View File

@@ -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
@@ -324,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));
}
/// <summary>
@@ -401,6 +402,21 @@ namespace S7.Net
}
}
/// <summary>
/// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
/// or when the PLC reports errors for item(s) written.
/// </summary>
/// <param name="dataItems">The DataItem(s) to write to the PLC.</param>
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;

View File

@@ -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<byte> itemResults = new ArraySegment<byte>(message, 14, dataItems.Length);
List<Exception> errors = null;
for (int i = 0; i < dataItems.Length; i++)
{
var result = itemResults[i];
if (result == 0xff) continue;
if (errors == null) errors = new List<Exception>();
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;
}
}
}
}

View File

@@ -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<byte> 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;
}
}
}

View File

@@ -85,6 +85,8 @@
<Compile Include="PlcHelpers.cs" />
<Compile Include="PlcSynchronous.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Protocol\S7WriteMultiple.cs" />
<Compile Include="Protocol\Serialization.cs" />
<Compile Include="TPKT.cs" />
<Compile Include="Types\Bit.cs" />
<Compile Include="Types\Boolean.cs" />

View File

@@ -6,11 +6,19 @@ namespace S7.Net.Types
{
List<byte> list = new List<byte>();
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<byte>();
@@ -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);