Merge branch 'release/0.3'

This commit is contained in:
Michael Croes
2018-07-11 22:50:23 +02:00
21 changed files with 692 additions and 398 deletions

17
GitVersion.yml Normal file
View File

@@ -0,0 +1,17 @@
assembly-informational-format: '{NuGetVersion}'
mode: ContinuousDeployment
branches:
master:
tag: rc
increment: Minor
features?[/-]:
tag: rc-{BranchName}
increment: Minor
(pull|pull\-requests|pr)[/-]:
tag: rc-pr-{BranchName}
increment: Minor
hotfix(es)?[/-]:
tag: rc
increment: Patch
dev(elop)?(ment)?$:
tag: b

View File

@@ -75,7 +75,7 @@ namespace S7.Net.UnitTest
destTsap1, destTsap2, //Destination TASP
192, //Parameter Code (tpdu-size)
1, //Parameter Length
9 //TPDU Size (2^9 = 512)
11 //TPDU Size (2^11 = 2048)
};
}
}

View File

@@ -78,6 +78,8 @@
<Compile Include="S7NetTestsSync.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\TestLongStruct.cs" />
<Compile Include="TypeTests\StringExTests.cs" />
<Compile Include="TypeTests\StringTests.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="snap7.dll">

View File

@@ -226,10 +226,8 @@ namespace S7.Net.UnitTest
IntVariable111 = 201
};
plc.WriteStruct(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongStruct tc2 = (TestLongStruct)await plc.ReadStructAsync(typeof(TestLongStruct), DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -292,11 +290,9 @@ namespace S7.Net.UnitTest
IntVariable111 = 201
};
await plc.WriteClassAsync(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongClass tc2 = new TestLongClass();
await plc.ReadClassAsync(tc2, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -571,7 +567,7 @@ namespace S7.Net.UnitTest
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public async Task Test_Async_ReadBytesThrowsExceptionIfPlcIsNotConnected()
public async Task Test_Async_ReadBytesReturnsNullIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -831,11 +827,9 @@ namespace S7.Net.UnitTest
{
double test_value = 55.66;
await plc.WriteAsync("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Double");
var helper = await plc.ReadAsync("DB1.DBD0");
double test_value2 = Conversion.ConvertToDouble((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Double");
Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals
}
@@ -844,11 +838,9 @@ namespace S7.Net.UnitTest
{
float test_value = 55.6632f;
await plc.WriteAsync("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Single");
var helper = await plc.ReadAsync("DB1.DBD0");
float test_value2 = Conversion.ConvertToFloat((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Single");
Assert.AreEqual(test_value, test_value2, "Compare Write/Read"); //No delta, datatype matches
}

View File

@@ -81,8 +81,14 @@ namespace S7.Net.UnitTest
{
if (plc.IsConnected == false)
{
var error = plc.Open();
Assert.AreEqual(ErrorCode.NoError, error, "If you have s7 installed you must close s7oiehsx64 service.");
try
{
plc.Open();
}
catch (Exception e)
{
throw new Exception("If you have s7 installed you must close s7oiehsx64 service.", e);
}
}
}
@@ -257,10 +263,8 @@ namespace S7.Net.UnitTest
tc.IntVariable110 = 200;
tc.IntVariable111 = 201;
plc.WriteStruct(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongStruct tc2 = (TestLongStruct)plc.ReadStruct(typeof(TestLongStruct), DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -321,11 +325,9 @@ namespace S7.Net.UnitTest
tc.IntVariable110 = 200;
tc.IntVariable111 = 201;
plc.WriteClass(tc, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestLongClass tc2 = new TestLongClass();
plc.ReadClass(tc2, DB2);
Assert.AreEqual(ErrorCode.NoError, plc.LastErrorCode);
Assert.AreEqual(tc.IntVariable0, tc2.IntVariable0);
Assert.AreEqual(tc.IntVariable1, tc2.IntVariable1);
Assert.AreEqual(tc.IntVariable10, tc2.IntVariable10);
@@ -596,19 +598,14 @@ namespace S7.Net.UnitTest
}
[TestMethod]
public void T13_ReadBytesReturnsEmptyArrayIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T13_ReadBytesThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
Assert.IsFalse(notConnectedPlc.IsConnected);
int expectedReadBytes = 0; // 0 bytes, because no connection was established
TestClass tc = new TestClass();
int actualReadBytes = notConnectedPlc.ReadClass(tc, DB2);
Assert.AreEqual(expectedReadBytes, actualReadBytes);
}
}
@@ -642,8 +639,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable);
}
[TestMethod]
public void T15_ReadClassWithGenericReturnsNullIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T15_ReadClassWithGenericThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -684,8 +681,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable);
}
[TestMethod]
public void T17_ReadClassWithGenericAndClassFactoryReturnsNullIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T17_ReadClassWithGenericAndClassFactoryThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -697,8 +694,8 @@ namespace S7.Net.UnitTest
}
}
[TestMethod]
public void T18_ReadStructReturnsNullIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T18_ReadStructThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -739,8 +736,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(ts2.DWordVariable, ts2Generic.DWordVariable);
}
[TestMethod]
public void T20_ReadStructWithGenericReturnsNullIfPlcIsNotConnected()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T20_ReadStructThrowsIfPlcIsNotConnected()
{
using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0))
{
@@ -883,11 +880,9 @@ namespace S7.Net.UnitTest
{
double test_value = 55.66;
plc.Write("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Double");
var helper = plc.Read("DB1.DBD0");
double test_value2 = Conversion.ConvertToDouble((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Double");
Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals
}
@@ -929,19 +924,17 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc.Bool1, tc2.Bool1);
}
[TestMethod]
public void T29_Read_Write_ExceptionHandlingWhenPlcIsNotReachable()
[TestMethod, ExpectedException(typeof(PlcException))]
public void T29_Read_Write_ThrowsWhenPlcIsNotReachable()
{
// leave plc Open
S7TestServer.Stop();
double test_value = 55.66;
plc.Write("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.WriteData, "No Write Error.");
var helper = plc.Read("DB1.DBD0");
Assert.AreEqual(helper, null, "Value in Read.");
Assert.AreEqual(plc.LastErrorCode, ErrorCode.ReadData, "No Read Error.");
}
[TestMethod]
@@ -949,11 +942,9 @@ namespace S7.Net.UnitTest
{
float test_value = 55.6632f;
plc.Write("DB1.DBD0", test_value);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Single");
var helper = plc.Read("DB1.DBD0");
float test_value2 = Conversion.ConvertToFloat((uint)helper);
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Single");
Assert.AreEqual(test_value, test_value2, "Compare Write/Read"); //No delta, datatype matches
}

View File

@@ -0,0 +1,115 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;
namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class StringExTests
{
[TestMethod]
public void ReadEmptyStringWithZeroByteLength()
{
AssertFromByteArrayEquals("", 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneByteLength()
{
AssertFromByteArrayEquals("", 1, 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneByteGarbage()
{
AssertFromByteArrayEquals("", 1, 0, (byte) 'a');
}
[TestMethod]
public void ReadA()
{
AssertFromByteArrayEquals("A", 1, 1, (byte) 'A');
}
[TestMethod]
public void ReadAbc()
{
AssertFromByteArrayEquals("Abc", 1, 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
[TestMethod]
public void WriteNullWithReservedLengthZero()
{
AssertToByteArrayEquals(null, 0, 0, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthZero()
{
AssertToByteArrayEquals("", 0, 0, 0);
}
[TestMethod]
public void WriteAWithReservedLengthZero()
{
AssertToByteArrayEquals("A", 0, 0, 0);
}
[TestMethod]
public void WriteNullWithReservedLengthOne()
{
AssertToByteArrayEquals(null, 1, 1, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthOne()
{
AssertToByteArrayEquals("", 1, 1, 0);
}
[TestMethod]
public void WriteAWithReservedLengthOne()
{
AssertToByteArrayEquals("A", 1, 1, 1, (byte) 'A');
}
[TestMethod]
public void WriteAWithReservedLengthTwo()
{
AssertToByteArrayEquals("A", 2, 2, 1, (byte) 'A');
}
[TestMethod]
public void WriteAbcWithReservedLengthOne()
{
AssertToByteArrayEquals("Abc", 1, 1, 1, (byte) 'A');
}
[TestMethod]
public void WriteAbcWithReservedLengthTwo()
{
AssertToByteArrayEquals("Abc", 2, 2, 2, (byte) 'A', (byte) 'b');
}
[TestMethod]
public void WriteAbcWithReservedLengthThree()
{
AssertToByteArrayEquals("Abc", 3, 3, 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
[TestMethod]
public void WriteAbcWithReservedLengthFour()
{
AssertToByteArrayEquals("Abc", 4, 4, 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{
Assert.AreEqual(expected, StringEx.FromByteArray(bytes));
}
private static void AssertToByteArrayEquals(string value, int reservedLength, params byte[] expected)
{
CollectionAssert.AreEqual(expected, StringEx.ToByteArray(value, reservedLength));
}
}
}

View File

@@ -0,0 +1,85 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;
namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class StringTests
{
[TestMethod]
public void WriteNullWIthReservedLengthZero()
{
AssertToByteArrayEquals(null, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthZero()
{
AssertToByteArrayEquals("", 0);
}
[TestMethod]
public void WriteAWithReservedLengthZero()
{
AssertToByteArrayEquals("A", 0);
}
[TestMethod]
public void WriteNullWithReservedLengthOne()
{
AssertToByteArrayEquals(null, 1, 0);
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthOne()
{
AssertToByteArrayEquals("", 1, 0);
}
[TestMethod]
public void WriteAWithReservedLengthOne()
{
AssertToByteArrayEquals("A", 1, (byte) 'A');
}
[TestMethod]
public void WriteAWithReservedLengthTwo()
{
AssertToByteArrayEquals("A", 2, (byte) 'A', 0);
}
[TestMethod]
public void WriteAbcWithReservedLengthOne()
{
AssertToByteArrayEquals("Abc", 1, (byte) 'A');
}
[TestMethod]
public void WriteAbcWithReservedLengthTwo()
{
AssertToByteArrayEquals("Abc", 2, (byte) 'A', (byte) 'b');
}
[TestMethod]
public void WriteAbcWithReservedLengthThree()
{
AssertToByteArrayEquals("Abc", 3, (byte) 'A', (byte) 'b', (byte) 'c');
}
[TestMethod]
public void WriteAbcWithReservedLengthFour()
{
AssertToByteArrayEquals("Abc", 4, (byte) 'A', (byte) 'b', (byte) 'c', 0);
}
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{
Assert.AreEqual(expected, String.FromByteArray(bytes));
}
private static void AssertToByteArrayEquals(string value, int reservedLength, params byte[] expected)
{
CollectionAssert.AreEqual(expected, String.ToByteArray(value, reservedLength));
}
}
}

View File

@@ -48,7 +48,15 @@ namespace S7.Net
//TODO: Fix This
get
{
return Connect() == ErrorCode.NoError;
try
{
Connect();
return true;
}
catch
{
return false;
}
}
}
@@ -72,16 +80,6 @@ namespace S7.Net
catch { return false; }
}
}
/// <summary>
/// Contains the last error registered when executing a function
/// </summary>
public string LastErrorString { get; private set; }
/// <summary>
/// Contains the last error code registered when executing a function
/// </summary>
public ErrorCode LastErrorCode { get; private set; }
/// <summary>
/// Creates a PLC object with all the parameters needed for connections.

197
S7.Net/PLCAddress.cs Normal file
View File

@@ -0,0 +1,197 @@
namespace S7.Net
{
internal class PLCAddress
{
private DataType dataType;
private int dbNumber;
private int startByte;
private int bitNumber;
private VarType varType;
public DataType DataType
{
get => dataType;
set => dataType = value;
}
public int DbNumber
{
get => dbNumber;
set => dbNumber = value;
}
public int StartByte
{
get => startByte;
set => startByte = value;
}
public int BitNumber
{
get => bitNumber;
set => bitNumber = value;
}
public VarType VarType
{
get => varType;
set => varType = value;
}
public PLCAddress(string address)
{
Parse(address, out dataType, out dbNumber, out varType, out startByte, out bitNumber);
}
public static void Parse(string input, out DataType dataType, out int dbNumber, out VarType varType, out int address, out int bitNumber)
{
bitNumber = -1;
dbNumber = 0;
switch (input.Substring(0, 2))
{
case "DB":
string[] strings = input.Split(new char[] { '.' });
if (strings.Length < 2)
throw new InvalidAddressException("To few periods for DB address");
dataType = DataType.DataBlock;
dbNumber = int.Parse(strings[0].Substring(2));
address = int.Parse(strings[1].Substring(3));
string dbType = strings[1].Substring(0, 3);
switch (dbType)
{
case "DBB":
varType = VarType.Byte;
return;
case "DBW":
varType = VarType.Word;
return;
case "DBD":
varType = VarType.DWord;
return;
case "DBX":
bitNumber = int.Parse(strings[2]);
if (bitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
varType = VarType.Bit;
return;
default:
throw new InvalidAddressException();
}
case "EB":
// Input byte
dataType = DataType.Input;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Byte;
return;
case "EW":
// Input word
dataType = DataType.Input;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Word;
return;
case "ED":
// Input double-word
dataType = DataType.Input;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.DWord;
return;
case "AB":
// Output byte
dataType = DataType.Output;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Byte;
return;
case "AW":
// Output word
dataType = DataType.Output;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Word;
return;
case "AD":
// Output double-word
dataType = DataType.Output;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.DWord;
return;
case "MB":
// Memory byte
dataType = DataType.Memory;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Byte;
return;
case "MW":
// Memory word
dataType = DataType.Memory;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.Word;
return;
case "MD":
// Memory double-word
dataType = DataType.Memory;
dbNumber = 0;
address = int.Parse(input.Substring(2));
varType = VarType.DWord;
return;
default:
switch (input.Substring(0, 1))
{
case "E":
case "I":
// Input
dataType = DataType.Input;
varType = VarType.Bit;
break;
case "A":
case "O":
// Output
dataType = DataType.Output;
varType = VarType.Bit;
break;
case "M":
// Memory
dataType = DataType.Memory;
varType = VarType.Byte;
break;
case "T":
// Timer
dataType = DataType.Timer;
dbNumber = 0;
address = int.Parse(input.Substring(1));
varType = VarType.Timer;
return;
case "Z":
case "C":
// Counter
dataType = DataType.Timer;
dbNumber = 0;
address = int.Parse(input.Substring(1));
varType = VarType.Counter;
return;
default:
throw new InvalidAddressException(string.Format("{0} is not a valid address", input.Substring(0, 1)));
}
string txt2 = input.Substring(1);
if (txt2.IndexOf(".") == -1)
throw new InvalidAddressException("To few periods for DB address");
address = int.Parse(txt2.Substring(0, txt2.IndexOf(".")));
bitNumber = int.Parse(txt2.Substring(txt2.IndexOf(".") + 1));
if (bitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
return;
}
}
}
}

View File

@@ -5,167 +5,6 @@ using System.Linq;
namespace S7.Net
{
internal class PLCAddress
{
public DataType dataType;
public int DBNumber;
public int Address;
public int BitNumber;
public VarType varType;
public PLCAddress(string address)
{
ParseString(address);
}
private void ParseString(string address)
{
BitNumber = -1;
switch (address.Substring(0, 2))
{
case "DB":
string[] strings = address.Split(new char[] { '.' });
if (strings.Length < 2)
throw new InvalidAddressException("To few periods for DB address");
dataType = DataType.DataBlock;
DBNumber = int.Parse(strings[0].Substring(2));
Address = int.Parse(strings[1].Substring(3));
string dbType = strings[1].Substring(0, 3);
switch (dbType)
{
case "DBB":
varType = VarType.Byte;
return;
case "DBW":
varType = VarType.Word;
return;
case "DBD":
varType = VarType.DWord;
return;
case "DBX":
BitNumber = int.Parse(strings[2]);
if (BitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
varType = VarType.Bit;
return;
default:
throw new InvalidAddressException();
}
case "EB":
// Input byte
dataType = DataType.Input;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Byte;
return;
case "EW":
// Input word
dataType = DataType.Input;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Word;
return;
case "ED":
// Input double-word
dataType = DataType.Input;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.DWord;
return;
case "AB":
// Output byte
dataType = DataType.Output;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Byte;
return;
case "AW":
// Output word
dataType = DataType.Output;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Word;
return;
case "AD":
// Output double-word
dataType = DataType.Output;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.DWord;
return;
case "MB":
// Memory byte
dataType = DataType.Memory;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Byte;
return;
case "MW":
// Memory word
dataType = DataType.Memory;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.Word;
return;
case "MD":
// Memory double-word
dataType = DataType.Memory;
DBNumber = 0;
Address = int.Parse(address.Substring(2));
varType = VarType.DWord;
return;
default:
switch (address.Substring(0, 1))
{
case "E":
case "I":
// Input
dataType = DataType.Input;
break;
case "A":
case "O":
// Output
dataType = DataType.Output;
break;
case "M":
// Memory
dataType = DataType.Memory;
break;
case "T":
// Timer
dataType = DataType.Timer;
DBNumber = 0;
Address = int.Parse(address.Substring(1));
varType = VarType.Timer;
return;
case "Z":
case "C":
// Counter
dataType = DataType.Timer;
DBNumber = 0;
Address = int.Parse(address.Substring(1));
varType = VarType.Counter;
return;
default:
throw new InvalidAddressException(string.Format("{0} is not a valid address", address.Substring(0, 1)));
}
string txt2 = address.Substring(1);
if (txt2.IndexOf(".") == -1)
throw new InvalidAddressException("To few periods for DB address");
Address = int.Parse(txt2.Substring(0, txt2.IndexOf(".")));
BitNumber = int.Parse(txt2.Substring(txt2.IndexOf(".") + 1));
if (BitNumber > 7)
throw new InvalidAddressException("Bit can only be 0-7");
return;
}
}
}
public partial class Plc
{
/// <summary>
@@ -312,15 +151,6 @@ namespace S7.Net
}
}
/// <summary>
/// Sets the <see cref="LastErrorCode"/> to <see cref="ErrorCode.NoError"/> and <see cref="LastErrorString"/> to <see cref="string.Empty"/>.
/// </summary>
public void ClearLastError()
{
LastErrorCode = ErrorCode.NoError;
LastErrorString = string.Empty;
}
/// <summary>
/// Given a S7 <see cref="VarType"/> (Bool, Word, DWord, etc.), it returns how many bytes to read.
/// </summary>
@@ -367,7 +197,7 @@ namespace S7.Net
{
// check for Return Code = Success
if (s7data[offset] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
// to Data bytes
offset += 4;

View File

@@ -14,10 +14,9 @@ namespace S7.Net
public partial class Plc
{
/// <summary>
/// Open a <see cref="Socket"/> and connects to the PLC, sending all the corrected package
/// and returning if the connection was successful (<see cref="ErrorCode.NoError"/>) of it was wrong.
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
/// </summary>
/// <returns>Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode</returns>
/// <returns>A task that represents the asynchronous open operation.</returns>
public async Task OpenAsync()
{
await ConnectAsync();
@@ -100,7 +99,7 @@ namespace S7.Net
public async Task<object> ReadAsync(string variable)
{
var adr = new PLCAddress(variable);
return await ReadAsync(adr.dataType, adr.DBNumber, adr.Address, adr.varType, 1, (byte)adr.BitNumber);
return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
}
/// <summary>
@@ -227,19 +226,17 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream); //TODO use Async
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
ParseDataIntoDataItems(s7data, dataItems);
}
catch (SocketException socketException)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
throw new PlcException(ErrorCode.ReadData, socketException);
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
throw new PlcException(ErrorCode.ReadData, exc);
}
return dataItems;
}
@@ -252,8 +249,8 @@ namespace S7.Net
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value)
{
int localIndex = 0;
int count = value.Length;
@@ -263,15 +260,10 @@ 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 = (int)Math.Min(count, 200);
ErrorCode lastError = await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
if (lastError != ErrorCode.NoError)
{
return lastError;
}
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
count -= maxToWrite;
localIndex += maxToWrite;
}
return ErrorCode.NoError;
}
/// <summary>
@@ -282,19 +274,13 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
{
if (bitAdr < 0 || bitAdr > 7)
throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
ErrorCode lastError = await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value);
if (lastError != ErrorCode.NoError)
{
return lastError;
}
return ErrorCode.NoError;
await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value);
}
/// <summary>
@@ -305,13 +291,13 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
{
if (value < 0 || value > 1)
throw new ArgumentException("Value must be 0 or 1", nameof(value));
return await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1);
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1);
}
/// <summary>
@@ -324,26 +310,29 @@ 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>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
{
if (bitAdr != -1)
{
//Must be writing a bit value as bitAdr is specified
if (value is bool)
{
return await WriteBitAsync(dataType, db, startByteAdr, bitAdr, (bool)value);
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, (bool) value);
}
else if (value is int intValue)
{
if (intValue < 0 || intValue > 7)
throw new ArgumentOutOfRangeException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr), nameof(bitAdr));
throw new ArgumentOutOfRangeException(
string.Format(
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
bitAdr), nameof(bitAdr));
return await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1);
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1);
}
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
else throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
}
return await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value));
else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value));
}
/// <summary>
@@ -352,11 +341,11 @@ namespace S7.Net
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <param name="value">Value to be written to the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteAsync(string variable, object value)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteAsync(string variable, object value)
{
var adr = new PLCAddress(variable);
return await WriteAsync(adr.dataType, adr.DBNumber, adr.Address, value, adr.BitNumber);
await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber);
}
/// <summary>
@@ -365,12 +354,11 @@ namespace S7.Net
/// <param name="structValue">The struct to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteStructAsync(object structValue, int db, int startByteAdr = 0)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0)
{
var bytes = Struct.ToBytes(structValue).ToList();
var errCode = await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
return errCode;
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
}
/// <summary>
@@ -379,12 +367,11 @@ namespace S7.Net
/// <param name="classValue">The class to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public async Task<ErrorCode> WriteClassAsync(object classValue, int db, int startByteAdr = 0)
/// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0)
{
var bytes = Types.Class.ToBytes(classValue).ToList();
var errCode = await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
return errCode;
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
}
private async Task<byte[]> ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count)
@@ -402,7 +389,7 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
@@ -427,15 +414,14 @@ namespace S7.Net
}
/// <summary>
/// Writes up to 200 bytes to the PLC and returns NoError if successful. You must specify the memory area type, memory are address, byte start address and bytes count.
/// If the write was not successful, check LastErrorCode or LastErrorString.
/// Writes up to 200 bytes to the PLC. You must specify the memory area type, memory are address, byte start address and bytes count.
/// </summary>
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
private async Task<ErrorCode> WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value)
/// <returns>A task that represents the asynchronous write operation.</returns>
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value)
{
byte[] bReceive = new byte[513];
int varCount = 0;
@@ -471,27 +457,23 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
{
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
return ErrorCode.NoError;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
private async Task<ErrorCode> WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
byte[] bReceive = new byte[513];
int varCount = 0;
try
{
var value = new[] { bitValue ? (byte)1 : (byte)0 };
var value = new[] {bitValue ? (byte) 1 : (byte) 0};
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
@@ -520,15 +502,11 @@ namespace S7.Net
var s7data = await COTP.TSDU.ReadAsync(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
return ErrorCode.NoError;
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
}

39
S7.Net/PlcException.cs Normal file
View File

@@ -0,0 +1,39 @@
using System;
namespace S7.Net
{
#if NET_FULL
[Serializable]
#endif
public class PlcException : Exception
{
public ErrorCode ErrorCode { get; }
public PlcException(ErrorCode errorCode) : this(errorCode, $"PLC communication failed with error '{errorCode}'.")
{
}
public PlcException(ErrorCode errorCode, Exception innerException) : this(errorCode, innerException.Message,
innerException)
{
}
public PlcException(ErrorCode errorCode, string message) : base(message)
{
ErrorCode = errorCode;
}
public PlcException(ErrorCode errorCode, string message, Exception inner) : base(message, inner)
{
ErrorCode = errorCode;
}
#if NET_FULL
protected PlcException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
ErrorCode = (ErrorCode) info.GetInt32(nameof(ErrorCode));
}
#endif
}
}

View File

@@ -11,16 +11,12 @@ namespace S7.Net
public partial class Plc
{
/// <summary>
/// Open a <see cref="Socket"/> and connects to the PLC, sending all the corrected package
/// and returning if the connection was successful (<see cref="ErrorCode.NoError"/>) of it was wrong.
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
/// </summary>
/// <returns>Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode</returns>
public ErrorCode Open()
public void Open()
{
if (Connect() != ErrorCode.NoError)
{
return LastErrorCode;
}
Connect();
try
{
stream.Write(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
@@ -41,14 +37,12 @@ namespace S7.Net
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ConnectionError;
LastErrorString = string.Format("Couldn't establish the connection to {0}.\nMessage: {1}", IP, exc.Message);
return ErrorCode.ConnectionError;
throw new PlcException(ErrorCode.ConnectionError,
$"Couldn't establish the connection to {IP}.\nMessage: {exc.Message}", exc);
}
return ErrorCode.NoError;
}
private ErrorCode Connect()
private void Connect()
{
try
{
@@ -59,23 +53,15 @@ namespace S7.Net
catch (SocketException sex)
{
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
if (sex.SocketErrorCode == SocketError.TimedOut)
{
LastErrorCode = ErrorCode.IPAddressNotAvailable;
}
else
{
LastErrorCode = ErrorCode.ConnectionError;
}
LastErrorString = sex.Message;
throw new PlcException(
sex.SocketErrorCode == SocketError.TimedOut
? ErrorCode.IPAddressNotAvailable
: ErrorCode.ConnectionError, sex);
}
catch (Exception ex)
{
LastErrorCode = ErrorCode.ConnectionError;
LastErrorString = ex.Message;
throw new PlcException(ErrorCode.ConnectionError, ex);
}
return LastErrorCode;
}
/// <summary>
@@ -133,7 +119,7 @@ namespace S7.Net
public object Read(string variable)
{
var adr = new PLCAddress(variable);
return Read(adr.dataType, adr.DBNumber, adr.Address, adr.varType, 1, (byte)adr.BitNumber);
return Read(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
}
/// <summary>
@@ -231,8 +217,7 @@ namespace S7.Net
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
public void WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
{
int localIndex = 0;
int count = value.Length;
@@ -241,16 +226,11 @@ namespace S7.Net
//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);
ErrorCode lastError = WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
if (lastError != ErrorCode.NoError)
{
return lastError;
}
var maxToWrite = Math.Min(count, 200);
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
count -= maxToWrite;
localIndex += maxToWrite;
}
return ErrorCode.NoError;
}
/// <summary>
@@ -261,36 +241,28 @@ namespace S7.Net
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
{
if (bitAdr < 0 || bitAdr > 7)
throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
ErrorCode lastError = WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value);
if (lastError != ErrorCode.NoError)
{
return lastError;
}
return ErrorCode.NoError;
WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value);
}
/// <summary>
/// Write a single bit from a DB with the specified index.
/// Write a single bit to a DB with the specified index.
/// </summary>
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="db">Address of the memory area (if you want to write DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
/// <param name="value">Value to write (0 or 1).</param>
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
{
if (value < 0 || value > 1)
throw new ArgumentException("Value must be 0 or 1", nameof(value));
return WriteBit(dataType, db, startByteAdr, bitAdr, value == 1);
WriteBit(dataType, db, startByteAdr, bitAdr, value == 1);
}
/// <summary>
@@ -303,26 +275,29 @@ 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>
/// <param name="bitAdr">The address of the bit. (0-7)</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
public void Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
{
if (bitAdr != -1)
{
//Must be writing a bit value as bitAdr is specified
if (value is bool)
{
return WriteBit(dataType, db, startByteAdr, bitAdr, (bool)value);
WriteBit(dataType, db, startByteAdr, bitAdr, (bool) value);
}
else if (value is int intValue)
{
if (intValue < 0 || intValue > 7)
throw new ArgumentOutOfRangeException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr), nameof(bitAdr));
throw new ArgumentOutOfRangeException(
string.Format(
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
bitAdr), nameof(bitAdr));
return WriteBit(dataType, db, startByteAdr, bitAdr, intValue == 1);
WriteBit(dataType, db, startByteAdr, bitAdr, intValue == 1);
}
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
else
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
}
return WriteBytes(dataType, db, startByteAdr, Serialization.SerializeValue(value));
else WriteBytes(dataType, db, startByteAdr, Serialization.SerializeValue(value));
}
/// <summary>
@@ -331,11 +306,10 @@ namespace S7.Net
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <param name="value">Value to be written to the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode Write(string variable, object value)
public void Write(string variable, object value)
{
var adr = new PLCAddress(variable);
return Write(adr.dataType, adr.DBNumber, adr.Address, value, adr.BitNumber);
Write(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber);
}
/// <summary>
@@ -344,10 +318,9 @@ namespace S7.Net
/// <param name="structValue">The struct to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteStruct(object structValue, int db, int startByteAdr = 0)
public void WriteStruct(object structValue, int db, int startByteAdr = 0)
{
return WriteStructAsync(structValue, db, startByteAdr).Result;
WriteStructAsync(structValue, db, startByteAdr).GetAwaiter().GetResult();
}
/// <summary>
@@ -356,10 +329,9 @@ namespace S7.Net
/// <param name="classValue">The class to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteClass(object classValue, int db, int startByteAdr = 0)
public void WriteClass(object classValue, int db, int startByteAdr = 0)
{
return WriteClassAsync(classValue, db, startByteAdr).Result;
WriteClassAsync(classValue, db, startByteAdr).GetAwaiter().GetResult();
}
private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, int count)
@@ -378,24 +350,16 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = s7data[cnt + 18];
return bytes;
}
catch (SocketException socketException)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
return null;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
return null;
throw new PlcException(ErrorCode.ReadData, exc);
}
}
@@ -414,7 +378,7 @@ namespace S7.Net
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
}
private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
{
int varCount = 0;
try
@@ -448,26 +412,22 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
{
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
return ErrorCode.NoError;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
private ErrorCode WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
int varCount = 0;
try
{
var value = new[] { bitValue ? (byte)1 : (byte)0 };
var value = new[] {bitValue ? (byte) 1 : (byte) 0};
varCount = value.Length;
// first create the header
int packageSize = 35 + value.Length;
@@ -496,15 +456,11 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
return ErrorCode.NoError;
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.WriteData;
LastErrorString = exc.Message;
return LastErrorCode;
throw new PlcException(ErrorCode.WriteData, exc);
}
}
@@ -531,7 +487,7 @@ namespace S7.Net
{
// first create the header
int packageSize = 19 + (dataItems.Count * 12);
Types.ByteArray package = new ByteArray(packageSize);
ByteArray package = new ByteArray(packageSize);
package.Add(ReadHeaderPackage(dataItems.Count));
// package.Add(0x02); // datenart
foreach (var dataItem in dataItems)
@@ -543,19 +499,13 @@ namespace S7.Net
var s7data = COTP.TSDU.Read(stream); //TODO use Async
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
ParseDataIntoDataItems(s7data, dataItems);
}
catch (SocketException socketException)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = socketException.Message;
}
catch (Exception exc)
{
LastErrorCode = ErrorCode.ReadData;
LastErrorString = exc.Message;
throw new PlcException(ErrorCode.ReadData, exc);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace S7.Net.Protocol
3, 0, //Destination TASP
192, //Parameter Code (tpdu-size)
1, //Parameter Length
9 //TPDU Size (2^9 = 512)
11 //TPDU Size (2^11 = 2048)
};
switch (cpu)

View File

@@ -23,7 +23,7 @@ namespace S7.Net.Protocol
foreach (var item in dataItems)
{
message.Add(Parameter.Template);
var value = Serialization.SerializeValue(item.Value);
var value = Serialization.SerializeDataItem(item);
var wordLen = item.Value is bool ? 1 : 2;
message[paramOffset + Parameter.Offsets.WordLength] = (byte) wordLen;

View File

@@ -11,6 +11,16 @@ namespace S7.Net.Protocol
return (ushort)((buf[index] << 8) + buf[index]);
}
public static byte[] SerializeDataItem(DataItem dataItem)
{
if (dataItem.Value is string s)
return dataItem.VarType == VarType.StringEx
? StringEx.ToByteArray(s, dataItem.Count)
: Types.String.ToByteArray(s, dataItem.Count);
return SerializeValue(dataItem.Value);
}
public static byte[] SerializeValue(object value)
{
switch (value.GetType().Name)
@@ -46,7 +56,10 @@ namespace S7.Net.Protocol
case "Single[]":
return Types.Single.ToByteArray((float[])value);
case "String":
return Types.String.ToByteArray(value as string);
// Hack: This is backwards compatible with the old code, but functionally it's broken
// if the consumer does not pay attention to string length.
var stringVal = (string) value;
return Types.String.ToByteArray(stringVal, stringVal.Length);
default:
throw new InvalidVariableTypeException();
}

View File

@@ -23,5 +23,6 @@
<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" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,4 +1,6 @@
namespace S7.Net.Types
using System;
namespace S7.Net.Types
{
/// <summary>
/// Create an instance of a memory block that can be read by using ReadMultipleVars
@@ -48,5 +50,42 @@
VarType = VarType.Byte;
Count = 1;
}
/// <summary>
/// Create an instance of <see cref="DataItem"/> from the supplied address.
/// </summary>
/// <param name="address">The address to create the DataItem for.</param>
/// <returns>A new <see cref="DataItem"/> instance with properties parsed from <paramref name="address"/>.</returns>
/// <remarks>The <see cref="Count" /> property is not parsed from the address.</remarks>
public static DataItem FromAddress(string address)
{
PLCAddress.Parse(address, out var dataType, out var dbNumber, out var varType, out var startByte,
out var bitNumber);
return new DataItem
{
DataType = dataType,
DB = dbNumber,
VarType = varType,
StartByteAdr = startByte,
BitAdr = (byte) bitNumber
};
}
/// <summary>
/// Create an instance of <see cref="DataItem"/> from the supplied address and value.
/// </summary>
/// <param name="address">The address to create the DataItem for.</param>
/// <param name="value">The value to be applied to the DataItem.</param>
/// <returns>A new <see cref="DataItem"/> instance with properties parsed from <paramref name="address"/> and the supplied value set.</returns>
public static DataItem FromAddressAndValue<T>(string address, T value)
{
var dataItem = FromAddress(address);
dataItem.Value = value;
if (typeof(T).IsArray) dataItem.Count = ((Array) dataItem.Value).Length;
return dataItem;
}
}
}

View File

@@ -6,11 +6,21 @@
public class String
{
/// <summary>
/// Converts a string to S7 bytes
/// Converts a string to <paramref name="reservedLength"/> of bytes, padded with 0-bytes if required.
/// </summary>
public static byte[] ToByteArray(string value)
/// <param name="value">The string to write to the PLC.</param>
/// <param name="reservedLength">The amount of bytes reserved for the <paramref name="value"/> in the PLC.</param>
public static byte[] ToByteArray(string value, int reservedLength)
{
return System.Text.Encoding.ASCII.GetBytes(value);
var length = value?.Length;
if (length > reservedLength) length = reservedLength;
var bytes = new byte[reservedLength];
if (length == null || length == 0) return bytes;
System.Text.Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 0);
return bytes;
}
/// <summary>

View File

@@ -1,4 +1,7 @@
namespace S7.Net.Types
using System;
using System.Text;
namespace S7.Net.Types
{
/// <summary>
/// Contains the methods to convert from S7 strings to C# strings
@@ -21,6 +24,27 @@
return System.Text.Encoding.ASCII.GetString(bytes, 2, length);
}
/// <summary>
/// Converts a <see cref="T:string"/> to S7 string with 2-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in bytes) allocated in PLC for string excluding header.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 2.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
{
if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}.");
var length = value?.Length;
if (length > reservedLength) length = reservedLength;
var bytes = new byte[(length ?? 0) + 2];
bytes[0] = (byte) reservedLength;
if (value == null) return bytes;
bytes[1] = (byte) Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 2);
return bytes;
}
}
}

13
appveyor.yml Normal file
View File

@@ -0,0 +1,13 @@
image: Visual Studio 2017
configuration: Release
install:
- choco install gitversion.portable -y
before_build:
- cmd: gitversion /l console /output buildserver /b %APPVEYOR_REPO_BRANCH%
- nuget 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
artifacts:
- path: artifacts\*.*