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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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.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);
};
}
}

View File

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

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