diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..c92669d --- /dev/null +++ b/GitVersion.yml @@ -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 \ No newline at end of file diff --git a/S7.Net.UnitTest/ConnectionRequestTest.cs b/S7.Net.UnitTest/ConnectionRequestTest.cs index 4615c3a..db7cabf 100644 --- a/S7.Net.UnitTest/ConnectionRequestTest.cs +++ b/S7.Net.UnitTest/ConnectionRequestTest.cs @@ -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) }; } } diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index bfaa48e..cbe6c47 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -78,6 +78,8 @@ + + diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 48d9ae1..0adb91b 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -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 } diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index 151b1bd..a3b01c9 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -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 } diff --git a/S7.Net.UnitTest/TypeTests/StringExTests.cs b/S7.Net.UnitTest/TypeTests/StringExTests.cs new file mode 100644 index 0000000..164ba4f --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/StringExTests.cs @@ -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)); + } + } +} diff --git a/S7.Net.UnitTest/TypeTests/StringTests.cs b/S7.Net.UnitTest/TypeTests/StringTests.cs new file mode 100644 index 0000000..15a528b --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/StringTests.cs @@ -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)); + } + } +} diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index 80cefba..284b81b 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -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; } } } - - /// - /// Contains the last error registered when executing a function - /// - public string LastErrorString { get; private set; } - - /// - /// Contains the last error code registered when executing a function - /// - public ErrorCode LastErrorCode { get; private set; } /// /// Creates a PLC object with all the parameters needed for connections. diff --git a/S7.Net/PLCAddress.cs b/S7.Net/PLCAddress.cs new file mode 100644 index 0000000..e78015f --- /dev/null +++ b/S7.Net/PLCAddress.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 2136b36..30aaaf0 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -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 { /// @@ -312,15 +151,6 @@ namespace S7.Net } } - /// - /// Sets the to and to . - /// - public void ClearLastError() - { - LastErrorCode = ErrorCode.NoError; - LastErrorString = string.Empty; - } - /// /// Given a S7 (Bool, Word, DWord, etc.), it returns how many bytes to read. /// @@ -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; diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index c829963..38cb7c6 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -14,10 +14,9 @@ namespace S7.Net public partial class Plc { /// - /// Open a and connects to the PLC, sending all the corrected package - /// and returning if the connection was successful () of it was wrong. + /// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup. /// - /// Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode + /// A task that represents the asynchronous open operation. public async Task OpenAsync() { await ConnectAsync(); @@ -100,7 +99,7 @@ namespace S7.Net public async Task 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); } /// @@ -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 /// 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. /// Start byte address. If you want to write DB1.DBW200, this is 200. /// Bytes to write. If more than 200, multiple requests will be made. - /// NoError if it was successful, or the error is specified - public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value) + /// A task that represents the asynchronous write operation. + 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; } /// @@ -282,19 +274,13 @@ namespace S7.Net /// Start byte address. If you want to write DB1.DBW200, this is 200. /// The address of the bit. (0-7) /// Bytes to write. If more than 200, multiple requests will be made. - /// NoError if it was successful, or the error is specified - public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value) + /// A task that represents the asynchronous write operation. + 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); } /// @@ -305,13 +291,13 @@ namespace S7.Net /// Start byte address. If you want to write DB1.DBW200, this is 200. /// The address of the bit. (0-7) /// Bytes to write. If more than 200, multiple requests will be made. - /// NoError if it was successful, or the error is specified - public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value) + /// A task that represents the asynchronous write operation. + 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); } /// @@ -324,26 +310,29 @@ namespace S7.Net /// Start byte address. If you want to read DB1.DBW200, this is 200. /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. /// The address of the bit. (0-7) - /// NoError if it was successful, or the error is specified - public async Task WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1) + /// A task that represents the asynchronous write operation. + 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)); } /// @@ -352,11 +341,11 @@ namespace S7.Net /// /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. /// Value to be written to the PLC - /// NoError if it was successful, or the error is specified - public async Task WriteAsync(string variable, object value) + /// A task that represents the asynchronous write operation. + 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); } /// @@ -365,12 +354,11 @@ namespace S7.Net /// The struct to be written /// Db address /// Start bytes on the PLC - /// NoError if it was successful, or the error is specified - public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0) + /// A task that represents the asynchronous write operation. + 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()); } /// @@ -379,12 +367,11 @@ namespace S7.Net /// The class to be written /// Db address /// Start bytes on the PLC - /// NoError if it was successful, or the error is specified - public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0) + /// A task that represents the asynchronous write operation. + 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 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 } /// - /// 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. /// /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. /// 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. /// Start byte address. If you want to read DB1.DBW200, this is 200. /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. - /// NoError if it was successful, or the error is specified - private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value) + /// A task that represents the asynchronous write operation. + 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 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); } } } diff --git a/S7.Net/PlcException.cs b/S7.Net/PlcException.cs new file mode 100644 index 0000000..772060b --- /dev/null +++ b/S7.Net/PlcException.cs @@ -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 + } +} \ No newline at end of file diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 8f1b5bc..dd7030c 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -11,16 +11,12 @@ namespace S7.Net public partial class Plc { /// - /// Open a and connects to the PLC, sending all the corrected package - /// and returning if the connection was successful () of it was wrong. + /// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup. /// - /// Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode - 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; } /// @@ -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); } /// @@ -231,8 +217,7 @@ namespace S7.Net /// 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. /// Start byte address. If you want to write DB1.DBW200, this is 200. /// Bytes to write. If more than 200, multiple requests will be made. - /// NoError if it was successful, or the error is specified - 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; } /// @@ -261,36 +241,28 @@ namespace S7.Net /// Start byte address. If you want to write DB1.DBW200, this is 200. /// The address of the bit. (0-7) /// Bytes to write. If more than 200, multiple requests will be made. - /// NoError if it was successful, or the error is specified - 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); } /// - /// Write a single bit from a DB with the specified index. + /// Write a single bit to a DB with the specified index. /// /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. - /// 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. + /// 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. /// Start byte address. If you want to write DB1.DBW200, this is 200. /// The address of the bit. (0-7) - /// Bytes to write. If more than 200, multiple requests will be made. - /// NoError if it was successful, or the error is specified - public ErrorCode WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value) + /// Value to write (0 or 1). + 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); } /// @@ -303,26 +275,29 @@ namespace S7.Net /// Start byte address. If you want to read DB1.DBW200, this is 200. /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. /// The address of the bit. (0-7) - /// NoError if it was successful, or the error is specified - 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)); } /// @@ -331,11 +306,10 @@ namespace S7.Net /// /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. /// Value to be written to the PLC - /// NoError if it was successful, or the error is specified - 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); } /// @@ -344,10 +318,9 @@ namespace S7.Net /// The struct to be written /// Db address /// Start bytes on the PLC - /// NoError if it was successful, or the error is specified - 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(); } /// @@ -356,10 +329,9 @@ namespace S7.Net /// The class to be written /// Db address /// Start bytes on the PLC - /// NoError if it was successful, or the error is specified - 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); } } } diff --git a/S7.Net/Protocol/ConnectionRequest.cs b/S7.Net/Protocol/ConnectionRequest.cs index f2403b2..0535cb2 100644 --- a/S7.Net/Protocol/ConnectionRequest.cs +++ b/S7.Net/Protocol/ConnectionRequest.cs @@ -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) diff --git a/S7.Net/Protocol/S7WriteMultiple.cs b/S7.Net/Protocol/S7WriteMultiple.cs index f4b2457..f5f32bd 100644 --- a/S7.Net/Protocol/S7WriteMultiple.cs +++ b/S7.Net/Protocol/S7WriteMultiple.cs @@ -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; diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs index acda50b..40cb629 100644 --- a/S7.Net/Protocol/Serialization.cs +++ b/S7.Net/Protocol/Serialization.cs @@ -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(); } diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj index 783ba81..d6c6216 100644 --- a/S7.Net/S7.Net.csproj +++ b/S7.Net/S7.Net.csproj @@ -23,5 +23,6 @@ + - \ No newline at end of file + diff --git a/S7.Net/Types/DataItem.cs b/S7.Net/Types/DataItem.cs index 5f48586..c40a604 100644 --- a/S7.Net/Types/DataItem.cs +++ b/S7.Net/Types/DataItem.cs @@ -1,4 +1,6 @@ -namespace S7.Net.Types +using System; + +namespace S7.Net.Types { /// /// Create an instance of a memory block that can be read by using ReadMultipleVars @@ -48,5 +50,42 @@ VarType = VarType.Byte; Count = 1; } + + /// + /// Create an instance of from the supplied address. + /// + /// The address to create the DataItem for. + /// A new instance with properties parsed from . + /// The property is not parsed from the address. + 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 + }; + } + + /// + /// Create an instance of from the supplied address and value. + /// + /// The address to create the DataItem for. + /// The value to be applied to the DataItem. + /// A new instance with properties parsed from and the supplied value set. + public static DataItem FromAddressAndValue(string address, T value) + { + var dataItem = FromAddress(address); + dataItem.Value = value; + + if (typeof(T).IsArray) dataItem.Count = ((Array) dataItem.Value).Length; + + return dataItem; + } } } diff --git a/S7.Net/Types/String.cs b/S7.Net/Types/String.cs index d1ca263..aaf3428 100644 --- a/S7.Net/Types/String.cs +++ b/S7.Net/Types/String.cs @@ -6,11 +6,21 @@ public class String { /// - /// Converts a string to S7 bytes + /// Converts a string to of bytes, padded with 0-bytes if required. /// - public static byte[] ToByteArray(string value) + /// The string to write to the PLC. + /// The amount of bytes reserved for the in the PLC. + 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; } /// diff --git a/S7.Net/Types/StringEx.cs b/S7.Net/Types/StringEx.cs index e22282b..1aec9ab 100644 --- a/S7.Net/Types/StringEx.cs +++ b/S7.Net/Types/StringEx.cs @@ -1,4 +1,7 @@ -namespace S7.Net.Types +using System; +using System.Text; + +namespace S7.Net.Types { /// /// Contains the methods to convert from S7 strings to C# strings @@ -21,6 +24,27 @@ return System.Text.Encoding.ASCII.GetString(bytes, 2, length); } - + + /// + /// Converts a to S7 string with 2-byte header. + /// + /// The string to convert to byte array. + /// The length (in bytes) allocated in PLC for string excluding header. + /// A containing the string header and string value with a maximum length of + 2. + 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; + } } } diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..95e1683 --- /dev/null +++ b/appveyor.yml @@ -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\*.* \ No newline at end of file