mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 14:28:25 +08:00
Merge branch 'main' into s5-date
This commit is contained in:
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user