From babbb1a6bcee963d27ddfcd4d4f56d1b75960d23 Mon Sep 17 00:00:00 2001 From: Peter Butzhammer Date: Wed, 7 Feb 2024 09:35:21 +0100 Subject: [PATCH] Cleanup --- Sharp7.Rx/AssemblyInfo.cs | 3 +- .../Basics/ConcurrentSubjectDictionary.cs | 3 +- Sharp7.Rx/Basics/DisposableItem.cs | 1 - Sharp7.Rx/Enums/ConnectionState.cs | 2 +- Sharp7.Rx/Enums/CpuType.cs | 2 +- Sharp7.Rx/Enums/DbType.cs | 2 +- Sharp7.Rx/Enums/Operand.cs | 2 +- Sharp7.Rx/Enums/TransmissionMode.cs | 2 +- Sharp7.Rx/Extensions/DisposableExtensions.cs | 2 +- Sharp7.Rx/Extensions/ObservableExtensions.cs | 142 +++++---- Sharp7.Rx/Extensions/PlcExtensions.cs | 10 +- Sharp7.Rx/Interfaces/IPlc.cs | 2 + Sharp7.Rx/Interfaces/IS7Connector.cs | 2 + Sharp7.Rx/S7ValueConverter.cs | 4 +- Sharp7.Rx/S7VariableAddress.cs | 4 +- Sharp7.Rx/S7VariableNameParser.cs | 13 +- Sharp7.Rx/Settings/PlcConnectionSettings.cs | 4 +- Sharp7.Rx/Sharp7.Rx.csproj | 1 + Sharp7.Rx/Sharp7Connector.cs | 263 ++++++++-------- Sharp7.Rx/Sharp7Plc.cs | 282 +++++++++--------- 20 files changed, 372 insertions(+), 374 deletions(-) diff --git a/Sharp7.Rx/AssemblyInfo.cs b/Sharp7.Rx/AssemblyInfo.cs index 9d8031c..e92998f 100644 --- a/Sharp7.Rx/AssemblyInfo.cs +++ b/Sharp7.Rx/AssemblyInfo.cs @@ -1,4 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Sharp7.Rx.Tests")] - +[assembly: InternalsVisibleTo("Sharp7.Rx.Tests")] \ No newline at end of file diff --git a/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs b/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs index 041991c..d831707 100644 --- a/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs +++ b/Sharp7.Rx/Basics/ConcurrentSubjectDictionary.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; -using Sharp7.Rx.Extensions; namespace Sharp7.Rx.Basics { @@ -85,7 +84,7 @@ namespace Sharp7.Rx.Basics return; if (disposing && dictionary != null) { - foreach (var subjectWithRefCounter in dictionary) + foreach (var subjectWithRefCounter in dictionary) subjectWithRefCounter.Value.Subject.OnCompleted(); dictionary.Clear(); dictionary = null; diff --git a/Sharp7.Rx/Basics/DisposableItem.cs b/Sharp7.Rx/Basics/DisposableItem.cs index f6ab006..29108a4 100644 --- a/Sharp7.Rx/Basics/DisposableItem.cs +++ b/Sharp7.Rx/Basics/DisposableItem.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Sharp7.Rx.Basics { diff --git a/Sharp7.Rx/Enums/ConnectionState.cs b/Sharp7.Rx/Enums/ConnectionState.cs index acf93b6..31e54ac 100644 --- a/Sharp7.Rx/Enums/ConnectionState.cs +++ b/Sharp7.Rx/Enums/ConnectionState.cs @@ -7,4 +7,4 @@ DisconnectedByUser, ConnectionLost } -} +} \ No newline at end of file diff --git a/Sharp7.Rx/Enums/CpuType.cs b/Sharp7.Rx/Enums/CpuType.cs index 28cd172..ad0a679 100644 --- a/Sharp7.Rx/Enums/CpuType.cs +++ b/Sharp7.Rx/Enums/CpuType.cs @@ -7,4 +7,4 @@ S7_1200, S7_1500 } -} +} \ No newline at end of file diff --git a/Sharp7.Rx/Enums/DbType.cs b/Sharp7.Rx/Enums/DbType.cs index 66116c8..8a134ec 100644 --- a/Sharp7.Rx/Enums/DbType.cs +++ b/Sharp7.Rx/Enums/DbType.cs @@ -10,4 +10,4 @@ DInteger, ULong } -} +} \ No newline at end of file diff --git a/Sharp7.Rx/Enums/Operand.cs b/Sharp7.Rx/Enums/Operand.cs index 79ed488..4f6a687 100644 --- a/Sharp7.Rx/Enums/Operand.cs +++ b/Sharp7.Rx/Enums/Operand.cs @@ -7,4 +7,4 @@ Marker = 77, Db = 68, } -} +} \ No newline at end of file diff --git a/Sharp7.Rx/Enums/TransmissionMode.cs b/Sharp7.Rx/Enums/TransmissionMode.cs index 2e9304a..c33ef62 100644 --- a/Sharp7.Rx/Enums/TransmissionMode.cs +++ b/Sharp7.Rx/Enums/TransmissionMode.cs @@ -5,4 +5,4 @@ Cyclic = 3, OnChange = 4, } -} +} \ No newline at end of file diff --git a/Sharp7.Rx/Extensions/DisposableExtensions.cs b/Sharp7.Rx/Extensions/DisposableExtensions.cs index 89f3c75..6f68c0f 100644 --- a/Sharp7.Rx/Extensions/DisposableExtensions.cs +++ b/Sharp7.Rx/Extensions/DisposableExtensions.cs @@ -10,4 +10,4 @@ namespace Sharp7.Rx.Extensions compositeDisposable.Add(disposable); } } -} +} \ No newline at end of file diff --git a/Sharp7.Rx/Extensions/ObservableExtensions.cs b/Sharp7.Rx/Extensions/ObservableExtensions.cs index 052b6ee..8cdc26e 100644 --- a/Sharp7.Rx/Extensions/ObservableExtensions.cs +++ b/Sharp7.Rx/Extensions/ObservableExtensions.cs @@ -1,91 +1,83 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Linq.Expressions; -using System.Reactive; using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Sharp7.Rx.Resources; namespace Sharp7.Rx.Extensions { - internal static class ObservableExtensions - { - public static IObservable LogAndRetry(this IObservable source, ILogger logger, string message) - { - return source - .Do( - _ => { }, - ex => logger?.LogError(ex, message)) - .Retry(); - } + internal static class ObservableExtensions + { + public static IObservable DisposeMany(this IObservable source) + { + return Observable.Create(obs => + { + var serialDisposable = new SerialDisposable(); + var subscription = + source.Subscribe( + item => + { + serialDisposable.Disposable = item as IDisposable; + obs.OnNext(item); + }, + obs.OnError, + obs.OnCompleted); + return new CompositeDisposable(serialDisposable, subscription); + }); + } - public static IObservable RetryAfterDelay( - this IObservable source, - TimeSpan retryDelay, - int retryCount = -1, - IScheduler scheduler = null) - { - return RedoAfterDelay(source, retryDelay, retryCount, scheduler, Observable.Retry, Observable.Retry); - } + public static IObservable LogAndRetry(this IObservable source, ILogger logger, string message) + { + return source + .Do( + _ => { }, + ex => logger?.LogError(ex, message)) + .Retry(); + } - public static IObservable RepeatAfterDelay( - this IObservable source, - TimeSpan retryDelay, - int repeatCount = -1, - IScheduler scheduler = null) - { - return RedoAfterDelay(source, retryDelay, repeatCount, scheduler, Observable.Repeat, Observable.Repeat); - } + public static IObservable LogAndRetryAfterDelay( + this IObservable source, + ILogger logger, + TimeSpan retryDelay, + string message, + int retryCount = -1, + IScheduler scheduler = null) + { + var sourceLogged = + source + .Do( + _ => { }, + ex => logger?.LogError(ex, message)); - public static IObservable LogAndRetryAfterDelay( - this IObservable source, - ILogger logger, - TimeSpan retryDelay, - string message, - int retryCount = -1, - IScheduler scheduler = null) - { - var sourceLogged = - source - .Do( - _ => { }, - ex => logger?.LogError(ex, message)); + return RetryAfterDelay(sourceLogged, retryDelay, retryCount, scheduler); + } - return RetryAfterDelay(sourceLogged, retryDelay, retryCount, scheduler); - } + public static IObservable RepeatAfterDelay( + this IObservable source, + TimeSpan retryDelay, + int repeatCount = -1, + IScheduler scheduler = null) + { + return RedoAfterDelay(source, retryDelay, repeatCount, scheduler, Observable.Repeat, Observable.Repeat); + } - private static IObservable RedoAfterDelay(IObservable source, TimeSpan retryDelay, int retryCount, IScheduler scheduler, Func, IObservable> reDo, - Func, int, IObservable> reDoCount) - { - scheduler = scheduler ?? TaskPoolScheduler.Default; - var attempt = 0; + public static IObservable RetryAfterDelay( + this IObservable source, + TimeSpan retryDelay, + int retryCount = -1, + IScheduler scheduler = null) + { + return RedoAfterDelay(source, retryDelay, retryCount, scheduler, Observable.Retry, Observable.Retry); + } - var deferedObs = Observable.Defer(() => ((++attempt == 1) ? source : source.DelaySubscription(retryDelay, scheduler))); - return retryCount > 0 ? reDoCount(deferedObs, retryCount) : reDo(deferedObs); - } + private static IObservable RedoAfterDelay(IObservable source, TimeSpan retryDelay, int retryCount, IScheduler scheduler, Func, IObservable> reDo, + Func, int, IObservable> reDoCount) + { + scheduler = scheduler ?? TaskPoolScheduler.Default; + var attempt = 0; - public static IObservable DisposeMany(this IObservable source) - { - return Observable.Create(obs => - { - var serialDisposable = new SerialDisposable(); - var subscription = - source.Subscribe( - item => - { - serialDisposable.Disposable = item as IDisposable; - obs.OnNext(item); - }, - obs.OnError, - obs.OnCompleted); - return new CompositeDisposable(serialDisposable, subscription); - }); - } - } + var deferedObs = Observable.Defer(() => ((++attempt == 1) ? source : source.DelaySubscription(retryDelay, scheduler))); + return retryCount > 0 ? reDoCount(deferedObs, retryCount) : reDo(deferedObs); + } + } } \ No newline at end of file diff --git a/Sharp7.Rx/Extensions/PlcExtensions.cs b/Sharp7.Rx/Extensions/PlcExtensions.cs index 14e3e58..2cfbc85 100644 --- a/Sharp7.Rx/Extensions/PlcExtensions.cs +++ b/Sharp7.Rx/Extensions/PlcExtensions.cs @@ -38,10 +38,10 @@ namespace Sharp7.Rx.Extensions notification .Where(trigger => !trigger) .SelectMany(async _ => - { - await plc.SetValue(ackTriggerAddress, false); - return Unit.Default; - }) + { + await plc.SetValue(ackTriggerAddress, false); + return Unit.Default; + }) .Subscribe() .AddDisposableTo(subscriptions); @@ -71,4 +71,4 @@ namespace Sharp7.Rx.Extensions } } } -} +} \ No newline at end of file diff --git a/Sharp7.Rx/Interfaces/IPlc.cs b/Sharp7.Rx/Interfaces/IPlc.cs index a438957..683aed9 100644 --- a/Sharp7.Rx/Interfaces/IPlc.cs +++ b/Sharp7.Rx/Interfaces/IPlc.cs @@ -1,10 +1,12 @@ using System; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Sharp7.Rx.Enums; namespace Sharp7.Rx.Interfaces { + [NoReorder] public interface IPlc : IDisposable { IObservable CreateNotification(string variableName, TransmissionMode transmissionMode); diff --git a/Sharp7.Rx/Interfaces/IS7Connector.cs b/Sharp7.Rx/Interfaces/IS7Connector.cs index 309f750..10aa850 100644 --- a/Sharp7.Rx/Interfaces/IS7Connector.cs +++ b/Sharp7.Rx/Interfaces/IS7Connector.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Sharp7.Rx.Enums; namespace Sharp7.Rx.Interfaces { + [NoReorder] internal interface IS7Connector : IDisposable { IObservable ConnectionState { get; } diff --git a/Sharp7.Rx/S7ValueConverter.cs b/Sharp7.Rx/S7ValueConverter.cs index d97fa31..fb6f985 100644 --- a/Sharp7.Rx/S7ValueConverter.cs +++ b/Sharp7.Rx/S7ValueConverter.cs @@ -35,7 +35,7 @@ namespace Sharp7.Rx if (typeof(TValue) == typeof(byte)) return (TValue) (object) buffer[0]; if (typeof(TValue) == typeof(char)) - return (TValue) (object) (char)buffer[0]; + return (TValue) (object) (char) buffer[0]; if (typeof(TValue) == typeof(byte[])) return (TValue) (object) buffer; @@ -46,7 +46,7 @@ namespace Sharp7.Rx { UInt32 = BinaryPrimitives.ReadUInt32BigEndian(buffer) }; - return (TValue) (object) (double)d.Single; + return (TValue) (object) (double) d.Single; } if (typeof(TValue) == typeof(float)) diff --git a/Sharp7.Rx/S7VariableAddress.cs b/Sharp7.Rx/S7VariableAddress.cs index ef2fb5a..7c751f7 100644 --- a/Sharp7.Rx/S7VariableAddress.cs +++ b/Sharp7.Rx/S7VariableAddress.cs @@ -1,7 +1,9 @@ -using Sharp7.Rx.Enums; +using JetBrains.Annotations; +using Sharp7.Rx.Enums; namespace Sharp7.Rx { + [NoReorder] internal class S7VariableAddress { public Operand Operand { get; set; } diff --git a/Sharp7.Rx/S7VariableNameParser.cs b/Sharp7.Rx/S7VariableNameParser.cs index c898537..df34b3a 100644 --- a/Sharp7.Rx/S7VariableNameParser.cs +++ b/Sharp7.Rx/S7VariableNameParser.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text.RegularExpressions; using Sharp7.Rx.Enums; using Sharp7.Rx.Interfaces; @@ -25,9 +24,9 @@ namespace Sharp7.Rx {"dint", DbType.DInteger}, {"w", DbType.Integer}, {"dbw", DbType.Integer}, - {"dul", DbType.ULong }, - {"dulint", DbType.ULong }, - {"dulong", DbType.ULong } + {"dul", DbType.ULong}, + {"dulint", DbType.ULong}, + {"dulong", DbType.ULong} }; public S7VariableAddress Parse(string input) @@ -35,7 +34,7 @@ namespace Sharp7.Rx var match = regex.Match(input); if (match.Success) { - var operand = (Operand)Enum.Parse(typeof(Operand), match.Groups["operand"].Value, true); + var operand = (Operand) Enum.Parse(typeof(Operand), match.Groups["operand"].Value, true); var dbNr = ushort.Parse(match.Groups["dbNr"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture); var start = ushort.Parse(match.Groups["start"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture); if (!types.TryGetValue(match.Groups["type"].Value, out var type)) @@ -57,10 +56,10 @@ namespace Sharp7.Rx s7VariableAddress.Bit = byte.Parse(match.Groups["bitOrLength"].Value); break; case DbType.Byte: - s7VariableAddress.Length = match.Groups["bitOrLength"].Success ? ushort.Parse(match.Groups["bitOrLength"].Value) : (ushort)1; + s7VariableAddress.Length = match.Groups["bitOrLength"].Success ? ushort.Parse(match.Groups["bitOrLength"].Value) : (ushort) 1; break; case DbType.String: - s7VariableAddress.Length = match.Groups["bitOrLength"].Success ? ushort.Parse(match.Groups["bitOrLength"].Value) : (ushort)0; + s7VariableAddress.Length = match.Groups["bitOrLength"].Success ? ushort.Parse(match.Groups["bitOrLength"].Value) : (ushort) 0; break; case DbType.Integer: s7VariableAddress.Length = 2; diff --git a/Sharp7.Rx/Settings/PlcConnectionSettings.cs b/Sharp7.Rx/Settings/PlcConnectionSettings.cs index b4b5f85..8adffbf 100644 --- a/Sharp7.Rx/Settings/PlcConnectionSettings.cs +++ b/Sharp7.Rx/Settings/PlcConnectionSettings.cs @@ -2,9 +2,9 @@ { internal class PlcConnectionSettings { - public string IpAddress { get; set; } - public int RackNumber { get; set; } public int CpuMpiAddress { get; set; } + public string IpAddress { get; set; } public int Port { get; set; } + public int RackNumber { get; set; } } } \ No newline at end of file diff --git a/Sharp7.Rx/Sharp7.Rx.csproj b/Sharp7.Rx/Sharp7.Rx.csproj index 329b3d9..4e36f88 100644 --- a/Sharp7.Rx/Sharp7.Rx.csproj +++ b/Sharp7.Rx/Sharp7.Rx.csproj @@ -14,6 +14,7 @@ + diff --git a/Sharp7.Rx/Sharp7Connector.cs b/Sharp7.Rx/Sharp7Connector.cs index 9b89375..b983c46 100644 --- a/Sharp7.Rx/Sharp7Connector.cs +++ b/Sharp7.Rx/Sharp7Connector.cs @@ -18,62 +18,39 @@ namespace Sharp7.Rx { internal class Sharp7Connector : IS7Connector { - private readonly IS7VariableNameParser variableNameParser; private readonly BehaviorSubject connectionStateSubject = new BehaviorSubject(Enums.ConnectionState.Initial); + private readonly int cpuSlotNr; private readonly CompositeDisposable disposables = new CompositeDisposable(); - private readonly LimitedConcurrencyLevelTaskScheduler scheduler = new LimitedConcurrencyLevelTaskScheduler(maxDegreeOfParallelism:1); private readonly string ipAddress; + private readonly int port; private readonly int rackNr; - private readonly int cpuSlotNr; - private readonly int port; - - private S7Client sharp7; + private readonly LimitedConcurrencyLevelTaskScheduler scheduler = new LimitedConcurrencyLevelTaskScheduler(maxDegreeOfParallelism: 1); + private readonly IS7VariableNameParser variableNameParser; private bool disposed; - public ILogger Logger { get; set; } - public async Task> ExecuteMultiVarRequest(IReadOnlyList variableNames) - { - if (variableNames.IsEmpty()) - return new Dictionary(); + private S7Client sharp7; - var s7MultiVar = new S7MultiVar(sharp7); - var buffers = variableNames - .Select(key => new {VariableName = key, Address = variableNameParser.Parse(key)}) - .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); - return new { x.VariableName, Buffer = buffer}; - }) - .ToArray(); - - var result = await Task.Factory.StartNew(() => s7MultiVar.Read(), CancellationToken.None, TaskCreationOptions.None, scheduler); - if (result != 0) - { - EvaluateErrorCode(result); - throw new InvalidOperationException($"Error in MultiVar request for variables: {string.Join(",", variableNames)}"); - } - - return buffers.ToDictionary(arg => arg.VariableName, arg => arg.Buffer); - } - - - public Sharp7Connector(PlcConnectionSettings settings, IS7VariableNameParser variableNameParser) - { + { this.variableNameParser = variableNameParser; ipAddress = settings.IpAddress; cpuSlotNr = settings.CpuMpiAddress; - port = settings.Port; - rackNr = settings.RackNumber; + port = settings.Port; + rackNr = settings.RackNumber; - ReconnectDelay = TimeSpan.FromSeconds(5); + ReconnectDelay = TimeSpan.FromSeconds(5); } + public IObservable ConnectionState => connectionStateSubject.DistinctUntilChanged().AsObservable(); + + public ILogger Logger { get; set; } + public TimeSpan ReconnectDelay { get; set; } + private bool IsConnected => connectionStateSubject.Value == Enums.ConnectionState.Connected; + public void Dispose() { Dispose(true); @@ -103,8 +80,6 @@ namespace Sharp7.Rx return false; } - public IObservable ConnectionState => connectionStateSubject.DistinctUntilChanged().AsObservable(); - public async Task Disconnect() { @@ -112,14 +87,41 @@ namespace Sharp7.Rx await CloseConnection(); } + public async Task> ExecuteMultiVarRequest(IReadOnlyList variableNames) + { + if (variableNames.IsEmpty()) + return new Dictionary(); + + var s7MultiVar = new S7MultiVar(sharp7); + + var buffers = variableNames + .Select(key => new {VariableName = key, Address = variableNameParser.Parse(key)}) + .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); + return new {x.VariableName, Buffer = buffer}; + }) + .ToArray(); + + var result = await Task.Factory.StartNew(() => s7MultiVar.Read(), CancellationToken.None, TaskCreationOptions.None, scheduler); + if (result != 0) + { + EvaluateErrorCode(result); + throw new InvalidOperationException($"Error in MultiVar request for variables: {string.Join(",", variableNames)}"); + } + + return buffers.ToDictionary(arg => arg.VariableName, arg => arg.Buffer); + } + public Task InitializeAsync() { try { sharp7 = new S7Client(); - sharp7.PLCPort = port; + sharp7.PLCPort = port; - var subscription = + var subscription = ConnectionState .Where(state => state == Enums.ConnectionState.ConnectionLost) .Take(1) @@ -132,12 +134,70 @@ namespace Sharp7.Rx } catch (Exception ex) { - Logger?.LogError(ex, StringResources.StrErrorS7DriverCouldNotBeInitialized); - } + Logger?.LogError(ex, StringResources.StrErrorS7DriverCouldNotBeInitialized); + } return Task.FromResult(true); } + public async Task ReadBytes(Operand operand, ushort startByteAddress, ushort bytesToRead, ushort dBNr, CancellationToken token) + { + EnsureConnectionValid(); + + var buffer = new byte[bytesToRead]; + + var area = FromOperand(operand); + + var result = + await Task.Factory.StartNew(() => sharp7.ReadArea(area, dBNr, startByteAddress, bytesToRead, S7Consts.S7WLByte, buffer), token, TaskCreationOptions.None, scheduler); + token.ThrowIfCancellationRequested(); + + if (result != 0) + { + EvaluateErrorCode(result); + var errorText = sharp7.ErrorText(result); + throw new InvalidOperationException($"Error reading {operand}{dBNr}:{startByteAddress}->{bytesToRead} ({errorText})"); + } + + return buffer; + } + + public async Task WriteBit(Operand operand, ushort startByteAddress, byte bitAdress, bool value, ushort dbNr, CancellationToken token) + { + EnsureConnectionValid(); + + var buffer = new[] {value ? (byte) 0xff : (byte) 0}; + + var offsetStart = (startByteAddress * 8) + bitAdress; + + var result = await Task.Factory.StartNew(() => sharp7.WriteArea(FromOperand(operand), dbNr, offsetStart, 1, S7Consts.S7WLBit, buffer), token, TaskCreationOptions.None, scheduler); + token.ThrowIfCancellationRequested(); + + if (result != 0) + { + EvaluateErrorCode(result); + return (false); + } + + return (true); + } + + public async Task WriteBytes(Operand operand, ushort startByteAdress, byte[] data, ushort dBNr, CancellationToken token) + { + EnsureConnectionValid(); + + var result = await Task.Factory.StartNew(() => sharp7.WriteArea(FromOperand(operand), dBNr, startByteAdress, data.Length, S7Consts.S7WLByte, data), token, TaskCreationOptions.None, scheduler); + token.ThrowIfCancellationRequested(); + + if (result != 0) + { + EvaluateErrorCode(result); + return 0; + } + + return (ushort) (data.Length); + } + protected virtual void Dispose(bool disposing) { @@ -169,6 +229,18 @@ namespace Sharp7.Rx await Task.Factory.StartNew(() => sharp7.Disconnect(), CancellationToken.None, TaskCreationOptions.None, scheduler); } + private void EnsureConnectionValid() + { + if (disposed) + throw new ObjectDisposedException("S7Connector"); + + if (sharp7 == null) + throw new InvalidOperationException(StringResources.StrErrorS7DriverNotInitialized); + + if (!IsConnected) + throw new InvalidOperationException("Plc is not connected"); + } + private bool EvaluateErrorCode(int errorCode) { if (errorCode == 0) @@ -186,6 +258,23 @@ namespace Sharp7.Rx return false; } + private int FromOperand(Operand operand) + { + switch (operand) + { + case Operand.Input: + return S7Consts.S7AreaPE; + case Operand.Output: + return S7Consts.S7AreaPA; + case Operand.Marker: + return S7Consts.S7AreaMK; + case Operand.Db: + return S7Consts.S7AreaDB; + default: + throw new ArgumentOutOfRangeException(nameof(operand), operand, null); + } + } + private async Task Reconnect() { await CloseConnection(); @@ -204,91 +293,5 @@ namespace Sharp7.Rx { Dispose(false); } - - private bool IsConnected => connectionStateSubject.Value == Enums.ConnectionState.Connected; - - public async Task ReadBytes(Operand operand, ushort startByteAddress, ushort bytesToRead, ushort dBNr, CancellationToken token) - { - EnsureConnectionValid(); - - var buffer = new byte[bytesToRead]; - - var area = FromOperand(operand); - - var result = - await Task.Factory.StartNew(() => sharp7.ReadArea(area, dBNr, startByteAddress, bytesToRead, S7Consts.S7WLByte, buffer), token, TaskCreationOptions.None, scheduler); - token.ThrowIfCancellationRequested(); - - if (result != 0) - { - EvaluateErrorCode(result); - var errorText = sharp7.ErrorText(result); - throw new InvalidOperationException($"Error reading {operand}{dBNr}:{startByteAddress}->{bytesToRead} ({errorText})"); - } - - return buffer; - } - - private int FromOperand(Operand operand) - { - switch (operand) - { - case Operand.Input: - return S7Consts.S7AreaPE; - case Operand.Output: - return S7Consts.S7AreaPA; - case Operand.Marker: - return S7Consts.S7AreaMK; - case Operand.Db: - return S7Consts.S7AreaDB; - default: - throw new ArgumentOutOfRangeException(nameof(operand), operand, null); - } - } - - private void EnsureConnectionValid() - { - if (disposed) - throw new ObjectDisposedException("S7Connector"); - - if (sharp7 == null) - throw new InvalidOperationException(StringResources.StrErrorS7DriverNotInitialized); - - if (!IsConnected) - throw new InvalidOperationException("Plc is not connected"); - } - - public async Task WriteBytes(Operand operand, ushort startByteAdress, byte[] data, ushort dBNr, CancellationToken token) - { - EnsureConnectionValid(); - - var result = await Task.Factory.StartNew(() => sharp7.WriteArea(FromOperand(operand), dBNr, startByteAdress, data.Length, S7Consts.S7WLByte, data), token, TaskCreationOptions.None, scheduler); - token.ThrowIfCancellationRequested(); - - if (result != 0) - { - EvaluateErrorCode(result); - return 0; - } - return (ushort)(data.Length); - } - public async Task WriteBit(Operand operand, ushort startByteAddress, byte bitAdress, bool value, ushort dbNr, CancellationToken token) - { - EnsureConnectionValid(); - - var buffer = new byte[] { value ? (byte)0xff : (byte)0 }; - - var offsetStart = (startByteAddress * 8) + bitAdress; - - var result = await Task.Factory.StartNew(() => sharp7.WriteArea(FromOperand(operand), dbNr, offsetStart, 1, S7Consts.S7WLBit, buffer), token, TaskCreationOptions.None, scheduler); - token.ThrowIfCancellationRequested(); - - if (result != 0) - { - EvaluateErrorCode(result); - return (false); - } - return (true); - } } } \ No newline at end of file diff --git a/Sharp7.Rx/Sharp7Plc.cs b/Sharp7.Rx/Sharp7Plc.cs index 887bb6f..9902224 100644 --- a/Sharp7.Rx/Sharp7Plc.cs +++ b/Sharp7.Rx/Sharp7Plc.cs @@ -19,13 +19,13 @@ namespace Sharp7.Rx { public class Sharp7Plc : IPlc { + protected readonly CompositeDisposable Disposables = new CompositeDisposable(); + private readonly ConcurrentSubjectDictionary multiVariableSubscriptions = new ConcurrentSubjectDictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly List performanceCoutner = new List(1000); + private readonly PlcConnectionSettings plcConnectionSettings; private readonly IS7VariableNameParser varaibleNameParser = new CacheVariableNameParser(new S7VariableNameParser()); private bool disposed; private IS7Connector s7Connector; - private readonly PlcConnectionSettings plcConnectionSettings; - private readonly ConcurrentSubjectDictionary multiVariableSubscriptions = new ConcurrentSubjectDictionary(StringComparer.InvariantCultureIgnoreCase); - protected readonly CompositeDisposable Disposables = new CompositeDisposable(); - private readonly List performanceCoutner = new List(1000); /// @@ -48,7 +48,7 @@ namespace Sharp7.Rx /// public Sharp7Plc(string ipAddress, int rackNumber, int cpuMpiAddress, int port = 102, TimeSpan? multiVarRequestCycleTime = null) { - plcConnectionSettings = new PlcConnectionSettings() { IpAddress = ipAddress, RackNumber = rackNumber, CpuMpiAddress = cpuMpiAddress, Port = port }; + plcConnectionSettings = new PlcConnectionSettings {IpAddress = ipAddress, RackNumber = rackNumber, CpuMpiAddress = cpuMpiAddress, Port = port}; if (multiVarRequestCycleTime != null && multiVarRequestCycleTime > TimeSpan.FromMilliseconds(5)) MultiVarRequestCycleTime = multiVarRequestCycleTime.Value; @@ -56,121 +56,14 @@ namespace Sharp7.Rx public IObservable ConnectionState { get; private set; } public ILogger Logger { get; set; } + public TimeSpan MultiVarRequestCycleTime { get; } = TimeSpan.FromSeconds(0.1); - public async Task InitializeAsync() + public int MultiVarRequestMaxItems { get; set; } = 16; + + public void Dispose() { - s7Connector = new Sharp7Connector(plcConnectionSettings, varaibleNameParser) { Logger = Logger }; - ConnectionState = s7Connector.ConnectionState; - - await s7Connector.InitializeAsync(); - -#pragma warning disable 4014 - Task.Run(async () => - { - try - { - await s7Connector.Connect(); - } - catch (Exception e) - { - Logger?.LogError(e, "Error while connecting to PLC"); - } - }); -#pragma warning restore 4014 - - RunNotifications(s7Connector, MultiVarRequestCycleTime) - .AddDisposableTo(Disposables); - - return true; - } - - public Task GetValue(string variableName) - { - return GetValue(variableName, CancellationToken.None); - } - - - - public async Task GetValue(string variableName, CancellationToken token) - { - var address = varaibleNameParser.Parse(variableName); - if (address == null) throw new ArgumentException("Input variable name is not valid", nameof(variableName)); - - var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token); - return S7ValueConverter.ConvertToType(data, address); - } - - - public Task SetValue(string variableName, TValue value) - { - return SetValue(variableName, value, CancellationToken.None); - } - - public async Task SetValue(string variableName, TValue value, CancellationToken token) - { - var address = varaibleNameParser.Parse(variableName); - if (address == null) throw new ArgumentException("Input variable name is not valid", "variableName"); - - if (typeof(TValue) == typeof(bool)) - { - await s7Connector.WriteBit(address.Operand, address.Start, address.Bit, (bool)(object)value, address.DbNr, token); - } - else if (typeof(TValue) == typeof(int) || typeof(TValue) == typeof(short)) - { - byte[] bytes; - if (address.Length == 4) - bytes = BitConverter.GetBytes((int)(object)value); - else - bytes = BitConverter.GetBytes((short)(object)value); - - Array.Reverse(bytes); - - await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); - } - else if (typeof(TValue) == typeof(byte) || typeof(TValue) == typeof(char)) - { - var bytes = new[] { Convert.ToByte(value) }; - await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); - } - else if (typeof(TValue) == typeof(byte[])) - { - await s7Connector.WriteBytes(address.Operand, address.Start, (byte[])(object)value, address.DbNr, token); - } - else if (typeof(TValue) == typeof(float)) - { - var buffer = new byte[sizeof(float)]; - S7.SetRealAt(buffer, 0, (float)(object)value); - await s7Connector.WriteBytes(address.Operand, address.Start, buffer, address.DbNr, token); - } - else if (typeof(TValue) == typeof(string)) - { - var stringValue = value as string; - if (stringValue == null) throw new ArgumentException("Value must be of type string", "value"); - - var bytes = Encoding.ASCII.GetBytes(stringValue); - Array.Resize(ref bytes, address.Length); - - if (address.Type == DbType.String) - { - var bytesWritten = await s7Connector.WriteBytes(address.Operand, address.Start, new[] { (byte)address.Length, (byte)bytes.Length }, address.DbNr, token); - token.ThrowIfCancellationRequested(); - if (bytesWritten == 2) - { - var stringStartAddress = (ushort)(address.Start + 2); - token.ThrowIfCancellationRequested(); - await s7Connector.WriteBytes(address.Operand, stringStartAddress, bytes, address.DbNr, token); - } - } - else - { - await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); - token.ThrowIfCancellationRequested(); - } - } - else - { - throw new InvalidOperationException($"type '{typeof(TValue)}' not supported."); - } + Dispose(true); + GC.SuppressFinalize(this); } public IObservable CreateNotification(string variableName, TransmissionMode transmissionMode) @@ -197,10 +90,119 @@ namespace Sharp7.Rx }); } - public void Dispose() + public Task GetValue(string variableName) { - Dispose(true); - GC.SuppressFinalize(this); + return GetValue(variableName, CancellationToken.None); + } + + + public Task SetValue(string variableName, TValue value) + { + return SetValue(variableName, value, CancellationToken.None); + } + + + public async Task GetValue(string variableName, CancellationToken token) + { + var address = varaibleNameParser.Parse(variableName); + if (address == null) throw new ArgumentException("Input variable name is not valid", nameof(variableName)); + + var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token); + return S7ValueConverter.ConvertToType(data, address); + } + + public async Task InitializeAsync() + { + s7Connector = new Sharp7Connector(plcConnectionSettings, varaibleNameParser) {Logger = Logger}; + ConnectionState = s7Connector.ConnectionState; + + await s7Connector.InitializeAsync(); + +#pragma warning disable 4014 + Task.Run(async () => + { + try + { + await s7Connector.Connect(); + } + catch (Exception e) + { + Logger?.LogError(e, "Error while connecting to PLC"); + } + }); +#pragma warning restore 4014 + + RunNotifications(s7Connector, MultiVarRequestCycleTime) + .AddDisposableTo(Disposables); + + return true; + } + + public async Task SetValue(string variableName, TValue value, CancellationToken token) + { + var address = varaibleNameParser.Parse(variableName); + if (address == null) throw new ArgumentException("Input variable name is not valid", "variableName"); + + if (typeof(TValue) == typeof(bool)) + { + await s7Connector.WriteBit(address.Operand, address.Start, address.Bit, (bool) (object) value, address.DbNr, token); + } + else if (typeof(TValue) == typeof(int) || typeof(TValue) == typeof(short)) + { + byte[] bytes; + if (address.Length == 4) + bytes = BitConverter.GetBytes((int) (object) value); + else + bytes = BitConverter.GetBytes((short) (object) value); + + Array.Reverse(bytes); + + await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); + } + else if (typeof(TValue) == typeof(byte) || typeof(TValue) == typeof(char)) + { + var bytes = new[] {Convert.ToByte(value)}; + await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); + } + else if (typeof(TValue) == typeof(byte[])) + { + await s7Connector.WriteBytes(address.Operand, address.Start, (byte[]) (object) value, address.DbNr, token); + } + else if (typeof(TValue) == typeof(float)) + { + var buffer = new byte[sizeof(float)]; + buffer.SetRealAt(0, (float) (object) value); + await s7Connector.WriteBytes(address.Operand, address.Start, buffer, address.DbNr, token); + } + else if (typeof(TValue) == typeof(string)) + { + var stringValue = value as string; + if (stringValue == null) throw new ArgumentException("Value must be of type string", "value"); + + var bytes = Encoding.ASCII.GetBytes(stringValue); + Array.Resize(ref bytes, address.Length); + + if (address.Type == DbType.String) + { + var bytesWritten = await s7Connector.WriteBytes(address.Operand, address.Start, new[] {(byte) address.Length, (byte) bytes.Length}, address.DbNr, token); + token.ThrowIfCancellationRequested(); + if (bytesWritten == 2) + { + var stringStartAddress = (ushort) (address.Start + 2); + token.ThrowIfCancellationRequested(); + await s7Connector.WriteBytes(address.Operand, stringStartAddress, bytes, address.DbNr, token); + } + } + else + { + await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token); + token.ThrowIfCancellationRequested(); + } + } + else + { + throw new InvalidOperationException($"type '{typeof(TValue)}' not supported."); + } } protected virtual void Dispose(bool disposing) @@ -223,21 +225,6 @@ namespace Sharp7.Rx } } - ~Sharp7Plc() - { - Dispose(false); - } - - private IDisposable RunNotifications(IS7Connector connector, TimeSpan cycle) - { - return ConnectionState.FirstAsync() - .Select(states => states == Enums.ConnectionState.Connected) - .SelectMany(connected => GetAllValues(connected, connector)) - .RepeatAfterDelay(MultiVarRequestCycleTime) - .LogAndRetryAfterDelay(Logger, cycle, "Error while getting batch notifications from plc") - .Subscribe(); - } - private async Task GetAllValues(bool connected, IS7Connector connector) { if (!connected) @@ -272,13 +259,26 @@ namespace Sharp7.Rx var min = performanceCoutner.Min(); var max = performanceCoutner.Max(); - Logger?.LogTrace("Performance statistic during {0} elements of plc notification. Min: {1}, Max: {2}, Average: {3}, Plc: '{4}', Number of variables: {5}, Batch size: {6}", performanceCoutner.Capacity, min, max, average, plcConnectionSettings.IpAddress, multiVariableSubscriptions.ExistingKeys.Count(), - MultiVarRequestMaxItems); + Logger?.LogTrace("Performance statistic during {0} elements of plc notification. Min: {1}, Max: {2}, Average: {3}, Plc: '{4}', Number of variables: {5}, Batch size: {6}", performanceCoutner.Capacity, min, max, average, plcConnectionSettings.IpAddress, + multiVariableSubscriptions.ExistingKeys.Count(), + MultiVarRequestMaxItems); performanceCoutner.Clear(); } } - public int MultiVarRequestMaxItems { get; set; } = 16; - public TimeSpan MultiVarRequestCycleTime { get; private set; } = TimeSpan.FromSeconds(0.1); + private IDisposable RunNotifications(IS7Connector connector, TimeSpan cycle) + { + return ConnectionState.FirstAsync() + .Select(states => states == Enums.ConnectionState.Connected) + .SelectMany(connected => GetAllValues(connected, connector)) + .RepeatAfterDelay(MultiVarRequestCycleTime) + .LogAndRetryAfterDelay(Logger, cycle, "Error while getting batch notifications from plc") + .Subscribe(); + } + + ~Sharp7Plc() + { + Dispose(false); + } } -} +} \ No newline at end of file