mirror of
https://github.com/evopro-ag/Sharp7Reactive.git
synced 2025-12-16 03:42:51 +00:00
302 lines
12 KiB
C#
302 lines
12 KiB
C#
using System;
|
|
using System.Reactive;
|
|
using System.Reactive.Linq;
|
|
using System.Reactive.Subjects;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Sharp7.Rx.Enums;
|
|
using Sharp7.Rx.Interfaces;
|
|
using Sharp7.Rx.Resources;
|
|
|
|
namespace Sharp7.Rx
|
|
{
|
|
public class Sharp7Plc : IPlc
|
|
{
|
|
private readonly string ipAddress;
|
|
private readonly int rackNumber;
|
|
private readonly int cpuMpiAddress;
|
|
private readonly S7VariableNameParser varaibleNameParser;
|
|
private bool disposed;
|
|
private ISubject<Unit> disposingSubject = new Subject<Unit>();
|
|
private IS7Connector s7Connector;
|
|
|
|
public Sharp7Plc(string ipAddress, int rackNumber, int cpuMpiAddress)
|
|
{
|
|
this.ipAddress = ipAddress;
|
|
this.rackNumber = rackNumber;
|
|
this.cpuMpiAddress = cpuMpiAddress;
|
|
|
|
varaibleNameParser = new S7VariableNameParser();
|
|
}
|
|
|
|
public IObservable<ConnectionState> ConnectionState { get; private set; }
|
|
|
|
public async Task<bool> InitializeAsync()
|
|
{
|
|
s7Connector = new Sharp7Connector(ipAddress, rackNumber, cpuMpiAddress);
|
|
ConnectionState = s7Connector.ConnectionState;
|
|
|
|
await s7Connector.InitializeAsync();
|
|
|
|
#pragma warning disable 4014
|
|
Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await s7Connector.Connect();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
|
|
}
|
|
});
|
|
#pragma warning restore 4014
|
|
|
|
return true;
|
|
}
|
|
|
|
public Task<TValue> GetValue<TValue>(string variableName)
|
|
{
|
|
return GetValue<TValue>(variableName, CancellationToken.None);
|
|
}
|
|
|
|
public async Task<TValue> GetValue<TValue>(string variableName, CancellationToken token)
|
|
{
|
|
var address = varaibleNameParser.Parse(variableName);
|
|
if (address == null) throw new ArgumentException("Input variable name is not valid", nameof(variableName));
|
|
|
|
if (typeof(TValue) == typeof(bool))
|
|
{
|
|
var b = await s7Connector.ReadBit(address.Operand, address.Start, address.Bit, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
return (TValue)(object)b;
|
|
}
|
|
|
|
if (typeof(TValue) == typeof(int))
|
|
{
|
|
var b = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
if (address.Length == 2)
|
|
return (TValue)(object)((b[0] << 8) + b[1]);
|
|
if (address.Length == 4)
|
|
{
|
|
Array.Reverse(b);
|
|
return (TValue)(object)Convert.ToInt32(b);
|
|
}
|
|
|
|
|
|
throw new InvalidOperationException($"length must be 2 or 4 but is {address.Length}");
|
|
}
|
|
|
|
if (typeof(TValue) == typeof(ulong))
|
|
{
|
|
var b = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
Array.Reverse(b);
|
|
return (TValue)(object)Convert.ToUInt64(b);
|
|
}
|
|
|
|
if (typeof(TValue) == typeof(short))
|
|
{
|
|
var b = await s7Connector.ReadBytes(address.Operand, address.Start, 2, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
return (TValue)(object)(short)((b[0] << 8) + b[1]);
|
|
}
|
|
|
|
if (typeof(TValue) == typeof(byte) || typeof(TValue) == typeof(char))
|
|
{
|
|
var b = await s7Connector.ReadBytes(address.Operand, address.Start, 1, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
return (TValue)(object)b[0];
|
|
}
|
|
|
|
if (typeof(TValue) == typeof(byte[]))
|
|
{
|
|
var b = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
return (TValue)(object)b;
|
|
}
|
|
|
|
if (typeof(TValue) == typeof(double) || typeof(TValue) == typeof(float))
|
|
{
|
|
var bytes = await s7Connector.ReadBytes(address.Operand, address.Start, 4, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
var d = Convert.ToSingle(bytes);
|
|
return (TValue)(object)d;
|
|
}
|
|
|
|
if (typeof(TValue) == typeof(string))
|
|
{
|
|
if (address.Type == DbType.String)
|
|
{
|
|
var bytes = await s7Connector.ReadBytes(address.Operand, address.Start, 2, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
var stringLength = bytes[1];
|
|
|
|
var stringStartAddress = (ushort)(address.Start + 2);
|
|
var stringInBytes = await s7Connector.ReadBytes(address.Operand, stringStartAddress, stringLength, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
return (TValue)(object)Encoding.ASCII.GetString(stringInBytes);
|
|
}
|
|
else
|
|
{
|
|
var stringInBytes = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
|
token.ThrowIfCancellationRequested();
|
|
return (TValue)(object)Encoding.ASCII.GetString(stringInBytes).Trim();
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException(string.Format("type '{0}' not supported.", typeof(TValue)));
|
|
}
|
|
|
|
|
|
public Task SetValue<TValue>(string variableName, TValue value)
|
|
{
|
|
return SetValue(variableName, value, CancellationToken.None);
|
|
}
|
|
|
|
public async Task SetValue<TValue>(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.");
|
|
}
|
|
}
|
|
|
|
public IObservable<TValue> CreateNotification<TValue>(string variableName, TransmissionMode transmissionMode, TimeSpan cycle)
|
|
{
|
|
var address = varaibleNameParser.Parse(variableName);
|
|
if (address == null) throw new ArgumentException("Input variable name is not valid", nameof(variableName));
|
|
|
|
if (cycle < TimeSpan.FromMilliseconds(100))
|
|
cycle = TimeSpan.FromMilliseconds(100);
|
|
|
|
var notification = ConnectionState.FirstAsync().Select(states => states == Enums.ConnectionState.Connected)
|
|
.SelectMany(async connected =>
|
|
{
|
|
var value = default(TValue);
|
|
if (connected)
|
|
{
|
|
value = await GetValue<TValue>(variableName, CancellationToken.None);
|
|
}
|
|
|
|
return new
|
|
{
|
|
HasValue = connected,
|
|
Value = value
|
|
};
|
|
})
|
|
// TODO: .RepeatAfterDelay(cycle)
|
|
// TODO: .LogAndRetryAfterDelay(Logger, cycle, StringResources.StrLogErrorReadingDataFromPlc)
|
|
.TakeUntil(disposingSubject)
|
|
.Where(union => union.HasValue)
|
|
.Select(union => union.Value);
|
|
|
|
if (transmissionMode == TransmissionMode.Cyclic)
|
|
return notification;
|
|
|
|
if (transmissionMode == TransmissionMode.OnChange)
|
|
return notification.DistinctUntilChanged();
|
|
|
|
throw new ArgumentException("Transmission mode can either be Cyclic or OnChange", nameof(transmissionMode));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
if (disposingSubject != null)
|
|
{
|
|
disposingSubject.OnNext(Unit.Default);
|
|
disposingSubject.OnCompleted();
|
|
var disposable = (disposingSubject as IDisposable);
|
|
if (disposable != null) disposable.Dispose();
|
|
disposingSubject = null;
|
|
}
|
|
if (s7Connector != null)
|
|
{
|
|
s7Connector.Disconnect().Wait();
|
|
s7Connector.Dispose();
|
|
s7Connector = null;
|
|
}
|
|
}
|
|
|
|
disposed = true;
|
|
}
|
|
}
|
|
|
|
~Sharp7Plc()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
}
|
|
}
|