321 lines
12 KiB
C#
321 lines
12 KiB
C#
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|