Modbus V2

This commit is contained in:
ig 2023-06-13 13:03:36 +02:00
parent 045de6ac0d
commit 7a44b1f307
33 changed files with 454 additions and 283 deletions

View File

@ -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;
} }

View File

@ -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
{ {

View File

@ -1,20 +1,17 @@
<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>
<IsTrimmable>false</IsTrimmable>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../../Utils/Utils.csproj" /> <ProjectReference Include="../../Utils/Utils.csproj"/>
<ProjectReference Include="../../SrcGen/SrcGen.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.IO.Ports" Version="7.0.0" /> <PackageReference Include="System.IO.Ports" Version="7.0.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -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;
}
} }

View File

@ -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,6 +130,7 @@ 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,
@ -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);
} }

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,10 @@
namespace InnovEnergy.Lib.Protocols.Modbus.Protocol;
[Flags]
public enum ModbusKind : byte
{
HoldingRegister,
InputRegister,
Coil,
DiscreteInput,
}

View File

@ -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;
} }

View File

@ -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)
{
}
}

View File

@ -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)
{
}
}

View File

@ -1,6 +0,0 @@
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
public class CoilAttribute : ModbusBooleanAttribute
{
public CoilAttribute(UInt16 address) : base(address) { }
}

View File

@ -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) { }
}

View File

@ -1,7 +0,0 @@
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
public class DiscreteInputAttribute : ModbusBooleanAttribute
{
public DiscreteInputAttribute(UInt16 address) : base(address) { }
}

View File

@ -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; }
}

View File

@ -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))
{
}
}

View File

@ -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) { }
}

View File

@ -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))
{
}
}

View File

@ -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) { }
}

View File

@ -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)
{
}
}

View File

@ -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,
};
} }

View File

@ -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)
{
}
}

View File

@ -1,9 +0,0 @@
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
public class ModbusBooleanAttribute : ModbusAttribute
{
protected ModbusBooleanAttribute(UInt16 address) : base(address, TypeCode.Boolean)
{
}
}

View File

@ -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),
};
}

View File

@ -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
};
}

View File

@ -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)
{
}
}

View File

@ -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 =>
{
foreach (var member in members)
{ {
var mbData = createMbData(); var mbData = createMbData();
foreach (var member in members)
member.RecordToModbus(rec!, mbData); member.RecordToModbus(rec!, mbData);
writeModbus(mbData); writeModbus(mbData);
}
}; };
} }
} }

View File

@ -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,71 +24,140 @@ 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)
{
return recordType
.GetCustomAttributes<AddressOffset>()
.Aggregate(globalAddressOffset, (a, b) => a + b.Offset);
}
private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset) 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 attribute = info.GetCustomAttributes<ModbusAttribute>().Single();
var startAddress = (UInt16)(attribute.Address + addressOffset); var endian = info.GetCustomAttributes<EndianAttribute>()
var endAddress = (UInt16)(startAddress + attribute.Size); .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 modbusType = attribute.ModbusType;
var transform = attribute is ModbusRegisterAttribute mra var transform = attribute is ModbusRegister mra
? (mra.Scale, mra.Offset) ? (mra.Scale, mra.Offset)
: NoTransform; : NoTransform;
var modbusToRecord = info.ModbusToRecord(startAddress, modbusType, transform); //Console.WriteLine(info.Name +" " + address + " " + modbusType);
var recordToModbus = info.RecordToModbus(startAddress, modbusType, transform);
return new ModbusMember return new ModbusMember
( (
startAddress, address,
endAddress, endAddress,
attribute, attribute.Kind,
modbusToRecord, ModbusToRecord(),
recordToModbus RecordToModbus()
); );
}
private static Action<MbData, Object> ModbusToRecord(this MemberInfo info, UInt16 address, TypeCode modbusType, (Double scale, Double offset) transform) Action<MbData, Object> ModbusToRecord()
{ {
var decode = ConvertModbusToRecord(transform); var decode = ConvertModbusToRecord(transform);
Func<MbData, IConvertible> readFromMbData = modbusType switch 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
{ {
TypeCode.Boolean => d => d.GetInput (address), FieldInfo fi => fi.FieldType,
TypeCode.UInt16 => d => d.GetUInt16 (address), PropertyInfo pi => pi.PropertyType,
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))
}; };
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 Action<Object, IConvertible> set = info switch
{ {
FieldInfo fi => (rec, value) => fi.SetValue(rec, value.ConvertTo(fi.FieldType)), FieldInfo fi => (rec, value) => fi.SetValue(rec, convert(value)),
PropertyInfo pi => (rec, value) => pi.SetValue(rec, value.ConvertTo(pi.PropertyType)), PropertyInfo pi => (rec, value) => pi.SetValue(rec, convert(value)),
}; };
return (mbData, rec) => return (mbData, rec) =>
{ {
var rawModbusValue = readFromMbData(mbData); var rawModbusValue = readFromMbData(mbData.WithEndian(endian));
var decoded = decode(rawModbusValue); var decoded = decode(rawModbusValue);
set(rec, decoded); 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 Func<IConvertible, IConvertible> ConvertModbusToRecord((Double scale, Double offset) transform) private static Func<IConvertible, IConvertible> ConvertModbusToRecord((Double scale, Double offset) transform)
{ {
if (transform == NoTransform) if (transform == NoTransform)
@ -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();
} }
} }

View File

@ -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();
}
}

View File

@ -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);