mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 14:28:25 +08:00
Merge branch 'main' into timespan
This commit is contained in:
54
.github/workflows/dotnet.yml
vendored
54
.github/workflows/dotnet.yml
vendored
@@ -64,77 +64,49 @@ jobs:
|
|||||||
${{ env.NuGetDirectory }}/*.snupkg
|
${{ env.NuGetDirectory }}/*.snupkg
|
||||||
|
|
||||||
run_test:
|
run_test:
|
||||||
name: test-${{ matrix.os }}-${{ matrix.test-framework }}
|
name: test-${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
configuration: Release
|
configuration: Release
|
||||||
artifacts: ${{ github.workspace }}/artifacts
|
artifacts: ${{ github.workspace }}/artifacts
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, ubuntu-22.04, macos-latest]
|
os: [windows-latest, ubuntu-20.04, macos-latest]
|
||||||
test-framework: [net6.0, net7.0]
|
|
||||||
include:
|
|
||||||
- os: ubuntu-22.04
|
|
||||||
test-framework: net6.0
|
|
||||||
installSnap7: true
|
|
||||||
dotnet-sdk: '6.x'
|
|
||||||
- os: ubuntu-22.04
|
|
||||||
test-framework: net7.0
|
|
||||||
installSnap7: true
|
|
||||||
dotnet-sdk: '7.x'
|
|
||||||
- os: macos-latest
|
|
||||||
test-framework: net6.0
|
|
||||||
installSnap7: true
|
|
||||||
dotnet-sdk: '6.x'
|
|
||||||
- os: macos-latest
|
|
||||||
test-framework: net7.0
|
|
||||||
installSnap7: true
|
|
||||||
dotnet-sdk: '7.x'
|
|
||||||
- os: windows-latest
|
|
||||||
test-framework: net6.0
|
|
||||||
dotnet-sdk: '6.x'
|
|
||||||
- os: windows-latest
|
|
||||||
test-framework: net7.0
|
|
||||||
dotnet-sdk: '7.x'
|
|
||||||
- os: windows-latest
|
|
||||||
test-framework: net462
|
|
||||||
dotnet-sdk: '7.x'
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Snap7 Linux
|
- name: Install Snap7 Linux
|
||||||
if: ${{ matrix.installSnap7 && matrix.os == 'ubuntu-20.04' }}
|
if: ${{ matrix.os == 'ubuntu-20.04' }}
|
||||||
run: |
|
run: |
|
||||||
sudo add-apt-repository ppa:gijzelaar/snap7
|
sudo add-apt-repository ppa:gijzelaar/snap7
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libsnap7-1 libsnap7-dev
|
sudo apt-get install libsnap7-1 libsnap7-dev
|
||||||
|
|
||||||
- name: Install Snap7 MacOs
|
- name: Install Snap7 MacOs
|
||||||
if: ${{ matrix.installSnap7 && matrix.os == 'macos-latest' }}
|
if: ${{ matrix.os == 'macos-latest' }}
|
||||||
run: |
|
run: |
|
||||||
brew install snap7
|
brew install snap7
|
||||||
|
|
||||||
- name: Setup Dotnet
|
- name: Setup Dotnet
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ matrix.dotnet-sdk }}
|
dotnet-version: |
|
||||||
|
6.x
|
||||||
|
7.x
|
||||||
|
|
||||||
- name: Nuget Cache
|
- name: Nuget Cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.nuget/packages
|
path: ~/.nuget/packages
|
||||||
# Look to see if there is a cache hit for the corresponding requirements file
|
# Look to see if there is a cache hit for the corresponding requirements file
|
||||||
key: ${{ runner.os }}-${{ matrix.test-framework }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ matrix.test-framework }}-nuget
|
${{ runner.os }}-nuget
|
||||||
|
|
||||||
- name: Restore
|
|
||||||
run: dotnet restore S7.Net.UnitTest
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test --no-restore --nologo --verbosity normal --logger GitHubActions --framework ${{ matrix.test-framework }}
|
run: dotnet test --nologo --verbosity normal --logger GitHubActions
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
# Publish only when creating a GitHub Release
|
# Publish only when creating a GitHub Release
|
||||||
|
|||||||
28
S7.Net.UnitTest/CommunicationTests/ConnectionOpen.cs
Normal file
28
S7.Net.UnitTest/CommunicationTests/ConnectionOpen.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using S7.Net.Protocol;
|
||||||
|
|
||||||
|
namespace S7.Net.UnitTest.CommunicationTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class ConnectionOpen
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Does_Not_Throw()
|
||||||
|
{
|
||||||
|
var cs = new CommunicationSequence {
|
||||||
|
ConnectionOpenTemplates.ConnectionRequestConfirm,
|
||||||
|
ConnectionOpenTemplates.CommunicationSetup
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
conn.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(cs.Serve(out var port), Client(port));
|
||||||
|
}
|
||||||
|
}
|
||||||
107
S7.Net.UnitTest/CommunicationTests/ConnectionOpenTemplates.cs
Normal file
107
S7.Net.UnitTest/CommunicationTests/ConnectionOpenTemplates.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
namespace S7.Net.UnitTest.CommunicationTests;
|
||||||
|
|
||||||
|
internal static class ConnectionOpenTemplates
|
||||||
|
{
|
||||||
|
public static RequestResponsePair ConnectionRequestConfirm { get; } = new RequestResponsePair(
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 // Version
|
||||||
|
00 // Reserved
|
||||||
|
00 16 // Length
|
||||||
|
|
||||||
|
// CR
|
||||||
|
11 // Number of bytes following
|
||||||
|
E0 // CR / Credit
|
||||||
|
00 00 // Destination reference, unused
|
||||||
|
__ __ // Source reference, unused
|
||||||
|
00 // Class / Option
|
||||||
|
|
||||||
|
// Source TSAP
|
||||||
|
C1 // Parameter code
|
||||||
|
02 // Parameter length
|
||||||
|
TSAP_SRC_CHAN // Channel
|
||||||
|
TSAP_SRC_POS // Position
|
||||||
|
|
||||||
|
// Destination TSAP
|
||||||
|
C2 // Parameter code
|
||||||
|
02 // Parameter length
|
||||||
|
TSAP_DEST_CHAN // Channel
|
||||||
|
TSAP_DEST_POS // Position
|
||||||
|
|
||||||
|
// PDU Size parameter
|
||||||
|
C0 // Parameter code
|
||||||
|
01 // Parameter length
|
||||||
|
0A // 1024 byte PDU (2 ^ 10)
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 // Version
|
||||||
|
00 // Reserved
|
||||||
|
00 0B // Length
|
||||||
|
|
||||||
|
// CC
|
||||||
|
06 // Length
|
||||||
|
D0 // CC / Credit
|
||||||
|
00 00 // Destination reference
|
||||||
|
00 00 // Source reference
|
||||||
|
00 // Class / Option
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
public static RequestResponsePair CommunicationSetup { get; } = new RequestResponsePair(
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 // Version
|
||||||
|
00 // Reserved
|
||||||
|
00 19 // Length
|
||||||
|
|
||||||
|
// Data header
|
||||||
|
02 // Length
|
||||||
|
F0 // Data identifier
|
||||||
|
80 // PDU number and end of transmission
|
||||||
|
|
||||||
|
// S7 header
|
||||||
|
32 // Protocol ID
|
||||||
|
01 // Message type job request
|
||||||
|
00 00 // Reserved
|
||||||
|
PDU1 PDU2 // PDU reference
|
||||||
|
00 08 // Parameter length (Communication Setup)
|
||||||
|
00 00 // Data length
|
||||||
|
|
||||||
|
// Communication Setup
|
||||||
|
F0 // Function code
|
||||||
|
00 // Reserved
|
||||||
|
00 03 // Max AMQ caller
|
||||||
|
00 03 // Max AMQ callee
|
||||||
|
03 C0 // PDU size (960)
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 // Version
|
||||||
|
00 // Reserved
|
||||||
|
00 1B // Length
|
||||||
|
|
||||||
|
// Data header
|
||||||
|
02 // Length
|
||||||
|
F0 // Data identifier
|
||||||
|
80 // PDU number and end of transmission
|
||||||
|
|
||||||
|
// S7 header
|
||||||
|
32 // Protocol ID
|
||||||
|
03 // Message type ack data
|
||||||
|
00 00 // Reserved
|
||||||
|
PDU1 PDU2 // PDU reference
|
||||||
|
00 08 // Parameter length (Communication Setup)
|
||||||
|
00 00 // Data length
|
||||||
|
00 // Error class
|
||||||
|
00 // Error code
|
||||||
|
|
||||||
|
// Communication Setup
|
||||||
|
F0 // Function code
|
||||||
|
00 // Reserved
|
||||||
|
00 03 // Max AMQ caller
|
||||||
|
00 03 // Max AMQ callee
|
||||||
|
03 C0 // PDU size (960)
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
}
|
||||||
57
S7.Net.UnitTest/CommunicationTests/ReadPlcStatus.cs
Normal file
57
S7.Net.UnitTest/CommunicationTests/ReadPlcStatus.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using S7.Net.Protocol;
|
||||||
|
|
||||||
|
namespace S7.Net.UnitTest.CommunicationTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class ReadPlcStatus
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Read_Status_Run()
|
||||||
|
{
|
||||||
|
var cs = new CommunicationSequence {
|
||||||
|
ConnectionOpenTemplates.ConnectionRequestConfirm,
|
||||||
|
ConnectionOpenTemplates.CommunicationSetup,
|
||||||
|
{
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 00 00 21
|
||||||
|
|
||||||
|
// COTP
|
||||||
|
02 f0 80
|
||||||
|
|
||||||
|
// S7 SZL read
|
||||||
|
32 07 00 00 PDU1 PDU2 00 08 00 08 00 01 12 04 11 44
|
||||||
|
01 00 ff 09 00 04 04 24 00 00
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
// TPKT
|
||||||
|
03 00 00 3d
|
||||||
|
|
||||||
|
// COTP
|
||||||
|
02 f0 80
|
||||||
|
|
||||||
|
// S7 SZL response
|
||||||
|
32 07 00 00 PDU1 PDU2 00 0c 00 20 00 01 12 08 12 84
|
||||||
|
01 02 00 00 00 00 ff 09 00 1c 04 24 00 00 00 14
|
||||||
|
00 01 51 44 ff 08 00 00 00 00 00 00 00 00 14 08
|
||||||
|
20 12 05 28 34 94
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 status = await conn.ReadStatusAsync();
|
||||||
|
|
||||||
|
Assert.AreEqual(0x08, status);
|
||||||
|
conn.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(cs.Serve(out var port), Client(port));
|
||||||
|
}
|
||||||
|
}
|
||||||
7
S7.Net.UnitTest/Framework/IsExternalInit.cs
Normal file
7
S7.Net.UnitTest/Framework/IsExternalInit.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace System.Runtime.CompilerServices
|
||||||
|
{
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
internal record IsExternalInit;
|
||||||
|
}
|
||||||
82
S7.Net.UnitTest/Infrastructure/CommunicationSequence.cs
Normal file
82
S7.Net.UnitTest/Infrastructure/CommunicationSequence.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace S7.Net.UnitTest;
|
||||||
|
|
||||||
|
internal class CommunicationSequence : IEnumerable<RequestResponsePair>
|
||||||
|
{
|
||||||
|
private readonly List<RequestResponsePair> _requestResponsePairs = new List<RequestResponsePair>();
|
||||||
|
|
||||||
|
public void Add(RequestResponsePair requestResponsePair)
|
||||||
|
{
|
||||||
|
_requestResponsePairs.Add(requestResponsePair);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string requestPattern, string responsePattern)
|
||||||
|
{
|
||||||
|
_requestResponsePairs.Add(new RequestResponsePair(requestPattern, responsePattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<RequestResponsePair> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _requestResponsePairs.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public Task Serve(out int port)
|
||||||
|
{
|
||||||
|
var socket = CreateBoundListenSocket(out port);
|
||||||
|
socket.Listen(0);
|
||||||
|
|
||||||
|
async Task Impl()
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
var socketIn = socket.Accept();
|
||||||
|
|
||||||
|
var buffer = ArrayPool<byte>.Shared.Rent(1024);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var pair in _requestResponsePairs)
|
||||||
|
{
|
||||||
|
var bytesReceived = socketIn.Receive(buffer, SocketFlags.None);
|
||||||
|
|
||||||
|
var received = buffer.Take(bytesReceived).ToArray();
|
||||||
|
Console.WriteLine($"=> {BitConverter.ToString(received)}");
|
||||||
|
|
||||||
|
var response = Responder.Respond(pair, received);
|
||||||
|
|
||||||
|
Console.WriteLine($"<= {BitConverter.ToString(response)}");
|
||||||
|
socketIn.Send(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
socketIn.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Impl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Socket CreateBoundListenSocket(out int port)
|
||||||
|
{
|
||||||
|
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
var endpoint = new IPEndPoint(IPAddress.Loopback, 0);
|
||||||
|
|
||||||
|
socket.Bind(endpoint);
|
||||||
|
|
||||||
|
var localEndpoint = (IPEndPoint)socket.LocalEndPoint!;
|
||||||
|
port = localEndpoint.Port;
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
S7.Net.UnitTest/Infrastructure/RequestResponsePair.cs
Normal file
3
S7.Net.UnitTest/Infrastructure/RequestResponsePair.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace S7.Net.UnitTest;
|
||||||
|
|
||||||
|
internal record RequestResponsePair(string RequestPattern, string ResponsePattern);
|
||||||
80
S7.Net.UnitTest/Infrastructure/Responder.cs
Normal file
80
S7.Net.UnitTest/Infrastructure/Responder.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace S7.Net.UnitTest;
|
||||||
|
|
||||||
|
internal static class Responder
|
||||||
|
{
|
||||||
|
private const string Comment = "//";
|
||||||
|
private static char[] Space = " ".ToCharArray();
|
||||||
|
|
||||||
|
public static byte[] Respond(RequestResponsePair pair, byte[] request)
|
||||||
|
{
|
||||||
|
var offset = 0;
|
||||||
|
var matches = new Dictionary<string, byte>();
|
||||||
|
var res = new List<byte>();
|
||||||
|
using var requestReader = new StringReader(pair.RequestPattern);
|
||||||
|
|
||||||
|
string line;
|
||||||
|
while ((line = requestReader.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
var tokens = line.Split(Space, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
if (token.StartsWith(Comment)) break;
|
||||||
|
|
||||||
|
if (offset >= request.Length)
|
||||||
|
{
|
||||||
|
throw new Exception("Request pattern has more data than request.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var received = request[offset];
|
||||||
|
|
||||||
|
if (token.Length == 2 && byte.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value))
|
||||||
|
{
|
||||||
|
// Number, exact match
|
||||||
|
if (value != received)
|
||||||
|
{
|
||||||
|
throw new Exception($"Incorrect data at offset {offset}. Expected {value:X2}, received {received:X2}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
matches[token] = received;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset != request.Length) throw new Exception("Request contained more data than request pattern.");
|
||||||
|
|
||||||
|
using var responseReader = new StringReader(pair.ResponsePattern);
|
||||||
|
while ((line = responseReader.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
var tokens = line.Split(Space, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
if (token.StartsWith(Comment)) break;
|
||||||
|
|
||||||
|
if (token.Length == 2 && byte.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value))
|
||||||
|
{
|
||||||
|
res.Add(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!matches.TryGetValue(token, out var match))
|
||||||
|
{
|
||||||
|
throw new Exception($"Unmatched token '{token}' in response.");
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Add(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT' ">
|
||||||
<TargetFrameworks>net452;netcoreapp3.1;net5.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
|
||||||
|
<TargetFrameworks>net6.0;net7.0;net462</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
@@ -11,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" />
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ namespace S7.Net
|
|||||||
/// See: https://tools.ietf.org/html/rfc905
|
/// See: https://tools.ietf.org/html/rfc905
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="stream">The socket to read from</param>
|
/// <param name="stream">The socket to read from</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
|
||||||
/// <returns>COTP DPDU instance</returns>
|
/// <returns>COTP DPDU instance</returns>
|
||||||
public static async Task<TPDU> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
public static async Task<TPDU> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -89,6 +90,7 @@ namespace S7.Net
|
|||||||
/// See: https://tools.ietf.org/html/rfc905
|
/// See: https://tools.ietf.org/html/rfc905
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="stream">The stream to read from</param>
|
/// <param name="stream">The stream to read from</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
|
||||||
/// <returns>Data in TSDU</returns>
|
/// <returns>Data in TSDU</returns>
|
||||||
public static async Task<byte[]> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
public static async Task<byte[]> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,29 +9,97 @@ namespace S7.Net
|
|||||||
{
|
{
|
||||||
public partial class Plc
|
public partial class Plc
|
||||||
{
|
{
|
||||||
/// <summary>
|
private static void WriteTpktHeader(System.IO.MemoryStream stream, int length)
|
||||||
/// Creates the header to read bytes from the PLC
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="amount"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1)
|
|
||||||
{
|
{
|
||||||
//header size = 19 bytes
|
|
||||||
stream.Write(new byte[] { 0x03, 0x00 });
|
stream.Write(new byte[] { 0x03, 0x00 });
|
||||||
//complete package size
|
stream.Write(Word.ToByteArray((ushort) length));
|
||||||
stream.Write(Int.ToByteArray((short)(19 + (12 * amount))));
|
}
|
||||||
stream.Write(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 });
|
|
||||||
//data part size
|
private static void WriteDataHeader(System.IO.MemoryStream stream)
|
||||||
stream.Write(Word.ToByteArray((ushort)(2 + (amount * 12))));
|
{
|
||||||
stream.Write(new byte[] { 0x00, 0x00, 0x04 });
|
stream.Write(new byte[] { 0x02, 0xf0, 0x80 });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteS7Header(System.IO.MemoryStream stream, byte messageType, int parameterLength, int dataLength)
|
||||||
|
{
|
||||||
|
stream.WriteByte(0x32); // S7 protocol ID
|
||||||
|
stream.WriteByte(messageType); // Message type
|
||||||
|
stream.Write(new byte[] { 0x00, 0x00 }); // Reserved
|
||||||
|
stream.Write(new byte[] { 0x00, 0x00 }); // PDU ref
|
||||||
|
stream.Write(Word.ToByteArray((ushort) parameterLength));
|
||||||
|
stream.Write(Word.ToByteArray((ushort) dataLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the header to read bytes from the PLC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to write to.</param>
|
||||||
|
/// <param name="amount">The number of items to read.</param>
|
||||||
|
private static void WriteReadHeader(System.IO.MemoryStream stream, int amount = 1)
|
||||||
|
{
|
||||||
|
// Header size 19, 12 bytes per item
|
||||||
|
WriteTpktHeader(stream, 19 + 12 * amount);
|
||||||
|
WriteDataHeader(stream);
|
||||||
|
WriteS7Header(stream, 0x01, 2 + 12 * amount, 0);
|
||||||
|
// Function code: read request
|
||||||
|
stream.WriteByte(0x04);
|
||||||
//amount of requests
|
//amount of requests
|
||||||
stream.WriteByte((byte)amount);
|
stream.WriteByte((byte)amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void WriteUserDataHeader(System.IO.MemoryStream stream, int parameterLength, int dataLength)
|
||||||
|
{
|
||||||
|
const byte s7MessageTypeUserData = 0x07;
|
||||||
|
|
||||||
|
WriteTpktHeader(stream, 17 + parameterLength + dataLength);
|
||||||
|
WriteDataHeader(stream);
|
||||||
|
WriteS7Header(stream, s7MessageTypeUserData, parameterLength, dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteSzlReadRequest(System.IO.MemoryStream stream, ushort szlId, ushort szlIndex)
|
||||||
|
{
|
||||||
|
WriteUserDataHeader(stream, 8, 8);
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
const byte szlMethodRequest = 0x11;
|
||||||
|
const byte szlTypeRequest = 0b100;
|
||||||
|
const byte szlFunctionGroupCpuFunctions = 0b100;
|
||||||
|
const byte subFunctionReadSzl = 0x01;
|
||||||
|
|
||||||
|
// Parameter head
|
||||||
|
stream.Write(new byte[] { 0x00, 0x01, 0x12 });
|
||||||
|
// Parameter length
|
||||||
|
stream.WriteByte(0x04);
|
||||||
|
// Method
|
||||||
|
stream.WriteByte(szlMethodRequest);
|
||||||
|
// Type / function group
|
||||||
|
stream.WriteByte(szlTypeRequest << 4 | szlFunctionGroupCpuFunctions);
|
||||||
|
// Subfunction
|
||||||
|
stream.WriteByte(subFunctionReadSzl);
|
||||||
|
// Sequence number
|
||||||
|
stream.WriteByte(0);
|
||||||
|
|
||||||
|
// Data
|
||||||
|
const byte success = 0xff;
|
||||||
|
const byte transportSizeOctetString = 0x09;
|
||||||
|
|
||||||
|
// Return code
|
||||||
|
stream.WriteByte(success);
|
||||||
|
// Transport size
|
||||||
|
stream.WriteByte(transportSizeOctetString);
|
||||||
|
// Length
|
||||||
|
stream.Write(Word.ToByteArray(4));
|
||||||
|
// SZL-ID
|
||||||
|
stream.Write(Word.ToByteArray(szlId));
|
||||||
|
// SZL-Index
|
||||||
|
stream.Write(Word.ToByteArray(szlIndex));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType),
|
/// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType),
|
||||||
/// the address of the memory, the address of the byte and the bytes count.
|
/// the address of the memory, the address of the byte and the bytes count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to write the read data request to.</param>
|
||||||
/// <param name="dataType">MemoryType (DB, Timer, Counter, etc.)</param>
|
/// <param name="dataType">MemoryType (DB, Timer, Counter, etc.)</param>
|
||||||
/// <param name="db">Address of the memory to be read</param>
|
/// <param name="db">Address of the memory to be read</param>
|
||||||
/// <param name="startByteAdr">Start address of the byte</param>
|
/// <param name="startByteAdr">Start address of the byte</param>
|
||||||
@@ -262,7 +330,7 @@ namespace S7.Net
|
|||||||
int packageSize = 19 + (dataItems.Count * 12);
|
int packageSize = 19 + (dataItems.Count * 12);
|
||||||
var package = new System.IO.MemoryStream(packageSize);
|
var package = new System.IO.MemoryStream(packageSize);
|
||||||
|
|
||||||
BuildHeaderPackage(package, dataItems.Count);
|
WriteReadHeader(package, dataItems.Count);
|
||||||
|
|
||||||
foreach (var dataItem in dataItems)
|
foreach (var dataItem in dataItems)
|
||||||
{
|
{
|
||||||
@@ -271,5 +339,15 @@ namespace S7.Net
|
|||||||
|
|
||||||
return package.ToArray();
|
return package.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] BuildSzlReadRequestPackage(ushort szlId, ushort szlIndex)
|
||||||
|
{
|
||||||
|
var stream = new System.IO.MemoryStream();
|
||||||
|
|
||||||
|
WriteSzlReadRequest(stream, szlId, szlIndex);
|
||||||
|
stream.SetLength(stream.Position);
|
||||||
|
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,20 @@ namespace S7.Net
|
|||||||
return dataItems;
|
return dataItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <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 status on completion.</returns>
|
||||||
|
public async Task<byte> ReadStatusAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var dataToSend = BuildSzlReadRequestPackage(0x0424, 0);
|
||||||
|
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
|
||||||
|
|
||||||
|
return (byte) (s7data[37] & 0x0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
/// 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.
|
/// If the write was not successful, check LastErrorCode or LastErrorString.
|
||||||
@@ -428,7 +442,6 @@ namespace S7.Net
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
||||||
/// If the write was not successful, check <see cref="LastErrorCode"/> or <see cref="LastErrorString"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
|
/// <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="value">Value to be written to the PLC</param>
|
||||||
@@ -507,6 +520,7 @@ 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="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="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="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
|
||||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||||
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, ReadOnlyMemory<byte> value, CancellationToken cancellationToken)
|
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, ReadOnlyMemory<byte> value, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -289,7 +289,6 @@ namespace S7.Net
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
||||||
/// If the write was not successful, check <see cref="LastErrorCode"/> or <see cref="LastErrorString"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
|
/// <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="value">Value to be written to the PLC</param>
|
||||||
@@ -329,7 +328,7 @@ namespace S7.Net
|
|||||||
const int packageSize = 19 + 12; // 19 header + 12 for 1 request
|
const int packageSize = 19 + 12; // 19 header + 12 for 1 request
|
||||||
var dataToSend = new byte[packageSize];
|
var dataToSend = new byte[packageSize];
|
||||||
var package = new MemoryStream(dataToSend);
|
var package = new MemoryStream(dataToSend);
|
||||||
BuildHeaderPackage(package);
|
WriteReadHeader(package);
|
||||||
// package.Add(0x02); // datenart
|
// package.Add(0x02); // datenart
|
||||||
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, buffer.Length);
|
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, buffer.Length);
|
||||||
|
|
||||||
@@ -474,7 +473,7 @@ namespace S7.Net
|
|||||||
int packageSize = 19 + (dataItems.Count * 12);
|
int packageSize = 19 + (dataItems.Count * 12);
|
||||||
var dataToSend = new byte[packageSize];
|
var dataToSend = new byte[packageSize];
|
||||||
var package = new MemoryStream(dataToSend);
|
var package = new MemoryStream(dataToSend);
|
||||||
BuildHeaderPackage(package, dataItems.Count);
|
WriteReadHeader(package, dataItems.Count);
|
||||||
// package.Add(0x02); // datenart
|
// package.Add(0x02); // datenart
|
||||||
foreach (var dataItem in dataItems)
|
foreach (var dataItem in dataItems)
|
||||||
{
|
{
|
||||||
@@ -493,6 +492,18 @@ namespace S7.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <returns>The current PLC status.</returns>
|
||||||
|
public byte ReadStatus()
|
||||||
|
{
|
||||||
|
var dataToSend = BuildSzlReadRequestPackage(0x0424, 0);
|
||||||
|
var s7data = RequestTsdu(dataToSend);
|
||||||
|
|
||||||
|
return (byte) (s7data[37] & 0x0f);
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] RequestTsdu(byte[] requestData) => RequestTsdu(requestData, 0, requestData.Length);
|
private byte[] RequestTsdu(byte[] requestData) => RequestTsdu(requestData, 0, requestData.Length);
|
||||||
|
|
||||||
private byte[] RequestTsdu(byte[] requestData, int offset, int length)
|
private byte[] RequestTsdu(byte[] requestData, int offset, int length)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net452;netstandard2.0;netstandard1.3;net5.0</TargetFrameworks>
|
<TargetFrameworks>net452;net462;netstandard2.0;netstandard1.3;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
|
||||||
<InternalsVisibleTo>S7.Net.UnitTest</InternalsVisibleTo>
|
<InternalsVisibleTo>S7.Net.UnitTest</InternalsVisibleTo>
|
||||||
@@ -15,19 +15,20 @@
|
|||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<PackageTags>PLC Siemens Communication S7</PackageTags>
|
<PackageTags>PLC Siemens Communication S7</PackageTags>
|
||||||
<Copyright>Derek Heiser 2015</Copyright>
|
<Copyright>Derek Heiser 2015</Copyright>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>Enable</Nullable>
|
<Nullable>Enable</Nullable>
|
||||||
<DebugType>portable</DebugType>
|
<DebugType>portable</DebugType>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net452' Or '$(TargetFramework)' == 'netstandard2.0' ">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net452' Or '$(TargetFramework)' == 'net462' Or '$(TargetFramework)' == 'netstandard2.0' ">
|
||||||
<DefineConstants>NET_FULL</DefineConstants>
|
<DefineConstants>NET_FULL</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
|
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0' And '$(TargetFramework)' != 'net6.0' And '$(TargetFramework)' != 'net7.0'">
|
||||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ namespace S7.Net
|
|||||||
/// <param name="buffer">the buffer to read into</param>
|
/// <param name="buffer">the buffer to read into</param>
|
||||||
/// <param name="offset">the offset in the buffer to read into</param>
|
/// <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>
|
/// <param name="count">the amount of bytes to read into the buffer</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
|
||||||
/// <returns>returns the amount of read bytes</returns>
|
/// <returns>returns the amount of read bytes</returns>
|
||||||
public static async Task<int> ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public static async Task<int> ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace S7.Net
|
|||||||
/// Reads a TPKT from the socket Async
|
/// Reads a TPKT from the socket Async
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="stream">The stream to read from</param>
|
/// <param name="stream">The stream to read from</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
|
||||||
/// <returns>Task TPKT Instace</returns>
|
/// <returns>Task TPKT Instace</returns>
|
||||||
public static async Task<TPKT> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
public static async Task<TPKT> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ namespace S7.Net.Types
|
|||||||
numBytes += attribute.ReservedLengthInBytes;
|
numBytes += attribute.ReservedLengthInBytes;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
var propertyClass = Activator.CreateInstance(type);
|
var propertyClass = Activator.CreateInstance(type) ??
|
||||||
|
throw new ArgumentException($"Failed to create instance of type {type}.", nameof(type));
|
||||||
numBytes = GetClassSize(propertyClass, numBytes, true);
|
numBytes = GetClassSize(propertyClass, numBytes, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -76,6 +77,8 @@ namespace S7.Net.Types
|
|||||||
/// Gets the size of the class in bytes.
|
/// Gets the size of the class in bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="instance">An instance of the class</param>
|
/// <param name="instance">An instance of the class</param>
|
||||||
|
/// <param name="numBytes">The offset of the current field.</param>
|
||||||
|
/// <param name="isInnerProperty"><see langword="true" /> if this property belongs to a class being serialized as member of the class requested for serialization; otherwise, <see langword="false" />.</param>
|
||||||
/// <returns>the number of bytes</returns>
|
/// <returns>the number of bytes</returns>
|
||||||
public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false)
|
public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false)
|
||||||
{
|
{
|
||||||
@@ -84,8 +87,10 @@ namespace S7.Net.Types
|
|||||||
{
|
{
|
||||||
if (property.PropertyType.IsArray)
|
if (property.PropertyType.IsArray)
|
||||||
{
|
{
|
||||||
Type elementType = property.PropertyType.GetElementType();
|
Type elementType = property.PropertyType.GetElementType()!;
|
||||||
Array array = (Array)property.GetValue(instance, null);
|
Array array = (Array?) property.GetValue(instance, null) ??
|
||||||
|
throw new ArgumentException($"Property {property.Name} on {instance} must have a non-null value to get it's size.", nameof(instance));
|
||||||
|
|
||||||
if (array.Length <= 0)
|
if (array.Length <= 0)
|
||||||
{
|
{
|
||||||
throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero.");
|
throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero.");
|
||||||
@@ -199,7 +204,9 @@ namespace S7.Net.Types
|
|||||||
numBytes += sData.Length;
|
numBytes += sData.Length;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
var propClass = Activator.CreateInstance(propertyType);
|
var propClass = Activator.CreateInstance(propertyType) ??
|
||||||
|
throw new ArgumentException($"Failed to create instance of type {propertyType}.", nameof(propertyType));
|
||||||
|
|
||||||
numBytes = FromBytes(propClass, bytes, numBytes);
|
numBytes = FromBytes(propClass, bytes, numBytes);
|
||||||
value = propClass;
|
value = propClass;
|
||||||
break;
|
break;
|
||||||
@@ -213,6 +220,8 @@ namespace S7.Net.Types
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sourceClass">The object to fill in the given array of bytes</param>
|
/// <param name="sourceClass">The object to fill in the given array of bytes</param>
|
||||||
/// <param name="bytes">The array of bytes</param>
|
/// <param name="bytes">The array of bytes</param>
|
||||||
|
/// <param name="numBytes">The offset for the current field.</param>
|
||||||
|
/// <param name="isInnerClass"><see langword="true" /> if this class is the type of a member of the class to be serialized; otherwise, <see langword="false" />.</param>
|
||||||
public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false)
|
public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false)
|
||||||
{
|
{
|
||||||
if (bytes == null)
|
if (bytes == null)
|
||||||
@@ -223,9 +232,11 @@ namespace S7.Net.Types
|
|||||||
{
|
{
|
||||||
if (property.PropertyType.IsArray)
|
if (property.PropertyType.IsArray)
|
||||||
{
|
{
|
||||||
Array array = (Array)property.GetValue(sourceClass, null);
|
Array array = (Array?) property.GetValue(sourceClass, null) ??
|
||||||
|
throw new ArgumentException($"Property {property.Name} on sourceClass must be an array instance.", nameof(sourceClass));
|
||||||
|
|
||||||
IncrementToEven(ref numBytes);
|
IncrementToEven(ref numBytes);
|
||||||
Type elementType = property.PropertyType.GetElementType();
|
Type elementType = property.PropertyType.GetElementType()!;
|
||||||
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
||||||
{
|
{
|
||||||
array.SetValue(
|
array.SetValue(
|
||||||
@@ -320,26 +331,30 @@ namespace S7.Net.Types
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a byte array depending on the struct type.
|
/// Creates a byte array depending on the struct type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sourceClass">The struct object</param>
|
/// <param name="sourceClass">The struct object.</param>
|
||||||
|
/// <param name="bytes">The target byte array.</param>
|
||||||
|
/// <param name="numBytes">The offset for the current field.</param>
|
||||||
/// <returns>A byte array or null if fails.</returns>
|
/// <returns>A byte array or null if fails.</returns>
|
||||||
public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
|
public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
|
||||||
{
|
{
|
||||||
var properties = GetAccessableProperties(sourceClass.GetType());
|
var properties = GetAccessableProperties(sourceClass.GetType());
|
||||||
foreach (var property in properties)
|
foreach (var property in properties)
|
||||||
{
|
{
|
||||||
|
var value = property.GetValue(sourceClass, null) ??
|
||||||
|
throw new ArgumentException($"Property {property.Name} on sourceClass can't be null.", nameof(sourceClass));
|
||||||
|
|
||||||
if (property.PropertyType.IsArray)
|
if (property.PropertyType.IsArray)
|
||||||
{
|
{
|
||||||
Array array = (Array)property.GetValue(sourceClass, null);
|
Array array = (Array) value;
|
||||||
IncrementToEven(ref numBytes);
|
IncrementToEven(ref numBytes);
|
||||||
Type elementType = property.PropertyType.GetElementType();
|
|
||||||
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
||||||
{
|
{
|
||||||
numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes);
|
numBytes = SetBytesFromProperty(array.GetValue(i)!, property, bytes, numBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes);
|
numBytes = SetBytesFromProperty(value, property, bytes, numBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return numBytes;
|
return numBytes;
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ namespace S7.Net.Types
|
|||||||
/// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
|
/// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dateTimes">The DateTime values to convert.</param>
|
/// <param name="dateTimes">The DateTime values to convert.</param>
|
||||||
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTime"/>.</returns>
|
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTimes"/>.</returns>
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
|
||||||
/// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
|
/// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
|
||||||
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ namespace S7.Net.Types
|
|||||||
/// An S7 String has a preceeding 2 byte header containing its capacity and length
|
/// An S7 String has a preceeding 2 byte header containing its capacity and length
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class S7String
|
public static class S7String
|
||||||
{
|
{
|
||||||
private static Encoding stringEncoding = Encoding.ASCII;
|
private static Encoding stringEncoding = Encoding.ASCII;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Encoding used when serializing and deserializing S7String (Encoding.ASCII by default)
|
/// The Encoding used when serializing and deserializing S7String (Encoding.ASCII by default)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="ArgumentNullException">StringEncoding must not be null</exception>
|
/// <exception cref="ArgumentNullException">StringEncoding must not be null</exception>
|
||||||
public static Encoding StringEncoding
|
public static Encoding StringEncoding
|
||||||
{
|
{
|
||||||
get => stringEncoding;
|
get => stringEncoding;
|
||||||
set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding));
|
set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,7 +58,7 @@ namespace S7.Net.Types
|
|||||||
/// <param name="value">The string to convert to byte array.</param>
|
/// <param name="value">The string to convert to byte array.</param>
|
||||||
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
|
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
|
||||||
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 2.</returns>
|
/// <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)
|
public static byte[] ToByteArray(string? value, int reservedLength)
|
||||||
{
|
{
|
||||||
if (value is null)
|
if (value is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace S7.Net.Types
|
|||||||
/// <param name="value">The string to convert to byte array.</param>
|
/// <param name="value">The string to convert to byte array.</param>
|
||||||
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
|
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
|
||||||
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 4.</returns>
|
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 4.</returns>
|
||||||
public static byte[] ToByteArray(string value, int reservedLength)
|
public static byte[] ToByteArray(string? value, int reservedLength)
|
||||||
{
|
{
|
||||||
if (value is null)
|
if (value is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,13 +12,15 @@
|
|||||||
/// <param name="reservedLength">The amount of bytes reserved for the <paramref name="value"/> in the PLC.</param>
|
/// <param name="reservedLength">The amount of bytes reserved for the <paramref name="value"/> in the PLC.</param>
|
||||||
public static byte[] ToByteArray(string value, int reservedLength)
|
public static byte[] ToByteArray(string value, int reservedLength)
|
||||||
{
|
{
|
||||||
var length = value?.Length;
|
|
||||||
if (length > reservedLength) length = reservedLength;
|
|
||||||
var bytes = new byte[reservedLength];
|
var bytes = new byte[reservedLength];
|
||||||
|
if (value == null) return bytes;
|
||||||
|
|
||||||
if (length == null || length == 0) return bytes;
|
var length = value.Length;
|
||||||
|
if (length == 0) return bytes;
|
||||||
|
|
||||||
System.Text.Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 0);
|
if (length > reservedLength) length = reservedLength;
|
||||||
|
|
||||||
|
System.Text.Encoding.ASCII.GetBytes(value, 0, length, bytes, 0);
|
||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ namespace S7.Net.Types
|
|||||||
int bytePos = 0;
|
int bytePos = 0;
|
||||||
int bitPos = 0;
|
int bitPos = 0;
|
||||||
double numBytes = 0.0;
|
double numBytes = 0.0;
|
||||||
object structValue = Activator.CreateInstance(structType);
|
object structValue = Activator.CreateInstance(structType) ??
|
||||||
|
throw new ArgumentException($"Failed to create an instance of the type {structType}.", nameof(structType));
|
||||||
|
|
||||||
var infos = structValue.GetType()
|
var infos = structValue.GetType()
|
||||||
#if NETSTANDARD1_3
|
#if NETSTANDARD1_3
|
||||||
@@ -270,6 +270,14 @@ namespace S7.Net.Types
|
|||||||
|
|
||||||
foreach (var info in infos)
|
foreach (var info in infos)
|
||||||
{
|
{
|
||||||
|
static TValue GetValueOrThrow<TValue>(FieldInfo fi, object structValue) where TValue : struct
|
||||||
|
{
|
||||||
|
var value = fi.GetValue(structValue) as TValue? ??
|
||||||
|
throw new ArgumentException($"Failed to convert value of field {fi.Name} of {structValue} to type {typeof(TValue)}");
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
bytes2 = null;
|
bytes2 = null;
|
||||||
switch (info.FieldType.Name)
|
switch (info.FieldType.Name)
|
||||||
{
|
{
|
||||||
@@ -277,7 +285,7 @@ namespace S7.Net.Types
|
|||||||
// get the value
|
// get the value
|
||||||
bytePos = (int)Math.Floor(numBytes);
|
bytePos = (int)Math.Floor(numBytes);
|
||||||
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
|
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
|
||||||
if ((bool)info.GetValue(structValue))
|
if (GetValueOrThrow<bool>(info, structValue))
|
||||||
bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
|
bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
|
||||||
else
|
else
|
||||||
bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
|
bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
|
||||||
@@ -286,26 +294,26 @@ namespace S7.Net.Types
|
|||||||
case "Byte":
|
case "Byte":
|
||||||
numBytes = (int)Math.Ceiling(numBytes);
|
numBytes = (int)Math.Ceiling(numBytes);
|
||||||
bytePos = (int)numBytes;
|
bytePos = (int)numBytes;
|
||||||
bytes[bytePos] = (byte)info.GetValue(structValue);
|
bytes[bytePos] = GetValueOrThrow<byte>(info, structValue);
|
||||||
numBytes++;
|
numBytes++;
|
||||||
break;
|
break;
|
||||||
case "Int16":
|
case "Int16":
|
||||||
bytes2 = Int.ToByteArray((Int16)info.GetValue(structValue));
|
bytes2 = Int.ToByteArray(GetValueOrThrow<short>(info, structValue));
|
||||||
break;
|
break;
|
||||||
case "UInt16":
|
case "UInt16":
|
||||||
bytes2 = Word.ToByteArray((UInt16)info.GetValue(structValue));
|
bytes2 = Word.ToByteArray(GetValueOrThrow<ushort>(info, structValue));
|
||||||
break;
|
break;
|
||||||
case "Int32":
|
case "Int32":
|
||||||
bytes2 = DInt.ToByteArray((Int32)info.GetValue(structValue));
|
bytes2 = DInt.ToByteArray(GetValueOrThrow<int>(info, structValue));
|
||||||
break;
|
break;
|
||||||
case "UInt32":
|
case "UInt32":
|
||||||
bytes2 = DWord.ToByteArray((UInt32)info.GetValue(structValue));
|
bytes2 = DWord.ToByteArray(GetValueOrThrow<uint>(info, structValue));
|
||||||
break;
|
break;
|
||||||
case "Single":
|
case "Single":
|
||||||
bytes2 = Real.ToByteArray((float)info.GetValue(structValue));
|
bytes2 = Real.ToByteArray(GetValueOrThrow<float>(info, structValue));
|
||||||
break;
|
break;
|
||||||
case "Double":
|
case "Double":
|
||||||
bytes2 = LReal.ToByteArray((double)info.GetValue(structValue));
|
bytes2 = LReal.ToByteArray(GetValueOrThrow<double>(info, structValue));
|
||||||
break;
|
break;
|
||||||
case "String":
|
case "String":
|
||||||
S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
|
S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
|
||||||
@@ -314,8 +322,8 @@ namespace S7.Net.Types
|
|||||||
|
|
||||||
bytes2 = attribute.Type switch
|
bytes2 = attribute.Type switch
|
||||||
{
|
{
|
||||||
S7StringType.S7String => S7String.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength),
|
S7StringType.S7String => S7String.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
|
||||||
S7StringType.S7WString => S7WString.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength),
|
S7StringType.S7WString => S7WString.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
|
||||||
_ => 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user