using System.Diagnostics; using System.Runtime.CompilerServices; using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Protocol; 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.Replies; using InnovEnergy.Lib.Protocols.Modbus.Util; using InnovEnergy.Lib.Utils; using Bytes = System.Collections.Generic.IReadOnlyList; using static System.Runtime.CompilerServices.MethodImplOptions; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; using UInt16s = IReadOnlyCollection; using Booleans = IReadOnlyCollection; public class ModbusRtuClient : ModbusClient { private const Int32 CrcSize = 2; public ModbusRtuClient(Channel channel, Byte slaveId) : base(channel, slaveId) { } public override MbData ReadCoils(UInt16 readAddress, UInt16 nValues) { throw new NotImplementedException(); } public override MbData ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) { throw new NotImplementedException(); } public override MbData ReadInputRegisters(UInt16 readAddress, UInt16 nValues) { var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues); var crc = CalcCrc(cmd); // TX cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX var response = Channel .Read(cmd.ExpectedResponseSize + CrcSize) .ToArraySegment() .Apply(AssertCrc) .SkipLast(CrcSize) .Apply(ReadInputRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); return new MbData(response.RegistersRead.RawData, readAddress, Endian); } public override MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) { var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); var crc = CalcCrc(cmd.Data); // TX cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX var response = Channel .Read(cmd.ExpectedResponseSize + CrcSize) .ToArraySegment() .Apply(AssertCrc) .SkipLast(CrcSize) .Apply(ReadHoldingRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); return new MbData(response.RegistersRead.RawData, readAddress, Endian); } public override UInt16 WriteCoils(UInt16 writeAddress, Booleans coils) { throw new NotImplementedException(); } public override UInt16 WriteRegisters(UInt16 writeAddress, UInt16s values) { var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values); var crc = CalcCrc(cmd); // TX cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX var response = Channel .Read(cmd.ExpectedResponseSize + CrcSize) .ToArraySegment() .Apply(AssertCrc) .SkipLast(CrcSize) .Apply(WriteRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); return response.NbWritten; } public override MbData ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) { var cmd = new ReadWriteRegistersCommandFrame(SlaveId, readAddress, nbToRead, writeAddress, registersToWrite); var crc = CalcCrc(cmd); // TX cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX var nToRead = cmd.ExpectedResponseSize + CrcSize; var response = nToRead .Apply(Channel.Read) .ToArraySegment() .SkipLast(CrcSize) .Apply(ReadWriteRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); return new MbData(response.RegistersRead.RawData, readAddress, Endian); } 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); return data; } // not used ATM, see AssertCrc public static Boolean CheckCrc(Bytes data) { var expectedCrc = data.SkipLast(CrcSize).Apply(CalcCrc); var actualCrc = data.TakeLast(CrcSize); return actualCrc.SequenceEqual(expectedCrc); } [DebuggerStepThrough][MethodImpl(AggressiveInlining)] private static Bytes CalcCrc(ModbusFrame frame) => CalcCrc(frame.Data); [DebuggerStepThrough][MethodImpl(AggressiveInlining)] private static Bytes CalcCrc(ArraySegment data) => CalcCrc((IEnumerable)data); private static Bytes CalcCrc(IEnumerable data) { UInt16 crc = 0xFFFF; foreach (var b in data) { crc ^= b; for (var bit = 0; bit < 8; bit++) { var bit0 = (crc & 0x0001) != 0; crc >>= 1; if (bit0) crc ^= 0xA001; } } var hi = 0xFF & crc; var lo = (crc >> 8) & 0xFF; return new[] { (Byte)hi, (Byte)lo }; // big endian } }