From 25bcfea835aa1d2c24636d3343f88c60278ae1eb Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 09:58:16 +0100 Subject: [PATCH 1/9] Add connectionState.Disposed --- Sharp7.Rx/Enums/ConnectionState.cs | 3 ++- Sharp7.Rx/Sharp7Connector.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sharp7.Rx/Enums/ConnectionState.cs b/Sharp7.Rx/Enums/ConnectionState.cs index 5cbedc5..ada7634 100644 --- a/Sharp7.Rx/Enums/ConnectionState.cs +++ b/Sharp7.Rx/Enums/ConnectionState.cs @@ -5,5 +5,6 @@ public enum ConnectionState Initial, Connected, DisconnectedByUser, - ConnectionLost + ConnectionLost, + Disposed } diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index 951ff0c..3fca9a7 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -206,6 +206,7 @@ internal class Sharp7Connector : IS7Connector sharp7 = null; } + connectionStateSubject?.OnNext(Enums.ConnectionState.Disposed); connectionStateSubject?.OnCompleted(); connectionStateSubject?.Dispose(); } From f3a92addaaa0175c3f02cf5b7b201a39fe534323 Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 10:26:23 +0100 Subject: [PATCH 2/9] Fix buffer length --- Sharp7.Rx/Sharp7Plc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sharp7.Rx/Sharp7Plc.cs b/Sharp7.Rx/Sharp7Plc.cs index bada2e2..501b578 100644 --- a/Sharp7.Rx/Sharp7Plc.cs +++ b/Sharp7.Rx/Sharp7Plc.cs @@ -127,7 +127,7 @@ public class Sharp7Plc : IPlc { var address = ParseAndVerify(variableName, typeof(TValue)); - var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token); + var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.BufferLength, address.DbNr, token); return S7ValueConverter.ReadFromBuffer(data, address); } From bfc9c93c803754d21754eafbfd06be979a364084 Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 10:26:54 +0100 Subject: [PATCH 3/9] improve error messages --- Sharp7.Rx/Sharp7Connector.cs | 38 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index 3fca9a7..843794d 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -104,11 +104,8 @@ internal class Sharp7Connector : IS7Connector .ToArray(); var result = await Task.Factory.StartNew(() => s7MultiVar.Read(), CancellationToken.None, TaskCreationOptions.None, scheduler); - if (result != 0) - { - var errorText = EvaluateErrorCode(result); - throw new S7CommunicationException($"Error in MultiVar request for variables: {string.Join(",", variableNames)} ({errorText})", result, errorText); - } + + EnsureSuccessOrThrow(result, $"Error in MultiVar request for variables: {string.Join(",", variableNames)}"); return buffers.ToDictionary(arg => arg.VariableName, arg => arg.Buffer); } @@ -150,11 +147,7 @@ internal class Sharp7Connector : IS7Connector await Task.Factory.StartNew(() => sharp7.ReadArea(operand.ToArea(), dbNo, startByteAddress, bytesToRead, S7WordLength.Byte, buffer), token, TaskCreationOptions.None, scheduler); token.ThrowIfCancellationRequested(); - if (result != 0) - { - var errorText = EvaluateErrorCode(result); - throw new S7CommunicationException($"Error reading {operand}{dbNo}:{startByteAddress}->{bytesToRead} ({errorText})", result, errorText); - } + EnsureSuccessOrThrow(result, $"Error reading {operand}{dbNo}:{startByteAddress}->{bytesToRead}"); return buffer; } @@ -170,11 +163,7 @@ internal class Sharp7Connector : IS7Connector var result = await Task.Factory.StartNew(() => sharp7.WriteArea(operand.ToArea(), dbNo, offsetStart, 1, S7WordLength.Bit, buffer), token, TaskCreationOptions.None, scheduler); token.ThrowIfCancellationRequested(); - if (result != 0) - { - var errorText = EvaluateErrorCode(result); - throw new S7CommunicationException($"Error writing {operand}{dbNo}:{startByteAddress} bit {bitAdress} ({errorText})", result, errorText); - } + EnsureSuccessOrThrow(result, $"Error writing {operand}{dbNo}:{startByteAddress} bit {bitAdress}"); } public async Task WriteBytes(Operand operand, ushort startByteAddress, byte[] data, ushort dbNo, CancellationToken token) @@ -184,11 +173,16 @@ internal class Sharp7Connector : IS7Connector var result = await Task.Factory.StartNew(() => sharp7.WriteArea(operand.ToArea(), dbNo, startByteAddress, data.Length, S7WordLength.Byte, data), token, TaskCreationOptions.None, scheduler); token.ThrowIfCancellationRequested(); - if (result != 0) - { - var errorText = EvaluateErrorCode(result); - throw new S7CommunicationException($"Error writing {operand}{dbNo}:{startByteAddress}.{data.Length} ({errorText})", result, errorText); - } + EnsureSuccessOrThrow(result, $"Error writing {operand}{dbNo}:{startByteAddress}.{data.Length}"); + } + + private void EnsureSuccessOrThrow(int result, string message) + { + if (result == 0) return; + + var errorText = EvaluateErrorCode(result); + // 0x40000: Maybe the DB is optimized or PUT/GET communication is not enabled. + throw new S7CommunicationException($"{message} ({errorText})", result, errorText); } @@ -243,8 +237,8 @@ internal class Sharp7Connector : IS7Connector if (sharp7 == null) throw new InvalidOperationException("S7 driver is not initialized."); - var errorText = sharp7.ErrorText(errorCode); - Logger?.LogError($"Error Code {errorCode} {errorText}"); + var errorText = $"0x{errorCode:X}: {sharp7.ErrorText(errorCode)}"; + Logger?.LogError($"S7 Error {errorText}"); if (S7ErrorCodes.AssumeConnectionLost(errorCode)) SetConnectionLostState(); From 280a894b1fc4a22696ad4ec4ad4bc0b7ef45a265 Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 11:18:48 +0100 Subject: [PATCH 4/9] Add additional hints to errors --- Sharp7.Rx/Sharp7Connector.cs | 36 ++++++++++++++++++++++++++---------- Sharp7.Rx/Sharp7Plc.cs | 18 +++++++++--------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index 843794d..e7b4c52 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -12,6 +12,18 @@ namespace Sharp7.Rx; internal class Sharp7Connector : IS7Connector { + private static readonly IReadOnlyDictionary additionalErrorTexts = new Dictionary + { + {0xC00000, "This happens when the DB does not exist."}, + {0x900000, "This happens when the DB is not long enough."}, + { + 0x40000, """ + This error occurs when the DB is "optimized" or "PUT/GET communication" is not enabled. + See https://snap7.sourceforge.net/snap7_client.html#target_compatibility. + """ + } + }; + private readonly BehaviorSubject connectionStateSubject = new(Enums.ConnectionState.Initial); private readonly int cpuSlotNr; @@ -176,15 +188,6 @@ internal class Sharp7Connector : IS7Connector EnsureSuccessOrThrow(result, $"Error writing {operand}{dbNo}:{startByteAddress}.{data.Length}"); } - private void EnsureSuccessOrThrow(int result, string message) - { - if (result == 0) return; - - var errorText = EvaluateErrorCode(result); - // 0x40000: Maybe the DB is optimized or PUT/GET communication is not enabled. - throw new S7CommunicationException($"{message} ({errorText})", result, errorText); - } - protected virtual void Dispose(bool disposing) { @@ -229,6 +232,19 @@ internal class Sharp7Connector : IS7Connector throw new InvalidOperationException("Plc is not connected"); } + private void EnsureSuccessOrThrow(int result, string message) + { + if (result == 0) return; + + var errorText = EvaluateErrorCode(result); + var completeMessage = $"{message}: {errorText}"; + + if (additionalErrorTexts.TryGetValue(result, out var additionalErrorText)) + completeMessage += Environment.NewLine + additionalErrorText; + + throw new S7CommunicationException(completeMessage, result, errorText); + } + private string EvaluateErrorCode(int errorCode) { if (errorCode == 0) @@ -237,7 +253,7 @@ internal class Sharp7Connector : IS7Connector if (sharp7 == null) throw new InvalidOperationException("S7 driver is not initialized."); - var errorText = $"0x{errorCode:X}: {sharp7.ErrorText(errorCode)}"; + var errorText = $"0x{errorCode:X}, {sharp7.ErrorText(errorCode)}"; Logger?.LogError($"S7 Error {errorText}"); if (S7ErrorCodes.AssumeConnectionLost(errorCode)) diff --git a/Sharp7.Rx/Sharp7Plc.cs b/Sharp7.Rx/Sharp7Plc.cs index 501b578..904294b 100644 --- a/Sharp7.Rx/Sharp7Plc.cs +++ b/Sharp7.Rx/Sharp7Plc.cs @@ -102,15 +102,6 @@ public class Sharp7Plc : IPlc }); } - private S7VariableAddress ParseAndVerify(string variableName, Type type) - { - var address = varaibleNameParser.Parse(variableName); - if (!address.MatchesType(type)) - throw new DataTypeMissmatchException($"Address \"{variableName}\" does not match type {type}.", type, address); - - return address; - } - public Task GetValue(string variableName) { return GetValue(variableName, CancellationToken.None); @@ -222,6 +213,15 @@ public class Sharp7Plc : IPlc return Unit.Default; } + private S7VariableAddress ParseAndVerify(string variableName, Type type) + { + var address = varaibleNameParser.Parse(variableName); + if (!address.MatchesType(type)) + throw new DataTypeMissmatchException($"Address \"{variableName}\" does not match type {type}.", type, address); + + return address; + } + private void PrintAndResetPerformanceStatistik() { if (performanceCoutner.Count == performanceCoutner.Capacity) From dd0af702625c0a55203d3b013a3b7dfff78041c5 Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 11:31:08 +0100 Subject: [PATCH 5/9] Fix multithreadign issue with lingering subscriptions --- .../Basics/ConcurrentSubjectDictionary.cs | 41 +++++++++++-------- Sharp7.Rx/Interfaces/IS7Connector.cs | 2 +- Sharp7.Rx/Sharp7Connector.cs | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs b/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs index 3ea9ef0..332e03c 100644 --- a/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs +++ b/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs @@ -53,11 +53,14 @@ internal class ConcurrentSubjectDictionary : IDisposable { lock (dictionaryLock) { - var subject = dictionary.AddOrUpdate(key, k => new SubjectWithRefCounter {Counter = 1, Subject = CreateSubject(k)}, (key1, counter) => - { - counter.Counter = counter.Counter + 1; - return counter; - }); + var subject = dictionary.AddOrUpdate( + key, + k => new SubjectWithRefCounter(CreateSubject(k)), + (_, subjectWithRefCounter) => + { + subjectWithRefCounter.IncreaseCount(); + return subjectWithRefCounter; + }); return new DisposableItem(subject.Subject.AsObservable(), () => RemoveIfNoLongerInUse(key)); } @@ -65,8 +68,7 @@ internal class ConcurrentSubjectDictionary : IDisposable public bool TryGetObserver(TKey key, out IObserver subject) { - SubjectWithRefCounter subjectWithRefCount; - if (dictionary.TryGetValue(key, out subjectWithRefCount)) + if (dictionary.TryGetValue(key, out var subjectWithRefCount)) { subject = subjectWithRefCount.Subject.AsObserver(); return true; @@ -101,15 +103,9 @@ internal class ConcurrentSubjectDictionary : IDisposable private void RemoveIfNoLongerInUse(TKey variableName) { lock (dictionaryLock) - { - SubjectWithRefCounter subjectWithRefCount; - if (dictionary.TryGetValue(variableName, out subjectWithRefCount)) - { - if (subjectWithRefCount.Counter == 1) - dictionary.TryRemove(variableName, out subjectWithRefCount); - else subjectWithRefCount.Counter--; - } - } + if (dictionary.TryGetValue(variableName, out var subjectWithRefCount)) + if (subjectWithRefCount.DecreaseCount() < 1) + dictionary.TryRemove(variableName, out _); } ~ConcurrentSubjectDictionary() @@ -119,7 +115,16 @@ internal class ConcurrentSubjectDictionary : IDisposable class SubjectWithRefCounter { - public int Counter { get; set; } - public ISubject Subject { get; set; } + private int counter = 1; + + public SubjectWithRefCounter(ISubject subject) + { + Subject = subject; + } + + public ISubject Subject { get; } + + public int DecreaseCount() => Interlocked.Decrement(ref counter); + public int IncreaseCount() => Interlocked.Increment(ref counter); } } diff --git a/Sharp7.Rx/Interfaces/IS7Connector.cs b/Sharp7.Rx/Interfaces/IS7Connector.cs index fe365c1..66566ac 100644 --- a/Sharp7.Rx/Interfaces/IS7Connector.cs +++ b/Sharp7.Rx/Interfaces/IS7Connector.cs @@ -17,5 +17,5 @@ internal interface IS7Connector : IDisposable Task WriteBit(Operand operand, ushort startByteAddress, byte bitAdress, bool value, ushort dbNo, CancellationToken token); Task WriteBytes(Operand operand, ushort startByteAddress, byte[] data, ushort dbNo, CancellationToken token); - Task> ExecuteMultiVarRequest(IReadOnlyList variableNames); + Task> ExecuteMultiVarRequest(IReadOnlyList variableNames); } diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index e7b4c52..e1684cf 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -98,7 +98,7 @@ internal class Sharp7Connector : IS7Connector await CloseConnection(); } - public async Task> ExecuteMultiVarRequest(IReadOnlyList variableNames) + public async Task> ExecuteMultiVarRequest(IReadOnlyList variableNames) { if (variableNames.IsEmpty()) return new Dictionary(); From 4701a224a7bbcde6de4ddc43b3f952b27e33f000 Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 12:33:24 +0100 Subject: [PATCH 6/9] Remove "S7" from some class names --- Sharp7.Rx.Tests/S7VariableNameParserTests.cs | 91 ------------------- .../ConvertBothWays.cs | 2 +- .../ConverterTestBase.cs | 12 +-- .../ReadFromBuffer.cs | 6 +- .../WriteToBuffer.cs | 6 +- .../MatchesType.cs | 6 +- Sharp7.Rx.Tests/VariableNameParserTests.cs | 91 +++++++++++++++++++ Sharp7.Rx/CacheVariableNameParser.cs | 10 +- Sharp7.Rx/Exceptions/S7Exception.cs | 8 +- Sharp7.Rx/Extensions/S7VariableExtensions.cs | 6 +- Sharp7.Rx/Interfaces/IS7VariableNameParser.cs | 7 -- Sharp7.Rx/Interfaces/IVariableNameParser.cs | 7 ++ Sharp7.Rx/Sharp7Connector.cs | 4 +- Sharp7.Rx/Sharp7Plc.cs | 10 +- ...{S7ValueConverter.cs => ValueConverter.cs} | 10 +- ...7VariableAddress.cs => VariableAddress.cs} | 2 +- ...bleNameParser.cs => VariableNameParser.cs} | 6 +- 17 files changed, 142 insertions(+), 142 deletions(-) delete mode 100644 Sharp7.Rx.Tests/S7VariableNameParserTests.cs rename Sharp7.Rx.Tests/{S7ValueConverterTests => ValueConverterTests}/ConvertBothWays.cs (92%) rename Sharp7.Rx.Tests/{S7ValueConverterTests => ValueConverterTests}/ConverterTestBase.cs (91%) rename Sharp7.Rx.Tests/{S7ValueConverterTests => ValueConverterTests}/ReadFromBuffer.cs (84%) rename Sharp7.Rx.Tests/{S7ValueConverterTests => ValueConverterTests}/WriteToBuffer.cs (84%) rename Sharp7.Rx.Tests/{S7VariableAddressTests => VariableAddressTests}/MatchesType.cs (92%) create mode 100644 Sharp7.Rx.Tests/VariableNameParserTests.cs delete mode 100644 Sharp7.Rx/Interfaces/IS7VariableNameParser.cs create mode 100644 Sharp7.Rx/Interfaces/IVariableNameParser.cs rename Sharp7.Rx/{S7ValueConverter.cs => ValueConverter.cs} (96%) rename Sharp7.Rx/{S7VariableAddress.cs => VariableAddress.cs} (96%) rename Sharp7.Rx/{S7VariableNameParser.cs => VariableNameParser.cs} (96%) diff --git a/Sharp7.Rx.Tests/S7VariableNameParserTests.cs b/Sharp7.Rx.Tests/S7VariableNameParserTests.cs deleted file mode 100644 index 50a460a..0000000 --- a/Sharp7.Rx.Tests/S7VariableNameParserTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -using DeepEqual.Syntax; -using NUnit.Framework; -using Sharp7.Rx.Enums; -using Shouldly; - -namespace Sharp7.Rx.Tests; - -[TestFixture] -internal class S7VariableNameParserTests -{ - [TestCaseSource(nameof(ValidTestCases))] - public void Run(TestCase tc) - { - var parser = new S7VariableNameParser(); - var resp = parser.Parse(tc.Input); - resp.ShouldDeepEqual(tc.Expected); - } - - [TestCase("DB506.Bit216", TestName = "Bit without Bit")] - [TestCase("DB506.Bit216.8", TestName = "Bit to high")] - [TestCase("DB506.String216", TestName = "String without Length")] - [TestCase("DB506.WString216", TestName = "WString without Length")] - - [TestCase("DB506.Int216.1", TestName = "Int with Length")] - [TestCase("DB506.UInt216.1", TestName = "UInt with Length")] - [TestCase("DB506.DInt216.1", TestName = "DInt with Length")] - [TestCase("DB506.UDInt216.1", TestName = "UDInt with Length")] - [TestCase("DB506.LInt216.1", TestName = "LInt with Length")] - [TestCase("DB506.ULInt216.1", TestName = "ULInt with Length")] - [TestCase("DB506.Real216.1", TestName = "LReal with Length")] - [TestCase("DB506.LReal216.1", TestName = "LReal with Length")] - - [TestCase("DB506.xx216", TestName = "Invalid type")] - [TestCase("DB506.216", TestName = "No type")] - [TestCase("DB506.Int216.", TestName = "Trailing dot")] - [TestCase("x506.Int216", TestName = "Wrong type")] - [TestCase("506.Int216", TestName = "No type")] - [TestCase("", TestName = "empty")] - [TestCase(" ", TestName = "space")] - [TestCase(" DB506.Int216", TestName = "leading space")] - [TestCase("DB506.Int216 ", TestName = "trailing space")] - [TestCase("DB.Int216 ", TestName = "No db")] - [TestCase("DB5061234.Int216.1", TestName = "DB too large")] - public void Invalid(string? input) - { - var parser = new S7VariableNameParser(); - Should.Throw(() => parser.Parse(input)); - } - - public static IEnumerable ValidTestCases() - { - yield return new TestCase("DB506.Bit216.2", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Bit = 2, Type = DbType.Bit}); - - yield return new TestCase("DB506.String216.10", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 10, Type = DbType.String}); - yield return new TestCase("DB506.WString216.10", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 10, Type = DbType.WString}); - - yield return new TestCase("DB506.Byte216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Type = DbType.Byte}); - yield return new TestCase("DB506.Byte216.100", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 100, Type = DbType.Byte}); - yield return new TestCase("DB506.Int216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); - yield return new TestCase("DB506.UInt216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.UInt}); - yield return new TestCase("DB506.DInt216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.DInt}); - yield return new TestCase("DB506.UDInt216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.UDInt}); - yield return new TestCase("DB506.LInt216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.LInt}); - yield return new TestCase("DB506.ULInt216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); - - yield return new TestCase("DB506.Real216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.Single}); - yield return new TestCase("DB506.LReal216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.Double}); - - - // Legacy - yield return new TestCase("DB13.DBX3.1", new S7VariableAddress {Operand = Operand.Db, DbNr = 13, Start = 3, Length = 1, Bit = 1, Type = DbType.Bit}); - yield return new TestCase("Db403.X5.2", new S7VariableAddress {Operand = Operand.Db, DbNr = 403, Start = 5, Length = 1, Bit = 2, Type = DbType.Bit}); - yield return new TestCase("DB55DBX23.6", new S7VariableAddress {Operand = Operand.Db, DbNr = 55, Start = 23, Length = 1, Bit = 6, Type = DbType.Bit}); - yield return new TestCase("DB1.S255.20", new S7VariableAddress {Operand = Operand.Db, DbNr = 1, Start = 255, Length = 20, Type = DbType.String}); - yield return new TestCase("DB5.String887.20", new S7VariableAddress {Operand = Operand.Db, DbNr = 5, Start = 887, Length = 20, Type = DbType.String}); - yield return new TestCase("DB506.B216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Type = DbType.Byte}); - yield return new TestCase("DB506.DBB216.5", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 5, Type = DbType.Byte}); - yield return new TestCase("DB506.D216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.Single}); - yield return new TestCase("DB506.DINT216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.DInt}); - yield return new TestCase("DB506.INT216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); - yield return new TestCase("DB506.DBW216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); - yield return new TestCase("DB506.DUL216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); - yield return new TestCase("DB506.DULINT216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); - yield return new TestCase("DB506.DULONG216", new S7VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); - } - - public record TestCase(string Input, S7VariableAddress Expected) - { - public override string ToString() => Input; - } -} diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs b/Sharp7.Rx.Tests/ValueConverterTests/ConvertBothWays.cs similarity index 92% rename from Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs rename to Sharp7.Rx.Tests/ValueConverterTests/ConvertBothWays.cs index b3956ac..39c9be3 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs +++ b/Sharp7.Rx.Tests/ValueConverterTests/ConvertBothWays.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using Shouldly; -namespace Sharp7.Rx.Tests.S7ValueConverterTests; +namespace Sharp7.Rx.Tests.ValueConverterTests; [TestFixture] internal class ConvertBothWays : ConverterTestBase diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ConverterTestBase.cs b/Sharp7.Rx.Tests/ValueConverterTests/ConverterTestBase.cs similarity index 91% rename from Sharp7.Rx.Tests/S7ValueConverterTests/ConverterTestBase.cs rename to Sharp7.Rx.Tests/ValueConverterTests/ConverterTestBase.cs index f344256..d09c3ed 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/ConverterTestBase.cs +++ b/Sharp7.Rx.Tests/ValueConverterTests/ConverterTestBase.cs @@ -1,15 +1,15 @@ using System.Reflection; using Sharp7.Rx.Interfaces; -namespace Sharp7.Rx.Tests.S7ValueConverterTests; +namespace Sharp7.Rx.Tests.ValueConverterTests; internal abstract class ConverterTestBase { - protected static readonly IS7VariableNameParser Parser = new S7VariableNameParser(); + protected static readonly IVariableNameParser Parser = new VariableNameParser(); public static MethodInfo CreateReadMethod(ConverterTestCase tc) { - var convertMi = typeof(S7ValueConverter).GetMethod(nameof(S7ValueConverter.ReadFromBuffer)); + var convertMi = typeof(ValueConverter).GetMethod(nameof(ValueConverter.ReadFromBuffer)); var convert = convertMi!.MakeGenericMethod(tc.Value.GetType()); return convert; } @@ -69,14 +69,14 @@ internal abstract class ConverterTestBase /// This helper method exists, since I could not manage to invoke a generic method /// accepring a Span<T> as parameter. /// - public static void WriteToBuffer(byte[] buffer, TValue value, S7VariableAddress address) + public static void WriteToBuffer(byte[] buffer, TValue value, VariableAddress address) { - S7ValueConverter.WriteToBuffer(buffer, value, address); + ValueConverter.WriteToBuffer(buffer, value, address); } public record ConverterTestCase(object Value, string Address, byte[] Data) { - public S7VariableAddress VariableAddress => Parser.Parse(Address); + public VariableAddress VariableAddress => Parser.Parse(Address); public override string ToString() => $"{Value.GetType().Name}, {Address}: {Value}"; } diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ReadFromBuffer.cs b/Sharp7.Rx.Tests/ValueConverterTests/ReadFromBuffer.cs similarity index 84% rename from Sharp7.Rx.Tests/S7ValueConverterTests/ReadFromBuffer.cs rename to Sharp7.Rx.Tests/ValueConverterTests/ReadFromBuffer.cs index cbb4542..66f08aa 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/ReadFromBuffer.cs +++ b/Sharp7.Rx.Tests/ValueConverterTests/ReadFromBuffer.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using Shouldly; -namespace Sharp7.Rx.Tests.S7ValueConverterTests; +namespace Sharp7.Rx.Tests.ValueConverterTests; [TestFixture] internal class ReadFromBuffer : ConverterTestBase @@ -34,7 +34,7 @@ internal class ReadFromBuffer : ConverterTestBase var variableAddress = Parser.Parse(address); //Act - Should.Throw(() => S7ValueConverter.ReadFromBuffer(data, variableAddress)); + Should.Throw(() => ValueConverter.ReadFromBuffer(data, variableAddress)); } [TestCase(123, "DB12.DINT3", new byte[] {0x01, 0x02, 0x03})] @@ -46,6 +46,6 @@ internal class ReadFromBuffer : ConverterTestBase var variableAddress = Parser.Parse(address); //Act - Should.Throw(() => S7ValueConverter.ReadFromBuffer(data, variableAddress)); + Should.Throw(() => ValueConverter.ReadFromBuffer(data, variableAddress)); } } diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs b/Sharp7.Rx.Tests/ValueConverterTests/WriteToBuffer.cs similarity index 84% rename from Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs rename to Sharp7.Rx.Tests/ValueConverterTests/WriteToBuffer.cs index 4e364fb..0d790f5 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs +++ b/Sharp7.Rx.Tests/ValueConverterTests/WriteToBuffer.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using Shouldly; -namespace Sharp7.Rx.Tests.S7ValueConverterTests; +namespace Sharp7.Rx.Tests.ValueConverterTests; [TestFixture] internal class WriteToBuffer : ConverterTestBase @@ -37,7 +37,7 @@ internal class WriteToBuffer : ConverterTestBase var buffer = new byte[bufferSize]; //Act - Should.Throw(() => S7ValueConverter.WriteToBuffer(buffer, input, variableAddress)); + Should.Throw(() => ValueConverter.WriteToBuffer(buffer, input, variableAddress)); } [TestCase((char) 18, "DB0.DBB0")] @@ -48,6 +48,6 @@ internal class WriteToBuffer : ConverterTestBase var buffer = new byte[variableAddress.BufferLength]; //Act - Should.Throw(() => S7ValueConverter.WriteToBuffer(buffer, input, variableAddress)); + Should.Throw(() => ValueConverter.WriteToBuffer(buffer, input, variableAddress)); } } diff --git a/Sharp7.Rx.Tests/S7VariableAddressTests/MatchesType.cs b/Sharp7.Rx.Tests/VariableAddressTests/MatchesType.cs similarity index 92% rename from Sharp7.Rx.Tests/S7VariableAddressTests/MatchesType.cs rename to Sharp7.Rx.Tests/VariableAddressTests/MatchesType.cs index 458c17d..abaeae5 100644 --- a/Sharp7.Rx.Tests/S7VariableAddressTests/MatchesType.cs +++ b/Sharp7.Rx.Tests/VariableAddressTests/MatchesType.cs @@ -1,15 +1,15 @@ using NUnit.Framework; using Sharp7.Rx.Extensions; using Sharp7.Rx.Interfaces; -using Sharp7.Rx.Tests.S7ValueConverterTests; +using Sharp7.Rx.Tests.ValueConverterTests; using Shouldly; -namespace Sharp7.Rx.Tests.S7VariableAddressTests; +namespace Sharp7.Rx.Tests.VariableAddressTests; [TestFixture] public class MatchesType { - static readonly IS7VariableNameParser parser = new S7VariableNameParser(); + static readonly IVariableNameParser parser = new VariableNameParser(); private static readonly IReadOnlyList typeList = new[] { diff --git a/Sharp7.Rx.Tests/VariableNameParserTests.cs b/Sharp7.Rx.Tests/VariableNameParserTests.cs new file mode 100644 index 0000000..577f7d0 --- /dev/null +++ b/Sharp7.Rx.Tests/VariableNameParserTests.cs @@ -0,0 +1,91 @@ +using DeepEqual.Syntax; +using NUnit.Framework; +using Sharp7.Rx.Enums; +using Shouldly; + +namespace Sharp7.Rx.Tests; + +[TestFixture] +internal class VariableNameParserTests +{ + [TestCaseSource(nameof(ValidTestCases))] + public void Run(TestCase tc) + { + var parser = new VariableNameParser(); + var resp = parser.Parse(tc.Input); + resp.ShouldDeepEqual(tc.Expected); + } + + [TestCase("DB506.Bit216", TestName = "Bit without Bit")] + [TestCase("DB506.Bit216.8", TestName = "Bit to high")] + [TestCase("DB506.String216", TestName = "String without Length")] + [TestCase("DB506.WString216", TestName = "WString without Length")] + + [TestCase("DB506.Int216.1", TestName = "Int with Length")] + [TestCase("DB506.UInt216.1", TestName = "UInt with Length")] + [TestCase("DB506.DInt216.1", TestName = "DInt with Length")] + [TestCase("DB506.UDInt216.1", TestName = "UDInt with Length")] + [TestCase("DB506.LInt216.1", TestName = "LInt with Length")] + [TestCase("DB506.ULInt216.1", TestName = "ULInt with Length")] + [TestCase("DB506.Real216.1", TestName = "LReal with Length")] + [TestCase("DB506.LReal216.1", TestName = "LReal with Length")] + + [TestCase("DB506.xx216", TestName = "Invalid type")] + [TestCase("DB506.216", TestName = "No type")] + [TestCase("DB506.Int216.", TestName = "Trailing dot")] + [TestCase("x506.Int216", TestName = "Wrong type")] + [TestCase("506.Int216", TestName = "No type")] + [TestCase("", TestName = "empty")] + [TestCase(" ", TestName = "space")] + [TestCase(" DB506.Int216", TestName = "leading space")] + [TestCase("DB506.Int216 ", TestName = "trailing space")] + [TestCase("DB.Int216 ", TestName = "No db")] + [TestCase("DB5061234.Int216.1", TestName = "DB too large")] + public void Invalid(string? input) + { + var parser = new VariableNameParser(); + Should.Throw(() => parser.Parse(input)); + } + + public static IEnumerable ValidTestCases() + { + yield return new TestCase("DB506.Bit216.2", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Bit = 2, Type = DbType.Bit}); + + yield return new TestCase("DB506.String216.10", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 10, Type = DbType.String}); + yield return new TestCase("DB506.WString216.10", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 10, Type = DbType.WString}); + + yield return new TestCase("DB506.Byte216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Type = DbType.Byte}); + yield return new TestCase("DB506.Byte216.100", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 100, Type = DbType.Byte}); + yield return new TestCase("DB506.Int216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); + yield return new TestCase("DB506.UInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.UInt}); + yield return new TestCase("DB506.DInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.DInt}); + yield return new TestCase("DB506.UDInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.UDInt}); + yield return new TestCase("DB506.LInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.LInt}); + yield return new TestCase("DB506.ULInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); + + yield return new TestCase("DB506.Real216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.Single}); + yield return new TestCase("DB506.LReal216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.Double}); + + + // Legacy + yield return new TestCase("DB13.DBX3.1", new VariableAddress {Operand = Operand.Db, DbNr = 13, Start = 3, Length = 1, Bit = 1, Type = DbType.Bit}); + yield return new TestCase("Db403.X5.2", new VariableAddress {Operand = Operand.Db, DbNr = 403, Start = 5, Length = 1, Bit = 2, Type = DbType.Bit}); + yield return new TestCase("DB55DBX23.6", new VariableAddress {Operand = Operand.Db, DbNr = 55, Start = 23, Length = 1, Bit = 6, Type = DbType.Bit}); + yield return new TestCase("DB1.S255.20", new VariableAddress {Operand = Operand.Db, DbNr = 1, Start = 255, Length = 20, Type = DbType.String}); + yield return new TestCase("DB5.String887.20", new VariableAddress {Operand = Operand.Db, DbNr = 5, Start = 887, Length = 20, Type = DbType.String}); + yield return new TestCase("DB506.B216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Type = DbType.Byte}); + yield return new TestCase("DB506.DBB216.5", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 5, Type = DbType.Byte}); + yield return new TestCase("DB506.D216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.Single}); + yield return new TestCase("DB506.DINT216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.DInt}); + yield return new TestCase("DB506.INT216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); + yield return new TestCase("DB506.DBW216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); + yield return new TestCase("DB506.DUL216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); + yield return new TestCase("DB506.DULINT216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); + yield return new TestCase("DB506.DULONG216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); + } + + public record TestCase(string Input, VariableAddress Expected) + { + public override string ToString() => Input; + } +} diff --git a/Sharp7.Rx/CacheVariableNameParser.cs b/Sharp7.Rx/CacheVariableNameParser.cs index d24ef98..c4d42a9 100644 --- a/Sharp7.Rx/CacheVariableNameParser.cs +++ b/Sharp7.Rx/CacheVariableNameParser.cs @@ -3,16 +3,16 @@ using Sharp7.Rx.Interfaces; namespace Sharp7.Rx; -internal class CacheVariableNameParser : IS7VariableNameParser +internal class CacheVariableNameParser : IVariableNameParser { - private static readonly ConcurrentDictionary addressCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary addressCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private readonly IS7VariableNameParser inner; + private readonly IVariableNameParser inner; - public CacheVariableNameParser(IS7VariableNameParser inner) + public CacheVariableNameParser(IVariableNameParser inner) { this.inner = inner; } - public S7VariableAddress Parse(string input) => addressCache.GetOrAdd(input, inner.Parse); + public VariableAddress Parse(string input) => addressCache.GetOrAdd(input, inner.Parse); } diff --git a/Sharp7.Rx/Exceptions/S7Exception.cs b/Sharp7.Rx/Exceptions/S7Exception.cs index 2d8fb44..7fd9915 100644 --- a/Sharp7.Rx/Exceptions/S7Exception.cs +++ b/Sharp7.Rx/Exceptions/S7Exception.cs @@ -31,13 +31,13 @@ public class S7CommunicationException : S7Exception public class DataTypeMissmatchException : S7Exception { - internal DataTypeMissmatchException(string message, Type type, S7VariableAddress address) : base(message) + internal DataTypeMissmatchException(string message, Type type, VariableAddress address) : base(message) { Type = type; Address = address.ToString(); } - internal DataTypeMissmatchException(string message, Exception innerException, Type type, S7VariableAddress address) : base(message, innerException) + internal DataTypeMissmatchException(string message, Exception innerException, Type type, VariableAddress address) : base(message, innerException) { Type = type; Address = address.ToString(); @@ -50,13 +50,13 @@ public class DataTypeMissmatchException : S7Exception public class UnsupportedS7TypeException : S7Exception { - internal UnsupportedS7TypeException(string message, Type type, S7VariableAddress address) : base(message) + internal UnsupportedS7TypeException(string message, Type type, VariableAddress address) : base(message) { Type = type; Address = address.ToString(); } - internal UnsupportedS7TypeException(string message, Exception innerException, Type type, S7VariableAddress address) : base(message, innerException) + internal UnsupportedS7TypeException(string message, Exception innerException, Type type, VariableAddress address) : base(message, innerException) { Type = type; Address = address.ToString(); diff --git a/Sharp7.Rx/Extensions/S7VariableExtensions.cs b/Sharp7.Rx/Extensions/S7VariableExtensions.cs index 40c8eed..ae04de3 100644 --- a/Sharp7.Rx/Extensions/S7VariableExtensions.cs +++ b/Sharp7.Rx/Extensions/S7VariableExtensions.cs @@ -2,9 +2,9 @@ namespace Sharp7.Rx.Extensions; -internal static class S7VariableAddressExtensions +internal static class VariableAddressExtensions { - private static readonly Dictionary> supportedTypeMap = new() + private static readonly Dictionary> supportedTypeMap = new() { {typeof(bool), a => a.Type == DbType.Bit}, {typeof(string), a => a.Type is DbType.String or DbType.WString or DbType.Byte }, @@ -20,6 +20,6 @@ internal static class S7VariableAddressExtensions {typeof(byte[]), a => a.Type==DbType.Byte}, }; - public static bool MatchesType(this S7VariableAddress address, Type type) => + public static bool MatchesType(this VariableAddress address, Type type) => supportedTypeMap.TryGetValue(type, out var map) && map(address); } diff --git a/Sharp7.Rx/Interfaces/IS7VariableNameParser.cs b/Sharp7.Rx/Interfaces/IS7VariableNameParser.cs deleted file mode 100644 index ae81d67..0000000 --- a/Sharp7.Rx/Interfaces/IS7VariableNameParser.cs +++ /dev/null @@ -1,7 +0,0 @@ -#nullable enable -namespace Sharp7.Rx.Interfaces; - -internal interface IS7VariableNameParser -{ - S7VariableAddress Parse(string input); -} diff --git a/Sharp7.Rx/Interfaces/IVariableNameParser.cs b/Sharp7.Rx/Interfaces/IVariableNameParser.cs new file mode 100644 index 0000000..d6f8219 --- /dev/null +++ b/Sharp7.Rx/Interfaces/IVariableNameParser.cs @@ -0,0 +1,7 @@ +#nullable enable +namespace Sharp7.Rx.Interfaces; + +internal interface IVariableNameParser +{ + VariableAddress Parse(string input); +} diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index e1684cf..695e20d 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -32,13 +32,13 @@ internal class Sharp7Connector : IS7Connector private readonly int port; private readonly int rackNr; private readonly LimitedConcurrencyLevelTaskScheduler scheduler = new(maxDegreeOfParallelism: 1); - private readonly IS7VariableNameParser variableNameParser; + private readonly IVariableNameParser variableNameParser; private bool disposed; private S7Client sharp7; - public Sharp7Connector(PlcConnectionSettings settings, IS7VariableNameParser variableNameParser) + public Sharp7Connector(PlcConnectionSettings settings, IVariableNameParser variableNameParser) { this.variableNameParser = variableNameParser; ipAddress = settings.IpAddress; diff --git a/Sharp7.Rx/Sharp7Plc.cs b/Sharp7.Rx/Sharp7Plc.cs index 904294b..b799924 100644 --- a/Sharp7.Rx/Sharp7Plc.cs +++ b/Sharp7.Rx/Sharp7Plc.cs @@ -17,7 +17,7 @@ public class Sharp7Plc : IPlc private readonly ConcurrentSubjectDictionary multiVariableSubscriptions = new(StringComparer.InvariantCultureIgnoreCase); private readonly List performanceCoutner = new(1000); private readonly PlcConnectionSettings plcConnectionSettings; - private readonly IS7VariableNameParser varaibleNameParser = new CacheVariableNameParser(new S7VariableNameParser()); + private readonly IVariableNameParser varaibleNameParser = new CacheVariableNameParser(new VariableNameParser()); private bool disposed; private Sharp7Connector s7Connector; @@ -89,7 +89,7 @@ public class Sharp7Plc : IPlc Observable.FromAsync(() => GetValue(variableName)) .Concat( disposeableContainer.Observable - .Select(bytes => S7ValueConverter.ReadFromBuffer(bytes, address)) + .Select(bytes => ValueConverter.ReadFromBuffer(bytes, address)) ); if (transmissionMode == TransmissionMode.OnChange) @@ -119,7 +119,7 @@ public class Sharp7Plc : IPlc var address = ParseAndVerify(variableName, typeof(TValue)); var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.BufferLength, address.DbNr, token); - return S7ValueConverter.ReadFromBuffer(data, address); + return ValueConverter.ReadFromBuffer(data, address); } public async Task InitializeAsync() @@ -161,7 +161,7 @@ public class Sharp7Plc : IPlc { // TODO: Use ArrayPool.Rent() once we drop Framwework support var bytes = new byte[address.BufferLength]; - S7ValueConverter.WriteToBuffer(bytes, value, address); + ValueConverter.WriteToBuffer(bytes, value, address); await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); } @@ -213,7 +213,7 @@ public class Sharp7Plc : IPlc return Unit.Default; } - private S7VariableAddress ParseAndVerify(string variableName, Type type) + private VariableAddress ParseAndVerify(string variableName, Type type) { var address = varaibleNameParser.Parse(variableName); if (!address.MatchesType(type)) diff --git a/Sharp7.Rx/S7ValueConverter.cs b/Sharp7.Rx/ValueConverter.cs similarity index 96% rename from Sharp7.Rx/S7ValueConverter.cs rename to Sharp7.Rx/ValueConverter.cs index 4bc23f8..83078c2 100644 --- a/Sharp7.Rx/S7ValueConverter.cs +++ b/Sharp7.Rx/ValueConverter.cs @@ -5,7 +5,7 @@ using Sharp7.Rx.Enums; namespace Sharp7.Rx; -internal static class S7ValueConverter +internal static class ValueConverter { private static readonly Dictionary writeFunctions = new() { @@ -175,7 +175,7 @@ internal static class S7ValueConverter }, }; - public static TValue ReadFromBuffer(byte[] buffer, S7VariableAddress address) + public static TValue ReadFromBuffer(byte[] buffer, VariableAddress address) { // Todo: Change to Span when switched to newer .net @@ -191,7 +191,7 @@ internal static class S7ValueConverter return (TValue) result; } - public static void WriteToBuffer(Span buffer, TValue value, S7VariableAddress address) + public static void WriteToBuffer(Span buffer, TValue value, VariableAddress address) { if (buffer.Length < address.BufferLength) throw new ArgumentException($"Buffer must be at least {address.BufferLength} bytes long for {address}", nameof(buffer)); @@ -204,7 +204,7 @@ internal static class S7ValueConverter writeFunc(buffer, address, value); } - delegate object ReadFunc(byte[] data, S7VariableAddress address); + delegate object ReadFunc(byte[] data, VariableAddress address); [StructLayout(LayoutKind.Explicit)] private struct UInt32SingleMap @@ -220,5 +220,5 @@ internal static class S7ValueConverter [FieldOffset(0)] public double Double; } - delegate void WriteFunc(Span data, S7VariableAddress address, object value); + delegate void WriteFunc(Span data, VariableAddress address, object value); } diff --git a/Sharp7.Rx/S7VariableAddress.cs b/Sharp7.Rx/VariableAddress.cs similarity index 96% rename from Sharp7.Rx/S7VariableAddress.cs rename to Sharp7.Rx/VariableAddress.cs index f04bd34..7e116e7 100644 --- a/Sharp7.Rx/S7VariableAddress.cs +++ b/Sharp7.Rx/VariableAddress.cs @@ -4,7 +4,7 @@ using Sharp7.Rx.Enums; namespace Sharp7.Rx; [NoReorder] -internal class S7VariableAddress +internal class VariableAddress { public Operand Operand { get; set; } public ushort DbNr { get; set; } diff --git a/Sharp7.Rx/S7VariableNameParser.cs b/Sharp7.Rx/VariableNameParser.cs similarity index 96% rename from Sharp7.Rx/S7VariableNameParser.cs rename to Sharp7.Rx/VariableNameParser.cs index b53fcc9..8c2bef7 100644 --- a/Sharp7.Rx/S7VariableNameParser.cs +++ b/Sharp7.Rx/VariableNameParser.cs @@ -6,7 +6,7 @@ using Sharp7.Rx.Interfaces; namespace Sharp7.Rx; -internal class S7VariableNameParser : IS7VariableNameParser +internal class VariableNameParser : IVariableNameParser { private static readonly Regex regex = new(@"^(?db)(?\d+)\.?(?[a-z]+)(?\d+)(\.(?\d+))?$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant); @@ -46,7 +46,7 @@ internal class S7VariableNameParser : IS7VariableNameParser {"x", DbType.Bit}, }; - public S7VariableAddress Parse(string input) + public VariableAddress Parse(string input) { if (input == null) throw new ArgumentNullException(nameof(input)); @@ -111,7 +111,7 @@ internal class S7VariableNameParser : IS7VariableNameParser byte? bit = type == DbType.Bit ? GetBit() : null; - var s7VariableAddress = new S7VariableAddress + var s7VariableAddress = new VariableAddress { Operand = operand, DbNr = dbNr, From 996706df2f1fea585deb65d788b6959d8c3341ec Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 12:38:16 +0100 Subject: [PATCH 7/9] Move GetAdditionalErrorText to S7ErrorCodes --- Sharp7.Rx/S7ErrorCodes.cs | 19 ++++++++++++++++++- Sharp7.Rx/Sharp7Connector.cs | 15 ++------------- Sharp7.Rx/Sharp7Plc.cs | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Sharp7.Rx/S7ErrorCodes.cs b/Sharp7.Rx/S7ErrorCodes.cs index f076bf8..5e54922 100644 --- a/Sharp7.Rx/S7ErrorCodes.cs +++ b/Sharp7.Rx/S7ErrorCodes.cs @@ -1,4 +1,6 @@ -namespace Sharp7.Rx; +#nullable enable + +namespace Sharp7.Rx; public static class S7ErrorCodes { @@ -12,6 +14,18 @@ public static class S7ErrorCodes 0x900000, // CPU: Address out of range }; + private static readonly IReadOnlyDictionary additionalErrorTexts = new Dictionary + { + {0xC00000, "This happens when the DB does not exist."}, + {0x900000, "This happens when the DB is not long enough."}, + { + 0x40000, """ + This error occurs when the DB is "optimized" or "PUT/GET communication" is not enabled. + See https://snap7.sourceforge.net/snap7_client.html#target_compatibility. + """ + } + }; + /// /// Some error codes indicate connection lost, in which case, the driver tries to reestablish connection. /// Other error codes indicate a user error, like reading from an unavailable DB or exceeding @@ -21,4 +35,7 @@ public static class S7ErrorCodes { return !notDisconnectedErrorCodes.Contains(errorCode); } + + public static string? GetAdditionalErrorText(int errorCode) => + additionalErrorTexts.TryGetValue(errorCode, out var text) ? text : null; } diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index 695e20d..9e4ceca 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -12,18 +12,6 @@ namespace Sharp7.Rx; internal class Sharp7Connector : IS7Connector { - private static readonly IReadOnlyDictionary additionalErrorTexts = new Dictionary - { - {0xC00000, "This happens when the DB does not exist."}, - {0x900000, "This happens when the DB is not long enough."}, - { - 0x40000, """ - This error occurs when the DB is "optimized" or "PUT/GET communication" is not enabled. - See https://snap7.sourceforge.net/snap7_client.html#target_compatibility. - """ - } - }; - private readonly BehaviorSubject connectionStateSubject = new(Enums.ConnectionState.Initial); private readonly int cpuSlotNr; @@ -239,7 +227,8 @@ internal class Sharp7Connector : IS7Connector var errorText = EvaluateErrorCode(result); var completeMessage = $"{message}: {errorText}"; - if (additionalErrorTexts.TryGetValue(result, out var additionalErrorText)) + var additionalErrorText = S7ErrorCodes.GetAdditionalErrorText(result); + if (additionalErrorText != null) completeMessage += Environment.NewLine + additionalErrorText; throw new S7CommunicationException(completeMessage, result, errorText); diff --git a/Sharp7.Rx/Sharp7Plc.cs b/Sharp7.Rx/Sharp7Plc.cs index b799924..8eff549 100644 --- a/Sharp7.Rx/Sharp7Plc.cs +++ b/Sharp7.Rx/Sharp7Plc.cs @@ -17,7 +17,7 @@ public class Sharp7Plc : IPlc private readonly ConcurrentSubjectDictionary multiVariableSubscriptions = new(StringComparer.InvariantCultureIgnoreCase); private readonly List performanceCoutner = new(1000); private readonly PlcConnectionSettings plcConnectionSettings; - private readonly IVariableNameParser varaibleNameParser = new CacheVariableNameParser(new VariableNameParser()); + private readonly CacheVariableNameParser varaibleNameParser = new CacheVariableNameParser(new VariableNameParser()); private bool disposed; private Sharp7Connector s7Connector; From 56b300b3ab9f95409131428ce23e2b02c82787b3 Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 12:46:45 +0100 Subject: [PATCH 8/9] Convert VariableAddress to record --- .../VariableAddressTests/MatchesType.cs | 8 +-- Sharp7.Rx.Tests/VariableNameParserTests.cs | 56 +++++++++---------- Sharp7.Rx/AssemblyInfo.cs | 2 +- Sharp7.Rx/Extensions/OperandExtensions.cs | 1 - Sharp7.Rx/Extensions/PlcExtensions.cs | 3 +- Sharp7.Rx/Extensions/S7VariableExtensions.cs | 22 ++++---- Sharp7.Rx/Sharp7Connector.cs | 2 +- Sharp7.Rx/Sharp7Plc.cs | 6 +- Sharp7.Rx/VariableAddress.cs | 24 ++++---- Sharp7.Rx/VariableNameParser.cs | 10 +--- 10 files changed, 62 insertions(+), 72 deletions(-) diff --git a/Sharp7.Rx.Tests/VariableAddressTests/MatchesType.cs b/Sharp7.Rx.Tests/VariableAddressTests/MatchesType.cs index abaeae5..0feffb3 100644 --- a/Sharp7.Rx.Tests/VariableAddressTests/MatchesType.cs +++ b/Sharp7.Rx.Tests/VariableAddressTests/MatchesType.cs @@ -61,10 +61,10 @@ public class MatchesType // Explicitly remove some valid combinations .Where(tc => !( - (tc.Type == typeof(string) && tc.Address == "DB99.Byte5") || - (tc.Type == typeof(string) && tc.Address == "DB99.Byte5.4") || - (tc.Type == typeof(byte[]) && tc.Address == "DB99.Byte5") - )) + (tc.Type == typeof(string) && tc.Address == "DB99.Byte5") || + (tc.Type == typeof(string) && tc.Address == "DB99.Byte5.4") || + (tc.Type == typeof(byte[]) && tc.Address == "DB99.Byte5") + )) ; } diff --git a/Sharp7.Rx.Tests/VariableNameParserTests.cs b/Sharp7.Rx.Tests/VariableNameParserTests.cs index 577f7d0..4ecf1c5 100644 --- a/Sharp7.Rx.Tests/VariableNameParserTests.cs +++ b/Sharp7.Rx.Tests/VariableNameParserTests.cs @@ -20,7 +20,6 @@ internal class VariableNameParserTests [TestCase("DB506.Bit216.8", TestName = "Bit to high")] [TestCase("DB506.String216", TestName = "String without Length")] [TestCase("DB506.WString216", TestName = "WString without Length")] - [TestCase("DB506.Int216.1", TestName = "Int with Length")] [TestCase("DB506.UInt216.1", TestName = "UInt with Length")] [TestCase("DB506.DInt216.1", TestName = "DInt with Length")] @@ -29,7 +28,6 @@ internal class VariableNameParserTests [TestCase("DB506.ULInt216.1", TestName = "ULInt with Length")] [TestCase("DB506.Real216.1", TestName = "LReal with Length")] [TestCase("DB506.LReal216.1", TestName = "LReal with Length")] - [TestCase("DB506.xx216", TestName = "Invalid type")] [TestCase("DB506.216", TestName = "No type")] [TestCase("DB506.Int216.", TestName = "Trailing dot")] @@ -49,39 +47,39 @@ internal class VariableNameParserTests public static IEnumerable ValidTestCases() { - yield return new TestCase("DB506.Bit216.2", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Bit = 2, Type = DbType.Bit}); + yield return new TestCase("DB506.Bit216.2", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Bit, Start: 216, Length: 1, Bit: 2)); - yield return new TestCase("DB506.String216.10", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 10, Type = DbType.String}); - yield return new TestCase("DB506.WString216.10", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 10, Type = DbType.WString}); + yield return new TestCase("DB506.String216.10", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.String, Start: 216, Length: 10)); + yield return new TestCase("DB506.WString216.10", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.WString, Start: 216, Length: 10)); - yield return new TestCase("DB506.Byte216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Type = DbType.Byte}); - yield return new TestCase("DB506.Byte216.100", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 100, Type = DbType.Byte}); - yield return new TestCase("DB506.Int216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); - yield return new TestCase("DB506.UInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.UInt}); - yield return new TestCase("DB506.DInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.DInt}); - yield return new TestCase("DB506.UDInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.UDInt}); - yield return new TestCase("DB506.LInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.LInt}); - yield return new TestCase("DB506.ULInt216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); + yield return new TestCase("DB506.Byte216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Byte, Start: 216, Length: 1)); + yield return new TestCase("DB506.Byte216.100", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Byte, Start: 216, Length: 100)); + yield return new TestCase("DB506.Int216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Int, Start: 216, Length: 2)); + yield return new TestCase("DB506.UInt216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.UInt, Start: 216, Length: 2)); + yield return new TestCase("DB506.DInt216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.DInt, Start: 216, Length: 4)); + yield return new TestCase("DB506.UDInt216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.UDInt, Start: 216, Length: 4)); + yield return new TestCase("DB506.LInt216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.LInt, Start: 216, Length: 8)); + yield return new TestCase("DB506.ULInt216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.ULInt, Start: 216, Length: 8)); - yield return new TestCase("DB506.Real216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.Single}); - yield return new TestCase("DB506.LReal216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.Double}); + yield return new TestCase("DB506.Real216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Single, Start: 216, Length: 4)); + yield return new TestCase("DB506.LReal216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Double, Start: 216, Length: 8)); // Legacy - yield return new TestCase("DB13.DBX3.1", new VariableAddress {Operand = Operand.Db, DbNr = 13, Start = 3, Length = 1, Bit = 1, Type = DbType.Bit}); - yield return new TestCase("Db403.X5.2", new VariableAddress {Operand = Operand.Db, DbNr = 403, Start = 5, Length = 1, Bit = 2, Type = DbType.Bit}); - yield return new TestCase("DB55DBX23.6", new VariableAddress {Operand = Operand.Db, DbNr = 55, Start = 23, Length = 1, Bit = 6, Type = DbType.Bit}); - yield return new TestCase("DB1.S255.20", new VariableAddress {Operand = Operand.Db, DbNr = 1, Start = 255, Length = 20, Type = DbType.String}); - yield return new TestCase("DB5.String887.20", new VariableAddress {Operand = Operand.Db, DbNr = 5, Start = 887, Length = 20, Type = DbType.String}); - yield return new TestCase("DB506.B216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 1, Type = DbType.Byte}); - yield return new TestCase("DB506.DBB216.5", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 5, Type = DbType.Byte}); - yield return new TestCase("DB506.D216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.Single}); - yield return new TestCase("DB506.DINT216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 4, Type = DbType.DInt}); - yield return new TestCase("DB506.INT216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); - yield return new TestCase("DB506.DBW216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 2, Type = DbType.Int}); - yield return new TestCase("DB506.DUL216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); - yield return new TestCase("DB506.DULINT216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); - yield return new TestCase("DB506.DULONG216", new VariableAddress {Operand = Operand.Db, DbNr = 506, Start = 216, Length = 8, Type = DbType.ULInt}); + yield return new TestCase("DB13.DBX3.1", new VariableAddress(Operand: Operand.Db, DbNo: 13, Type: DbType.Bit, Start: 3, Length: 1, Bit: 1)); + yield return new TestCase("Db403.X5.2", new VariableAddress(Operand: Operand.Db, DbNo: 403, Type: DbType.Bit, Start: 5, Length: 1, Bit: 2)); + yield return new TestCase("DB55DBX23.6", new VariableAddress(Operand: Operand.Db, DbNo: 55, Type: DbType.Bit, Start: 23, Length: 1, Bit: 6)); + yield return new TestCase("DB1.S255.20", new VariableAddress(Operand: Operand.Db, DbNo: 1, Type: DbType.String, Start: 255, Length: 20)); + yield return new TestCase("DB5.String887.20", new VariableAddress(Operand: Operand.Db, DbNo: 5, Type: DbType.String, Start: 887, Length: 20)); + yield return new TestCase("DB506.B216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Byte, Start: 216, Length: 1)); + yield return new TestCase("DB506.DBB216.5", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Byte, Start: 216, Length: 5)); + yield return new TestCase("DB506.D216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Single, Start: 216, Length: 4)); + yield return new TestCase("DB506.DINT216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.DInt, Start: 216, Length: 4)); + yield return new TestCase("DB506.INT216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Int, Start: 216, Length: 2)); + yield return new TestCase("DB506.DBW216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.Int, Start: 216, Length: 2)); + yield return new TestCase("DB506.DUL216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.ULInt, Start: 216, Length: 8)); + yield return new TestCase("DB506.DULINT216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.ULInt, Start: 216, Length: 8)); + yield return new TestCase("DB506.DULONG216", new VariableAddress(Operand: Operand.Db, DbNo: 506, Type: DbType.ULInt, Start: 216, Length: 8)); } public record TestCase(string Input, VariableAddress Expected) diff --git a/Sharp7.Rx/AssemblyInfo.cs b/Sharp7.Rx/AssemblyInfo.cs index e92998f..d9d730c 100644 --- a/Sharp7.Rx/AssemblyInfo.cs +++ b/Sharp7.Rx/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Sharp7.Rx.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Sharp7.Rx.Tests")] diff --git a/Sharp7.Rx/Extensions/OperandExtensions.cs b/Sharp7.Rx/Extensions/OperandExtensions.cs index c78cfa5..b2c6744 100644 --- a/Sharp7.Rx/Extensions/OperandExtensions.cs +++ b/Sharp7.Rx/Extensions/OperandExtensions.cs @@ -13,5 +13,4 @@ internal static class OperandExtensions Operand.Db => S7Area.DB, _ => throw new ArgumentOutOfRangeException(nameof(operand), operand, null) }; - } diff --git a/Sharp7.Rx/Extensions/PlcExtensions.cs b/Sharp7.Rx/Extensions/PlcExtensions.cs index b1f1cd2..af2738d 100644 --- a/Sharp7.Rx/Extensions/PlcExtensions.cs +++ b/Sharp7.Rx/Extensions/PlcExtensions.cs @@ -9,7 +9,8 @@ namespace Sharp7.Rx.Extensions; public static class PlcExtensions { - public static IObservable CreateDatatransferWithHandshake(this IPlc plc, string triggerAddress, string ackTriggerAddress, Func> readData, bool initialTransfer) + public static IObservable CreateDatatransferWithHandshake(this IPlc plc, string triggerAddress, string ackTriggerAddress, Func> readData, + bool initialTransfer) { return Observable.Create(async observer => { diff --git a/Sharp7.Rx/Extensions/S7VariableExtensions.cs b/Sharp7.Rx/Extensions/S7VariableExtensions.cs index ae04de3..fa1c87f 100644 --- a/Sharp7.Rx/Extensions/S7VariableExtensions.cs +++ b/Sharp7.Rx/Extensions/S7VariableExtensions.cs @@ -7,17 +7,17 @@ internal static class VariableAddressExtensions private static readonly Dictionary> supportedTypeMap = new() { {typeof(bool), a => a.Type == DbType.Bit}, - {typeof(string), a => a.Type is DbType.String or DbType.WString or DbType.Byte }, - {typeof(byte), a => a.Type==DbType.Byte && a.Length == 1}, - {typeof(short), a => a.Type==DbType.Int}, - {typeof(ushort), a => a.Type==DbType.UInt}, - {typeof(int), a => a.Type==DbType.DInt}, - {typeof(uint), a => a.Type==DbType.UDInt}, - {typeof(long), a => a.Type==DbType.LInt}, - {typeof(ulong), a => a.Type==DbType.ULInt}, - {typeof(float), a => a.Type==DbType.Single}, - {typeof(double), a => a.Type==DbType.Double}, - {typeof(byte[]), a => a.Type==DbType.Byte}, + {typeof(string), a => a.Type is DbType.String or DbType.WString or DbType.Byte}, + {typeof(byte), a => a.Type == DbType.Byte && a.Length == 1}, + {typeof(short), a => a.Type == DbType.Int}, + {typeof(ushort), a => a.Type == DbType.UInt}, + {typeof(int), a => a.Type == DbType.DInt}, + {typeof(uint), a => a.Type == DbType.UDInt}, + {typeof(long), a => a.Type == DbType.LInt}, + {typeof(ulong), a => a.Type == DbType.ULInt}, + {typeof(float), a => a.Type == DbType.Single}, + {typeof(double), a => a.Type == DbType.Double}, + {typeof(byte[]), a => a.Type == DbType.Byte}, }; public static bool MatchesType(this VariableAddress address, Type type) => diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index 9e4ceca..f33c55b 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -98,7 +98,7 @@ internal class Sharp7Connector : IS7Connector .Select(x => { var buffer = new byte[x.Address.Length]; - s7MultiVar.Add(S7Consts.S7AreaDB, S7Consts.S7WLByte, x.Address.DbNr, x.Address.Start, x.Address.Length, ref buffer); + s7MultiVar.Add(S7Consts.S7AreaDB, S7Consts.S7WLByte, x.Address.DbNo, x.Address.Start, x.Address.Length, ref buffer); return new {x.VariableName, Buffer = buffer}; }) .ToArray(); diff --git a/Sharp7.Rx/Sharp7Plc.cs b/Sharp7.Rx/Sharp7Plc.cs index 8eff549..a50d24d 100644 --- a/Sharp7.Rx/Sharp7Plc.cs +++ b/Sharp7.Rx/Sharp7Plc.cs @@ -118,7 +118,7 @@ public class Sharp7Plc : IPlc { var address = ParseAndVerify(variableName, typeof(TValue)); - var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.BufferLength, address.DbNr, token); + var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.BufferLength, address.DbNo, token); return ValueConverter.ReadFromBuffer(data, address); } @@ -155,7 +155,7 @@ public class Sharp7Plc : IPlc // Special handling for bools, which are written on a by-bit basis. Writing a complete byte would // overwrite other bits within this byte. - await s7Connector.WriteBit(address.Operand, address.Start, address.Bit!.Value, (bool) (object) value, address.DbNr, token); + await s7Connector.WriteBit(address.Operand, address.Start, address.Bit!.Value, (bool) (object) value, address.DbNo, token); } else { @@ -163,7 +163,7 @@ public class Sharp7Plc : IPlc var bytes = new byte[address.BufferLength]; ValueConverter.WriteToBuffer(bytes, value, address); - await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); + await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNo, token); } } diff --git a/Sharp7.Rx/VariableAddress.cs b/Sharp7.Rx/VariableAddress.cs index 7e116e7..8405916 100644 --- a/Sharp7.Rx/VariableAddress.cs +++ b/Sharp7.Rx/VariableAddress.cs @@ -4,14 +4,14 @@ using Sharp7.Rx.Enums; namespace Sharp7.Rx; [NoReorder] -internal class VariableAddress +internal record VariableAddress(Operand Operand, ushort DbNo, DbType Type, ushort Start, ushort Length, byte? Bit = null) { - public Operand Operand { get; set; } - public ushort DbNr { get; set; } - public ushort Start { get; set; } - public ushort Length { get; set; } - public byte? Bit { get; set; } - public DbType Type { get; set; } + public Operand Operand { get; } = Operand; + public ushort DbNo { get; } = DbNo; + public ushort Start { get; } = Start; + public ushort Length { get; } = Length; + public byte? Bit { get; } = Bit; + public DbType Type { get; } = Type; public ushort BufferLength => Type switch { @@ -23,10 +23,10 @@ internal class VariableAddress public override string ToString() => Type switch { - DbType.Bit => $"{Operand}{DbNr}.{Type}{Start}.{Bit}", - DbType.String => $"{Operand}{DbNr}.{Type}{Start}.{Length}", - DbType.WString => $"{Operand}{DbNr}.{Type}{Start}.{Length}", - DbType.Byte => Length == 1 ? $"{Operand}{DbNr}.{Type}{Start}" : $"{Operand}{DbNr}.{Type}{Start}.{Length}", - _ => $"{Operand}{DbNr}.{Type}{Start}", + DbType.Bit => $"{Operand}{DbNo}.{Type}{Start}.{Bit}", + DbType.String => $"{Operand}{DbNo}.{Type}{Start}.{Length}", + DbType.WString => $"{Operand}{DbNo}.{Type}{Start}.{Length}", + DbType.Byte => Length == 1 ? $"{Operand}{DbNo}.{Type}{Start}" : $"{Operand}{DbNo}.{Type}{Start}.{Length}", + _ => $"{Operand}{DbNo}.{Type}{Start}", }; } diff --git a/Sharp7.Rx/VariableNameParser.cs b/Sharp7.Rx/VariableNameParser.cs index 8c2bef7..734c33e 100644 --- a/Sharp7.Rx/VariableNameParser.cs +++ b/Sharp7.Rx/VariableNameParser.cs @@ -111,15 +111,7 @@ internal class VariableNameParser : IVariableNameParser byte? bit = type == DbType.Bit ? GetBit() : null; - var s7VariableAddress = new VariableAddress - { - Operand = operand, - DbNr = dbNr, - Start = start, - Type = type, - Length = length, - Bit = bit - }; + var s7VariableAddress = new VariableAddress(Operand: operand, DbNo: dbNr, Type: type, Start: start, Length: length, Bit: bit); return s7VariableAddress; From 9a1d0f70f02328c7228e01b32d713d09072dbbac Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Fri, 9 Feb 2024 13:17:10 +0100 Subject: [PATCH 9/9] Adapt readme --- README.md | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a5f27b9..a80d530 100644 --- a/README.md +++ b/README.md @@ -50,21 +50,37 @@ using (var disposables = new CompositeDisposable()) the best way to test your PLC application is running your [SoftPLC](https://github.com/fbarresi/softplc) locally. -## S7 Addressing rules +## Addressing rules Sharp7Reactive uses a syntax for identifying addresses similar to official siemens syntax. -Every address has the form (case unsensible) `DB..`. -
i.e.: `DB42.DBX0.7` => (means) Datablock 42, bit (DBX), Start: 0, Position: 7 -
or
-`DB42.DBB4.25` => (means) Datablock 42, bytes (DBB), Start: 4, Length: 25. +Every address has the form (case unsensitive) `DB..`. -Following types are supported: -- `DBX` => Bit (bool) -- `DBB` => byte or byte[] -- `INT` -- `DINT` -- `DUL` => LINT -- `D` => REAL +| Example | Explaination | +| ------------------------------------ | ----------------------------------------------------------------- | +| `DB42.Int4` or
`DB42.DBD4` | Datablock 42, 16 bit integer from bytes 4 to 5 (zero based index) | +| `DB42.Bit0.7` or
`DB42.DBX0.7` | Datablock 42, bit from byte 0, position 7 | +| `DB42.Byte4.25` or
`DB42.DBB4.25` | Datablock 42, 25 bytes from byte 4 to 29 (zero based index) | + +Here is a table of supported data types: + +|.Net Type|Identifier |Description |Length or bit |Example |Example remark | +|---------|-----------------------------|----------------------------------------------|----------------------------------------|-------------------|--------------------------| +|bool |bit, dbx |Bit as boolean value |Bit index (0 .. 7) |`Db200.Bit2.2` |Reads bit 3 | +|byte |byte, dbb, b* |8 bit unsigned integer | |`Db200.Byte4` | | +|byte[] |byte, dbb, b* |Array of bytes |Array length in bytes |`Db200.Byte4.16` | | +|short |int, dbw, w* |16 bit signed integer | |`Db200.Int4` | | +|ushort |uint |16 bit unsigned integer | |`Db200.UInt4` | | +|int |dint, dbd |32 bit signed integer | |`Db200.DInt4` | | +|uint |udint |32 bit unsigned integer | |`Db200.UDInt4` | | +|long |lint |64 bit signed integer | |`Db200.LInt4` | | +|ulong |ulint, dul*, dulint*, dulong*|64 bit unsigned integer | |`Db200.ULInt4` | | +|float |real, d* |32 bit float | |`Db200.Real4` | | +|double |lreal |64 bit float | |`Db200.LReal4` | | +|string |string, s* |ASCII text string with string size |String length in bytes (1 .. 254) |`Db200.String4.16` |Uses 18 bytes = 16 + 2 | +|string |wstring |UTF-16 Big Endian text string with string size|String length in characters (1 .. 16382)|`Db200.WString4.16`|Uses 36 bytes = 16 * 2 + 4| +|string |byte[] |ASCII string as byte array |String length in bytes |`Db200.Byte4.16` |Uses 16 bytes | + +> Identifiers marked with * are kept for compatability reasons and might be removed in the future. ## Would you like to contribute?