27 Commits

Author SHA1 Message Date
Michael Croes
3b2dbd1148 Release S7NetPlus 0.7.0
Release highlights:
- Nullable reference type support
- Cleanups of TSDU and TPDU message parsing
- Better exceptions for connection Open() errors
- Support for buffered reads
2020-09-04 20:51:29 +02:00
Michael Croes
3c91aa02b0 Merge pull request #291 from scamille/fb-readFixed
Fixed size reads version 2
2020-09-03 23:46:42 +02:00
Serge Camille
edfa208c3e Simplify TPKT Read buffer length arguments and improve exception message. 2020-09-03 20:48:18 +02:00
Serge Camille
d11f46eedb Rename ReadFixed to ReadExact, separate Unit test for ReadExact. 2020-09-03 20:48:18 +02:00
Serge Camille
09c8b18d3d Adjust ReadFixed implementation somewhat. Exceeding the length of the buffer was already an error before.
Change the tests by replacing the memory buffer with a Fake stream giving 1 byte at a time.
2020-09-03 20:48:18 +02:00
Jakob Ledermann
10e5562706 Read desired amount from stream. see #273
The read method on a networkstream blocks only until the first byte is available.
It is not guaranteed to read the desired count into the buffer.

This solution tries to read the remaining bytes in a loop and aborts once the
full count is read or the Stream.Read method returns with 0 or less bytes read.

The synchronous read can block indefinitly if the lenght field is larger than the send package.
2020-09-03 20:48:17 +02:00
Jakob Ledermann
7d570f93c1 Unittests for issue #283
These unittests fail intentionally as the issue discussion has not reached
a consesual solution. Any solution should pass the unittests added in this commit.
2020-09-03 20:48:17 +02:00
Michael Croes
c79ae13ea1 Merge pull request #285 from scamille/fb-var
Various binary data adjustments, small code refactoring
2020-09-03 20:18:57 +02:00
Serge Camille
09851ec30b Optimize ReadBytes by copying less data around. 2020-09-02 20:17:09 +02:00
Serge Camille
592d21c3aa Add some response length checks in connection Open()
I don't know what the correct expected connection response size is, so I just added checks for the minimal index access by the current code.

This change will just change NullReferenceExceptions into WrongNumberOfBytesException when the PLC response with not enough data for a connection attempt.
2020-09-02 20:16:53 +02:00
Serge Camille
d530f1e422 COTP TPDU: change byte array copy.
This removes the binary reader, and fixes too things:

1. Properly set the data length (previous implementation requested too much, but that did not matter with BinaryReader)
2. Start reading Data after HeaderLength+1 offset, not always at 3.
2020-09-02 20:16:53 +02:00
Serge Camille
ba3dd084cb TSDU: Return data early without copying when there is only 1 segment. 2020-09-02 20:16:53 +02:00
Serge Camille
a047c5bba4 Fix chunk size for WriteBytesAsync.
The previous problem of not being able to use a larger chunk size than 200 was that the complete header building code for the async implementation was incorrect.  Specifically, it wrote the package size only as a byte instead of a int16, thus restricting the package size to something < 256.

With the sharing of the Header building code in the previous commits, this problem was resolved by accident, and thus the chunk size can be increased to the maximum value allowed by the PDUSize.
2020-09-02 20:16:53 +02:00
Serge Camille
2bb7ac7d2a Write large byte array in S7NetTests to check for MaxPDU problems on Snap7.
For me the tests work fine even when adjust the "chunk size" in WriteBytesAsync. So Snap7 seems to be fine, at least the current version.

This should probably be tested with some live PLC's as well.
2020-09-02 20:16:53 +02:00
Serge Camille
783c456dc9 Copy ReadBytes data in one go.
Instead of using a loop, use Array.Copy to
2020-09-02 20:16:52 +02:00
Serge Camille
688d4e2a28 Change implementation header package creation for reading bytes.
Use MemoryStream as well.
2020-09-02 20:16:19 +02:00
Serge Camille
bb0b57c574 Add method BuildWriteBitPackage
Merges data creation between sync and async for writing bit values.
2020-09-02 20:16:17 +02:00
Serge Camille
2f07d43062 Change BuildWriteBytesPackage to use MemoryStream. 2020-09-02 20:13:18 +02:00
Serge Camille
4ef037881c Simplify WriteBytes functiosn by merging common BuildPackage code.
Both Synchronous and Asynchronous need to build the same binary data package to write a bytes array. Move that package building out into a common function.

Also use IEnumerable to pass in data instead of converting it to array and back multiple times. Not that happy with the whole ByteArray class, we could probably just use a MemoryStream instead.
2020-09-02 20:11:35 +02:00
Serge Camille
fbd8a13c6c ReadBytesAsync: Replace list with a plain byte array. 2020-09-02 20:09:51 +02:00
Serge Camille
2a451bc049 TSDU: Use Array.Copy for Read functions. 2020-09-02 20:09:51 +02:00
Michael Croes
324ae95c42 Merge pull request #293 from scamille/fb-nullable
Add nullable support throughout the whole library.
2020-09-01 21:06:52 +02:00
Serge Camille
bd8177d39e Add nullable support throughout the whole library.
This requires reference types that can be null to be annotated by a ? operator, similar to value types.

This gives the advantage that the compiler can warn against any null dereference exceptions, of which this commits elimits a few.

To make the underlying protocol implementation not any more complicated and to eliminate existing problems, and not that precise error reporting, I replaced some return null statements with explicit Exceptions. This lead to the assumption that those core protocoll functions always return non-null objects if they do not throw, making the PLC code simpler.

Adjust some NotConnected tests to look for explicit PlcException instead of NullReferenceException.
2020-08-31 22:48:45 +02:00
Michael Croes
e68ca64596 Merge pull request #302 from mycroes/build-infra-update
AppVeyor and infra updates
2020-08-31 22:31:03 +02:00
Michael Croes
647d4c7ae2 AppVeyor and infra updates
- Change build image to VS 2019
- Update Microsoft.SourceLink.GitHub package
- Update solution file
- Build separate symbol package (.snupkg)
2020-08-31 22:28:59 +02:00
Michael Croes
c7ef055be2 Merge pull request #286 from scamille/fb-sdkTestProject
Convert UnitTest project to SDK project type.
2020-08-18 22:09:39 +02:00
Serge Camille
7035d22506 Convert UnitTest project to SDK project type. 2020-08-18 09:20:31 +02:00
25 changed files with 587 additions and 448 deletions

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("S7.Net.UnitTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("S7Net.UnitTest")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6f73e1b1-301b-471e-9f38-3dcbddbcfc21")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -34,13 +34,14 @@ namespace S7.Net.UnitTest
var t = TPKT.Read(m);
}
[TestMethod]
[ExpectedException(typeof(TPKTInvalidException))]
public async Task TPKT_ReadShortAsync()
{
var m = new MemoryStream(StringToByteArray("0300002902f0803203000000010002001400000401ff040080"));
var t = await TPKT.ReadAsync(m);
}
}
[TestMethod]
public void COTP_ReadTSDU()
@@ -54,7 +55,7 @@ namespace S7.Net.UnitTest
Assert.IsTrue(expected.SequenceEqual(t));
}
private static byte[] StringToByteArray(string hex)
public static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
@@ -62,4 +63,5 @@ namespace S7.Net.UnitTest
.ToArray();
}
}
}

View File

@@ -1,130 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{303CCED6-9ABC-4899-A509-743341AAA804}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>S7.Net.UnitTest</RootNamespace>
<AssemblyName>S7Net.UnitTest</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452</TargetFrameworks>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
<IsPackable>false</IsPackable>
<Copyright>Copyright © 2014</Copyright>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>S7.Net.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\S7.Net\S7.Net.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="ConnectionRequestTest.cs" />
<Compile Include="ConvertersUnitTest.cs" />
<Compile Include="Helpers\TestClassWithNestedClass.cs" />
<Compile Include="PLCAddressParsingTests.cs" />
<Compile Include="ProtocolTests.cs" />
<Compile Include="Helpers\ConsoleManager.cs" />
<Compile Include="Helpers\NativeMethods.cs" />
<Compile Include="Helpers\S7TestServer.cs" />
<Compile Include="Helpers\TestClassWithArrays.cs" />
<Compile Include="Helpers\TestClassWithCustomType.cs" />
<Compile Include="Helpers\TestClassWithPrivateSetters.cs" />
<Compile Include="Helpers\TestLongClass.cs" />
<Compile Include="S7NetTestsAsync.cs" />
<Compile Include="Helpers\TestSmallClass.cs" />
<Compile Include="Snap7\snap7.net.cs" />
<Compile Include="Helpers\TestClass.cs" />
<Compile Include="Helpers\TestStruct.cs" />
<Compile Include="S7NetTestsSync.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\TestLongStruct.cs" />
<Compile Include="TypeTests\ClassTests.cs" />
<Compile Include="TypeTests\DateTimeLongTests.cs" />
<Compile Include="TypeTests\DateTimeTests.cs" />
<Compile Include="TypeTests\StringExTests.cs" />
<Compile Include="TypeTests\StringTests.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="snap7.dll">
<None Update="snap7.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\S7.Net\S7.Net.csproj">
<Project>{bfd484f9-3f04-42a2-bf2a-60a189a25dcf}</Project>
<Name>S7.Net</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="S7.Net.snk" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -128,6 +128,26 @@ namespace S7.Net.UnitTest
Assert.AreEqual(val3, result3);
}
/// <summary>
/// Write/Read a large amount of data to test PDU max
/// </summary>
[TestMethod]
public async Task Test_Async_WriteLargeByteArray()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
var randomEngine = new Random();
var data = new byte[8192];
var db = 2;
randomEngine.NextBytes(data);
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data);
var readData = await plc.ReadBytesAsync(DataType.DataBlock, db, 0, data.Length);
CollectionAssert.AreEqual(data, readData);
}
/// <summary>
/// Read/Write a class that has the same properties of a DB with the same field in the same order
/// </summary>
@@ -591,15 +611,13 @@ namespace S7.Net.UnitTest
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadBytesReturnsNullIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
TestClass tc = new TestClass();
var res = await notConnectedPlc.ReadClassAsync(tc, DB2);
Assert.Fail();
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadClassAsync(tc, DB2));
}
}
@@ -637,13 +655,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadClassWithGenericReturnsNullIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
TestClass tc = await notConnectedPlc.ReadClassAsync<TestClass>(DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadClassAsync<TestClass>(DB2));
}
}
@@ -678,13 +695,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadClassWithGenericAndClassFactoryThrowsExceptionPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
TestClass tc = await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2));
}
}
@@ -711,13 +727,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
object tsObj = await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2));
}
}
@@ -754,13 +769,12 @@ namespace S7.Net.UnitTest
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadStructWithGenericThrowsExceptionIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
object tsObj = await notConnectedPlc.ReadStructAsync<TestStruct>(DB2);
await Assert.ThrowsExceptionAsync<PlcException>(async () => await notConnectedPlc.ReadStructAsync<TestStruct>(DB2));
}
}

View File

@@ -743,6 +743,26 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00);
}
/// <summary>
/// Write/Read a large amount of data to test PDU max
/// </summary>
[TestMethod]
public void T33_WriteLargeByteArray()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
var randomEngine = new Random();
var data = new byte[8192];
var db = 2;
randomEngine.NextBytes(data);
plc.WriteBytes(DataType.DataBlock, db, 0, data);
var readData = plc.ReadBytes(DataType.DataBlock, db, 0, data.Length);
CollectionAssert.AreEqual(data, readData);
}
[TestMethod, ExpectedException(typeof(PlcException))]
public void T18_ReadStructThrowsIfPlcIsNotConnected()
{

View File

@@ -0,0 +1,97 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace S7.Net.UnitTest
{
/// <summary>
/// Test stream which only gives 1 byte per read.
/// </summary>
class TestStream1BytePerRead : Stream
{
public TestStream1BytePerRead(byte[] data)
{
Data = data;
}
public override bool CanRead => _position < Data.Length;
public override bool CanSeek => throw new NotImplementedException();
public override bool CanWrite => throw new NotImplementedException();
public override long Length => throw new NotImplementedException();
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public byte[] Data { get; }
public override void Flush()
{
throw new NotImplementedException();
}
int _position = 0;
public override int Read(byte[] buffer, int offset, int count)
{
if (_position >= Data.Length)
{
return 0;
}
buffer[offset] = Data[_position];
++_position;
return 1;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
}
/// <summary>
/// These tests are intended to test <see cref="StreamExtensions"/> functions and other stream-related special cases.
/// </summary>
[TestClass]
public class StreamTests
{
[TestMethod]
public async Task TPKT_ReadRestrictedStreamAsync()
{
var fullMessage = ProtocolUnitTest.StringToByteArray("0300002902f0803203000000010002001400000401ff0400807710000100000103000000033f8ccccd");
var m = new TestStream1BytePerRead(fullMessage);
var t = await TPKT.ReadAsync(m);
Assert.AreEqual(fullMessage.Length, t.Length);
Assert.AreEqual(fullMessage.Last(), t.Data.Last());
}
[TestMethod]
public void TPKT_ReadRestrictedStream()
{
var fullMessage = ProtocolUnitTest.StringToByteArray("0300002902f0803203000000010002001400000401ff0400807710000100000103000000033f8ccccd");
var m = new TestStream1BytePerRead(fullMessage);
var t = TPKT.Read(m);
Assert.AreEqual(fullMessage.Length, t.Length);
Assert.AreEqual(fullMessage.Last(), t.Data.Last());
}
[TestMethod]
public void TPKT_ReadStreamTooShort()
{
var fullMessage = ProtocolUnitTest.StringToByteArray("0300002902f0803203000000010002001400");
var m = new TestStream1BytePerRead(fullMessage);
Assert.ThrowsException<TPKTInvalidException>(() => TPKT.Read(m));
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
namespace S7.Net
{
@@ -27,17 +26,17 @@ namespace S7.Net
{
TPkt = tPKT;
var br = new BinaryReader(new MemoryStream(tPKT.Data));
HeaderLength = br.ReadByte();
HeaderLength = tPKT.Data[0]; // Header length excluding this length byte
if (HeaderLength >= 2)
{
PDUType = br.ReadByte();
PDUType = tPKT.Data[1];
if (PDUType == 0xf0) //DT Data
{
var flags = br.ReadByte();
var flags = tPKT.Data[2];
TPDUNumber = flags & 0x7F;
LastDataUnit = (flags & 0x80) > 0;
Data = br.ReadBytes(tPKT.Length - HeaderLength - 4); //4 = TPKT Size
Data = new byte[tPKT.Data.Length - HeaderLength - 1]; // substract header length byte + header length.
Array.Copy(tPKT.Data, HeaderLength + 1, Data, 0, Data.Length);
return;
}
//TODO: Handle other PDUTypes
@@ -54,8 +53,11 @@ namespace S7.Net
public static TPDU Read(Stream stream)
{
var tpkt = TPKT.Read(stream);
if (tpkt.Length > 0) return new TPDU(tpkt);
return null;
if (tpkt.Length == 0)
{
throw new TPDUInvalidException("No protocol data received");
}
return new TPDU(tpkt);
}
/// <summary>
@@ -67,8 +69,11 @@ namespace S7.Net
public static async Task<TPDU> ReadAsync(Stream stream)
{
var tpkt = await TPKT.ReadAsync(stream);
if (tpkt.Length > 0) return new TPDU(tpkt);
return null;
if (tpkt.Length == 0)
{
throw new TPDUInvalidException("No protocol data received");
}
return new TPDU(tpkt);
}
public override string ToString()
@@ -98,22 +103,25 @@ namespace S7.Net
public static byte[] Read(Stream stream)
{
var segment = TPDU.Read(stream);
if (segment == null) return null;
if (segment.LastDataUnit)
{
return segment.Data;
}
// More segments are expected, prepare a buffer to store all data
var buffer = new byte[segment.Data.Length];
var output = new MemoryStream(buffer);
output.Write(segment.Data, 0, segment.Data.Length);
Array.Copy(segment.Data, buffer, segment.Data.Length);
while (!segment.LastDataUnit)
{
segment = TPDU.Read(stream);
var previousLength = buffer.Length;
Array.Resize(ref buffer, buffer.Length + segment.Data.Length);
var lastPosition = output.Position;
output = new MemoryStream(buffer);
output.Write(segment.Data, (int) lastPosition, segment.Data.Length);
Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length);
}
return buffer.Take((int)output.Position).ToArray();
return buffer;
}
/// <summary>
@@ -125,21 +133,25 @@ namespace S7.Net
public static async Task<byte[]> ReadAsync(Stream stream)
{
var segment = await TPDU.ReadAsync(stream);
if (segment == null) return null;
if (segment.LastDataUnit)
{
return segment.Data;
}
// More segments are expected, prepare a buffer to store all data
var buffer = new byte[segment.Data.Length];
var output = new MemoryStream(buffer);
output.Write(segment.Data, 0, segment.Data.Length);
Array.Copy(segment.Data, buffer, segment.Data.Length);
while (!segment.LastDataUnit)
{
segment = await TPDU.ReadAsync(stream);
var previousLength = buffer.Length;
Array.Resize(ref buffer, buffer.Length + segment.Data.Length);
var lastPosition = output.Position;
output = new MemoryStream(buffer);
output.Write(segment.Data, (int) lastPosition, segment.Data.Length);
Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length);
}
return buffer.Take((int)output.Position).ToArray();
return buffer;
}
}
}

View File

@@ -0,0 +1,18 @@

namespace S7.Net.Helper
{
internal static class MemoryStreamExtension
{
/// <summary>
/// Helper function to write to whole content of the given byte array to a memory stream.
///
/// Writes all bytes in value from 0 to value.Length to the memory stream.
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
public static void WriteByteArray(this System.IO.MemoryStream stream, byte[] value)
{
stream.Write(value, 0, value.Length);
}
}
}

View File

@@ -15,8 +15,8 @@ namespace S7.Net
private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060;
//TCP connection to device
private TcpClient tcpClient;
private NetworkStream stream;
private TcpClient? tcpClient;
private NetworkStream? _stream;
private int readTimeout = System.Threading.Timeout.Infinite;
private int writeTimeout = System.Threading.Timeout.Infinite;
@@ -24,27 +24,27 @@ namespace S7.Net
/// <summary>
/// IP address of the PLC
/// </summary>
public string IP { get; private set; }
public string IP { get; }
/// <summary>
/// PORT Number of the PLC, default is 102
/// </summary>
public int Port { get; private set; }
public int Port { get; }
/// <summary>
/// CPU type of the PLC
/// </summary>
public CpuType CPU { get; private set; }
public CpuType CPU { get; }
/// <summary>
/// Rack of the PLC
/// </summary>
public Int16 Rack { get; private set; }
public Int16 Rack { get; }
/// <summary>
/// Slot of the CPU of the PLC
/// </summary>
public Int16 Slot { get; private set; }
public Int16 Slot { get; }
/// <summary>
/// Max PDU size this cpu supports

View File

@@ -89,4 +89,25 @@ namespace S7.Net
}
#endif
}
internal class TPDUInvalidException : Exception
{
public TPDUInvalidException() : base()
{
}
public TPDUInvalidException(string message) : base(message)
{
}
public TPDUInvalidException(string message, Exception innerException) : base(message, innerException)
{
}
#if NET_FULL
protected TPDUInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
#endif
}
}

View File

@@ -1,4 +1,5 @@
using S7.Net.Types;
using S7.Net.Helper;
using S7.Net.Types;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -13,21 +14,18 @@ namespace S7.Net
/// </summary>
/// <param name="amount"></param>
/// <returns></returns>
private ByteArray ReadHeaderPackage(int amount = 1)
private void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1)
{
//header size = 19 bytes
var package = new Types.ByteArray(19);
package.Add(new byte[] { 0x03, 0x00 });
stream.WriteByteArray(new byte[] { 0x03, 0x00 });
//complete package size
package.Add(Types.Int.ToByteArray((short)(19 + (12 * amount))));
package.Add(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 });
stream.WriteByteArray(Types.Int.ToByteArray((short)(19 + (12 * amount))));
stream.WriteByteArray(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 });
//data part size
package.Add(Types.Word.ToByteArray((ushort)(2 + (amount * 12))));
package.Add(new byte[] { 0x00, 0x00, 0x04 });
stream.WriteByteArray(Types.Word.ToByteArray((ushort)(2 + (amount * 12))));
stream.WriteByteArray(new byte[] { 0x00, 0x00, 0x04 });
//amount of requests
package.Add((byte)amount);
return package;
stream.WriteByte((byte)amount);
}
/// <summary>
@@ -39,39 +37,36 @@ namespace S7.Net
/// <param name="startByteAdr">Start address of the byte</param>
/// <param name="count">Number of bytes to be read</param>
/// <returns></returns>
private ByteArray CreateReadDataRequestPackage(DataType dataType, int db, int startByteAdr, int count = 1)
private void BuildReadDataRequestPackage(System.IO.MemoryStream stream, DataType dataType, int db, int startByteAdr, int count = 1)
{
//single data req = 12
var package = new Types.ByteArray(12);
package.Add(new byte[] { 0x12, 0x0a, 0x10 });
stream.WriteByteArray(new byte[] { 0x12, 0x0a, 0x10 });
switch (dataType)
{
case DataType.Timer:
case DataType.Counter:
package.Add((byte)dataType);
stream.WriteByte((byte)dataType);
break;
default:
package.Add(0x02);
stream.WriteByte(0x02);
break;
}
package.Add(Word.ToByteArray((ushort)(count)));
package.Add(Word.ToByteArray((ushort)(db)));
package.Add((byte)dataType);
stream.WriteByteArray(Word.ToByteArray((ushort)(count)));
stream.WriteByteArray(Word.ToByteArray((ushort)(db)));
stream.WriteByte((byte)dataType);
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.Add((byte)overflow);
stream.WriteByte((byte)overflow);
switch (dataType)
{
case DataType.Timer:
case DataType.Counter:
package.Add(Types.Word.ToByteArray((ushort)(startByteAdr)));
stream.WriteByteArray(Types.Word.ToByteArray((ushort)(startByteAdr)));
break;
default:
package.Add(Types.Word.ToByteArray((ushort)((startByteAdr) * 8)));
stream.WriteByteArray(Types.Word.ToByteArray((ushort)((startByteAdr) * 8)));
break;
}
return package;
}
/// <summary>
@@ -82,7 +77,7 @@ namespace S7.Net
/// <param name="varCount"></param>
/// <param name="bitAdr"></param>
/// <returns></returns>
private object ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0)
private object? ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0)
{
if (bytes == null || bytes.Length == 0)
return null;

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
using S7.Net.Protocol;
using System.IO;
namespace S7.Net
{
@@ -20,9 +21,14 @@ namespace S7.Net
public async Task OpenAsync()
{
await ConnectAsync();
var stream = GetStreamIfAvailable();
await stream.WriteAsync(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
var response = await COTP.TPDU.ReadAsync(stream);
if (response == null)
{
throw new Exception("Error reading Connection Confirm. Malformed TPDU packet");
}
if (response.PDUType != 0xd0) //Connect Confirm
{
throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d);
@@ -33,11 +39,16 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null)
throw new WrongNumberOfBytesException("No data received in response to Communication Setup");
if (s7data.Length < 2)
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
//Check for S7 Ack Data
if (s7data[1] != 0x03)
throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03);
if (s7data.Length < 20)
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
}
@@ -46,9 +57,10 @@ namespace S7.Net
tcpClient = new TcpClient();
ConfigureConnection();
await tcpClient.ConnectAsync(IP, Port);
stream = tcpClient.GetStream();
_stream = tcpClient.GetStream();
}
/// <summary>
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
/// If the read was not successful, check LastErrorCode or LastErrorString.
@@ -60,20 +72,17 @@ namespace S7.Net
/// <returns>Returns the bytes in an array</returns>
public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count)
{
List<byte> resultBytes = new List<byte>();
int index = startByteAdr;
var resultBytes = new byte[count];
int index = 0;
while (count > 0)
{
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
var maxToRead = (int)Math.Min(count, MaxPDUSize - 18);
byte[] bytes = await ReadBytesWithSingleRequestAsync(dataType, db, index, maxToRead);
if (bytes == null)
return resultBytes.ToArray();
resultBytes.AddRange(bytes);
var maxToRead = Math.Min(count, MaxPDUSize - 18);
await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead);
count -= maxToRead;
index += maxToRead;
}
return resultBytes.ToArray();
return resultBytes;
}
/// <summary>
@@ -87,7 +96,7 @@ namespace S7.Net
/// <param name="varType">Type of the variable/s that you are reading</param>
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
/// <param name="varCount"></param>
public async Task<object> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
public async Task<object?> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
{
int cntBytes = VarTypeToByteLength(varType, varCount);
byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes);
@@ -100,7 +109,7 @@ namespace S7.Net
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <returns>Returns an object that contains the value. This object must be cast accordingly.</returns>
public async Task<object> ReadAsync(string variable)
public async Task<object?> ReadAsync(string variable)
{
var adr = new PLCAddress(variable);
return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
@@ -113,7 +122,7 @@ namespace S7.Net
/// <param name="db">Address of the DB.</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>Returns a struct that must be cast.</returns>
public async Task<object> ReadStructAsync(Type structType, int db, int startByteAdr = 0)
public async Task<object?> ReadStructAsync(Type structType, int db, int startByteAdr = 0)
{
int numBytes = Types.Struct.GetStructSize(structType);
// now read the package
@@ -168,7 +177,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public async Task<T> ReadClassAsync<T>(int db, int startByteAdr = 0) where T : class
public async Task<T?> ReadClassAsync<T>(int db, int startByteAdr = 0) where T : class
{
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr);
}
@@ -182,7 +191,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public async Task<T> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
public async Task<T?> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
{
var instance = classFactory();
var res = await ReadClassAsync(instance, db, startByteAdr);
@@ -208,20 +217,23 @@ namespace S7.Net
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try
{
// first create the header
int packageSize = 19 + (dataItems.Count * 12);
ByteArray package = new ByteArray(packageSize);
package.Add(ReadHeaderPackage(dataItems.Count));
var package = new System.IO.MemoryStream(packageSize);
BuildHeaderPackage(package, dataItems.Count);
// package.Add(0x02); // datenart
foreach (var dataItem in dataItems)
{
package.Add(CreateReadDataRequestPackage(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count)));
BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count));
}
await stream.WriteAsync(package.Array, 0, package.Array.Length);
var dataToSend = package.ToArray();
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
var s7data = await COTP.TSDU.ReadAsync(stream); //TODO use Async
if (s7data == null || s7data[14] != 0xff)
@@ -255,11 +267,8 @@ namespace S7.Net
int count = value.Length;
while (count > 0)
{
//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
var maxToWrite = (int)Math.Min(count, 200);
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35);
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite);
count -= maxToWrite;
localIndex += maxToWrite;
}
@@ -374,26 +383,24 @@ namespace S7.Net
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes);
}
private async Task<byte[]> ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count)
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count)
{
byte[] bytes = new byte[count];
var stream = GetStreamIfAvailable();
// first create the header
int packageSize = 31;
ByteArray package = new ByteArray(packageSize);
package.Add(ReadHeaderPackage());
int packageSize = 31;
var package = new System.IO.MemoryStream(packageSize);
BuildHeaderPackage(package);
// package.Add(0x02); // datenart
package.Add(CreateReadDataRequestPackage(dataType, db, startByteAdr, count));
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count);
await stream.WriteAsync(package.Array, 0, package.Array.Length);
var dataToSend = package.ToArray();
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
var s7data = await COTP.TSDU.ReadAsync(stream);
AssertReadResponse(s7data, count);
for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
return bytes;
Array.Copy(s7data, 18, buffer, offset, count);
}
/// <summary>
@@ -406,6 +413,8 @@ namespace S7.Net
{
AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false);
@@ -422,38 +431,15 @@ namespace S7.Net
/// <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>
/// <returns>A task that represents the asynchronous write operation.</returns>
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value)
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
{
byte[] bReceive = new byte[513];
int varCount = 0;
try
{
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
ByteArray package = new ByteArray(packageSize);
var stream = GetStreamIfAvailable();
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
package.Add(new byte[] { 3, 0, 0 });
package.Add((byte)packageSize);
package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.Add(Word.ToByteArray((ushort)(varCount - 1)));
package.Add(new byte[] { 0, 0x0e });
package.Add(Word.ToByteArray((ushort)(varCount + 4)));
package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 });
package.Add(Word.ToByteArray((ushort)varCount));
package.Add(Word.ToByteArray((ushort)(db)));
package.Add((byte)dataType);
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.Add((byte)overflow);
package.Add(Word.ToByteArray((ushort)(startByteAdr * 8)));
package.Add(new byte[] { 0, 4 });
package.Add(Word.ToByteArray((ushort)(varCount * 8)));
// now join the header and the data
package.Add(value);
await stream.WriteAsync(package.Array, 0, package.Array.Length);
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
@@ -469,46 +455,33 @@ namespace S7.Net
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
byte[] bReceive = new byte[513];
int varCount = 0;
var stream = GetStreamIfAvailable();
try
{
var value = new[] {bitValue ? (byte) 1 : (byte) 0};
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
ByteArray package = new Types.ByteArray(packageSize);
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
package.Add(new byte[] { 3, 0, 0 });
package.Add((byte)packageSize);
package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.Add(Word.ToByteArray((ushort)(varCount - 1)));
package.Add(new byte[] { 0, 0x0e });
package.Add(Word.ToByteArray((ushort)(varCount + 4)));
package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit
package.Add(Word.ToByteArray((ushort)varCount));
package.Add(Word.ToByteArray((ushort)(db)));
package.Add((byte)dataType);
int overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.Add((byte)overflow);
package.Add(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr)));
package.Add(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit
package.Add(Word.ToByteArray((ushort)(varCount)));
// now join the header and the data
package.Add(value);
await stream.WriteAsync(package.Array, 0, package.Array.Length);
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
{
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
}
catch (Exception exc)
{
throw new PlcException(ErrorCode.WriteData, exc);
}
}
private Stream GetStreamIfAvailable()
{
if (_stream == null)
{
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
}
return _stream;
}
}
}

View File

@@ -1,9 +1,11 @@
using S7.Net.Types;
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using S7.Net.Protocol;
using S7.Net.Helper;
//Implement synchronous methods here
namespace S7.Net
@@ -19,6 +21,7 @@ namespace S7.Net
try
{
var stream = GetStreamIfAvailable();
stream.Write(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
var response = COTP.TPDU.Read(stream);
if (response.PDUType != 0xd0) //Connect Confirm
@@ -31,11 +34,16 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream);
if (s7data == null)
throw new WrongNumberOfBytesException("No data received in response to Communication Setup");
if (s7data.Length < 2)
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
//Check for S7 Ack Data
if (s7data[1] != 0x03)
throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03);
if (s7data.Length < 20)
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
}
catch (Exception exc)
@@ -52,7 +60,7 @@ namespace S7.Net
tcpClient = new TcpClient();
ConfigureConnection();
tcpClient.Connect(IP, Port);
stream = tcpClient.GetStream();
_stream = tcpClient.GetStream();
}
catch (SocketException sex)
{
@@ -79,20 +87,17 @@ namespace S7.Net
/// <returns>Returns the bytes in an array</returns>
public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count)
{
List<byte> resultBytes = new List<byte>();
int index = startByteAdr;
var result = new byte[count];
int index = 0;
while (count > 0)
{
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
var maxToRead = (int)Math.Min(count, MaxPDUSize - 18);
byte[] bytes = ReadBytesWithSingleRequest(dataType, db, index, maxToRead);
if (bytes == null)
return resultBytes.ToArray();
resultBytes.AddRange(bytes);
var maxToRead = Math.Min(count, MaxPDUSize - 18);
ReadBytesWithSingleRequest(dataType, db, startByteAdr + index, result, index, maxToRead);
count -= maxToRead;
index += maxToRead;
}
return resultBytes.ToArray();
return result;
}
/// <summary>
@@ -106,7 +111,7 @@ namespace S7.Net
/// <param name="varType">Type of the variable/s that you are reading</param>
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
/// <param name="varCount"></param>
public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
public object? Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
{
int cntBytes = VarTypeToByteLength(varType, varCount);
byte[] bytes = ReadBytes(dataType, db, startByteAdr, cntBytes);
@@ -119,8 +124,8 @@ namespace S7.Net
/// If the read was not successful, check LastErrorCode or LastErrorString.
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <returns>Returns an object that contains the value. This object must be cast accordingly.</returns>
public object Read(string variable)
/// <returns>Returns an object that contains the value. This object must be cast accordingly. If no data has been read, null will be returned</returns>
public object? Read(string variable)
{
var adr = new PLCAddress(variable);
return Read(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
@@ -132,8 +137,8 @@ namespace S7.Net
/// <param name="structType">Type of the struct to be readed (es.: TypeOf(MyStruct)).</param>
/// <param name="db">Address of the DB.</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>Returns a struct that must be cast.</returns>
public object ReadStruct(Type structType, int db, int startByteAdr = 0)
/// <returns>Returns a struct that must be cast. If no data has been read, null will be returned</returns>
public object? ReadStruct(Type structType, int db, int startByteAdr = 0)
{
int numBytes = Struct.GetStructSize(structType);
// now read the package
@@ -188,7 +193,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public T ReadClass<T>(int db, int startByteAdr = 0) where T : class
public T? ReadClass<T>(int db, int startByteAdr = 0) where T : class
{
return ReadClass(() => Activator.CreateInstance<T>(), db, startByteAdr);
}
@@ -202,7 +207,7 @@ namespace S7.Net
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public T ReadClass<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
public T? ReadClass<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
{
var instance = classFactory();
int readBytes = ReadClass(instance, db, startByteAdr);
@@ -231,7 +236,7 @@ namespace S7.Net
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
var maxToWrite = Math.Min(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite);
count -= maxToWrite;
localIndex += maxToWrite;
}
@@ -338,27 +343,25 @@ namespace S7.Net
WriteClassAsync(classValue, db, startByteAdr).GetAwaiter().GetResult();
}
private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, int count)
private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count)
{
byte[] bytes = new byte[count];
var stream = GetStreamIfAvailable();
try
{
// first create the header
int packageSize = 31;
ByteArray package = new ByteArray(packageSize);
package.Add(ReadHeaderPackage());
int packageSize = 19 + 12; // 19 header + 12 for 1 request
var package = new System.IO.MemoryStream(packageSize);
BuildHeaderPackage(package);
// package.Add(0x02); // datenart
package.Add(CreateReadDataRequestPackage(dataType, db, startByteAdr, count));
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count);
stream.Write(package.Array, 0, package.Array.Length);
var dataToSend = package.ToArray();
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream);
AssertReadResponse(s7data, count);
for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
return bytes;
Array.Copy(s7data, 18, buffer, offset, count);
}
catch (Exception exc)
{
@@ -375,6 +378,8 @@ namespace S7.Net
{
AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
stream.Write(message.Array, 0, length);
@@ -383,37 +388,14 @@ namespace S7.Net
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
}
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
{
int varCount = 0;
try
{
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
ByteArray package = new ByteArray(packageSize);
var stream = GetStreamIfAvailable();
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
package.Add(new byte[] { 3, 0 });
//complete package size
package.Add(Int.ToByteArray((short)packageSize));
package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.Add(Word.ToByteArray((ushort)(varCount - 1)));
package.Add(new byte[] { 0, 0x0e });
package.Add(Word.ToByteArray((ushort)(varCount + 4)));
package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 });
package.Add(Word.ToByteArray((ushort)varCount));
package.Add(Word.ToByteArray((ushort)(db)));
package.Add((byte)dataType);
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.Add((byte)overflow);
package.Add(Word.ToByteArray((ushort)(startByteAdr * 8)));
package.Add(new byte[] { 0, 4 });
package.Add(Word.ToByteArray((ushort)(varCount * 8)));
// now join the header and the data
package.Add(value);
stream.Write(package.Array, 0, package.Array.Length);
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
@@ -427,42 +409,85 @@ namespace S7.Net
}
}
private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
{
int varCount = count;
// first create the header
int packageSize = 35 + varCount;
var package = new MemoryStream(new byte[packageSize]);
package.WriteByte(3);
package.WriteByte(0);
//complete package size
package.WriteByteArray(Int.ToByteArray((short)packageSize));
package.WriteByteArray(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.WriteByteArray(Word.ToByteArray((ushort)(varCount - 1)));
package.WriteByteArray(new byte[] { 0, 0x0e });
package.WriteByteArray(Word.ToByteArray((ushort)(varCount + 4)));
package.WriteByteArray(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 });
package.WriteByteArray(Word.ToByteArray((ushort)varCount));
package.WriteByteArray(Word.ToByteArray((ushort)(db)));
package.WriteByte((byte)dataType);
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.WriteByte((byte)overflow);
package.WriteByteArray(Word.ToByteArray((ushort)(startByteAdr * 8)));
package.WriteByteArray(new byte[] { 0, 4 });
package.WriteByteArray(Word.ToByteArray((ushort)(varCount * 8)));
// now join the header and the data
package.Write(value, dataOffset, count);
return package.ToArray();
}
private byte[] BuildWriteBitPackage(DataType dataType, int db, int startByteAdr, bool bitValue, int bitAdr)
{
var stream = GetStreamIfAvailable();
var value = new[] { bitValue ? (byte)1 : (byte)0 };
int varCount = 1;
// first create the header
int packageSize = 35 + varCount;
var package = new MemoryStream(new byte[packageSize]);
package.WriteByte(3);
package.WriteByte(0);
//complete package size
package.WriteByteArray(Int.ToByteArray((short)packageSize));
package.WriteByteArray(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.WriteByteArray(Word.ToByteArray((ushort)(varCount - 1)));
package.WriteByteArray(new byte[] { 0, 0x0e });
package.WriteByteArray(Word.ToByteArray((ushort)(varCount + 4)));
package.WriteByteArray(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit
package.WriteByteArray(Word.ToByteArray((ushort)varCount));
package.WriteByteArray(Word.ToByteArray((ushort)(db)));
package.WriteByte((byte)dataType);
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.WriteByte((byte)overflow);
package.WriteByteArray(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr)));
package.WriteByteArray(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit
package.WriteByteArray(Word.ToByteArray((ushort)(varCount)));
// now join the header and the data
package.WriteByteArray(value);
return package.ToArray();
}
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
int varCount = 0;
var stream = GetStreamIfAvailable();
try
{
var value = new[] {bitValue ? (byte) 1 : (byte) 0};
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
ByteArray package = new ByteArray(packageSize);
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
package.Add(new byte[] { 3, 0, 0 });
package.Add((byte)packageSize);
package.Add(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.Add(Word.ToByteArray((ushort)(varCount - 1)));
package.Add(new byte[] { 0, 0x0e });
package.Add(Word.ToByteArray((ushort)(varCount + 4)));
package.Add(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit
package.Add(Word.ToByteArray((ushort)varCount));
package.Add(Word.ToByteArray((ushort)(db)));
package.Add((byte)dataType);
int overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.Add((byte)overflow);
package.Add(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr)));
package.Add(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit
package.Add(Word.ToByteArray((ushort)(varCount)));
// now join the header and the data
package.Add(value);
stream.Write(package.Array, 0, package.Array.Length);
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
{
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
}
catch (Exception exc)
{
@@ -484,19 +509,22 @@ namespace S7.Net
//replies with bigger PDU size in connection setup.
AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try
{
// first create the header
int packageSize = 19 + (dataItems.Count * 12);
ByteArray package = new ByteArray(packageSize);
package.Add(ReadHeaderPackage(dataItems.Count));
var package = new System.IO.MemoryStream(packageSize);
BuildHeaderPackage(package, dataItems.Count);
// package.Add(0x02); // datenart
foreach (var dataItem in dataItems)
{
package.Add(CreateReadDataRequestPackage(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count)));
BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count));
}
stream.Write(package.Array, 0, package.Array.Length);
var dataToSend = package.ToArray();
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream); //TODO use Async
if (s7data == null || s7data[14] != 0xff)

View File

@@ -1,3 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("S7Net.UnitTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d1032db55f60d64bf90ea1cc2247b5a8b9b6168a07bcd464a07ce2e425d027ff9409a64ba0e3f37718e14c50cf964d0d921e5ae8b8d74bd8a82431794f897cebf0ee668feb2ccd030153611b2808fcb7785c5e5136a98e0ec23de3c1ed385d2026c26e4bed5805ff9db7e0544f59b1f19d369d43403a624586795926e38c48d")]
[assembly: InternalsVisibleTo("S7.Net.UnitTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d1032db55f60d64bf90ea1cc2247b5a8b9b6168a07bcd464a07ce2e425d027ff9409a64ba0e3f37718e14c50cf964d0d921e5ae8b8d74bd8a82431794f897cebf0ee668feb2ccd030153611b2808fcb7785c5e5136a98e0ec23de3c1ed385d2026c26e4bed5805ff9db7e0544f59b1f19d369d43403a624586795926e38c48d")]

View File

@@ -91,7 +91,7 @@ namespace S7.Net.Protocol
IList<byte> itemResults = new ArraySegment<byte>(message, 14, dataItems.Length);
List<Exception> errors = null;
List<Exception>? errors = null;
for (int i = 0; i < dataItems.Length; i++)
{

View File

@@ -15,6 +15,12 @@
<RepositoryType>git</RepositoryType>
<PackageTags>PLC Siemens Communication S7</PackageTags>
<Copyright>Derek Heiser 2015</Copyright>
<LangVersion>8.0</LangVersion>
<Nullable>Enable</Nullable>
<DebugType>portable</DebugType>
<EmbedAllSources>true</EmbedAllSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net452' Or '$(TargetFramework)' == 'netstandard2.0' ">
@@ -22,7 +28,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-62925-02" PrivateAssets="All" />
<PackageReference Include="SourceLink.Copy.PdbFiles" Version="2.8.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Threading.Tasks;
namespace S7.Net
{
/// <summary>
/// Extensions for Streams
/// </summary>
public static class StreamExtensions
{
/// <summary>
/// Reads bytes from the stream into the buffer until exactly the requested number of bytes (or EOF) have been read
/// </summary>
/// <param name="stream">the Stream to read from</param>
/// <param name="buffer">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>
/// <returns>returns the amount of read bytes</returns>
public static int ReadExact(this Stream stream, byte[] buffer, int offset, int count)
{
int read = 0;
int received;
do
{
received = stream.Read(buffer, offset + read, count - read);
read += received;
}
while (read < count && received > 0);
return read;
}
/// <summary>
/// Reads bytes from the stream into the buffer until exactly the requested number of bytes (or EOF) have been read
/// </summary>
/// <param name="stream">the Stream to read from</param>
/// <param name="buffer">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>
/// <returns>returns the amount of read bytes</returns>
public static async Task<int> ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count)
{
int read = 0;
int received;
do
{
received = await stream.ReadAsync(buffer, offset + read, count - read);
read += received;
}
while (read < count && received > 0);
return read;
}
}
}

View File

@@ -10,10 +10,19 @@ namespace S7.Net
/// </summary>
internal class TPKT
{
public byte Version;
public byte Reserved1;
public int Length;
public byte[] Data;
private TPKT(byte version, byte reserved1, int length, byte[] data)
{
Version = version;
Reserved1 = reserved1;
Length = length;
Data = data;
}
/// <summary>
/// Reads a TPKT from the socket
@@ -23,22 +32,24 @@ namespace S7.Net
public static TPKT Read(Stream stream)
{
var buf = new byte[4];
int len = stream.Read(buf, 0, 4);
if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
var pkt = new TPKT
{
Version = buf[0],
Reserved1 = buf[1],
Length = buf[2] * 256 + buf[3] //BigEndian
};
if (pkt.Length > 0)
{
pkt.Data = new byte[pkt.Length - 4];
len = stream.Read(pkt.Data, 0, pkt.Length - 4);
if (len < pkt.Length - 4)
throw new TPKTInvalidException("TPKT is incomplete / invalid");
}
return pkt;
int len = stream.ReadExact(buf, 0, 4);
if (len < 4) throw new TPKTInvalidException($"TPKT header is incomplete / invalid. Received Bytes: {len} expected: {buf.Length}");
var version = buf[0];
var reserved1 = buf[1];
var length = buf[2] * 256 + buf[3]; //BigEndian
var data = new byte[length - 4];
len = stream.ReadExact(data, 0, data.Length);
if (len < data.Length)
throw new TPKTInvalidException($"TPKT payload is incomplete / invalid. Received Bytes: {len} expected: {data.Length}");
return new TPKT
(
version: version,
reserved1: reserved1,
length: length,
data: data
);
}
/// <summary>
@@ -49,21 +60,25 @@ namespace S7.Net
public static async Task<TPKT> ReadAsync(Stream stream)
{
var buf = new byte[4];
int len = await stream.ReadAsync(buf, 0, 4);
int len = await stream.ReadExactAsync(buf, 0, 4);
if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
var pkt = new TPKT
{
Version = buf[0],
Reserved1 = buf[1],
Length = buf[2] * 256 + buf[3] //BigEndian
};
if (pkt.Length > 0)
{
pkt.Data = new byte[pkt.Length - 4];
len = await stream.ReadAsync(pkt.Data, 0, pkt.Length - 4);
if (len < pkt.Length - 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
}
return pkt;
var version = buf[0];
var reserved1 = buf[1];
var length = buf[2] * 256 + buf[3]; //BigEndian
var data = new byte[length - 4];
len = await stream.ReadExactAsync(data, 0, data.Length);
if (len < data.Length)
throw new TPKTInvalidException("TPKT payload incomplete / invalid");
return new TPKT
(
version: version,
reserved1: reserved1,
length: length,
data: data
);
}
public override string ToString()

View File

@@ -50,6 +50,11 @@ namespace S7.Net.Types
list.AddRange(items);
}
public void Add(IEnumerable<byte> items)
{
list.AddRange(items);
}
public void Add(ByteArray byteArray)
{
list.AddRange(byteArray.Array);

View File

@@ -106,9 +106,9 @@ namespace S7.Net.Types
return numBytes;
}
private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes)
private static object? GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes)
{
object value = null;
object? value = null;
switch (propertyType.Name)
{
@@ -245,7 +245,7 @@ namespace S7.Net.Types
{
int bytePos = 0;
int bitPos = 0;
byte[] bytes2 = null;
byte[]? bytes2 = null;
switch (propertyValue.GetType().Name)
{

View File

@@ -40,7 +40,7 @@ namespace S7.Net.Types
/// <summary>
/// Contains the value of the memory area after the read has been executed
/// </summary>
public object Value { get; set; }
public object? Value { get; set; }
/// <summary>
/// Create an instance of DataItem
@@ -83,7 +83,14 @@ namespace S7.Net.Types
var dataItem = FromAddress(address);
dataItem.Value = value;
if (typeof(T).IsArray) dataItem.Count = ((Array) dataItem.Value).Length;
if (typeof(T).IsArray)
{
var array = ((Array?)dataItem.Value);
if ( array != null)
{
dataItem.Count = array.Length;
}
}
return dataItem;
}

View File

@@ -70,7 +70,7 @@ namespace S7.Net.Types
/// <param name="structType">The struct type</param>
/// <param name="bytes">The array of bytes</param>
/// <returns>The object depending on the struct type or null if fails(array-length != struct-length</returns>
public static object FromBytes(Type structType, byte[] bytes)
public static object? FromBytes(Type structType, byte[] bytes)
{
if (bytes == null)
return null;
@@ -198,7 +198,7 @@ namespace S7.Net.Types
int size = Struct.GetStructSize(type);
byte[] bytes = new byte[size];
byte[] bytes2 = null;
byte[]? bytes2 = null;
int bytePos = 0;
int bitPos = 0;

7
S7.sln
View File

@@ -1,16 +1,17 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2026
# Visual Studio Version 16
VisualStudioVersion = 16.0.29806.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "S7.Net", "S7.Net\S7.Net.csproj", "{BFD484F9-3F04-42A2-BF2A-60A189A25DCF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7A8252C3-E6AE-435A-809D-4413C06E0711}"
ProjectSection(SolutionItems) = preProject
appveyor.yml = appveyor.yml
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S7.Net.UnitTest", "S7.Net.UnitTest\S7.Net.UnitTest.csproj", "{303CCED6-9ABC-4899-A509-743341AAA804}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "S7.Net.UnitTest", "S7.Net.UnitTest\S7.Net.UnitTest.csproj", "{303CCED6-9ABC-4899-A509-743341AAA804}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -1,13 +1,13 @@
image: Visual Studio 2017
image: Visual Studio 2019
configuration: Release
install:
- choco install gitversion.portable -y
before_build:
- cmd: gitversion /l console /output buildserver
- nuget restore
- 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
- dotnet pack S7.Net -c %CONFIGURATION% /p:Version=%GitVersion_NuGetVersion% --no-build -o artifacts
artifacts:
- path: artifacts\*.*