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; using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Protocols.Modbus.Reflection; #pragma warning disable CS8509 #pragma warning disable CS8524 internal record Batch(Action Read, Action Write, String DebugString) { public override String ToString() => DebugString; } public static class Batches { internal static IReadOnlyList> MakeBatchesFor(this ModbusClient modbusClient, Int32 addressOffset) { var members = ModbusMembers .From(addressOffset) .OrderBy(m => m.Kind) .ThenBy(m => m.StartAddress) .ThenBy(m => m.EndAddress); return MakeBatches(modbusClient, members).ToList(); } private static IEnumerable> MakeBatches(ModbusClient mb, IEnumerable modbusMembers) { var batchMembers = new List(); foreach (var member in modbusMembers) { if (CloseBatch(member)) { //PrintBatchStart(batchMembers); yield return MakeBatch(mb, batchMembers); batchMembers = new List(); } batchMembers.Add(member); } if (batchMembers.Count > 0) { //PrintBatchStart(batchMembers); yield return MakeBatch(mb, batchMembers); } Boolean CloseBatch(ModbusMember m) { if (batchMembers.Count == 0) return false; return m.StartAddress > batchMembers[^1].EndAddress // gap between registers || m.EndAddress > batchMembers[0].StartAddress + Constants.MaxRegs // max batch size reached || m.Kind != batchMembers[0].Kind // different Kind || m.IsWritable != batchMembers[0].IsWritable; // writable mismatch } } // for debuging private static void PrintBatchStart(List members) { if (members.Count == 0) return; var first = members.First(); var last = members.Last(); var range = first.StartAddress == last.EndAddress - 1 ? first.StartAddress.ToString() : $"{first.StartAddress}-{last.EndAddress - 1}"; var writable = first.IsWritable ? "Writable" : "ReadOnly"; Console.WriteLine($"📦 New Batch: {first.Kind} [{range}] ({writable})"); } private static Batch MakeBatch(ModbusClient modbusClient, IReadOnlyList members) { var startAddress = members[0].StartAddress; var endAddress = members[^1].EndAddress; var count = (UInt16)(endAddress - startAddress); var kind = members[0].Kind; //var isWritable = kind is ModbusKind.HoldingRegister or ModbusKind.Coil; var debugString = $"{kind}: {startAddress}" + (endAddress - 1 == startAddress ? "" : $"-{endAddress - 1}"); var isWritable = members.Any(m => m.IsWritable); //foreach (var m in members) // Console.WriteLine($"🧪 Address {m.StartAddress} Writable: {m.IsWritable}"); // var modPoll = $"{kind}: {startAddress}-{endAddress}"; // TODO var read = MakeRead(); var write = MakeWrite(); return new Batch(read, write, debugString); Action MakeRead() { Func readModbus = kind switch { 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) { member.ModbusToRecord(mbData, record!); } }; } Action MakeWrite() { if (!isWritable) return _ => { }; // nop Func createMbData = kind switch { ModbusKind.HoldingRegister => () => MbData.Registers(startAddress, count), ModbusKind.Coil => () => MbData.Coils (startAddress, count), }; Action writeModbus = kind switch { 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 }; //if (isWritable && members.Any(m => !m.IsWritable)) //{ // Console.WriteLine($"⚠️ Batch {debugString} has both writable and non-writable members — writing might fail."); //} var writableMembers = members.Where(m => m.IsWritable).ToList(); if (!writableMembers.Any()) return _ => { }; // No writable members = no-op*/ return rec => { var mbData = createMbData(); foreach (var member in writableMembers) member.RecordToModbus(rec!, mbData); writeModbus(mbData); }; } } }