using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Slaves; using InnovEnergy.Lib.Time.Unix; 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) { 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; // 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 dc = DcBus.FromVoltageCurrent(busVoltage, busCurrent); // flatten the 2 strings of each SO into one array var strings = soStati.SelectMany(GetStrings).ToArray(nStrings); return new AmptStatus { Dc = dc, Strings = strings }; } private static IEnumerable GetStrings(StringOptimizerRegisters r) { // hardcoded: every SO has 2 strings (produced like this by AMPT) yield return DcBus.FromVoltageCurrent(r.String1Voltage, r.String1Current); yield return DcBus.FromVoltageCurrent(r.String2Voltage, 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); } }