diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs index 0a4ca6948..0e2e7d158 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs @@ -1,54 +1,35 @@ using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Protocols.Modbus.Conversions; -using InnovEnergy.Lib.Protocols.Modbus.Protocol; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; -using Coils = IReadOnlyList; +using Booleans = IReadOnlyList; +using UInt16s = IReadOnlyList; // TODO: ModbusClient = Framer(TCP/RTU) + Connection(Serial/TCP) + Encoder(binary/ascii) + quirk(+1/0) // Transport public abstract class ModbusClient { - protected ModbusConnection Connection { get; } - protected Byte SlaveId { get; } - + internal ModbusConnection Connection { get; } + internal Byte SlaveId { get; } // TODO: add additional functions: coils... - - public abstract Coils ReadDiscreteInputs (UInt16 readAddress, UInt16 nValues); - public abstract ModbusRegisters ReadInputRegisters (UInt16 readAddress, UInt16 nValues); - public abstract ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues); - public abstract UInt16 WriteMultipleCoils (UInt16 writeAddress, Coils coils); - public abstract UInt16 WriteRegisters (UInt16 writeAddress, IReadOnlyList values); - public abstract ModbusRegisters ReadWriteRegisters (UInt16 readAddress, UInt16 nbToRead, - UInt16 writeAddress, IReadOnlyList registersToWrite); - - public UInt16 WriteMultipleCoils(UInt16 writeAddress, params Boolean[] coils) - { - return WriteMultipleCoils(writeAddress, (IReadOnlyList)coils); - } - - public UInt16 WriteRegisters(ModbusRegisters registers) - { - return WriteRegisters(registers.StartRegister, registers); - } - public UInt16 WriteRegisters(UInt16 writeAddress, params UInt16[] values) - { - return WriteRegisters(writeAddress, (IReadOnlyList)values); - } - - protected ModbusClient(ModbusConnection connection, - Byte slaveId) - { - Connection = connection; - SlaveId = slaveId; - } + public abstract Booleans ReadCoils (UInt16 readAddress, UInt16 nValues); + public abstract Booleans ReadDiscreteInputs (UInt16 readAddress, UInt16 nValues); + public abstract UInt16s ReadInputRegisters (UInt16 readAddress, UInt16 nValues); + public abstract UInt16s ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues); + public abstract UInt16 WriteCoils (UInt16 writeAddress, Booleans coils); + public abstract UInt16 WriteRegisters (UInt16 writeAddress, UInt16s values); - public void CloseConnection() => Connection.Close(); + public abstract UInt16s ReadWriteRegisters (UInt16 readAddress, + UInt16 nbToRead, + UInt16 writeAddress, + UInt16s registersToWrite); - -} - + protected ModbusClient(ModbusConnection connection, Byte slaveId) + { + Connection = connection; + SlaveId = slaveId; + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs index 24171c847..4e3afe361 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Protocols.Modbus.Conversions; using InnovEnergy.Lib.Protocols.Modbus.Protocol; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; @@ -23,12 +22,17 @@ public class ModbusRtuClient : ModbusClient { } + public override IReadOnlyList ReadCoils(UInt16 readAddress, UInt16 nValues) + { + throw new NotImplementedException(); + } + public override IReadOnlyList ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) { throw new NotImplementedException(); } - public override ModbusRegisters ReadInputRegisters(UInt16 readAddress, UInt16 nValues) + public override UInt16s ReadInputRegisters(UInt16 readAddress, UInt16 nValues) { var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues); var crc = CalcCrc(cmd); @@ -48,11 +52,11 @@ public class ModbusRtuClient : ModbusClient .Apply(ReadInputRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); - return new ModbusRegisters(readAddress, response.RegistersRead.ToArray()); + return response.RegistersRead; } - public override ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) + public override UInt16s ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) { var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); var crc = CalcCrc(cmd.Data); @@ -71,10 +75,10 @@ public class ModbusRtuClient : ModbusClient .Apply(ReadHoldingRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); - return new ModbusRegisters(readAddress, response.RegistersRead.ToArray()); + return response.RegistersRead; } - public override UInt16 WriteMultipleCoils(UInt16 writeAddress, IReadOnlyList coils) + public override UInt16 WriteCoils(UInt16 writeAddress, IReadOnlyList coils) { throw new NotImplementedException(); } @@ -103,7 +107,7 @@ public class ModbusRtuClient : ModbusClient return response.NbWritten; } - public override ModbusRegisters ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) + public override UInt16s ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) { var cmd = new ReadWriteRegistersCommandFrame(SlaveId, readAddress, @@ -125,14 +129,14 @@ public class ModbusRtuClient : ModbusClient .Apply(ReadWriteRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); - return new ModbusRegisters(readAddress, response.RegistersRead.ToArray()); + return response.RegistersRead; } public static ArraySegment AssertCrc(ArraySegment data) { var expectedCrc = data.SkipLast(CrcSize).Apply(CalcCrc); var actualCrc = data.TakeLast(CrcSize); - + if (!actualCrc.SequenceEqual(expectedCrc)) throw new CrcException(expectedCrc, actualCrc); diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusSlave.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusSlave.cs new file mode 100644 index 000000000..0d854cb9f --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusSlave.cs @@ -0,0 +1,159 @@ +using InnovEnergy.Lib.Protocols.Modbus.Reflection; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +namespace InnovEnergy.Lib.Protocols.Modbus.Clients; + +public class ModbusSlave 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 ReadDiscreteInputs(Object statusRecord) + { + return from batch in statusRecord.GetModbusBooleanBatches() + 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 ReadCoils(Object statusRecord) + { + return from batch in statusRecord.GetModbusBooleanBatches() + 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 ReadInputRegisters(Object statusRecord) + { + return from batch in statusRecord.GetModbusRegisterBatches() + 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 ReadHoldingRegisters(Object statusRecord) + { + return from batch in statusRecord.GetModbusRegisterBatches() + 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()) + { + 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()) + { + var values = batch.GetRawModbusValuesFromControlRecord(); + _ModbusClient.WriteRegisters(batch[0].Address, values); + nBatches++; + } + + return nBatches; + } + + +} + +public class ModbusSlave : ModbusSlave where T : notnull, new() +{ + public ModbusSlave(ModbusClient modbusClient) : base(modbusClient) + { + } +} + +public static class ModbusSlave +{ + public static ModbusSlave Slave(this ModbusClient modbusClient) where T : notnull, new() + { + return new ModbusSlave(modbusClient); + } + + public static ModbusSlave Slave(this ModbusClient modbusClient) where S : notnull, new() where C : notnull + { + return new ModbusSlave(modbusClient); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs index 795cb9189..badcff48a 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs @@ -1,15 +1,15 @@ using System.Diagnostics; using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Protocols.Modbus.Conversions; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; using InnovEnergy.Lib.Protocols.Modbus.Tcp; +using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; using UInt16s = IReadOnlyList; -using Coils = IReadOnlyList; + public class ModbusTcpClient : ModbusClient { @@ -24,7 +24,26 @@ public class ModbusTcpClient : ModbusClient { } - + public override IReadOnlyList ReadCoils(UInt16 readAddress, UInt16 nValues) + { + var id = NextId(); // TODO: check response id + + var cmd = new ReadCoilsCommandFrame(SlaveId, readAddress, nValues); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Connection.Transmit(frm.Data); + + var hData = Connection.Receive(MbapHeader.Size).ToArray(); // TODO: optimize .ToArray() ? + var rxHdr = new MbapHeader(hData); + + var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); // TODO: optimize .ToArray() ? + var rxFrm = ReadCoilsResponseFrame.Parse(fData); + + return cmd.VerifyResponse(rxFrm).Inputs; + } + + public override IReadOnlyList ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) { var id = NextId(); // TODO: check response id @@ -39,12 +58,12 @@ public class ModbusTcpClient : ModbusClient var rxHdr = new MbapHeader(hData); var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); // TODO: optimize .ToArray() ? - var rxFrm = ReadDiscreteInputResponseFrame.Parse(fData); + var rxFrm = ReadDiscreteInputsResponseFrame.Parse(fData); return cmd.VerifyResponse(rxFrm).Inputs; } - public override ModbusRegisters ReadInputRegisters(UInt16 readAddress, UInt16 nValues) + public override IReadOnlyList ReadInputRegisters(UInt16 readAddress, UInt16 nValues) { var id = NextId(); // TODO: check response id @@ -62,13 +81,11 @@ public class ModbusTcpClient : ModbusClient var verified = cmd.VerifyResponse(rxFrm); - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); + return verified.RegistersRead; } - - - public override ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) + public override IReadOnlyList ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) { var id = NextId(); // TODO: check response id var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); @@ -77,20 +94,20 @@ public class ModbusTcpClient : ModbusClient Connection.Transmit(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); + var hData = Connection.Receive(MbapHeader.Size).ToArray(MbapHeader.Size); var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); + var fData = Connection.Receive(rxHdr.FrameLength).ToArray(rxHdr.FrameLength); var rxFrm = ReadHoldingRegistersResponseFrame.Parse(fData); var verified = cmd.VerifyResponse(rxFrm); - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); // TODO + return verified.RegistersRead; // TODO } - public override UInt16 WriteMultipleCoils(UInt16 writeAddress, Coils coils) + public override UInt16 WriteCoils(UInt16 writeAddress, IReadOnlyList coils) { - var id = NextId(); // TODO: check response id + var id = NextId(); // TODO: check response id var cmd = new WriteCoilsCommandFrame(SlaveId, writeAddress, coils); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); @@ -105,8 +122,8 @@ public class ModbusTcpClient : ModbusClient return cmd.VerifyResponse(rxFrm).NbWritten; } - - + + public override UInt16 WriteRegisters(UInt16 writeAddress, UInt16s values) { var id = NextId(); // TODO: check response id @@ -127,10 +144,8 @@ public class ModbusTcpClient : ModbusClient return verified.NbWritten; } - public override ModbusRegisters ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) + public override IReadOnlyList ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) { - - var id = NextId(); // TODO: check response id var cmd = new ReadWriteRegistersCommandFrame(SlaveId, @@ -144,15 +159,15 @@ public class ModbusTcpClient : ModbusClient Connection.Transmit(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); + var hData = Enumerable.ToArray(Connection.Receive(MbapHeader.Size)); var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); + var fData = Enumerable.ToArray(Connection.Receive(rxHdr.FrameLength)); var rxFrm = ReadWriteRegistersResponseFrame.Parse(fData); var verified = cmd.VerifyResponse(rxFrm); - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); // TODO + return verified.RegistersRead; // TODO } } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs index 7c75a7354..17b4c96c1 100644 --- a/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs +++ b/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs @@ -14,7 +14,7 @@ public class ModbusTcpConnection : ModbusConnection public ModbusTcpConnection(String hostname, Int32 port = 502) { Hostname = hostname; - Port = port; + Port = port; } public override IReadOnlyList Receive(UInt16 bytesToRead) diff --git a/csharp/Lib/Protocols/Modbus/Connections/PushToPullHelper.cs b/csharp/Lib/Protocols/Modbus/Connections/PushToPullHelper.cs new file mode 100644 index 000000000..ad78f46e8 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Connections/PushToPullHelper.cs @@ -0,0 +1,38 @@ +using System.Reactive.Linq; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Connections; + +public static class PushToPullHelper +{ + // TODO: this is incredibly hacky, improve + + public static Func>> PushToPull(this IObservable src) + { + var buffer = new Queue(); + + var nAvailable = src + .Do(buffer.Enqueue) + .Select(_ => buffer.Count) + .Publish(); + + nAvailable.Connect(); + + async Task> Read(Int32 n) + { + var available = buffer.Count; + if (available < n) + available = await nAvailable.FirstOrDefaultAsync(a => a >= n); + + if (available < n) + throw new Exception("Connection closed"); + + return Enumerable + .Range(0, n) + .Select(_ => buffer.Dequeue()) + .ToArray(n); + } + + return Read; + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Connections/RemoteSerialConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/RemoteSerialConnection.cs new file mode 100644 index 000000000..7140b62ed --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Connections/RemoteSerialConnection.cs @@ -0,0 +1,113 @@ +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>>? _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 Receive(UInt16 bytesToRead) + { + Open(); + + return _Read!(bytesToRead).Result; + } + + public override void Transmit(IEnumerable 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 + + + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Modbus.csproj b/csharp/Lib/Protocols/Modbus/Modbus.csproj index 70b28e699..0bcc73c8c 100644 --- a/csharp/Lib/Protocols/Modbus/Modbus.csproj +++ b/csharp/Lib/Protocols/Modbus/Modbus.csproj @@ -4,6 +4,11 @@ AnyCPU;linux-arm + + + false + + diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadCoilsCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadCoilsCommandFrame.cs new file mode 100644 index 000000000..edb4548de --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadCoilsCommandFrame.cs @@ -0,0 +1,50 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; + +internal class ReadCoilsCommandFrame : ModbusFrame +{ + private const Int32 Size = 6; + + private MbWord ReadAddress => Data.WordAt(2); + private MbWord QuantityOfInputs => Data.WordAt(4); + + + public ReadCoilsCommandFrame(Byte slave, UInt16 readAddress, UInt16 nBits) : base(Size) + { + if (nBits > Constants.MaxCoils) + throw new ArgumentOutOfRangeException($"Maximum number of registers ({Constants.MaxCoils}) exceeeded!", nameof(nBits)); + + SlaveAddress .Set(slave); + FunctionCode .Set(Protocol.FunctionCode.ReadCoils); + ReadAddress .Set(readAddress); + QuantityOfInputs.Set(nBits); + } + + + private ReadCoilsCommandFrame(ArraySegment data) : base(data) + { + if (data.Count != Size) + throw new ArgumentException($"Expecting an array of size {Size}", nameof(data)); + + AssertFunctionCode(Protocol.FunctionCode.ReadCoils); + } + + public ReadCoilsResponseFrame VerifyResponse(ReadCoilsResponseFrame response) + { + if (response.SlaveAddress != SlaveAddress) + throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress); + + if (response.Inputs.Count != Math.Ceiling(QuantityOfInputs / 8.0).ConvertTo() * 8) + throw new UnexpectedResponseFieldException(nameof(response.Inputs), QuantityOfInputs.ToString(), response.Inputs.Count); + + return response; + } + + public static ReadCoilsCommandFrame Parse(ArraySegment data) + { + return new ReadCoilsCommandFrame(data); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs index 325d88e5c..6133ad74f 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs @@ -37,7 +37,7 @@ internal class ReadDiscreteInputsCommandFrame : ModbusFrame AssertFunctionCode(ReadDiscreteInputs); } - public ReadDiscreteInputResponseFrame VerifyResponse(ReadDiscreteInputResponseFrame response) + public ReadDiscreteInputsResponseFrame VerifyResponse(ReadDiscreteInputsResponseFrame response) { if (response.SlaveAddress != SlaveAddress) throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadCoilsResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadCoilsResponseFrame.cs new file mode 100644 index 000000000..916bf0d96 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadCoilsResponseFrame.cs @@ -0,0 +1,51 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; + +public class ReadCoilsResponseFrame : ModbusFrame +{ + private new const Int32 MinSize = 3; + + private MbByte ByteCount => Data.ByteAt(2); + public MbBits Inputs => Data.BitsAt(3); + + public ReadCoilsResponseFrame(Byte slave, IReadOnlyList inputs) : base (inputs.Count) + { + var nBytes = Math.Ceiling(inputs.Count / 8.0).ConvertTo(); + + SlaveAddress .Set(slave); + FunctionCode .Set(Protocol.FunctionCode.ReadCoils); + ByteCount .Set(nBytes); + Inputs .Set(inputs); + } + + private ReadCoilsResponseFrame(Byte[] data) : this(new ArraySegment(data)) + { } + + + private ReadCoilsResponseFrame(ArraySegment data) : base(data) + { + + AssertFunctionCode(Protocol.FunctionCode.ReadCoils); + + // TODO + // var expectedSize = ByteCount + MinSize; + // if (data.Count != expectedSize) + // throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data)); + // if (data.Count < MinSize) + // throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data)); + + } + + + public static ReadCoilsResponseFrame Parse(ModbusFrame rawFrame) + { + return new ReadCoilsResponseFrame(rawFrame.Data); + } + + public static ReadCoilsResponseFrame Parse(ArraySegment data) + { + return new ReadCoilsResponseFrame(data); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputsResponseFrame.cs similarity index 65% rename from csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputsResponseFrame.cs index 438523786..e05234866 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputsResponseFrame.cs @@ -7,14 +7,14 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; using Booleans = IReadOnlyList; using MbFc = MbByte; -public class ReadDiscreteInputResponseFrame : ModbusFrame +public class ReadDiscreteInputsResponseFrame : ModbusFrame { private new const Int32 MinSize = 3; private MbByte ByteCount => Data.ByteAt(2); public MbBits Inputs => Data.BitsAt(3); - public ReadDiscreteInputResponseFrame(Byte slave, Booleans inputs) : base (inputs.Count) + public ReadDiscreteInputsResponseFrame(Byte slave, Booleans inputs) : base (inputs.Count) { var nBytes = Math.Ceiling(inputs.Count / 8.0).ConvertTo(); @@ -24,11 +24,11 @@ public class ReadDiscreteInputResponseFrame : ModbusFrame Inputs .Set(inputs); } - private ReadDiscreteInputResponseFrame(Byte[] data) : this(new ArraySegment(data)) + private ReadDiscreteInputsResponseFrame(Byte[] data) : this(new ArraySegment(data)) { } - private ReadDiscreteInputResponseFrame(ArraySegment data) : base(data) + private ReadDiscreteInputsResponseFrame(ArraySegment data) : base(data) { AssertFunctionCode(ReadDiscreteInputs); @@ -43,13 +43,13 @@ public class ReadDiscreteInputResponseFrame : ModbusFrame } - public static ReadDiscreteInputResponseFrame Parse(ModbusFrame rawFrame) + public static ReadDiscreteInputsResponseFrame Parse(ModbusFrame rawFrame) { - return new ReadDiscreteInputResponseFrame(rawFrame.Data); + return new ReadDiscreteInputsResponseFrame(rawFrame.Data); } - public static ReadDiscreteInputResponseFrame Parse(ArraySegment data) + public static ReadDiscreteInputsResponseFrame Parse(ArraySegment data) { - return new ReadDiscreteInputResponseFrame(data); + return new ReadDiscreteInputsResponseFrame(data); } } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs b/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs index a21f30d94..101175d7a 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs @@ -1,39 +1,42 @@ +using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode; + namespace InnovEnergy.Lib.Protocols.Modbus.Protocol; [Flags] public enum FunctionCode : byte { - ReadCoil = 1, ReadCoilError = ReadCoil | Error , - ReadDiscreteInputs = 2, ReadDiscreteInputsError = ReadDiscreteInputs | Error , - ReadHoldingRegisters = 3, ReadHoldingRegistersError = ReadHoldingRegisters | Error , - ReadInputRegisters = 4, ReadInputRegistersError = ReadInputRegisters | Error , + ReadCoils = 1, + ReadDiscreteInputs = 2, + ReadHoldingRegisters = 3, + ReadInputRegisters = 4, - WriteSingleCoil = 5, WriteSingleCoilError = WriteSingleCoil | Error , - WriteSingleRegister = 6, WriteSingleRegisterError = WriteSingleRegister | Error , - WriteMultipleCoils = 15, WriteMultipleCoilsError = WriteMultipleCoils | Error , - WriteMultipleRegisters = 16, WriteMultipleRegistersError = WriteMultipleRegisters | Error , + WriteSingleCoil = 5, + WriteSingleRegister = 6, + WriteMultipleCoils = 15, + WriteMultipleRegisters = 16, - ReadWriteMultipleRegisters = 23, ReadWriteMultipleRegistersError = ReadWriteMultipleRegisters | Error , + ReadWriteMultipleRegisters = 23, Error = 128 } + public static class FunctionCodeExtensions { public static Boolean IsWriteOnly(this FunctionCode fc) { - return fc == FunctionCode.WriteSingleCoil || - fc == FunctionCode.WriteSingleRegister || - fc == FunctionCode.WriteMultipleCoils || - fc == FunctionCode.WriteMultipleRegisters; + return fc is WriteSingleCoil + or WriteSingleRegister + or WriteMultipleCoils + or WriteMultipleRegisters; } public static Boolean IsMultiWrite(this FunctionCode fc) { - return fc == FunctionCode.WriteMultipleCoils || - fc == FunctionCode.WriteMultipleRegisters || - fc == FunctionCode.ReadWriteMultipleRegisters; + return fc is WriteMultipleCoils + or WriteMultipleRegisters + or ReadWriteMultipleRegisters; } public static Boolean IsError(this FunctionCode fc) @@ -47,6 +50,5 @@ public static class FunctionCodeExtensions { return fc | FunctionCode.Error; } - } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/CoilAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/CoilAttribute.cs new file mode 100644 index 000000000..833f64208 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/CoilAttribute.cs @@ -0,0 +1,6 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class CoilAttribute : ModbusBooleanAttribute +{ + public CoilAttribute(UInt16 address) : base(address) { } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInputAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInputAttribute.cs new file mode 100644 index 000000000..6902b0454 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInputAttribute.cs @@ -0,0 +1,8 @@ + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class DiscreteInputAttribute : ModbusBooleanAttribute +{ + public DiscreteInputAttribute(UInt16 address) : base(address) { } + +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegisterAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegisterAttribute.cs new file mode 100644 index 000000000..8ecb7f333 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegisterAttribute.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class HoldingRegisterAttribute : ModbusRegisterAttribute +{ + public HoldingRegisterAttribute(UInt16 address) : base(address) { } + +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegisterAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegisterAttribute.cs new file mode 100644 index 000000000..6c53233c3 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegisterAttribute.cs @@ -0,0 +1,7 @@ + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class InputRegisterAttribute : ModbusRegisterAttribute +{ + public InputRegisterAttribute(UInt16 address) : base(address) { } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs new file mode 100644 index 000000000..c67548fe6 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public abstract class ModbusAttribute : Attribute +{ + public UInt16 Address { get; } + protected ModbusAttribute(UInt16 address) { Address = address; } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBooleanAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBooleanAttribute.cs new file mode 100644 index 000000000..df2f55c11 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBooleanAttribute.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class ModbusBooleanAttribute : ModbusAttribute +{ + protected ModbusBooleanAttribute(UInt16 address) : base(address) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegisterAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegisterAttribute.cs new file mode 100644 index 000000000..e8e1d8501 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegisterAttribute.cs @@ -0,0 +1,11 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public abstract class ModbusRegisterAttribute : ModbusAttribute +{ + protected ModbusRegisterAttribute(UInt16 address) : base(address) + { + } + + public Double Scale { get; init; } = 1; + public Double Offset { get; init; } = 0; +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Batches.cs b/csharp/Lib/Protocols/Modbus/Reflection/Batches.cs new file mode 100644 index 000000000..c98cc1aa4 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Batches.cs @@ -0,0 +1,100 @@ +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; +using ModbusBooleans = IReadOnlyList; +using UInt16s = IReadOnlyList; +using Booleans = IReadOnlyList; + +internal static class Batches +{ + + internal static IEnumerable GetModbusRegisterBatches(this Object statusRecord) where T : ModbusRegisterAttribute + { + var modbusRegisters = from mi in statusRecord.GetDataMembers() + from a in mi.GetCustomAttributes() + select new ModbusRegister(a, mi, statusRecord); + + return modbusRegisters.SplitIntoFrameSizedBatches(); + } + + + internal static IEnumerable GetModbusBooleanBatches(this Object statusRecord) where T : ModbusBooleanAttribute + { + var modbusBooleans = from mi in statusRecord.GetDataMembers() + from a in mi.GetCustomAttributes() + select new ModbusBoolean(a, mi, statusRecord); + + return modbusBooleans.SplitIntoFrameSizedBatches(); + } + + + private static IEnumerable 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(props); + } + + + private static IEnumerable SplitIntoFrameSizedBatches(this IEnumerable 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 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 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); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/IAddress.cs b/csharp/Lib/Protocols/Modbus/Reflection/IAddress.cs new file mode 100644 index 000000000..5228d8661 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/IAddress.cs @@ -0,0 +1,6 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection; + +internal interface IAddress +{ + UInt16 Address { get; } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/ModbusBoolean.cs b/csharp/Lib/Protocols/Modbus/Reflection/ModbusBoolean.cs new file mode 100644 index 000000000..0178f681d --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/ModbusBoolean.cs @@ -0,0 +1,54 @@ +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() + .ConvertTo(); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/ModbusRegister.cs b/csharp/Lib/Protocols/Modbus/Reflection/ModbusRegister.cs new file mode 100644 index 000000000..25942638a --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/ModbusRegister.cs @@ -0,0 +1,98 @@ +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() + .ConvertTo() / Scale - Offset; + + return IsUnsigned + ? transformed.ConvertTo() + : transformed.ConvertTo().CastTo(); + } + + +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs b/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs index bad3ee640..9c069d02b 100644 --- a/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs +++ b/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs @@ -1,4 +1,3 @@ -using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; namespace InnovEnergy.Lib.Protocols.Modbus.Tcp;