2023-02-16 12:57:06 +00:00
|
|
|
using System.Diagnostics;
|
|
|
|
using System.Runtime.CompilerServices;
|
2023-05-04 07:32:35 +00:00
|
|
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
2023-02-16 12:57:06 +00:00
|
|
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
|
|
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames;
|
2023-05-04 07:32:35 +00:00
|
|
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
2023-02-16 12:57:06 +00:00
|
|
|
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<System.Byte>;
|
|
|
|
using static System.Runtime.CompilerServices.MethodImplOptions;
|
|
|
|
|
|
|
|
namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
using UInt16s = IReadOnlyCollection<UInt16>;
|
|
|
|
using Booleans = IReadOnlyCollection<Boolean>;
|
2023-02-16 12:57:06 +00:00
|
|
|
|
|
|
|
public class ModbusRtuClient : ModbusClient
|
|
|
|
{
|
|
|
|
private const Int32 CrcSize = 2;
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
public ModbusRtuClient(Channel channel, Byte slaveId) : base(channel, slaveId)
|
2023-02-16 12:57:06 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
public override MbData ReadCoils(UInt16 readAddress, UInt16 nValues)
|
2023-04-20 13:03:13 +00:00
|
|
|
{
|
|
|
|
throw new NotImplementedException();
|
|
|
|
}
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
public override MbData ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues)
|
2023-02-16 12:57:06 +00:00
|
|
|
{
|
|
|
|
throw new NotImplementedException();
|
|
|
|
}
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
public override MbData ReadInputRegisters(UInt16 readAddress, UInt16 nValues)
|
2023-02-16 12:57:06 +00:00
|
|
|
{
|
2023-04-04 12:51:06 +00:00
|
|
|
var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues);
|
2023-02-16 12:57:06 +00:00
|
|
|
var crc = CalcCrc(cmd);
|
|
|
|
|
|
|
|
// TX
|
2023-05-04 07:32:35 +00:00
|
|
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
2023-02-16 12:57:06 +00:00
|
|
|
|
|
|
|
// RX
|
2023-05-04 07:32:35 +00:00
|
|
|
var response = Channel
|
|
|
|
.Read(cmd.ExpectedResponseSize + CrcSize)
|
2023-02-16 12:57:06 +00:00
|
|
|
.ToArraySegment()
|
|
|
|
.Apply(AssertCrc)
|
|
|
|
.SkipLast(CrcSize)
|
|
|
|
.Apply(ReadInputRegistersResponseFrame.Parse)
|
|
|
|
.Apply(cmd.VerifyResponse);
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
return new MbData(response.RegistersRead.RawData, readAddress, Endian);
|
2023-02-16 12:57:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
public override MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues)
|
2023-02-16 12:57:06 +00:00
|
|
|
{
|
2023-04-04 12:51:06 +00:00
|
|
|
var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues);
|
2023-02-16 12:57:06 +00:00
|
|
|
var crc = CalcCrc(cmd.Data);
|
|
|
|
|
|
|
|
// TX
|
2023-05-04 07:32:35 +00:00
|
|
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
2023-02-16 12:57:06 +00:00
|
|
|
|
|
|
|
// RX
|
2023-05-04 07:32:35 +00:00
|
|
|
var response = Channel
|
|
|
|
.Read(cmd.ExpectedResponseSize + CrcSize)
|
2023-02-16 12:57:06 +00:00
|
|
|
.ToArraySegment()
|
|
|
|
.Apply(AssertCrc)
|
|
|
|
.SkipLast(CrcSize)
|
|
|
|
.Apply(ReadHoldingRegistersResponseFrame.Parse)
|
|
|
|
.Apply(cmd.VerifyResponse);
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
return new MbData(response.RegistersRead.RawData, readAddress, Endian);
|
2023-02-16 12:57:06 +00:00
|
|
|
}
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
public override UInt16 WriteCoils(UInt16 writeAddress, Booleans coils)
|
2023-02-16 12:57:06 +00:00
|
|
|
{
|
|
|
|
throw new NotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override UInt16 WriteRegisters(UInt16 writeAddress, UInt16s values)
|
|
|
|
{
|
2023-04-04 12:51:06 +00:00
|
|
|
var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values);
|
2023-02-16 12:57:06 +00:00
|
|
|
var crc = CalcCrc(cmd);
|
|
|
|
|
|
|
|
// TX
|
2023-05-04 07:32:35 +00:00
|
|
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
2023-02-16 12:57:06 +00:00
|
|
|
|
|
|
|
// RX
|
2023-05-04 07:32:35 +00:00
|
|
|
var response = Channel
|
|
|
|
.Read(cmd.ExpectedResponseSize + CrcSize)
|
2023-02-16 12:57:06 +00:00
|
|
|
.ToArraySegment()
|
|
|
|
.Apply(AssertCrc)
|
|
|
|
.SkipLast(CrcSize)
|
|
|
|
.Apply(WriteRegistersResponseFrame.Parse)
|
|
|
|
.Apply(cmd.VerifyResponse);
|
|
|
|
|
|
|
|
return response.NbWritten;
|
|
|
|
}
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
public override MbData ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite)
|
2023-02-16 12:57:06 +00:00
|
|
|
{
|
2023-03-10 12:57:39 +00:00
|
|
|
var cmd = new ReadWriteRegistersCommandFrame(SlaveId,
|
2023-04-04 12:51:06 +00:00
|
|
|
readAddress,
|
2023-03-10 12:57:39 +00:00
|
|
|
nbToRead,
|
2023-04-04 12:51:06 +00:00
|
|
|
writeAddress,
|
2023-03-10 12:57:39 +00:00
|
|
|
registersToWrite);
|
2023-02-16 12:57:06 +00:00
|
|
|
var crc = CalcCrc(cmd);
|
|
|
|
|
|
|
|
// TX
|
2023-05-04 07:32:35 +00:00
|
|
|
cmd.Data.Concat(crc).ToList().Apply(Channel.Write);
|
2023-02-16 12:57:06 +00:00
|
|
|
|
|
|
|
// RX
|
|
|
|
var nToRead = cmd.ExpectedResponseSize + CrcSize;
|
|
|
|
var response = nToRead
|
2023-05-04 07:32:35 +00:00
|
|
|
.Apply(Channel.Read)
|
2023-02-16 12:57:06 +00:00
|
|
|
.ToArraySegment()
|
|
|
|
.SkipLast(CrcSize)
|
|
|
|
.Apply(ReadWriteRegistersResponseFrame.Parse)
|
|
|
|
.Apply(cmd.VerifyResponse);
|
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
return new MbData(response.RegistersRead.RawData, readAddress, Endian);
|
2023-02-16 12:57:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static ArraySegment<Byte> AssertCrc(ArraySegment<Byte> data)
|
|
|
|
{
|
|
|
|
var expectedCrc = data.SkipLast(CrcSize).Apply(CalcCrc);
|
|
|
|
var actualCrc = data.TakeLast(CrcSize);
|
2023-04-20 13:03:13 +00:00
|
|
|
|
2023-02-16 12:57:06 +00:00
|
|
|
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<Byte> data) => CalcCrc((IEnumerable<Byte>)data);
|
|
|
|
|
|
|
|
private static Bytes CalcCrc(IEnumerable<Byte> 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
|
|
|
|
}
|
|
|
|
}
|