modbus v2
This commit is contained in:
parent
36af277559
commit
7b0c7dce36
|
@ -0,0 +1,7 @@
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
|
||||||
|
public abstract class Channel
|
||||||
|
{
|
||||||
|
public abstract IReadOnlyList<Byte> Read(Int32 nBytes);
|
||||||
|
public abstract void Write(IReadOnlyList<Byte> bytes);
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
|
||||||
|
public abstract class ConnectionChannel<T> : Channel, IDisposable
|
||||||
|
{
|
||||||
|
private readonly Func<Exception, Boolean> _CloseAfterException ;
|
||||||
|
private readonly Boolean _CloseAfterSuccessfulRead ;
|
||||||
|
private readonly Boolean _CloseAfterSuccessfulWrite ;
|
||||||
|
|
||||||
|
protected abstract T Open();
|
||||||
|
protected abstract void Close(T connection);
|
||||||
|
|
||||||
|
protected abstract IReadOnlyList<Byte> Read (T connection, Int32 nBytes);
|
||||||
|
protected abstract void Write(T connection, IReadOnlyList<Byte> data);
|
||||||
|
|
||||||
|
private T? _Connection;
|
||||||
|
|
||||||
|
protected ConnectionChannel(Boolean closeAfterSuccessfulRead = false,
|
||||||
|
Boolean closeAfterSuccessfulWrite = false,
|
||||||
|
Func<Exception, Boolean>? closeAfterException = null)
|
||||||
|
{
|
||||||
|
_CloseAfterSuccessfulRead = closeAfterSuccessfulRead;
|
||||||
|
_CloseAfterSuccessfulWrite = closeAfterSuccessfulWrite;
|
||||||
|
_CloseAfterException = closeAfterException ?? (_ => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyList<Byte> Read(Int32 nBytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Read(Connection, nBytes);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (_CloseAfterException(e))
|
||||||
|
Close();
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_CloseAfterSuccessfulRead)
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void Write(IReadOnlyList<Byte> data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Write(Connection, data);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (_CloseAfterException(e))
|
||||||
|
Close();
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_CloseAfterSuccessfulWrite)
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private T Connection => _Connection ??= Open();
|
||||||
|
|
||||||
|
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
if (_Connection is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Close(_Connection);
|
||||||
|
_Connection = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose() => Close();
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Connections;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
|
||||||
public static class PushToPullHelper
|
public static class PushToPullHelper
|
||||||
{
|
{
|
||||||
|
@ -14,12 +14,16 @@ public static class PushToPullHelper
|
||||||
var nAvailable = src
|
var nAvailable = src
|
||||||
.Do(buffer.Enqueue)
|
.Do(buffer.Enqueue)
|
||||||
.Select(_ => buffer.Count)
|
.Select(_ => buffer.Count)
|
||||||
.Publish();
|
.Publish()
|
||||||
|
.RefCount();
|
||||||
|
|
||||||
nAvailable.Connect();
|
nAvailable.SelectError()
|
||||||
|
.Subscribe(e => e.WriteLine());
|
||||||
|
|
||||||
async Task<IReadOnlyList<T>> Read(Int32 n)
|
async Task<IReadOnlyList<T>> Read(Int32 n)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"requesting {n}");
|
||||||
|
|
||||||
var available = buffer.Count;
|
var available = buffer.Count;
|
||||||
if (available < n)
|
if (available < n)
|
||||||
available = await nAvailable.FirstOrDefaultAsync(a => a >= n);
|
available = await nAvailable.FirstOrDefaultAsync(a => a >= n);
|
|
@ -0,0 +1,207 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using CliWrap;
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
|
||||||
|
public record RemoteSerialConnection
|
||||||
|
(
|
||||||
|
Func<Int32, IReadOnlyList<Byte>> Read,
|
||||||
|
Action<IReadOnlyList<Byte>> Write,
|
||||||
|
Action Close
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// public class RemoteSerialChannel : ConnectionChannel<RemoteSerialConnection>
|
||||||
|
// {
|
||||||
|
// private readonly Command _Command;
|
||||||
|
//
|
||||||
|
// public RemoteSerialChannel(SshHost host,
|
||||||
|
// String tty,
|
||||||
|
// Int32 baudRate,
|
||||||
|
// Parity parity,
|
||||||
|
// Int32 stopBits,
|
||||||
|
// Int32 dataBits)
|
||||||
|
// {
|
||||||
|
// tty = tty.EnsureStartsWith("/dev/");
|
||||||
|
//
|
||||||
|
// var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits);
|
||||||
|
// var redirectStreams = RedirectStreams(tty);
|
||||||
|
//
|
||||||
|
// var call = $"{configureTty}; {redirectStreams}";
|
||||||
|
//
|
||||||
|
// _Command = host
|
||||||
|
// .Command
|
||||||
|
// .AppendArgument(call);
|
||||||
|
//
|
||||||
|
// _Command.WriteLine();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// protected override RemoteSerialConnection Open()
|
||||||
|
// {
|
||||||
|
// var observableProcess = new ObservableProcess(_Command);
|
||||||
|
//
|
||||||
|
// observableProcess.Start();
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// IReadOnlyList<Byte> Read(Int32 i)
|
||||||
|
// {
|
||||||
|
// return observableProcess.Read(i).Result;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void Write(IReadOnlyList<Byte> data) => observableProcess.StdIn.OnNext(data.ToArray());
|
||||||
|
//
|
||||||
|
// return new RemoteSerialConnection(Read, Write, observableProcess.Interrupt);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// protected override void Close(RemoteSerialConnection connection)
|
||||||
|
// {
|
||||||
|
// connection.Close();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// protected override IReadOnlyList<Byte> Read(RemoteSerialConnection connection, Int32 nBytes)
|
||||||
|
// {
|
||||||
|
// return connection.Read(nBytes);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// protected override void Write(RemoteSerialConnection connection, IReadOnlyList<Byte> data)
|
||||||
|
// {
|
||||||
|
// connection.Write(data);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// private static String RedirectStreams(String tty)
|
||||||
|
// {
|
||||||
|
// // https://unix.stackexchange.com/questions/19604/all-about-ssh-proxycommand
|
||||||
|
//
|
||||||
|
// return $"exec 3<>{tty}; " +
|
||||||
|
// $"cat <&3 & cat >&3; " +
|
||||||
|
// // $"(cat <&3 | tee -a ~/read) & cat | tee -a ~/write >&3; " +
|
||||||
|
// $"kill $!";
|
||||||
|
//
|
||||||
|
// // var call = $"trap 'kill -HUP $(jobs -lp) 2>/dev/null || true' EXIT; " +
|
||||||
|
// // $"{configure} ; "+
|
||||||
|
// // $"dd if={tty} of=/dev/stdout bs=1 & " +
|
||||||
|
// // $"dd if=/dev/stdin of={tty} bs=1 ;"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// [SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||||
|
// private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits)
|
||||||
|
// {
|
||||||
|
// var oParity = parity switch
|
||||||
|
// {
|
||||||
|
// Parity.Even => "parenb -parodd",
|
||||||
|
// Parity.Odd => "parenb parodd",
|
||||||
|
// Parity.None => "-parenb",
|
||||||
|
// _ => throw new NotImplementedException()
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// var oStopBits = stopBits switch
|
||||||
|
// {
|
||||||
|
// 1 => "-cstopb",
|
||||||
|
// 2 => "cstopb",
|
||||||
|
// _ => throw new NotImplementedException()
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// var oDataBits = "cs" + dataBits;
|
||||||
|
//
|
||||||
|
// return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}";
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
|
||||||
|
{
|
||||||
|
private readonly Command _Command;
|
||||||
|
private readonly TcpChannel _TcpChannel;
|
||||||
|
|
||||||
|
const String SsDir = "/opt/victronenergy/serial-starter";
|
||||||
|
const String KillTasks = "kill $!";
|
||||||
|
|
||||||
|
private CancellationTokenSource _CancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
private CommandTask<CommandResult>? _CommandTask;
|
||||||
|
|
||||||
|
public RemoteSerialChannel(SshHost host,
|
||||||
|
String tty,
|
||||||
|
Int32 baudRate,
|
||||||
|
Parity parity,
|
||||||
|
Int32 stopBits,
|
||||||
|
Int32 dataBits)
|
||||||
|
{
|
||||||
|
const Int32 port = 6855;
|
||||||
|
|
||||||
|
tty = tty.EnsureStartsWith("/dev/");
|
||||||
|
|
||||||
|
var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits);
|
||||||
|
|
||||||
|
var stopTty = $"{SsDir}/stop-tty.sh {tty}";
|
||||||
|
var startTty = $"{SsDir}/start-tty.sh {tty}";
|
||||||
|
|
||||||
|
// ReSharper disable once StringLiteralTypo
|
||||||
|
var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw";
|
||||||
|
|
||||||
|
//var script = $"-n -o RemoteCommand='{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}'";
|
||||||
|
var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}";
|
||||||
|
|
||||||
|
_Command = host.Command.AppendArgument(script);
|
||||||
|
|
||||||
|
_Command.WriteLine();
|
||||||
|
|
||||||
|
_TcpChannel = new TcpChannel(host.HostName, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits)
|
||||||
|
{
|
||||||
|
var oParity = parity switch
|
||||||
|
{
|
||||||
|
Parity.Even => "parenb -parodd",
|
||||||
|
Parity.Odd => "parenb parodd",
|
||||||
|
Parity.None => "-parenb",
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
var oStopBits = stopBits switch
|
||||||
|
{
|
||||||
|
1 => "-cstopb",
|
||||||
|
2 => "cstopb",
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
var oDataBits = "cs" + dataBits;
|
||||||
|
|
||||||
|
return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override TcpChannel Open()
|
||||||
|
{
|
||||||
|
_CommandTask ??= _Command.ExecuteAsync(_CancellationTokenSource.Token);
|
||||||
|
|
||||||
|
Thread.Sleep(2000); // wait until socat is ready
|
||||||
|
return _TcpChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Close(TcpChannel connection)
|
||||||
|
{
|
||||||
|
_CancellationTokenSource.Cancel();
|
||||||
|
connection.Dispose();
|
||||||
|
|
||||||
|
_CommandTask = null;
|
||||||
|
|
||||||
|
_CancellationTokenSource = new CancellationTokenSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyList<Byte> Read(TcpChannel connection, Int32 nBytes)
|
||||||
|
{
|
||||||
|
return connection.Read(nBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Write(TcpChannel connection, IReadOnlyList<Byte> data)
|
||||||
|
{
|
||||||
|
connection.Write(data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System.IO.Ports;
|
||||||
|
using CliWrap;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
|
||||||
|
public class SerialPortChannel : ConnectionChannel<SerialPort>
|
||||||
|
{
|
||||||
|
private readonly Func<SerialPort> _Open;
|
||||||
|
|
||||||
|
public SerialPortChannel(String portName,
|
||||||
|
Int32 baudRate,
|
||||||
|
Parity parity,
|
||||||
|
Int32 dataBits,
|
||||||
|
Int32 stopBits,
|
||||||
|
Boolean closeAfterSuccessfulRead = false,
|
||||||
|
Boolean closeAfterSuccessfulWrite = false)
|
||||||
|
:
|
||||||
|
base(
|
||||||
|
closeAfterSuccessfulRead,
|
||||||
|
closeAfterSuccessfulWrite
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var sb = stopBits switch
|
||||||
|
{
|
||||||
|
0 => StopBits.None,
|
||||||
|
1 => StopBits.One,
|
||||||
|
2 => StopBits.Two,
|
||||||
|
_ => StopBits.OnePointFive
|
||||||
|
};
|
||||||
|
|
||||||
|
_Open = () =>
|
||||||
|
{
|
||||||
|
var serialPort = new SerialPort(portName, baudRate, parity, dataBits, sb);
|
||||||
|
serialPort.Open();
|
||||||
|
return serialPort;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SerialPort Open() => _Open();
|
||||||
|
|
||||||
|
protected override void Close(SerialPort serialPort) => serialPort.Dispose();
|
||||||
|
|
||||||
|
protected override IReadOnlyList<Byte> Read(SerialPort serialPort, Int32 nBytes)
|
||||||
|
{
|
||||||
|
var buffer = new Byte[nBytes];
|
||||||
|
|
||||||
|
var bytesReceived = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var received = serialPort.Read(buffer, bytesReceived, nBytes - bytesReceived);
|
||||||
|
if (received < 0)
|
||||||
|
throw new NotConnectedException("Serial Connection has been closed");
|
||||||
|
|
||||||
|
bytesReceived += received;
|
||||||
|
}
|
||||||
|
while (bytesReceived < nBytes);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Write(SerialPort serialPort, IReadOnlyList<Byte> data)
|
||||||
|
{
|
||||||
|
var array = data.ToArray();
|
||||||
|
serialPort.Write(array, 0, array.Length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
|
||||||
|
public class TcpChannel : ConnectionChannel<TcpClient>
|
||||||
|
{
|
||||||
|
public TcpChannel(String hostname,
|
||||||
|
Int32 port,
|
||||||
|
Boolean closeAfterSuccessfulRead = false,
|
||||||
|
Boolean closeAfterSuccessfulWrite = false) : base(closeAfterSuccessfulRead, closeAfterSuccessfulWrite)
|
||||||
|
{
|
||||||
|
TcpClient Open() => new(hostname, port);
|
||||||
|
_Open = Open;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Func<TcpClient> _Open;
|
||||||
|
|
||||||
|
protected override TcpClient Open() => _Open();
|
||||||
|
protected override void Close(TcpClient tcpClient) => tcpClient.Close();
|
||||||
|
|
||||||
|
protected override IReadOnlyList<Byte> Read(TcpClient tcpClient, Int32 nToRead)
|
||||||
|
{
|
||||||
|
var buffer = new Byte[nToRead];
|
||||||
|
|
||||||
|
var stream = tcpClient.GetStream();
|
||||||
|
var nReceived = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var nRemaining = nToRead - nReceived;
|
||||||
|
var read = stream.Read(buffer, nReceived, nRemaining);
|
||||||
|
if (read <= 0)
|
||||||
|
throw new NotConnectedException("The TCP Connection was closed");
|
||||||
|
|
||||||
|
nReceived += read;
|
||||||
|
}
|
||||||
|
while (nReceived < nToRead);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Write(TcpClient tcpClient, IReadOnlyList<Byte> data)
|
||||||
|
{
|
||||||
|
var array = data.ToArray();
|
||||||
|
tcpClient.GetStream().Write(array, 0, array.Length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +1,38 @@
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Connections;
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
|
|
||||||
using Booleans = IReadOnlyList<Boolean>;
|
using Booleans = IReadOnlyCollection<Boolean>;
|
||||||
using UInt16s = IReadOnlyList<UInt16>;
|
using UInt16s = IReadOnlyCollection<UInt16>;
|
||||||
|
|
||||||
// TODO: ModbusClient = Framer(TCP/RTU) + Connection(Serial/TCP) + Encoder(binary/ascii) + quirk(+1/0)
|
// TODO: ModbusClient = Framer(TCP/RTU) + Connection(Serial/TCP) + Encoder(binary/ascii) + quirk(+1/0)
|
||||||
// Transport
|
// Transport
|
||||||
|
|
||||||
public abstract class ModbusClient
|
public abstract class ModbusClient
|
||||||
{
|
{
|
||||||
internal ModbusConnection Connection { get; }
|
internal Channel Channel { get; }
|
||||||
internal Byte SlaveId { get; }
|
internal Byte SlaveId { get; }
|
||||||
|
internal Endian Endian { get; }
|
||||||
|
|
||||||
// TODO: add additional functions: coils...
|
// TODO: add additional functions: coils...
|
||||||
|
|
||||||
public abstract Booleans ReadCoils (UInt16 readAddress, UInt16 nValues);
|
public abstract MbData ReadCoils (UInt16 readAddress, UInt16 nValues);
|
||||||
public abstract Booleans ReadDiscreteInputs (UInt16 readAddress, UInt16 nValues);
|
public abstract MbData ReadDiscreteInputs (UInt16 readAddress, UInt16 nValues);
|
||||||
public abstract UInt16s ReadInputRegisters (UInt16 readAddress, UInt16 nValues);
|
public abstract MbData ReadInputRegisters (UInt16 readAddress, UInt16 nValues);
|
||||||
public abstract UInt16s ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues);
|
public abstract MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues);
|
||||||
public abstract UInt16 WriteCoils (UInt16 writeAddress, Booleans coils);
|
public abstract UInt16 WriteCoils (UInt16 writeAddress, Booleans coils);
|
||||||
public abstract UInt16 WriteRegisters (UInt16 writeAddress, UInt16s values);
|
public abstract UInt16 WriteRegisters (UInt16 writeAddress, UInt16s values);
|
||||||
|
|
||||||
public abstract UInt16s ReadWriteRegisters (UInt16 readAddress,
|
public abstract MbData ReadWriteRegisters (UInt16 readAddress,
|
||||||
UInt16 nbToRead,
|
UInt16 nbToRead,
|
||||||
UInt16 writeAddress,
|
UInt16 writeAddress,
|
||||||
UInt16s registersToWrite);
|
UInt16s registersToWrite);
|
||||||
|
|
||||||
protected ModbusClient(ModbusConnection connection, Byte slaveId)
|
protected ModbusClient(Channel channel, Byte slaveId, Endian endian = Endian.Little)
|
||||||
{
|
{
|
||||||
Connection = connection;
|
Channel = channel;
|
||||||
SlaveId = slaveId;
|
SlaveId = slaveId;
|
||||||
|
Endian = endian;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Connections;
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Util;
|
using InnovEnergy.Lib.Protocols.Modbus.Util;
|
||||||
|
@ -12,73 +13,69 @@ using static System.Runtime.CompilerServices.MethodImplOptions;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
|
|
||||||
using UInt16s = IReadOnlyList<UInt16>;
|
using UInt16s = IReadOnlyCollection<UInt16>;
|
||||||
|
using Booleans = IReadOnlyCollection<Boolean>;
|
||||||
|
|
||||||
public class ModbusRtuClient : ModbusClient
|
public class ModbusRtuClient : ModbusClient
|
||||||
{
|
{
|
||||||
private const Int32 CrcSize = 2;
|
private const Int32 CrcSize = 2;
|
||||||
|
|
||||||
public ModbusRtuClient(ModbusConnection connection, Byte slaveId) : base(connection, slaveId)
|
public ModbusRtuClient(Channel channel, Byte slaveId) : base(channel, slaveId)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<Boolean> ReadCoils(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadCoils(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<Boolean> ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override UInt16s ReadInputRegisters(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadInputRegisters(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues);
|
var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues);
|
||||||
var crc = CalcCrc(cmd);
|
var crc = CalcCrc(cmd);
|
||||||
|
|
||||||
// TX
|
// TX
|
||||||
cmd.Data.Concat(crc).Apply(Connection.Transmit);
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
||||||
|
|
||||||
var nToRead = cmd.ExpectedResponseSize + CrcSize;
|
|
||||||
|
|
||||||
// RX
|
// RX
|
||||||
var response = nToRead
|
var response = Channel
|
||||||
.ConvertTo<UInt16>()
|
.Read(cmd.ExpectedResponseSize + CrcSize)
|
||||||
.Apply(Connection.Receive)
|
|
||||||
.ToArraySegment()
|
.ToArraySegment()
|
||||||
.Apply(AssertCrc)
|
.Apply(AssertCrc)
|
||||||
.SkipLast(CrcSize)
|
.SkipLast(CrcSize)
|
||||||
.Apply(ReadInputRegistersResponseFrame.Parse)
|
.Apply(ReadInputRegistersResponseFrame.Parse)
|
||||||
.Apply(cmd.VerifyResponse);
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
return response.RegistersRead;
|
return new MbData(response.RegistersRead.RawData, readAddress, Endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override UInt16s ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues);
|
var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues);
|
||||||
var crc = CalcCrc(cmd.Data);
|
var crc = CalcCrc(cmd.Data);
|
||||||
|
|
||||||
// TX
|
// TX
|
||||||
cmd.Data.Concat(crc).Apply(Connection.Transmit);
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
||||||
|
|
||||||
// RX
|
// RX
|
||||||
var nToRead = cmd.ExpectedResponseSize + CrcSize;
|
var response = Channel
|
||||||
var response = nToRead
|
.Read(cmd.ExpectedResponseSize + CrcSize)
|
||||||
.ConvertTo<UInt16>()
|
|
||||||
.Apply(Connection.Receive)
|
|
||||||
.ToArraySegment()
|
.ToArraySegment()
|
||||||
.Apply(AssertCrc)
|
.Apply(AssertCrc)
|
||||||
.SkipLast(CrcSize)
|
.SkipLast(CrcSize)
|
||||||
.Apply(ReadHoldingRegistersResponseFrame.Parse)
|
.Apply(ReadHoldingRegistersResponseFrame.Parse)
|
||||||
.Apply(cmd.VerifyResponse);
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
return response.RegistersRead;
|
return new MbData(response.RegistersRead.RawData, readAddress, Endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override UInt16 WriteCoils(UInt16 writeAddress, IReadOnlyList<Boolean> coils)
|
public override UInt16 WriteCoils(UInt16 writeAddress, Booleans coils)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -88,16 +85,13 @@ public class ModbusRtuClient : ModbusClient
|
||||||
{
|
{
|
||||||
var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values);
|
var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values);
|
||||||
var crc = CalcCrc(cmd);
|
var crc = CalcCrc(cmd);
|
||||||
var nToRead = cmd.ExpectedResponseSize + CrcSize;
|
|
||||||
|
|
||||||
// TX
|
// TX
|
||||||
cmd.Data.Concat(crc).Apply(Connection.Transmit);
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
||||||
|
|
||||||
|
|
||||||
// RX
|
// RX
|
||||||
var response = nToRead
|
var response = Channel
|
||||||
.ConvertTo<UInt16>()
|
.Read(cmd.ExpectedResponseSize + CrcSize)
|
||||||
.Apply(Connection.Receive)
|
|
||||||
.ToArraySegment()
|
.ToArraySegment()
|
||||||
.Apply(AssertCrc)
|
.Apply(AssertCrc)
|
||||||
.SkipLast(CrcSize)
|
.SkipLast(CrcSize)
|
||||||
|
@ -107,7 +101,7 @@ public class ModbusRtuClient : ModbusClient
|
||||||
return response.NbWritten;
|
return response.NbWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override UInt16s ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite)
|
public override MbData ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite)
|
||||||
{
|
{
|
||||||
var cmd = new ReadWriteRegistersCommandFrame(SlaveId,
|
var cmd = new ReadWriteRegistersCommandFrame(SlaveId,
|
||||||
readAddress,
|
readAddress,
|
||||||
|
@ -117,19 +111,18 @@ public class ModbusRtuClient : ModbusClient
|
||||||
var crc = CalcCrc(cmd);
|
var crc = CalcCrc(cmd);
|
||||||
|
|
||||||
// TX
|
// TX
|
||||||
cmd.Data.Concat(crc).Apply(Connection.Transmit);
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
||||||
|
|
||||||
// RX
|
// RX
|
||||||
var nToRead = cmd.ExpectedResponseSize + CrcSize;
|
var nToRead = cmd.ExpectedResponseSize + CrcSize;
|
||||||
var response = nToRead
|
var response = nToRead
|
||||||
.ConvertTo<UInt16>()
|
.Apply(Channel.Read)
|
||||||
.Apply(Connection.Receive)
|
|
||||||
.ToArraySegment()
|
.ToArraySegment()
|
||||||
.SkipLast(CrcSize)
|
.SkipLast(CrcSize)
|
||||||
.Apply(ReadWriteRegistersResponseFrame.Parse)
|
.Apply(ReadWriteRegistersResponseFrame.Parse)
|
||||||
.Apply(cmd.VerifyResponse);
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
return response.RegistersRead;
|
return new MbData(response.RegistersRead.RawData, readAddress, Endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArraySegment<Byte> AssertCrc(ArraySegment<Byte> data)
|
public static ArraySegment<Byte> AssertCrc(ArraySegment<Byte> data)
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
|
||||||
|
|
||||||
public class ModbusSlave<S, C> where S : notnull, new() where C : notnull
|
|
||||||
{
|
|
||||||
private readonly ModbusClient _ModbusClient;
|
|
||||||
|
|
||||||
public ModbusSlave(ModbusClient modbusClient) => _ModbusClient = modbusClient;
|
|
||||||
|
|
||||||
public S? Read()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return ReadInto(new S());
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return default; // TODO :stdErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Exception? Write(C controlRecord)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
WriteHoldingRegisters(controlRecord);
|
|
||||||
WriteCoils(controlRecord);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_ModbusClient.Connection.Close();
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public S ReadInto(S statusRecord)
|
|
||||||
{
|
|
||||||
Int32 holdingRegisters;
|
|
||||||
Int32 coils;
|
|
||||||
Int32 inputRegisters;
|
|
||||||
Int32 discreteInputs;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
holdingRegisters = ReadHoldingRegisters(statusRecord).Count(); // force enumeration!
|
|
||||||
inputRegisters = ReadInputRegisters(statusRecord).Count();
|
|
||||||
discreteInputs = ReadDiscreteInputs(statusRecord).Count();
|
|
||||||
coils = ReadCoils(statusRecord).Count();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_ModbusClient.Connection.Close();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var nUpdated = holdingRegisters
|
|
||||||
+ inputRegisters
|
|
||||||
+ discreteInputs
|
|
||||||
+ coils;
|
|
||||||
|
|
||||||
if (nUpdated == 0)
|
|
||||||
throw new ArgumentException(nameof(statusRecord));
|
|
||||||
|
|
||||||
return statusRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns an enumerable of read addresses
|
|
||||||
private IEnumerable<UInt16> ReadDiscreteInputs(Object statusRecord)
|
|
||||||
{
|
|
||||||
return from batch in statusRecord.GetModbusBooleanBatches<DiscreteInputAttribute>()
|
|
||||||
let received = _ModbusClient.ReadDiscreteInputs(batch[0].Address, (UInt16)batch.Length)
|
|
||||||
from address in batch.SetStatusRecordMembersFromRawModbusValues(received)
|
|
||||||
select address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns an enumerable of read addresses
|
|
||||||
private IEnumerable<UInt16> ReadCoils(Object statusRecord)
|
|
||||||
{
|
|
||||||
return from batch in statusRecord.GetModbusBooleanBatches<CoilAttribute>()
|
|
||||||
let received = _ModbusClient.ReadCoils(batch[0].Address, (UInt16)batch.Length)
|
|
||||||
from address in batch.SetStatusRecordMembersFromRawModbusValues(received)
|
|
||||||
select address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns an enumerable of read addresses
|
|
||||||
private IEnumerable<UInt16> ReadInputRegisters(Object statusRecord)
|
|
||||||
{
|
|
||||||
return from batch in statusRecord.GetModbusRegisterBatches<InputRegisterAttribute>()
|
|
||||||
let received = _ModbusClient.ReadInputRegisters(batch[0].Address, (UInt16)batch.Length)
|
|
||||||
from address in batch.SetStatusRecordMembersFromRawModbusValues(received)
|
|
||||||
select address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns an enumerable of read addresses
|
|
||||||
private IEnumerable<UInt16> ReadHoldingRegisters(Object statusRecord)
|
|
||||||
{
|
|
||||||
return from batch in statusRecord.GetModbusRegisterBatches<HoldingRegisterAttribute>()
|
|
||||||
let received = _ModbusClient.ReadHoldingRegisters(batch[0].Address, (UInt16)batch.Length)
|
|
||||||
from address in batch.SetStatusRecordMembersFromRawModbusValues(received)
|
|
||||||
select address;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Int32 WriteCoils(C controlRecord)
|
|
||||||
{
|
|
||||||
var nBatches = 0;
|
|
||||||
|
|
||||||
foreach (var batch in controlRecord.GetModbusBooleanBatches<CoilAttribute>())
|
|
||||||
{
|
|
||||||
var values = batch.GetRawModbusValuesFromControlRecord();
|
|
||||||
_ModbusClient.WriteCoils(batch[0].Address, values);
|
|
||||||
nBatches++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nBatches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Int32 WriteHoldingRegisters(C controlRecord)
|
|
||||||
{
|
|
||||||
var nBatches = 0;
|
|
||||||
|
|
||||||
foreach (var batch in controlRecord.GetModbusRegisterBatches<HoldingRegisterAttribute>())
|
|
||||||
{
|
|
||||||
var values = batch.GetRawModbusValuesFromControlRecord();
|
|
||||||
_ModbusClient.WriteRegisters(batch[0].Address, values);
|
|
||||||
nBatches++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nBatches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ModbusSlave<T> : ModbusSlave<T, T> where T : notnull, new()
|
|
||||||
{
|
|
||||||
public ModbusSlave(ModbusClient modbusClient) : base(modbusClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ModbusSlave
|
|
||||||
{
|
|
||||||
public static ModbusSlave<T> Slave<T>(this ModbusClient modbusClient) where T : notnull, new()
|
|
||||||
{
|
|
||||||
return new ModbusSlave<T>(modbusClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ModbusSlave<S,C> Slave<S,C>(this ModbusClient modbusClient) where S : notnull, new() where C : notnull
|
|
||||||
{
|
|
||||||
return new ModbusSlave<S, C>(modbusClient);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Connections;
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Tcp;
|
using InnovEnergy.Lib.Protocols.Modbus.Tcp;
|
||||||
|
@ -8,8 +9,8 @@ using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
|
|
||||||
using UInt16s = IReadOnlyList<UInt16>;
|
using UInt16s = IReadOnlyCollection<UInt16>;
|
||||||
|
using Booleans = IReadOnlyCollection<Boolean>;
|
||||||
|
|
||||||
public class ModbusTcpClient : ModbusClient
|
public class ModbusTcpClient : ModbusClient
|
||||||
{
|
{
|
||||||
|
@ -20,11 +21,11 @@ public class ModbusTcpClient : ModbusClient
|
||||||
private UInt16 NextId() => unchecked(++_Id);
|
private UInt16 NextId() => unchecked(++_Id);
|
||||||
|
|
||||||
|
|
||||||
public ModbusTcpClient(ModbusConnection connection, Byte slaveId) : base(connection, slaveId)
|
public ModbusTcpClient(Channel channel, Byte slaveId) : base(channel, slaveId)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<Boolean> ReadCoils(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadCoils(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
var id = NextId(); // TODO: check response id
|
var id = NextId(); // TODO: check response id
|
||||||
|
|
||||||
|
@ -32,19 +33,22 @@ public class ModbusTcpClient : ModbusClient
|
||||||
var hdr = new MbapHeader(id, cmd.Data.Count);
|
var hdr = new MbapHeader(id, cmd.Data.Count);
|
||||||
var frm = new ModbusTcpFrame(hdr, cmd);
|
var frm = new ModbusTcpFrame(hdr, cmd);
|
||||||
|
|
||||||
Connection.Transmit(frm.Data);
|
Channel.Write(frm.Data);
|
||||||
|
|
||||||
var hData = Connection.Receive(MbapHeader.Size).ToArray(); // TODO: optimize .ToArray() ?
|
var hData = Channel.Read(MbapHeader.Size).ToArray();
|
||||||
var rxHdr = new MbapHeader(hData);
|
var rxHdr = new MbapHeader(hData);
|
||||||
|
|
||||||
var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); // TODO: optimize .ToArray() ?
|
var rxFrm = Channel
|
||||||
var rxFrm = ReadCoilsResponseFrame.Parse(fData);
|
.Read(rxHdr.FrameLength)
|
||||||
|
.ToArray() // TODO: optimize .ToArray() ?
|
||||||
|
.Apply(ReadCoilsResponseFrame.Parse)
|
||||||
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
return cmd.VerifyResponse(rxFrm).Inputs;
|
return new MbData(rxFrm.Coils.RawData, readAddress, Endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override IReadOnlyList<Boolean> ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
var id = NextId(); // TODO: check response id
|
var id = NextId(); // TODO: check response id
|
||||||
|
|
||||||
|
@ -52,18 +56,21 @@ public class ModbusTcpClient : ModbusClient
|
||||||
var hdr = new MbapHeader(id, cmd.Data.Count);
|
var hdr = new MbapHeader(id, cmd.Data.Count);
|
||||||
var frm = new ModbusTcpFrame(hdr, cmd);
|
var frm = new ModbusTcpFrame(hdr, cmd);
|
||||||
|
|
||||||
Connection.Transmit(frm.Data);
|
Channel.Write(frm.Data);
|
||||||
|
|
||||||
var hData = Connection.Receive(MbapHeader.Size).ToArray(); // TODO: optimize .ToArray() ?
|
var hData = Channel.Read(MbapHeader.Size).ToArray(); // TODO: optimize .ToArray() ?
|
||||||
var rxHdr = new MbapHeader(hData);
|
var rxHdr = new MbapHeader(hData);
|
||||||
|
|
||||||
var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); // TODO: optimize .ToArray() ?
|
var rxFrm = Channel
|
||||||
var rxFrm = ReadDiscreteInputsResponseFrame.Parse(fData);
|
.Read(rxHdr.FrameLength)
|
||||||
|
.ToArray() // TODO: optimize .ToArray() ?
|
||||||
|
.Apply(ReadDiscreteInputsResponseFrame.Parse)
|
||||||
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
return cmd.VerifyResponse(rxFrm).Inputs;
|
return new MbData(rxFrm.Inputs.RawData, readAddress, Endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<UInt16> ReadInputRegisters(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadInputRegisters(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
var id = NextId(); // TODO: check response id
|
var id = NextId(); // TODO: check response id
|
||||||
|
|
||||||
|
@ -71,56 +78,62 @@ public class ModbusTcpClient : ModbusClient
|
||||||
var hdr = new MbapHeader(id, cmd.Data.Count);
|
var hdr = new MbapHeader(id, cmd.Data.Count);
|
||||||
var frm = new ModbusTcpFrame(hdr, cmd);
|
var frm = new ModbusTcpFrame(hdr, cmd);
|
||||||
|
|
||||||
Connection.Transmit(frm.Data);
|
Channel.Write(frm.Data);
|
||||||
|
|
||||||
var hData = Connection.Receive(MbapHeader.Size).ToArray();
|
var hData = Channel.Read(MbapHeader.Size).ToArray();
|
||||||
var rxHdr = new MbapHeader(hData);
|
var rxHdr = new MbapHeader(hData);
|
||||||
|
|
||||||
var fData = Connection.Receive(rxHdr.FrameLength).ToArray();
|
var rxFrm = Channel
|
||||||
var rxFrm = ReadInputRegistersResponseFrame.Parse(fData);
|
.Read(rxHdr.FrameLength)
|
||||||
|
.ToArray()
|
||||||
|
.Apply(ReadInputRegistersResponseFrame.Parse)
|
||||||
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
var verified = cmd.VerifyResponse(rxFrm);
|
return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian);
|
||||||
|
|
||||||
return verified.RegistersRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override IReadOnlyList<UInt16> ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues)
|
public override MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues)
|
||||||
{
|
{
|
||||||
var id = NextId(); // TODO: check response id
|
var id = NextId(); // TODO: check response id
|
||||||
|
|
||||||
var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues);
|
var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues);
|
||||||
var hdr = new MbapHeader(id, cmd.Data.Count);
|
var hdr = new MbapHeader(id, cmd.Data.Count);
|
||||||
var frm = new ModbusTcpFrame(hdr, cmd);
|
var frm = new ModbusTcpFrame(hdr, cmd);
|
||||||
|
|
||||||
Connection.Transmit(frm.Data);
|
Channel.Write(frm.Data);
|
||||||
|
|
||||||
var hData = Connection.Receive(MbapHeader.Size).ToArray(MbapHeader.Size);
|
var hData = Channel.Read(MbapHeader.Size).ToArray();
|
||||||
var rxHdr = new MbapHeader(hData);
|
var rxHdr = new MbapHeader(hData);
|
||||||
|
|
||||||
var fData = Connection.Receive(rxHdr.FrameLength).ToArray(rxHdr.FrameLength);
|
var rxFrm = Channel
|
||||||
var rxFrm = ReadHoldingRegistersResponseFrame.Parse(fData);
|
.Read(rxHdr.FrameLength)
|
||||||
|
.ToArray()
|
||||||
|
.Apply(ReadHoldingRegistersResponseFrame.Parse)
|
||||||
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
var verified = cmd.VerifyResponse(rxFrm);
|
return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian);
|
||||||
|
|
||||||
return verified.RegistersRead; // TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override UInt16 WriteCoils(UInt16 writeAddress, IReadOnlyList<Boolean> coils)
|
public override UInt16 WriteCoils(UInt16 writeAddress, Booleans coils)
|
||||||
{
|
{
|
||||||
var id = NextId(); // TODO: check response id
|
var id = NextId(); // TODO: check response id
|
||||||
var cmd = new WriteCoilsCommandFrame(SlaveId, writeAddress, coils);
|
var cmd = new WriteCoilsCommandFrame(SlaveId, writeAddress, coils);
|
||||||
var hdr = new MbapHeader(id, cmd.Data.Count);
|
var hdr = new MbapHeader(id, cmd.Data.Count);
|
||||||
var frm = new ModbusTcpFrame(hdr, cmd);
|
var frm = new ModbusTcpFrame(hdr, cmd);
|
||||||
|
|
||||||
Connection.Transmit(frm.Data);
|
Channel.Write(frm.Data);
|
||||||
|
|
||||||
var hData = Connection.Receive(MbapHeader.Size).ToArray();
|
var hData = Channel.Read(MbapHeader.Size).ToArray();
|
||||||
var rxHdr = new MbapHeader(hData);
|
var rxHdr = new MbapHeader(hData);
|
||||||
|
|
||||||
var fData = Connection.Receive(rxHdr.FrameLength).ToArray();
|
var rxFrm = Channel
|
||||||
var rxFrm = WriteCoilsResponseFrame.Parse(fData);
|
.Read(rxHdr.FrameLength)
|
||||||
|
.ToArray()
|
||||||
|
.Apply(WriteCoilsResponseFrame.Parse)
|
||||||
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
return cmd.VerifyResponse(rxFrm).NbWritten;
|
return rxFrm.NbWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,20 +144,21 @@ public class ModbusTcpClient : ModbusClient
|
||||||
var hdr = new MbapHeader(id, cmd.Data.Count);
|
var hdr = new MbapHeader(id, cmd.Data.Count);
|
||||||
var frm = new ModbusTcpFrame(hdr, cmd);
|
var frm = new ModbusTcpFrame(hdr, cmd);
|
||||||
|
|
||||||
Connection.Transmit(frm.Data);
|
Channel.Write(frm.Data);
|
||||||
|
|
||||||
var hData = Connection.Receive(MbapHeader.Size).ToArray();
|
var hData = Channel.Read(MbapHeader.Size).ToArray();
|
||||||
var rxHdr = new MbapHeader(hData);
|
var rxHdr = new MbapHeader(hData);
|
||||||
|
|
||||||
var fData = Connection.Receive(rxHdr.FrameLength).ToArray();
|
var rxFrm = Channel
|
||||||
var rxFrm = WriteRegistersResponseFrame.Parse(fData);
|
.Read(rxHdr.FrameLength)
|
||||||
|
.ToArray()
|
||||||
|
.Apply(WriteRegistersResponseFrame.Parse)
|
||||||
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
var verified = cmd.VerifyResponse(rxFrm);
|
return rxFrm.NbWritten;
|
||||||
|
|
||||||
return verified.NbWritten;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<UInt16> ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite)
|
public override MbData ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite)
|
||||||
{
|
{
|
||||||
var id = NextId(); // TODO: check response id
|
var id = NextId(); // TODO: check response id
|
||||||
|
|
||||||
|
@ -157,17 +171,17 @@ public class ModbusTcpClient : ModbusClient
|
||||||
var hdr = new MbapHeader(id, cmd.Data.Count);
|
var hdr = new MbapHeader(id, cmd.Data.Count);
|
||||||
var frm = new ModbusTcpFrame(hdr, cmd);
|
var frm = new ModbusTcpFrame(hdr, cmd);
|
||||||
|
|
||||||
Connection.Transmit(frm.Data);
|
Channel.Write(frm.Data);
|
||||||
|
|
||||||
var hData = Enumerable.ToArray(Connection.Receive(MbapHeader.Size));
|
var hData = Enumerable.ToArray(Channel.Read(MbapHeader.Size));
|
||||||
var rxHdr = new MbapHeader(hData);
|
var rxHdr = new MbapHeader(hData);
|
||||||
|
|
||||||
var fData = Enumerable.ToArray(Connection.Receive(rxHdr.FrameLength));
|
var fData = Enumerable.ToArray(Channel.Read(rxHdr.FrameLength));
|
||||||
var rxFrm = ReadWriteRegistersResponseFrame.Parse(fData);
|
var rxFrm = fData
|
||||||
|
.Apply(ReadWriteRegistersResponseFrame.Parse)
|
||||||
|
.Apply(cmd.VerifyResponse);
|
||||||
|
|
||||||
var verified = cmd.VerifyResponse(rxFrm);
|
return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian);
|
||||||
|
|
||||||
return verified.RegistersRead; // TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Connections;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class ModbusConnection
|
|
||||||
{
|
|
||||||
public abstract IReadOnlyList<Byte> Receive(UInt16 nBytes);
|
|
||||||
public abstract void Transmit(IEnumerable<Byte> bytes);
|
|
||||||
|
|
||||||
public abstract void Open(); // calls to opening an already open connection must be ignored
|
|
||||||
public abstract void Close(); // calls to closing an already closed connection must be ignored, must not throw
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
using System.IO.Ports;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Connections;
|
|
||||||
|
|
||||||
public class ModbusSerialConnection : ModbusConnection
|
|
||||||
{
|
|
||||||
private String PortName { get; }
|
|
||||||
private Int32 BaudRate { get; }
|
|
||||||
private Parity Parity { get; }
|
|
||||||
private Int32 DataBits { get; }
|
|
||||||
private StopBits StopBits { get; }
|
|
||||||
private Int32 TimeoutMs { get; }
|
|
||||||
|
|
||||||
private Byte[] Buffer { get; } = new Byte[1024];
|
|
||||||
private SerialPort? _SerialPort;
|
|
||||||
|
|
||||||
public ModbusSerialConnection(String portName, Int32 baudRate, Parity parity, Int32 dataBits, StopBits stopBits, TimeSpan timeout)
|
|
||||||
{
|
|
||||||
PortName = portName;
|
|
||||||
BaudRate = baudRate;
|
|
||||||
Parity = parity;
|
|
||||||
DataBits = dataBits;
|
|
||||||
StopBits = stopBits;
|
|
||||||
TimeoutMs = (Int32) timeout.TotalMilliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override IReadOnlyList<Byte> Receive(UInt16 bytesToRead)
|
|
||||||
{
|
|
||||||
var bytesReceived = 0;
|
|
||||||
var serialPort = SerialPort();
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var received = serialPort.Read(Buffer, bytesReceived, Buffer.Length - bytesReceived);
|
|
||||||
if (received < 0)
|
|
||||||
throw new NotConnectedException("Serial Connection has been closed");
|
|
||||||
|
|
||||||
bytesReceived += received;
|
|
||||||
}
|
|
||||||
while (bytesReceived < bytesToRead);
|
|
||||||
|
|
||||||
return new ArraySegment<Byte>(Buffer, 0, bytesToRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Transmit(IEnumerable<Byte> data)
|
|
||||||
{
|
|
||||||
var array = data.ToArray();
|
|
||||||
SerialPort().Write(array, 0, array.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Open() => SerialPort();
|
|
||||||
|
|
||||||
private SerialPort SerialPort()
|
|
||||||
{
|
|
||||||
if (_SerialPort == null)
|
|
||||||
{
|
|
||||||
_SerialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits) { ReadTimeout = TimeoutMs, WriteTimeout = TimeoutMs};
|
|
||||||
_SerialPort.Open();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _SerialPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
if (_SerialPort != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_SerialPort.Close();
|
|
||||||
_SerialPort?.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_SerialPort = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
using System.Net.Sockets;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Connections;
|
|
||||||
|
|
||||||
public class ModbusTcpConnection : ModbusConnection
|
|
||||||
{
|
|
||||||
private String Hostname { get; }
|
|
||||||
private Int32 Port { get; }
|
|
||||||
|
|
||||||
private Byte[] Buffer { get; } = new Byte[1024];
|
|
||||||
private TcpClient? _TcpClient;
|
|
||||||
|
|
||||||
public ModbusTcpConnection(String hostname, Int32 port = 502)
|
|
||||||
{
|
|
||||||
Hostname = hostname;
|
|
||||||
Port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IReadOnlyList<Byte> Receive(UInt16 bytesToRead)
|
|
||||||
{
|
|
||||||
var bytesReceived = 0;
|
|
||||||
var stream = TcpClient().GetStream();
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var maxBytes = Math.Min(bytesToRead, Buffer.Length) - bytesReceived;
|
|
||||||
var read = stream.Read(Buffer, bytesReceived, maxBytes);
|
|
||||||
if (read <= 0)
|
|
||||||
throw new NotConnectedException("The TCP Connection was closed");
|
|
||||||
|
|
||||||
bytesReceived += read;
|
|
||||||
}
|
|
||||||
while (bytesReceived < bytesToRead);
|
|
||||||
|
|
||||||
return new ArraySegment<Byte>(Buffer, 0, bytesToRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Transmit(IEnumerable<Byte> data)
|
|
||||||
{
|
|
||||||
var array = data.ToArray();
|
|
||||||
TcpClient().GetStream().Write(array, 0, array.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Open() => TcpClient();
|
|
||||||
|
|
||||||
private TcpClient TcpClient()
|
|
||||||
{
|
|
||||||
return _TcpClient ??= new TcpClient(Hostname, Port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_TcpClient?.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
_TcpClient = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO.Ports;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using CliWrap;
|
|
||||||
using InnovEnergy.Lib.Utils;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Connections;
|
|
||||||
|
|
||||||
public class RemoteSerialConnection : ModbusConnection
|
|
||||||
{
|
|
||||||
private readonly Command _Command;
|
|
||||||
private ObservableProcess? _ObservableProcess;
|
|
||||||
private Func<Int32, Task<IReadOnlyList<Byte>>>? _Read;
|
|
||||||
|
|
||||||
|
|
||||||
public RemoteSerialConnection(SshHost host,
|
|
||||||
String tty,
|
|
||||||
Int32 baudRate,
|
|
||||||
Parity parity,
|
|
||||||
Int32 stopBits,
|
|
||||||
Int32 dataBits)
|
|
||||||
{
|
|
||||||
tty = tty.EnsureStartsWith("/dev/");
|
|
||||||
|
|
||||||
var configure = ConfigureTty(tty, baudRate, parity, stopBits, dataBits);
|
|
||||||
|
|
||||||
// https://unix.stackexchange.com/questions/19604/all-about-ssh-proxycommand
|
|
||||||
|
|
||||||
var call = $"{configure}; " +
|
|
||||||
$"exec 3<>{tty}; " +
|
|
||||||
$"cat <&3 & cat >&3; " +
|
|
||||||
$"kill $!";
|
|
||||||
|
|
||||||
_Command = host
|
|
||||||
.Command
|
|
||||||
.AppendArgument(call);
|
|
||||||
|
|
||||||
_Command.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
|
||||||
private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits)
|
|
||||||
{
|
|
||||||
var oParity = parity switch
|
|
||||||
{
|
|
||||||
Parity.Even => "parenb -parodd",
|
|
||||||
Parity.Odd => "parenb parodd",
|
|
||||||
Parity.None => "-parenb",
|
|
||||||
_ => throw new NotImplementedException()
|
|
||||||
};
|
|
||||||
|
|
||||||
var oStopBits = stopBits switch
|
|
||||||
{
|
|
||||||
1 => "-cstopb",
|
|
||||||
2 => "cstopb",
|
|
||||||
_ => throw new NotImplementedException()
|
|
||||||
};
|
|
||||||
|
|
||||||
var oDataBits = "cs" + dataBits;
|
|
||||||
|
|
||||||
return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IReadOnlyList<Byte> Receive(UInt16 bytesToRead)
|
|
||||||
{
|
|
||||||
Open();
|
|
||||||
|
|
||||||
return _Read!(bytesToRead).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Transmit(IEnumerable<Byte> bytes)
|
|
||||||
{
|
|
||||||
Open();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ObservableProcess!.StdIn.OnNext(bytes.ToArray());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override void Open()
|
|
||||||
{
|
|
||||||
if (_ObservableProcess is not null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_ObservableProcess = new ObservableProcess(_Command);
|
|
||||||
|
|
||||||
_ObservableProcess.Start();
|
|
||||||
|
|
||||||
_Read = _ObservableProcess
|
|
||||||
.StdOut
|
|
||||||
.Select(b => b.ToArray())
|
|
||||||
.SelectMany(t => t)
|
|
||||||
.PushToPull();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
// if (_ObservableProcess is null)
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/6960520/when-to-dispose-cancellationtokensource
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
|
@ -9,7 +9,6 @@
|
||||||
<IsTrimmable>false</IsTrimmable>
|
<IsTrimmable>false</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../Utils/Utils.csproj" />
|
<ProjectReference Include="../../Utils/Utils.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -18,7 +17,6 @@
|
||||||
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
|
||||||
|
public enum Endian
|
||||||
|
{
|
||||||
|
Little,
|
||||||
|
Big
|
||||||
|
}
|
|
@ -3,11 +3,15 @@ using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
|
||||||
public readonly struct MbBits : IReadOnlyList<Boolean>
|
public readonly struct MbBits : IReadOnlyList<Boolean>, IReadOnlyList<IConvertible>
|
||||||
{
|
{
|
||||||
private readonly ArraySegment<Byte> _Data;
|
public readonly ArraySegment<Byte> RawData;
|
||||||
|
|
||||||
internal MbBits(ArraySegment<Byte> data, Byte startIndex) : this(data, startIndex, CountBits(data, startIndex))
|
internal MbBits(Int32 bitCount) : this(new Byte[NbRegistersFromNbBits(bitCount)])
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MbBits(ArraySegment<Byte> data, Byte startIndex = 0) : this(data, startIndex, CountBits(data, startIndex))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,23 +28,32 @@ public readonly struct MbBits : IReadOnlyList<Boolean>
|
||||||
|
|
||||||
internal MbBits(Byte[] data, Byte startIndex, UInt16 bitCount)
|
internal MbBits(Byte[] data, Byte startIndex, UInt16 bitCount)
|
||||||
{
|
{
|
||||||
_Data = new ArraySegment<Byte>(data, startIndex, Math.Ceiling(bitCount / 8.0).ConvertTo<UInt16>());
|
RawData = new ArraySegment<Byte>(data, startIndex, NbRegistersFromNbBits(bitCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Set(IReadOnlyList<Boolean> values)
|
private static UInt16 NbRegistersFromNbBits(Int32 bitCount)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < values.Count; i++)
|
return Math.Ceiling(bitCount / 8.0).ConvertTo<UInt16>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Set(IReadOnlyCollection<Boolean> values)
|
||||||
{
|
{
|
||||||
SetBit((UInt16)i, values[i]);
|
var i = 0;
|
||||||
}
|
foreach (var value in values)
|
||||||
|
SetBit((UInt16)i++, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerator<IConvertible> IEnumerable<IConvertible>.GetEnumerator() => GetBits()
|
||||||
|
.OfType<IConvertible>()
|
||||||
|
.GetEnumerator();
|
||||||
|
|
||||||
public IEnumerator<Boolean> GetEnumerator() => Enumerable.Range(0, Count).Select(GetBit).GetEnumerator();
|
public IEnumerator<Boolean> GetEnumerator() => GetBits().GetEnumerator();
|
||||||
|
|
||||||
|
private IEnumerable<Boolean> GetBits() => Enumerable.Range(0, Count).Select(GetBit);
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
public Int32 Count => _Data.Count * 8;
|
public Int32 Count => RawData.Count * 8;
|
||||||
|
|
||||||
|
|
||||||
private Boolean GetBit(Int32 index)
|
private Boolean GetBit(Int32 index)
|
||||||
|
@ -48,10 +61,10 @@ public readonly struct MbBits : IReadOnlyList<Boolean>
|
||||||
var byteIndex = index / 8;
|
var byteIndex = index / 8;
|
||||||
var bitIndex = index % 8;
|
var bitIndex = index % 8;
|
||||||
|
|
||||||
return (_Data[byteIndex] & (1 << bitIndex)) != 0;
|
return (RawData[byteIndex] & (1 << bitIndex)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetBit(UInt16 index, Boolean value)
|
internal void SetBit(UInt16 index, Boolean value)
|
||||||
{
|
{
|
||||||
var byteIndex = index / 8;
|
var byteIndex = index / 8;
|
||||||
var bitIndex = index % 8;
|
var bitIndex = index % 8;
|
||||||
|
@ -59,12 +72,13 @@ public readonly struct MbBits : IReadOnlyList<Boolean>
|
||||||
var mask = 1 << bitIndex; // !! needs the u suffix!
|
var mask = 1 << bitIndex; // !! needs the u suffix!
|
||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
_Data[byteIndex] |= (Byte)mask;
|
RawData[byteIndex] |= (Byte)mask;
|
||||||
else
|
else
|
||||||
_Data[byteIndex] &= (Byte)~mask;
|
RawData[byteIndex] &= (Byte)~mask;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean this[Int32 index] => GetBit(index);
|
public Boolean this[Int32 index] => GetBit(index);
|
||||||
|
IConvertible IReadOnlyList<IConvertible>.this[Int32 index] => this[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
|
||||||
|
using Float32 = Single;
|
||||||
|
using Float64 = Double;
|
||||||
|
|
||||||
|
// switch exhaustion
|
||||||
|
#pragma warning disable CS8524
|
||||||
|
|
||||||
|
public struct MbData
|
||||||
|
{
|
||||||
|
|
||||||
|
//TODO: use System.Buffers.Binary.BinaryPrimitives
|
||||||
|
// see Decimal class
|
||||||
|
|
||||||
|
private readonly ArraySegment<Byte> _Data;
|
||||||
|
private readonly Endian _Endian;
|
||||||
|
private readonly UInt16 _StartAddress;
|
||||||
|
|
||||||
|
public static MbData Registers(UInt16 startAddress, UInt16 nRegisters)
|
||||||
|
{
|
||||||
|
if (nRegisters > Constants.MaxRegs)
|
||||||
|
throw new ArgumentException(nameof(nRegisters));
|
||||||
|
|
||||||
|
var nBytes = nRegisters * 2;
|
||||||
|
var data = new Byte[nBytes];
|
||||||
|
return new MbData(data, startAddress, Endian.Big); // endian has no influence on coils
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MbData Coils(UInt16 startAddress, UInt16 nCoils)
|
||||||
|
{
|
||||||
|
if (nCoils > Constants.MaxCoils)
|
||||||
|
throw new ArgumentException(nameof(nCoils));
|
||||||
|
|
||||||
|
var nBytes = Math.Ceiling(nCoils / 8.0).ConvertTo<UInt16>();
|
||||||
|
var data = new Byte[nBytes];
|
||||||
|
return new MbData(data, startAddress, Endian.Big); // endian has no influence on coils
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MbData(ArraySegment<Byte> data, UInt16 startAddress, Endian endian)
|
||||||
|
{
|
||||||
|
if (endian != Endian.Big && endian != Endian.Little)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(endian), endian, null);
|
||||||
|
|
||||||
|
_Endian = endian;
|
||||||
|
_StartAddress = startAddress;
|
||||||
|
_Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Coils
|
||||||
|
|
||||||
|
public IReadOnlyList<Boolean> GetCoils()
|
||||||
|
{
|
||||||
|
IEnumerable<Boolean> GetBits(Byte b) => 1.Unfold(m => m << 1)
|
||||||
|
.Take(8)
|
||||||
|
.Select(m => (b & m) > 0);
|
||||||
|
|
||||||
|
return _Data.SelectMany(GetBits).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean GetInput(UInt16 address) => GetCoil(address);
|
||||||
|
|
||||||
|
public Boolean GetCoil(UInt16 address)
|
||||||
|
{
|
||||||
|
var index = address - _StartAddress;
|
||||||
|
|
||||||
|
var byteIndex = index / 8;
|
||||||
|
var bitIndex = index % 8;
|
||||||
|
|
||||||
|
return (_Data[byteIndex] & (1 << bitIndex)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCoil(UInt16 address, Boolean value)
|
||||||
|
{
|
||||||
|
var index = address - _StartAddress;
|
||||||
|
|
||||||
|
var byteIndex = index / 8;
|
||||||
|
var bitIndex = index % 8;
|
||||||
|
|
||||||
|
_Data[byteIndex] = (Byte)(_Data[byteIndex] | (1 << bitIndex)) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Coils
|
||||||
|
|
||||||
|
#region 16Bit
|
||||||
|
|
||||||
|
public UInt16 GetUInt16(UInt16 address)
|
||||||
|
{
|
||||||
|
return GetRegister((address - _StartAddress) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Int16 GetInt16(UInt16 address)
|
||||||
|
{
|
||||||
|
return (Int16) GetUInt16(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UInt16 SetUInt16(UInt16 address, UInt16 value)
|
||||||
|
{
|
||||||
|
var i = (address - _StartAddress) * 2;
|
||||||
|
|
||||||
|
_Data[i ] = (Byte)(value >> 8);
|
||||||
|
_Data[i + 1] = (Byte)(value & 0xFF);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInt16(UInt16 address, Int16 value)
|
||||||
|
{
|
||||||
|
SetUInt16(address, (UInt16)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion 16Bit
|
||||||
|
|
||||||
|
#region 32Bit
|
||||||
|
|
||||||
|
public UInt32 GetUInt32(UInt16 address)
|
||||||
|
{
|
||||||
|
var hi = (UInt32) GetUInt16(address);
|
||||||
|
var lo = (UInt32) GetUInt16(++address);
|
||||||
|
|
||||||
|
return _Endian switch
|
||||||
|
{
|
||||||
|
Endian.Big => hi << 16 | lo,
|
||||||
|
Endian.Little => lo << 16 | hi,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Int32 GetInt32(UInt16 address) => (Int32)GetUInt32(address);
|
||||||
|
|
||||||
|
public void SetUInt32(UInt16 address, UInt32 value)
|
||||||
|
{
|
||||||
|
var hi = (UInt16)(value >> 16);
|
||||||
|
var lo = (UInt16)(value & 0xFF_FF);
|
||||||
|
|
||||||
|
if (_Endian == Endian.Big)
|
||||||
|
{
|
||||||
|
SetUInt16(address, hi);
|
||||||
|
SetUInt16(++address, lo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetUInt16(address, lo);
|
||||||
|
SetUInt16(++address, hi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInt32(UInt16 address, Int64 value) => SetUInt32(address, (UInt32)value);
|
||||||
|
|
||||||
|
public Float32 GetFloat32(UInt16 address)
|
||||||
|
{
|
||||||
|
return address
|
||||||
|
.Apply(GetUInt32)
|
||||||
|
.Apply(BitConverter.UInt32BitsToSingle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFloat32(UInt16 address, Float32 value)
|
||||||
|
{
|
||||||
|
SetUInt32(address, BitConverter.SingleToUInt32Bits(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion 32Bit
|
||||||
|
|
||||||
|
#region 64Bit
|
||||||
|
|
||||||
|
public UInt64 GetUInt64(UInt16 address)
|
||||||
|
{
|
||||||
|
var hi = (UInt64) GetUInt32(address);
|
||||||
|
var lo = (UInt64) GetUInt32(++address);
|
||||||
|
|
||||||
|
return _Endian switch
|
||||||
|
{
|
||||||
|
Endian.Big => hi << 32 | lo,
|
||||||
|
Endian.Little => lo << 32 | hi,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Int64 GetInt64(UInt16 address) => (Int32)GetUInt64(address);
|
||||||
|
|
||||||
|
public void SetUInt64(UInt16 address, UInt64 value)
|
||||||
|
{
|
||||||
|
var hi = (UInt32)(value >> 32);
|
||||||
|
var lo = (UInt32)(value & 0xFF_FF_FF_FF);
|
||||||
|
|
||||||
|
if (_Endian == Endian.Big)
|
||||||
|
{
|
||||||
|
SetUInt32(address, hi);
|
||||||
|
SetUInt32((UInt16)(address + 2), lo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetUInt32(address, lo);
|
||||||
|
SetUInt32((UInt16)(address + 2), hi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInt64(UInt16 address, Int64 value) => SetUInt64(address, (UInt64)value);
|
||||||
|
|
||||||
|
public Float64 GetFloat64(UInt16 address)
|
||||||
|
{
|
||||||
|
return address
|
||||||
|
.Apply(GetUInt64)
|
||||||
|
.Apply(BitConverter.UInt64BitsToDouble);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFloat64(UInt16 address, Float64 value)
|
||||||
|
{
|
||||||
|
SetUInt64(address, BitConverter.DoubleToUInt64Bits(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion 64Bit
|
||||||
|
|
||||||
|
|
||||||
|
private UInt16 GetRegister(Int32 i)
|
||||||
|
{
|
||||||
|
var hi = _Data[i] << 8;
|
||||||
|
var lo = _Data[i + 1];
|
||||||
|
|
||||||
|
return (UInt16)(hi | lo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<UInt16> GetRegisters()
|
||||||
|
{
|
||||||
|
var nRegisters = _Data.Count / 2;
|
||||||
|
|
||||||
|
return Enumerable
|
||||||
|
.Range(0, nRegisters)
|
||||||
|
.Select(GetRegister)
|
||||||
|
.ToArray(nRegisters);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,90 +0,0 @@
|
||||||
using System.Collections;
|
|
||||||
using InnovEnergy.Lib.Utils;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
|
||||||
|
|
||||||
using Float32 = Single;
|
|
||||||
|
|
||||||
public struct MbRegisters : IReadOnlyList<UInt16>
|
|
||||||
{
|
|
||||||
private MbWords Words { get; }
|
|
||||||
private UInt16 StartRegister { get; }
|
|
||||||
|
|
||||||
public MbRegisters(MbWords words, UInt16 startRegister)
|
|
||||||
{
|
|
||||||
Words = words;
|
|
||||||
StartRegister = startRegister;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<UInt16> GetEnumerator() => Words.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
public Int32 Count => Words.Count;
|
|
||||||
|
|
||||||
public UInt16 this[Int32 index] => Words[index];
|
|
||||||
|
|
||||||
private Byte MapIndex(UInt16 index)
|
|
||||||
{
|
|
||||||
var i = index - StartRegister;
|
|
||||||
if (i is < Byte.MinValue or > Byte.MaxValue)
|
|
||||||
throw new IndexOutOfRangeException();
|
|
||||||
|
|
||||||
return (Byte)i;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt16 GetUInt16(UInt16 index)
|
|
||||||
{
|
|
||||||
return index
|
|
||||||
.Apply(MapIndex)
|
|
||||||
.Apply(Words.GetUInt16);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetUInt16(UInt16 index, UInt16 value)
|
|
||||||
{
|
|
||||||
Words.SetUInt16(MapIndex(index), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int16 GetInt16(UInt16 index)
|
|
||||||
{
|
|
||||||
return (Int16) GetUInt16(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetUInt16(UInt16 index, Int16 value)
|
|
||||||
{
|
|
||||||
SetUInt16(index, (UInt16)value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt32 GetUInt32(UInt16 index)
|
|
||||||
{
|
|
||||||
var i = MapIndex(index);
|
|
||||||
|
|
||||||
var hi = (UInt32) GetUInt16(i);
|
|
||||||
var lo = (UInt32) GetUInt16(++i);
|
|
||||||
|
|
||||||
return hi << 16 | lo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// public void SetUInt32(UInt32 index, UInt32 value)
|
|
||||||
|
|
||||||
public Int32 GetInt32(UInt16 index) => (Int32)GetUInt32(index);
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// public void SetInt32(Int32 index, Int32 value)
|
|
||||||
|
|
||||||
|
|
||||||
public Float32 GetFloat32(UInt16 index)
|
|
||||||
{
|
|
||||||
return index
|
|
||||||
.Apply(GetInt32)
|
|
||||||
.Apply(BitConverter.Int32BitsToSingle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// public void SetFloat32(Float32 index, Float32 value)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,9 +3,9 @@ using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
|
||||||
public readonly struct MbWords : IReadOnlyList<UInt16>
|
public readonly struct MbWords : IReadOnlyList<UInt16> , IReadOnlyList<IConvertible>
|
||||||
{
|
{
|
||||||
internal readonly ArraySegment<Byte> Data;
|
public readonly ArraySegment<Byte> RawData;
|
||||||
|
|
||||||
internal MbWords(ArraySegment<Byte> data, Byte startIndex = 0) :
|
internal MbWords(ArraySegment<Byte> data, Byte startIndex = 0) :
|
||||||
this(data, startIndex, CountWords(data, startIndex))
|
this(data, startIndex, CountWords(data, startIndex))
|
||||||
|
@ -13,7 +13,7 @@ public readonly struct MbWords : IReadOnlyList<UInt16>
|
||||||
|
|
||||||
internal MbWords(ArraySegment<Byte> data, Byte startIndex, UInt16 wordCount)
|
internal MbWords(ArraySegment<Byte> data, Byte startIndex, UInt16 wordCount)
|
||||||
{
|
{
|
||||||
Data = new ArraySegment<Byte>(data.Array!, startIndex + data.Offset, wordCount * 2);
|
RawData = new ArraySegment<Byte>(data.Array!, startIndex + data.Offset, wordCount * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt16 CountWords(ArraySegment<Byte> data, Byte startIndex)
|
private static UInt16 CountWords(ArraySegment<Byte> data, Byte startIndex)
|
||||||
|
@ -24,28 +24,40 @@ public readonly struct MbWords : IReadOnlyList<UInt16>
|
||||||
|
|
||||||
internal IReadOnlyCollection<UInt16> Set(IReadOnlyCollection<UInt16> values)
|
internal IReadOnlyCollection<UInt16> Set(IReadOnlyCollection<UInt16> values)
|
||||||
{
|
{
|
||||||
if (values.Count != Data.Count / 2)
|
if (values.Count != RawData.Count / 2)
|
||||||
throw new ArgumentException($"Expecting an list of size {values.Count}!", nameof(values));
|
throw new ArgumentException($"Expecting an list of size {values.Count}!", nameof(values));
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
foreach (var value in values)
|
foreach (var value in values)
|
||||||
{
|
{
|
||||||
Data[i++] = (Byte) (value >> 8);
|
RawData[i++] = (Byte) (value >> 8);
|
||||||
Data[i++] = (Byte) (value & 0xFF);
|
RawData[i++] = (Byte) (value & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerator<IConvertible> IEnumerable<IConvertible>.GetEnumerator()
|
||||||
public IEnumerator<UInt16> GetEnumerator()
|
|
||||||
{
|
{
|
||||||
var end = Data.Count;
|
var end = RawData.Count;
|
||||||
|
|
||||||
for (var i = 0; i < end; )
|
for (var i = 0; i < end; )
|
||||||
{
|
{
|
||||||
var hi = Data[i++] << 8;
|
var hi = RawData[i++] << 8;
|
||||||
var lo = Data[i++];
|
var lo = RawData[i++];
|
||||||
|
|
||||||
|
yield return (UInt16) (hi | lo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<UInt16> GetEnumerator()
|
||||||
|
{
|
||||||
|
var end = RawData.Count;
|
||||||
|
|
||||||
|
for (var i = 0; i < end; )
|
||||||
|
{
|
||||||
|
var hi = RawData[i++] << 8;
|
||||||
|
var lo = RawData[i++];
|
||||||
|
|
||||||
yield return (UInt16) (hi | lo);
|
yield return (UInt16) (hi | lo);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +65,7 @@ public readonly struct MbWords : IReadOnlyList<UInt16>
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
public Int32 Count => Data.Count / 2;
|
public Int32 Count => RawData.Count / 2;
|
||||||
|
|
||||||
public UInt16 this[Int32 index]
|
public UInt16 this[Int32 index]
|
||||||
{
|
{
|
||||||
|
@ -63,8 +75,8 @@ public readonly struct MbWords : IReadOnlyList<UInt16>
|
||||||
{
|
{
|
||||||
var i = index * 2;
|
var i = index * 2;
|
||||||
|
|
||||||
var hi = Data[i] << 8;
|
var hi = RawData[i] << 8;
|
||||||
var lo = Data[i+1];
|
var lo = RawData[i+1];
|
||||||
|
|
||||||
return (UInt16) (hi | lo);
|
return (UInt16) (hi | lo);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +88,9 @@ public readonly struct MbWords : IReadOnlyList<UInt16>
|
||||||
public void SetUInt16(Byte index, UInt16 value)
|
public void SetUInt16(Byte index, UInt16 value)
|
||||||
{
|
{
|
||||||
var i = index * 2;
|
var i = index * 2;
|
||||||
Data[i + 0] = (Byte)(value >> 8);
|
RawData[i + 0] = (Byte)(value >> 8);
|
||||||
Data[i + 1] = (Byte)(value & 0xFF);
|
RawData[i + 1] = (Byte)(value & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IConvertible IReadOnlyList<IConvertible>.this[Int32 index] => this[index];
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
||||||
|
|
||||||
|
@ -8,8 +9,8 @@ internal class ReadCoilsCommandFrame : ModbusFrame
|
||||||
{
|
{
|
||||||
private const Int32 Size = 6;
|
private const Int32 Size = 6;
|
||||||
|
|
||||||
private MbWord ReadAddress => Data.WordAt(2);
|
public MbWord ReadAddress => Data.WordAt(2);
|
||||||
private MbWord QuantityOfInputs => Data.WordAt(4);
|
public MbWord NumberOfCoils => Data.WordAt(4);
|
||||||
|
|
||||||
|
|
||||||
public ReadCoilsCommandFrame(Byte slave, UInt16 readAddress, UInt16 nBits) : base(Size)
|
public ReadCoilsCommandFrame(Byte slave, UInt16 readAddress, UInt16 nBits) : base(Size)
|
||||||
|
@ -18,9 +19,9 @@ internal class ReadCoilsCommandFrame : ModbusFrame
|
||||||
throw new ArgumentOutOfRangeException($"Maximum number of registers ({Constants.MaxCoils}) exceeeded!", nameof(nBits));
|
throw new ArgumentOutOfRangeException($"Maximum number of registers ({Constants.MaxCoils}) exceeeded!", nameof(nBits));
|
||||||
|
|
||||||
SlaveAddress .Set(slave);
|
SlaveAddress .Set(slave);
|
||||||
FunctionCode .Set(Protocol.FunctionCode.ReadCoils);
|
FunctionCode .Set(ReadCoils);
|
||||||
ReadAddress .Set(readAddress);
|
ReadAddress .Set(readAddress);
|
||||||
QuantityOfInputs.Set(nBits);
|
NumberOfCoils.Set(nBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ internal class ReadCoilsCommandFrame : ModbusFrame
|
||||||
if (data.Count != Size)
|
if (data.Count != Size)
|
||||||
throw new ArgumentException($"Expecting an array of size {Size}", nameof(data));
|
throw new ArgumentException($"Expecting an array of size {Size}", nameof(data));
|
||||||
|
|
||||||
AssertFunctionCode(Protocol.FunctionCode.ReadCoils);
|
AssertFunctionCode(ReadCoils);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadCoilsResponseFrame VerifyResponse(ReadCoilsResponseFrame response)
|
public ReadCoilsResponseFrame VerifyResponse(ReadCoilsResponseFrame response)
|
||||||
|
@ -37,8 +38,8 @@ internal class ReadCoilsCommandFrame : ModbusFrame
|
||||||
if (response.SlaveAddress != SlaveAddress)
|
if (response.SlaveAddress != SlaveAddress)
|
||||||
throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress);
|
throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress);
|
||||||
|
|
||||||
if (response.Inputs.Count != Math.Ceiling(QuantityOfInputs / 8.0).ConvertTo<Int32>() * 8)
|
if (response.Coils.Count != Math.Ceiling(NumberOfCoils / 8.0).ConvertTo<Int32>() * 8)
|
||||||
throw new UnexpectedResponseFieldException(nameof(response.Inputs), QuantityOfInputs.ToString(), response.Inputs.Count);
|
throw new UnexpectedResponseFieldException(nameof(response.Coils), NumberOfCoils.ToString(), response.Coils.Count);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,12 +40,10 @@ internal class ReadHoldingRegistersCommandFrame : ModbusFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ReadHoldingRegistersResponseFrame VerifyResponse(ModbusFrame response)
|
public ReadHoldingRegistersResponseFrame VerifyResponse(ReadHoldingRegistersResponseFrame r)
|
||||||
{
|
{
|
||||||
if (response.SlaveAddress != SlaveAddress)
|
if (r.SlaveAddress != SlaveAddress)
|
||||||
throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress);
|
throw new UnexpectedResponseFieldException(nameof(r.SlaveAddress), SlaveAddress.ToString(), r.SlaveAddress);
|
||||||
|
|
||||||
var r = ReadHoldingRegistersResponseFrame.Parse(response.Data);
|
|
||||||
|
|
||||||
if (r.RegistersRead.Count != NbToRead)
|
if (r.RegistersRead.Count != NbToRead)
|
||||||
throw new UnexpectedResponseFieldException(nameof(r.RegistersRead), NbToRead.ToString(), r.RegistersRead.Count);
|
throw new UnexpectedResponseFieldException(nameof(r.RegistersRead), NbToRead.ToString(), r.RegistersRead.Count);
|
||||||
|
|
|
@ -40,7 +40,6 @@ internal class ReadInputRegistersCommandFrame : ModbusFrame
|
||||||
if (response.SlaveAddress != SlaveAddress)
|
if (response.SlaveAddress != SlaveAddress)
|
||||||
throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress);
|
throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress);
|
||||||
|
|
||||||
|
|
||||||
if (response.RegistersRead.Count != NbToRead)
|
if (response.RegistersRead.Count != NbToRead)
|
||||||
throw new UnexpectedResponseFieldException(nameof(response.RegistersRead), NbToRead.ToString(), response.RegistersRead.Count);
|
throw new UnexpectedResponseFieldException(nameof(response.RegistersRead), NbToRead.ToString(), response.RegistersRead.Count);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
||||||
|
|
||||||
using UInt16s = IReadOnlyList<UInt16>;
|
using UInt16s = IReadOnlyCollection<UInt16>;
|
||||||
|
|
||||||
internal class ReadWriteRegistersCommandFrame : ModbusFrame
|
internal class ReadWriteRegistersCommandFrame : ModbusFrame
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,7 +7,7 @@ using static InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Constants;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
||||||
|
|
||||||
using Booleans = IReadOnlyList<Boolean>;
|
using Booleans = IReadOnlyCollection<Boolean>;
|
||||||
using MbFc = MbByte<FunctionCode>;
|
using MbFc = MbByte<FunctionCode>;
|
||||||
|
|
||||||
public class WriteCoilsCommandFrame : ModbusFrame
|
public class WriteCoilsCommandFrame : ModbusFrame
|
||||||
|
@ -17,7 +17,7 @@ public class WriteCoilsCommandFrame : ModbusFrame
|
||||||
private MbWord WriteAddress => Data.WordAt(2);
|
private MbWord WriteAddress => Data.WordAt(2);
|
||||||
private MbWord NbOfCoils => Data.WordAt(4);
|
private MbWord NbOfCoils => Data.WordAt(4);
|
||||||
private MbByte ByteCount => Data.ByteAt(6);
|
private MbByte ByteCount => Data.ByteAt(6);
|
||||||
public MbBits CoilsToWrite => Data.BitsAt(7);
|
private MbBits CoilsToWrite => Data.BitsAt(7);
|
||||||
|
|
||||||
public WriteCoilsCommandFrame(Byte slave, UInt16 writeAddress, Booleans coils) : base(MinSize + NbBytes(coils))
|
public WriteCoilsCommandFrame(Byte slave, UInt16 writeAddress, Booleans coils) : base(MinSize + NbBytes(coils))
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@ using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands;
|
||||||
|
|
||||||
using UInt16s = IReadOnlyList<UInt16>;
|
using UInt16s = IReadOnlyCollection<UInt16>;
|
||||||
using MbFc = MbByte<FunctionCode>;
|
using MbFc = MbByte<FunctionCode>;
|
||||||
|
|
||||||
internal class WriteRegistersCommandFrame : ModbusFrame
|
internal class WriteRegistersCommandFrame : ModbusFrame
|
||||||
|
|
|
@ -8,7 +8,7 @@ public class ReadCoilsResponseFrame : ModbusFrame
|
||||||
private new const Int32 MinSize = 3;
|
private new const Int32 MinSize = 3;
|
||||||
|
|
||||||
private MbByte ByteCount => Data.ByteAt(2);
|
private MbByte ByteCount => Data.ByteAt(2);
|
||||||
public MbBits Inputs => Data.BitsAt(3);
|
internal MbBits Coils => Data.BitsAt(3);
|
||||||
|
|
||||||
public ReadCoilsResponseFrame(Byte slave, IReadOnlyList<Boolean> inputs) : base (inputs.Count)
|
public ReadCoilsResponseFrame(Byte slave, IReadOnlyList<Boolean> inputs) : base (inputs.Count)
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ public class ReadCoilsResponseFrame : ModbusFrame
|
||||||
SlaveAddress .Set(slave);
|
SlaveAddress .Set(slave);
|
||||||
FunctionCode .Set(Protocol.FunctionCode.ReadCoils);
|
FunctionCode .Set(Protocol.FunctionCode.ReadCoils);
|
||||||
ByteCount .Set(nBytes);
|
ByteCount .Set(nBytes);
|
||||||
Inputs .Set(inputs);
|
Coils .Set(inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadCoilsResponseFrame(Byte[] data) : this(new ArraySegment<Byte>(data))
|
private ReadCoilsResponseFrame(Byte[] data) : this(new ArraySegment<Byte>(data))
|
||||||
|
@ -39,13 +39,7 @@ public class ReadCoilsResponseFrame : ModbusFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ReadCoilsResponseFrame Parse(ModbusFrame rawFrame)
|
public static ReadCoilsResponseFrame Parse(Byte[] data) => new(data);
|
||||||
{
|
|
||||||
return new ReadCoilsResponseFrame(rawFrame.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadCoilsResponseFrame Parse(ArraySegment<Byte> data)
|
public static ReadCoilsResponseFrame Parse(ArraySegment<Byte> data) => new(data);
|
||||||
{
|
|
||||||
return new ReadCoilsResponseFrame(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -43,9 +43,9 @@ public class ReadDiscreteInputsResponseFrame : ModbusFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ReadDiscreteInputsResponseFrame Parse(ModbusFrame rawFrame)
|
public static ReadDiscreteInputsResponseFrame Parse(Byte[] data)
|
||||||
{
|
{
|
||||||
return new ReadDiscreteInputsResponseFrame(rawFrame.Data);
|
return new ReadDiscreteInputsResponseFrame(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadDiscreteInputsResponseFrame Parse(ArraySegment<Byte> data)
|
public static ReadDiscreteInputsResponseFrame Parse(ArraySegment<Byte> data)
|
||||||
|
|
|
@ -44,7 +44,6 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ReadHoldingRegistersResponseFrame Parse(ModbusFrame frame) => new ReadHoldingRegistersResponseFrame(frame.Data);
|
|
||||||
public static ReadHoldingRegistersResponseFrame Parse(Byte[] data) => new ReadHoldingRegistersResponseFrame(data);
|
public static ReadHoldingRegistersResponseFrame Parse(Byte[] data) => new ReadHoldingRegistersResponseFrame(data);
|
||||||
public static ReadHoldingRegistersResponseFrame Parse(ArraySegment<Byte> data) => new ReadHoldingRegistersResponseFrame(data);
|
public static ReadHoldingRegistersResponseFrame Parse(ArraySegment<Byte> data) => new ReadHoldingRegistersResponseFrame(data);
|
||||||
}
|
}
|
|
@ -46,9 +46,9 @@ internal class ReadInputRegistersResponseFrame : ModbusFrame
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static ReadInputRegistersResponseFrame Parse(ModbusFrame rawFrame)
|
public static ReadInputRegistersResponseFrame Parse(Byte[] data)
|
||||||
{
|
{
|
||||||
return new ReadInputRegistersResponseFrame(rawFrame.Data);
|
return new ReadInputRegistersResponseFrame(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadInputRegistersResponseFrame Parse(ArraySegment<Byte> data)
|
public static ReadInputRegistersResponseFrame Parse(ArraySegment<Byte> data)
|
||||||
|
|
|
@ -33,7 +33,6 @@ public class WriteCoilsResponseFrame : ModbusFrame
|
||||||
AssertFunctionCode(WriteMultipleCoils);
|
AssertFunctionCode(WriteMultipleCoils);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WriteCoilsResponseFrame Parse(ArraySegment<Byte> data) => new WriteCoilsResponseFrame(data);
|
|
||||||
public static WriteCoilsResponseFrame Parse(Byte[] data) => new WriteCoilsResponseFrame(data);
|
public static WriteCoilsResponseFrame Parse(Byte[] data) => new WriteCoilsResponseFrame(data);
|
||||||
public static WriteCoilsResponseFrame Parse(ModbusFrame frame) => new WriteCoilsResponseFrame(frame.Data);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,5 +35,5 @@ internal class WriteRegistersResponseFrame : ModbusFrame
|
||||||
|
|
||||||
public static WriteRegistersResponseFrame Parse(ArraySegment<Byte> data) => new WriteRegistersResponseFrame(data);
|
public static WriteRegistersResponseFrame Parse(ArraySegment<Byte> data) => new WriteRegistersResponseFrame(data);
|
||||||
public static WriteRegistersResponseFrame Parse(Byte[] data) => new WriteRegistersResponseFrame(data);
|
public static WriteRegistersResponseFrame Parse(Byte[] data) => new WriteRegistersResponseFrame(data);
|
||||||
public static WriteRegistersResponseFrame Parse(ModbusFrame frame) => new WriteRegistersResponseFrame(frame.Data);
|
|
||||||
}
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
using static System.AttributeTargets;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(Class | Struct)]
|
||||||
|
public class AddressOffsetAttribute : Attribute
|
||||||
|
{
|
||||||
|
public Int32 Offset { get; }
|
||||||
|
public AddressOffsetAttribute(Int32 offset) => Offset = offset;
|
||||||
|
}
|
|
@ -4,5 +4,4 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
public class DiscreteInputAttribute : ModbusBooleanAttribute
|
public class DiscreteInputAttribute : ModbusBooleanAttribute
|
||||||
{
|
{
|
||||||
public DiscreteInputAttribute(UInt16 address) : base(address) { }
|
public DiscreteInputAttribute(UInt16 address) : base(address) { }
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
public class HoldingRegisterAttribute : ModbusRegisterAttribute
|
public class HoldingRegisterAttribute : ModbusRegisterAttribute
|
||||||
{
|
{
|
||||||
public HoldingRegisterAttribute(UInt16 address) : base(address) { }
|
public HoldingRegisterAttribute(UInt16 address, TypeCode modbusType = TypeCode.UInt16) : base(address, modbusType) { }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,6 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
public class InputRegisterAttribute : ModbusRegisterAttribute
|
public class InputRegisterAttribute : ModbusRegisterAttribute
|
||||||
{
|
{
|
||||||
public InputRegisterAttribute(UInt16 address) : base(address) { }
|
public InputRegisterAttribute(UInt16 address, TypeCode modbusType = TypeCode.UInt16) : base(address, modbusType) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,32 @@
|
||||||
|
using static System.AttributeTargets;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
[AttributeUsage(Field | Property)]
|
||||||
public abstract class ModbusAttribute : Attribute
|
public class ModbusAttribute : Attribute
|
||||||
{
|
{
|
||||||
public UInt16 Address { get; }
|
public UInt16 Address { get; }
|
||||||
protected ModbusAttribute(UInt16 address) { Address = address; }
|
public UInt16 Size { get; }
|
||||||
|
public TypeCode ModbusType { get; }
|
||||||
|
|
||||||
|
protected ModbusAttribute(UInt16 address, TypeCode modbusType)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
ModbusType = modbusType;
|
||||||
|
|
||||||
|
Size = modbusType switch
|
||||||
|
{
|
||||||
|
TypeCode.Boolean => 1,
|
||||||
|
TypeCode.Int16 => 1,
|
||||||
|
TypeCode.UInt16 => 1,
|
||||||
|
TypeCode.Int32 => 2,
|
||||||
|
TypeCode.UInt32 => 2,
|
||||||
|
TypeCode.Single => 2,
|
||||||
|
TypeCode.Int64 => 4,
|
||||||
|
TypeCode.UInt64 => 4,
|
||||||
|
TypeCode.Double => 4,
|
||||||
|
_ => throw new ArgumentException(nameof(modbusType))
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
public class ModbusBooleanAttribute : ModbusAttribute
|
public class ModbusBooleanAttribute : ModbusAttribute
|
||||||
{
|
{
|
||||||
protected ModbusBooleanAttribute(UInt16 address) : base(address)
|
protected ModbusBooleanAttribute(UInt16 address) : base(address, TypeCode.Boolean)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,30 @@
|
||||||
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
public abstract class ModbusRegisterAttribute : ModbusAttribute
|
public abstract class ModbusRegisterAttribute : ModbusAttribute
|
||||||
{
|
{
|
||||||
protected ModbusRegisterAttribute(UInt16 address) : base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double Scale { get; init; } = 1;
|
public Double Scale { get; init; } = 1;
|
||||||
public Double Offset { get; init; } = 0;
|
public Double Offset { get; init; } = 0;
|
||||||
|
|
||||||
|
protected ModbusRegisterAttribute(UInt16 address, TypeCode modbusType) : base(address, modbusType)
|
||||||
|
{
|
||||||
|
if (!SupportedTypes.Contains(modbusType))
|
||||||
|
throw new ArgumentException(nameof(modbusType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal static readonly TypeCode[] SupportedTypes =
|
||||||
|
{
|
||||||
|
TypeCode.UInt16,
|
||||||
|
TypeCode.Int16,
|
||||||
|
TypeCode.UInt32,
|
||||||
|
TypeCode.Int32,
|
||||||
|
TypeCode.UInt64,
|
||||||
|
TypeCode.Int64,
|
||||||
|
TypeCode.Single,
|
||||||
|
TypeCode.Double
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||||
|
|
||||||
|
#pragma warning disable CS8509
|
||||||
|
|
||||||
|
internal record Batch<R>(Action<R> Read, Action<R> Write);
|
||||||
|
|
||||||
|
public static class Batches
|
||||||
|
{
|
||||||
|
internal static IEnumerable<Batch<R>> MakeBatchesFor<R>(this ModbusClient modbusClient, Int32 addressOffset)
|
||||||
|
{
|
||||||
|
var members = ModbusMembers
|
||||||
|
.From<R>(addressOffset)
|
||||||
|
.OrderBy(m => m.Attribute.GetType().Name)
|
||||||
|
.ThenBy(m => m.StartAddress)
|
||||||
|
.ThenBy(m => m.EndAddress);
|
||||||
|
|
||||||
|
return MakeBatches<R>(modbusClient, members);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Batch<R>> MakeBatches<R>(ModbusClient mb, IEnumerable<ModbusMember> modbusMembers)
|
||||||
|
{
|
||||||
|
var batchMembers = new List<ModbusMember>();
|
||||||
|
|
||||||
|
foreach (var member in modbusMembers)
|
||||||
|
{
|
||||||
|
if (CloseBatch(member))
|
||||||
|
{
|
||||||
|
yield return MakeBatch<R>(mb, batchMembers);
|
||||||
|
batchMembers = new List<ModbusMember>();
|
||||||
|
}
|
||||||
|
|
||||||
|
batchMembers.Add(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchMembers.Count > 0)
|
||||||
|
yield return MakeBatch<R>(mb, batchMembers);
|
||||||
|
|
||||||
|
Boolean CloseBatch(ModbusMember m)
|
||||||
|
{
|
||||||
|
if (batchMembers.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m.StartAddress > batchMembers[^1].EndAddress // gap between registers
|
||||||
|
|| m.EndAddress > batchMembers[0].StartAddress + Constants.MaxRegs // max batch size reached
|
||||||
|
|| m.Attribute.GetType() != batchMembers[0].Attribute.GetType(); // different ModbusType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Batch<R> MakeBatch<R>(ModbusClient modbusClient, IReadOnlyList<ModbusMember> members)
|
||||||
|
{
|
||||||
|
var startAddress = members[0].StartAddress;
|
||||||
|
var endAddress = members[^1].EndAddress;
|
||||||
|
var count = (UInt16)(endAddress - startAddress);
|
||||||
|
var attribute = members[0].Attribute;
|
||||||
|
var isWritable = attribute is HoldingRegisterAttribute or CoilAttribute;
|
||||||
|
|
||||||
|
return new Batch<R>(Read(), Write());
|
||||||
|
|
||||||
|
Action<R> Read()
|
||||||
|
{
|
||||||
|
Func<MbData> readModbus = attribute switch
|
||||||
|
{
|
||||||
|
InputRegisterAttribute => () => modbusClient.ReadInputRegisters (startAddress, count),
|
||||||
|
HoldingRegisterAttribute => () => modbusClient.ReadHoldingRegisters(startAddress, count),
|
||||||
|
DiscreteInputAttribute => () => modbusClient.ReadDiscreteInputs (startAddress, count),
|
||||||
|
CoilAttribute => () => modbusClient.ReadCoils (startAddress, count),
|
||||||
|
};
|
||||||
|
|
||||||
|
return record =>
|
||||||
|
{
|
||||||
|
foreach (var member in members)
|
||||||
|
{
|
||||||
|
var mbData = readModbus();
|
||||||
|
member.ModbusToRecord(mbData, record!);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Action<R> Write()
|
||||||
|
{
|
||||||
|
if (!isWritable)
|
||||||
|
return _ => { }; // nop
|
||||||
|
|
||||||
|
Func<MbData> createMbData = attribute switch
|
||||||
|
{
|
||||||
|
HoldingRegisterAttribute => () => MbData.Registers(startAddress, count),
|
||||||
|
CoilAttribute => () => MbData.Coils (startAddress, count),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Action<MbData> writeModbus = attribute switch
|
||||||
|
{
|
||||||
|
HoldingRegisterAttribute => d => modbusClient.WriteRegisters(startAddress, d.GetRegisters()),
|
||||||
|
CoilAttribute => d => modbusClient.WriteCoils (startAddress, d.GetCoils()),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return rec =>
|
||||||
|
{
|
||||||
|
foreach (var member in members)
|
||||||
|
{
|
||||||
|
var mbData = createMbData();
|
||||||
|
member.RecordToModbus(rec!, mbData);
|
||||||
|
writeModbus(mbData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,100 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
using InnovEnergy.Lib.Utils;
|
|
||||||
using static System.Reflection.BindingFlags;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
|
||||||
|
|
||||||
using ModbusRegisters = IReadOnlyList<ModbusRegister>;
|
|
||||||
using ModbusBooleans = IReadOnlyList<ModbusBoolean>;
|
|
||||||
using UInt16s = IReadOnlyList<UInt16>;
|
|
||||||
using Booleans = IReadOnlyList<Boolean>;
|
|
||||||
|
|
||||||
internal static class Batches
|
|
||||||
{
|
|
||||||
|
|
||||||
internal static IEnumerable<ModbusRegister[]> GetModbusRegisterBatches<T>(this Object statusRecord) where T : ModbusRegisterAttribute
|
|
||||||
{
|
|
||||||
var modbusRegisters = from mi in statusRecord.GetDataMembers()
|
|
||||||
from a in mi.GetCustomAttributes<T>()
|
|
||||||
select new ModbusRegister(a, mi, statusRecord);
|
|
||||||
|
|
||||||
return modbusRegisters.SplitIntoFrameSizedBatches();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal static IEnumerable<ModbusBoolean[]> GetModbusBooleanBatches<T>(this Object statusRecord) where T : ModbusBooleanAttribute
|
|
||||||
{
|
|
||||||
var modbusBooleans = from mi in statusRecord.GetDataMembers()
|
|
||||||
from a in mi.GetCustomAttributes<T>()
|
|
||||||
select new ModbusBoolean(a, mi, statusRecord);
|
|
||||||
|
|
||||||
return modbusBooleans.SplitIntoFrameSizedBatches();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static IEnumerable<MemberInfo> GetDataMembers(this Object statusRecord)
|
|
||||||
{
|
|
||||||
const BindingFlags bindingFlags = Instance
|
|
||||||
| Public
|
|
||||||
| NonPublic
|
|
||||||
| FlattenHierarchy;
|
|
||||||
|
|
||||||
var type = statusRecord.GetType();
|
|
||||||
|
|
||||||
var fields = type.GetFields(bindingFlags);
|
|
||||||
var props = type.GetProperties(bindingFlags);
|
|
||||||
|
|
||||||
return fields.Concat<MemberInfo>(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static IEnumerable<T[]> SplitIntoFrameSizedBatches<T>(this IEnumerable<T> members) where T : IAddress
|
|
||||||
{
|
|
||||||
return members
|
|
||||||
.OrderBy(m => m.Address)
|
|
||||||
.GroupWhile((a, b) => b.Address - a.Address > 1) // split when there is a gap between consecutive addresses
|
|
||||||
.SelectMany(g => g.Chunk(Constants.MaxRegs)); // group cannot be larger than Constants.MaxRegs
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static IEnumerable<UInt16> SetStatusRecordMembersFromRawModbusValues(this ModbusRegisters modbusBooleans, UInt16s rawRegisterValues)
|
|
||||||
{
|
|
||||||
foreach (var modbusBoolean in modbusBooleans)
|
|
||||||
{
|
|
||||||
var index = modbusBoolean.Address - modbusBooleans[0].Address;
|
|
||||||
var rawRegisterValue = rawRegisterValues[index];
|
|
||||||
modbusBoolean.SetStatusRecordMemberFromRawModbusValue(rawRegisterValue);
|
|
||||||
yield return modbusBoolean.Address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static UInt16[] GetRawModbusValuesFromControlRecord(this ModbusRegisters modbusBooleans)
|
|
||||||
{
|
|
||||||
return modbusBooleans
|
|
||||||
.Select(v => v.GetRawModbusValueFromControlRecord())
|
|
||||||
.ToArray(modbusBooleans.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static IEnumerable<UInt16> SetStatusRecordMembersFromRawModbusValues(this ModbusBooleans modbusBooleans, Booleans rawRegisterValues)
|
|
||||||
{
|
|
||||||
foreach (var modbusBoolean in modbusBooleans)
|
|
||||||
{
|
|
||||||
var index = modbusBoolean.Address - modbusBooleans[0].Address;
|
|
||||||
var rawRegisterValue = rawRegisterValues[index];
|
|
||||||
modbusBoolean.SetStatusRecordMemberFromRawModbusValue(rawRegisterValue);
|
|
||||||
yield return modbusBoolean.Address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static Boolean[] GetRawModbusValuesFromControlRecord(this ModbusBooleans modbusBooleans)
|
|
||||||
{
|
|
||||||
return modbusBooleans
|
|
||||||
.Select(v => v.GetRawModbusValueFromControlRecord())
|
|
||||||
.ToArray(modbusBooleans.Count);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
|
||||||
|
|
||||||
internal interface IAddress
|
|
||||||
{
|
|
||||||
UInt16 Address { get; }
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
using InnovEnergy.Lib.Utils;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
|
||||||
|
|
||||||
internal readonly struct ModbusBoolean : IAddress
|
|
||||||
{
|
|
||||||
public UInt16 Address => _Attribute.Address;
|
|
||||||
|
|
||||||
private readonly ModbusBooleanAttribute _Attribute ;
|
|
||||||
private readonly MemberInfo _MemberInfo ;
|
|
||||||
private readonly Object _Record;
|
|
||||||
|
|
||||||
[Obsolete] public ModbusBoolean() => throw new Exception("Forbidden");
|
|
||||||
|
|
||||||
public ModbusBoolean(ModbusBooleanAttribute attribute, MemberInfo memberInfo, Object record)
|
|
||||||
{
|
|
||||||
_Attribute = attribute;
|
|
||||||
_MemberInfo = memberInfo;
|
|
||||||
_Record = record;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public UInt16 SetStatusRecordMemberFromRawModbusValue(Boolean rawBooleanValue)
|
|
||||||
{
|
|
||||||
if (_MemberInfo is FieldInfo fi)
|
|
||||||
{
|
|
||||||
var converted = rawBooleanValue.ConvertTo(fi.FieldType);
|
|
||||||
fi.SetValue(_Record, converted);
|
|
||||||
}
|
|
||||||
else if (_MemberInfo is PropertyInfo pi)
|
|
||||||
{
|
|
||||||
var converted = rawBooleanValue.ConvertTo(pi.PropertyType);
|
|
||||||
pi.SetValue(_Record, converted);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean GetRawModbusValueFromControlRecord()
|
|
||||||
{
|
|
||||||
var value = _MemberInfo switch
|
|
||||||
{
|
|
||||||
FieldInfo fi => fi.GetValue(_Record)!,
|
|
||||||
PropertyInfo pi => pi.GetValue(_Record)!,
|
|
||||||
_ => throw new ArgumentException(nameof(_MemberInfo))
|
|
||||||
};
|
|
||||||
|
|
||||||
return value
|
|
||||||
.CastTo<IConvertible>()
|
|
||||||
.ConvertTo<Boolean>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
|
using static System.Reflection.BindingFlags;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||||
|
|
||||||
|
#pragma warning disable CS8509
|
||||||
|
|
||||||
|
internal record ModbusMember
|
||||||
|
(
|
||||||
|
UInt16 StartAddress,
|
||||||
|
UInt16 EndAddress,
|
||||||
|
ModbusAttribute Attribute,
|
||||||
|
Action<MbData, Object> ModbusToRecord,
|
||||||
|
Action<Object, MbData> RecordToModbus
|
||||||
|
);
|
||||||
|
|
||||||
|
internal static class ModbusMembers
|
||||||
|
{
|
||||||
|
private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0);
|
||||||
|
|
||||||
|
internal static IEnumerable<ModbusMember> From<R>(Int32 addressOffset)
|
||||||
|
{
|
||||||
|
return GetDataMembers<R>()
|
||||||
|
.Where(HasAttribute<ModbusAttribute>)
|
||||||
|
.Select(m => m.CreateModbusMember(addressOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset)
|
||||||
|
{
|
||||||
|
var attribute = info.GetCustomAttributes<ModbusAttribute>().Single();
|
||||||
|
var startAddress = (UInt16)(attribute.Address + addressOffset);
|
||||||
|
var endAddress = (UInt16)(startAddress + attribute.Size);
|
||||||
|
var modbusType = attribute.ModbusType;
|
||||||
|
var transform = attribute is ModbusRegisterAttribute mra
|
||||||
|
? (mra.Scale, mra.Offset)
|
||||||
|
: NoTransform;
|
||||||
|
|
||||||
|
var modbusToRecord = info.ModbusToRecord(startAddress, modbusType, transform);
|
||||||
|
var recordToModbus = info.RecordToModbus(startAddress, modbusType, transform);
|
||||||
|
|
||||||
|
return new ModbusMember
|
||||||
|
(
|
||||||
|
startAddress,
|
||||||
|
endAddress,
|
||||||
|
attribute,
|
||||||
|
modbusToRecord,
|
||||||
|
recordToModbus
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Action<MbData, Object> ModbusToRecord(this MemberInfo info, UInt16 address, TypeCode modbusType, (Double scale, Double offset) transform)
|
||||||
|
{
|
||||||
|
var decode = ConvertModbusToRecord(transform);
|
||||||
|
|
||||||
|
Func<MbData, IConvertible> readFromMbData = modbusType switch
|
||||||
|
{
|
||||||
|
TypeCode.Boolean => d => d.GetInput (address),
|
||||||
|
TypeCode.UInt16 => d => d.GetUInt16 (address),
|
||||||
|
TypeCode.Int16 => d => d.GetInt16 (address),
|
||||||
|
TypeCode.UInt32 => d => d.GetUInt32 (address),
|
||||||
|
TypeCode.Int32 => d => d.GetInt32 (address),
|
||||||
|
TypeCode.Single => d => d.GetFloat32(address),
|
||||||
|
TypeCode.UInt64 => d => d.GetUInt64 (address),
|
||||||
|
TypeCode.Int64 => d => d.GetInt64 (address),
|
||||||
|
TypeCode.Double => d => d.GetFloat64(address),
|
||||||
|
_ => throw new ArgumentException(nameof(modbusType))
|
||||||
|
};
|
||||||
|
|
||||||
|
Action<Object, IConvertible> set = info switch
|
||||||
|
{
|
||||||
|
FieldInfo fi => (rec, value) => fi.SetValue(rec, value.ConvertTo(fi.FieldType)),
|
||||||
|
PropertyInfo pi => (rec, value) => pi.SetValue(rec, value.ConvertTo(pi.PropertyType)),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (mbData, rec) =>
|
||||||
|
{
|
||||||
|
var rawModbusValue = readFromMbData(mbData);
|
||||||
|
var decoded = decode(rawModbusValue);
|
||||||
|
|
||||||
|
set(rec, decoded);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Func<IConvertible, IConvertible> ConvertModbusToRecord((Double scale, Double offset) transform)
|
||||||
|
{
|
||||||
|
if (transform == NoTransform)
|
||||||
|
return Nop;
|
||||||
|
|
||||||
|
var scale = transform.scale.ConvertTo<Decimal>();
|
||||||
|
var offset = transform.offset.ConvertTo<Decimal>();
|
||||||
|
|
||||||
|
return c =>
|
||||||
|
{
|
||||||
|
var value = c.ConvertTo<Decimal>();
|
||||||
|
|
||||||
|
return (value + offset) * scale;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Action<Object, MbData> RecordToModbus(this MemberInfo info, UInt16 addr, TypeCode modbusType, (Double scale, Double offset) transform)
|
||||||
|
{
|
||||||
|
var encode = ConvertRecordToModbus(transform);
|
||||||
|
|
||||||
|
Func<Object, IConvertible> get = info switch
|
||||||
|
{
|
||||||
|
FieldInfo fi => rec => (IConvertible)fi.GetValue(rec)!,
|
||||||
|
PropertyInfo pi => rec => (IConvertible)pi.GetValue(rec)!,
|
||||||
|
};
|
||||||
|
|
||||||
|
Action<IConvertible, MbData> writeToMbData = modbusType switch
|
||||||
|
{
|
||||||
|
TypeCode.Boolean => (value, mbData) => mbData.SetCoil (addr, value.ConvertTo<Boolean>()),
|
||||||
|
TypeCode.UInt16 => (value, mbData) => mbData.SetUInt16 (addr, value.ConvertTo<UInt16>()),
|
||||||
|
TypeCode.Int16 => (value, mbData) => mbData.SetInt16 (addr, value.ConvertTo<Int16>()),
|
||||||
|
TypeCode.UInt32 => (value, mbData) => mbData.SetUInt32 (addr, value.ConvertTo<UInt32>()),
|
||||||
|
TypeCode.Int32 => (value, mbData) => mbData.SetInt32 (addr, value.ConvertTo<Int32>()),
|
||||||
|
TypeCode.Single => (value, mbData) => mbData.SetFloat32(addr, value.ConvertTo<Single>()),
|
||||||
|
TypeCode.UInt64 => (value, mbData) => mbData.SetUInt64 (addr, value.ConvertTo<UInt64>()),
|
||||||
|
TypeCode.Int64 => (value, mbData) => mbData.SetInt64 (addr, value.ConvertTo<Int64>()),
|
||||||
|
TypeCode.Double => (value, mbData) => mbData.SetFloat64(addr, value.ConvertTo<Double>()),
|
||||||
|
_ => throw new ArgumentException(nameof(modbusType))
|
||||||
|
};
|
||||||
|
|
||||||
|
return (rec, mbData) =>
|
||||||
|
{
|
||||||
|
var memberValue = get(rec);
|
||||||
|
var encoded = encode(memberValue);
|
||||||
|
|
||||||
|
writeToMbData(encoded, mbData);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Func<IConvertible, IConvertible> ConvertRecordToModbus((Double scale, Double offset) transform)
|
||||||
|
{
|
||||||
|
if (transform == NoTransform)
|
||||||
|
return Nop;
|
||||||
|
|
||||||
|
var scale = transform.scale.ConvertTo<Decimal>();
|
||||||
|
var offset = transform.offset.ConvertTo<Decimal>();
|
||||||
|
|
||||||
|
return c =>
|
||||||
|
{
|
||||||
|
var value = c.ConvertTo<Decimal>();
|
||||||
|
|
||||||
|
return value / scale - offset;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static T Nop<T>(T c) => c;
|
||||||
|
|
||||||
|
|
||||||
|
private static IEnumerable<MemberInfo> GetDataMembers<R>()
|
||||||
|
{
|
||||||
|
var recordType = typeof(R);
|
||||||
|
|
||||||
|
const BindingFlags bindingFlags = Instance
|
||||||
|
| Public
|
||||||
|
| NonPublic
|
||||||
|
| FlattenHierarchy;
|
||||||
|
|
||||||
|
var fields = recordType.GetFields(bindingFlags);
|
||||||
|
var props = recordType.GetProperties(bindingFlags);
|
||||||
|
|
||||||
|
return fields.Concat<MemberInfo>(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Boolean HasAttribute<T>(MemberInfo i) where T : Attribute
|
||||||
|
{
|
||||||
|
return i.GetCustomAttributes<T>().Any();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
using InnovEnergy.Lib.Utils;
|
|
||||||
|
|
||||||
#pragma warning disable CS8509
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
|
||||||
|
|
||||||
internal readonly struct ModbusRegister : IAddress
|
|
||||||
{
|
|
||||||
|
|
||||||
public UInt16 Address => _Attribute.Address;
|
|
||||||
public Decimal Offset => (Decimal)_Attribute.Offset;
|
|
||||||
public Decimal Scale => (Decimal)_Attribute.Scale;
|
|
||||||
|
|
||||||
private readonly Object _Record;
|
|
||||||
private readonly ModbusRegisterAttribute _Attribute;
|
|
||||||
private readonly MemberInfo _MemberInfo;
|
|
||||||
|
|
||||||
private static readonly Type[] UnsignedTypes =
|
|
||||||
{
|
|
||||||
typeof(Byte),
|
|
||||||
typeof(UInt16),
|
|
||||||
typeof(UInt32),
|
|
||||||
typeof(UInt64),
|
|
||||||
};
|
|
||||||
|
|
||||||
private Boolean IsUnsigned
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var type = _MemberInfo switch
|
|
||||||
{
|
|
||||||
FieldInfo fi => fi.FieldType,
|
|
||||||
PropertyInfo pi => pi.PropertyType
|
|
||||||
};
|
|
||||||
|
|
||||||
type = type.IsEnum
|
|
||||||
? Enum.GetUnderlyingType(type)
|
|
||||||
: type;
|
|
||||||
|
|
||||||
return UnsignedTypes.Contains(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ModbusRegister(ModbusRegisterAttribute attribute, MemberInfo memberInfo, Object record)
|
|
||||||
{
|
|
||||||
_Record = record;
|
|
||||||
_Attribute = attribute;
|
|
||||||
_MemberInfo = memberInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Obsolete] public ModbusRegister() => throw new Exception("Forbidden");
|
|
||||||
|
|
||||||
public UInt16 SetStatusRecordMemberFromRawModbusValue(UInt16 rawRegisterValue)
|
|
||||||
{
|
|
||||||
var raw = IsUnsigned
|
|
||||||
? (Decimal)rawRegisterValue
|
|
||||||
: (Int16)rawRegisterValue; // if it is signed we must first reinterpret cast the native UInt16 to Int16
|
|
||||||
|
|
||||||
var transformed = (raw + Offset) * Scale;
|
|
||||||
|
|
||||||
if (_MemberInfo is FieldInfo fi)
|
|
||||||
{
|
|
||||||
var converted = transformed.ConvertTo(fi.FieldType);
|
|
||||||
fi.SetValue(_Record, converted);
|
|
||||||
}
|
|
||||||
else if (_MemberInfo is PropertyInfo pi)
|
|
||||||
{
|
|
||||||
var converted = transformed.ConvertTo(pi.PropertyType);
|
|
||||||
pi.SetValue(_Record, converted);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt16 GetRawModbusValueFromControlRecord()
|
|
||||||
{
|
|
||||||
var value = _MemberInfo switch
|
|
||||||
{
|
|
||||||
FieldInfo fi => fi.GetValue(_Record),
|
|
||||||
PropertyInfo pi => pi.GetValue(_Record),
|
|
||||||
_ => throw new ArgumentException(nameof(_MemberInfo))
|
|
||||||
};
|
|
||||||
|
|
||||||
var transformed = value!
|
|
||||||
.CastTo<IConvertible>()
|
|
||||||
.ConvertTo<Decimal>() / Scale - Offset;
|
|
||||||
|
|
||||||
return IsUnsigned
|
|
||||||
? transformed.ConvertTo<UInt16>()
|
|
||||||
: transformed.ConvertTo<UInt16>().CastTo<UInt16>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
#pragma warning disable CS8509
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||||
|
|
||||||
|
public static class Record
|
||||||
|
{
|
||||||
|
internal static Int32 GetAddressOffset<R>()
|
||||||
|
{
|
||||||
|
return typeof(R)
|
||||||
|
.GetCustomAttributes<AddressOffsetAttribute>()
|
||||||
|
.Select(a => a.Offset)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
|
||||||
|
|
||||||
|
using Float32 = Single;
|
||||||
|
using Float64 = Double;
|
||||||
|
|
||||||
|
public class ModbusDevice<R> where R : notnull
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<Batch<R>> _Batches;
|
||||||
|
|
||||||
|
public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0)
|
||||||
|
{
|
||||||
|
var offset = addressOffset + Record.GetAddressOffset<R>();
|
||||||
|
_Batches = modbusClient.MakeBatchesFor<R>(offset).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public R Read()
|
||||||
|
{
|
||||||
|
R r;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
r = Activator.CreateInstance<R>();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception
|
||||||
|
(
|
||||||
|
$"type {typeof(R).Name} seems to lack a parameterless constructor. " +
|
||||||
|
$"Either create one or use the other overload of{nameof(Read)} instead.",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Read(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public R Read(R record)
|
||||||
|
{
|
||||||
|
foreach (var batch in _Batches)
|
||||||
|
batch.Read(record);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(R record)
|
||||||
|
{
|
||||||
|
foreach (var batch in _Batches)
|
||||||
|
batch.Write(record);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
|
||||||
|
|
||||||
|
public enum ModbusEncoding
|
||||||
|
{
|
||||||
|
Binary,
|
||||||
|
Ascii
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
|
||||||
|
|
||||||
|
public enum ModbusProtocol
|
||||||
|
{
|
||||||
|
Rtu,
|
||||||
|
Tcp
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
|
||||||
|
|
||||||
|
public static class ModbusSlave
|
||||||
|
{
|
||||||
|
|
||||||
|
public static Func<Byte, ModbusTcpClient> ModbusTcp(this Channel channel)
|
||||||
|
{
|
||||||
|
ModbusTcpClient SlaveId(Byte slaveId) => new ModbusTcpClient(channel, slaveId);
|
||||||
|
return SlaveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<Byte, ModbusRtuClient> ModbusRtu(this Channel channel)
|
||||||
|
{
|
||||||
|
ModbusRtuClient SlaveId(Byte slaveId) => new ModbusRtuClient(channel, slaveId);
|
||||||
|
return SlaveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Func<Byte, ModbusTcpClient> ModbusTcp<R>(this Channel channel) where R : notnull, new()
|
||||||
|
{
|
||||||
|
ModbusTcpClient SlaveId(Byte slaveId)
|
||||||
|
{
|
||||||
|
return new ModbusTcpClient(channel, slaveId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SlaveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<Byte, ModbusRtuClient> ModbusRtu<R>(this Channel channel) where R : notnull, new()
|
||||||
|
{
|
||||||
|
ModbusRtuClient SlaveId(Byte slaveId) => new ModbusRtuClient(channel, slaveId);
|
||||||
|
return SlaveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModbusDevice<T> TcpSlave<T>(this Channel channel, Byte slaveId) where T : notnull, new()
|
||||||
|
{
|
||||||
|
var client = new ModbusTcpClient(channel, slaveId);
|
||||||
|
return new ModbusDevice<T>(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModbusDevice<T> RtuSlave<T>(this Channel channel, Byte slaveId) where T : notnull, new()
|
||||||
|
{
|
||||||
|
var client = new ModbusRtuClient(channel, slaveId);
|
||||||
|
return new ModbusDevice<T>(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModbusDevice<T> Slave<T>(this ModbusClient modbusClient) where T : notnull, new()
|
||||||
|
{
|
||||||
|
return new ModbusDevice<T>(modbusClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Util;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Util;
|
||||||
|
|
||||||
public static class ArraySegmentExtensions
|
public static class ArraySegmentExtensions
|
||||||
{
|
{
|
||||||
public static ArraySegment<T> ToArraySegment<T>(this IEnumerable<T> enumerable)
|
public static ArraySegment<T> ToArraySegment<T>(this IEnumerable<T> enumerable) => enumerable switch
|
||||||
{
|
{
|
||||||
if (enumerable is T[] array)
|
ArraySegment<T> ars => ars,
|
||||||
return new ArraySegment<T>(array);
|
IReadOnlyList<T> rol => rol.ToArray(),
|
||||||
|
_ => enumerable.ToArray()
|
||||||
if (enumerable is ArraySegment<T> arraySegment)
|
};
|
||||||
return arraySegment; // already an ArraySegment, doh!
|
|
||||||
|
|
||||||
return new ArraySegment<T>(enumerable.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArraySegment<T> Skip<T>(this ArraySegment<T> seg, Int32 count)
|
public static ArraySegment<T> Skip<T>(this ArraySegment<T> seg, Int32 count)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue