mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
Merge branch 'master' into master
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
/// <returns></returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="LastErrorCode"/> to <see cref="ErrorCode.NoError"/> and <see cref="LastErrorString"/> to <see cref="string.Empty"/>.
|
||||
/// </summary>
|
||||
public void ClearLastError()
|
||||
/// <summary>
|
||||
/// Sets the <see cref="LastErrorCode"/> to <see cref="ErrorCode.NoError"/> and <see cref="LastErrorString"/> to <see cref="string.Empty"/>.
|
||||
/// </summary>
|
||||
public void ClearLastError()
|
||||
{
|
||||
LastErrorCode = ErrorCode.NoError;
|
||||
LastErrorString = string.Empty;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/// <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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
147
S7.Net/Protocol/S7WriteMultiple.cs
Normal file
147
S7.Net/Protocol/S7WriteMultiple.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
S7.Net/Protocol/Serialization.cs
Normal file
67
S7.Net/Protocol/Serialization.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -7,5 +7,5 @@
|
||||
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<InternalsVisibleTo>S7.Net.UnitTest</InternalsVisibleTo>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user