Modbus V2
This commit is contained in:
parent
045de6ac0d
commit
7a44b1f307
|
@ -129,8 +129,8 @@ public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
|
|||
String tty,
|
||||
Int32 baudRate,
|
||||
Parity parity,
|
||||
Int32 stopBits,
|
||||
Int32 dataBits)
|
||||
Int32 dataBits,
|
||||
Int32 stopBits)
|
||||
{
|
||||
const Int32 port = 6855;
|
||||
|
||||
|
@ -145,7 +145,10 @@ public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
|
|||
var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw";
|
||||
|
||||
//var script = $"-n -o RemoteCommand='{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}'";
|
||||
var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}";
|
||||
//var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}";
|
||||
|
||||
//var script = $"{configureTty} && {socat} ; {KillTasks}";
|
||||
var script = $"{configureTty} && {socat}";
|
||||
|
||||
_Command = host.Command.AppendArgument(script);
|
||||
|
||||
|
@ -179,9 +182,9 @@ public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.IO.Ports;
|
||||
using CliWrap;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||
|
||||
namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||
|
@ -16,10 +15,7 @@ public class SerialPortChannel : ConnectionChannel<SerialPort>
|
|||
Boolean closeAfterSuccessfulRead = false,
|
||||
Boolean closeAfterSuccessfulWrite = false)
|
||||
:
|
||||
base(
|
||||
closeAfterSuccessfulRead,
|
||||
closeAfterSuccessfulWrite
|
||||
)
|
||||
base(closeAfterSuccessfulRead, closeAfterSuccessfulWrite)
|
||||
{
|
||||
var sb = stopBits switch
|
||||
{
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Configurations>Debug;Release;Release-Server</Configurations>
|
||||
<Platforms>AnyCPU;linux-arm</Platforms>
|
||||
</PropertyGroup>
|
||||
<Import Project="../../InnovEnergy.Lib.props" />
|
||||
<Import Project="../../InnovEnergy.Lib.props"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<IsTrimmable>false</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Utils/Utils.csproj"/>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Utils/Utils.csproj" />
|
||||
</ItemGroup>
|
||||
<ProjectReference Include="../../SrcGen/SrcGen.csproj"
|
||||
OutputItemType="Analyzer"
|
||||
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>
|
||||
|
||||
|
|
|
@ -2,6 +2,19 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
|||
|
||||
public enum Endian
|
||||
{
|
||||
Little,
|
||||
Big
|
||||
Undefined = 0,
|
||||
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
|
||||
#pragma warning disable CS8524
|
||||
#pragma warning disable CS8509
|
||||
|
||||
public struct MbData
|
||||
{
|
||||
|
@ -18,24 +19,24 @@ public struct MbData
|
|||
private readonly Endian _Endian;
|
||||
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)
|
||||
throw new ArgumentException(nameof(nRegisters));
|
||||
|
||||
var nBytes = nRegisters * 2;
|
||||
var data = new Byte[nBytes];
|
||||
return new MbData(data, startAddress, Endian.Big); // endian has no influence on coils
|
||||
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)
|
||||
throw new ArgumentException(nameof(nCoils));
|
||||
|
||||
var nBytes = Math.Ceiling(nCoils / 8.0).ConvertTo<UInt16>();
|
||||
var data = new Byte[nBytes];
|
||||
return new MbData(data, startAddress, Endian.Big); // endian has no influence on coils
|
||||
return new MbData(data, startAddress, endian); // endian has no influence on coils
|
||||
}
|
||||
|
||||
internal MbData(ArraySegment<Byte> data, UInt16 startAddress, Endian endian)
|
||||
|
@ -48,6 +49,11 @@ public struct MbData
|
|||
_Data = data;
|
||||
}
|
||||
|
||||
public MbData WithEndian(Endian endian)
|
||||
{
|
||||
return new MbData(_Data, _StartAddress, endian.InheritFrom(_Endian));
|
||||
}
|
||||
|
||||
#region Coils
|
||||
|
||||
public IReadOnlyList<Boolean> GetCoils()
|
||||
|
@ -78,7 +84,12 @@ public struct MbData
|
|||
var byteIndex = 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
|
||||
|
@ -119,10 +130,11 @@ public struct MbData
|
|||
var hi = (UInt32) GetUInt16(address);
|
||||
var lo = (UInt32) GetUInt16(++address);
|
||||
|
||||
|
||||
return _Endian switch
|
||||
{
|
||||
Endian.Big => hi << 16 | lo,
|
||||
Endian.Little => lo << 16 | hi,
|
||||
Endian.Big => hi << 16 | lo,
|
||||
Endian.Little => lo << 16 | hi,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -225,6 +237,7 @@ public struct MbData
|
|||
|
||||
return Enumerable
|
||||
.Range(0, nRegisters)
|
||||
.Select(r => r * 2)
|
||||
.Select(GetRegister)
|
||||
.ToArray(nRegisters);
|
||||
}
|
||||
|
|
|
@ -30,11 +30,11 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame
|
|||
|
||||
private ReadHoldingRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
||||
{
|
||||
AssertFunctionCode(ReadHoldingRegisters);
|
||||
|
||||
if (data.Count < MinSize)
|
||||
throw new ArgumentOutOfRangeException($"Expecting an array of size {MinSize} or more", nameof(data));
|
||||
|
||||
AssertFunctionCode(ReadHoldingRegisters);
|
||||
|
||||
var expectedSize = ByteCount + MinSize;
|
||||
if (data.Count != expectedSize)
|
||||
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)
|
||||
{
|
||||
AssertFunctionCode(ReadInputRegisters);
|
||||
|
||||
if (data.Count < MinSize)
|
||||
throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data));
|
||||
|
||||
AssertFunctionCode(ReadInputRegisters);
|
||||
|
||||
var expectedSize = ByteCount + MinSize;
|
||||
if (data.Count != expectedSize)
|
||||
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)
|
||||
{
|
||||
AssertFunctionCode(ReadWriteMultipleRegisters);
|
||||
|
||||
if (data.Count < MinSize)
|
||||
throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data));
|
||||
|
||||
AssertFunctionCode(ReadWriteMultipleRegisters);
|
||||
|
||||
var expectedSize = ByteCount + MinSize;
|
||||
if (data.Count != expectedSize)
|
||||
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.Utils;
|
||||
using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode;
|
||||
|
||||
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(ArraySegment<Byte> data) : base(data)
|
||||
{
|
||||
AssertFunctionCode(WriteMultipleCoils);
|
||||
|
||||
if (data.Count != Size)
|
||||
throw new ArgumentException($"Expecting an array of size {Size}", nameof(data));
|
||||
|
||||
AssertFunctionCode(WriteMultipleCoils);
|
||||
}
|
||||
|
||||
public static WriteCoilsResponseFrame Parse(Byte[] data) => new WriteCoilsResponseFrame(data);
|
||||
|
|
|
@ -27,10 +27,10 @@ internal class WriteRegistersResponseFrame : ModbusFrame
|
|||
|
||||
private WriteRegistersResponseFrame(ArraySegment<Byte> data) : base(data)
|
||||
{
|
||||
AssertFunctionCode(WriteMultipleRegisters);
|
||||
|
||||
if (data.Count != Size)
|
||||
throw new ArgumentException($"Expecting an array of size {Size}", nameof(data));
|
||||
|
||||
AssertFunctionCode(WriteMultipleRegisters);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
[AttributeUsage(Class | Struct)]
|
||||
public class AddressOffsetAttribute : Attribute
|
||||
public class AddressOffset : Attribute
|
||||
{
|
||||
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;
|
||||
|
||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||
|
||||
[AttributeUsage(Field | Property)]
|
||||
public class ModbusAttribute : Attribute
|
||||
public abstract class ModbusAttribute : Attribute
|
||||
{
|
||||
public UInt16 Address { get; }
|
||||
public UInt16 Size { get; }
|
||||
public TypeCode ModbusType { get; }
|
||||
public UInt16 Address { get; }
|
||||
public UInt16 Size { 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;
|
||||
Size = size;
|
||||
ModbusType = modbusType;
|
||||
|
||||
Size = modbusType switch
|
||||
{
|
||||
TypeCode.Boolean => 1,
|
||||
TypeCode.Int16 => 1,
|
||||
TypeCode.UInt16 => 1,
|
||||
TypeCode.Int32 => 2,
|
||||
TypeCode.UInt32 => 2,
|
||||
TypeCode.Single => 2,
|
||||
TypeCode.Int64 => 4,
|
||||
TypeCode.UInt64 => 4,
|
||||
TypeCode.Double => 4,
|
||||
_ => throw new ArgumentException(nameof(modbusType))
|
||||
};
|
||||
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
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.Protocol;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||
|
||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||
|
||||
#pragma warning disable CS8509
|
||||
#pragma warning disable CS8524
|
||||
|
||||
internal record Batch<R>(Action<R> Read, Action<R> Write);
|
||||
|
||||
public static class Batches
|
||||
{
|
||||
internal static IEnumerable<Batch<R>> MakeBatchesFor<R>(this ModbusClient modbusClient, Int32 addressOffset)
|
||||
internal static IReadOnlyList<Batch<R>> MakeBatchesFor<R>(this ModbusClient modbusClient, Int32 addressOffset)
|
||||
{
|
||||
var members = ModbusMembers
|
||||
.From<R>(addressOffset)
|
||||
.OrderBy(m => m.Attribute.GetType().Name)
|
||||
.OrderBy(m => m.Kind)
|
||||
.ThenBy(m => m.StartAddress)
|
||||
.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)
|
||||
|
@ -47,7 +48,7 @@ public static class Batches
|
|||
|
||||
return m.StartAddress > batchMembers[^1].EndAddress // gap between registers
|
||||
|| m.EndAddress > batchMembers[0].StartAddress + Constants.MaxRegs // max batch size reached
|
||||
|| m.Attribute.GetType() != batchMembers[0].Attribute.GetType(); // different ModbusType
|
||||
|| m.Kind != batchMembers[0].Kind; // different Kind
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,26 +58,28 @@ public static class Batches
|
|||
var startAddress = members[0].StartAddress;
|
||||
var endAddress = members[^1].EndAddress;
|
||||
var count = (UInt16)(endAddress - startAddress);
|
||||
var attribute = members[0].Attribute;
|
||||
var isWritable = attribute is HoldingRegisterAttribute or CoilAttribute;
|
||||
var kind = members[0].Kind;
|
||||
var isWritable = kind is ModbusKind.HoldingRegister or ModbusKind.Coil;
|
||||
|
||||
return new Batch<R>(Read(), Write());
|
||||
|
||||
Action<R> Read()
|
||||
{
|
||||
Func<MbData> readModbus = attribute switch
|
||||
Func<MbData> readModbus = kind switch
|
||||
{
|
||||
InputRegisterAttribute => () => modbusClient.ReadInputRegisters (startAddress, count),
|
||||
HoldingRegisterAttribute => () => modbusClient.ReadHoldingRegisters(startAddress, count),
|
||||
DiscreteInputAttribute => () => modbusClient.ReadDiscreteInputs (startAddress, count),
|
||||
CoilAttribute => () => modbusClient.ReadCoils (startAddress, count),
|
||||
ModbusKind.InputRegister => () => modbusClient.ReadInputRegisters (startAddress, count),
|
||||
ModbusKind.HoldingRegister => () => modbusClient.ReadHoldingRegisters(startAddress, count),
|
||||
ModbusKind.DiscreteInput => () => modbusClient.ReadDiscreteInputs (startAddress, count),
|
||||
ModbusKind.Coil => () => modbusClient.ReadCoils (startAddress, count),
|
||||
};
|
||||
|
||||
//Console.WriteLine("start: " + startAddress + " count: " + count);
|
||||
|
||||
return record =>
|
||||
{
|
||||
var mbData = readModbus();
|
||||
foreach (var member in members)
|
||||
{
|
||||
var mbData = readModbus();
|
||||
member.ModbusToRecord(mbData, record!);
|
||||
}
|
||||
};
|
||||
|
@ -87,28 +90,29 @@ public static class Batches
|
|||
if (!isWritable)
|
||||
return _ => { }; // nop
|
||||
|
||||
Func<MbData> createMbData = attribute switch
|
||||
Func<MbData> createMbData = kind switch
|
||||
{
|
||||
HoldingRegisterAttribute => () => MbData.Registers(startAddress, count),
|
||||
CoilAttribute => () => MbData.Coils (startAddress, count),
|
||||
ModbusKind.HoldingRegister => () => MbData.Registers(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()),
|
||||
CoilAttribute => d => modbusClient.WriteCoils (startAddress, d.GetCoils()),
|
||||
ModbusKind.HoldingRegister => d => modbusClient.WriteRegisters(startAddress, d.GetRegisters()),
|
||||
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 =>
|
||||
{
|
||||
var mbData = createMbData();
|
||||
|
||||
foreach (var member in members)
|
||||
{
|
||||
var mbData = createMbData();
|
||||
member.RecordToModbus(rec!, mbData);
|
||||
writeModbus(mbData);
|
||||
}
|
||||
|
||||
writeModbus(mbData);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
|
||||
using static System.Reflection.BindingFlags;
|
||||
|
||||
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
||||
|
@ -12,7 +15,7 @@ internal record ModbusMember
|
|||
(
|
||||
UInt16 StartAddress,
|
||||
UInt16 EndAddress,
|
||||
ModbusAttribute Attribute,
|
||||
ModbusKind Kind,
|
||||
Action<MbData, Object> ModbusToRecord,
|
||||
Action<Object, MbData> RecordToModbus
|
||||
);
|
||||
|
@ -21,70 +24,139 @@ internal static class ModbusMembers
|
|||
{
|
||||
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>)
|
||||
.Select(m => m.CreateModbusMember(addressOffset));
|
||||
.Select(m => m.CreateModbusMember(offset, endian));
|
||||
}
|
||||
|
||||
|
||||
private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset)
|
||||
private static Int32 GetRecordOffset([DynamicallyAccessedMembers(All)] this Type recordType, Int32 globalAddressOffset)
|
||||
{
|
||||
var attribute = info.GetCustomAttributes<ModbusAttribute>().Single();
|
||||
var startAddress = (UInt16)(attribute.Address + addressOffset);
|
||||
var endAddress = (UInt16)(startAddress + attribute.Size);
|
||||
var modbusType = attribute.ModbusType;
|
||||
var transform = attribute is ModbusRegisterAttribute mra
|
||||
? (mra.Scale, mra.Offset)
|
||||
: NoTransform;
|
||||
return recordType
|
||||
.GetCustomAttributes<AddressOffset>()
|
||||
.Aggregate(globalAddressOffset, (a, b) => a + b.Offset);
|
||||
}
|
||||
|
||||
var modbusToRecord = info.ModbusToRecord(startAddress, modbusType, transform);
|
||||
var recordToModbus = info.RecordToModbus(startAddress, modbusType, transform);
|
||||
private static Endian GetEndian([DynamicallyAccessedMembers(All)] this Type recordType, Endian endian)
|
||||
{
|
||||
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
|
||||
(
|
||||
startAddress,
|
||||
address,
|
||||
endAddress,
|
||||
attribute,
|
||||
modbusToRecord,
|
||||
recordToModbus
|
||||
attribute.Kind,
|
||||
ModbusToRecord(),
|
||||
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)
|
||||
{
|
||||
|
@ -98,41 +170,10 @@ internal static class ModbusMembers
|
|||
{
|
||||
var value = c.ConvertTo<Decimal>();
|
||||
|
||||
return (value + offset) * scale;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static Action<Object, MbData> RecordToModbus(this MemberInfo info, UInt16 addr, TypeCode modbusType, (Double scale, Double offset) transform)
|
||||
{
|
||||
var encode = ConvertRecordToModbus(transform);
|
||||
|
||||
Func<Object, IConvertible> get = info switch
|
||||
{
|
||||
FieldInfo fi => rec => (IConvertible)fi.GetValue(rec)!,
|
||||
PropertyInfo pi => rec => (IConvertible)pi.GetValue(rec)!,
|
||||
};
|
||||
|
||||
Action<IConvertible, MbData> writeToMbData = modbusType switch
|
||||
{
|
||||
TypeCode.Boolean => (value, mbData) => mbData.SetCoil (addr, value.ConvertTo<Boolean>()),
|
||||
TypeCode.UInt16 => (value, mbData) => mbData.SetUInt16 (addr, value.ConvertTo<UInt16>()),
|
||||
TypeCode.Int16 => (value, mbData) => mbData.SetInt16 (addr, value.ConvertTo<Int16>()),
|
||||
TypeCode.UInt32 => (value, mbData) => mbData.SetUInt32 (addr, value.ConvertTo<UInt32>()),
|
||||
TypeCode.Int32 => (value, mbData) => mbData.SetInt32 (addr, value.ConvertTo<Int32>()),
|
||||
TypeCode.Single => (value, mbData) => mbData.SetFloat32(addr, value.ConvertTo<Single>()),
|
||||
TypeCode.UInt64 => (value, mbData) => mbData.SetUInt64 (addr, value.ConvertTo<UInt64>()),
|
||||
TypeCode.Int64 => (value, mbData) => mbData.SetInt64 (addr, value.ConvertTo<Int64>()),
|
||||
TypeCode.Double => (value, mbData) => mbData.SetFloat64(addr, value.ConvertTo<Double>()),
|
||||
_ => throw new ArgumentException(nameof(modbusType))
|
||||
};
|
||||
|
||||
return (rec, mbData) =>
|
||||
{
|
||||
var memberValue = get(rec);
|
||||
var encoded = encode(memberValue);
|
||||
|
||||
writeToMbData(encoded, mbData);
|
||||
return
|
||||
/**********************************/
|
||||
/**/ (value + offset) * scale; /**/
|
||||
/**********************************/
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -148,7 +189,10 @@ internal static class ModbusMembers
|
|||
{
|
||||
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 IEnumerable<MemberInfo> GetDataMembers<R>()
|
||||
private static IEnumerable<MemberInfo> GetDataMembers([DynamicallyAccessedMembers(All)] this Type recordType)
|
||||
{
|
||||
var recordType = typeof(R);
|
||||
|
||||
const BindingFlags bindingFlags = Instance
|
||||
| Public
|
||||
| NonPublic
|
||||
|
@ -176,4 +218,3 @@ internal static class ModbusMembers
|
|||
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.Reflection;
|
||||
using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
|
||||
|
||||
namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
|
||||
|
||||
using Float32 = Single;
|
||||
using Float64 = Double;
|
||||
|
||||
public class ModbusDevice<R> where R : notnull
|
||||
{
|
||||
private readonly IReadOnlyList<Batch<R>> _Batches;
|
||||
|
||||
public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0)
|
||||
{
|
||||
var offset = addressOffset + Record.GetAddressOffset<R>();
|
||||
_Batches = modbusClient.MakeBatchesFor<R>(offset).ToList();
|
||||
_Batches = modbusClient.MakeBatchesFor<R>(addressOffset);
|
||||
}
|
||||
|
||||
public R Read()
|
||||
|
@ -37,7 +35,7 @@ public class ModbusDevice<R> where R : notnull
|
|||
return Read(r);
|
||||
}
|
||||
|
||||
public R Read(R record)
|
||||
public R Read([DynamicallyAccessedMembers(All)] R record)
|
||||
{
|
||||
foreach (var batch in _Batches)
|
||||
batch.Read(record);
|
||||
|
@ -45,7 +43,7 @@ public class ModbusDevice<R> where R : notnull
|
|||
return record;
|
||||
}
|
||||
|
||||
public void Write(R record)
|
||||
public void Write([DynamicallyAccessedMembers(All)] R record)
|
||||
{
|
||||
foreach (var batch in _Batches)
|
||||
batch.Write(record);
|
||||
|
|
Loading…
Reference in New Issue