#undef BatteriesAllowed using System.Diagnostics; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Flurl.Http; using InnovEnergy.Lib.Devices.EmuMeter; using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; using InnovEnergy.Lib.Devices.Ampt; using InnovEnergy.Lib.Devices.Battery48TL; using InnovEnergy.Lib.Utils; using InnovEnergy.SaliMax.Controller; using InnovEnergy.SaliMax.Log; using InnovEnergy.SaliMax.SaliMaxRelays; using InnovEnergy.SaliMax.SystemConfig; using InnovEnergy.Time.Unix; using Utils = InnovEnergy.Lib.StatusApi.Utils; #pragma warning disable IL2026 namespace InnovEnergy.SaliMax; internal static class Program { private const UInt32 UpdateIntervalSeconds = 2; public static async Task Main(String[] args) { try { await Run(); } catch (Exception e) { await File.AppendAllTextAsync(Config.LogSalimaxLog, String.Join(Environment.NewLine, UnixTime.Now + " \n" + e)); throw; } } private static async Task Run() { Console.WriteLine("Starting SaliMax"); var s3Config = new S3Config { Bucket = "saliomameiringen", Region = "sos-ch-dk-2", Provider = "exo.io", ContentType = "text/plain; charset=utf-8", Key = "EXO2bf0cbd97fbfa75aa36ed46f", Secret = "Bn1CDPqOG-XpDSbYjfIJxojcHTm391vZTc8z8l_fEPs" }; #if DEBUG var inverterDevice = new TruConvertAcDevice("127.0.0.1", 5001); var dcDcDevice = new TruConvertDcDevice("127.0.0.1", 5002); var gridMeterDevice = new EmuMeterDevice("127.0.0.1", 5003); var saliMaxRelaysDevice = new SaliMaxRelaysDevice("127.0.0.1", 5004); var amptDevice = new AmptCommunicationUnit("127.0.0.1", 5005); var acInToAcOutMeterDevice = new EmuMeterDevice("127.0.0.1", 5003); // TODO: use real device var secondBattery48TlDevice = Battery48TlDevice.Fake(); var firstBattery48TlDevice =Battery48TlDevice.Fake();; var salimaxConfig = new SalimaxConfig(); #else #if BatteriesAllowed var firstBattery48TlDevice = new Battery48TlDevice("/dev/ttyUSB0", 2); var secondBattery48TlDevice = new Battery48TlDevice("/dev/ttyUSB0", 3); #endif var inverterDevice = new TruConvertAcDevice("192.168.1.2"); var dcDcDevice = new TruConvertDcDevice("192.168.1.3"); var gridMeterDevice = new EmuMeterDevice("192.168.1.241"); var acInToAcOutMeterDevice = new EmuMeterDevice("192.168.1.241"); // TODO: use real device var amptDevice = new AmptCommunicationUnit("192.168.1.249"); var saliMaxRelaysDevice = new SaliMaxRelaysDevice("192.168.1.242"); var salimaxConfig = new SalimaxConfig(); #endif // This is will be always add manually ? or do we need to read devices automatically in a range of IP @ #if BatteriesAllowed var battery48TlDevices = new[] { firstBattery48TlDevice, secondBattery48TlDevice }; #endif var dcDcDevices = new[] { dcDcDevice }; var inverterDevices = new[] { inverterDevice}; StatusRecord ReadStatus() { #if BatteriesAllowed var battery48TlStatusArray = battery48TlDevices.Select(b => b.ReadStatus()).NotNull().ToArray(); #endif // var dcDcStatusArray = dcDcDevices.Select(b => b.ReadStatus()).NotNull().ToArray(); // var inverterStatusArray = inverterDevices.Select(b => b.ReadStatus()).NotNull().ToArray(); return new StatusRecord { InverterStatus = inverterDevice.ReadStatus(), DcDcStatus = dcDcDevice.ReadStatus(), #if BatteriesAllowed BatteriesStatus = battery48TlStatusArray, AvgBatteriesStatus = AvgBatteriesStatus.ReadBatteriesStatus(battery48TlStatusArray), #else BatteriesStatus = null, AvgBatteriesStatus = null, #endif AcInToAcOutMeterStatus = acInToAcOutMeterDevice.ReadStatus(), GridMeterStatus = gridMeterDevice.ReadStatus(), SaliMaxRelayStatus = saliMaxRelaysDevice.ReadStatus(), AmptStatus = amptDevice.ReadStatus(), SalimaxConfig = salimaxConfig.Load().Result, }; } var startTime = UnixTime.Now; const Int32 delayTime = 10; await UploadTopology(s3Config, Salimax.TopologyToLog(startTime), startTime); DebugWriteTopology(startTime); Console.WriteLine("press ctrl-C to stop"); while (true) { var t = UnixTime.Now; while (t.Ticks % UpdateIntervalSeconds != 0) { await Task.Delay(delayTime); t = UnixTime.Now; } var status = ReadStatus(); #if BatteriesAllowed var jsonLog = status.ToLog(t); await UploadTimeSeries(s3Config, jsonLog, t); var controlRecord = Controller.Controller.SaliMaxControl(status); Controller.Controller.WriteControlRecord(controlRecord, inverterDevice, dcDcDevice, saliMaxRelaysDevice); //JsonSerializer.Serialize(jsonLog, JsonOptions).WriteLine(ConsoleColor.DarkBlue); #endif PrintTopology(status); while (UnixTime.Now == t) await Task.Delay(delayTime); } // ReSharper disable once FunctionNeverReturns } private static void PrintTopology(StatusRecord s) { const String chargingSeparator = ">>>>>>>>>>"; const String dischargingSeparator = "<<<<<<<<<"; const Int32 height = 25; var pwr = s.InverterStatus!.Ac.ActivePower; var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt var loadPower = Utils.Round3((s.GridMeterStatus!.ActivePowerL123 + pwr)); // it's a + because the pwr is inverted var gridSeparator = s.GridMeterStatus!.ActivePowerL123 > 0 ? chargingSeparator : dischargingSeparator; var inverterSeparator = -pwr > 0 ? chargingSeparator : dischargingSeparator; var dcSeparator = -s.DcDcStatus!.Dc.Power > 0 ? chargingSeparator : dischargingSeparator; #if BatteriesAllowed var battery1Separator = s.BatteriesStatus[0]!.Power > 0 ? chargingSeparator : dischargingSeparator; var battery2Separator = s.BatteriesStatus[1]!.Power > 0 ? chargingSeparator : dischargingSeparator; #endif ////////////////// Grid ////////////////////// var boxGrid = AsciiArt.CreateBox ( "Grid", s.GridMeterStatus.Ac.L1.Voltage.V(), s.GridMeterStatus.Ac.L2.Voltage.V(), s.GridMeterStatus.Ac.L3.Voltage.V() ).AlignCenterVertical(height); var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.ActivePowerL123.Round0(), gridSeparator) .AlignCenterVertical(height); ////////////////// Ac Bus ////////////////////// var boxAcBus = AsciiArt.CreateBox ( "AC Bus", s.InverterStatus.Ac.L1.Voltage.V(), s.InverterStatus.Ac.L2.Voltage.V(), s.InverterStatus.Ac.L3.Voltage.V() ); var boxLoad = AsciiArt.CreateBox ( "", "LOAD", "" ); var loadRect = CreateRect(boxAcBus, boxLoad, loadPower).AlignBottom(height); var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) .AlignCenterVertical(height); //////////////////// Inverter ///////////////////////// var inverterBox = AsciiArt.CreateBox ( "", "Inverter", "" ).AlignCenterVertical(height); var inverterArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) .AlignCenterVertical(height); //////////////////// DC Bus ///////////////////////// var dcBusBox = AsciiArt.CreateBox ( "DC Bus", (s.InverterStatus.ActualDcLinkVoltageLowerHalfExt + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt).V(), "" ); var pvBox = AsciiArt.CreateBox ( "MPPT", ((s.AmptStatus!.Devices[0].Dc.Voltage + s.AmptStatus!.Devices[1].Dc.Voltage) / 2).Round0().V(), "" ); var pvRect = CreateRect(pvBox, dcBusBox, pvPower).AlignTop(height); var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Dc.Power, dcSeparator) .AlignCenterVertical(height); //////////////////// Dc/Dc ///////////////////////// var dcBox = AsciiArt.CreateBox ( "Dc/Dc", s.DcDcStatus.BatteryVoltage.V(), "" ).AlignCenterVertical(height); #if BatteriesAllowed var dcArrow1 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[0]!.Power.Round0(), battery1Separator); var dcArrow2 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[1]!.Power.Round0(), battery2Separator); #else var dcArrow1 =""; var dcArrow2 = ""; var dcArrowRect = CreateRect(dcArrow1, dcArrow2).AlignCenterVertical(height); #endif #if BatteriesAllowed //////////////////// Batteries ///////////////////////// var battery1Box = AsciiArt.CreateBox ( "Battery 1", s.BatteriesStatus[0].Voltage.V(), s.BatteriesStatus[0].Soc.Percent(), s.BatteriesStatus[0].Temperature.Celsius() ); var battery2Box = AsciiArt.CreateBox ( "Battery 2", s.BatteriesStatus[1].Voltage.V(), s.BatteriesStatus[1].Soc.Percent(), s.BatteriesStatus[1].Temperature.Celsius() ); var batteryRect = CreateRect(battery1Box, battery2Box).AlignCenterVertical(height); var avgBatteryBox = AsciiArt.CreateBox ( "Batteries", s.AvgBatteriesStatus!.Voltage.V(), s.AvgBatteriesStatus.Soc.Percent(), s.AvgBatteriesStatus.Temperature.Celsius() ).AlignCenterVertical(height); var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") .SideBySideWith(loadRect, "") .SideBySideWith(acBusInvertArrow, "") .SideBySideWith(inverterBox, "") .SideBySideWith(inverterArrow, "") .SideBySideWith(pvRect, "") .SideBySideWith(dcBusArrow, "") .SideBySideWith(dcBox, "") .SideBySideWith(dcArrowRect, "") .SideBySideWith(batteryRect, "") .SideBySideWith(avgBatteryBox, "")+ "\n"; #else var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") .SideBySideWith(loadRect, "") .SideBySideWith(acBusInvertArrow, "") .SideBySideWith(inverterBox, "") .SideBySideWith(inverterArrow, "") .SideBySideWith(pvRect, "") .SideBySideWith(dcBusArrow, "") .SideBySideWith(dcBox, "")+ "\n"; #endif Console.WriteLine(topology); } private static String CreateRect(String boxTop, String boxBottom, Decimal power) { var powerArrow = AsciiArt.CreateVerticalArrow(power); var boxes = new[] { boxTop, powerArrow, boxBottom }; var maxWidth = boxes.Max(l => l.Width()); var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); return rect; } private static String CreateRect(String boxTop, String boxBottom) { var boxes = new[] { boxTop, boxBottom }; var maxWidth = boxes.Max(l => l.Width()); var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); return rect; } // to delete not used anymore [Conditional("RELEASE")] private static void ReleaseWriteLog(JsonObject jsonLog, UnixTime timestamp) { // WriteToFile(jsonLog, "/home/debian/DataSaliMax/" + timestamp); // this is was for beaglebone TODO } // [Conditional("RELEASE")] private static JsonObject ReleaseWriteTopology(UnixTime timestamp) { var topologyJson = Salimax.TopologyToLog(timestamp); // WriteToFile(topologyJson, "/home/debian/DataSaliMax/topology" + timestamp); // this is was for beaglebone return topologyJson; } [Conditional("DEBUG")] private static void DebugWriteTopology(UnixTime timestamp) { var topologyJson = Salimax.TopologyToLog(timestamp); WriteToFile(topologyJson, "/home/atef/JsonData/topology" + timestamp); } [Conditional("DEBUG")] private static void DebugWriteLog(JsonObject jsonLog, UnixTime timestamp) { WriteToFile(jsonLog, "/home/atef/JsonData/" + timestamp); } private static void WriteToFile(Object obj, String fileName) { var jsonString = JsonSerializer.Serialize(obj, JsonOptions); File.WriteAllText(fileName, jsonString); } private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, IgnoreReadOnlyProperties = false, Converters = { new JsonStringEnumConverter() }, NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, //TODO }; private static async Task UploadTimeSeries(S3Config config, JsonObject json, UnixTime unixTime) { var payload = JsonSerializer.Serialize(json, JsonOptions); var s3Path = unixTime.Ticks + ".json"; var request = config.CreatePutRequest(s3Path); var response = await request.PutAsync(new StringContent(payload)); if (response.StatusCode != 200) { Console.WriteLine("ERROR: PUT"); var error = response.GetStringAsync(); Console.WriteLine(error); } } private static async Task UploadTopology(S3Config config, JsonObject json, UnixTime unixTime) { var payload = JsonSerializer.Serialize(json, JsonOptions); var s3Path = "topology" + unixTime.Ticks + ".json"; var request = config.CreatePutRequest(s3Path); var response = await request.PutAsync(new StringContent(payload)); if (response.StatusCode != 200) { Console.WriteLine("ERROR: PUT"); var error = response.GetStringAsync(); Console.WriteLine(error); } } }