mirror of
https://github.com/evopro-ag/Sharp7Reactive.git
synced 2025-12-19 04:32:52 +00:00
Add non-generic GetValue and improve documentation
This commit is contained in:
@@ -22,4 +22,23 @@ internal static class VariableAddressExtensions
|
|||||||
|
|
||||||
public static bool MatchesType(this VariableAddress address, Type type) =>
|
public static bool MatchesType(this VariableAddress address, Type type) =>
|
||||||
supportedTypeMap.TryGetValue(type, out var map) && map(address);
|
supportedTypeMap.TryGetValue(type, out var map) && map(address);
|
||||||
|
|
||||||
|
public static Type GetClrType(this VariableAddress address) =>
|
||||||
|
address.Type switch
|
||||||
|
{
|
||||||
|
DbType.Bit => typeof(bool),
|
||||||
|
DbType.String => typeof(string),
|
||||||
|
DbType.WString => typeof(string),
|
||||||
|
DbType.Byte => address.Length == 1 ? typeof(byte) : typeof(byte[]),
|
||||||
|
DbType.Int => typeof(short),
|
||||||
|
DbType.UInt => typeof(ushort),
|
||||||
|
DbType.DInt => typeof(int),
|
||||||
|
DbType.UDInt => typeof(uint),
|
||||||
|
DbType.LInt => typeof(long),
|
||||||
|
DbType.ULInt => typeof(ulong),
|
||||||
|
DbType.Single => typeof(float),
|
||||||
|
DbType.Double => typeof(double),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(address))
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ namespace Sharp7.Rx.Interfaces;
|
|||||||
public interface IPlc : IDisposable
|
public interface IPlc : IDisposable
|
||||||
{
|
{
|
||||||
IObservable<TValue> CreateNotification<TValue>(string variableName, TransmissionMode transmissionMode);
|
IObservable<TValue> CreateNotification<TValue>(string variableName, TransmissionMode transmissionMode);
|
||||||
Task SetValue<TValue>(string variableName, TValue value);
|
Task SetValue<TValue>(string variableName, TValue value, CancellationToken token = default);
|
||||||
Task<TValue> GetValue<TValue>(string variableName);
|
Task<TValue> GetValue<TValue>(string variableName, CancellationToken token = default);
|
||||||
IObservable<ConnectionState> ConnectionState { get; }
|
IObservable<ConnectionState> ConnectionState { get; }
|
||||||
|
|
||||||
|
Task<object> GetValue(string variableName, CancellationToken token = default);
|
||||||
|
|
||||||
ILogger Logger { get; }
|
ILogger Logger { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using System.Diagnostics;
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Sharp7.Rx.Basics;
|
using Sharp7.Rx.Basics;
|
||||||
using Sharp7.Rx.Enums;
|
using Sharp7.Rx.Enums;
|
||||||
@@ -14,6 +16,11 @@ namespace Sharp7.Rx;
|
|||||||
|
|
||||||
public class Sharp7Plc : IPlc
|
public class Sharp7Plc : IPlc
|
||||||
{
|
{
|
||||||
|
private static readonly ArrayPool<byte> arrayPool = ArrayPool<byte>.Shared;
|
||||||
|
|
||||||
|
private static readonly MethodInfo getValueMethod = typeof(Sharp7Plc).GetMethods()
|
||||||
|
.Single(m => m.Name == nameof(GetValue) && m.GetGenericArguments().Length == 1);
|
||||||
|
|
||||||
private readonly CompositeDisposable disposables = new();
|
private readonly CompositeDisposable disposables = new();
|
||||||
private readonly ConcurrentSubjectDictionary<string, byte[]> multiVariableSubscriptions = new(StringComparer.InvariantCultureIgnoreCase);
|
private readonly ConcurrentSubjectDictionary<string, byte[]> multiVariableSubscriptions = new(StringComparer.InvariantCultureIgnoreCase);
|
||||||
private readonly List<long> performanceCounter = new(1000);
|
private readonly List<long> performanceCounter = new(1000);
|
||||||
@@ -21,8 +28,6 @@ public class Sharp7Plc : IPlc
|
|||||||
private readonly CacheVariableNameParser variableNameParser = new CacheVariableNameParser(new VariableNameParser());
|
private readonly CacheVariableNameParser variableNameParser = new CacheVariableNameParser(new VariableNameParser());
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
private Sharp7Connector s7Connector;
|
private Sharp7Connector s7Connector;
|
||||||
private static readonly ArrayPool<byte> arrayPool = ArrayPool<byte>.Shared;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -75,6 +80,14 @@ public class Sharp7Plc : IPlc
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an Observable for a given variable. Multiple notifications are automatically combined into a multi-variable subscription to
|
||||||
|
/// reduce network trafic and PLC workload.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue"></typeparam>
|
||||||
|
/// <param name="variableName"></param>
|
||||||
|
/// <param name="transmissionMode"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public IObservable<TValue> CreateNotification<TValue>(string variableName, TransmissionMode transmissionMode)
|
public IObservable<TValue> CreateNotification<TValue>(string variableName, TransmissionMode transmissionMode)
|
||||||
{
|
{
|
||||||
return Observable.Create<TValue>(observer =>
|
return Observable.Create<TValue>(observer =>
|
||||||
@@ -104,19 +117,14 @@ public class Sharp7Plc : IPlc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TValue> GetValue<TValue>(string variableName)
|
/// <summary>
|
||||||
{
|
/// Read PLC variable as generic variable.
|
||||||
return GetValue<TValue>(variableName, CancellationToken.None);
|
/// </summary>
|
||||||
}
|
/// <typeparam name="TValue"></typeparam>
|
||||||
|
/// <param name="variableName"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
public Task SetValue<TValue>(string variableName, TValue value)
|
/// <returns></returns>
|
||||||
{
|
public async Task<TValue> GetValue<TValue>(string variableName, CancellationToken token = default)
|
||||||
return SetValue(variableName, value, CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<TValue> GetValue<TValue>(string variableName, CancellationToken token)
|
|
||||||
{
|
{
|
||||||
var address = ParseAndVerify(variableName, typeof(TValue));
|
var address = ParseAndVerify(variableName, typeof(TValue));
|
||||||
|
|
||||||
@@ -124,31 +132,38 @@ public class Sharp7Plc : IPlc
|
|||||||
return ValueConverter.ReadFromBuffer<TValue>(data, address);
|
return ValueConverter.ReadFromBuffer<TValue>(data, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> InitializeAsync()
|
/// <summary>
|
||||||
|
/// Read PLC variable as object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="variableName"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<object> GetValue(string variableName, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
await s7Connector.InitializeAsync();
|
var address = variableNameParser.Parse(variableName);
|
||||||
|
var clrType = address.GetClrType();
|
||||||
|
|
||||||
#pragma warning disable 4014
|
var genericGetValue = getValueMethod!.MakeGenericMethod(clrType);
|
||||||
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)
|
var task = genericGetValue.Invoke(this, [variableName, token]) as Task;
|
||||||
.AddDisposableTo(disposables);
|
|
||||||
|
|
||||||
return true;
|
await task!;
|
||||||
|
var taskType = typeof(Task<>).MakeGenericType(clrType);
|
||||||
|
var propertyInfo = taskType.GetProperty(nameof(Task<object>.Result));
|
||||||
|
var result = propertyInfo!.GetValue(task);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetValue<TValue>(string variableName, TValue value, CancellationToken token)
|
/// <summary>
|
||||||
|
/// Write value to the PLC.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue"></typeparam>
|
||||||
|
/// <param name="variableName"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task SetValue<TValue>(string variableName, TValue value, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var address = ParseAndVerify(variableName, typeof(TValue));
|
var address = ParseAndVerify(variableName, typeof(TValue));
|
||||||
|
|
||||||
@@ -170,11 +185,105 @@ public class Sharp7Plc : IPlc
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ArrayPool<Byte>.Shared.Return(buffer);
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trigger PLC connection and start notification loop.
|
||||||
|
/// <para>
|
||||||
|
/// This method returns immediately and does not wait for the connection to be established.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Always true</returns>
|
||||||
|
[Obsolete("Use InitializeConnection.")]
|
||||||
|
public async Task<bool> InitializeAsync()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize PLC connection and wait for connection to be established.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task TriggerInitialize(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
await s7Connector.InitializeAsync();
|
||||||
|
|
||||||
|
// Triger connection.
|
||||||
|
// The initial connection might fail. In this case a reconnect is initiated.
|
||||||
|
// So we ignore any errors and wait for ConnectionState Connected afterward.
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await s7Connector.Connect();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger?.LogError(e, "Error while connecting to PLC");
|
||||||
|
}
|
||||||
|
}, token);
|
||||||
|
|
||||||
|
await s7Connector.ConnectionState
|
||||||
|
.FirstAsync(c => c == Enums.ConnectionState.Connected)
|
||||||
|
.ToTask(token);
|
||||||
|
|
||||||
|
RunNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize PLC connection and wait for connection to be established.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task InitializeConnection(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
await s7Connector.InitializeAsync();
|
||||||
|
|
||||||
|
// Triger connection.
|
||||||
|
// The initial connection might fail. In this case a reconnect is initiated.
|
||||||
|
// So we ignore any errors and wait for ConnectionState Connected afterward.
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await s7Connector.Connect();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger?.LogError(e, "Error while connecting to PLC");
|
||||||
|
}
|
||||||
|
}, token);
|
||||||
|
|
||||||
|
await s7Connector.ConnectionState
|
||||||
|
.FirstAsync(c => c == Enums.ConnectionState.Connected)
|
||||||
|
.ToTask(token);
|
||||||
|
|
||||||
|
RunNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
@@ -247,14 +356,15 @@ public class Sharp7Plc : IPlc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IDisposable RunNotifications(IS7Connector connector, TimeSpan cycle)
|
private void RunNotifications()
|
||||||
{
|
{
|
||||||
return ConnectionState.FirstAsync()
|
ConnectionState.FirstAsync()
|
||||||
.Select(states => states == Enums.ConnectionState.Connected)
|
.Select(states => states == Enums.ConnectionState.Connected)
|
||||||
.SelectMany(connected => GetAllValues(connected, connector))
|
.SelectMany(connected => GetAllValues(connected, s7Connector))
|
||||||
.RepeatAfterDelay(MultiVarRequestCycleTime)
|
.RepeatAfterDelay(MultiVarRequestCycleTime)
|
||||||
.LogAndRetryAfterDelay(Logger, cycle, "Error while getting batch notifications from plc")
|
.LogAndRetryAfterDelay(Logger, MultiVarRequestCycleTime, "Error while getting batch notifications from plc")
|
||||||
.Subscribe();
|
.Subscribe()
|
||||||
|
.AddDisposableTo(disposables);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Sharp7Plc()
|
~Sharp7Plc()
|
||||||
|
|||||||
Reference in New Issue
Block a user