mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-22 14:08:25 +08:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f03ba93a96 | ||
|
|
9cd63d906f | ||
|
|
d051b93bdc | ||
|
|
c9bab7523a | ||
|
|
a7608e3cb7 | ||
|
|
4d37679c75 | ||
|
|
e2a0ed548d | ||
|
|
4124bae1bc | ||
|
|
2bcc5e6b9c | ||
|
|
33981ab4f9 | ||
|
|
de60a7b6b0 | ||
|
|
de87409458 | ||
|
|
ca89736c7c | ||
|
|
4a72c3596b | ||
|
|
786e012179 | ||
|
|
f46833606f | ||
|
|
b95b71e9aa | ||
|
|
1069641606 | ||
|
|
36a9ecb2c8 | ||
|
|
243e868488 | ||
|
|
ff65f06b7d | ||
|
|
065b1fbdf8 | ||
|
|
8f3c701a2f | ||
|
|
023530322e | ||
|
|
9198fc1686 | ||
|
|
12a2e3c0b1 | ||
|
|
b088fe276b | ||
|
|
80ad95372b | ||
|
|
106e9912ab | ||
|
|
af39659944 | ||
|
|
e5bdb10ce3 | ||
|
|
faea428e4c | ||
|
|
4da76c7f6d | ||
|
|
6c4d4605f0 | ||
|
|
730ccbf9fc | ||
|
|
b36c4a98ec | ||
|
|
70c3f8e996 | ||
|
|
cf1b71220a | ||
|
|
88c45bd995 | ||
|
|
cf493d47f0 | ||
|
|
9e2f17fdf3 | ||
|
|
1919b0083a | ||
|
|
fd4bc0fe84 | ||
|
|
9ff73ff3f7 | ||
|
|
c31353bed2 | ||
|
|
b16097092b | ||
|
|
eca2ed6474 | ||
|
|
b92242f911 | ||
|
|
c99c3d745a | ||
|
|
64c781ec8b | ||
|
|
e8a9983367 |
@@ -12,8 +12,6 @@ to my request for committing code, I decided to pick up where he left off here o
|
||||
## Documentation
|
||||
Check the Wiki and feel free to edit it: https://github.com/killnine/s7netplus/wiki
|
||||
|
||||
S7.Net Plus has a [User Manual](https://github.com/killnine/s7netplus/blob/master/Documentation/Documentation.pdf), check it out.
|
||||
|
||||
## Supported PLC
|
||||
|
||||
+ Compatible S7 PLC (S7-200, S7-300, S7-400, S7-1200, S7-1500)
|
||||
@@ -36,6 +34,5 @@ PM> Install-Package S7netplus
|
||||
|
||||
## Running the tests
|
||||
|
||||
Unit tests use Snap7 server, so port 102 must be not in use.
|
||||
If you have Siemens Step7 installed, the service s7oiehsx64 is stopped when running unit tests.
|
||||
You have to restart the service manually if you need it.
|
||||
Unit tests use Snap7 server.
|
||||
On Windows, the DLL is included with the test project. On other platforms, Snap7 must be installed manually before running tests.
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace S7.Net.UnitTest.Helpers
|
||||
Console.WriteLine(Server.EventText(ref Event));
|
||||
}
|
||||
|
||||
public static void Start()
|
||||
public static void Start(short port)
|
||||
{
|
||||
Server = new S7Server();
|
||||
// Share some resources with our virtual PLC
|
||||
@@ -59,7 +59,14 @@ namespace S7.Net.UnitTest.Helpers
|
||||
// Start the server onto the default adapter.
|
||||
// To select an adapter we have to use Server->StartTo("192.168.x.y").
|
||||
// Start() is the same of StartTo("0.0.0.0");
|
||||
|
||||
Server.SetParam(S7Consts.p_u16_LocalPort, ref port);
|
||||
|
||||
int Error = Server.Start();
|
||||
if (Error != 0)
|
||||
{
|
||||
throw new Exception($"Error starting Snap7 server: {Server.ErrorText(Error)}");
|
||||
}
|
||||
//if (Error == 0)
|
||||
//{
|
||||
// // Now the server is running ... wait a key to terminate
|
||||
|
||||
@@ -35,12 +35,12 @@ namespace S7.Net.UnitTest.Helpers
|
||||
/// <summary>
|
||||
/// DB1.DBD4
|
||||
/// </summary>
|
||||
public double RealVariableDouble { get; set; }
|
||||
public double LRealVariable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBD8
|
||||
/// </summary>
|
||||
public float RealVariableFloat { get; set; }
|
||||
public float RealVariable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBD12
|
||||
|
||||
@@ -35,12 +35,12 @@ namespace S7.Net.UnitTest.Helpers
|
||||
/// <summary>
|
||||
/// DB1.DBD4
|
||||
/// </summary>
|
||||
public double RealVariableDouble;
|
||||
public double LRealVariable;
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBD8
|
||||
/// </summary>
|
||||
public float RealVariableFloat;
|
||||
public float RealVariable;
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBD12
|
||||
|
||||
@@ -7,21 +7,25 @@ using S7.Net;
|
||||
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using S7.Net.Protocol;
|
||||
using System.Collections;
|
||||
|
||||
namespace S7.Net.UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class ProtocolUnitTest
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
public void TPKT_Read()
|
||||
public async Task TPKT_Read()
|
||||
{
|
||||
var m = new MemoryStream(StringToByteArray("0300002902f0803203000000010002001400000401ff0400807710000100000103000000033f8ccccd"));
|
||||
var t = TPKT.Read(m);
|
||||
Assert.AreEqual(0x03, t.Version);
|
||||
Assert.AreEqual(0x29, t.Length);
|
||||
m.Position = 0;
|
||||
t = TPKT.ReadAsync(m).Result;
|
||||
t = await TPKT.ReadAsync(m, TestContext.CancellationTokenSource.Token);
|
||||
Assert.AreEqual(0x03, t.Version);
|
||||
Assert.AreEqual(0x29, t.Length);
|
||||
}
|
||||
@@ -40,7 +44,7 @@ namespace S7.Net.UnitTest
|
||||
public async Task TPKT_ReadShortAsync()
|
||||
{
|
||||
var m = new MemoryStream(StringToByteArray("0300002902f0803203000000010002001400000401ff040080"));
|
||||
var t = await TPKT.ReadAsync(m);
|
||||
var t = await TPKT.ReadAsync(m, TestContext.CancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -51,7 +55,7 @@ namespace S7.Net.UnitTest
|
||||
var t = COTP.TSDU.Read(m);
|
||||
Assert.IsTrue(expected.SequenceEqual(t));
|
||||
m.Position = 0;
|
||||
t = COTP.TSDU.ReadAsync(m).Result;
|
||||
t = COTP.TSDU.ReadAsync(m, TestContext.CancellationTokenSource.Token).Result;
|
||||
Assert.IsTrue(expected.SequenceEqual(t));
|
||||
}
|
||||
|
||||
@@ -62,6 +66,32 @@ namespace S7.Net.UnitTest
|
||||
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void TestResponseCode()
|
||||
{
|
||||
var expected = StringToByteArray("320700000400000800080001120411440100ff09000400000000");
|
||||
var m = new MemoryStream(StringToByteArray("0300000702f0000300000702f0000300002102f080320700000400000800080001120411440100ff09000400000000"));
|
||||
var t = COTP.TSDU.Read(m);
|
||||
Assert.IsTrue(expected.SequenceEqual(t));
|
||||
|
||||
|
||||
// Test all possible byte values. Everything except 0xff should throw an exception.
|
||||
var testData = Enumerable.Range(0, 256).Select(i => new { StatusCode = (ReadWriteErrorCode)i, ThrowsException = i != (byte)ReadWriteErrorCode.Success });
|
||||
|
||||
foreach (var entry in testData)
|
||||
{
|
||||
if (entry.ThrowsException)
|
||||
{
|
||||
Assert.ThrowsException<Exception>(() => Plc.ValidateResponseCode(entry.StatusCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
Plc.ValidateResponseCode(entry.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netcoreapp3.1</TargetFrameworks>
|
||||
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Copyright>Copyright © 2014</Copyright>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
@@ -24,13 +26,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="snap7.dll">
|
||||
<None Update="runtimes\win-x64\native\snap7.dll" Link="snap7.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
#region Using
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using S7.Net;
|
||||
using S7.Net.UnitTest.Helpers;
|
||||
using S7.Net.UnitTest;
|
||||
using System.ServiceProcess;
|
||||
using S7.Net.Types;
|
||||
using S7.UnitTest.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -101,20 +98,13 @@ namespace S7.Net.UnitTest
|
||||
|
||||
/// <summary>
|
||||
/// Read/Write a single REAL with a single request.
|
||||
/// Test that writing a double and reading it gives the correct value.
|
||||
/// Test that writing a float and reading it gives the correct value.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Test_Async_WriteAndReadRealVariables()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
// Reading and writing a double is quite complicated, because it needs to be converted to DWord before the write,
|
||||
// then reconvert to double after the read.
|
||||
double val = 35.68729;
|
||||
await plc.WriteAsync("DB1.DBD40", val.ConvertToUInt());
|
||||
double result = ((uint)await plc.ReadAsync("DB1.DBD40")).ConvertToDouble();
|
||||
Assert.AreEqual(val, Math.Round(result, 5)); // float lose precision, so i need to round it
|
||||
|
||||
// Reading and writing a float is quite complicated, because it needs to be converted to DWord before the write,
|
||||
// then reconvert to float after the read. Float values can contain only 7 digits, so no precision is lost.
|
||||
float val2 = 1234567;
|
||||
@@ -162,8 +152,8 @@ namespace S7.Net.UnitTest
|
||||
BitVariable10 = true,
|
||||
DIntVariable = -100000,
|
||||
IntVariable = -15000,
|
||||
RealVariableDouble = -154.789,
|
||||
RealVariableFloat = -154.789f,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
};
|
||||
|
||||
@@ -175,8 +165,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.BitVariable10, tc2.BitVariable10);
|
||||
Assert.AreEqual(tc.DIntVariable, tc2.DIntVariable);
|
||||
Assert.AreEqual(tc.IntVariable, tc2.IntVariable);
|
||||
Assert.AreEqual(tc.RealVariableDouble, Math.Round(tc2.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc.RealVariableFloat, tc2.RealVariableFloat);
|
||||
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable);
|
||||
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
|
||||
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -219,8 +209,8 @@ namespace S7.Net.UnitTest
|
||||
BitVariable10 = true,
|
||||
DIntVariable = -100000,
|
||||
IntVariable = -15000,
|
||||
RealVariableDouble = -154.789,
|
||||
RealVariableFloat = -154.789f,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
};
|
||||
plc.WriteStruct(tc, DB2);
|
||||
@@ -230,8 +220,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.BitVariable10, tc2.BitVariable10);
|
||||
Assert.AreEqual(tc.DIntVariable, tc2.DIntVariable);
|
||||
Assert.AreEqual(tc.IntVariable, tc2.IntVariable);
|
||||
Assert.AreEqual(tc.RealVariableDouble, Math.Round(tc2.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc.RealVariableFloat, tc2.RealVariableFloat);
|
||||
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable);
|
||||
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
|
||||
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -584,8 +574,8 @@ namespace S7.Net.UnitTest
|
||||
BitVariable10 = true,
|
||||
DIntVariable = -100000,
|
||||
IntVariable = -15000,
|
||||
RealVariableDouble = -154.789,
|
||||
RealVariableFloat = -154.789f,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
};
|
||||
|
||||
@@ -599,8 +589,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.BitVariable10, tc2.BitVariable10);
|
||||
Assert.AreEqual(tc.DIntVariable, tc2.DIntVariable);
|
||||
Assert.AreEqual(tc.IntVariable, tc2.IntVariable);
|
||||
Assert.AreEqual(tc.RealVariableDouble, tc2.RealVariableDouble, 0.1);
|
||||
Assert.AreEqual(tc.RealVariableFloat, tc2.RealVariableFloat);
|
||||
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable, 0.1);
|
||||
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
|
||||
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
|
||||
|
||||
Assert.AreEqual(TestClassWithPrivateSetters.PRIVATE_SETTER_VALUE, tc2.PrivateSetterProperty);
|
||||
@@ -632,8 +622,8 @@ namespace S7.Net.UnitTest
|
||||
BitVariable10 = true,
|
||||
DIntVariable = -100000,
|
||||
IntVariable = -15000,
|
||||
RealVariableDouble = -154.789,
|
||||
RealVariableFloat = -154.789f,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
};
|
||||
|
||||
@@ -649,8 +639,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc2.BitVariable10, tc2Generic.BitVariable10);
|
||||
Assert.AreEqual(tc2.DIntVariable, tc2Generic.DIntVariable);
|
||||
Assert.AreEqual(tc2.IntVariable, tc2Generic.IntVariable);
|
||||
Assert.AreEqual(Math.Round(tc2.RealVariableDouble, 3), Math.Round(tc2Generic.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc2.RealVariableFloat, tc2Generic.RealVariableFloat);
|
||||
Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable);
|
||||
Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -675,8 +665,8 @@ namespace S7.Net.UnitTest
|
||||
BitVariable10 = true,
|
||||
DIntVariable = -100000,
|
||||
IntVariable = -15000,
|
||||
RealVariableDouble = -154.789,
|
||||
RealVariableFloat = -154.789f,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
};
|
||||
|
||||
@@ -689,8 +679,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc2Generic.BitVariable00, tc2GenericWithClassFactory.BitVariable00);
|
||||
Assert.AreEqual(tc2Generic.BitVariable10, tc2GenericWithClassFactory.BitVariable10);
|
||||
Assert.AreEqual(tc2Generic.DIntVariable, tc2GenericWithClassFactory.DIntVariable);
|
||||
Assert.AreEqual(Math.Round(tc2Generic.RealVariableDouble, 3), Math.Round(tc2GenericWithClassFactory.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc2Generic.RealVariableFloat, tc2GenericWithClassFactory.RealVariableFloat);
|
||||
Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable);
|
||||
Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -747,8 +737,8 @@ namespace S7.Net.UnitTest
|
||||
BitVariable10 = true,
|
||||
DIntVariable = -100000,
|
||||
IntVariable = -15000,
|
||||
RealVariableDouble = -154.789,
|
||||
RealVariableFloat = -154.789f,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
};
|
||||
|
||||
@@ -763,8 +753,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(ts2.BitVariable10, ts2Generic.BitVariable10);
|
||||
Assert.AreEqual(ts2.DIntVariable, ts2Generic.DIntVariable);
|
||||
Assert.AreEqual(ts2.IntVariable, ts2Generic.IntVariable);
|
||||
Assert.AreEqual(Math.Round(ts2.RealVariableDouble, 3), Math.Round(ts2Generic.RealVariableDouble, 3));
|
||||
Assert.AreEqual(ts2.RealVariableFloat, ts2Generic.RealVariableFloat);
|
||||
Assert.AreEqual(ts2.LRealVariable, ts2Generic.LRealVariable);
|
||||
Assert.AreEqual(ts2.RealVariable, ts2Generic.RealVariable);
|
||||
Assert.AreEqual(ts2.DWordVariable, ts2Generic.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -792,8 +782,8 @@ namespace S7.Net.UnitTest
|
||||
BitVariable10 = true,
|
||||
DIntVariable = -100000,
|
||||
IntVariable = -15000,
|
||||
RealVariableDouble = -154.789,
|
||||
RealVariableFloat = -154.789f,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
};
|
||||
plc.WriteClass(tc, DB2);
|
||||
@@ -883,17 +873,6 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.CustomTypes[1].Bools[1], tc2.CustomTypes[1].Bools[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Test_Async_ReadWriteDouble()
|
||||
{
|
||||
double test_value = 55.66;
|
||||
await plc.WriteAsync("DB1.DBD0", test_value);
|
||||
var helper = await plc.ReadAsync("DB1.DBD0");
|
||||
double test_value2 = Conversion.ConvertToDouble((uint)helper);
|
||||
|
||||
Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Test_Async_ReadWriteSingle()
|
||||
{
|
||||
@@ -926,6 +905,42 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(x % 256, res[x], string.Format("Bit {0} failed", x));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a large amount of data and test cancellation
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Test_Async_WriteLargeByteArrayWithCancellation()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
var cancellationSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancellationSource.Token;
|
||||
|
||||
var randomEngine = new Random();
|
||||
var data = new byte[8192];
|
||||
var db = 2;
|
||||
randomEngine.NextBytes(data);
|
||||
|
||||
cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5));
|
||||
try
|
||||
{
|
||||
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
|
||||
}
|
||||
catch(TaskCanceledException)
|
||||
{
|
||||
// everything is good, that is the exception we expect
|
||||
Console.WriteLine("Task was cancelled as expected.");
|
||||
return;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Assert.Fail($"Wrong exception type received. Expected {typeof(TaskCanceledException)}, received {e.GetType()}.");
|
||||
}
|
||||
|
||||
// Depending on how tests run, this can also just succeed without getting cancelled at all. Do nothing in this case.
|
||||
Console.WriteLine("Task was not cancelled as expected.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
#region Using
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using S7.Net;
|
||||
using S7.Net.UnitTest.Helpers;
|
||||
using S7.Net.UnitTest;
|
||||
using System.ServiceProcess;
|
||||
using S7.Net.Types;
|
||||
using S7.UnitTest.Helpers;
|
||||
|
||||
@@ -41,6 +37,8 @@ namespace S7.Net.UnitTest
|
||||
#region Constants
|
||||
const int DB2 = 2;
|
||||
const int DB4 = 4;
|
||||
const short TestServerPort = 31122;
|
||||
const string TestServerIp = "127.0.0.1";
|
||||
#endregion
|
||||
|
||||
#region Private fields
|
||||
@@ -53,16 +51,19 @@ namespace S7.Net.UnitTest
|
||||
/// </summary>
|
||||
public S7NetTests()
|
||||
{
|
||||
plc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2);
|
||||
//ConsoleManager.Show();
|
||||
ShutDownServiceS7oiehsx64();
|
||||
plc = CreatePlc();
|
||||
|
||||
}
|
||||
|
||||
private static Plc CreatePlc()
|
||||
{
|
||||
return new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2);
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
S7TestServer.Start();
|
||||
S7TestServer.Start(TestServerPort);
|
||||
plc.Open();
|
||||
}
|
||||
|
||||
@@ -146,20 +147,13 @@ namespace S7.Net.UnitTest
|
||||
|
||||
/// <summary>
|
||||
/// Read/Write a single REAL with a single request.
|
||||
/// Test that writing a double and reading it gives the correct value.
|
||||
/// Test that writing a float and reading it gives the correct value.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void T03_WriteAndReadRealVariables()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
// Reading and writing a double is quite complicated, because it needs to be converted to DWord before the write,
|
||||
// then reconvert to double after the read.
|
||||
double val = 35.68729;
|
||||
plc.Write("DB1.DBD40", val.ConvertToUInt());
|
||||
double result = ((uint)plc.Read("DB1.DBD40")).ConvertToDouble();
|
||||
Assert.AreEqual(val, Math.Round(result, 5)); // float lose precision, so i need to round it
|
||||
|
||||
// Reading and writing a float is quite complicated, because it needs to be converted to DWord before the write,
|
||||
// then reconvert to float after the read. Float values can contain only 7 digits, so no precision is lost.
|
||||
float val2 = 1234567;
|
||||
@@ -186,8 +180,8 @@ namespace S7.Net.UnitTest
|
||||
tc.BitVariable10 = true;
|
||||
tc.DIntVariable = -100000;
|
||||
tc.IntVariable = -15000;
|
||||
tc.RealVariableDouble = -154.789;
|
||||
tc.RealVariableFloat = -154.789f;
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
plc.WriteClass(tc, DB2);
|
||||
TestClass tc2 = new TestClass();
|
||||
@@ -197,8 +191,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.BitVariable10, tc2.BitVariable10);
|
||||
Assert.AreEqual(tc.DIntVariable, tc2.DIntVariable);
|
||||
Assert.AreEqual(tc.IntVariable, tc2.IntVariable);
|
||||
Assert.AreEqual(Math.Round(tc.RealVariableDouble, 3), Math.Round(tc2.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc.RealVariableFloat, tc2.RealVariableFloat);
|
||||
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable);
|
||||
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
|
||||
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -215,8 +209,8 @@ namespace S7.Net.UnitTest
|
||||
tc.BitVariable10 = true;
|
||||
tc.DIntVariable = -100000;
|
||||
tc.IntVariable = -15000;
|
||||
tc.RealVariableDouble = -154.789;
|
||||
tc.RealVariableFloat = -154.789f;
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
plc.WriteStruct(tc, DB2);
|
||||
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
|
||||
@@ -225,8 +219,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.BitVariable10, tc2.BitVariable10);
|
||||
Assert.AreEqual(tc.DIntVariable, tc2.DIntVariable);
|
||||
Assert.AreEqual(tc.IntVariable, tc2.IntVariable);
|
||||
Assert.AreEqual(tc.RealVariableDouble, Math.Round(tc2.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc.RealVariableFloat, tc2.RealVariableFloat);
|
||||
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable);
|
||||
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
|
||||
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -575,8 +569,8 @@ namespace S7.Net.UnitTest
|
||||
tc.BitVariable10 = true;
|
||||
tc.DIntVariable = -100000;
|
||||
tc.IntVariable = -15000;
|
||||
tc.RealVariableDouble = -154.789;
|
||||
tc.RealVariableFloat = -154.789f;
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
@@ -588,8 +582,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.BitVariable10, tc2.BitVariable10);
|
||||
Assert.AreEqual(tc.DIntVariable, tc2.DIntVariable);
|
||||
Assert.AreEqual(tc.IntVariable, tc2.IntVariable);
|
||||
Assert.AreEqual(Math.Round(tc.RealVariableDouble, 3), Math.Round(tc2.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc.RealVariableFloat, tc2.RealVariableFloat);
|
||||
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable);
|
||||
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
|
||||
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
|
||||
|
||||
Assert.AreEqual(TestClassWithPrivateSetters.PRIVATE_SETTER_VALUE, tc2.PrivateSetterProperty);
|
||||
@@ -620,8 +614,8 @@ namespace S7.Net.UnitTest
|
||||
tc.BitVariable10 = true;
|
||||
tc.DIntVariable = -100000;
|
||||
tc.IntVariable = -15000;
|
||||
tc.RealVariableDouble = -154.789;
|
||||
tc.RealVariableFloat = -154.789f;
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
@@ -635,8 +629,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc2.BitVariable10, tc2Generic.BitVariable10);
|
||||
Assert.AreEqual(tc2.DIntVariable, tc2Generic.DIntVariable);
|
||||
Assert.AreEqual(tc2.IntVariable, tc2Generic.IntVariable);
|
||||
Assert.AreEqual(Math.Round(tc2.RealVariableDouble, 3), Math.Round(tc2Generic.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc2.RealVariableFloat, tc2Generic.RealVariableFloat);
|
||||
Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable);
|
||||
Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -663,8 +657,8 @@ namespace S7.Net.UnitTest
|
||||
tc.BitVariable10 = true;
|
||||
tc.DIntVariable = -100000;
|
||||
tc.IntVariable = -15000;
|
||||
tc.RealVariableDouble = -154.789;
|
||||
tc.RealVariableFloat = -154.789f;
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
@@ -677,8 +671,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc2Generic.BitVariable10, tc2GenericWithClassFactory.BitVariable10);
|
||||
Assert.AreEqual(tc2Generic.DIntVariable, tc2GenericWithClassFactory.DIntVariable);
|
||||
Assert.AreEqual(tc2Generic.IntVariable, tc2GenericWithClassFactory.IntVariable);
|
||||
Assert.AreEqual(Math.Round(tc2Generic.RealVariableDouble, 3), Math.Round(tc2GenericWithClassFactory.RealVariableDouble, 3));
|
||||
Assert.AreEqual(tc2Generic.RealVariableFloat, tc2GenericWithClassFactory.RealVariableFloat);
|
||||
Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable);
|
||||
Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -786,8 +780,8 @@ namespace S7.Net.UnitTest
|
||||
ts.BitVariable10 = true;
|
||||
ts.DIntVariable = -100000;
|
||||
ts.IntVariable = -15000;
|
||||
ts.RealVariableDouble = -154.789;
|
||||
ts.RealVariableFloat = -154.789f;
|
||||
ts.LRealVariable = -154.789;
|
||||
ts.RealVariable = -154.789f;
|
||||
ts.DWordVariable = 850;
|
||||
|
||||
plc.WriteStruct(ts, DB2);
|
||||
@@ -800,8 +794,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(ts2.BitVariable10, ts2Generic.BitVariable10);
|
||||
Assert.AreEqual(ts2.DIntVariable, ts2Generic.DIntVariable);
|
||||
Assert.AreEqual(ts2.IntVariable, ts2Generic.IntVariable);
|
||||
Assert.AreEqual(Math.Round(ts2.RealVariableDouble, 3), Math.Round(ts2Generic.RealVariableDouble, 3));
|
||||
Assert.AreEqual(ts2.RealVariableFloat, ts2Generic.RealVariableFloat);
|
||||
Assert.AreEqual(ts2.LRealVariable, ts2Generic.LRealVariable);
|
||||
Assert.AreEqual(ts2.RealVariable, ts2Generic.RealVariable);
|
||||
Assert.AreEqual(ts2.DWordVariable, ts2Generic.DWordVariable);
|
||||
}
|
||||
|
||||
@@ -831,8 +825,8 @@ namespace S7.Net.UnitTest
|
||||
tc.BitVariable10 = true;
|
||||
tc.DIntVariable = -100000;
|
||||
tc.IntVariable = -15000;
|
||||
tc.RealVariableDouble = -154.789;
|
||||
tc.RealVariableFloat = -154.789f;
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
plc.WriteClass(tc, DB2);
|
||||
|
||||
@@ -938,9 +932,9 @@ namespace S7.Net.UnitTest
|
||||
{
|
||||
plc.Close();
|
||||
S7TestServer.Stop();
|
||||
S7TestServer.Start();
|
||||
S7TestServer.Start(TestServerPort);
|
||||
|
||||
var reachablePlc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2);
|
||||
var reachablePlc = CreatePlc();
|
||||
Assert.IsTrue(reachablePlc.IsAvailable);
|
||||
}
|
||||
|
||||
@@ -948,11 +942,10 @@ namespace S7.Net.UnitTest
|
||||
public void T26_ReadWriteDouble()
|
||||
{
|
||||
double test_value = 55.66;
|
||||
plc.Write("DB1.DBD0", test_value);
|
||||
var helper = plc.Read("DB1.DBD0");
|
||||
double test_value2 = Conversion.ConvertToDouble((uint)helper);
|
||||
plc.Write(DataType.DataBlock, 1, 0, test_value);
|
||||
var result = (double)plc.Read(DataType.DataBlock, 1, 0, VarType.LReal, 1);
|
||||
|
||||
Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals
|
||||
Assert.AreEqual(test_value, result, "Compare Write/Read");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -1034,18 +1027,6 @@ namespace S7.Net.UnitTest
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
private static void ShutDownServiceS7oiehsx64()
|
||||
{
|
||||
ServiceController[] services = ServiceController.GetServices();
|
||||
var service = services.FirstOrDefault(s => s.ServiceName == "s7oiehsx64");
|
||||
if (service != null)
|
||||
{
|
||||
if (service.Status == ServiceControllerStatus.Running)
|
||||
{
|
||||
service.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
|
||||
@@ -36,11 +36,8 @@ namespace Snap7
|
||||
|
||||
public class S7Consts
|
||||
{
|
||||
#if __MonoCS__ // Assuming that we are using Unix release of Mono (otherwise modify it)
|
||||
public const string Snap7LibName = "libsnap7.so";
|
||||
#else
|
||||
public const string Snap7LibName = "snap7.dll";
|
||||
#endif
|
||||
public const string Snap7LibName = "snap7";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// PARAMS LIST
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -65,13 +65,14 @@ namespace S7.Net.UnitTest
|
||||
[TestClass]
|
||||
public class StreamTests
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
public async Task TPKT_ReadRestrictedStreamAsync()
|
||||
{
|
||||
var fullMessage = ProtocolUnitTest.StringToByteArray("0300002902f0803203000000010002001400000401ff0400807710000100000103000000033f8ccccd");
|
||||
var m = new TestStream1BytePerRead(fullMessage);
|
||||
var t = await TPKT.ReadAsync(m);
|
||||
var t = await TPKT.ReadAsync(m, TestContext.CancellationTokenSource.Token);
|
||||
Assert.AreEqual(fullMessage.Length, t.Length);
|
||||
Assert.AreEqual(fullMessage.Last(), t.Data.Last());
|
||||
}
|
||||
|
||||
135
S7.Net.UnitTest/TypeTests/S7StringTests.cs
Normal file
135
S7.Net.UnitTest/TypeTests/S7StringTests.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using S7.Net.Types;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
|
||||
namespace S7.Net.UnitTest.TypeTests
|
||||
{
|
||||
[TestClass]
|
||||
public class S7StringTests
|
||||
{
|
||||
[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 ReadMalformedStringTooShort()
|
||||
{
|
||||
Assert.ThrowsException<PlcException>(() => AssertFromByteArrayEquals("", 1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadMalformedStringSizeLargerThanCapacity()
|
||||
{
|
||||
Assert.ThrowsException<PlcException>(() => S7String.FromByteArray(new byte[] { 3, 5, 0, 1, 2 }));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadMalformedStringCapacityTooLarge()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentException>(() => AssertToByteArrayAndBackEquals("", 300, 0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadA()
|
||||
{
|
||||
AssertFromByteArrayEquals("A", 1, 1, (byte) 'A');
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAbc()
|
||||
{
|
||||
AssertFromByteArrayEquals("Abc", 3, 3, (byte) 'A', (byte) 'b', (byte) 'c');
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteNullWithReservedLengthZero()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => AssertToByteArrayAndBackEquals(null, 0, 0, 0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteEmptyStringWithReservedLengthZero()
|
||||
{
|
||||
AssertToByteArrayAndBackEquals("", 0, 0, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteAWithReservedLengthZero()
|
||||
{
|
||||
AssertToByteArrayAndBackEquals("", 0, 0, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteNullWithReservedLengthOne()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => AssertToByteArrayAndBackEquals(null, 1, 1, 0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteEmptyStringWithReservedLengthOne()
|
||||
{
|
||||
AssertToByteArrayAndBackEquals("", 1, 1, 0, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteAWithReservedLengthOne()
|
||||
{
|
||||
AssertToByteArrayAndBackEquals("A", 1, 1, 1, (byte) 'A');
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteAWithReservedLengthTwo()
|
||||
{
|
||||
AssertToByteArrayAndBackEquals("A", 2, 2, 1, (byte) 'A', 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteAbcWithStringLargetThanReservedLength()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentException>(() => S7String.ToByteArray("Abc", 2));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteAbcWithReservedLengthThree()
|
||||
{
|
||||
AssertToByteArrayAndBackEquals("Abc", 3, 3, 3, (byte) 'A', (byte) 'b', (byte) 'c');
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteAbcWithReservedLengthFour()
|
||||
{
|
||||
AssertToByteArrayAndBackEquals("Abc", 4, 4, 3, (byte) 'A', (byte) 'b', (byte) 'c', 0);
|
||||
}
|
||||
|
||||
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
|
||||
{
|
||||
var convertedString = S7String.FromByteArray(bytes);
|
||||
Assert.AreEqual(expected, convertedString);
|
||||
}
|
||||
|
||||
|
||||
private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected)
|
||||
{
|
||||
var convertedData = S7String.ToByteArray(value, reservedLength);
|
||||
CollectionAssert.AreEqual(expected, convertedData);
|
||||
var convertedBack = S7String.FromByteArray(convertedData);
|
||||
Assert.AreEqual(value, convertedBack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
S7.Net.UnitTest/runtimes/win-x64/native/snap7.dll
Normal file
BIN
S7.Net.UnitTest/runtimes/win-x64/native/snap7.dll
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace S7.Net
|
||||
@@ -10,6 +11,11 @@ namespace S7.Net
|
||||
/// </summary>
|
||||
internal class COTP
|
||||
{
|
||||
public enum PduType : byte
|
||||
{
|
||||
Data = 0xf0,
|
||||
ConnectionConfirmed = 0xd0
|
||||
}
|
||||
/// <summary>
|
||||
/// Describes a COTP TPDU (Transport protocol data unit)
|
||||
/// </summary>
|
||||
@@ -17,7 +23,7 @@ namespace S7.Net
|
||||
{
|
||||
public TPKT TPkt { get; }
|
||||
public byte HeaderLength;
|
||||
public byte PDUType;
|
||||
public PduType PDUType;
|
||||
public int TPDUNumber;
|
||||
public byte[] Data;
|
||||
public bool LastDataUnit;
|
||||
@@ -29,8 +35,8 @@ namespace S7.Net
|
||||
HeaderLength = tPKT.Data[0]; // Header length excluding this length byte
|
||||
if (HeaderLength >= 2)
|
||||
{
|
||||
PDUType = tPKT.Data[1];
|
||||
if (PDUType == 0xf0) //DT Data
|
||||
PDUType = (PduType)tPKT.Data[1];
|
||||
if (PDUType == PduType.Data) //DT Data
|
||||
{
|
||||
var flags = tPKT.Data[2];
|
||||
TPDUNumber = flags & 0x7F;
|
||||
@@ -66,9 +72,9 @@ namespace S7.Net
|
||||
/// </summary>
|
||||
/// <param name="stream">The socket to read from</param>
|
||||
/// <returns>COTP DPDU instance</returns>
|
||||
public static async Task<TPDU> ReadAsync(Stream stream)
|
||||
public static async Task<TPDU> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var tpkt = await TPKT.ReadAsync(stream);
|
||||
var tpkt = await TPKT.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
if (tpkt.Length == 0)
|
||||
{
|
||||
throw new TPDUInvalidException("No protocol data received");
|
||||
@@ -130,9 +136,9 @@ namespace S7.Net
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from</param>
|
||||
/// <returns>Data in TSDU</returns>
|
||||
public static async Task<byte[]> ReadAsync(Stream stream)
|
||||
public static async Task<byte[]> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var segment = await TPDU.ReadAsync(stream);
|
||||
var segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (segment.LastDataUnit)
|
||||
{
|
||||
@@ -145,7 +151,7 @@ namespace S7.Net
|
||||
|
||||
while (!segment.LastDataUnit)
|
||||
{
|
||||
segment = await TPDU.ReadAsync(stream);
|
||||
segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
var previousLength = buffer.Length;
|
||||
Array.Resize(ref buffer, buffer.Length + segment.Data.Length);
|
||||
Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length);
|
||||
|
||||
@@ -199,19 +199,6 @@ namespace S7.Net
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from double to DWord (DBD)
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Double support is obsolete. Use ConvertToUInt(float) instead.")]
|
||||
public static UInt32 ConvertToUInt(this double input)
|
||||
{
|
||||
uint output;
|
||||
output = S7.Net.Types.DWord.FromByteArray(S7.Net.Types.Double.ToByteArray(input));
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from float to DWord (DBD)
|
||||
/// </summary>
|
||||
@@ -220,20 +207,7 @@ namespace S7.Net
|
||||
public static UInt32 ConvertToUInt(this float input)
|
||||
{
|
||||
uint output;
|
||||
output = S7.Net.Types.DWord.FromByteArray(S7.Net.Types.Single.ToByteArray(input));
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from DWord (DBD) to double
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Double support is obsolete. Use ConvertToFloat(uint) instead.")]
|
||||
public static double ConvertToDouble(this uint input)
|
||||
{
|
||||
double output;
|
||||
output = S7.Net.Types.Double.FromByteArray(S7.Net.Types.DWord.ToByteArray(input));
|
||||
output = S7.Net.Types.DWord.FromByteArray(S7.Net.Types.Real.ToByteArray(input));
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -245,7 +219,7 @@ namespace S7.Net
|
||||
public static float ConvertToFloat(this uint input)
|
||||
{
|
||||
float output;
|
||||
output = S7.Net.Types.Single.FromByteArray(S7.Net.Types.DWord.ToByteArray(input));
|
||||
output = S7.Net.Types.Real.FromByteArray(S7.Net.Types.DWord.ToByteArray(input));
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,14 +164,19 @@
|
||||
Real,
|
||||
|
||||
/// <summary>
|
||||
/// String variable type (variable)
|
||||
/// LReal variable type (64 bits, 8 bytes)
|
||||
/// </summary>
|
||||
LReal,
|
||||
|
||||
/// <summary>
|
||||
/// Char Array / C-String variable type (variable)
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// String variable type (variable)
|
||||
/// S7 String variable type (variable)
|
||||
/// </summary>
|
||||
StringEx,
|
||||
S7String,
|
||||
|
||||
/// <summary>
|
||||
/// Timer variable type
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using S7.Net.Protocol;
|
||||
using S7.Net.Types;
|
||||
|
||||
|
||||
@@ -18,8 +19,8 @@ namespace S7.Net
|
||||
private TcpClient? tcpClient;
|
||||
private NetworkStream? _stream;
|
||||
|
||||
private int readTimeout = System.Threading.Timeout.Infinite;
|
||||
private int writeTimeout = System.Threading.Timeout.Infinite;
|
||||
private int readTimeout = 0; // default no timeout
|
||||
private int writeTimeout = 0; // default no timeout
|
||||
|
||||
/// <summary>
|
||||
/// IP address of the PLC
|
||||
@@ -49,7 +50,7 @@ namespace S7.Net
|
||||
/// <summary>
|
||||
/// Max PDU size this cpu supports
|
||||
/// </summary>
|
||||
public Int16 MaxPDUSize { get; set; }
|
||||
public int MaxPDUSize { get; private 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>
|
||||
@@ -85,7 +86,7 @@ namespace S7.Net
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect();
|
||||
OpenAsync().GetAwaiter().GetResult();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -183,11 +184,13 @@ 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");
|
||||
// send request limit: 19 bytes of header data, 12 bytes of parameter data for each dataItem
|
||||
var requiredRequestSize = 19 + dataItems.Count * 12;
|
||||
if (requiredRequestSize > MaxPDUSize) throw new Exception($"Too many vars requested for read. Request size ({requiredRequestSize}) is larger than protocol limit ({MaxPDUSize}).");
|
||||
|
||||
// response limit: 14 bytes of header data, 4 bytes of result data for each dataItem and the actual data
|
||||
var requiredResponseSize = GetDataLength(dataItems) + dataItems.Count * 4 + 14;
|
||||
if (requiredResponseSize > MaxPDUSize) throw new Exception($"Too much data requested for read. Response size ({requiredResponseSize}) is larger than protocol limit ({MaxPDUSize}).");
|
||||
}
|
||||
|
||||
private void AssertPduSizeForWrite(ICollection<DataItem> dataItems)
|
||||
@@ -232,13 +235,34 @@ namespace S7.Net
|
||||
|
||||
if (s7Data.Length < 15) throw NotEnoughBytes();
|
||||
|
||||
if (s7Data[14] != 0xff)
|
||||
throw new PlcException(ErrorCode.ReadData,
|
||||
$"Invalid response from PLC: '{BitConverter.ToString(s7Data)}'.");
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7Data[14]);
|
||||
|
||||
if (s7Data.Length < expectedLength) throw NotEnoughBytes();
|
||||
}
|
||||
|
||||
internal static void ValidateResponseCode(ReadWriteErrorCode statusCode)
|
||||
{
|
||||
switch (statusCode)
|
||||
{
|
||||
case ReadWriteErrorCode.ObjectDoesNotExist:
|
||||
throw new Exception("Received error from PLC: Object does not exist.");
|
||||
case ReadWriteErrorCode.DataTypeInconsistent:
|
||||
throw new Exception("Received error from PLC: Data type inconsistent.");
|
||||
case ReadWriteErrorCode.DataTypeNotSupported:
|
||||
throw new Exception("Received error from PLC: Data type not supported.");
|
||||
case ReadWriteErrorCode.AccessingObjectNotAllowed:
|
||||
throw new Exception("Received error from PLC: Accessing object not allowed.");
|
||||
case ReadWriteErrorCode.AddressOutOfRange:
|
||||
throw new Exception("Received error from PLC: Address out of range.");
|
||||
case ReadWriteErrorCode.HardwareFault:
|
||||
throw new Exception("Received error from PLC: Hardware fault.");
|
||||
case ReadWriteErrorCode.Success:
|
||||
break;
|
||||
default:
|
||||
throw new Exception( $"Invalid response from PLC: statusCode={(byte)statusCode}.");
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using S7.Net.Helper;
|
||||
using S7.Net.Protocol.S7;
|
||||
using S7.Net.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -14,7 +15,7 @@ namespace S7.Net
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <returns></returns>
|
||||
private void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1)
|
||||
private static void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1)
|
||||
{
|
||||
//header size = 19 bytes
|
||||
stream.WriteByteArray(new byte[] { 0x03, 0x00 });
|
||||
@@ -37,7 +38,7 @@ namespace S7.Net
|
||||
/// <param name="startByteAdr">Start address of the byte</param>
|
||||
/// <param name="count">Number of bytes to be read</param>
|
||||
/// <returns></returns>
|
||||
private void BuildReadDataRequestPackage(System.IO.MemoryStream stream, DataType dataType, int db, int startByteAdr, int count = 1)
|
||||
private static void BuildReadDataRequestPackage(System.IO.MemoryStream stream, DataType dataType, int db, int startByteAdr, int count = 1)
|
||||
{
|
||||
//single data req = 12
|
||||
stream.WriteByteArray(new byte[] { 0x12, 0x0a, 0x10 });
|
||||
@@ -111,14 +112,19 @@ namespace S7.Net
|
||||
return DInt.ToArray(bytes);
|
||||
case VarType.Real:
|
||||
if (varCount == 1)
|
||||
return Types.Single.FromByteArray(bytes);
|
||||
return Types.Real.FromByteArray(bytes);
|
||||
else
|
||||
return Types.Single.ToArray(bytes);
|
||||
return Types.Real.ToArray(bytes);
|
||||
case VarType.LReal:
|
||||
if (varCount == 1)
|
||||
return Types.LReal.FromByteArray(bytes);
|
||||
else
|
||||
return Types.LReal.ToArray(bytes);
|
||||
|
||||
case VarType.String:
|
||||
return Types.String.FromByteArray(bytes);
|
||||
case VarType.StringEx:
|
||||
return StringEx.FromByteArray(bytes);
|
||||
case VarType.S7String:
|
||||
return S7String.FromByteArray(bytes);
|
||||
|
||||
case VarType.Timer:
|
||||
if (varCount == 1)
|
||||
@@ -171,7 +177,7 @@ namespace S7.Net
|
||||
/// <param name="varType"></param>
|
||||
/// <param name="varCount"></param>
|
||||
/// <returns>Byte lenght of variable</returns>
|
||||
private int VarTypeToByteLength(VarType varType, int varCount = 1)
|
||||
internal static int VarTypeToByteLength(VarType varType, int varCount = 1)
|
||||
{
|
||||
switch (varType)
|
||||
{
|
||||
@@ -181,7 +187,7 @@ namespace S7.Net
|
||||
return (varCount < 1) ? 1 : varCount;
|
||||
case VarType.String:
|
||||
return varCount;
|
||||
case VarType.StringEx:
|
||||
case VarType.S7String:
|
||||
return varCount + 2;
|
||||
case VarType.Word:
|
||||
case VarType.Timer:
|
||||
@@ -192,6 +198,7 @@ namespace S7.Net
|
||||
case VarType.DInt:
|
||||
case VarType.Real:
|
||||
return varCount * 4;
|
||||
case VarType.LReal:
|
||||
case VarType.DateTime:
|
||||
return varCount * 8;
|
||||
case VarType.DateTimeLong:
|
||||
@@ -236,5 +243,20 @@ namespace S7.Net
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] BuildReadRequestPackage(IList<DataItemAddress> dataItems)
|
||||
{
|
||||
int packageSize = 19 + (dataItems.Count * 12);
|
||||
var package = new System.IO.MemoryStream(packageSize);
|
||||
|
||||
BuildHeaderPackage(package, dataItems.Count);
|
||||
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAddress, dataItem.ByteLength);
|
||||
}
|
||||
|
||||
return package.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using S7.Net.Protocol;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using S7.Net.Protocol.S7;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
@@ -17,28 +19,57 @@ namespace S7.Net
|
||||
/// <summary>
|
||||
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that the cancellation will not affect opening the socket in any way and only affects data transfers for configuring the connection after the socket connection is successfully established.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous open operation.</returns>
|
||||
public async Task OpenAsync()
|
||||
public async Task OpenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ConnectAsync();
|
||||
var stream = GetStreamIfAvailable();
|
||||
|
||||
await stream.WriteAsync(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
|
||||
var response = await COTP.TPDU.ReadAsync(stream);
|
||||
if (response == null)
|
||||
var stream = await ConnectAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
throw new Exception("Error reading Connection Confirm. Malformed TPDU packet");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
|
||||
_stream = stream;
|
||||
}
|
||||
if (response.PDUType != 0xd0) //Connect Confirm
|
||||
catch(Exception)
|
||||
{
|
||||
throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d);
|
||||
stream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
await stream.WriteAsync(GetS7ConnectionSetup(), 0, 25);
|
||||
private async Task<NetworkStream> ConnectAsync()
|
||||
{
|
||||
tcpClient = new TcpClient();
|
||||
ConfigureConnection();
|
||||
await tcpClient.ConnectAsync(IP, Port).ConfigureAwait(false);
|
||||
return tcpClient.GetStream();
|
||||
}
|
||||
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream);
|
||||
if (s7data == null)
|
||||
throw new WrongNumberOfBytesException("No data received in response to Communication Setup");
|
||||
private async Task EstablishConnection(NetworkStream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
await RequestConnection(stream, cancellationToken).ConfigureAwait(false);
|
||||
await SetupConnection(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task RequestConnection(NetworkStream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot);
|
||||
await stream.WriteAsync(requestData, 0, requestData.Length).ConfigureAwait(false);
|
||||
var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
if (response.PDUType != COTP.PduType.ConnectionConfirmed)
|
||||
{
|
||||
throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetupConnection(NetworkStream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var setupData = GetS7ConnectionSetup();
|
||||
await stream.WriteAsync(setupData, 0, setupData.Length).ConfigureAwait(false);
|
||||
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
if (s7data.Length < 2)
|
||||
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
|
||||
|
||||
@@ -49,15 +80,8 @@ namespace S7.Net
|
||||
if (s7data.Length < 20)
|
||||
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
|
||||
|
||||
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
|
||||
}
|
||||
|
||||
private async Task ConnectAsync()
|
||||
{
|
||||
tcpClient = new TcpClient();
|
||||
ConfigureConnection();
|
||||
await tcpClient.ConnectAsync(IP, Port);
|
||||
_stream = tcpClient.GetStream();
|
||||
// TODO: check if this should not rather be UInt16.
|
||||
MaxPDUSize = s7data[18] * 256 + s7data[19];
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +93,10 @@ 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 read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="count">Byte count, if you want to read 120 bytes, set this to 120.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns the bytes in an array</returns>
|
||||
public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count)
|
||||
public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var resultBytes = new byte[count];
|
||||
int index = 0;
|
||||
@@ -78,7 +104,7 @@ namespace S7.Net
|
||||
{
|
||||
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
|
||||
var maxToRead = Math.Min(count, MaxPDUSize - 18);
|
||||
await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead);
|
||||
await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead, cancellationToken);
|
||||
count -= maxToRead;
|
||||
index += maxToRead;
|
||||
}
|
||||
@@ -96,10 +122,12 @@ namespace S7.Net
|
||||
/// <param name="varType">Type of the variable/s that you are reading</param>
|
||||
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
|
||||
/// <param name="varCount"></param>
|
||||
public async Task<object?> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
public async Task<object?> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int cntBytes = VarTypeToByteLength(varType, varCount);
|
||||
byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes);
|
||||
byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes, cancellationToken);
|
||||
return ParseBytes(varType, bytes, varCount, bitAdr);
|
||||
}
|
||||
|
||||
@@ -108,11 +136,13 @@ namespace S7.Net
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns an object that contains the value. This object must be cast accordingly.</returns>
|
||||
public async Task<object?> ReadAsync(string variable)
|
||||
public async Task<object?> ReadAsync(string variable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var adr = new PLCAddress(variable);
|
||||
return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
|
||||
return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -121,12 +151,14 @@ namespace S7.Net
|
||||
/// <param name="structType">Type of the struct to be readed (es.: TypeOf(MyStruct)).</param>
|
||||
/// <param name="db">Address of the DB.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns a struct that must be cast.</returns>
|
||||
public async Task<object?> ReadStructAsync(Type structType, int db, int startByteAdr = 0)
|
||||
public async Task<object?> ReadStructAsync(Type structType, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int numBytes = Types.Struct.GetStructSize(structType);
|
||||
// now read the package
|
||||
var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes);
|
||||
var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken);
|
||||
|
||||
// and decode it
|
||||
return Types.Struct.FromBytes(structType, resultBytes);
|
||||
@@ -138,10 +170,12 @@ namespace S7.Net
|
||||
/// <typeparam name="T">The struct type</typeparam>
|
||||
/// <param name="db">Address of the DB.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns a nulable struct. If nothing was read null will be returned.</returns>
|
||||
public async Task<T?> ReadStructAsync<T>(int db, int startByteAdr = 0) where T : struct
|
||||
public async Task<T?> ReadStructAsync<T>(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : struct
|
||||
{
|
||||
return await ReadStructAsync(typeof(T), db, startByteAdr) as T?;
|
||||
return await ReadStructAsync(typeof(T), db, startByteAdr, cancellationToken) as T?;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,8 +185,10 @@ namespace S7.Net
|
||||
/// <param name="sourceClass">Instance of the class that will store the values</param>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>The number of read bytes</returns>
|
||||
public async Task<Tuple<int, object>> ReadClassAsync(object sourceClass, int db, int startByteAdr = 0)
|
||||
public async Task<Tuple<int, object>> ReadClassAsync(object sourceClass, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int numBytes = (int)Class.GetClassSize(sourceClass);
|
||||
if (numBytes <= 0)
|
||||
@@ -161,7 +197,7 @@ namespace S7.Net
|
||||
}
|
||||
|
||||
// now read the package
|
||||
var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes);
|
||||
var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken);
|
||||
// and decode it
|
||||
Class.FromBytes(sourceClass, resultBytes);
|
||||
|
||||
@@ -176,10 +212,12 @@ namespace S7.Net
|
||||
/// <typeparam name="T">The class that will be instantiated. Requires a default constructor</typeparam>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
|
||||
public async Task<T?> ReadClassAsync<T>(int db, int startByteAdr = 0) where T : class
|
||||
public async Task<T?> ReadClassAsync<T>(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr);
|
||||
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,11 +228,13 @@ namespace S7.Net
|
||||
/// <param name="classFactory">Function to instantiate the class</param>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
|
||||
public async Task<T?> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
|
||||
public async Task<T?> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
var instance = classFactory();
|
||||
var res = await ReadClassAsync(instance, db, startByteAdr);
|
||||
var res = await ReadClassAsync(instance, db, startByteAdr, cancellationToken);
|
||||
int readBytes = res.Item1;
|
||||
if (readBytes <= 0)
|
||||
{
|
||||
@@ -209,10 +249,12 @@ namespace S7.Net
|
||||
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
|
||||
/// Values are stored in the property "Value" of the dataItem and are already converted.
|
||||
/// If you don't want the conversion, just create a dataItem of bytes.
|
||||
/// DataItems must not be more than 20 (protocol restriction) and bytes must not be more than 200 + 22 of header (protocol restriction).
|
||||
/// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction).
|
||||
/// </summary>
|
||||
/// <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)
|
||||
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
public async Task<List<DataItem>> ReadMultipleVarsAsync(List<DataItem> dataItems, CancellationToken cancellationToken = default)
|
||||
{
|
||||
//Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
//replies with bigger PDU size in connection setup.
|
||||
@@ -222,22 +264,11 @@ namespace S7.Net
|
||||
|
||||
try
|
||||
{
|
||||
// first create the header
|
||||
int packageSize = 19 + (dataItems.Count * 12);
|
||||
var package = new System.IO.MemoryStream(packageSize);
|
||||
BuildHeaderPackage(package, dataItems.Count);
|
||||
// package.Add(0x02); // datenart
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count));
|
||||
}
|
||||
|
||||
var dataToSend = package.ToArray();
|
||||
var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList());
|
||||
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
|
||||
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream); //TODO use Async
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
|
||||
ParseDataIntoDataItems(s7data, dataItems);
|
||||
}
|
||||
@@ -245,6 +276,10 @@ namespace S7.Net
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, socketException);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, exc);
|
||||
@@ -252,6 +287,7 @@ namespace S7.Net
|
||||
return dataItems;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the write was not successful, check LastErrorCode or LastErrorString.
|
||||
@@ -260,15 +296,17 @@ 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>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value)
|
||||
public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int localIndex = 0;
|
||||
int count = value.Length;
|
||||
while (count > 0)
|
||||
{
|
||||
var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35);
|
||||
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite);
|
||||
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite, cancellationToken);
|
||||
count -= maxToWrite;
|
||||
localIndex += maxToWrite;
|
||||
}
|
||||
@@ -282,13 +320,15 @@ 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>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
|
||||
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
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));
|
||||
|
||||
await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value);
|
||||
await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -299,13 +339,15 @@ 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>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
|
||||
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (value < 0 || value > 1)
|
||||
throw new ArgumentException("Value must be 0 or 1", nameof(value));
|
||||
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1);
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -318,15 +360,17 @@ 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>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <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)
|
||||
public async Task WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (bitAdr != -1)
|
||||
{
|
||||
//Must be writing a bit value as bitAdr is specified
|
||||
if (value is bool)
|
||||
if (value is bool boolean)
|
||||
{
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, (bool) value);
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, boolean, cancellationToken);
|
||||
}
|
||||
else if (value is int intValue)
|
||||
{
|
||||
@@ -336,11 +380,11 @@ namespace S7.Net
|
||||
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
|
||||
bitAdr), nameof(bitAdr));
|
||||
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1);
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1, cancellationToken);
|
||||
}
|
||||
else throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
|
||||
}
|
||||
else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value));
|
||||
else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -349,11 +393,13 @@ 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>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteAsync(string variable, object value)
|
||||
public async Task WriteAsync(string variable, object value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var adr = new PLCAddress(variable);
|
||||
await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber);
|
||||
await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -362,11 +408,13 @@ 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>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0)
|
||||
public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bytes = Struct.ToBytes(structValue).ToList();
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -375,29 +423,24 @@ 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>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0)
|
||||
public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
byte[] bytes = new byte[(int)Class.GetClassSize(classValue)];
|
||||
Types.Class.ToBytes(classValue, bytes);
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes);
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count)
|
||||
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var stream = GetStreamIfAvailable();
|
||||
|
||||
// first create the header
|
||||
int packageSize = 31;
|
||||
var package = new System.IO.MemoryStream(packageSize);
|
||||
BuildHeaderPackage(package);
|
||||
// package.Add(0x02); // datenart
|
||||
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count);
|
||||
var dataToSend = BuildReadRequestPackage(new [] { new DataItemAddress(dataType, db, startByteAdr, count)});
|
||||
await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken);
|
||||
|
||||
var dataToSend = package.ToArray();
|
||||
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
|
||||
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream);
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
|
||||
AssertReadResponse(s7data, count);
|
||||
|
||||
Array.Copy(s7data, 18, buffer, offset, count);
|
||||
@@ -419,7 +462,7 @@ namespace S7.Net
|
||||
var length = S7WriteMultiple.CreateRequest(message, dataItems);
|
||||
await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false);
|
||||
|
||||
var response = await COTP.TSDU.ReadAsync(stream).ConfigureAwait(false);
|
||||
var response = await COTP.TSDU.ReadAsync(stream, CancellationToken.None).ConfigureAwait(false);
|
||||
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
|
||||
}
|
||||
|
||||
@@ -431,7 +474,7 @@ 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>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
|
||||
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
try
|
||||
@@ -439,13 +482,14 @@ namespace S7.Net
|
||||
var stream = GetStreamIfAvailable();
|
||||
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
|
||||
|
||||
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
|
||||
await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken);
|
||||
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
}
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
@@ -453,7 +497,7 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
private async Task 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, CancellationToken cancellationToken)
|
||||
{
|
||||
var stream = GetStreamIfAvailable();
|
||||
|
||||
@@ -463,11 +507,12 @@ namespace S7.Net
|
||||
|
||||
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
|
||||
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
}
|
||||
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
|
||||
@@ -17,34 +17,9 @@ namespace S7.Net
|
||||
/// </summary>
|
||||
public void Open()
|
||||
{
|
||||
Connect();
|
||||
|
||||
try
|
||||
{
|
||||
var stream = GetStreamIfAvailable();
|
||||
stream.Write(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
|
||||
var response = COTP.TPDU.Read(stream);
|
||||
if (response.PDUType != 0xd0) //Connect Confirm
|
||||
{
|
||||
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)
|
||||
throw new WrongNumberOfBytesException("No data received in response to Communication Setup");
|
||||
if (s7data.Length < 2)
|
||||
throw new WrongNumberOfBytesException("Not enough 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);
|
||||
|
||||
if (s7data.Length < 20)
|
||||
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
|
||||
|
||||
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
|
||||
OpenAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
@@ -53,28 +28,6 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
tcpClient = new TcpClient();
|
||||
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
|
||||
throw new PlcException(
|
||||
sex.SocketErrorCode == SocketError.TimedOut
|
||||
? ErrorCode.IPAddressNotAvailable
|
||||
: ErrorCode.ConnectionError, sex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ConnectionError, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
@@ -289,9 +242,9 @@ namespace S7.Net
|
||||
if (bitAdr != -1)
|
||||
{
|
||||
//Must be writing a bit value as bitAdr is specified
|
||||
if (value is bool)
|
||||
if (value is bool boolean)
|
||||
{
|
||||
WriteBit(dataType, db, startByteAdr, bitAdr, (bool) value);
|
||||
WriteBit(dataType, db, startByteAdr, bitAdr, boolean);
|
||||
}
|
||||
else if (value is int intValue)
|
||||
{
|
||||
@@ -398,10 +351,7 @@ namespace S7.Net
|
||||
stream.Write(dataToSend, 0, dataToSend.Length);
|
||||
|
||||
var s7data = COTP.TSDU.Read(stream);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
}
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
@@ -442,7 +392,6 @@ namespace S7.Net
|
||||
|
||||
private byte[] BuildWriteBitPackage(DataType dataType, int db, int startByteAdr, bool bitValue, int bitAdr)
|
||||
{
|
||||
var stream = GetStreamIfAvailable();
|
||||
var value = new[] { bitValue ? (byte)1 : (byte)0 };
|
||||
int varCount = 1;
|
||||
// first create the header
|
||||
@@ -484,10 +433,7 @@ namespace S7.Net
|
||||
stream.Write(dataToSend, 0, dataToSend.Length);
|
||||
|
||||
var s7data = COTP.TSDU.Read(stream);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
}
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
@@ -500,13 +446,11 @@ namespace S7.Net
|
||||
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
|
||||
/// Values are stored in the property "Value" of the dataItem and are already converted.
|
||||
/// If you don't want the conversion, just create a dataItem of bytes.
|
||||
/// DataItems must not be more than 20 (protocol restriction) and bytes must not be more than 200 + 22 of header (protocol restriction).
|
||||
/// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction).
|
||||
/// </summary>
|
||||
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read. Maximum 20 dataitems are accepted.</param>
|
||||
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read.</param>
|
||||
public void ReadMultipleVars(List<DataItem> dataItems)
|
||||
{
|
||||
//Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
//replies with bigger PDU size in connection setup.
|
||||
AssertPduSizeForRead(dataItems);
|
||||
|
||||
var stream = GetStreamIfAvailable();
|
||||
@@ -527,8 +471,8 @@ namespace S7.Net
|
||||
stream.Write(dataToSend, 0, dataToSend.Length);
|
||||
|
||||
var s7data = COTP.TSDU.Read(stream); //TODO use Async
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
|
||||
ParseDataIntoDataItems(s7data, dataItems);
|
||||
}
|
||||
|
||||
15
S7.Net/Protocol/ReadWriteErrorCode.cs
Normal file
15
S7.Net/Protocol/ReadWriteErrorCode.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace S7.Net.Protocol
|
||||
{
|
||||
internal enum ReadWriteErrorCode : byte
|
||||
{
|
||||
Reserved = 0x00,
|
||||
HardwareFault = 0x01,
|
||||
AccessingObjectNotAllowed = 0x03,
|
||||
AddressOutOfRange = 0x05,
|
||||
DataTypeNotSupported = 0x06,
|
||||
DataTypeInconsistent = 0x07,
|
||||
ObjectDoesNotExist = 0x0a,
|
||||
Success = 0xff
|
||||
}
|
||||
}
|
||||
37
S7.Net/Protocol/S7/DataItemAddress.cs
Normal file
37
S7.Net/Protocol/S7/DataItemAddress.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace S7.Net.Protocol.S7
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an area of memory in the PLC
|
||||
/// </summary>
|
||||
internal class DataItemAddress
|
||||
{
|
||||
public DataItemAddress(DataType dataType, int db, int startByteAddress, int byteLength)
|
||||
{
|
||||
DataType = dataType;
|
||||
DB = db;
|
||||
StartByteAddress = startByteAddress;
|
||||
ByteLength = byteLength;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Memory area to read
|
||||
/// </summary>
|
||||
public DataType DataType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Address of memory area to read (example: for DB1 this value is 1, for T45 this value is 45)
|
||||
/// </summary>
|
||||
public int DB { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Address of the first byte to read
|
||||
/// </summary>
|
||||
public int StartByteAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of data to read
|
||||
/// </summary>
|
||||
public int ByteLength { get; }
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ namespace S7.Net.Protocol
|
||||
(ushort) (2 + paramSize));
|
||||
|
||||
var paramOffset = Header.Template.Length;
|
||||
var dataOffset = paramOffset + paramSize;
|
||||
var data = new ByteArray();
|
||||
|
||||
var itemCount = 0;
|
||||
@@ -95,11 +94,16 @@ namespace S7.Net.Protocol
|
||||
|
||||
for (int i = 0; i < dataItems.Length; i++)
|
||||
{
|
||||
var result = itemResults[i];
|
||||
if (result == 0xff) continue;
|
||||
try
|
||||
{
|
||||
Plc.ValidateResponseCode((ReadWriteErrorCode)itemResults[i]);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
if (errors == null) errors = new List<Exception>();
|
||||
errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed: {e.Message}."));
|
||||
}
|
||||
|
||||
if (errors == null) errors = new List<Exception>();
|
||||
errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed with error code {result}."));
|
||||
}
|
||||
|
||||
if (errors != null)
|
||||
|
||||
@@ -13,9 +13,13 @@ namespace S7.Net.Protocol
|
||||
|
||||
public static byte[] SerializeDataItem(DataItem dataItem)
|
||||
{
|
||||
if (dataItem.Value == null)
|
||||
{
|
||||
throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}");
|
||||
}
|
||||
if (dataItem.Value is string s)
|
||||
return dataItem.VarType == VarType.StringEx
|
||||
? StringEx.ToByteArray(s, dataItem.Count)
|
||||
return dataItem.VarType == VarType.S7String
|
||||
? S7String.ToByteArray(s, dataItem.Count)
|
||||
: Types.String.ToByteArray(s, dataItem.Count);
|
||||
|
||||
return SerializeValue(dataItem.Value);
|
||||
@@ -37,10 +41,10 @@ namespace S7.Net.Protocol
|
||||
return Types.DInt.ToByteArray((Int32)value);
|
||||
case "UInt32":
|
||||
return Types.DWord.ToByteArray((UInt32)value);
|
||||
case "Double":
|
||||
return Types.Double.ToByteArray((double)value);
|
||||
case "Single":
|
||||
return Types.Single.ToByteArray((float)value);
|
||||
return Types.Real.ToByteArray((float)value);
|
||||
case "Double":
|
||||
return Types.LReal.ToByteArray((double)value);
|
||||
case "DateTime":
|
||||
return Types.DateTime.ToByteArray((System.DateTime) value);
|
||||
case "Byte[]":
|
||||
@@ -53,10 +57,10 @@ namespace S7.Net.Protocol
|
||||
return Types.DInt.ToByteArray((Int32[])value);
|
||||
case "UInt32[]":
|
||||
return Types.DWord.ToByteArray((UInt32[])value);
|
||||
case "Double[]":
|
||||
return Types.Double.ToByteArray((double[])value);
|
||||
case "Single[]":
|
||||
return Types.Single.ToByteArray((float[])value);
|
||||
return Types.Real.ToByteArray((float[])value);
|
||||
case "Double[]":
|
||||
return Types.LReal.ToByteArray((double[])value);
|
||||
case "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.
|
||||
@@ -75,9 +79,9 @@ namespace S7.Net.Protocol
|
||||
{
|
||||
var start = startByte * 8 + bitNumber;
|
||||
buffer[index + 2] = (byte)start;
|
||||
start = start >> 8;
|
||||
start >>= 8;
|
||||
buffer[index + 1] = (byte)start;
|
||||
start = start >> 8;
|
||||
start >>= 8;
|
||||
buffer[index] = (byte)start;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>Enable</Nullable>
|
||||
<DebugType>portable</DebugType>
|
||||
<EmbedAllSources>true</EmbedAllSources>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace S7.Net
|
||||
@@ -39,13 +40,13 @@ namespace S7.Net
|
||||
/// <param name="offset">the offset in the buffer to read into</param>
|
||||
/// <param name="count">the amount of bytes to read into the buffer</param>
|
||||
/// <returns>returns the amount of read bytes</returns>
|
||||
public static async Task<int> ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count)
|
||||
public static async Task<int> ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
int read = 0;
|
||||
int received;
|
||||
do
|
||||
{
|
||||
received = await stream.ReadAsync(buffer, offset + read, count - read);
|
||||
received = await stream.ReadAsync(buffer, offset + read, count - read, cancellationToken).ConfigureAwait(false);
|
||||
read += received;
|
||||
}
|
||||
while (read < count && received > 0);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace S7.Net
|
||||
@@ -57,10 +58,10 @@ namespace S7.Net
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from</param>
|
||||
/// <returns>Task TPKT Instace</returns>
|
||||
public static async Task<TPKT> ReadAsync(Stream stream)
|
||||
public static async Task<TPKT> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var buf = new byte[4];
|
||||
int len = await stream.ReadExactAsync(buf, 0, 4);
|
||||
int len = await stream.ReadExactAsync(buf, 0, 4, cancellationToken).ConfigureAwait(false);
|
||||
if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
|
||||
|
||||
var version = buf[0];
|
||||
@@ -68,7 +69,7 @@ namespace S7.Net
|
||||
var length = buf[2] * 256 + buf[3]; //BigEndian
|
||||
|
||||
var data = new byte[length - 4];
|
||||
len = await stream.ReadExactAsync(data, 0, data.Length);
|
||||
len = await stream.ReadExactAsync(data, 0, data.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (len < data.Length)
|
||||
throw new TPKTInvalidException("TPKT payload incomplete / invalid");
|
||||
|
||||
|
||||
@@ -51,12 +51,17 @@ namespace S7.Net.Types
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
numBytes += 8;
|
||||
break;
|
||||
default:
|
||||
var propertyClass = Activator.CreateInstance(type);
|
||||
numBytes = GetClassSize(propertyClass, numBytes, true);
|
||||
@@ -168,12 +173,12 @@ namespace S7.Net.Types
|
||||
bytes[(int)numBytes + 3]);
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
case "Single":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
// hier auswerten
|
||||
value = Double.FromByteArray(
|
||||
value = Real.FromByteArray(
|
||||
new byte[] {
|
||||
bytes[(int)numBytes],
|
||||
bytes[(int)numBytes + 1],
|
||||
@@ -181,18 +186,15 @@ namespace S7.Net.Types
|
||||
bytes[(int)numBytes + 3] });
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
var buffer = new byte[8];
|
||||
Array.Copy(bytes, (int)numBytes, buffer, 0, 8);
|
||||
// hier auswerten
|
||||
value = Single.FromByteArray(
|
||||
new byte[] {
|
||||
bytes[(int)numBytes],
|
||||
bytes[(int)numBytes + 1],
|
||||
bytes[(int)numBytes + 2],
|
||||
bytes[(int)numBytes + 3] });
|
||||
numBytes += 4;
|
||||
value = LReal.FromByteArray(buffer);
|
||||
numBytes += 8;
|
||||
break;
|
||||
default:
|
||||
var propClass = Activator.CreateInstance(propertyType);
|
||||
@@ -277,11 +279,11 @@ namespace S7.Net.Types
|
||||
case "UInt32":
|
||||
bytes2 = DWord.ToByteArray((UInt32)propertyValue);
|
||||
break;
|
||||
case "Double":
|
||||
bytes2 = Double.ToByteArray((double)propertyValue);
|
||||
break;
|
||||
case "Single":
|
||||
bytes2 = Single.ToByteArray((float)propertyValue);
|
||||
bytes2 = Real.ToByteArray((float)propertyValue);
|
||||
break;
|
||||
case "Double":
|
||||
bytes2 = LReal.ToByteArray((double)propertyValue);
|
||||
break;
|
||||
default:
|
||||
numBytes = ToBytes(propertyValue, bytes, numBytes);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using S7.Net.Protocol.S7;
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
@@ -94,5 +95,10 @@ namespace S7.Net.Types
|
||||
|
||||
return dataItem;
|
||||
}
|
||||
|
||||
internal static DataItemAddress GetDataItemAddress(DataItem dataItem)
|
||||
{
|
||||
return new DataItemAddress(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, Plc.VarTypeToByteLength(dataItem.VarType, dataItem.Count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,27 +5,13 @@ namespace S7.Net.Types
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Real from S7 plc to C# double.
|
||||
/// </summary>
|
||||
[Obsolete("Class Double is obsolete. Use Real instead for 32bit floating point, or LReal for 64bit floating point.")]
|
||||
public static class Double
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 Real (4 bytes) to double
|
||||
/// </summary>
|
||||
public static double FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 4)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes.");
|
||||
}
|
||||
|
||||
// sps uses bigending so we have to reverse if platform needs
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
// create deep copy of the array and reverse
|
||||
bytes = new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
public static double FromByteArray(byte[] bytes) => Real.FromByteArray(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a S7 DInt to double
|
||||
@@ -51,16 +37,7 @@ namespace S7.Net.Types
|
||||
/// <summary>
|
||||
/// Converts a double to S7 Real (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(double value)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes((float)(value));
|
||||
|
||||
// sps uses bigending so we have to check if platform is same
|
||||
if (!BitConverter.IsLittleEndian) return bytes;
|
||||
|
||||
// create deep copy of the array and reverse
|
||||
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
public static byte[] ToByteArray(double value) => Real.ToByteArray((float)value);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of double to an array of bytes
|
||||
|
||||
57
S7.Net/Types/LReal.cs
Normal file
57
S7.Net/Types/LReal.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Real from S7 plc to C# double.
|
||||
/// </summary>
|
||||
public static class LReal
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 LReal (8 bytes) to double
|
||||
/// </summary>
|
||||
public static double FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 8)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 8 bytes.");
|
||||
}
|
||||
var buffer = bytes;
|
||||
|
||||
// sps uses bigending so we have to reverse if platform needs
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(buffer);
|
||||
}
|
||||
|
||||
return BitConverter.ToDouble(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a double to S7 LReal (8 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(double value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
// sps uses bigending so we have to check if platform is same
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of double to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(double[] value) => TypeHelper.ToByteArray(value, ToByteArray);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 LReal to an array of double
|
||||
/// </summary>
|
||||
public static double[] ToArray(byte[] bytes) => TypeHelper.ToArray(bytes, FromByteArray);
|
||||
|
||||
}
|
||||
}
|
||||
75
S7.Net/Types/Real.cs
Normal file
75
S7.Net/Types/Real.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Real from S7 plc to C# double.
|
||||
/// </summary>
|
||||
public static class Real
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 Real (4 bytes) to float
|
||||
/// </summary>
|
||||
public static float FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 4)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes.");
|
||||
}
|
||||
|
||||
// sps uses bigending so we have to reverse if platform needs
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
// create deep copy of the array and reverse
|
||||
bytes = new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a float to S7 Real (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(float value)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
|
||||
// sps uses bigending so we have to check if platform is same
|
||||
if (!BitConverter.IsLittleEndian) return bytes;
|
||||
|
||||
// create deep copy of the array and reverse
|
||||
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of float to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(float[] value)
|
||||
{
|
||||
var buffer = new byte[4 * value.Length];
|
||||
var stream = new MemoryStream(buffer);
|
||||
foreach (var val in value)
|
||||
{
|
||||
stream.Write(ToByteArray(val), 0, 4);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 Real to an array of float
|
||||
/// </summary>
|
||||
public static float[] ToArray(byte[] bytes)
|
||||
{
|
||||
var values = new float[bytes.Length / 4];
|
||||
|
||||
int counter = 0;
|
||||
for (int cnt = 0; cnt < bytes.Length / 4; cnt++)
|
||||
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] });
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
69
S7.Net/Types/S7String.cs
Normal file
69
S7.Net/Types/S7String.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert from S7 strings to C# strings
|
||||
/// An S7 String has a preceeding 2 byte header containing its capacity and length
|
||||
/// </summary>
|
||||
public static class S7String
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts S7 bytes to a string
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static string FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length < 2)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / too short");
|
||||
}
|
||||
|
||||
int size = bytes[0];
|
||||
int length = bytes[1];
|
||||
if (length > size)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / length larger than capacity");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Encoding.ASCII.GetString(bytes, 2, length);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData,
|
||||
$"Failed to parse {VarType.S7String} 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 (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}.");
|
||||
|
||||
var bytes = Encoding.ASCII.GetBytes(value);
|
||||
if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength}).");
|
||||
|
||||
var buffer = new byte[2 + reservedLength];
|
||||
Array.Copy(bytes, 0, buffer, 2, bytes.Length);
|
||||
buffer[0] = (byte)reservedLength;
|
||||
buffer[1] = (byte)bytes.Length;
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,27 +5,13 @@ namespace S7.Net.Types
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Real from S7 plc to C# float.
|
||||
/// </summary>
|
||||
[Obsolete("Class Single is obsolete. Use Real instead.")]
|
||||
public static class Single
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 Real (4 bytes) to float
|
||||
/// </summary>
|
||||
public static float FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 4)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes.");
|
||||
}
|
||||
|
||||
// sps uses bigending so we have to reverse if platform needs
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
// create deep copy of the array and reverse
|
||||
bytes = new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
public static float FromByteArray(byte[] bytes) => Real.FromByteArray(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a S7 DInt to float
|
||||
@@ -51,16 +37,7 @@ namespace S7.Net.Types
|
||||
/// <summary>
|
||||
/// Converts a double to S7 Real (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(float value)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes((float)(value));
|
||||
|
||||
// sps uses bigending so we have to check if platform is same
|
||||
if (!BitConverter.IsLittleEndian) return bytes;
|
||||
|
||||
// create deep copy of the array and reverse
|
||||
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
public static byte[] ToByteArray(float value) => Real.ToByteArray(value);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of float to an array of bytes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert from S7 strings to C# strings
|
||||
/// Contains the methods to convert from S7 Array of Chars (like a const char[N] C-String) to C# strings
|
||||
/// </summary>
|
||||
public class String
|
||||
{
|
||||
|
||||
@@ -1,60 +1,15 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert from S7 strings to C# strings
|
||||
/// there are two kinds how strings a send. This one is with a pre of two bytes
|
||||
/// they contain the length of the string
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="S7String"/>
|
||||
[Obsolete("Please use S7String class")]
|
||||
public static class StringEx
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts S7 bytes to a string
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static string FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length < 2) return "";
|
||||
/// <inheritdoc cref="S7String.FromByteArray(byte[])"/>
|
||||
public static string FromByteArray(byte[] bytes) => S7String.FromByteArray(bytes);
|
||||
|
||||
int size = bytes[0];
|
||||
int length = bytes[1];
|
||||
|
||||
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;
|
||||
}
|
||||
/// <inheritdoc cref="S7String.ToByteArray(string, int)"/>
|
||||
public static byte[] ToByteArray(string value, int reservedLength) => S7String.ToByteArray(value, reservedLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +50,17 @@ namespace S7.Net.Types
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
numBytes += 8;
|
||||
break;
|
||||
default:
|
||||
numBytes += GetStructSize(info.FieldType);
|
||||
break;
|
||||
@@ -152,28 +157,27 @@ namespace S7.Net.Types
|
||||
bytes[(int)numBytes + 3]));
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
// hier auswerten
|
||||
info.SetValue(structValue, Double.FromByteArray(new byte[] { bytes[(int)numBytes],
|
||||
bytes[(int)numBytes + 1],
|
||||
bytes[(int)numBytes + 2],
|
||||
bytes[(int)numBytes + 3] }));
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
// hier auswerten
|
||||
info.SetValue(structValue, Single.FromByteArray(new byte[] { bytes[(int)numBytes],
|
||||
info.SetValue(structValue, Real.FromByteArray(new byte[] { bytes[(int)numBytes],
|
||||
bytes[(int)numBytes + 1],
|
||||
bytes[(int)numBytes + 2],
|
||||
bytes[(int)numBytes + 3] }));
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
// hier auswerten
|
||||
var data = new byte[8];
|
||||
Array.Copy(bytes, (int)numBytes, data, 0, 8);
|
||||
info.SetValue(structValue, LReal.FromByteArray(data));
|
||||
numBytes += 8;
|
||||
break;
|
||||
default:
|
||||
var buffer = new byte[GetStructSize(info.FieldType)];
|
||||
if (buffer.Length == 0)
|
||||
@@ -244,11 +248,11 @@ namespace S7.Net.Types
|
||||
case "UInt32":
|
||||
bytes2 = DWord.ToByteArray((UInt32)info.GetValue(structValue));
|
||||
break;
|
||||
case "Double":
|
||||
bytes2 = Double.ToByteArray((double)info.GetValue(structValue));
|
||||
break;
|
||||
case "Single":
|
||||
bytes2 = Single.ToByteArray((float)info.GetValue(structValue));
|
||||
bytes2 = Real.ToByteArray((float)info.GetValue(structValue));
|
||||
break;
|
||||
case "Double":
|
||||
bytes2 = LReal.ToByteArray((double)info.GetValue(structValue));
|
||||
break;
|
||||
}
|
||||
if (bytes2 != null)
|
||||
|
||||
43
S7.Net/Types/TypeHelper.cs
Normal file
43
S7.Net/Types/TypeHelper.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
internal static class TypeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an array of T to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray<T>(T[] value, Func<T, byte[]> converter) where T : struct
|
||||
{
|
||||
var buffer = new byte[Marshal.SizeOf(default(T)) * value.Length];
|
||||
var stream = new MemoryStream(buffer);
|
||||
foreach (var val in value)
|
||||
{
|
||||
stream.Write(converter(val), 0, 4);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of T repesented as S7 binary data to an array of T
|
||||
/// </summary>
|
||||
public static T[] ToArray<T>(byte[] bytes, Func<byte[], T> converter) where T : struct
|
||||
{
|
||||
var typeSize = Marshal.SizeOf(default(T));
|
||||
var entries = bytes.Length / typeSize;
|
||||
var values = new T[entries];
|
||||
|
||||
for(int i = 0; i < entries; ++i)
|
||||
{
|
||||
var buffer = new byte[typeSize];
|
||||
Array.Copy(bytes, i * typeSize, buffer, 0, typeSize);
|
||||
values[i] = converter(buffer);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user