using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Slaves; using InnovEnergy.Lib.Units.Composite; using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Devices.AMPT; public class AmptDevices { private readonly ModbusDevice _CommunicationUnit; private readonly IEnumerable> _StringOptimizers; public AmptDevices(String hostname, UInt16 port = 502) : this(new TcpChannel(hostname, port)) { } public AmptDevices(Channel transport) : this(new ModbusTcpClient(transport, 2)) { } public AmptDevices(ModbusClient modbusClient) { _CommunicationUnit = new ModbusDevice(modbusClient); _StringOptimizers = StringOptimizers(modbusClient); } public AmptStatus? Read() { try { return TryRead(); } catch ( Exception e) { if( e is not NullChannelException) Console.WriteLine("Failed to read Ampt data \n" + e.Message); return null; } } public AmptStatus TryRead() { var cuStatus = _CommunicationUnit.Read(); // CommunicationUnit knows how many StringOptimizers are connected var nStringOptimizers = cuStatus.NumberOfStringOptimizers; //Factor var nEnergyFactor = cuStatus.EnergyScaleFactor; var nCurrentFactor = cuStatus.CurrentScaleFactor; var nVoltageFactor = cuStatus.VoltageScaleFactor; // hardcoded: every SO has 2 strings (produced like this by AMPT) var nStrings = nStringOptimizers * 2; // read stati from optimizers var soStati = _StringOptimizers .Take(nStringOptimizers) .Select(so => so.Read()) .ToArray(nStringOptimizers); // every SO has 2 strings but ONE Dc Link Connection // they are connected to a shared Dc Link, so Voltage seen by them should be approx the same. // voltages are averaged, currents added // TODO: alarm when we see substantially different voltages var busVoltage = nStringOptimizers == 0 ? 0 : soStati.Average(r => r.Voltage); var busCurrent = nStringOptimizers == 0 ? 0 : soStati.Sum (r => r.Current); var dailyOutputEnergy = nStringOptimizers == 0 ? 0 : soStati.Sum (r => r.ProductionToday); var dc = new DcBus { Voltage = busVoltage, Current = busCurrent }; // flatten the 2 strings of each SO into one array var strings = soStati.SelectMany(GetStrings).ToArray(nStrings); return new AmptStatus { Dc = dc, NbrOfStrings = nStringOptimizers, Strings = strings, DcWh = dailyOutputEnergy }; } private static IEnumerable GetStrings(StringOptimizerRegisters r) { // hardcoded: every SO has 2 strings (produced like this by AMPT) yield return new() { Voltage = r.String1Voltage, Current = r.String1Current, }; yield return new() { Voltage = r.String2Voltage, Current = r.String2Current, }; } private static IEnumerable> StringOptimizers(ModbusClient modbusClient) { var cache = new List>(); ModbusDevice GetOptimizer(Int32 i) { if (i < cache.Count) return cache[i]; var modbusDevice = new ModbusDevice(modbusClient, i * 16); cache.Add(modbusDevice); return modbusDevice; } return Enumerable .Range(0, Byte.MaxValue) .Select(GetOptimizer); } }