mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab6308eacd | ||
|
|
130eeadbd8 | ||
|
|
76a7ea04f7 | ||
|
|
4764c997ed | ||
|
|
cb24e9a046 | ||
|
|
10315b4b4c | ||
|
|
0774e124bf | ||
|
|
1ebffe08e7 | ||
|
|
f419df4d73 | ||
|
|
1969aac1b2 | ||
|
|
2f2dcf7281 | ||
|
|
07325db2fa | ||
|
|
eada47cd24 | ||
|
|
5e1ac8c7bf | ||
|
|
13544a1bcf | ||
|
|
6fc526b886 | ||
|
|
f227ad4b53 | ||
|
|
e4cc42fa51 | ||
|
|
689e7ffd96 | ||
|
|
8087b8d315 | ||
|
|
a55ceba679 | ||
|
|
eb1fad9333 | ||
|
|
0de9364dee | ||
|
|
9380ea85c3 | ||
|
|
22451bc440 | ||
|
|
e98ce005c5 | ||
|
|
11a40cc5e3 | ||
|
|
f79286b2d0 | ||
|
|
fadd7d0cb3 | ||
|
|
652ff3a9bb | ||
|
|
9c0fea721a | ||
|
|
2ec73224c1 | ||
|
|
a8ef47b475 | ||
|
|
55aa06a1fc | ||
|
|
7e631a713f | ||
|
|
0797c5858f | ||
|
|
49e4d3369a | ||
|
|
ee06bec0fb | ||
|
|
05ccb05f3a | ||
|
|
0d2817661e |
259
S7.Net.UnitTest/CommunicationTests/Clock.cs
Normal file
259
S7.Net.UnitTest/CommunicationTests/Clock.cs
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using S7.Net.Protocol;
|
||||||
|
|
||||||
|
namespace S7.Net.UnitTest.CommunicationTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class Clock
|
||||||
|
{
|
||||||
|
[TestMethod, Timeout(1000)]
|
||||||
|
public async Task Read_Clock_Value()
|
||||||
|
{
|
||||||
|
var cs = new CommunicationSequence
|
||||||
|
{
|
||||||
|
ConnectionOpenTemplates.ConnectionRequestConfirm,
|
||||||
|
ConnectionOpenTemplates.CommunicationSetup,
|
||||||
|
{
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 00 00 1d
|
||||||
|
|
||||||
|
// COTP
|
||||||
|
02 f0 80
|
||||||
|
|
||||||
|
// S7 read clock
|
||||||
|
// UserData header
|
||||||
|
32 07 00 00 PDU1 PDU2
|
||||||
|
// Parameter length
|
||||||
|
00 08
|
||||||
|
// Data length
|
||||||
|
00 04
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
// Head
|
||||||
|
00 01 12
|
||||||
|
// Length
|
||||||
|
04
|
||||||
|
// Method (Request/Response): Req
|
||||||
|
11
|
||||||
|
// Type request (4...) Function group timers (...7)
|
||||||
|
47
|
||||||
|
// Subfunction: read clock
|
||||||
|
01
|
||||||
|
// Sequence number
|
||||||
|
00
|
||||||
|
|
||||||
|
// Data
|
||||||
|
// Return code
|
||||||
|
0a
|
||||||
|
// Transport size
|
||||||
|
00
|
||||||
|
// Payload length
|
||||||
|
00 00
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 00 00 2b
|
||||||
|
|
||||||
|
// COTP
|
||||||
|
02 f0 80
|
||||||
|
|
||||||
|
// S7 read clock response
|
||||||
|
// UserData header
|
||||||
|
32 07 00 00 PDU1 PDU2
|
||||||
|
// Parameter length
|
||||||
|
00 0c
|
||||||
|
// Data length
|
||||||
|
00 0e
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
// Head
|
||||||
|
00 01 12
|
||||||
|
// Length
|
||||||
|
08
|
||||||
|
// Method (Request/Response): Res
|
||||||
|
12
|
||||||
|
// Type response (8...) Function group timers (...7)
|
||||||
|
87
|
||||||
|
// Subfunction: read clock
|
||||||
|
01
|
||||||
|
// Sequence number
|
||||||
|
01
|
||||||
|
// Data unit reference
|
||||||
|
00
|
||||||
|
// Last data unit? Yes
|
||||||
|
00
|
||||||
|
// Error code
|
||||||
|
00 00
|
||||||
|
|
||||||
|
// Data
|
||||||
|
// Error code
|
||||||
|
ff
|
||||||
|
// Transport size: OCTET STRING
|
||||||
|
09
|
||||||
|
// Length
|
||||||
|
00 0a
|
||||||
|
|
||||||
|
// Timestamp
|
||||||
|
// Reserved
|
||||||
|
00
|
||||||
|
// Year 1
|
||||||
|
19
|
||||||
|
// Year 2
|
||||||
|
14
|
||||||
|
// Month
|
||||||
|
08
|
||||||
|
// Day
|
||||||
|
20
|
||||||
|
// Hour
|
||||||
|
11
|
||||||
|
// Minute
|
||||||
|
59
|
||||||
|
// Seconds
|
||||||
|
43
|
||||||
|
// Milliseconds: 912..., Day of week: ...4
|
||||||
|
91 24
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static async Task Client(int port)
|
||||||
|
{
|
||||||
|
var conn = new Plc(IPAddress.Loopback.ToString(), port, new TsapPair(new Tsap(1, 2), new Tsap(3, 4)));
|
||||||
|
await conn.OpenAsync();
|
||||||
|
var time = await conn.ReadClockAsync();
|
||||||
|
|
||||||
|
Assert.AreEqual(new DateTime(2014, 8, 20, 11, 59, 43, 912), time);
|
||||||
|
conn.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(cs.Serve(out var port), Client(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod, Timeout(1000)]
|
||||||
|
public async Task Write_Clock_Value()
|
||||||
|
{
|
||||||
|
var cs = new CommunicationSequence
|
||||||
|
{
|
||||||
|
ConnectionOpenTemplates.ConnectionRequestConfirm,
|
||||||
|
ConnectionOpenTemplates.CommunicationSetup,
|
||||||
|
{
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 00 00 27
|
||||||
|
|
||||||
|
// COTP
|
||||||
|
02 f0 80
|
||||||
|
|
||||||
|
// S7 read clock
|
||||||
|
// UserData header
|
||||||
|
32 07 00 00 PDU1 PDU2
|
||||||
|
// Parameter length
|
||||||
|
00 08
|
||||||
|
// Data length
|
||||||
|
00 0e
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
// Head
|
||||||
|
00 01 12
|
||||||
|
// Length
|
||||||
|
04
|
||||||
|
// Method (Request/Response): Req
|
||||||
|
11
|
||||||
|
// Type request (4...) Function group timers (...7)
|
||||||
|
47
|
||||||
|
// Subfunction: write clock
|
||||||
|
02
|
||||||
|
// Sequence number
|
||||||
|
00
|
||||||
|
|
||||||
|
// Data
|
||||||
|
// Return code
|
||||||
|
ff
|
||||||
|
// Transport size
|
||||||
|
09
|
||||||
|
// Payload length
|
||||||
|
00 0a
|
||||||
|
|
||||||
|
// Payload
|
||||||
|
// Timestamp
|
||||||
|
// Reserved
|
||||||
|
00
|
||||||
|
// Year 1
|
||||||
|
19
|
||||||
|
// Year 2
|
||||||
|
14
|
||||||
|
// Month
|
||||||
|
08
|
||||||
|
// Day
|
||||||
|
20
|
||||||
|
// Hour
|
||||||
|
11
|
||||||
|
// Minute
|
||||||
|
59
|
||||||
|
// Seconds
|
||||||
|
43
|
||||||
|
// Milliseconds: 912..., Day of week: ...4
|
||||||
|
91 24
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 00 00 21
|
||||||
|
|
||||||
|
// COTP
|
||||||
|
02 f0 80
|
||||||
|
|
||||||
|
// S7 read clock response
|
||||||
|
// UserData header
|
||||||
|
32 07 00 00 PDU1 PDU2
|
||||||
|
// Parameter length
|
||||||
|
00 0c
|
||||||
|
// Data length
|
||||||
|
00 04
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
// Head
|
||||||
|
00 01 12
|
||||||
|
// Length
|
||||||
|
08
|
||||||
|
// Method (Request/Response): Res
|
||||||
|
12
|
||||||
|
// Type response (8...) Function group timers (...7)
|
||||||
|
87
|
||||||
|
// Subfunction: write clock
|
||||||
|
02
|
||||||
|
// Sequence number
|
||||||
|
01
|
||||||
|
// Data unit reference
|
||||||
|
00
|
||||||
|
// Last data unit? Yes
|
||||||
|
00
|
||||||
|
// Error code
|
||||||
|
00 00
|
||||||
|
|
||||||
|
// Data
|
||||||
|
// Error code
|
||||||
|
0a
|
||||||
|
// Transport size: NONE
|
||||||
|
00
|
||||||
|
// Length
|
||||||
|
00 00
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static async Task Client(int port)
|
||||||
|
{
|
||||||
|
var conn = new Plc(IPAddress.Loopback.ToString(), port, new TsapPair(new Tsap(1, 2), new Tsap(3, 4)));
|
||||||
|
await conn.OpenAsync();
|
||||||
|
await conn.WriteClockAsync(new DateTime(2014, 08, 20, 11, 59, 43, 912));
|
||||||
|
|
||||||
|
conn.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(cs.Serve(out var port), Client(port));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,5 +24,21 @@ namespace S7.Net.UnitTest
|
|||||||
Assert.IsFalse(dummyByte.SelectBit(7));
|
Assert.IsFalse(dummyByte.SelectBit(7));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void T01_TestSetBit()
|
||||||
|
{
|
||||||
|
byte dummyByte = 0xAA; // 1010 1010
|
||||||
|
dummyByte.SetBit(0, true);
|
||||||
|
dummyByte.SetBit(1, false);
|
||||||
|
dummyByte.SetBit(2, true);
|
||||||
|
dummyByte.SetBit(3, false);
|
||||||
|
Assert.AreEqual<byte>(dummyByte, 0xA5);// 1010 0101
|
||||||
|
dummyByte.SetBit(4, true);
|
||||||
|
dummyByte.SetBit(5, true);
|
||||||
|
dummyByte.SetBit(6, true);
|
||||||
|
dummyByte.SetBit(7, true);
|
||||||
|
Assert.AreEqual<byte>(dummyByte, 0xF5);// 1111 0101
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
|
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
|
||||||
<TargetFrameworks>net462;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net7.0;net462</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -17,7 +17,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.0" />
|
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.2">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||||
|
|||||||
@@ -1006,7 +1006,7 @@ namespace S7.Net.UnitTest
|
|||||||
var db = 2;
|
var db = 2;
|
||||||
randomEngine.NextBytes(data);
|
randomEngine.NextBytes(data);
|
||||||
|
|
||||||
cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5));
|
cancellationSource.CancelAfter(System.TimeSpan.FromMilliseconds(5));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
|
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
|
||||||
@@ -1045,7 +1045,7 @@ namespace S7.Net.UnitTest
|
|||||||
var db = 2;
|
var db = 2;
|
||||||
randomEngine.NextBytes(data.Span);
|
randomEngine.NextBytes(data.Span);
|
||||||
|
|
||||||
cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5));
|
cancellationSource.CancelAfter(System.TimeSpan.FromMilliseconds(5));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
|
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
|
||||||
|
|||||||
82
S7.Net.UnitTest/TypeTests/TimeSpanTests.cs
Normal file
82
S7.Net.UnitTest/TypeTests/TimeSpanTests.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace S7.Net.UnitTest.TypeTests
|
||||||
|
{
|
||||||
|
public static class TimeSpanTests
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan SampleTimeSpan = new TimeSpan(12, 0, 59, 37, 856);
|
||||||
|
|
||||||
|
private static readonly byte[] SampleByteArray = { 0x3E, 0x02, 0xE8, 0x00 };
|
||||||
|
|
||||||
|
private static readonly byte[] SpecMinByteArray = { 0x80, 0x00, 0x00, 0x00 };
|
||||||
|
|
||||||
|
private static readonly byte[] SpecMaxByteArray = { 0x7F, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class FromByteArray
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Sample()
|
||||||
|
{
|
||||||
|
AssertFromByteArrayEquals(SampleTimeSpan, SampleByteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SpecMinimum()
|
||||||
|
{
|
||||||
|
AssertFromByteArrayEquals(Types.TimeSpan.SpecMinimumTimeSpan, SpecMinByteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SpecMaximum()
|
||||||
|
{
|
||||||
|
AssertFromByteArrayEquals(Types.TimeSpan.SpecMaximumTimeSpan, SpecMaxByteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertFromByteArrayEquals(TimeSpan expected, params byte[] bytes)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expected, Types.TimeSpan.FromByteArray(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class ToByteArray
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Sample()
|
||||||
|
{
|
||||||
|
AssertToByteArrayEquals(SampleTimeSpan, SampleByteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SpecMinimum()
|
||||||
|
{
|
||||||
|
AssertToByteArrayEquals(Types.TimeSpan.SpecMinimumTimeSpan, SpecMinByteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SpecMaximum()
|
||||||
|
{
|
||||||
|
AssertToByteArrayEquals(Types.TimeSpan.SpecMaximumTimeSpan, SpecMaxByteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||||
|
public void ThrowsOnTimeBeforeSpecMinimum()
|
||||||
|
{
|
||||||
|
Types.TimeSpan.ToByteArray(TimeSpan.FromDays(-25));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||||
|
public void ThrowsOnTimeAfterSpecMaximum()
|
||||||
|
{
|
||||||
|
Types.TimeSpan.ToByteArray(new TimeSpan(30, 15, 15, 15, 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertToByteArrayEquals(TimeSpan value, params byte[] expected)
|
||||||
|
{
|
||||||
|
CollectionAssert.AreEqual(expected, Types.TimeSpan.ToByteArray(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,19 +138,59 @@ namespace S7.Net
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper to get a bit value given a byte and the bit index.
|
/// Helper to get a bit value given a byte and the bit index.
|
||||||
/// Example: DB1.DBX0.5 -> var bytes = ReadBytes(DB1.DBW0); bool bit = bytes[0].SelectBit(5);
|
/// <br/>
|
||||||
|
/// <example>
|
||||||
|
/// Get the bit at DB1.DBX0.5:
|
||||||
|
/// <code>
|
||||||
|
/// byte data = ReadByte("DB1.DBB0");
|
||||||
|
/// bool bit = data.SelectBit(5);
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
/// <param name="data">The data to get from.</param>
|
||||||
/// <param name="bitPosition"></param>
|
/// <param name="index">The zero-based index of the bit to get.</param>
|
||||||
/// <returns></returns>
|
/// <returns>The Boolean value will get.</returns>
|
||||||
public static bool SelectBit(this byte data, int bitPosition)
|
public static bool SelectBit(this byte data, int index)
|
||||||
{
|
{
|
||||||
int mask = 1 << bitPosition;
|
int mask = 1 << index;
|
||||||
int result = data & mask;
|
int result = data & mask;
|
||||||
|
|
||||||
return (result != 0);
|
return (result != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to set a bit value to the given byte at the bit index.
|
||||||
|
/// <br/>
|
||||||
|
/// <example>
|
||||||
|
/// Set the bit at index 4:
|
||||||
|
/// <code>
|
||||||
|
/// byte data = 0;
|
||||||
|
/// data.SetBit(4, true);
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to be modified.</param>
|
||||||
|
/// <param name="index">The zero-based index of the bit to set.</param>
|
||||||
|
/// <param name="value">The Boolean value to assign to the bit.</param>
|
||||||
|
public static void SetBit(this ref byte data, int index, bool value)
|
||||||
|
{
|
||||||
|
if ((uint)index > 7)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
byte mask = (byte)(1 << index);
|
||||||
|
data |= mask;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte mask = (byte)~(1 << index);
|
||||||
|
data &= mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts from ushort value to short value; it's used to retrieve negative values from words
|
/// Converts from ushort value to short value; it's used to retrieve negative values from words
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -202,10 +202,20 @@
|
|||||||
/// DateTIme variable type
|
/// DateTIme variable type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DateTime,
|
DateTime,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IEC date (legacy) variable type
|
||||||
|
/// </summary>
|
||||||
|
Date,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DateTimeLong variable type
|
/// DateTimeLong variable type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DateTimeLong
|
DateTimeLong,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S7 TIME variable type - serialized as S7 DInt and deserialized as C# TimeSpan
|
||||||
|
/// </summary>
|
||||||
|
Time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
S7.Net/Helper/DateTimeExtensions.cs
Normal file
23
S7.Net/Helper/DateTimeExtensions.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using S7.Net.Types;
|
||||||
|
using DateTime = System.DateTime;
|
||||||
|
|
||||||
|
namespace S7.Net.Helper
|
||||||
|
{
|
||||||
|
public static class DateTimeExtensions
|
||||||
|
{
|
||||||
|
public static ushort GetDaysSinceIecDateStart(this DateTime dateTime)
|
||||||
|
{
|
||||||
|
if (dateTime < Date.IecMinDate)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException($"DateTime must be at least {Date.IecMinDate:d}");
|
||||||
|
}
|
||||||
|
if (dateTime > Date.IecMaxDate)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException($"DateTime must be lower than {Date.IecMaxDate:d}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ushort)(dateTime - Date.IecMinDate).TotalDays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,28 +56,35 @@ namespace S7.Net
|
|||||||
WriteS7Header(stream, s7MessageTypeUserData, parameterLength, dataLength);
|
WriteS7Header(stream, s7MessageTypeUserData, parameterLength, dataLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteSzlReadRequest(System.IO.MemoryStream stream, ushort szlId, ushort szlIndex)
|
private static void WriteUserDataRequest(System.IO.MemoryStream stream, byte functionGroup, byte subFunction, int dataLength)
|
||||||
{
|
{
|
||||||
WriteUserDataHeader(stream, 8, 8);
|
WriteUserDataHeader(stream, 8, dataLength);
|
||||||
|
|
||||||
// Parameter
|
// Parameter
|
||||||
const byte szlMethodRequest = 0x11;
|
const byte userDataMethodRequest = 0x11;
|
||||||
const byte szlTypeRequest = 0b100;
|
const byte userDataTypeRequest = 0x4;
|
||||||
const byte szlFunctionGroupCpuFunctions = 0b100;
|
|
||||||
const byte subFunctionReadSzl = 0x01;
|
|
||||||
|
|
||||||
// Parameter head
|
// Parameter head
|
||||||
stream.Write(new byte[] { 0x00, 0x01, 0x12 });
|
stream.Write(new byte[] { 0x00, 0x01, 0x12 });
|
||||||
// Parameter length
|
// Parameter length
|
||||||
stream.WriteByte(0x04);
|
stream.WriteByte(0x04);
|
||||||
// Method
|
// Method
|
||||||
stream.WriteByte(szlMethodRequest);
|
stream.WriteByte(userDataMethodRequest);
|
||||||
// Type / function group
|
// Type / function group
|
||||||
stream.WriteByte(szlTypeRequest << 4 | szlFunctionGroupCpuFunctions);
|
stream.WriteByte((byte)(userDataTypeRequest << 4 | (functionGroup & 0x0f)));
|
||||||
// Subfunction
|
// Subfunction
|
||||||
stream.WriteByte(subFunctionReadSzl);
|
stream.WriteByte(subFunction);
|
||||||
// Sequence number
|
// Sequence number
|
||||||
stream.WriteByte(0);
|
stream.WriteByte(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteSzlReadRequest(System.IO.MemoryStream stream, ushort szlId, ushort szlIndex)
|
||||||
|
{
|
||||||
|
// Parameter
|
||||||
|
const byte szlFunctionGroupCpuFunctions = 0b100;
|
||||||
|
const byte subFunctionReadSzl = 0x01;
|
||||||
|
|
||||||
|
WriteUserDataRequest(stream, szlFunctionGroupCpuFunctions, subFunctionReadSzl, 8);
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
const byte success = 0xff;
|
const byte success = 0xff;
|
||||||
@@ -235,6 +242,24 @@ namespace S7.Net
|
|||||||
{
|
{
|
||||||
return DateTimeLong.ToArray(bytes);
|
return DateTimeLong.ToArray(bytes);
|
||||||
}
|
}
|
||||||
|
case VarType.Time:
|
||||||
|
if (varCount == 1)
|
||||||
|
{
|
||||||
|
return TimeSpan.FromByteArray(bytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TimeSpan.ToArray(bytes);
|
||||||
|
}
|
||||||
|
case VarType.Date:
|
||||||
|
if (varCount == 1)
|
||||||
|
{
|
||||||
|
return Date.FromByteArray(bytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Date.ToArray(bytes);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -264,10 +289,12 @@ namespace S7.Net
|
|||||||
case VarType.Timer:
|
case VarType.Timer:
|
||||||
case VarType.Int:
|
case VarType.Int:
|
||||||
case VarType.Counter:
|
case VarType.Counter:
|
||||||
|
case VarType.Date:
|
||||||
return varCount * 2;
|
return varCount * 2;
|
||||||
case VarType.DWord:
|
case VarType.DWord:
|
||||||
case VarType.DInt:
|
case VarType.DInt:
|
||||||
case VarType.Real:
|
case VarType.Real:
|
||||||
|
case VarType.Time:
|
||||||
return varCount * 4;
|
return varCount * 4;
|
||||||
case VarType.LReal:
|
case VarType.LReal:
|
||||||
case VarType.DateTime:
|
case VarType.DateTime:
|
||||||
@@ -333,7 +360,7 @@ namespace S7.Net
|
|||||||
private static byte[] BuildSzlReadRequestPackage(ushort szlId, ushort szlIndex)
|
private static byte[] BuildSzlReadRequestPackage(ushort szlId, ushort szlIndex)
|
||||||
{
|
{
|
||||||
var stream = new System.IO.MemoryStream();
|
var stream = new System.IO.MemoryStream();
|
||||||
|
|
||||||
WriteSzlReadRequest(stream, szlId, szlIndex);
|
WriteSzlReadRequest(stream, szlId, szlIndex);
|
||||||
stream.SetLength(stream.Position);
|
stream.SetLength(stream.Position);
|
||||||
|
|
||||||
|
|||||||
92
S7.Net/Plc.Clock.cs
Normal file
92
S7.Net/Plc.Clock.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using S7.Net.Helper;
|
||||||
|
using S7.Net.Types;
|
||||||
|
using DateTime = System.DateTime;
|
||||||
|
|
||||||
|
namespace S7.Net;
|
||||||
|
|
||||||
|
partial class Plc
|
||||||
|
{
|
||||||
|
private const byte SzlFunctionGroupTimers = 0x07;
|
||||||
|
private const byte SzlSubFunctionReadClock = 0x01;
|
||||||
|
private const byte SzlSubFunctionWriteClock = 0x02;
|
||||||
|
private const byte TransportSizeOctetString = 0x09;
|
||||||
|
private const int PduErrOffset = 20;
|
||||||
|
private const int UserDataResultOffset = PduErrOffset + 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length in bytes of DateTime stored in the PLC.
|
||||||
|
/// </summary>
|
||||||
|
private const int DateTimeLength = 10;
|
||||||
|
|
||||||
|
private static byte[] BuildClockReadRequest()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
|
||||||
|
WriteUserDataRequest(stream, SzlFunctionGroupTimers, SzlSubFunctionReadClock, 4);
|
||||||
|
stream.Write(new byte[] { 0x0a, 0x00, 0x00, 0x00 });
|
||||||
|
|
||||||
|
stream.SetLength(stream.Position);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime ParseClockReadResponse(byte[] message)
|
||||||
|
{
|
||||||
|
const int udLenOffset = UserDataResultOffset + 2;
|
||||||
|
const int udValueOffset = udLenOffset + 2;
|
||||||
|
const int dateTimeSkip = 2;
|
||||||
|
|
||||||
|
AssertPduResult(message);
|
||||||
|
AssertUserDataResult(message, 0xff);
|
||||||
|
|
||||||
|
var len = Word.FromByteArray(message.Skip(udLenOffset).Take(2).ToArray());
|
||||||
|
if (len != DateTimeLength)
|
||||||
|
{
|
||||||
|
throw new Exception($"Unexpected response length {len}, expected {DateTimeLength}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip first 2 bytes from date time value because DateTime.FromByteArray doesn't parse them.
|
||||||
|
return Types.DateTime.FromByteArray(message.Skip(udValueOffset + dateTimeSkip)
|
||||||
|
.Take(DateTimeLength - dateTimeSkip).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] BuildClockWriteRequest(DateTime value)
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
|
||||||
|
WriteUserDataRequest(stream, SzlFunctionGroupTimers, SzlSubFunctionWriteClock, 14);
|
||||||
|
stream.Write(new byte[] { 0xff, TransportSizeOctetString, 0x00, DateTimeLength });
|
||||||
|
// Start of DateTime value, DateTime.ToByteArray only serializes the final 8 bytes
|
||||||
|
stream.Write(new byte[] { 0x00, 0x19 });
|
||||||
|
stream.Write(Types.DateTime.ToByteArray(value));
|
||||||
|
|
||||||
|
stream.SetLength(stream.Position);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseClockWriteResponse(byte[] message)
|
||||||
|
{
|
||||||
|
AssertPduResult(message);
|
||||||
|
AssertUserDataResult(message, 0x0a);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertPduResult(byte[] message)
|
||||||
|
{
|
||||||
|
var pduErr = Word.FromByteArray(message.Skip(PduErrOffset).Take(2).ToArray());
|
||||||
|
if (pduErr != 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"Response from PLC indicates error 0x{pduErr:X4}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertUserDataResult(byte[] message, byte expected)
|
||||||
|
{
|
||||||
|
var dtResult = message[UserDataResultOffset];
|
||||||
|
if (dtResult != expected)
|
||||||
|
{
|
||||||
|
throw new Exception($"Response from PLC was 0x{dtResult:X2}, expected 0x{expected:X2}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -312,6 +312,35 @@ namespace S7.Net
|
|||||||
return dataItems;
|
return dataItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the PLC clock value.
|
||||||
|
/// </summary>
|
||||||
|
/// <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 operation, with it's result set to the current PLC time on completion.</returns>
|
||||||
|
public async Task<System.DateTime> ReadClockAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var request = BuildClockReadRequest();
|
||||||
|
var response = await RequestTsduAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
return ParseClockReadResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write the PLC clock value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The date and time to set the PLC clock to</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 operation.</returns>
|
||||||
|
public async Task WriteClockAsync(System.DateTime value, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var request = BuildClockWriteRequest(value);
|
||||||
|
var response = await RequestTsduAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
ParseClockWriteResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read the current status from the PLC. A value of 0x08 indicates the PLC is in run status, regardless of the PLC type.
|
/// Read the current status from the PLC. A value of 0x08 indicates the PLC is in run status, regardless of the PLC type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -492,6 +492,30 @@ namespace S7.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the PLC clock value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current PLC time.</returns>
|
||||||
|
public System.DateTime ReadClock()
|
||||||
|
{
|
||||||
|
var request = BuildClockReadRequest();
|
||||||
|
var response = RequestTsdu(request);
|
||||||
|
|
||||||
|
return ParseClockReadResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write the PLC clock value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The date and time to set the PLC clock to.</param>
|
||||||
|
public void WriteClock(System.DateTime value)
|
||||||
|
{
|
||||||
|
var request = BuildClockWriteRequest(value);
|
||||||
|
var response = RequestTsdu(request);
|
||||||
|
|
||||||
|
ParseClockWriteResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read the current status from the PLC. A value of 0x08 indicates the PLC is in run status, regardless of the PLC type.
|
/// Read the current status from the PLC. A value of 0x08 indicates the PLC is in run status, regardless of the PLC type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ namespace S7.Net.Protocol
|
|||||||
_ => Types.String.ToByteArray(s, dataItem.Count)
|
_ => Types.String.ToByteArray(s, dataItem.Count)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (dataItem.VarType == VarType.Date)
|
||||||
|
{
|
||||||
|
return Date.ToByteArray((System.DateTime)dataItem.Value);
|
||||||
|
}
|
||||||
|
|
||||||
return SerializeValue(dataItem.Value);
|
return SerializeValue(dataItem.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
82
S7.Net/Types/Date.cs
Normal file
82
S7.Net/Types/Date.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using S7.Net.Helper;
|
||||||
|
|
||||||
|
namespace S7.Net.Types
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the conversion methods to convert Words from S7 plc to C#.
|
||||||
|
/// </summary>
|
||||||
|
public static class Date
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum allowed date for the IEC date type
|
||||||
|
/// </summary>
|
||||||
|
public static System.DateTime IecMinDate { get; } = new(year: 1990, month: 01, day: 01);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum allowed date for the IEC date type
|
||||||
|
/// <remarks>
|
||||||
|
/// Although the spec allows only a max date of 31-12-2168, the PLC IEC date goes up to 06-06-2169 (which is the actual
|
||||||
|
/// WORD max value - 65535)
|
||||||
|
/// </remarks>
|
||||||
|
/// </summary>
|
||||||
|
public static System.DateTime IecMaxDate { get; } = new(year: 2169, month: 06, day: 06);
|
||||||
|
|
||||||
|
private static readonly ushort MaxNumberOfDays = (ushort)(IecMaxDate - IecMinDate).TotalDays;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a word (2 bytes) to IEC date (<see cref="System.DateTime"/>)
|
||||||
|
/// </summary>
|
||||||
|
public static System.DateTime FromByteArray(byte[] bytes)
|
||||||
|
{
|
||||||
|
if (bytes.Length != 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var daysSinceDateStart = Word.FromByteArray(bytes);
|
||||||
|
if (daysSinceDateStart > MaxNumberOfDays)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Read number exceeded the number of maximum days in the IEC date (read: {daysSinceDateStart}, max: {MaxNumberOfDays})",
|
||||||
|
nameof(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
return IecMinDate.AddDays(daysSinceDateStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a <see cref="System.DateTime"/> to word (2 bytes)
|
||||||
|
/// </summary>
|
||||||
|
public static byte[] ToByteArray(System.DateTime dateTime) => Word.ToByteArray(dateTime.GetDaysSinceIecDateStart());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts an array of <see cref="System.DateTime"/>s to an array of bytes
|
||||||
|
/// </summary>
|
||||||
|
public static byte[] ToByteArray(System.DateTime[] value)
|
||||||
|
{
|
||||||
|
var arr = new ByteArray();
|
||||||
|
foreach (var date in value)
|
||||||
|
arr.Add(ToByteArray(date));
|
||||||
|
return arr.Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts an array of bytes to an array of <see cref="System.DateTime"/>s
|
||||||
|
/// </summary>
|
||||||
|
public static System.DateTime[] ToArray(byte[] bytes)
|
||||||
|
{
|
||||||
|
var values = new System.DateTime[bytes.Length / sizeof(ushort)];
|
||||||
|
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
values[i] = FromByteArray(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
bytes[i], bytes[i + 1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,7 @@ namespace S7.Net.Types
|
|||||||
break;
|
break;
|
||||||
case "Int32":
|
case "Int32":
|
||||||
case "UInt32":
|
case "UInt32":
|
||||||
|
case "TimeSpan":
|
||||||
numBytes = Math.Ceiling(numBytes);
|
numBytes = Math.Ceiling(numBytes);
|
||||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||||
numBytes++;
|
numBytes++;
|
||||||
@@ -215,6 +216,21 @@ namespace S7.Net.Types
|
|||||||
|
|
||||||
numBytes += sData.Length;
|
numBytes += sData.Length;
|
||||||
break;
|
break;
|
||||||
|
case "TimeSpan":
|
||||||
|
numBytes = Math.Ceiling(numBytes);
|
||||||
|
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||||
|
numBytes++;
|
||||||
|
|
||||||
|
// get the value
|
||||||
|
info.SetValue(structValue, TimeSpan.FromByteArray(new[]
|
||||||
|
{
|
||||||
|
bytes[(int)numBytes + 0],
|
||||||
|
bytes[(int)numBytes + 1],
|
||||||
|
bytes[(int)numBytes + 2],
|
||||||
|
bytes[(int)numBytes + 3]
|
||||||
|
}));
|
||||||
|
numBytes += 4;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
var buffer = new byte[GetStructSize(info.FieldType)];
|
var buffer = new byte[GetStructSize(info.FieldType)];
|
||||||
if (buffer.Length == 0)
|
if (buffer.Length == 0)
|
||||||
@@ -311,6 +327,9 @@ namespace S7.Net.Types
|
|||||||
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
|
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case "TimeSpan":
|
||||||
|
bytes2 = TimeSpan.ToByteArray((System.TimeSpan)info.GetValue(structValue));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (bytes2 != null)
|
if (bytes2 != null)
|
||||||
{
|
{
|
||||||
|
|||||||
97
S7.Net/Types/TimeSpan.cs
Normal file
97
S7.Net/Types/TimeSpan.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace S7.Net.Types
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the methods to convert between <see cref="T:System.TimeSpan"/> and S7 representation of TIME values.
|
||||||
|
/// </summary>
|
||||||
|
public static class TimeSpan
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum <see cref="T:System.TimeSpan"/> value supported by the specification.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly System.TimeSpan SpecMinimumTimeSpan = System.TimeSpan.FromMilliseconds(int.MinValue);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum <see cref="T:System.TimeSpan"/> value supported by the specification.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly System.TimeSpan SpecMaximumTimeSpan = System.TimeSpan.FromMilliseconds(int.MaxValue);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a <see cref="T:System.TimeSpan"/> value from bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||||
|
/// <returns>A <see cref="T:System.TimeSpan"/> object representing the value read from PLC.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||||
|
/// <paramref name="bytes"/> is not 4 or any value in <paramref name="bytes"/>
|
||||||
|
/// is outside the valid range of values.</exception>
|
||||||
|
public static System.TimeSpan FromByteArray(byte[] bytes)
|
||||||
|
{
|
||||||
|
var milliseconds = DInt.FromByteArray(bytes);
|
||||||
|
return System.TimeSpan.FromMilliseconds(milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses an array of <see cref="T:System.TimeSpan"/> values from bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||||
|
/// <returns>An array of <see cref="T:System.TimeSpan"/> objects representing the values read from PLC.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||||
|
/// <paramref name="bytes"/> is not a multiple of 4 or any value in
|
||||||
|
/// <paramref name="bytes"/> is outside the valid range of values.</exception>
|
||||||
|
public static System.TimeSpan[] ToArray(byte[] bytes)
|
||||||
|
{
|
||||||
|
const int singleTimeSpanLength = 4;
|
||||||
|
|
||||||
|
if (bytes.Length % singleTimeSpanLength != 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||||
|
$"Parsing an array of {nameof(System.TimeSpan)} requires a multiple of {singleTimeSpanLength} bytes of input data, input data is '{bytes.Length}' long.");
|
||||||
|
|
||||||
|
var result = new System.TimeSpan[bytes.Length / singleTimeSpanLength];
|
||||||
|
|
||||||
|
var milliseconds = DInt.ToArray(bytes);
|
||||||
|
for (var i = 0; i < milliseconds.Length; i++)
|
||||||
|
result[i] = System.TimeSpan.FromMilliseconds(milliseconds[i]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a <see cref="T:System.TimeSpan"/> value to a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSpan">The TimeSpan value to convert.</param>
|
||||||
|
/// <returns>A byte array containing the S7 date time representation of <paramref name="timeSpan"/>.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the value of
|
||||||
|
/// <paramref name="timeSpan"/> is before <see cref="P:SpecMinimumTimeSpan"/>
|
||||||
|
/// or after <see cref="P:SpecMaximumTimeSpan"/>.</exception>
|
||||||
|
public static byte[] ToByteArray(System.TimeSpan timeSpan)
|
||||||
|
{
|
||||||
|
if (timeSpan < SpecMinimumTimeSpan)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(timeSpan), timeSpan,
|
||||||
|
$"Time span '{timeSpan}' is before the minimum '{SpecMinimumTimeSpan}' supported in S7 time representation.");
|
||||||
|
|
||||||
|
if (timeSpan > SpecMaximumTimeSpan)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(timeSpan), timeSpan,
|
||||||
|
$"Time span '{timeSpan}' is after the maximum '{SpecMaximumTimeSpan}' supported in S7 time representation.");
|
||||||
|
|
||||||
|
return DInt.ToByteArray(Convert.ToInt32(timeSpan.TotalMilliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts an array of <see cref="T:System.TimeSpan"/> values to a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSpans">The TimeSpan values to convert.</param>
|
||||||
|
/// <returns>A byte array containing the S7 date time representations of <paramref name="timeSpans"/>.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
|
||||||
|
/// <paramref name="timeSpans"/> is before <see cref="P:SpecMinimumTimeSpan"/>
|
||||||
|
/// or after <see cref="P:SpecMaximumTimeSpan"/>.</exception>
|
||||||
|
public static byte[] ToByteArray(System.TimeSpan[] timeSpans)
|
||||||
|
{
|
||||||
|
var bytes = new List<byte>(timeSpans.Length * 4);
|
||||||
|
foreach (var timeSpan in timeSpans) bytes.AddRange(ToByteArray(timeSpan));
|
||||||
|
|
||||||
|
return bytes.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
appveyor.yml
13
appveyor.yml
@@ -1,13 +0,0 @@
|
|||||||
image: Visual Studio 2022
|
|
||||||
configuration: Release
|
|
||||||
install:
|
|
||||||
- choco install gitversion.portable -y
|
|
||||||
before_build:
|
|
||||||
- cmd: gitversion /l console /output buildserver
|
|
||||||
- dotnet restore
|
|
||||||
build_script:
|
|
||||||
msbuild /nologo /v:m /p:AssemblyVersion=%GitVersion_AssemblySemVer% /p:FileVersion=%GitVersion_MajorMinorPatch% /p:InformationalVersion=%GitVersion_InformationalVersion% /p:Configuration=%CONFIGURATION% S7.sln
|
|
||||||
after_build:
|
|
||||||
- dotnet pack S7.Net -c %CONFIGURATION% /p:Version=%GitVersion_NuGetVersion% --no-build -o artifacts
|
|
||||||
artifacts:
|
|
||||||
- path: artifacts\*.*
|
|
||||||
Reference in New Issue
Block a user