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; #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 battery48TlDevice = Battery48TlDevice.Fake(); var salimaxConfig = new SalimaxConfig(); #else var battery48TlDevice = new Battery48TlDevice("/dev/ttyUSB0", 3); 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 StatusRecord ReadStatus() { return new StatusRecord { InverterStatus = inverterDevice.ReadStatus(), DcDcStatus = dcDcDevice.ReadStatus(), BatteryStatus = battery48TlDevice.ReadStatus(), AcInToAcOutMeterStatus = acInToAcOutMeterDevice.ReadStatus(), GridMeterStatus = gridMeterDevice.ReadStatus(), SaliMaxRelayStatus = saliMaxRelaysDevice.ReadStatus(), AmptStatus = amptDevice.ReadStatus(), SalimaxConfig = salimaxConfig.Load().Result, }; } var startTime = UnixTime.Now; const Int32 delayTime = 10; ReleaseWriteTopology(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(); //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); //ReleaseWriteLog(jsonLog, t); 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 boxSize = chargingSeparator.Length / 2; var pwr = s.InverterStatus!.PowerAcL1 + s.InverterStatus.PowerAcL2 + s.InverterStatus.PowerAcL3; var pvPower = (s.AmptStatus!.Voltage1 * s.AmptStatus.Current1 + s.AmptStatus!.Voltage2 * s.AmptStatus.Current2).Round0(); var loadPower = (s.GridMeterStatus!.ActivePowerL123 + pwr).Round3(); // 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!.DcPower > 0 ? chargingSeparator : dischargingSeparator; var batterySeparator = s.BatteryStatus!.Power > 0 ? chargingSeparator : dischargingSeparator; ////////////////// Grid ////////////////////// var boxGrid = AsciiArt.CreateBox ( "Grid", s.GridMeterStatus.VoltageL1N.V(), s.GridMeterStatus.VoltageL2N.V(), s.GridMeterStatus.VoltageL3N.V() ).AlignCenterVertical(height); //var gridBox = CreateRect(0, boxGrid).AlignBottom(height); var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.ActivePowerL123.Round0(), gridSeparator).AlignCenterVertical(height); ////////////////// Ac Bus ////////////////////// var boxAcBus = AsciiArt.CreateBox ( "AC Bus", s.InverterStatus.GridVoltageL1.V(), s.InverterStatus.GridVoltageL2.V(), s.InverterStatus.GridVoltageL3.V() ); var boxLoad = AsciiArt.CreateBox ( "", "LOAD", "" ); var loadRect = CreateRect(loadPower, boxAcBus, boxLoad).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", "" ); var pvRect = CreateRect(pvPower, pvBox, dcBusBox).AlignTop(height); var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.DcPower.Round0(), dcSeparator).AlignCenterVertical(height); //////////////////// Dc/Dc ///////////////////////// var dcBox = AsciiArt.CreateBox ( "", "Dc/Dc", "" ).AlignCenterVertical(height); var dcArrow = AsciiArt.CreateHorizontalArrow(s.BatteryStatus!.Power.Round0(), batterySeparator).AlignCenterVertical(height); //////////////////// Battery ///////////////////////// var batteryBox = AsciiArt.CreateBox ( "Battery", s.BatteryStatus!.Voltage.V(), "" ).AlignCenterVertical(height); var batteryDegree = AsciiArt.CreateTransitionPadLeft(s.BatteryStatus!.Soc.Percent(), s.BatteryStatus!.BatteryTemperature.Celsius()).AlignCenterVertical(height); // var loadLength = boxSize * 6; // var mpptLength = boxSize * 14; // // var mpptBox = AsciiArt.CreateVerticalPad(pvPower.W(),(Int16)mpptLength, true, "MPPT") + "\n"; // var loadBox = AsciiArt.CreateVerticalPad(loadPower.W(), (Int16)loadLength, false, "Load"); var topology = boxGrid.SideBySideWith(gridAcBusArrow,"") .SideBySideWith(loadRect,"") .SideBySideWith(acBusInvertArrow,"") .SideBySideWith(inverterBox,"") .SideBySideWith(inverterArrow,"") .SideBySideWith(pvRect,"") .SideBySideWith(dcBusArrow,"") .SideBySideWith(dcBox,"") .SideBySideWith(dcArrow,"") .SideBySideWith(batteryBox,"") .SideBySideWith(batteryDegree, "") + "\n"; // var topology = mpptBox + boxSide11 + loadBox; Console.WriteLine(topology); // Console.WriteLine(loadRect); // Console.WriteLine(pvRect); } private static String CreateRect(Decimal loadPower, String boxAcBus, String boxLoad = "") { var loadAcBusArrow = AsciiArt.CreateVerticalArrow(loadPower); var boxes = new[] { boxAcBus, loadAcBusArrow, boxLoad }; var maxWidth = boxes.Max(l => l.Width()); var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); return rect; } [Conditional("RELEASE")] private static void ReleaseWriteLog(JsonObject jsonLog, UnixTime timestamp) { WriteToFile(jsonLog, "/home/debian/DataSaliMax/" + timestamp); } [Conditional("RELEASE")] private static void ReleaseWriteTopology(UnixTime timestamp) { var topologyJson = Salimax.TopologyToLog(timestamp); WriteToFile(topologyJson, "/home/debian/DataSaliMax/topology" + timestamp); } [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); } } }