70 Commits

Author SHA1 Message Date
Michael Croes
6554b999c0 Release S7NetPlus 0.5.0
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
2020-08-13 22:57:22 +02:00
Michael Croes
ff20687776 Merge pull request #282 from mycroes/develop
PLC: Improve exceptions on Read
2020-08-13 22:50:20 +02:00
Michael Croes
0b6226327b PLC: Improve exceptions on Read
Close #258.
2020-08-13 22:47:32 +02:00
Michael Croes
385240ba5e Merge pull request #281 from mycroes/develop
PLCAddress: Add OB, OW, OD types from PR #277
2020-08-13 22:08:13 +02:00
Michael Croes
0a8ee0e091 PLCAddress: Add OB, OW, OD types from PR #277
PR #246 included most types also included in #277, this adds OB, OW and
OD that were only in #277.

Close #277.
2020-08-13 22:04:53 +02:00
Michael Croes
1685270535 Merge pull request #280 from mycroes/develop
Tests/TypeTests: Add ClassTests from #178
2020-08-13 22:00:48 +02:00
Michael Croes
9a34b14e1e Tests/TypeTests: Add ClassTests from #178
Close #178.
2020-08-13 21:57:42 +02:00
Michael Croes
50f0e62573 Merge pull request #279 from mycroes/develop
Types/Class: Start arrays on even bytes.

Close #175, #220.
2020-08-13 21:49:30 +02:00
Michael Croes
9ea54be524 Types/Class: Start arrays on even bytes
Addresses #175
2020-08-13 21:40:44 +02:00
Michael Croes
dcd5bb3437 Merge pull request #246 from timverwaal/PLCAdddres-Parsing
Extended PLCAddress.Parse method
2020-08-13 20:43:16 +02:00
Tim Verwaal
8dc89867e9 Extended PLCAddress.Parse method 2019-12-19 10:55:51 +01:00
Michael Croes
798913c4c6 Release S7NetPlus 0.4.0
Release highlights:
- Adress checks for bit writes
- Support for reading/writing complex objects
- Better exceptions on Open(Async)
- Revert negotiated max PDU size to 960
- Add Logo0BA8 support
- Read/Write-Timeout support on TCP connection
- Fix for 0-padding of last dataItem in WriteMultiple
- Allow override of default port
- Improve exception message when parsing string
- Add DateTime type for reading/writing
- Verify items fit in a PDU on Read/Write-Multiple
- Fix size calculation for bit arrays
2019-07-17 22:01:55 +02:00
Michael Croes
2fbf659136 Merge pull request #167 from mycroes/bit-arrays
Fix incorrect length when reading BitArray
2019-07-17 21:50:12 +02:00
Michael Croes
6a84b6f124 Use ToBitArray overload with length in ParseBytes 2019-07-17 21:46:18 +02:00
Michael Croes
b3cb45de37 Add ToBitArray overload with length 2019-07-17 21:46:17 +02:00
Michael Croes
4fcab9a167 Fix VarTypeToByteLength for bit arrays 2019-07-17 21:46:17 +02:00
Michael Croes
94a178478b Merge pull request #227 from mycroes/calculate-pdu-constraint
Calculate PDU size constraints
2019-07-17 21:43:38 +02:00
Michael Croes
2a4485941f Calculate PDU size constraints 2019-07-17 21:39:41 +02:00
Michael Croes
47cce5887d Merge pull request #223 from thoj/develop
Fix padding of last dataitem in WriteMulitple fixes #222
2019-07-17 21:39:19 +02:00
Michael Croes
0e5651b37f Merge branch 'develop' into develop 2019-07-17 21:22:45 +02:00
Michael Croes
1d1f1e76a9 Merge pull request #189 from mycroes/add-datetime-support
Add DateTime support
2019-07-17 21:10:12 +02:00
Michael Croes
555d1e8956 Add unit test for Types.DateTime 2019-07-17 21:07:45 +02:00
Michael Croes
427d8124de Add DateTime support for read/write methods 2019-07-17 21:07:45 +02:00
Michael Croes
735f1d4533 Add support for DateTime array conversion 2019-07-17 21:07:45 +02:00
Michael Croes
2aa4f08836 Add Types.DateTime 2019-07-17 21:07:44 +02:00
Michael Croes
8d8b2ec36e Merge pull request #173 from mycroes/stringex-read-rubbish
Provide a clear message on Encoding.ASCII.GetString exceptions
2019-07-17 21:06:47 +02:00
Michael Croes
cf64c65c23 Provide a clear message on Encoding.ASCII.GetString exceptions 2019-07-17 21:04:26 +02:00
Michael Croes
93752a3485 Merge pull request #225 from tatzemax/develop
Add Property to selected Port from PLC
2019-07-11 20:10:29 +02:00
max
229df2dfcd Merge branch 'develop' of https://github.com/tatzemax/s7netplus into develop 2019-07-11 18:11:14 +02:00
max
3b23ab76e7 correction of wrong changes 2019-07-11 18:10:14 +02:00
tatzemax
3872e638aa Merge branch 'develop' into develop 2019-07-11 17:57:14 +02:00
Thomas Jager
e124c70fb1 Merge branch 'develop' into develop 2019-07-11 12:24:07 +02:00
Michael Croes
b8b4071e39 Merge pull request #226 from SevenMag/develop
Use PDU size in WriteBytes
2019-07-11 12:13:01 +02:00
Max
b2183dd760 rename PORT to Port
add a Space
2019-07-10 20:42:52 +02:00
max
f2d33855ca add Port Setting for PLC.
is required for port forwarding
2019-07-10 18:44:04 +02:00
max
b1d2d11904 add Port Setting for PLC.
is required for port forwarding
2019-07-10 18:17:02 +02:00
Evgeniy
23796de8bf Update PlcSynchronous.cs
MaxPDUSize for WriteBytes
2019-07-03 13:04:33 +05:00
Thomas Jäger
02b38326c8 Fix padding of last dataitem in WriteMulitple fixes #222 2019-06-19 13:02:53 +02:00
Michael Croes
ea96891a31 Merge pull request #218 from ChipsetSV/develop
feature: added ReadTimeout and WriteTimeout to PLC class for NetworkStream
2019-05-28 12:34:04 +02:00
Смирнов Виталий
2fbabd5517 style: updated code style. 2019-05-28 10:01:28 +03:00
Смирнов Виталий
800a790b89 feature: added changing ReadTimeout and WriteTimeout for connected tcpClient. 2019-05-28 08:56:06 +03:00
Смирнов Виталий
0dbe401ce9 feature: added ReadTimeout and WriteTimeout to PLC class for NetworkStream. 2019-05-27 13:52:38 +03:00
Michael Croes
e623b535ac Merge pull request #196 from mycroes/invalid-data-on-open
Invalid data on open
2019-03-14 23:09:50 +01:00
Michael Croes
ce359789dc Add explicit support for Logo0BA8
Unsure if the PLC actually requires the specfically provided TSAP's,
but these have been verified to work.
2019-03-14 23:04:26 +01:00
Michael Croes
4ae905ffd5 Set PDU size to 960
Allows communication with Siemens Logo 0BA8, currently no Siemens PLC
is known to support >960 PDU size.
2019-03-14 23:04:26 +01:00
Michael Croes
70506e7dba Throw InvalidDataException in Open(Async) 2019-03-14 23:02:15 +01:00
Michael Croes
20458d4b46 Add InvalidDataException 2019-03-14 23:02:14 +01:00
Michael Croes
898b870221 Expose TPKT on TPDU
Temporary hack to allow access to the received TPKT data.
2019-03-14 23:02:14 +01:00
Michael Croes
0fd193e08f Merge pull request #212 from mycroes/gitversion-v4
Update GitVersion.yml to V4
2019-03-14 22:56:36 +01:00
Michael Croes
98df02d7d4 Update GitVersion.yml to V4 2019-03-14 22:53:16 +01:00
Michael Croes
ae620f3c62 Merge pull request #191 from admo/issue190_nested_classes
Fix for Addresses are not aligned in case of custom type in class
2018-10-10 20:28:40 +02:00
Adam Oleksy
de084394a6 Fix writing nested classes 2018-10-04 18:14:44 +02:00
Adam Oleksy
370fd6b3d9 Fix reading nested classes 2018-09-26 14:43:49 +02:00
Derek Heiser
fba3ffd5db Merge pull request #169 from mycroes/dataitem-from-string-write-fix
Dataitem from string write fix
2018-07-18 18:39:21 -05:00
Michael Croes
e44cb1571c Validate BitAdr when writing a bit 2018-07-12 20:51:37 +02:00
Michael Croes
ab486e3d1f Only set bitNumber in address for Write when a bit is written 2018-07-12 20:51:20 +02:00
Michael Croes
705743e5f1 Convert bitNumber '-1' to 0 in DataItem 2018-07-12 20:49:44 +02:00
Michael Croes
fd17bfa03b Merge branch 'release/0.3' 2018-07-11 22:51:36 +02:00
Michael Croes
8dad14955e Merge pull request #166 from mycroes/fix-string-to-byte-array
Fix string to byte array
2018-07-11 22:46:44 +02:00
Michael Croes
96efb9d56a Add tests for String.ToByteArray(...) 2018-07-11 22:07:34 +02:00
Michael Croes
09d323925a Add length to String.ToByteArray(...) 2018-07-11 22:07:08 +02:00
Michael Croes
bf4550655e Add support for creating DataItem from string (#149)
* Refactor PLCAddress

- Change fields into properties
- Apply PascalCase naming to all properties
- Make Parse public static with out parameters

* Support creating DataItem from string

* Rename PlcAddress.Address to StartByte
2018-07-11 10:21:49 +02:00
Raphael Schlameuß
214a7a73c8 Read many bytes 1500 (develop branch) (#160)
* ReadBytesWithSingleRequest cannot read >491 Bytes on S7-1500
2018-07-11 10:15:08 +02:00
Michael Croes
a5d3c70373 Add SourceLink.Copy.PdbFiles to add missing .pdb files (#163) 2018-07-11 09:53:33 +02:00
Michael Croes
2204ab360c Fix write stringex (#162)
* Add StringEx.ToByteArray(...)

* Add Serialization.SerializeDataItem(DataItem)

Supports StringEx VarType or offloads to SerializeValue method.

* Use SerializeDataItem in S7WriteMultiple

* Assume string length without header in StringEx.ToByteArray

VarTypeToByteLength already assumed that StringEx declared count for
the number of characters without the header, this now matches that
behavior.

* Add unit tests for StringEx conversions

* Fix incorrect value passed to Encoding.GetBytes

The length must actually be within string limits.
2018-07-11 09:47:43 +02:00
Raphael Schlameuß
a1b69a5c5a Merge pull request #161 from mycroes/throw-on-error
Replace LastErrorCode and LastErrorString with exceptions
2018-07-11 09:26:27 +02:00
Michael Croes
1538de148b Replace LastErrorCode and LastErrorString with exceptions 2018-07-09 20:07:47 +02:00
Michael Croes
ff7e13cd49 Merge pull request #158 from mycroes/feature/ci
Add AppVeyor and GitVersion configuration
2018-07-09 19:57:06 +02:00
Michael Croes
c651380647 Add AppVeyor and GitVersion configuration 2018-07-08 21:10:05 +02:00
Michael Croes
0298371bfc Remove accidental .nuget leftover 2018-07-07 10:19:44 +02:00
33 changed files with 1707 additions and 495 deletions

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>

21
GitVersion.yml Normal file
View File

@@ -0,0 +1,21 @@
assembly-informational-format: '{NuGetVersion}'
mode: ContinuousDeployment
branches:
master:
tag: rc
increment: Minor
feature:
regex: features?[/-]
tag: rc-{BranchName}
increment: Minor
pull-request:
regex: (pull|pull\-requests|pr)[/-]
tag: rc-pr-{BranchName}
increment: Minor
hotfix:
regex: hotfix(es)?[/-]
tag: rc
increment: Patch
develop:
regex: dev(elop)?(ment)?$
tag: b

View File

@@ -75,7 +75,7 @@ namespace S7.Net.UnitTest
destTsap1, destTsap2, //Destination TASP
192, //Parameter Code (tpdu-size)
1, //Parameter Length
9 //TPDU Size (2^9 = 512)
10 //TPDU Size (2^11 = 2048)
};
}
}

View File

@@ -9,6 +9,7 @@ namespace S7.Net.UnitTest.Helpers
static private byte[] DB1 = new byte[1024]; // Our DB1
static private byte[] DB2 = new byte[64000]; // Our DB2
static private byte[] DB3 = new byte[1024]; // Our DB3
static private byte[] DB4 = new byte[6] { 3, 128, 1, 0, 197, 104 }; // Our DB4
private static S7Server.TSrvCallback TheEventCallBack; // <== Static var containig the callback
private static S7Server.TSrvCallback TheReadCallBack; // <== Static var containig the callback
@@ -36,9 +37,10 @@ namespace S7.Net.UnitTest.Helpers
1, // Its number is 1 (DB1)
DB1, // Our buffer for DB1
DB1.Length); // Its size
// Do the same for DB2 and DB3
// Do the same for DB2, DB3, and DB4
Server.RegisterArea(S7Server.srvAreaDB, 2, DB2, DB2.Length);
Server.RegisterArea(S7Server.srvAreaDB, 3, DB3, DB3.Length);
Server.RegisterArea(S7Server.srvAreaDB, 4, DB4, DB4.Length);
// Exclude read event to avoid the double report
// Set the callbacks (using the static var to avoid the garbage collect)

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace S7.Net.UnitTest.Helpers
{
class TestClassInnerWithBool
{
public bool BitVariable00 { get; set; }
}
class TestClassInnerWithByte
{
public byte ByteVariable00 { get; set; }
}
class TestClassInnerWithShort
{
public short ShortVarialbe00 { get; set; }
}
class TestClassWithNestedClass
{
/// <summary>
/// DB1.DBX0.0
/// </summary>
public bool BitVariable00 { get; set; }
/// <summary>
/// DB1.DBX0.1
/// </summary>
public TestClassInnerWithBool BitVariable01 { get; set; } = new TestClassInnerWithBool();
/// <summary>
/// DB1.DBB1.0
/// </summary>
public TestClassInnerWithByte ByteVariable02 { get; set; } = new TestClassInnerWithByte();
/// <summary>
/// DB1.DBX2.0
/// </summary>
public bool BitVariable03 { get; set; }
/// <summary>
/// DB1.DBW4
/// </summary>
public TestClassInnerWithShort ShortVariable04 { get; set; } = new TestClassInnerWithShort();
}
}

View File

@@ -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");
}
}
}

View File

@@ -62,6 +62,8 @@
<ItemGroup>
<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" />
@@ -78,6 +80,10 @@
<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" />
</ItemGroup>
<ItemGroup>
<Content Include="snap7.dll">

View File

@@ -160,6 +160,31 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
}
[TestMethod]
public async Task Test_Async_ReadAndWriteNestedClass()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
TestClassWithNestedClass tc = new TestClassWithNestedClass
{
BitVariable00 = true,
BitVariable01 = new TestClassInnerWithBool { BitVariable00 = true },
ByteVariable02 = new TestClassInnerWithByte { ByteVariable00 = 128 },
BitVariable03 = true,
ShortVariable04 = new TestClassInnerWithShort { ShortVarialbe00 = -15000 }
};
await plc.WriteClassAsync(tc, DB4);
TestClassWithNestedClass tc2 = new TestClassWithNestedClass();
// Values that are read from a class are stored inside the class itself, that is passed by reference
await plc.ReadClassAsync(tc2, DB4);
Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00);
Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00);
Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00);
Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03);
Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00);
}
/// <summary>
/// Read/Write a struct that has the same properties of a DB with the same field in the same order
/// </summary>
@@ -226,10 +251,8 @@ namespace S7.Net.UnitTest
IntVariable111 = 201
};
plc.WriteStruct(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongStruct tc2 = (TestLongStruct)await plc.ReadStructAsync(typeof(TestLongStruct), DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -292,11 +315,9 @@ namespace S7.Net.UnitTest
IntVariable111 = 201
};
await plc.WriteClassAsync(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongClass tc2 = new TestLongClass();
await plc.ReadClassAsync(tc2, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -571,7 +592,7 @@ namespace S7.Net.UnitTest
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadBytesThrowsExceptionIfPlcIsNotConnected()
public async Task Test_Async_ReadBytesReturnsNullIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -667,6 +688,28 @@ namespace S7.Net.UnitTest
}
}
[TestMethod]
public async Task Test_Async_ReadClassWithNestedClassAfterBit()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
Assert.AreEqual(6, Types.Class.GetClassSize(new TestClassWithNestedClass()));
TestClassWithNestedClass tc = new TestClassWithNestedClass();
tc.BitVariable00 = true;
tc.BitVariable01.BitVariable00 = true;
tc.ByteVariable02.ByteVariable00 = 128;
tc.BitVariable03 = true;
tc.ShortVariable04.ShortVarialbe00 = -15000;
TestClassWithNestedClass tc2 = await plc.ReadClassAsync<TestClassWithNestedClass>(DB4);
Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00);
Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00);
Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00);
Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03);
Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00);
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected()
@@ -741,7 +784,7 @@ namespace S7.Net.UnitTest
};
plc.WriteClass(tc, DB2);
int expectedReadBytes = Types.Class.GetClassSize(tc);
int expectedReadBytes = (int)Types.Class.GetClassSize(tc);
TestClass tc2 = new TestClass();
// Values that are read from a class are stored inside the class itself, that is passed by reference
@@ -831,11 +874,9 @@ namespace S7.Net.UnitTest
{
double test_value = 55.66;
await plc.WriteAsync("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Double");
var helper = await plc.ReadAsync("DB1.DBD0");
double test_value2 = Conversion.ConvertToDouble((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Double");
Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals
}
@@ -844,11 +885,9 @@ namespace S7.Net.UnitTest
{
float test_value = 55.6632f;
await plc.WriteAsync("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Single");
var helper = await plc.ReadAsync("DB1.DBD0");
float test_value2 = Conversion.ConvertToFloat((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Single");
Assert.AreEqual(test_value, test_value2, "Compare Write/Read"); //No delta, datatype matches
}

View File

@@ -40,6 +40,7 @@ namespace S7.Net.UnitTest
{
#region Constants
const int DB2 = 2;
const int DB4 = 4;
#endregion
#region Private fields
@@ -81,8 +82,14 @@ namespace S7.Net.UnitTest
{
if (plc.IsConnected == false)
{
var error = plc.Open();
Assert.AreEqual(ErrorCode.NoError, error, "If you have s7 installed you must close s7oiehsx64 service.");
try
{
plc.Open();
}
catch (Exception e)
{
throw new Exception("If you have s7 installed you must close s7oiehsx64 service.", e);
}
}
}
@@ -257,10 +264,8 @@ namespace S7.Net.UnitTest
tc.IntVariable110 = 200;
tc.IntVariable111 = 201;
plc.WriteStruct(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongStruct tc2 = (TestLongStruct)plc.ReadStruct(typeof(TestLongStruct), DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -321,11 +326,9 @@ namespace S7.Net.UnitTest
tc.IntVariable110 = 200;
tc.IntVariable111 = 201;
plc.WriteClass(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongClass tc2 = new TestLongClass();
plc.ReadClass(tc2, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -596,19 +599,14 @@ namespace S7.Net.UnitTest
}
[TestMethod]
public void T13_ReadBytesReturnsEmptyArrayIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T13_ReadBytesThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
int expectedReadBytes = 0; // 0 bytes, because no connection was established
TestClass tc = new TestClass();
int actualReadBytes = notConnectedPlc.ReadClass(tc, DB2);
Assert.AreEqual(expectedReadBytes, actualReadBytes);
}
}
@@ -642,8 +640,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable);
}
[TestMethod]
public void T15_ReadClassWithGenericReturnsNullIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T15_ReadClassWithGenericThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -684,8 +682,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable);
}
[TestMethod]
public void T17_ReadClassWithGenericAndClassFactoryReturnsNullIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T17_ReadClassWithGenericAndClassFactoryThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -698,7 +696,55 @@ namespace S7.Net.UnitTest
}
[TestMethod]
public void T18_ReadStructReturnsNullIfPlcIsNotConnected()
public void T31_ReadClassWithNestedClassAfterBit()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
Assert.AreEqual(6, Types.Class.GetClassSize(new TestClassWithNestedClass()));
TestClassWithNestedClass tc = new TestClassWithNestedClass();
tc.BitVariable00 = true;
tc.BitVariable01.BitVariable00 = true;
tc.ByteVariable02.ByteVariable00 = 128;
tc.BitVariable03 = true;
tc.ShortVariable04.ShortVarialbe00 = -15000;
TestClassWithNestedClass tc2 = new TestClassWithNestedClass();
plc.ReadClass(tc2, DB4);
Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00);
Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00);
Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00);
Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03);
Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00);
}
[TestMethod]
public void T32_ReadAndWriteNestedClass()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
TestClassWithNestedClass tc = new TestClassWithNestedClass
{
BitVariable00 = true,
BitVariable01 = new TestClassInnerWithBool { BitVariable00 = true },
ByteVariable02 = new TestClassInnerWithByte { ByteVariable00 = 128 },
BitVariable03 = true,
ShortVariable04 = new TestClassInnerWithShort { ShortVarialbe00 = -15000 }
};
plc.WriteClass(tc, DB4);
TestClassWithNestedClass tc2 = new TestClassWithNestedClass();
// Values that are read from a class are stored inside the class itself, that is passed by reference
plc.ReadClass(tc2, DB4);
Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00);
Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00);
Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00);
Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03);
Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00);
}
[TestMethod, ExpectedException(typeof(PlcException))]
public void T18_ReadStructThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -739,8 +785,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(ts2.DWordVariable, ts2Generic.DWordVariable);
}
[TestMethod]
public void T20_ReadStructWithGenericReturnsNullIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T20_ReadStructThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -770,7 +816,7 @@ namespace S7.Net.UnitTest
tc.DWordVariable = 850;
plc.WriteClass(tc, DB2);
int expectedReadBytes = Types.Class.GetClassSize(tc);
int expectedReadBytes = (int)Types.Class.GetClassSize(tc);
TestClass tc2 = new TestClass();
// Values that are read from a class are stored inside the class itself, that is passed by reference
@@ -883,11 +929,9 @@ namespace S7.Net.UnitTest
{
double test_value = 55.66;
plc.Write("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Double");
var helper = plc.Read("DB1.DBD0");
double test_value2 = Conversion.ConvertToDouble((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Double");
Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals
}
@@ -929,19 +973,17 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc.Bool1, tc2.Bool1);
}
[TestMethod]
public void T29_Read_Write_ExceptionHandlingWhenPlcIsNotReachable()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T29_Read_Write_ThrowsWhenPlcIsNotReachable()
{
// 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.");
}
[TestMethod]
@@ -949,11 +991,9 @@ namespace S7.Net.UnitTest
{
float test_value = 55.6632f;
plc.Write("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Single");
var helper = plc.Read("DB1.DBD0");
float test_value2 = Conversion.ConvertToFloat((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Single");
Assert.AreEqual(test_value, test_value2, "Compare Write/Read"); //No delta, datatype matches
}

View File

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

View File

@@ -0,0 +1,176 @@
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace S7.Net.UnitTest.TypeTests
{
public static class DateTimeTests
{
private static readonly DateTime SampleDateTime = new DateTime(1993, 12, 25, 8, 12, 34, 567);
private static readonly byte[] SampleByteArray = {0x93, 0x12, 0x25, 0x08, 0x12, 0x34, 0x56, 7 << 4 | 7};
private static readonly byte[] SpecMinByteArray =
{
0x90, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, (byte) (int) (Types.DateTime.SpecMinimumDateTime.DayOfWeek + 1)
};
private static readonly byte[] SpecMaxByteArray =
{
0x89, 0x12, 0x31, 0x23, 0x59, 0x59, 0x99, (byte) (9 << 4 | (int) (Types.DateTime.SpecMaximumDateTime.DayOfWeek + 1))
};
[TestClass]
public class FromByteArray
{
[TestMethod]
public void Sample()
{
AssertFromByteArrayEquals(SampleDateTime, SampleByteArray);
}
[TestMethod]
public void SpecMinimum()
{
AssertFromByteArrayEquals(Types.DateTime.SpecMinimumDateTime, SpecMinByteArray);
}
[TestMethod]
public void SpecMaximum()
{
AssertFromByteArrayEquals(Types.DateTime.SpecMaximumDateTime, SpecMaxByteArray);
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnLessThan8Bytes()
{
Types.DateTime.FromByteArray(new byte[7]);
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnMoreTHan8Bytes()
{
Types.DateTime.FromByteArray(new byte[9]);
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnInvalidYear()
{
Types.DateTime.FromByteArray(MutateSample(0, 0xa0));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnZeroMonth()
{
Types.DateTime.FromByteArray(MutateSample(1, 0x00));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnTooLargeMonth()
{
Types.DateTime.FromByteArray(MutateSample(1, 0x13));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnZeroDay()
{
Types.DateTime.FromByteArray(MutateSample(2, 0x00));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnTooLargeDay()
{
Types.DateTime.FromByteArray(MutateSample(2, 0x32));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnInvalidHour()
{
Types.DateTime.FromByteArray(MutateSample(3, 0x24));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnInvalidMinute()
{
Types.DateTime.FromByteArray(MutateSample(4, 0x60));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnInvalidSecond()
{
Types.DateTime.FromByteArray(MutateSample(5, 0x60));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnInvalidFirstTwoMillisecondDigits()
{
Types.DateTime.FromByteArray(MutateSample(6, 0xa0));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnInvalidThirdMillisecondDigit()
{
Types.DateTime.FromByteArray(MutateSample(7, 10 << 4));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnZeroDayOfWeek()
{
Types.DateTime.FromByteArray(MutateSample(7, 0));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnTooLargeDayOfWeek()
{
Types.DateTime.FromByteArray(MutateSample(7, 8));
}
private static void AssertFromByteArrayEquals(DateTime expected, params byte[] bytes)
{
Assert.AreEqual(expected, Types.DateTime.FromByteArray(bytes));
}
private static byte[] MutateSample(int index, byte value) =>
SampleByteArray.Select((b, i) => i == index ? value : b).ToArray();
}
[TestClass]
public class ToByteArray
{
[TestMethod]
public void Sample()
{
AssertToByteArrayEquals(SampleDateTime, SampleByteArray);
}
[TestMethod]
public void SpecMinimum()
{
AssertToByteArrayEquals(Types.DateTime.SpecMinimumDateTime, SpecMinByteArray);
}
[TestMethod]
public void SpecMaximum()
{
AssertToByteArrayEquals(Types.DateTime.SpecMaximumDateTime, SpecMaxByteArray);
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnTimeBeforeSpecMinimum()
{
Types.DateTime.ToByteArray(new DateTime(1970, 1, 1));
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ThrowsOnTimeAfterSpecMaximum()
{
Types.DateTime.ToByteArray(new DateTime(2090, 1, 1));
}
private static void AssertToByteArrayEquals(DateTime value, params byte[] expected)
{
CollectionAssert.AreEqual(expected, Types.DateTime.ToByteArray(value));
}
}
}
}

View File

@@ -0,0 +1,115 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;
namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class StringExTests
{
[TestMethod]
public void ReadEmptyStringWithZeroByteLength()
{
AssertFromByteArrayEquals("", 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneByteLength()
{
AssertFromByteArrayEquals("", 1, 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneByteGarbage()
{
AssertFromByteArrayEquals("", 1, 0, (byte) 'a');
}
[TestMethod]
public void ReadA()
{
AssertFromByteArrayEquals("A", 1, 1, (byte) 'A');
}
[TestMethod]
public void ReadAbc()
{
AssertFromByteArrayEquals("Abc", 1, 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
[TestMethod]
public void WriteNullWithReservedLengthZero()
{
AssertToByteArrayEquals(null, 0, 0, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthZero()
{
AssertToByteArrayEquals("", 0, 0, 0);
}
[TestMethod]
public void WriteAWithReservedLengthZero()
{
AssertToByteArrayEquals("A", 0, 0, 0);
}
[TestMethod]
public void WriteNullWithReservedLengthOne()
{
AssertToByteArrayEquals(null, 1, 1, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthOne()
{
AssertToByteArrayEquals("", 1, 1, 0);
}
[TestMethod]
public void WriteAWithReservedLengthOne()
{
AssertToByteArrayEquals("A", 1, 1, 1, (byte) 'A');
}
[TestMethod]
public void WriteAWithReservedLengthTwo()
{
AssertToByteArrayEquals("A", 2, 2, 1, (byte) 'A');
}
[TestMethod]
public void WriteAbcWithReservedLengthOne()
{
AssertToByteArrayEquals("Abc", 1, 1, 1, (byte) 'A');
}
[TestMethod]
public void WriteAbcWithReservedLengthTwo()
{
AssertToByteArrayEquals("Abc", 2, 2, 2, (byte) 'A', (byte) 'b');
}
[TestMethod]
public void WriteAbcWithReservedLengthThree()
{
AssertToByteArrayEquals("Abc", 3, 3, 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
[TestMethod]
public void WriteAbcWithReservedLengthFour()
{
AssertToByteArrayEquals("Abc", 4, 4, 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{
Assert.AreEqual(expected, StringEx.FromByteArray(bytes));
}
private static void AssertToByteArrayEquals(string value, int reservedLength, params byte[] expected)
{
CollectionAssert.AreEqual(expected, StringEx.ToByteArray(value, reservedLength));
}
}
}

View File

@@ -0,0 +1,85 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;
namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class StringTests
{
[TestMethod]
public void WriteNullWIthReservedLengthZero()
{
AssertToByteArrayEquals(null, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthZero()
{
AssertToByteArrayEquals("", 0);
}
[TestMethod]
public void WriteAWithReservedLengthZero()
{
AssertToByteArrayEquals("A", 0);
}
[TestMethod]
public void WriteNullWithReservedLengthOne()
{
AssertToByteArrayEquals(null, 1, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthOne()
{
AssertToByteArrayEquals("", 1, 0);
}
[TestMethod]
public void WriteAWithReservedLengthOne()
{
AssertToByteArrayEquals("A", 1, (byte) 'A');
}
[TestMethod]
public void WriteAWithReservedLengthTwo()
{
AssertToByteArrayEquals("A", 2, (byte) 'A', 0);
}
[TestMethod]
public void WriteAbcWithReservedLengthOne()
{
AssertToByteArrayEquals("Abc", 1, (byte) 'A');
}
[TestMethod]
public void WriteAbcWithReservedLengthTwo()
{
AssertToByteArrayEquals("Abc", 2, (byte) 'A', (byte) 'b');
}
[TestMethod]
public void WriteAbcWithReservedLengthThree()
{
AssertToByteArrayEquals("Abc", 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
[TestMethod]
public void WriteAbcWithReservedLengthFour()
{
AssertToByteArrayEquals("Abc", 4, (byte) 'A', (byte) 'b', (byte) 'c', 0);
}
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{
Assert.AreEqual(expected, String.FromByteArray(bytes));
}
private static void AssertToByteArrayEquals(string value, int reservedLength, params byte[] expected)
{
CollectionAssert.AreEqual(expected, String.ToByteArray(value, reservedLength));
}
}
}

View File

@@ -16,6 +16,7 @@ namespace S7.Net
/// </summary>
public class TPDU
{
public TPKT TPkt { get; }
public byte HeaderLength;
public byte PDUType;
public int TPDUNumber;
@@ -24,6 +25,8 @@ namespace S7.Net
public TPDU(TPKT tPKT)
{
TPkt = tPKT;
var br = new BinaryReader(new MemoryStream(tPKT.Data));
HeaderLength = br.ReadByte();
if (HeaderLength >= 2)

View File

@@ -10,6 +10,11 @@
/// </summary>
S7200 = 0,
/// <summary>
/// Siemens Logo 0BA8
/// </summary>
Logo0BA8 = 1,
/// <summary>
/// S7 300 cpu type
/// </summary>
@@ -176,6 +181,11 @@
/// <summary>
/// Counter variable type
/// </summary>
Counter
Counter,
/// <summary>
/// DateTIme variable type
/// </summary>
DateTime
}
}

View File

@@ -0,0 +1,43 @@
using System;
namespace S7.Net
{
#if NET_FULL
[Serializable]
#endif
public class InvalidDataException : Exception
{
public byte[] ReceivedData { get; }
public int ErrorIndex { get; }
public byte ExpectedValue { get; }
public InvalidDataException(string message, byte[] receivedData, int errorIndex, byte expectedValue)
: base(FormatMessage(message, receivedData, errorIndex, expectedValue))
{
ReceivedData = receivedData;
ErrorIndex = errorIndex;
ExpectedValue = expectedValue;
}
#if NET_FULL
protected InvalidDataException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
ReceivedData = (byte[]) info.GetValue(nameof(ReceivedData), typeof(byte[]));
ErrorIndex = info.GetInt32(nameof(ErrorIndex));
ExpectedValue = info.GetByte(nameof(ExpectedValue));
}
#endif
private static string FormatMessage(string message, byte[] receivedData, int errorIndex, byte expectedValue)
{
if (errorIndex >= receivedData.Length)
throw new ArgumentOutOfRangeException(nameof(errorIndex),
$"{nameof(errorIndex)} {errorIndex} is outside the bounds of {nameof(receivedData)} with length {receivedData.Length}.");
return $"{message} Invalid data received. Expected '{expectedValue}' at index {errorIndex}, " +
$"but received {receivedData[errorIndex]}. See the {nameof(ReceivedData)} property " +
"for the full message received.";
}
}
}

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using S7.Net.Types;
namespace S7.Net
@@ -15,11 +18,19 @@ namespace S7.Net
private TcpClient tcpClient;
private NetworkStream stream;
private int readTimeout = System.Threading.Timeout.Infinite;
private int writeTimeout = System.Threading.Timeout.Infinite;
/// <summary>
/// IP address of the PLC
/// </summary>
public string IP { get; private set; }
/// <summary>
/// PORT Number of the PLC, default is 102
/// </summary>
public int Port { get; private set; }
/// <summary>
/// CPU type of the PLC
/// </summary>
@@ -39,6 +50,30 @@ namespace S7.Net
/// Max PDU size this cpu supports
/// </summary>
public Int16 MaxPDUSize { get; set; }
/// <summary>Gets or sets the amount of time that a read operation blocks waiting for data from PLC.</summary>
/// <returns>A <see cref="T:System.Int32" /> that specifies the amount of time, in milliseconds, that will elapse before a read operation fails. The default value, <see cref="F:System.Threading.Timeout.Infinite" />, specifies that the read operation does not time out.</returns>
public int ReadTimeout
{
get => readTimeout;
set
{
readTimeout = value;
if (tcpClient != null) tcpClient.ReceiveTimeout = readTimeout;
}
}
/// <summary>Gets or sets the amount of time that a write operation blocks waiting for data to PLC. </summary>
/// <returns>A <see cref="T:System.Int32" /> that specifies the amount of time, in milliseconds, that will elapse before a write operation fails. The default value, <see cref="F:System.Threading.Timeout.Infinite" />, specifies that the write operation does not time out.</returns>
public int WriteTimeout
{
get => writeTimeout;
set
{
writeTimeout = value;
if (tcpClient != null) tcpClient.SendTimeout = writeTimeout;
}
}
/// <summary>
/// Returns true if a connection to the PLC can be established
@@ -48,7 +83,15 @@ namespace S7.Net
//TODO: Fix This
get
{
return Connect() == ErrorCode.NoError;
try
{
Connect();
return true;
}
catch
{
return false;
}
}
}
@@ -74,15 +117,32 @@ namespace S7.Net
}
/// <summary>
/// Contains the last error registered when executing a function
/// Creates a PLC object with all the parameters needed for connections.
/// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0.
/// You need slot > 0 if you are connecting to external ethernet card (CP).
/// For S7-300 and S7-400 the default is rack = 0 and slot = 2.
/// </summary>
public string LastErrorString { get; private set; }
/// <param name="cpu">CpuType of the PLC (select from the enum)</param>
/// <param name="ip">Ip address of the PLC</param>
/// <param name="port">Port address of the PLC, default 102</param>
/// <param name="rack">rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal</param>
/// <param name="slot">slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
/// If you use an external ethernet card, this must be set accordingly.</param>
public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot)
{
if (!Enum.IsDefined(typeof(CpuType), cpu))
throw new ArgumentException($"The value of argument '{nameof(cpu)}' ({cpu}) is invalid for Enum type '{typeof(CpuType).Name}'.", nameof(cpu));
/// <summary>
/// Contains the last error code registered when executing a function
/// </summary>
public ErrorCode LastErrorCode { get; private set; }
if (string.IsNullOrEmpty(ip))
throw new ArgumentException("IP address must valid.", nameof(ip));
CPU = cpu;
IP = ip;
Port = port;
Rack = rack;
Slot = slot;
MaxPDUSize = 240;
}
/// <summary>
/// Creates a PLC object with all the parameters needed for connections.
/// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0.
@@ -104,10 +164,12 @@ namespace S7.Net
CPU = cpu;
IP = ip;
Port = 102;
Rack = rack;
Slot = slot;
MaxPDUSize = 240;
}
/// <summary>
/// Close connection to PLC
/// </summary>
@@ -119,6 +181,64 @@ namespace S7.Net
}
}
private void AssertPduSizeForRead(ICollection<DataItem> dataItems)
{
// 12 bytes of header data, 12 bytes of parameter data for each dataItem
if ((dataItems.Count + 1) * 12 > MaxPDUSize) throw new Exception("Too many vars requested for read");
// 14 bytes of header data, 4 bytes of result data for each dataItem and the actual data
if (GetDataLength(dataItems) + dataItems.Count * 4 + 14 > MaxPDUSize) throw new Exception("Too much data requested for read");
}
private void AssertPduSizeForWrite(ICollection<DataItem> dataItems)
{
// 12 bytes of header data, 18 bytes of parameter data for each dataItem
if (dataItems.Count * 18 + 12 > MaxPDUSize) throw new Exception("Too many vars supplied for write");
// 12 bytes of header data, 16 bytes of data for each dataItem and the actual data
if (GetDataLength(dataItems) + dataItems.Count * 16 + 12 > MaxPDUSize)
throw new Exception("Too much data supplied for write");
}
private void ConfigureConnection()
{
if (tcpClient == null)
{
return;
}
tcpClient.ReceiveTimeout = ReadTimeout;
tcpClient.SendTimeout = WriteTimeout;
}
private int GetDataLength(IEnumerable<DataItem> dataItems)
{
// Odd length variables are 0-padded
return dataItems.Select(di => VarTypeToByteLength(di.VarType, di.Count))
.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

207
S7.Net/PLCAddress.cs Normal file
View File

@@ -0,0 +1,207 @@
namespace S7.Net
{
internal class PLCAddress
{
private DataType dataType;
private int dbNumber;
private int startByte;
private int bitNumber;
private VarType varType;
public DataType DataType
{
get => dataType;
set => dataType = value;
}
public int DbNumber
{
get => dbNumber;
set => dbNumber = value;
}
public int StartByte
{
get => startByte;
set => startByte = value;
}
public int BitNumber
{
get => bitNumber;
set => bitNumber = value;
}
public VarType VarType
{
get => varType;
set => varType = value;
}
public PLCAddress(string address)
{
Parse(address, out dataType, out dbNumber, out varType, out startByte, out bitNumber);
}
public static void Parse(string input, out DataType dataType, out int dbNumber, out VarType varType, out int address, out int bitNumber)
{
bitNumber = -1;
dbNumber = 0;
switch (input.Substring(0, 2))
{
case "DB":
string[] strings = input.Split(new char[] { '.' });
if (strings.Length < 2)
throw new InvalidAddressException("To few periods for DB address");
dataType = DataType.DataBlock;
dbNumber = int.Parse(strings[0].Substring(2));
address = int.Parse(strings[1].Substring(3));
string dbType = strings[1].Substring(0, 3);
switch (dbType)
{
case "DBB":
varType = VarType.Byte;
return;
case "DBW":
varType = VarType.Word;
return;
case "DBD":
varType = VarType.DWord;
return;
case "DBX":
bitNumber = int.Parse(strings[2]);
if (bitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
varType = VarType.Bit;
return;
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;
address = int.Parse(input.Substring(2));
varType = VarType.DWord;
return;
case "MB":
// Memory byte
dataType = DataType.Memory;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Byte;
return;
case "MW":
// Memory word
dataType = DataType.Memory;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Word;
return;
case "MD":
// Memory double-word
dataType = DataType.Memory;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.DWord;
return;
default:
switch (input.Substring(0, 1))
{
case "E":
case "I":
// Input
dataType = DataType.Input;
varType = VarType.Bit;
break;
case "Q":
case "A":
case "O":
// Output
dataType = DataType.Output;
varType = VarType.Bit;
break;
case "M":
// Memory
dataType = DataType.Memory;
varType = VarType.Bit;
break;
case "T":
// Timer
dataType = DataType.Timer;
dbNumber = 0;
address = int.Parse(input.Substring(1));
varType = VarType.Timer;
return;
case "Z":
case "C":
// Counter
dataType = DataType.Timer;
dbNumber = 0;
address = int.Parse(input.Substring(1));
varType = VarType.Counter;
return;
default:
throw new InvalidAddressException(string.Format("{0} is not a valid address", input.Substring(0, 1)));
}
string txt2 = input.Substring(1);
if (txt2.IndexOf(".") == -1)
throw new InvalidAddressException("To few periods for DB address");
address = int.Parse(txt2.Substring(0, txt2.IndexOf(".")));
bitNumber = int.Parse(txt2.Substring(txt2.IndexOf(".") + 1));
if (bitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
return;
}
}
}
}

View File

@@ -2,170 +2,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DateTime = S7.Net.Types.DateTime;
namespace S7.Net
{
internal class PLCAddress
{
public DataType dataType;
public int DBNumber;
public int Address;
public int BitNumber;
public VarType varType;
public PLCAddress(string address)
{
ParseString(address);
}
private void ParseString(string address)
{
BitNumber = -1;
switch (address.Substring(0, 2))
{
case "DB":
string[] strings = address.Split(new char[] { '.' });
if (strings.Length < 2)
throw new InvalidAddressException("To few periods for DB address");
dataType = DataType.DataBlock;
DBNumber = int.Parse(strings[0].Substring(2));
Address = int.Parse(strings[1].Substring(3));
string dbType = strings[1].Substring(0, 3);
switch (dbType)
{
case "DBB":
varType = VarType.Byte;
return;
case "DBW":
varType = VarType.Word;
return;
case "DBD":
varType = VarType.DWord;
return;
case "DBX":
BitNumber = int.Parse(strings[2]);
if (BitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
varType = VarType.Bit;
return;
default:
throw new InvalidAddressException();
}
case "EB":
// Input byte
dataType = DataType.Input;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Byte;
return;
case "EW":
// Input word
dataType = DataType.Input;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Word;
return;
case "ED":
// Input double-word
dataType = DataType.Input;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.DWord;
return;
case "AB":
// Output byte
dataType = DataType.Output;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Byte;
return;
case "AW":
// Output word
dataType = DataType.Output;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Word;
return;
case "AD":
// Output double-word
dataType = DataType.Output;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.DWord;
return;
case "MB":
// Memory byte
dataType = DataType.Memory;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Byte;
return;
case "MW":
// Memory word
dataType = DataType.Memory;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Word;
return;
case "MD":
// Memory double-word
dataType = DataType.Memory;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.DWord;
return;
default:
switch (address.Substring(0, 1))
{
case "E":
case "I":
// Input
dataType = DataType.Input;
break;
case "A":
case "O":
// Output
dataType = DataType.Output;
break;
case "M":
// Memory
dataType = DataType.Memory;
break;
case "T":
// Timer
dataType = DataType.Timer;
DBNumber = 0;
Address = int.Parse(address.Substring(1));
varType = VarType.Timer;
return;
case "Z":
case "C":
// Counter
dataType = DataType.Timer;
DBNumber = 0;
Address = int.Parse(address.Substring(1));
varType = VarType.Counter;
return;
default:
throw new InvalidAddressException(string.Format("{0} is not a valid address", address.Substring(0, 1)));
}
string txt2 = address.Substring(1);
if (txt2.IndexOf(".") == -1)
throw new InvalidAddressException("To few periods for DB address");
Address = int.Parse(txt2.Substring(0, txt2.IndexOf(".")));
BitNumber = int.Parse(txt2.Substring(txt2.IndexOf(".") + 1));
if (BitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
return;
}
}
}
public partial class Plc
{
/// <summary>
@@ -305,22 +145,22 @@ namespace S7.Net
}
else
{
return Bit.ToBitArray(bytes);
return Bit.ToBitArray(bytes, varCount);
}
case VarType.DateTime:
if (varCount == 1)
{
return DateTime.FromByteArray(bytes);
}
else
{
return DateTime.ToArray(bytes);
}
default:
return null;
}
}
/// <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;
}
/// <summary>
/// Given a S7 <see cref="VarType"/> (Bool, Word, DWord, etc.), it returns how many bytes to read.
/// </summary>
@@ -332,7 +172,7 @@ namespace S7.Net
switch (varType)
{
case VarType.Bit:
return varCount; //TODO
return varCount + 7 / 8;
case VarType.Byte:
return (varCount < 1) ? 1 : varCount;
case VarType.String:
@@ -348,6 +188,8 @@ namespace S7.Net
case VarType.DInt:
case VarType.Real:
return varCount * 4;
case VarType.DateTime:
return varCount * 8;
default:
return 0;
}
@@ -356,7 +198,7 @@ namespace S7.Net
private byte[] GetS7ConnectionSetup()
{
return new byte[] { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3,
7, 128 //Try 1920 PDU Size. Same as libnodave.
3, 192 // Use 960 PDU size
};
}
@@ -367,7 +209,7 @@ namespace S7.Net
{
// check for Return Code = Success
if (s7data[offset] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
// to Data bytes
offset += 4;

View File

@@ -14,10 +14,9 @@ namespace S7.Net
public partial class Plc
{
/// <summary>
/// Open a <see cref="Socket"/> and connects to the PLC, sending all the corrected package
/// and returning if the connection was successful (<see cref="ErrorCode.NoError"/>) of it was wrong.
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
/// </summary>
/// <returns>Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode</returns>
/// <returns>A task that represents the asynchronous open operation.</returns>
public async Task OpenAsync()
{
await ConnectAsync();
@@ -26,23 +25,27 @@ namespace S7.Net
var response = await COTP.TPDU.ReadAsync(stream);
if (response.PDUType != 0xd0) //Connect Confirm
{
throw new WrongNumberOfBytesException("Waiting for COTP connect confirm");
throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d);
}
await stream.WriteAsync(GetS7ConnectionSetup(), 0, 25);
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[1] != 0x03) //Check for S7 Ack Data
{
throw new WrongNumberOfBytesException("Waiting for S7 connection setup");
}
if (s7data == null)
throw new WrongNumberOfBytesException("No data received in response to Communication Setup");
//Check for S7 Ack Data
if (s7data[1] != 0x03)
throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03);
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
}
private async Task ConnectAsync()
{
tcpClient = new TcpClient();
await tcpClient.ConnectAsync(IP, 102);
ConfigureConnection();
await tcpClient.ConnectAsync(IP, Port);
stream = tcpClient.GetStream();
}
@@ -100,7 +103,7 @@ namespace S7.Net
public async Task<object> ReadAsync(string variable)
{
var adr = new PLCAddress(variable);
return await ReadAsync(adr.dataType, adr.DBNumber, adr.Address, adr.varType, 1, (byte)adr.BitNumber);
return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
}
/// <summary>
@@ -142,7 +145,7 @@ namespace S7.Net
/// <returns>The number of read bytes</returns>
public async Task<Tuple<int, object>> ReadClassAsync(object sourceClass, int db, int startByteAdr = 0)
{
int numBytes = Class.GetClassSize(sourceClass);
int numBytes = (int)Class.GetClassSize(sourceClass);
if (numBytes <= 0)
{
throw new Exception("The size of the class is less than 1 byte and therefore cannot be read");
@@ -202,15 +205,10 @@ namespace S7.Net
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read. Maximum 20 dataitems are accepted.</param>
public async Task<List<DataItem>> ReadMultipleVarsAsync(List<DataItem> dataItems)
{
int cntBytes = dataItems.Sum(dataItem => VarTypeToByteLength(dataItem.VarType, dataItem.Count));
//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
if (dataItems.Count > 20)
throw new Exception("Too many vars requested");
if (cntBytes > 222)
throw new Exception("Too many bytes requested"); // TODO: proper TDU check + split in multiple requests
AssertPduSizeForRead(dataItems);
try
{
// first create the header
@@ -227,19 +225,17 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream); //TODO use Async
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
ParseDataIntoDataItems(s7data, dataItems);
}
catch (SocketException socketException)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
throw new PlcException(ErrorCode.ReadData, socketException);
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
throw new PlcException(ErrorCode.ReadData, exc);
}
return dataItems;
}
@@ -252,8 +248,8 @@ namespace S7.Net
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value)
{
int localIndex = 0;
int count = value.Length;
@@ -263,15 +259,10 @@ namespace S7.Net
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
var maxToWrite = (int)Math.Min(count, 200);
ErrorCode lastError = await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
if (lastError != ErrorCode.NoError)
{
return lastError;
}
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
count -= maxToWrite;
localIndex += maxToWrite;
}
return ErrorCode.NoError;
}
/// <summary>
@@ -282,19 +273,13 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
{
if (bitAdr < 0 || bitAdr > 7)
throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
ErrorCode lastError = await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value);
if (lastError != ErrorCode.NoError)
{
return lastError;
}
return ErrorCode.NoError;
await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value);
}
/// <summary>
@@ -305,13 +290,13 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
{
if (value < 0 || value > 1)
throw new ArgumentException("Value must be 0 or 1", nameof(value));
return await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1);
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1);
}
/// <summary>
@@ -324,26 +309,29 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
{
if (bitAdr != -1)
{
//Must be writing a bit value as bitAdr is specified
if (value is bool)
{
return await WriteBitAsync(dataType, db, startByteAdr, bitAdr, (bool)value);
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, (bool) value);
}
else if (value is int intValue)
{
if (intValue < 0 || intValue > 7)
throw new ArgumentOutOfRangeException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr), nameof(bitAdr));
throw new ArgumentOutOfRangeException(
string.Format(
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
bitAdr), nameof(bitAdr));
return await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1);
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1);
}
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
else throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
}
return await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value));
else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value));
}
/// <summary>
@@ -352,11 +340,11 @@ namespace S7.Net
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <param name="value">Value to be written to the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteAsync(string variable, object value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteAsync(string variable, object value)
{
var adr = new PLCAddress(variable);
return await WriteAsync(adr.dataType, adr.DBNumber, adr.Address, value, adr.BitNumber);
await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber);
}
/// <summary>
@@ -365,12 +353,11 @@ namespace S7.Net
/// <param name="structValue">The struct to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteStructAsync(object structValue, int db, int startByteAdr = 0)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0)
{
var bytes = Struct.ToBytes(structValue).ToList();
var errCode = await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
return errCode;
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
}
/// <summary>
@@ -379,12 +366,12 @@ namespace S7.Net
/// <param name="classValue">The class to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteClassAsync(object classValue, int db, int startByteAdr = 0)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0)
{
var bytes = Types.Class.ToBytes(classValue).ToList();
var errCode = await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
return errCode;
byte[] bytes = new byte[(int)Class.GetClassSize(classValue)];
Types.Class.ToBytes(classValue, bytes);
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes);
}
private async Task<byte[]> ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count)
@@ -401,8 +388,7 @@ namespace S7.Net
await stream.WriteAsync(package.Array, 0, package.Array.Length);
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
AssertReadResponse(s7data, count);
for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
@@ -418,6 +404,8 @@ namespace S7.Net
/// <returns>Task that completes when response from PLC is parsed.</returns>
public async Task WriteAsync(params DataItem[] dataItems)
{
AssertPduSizeForWrite(dataItems);
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false);
@@ -427,15 +415,14 @@ namespace S7.Net
}
/// <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.
/// Writes up to 200 bytes to the PLC. You must specify the memory area type, memory are address, byte start address and bytes count.
/// </summary>
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
private async Task<ErrorCode> WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value)
/// <returns>A task that represents the asynchronous write operation.</returns>
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value)
{
byte[] bReceive = new byte[513];
int varCount = 0;
@@ -471,27 +458,23 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
{
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
return ErrorCode.NoError;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
private async Task<ErrorCode> WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
byte[] bReceive = new byte[513];
int varCount = 0;
try
{
var value = new[] { bitValue ? (byte)1 : (byte)0 };
var value = new[] {bitValue ? (byte) 1 : (byte) 0};
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
@@ -520,15 +503,11 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
return ErrorCode.NoError;
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
}

39
S7.Net/PlcException.cs Normal file
View File

@@ -0,0 +1,39 @@
using System;
namespace S7.Net
{
#if NET_FULL
[Serializable]
#endif
public class PlcException : Exception
{
public ErrorCode ErrorCode { get; }
public PlcException(ErrorCode errorCode) : this(errorCode, $"PLC communication failed with error '{errorCode}'.")
{
}
public PlcException(ErrorCode errorCode, Exception innerException) : this(errorCode, innerException.Message,
innerException)
{
}
public PlcException(ErrorCode errorCode, string message) : base(message)
{
ErrorCode = errorCode;
}
public PlcException(ErrorCode errorCode, string message, Exception inner) : base(message, inner)
{
ErrorCode = errorCode;
}
#if NET_FULL
protected PlcException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
ErrorCode = (ErrorCode) info.GetInt32(nameof(ErrorCode));
}
#endif
}
}

View File

@@ -11,71 +11,61 @@ namespace S7.Net
public partial class Plc
{
/// <summary>
/// Open a <see cref="Socket"/> and connects to the PLC, sending all the corrected package
/// and returning if the connection was successful (<see cref="ErrorCode.NoError"/>) of it was wrong.
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
/// </summary>
/// <returns>Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode</returns>
public ErrorCode Open()
public void Open()
{
if (Connect() != ErrorCode.NoError)
{
return LastErrorCode;
}
Connect();
try
{
stream.Write(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
var response = COTP.TPDU.Read(stream);
if (response.PDUType != 0xd0) //Connect Confirm
{
throw new WrongNumberOfBytesException("Waiting for COTP connect confirm");
throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d);
}
stream.Write(GetS7ConnectionSetup(), 0, 25);
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[1] != 0x03) //Check for S7 Ack Data
{
throw new WrongNumberOfBytesException("Waiting for S7 connection setup");
}
if (s7data == null)
throw new WrongNumberOfBytesException("No data received in response to Communication Setup");
//Check for S7 Ack Data
if (s7data[1] != 0x03)
throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03);
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ConnectionError;
LastErrorString = string.Format("Couldn't establish the connection to {0}.\nMessage: {1}", IP, exc.Message);
return ErrorCode.ConnectionError;
throw new PlcException(ErrorCode.ConnectionError,
$"Couldn't establish the connection to {IP}.\nMessage: {exc.Message}", exc);
}
return ErrorCode.NoError;
}
private ErrorCode Connect()
private void Connect()
{
try
{
tcpClient = new TcpClient();
tcpClient.Connect(IP, 102);
ConfigureConnection();
tcpClient.Connect(IP, Port);
stream = tcpClient.GetStream();
}
catch (SocketException sex)
{
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
if (sex.SocketErrorCode == SocketError.TimedOut)
{
LastErrorCode = ErrorCode.IPAddressNotAvailable;
}
else
{
LastErrorCode = ErrorCode.ConnectionError;
}
LastErrorString = sex.Message;
throw new PlcException(
sex.SocketErrorCode == SocketError.TimedOut
? ErrorCode.IPAddressNotAvailable
: ErrorCode.ConnectionError, sex);
}
catch (Exception ex)
{
LastErrorCode = ErrorCode.ConnectionError;
LastErrorString = ex.Message;
throw new PlcException(ErrorCode.ConnectionError, ex);
}
return LastErrorCode;
}
/// <summary>
@@ -133,7 +123,7 @@ namespace S7.Net
public object Read(string variable)
{
var adr = new PLCAddress(variable);
return Read(adr.dataType, adr.DBNumber, adr.Address, adr.varType, 1, (byte)adr.BitNumber);
return Read(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
}
/// <summary>
@@ -176,7 +166,7 @@ namespace S7.Net
/// <returns>The number of read bytes</returns>
public int ReadClass(object sourceClass, int db, int startByteAdr = 0)
{
int numBytes = Class.GetClassSize(sourceClass);
int numBytes = (int)Class.GetClassSize(sourceClass);
if (numBytes <= 0)
{
throw new Exception("The size of the class is less than 1 byte and therefore cannot be read");
@@ -231,8 +221,7 @@ namespace S7.Net
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
public void WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
{
int localIndex = 0;
int count = value.Length;
@@ -241,16 +230,11 @@ namespace S7.Net
//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
var maxToWrite = (int)Math.Min(count, 200);
ErrorCode lastError = WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
if (lastError != ErrorCode.NoError)
{
return lastError;
}
var maxToWrite = Math.Min(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
count -= maxToWrite;
localIndex += maxToWrite;
}
return ErrorCode.NoError;
}
/// <summary>
@@ -261,36 +245,28 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
{
if (bitAdr < 0 || bitAdr > 7)
throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
ErrorCode lastError = WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value);
if (lastError != ErrorCode.NoError)
{
return lastError;
}
return ErrorCode.NoError;
WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value);
}
/// <summary>
/// Write a single bit from a DB with the specified index.
/// Write a single bit to a DB with the specified index.
/// </summary>
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="db">Address of the memory area (if you want to write DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
/// <param name="value">Value to write (0 or 1).</param>
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
{
if (value < 0 || value > 1)
throw new ArgumentException("Value must be 0 or 1", nameof(value));
return WriteBit(dataType, db, startByteAdr, bitAdr, value == 1);
WriteBit(dataType, db, startByteAdr, bitAdr, value == 1);
}
/// <summary>
@@ -303,26 +279,29 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
public void Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
{
if (bitAdr != -1)
{
//Must be writing a bit value as bitAdr is specified
if (value is bool)
{
return WriteBit(dataType, db, startByteAdr, bitAdr, (bool)value);
WriteBit(dataType, db, startByteAdr, bitAdr, (bool) value);
}
else if (value is int intValue)
{
if (intValue < 0 || intValue > 7)
throw new ArgumentOutOfRangeException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr), nameof(bitAdr));
throw new ArgumentOutOfRangeException(
string.Format(
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
bitAdr), nameof(bitAdr));
return WriteBit(dataType, db, startByteAdr, bitAdr, intValue == 1);
WriteBit(dataType, db, startByteAdr, bitAdr, intValue == 1);
}
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
else
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
}
return WriteBytes(dataType, db, startByteAdr, Serialization.SerializeValue(value));
else WriteBytes(dataType, db, startByteAdr, Serialization.SerializeValue(value));
}
/// <summary>
@@ -331,11 +310,10 @@ namespace S7.Net
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <param name="value">Value to be written to the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode Write(string variable, object value)
public void Write(string variable, object value)
{
var adr = new PLCAddress(variable);
return Write(adr.dataType, adr.DBNumber, adr.Address, value, adr.BitNumber);
Write(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber);
}
/// <summary>
@@ -344,10 +322,9 @@ namespace S7.Net
/// <param name="structValue">The struct to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteStruct(object structValue, int db, int startByteAdr = 0)
public void WriteStruct(object structValue, int db, int startByteAdr = 0)
{
return WriteStructAsync(structValue, db, startByteAdr).Result;
WriteStructAsync(structValue, db, startByteAdr).GetAwaiter().GetResult();
}
/// <summary>
@@ -356,10 +333,9 @@ namespace S7.Net
/// <param name="classValue">The class to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteClass(object classValue, int db, int startByteAdr = 0)
public void WriteClass(object classValue, int db, int startByteAdr = 0)
{
return WriteClassAsync(classValue, db, startByteAdr).Result;
WriteClassAsync(classValue, db, startByteAdr).GetAwaiter().GetResult();
}
private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, int count)
@@ -377,25 +353,16 @@ namespace S7.Net
stream.Write(package.Array, 0, package.Array.Length);
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
AssertReadResponse(s7data, count);
for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
return bytes;
}
catch (SocketException socketException)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
return null;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
return null;
throw new PlcException(ErrorCode.ReadData, exc);
}
}
@@ -406,6 +373,8 @@ namespace S7.Net
/// <param name="dataItems">The DataItem(s) to write to the PLC.</param>
public void Write(params DataItem[] dataItems)
{
AssertPduSizeForWrite(dataItems);
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
stream.Write(message.Array, 0, length);
@@ -414,7 +383,7 @@ namespace S7.Net
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
}
private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
{
int varCount = 0;
try
@@ -424,8 +393,9 @@ namespace S7.Net
int packageSize = 35 + value.Length;
ByteArray package = new ByteArray(packageSize);
package.Add(new byte[] { 3, 0, 0 });
package.Add((byte)packageSize);
package.Add(new byte[] { 3, 0 });
//complete package size
package.Add(Int.ToByteArray((short)packageSize));
package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.Add(Word.ToByteArray((ushort)(varCount - 1)));
package.Add(new byte[] { 0, 0x0e });
@@ -448,26 +418,22 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
{
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
return ErrorCode.NoError;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
private ErrorCode WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
int varCount = 0;
try
{
var value = new[] { bitValue ? (byte)1 : (byte)0 };
var value = new[] {bitValue ? (byte) 1 : (byte) 0};
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
@@ -496,15 +462,11 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
return ErrorCode.NoError;
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
@@ -518,20 +480,15 @@ namespace S7.Net
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read. Maximum 20 dataitems are accepted.</param>
public void ReadMultipleVars(List<DataItem> dataItems)
{
int cntBytes = dataItems.Sum(dataItem => VarTypeToByteLength(dataItem.VarType, dataItem.Count));
//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
if (dataItems.Count > 20)
throw new Exception("Too many vars requested");
if (cntBytes > 222)
throw new Exception("Too many bytes requested"); // TODO: proper TDU check + split in multiple requests
AssertPduSizeForRead(dataItems);
try
{
// first create the header
int packageSize = 19 + (dataItems.Count * 12);
Types.ByteArray package = new ByteArray(packageSize);
ByteArray package = new ByteArray(packageSize);
package.Add(ReadHeaderPackage(dataItems.Count));
// package.Add(0x02); // datenart
foreach (var dataItem in dataItems)
@@ -543,19 +500,13 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream); //TODO use Async
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
ParseDataIntoDataItems(s7data, dataItems);
}
catch (SocketException socketException)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
throw new PlcException(ErrorCode.ReadData, exc);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace S7.Net.Protocol
3, 0, //Destination TASP
192, //Parameter Code (tpdu-size)
1, //Parameter Length
9 //TPDU Size (2^9 = 512)
10 //TPDU Size (2^10 = 1024)
};
switch (cpu)
@@ -34,6 +34,14 @@ namespace S7.Net.Protocol
bSend1[17] = 0x10;
bSend1[18] = 0x00;
break;
case CpuType.Logo0BA8:
// These values are taken from NodeS7, it's not verified if these are
// exact requirements to connect to the Logo0BA8.
bSend1[13] = 0x01;
bSend1[14] = 0x00;
bSend1[17] = 0x01;
bSend1[18] = 0x02;
break;
case CpuType.S71200:
case CpuType.S7300:
case CpuType.S7400:

View File

@@ -20,43 +20,55 @@ namespace S7.Net.Protocol
var dataOffset = paramOffset + paramSize;
var data = new ByteArray();
var itemCount = 0;
foreach (var item in dataItems)
{
itemCount++;
message.Add(Parameter.Template);
var value = Serialization.SerializeValue(item.Value);
var value = Serialization.SerializeDataItem(item);
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)
{
if (item.BitAdr > 7)
throw new ArgumentException(
$"Cannot read bit with invalid {nameof(item.BitAdr)} '{item.BitAdr}'.", nameof(dataItems));
Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr,
item.BitAdr);
data.Add(0x03);
data.AddWord(1);
data.Add(b ? (byte) 1 : (byte) 0);
data.Add(0);
data.Add(b ? (byte)1 : (byte)0);
if (itemCount != dataItems.Length) {
data.Add(0);
}
}
else
{
Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr, 0);
var len = value.Length;
data.Add(0x04);
data.AddWord((ushort) (len << 3));
data.Add(value);
if ((len & 0b1) == 1)
if ((len & 0b1) == 1 && itemCount != dataItems.Length)
{
data.Add(0);
}
}
}
paramOffset += Parameter.Template.Length;
}
message.Add(data.Array);

View File

@@ -11,6 +11,16 @@ namespace S7.Net.Protocol
return (ushort)((buf[index] << 8) + buf[index]);
}
public static byte[] SerializeDataItem(DataItem dataItem)
{
if (dataItem.Value is string s)
return dataItem.VarType == VarType.StringEx
? StringEx.ToByteArray(s, dataItem.Count)
: Types.String.ToByteArray(s, dataItem.Count);
return SerializeValue(dataItem.Value);
}
public static byte[] SerializeValue(object value)
{
switch (value.GetType().Name)
@@ -31,6 +41,8 @@ namespace S7.Net.Protocol
return Types.Double.ToByteArray((double)value);
case "Single":
return Types.Single.ToByteArray((float)value);
case "DateTime":
return Types.DateTime.ToByteArray((System.DateTime) value);
case "Byte[]":
return (byte[])value;
case "Int16[]":
@@ -46,7 +58,12 @@ namespace S7.Net.Protocol
case "Single[]":
return Types.Single.ToByteArray((float[])value);
case "String":
return Types.String.ToByteArray(value as string);
// Hack: This is backwards compatible with the old code, but functionally it's broken
// if the consumer does not pay attention to string length.
var stringVal = (string) value;
return Types.String.ToByteArray(stringVal, stringVal.Length);
case "DateTime[]":
return Types.DateTime.ToByteArray((System.DateTime[]) value);
default:
throw new InvalidVariableTypeException();
}

View File

@@ -23,5 +23,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-62925-02" PrivateAssets="All" />
<PackageReference Include="SourceLink.Copy.PdbFiles" Version="2.8.3" PrivateAssets="All" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,4 +1,5 @@
using System.Collections;
using System;
using System.Collections;
namespace S7.Net.Types
{
@@ -16,12 +17,27 @@ namespace S7.Net.Types
}
/// <summary>
/// Converts an array of bytes to a BitArray
/// Converts an array of bytes to a BitArray.
/// </summary>
public static BitArray ToBitArray(byte[] bytes)
/// <param name="bytes">The bytes to convert.</param>
/// <returns>A BitArray with the same number of bits and equal values as <paramref name="bytes"/>.</returns>
public static BitArray ToBitArray(byte[] bytes) => ToBitArray(bytes, bytes.Length * 8);
/// <summary>
/// Converts an array of bytes to a BitArray.
/// </summary>
/// <param name="bytes">The bytes to convert.</param>
/// <param name="length">The number of bits to return.</param>
/// <returns>A BitArray with <paramref name="length"/> bits.</returns>
public static BitArray ToBitArray(byte[] bytes, int length)
{
BitArray bitArr = new BitArray(bytes);
return bitArr;
if (length > bytes.Length * 8) throw new ArgumentException($"Not enough data in bytes to return {length} bits.", nameof(bytes));
var bitArr = new BitArray(bytes);
var bools = new bool[length];
for (var i = 0; i < length; i++) bools[i] = bitArr[i];
return new BitArray(bools);
}
}
}

View File

@@ -25,10 +25,8 @@ namespace S7.Net.Types
}
private static double GetIncreasedNumberOfBytes(double startingNumberOfBytes, Type type)
private static double GetIncreasedNumberOfBytes(double numBytes, Type type)
{
double numBytes = startingNumberOfBytes;
switch (type.Name)
{
case "Boolean":
@@ -61,7 +59,7 @@ namespace S7.Net.Types
break;
default:
var propertyClass = Activator.CreateInstance(type);
numBytes += GetClassSize(propertyClass);
numBytes = GetClassSize(propertyClass, numBytes, true);
break;
}
@@ -73,10 +71,8 @@ namespace S7.Net.Types
/// </summary>
/// <param name="instance">An instance of the class</param>
/// <returns>the number of bytes</returns>
public static int GetClassSize(object instance)
public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false)
{
double numBytes = 0.0;
var properties = GetAccessableProperties(instance.GetType());
foreach (var property in properties)
{
@@ -89,6 +85,7 @@ namespace S7.Net.Types
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);
@@ -99,11 +96,14 @@ namespace S7.Net.Types
numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType);
}
}
// enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
return (int)numBytes;
if (false == isInnerProperty)
{
// enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
}
return numBytes;
}
private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes)
@@ -196,14 +196,8 @@ namespace S7.Net.Types
break;
default:
var propClass = Activator.CreateInstance(propertyType);
var buffer = new byte[GetClassSize(propClass)];
if (buffer.Length > 0)
{
Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length);
FromBytes(propClass, buffer);
value = propClass;
numBytes += buffer.Length;
}
numBytes = FromBytes(propClass, bytes, numBytes);
value = propClass;
break;
}
@@ -215,16 +209,10 @@ namespace S7.Net.Types
/// </summary>
/// <param name="sourceClass">The object to fill in the given array of bytes</param>
/// <param name="bytes">The array of bytes</param>
public static void FromBytes(object sourceClass, byte[] bytes)
public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false)
{
if (bytes == null)
return;
if (bytes.Length != GetClassSize(sourceClass))
return;
// and decode it
double numBytes = 0.0;
return numBytes;
var properties = GetAccessableProperties(sourceClass.GetType());
foreach (var property in properties)
@@ -232,6 +220,7 @@ namespace S7.Net.Types
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++)
{
@@ -248,9 +237,11 @@ namespace S7.Net.Types
null);
}
}
return numBytes;
}
private static void ToBytes(object propertyValue, byte[] bytes, ref double numBytes)
private static double SetBytesFromProperty(object propertyValue, byte[] bytes, double numBytes)
{
int bytePos = 0;
int bitPos = 0;
@@ -293,21 +284,21 @@ namespace S7.Net.Types
bytes2 = Single.ToByteArray((float)propertyValue);
break;
default:
bytes2 = ToBytes(propertyValue);
numBytes = ToBytes(propertyValue, bytes, numBytes);
break;
}
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];
numBytes += bytes2.Length;
}
return numBytes;
}
/// <summary>
@@ -315,30 +306,33 @@ namespace S7.Net.Types
/// </summary>
/// <param name="sourceClass">The struct object</param>
/// <returns>A byte array or null if fails.</returns>
public static byte[] ToBytes(object sourceClass)
public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
{
int size = GetClassSize(sourceClass);
byte[] bytes = new byte[size];
double numBytes = 0.0;
var properties = GetAccessableProperties(sourceClass.GetType());
foreach (var property in properties)
{
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++)
{
ToBytes(array.GetValue(i), bytes, ref numBytes);
numBytes = SetBytesFromProperty(array.GetValue(i), bytes, numBytes);
}
}
else
{
ToBytes(property.GetValue(sourceClass, null), bytes, ref numBytes);
numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), bytes, numBytes);
}
}
return bytes;
return numBytes;
}
private static void IncrementToEven(ref double numBytes)
{
numBytes = Math.Ceiling(numBytes);
if (numBytes % 2 > 0) numBytes++;
}
}
}

View File

@@ -1,4 +1,6 @@
namespace S7.Net.Types
using System;
namespace S7.Net.Types
{
/// <summary>
/// Create an instance of a memory block that can be read by using ReadMultipleVars
@@ -48,5 +50,42 @@
VarType = VarType.Byte;
Count = 1;
}
/// <summary>
/// Create an instance of <see cref="DataItem"/> from the supplied address.
/// </summary>
/// <param name="address">The address to create the DataItem for.</param>
/// <returns>A new <see cref="DataItem"/> instance with properties parsed from <paramref name="address"/>.</returns>
/// <remarks>The <see cref="Count" /> property is not parsed from the address.</remarks>
public static DataItem FromAddress(string address)
{
PLCAddress.Parse(address, out var dataType, out var dbNumber, out var varType, out var startByte,
out var bitNumber);
return new DataItem
{
DataType = dataType,
DB = dbNumber,
VarType = varType,
StartByteAdr = startByte,
BitAdr = (byte) (bitNumber == -1 ? 0 : bitNumber)
};
}
/// <summary>
/// Create an instance of <see cref="DataItem"/> from the supplied address and value.
/// </summary>
/// <param name="address">The address to create the DataItem for.</param>
/// <param name="value">The value to be applied to the DataItem.</param>
/// <returns>A new <see cref="DataItem"/> instance with properties parsed from <paramref name="address"/> and the supplied value set.</returns>
public static DataItem FromAddressAndValue<T>(string address, T value)
{
var dataItem = FromAddress(address);
dataItem.Value = value;
if (typeof(T).IsArray) dataItem.Count = ((Array) dataItem.Value).Length;
return dataItem;
}
}
}

156
S7.Net/Types/DateTime.cs Normal file
View File

@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
namespace S7.Net.Types
{
/// <summary>
/// Contains the methods to convert between <see cref="T:System.DateTime"/> and S7 representation of datetime values.
/// </summary>
public static class DateTime
{
/// <summary>
/// The minimum <see cref="T:System.DateTime"/> value supported by the specification.
/// </summary>
public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1);
/// <summary>
/// The maximum <see cref="T:System.DateTime"/> value supported by the specification.
/// </summary>
public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999);
/// <summary>
/// Parses a <see cref="T:System.DateTime"/> value from bytes.
/// </summary>
/// <param name="bytes">Input bytes read from PLC.</param>
/// <returns>A <see cref="T:System.DateTime"/> object representing the value read from PLC.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
/// <paramref name="bytes"/> is not 8 or any value in <paramref name="bytes"/>
/// is outside the valid range of values.</exception>
public static System.DateTime FromByteArray(byte[] bytes)
{
return FromByteArrayImpl(bytes);
}
/// <summary>
/// Parses an array of <see cref="T:System.DateTime"/> values from bytes.
/// </summary>
/// <param name="bytes">Input bytes read from PLC.</param>
/// <returns>An array of <see cref="T:System.DateTime"/> objects representing the values read from PLC.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
/// <paramref name="bytes"/> is not a multiple of 8 or any value in
/// <paramref name="bytes"/> is outside the valid range of values.</exception>
public static System.DateTime[] ToArray(byte[] bytes)
{
if (bytes.Length % 8 != 0)
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
$"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long.");
var cnt = bytes.Length / 8;
var result = new System.DateTime[bytes.Length / 8];
for (var i = 0; i < cnt; i++)
result[i] = FromByteArrayImpl(new ArraySegment<byte>(bytes, i * 8, 8));
return result;
}
private static System.DateTime FromByteArrayImpl(IList<byte> bytes)
{
if (bytes.Count != 8)
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count,
$"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long.");
int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111);
int ByteToYear(byte bcdYear)
{
var input = DecodeBcd(bcdYear);
if (input < 90) return input + 2000;
if (input < 100) return input + 1900;
throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear,
$"Value '{input}' is higher than the maximum '99' of S7 date and time representation.");
}
int AssertRangeInclusive(int input, byte min, byte max, string field)
{
if (input < min)
throw new ArgumentOutOfRangeException(nameof(input), input,
$"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
if (input > max)
throw new ArgumentOutOfRangeException(nameof(input), input,
$"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
return input;
}
var year = ByteToYear(bytes[0]);
var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month");
var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month");
var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour");
var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute");
var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second");
var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits");
var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit");
var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week");
return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec);
}
/// <summary>
/// Converts a <see cref="T:System.DateTime"/> value to a byte array.
/// </summary>
/// <param name="dateTime">The DateTime value to convert.</param>
/// <returns>A byte array containing the S7 date time representation of <paramref name="dateTime"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the value of
/// <paramref name="dateTime"/> is before <see cref="P:SpecMinimumDateTime"/>
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
public static byte[] ToByteArray(System.DateTime dateTime)
{
byte EncodeBcd(int value)
{
return (byte) ((value / 10 << 4) | value % 10);
}
if (dateTime < SpecMinimumDateTime)
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
$"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation.");
if (dateTime > SpecMaximumDateTime)
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
$"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation.");
byte MapYear(int year) => (byte) (year < 2000 ? year - 1900 : year - 2000);
int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int) dayOfWeek + 1;
return new[]
{
EncodeBcd(MapYear(dateTime.Year)),
EncodeBcd(dateTime.Month),
EncodeBcd(dateTime.Day),
EncodeBcd(dateTime.Hour),
EncodeBcd(dateTime.Minute),
EncodeBcd(dateTime.Second),
EncodeBcd(dateTime.Millisecond / 10),
(byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek))
};
}
/// <summary>
/// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
/// </summary>
/// <param name="dateTimes">The DateTime values to convert.</param>
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTime"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
/// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
public static byte[] ToByteArray(System.DateTime[] dateTimes)
{
var bytes = new List<byte>(dateTimes.Length * 8);
foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime));
return bytes.ToArray();
}
}
}

View File

@@ -6,11 +6,21 @@
public class String
{
/// <summary>
/// Converts a string to S7 bytes
/// Converts a string to <paramref name="reservedLength"/> of bytes, padded with 0-bytes if required.
/// </summary>
public static byte[] ToByteArray(string value)
/// <param name="value">The string to write to the PLC.</param>
/// <param name="reservedLength">The amount of bytes reserved for the <paramref name="value"/> in the PLC.</param>
public static byte[] ToByteArray(string value, int reservedLength)
{
return System.Text.Encoding.ASCII.GetBytes(value);
var length = value?.Length;
if (length > reservedLength) length = reservedLength;
var bytes = new byte[reservedLength];
if (length == null || length == 0) return bytes;
System.Text.Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 0);
return bytes;
}
/// <summary>

View File

@@ -1,4 +1,7 @@
namespace S7.Net.Types
using System;
using System.Text;
namespace S7.Net.Types
{
/// <summary>
/// Contains the methods to convert from S7 strings to C# strings
@@ -19,8 +22,39 @@
int size = bytes[0];
int length = bytes[1];
return System.Text.Encoding.ASCII.GetString(bytes, 2, length);
try
{
return Encoding.ASCII.GetString(bytes, 2, length);
}
catch (Exception e)
{
throw new PlcException(ErrorCode.ReadData,
$"Failed to parse {VarType.StringEx} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.",
e);
}
}
/// <summary>
/// Converts a <see cref="T:string"/> to S7 string with 2-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in bytes) allocated in PLC for string excluding header.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 2.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
{
if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}.");
var length = value?.Length;
if (length > reservedLength) length = reservedLength;
var bytes = new byte[(length ?? 0) + 2];
bytes[0] = (byte) reservedLength;
if (value == null) return bytes;
bytes[1] = (byte) Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 2);
return bytes;
}
}
}

13
appveyor.yml Normal file
View File

@@ -0,0 +1,13 @@
image: Visual Studio 2017
configuration: Release
install:
- choco install gitversion.portable -y
before_build:
- cmd: gitversion /l console /output buildserver
- nuget restore
build_script:
msbuild /nologo /v:m /p:AssemblyVersion=%GitVersion_AssemblySemVer% /p:FileVersion=%GitVersion_MajorMinorPatch% /p:InformationalVersion=%GitVersion_InformationalVersion% /p:Configuration=%CONFIGURATION% S7.sln
after_build:
- dotnet pack S7.Net -c %CONFIGURATION% /p:Version=%GitVersion_NuGetVersion% --no-build -o ..\artifacts
artifacts:
- path: artifacts\*.*