Merge pull request #293 from scamille/fb-nullable

Add nullable support throughout the whole library.
This commit is contained in:
Michael Croes
2020-09-01 21:06:52 +02:00
committed by GitHub
13 changed files with 159 additions and 73 deletions

View File

@@ -591,15 +591,13 @@ namespace S7.Net.UnitTest
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadBytesReturnsNullIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
TestClass tc = new TestClass();
var res = await notConnectedPlc.ReadClassAsync(tc, DB2);
Assert.Fail();
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadClassAsync(tc, DB2));
}
}
@@ -637,13 +635,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadClassWithGenericReturnsNullIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
TestClass tc = await notConnectedPlc.ReadClassAsync<TestClass>(DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadClassAsync<TestClass>(DB2));
}
}
@@ -678,13 +675,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadClassWithGenericAndClassFactoryThrowsExceptionPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
TestClass tc = await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2));
}
}
@@ -711,13 +707,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
object tsObj = await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2));
}
}
@@ -754,13 +749,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadStructWithGenericThrowsExceptionIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
object tsObj = await notConnectedPlc.ReadStructAsync<TestStruct>(DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadStructAsync<TestStruct>(DB2));
}
}

View File

@@ -54,8 +54,11 @@ namespace S7.Net
public static TPDU Read(Stream stream)
{
var tpkt = TPKT.Read(stream);
if (tpkt.Length > 0) return new TPDU(tpkt);
return null;
if (tpkt.Length == 0)
{
throw new TPDUInvalidException("No protocol data received");
}
return new TPDU(tpkt);
}
/// <summary>
@@ -67,8 +70,11 @@ namespace S7.Net
public static async Task<TPDU> ReadAsync(Stream stream)
{
var tpkt = await TPKT.ReadAsync(stream);
if (tpkt.Length > 0) return new TPDU(tpkt);
return null;
if (tpkt.Length == 0)
{
throw new TPDUInvalidException("No protocol data received");
}
return new TPDU(tpkt);
}
public override string ToString()
@@ -98,7 +104,6 @@ namespace S7.Net
public static byte[] Read(Stream stream)
{
var segment = TPDU.Read(stream);
if (segment == null) return null;
var buffer = new byte[segment.Data.Length];
var output = new MemoryStream(buffer);
@@ -125,7 +130,6 @@ namespace S7.Net
public static async Task<byte[]> ReadAsync(Stream stream)
{
var segment = await TPDU.ReadAsync(stream);
if (segment == null) return null;
var buffer = new byte[segment.Data.Length];
var output = new MemoryStream(buffer);

View File

@@ -15,8 +15,8 @@ namespace S7.Net
private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060;
//TCP connection to device
private TcpClient tcpClient;
private NetworkStream stream;
private TcpClient? tcpClient;
private NetworkStream? _stream;
private int readTimeout = System.Threading.Timeout.Infinite;
private int writeTimeout = System.Threading.Timeout.Infinite;
@@ -24,27 +24,27 @@ namespace S7.Net
/// <summary>
/// IP address of the PLC
/// </summary>
public string IP { get; private set; }
public string IP { get; }
/// <summary>
/// PORT Number of the PLC, default is 102
/// </summary>
public int Port { get; private set; }
public int Port { get; }
/// <summary>
/// CPU type of the PLC
/// </summary>
public CpuType CPU { get; private set; }
public CpuType CPU { get; }
/// <summary>
/// Rack of the PLC
/// </summary>
public Int16 Rack { get; private set; }
public Int16 Rack { get; }
/// <summary>
/// Slot of the CPU of the PLC
/// </summary>
public Int16 Slot { get; private set; }
public Int16 Slot { get; }
/// <summary>
/// Max PDU size this cpu supports

View File

@@ -89,4 +89,25 @@ namespace S7.Net
}
#endif
}
internal class TPDUInvalidException : Exception
{
public TPDUInvalidException() : base()
{
}
public TPDUInvalidException(string message) : base(message)
{
}
public TPDUInvalidException(string message, Exception innerException) : base(message, innerException)
{
}
#if NET_FULL
protected TPDUInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
#endif
}
}

View File

@@ -82,7 +82,7 @@ namespace S7.Net
/// <param name="varCount"></param>
/// <param name="bitAdr"></param>
/// <returns></returns>
private object ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0)
private object? ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0)
{
if (bytes == null || bytes.Length == 0)
return null;

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
using S7.Net.Protocol;
using System.IO;
namespace S7.Net
{
@@ -20,9 +21,14 @@ namespace S7.Net
public async Task OpenAsync()
{
await ConnectAsync();
var stream = GetStreamIfAvailable();
await stream.WriteAsync(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
var response = await COTP.TPDU.ReadAsync(stream);
if (response == null)
{
throw new Exception("Error reading Connection Confirm. Malformed TPDU packet");
}
if (response.PDUType != 0xd0) //Connect Confirm
{
throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d);
@@ -46,9 +52,10 @@ namespace S7.Net
tcpClient = new TcpClient();
ConfigureConnection();
await tcpClient.ConnectAsync(IP, Port);
stream = tcpClient.GetStream();
_stream = tcpClient.GetStream();
}
/// <summary>
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
/// If the read was not successful, check LastErrorCode or LastErrorString.
@@ -87,7 +94,7 @@ namespace S7.Net
/// <param name="varType">Type of the variable/s that you are reading</param>
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
/// <param name="varCount"></param>
public async Task<object> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
public async Task<object?> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
{
int cntBytes = VarTypeToByteLength(varType, varCount);
byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes);
@@ -100,7 +107,7 @@ namespace S7.Net
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <returns>Returns an object that contains the value. This object must be cast accordingly.</returns>
public async Task<object> ReadAsync(string variable)
public async Task<object?> ReadAsync(string variable)
{
var adr = new PLCAddress(variable);
return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
@@ -113,7 +120,7 @@ namespace S7.Net
/// <param name="db">Address of the DB.</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>Returns a struct that must be cast.</returns>
public async Task<object> ReadStructAsync(Type structType, int db, int startByteAdr = 0)
public async Task<object?> ReadStructAsync(Type structType, int db, int startByteAdr = 0)
{
int numBytes = Types.Struct.GetStructSize(structType);
// now read the package
@@ -168,7 +175,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public async Task<T> ReadClassAsync<T>(int db, int startByteAdr = 0) where T : class
public async Task<T?> ReadClassAsync<T>(int db, int startByteAdr = 0) where T : class
{
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr);
}
@@ -182,7 +189,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public async Task<T> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
public async Task<T?> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
{
var instance = classFactory();
var res = await ReadClassAsync(instance, db, startByteAdr);
@@ -208,7 +215,9 @@ namespace S7.Net
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try
{
// first create the header
@@ -376,6 +385,8 @@ namespace S7.Net
private async Task<byte[]> ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count)
{
var stream = GetStreamIfAvailable();
byte[] bytes = new byte[count];
// first create the header
@@ -406,6 +417,8 @@ namespace S7.Net
{
AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false);
@@ -424,6 +437,8 @@ namespace S7.Net
/// <returns>A task that represents the asynchronous write operation.</returns>
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value)
{
var stream = GetStreamIfAvailable();
byte[] bReceive = new byte[513];
int varCount = 0;
@@ -469,6 +484,8 @@ namespace S7.Net
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
var stream = GetStreamIfAvailable();
byte[] bReceive = new byte[513];
int varCount = 0;
@@ -510,5 +527,14 @@ namespace S7.Net
throw new PlcException(ErrorCode.WriteData, exc);
}
}
private Stream GetStreamIfAvailable()
{
if (_stream == null)
{
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
}
return _stream;
}
}
}

View File

@@ -19,6 +19,7 @@ namespace S7.Net
try
{
var stream = GetStreamIfAvailable();
stream.Write(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
var response = COTP.TPDU.Read(stream);
if (response.PDUType != 0xd0) //Connect Confirm
@@ -52,7 +53,7 @@ namespace S7.Net
tcpClient = new TcpClient();
ConfigureConnection();
tcpClient.Connect(IP, Port);
stream = tcpClient.GetStream();
_stream = tcpClient.GetStream();
}
catch (SocketException sex)
{
@@ -106,7 +107,7 @@ namespace S7.Net
/// <param name="varType">Type of the variable/s that you are reading</param>
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
/// <param name="varCount"></param>
public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
public object? Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
{
int cntBytes = VarTypeToByteLength(varType, varCount);
byte[] bytes = ReadBytes(dataType, db, startByteAdr, cntBytes);
@@ -119,8 +120,8 @@ namespace S7.Net
/// If the read was not successful, check LastErrorCode or LastErrorString.
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <returns>Returns an object that contains the value. This object must be cast accordingly.</returns>
public object Read(string variable)
/// <returns>Returns an object that contains the value. This object must be cast accordingly. If no data has been read, null will be returned</returns>
public object? Read(string variable)
{
var adr = new PLCAddress(variable);
return Read(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
@@ -132,8 +133,8 @@ namespace S7.Net
/// <param name="structType">Type of the struct to be readed (es.: TypeOf(MyStruct)).</param>
/// <param name="db">Address of the DB.</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>Returns a struct that must be cast.</returns>
public object ReadStruct(Type structType, int db, int startByteAdr = 0)
/// <returns>Returns a struct that must be cast. If no data has been read, null will be returned</returns>
public object? ReadStruct(Type structType, int db, int startByteAdr = 0)
{
int numBytes = Struct.GetStructSize(structType);
// now read the package
@@ -188,7 +189,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public T ReadClass<T>(int db, int startByteAdr = 0) where T : class
public T? ReadClass<T>(int db, int startByteAdr = 0) where T : class
{
return ReadClass(() => Activator.CreateInstance<T>(), db, startByteAdr);
}
@@ -202,7 +203,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public T ReadClass<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
public T? ReadClass<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
{
var instance = classFactory();
int readBytes = ReadClass(instance, db, startByteAdr);
@@ -340,6 +341,7 @@ namespace S7.Net
private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, int count)
{
var stream = GetStreamIfAvailable();
byte[] bytes = new byte[count];
try
{
@@ -375,6 +377,8 @@ namespace S7.Net
{
AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
stream.Write(message.Array, 0, length);
@@ -385,6 +389,7 @@ namespace S7.Net
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
{
var stream = GetStreamIfAvailable();
int varCount = 0;
try
{
@@ -429,6 +434,7 @@ namespace S7.Net
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
var stream = GetStreamIfAvailable();
int varCount = 0;
try
@@ -484,6 +490,8 @@ namespace S7.Net
//replies with bigger PDU size in connection setup.
AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try
{
// first create the header

View File

@@ -91,7 +91,7 @@ namespace S7.Net.Protocol
IList<byte> itemResults = new ArraySegment<byte>(message, 14, dataItems.Length);
List<Exception> errors = null;
List<Exception>? errors = null;
for (int i = 0; i < dataItems.Length; i++)
{

View File

@@ -15,6 +15,8 @@
<RepositoryType>git</RepositoryType>
<PackageTags>PLC Siemens Communication S7</PackageTags>
<Copyright>Derek Heiser 2015</Copyright>
<LangVersion>8.0</LangVersion>
<Nullable>Enable</Nullable>
<DebugType>portable</DebugType>
<EmbedAllSources>true</EmbedAllSources>
<IncludeSymbols>true</IncludeSymbols>

View File

@@ -10,10 +10,19 @@ namespace S7.Net
/// </summary>
internal class TPKT
{
public byte Version;
public byte Reserved1;
public int Length;
public byte[] Data;
private TPKT(byte version, byte reserved1, int length, byte[] data)
{
Version = version;
Reserved1 = reserved1;
Length = length;
Data = data;
}
/// <summary>
/// Reads a TPKT from the socket
@@ -24,21 +33,28 @@ namespace S7.Net
{
var buf = new byte[4];
int len = stream.Read(buf, 0, 4);
if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
var pkt = new TPKT
if (len < 4) throw new TPKTInvalidException("TPKT header incomplete / invalid");
var version = buf[0];
var reserved1 = buf[1];
var length = buf[2] * 256 + buf[3]; //BigEndian
if (length == 0)
{
Version = buf[0],
Reserved1 = buf[1],
Length = buf[2] * 256 + buf[3] //BigEndian
};
if (pkt.Length > 0)
{
pkt.Data = new byte[pkt.Length - 4];
len = stream.Read(pkt.Data, 0, pkt.Length - 4);
if (len < pkt.Length - 4)
throw new TPKTInvalidException("TPKT is incomplete / invalid");
throw new TPKTInvalidException("TPKT payload length is zero");
}
return pkt;
var data = new byte[length - 4];
len = stream.Read(data, 0, length - 4);
if (len < length - 4)
throw new TPKTInvalidException("TPKT payload incomplete / invalid");
return new TPKT
(
version: version,
reserved1: reserved1,
length: length,
data: data
);
}
/// <summary>
@@ -50,20 +66,28 @@ namespace S7.Net
{
var buf = new byte[4];
int len = await stream.ReadAsync(buf, 0, 4);
if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
var pkt = new TPKT
if (len < 4) throw new TPKTInvalidException("TPKT header incomplete / invalid");
var version = buf[0];
var reserved1 = buf[1];
var length = buf[2] * 256 + buf[3]; //BigEndian
if (length == 0)
{
Version = buf[0],
Reserved1 = buf[1],
Length = buf[2] * 256 + buf[3] //BigEndian
};
if (pkt.Length > 0)
{
pkt.Data = new byte[pkt.Length - 4];
len = await stream.ReadAsync(pkt.Data, 0, pkt.Length - 4);
if (len < pkt.Length - 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
throw new TPKTInvalidException("TPKT payload length is zero");
}
return pkt;
var data = new byte[length - 4];
len = await stream.ReadAsync(data, 0, length - 4);
if (len < length - 4)
throw new TPKTInvalidException("TPKT payload incomplete / invalid");
return new TPKT
(
version: version,
reserved1: reserved1,
length: length,
data: data
);
}
public override string ToString()

View File

@@ -106,9 +106,9 @@ namespace S7.Net.Types
return numBytes;
}
private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes)
private static object? GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes)
{
object value = null;
object? value = null;
switch (propertyType.Name)
{
@@ -245,7 +245,7 @@ namespace S7.Net.Types
{
int bytePos = 0;
int bitPos = 0;
byte[] bytes2 = null;
byte[]? bytes2 = null;
switch (propertyValue.GetType().Name)
{

View File

@@ -40,7 +40,7 @@ namespace S7.Net.Types
/// <summary>
/// Contains the value of the memory area after the read has been executed
/// </summary>
public object Value { get; set; }
public object? Value { get; set; }
/// <summary>
/// Create an instance of DataItem
@@ -83,7 +83,14 @@ namespace S7.Net.Types
var dataItem = FromAddress(address);
dataItem.Value = value;
if (typeof(T).IsArray) dataItem.Count = ((Array) dataItem.Value).Length;
if (typeof(T).IsArray)
{
var array = ((Array?)dataItem.Value);
if ( array != null)
{
dataItem.Count = array.Length;
}
}
return dataItem;
}

View File

@@ -70,7 +70,7 @@ namespace S7.Net.Types
/// <param name="structType">The struct type</param>
/// <param name="bytes">The array of bytes</param>
/// <returns>The object depending on the struct type or null if fails(array-length != struct-length</returns>
public static object FromBytes(Type structType, byte[] bytes)
public static object? FromBytes(Type structType, byte[] bytes)
{
if (bytes == null)
return null;
@@ -198,7 +198,7 @@ namespace S7.Net.Types
int size = Struct.GetStructSize(type);
byte[] bytes = new byte[size];
byte[] bytes2 = null;
byte[]? bytes2 = null;
int bytePos = 0;
int bitPos = 0;