Modbus V2
This commit is contained in:
parent
045de6ac0d
commit
7a44b1f307
|
@ -129,8 +129,8 @@ public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
|
||||||
String tty,
|
String tty,
|
||||||
Int32 baudRate,
|
Int32 baudRate,
|
||||||
Parity parity,
|
Parity parity,
|
||||||
Int32 stopBits,
|
Int32 dataBits,
|
||||||
Int32 dataBits)
|
Int32 stopBits)
|
||||||
{
|
{
|
||||||
const Int32 port = 6855;
|
const Int32 port = 6855;
|
||||||
|
|
||||||
|
@ -145,7 +145,10 @@ public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
|
||||||
var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw";
|
var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw";
|
||||||
|
|
||||||
//var script = $"-n -o RemoteCommand='{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}'";
|
//var script = $"-n -o RemoteCommand='{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}'";
|
||||||
var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}";
|
//var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}";
|
||||||
|
|
||||||
|
//var script = $"{configureTty} && {socat} ; {KillTasks}";
|
||||||
|
var script = $"{configureTty} && {socat}";
|
||||||
|
|
||||||
_Command = host.Command.AppendArgument(script);
|
_Command = host.Command.AppendArgument(script);
|
||||||
|
|
||||||
|
@ -179,9 +182,9 @@ public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
|
||||||
|
|
||||||
protected override TcpChannel Open()
|
protected override TcpChannel Open()
|
||||||
{
|
{
|
||||||
_CommandTask ??= _Command.ExecuteAsync(_CancellationTokenSource.Token);
|
//_CommandTask ??= _Command.ExecuteAsync(_CancellationTokenSource.Token);
|
||||||
|
|
||||||
Thread.Sleep(2000); // wait until socat is ready
|
//Thread.Sleep(2000); // wait until socat is ready
|
||||||
return _TcpChannel;
|
return _TcpChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using CliWrap;
|
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
@ -16,10 +15,7 @@ public class SerialPortChannel : ConnectionChannel<SerialPort>
|
||||||
Boolean closeAfterSuccessfulRead = false,
|
Boolean closeAfterSuccessfulRead = false,
|
||||||
Boolean closeAfterSuccessfulWrite = false)
|
Boolean closeAfterSuccessfulWrite = false)
|
||||||
:
|
:
|
||||||
base(
|
base(closeAfterSuccessfulRead, closeAfterSuccessfulWrite)
|
||||||
closeAfterSuccessfulRead,
|
|
||||||
closeAfterSuccessfulWrite
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var sb = stopBits switch
|
var sb = stopBits switch
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<Import Project="../../InnovEnergy.Lib.props"/>
|
||||||
<Configurations>Debug;Release;Release-Server</Configurations>
|
|
||||||
<Platforms>AnyCPU;linux-arm</Platforms>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="../../InnovEnergy.Lib.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<ItemGroup>
|
||||||
<IsTrimmable>false</IsTrimmable>
|
<ProjectReference Include="../../Utils/Utils.csproj"/>
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ProjectReference Include="../../SrcGen/SrcGen.csproj"
|
||||||
<ProjectReference Include="../../Utils/Utils.csproj" />
|
OutputItemType="Analyzer"
|
||||||
</ItemGroup>
|
ReferenceOutputAssembly="false"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
<ItemGroup>
|
||||||
</ItemGroup>
|
<PackageReference Include="System.IO.Ports" Version="7.0.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,19 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
|
||||||
public enum Endian
|
public enum Endian
|
||||||
{
|
{
|
||||||
Little,
|
Undefined = 0,
|
||||||
Big
|
Little = 1,
|
||||||
|
Big = 2,
|
||||||
|
Default = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class EndianCombinator
|
||||||
|
{
|
||||||
|
public static Endian InheritFrom(this Endian child, Endian parent)
|
||||||
|
{
|
||||||
|
return child == Endian.Undefined
|
||||||
|
? parent
|
||||||
|
: child;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ using Float64 = Double;
|
||||||
|
|
||||||
// switch exhaustion
|
// switch exhaustion
|
||||||
#pragma warning disable CS8524
|
#pragma warning disable CS8524
|
||||||
|
#pragma warning disable CS8509
|
||||||
|
|
||||||
public struct MbData
|
public struct MbData
|
||||||
{
|
{
|
||||||
|
@ -18,24 +19,24 @@ public struct MbData
|
||||||
private readonly Endian _Endian;
|
private readonly Endian _Endian;
|
||||||
private readonly UInt16 _StartAddress;
|
private readonly UInt16 _StartAddress;
|
||||||
|
|
||||||
public static MbData Registers(UInt16 startAddress, UInt16 nRegisters)
|
public static MbData Registers(UInt16 startAddress, UInt16 nRegisters, Endian endian = Endian.Default)
|
||||||
{
|
{
|
||||||
if (nRegisters > Constants.MaxRegs)
|
if (nRegisters > Constants.MaxRegs)
|
||||||
throw new ArgumentException(nameof(nRegisters));
|
throw new ArgumentException(nameof(nRegisters));
|
||||||
|
|
||||||
var nBytes = nRegisters * 2;
|
var nBytes = nRegisters * 2;
|
||||||
var data = new Byte[nBytes];
|
var data = new Byte[nBytes];
|
||||||
return new MbData(data, startAddress, Endian.Big); // endian has no influence on coils
|
return new MbData(data, startAddress, endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MbData Coils(UInt16 startAddress, UInt16 nCoils)
|
public static MbData Coils(UInt16 startAddress, UInt16 nCoils, Endian endian = Endian.Default)
|
||||||
{
|
{
|
||||||
if (nCoils > Constants.MaxCoils)
|
if (nCoils > Constants.MaxCoils)
|
||||||
throw new ArgumentException(nameof(nCoils));
|
throw new ArgumentException(nameof(nCoils));
|
||||||
|
|
||||||
var nBytes = Math.Ceiling(nCoils / 8.0).ConvertTo<UInt16>();
|
var nBytes = Math.Ceiling(nCoils / 8.0).ConvertTo<UInt16>();
|
||||||
var data = new Byte[nBytes];
|
var data = new Byte[nBytes];
|
||||||
return new MbData(data, startAddress, Endian.Big); // endian has no influence on coils
|
return new MbData(data, startAddress, endian); // endian has no influence on coils
|
||||||
}
|
}
|
||||||
|
|
||||||
internal MbData(ArraySegment<Byte> data, UInt16 startAddress, Endian endian)
|
internal MbData(ArraySegment<Byte> data, UInt16 startAddress, Endian endian)
|
||||||
|
@ -48,6 +49,11 @@ public struct MbData
|
||||||
_Data = data;
|
_Data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MbData WithEndian(Endian endian)
|
||||||
|
{
|
||||||
|
return new MbData(_Data, _StartAddress, endian.InheritFrom(_Endian));
|
||||||
|
}
|
||||||
|
|
||||||
#region Coils
|
#region Coils
|
||||||
|
|
||||||
public IReadOnlyList<Boolean> GetCoils()
|
public IReadOnlyList<Boolean> GetCoils()
|
||||||
|
@ -78,7 +84,12 @@ public struct MbData
|
||||||
var byteIndex = index / 8;
|
var byteIndex = index / 8;
|
||||||
var bitIndex = index % 8;
|
var bitIndex = index % 8;
|
||||||
|
|
||||||
_Data[byteIndex] = (Byte)(_Data[byteIndex] | (1 << bitIndex)) ;
|
var mask = 1 << bitIndex;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
_Data[byteIndex] |= (Byte)mask;
|
||||||
|
else
|
||||||
|
_Data[byteIndex] &= (Byte)~mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Coils
|
#endregion Coils
|
||||||
|
@ -119,10 +130,11 @@ public struct MbData
|
||||||
var hi = (UInt32) GetUInt16(address);
|
var hi = (UInt32) GetUInt16(address);
|
||||||
var lo = (UInt32) GetUInt16(++address);
|
var lo = (UInt32) GetUInt16(++address);
|
||||||
|
|
||||||
|
|
||||||
return _Endian switch
|
return _Endian switch
|
||||||
{
|
{
|
||||||
Endian.Big => hi << 16 | lo,
|
Endian.Big => hi << 16 | lo,
|
||||||
Endian.Little => lo << 16 | hi,
|
Endian.Little => lo << 16 | hi,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +237,7 @@ public struct MbData
|
||||||
|
|
||||||
return Enumerable
|
return Enumerable
|
||||||
.Range(0, nRegisters)
|
.Range(0, nRegisters)
|
||||||
|
.Select(r => r * 2)
|
||||||
.Select(GetRegister)
|
.Select(GetRegister)
|
||||||
.ToArray(nRegisters);
|
.ToArray(nRegisters);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,11 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame
|
||||||
|
|
||||||
private ReadHoldingRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
private ReadHoldingRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
||||||
{
|
{
|
||||||
|
AssertFunctionCode(ReadHoldingRegisters);
|
||||||
|
|
||||||
if (data.Count < MinSize)
|
if (data.Count < MinSize)
|
||||||
throw new ArgumentOutOfRangeException($"Expecting an array of size {MinSize} or more", nameof(data));
|
throw new ArgumentOutOfRangeException($"Expecting an array of size {MinSize} or more", nameof(data));
|
||||||
|
|
||||||
AssertFunctionCode(ReadHoldingRegisters);
|
|
||||||
|
|
||||||
var expectedSize = ByteCount + MinSize;
|
var expectedSize = ByteCount + MinSize;
|
||||||
if (data.Count != expectedSize)
|
if (data.Count != expectedSize)
|
||||||
throw new ArgumentOutOfRangeException($"Expecting an array of size {expectedSize}", nameof(data));
|
throw new ArgumentOutOfRangeException($"Expecting an array of size {expectedSize}", nameof(data));
|
||||||
|
|
|
@ -31,11 +31,11 @@ internal class ReadInputRegistersResponseFrame : ModbusFrame
|
||||||
|
|
||||||
private ReadInputRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
private ReadInputRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
||||||
{
|
{
|
||||||
|
AssertFunctionCode(ReadInputRegisters);
|
||||||
|
|
||||||
if (data.Count < MinSize)
|
if (data.Count < MinSize)
|
||||||
throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data));
|
throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data));
|
||||||
|
|
||||||
AssertFunctionCode(ReadInputRegisters);
|
|
||||||
|
|
||||||
var expectedSize = ByteCount + MinSize;
|
var expectedSize = ByteCount + MinSize;
|
||||||
if (data.Count != expectedSize)
|
if (data.Count != expectedSize)
|
||||||
throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data));
|
throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data));
|
||||||
|
|
|
@ -32,11 +32,11 @@ internal class ReadWriteRegistersResponseFrame : ModbusFrame
|
||||||
|
|
||||||
private ReadWriteRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
private ReadWriteRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
||||||
{
|
{
|
||||||
|
AssertFunctionCode(ReadWriteMultipleRegisters);
|
||||||
|
|
||||||
if (data.Count < MinSize)
|
if (data.Count < MinSize)
|
||||||
throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data));
|
throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data));
|
||||||
|
|
||||||
AssertFunctionCode(ReadWriteMultipleRegisters);
|
|
||||||
|
|
||||||
var expectedSize = ByteCount + MinSize;
|
var expectedSize = ByteCount + MinSize;
|
||||||
if (data.Count != expectedSize)
|
if (data.Count != expectedSize)
|
||||||
throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data));
|
throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode;
|
using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies;
|
||||||
|
@ -24,13 +25,12 @@ public class WriteCoilsResponseFrame : ModbusFrame
|
||||||
private WriteCoilsResponseFrame(Byte[] data) : this (new ArraySegment<Byte>(data) )
|
private WriteCoilsResponseFrame(Byte[] data) : this (new ArraySegment<Byte>(data) )
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
||||||
private WriteCoilsResponseFrame(ArraySegment<Byte> data) : base(data)
|
private WriteCoilsResponseFrame(ArraySegment<Byte> data) : base(data)
|
||||||
{
|
{
|
||||||
|
AssertFunctionCode(WriteMultipleCoils);
|
||||||
|
|
||||||
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(WriteMultipleCoils);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WriteCoilsResponseFrame Parse(Byte[] data) => new WriteCoilsResponseFrame(data);
|
public static WriteCoilsResponseFrame Parse(Byte[] data) => new WriteCoilsResponseFrame(data);
|
||||||
|
|
|
@ -27,10 +27,10 @@ internal class WriteRegistersResponseFrame : ModbusFrame
|
||||||
|
|
||||||
private WriteRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
private WriteRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
||||||
{
|
{
|
||||||
|
AssertFunctionCode(WriteMultipleRegisters);
|
||||||
|
|
||||||
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(WriteMultipleRegisters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WriteRegistersResponseFrame Parse(ArraySegment<Byte> data) => new WriteRegistersResponseFrame(data);
|
public static WriteRegistersResponseFrame Parse(ArraySegment<Byte> data) => new WriteRegistersResponseFrame(data);
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ModbusKind : byte
|
||||||
|
{
|
||||||
|
HoldingRegister,
|
||||||
|
InputRegister,
|
||||||
|
Coil,
|
||||||
|
DiscreteInput,
|
||||||
|
}
|
|
@ -4,8 +4,8 @@ using static System.AttributeTargets;
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
[AttributeUsage(Class | Struct)]
|
[AttributeUsage(Class | Struct)]
|
||||||
public class AddressOffsetAttribute : Attribute
|
public class AddressOffset : Attribute
|
||||||
{
|
{
|
||||||
public Int32 Offset { get; }
|
public Int32 Offset { get; }
|
||||||
public AddressOffsetAttribute(Int32 offset) => Offset = offset;
|
public AddressOffset(Int32 offset) => Offset = offset;
|
||||||
}
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
using static System.AttributeTargets;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(Class | Struct | Property | Field)]
|
||||||
|
public class BigEndian : EndianAttribute
|
||||||
|
{
|
||||||
|
public BigEndian() : base(Endian.Big)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
public class Coil : ModbusBoolean
|
||||||
|
{
|
||||||
|
public Coil(UInt16 address) : base(address, ModbusKind.Coil)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
public class CoilAttribute : ModbusBooleanAttribute
|
|
||||||
{
|
|
||||||
public CoilAttribute(UInt16 address) : base(address) { }
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
public class DiscreteInput : ModbusBoolean
|
||||||
|
{
|
||||||
|
public DiscreteInput(UInt16 address) : base(address, ModbusKind.DiscreteInput) { }
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
public class DiscreteInputAttribute : ModbusBooleanAttribute
|
|
||||||
{
|
|
||||||
public DiscreteInputAttribute(UInt16 address) : base(address) { }
|
|
||||||
}
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
using static System.AttributeTargets;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(Class | Struct | Property | Field)]
|
||||||
|
public class EndianAttribute : Attribute
|
||||||
|
{
|
||||||
|
public EndianAttribute(Endian endian) => Endian = endian;
|
||||||
|
public Endian Endian { get; }
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
|
||||||
|
public class HoldingRegister : ModbusRegister
|
||||||
|
{
|
||||||
|
private static readonly Type DefaultModbusType = typeof(UInt16);
|
||||||
|
|
||||||
|
public HoldingRegister(UInt16 address) : this(address, DefaultModbusType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public HoldingRegister(UInt16 address, Type modbusType) : base(address, modbusType, ModbusKind.HoldingRegister)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class HoldingRegister<T> : HoldingRegister where T : IConvertible
|
||||||
|
{
|
||||||
|
public HoldingRegister(UInt16 address) : base(address, typeof(T))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
public class HoldingRegisterAttribute : ModbusRegisterAttribute
|
|
||||||
{
|
|
||||||
public HoldingRegisterAttribute(UInt16 address, TypeCode modbusType = TypeCode.UInt16) : base(address, modbusType) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
public class InputRegister : ModbusRegister
|
||||||
|
{
|
||||||
|
private static readonly Type DefaultModbusType = typeof(UInt16);
|
||||||
|
|
||||||
|
public InputRegister(UInt16 address) : this(address, DefaultModbusType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputRegister(UInt16 address, Type modbusType) : base(address, modbusType, ModbusKind.InputRegister)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class InputRegister<T> : InputRegister where T : IConvertible
|
||||||
|
{
|
||||||
|
public InputRegister(UInt16 address) : base(address, typeof(T))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
public class InputRegisterAttribute : ModbusRegisterAttribute
|
|
||||||
{
|
|
||||||
public InputRegisterAttribute(UInt16 address, TypeCode modbusType = TypeCode.UInt16) : base(address, modbusType) { }
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
public class LittleEndian : EndianAttribute
|
||||||
|
{
|
||||||
|
public LittleEndian() : base(Endian.Little)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,32 +1,48 @@
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
using static System.AttributeTargets;
|
using static System.AttributeTargets;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
[AttributeUsage(Field | Property)]
|
[AttributeUsage(Field | Property)]
|
||||||
public class ModbusAttribute : Attribute
|
public abstract class ModbusAttribute : Attribute
|
||||||
{
|
{
|
||||||
public UInt16 Address { get; }
|
public UInt16 Address { get; }
|
||||||
public UInt16 Size { get; }
|
public UInt16 Size { get; }
|
||||||
public TypeCode ModbusType { get; }
|
public Type ModbusType { get; }
|
||||||
|
public ModbusKind Kind { get; }
|
||||||
|
|
||||||
protected ModbusAttribute(UInt16 address, TypeCode modbusType)
|
protected ModbusAttribute(UInt16 address, UInt16 size, Type modbusType, ModbusKind kind)
|
||||||
{
|
{
|
||||||
Address = address;
|
Address = address;
|
||||||
|
Size = size;
|
||||||
ModbusType = modbusType;
|
ModbusType = modbusType;
|
||||||
|
Kind = kind;
|
||||||
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))
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ModbusAttribute(UInt16 address, Type modbusType, ModbusKind kind) : this(address, GetSize(modbusType), modbusType, kind)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UInt16 GetSize(Type modbusType)
|
||||||
|
{
|
||||||
|
if (!TypeToSize.TryGetValue(modbusType, out var size))
|
||||||
|
throw new ArgumentException("cannot infer size of" + nameof(modbusType), nameof(modbusType));
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, UInt16> TypeToSize = new()
|
||||||
|
{
|
||||||
|
[typeof(Boolean)] = 1,
|
||||||
|
[typeof(Int16)] = 1,
|
||||||
|
[typeof(UInt16)] = 1,
|
||||||
|
[typeof(Int32)] = 2,
|
||||||
|
[typeof(UInt32)] = 2,
|
||||||
|
[typeof(Single)] = 2,
|
||||||
|
[typeof(Int64)] = 4,
|
||||||
|
[typeof(UInt64)] = 4,
|
||||||
|
[typeof(Double)] = 4,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
public class ModbusBoolean : ModbusAttribute
|
||||||
|
{
|
||||||
|
protected ModbusBoolean(UInt16 address, ModbusKind kind) : base(address, typeof(Boolean), kind)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
public class ModbusBooleanAttribute : ModbusAttribute
|
|
||||||
{
|
|
||||||
protected ModbusBooleanAttribute(UInt16 address) : base(address, TypeCode.Boolean)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class ModbusRegister : ModbusAttribute
|
||||||
|
{
|
||||||
|
public Double Scale { get; init; } = 1;
|
||||||
|
public Double Offset { get; init; } = 0;
|
||||||
|
|
||||||
|
protected ModbusRegister(UInt16 address, Type modbusType, ModbusKind kind) : base(address, modbusType, kind)
|
||||||
|
{
|
||||||
|
if (!SupportedTypes.Contains(modbusType))
|
||||||
|
throw new ArgumentException($"Type {modbusType.Name} is not supported " +
|
||||||
|
$"for {nameof(ModbusRegister)}",
|
||||||
|
nameof(modbusType));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly Type[] SupportedTypes =
|
||||||
|
{
|
||||||
|
typeof(Boolean),
|
||||||
|
typeof(Int16) ,
|
||||||
|
typeof(UInt16),
|
||||||
|
typeof(Int32),
|
||||||
|
typeof(UInt32),
|
||||||
|
typeof(Single),
|
||||||
|
typeof(Int64) ,
|
||||||
|
typeof(UInt64),
|
||||||
|
typeof(Double),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
public abstract class ModbusRegisterAttribute : ModbusAttribute
|
|
||||||
{
|
|
||||||
public Double Scale { get; init; } = 1;
|
|
||||||
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,11 @@
|
||||||
|
using static System.AttributeTargets;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(Class | Struct)]
|
||||||
|
public class OneBasedAddressing : AddressOffset
|
||||||
|
{
|
||||||
|
public OneBasedAddressing() : base(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,26 @@
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
|
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.Accessors;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||||
|
|
||||||
#pragma warning disable CS8509
|
#pragma warning disable CS8509
|
||||||
|
#pragma warning disable CS8524
|
||||||
|
|
||||||
internal record Batch<R>(Action<R> Read, Action<R> Write);
|
internal record Batch<R>(Action<R> Read, Action<R> Write);
|
||||||
|
|
||||||
public static class Batches
|
public static class Batches
|
||||||
{
|
{
|
||||||
internal static IEnumerable<Batch<R>> MakeBatchesFor<R>(this ModbusClient modbusClient, Int32 addressOffset)
|
internal static IReadOnlyList<Batch<R>> MakeBatchesFor<R>(this ModbusClient modbusClient, Int32 addressOffset)
|
||||||
{
|
{
|
||||||
var members = ModbusMembers
|
var members = ModbusMembers
|
||||||
.From<R>(addressOffset)
|
.From<R>(addressOffset)
|
||||||
.OrderBy(m => m.Attribute.GetType().Name)
|
.OrderBy(m => m.Kind)
|
||||||
.ThenBy(m => m.StartAddress)
|
.ThenBy(m => m.StartAddress)
|
||||||
.ThenBy(m => m.EndAddress);
|
.ThenBy(m => m.EndAddress);
|
||||||
|
|
||||||
return MakeBatches<R>(modbusClient, members);
|
return MakeBatches<R>(modbusClient, members).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<Batch<R>> MakeBatches<R>(ModbusClient mb, IEnumerable<ModbusMember> modbusMembers)
|
private static IEnumerable<Batch<R>> MakeBatches<R>(ModbusClient mb, IEnumerable<ModbusMember> modbusMembers)
|
||||||
|
@ -47,7 +48,7 @@ public static class Batches
|
||||||
|
|
||||||
return m.StartAddress > batchMembers[^1].EndAddress // gap between registers
|
return m.StartAddress > batchMembers[^1].EndAddress // gap between registers
|
||||||
|| m.EndAddress > batchMembers[0].StartAddress + Constants.MaxRegs // max batch size reached
|
|| m.EndAddress > batchMembers[0].StartAddress + Constants.MaxRegs // max batch size reached
|
||||||
|| m.Attribute.GetType() != batchMembers[0].Attribute.GetType(); // different ModbusType
|
|| m.Kind != batchMembers[0].Kind; // different Kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,26 +58,28 @@ public static class Batches
|
||||||
var startAddress = members[0].StartAddress;
|
var startAddress = members[0].StartAddress;
|
||||||
var endAddress = members[^1].EndAddress;
|
var endAddress = members[^1].EndAddress;
|
||||||
var count = (UInt16)(endAddress - startAddress);
|
var count = (UInt16)(endAddress - startAddress);
|
||||||
var attribute = members[0].Attribute;
|
var kind = members[0].Kind;
|
||||||
var isWritable = attribute is HoldingRegisterAttribute or CoilAttribute;
|
var isWritable = kind is ModbusKind.HoldingRegister or ModbusKind.Coil;
|
||||||
|
|
||||||
return new Batch<R>(Read(), Write());
|
return new Batch<R>(Read(), Write());
|
||||||
|
|
||||||
Action<R> Read()
|
Action<R> Read()
|
||||||
{
|
{
|
||||||
Func<MbData> readModbus = attribute switch
|
Func<MbData> readModbus = kind switch
|
||||||
{
|
{
|
||||||
InputRegisterAttribute => () => modbusClient.ReadInputRegisters (startAddress, count),
|
ModbusKind.InputRegister => () => modbusClient.ReadInputRegisters (startAddress, count),
|
||||||
HoldingRegisterAttribute => () => modbusClient.ReadHoldingRegisters(startAddress, count),
|
ModbusKind.HoldingRegister => () => modbusClient.ReadHoldingRegisters(startAddress, count),
|
||||||
DiscreteInputAttribute => () => modbusClient.ReadDiscreteInputs (startAddress, count),
|
ModbusKind.DiscreteInput => () => modbusClient.ReadDiscreteInputs (startAddress, count),
|
||||||
CoilAttribute => () => modbusClient.ReadCoils (startAddress, count),
|
ModbusKind.Coil => () => modbusClient.ReadCoils (startAddress, count),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Console.WriteLine("start: " + startAddress + " count: " + count);
|
||||||
|
|
||||||
return record =>
|
return record =>
|
||||||
{
|
{
|
||||||
|
var mbData = readModbus();
|
||||||
foreach (var member in members)
|
foreach (var member in members)
|
||||||
{
|
{
|
||||||
var mbData = readModbus();
|
|
||||||
member.ModbusToRecord(mbData, record!);
|
member.ModbusToRecord(mbData, record!);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -87,28 +90,29 @@ public static class Batches
|
||||||
if (!isWritable)
|
if (!isWritable)
|
||||||
return _ => { }; // nop
|
return _ => { }; // nop
|
||||||
|
|
||||||
Func<MbData> createMbData = attribute switch
|
Func<MbData> createMbData = kind switch
|
||||||
{
|
{
|
||||||
HoldingRegisterAttribute => () => MbData.Registers(startAddress, count),
|
ModbusKind.HoldingRegister => () => MbData.Registers(startAddress, count),
|
||||||
CoilAttribute => () => MbData.Coils (startAddress, count),
|
ModbusKind.Coil => () => MbData.Coils (startAddress, count),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Action<MbData> writeModbus = attribute switch
|
Action<MbData> writeModbus = kind switch
|
||||||
{
|
{
|
||||||
HoldingRegisterAttribute => d => modbusClient.WriteRegisters(startAddress, d.GetRegisters()),
|
ModbusKind.HoldingRegister => d => modbusClient.WriteRegisters(startAddress, d.GetRegisters()),
|
||||||
CoilAttribute => d => modbusClient.WriteCoils (startAddress, d.GetCoils()),
|
ModbusKind.Coil => d => modbusClient.WriteCoils (startAddress, d.GetCoils().Take(count).ToList()),
|
||||||
|
// ^^ TODO: Coils.count is broken, fix when refactoring to use direct binary codec
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return rec =>
|
return rec =>
|
||||||
{
|
{
|
||||||
|
var mbData = createMbData();
|
||||||
|
|
||||||
foreach (var member in members)
|
foreach (var member in members)
|
||||||
{
|
|
||||||
var mbData = createMbData();
|
|
||||||
member.RecordToModbus(rec!, mbData);
|
member.RecordToModbus(rec!, mbData);
|
||||||
writeModbus(mbData);
|
|
||||||
}
|
writeModbus(mbData);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
|
||||||
using static System.Reflection.BindingFlags;
|
using static System.Reflection.BindingFlags;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||||
|
@ -12,7 +15,7 @@ internal record ModbusMember
|
||||||
(
|
(
|
||||||
UInt16 StartAddress,
|
UInt16 StartAddress,
|
||||||
UInt16 EndAddress,
|
UInt16 EndAddress,
|
||||||
ModbusAttribute Attribute,
|
ModbusKind Kind,
|
||||||
Action<MbData, Object> ModbusToRecord,
|
Action<MbData, Object> ModbusToRecord,
|
||||||
Action<Object, MbData> RecordToModbus
|
Action<Object, MbData> RecordToModbus
|
||||||
);
|
);
|
||||||
|
@ -21,70 +24,139 @@ internal static class ModbusMembers
|
||||||
{
|
{
|
||||||
private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0);
|
private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0);
|
||||||
|
|
||||||
internal static IEnumerable<ModbusMember> From<R>(Int32 addressOffset)
|
internal static IEnumerable<ModbusMember> From<R>(Int32 globalAddressOffset, Endian globalEndian = Endian.Undefined)
|
||||||
{
|
{
|
||||||
return GetDataMembers<R>()
|
var recordType = typeof(R);
|
||||||
|
|
||||||
|
// "=======================================================================".WriteLine();
|
||||||
|
// recordType.Name.WriteLine();
|
||||||
|
|
||||||
|
var offset = recordType.GetRecordOffset(globalAddressOffset);
|
||||||
|
var endian = recordType.GetEndian(globalEndian);
|
||||||
|
|
||||||
|
return recordType
|
||||||
|
.GetDataMembers()
|
||||||
.Where(HasAttribute<ModbusAttribute>)
|
.Where(HasAttribute<ModbusAttribute>)
|
||||||
.Select(m => m.CreateModbusMember(addressOffset));
|
.Select(m => m.CreateModbusMember(offset, endian));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Int32 GetRecordOffset([DynamicallyAccessedMembers(All)] this Type recordType, Int32 globalAddressOffset)
|
||||||
private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset)
|
|
||||||
{
|
{
|
||||||
var attribute = info.GetCustomAttributes<ModbusAttribute>().Single();
|
return recordType
|
||||||
var startAddress = (UInt16)(attribute.Address + addressOffset);
|
.GetCustomAttributes<AddressOffset>()
|
||||||
var endAddress = (UInt16)(startAddress + attribute.Size);
|
.Aggregate(globalAddressOffset, (a, b) => a + b.Offset);
|
||||||
var modbusType = attribute.ModbusType;
|
}
|
||||||
var transform = attribute is ModbusRegisterAttribute mra
|
|
||||||
? (mra.Scale, mra.Offset)
|
|
||||||
: NoTransform;
|
|
||||||
|
|
||||||
var modbusToRecord = info.ModbusToRecord(startAddress, modbusType, transform);
|
private static Endian GetEndian([DynamicallyAccessedMembers(All)] this Type recordType, Endian endian)
|
||||||
var recordToModbus = info.RecordToModbus(startAddress, modbusType, transform);
|
{
|
||||||
|
return recordType
|
||||||
|
.GetCustomAttributes<EndianAttribute>()
|
||||||
|
.Aggregate(endian, (a, b) => b.Endian.InheritFrom(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset, Endian globalEndian)
|
||||||
|
{
|
||||||
|
var attribute = info.GetCustomAttributes<ModbusAttribute>().Single();
|
||||||
|
var endian = info.GetCustomAttributes<EndianAttribute>()
|
||||||
|
.Select(a => a.Endian)
|
||||||
|
.Append(Endian.Undefined)
|
||||||
|
.First()
|
||||||
|
.InheritFrom(globalEndian);
|
||||||
|
|
||||||
|
var address = (UInt16)(attribute.Address + addressOffset);
|
||||||
|
var endAddress = (UInt16)(address + attribute.Size);
|
||||||
|
var modbusType = attribute.ModbusType;
|
||||||
|
var transform = attribute is ModbusRegister mra
|
||||||
|
? (mra.Scale, mra.Offset)
|
||||||
|
: NoTransform;
|
||||||
|
|
||||||
|
//Console.WriteLine(info.Name +" " + address + " " + modbusType);
|
||||||
|
|
||||||
return new ModbusMember
|
return new ModbusMember
|
||||||
(
|
(
|
||||||
startAddress,
|
address,
|
||||||
endAddress,
|
endAddress,
|
||||||
attribute,
|
attribute.Kind,
|
||||||
modbusToRecord,
|
ModbusToRecord(),
|
||||||
recordToModbus
|
RecordToModbus()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Action<MbData, Object> ModbusToRecord()
|
||||||
|
{
|
||||||
|
var decode = ConvertModbusToRecord(transform);
|
||||||
|
|
||||||
|
Func<MbData, IConvertible> readFromMbData =
|
||||||
|
modbusType == typeof(Boolean) ? d => d.GetInput(address) :
|
||||||
|
modbusType == typeof(UInt16) ? d => d.GetUInt16(address) :
|
||||||
|
modbusType == typeof(Int16) ? d => d.GetInt16(address) :
|
||||||
|
modbusType == typeof(UInt32) ? d => d.GetUInt32(address) :
|
||||||
|
modbusType == typeof(Int32) ? d => d.GetInt32(address) :
|
||||||
|
modbusType == typeof(Single) ? d => d.GetFloat32(address) :
|
||||||
|
modbusType == typeof(UInt64) ? d => d.GetUInt64(address) :
|
||||||
|
modbusType == typeof(Int64) ? d => d.GetInt64(address) :
|
||||||
|
modbusType == typeof(Double) ? d => d.GetFloat64(address) :
|
||||||
|
throw new ArgumentException(nameof(modbusType));
|
||||||
|
|
||||||
|
|
||||||
|
var memberType = info switch
|
||||||
|
{
|
||||||
|
FieldInfo fi => fi.FieldType,
|
||||||
|
PropertyInfo pi => pi.PropertyType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var ctr = memberType.GetConstructor(new[] { typeof(Double) }); // TODO: hardcoded double constructor for Units
|
||||||
|
|
||||||
|
Func<IConvertible, Object> convert = ctr is null
|
||||||
|
? value => value.ConvertTo(memberType)
|
||||||
|
: value => ctr.Invoke(new Object[] { value.ConvertTo<Double>() });
|
||||||
|
|
||||||
|
Action<Object, IConvertible> set = info switch
|
||||||
|
{
|
||||||
|
FieldInfo fi => (rec, value) => fi.SetValue(rec, convert(value)),
|
||||||
|
PropertyInfo pi => (rec, value) => pi.SetValue(rec, convert(value)),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (mbData, rec) =>
|
||||||
|
{
|
||||||
|
var rawModbusValue = readFromMbData(mbData.WithEndian(endian));
|
||||||
|
var decoded = decode(rawModbusValue);
|
||||||
|
|
||||||
|
set(rec, decoded);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Action<Object, MbData> RecordToModbus()
|
||||||
|
{
|
||||||
|
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 == typeof(Boolean)? (value, mbData) => mbData.SetCoil (address, value.ConvertTo<Boolean>()) :
|
||||||
|
modbusType == typeof(UInt16) ? (value, mbData) => mbData.SetUInt16 (address, value.ConvertTo<UInt16>()) :
|
||||||
|
modbusType == typeof(Int16) ? (value, mbData) => mbData.SetInt16 (address, value.ConvertTo<Int16>()) :
|
||||||
|
modbusType == typeof(UInt32) ? (value, mbData) => mbData.SetUInt32 (address, value.ConvertTo<UInt32>()) :
|
||||||
|
modbusType == typeof(Int32) ? (value, mbData) => mbData.SetInt32 (address, value.ConvertTo<Int32>()) :
|
||||||
|
modbusType == typeof(Single) ? (value, mbData) => mbData.SetFloat32(address, value.ConvertTo<Single>()) :
|
||||||
|
modbusType == typeof(UInt64) ? (value, mbData) => mbData.SetUInt64 (address, value.ConvertTo<UInt64>()) :
|
||||||
|
modbusType == typeof(Int64) ? (value, mbData) => mbData.SetInt64 (address, value.ConvertTo<Int64>()) :
|
||||||
|
modbusType == typeof(Double) ? (value, mbData) => mbData.SetFloat64(address, value.ConvertTo<Double>()) :
|
||||||
|
throw new ArgumentException(nameof(modbusType));
|
||||||
|
|
||||||
|
return (rec, mbData) =>
|
||||||
|
{
|
||||||
|
var memberValue = get(rec);
|
||||||
|
var encoded = encode(memberValue);
|
||||||
|
|
||||||
|
writeToMbData(encoded, mbData.WithEndian(endian));
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
private static Func<IConvertible, IConvertible> ConvertModbusToRecord((Double scale, Double offset) transform)
|
||||||
{
|
{
|
||||||
|
@ -98,41 +170,10 @@ internal static class ModbusMembers
|
||||||
{
|
{
|
||||||
var value = c.ConvertTo<Decimal>();
|
var value = c.ConvertTo<Decimal>();
|
||||||
|
|
||||||
return (value + offset) * scale;
|
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);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +189,10 @@ internal static class ModbusMembers
|
||||||
{
|
{
|
||||||
var value = c.ConvertTo<Decimal>();
|
var value = c.ConvertTo<Decimal>();
|
||||||
|
|
||||||
return value / scale - offset;
|
return
|
||||||
|
/*******************************/
|
||||||
|
/**/ value / scale - offset; /**/
|
||||||
|
/*******************************/
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,10 +200,8 @@ internal static class ModbusMembers
|
||||||
private static T Nop<T>(T c) => c;
|
private static T Nop<T>(T c) => c;
|
||||||
|
|
||||||
|
|
||||||
private static IEnumerable<MemberInfo> GetDataMembers<R>()
|
private static IEnumerable<MemberInfo> GetDataMembers([DynamicallyAccessedMembers(All)] this Type recordType)
|
||||||
{
|
{
|
||||||
var recordType = typeof(R);
|
|
||||||
|
|
||||||
const BindingFlags bindingFlags = Instance
|
const BindingFlags bindingFlags = Instance
|
||||||
| Public
|
| Public
|
||||||
| NonPublic
|
| NonPublic
|
||||||
|
@ -176,4 +218,3 @@ internal static class ModbusMembers
|
||||||
return i.GetCustomAttributes<T>().Any();
|
return i.GetCustomAttributes<T>().Any();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +1,17 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||||
|
using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
|
namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
|
||||||
|
|
||||||
using Float32 = Single;
|
|
||||||
using Float64 = Double;
|
|
||||||
|
|
||||||
public class ModbusDevice<R> where R : notnull
|
public class ModbusDevice<R> where R : notnull
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyList<Batch<R>> _Batches;
|
private readonly IReadOnlyList<Batch<R>> _Batches;
|
||||||
|
|
||||||
public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0)
|
public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0)
|
||||||
{
|
{
|
||||||
var offset = addressOffset + Record.GetAddressOffset<R>();
|
_Batches = modbusClient.MakeBatchesFor<R>(addressOffset);
|
||||||
_Batches = modbusClient.MakeBatchesFor<R>(offset).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public R Read()
|
public R Read()
|
||||||
|
@ -37,7 +35,7 @@ public class ModbusDevice<R> where R : notnull
|
||||||
return Read(r);
|
return Read(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
public R Read(R record)
|
public R Read([DynamicallyAccessedMembers(All)] R record)
|
||||||
{
|
{
|
||||||
foreach (var batch in _Batches)
|
foreach (var batch in _Batches)
|
||||||
batch.Read(record);
|
batch.Read(record);
|
||||||
|
@ -45,7 +43,7 @@ public class ModbusDevice<R> where R : notnull
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(R record)
|
public void Write([DynamicallyAccessedMembers(All)] R record)
|
||||||
{
|
{
|
||||||
foreach (var batch in _Batches)
|
foreach (var batch in _Batches)
|
||||||
batch.Write(record);
|
batch.Write(record);
|
||||||
|
|
Loading…
Reference in New Issue