mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc6781c37f | ||
|
|
3555436c04 | ||
|
|
3258c84fbc | ||
|
|
28257f28b3 | ||
|
|
a1d87de2d9 | ||
|
|
4be5765fc9 | ||
|
|
6614c7330a | ||
|
|
5d59c8284d | ||
|
|
6554b999c0 | ||
|
|
ff20687776 | ||
|
|
0b6226327b | ||
|
|
385240ba5e | ||
|
|
0a8ee0e091 | ||
|
|
1685270535 | ||
|
|
9a34b14e1e | ||
|
|
50f0e62573 | ||
|
|
9ea54be524 | ||
|
|
dcd5bb3437 | ||
|
|
8dc89867e9 | ||
|
|
798913c4c6 | ||
|
|
2fbf659136 | ||
|
|
6a84b6f124 | ||
|
|
b3cb45de37 | ||
|
|
4fcab9a167 | ||
|
|
94a178478b | ||
|
|
2a4485941f | ||
|
|
47cce5887d | ||
|
|
0e5651b37f | ||
|
|
1d1f1e76a9 | ||
|
|
555d1e8956 | ||
|
|
427d8124de | ||
|
|
735f1d4533 | ||
|
|
2aa4f08836 | ||
|
|
8d8b2ec36e | ||
|
|
cf64c65c23 | ||
|
|
93752a3485 | ||
|
|
229df2dfcd | ||
|
|
3b23ab76e7 | ||
|
|
3872e638aa | ||
|
|
e124c70fb1 | ||
|
|
b8b4071e39 | ||
|
|
b2183dd760 | ||
|
|
f2d33855ca | ||
|
|
b1d2d11904 | ||
|
|
23796de8bf | ||
|
|
02b38326c8 | ||
|
|
ea96891a31 | ||
|
|
2fbabd5517 | ||
|
|
800a790b89 | ||
|
|
0dbe401ce9 | ||
|
|
e623b535ac | ||
|
|
ce359789dc | ||
|
|
4ae905ffd5 | ||
|
|
70506e7dba | ||
|
|
20458d4b46 | ||
|
|
898b870221 | ||
|
|
0fd193e08f | ||
|
|
98df02d7d4 | ||
|
|
ae620f3c62 | ||
|
|
de084394a6 | ||
|
|
370fd6b3d9 | ||
|
|
fba3ffd5db | ||
|
|
e44cb1571c | ||
|
|
ab486e3d1f | ||
|
|
705743e5f1 |
@@ -4,14 +4,18 @@ branches:
|
||||
master:
|
||||
tag: rc
|
||||
increment: Minor
|
||||
features?[/-]:
|
||||
feature:
|
||||
regex: features?[/-]
|
||||
tag: rc-{BranchName}
|
||||
increment: Minor
|
||||
(pull|pull\-requests|pr)[/-]:
|
||||
pull-request:
|
||||
regex: (pull|pull\-requests|pr)[/-]
|
||||
tag: rc-pr-{BranchName}
|
||||
increment: Minor
|
||||
hotfix(es)?[/-]:
|
||||
hotfix:
|
||||
regex: hotfix(es)?[/-]
|
||||
tag: rc
|
||||
increment: Patch
|
||||
dev(elop)?(ment)?$:
|
||||
develop:
|
||||
regex: dev(elop)?(ment)?$
|
||||
tag: b
|
||||
@@ -75,7 +75,7 @@ namespace S7.Net.UnitTest
|
||||
destTsap1, destTsap2, //Destination TASP
|
||||
192, //Parameter Code (tpdu-size)
|
||||
1, //Parameter Length
|
||||
11 //TPDU Size (2^11 = 2048)
|
||||
10 //TPDU Size (2^11 = 2048)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
51
S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs
Normal file
51
S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
156
S7.Net.UnitTest/PLCAddressParsingTests.cs
Normal file
156
S7.Net.UnitTest/PLCAddressParsingTests.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,9 @@
|
||||
<Compile Include="S7NetTestsSync.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Helpers\TestLongStruct.cs" />
|
||||
<Compile Include="TypeTests\ClassTests.cs" />
|
||||
<Compile Include="TypeTests\DateTimeLongTests.cs" />
|
||||
<Compile Include="TypeTests\DateTimeTests.cs" />
|
||||
<Compile Include="TypeTests\StringExTests.cs" />
|
||||
<Compile Include="TypeTests\StringTests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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>
|
||||
@@ -663,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()
|
||||
@@ -737,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
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace S7.Net.UnitTest
|
||||
{
|
||||
#region Constants
|
||||
const int DB2 = 2;
|
||||
const int DB4 = 4;
|
||||
#endregion
|
||||
|
||||
#region Private fields
|
||||
@@ -694,6 +695,54 @@ namespace S7.Net.UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
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()
|
||||
{
|
||||
@@ -767,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
|
||||
@@ -948,6 +997,20 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(test_value, test_value2, "Compare Write/Read"); //No delta, datatype matches
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void T33_ReadWriteDateTimeLong()
|
||||
{
|
||||
var test_value = System.DateTime.Now;
|
||||
var db = 1;
|
||||
var offset = 0;
|
||||
|
||||
plc.WriteBytes(DataType.DataBlock, db, offset, Types.DateTimeLong.ToByteArray(test_value));
|
||||
var test_value2 = plc.Read(DataType.DataBlock, db, offset, VarType.DateTimeLong, 1);
|
||||
Assert.IsInstanceOfType(test_value2, typeof(System.DateTime));
|
||||
|
||||
Assert.AreEqual(test_value, test_value2, "Compare DateTimeLong Write/Read");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
|
||||
33
S7.Net.UnitTest/TypeTests/ClassTests.cs
Normal file
33
S7.Net.UnitTest/TypeTests/ClassTests.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs
Normal file
171
S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace S7.Net.UnitTest.TypeTests
|
||||
{
|
||||
public static class DateTimeLongTests
|
||||
{
|
||||
private static readonly DateTime SampleDateTime = new DateTime(1993, 12, 25, 8, 12, 34, 567);
|
||||
|
||||
private static readonly byte[] SampleByteArray = {0x07, 0xC9, 0x0C, 0x19, 0x07, 0x08, 0x0C, 0x22, 0x21, 0xCB, 0xBB, 0xC0 };
|
||||
|
||||
private static readonly byte[] SpecMinByteArray =
|
||||
{
|
||||
0x07, 0xB2, 0x01, 0x01, (byte) (int) (Types.DateTimeLong.SpecMinimumDateTime.DayOfWeek + 1), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
private static readonly byte[] SpecMaxByteArray =
|
||||
{
|
||||
0x08, 0xD6, 0x04, 0x0B, (byte) (int) (Types.DateTimeLong.SpecMaximumDateTime.DayOfWeek + 1), 0x17, 0x2F, 0x10, 0x32, 0xE7, 0x01, 0x80
|
||||
};
|
||||
|
||||
[TestClass]
|
||||
public class FromByteArray
|
||||
{
|
||||
[TestMethod]
|
||||
public void Sample()
|
||||
{
|
||||
AssertFromByteArrayEquals(SampleDateTime, SampleByteArray);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpecMinimum()
|
||||
{
|
||||
AssertFromByteArrayEquals(Types.DateTimeLong.SpecMinimumDateTime, SpecMinByteArray);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpecMaximum()
|
||||
{
|
||||
AssertFromByteArrayEquals(Types.DateTimeLong.SpecMaximumDateTime, SpecMaxByteArray);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnLessThan12Bytes()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(new byte[11]);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnMoreTHan12Bytes()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(new byte[13]);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidYear()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(0, 0xa0));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnZeroMonth()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(2, 0x00));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTooLargeMonth()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(2, 0x13));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnZeroDay()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(3, 0x00));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTooLargeDay()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(3, 0x32));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidHour()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(5, 0x24));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidMinute()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(6, 0x60));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidSecond()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(7, 0x60));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidNanosecondsFirstDigit()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(8, 0x3B));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnZeroDayOfWeek()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(4, 0));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTooLargeDayOfWeek()
|
||||
{
|
||||
Types.DateTimeLong.FromByteArray(MutateSample(4, 8));
|
||||
}
|
||||
|
||||
private static void AssertFromByteArrayEquals(DateTime expected, params byte[] bytes)
|
||||
{
|
||||
Assert.AreEqual(expected, Types.DateTimeLong.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.DateTimeLong.SpecMinimumDateTime, SpecMinByteArray);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpecMaximum()
|
||||
{
|
||||
AssertToByteArrayEquals(Types.DateTimeLong.SpecMaximumDateTime, SpecMaxByteArray);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTimeBeforeSpecMinimum()
|
||||
{
|
||||
Types.DateTimeLong.ToByteArray(new DateTime(1950, 1, 1));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTimeAfterSpecMaximum()
|
||||
{
|
||||
Types.DateTimeLong.ToByteArray(new DateTime(2790, 1, 1));
|
||||
}
|
||||
|
||||
private static void AssertToByteArrayEquals(DateTime value, params byte[] expected)
|
||||
{
|
||||
CollectionAssert.AreEqual(expected, Types.DateTimeLong.ToByteArray(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
176
S7.Net.UnitTest/TypeTests/DateTimeTests.cs
Normal file
176
S7.Net.UnitTest/TypeTests/DateTimeTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
/// </summary>
|
||||
S7200 = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Siemens Logo 0BA8
|
||||
/// </summary>
|
||||
Logo0BA8 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// S7 300 cpu type
|
||||
/// </summary>
|
||||
@@ -176,6 +181,16 @@
|
||||
/// <summary>
|
||||
/// Counter variable type
|
||||
/// </summary>
|
||||
Counter
|
||||
Counter,
|
||||
|
||||
/// <summary>
|
||||
/// DateTIme variable type
|
||||
/// </summary>
|
||||
DateTime,
|
||||
|
||||
/// <summary>
|
||||
/// DateTimeLong variable type
|
||||
/// </summary>
|
||||
DateTimeLong
|
||||
}
|
||||
}
|
||||
|
||||
43
S7.Net/InvalidDataException.cs
Normal file
43
S7.Net/InvalidDataException.cs
Normal 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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
124
S7.Net/PLC.cs
124
S7.Net/PLC.cs
@@ -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
|
||||
@@ -80,7 +115,34 @@ namespace S7.Net
|
||||
catch { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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.
|
||||
/// 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>
|
||||
/// <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));
|
||||
|
||||
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.
|
||||
@@ -102,10 +164,12 @@ namespace S7.Net
|
||||
|
||||
CPU = cpu;
|
||||
IP = ip;
|
||||
Port = 102;
|
||||
Rack = rack;
|
||||
Slot = slot;
|
||||
MaxPDUSize = 240;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close connection to PLC
|
||||
/// </summary>
|
||||
@@ -117,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
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
default:
|
||||
throw new InvalidAddressException();
|
||||
}
|
||||
case "IB":
|
||||
case "EB":
|
||||
// Input byte
|
||||
dataType = DataType.Input;
|
||||
@@ -87,6 +88,7 @@
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Byte;
|
||||
return;
|
||||
case "IW":
|
||||
case "EW":
|
||||
// Input word
|
||||
dataType = DataType.Input;
|
||||
@@ -94,6 +96,7 @@
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Word;
|
||||
return;
|
||||
case "ID":
|
||||
case "ED":
|
||||
// Input double-word
|
||||
dataType = DataType.Input;
|
||||
@@ -101,21 +104,27 @@
|
||||
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;
|
||||
@@ -152,6 +161,7 @@
|
||||
dataType = DataType.Input;
|
||||
varType = VarType.Bit;
|
||||
break;
|
||||
case "Q":
|
||||
case "A":
|
||||
case "O":
|
||||
// Output
|
||||
@@ -161,7 +171,7 @@
|
||||
case "M":
|
||||
// Memory
|
||||
dataType = DataType.Memory;
|
||||
varType = VarType.Byte;
|
||||
varType = VarType.Bit;
|
||||
break;
|
||||
case "T":
|
||||
// Timer
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DateTime = S7.Net.Types.DateTime;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
@@ -144,7 +145,25 @@ 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);
|
||||
}
|
||||
case VarType.DateTimeLong:
|
||||
if (varCount == 1)
|
||||
{
|
||||
return DateTimeLong.FromByteArray(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTimeLong.ToArray(bytes);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
@@ -162,7 +181,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:
|
||||
@@ -178,6 +197,10 @@ namespace S7.Net
|
||||
case VarType.DInt:
|
||||
case VarType.Real:
|
||||
return varCount * 4;
|
||||
case VarType.DateTime:
|
||||
return varCount * 8;
|
||||
case VarType.DateTimeLong:
|
||||
return varCount * 12;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -186,7 +209,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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,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();
|
||||
}
|
||||
|
||||
@@ -141,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");
|
||||
@@ -201,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
|
||||
@@ -370,8 +369,9 @@ namespace S7.Net
|
||||
/// <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();
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
|
||||
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)
|
||||
@@ -388,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 PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
AssertReadResponse(s7data, count);
|
||||
|
||||
for (int cnt = 0; cnt < count; cnt++)
|
||||
bytes[cnt] = s7data[cnt + 18];
|
||||
@@ -405,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);
|
||||
|
||||
@@ -23,16 +23,19 @@ namespace S7.Net
|
||||
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)
|
||||
@@ -47,7 +50,8 @@ namespace S7.Net
|
||||
try
|
||||
{
|
||||
tcpClient = new TcpClient();
|
||||
tcpClient.Connect(IP, 102);
|
||||
ConfigureConnection();
|
||||
tcpClient.Connect(IP, Port);
|
||||
stream = tcpClient.GetStream();
|
||||
}
|
||||
catch (SocketException sex)
|
||||
@@ -162,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");
|
||||
@@ -226,7 +230,7 @@ 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 = Math.Min(count, 200);
|
||||
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;
|
||||
@@ -349,8 +353,7 @@ 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 PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
AssertReadResponse(s7data, count);
|
||||
|
||||
for (int cnt = 0; cnt < count; cnt++)
|
||||
bytes[cnt] = s7data[cnt + 18];
|
||||
@@ -370,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);
|
||||
@@ -388,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 });
|
||||
@@ -474,15 +480,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 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
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace S7.Net.Protocol
|
||||
3, 0, //Destination TASP
|
||||
192, //Parameter Code (tpdu-size)
|
||||
1, //Parameter Length
|
||||
11 //TPDU Size (2^11 = 2048)
|
||||
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:
|
||||
|
||||
@@ -20,8 +20,11 @@ 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.SerializeDataItem(item);
|
||||
var wordLen = item.Value is bool ? 1 : 2;
|
||||
@@ -30,33 +33,42 @@ namespace S7.Net.Protocol
|
||||
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);
|
||||
|
||||
|
||||
@@ -41,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[]":
|
||||
@@ -60,6 +62,10 @@ namespace S7.Net.Protocol
|
||||
// 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);
|
||||
case "DateTimeLong[]":
|
||||
return Types.DateTimeLong.ToByteArray((System.DateTime[])value);
|
||||
default:
|
||||
throw new InvalidVariableTypeException();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace S7.Net.Types
|
||||
DB = dbNumber,
|
||||
VarType = varType,
|
||||
StartByteAdr = startByte,
|
||||
BitAdr = (byte) bitNumber
|
||||
BitAdr = (byte) (bitNumber == -1 ? 0 : bitNumber)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
156
S7.Net/Types/DateTime.cs
Normal file
156
S7.Net/Types/DateTime.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
185
S7.Net/Types/DateTimeLong.cs
Normal file
185
S7.Net/Types/DateTimeLong.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert between <see cref="T:System.DateTime" /> and S7 representation of DateTimeLong (DTL) values.
|
||||
/// </summary>
|
||||
public static class DateTimeLong
|
||||
{
|
||||
public const int TypeLengthInBytes = 12;
|
||||
/// <summary>
|
||||
/// The minimum <see cref="T:System.DateTime" /> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1970, 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(2262, 4, 11, 23, 47, 16, 854);
|
||||
|
||||
/// <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 12 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 12 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 % TypeLengthInBytes != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||
$"Parsing an array of DateTimeLong requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long.");
|
||||
}
|
||||
|
||||
var cnt = bytes.Length / TypeLengthInBytes;
|
||||
var result = new System.DateTime[cnt];
|
||||
|
||||
for (var i = 0; i < cnt; i++)
|
||||
{
|
||||
var slice = new byte[TypeLengthInBytes];
|
||||
Array.Copy(bytes, i * TypeLengthInBytes, slice, 0, TypeLengthInBytes);
|
||||
result[i] = FromByteArrayImpl(slice);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static System.DateTime FromByteArrayImpl(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != TypeLengthInBytes)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||
$"Parsing a DateTimeLong requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long.");
|
||||
}
|
||||
|
||||
|
||||
var year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year");
|
||||
var month = AssertRangeInclusive(bytes[2], 1, 12, "month");
|
||||
var day = AssertRangeInclusive(bytes[3], 1, 31, "day of month");
|
||||
var dayOfWeek = AssertRangeInclusive(bytes[4], 1, 7, "day of week");
|
||||
var hour = AssertRangeInclusive(bytes[5], 0, 23, "hour");
|
||||
var minute = AssertRangeInclusive(bytes[6], 0, 59, "minute");
|
||||
var second = AssertRangeInclusive(bytes[7], 0, 59, "second");
|
||||
;
|
||||
|
||||
var nanoseconds = AssertRangeInclusive<uint>(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0,
|
||||
999999999, "nanoseconds");
|
||||
|
||||
var time = new System.DateTime(year, month, day, hour, minute, second);
|
||||
return time.AddTicks(nanoseconds / 100);
|
||||
}
|
||||
|
||||
/// <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 DateTimeLong 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)
|
||||
{
|
||||
if (dateTime < SpecMinimumDateTime)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DateTimeLong representation.");
|
||||
}
|
||||
|
||||
if (dateTime > SpecMaximumDateTime)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DateTimeLong representation.");
|
||||
}
|
||||
|
||||
var stream = new MemoryStream(TypeLengthInBytes);
|
||||
// Convert Year
|
||||
stream.Write(Word.ToByteArray(Convert.ToUInt16(dateTime.Year)), 0, 2);
|
||||
|
||||
// Convert Month
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Month));
|
||||
|
||||
// Convert Day
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Day));
|
||||
|
||||
// Convert WeekDay. NET DateTime starts with Sunday = 0, while S7DT has Sunday = 1.
|
||||
stream.WriteByte(Convert.ToByte(dateTime.DayOfWeek + 1));
|
||||
|
||||
// Convert Hour
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Hour));
|
||||
|
||||
// Convert Minutes
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Minute));
|
||||
|
||||
// Convert Seconds
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Second));
|
||||
|
||||
// Convert Nanoseconds. Net DateTime has a representation of 1 Tick = 100ns.
|
||||
// Thus First take the ticks Mod 1 Second (1s = 10'000'000 ticks), and then Convert to nanoseconds.
|
||||
stream.Write(DWord.ToByteArray(Convert.ToUInt32(dateTime.Ticks % 10000000 * 100)), 0, 4);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
/// <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 DateTimeLong representations of <paramref name="dateTimes" />.</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 * TypeLengthInBytes);
|
||||
foreach (var dateTime in dateTimes)
|
||||
{
|
||||
bytes.AddRange(ToByteArray(dateTime));
|
||||
}
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
private static T AssertRangeInclusive<T>(T input, T min, T max, string field) where T : IComparable<T>
|
||||
{
|
||||
if (input.CompareTo(min) < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
|
||||
}
|
||||
|
||||
if (input.CompareTo(max) > 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,17 @@ namespace S7.Net.Types
|
||||
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>
|
||||
|
||||
@@ -3,7 +3,7 @@ configuration: Release
|
||||
install:
|
||||
- choco install gitversion.portable -y
|
||||
before_build:
|
||||
- cmd: gitversion /l console /output buildserver /b %APPVEYOR_REPO_BRANCH%
|
||||
- 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
|
||||
|
||||
Reference in New Issue
Block a user