mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-22 14:08:25 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b2dbd1148 | ||
|
|
3c91aa02b0 | ||
|
|
edfa208c3e | ||
|
|
d11f46eedb | ||
|
|
09c8b18d3d | ||
|
|
10e5562706 | ||
|
|
7d570f93c1 | ||
|
|
c79ae13ea1 | ||
|
|
09851ec30b | ||
|
|
592d21c3aa | ||
|
|
d530f1e422 | ||
|
|
ba3dd084cb | ||
|
|
a047c5bba4 | ||
|
|
2bb7ac7d2a | ||
|
|
783c456dc9 | ||
|
|
688d4e2a28 | ||
|
|
bb0b57c574 | ||
|
|
2f07d43062 | ||
|
|
4ef037881c | ||
|
|
fbd8a13c6c | ||
|
|
2a451bc049 | ||
|
|
324ae95c42 | ||
|
|
bd8177d39e | ||
|
|
e68ca64596 | ||
|
|
647d4c7ae2 | ||
|
|
c7ef055be2 | ||
|
|
7035d22506 |
@@ -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")]
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
97
S7.Net.UnitTest/StreamTests.cs
Normal file
97
S7.Net.UnitTest/StreamTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
S7.Net/Helper/MemoryStreamExtension.cs
Normal file
18
S7.Net/Helper/MemoryStreamExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("S7Net.UnitTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d1032db55f60d64bf90ea1cc2247b5a8b9b6168a07bcd464a07ce2e425d027ff9409a64ba0e3f37718e14c50cf964d0d921e5ae8b8d74bd8a82431794f897cebf0ee668feb2ccd030153611b2808fcb7785c5e5136a98e0ec23de3c1ed385d2026c26e4bed5805ff9db7e0544f59b1f19d369d43403a624586795926e38c48d")]
|
||||
[assembly: InternalsVisibleTo("S7.Net.UnitTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d1032db55f60d64bf90ea1cc2247b5a8b9b6168a07bcd464a07ce2e425d027ff9409a64ba0e3f37718e14c50cf964d0d921e5ae8b8d74bd8a82431794f897cebf0ee668feb2ccd030153611b2808fcb7785c5e5136a98e0ec23de3c1ed385d2026c26e4bed5805ff9db7e0544f59b1f19d369d43403a624586795926e38c48d")]
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
56
S7.Net/StreamExtensions.cs
Normal file
56
S7.Net/StreamExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
7
S7.sln
@@ -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
|
||||
|
||||
@@ -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\*.*
|
||||
Reference in New Issue
Block a user