Innovenergy_trunk/csharp/app/SaliMax/src/Program.cs

409 lines
15 KiB
C#

#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);
}
}
}