diff --git a/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs index 46fd27f72..98ecd1cd5 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs @@ -125,12 +125,12 @@ public class RemoteSerialChannel : ConnectionChannel private CommandTask? _CommandTask; - public RemoteSerialChannel(SshHost host, - String tty, - Int32 baudRate, + public RemoteSerialChannel(SshHost host, + 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 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 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; } diff --git a/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs index 904be5b29..35e6ef694 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs @@ -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 Boolean closeAfterSuccessfulRead = false, Boolean closeAfterSuccessfulWrite = false) : - base( - closeAfterSuccessfulRead, - closeAfterSuccessfulWrite - ) + base(closeAfterSuccessfulRead, closeAfterSuccessfulWrite) { var sb = stopBits switch { diff --git a/csharp/Lib/Protocols/Modbus/Modbus.csproj b/csharp/Lib/Protocols/Modbus/Modbus.csproj index 85aaefa65..95b2d054a 100644 --- a/csharp/Lib/Protocols/Modbus/Modbus.csproj +++ b/csharp/Lib/Protocols/Modbus/Modbus.csproj @@ -1,21 +1,18 @@ - - Debug;Release;Release-Server - AnyCPU;linux-arm - - - - - false - - - - - + + + + - - - + + + + + + + diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Endian.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Endian.cs index 5ec75397d..7c6597d42 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Endian.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Endian.cs @@ -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; + } } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbData.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbData.cs index 42f4efc9d..4273882b7 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbData.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbData.cs @@ -6,7 +6,8 @@ using Float32 = Single; using Float64 = Double; // switch exhaustion -#pragma warning disable CS8524 +#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(); 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 data, UInt16 startAddress, Endian endian) @@ -47,6 +48,11 @@ public struct MbData _StartAddress = startAddress; _Data = data; } + + public MbData WithEndian(Endian endian) + { + return new MbData(_Data, _StartAddress, endian.InheritFrom(_Endian)); + } #region Coils @@ -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); } diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs index 5e594a059..5edbaef45 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs @@ -30,11 +30,11 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame private ReadHoldingRegistersResponseFrame(ArraySegment 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)); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs index 05e8ed3bd..63479732c 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs @@ -31,11 +31,11 @@ internal class ReadInputRegistersResponseFrame : ModbusFrame private ReadInputRegistersResponseFrame(ArraySegment 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)); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs index 43eed5e21..0248feb10 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs @@ -32,11 +32,11 @@ internal class ReadWriteRegistersResponseFrame : ModbusFrame private ReadWriteRegistersResponseFrame(ArraySegment 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)); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs index dc83498f0..d48c7c33e 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs @@ -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(data) ) { } - private WriteCoilsResponseFrame(ArraySegment 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); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs index f77e7793c..bf64eac81 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs @@ -27,10 +27,10 @@ internal class WriteRegistersResponseFrame : ModbusFrame private WriteRegistersResponseFrame(ArraySegment 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 data) => new WriteRegistersResponseFrame(data); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/ModbusKind.cs b/csharp/Lib/Protocols/Modbus/Protocol/ModbusKind.cs new file mode 100644 index 000000000..f97d6cce0 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/ModbusKind.cs @@ -0,0 +1,10 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol; + +[Flags] +public enum ModbusKind : byte +{ + HoldingRegister, + InputRegister, + Coil, + DiscreteInput, +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffsetAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffset.cs similarity index 59% rename from csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffsetAttribute.cs rename to csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffset.cs index 48e35cdd0..8e45974ac 100644 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffsetAttribute.cs +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffset.cs @@ -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; } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/BigEndian.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/BigEndian.cs new file mode 100644 index 000000000..410b35b13 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/BigEndian.cs @@ -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) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs new file mode 100644 index 000000000..1a73e8142 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs @@ -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) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/CoilAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/CoilAttribute.cs deleted file mode 100644 index 833f64208..000000000 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/CoilAttribute.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; - -public class CoilAttribute : ModbusBooleanAttribute -{ - public CoilAttribute(UInt16 address) : base(address) { } -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInput.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInput.cs new file mode 100644 index 000000000..8840f8f33 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInput.cs @@ -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) { } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInputAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInputAttribute.cs deleted file mode 100644 index 1f5719fdd..000000000 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInputAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; - -public class DiscreteInputAttribute : ModbusBooleanAttribute -{ - public DiscreteInputAttribute(UInt16 address) : base(address) { } -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/EndianAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/EndianAttribute.cs new file mode 100644 index 000000000..de9fa6dc8 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/EndianAttribute.cs @@ -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; } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegister.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegister.cs new file mode 100644 index 000000000..0e0128a4c --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegister.cs @@ -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 : HoldingRegister where T : IConvertible +{ + public HoldingRegister(UInt16 address) : base(address, typeof(T)) + { + } +} + + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegisterAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegisterAttribute.cs deleted file mode 100644 index 5b1d9cf85..000000000 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegisterAttribute.cs +++ /dev/null @@ -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) { } -} - - diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegister.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegister.cs new file mode 100644 index 000000000..38ce1accd --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegister.cs @@ -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 : InputRegister where T : IConvertible +{ + public InputRegister(UInt16 address) : base(address, typeof(T)) + { + } +} + + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegisterAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegisterAttribute.cs deleted file mode 100644 index 76649a15d..000000000 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegisterAttribute.cs +++ /dev/null @@ -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) { } -} - diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/LittleEndian.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/LittleEndian.cs new file mode 100644 index 000000000..06de598cf --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/LittleEndian.cs @@ -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) + { + } +} + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs index 057a8e799..cbe40dda8 100644 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs @@ -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; } -} \ No newline at end of file + + 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 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, + }; +} + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBoolean.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBoolean.cs new file mode 100644 index 000000000..fa527d80e --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBoolean.cs @@ -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) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBooleanAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBooleanAttribute.cs deleted file mode 100644 index d2a10da79..000000000 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBooleanAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ - -namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; - -public class ModbusBooleanAttribute : ModbusAttribute -{ - protected ModbusBooleanAttribute(UInt16 address) : base(address, TypeCode.Boolean) - { - } -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegister.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegister.cs new file mode 100644 index 000000000..6a456a48f --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegister.cs @@ -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), + }; +} + + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegisterAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegisterAttribute.cs deleted file mode 100644 index 1ee6a3907..000000000 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegisterAttribute.cs +++ /dev/null @@ -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 - }; - -} - diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/OneBasedAddressing.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/OneBasedAddressing.cs new file mode 100644 index 000000000..de10759fc --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/OneBasedAddressing.cs @@ -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) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs b/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs index fee6090ae..21542ba89 100644 --- a/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs +++ b/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs @@ -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(Action Read, Action Write); public static class Batches { - internal static IEnumerable> MakeBatchesFor(this ModbusClient modbusClient, Int32 addressOffset) + internal static IReadOnlyList> MakeBatchesFor(this ModbusClient modbusClient, Int32 addressOffset) { var members = ModbusMembers .From(addressOffset) - .OrderBy(m => m.Attribute.GetType().Name) + .OrderBy(m => m.Kind) .ThenBy(m => m.StartAddress) .ThenBy(m => m.EndAddress); - return MakeBatches(modbusClient, members); + return MakeBatches(modbusClient, members).ToList(); } private static IEnumerable> MakeBatches(ModbusClient mb, IEnumerable 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(Read(), Write()); Action Read() { - Func readModbus = attribute switch + Func 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 createMbData = attribute switch + Func 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 writeModbus = attribute switch + Action 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); }; } } diff --git a/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs b/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs index cd435a711..ed62ba2b1 100644 --- a/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs +++ b/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs @@ -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 ModbusToRecord, Action RecordToModbus ); @@ -21,71 +24,140 @@ internal static class ModbusMembers { private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0); - internal static IEnumerable From(Int32 addressOffset) + internal static IEnumerable From(Int32 globalAddressOffset, Endian globalEndian = Endian.Undefined) { - return GetDataMembers() + var recordType = typeof(R); + + // "=======================================================================".WriteLine(); + // recordType.Name.WriteLine(); + + var offset = recordType.GetRecordOffset(globalAddressOffset); + var endian = recordType.GetEndian(globalEndian); + + return recordType + .GetDataMembers() .Where(HasAttribute) - .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().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() + .Aggregate(globalAddressOffset, (a, b) => a + b.Offset); + } + + private static Endian GetEndian([DynamicallyAccessedMembers(All)] this Type recordType, Endian endian) + { + return recordType + .GetCustomAttributes() + .Aggregate(endian, (a, b) => b.Endian.InheritFrom(a)); + } + + private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset, Endian globalEndian) + { + var attribute = info.GetCustomAttributes().Single(); + var endian = info.GetCustomAttributes() + .Select(a => a.Endian) + .Append(Endian.Undefined) + .First() + .InheritFrom(globalEndian); - var modbusToRecord = info.ModbusToRecord(startAddress, modbusType, transform); - var recordToModbus = info.RecordToModbus(startAddress, modbusType, transform); + 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() ); - } - private static Action ModbusToRecord(this MemberInfo info, UInt16 address, TypeCode modbusType, (Double scale, Double offset) transform) - { - var decode = ConvertModbusToRecord(transform); - - Func readFromMbData = modbusType switch + Action ModbusToRecord() { - 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 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)), - }; + var decode = ConvertModbusToRecord(transform); + + Func 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)); - return (mbData, rec) => - { - var rawModbusValue = readFromMbData(mbData); - var decoded = decode(rawModbusValue); + 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 convert = ctr is null + ? value => value.ConvertTo(memberType) + : value => ctr.Invoke(new Object[] { value.ConvertTo() }); - set(rec, decoded); - }; + Action 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 RecordToModbus() + { + var encode = ConvertRecordToModbus(transform); + + Func get = info switch + { + FieldInfo fi => rec => (IConvertible)fi.GetValue(rec)!, + PropertyInfo pi => rec => (IConvertible)pi.GetValue(rec)!, + }; + + Action writeToMbData = + modbusType == typeof(Boolean)? (value, mbData) => mbData.SetCoil (address, value.ConvertTo()) : + modbusType == typeof(UInt16) ? (value, mbData) => mbData.SetUInt16 (address, value.ConvertTo()) : + modbusType == typeof(Int16) ? (value, mbData) => mbData.SetInt16 (address, value.ConvertTo()) : + modbusType == typeof(UInt32) ? (value, mbData) => mbData.SetUInt32 (address, value.ConvertTo()) : + modbusType == typeof(Int32) ? (value, mbData) => mbData.SetInt32 (address, value.ConvertTo()) : + modbusType == typeof(Single) ? (value, mbData) => mbData.SetFloat32(address, value.ConvertTo()) : + modbusType == typeof(UInt64) ? (value, mbData) => mbData.SetUInt64 (address, value.ConvertTo()) : + modbusType == typeof(Int64) ? (value, mbData) => mbData.SetInt64 (address, value.ConvertTo()) : + modbusType == typeof(Double) ? (value, mbData) => mbData.SetFloat64(address, value.ConvertTo()) : + throw new ArgumentException(nameof(modbusType)); + + return (rec, mbData) => + { + var memberValue = get(rec); + var encoded = encode(memberValue); + + writeToMbData(encoded, mbData.WithEndian(endian)); + }; + } } + private static Func ConvertModbusToRecord((Double scale, Double offset) transform) { if (transform == NoTransform) @@ -98,41 +170,10 @@ internal static class ModbusMembers { var value = c.ConvertTo(); - return (value + offset) * scale; - }; - } - - - private static Action RecordToModbus(this MemberInfo info, UInt16 addr, TypeCode modbusType, (Double scale, Double offset) transform) - { - var encode = ConvertRecordToModbus(transform); - - Func get = info switch - { - FieldInfo fi => rec => (IConvertible)fi.GetValue(rec)!, - PropertyInfo pi => rec => (IConvertible)pi.GetValue(rec)!, - }; - - Action writeToMbData = modbusType switch - { - TypeCode.Boolean => (value, mbData) => mbData.SetCoil (addr, value.ConvertTo()), - TypeCode.UInt16 => (value, mbData) => mbData.SetUInt16 (addr, value.ConvertTo()), - TypeCode.Int16 => (value, mbData) => mbData.SetInt16 (addr, value.ConvertTo()), - TypeCode.UInt32 => (value, mbData) => mbData.SetUInt32 (addr, value.ConvertTo()), - TypeCode.Int32 => (value, mbData) => mbData.SetInt32 (addr, value.ConvertTo()), - TypeCode.Single => (value, mbData) => mbData.SetFloat32(addr, value.ConvertTo()), - TypeCode.UInt64 => (value, mbData) => mbData.SetUInt64 (addr, value.ConvertTo()), - TypeCode.Int64 => (value, mbData) => mbData.SetInt64 (addr, value.ConvertTo()), - TypeCode.Double => (value, mbData) => mbData.SetFloat64(addr, value.ConvertTo()), - _ => throw new ArgumentException(nameof(modbusType)) - }; - - return (rec, mbData) => - { - var memberValue = get(rec); - var encoded = encode(memberValue); - - writeToMbData(encoded, mbData); + return + /**********************************/ + /**/ (value + offset) * scale; /**/ + /**********************************/ }; } @@ -147,8 +188,11 @@ internal static class ModbusMembers return c => { var value = c.ConvertTo(); - - return value / scale - offset; + + return + /*******************************/ + /**/ value / scale - offset; /**/ + /*******************************/ }; } @@ -156,13 +200,11 @@ internal static class ModbusMembers private static T Nop(T c) => c; - private static IEnumerable GetDataMembers() + private static IEnumerable GetDataMembers([DynamicallyAccessedMembers(All)] this Type recordType) { - var recordType = typeof(R); - - const BindingFlags bindingFlags = Instance - | Public - | NonPublic + const BindingFlags bindingFlags = Instance + | Public + | NonPublic | FlattenHierarchy; var fields = recordType.GetFields(bindingFlags); @@ -170,10 +212,9 @@ internal static class ModbusMembers return fields.Concat(props); } - + private static Boolean HasAttribute(MemberInfo i) where T : Attribute { return i.GetCustomAttributes().Any(); } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Record.cs b/csharp/Lib/Protocols/Modbus/Reflection/Record.cs deleted file mode 100644 index da37a95d3..000000000 --- a/csharp/Lib/Protocols/Modbus/Reflection/Record.cs +++ /dev/null @@ -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() - { - return typeof(R) - .GetCustomAttributes() - .Select(a => a.Offset) - .FirstOrDefault(); - } -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs index b8bf2b2ca..8f0acf770 100644 --- a/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs @@ -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 where R : notnull { private readonly IReadOnlyList> _Batches; public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0) { - var offset = addressOffset + Record.GetAddressOffset(); - _Batches = modbusClient.MakeBatchesFor(offset).ToList(); + _Batches = modbusClient.MakeBatchesFor(addressOffset); } public R Read() @@ -37,7 +35,7 @@ public class ModbusDevice 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 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);