Skip to content

Commit

Permalink
Release S7NetPlus 0.5.0
Browse files Browse the repository at this point in the history
Release highlights:
- Add support for (I|O|Q)(B|D|W) addressing
- Fix Type for Mxxxx.x addresses
- Align array offsets to even bytes in classes
- Improve exceptions on failed reads
  • Loading branch information
mycroes committed Aug 13, 2020
2 parents 798913c + ff20687 commit 6554b99
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 9 deletions.
156 changes: 156 additions & 0 deletions S7.Net.UnitTest/PLCAddressParsingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;
using System;

namespace S7.Net.UnitTest
{
[TestClass]
public class PLCAddressParsingTests
{
[TestMethod]
public void T01_ParseM2000_1()
{
DataItem dataItem = DataItem.FromAddress("M2000.1");

Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for M2000.1");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for M2000.1");
Assert.AreEqual(VarType.Bit, dataItem.VarType, "Wrong vartype for M2000.1");
Assert.AreEqual(2000, dataItem.StartByteAdr, "Wrong startbyte for M2000.1");
Assert.AreEqual(1, dataItem.BitAdr, "Wrong bit for M2000.1");
}

[TestMethod]
public void T02_ParseMB200()
{
DataItem dataItem = DataItem.FromAddress("MB200");

Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for MB200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for MB200");
Assert.AreEqual(VarType.Byte, dataItem.VarType, "Wrong vartype for MB200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for MB200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for MB200");
}

[TestMethod]
public void T03_ParseMW200()
{
DataItem dataItem = DataItem.FromAddress("MW200");

Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for MW200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for MW200");
Assert.AreEqual(VarType.Word, dataItem.VarType, "Wrong vartype for MW200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for MW200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for MW200");
}

[TestMethod]
public void T04_ParseMD200()
{
DataItem dataItem = DataItem.FromAddress("MD200");

Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for MD200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for MD200");
Assert.AreEqual(VarType.DWord, dataItem.VarType, "Wrong vartype for MD200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for MD200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for MD200");
}


[TestMethod]
public void T05_ParseI2000_1()
{
DataItem dataItem = DataItem.FromAddress("I2000.1");

Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for I2000.1");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for I2000.1");
Assert.AreEqual(VarType.Bit, dataItem.VarType, "Wrong vartype for I2000.1");
Assert.AreEqual(2000, dataItem.StartByteAdr, "Wrong startbyte for I2000.1");
Assert.AreEqual(1, dataItem.BitAdr, "Wrong bit for I2000.1");
}

[TestMethod]
public void T06_ParseIB200()
{
DataItem dataItem = DataItem.FromAddress("IB200");

Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for IB200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for IB200");
Assert.AreEqual(VarType.Byte, dataItem.VarType, "Wrong vartype for IB200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for IB200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for IB200");
}

[TestMethod]
public void T07_ParseIW200()
{
DataItem dataItem = DataItem.FromAddress("IW200");

Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for IW200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for IW200");
Assert.AreEqual(VarType.Word, dataItem.VarType, "Wrong vartype for IW200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for IW200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for IW200");
}

[TestMethod]
public void T08_ParseID200()
{
DataItem dataItem = DataItem.FromAddress("ID200");

Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for ID200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for ID200");
Assert.AreEqual(VarType.DWord, dataItem.VarType, "Wrong vartype for ID200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for ID200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for ID200");
}


[TestMethod]
public void T09_ParseQ2000_1()
{
DataItem dataItem = DataItem.FromAddress("Q2000.1");

Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for Q2000.1");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for Q2000.1");
Assert.AreEqual(VarType.Bit, dataItem.VarType, "Wrong vartype for Q2000.1");
Assert.AreEqual(2000, dataItem.StartByteAdr, "Wrong startbyte for Q2000.1");
Assert.AreEqual(1, dataItem.BitAdr, "Wrong bit for Q2000.1");
}

[TestMethod]
public void T10_ParseQB200()
{
DataItem dataItem = DataItem.FromAddress("QB200");

Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for QB200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for QB200");
Assert.AreEqual(VarType.Byte, dataItem.VarType, "Wrong vartype for QB200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for QB200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for QB200");
}

[TestMethod]
public void T11_ParseQW200()
{
DataItem dataItem = DataItem.FromAddress("QW200");

Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for QW200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for QW200");
Assert.AreEqual(VarType.Word, dataItem.VarType, "Wrong vartype for QW200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for QW200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for QW200");
}

[TestMethod]
public void T12_ParseQD200()
{
DataItem dataItem = DataItem.FromAddress("QD200");

Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for QD200");
Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for QD200");
Assert.AreEqual(VarType.DWord, dataItem.VarType, "Wrong vartype for QD200");
Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for QD200");
Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for QD200");
}
}
}
2 changes: 2 additions & 0 deletions S7.Net.UnitTest/S7.Net.UnitTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<Compile Include="ConnectionRequestTest.cs" />
<Compile Include="ConvertersUnitTest.cs" />
<Compile Include="Helpers\TestClassWithNestedClass.cs" />
<Compile Include="PLCAddressParsingTests.cs" />
<Compile Include="ProtocolTests.cs" />
<Compile Include="Helpers\ConsoleManager.cs" />
<Compile Include="Helpers\NativeMethods.cs" />
Expand All @@ -79,6 +80,7 @@
<Compile Include="S7NetTestsSync.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\TestLongStruct.cs" />
<Compile Include="TypeTests\ClassTests.cs" />
<Compile Include="TypeTests\DateTimeTests.cs" />
<Compile Include="TypeTests\StringExTests.cs" />
<Compile Include="TypeTests\StringTests.cs" />
Expand Down
33 changes: 33 additions & 0 deletions S7.Net.UnitTest/TypeTests/ClassTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;

namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class ClassTests
{
[TestMethod]
public void GetClassSizeTest()
{
Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(1, 1)), 6);
Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(2, 15)), 6);
Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(2, 16)), 6);
Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(2, 17)), 8);
Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(3, 15)), 8);
Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(3, 17)), 10);
}

private class TestClassUnevenSize
{
public bool Bool { get; set; }
public byte[] Bytes { get; set; }
public bool[] Bools { get; set; }

public TestClassUnevenSize(int byteCount, int bitCount)
{
Bytes = new byte[byteCount];
Bools = new bool[bitCount];
}
}
}
}
21 changes: 21 additions & 0 deletions S7.Net/PLC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,27 @@ private int GetDataLength(IEnumerable<DataItem> dataItems)
.Sum(len => (len & 1) == 1 ? len + 1 : len);
}

private static void AssertReadResponse(byte[] s7Data, int dataLength)
{
var expectedLength = dataLength + 18;

PlcException NotEnoughBytes() =>
new PlcException(ErrorCode.WrongNumberReceivedBytes,
$"Received {s7Data.Length} bytes: '{BitConverter.ToString(s7Data)}', expected {expectedLength} bytes.")
;

if (s7Data == null)
throw new PlcException(ErrorCode.WrongNumberReceivedBytes, "No s7Data received.");

if (s7Data.Length < 15) throw NotEnoughBytes();

if (s7Data[14] != 0xff)
throw new PlcException(ErrorCode.ReadData,
$"Invalid response from PLC: '{BitConverter.ToString(s7Data)}'.");

if (s7Data.Length < expectedLength) throw NotEnoughBytes();
}

#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls

Expand Down
12 changes: 11 additions & 1 deletion S7.Net/PLCAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,42 +80,51 @@ public static void Parse(string input, out DataType dataType, out int dbNumber,
default:
throw new InvalidAddressException();
}
case "IB":
case "EB":
// Input byte
dataType = DataType.Input;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Byte;
return;
case "IW":
case "EW":
// Input word
dataType = DataType.Input;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Word;
return;
case "ID":
case "ED":
// Input double-word
dataType = DataType.Input;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.DWord;
return;
case "QB":
case "AB":
case "OB":
// Output byte
dataType = DataType.Output;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Byte;
return;
case "QW":
case "AW":
case "OW":
// Output word
dataType = DataType.Output;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Word;
return;
case "QD":
case "AD":
case "OD":
// Output double-word
dataType = DataType.Output;
dbNumber = 0;
Expand Down Expand Up @@ -152,6 +161,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber,
dataType = DataType.Input;
varType = VarType.Bit;
break;
case "Q":
case "A":
case "O":
// Output
Expand All @@ -161,7 +171,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber,
case "M":
// Memory
dataType = DataType.Memory;
varType = VarType.Byte;
varType = VarType.Bit;
break;
case "T":
// Timer
Expand Down
3 changes: 1 addition & 2 deletions S7.Net/PlcAsynchronous.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,7 @@ private async Task<byte[]> ReadBytesWithSingleRequestAsync(DataType dataType, in
await stream.WriteAsync(package.Array, 0, package.Array.Length);

var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
AssertReadResponse(s7data, count);

for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
Expand Down
3 changes: 1 addition & 2 deletions S7.Net/PlcSynchronous.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,7 @@ private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startBy
stream.Write(package.Array, 0, package.Array.Length);

var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
AssertReadResponse(s7data, count);

for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
Expand Down
15 changes: 11 additions & 4 deletions S7.Net/Types/Class.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i
throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero.");
}

IncrementToEven(ref numBytes);
for (int i = 0; i < array.Length; i++)
{
numBytes = GetIncreasedNumberOfBytes(numBytes, elementType);
Expand Down Expand Up @@ -219,6 +220,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes
if (property.PropertyType.IsArray)
{
Array array = (Array)property.GetValue(sourceClass, null);
IncrementToEven(ref numBytes);
Type elementType = property.PropertyType.GetElementType();
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
Expand Down Expand Up @@ -288,10 +290,8 @@ private static double SetBytesFromProperty(object propertyValue, byte[] bytes, d

if (bytes2 != null)
{
// add them
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
IncrementToEven(ref numBytes);

bytePos = (int)numBytes;
for (int bCnt = 0; bCnt < bytes2.Length; bCnt++)
bytes[bytePos + bCnt] = bytes2[bCnt];
Expand All @@ -314,6 +314,7 @@ public static double ToBytes(object sourceClass, byte[] bytes, double numBytes =
if (property.PropertyType.IsArray)
{
Array array = (Array)property.GetValue(sourceClass, null);
IncrementToEven(ref numBytes);
Type elementType = property.PropertyType.GetElementType();
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
Expand All @@ -327,5 +328,11 @@ public static double ToBytes(object sourceClass, byte[] bytes, double numBytes =
}
return numBytes;
}

private static void IncrementToEven(ref double numBytes)
{
numBytes = Math.Ceiling(numBytes);
if (numBytes % 2 > 0) numBytes++;
}
}
}

0 comments on commit 6554b99

Please sign in to comment.