Merge branch 'main' into s5-date

This commit is contained in:
Michael Croes
2023-09-07 21:20:54 +02:00
committed by GitHub
5 changed files with 421 additions and 10 deletions

View File

@@ -56,28 +56,35 @@ namespace S7.Net
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
const byte szlMethodRequest = 0x11;
const byte szlTypeRequest = 0b100;
const byte szlFunctionGroupCpuFunctions = 0b100;
const byte subFunctionReadSzl = 0x01;
const byte userDataMethodRequest = 0x11;
const byte userDataTypeRequest = 0x4;
// Parameter head
stream.Write(new byte[] { 0x00, 0x01, 0x12 });
// Parameter length
stream.WriteByte(0x04);
// Method
stream.WriteByte(szlMethodRequest);
stream.WriteByte(userDataMethodRequest);
// Type / function group
stream.WriteByte(szlTypeRequest << 4 | szlFunctionGroupCpuFunctions);
stream.WriteByte((byte)(userDataTypeRequest << 4 | (functionGroup & 0x0f)));
// Subfunction
stream.WriteByte(subFunctionReadSzl);
stream.WriteByte(subFunction);
// Sequence number
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
const byte success = 0xff;
@@ -353,7 +360,7 @@ namespace S7.Net
private static byte[] BuildSzlReadRequestPackage(ushort szlId, ushort szlIndex)
{
var stream = new System.IO.MemoryStream();
WriteSzlReadRequest(stream, szlId, szlIndex);
stream.SetLength(stream.Position);

92
S7.Net/Plc.Clock.cs Normal file
View 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}.");
}
}
}

View File

@@ -312,6 +312,35 @@ namespace S7.Net
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>
/// Read the current status from the PLC. A value of 0x08 indicates the PLC is in run status, regardless of the PLC type.
/// </summary>

View File

@@ -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>
/// Read the current status from the PLC. A value of 0x08 indicates the PLC is in run status, regardless of the PLC type.
/// </summary>