Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for serializing TimeSpan #490

Merged
merged 5 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions S7.Net.UnitTest/S7NetTestsAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ public async Task Test_Async_WriteLargeByteArrayWithCancellation()
var db = 2;
randomEngine.NextBytes(data);

cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5));
cancellationSource.CancelAfter(System.TimeSpan.FromMilliseconds(5));
try
{
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
Expand Down Expand Up @@ -1045,7 +1045,7 @@ public async Task Test_Async_WriteLargeByteArrayWithCancellationWithMemory()
var db = 2;
randomEngine.NextBytes(data.Span);

cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5));
cancellationSource.CancelAfter(System.TimeSpan.FromMilliseconds(5));
try
{
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
Expand Down
82 changes: 82 additions & 0 deletions S7.Net.UnitTest/TypeTests/TimeSpanTests.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
}
7 changes: 6 additions & 1 deletion S7.Net/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ public enum VarType
/// <summary>
/// DateTimeLong variable type
/// </summary>
DateTimeLong
DateTimeLong,

/// <summary>
/// S7 TIME variable type - serialized as S7 DInt and deserialized as C# TimeSpan
/// </summary>
Time
}
}
10 changes: 10 additions & 0 deletions S7.Net/PLCHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ private static void BuildReadDataRequestPackage(System.IO.MemoryStream stream, D
{
return DateTimeLong.ToArray(bytes);
}
case VarType.Time:
if (varCount == 1)
{
return TimeSpan.FromByteArray(bytes);
}
else
{
return TimeSpan.ToArray(bytes);
}
default:
return null;
}
Expand Down Expand Up @@ -268,6 +277,7 @@ internal static int VarTypeToByteLength(VarType varType, int varCount = 1)
case VarType.DWord:
case VarType.DInt:
case VarType.Real:
case VarType.Time:
return varCount * 4;
case VarType.LReal:
case VarType.DateTime:
Expand Down
19 changes: 19 additions & 0 deletions S7.Net/Types/Struct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
break;
case "Int32":
case "UInt32":
case "TimeSpan":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
Expand Down Expand Up @@ -215,6 +216,21 @@

numBytes += sData.Length;
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:
var buffer = new byte[GetStructSize(info.FieldType)];
if (buffer.Length == 0)
Expand Down Expand Up @@ -311,6 +327,9 @@
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
};
break;
case "TimeSpan":
bytes2 = TimeSpan.ToByteArray((System.TimeSpan)info.GetValue(structValue));

Check warning on line 331 in S7.Net/Types/Struct.cs

View workflow job for this annotation

GitHub Actions / create_nuget

Unboxing a possibly null value.

Check warning on line 331 in S7.Net/Types/Struct.cs

View workflow job for this annotation

GitHub Actions / create_nuget

Unboxing a possibly null value.

Check warning on line 331 in S7.Net/Types/Struct.cs

View workflow job for this annotation

GitHub Actions / create_nuget

Unboxing a possibly null value.
break;
}
if (bytes2 != null)
{
Expand Down
97 changes: 97 additions & 0 deletions S7.Net/Types/TimeSpan.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}