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

391 lines
15 KiB
C#
Raw Normal View History

2023-06-13 10:53:17 +00:00
using System.Runtime.InteropServices;
2023-02-16 12:57:06 +00:00
using Flurl.Http;
2023-06-13 10:53:17 +00:00
using InnovEnergy.App.SaliMax.Ess;
using InnovEnergy.App.SaliMax.SaliMaxRelays;
2023-06-13 10:53:17 +00:00
using InnovEnergy.App.SaliMax.System;
using InnovEnergy.App.SaliMax.SystemConfig;
2023-06-20 08:21:06 +00:00
using InnovEnergy.App.SaliMax.VirtualDevices;
using InnovEnergy.Lib.Devices.AMPT;
using InnovEnergy.Lib.Devices.Battery48TL;
2023-02-16 12:57:06 +00:00
using InnovEnergy.Lib.Devices.EmuMeter;
2023-06-13 10:53:17 +00:00
using InnovEnergy.Lib.Devices.Trumpf.SystemControl;
using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes;
2023-02-16 12:57:06 +00:00
using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
2023-06-13 10:53:17 +00:00
using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes;
2023-02-16 12:57:06 +00:00
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
2023-06-13 10:53:17 +00:00
using InnovEnergy.Lib.Protocols.Modbus.Channels;
using InnovEnergy.Lib.Time.Unix;
2023-06-13 10:53:17 +00:00
using InnovEnergy.Lib.Units;
2023-04-04 14:37:37 +00:00
using InnovEnergy.Lib.Utils;
2023-06-13 10:53:17 +00:00
using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig;
2023-06-20 08:21:06 +00:00
using AcPower = InnovEnergy.Lib.Units.Composite.AcPower;
2023-06-13 10:53:17 +00:00
using Exception = System.Exception;
2023-02-16 12:57:06 +00:00
#pragma warning disable IL2026
namespace InnovEnergy.App.SaliMax;
2023-02-16 12:57:06 +00:00
internal static class Program
{
2023-06-13 10:53:17 +00:00
[DllImport("libsystemd.so.0")]
private static extern Int32 sd_notify(Int32 unsetEnvironment, String state);
2023-02-16 12:57:06 +00:00
private const UInt32 UpdateIntervalSeconds = 2;
2023-06-13 10:53:17 +00:00
2023-06-20 08:21:06 +00:00
private static readonly Byte[] BatteryNodes = { 2, 3, 4, 5, 6 };
2023-06-13 10:53:17 +00:00
private const String BatteryTty = "/dev/ttyUSB0";
// private const String RelaysIp = "10.0.1.1"; // "192.168.1.242";
// private const String TruConvertAcIp = "10.0.2.1"; // "192.168.1.2";
// private const String TruConvertDcIp = "10.0.3.1"; // "192.168.1.3";
// private const String GridMeterIp = "10.0.4.1"; // "192.168.1.241";
// private const String InternalMeter = "10.0.4.2"; // "192.168.1.241";
// private const String AmptIp = "10.0.5.1"; // "192.168.1.249";
private static readonly TcpChannel TruConvertAcChannel = new TcpChannel("localhost", 5001);
private static readonly TcpChannel TruConvertDcChannel = new TcpChannel("localhost", 5002);
private static readonly TcpChannel GridMeterChannel = new TcpChannel("localhost", 5003);
private static readonly TcpChannel AcOutLoadChannel = new TcpChannel("localhost", 5004);
private static readonly TcpChannel AmptChannel = new TcpChannel("localhost", 5005);
private static readonly TcpChannel RelaysChannel = new TcpChannel("localhost", 5006);
private static readonly TcpChannel BatteriesChannel = new TcpChannel("localhost", 5007);
2023-06-13 10:53:17 +00:00
private static readonly S3Config 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"
};
2023-02-16 12:57:06 +00:00
public static async Task Main(String[] args)
{
2023-06-13 10:53:17 +00:00
while (true)
2023-02-16 12:57:06 +00:00
{
2023-06-13 10:53:17 +00:00
try
{
await Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
2023-02-16 12:57:06 +00:00
}
}
private static async Task Run()
{
Console.WriteLine("Starting SaliMax");
2023-04-04 14:37:37 +00:00
2023-06-13 10:53:17 +00:00
// Send the initial "service started" message to systemd
var sdNotifyReturn = sd_notify(0, "READY=1");
2023-04-04 14:37:37 +00:00
2023-06-13 10:53:17 +00:00
var battery48TlDevices = BatteryNodes
.Select(n => new Battery48TlDevice(BatteriesChannel, n))
.ToList();
2023-06-20 08:21:06 +00:00
var batteryDevices = new Battery48TlDevices(battery48TlDevices);
var acDcDevices = new TruConvertAcDcDevices(TruConvertAcChannel);
var dcDcDevices = new TruConvertDcDcDevices(TruConvertDcChannel);
var gridMeterDevice = new EmuMeterDevice(GridMeterChannel);
var acIslandLoadMeter = new EmuMeterDevice(AcOutLoadChannel);
var amptDevice = new AmptDevices(AmptChannel);
var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel);
StatusRecord ReadStatus()
2023-02-16 12:57:06 +00:00
{
2023-06-20 08:21:06 +00:00
var acDc = acDcDevices.Read();
var dcDc = dcDcDevices.Read();
var battery = batteryDevices.Read();
var relays = saliMaxRelaysDevice.Read();
var loadOnAcIsland = acIslandLoadMeter.Read();
var gridMeter = gridMeterDevice.Read();
var pvOnDc = amptDevice.Read();
var pvOnAcGrid = AcDevicePower.Null;
var pvOnAcIsland = AcDevicePower.Null;
var loadOnAcGrid = pvOnAcGrid.Power +
pvOnAcIsland.Power +
(gridMeter is null ? AcPower.Null : gridMeter.Ac.Power) +
(loadOnAcIsland is null ? AcPower.Null : loadOnAcIsland.Ac.Power);
var dcPowers = new[]
{
acDc?.Dc.Power.Value,
pvOnDc?.Dc?.Power.Value,
dcDc?.Dc.Link.Power.Value
};
var loadOnDc = dcPowers.Any(p => p is null)
? null
: new DcDevicePower { Power = dcPowers.Sum(p => p)!} ;
return new StatusRecord
{
AcDc = acDc ?? AcDcDevicesRecord.Null,
DcDc = dcDc ?? DcDcDevicesRecord.Null,
Battery = battery ?? Battery48TlRecords.Null,
Relays = relays,
GridMeter = gridMeter,
PvOnAcGrid = pvOnAcGrid,
PvOnAcIsland = pvOnAcIsland,
PvOnDc = pvOnDc ?? AmptStatus.Null,
LoadOnAcGrid = new AcDevicePower { Power = -loadOnAcGrid },
LoadOnAcIsland = loadOnAcIsland,
LoadOnDc = loadOnDc,
Config = Config.Load() // load from disk every iteration, so config can be changed while running
};
}
// async Task<StatusRecord> ReadStatus()
// {
// var acDcTask = Task.Run(() => acDcDevices.Read());
// var dcDcTask = Task.Run(() => dcDcDevices.Read());
// var batteryTask = Task.Run(() => batteryDevices.Read());
// var relaysTask = Task.Run(() => saliMaxRelaysDevice.Read());
// var loadOnAcIslandTask = Task.Run(() => acIslandLoadMeter.Read());
// var gridMeterTask = Task.Run(() => gridMeterDevice.Read());
// var pvOnDcTask = Task.Run(() => amptDevice.Read());
//
//
// var timeout = Task.Delay(TimeSpan.FromSeconds(4));
// var whenAll = Task
// .WhenAll
// (
// acDcTask,
// dcDcTask,
// batteryTask,
// relaysTask,
// loadOnAcIslandTask,
// gridMeterTask,
// pvOnDcTask
// );
//
//
// await Task.WhenAny(whenAll, timeout);
//
// var acDc = await acDcTask.ResultOrNull() ;
// var dcDc = await dcDcTask.ResultOrNull();
// var battery = await batteryTask.ResultOrNull();
// var relays = await relaysTask.ResultOrNull();
// var loadOnAcIsland = await loadOnAcIslandTask.ResultOrNull();
// var gridMeter = await gridMeterTask.ResultOrNull();
// var pvOnDc = await pvOnDcTask.ResultOrNull();
//
//
// var pvOnAcGrid = AcDevicePower.Null;
// var pvOnAcIsland = AcDevicePower.Null;
// var loadOnAcGrid = pvOnAcGrid.Power +
// pvOnAcIsland.Power +
// (gridMeter is null ? AcPower.Null : gridMeter.Ac.Power) +
// (loadOnAcIsland is null ? AcPower.Null : loadOnAcIsland.Ac.Power);
//
//
// var dcPowers = new[]
// {
// acDc?.Dc.Power.Value,
// pvOnDc?.Dc?.Power.Value,
// dcDc?.Dc.Link.Power.Value
// };
//
// var loadOnDc = dcPowers.Any(p => p is null)
// ? null
// : new DcDevicePower { Power = dcPowers.Sum(p => p)!} ;
//
//
// return new StatusRecord
// {
// AcDc = acDc ?? AcDcDevicesRecord.Null,
// DcDc = dcDc ?? DcDcDevicesRecord.Null,
// Battery = battery ?? Battery48TlRecords.Null,
// Relays = relays,
// GridMeter = gridMeter,
//
// PvOnAcGrid = pvOnAcGrid,
// PvOnAcIsland = pvOnAcIsland,
// PvOnDc = pvOnDc ?? AmptStatus.Null,
//
// LoadOnAcGrid = new AcDevicePower { Power = -loadOnAcGrid },
// LoadOnAcIsland = loadOnAcIsland,
// LoadOnDc = loadOnDc,
//
// Config = Config.Load() // load from disk every iteration, so config can be changed while running
// };
// }
2023-04-04 14:37:37 +00:00
2023-06-13 10:53:17 +00:00
void WriteControl(StatusRecord r)
2023-02-16 12:57:06 +00:00
{
2023-06-13 10:53:17 +00:00
if (r.Relays is not null)
saliMaxRelaysDevice.Write(r.Relays);
2023-04-04 14:37:37 +00:00
2023-06-13 10:53:17 +00:00
acDcDevices.Write(r.AcDc);
dcDcDevices.Write(r.DcDc);
2023-02-16 12:57:06 +00:00
}
2023-02-16 12:57:06 +00:00
Console.WriteLine("press ctrl-C to stop");
2023-06-13 10:53:17 +00:00
2023-02-16 12:57:06 +00:00
while (true)
{
2023-06-13 10:53:17 +00:00
sd_notify(0, "WATCHDOG=1");
var t = UnixTime.FromTicks(UnixTime.Now.Ticks / 2 * 2);
2023-06-20 08:21:06 +00:00
//t.ToUtcDateTime().WriteLine();
2023-06-13 10:53:17 +00:00
var record = ReadStatus();
2023-06-20 08:21:06 +00:00
var emuMeterRegisters = record.GridMeter;
if (emuMeterRegisters is not null)
{
emuMeterRegisters.Ac.Power.Active.WriteLine("Grid Active");
emuMeterRegisters.Ac.Power.Reactive.WriteLine("Grid Reactive");
}
2023-06-13 10:53:17 +00:00
record.AcDc.ResetAlarms();
2023-06-20 08:21:06 +00:00
record.DcDc.ResetAlarms();
record.ControlConstants();
2023-06-13 10:53:17 +00:00
record.ControlSystemState();
2023-06-20 08:21:06 +00:00
Console.WriteLine($"{record.StateMachine.State}: {record.StateMachine.Message}");
2023-06-13 10:53:17 +00:00
var essControl = record.ControlEss();
2023-06-20 08:21:06 +00:00
record.EssControl = essControl;
2023-06-13 10:53:17 +00:00
record.AcDc.SystemControl.ApplyDefaultSettings();
record.DcDc.SystemControl.ApplyDefaultSettings();
DistributePower(record, essControl);
2023-06-13 10:53:17 +00:00
WriteControl(record);
2023-06-20 08:21:06 +00:00
2023-06-13 10:53:17 +00:00
await UploadCsv(record, t);
2023-06-20 08:21:06 +00:00
record.Config.Save();
"===========================================".WriteLine();
2023-02-16 12:57:06 +00:00
}
// ReSharper disable once FunctionNeverReturns
}
2023-06-20 08:21:06 +00:00
private static async Task<T?> ResultOrNull<T>(this Task<T> task)
{
if (task.Status == TaskStatus.RanToCompletion)
return await task;
return default;
}
private static void ControlConstants(this StatusRecord r)
{
var inverters = r.AcDc.Devices;
inverters.ForEach(d => d.Control.Dc.MaxVoltage = r.Config.MaxDcBusVoltage);
inverters.ForEach(d => d.Control.Dc.MinVoltage = r.Config.MinDcBusVoltage);
inverters.ForEach(d => d.Control.Dc.ReferenceVoltage = r.Config.ReferenceDcBusVoltage);
}
2023-06-13 10:53:17 +00:00
private static void DistributePower(StatusRecord record, EssControl essControl)
2023-02-16 12:57:06 +00:00
{
2023-06-13 10:53:17 +00:00
var nInverters = record.AcDc.Devices.Count;
var powerPerInverterPhase = nInverters > 0
? AcPower.FromActiveReactive(essControl.PowerSetpoint / nInverters / 3, 0)
: AcPower.Null;
//var powerPerInverterPhase = AcPower.Null;
record.AcDc.Devices.ForEach(d =>
{
d.Control.Ac.PhaseControl = PhaseControl.Asymmetric;
d.Control.Ac.Power.L1 = powerPerInverterPhase;
d.Control.Ac.Power.L2 = powerPerInverterPhase;
d.Control.Ac.Power.L3 = powerPerInverterPhase;
});
}
2023-02-16 12:57:06 +00:00
2023-06-13 10:53:17 +00:00
private static void ApplyDefaultSettings(this SystemControlRegisters? sc)
2023-02-16 12:57:06 +00:00
{
2023-06-13 10:53:17 +00:00
if (sc is null)
return;
sc.ReferenceFrame = ReferenceFrame.Consumer;
sc.SystemConfig = AcDcAndDcDc;
#if DEBUG
2023-06-20 08:21:06 +00:00
sc.CommunicationTimeout = TimeSpan.FromMinutes(2);
2023-06-13 10:53:17 +00:00
#else
sc.CommunicationTimeout = TimeSpan.FromSeconds(10);
#endif
sc.PowerSetPointActivation = PowerSetPointActivation.Immediate;
sc.UseSlaveIdForAddressing = true;
sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed;
sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off;
sc.ResetAlarmsAndWarnings = true;
2023-02-16 12:57:06 +00:00
}
2023-06-13 10:53:17 +00:00
private static DcDcDevicesRecord ResetAlarms(this DcDcDevicesRecord dcDcStatus)
2023-02-16 12:57:06 +00:00
{
2023-06-13 10:53:17 +00:00
var sc = dcDcStatus.SystemControl;
if (sc is not null)
sc.ResetAlarmsAndWarnings = sc.Alarms.Any();
foreach (var d in dcDcStatus.Devices)
d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any();
return dcDcStatus;
2023-02-16 12:57:06 +00:00
}
2023-06-13 10:53:17 +00:00
private static AcDcDevicesRecord ResetAlarms(this AcDcDevicesRecord acDcRecord)
2023-02-16 12:57:06 +00:00
{
2023-06-13 10:53:17 +00:00
var sc = acDcRecord.SystemControl;
2023-02-16 12:57:06 +00:00
2023-06-13 10:53:17 +00:00
if (sc is not null)
sc.ResetAlarmsAndWarnings = sc.Alarms.Any() || sc.Warnings.Any();
2023-02-16 12:57:06 +00:00
2023-06-13 10:53:17 +00:00
foreach (var d in acDcRecord.Devices)
d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any();
2023-02-16 12:57:06 +00:00
2023-06-13 10:53:17 +00:00
return acDcRecord;
2023-02-16 12:57:06 +00:00
}
2023-06-13 10:53:17 +00:00
private static async Task UploadCsv(StatusRecord status, UnixTime timeStamp)
{
2023-06-20 08:21:06 +00:00
timeStamp.WriteLine();
var csv = status.ToCsv().WriteLine();
2023-06-13 10:53:17 +00:00
var s3Path = timeStamp + ".csv";
var request = S3Config.CreatePutRequest(s3Path);
var response = await request.PutAsync(new StringContent(csv));
2023-06-20 08:21:06 +00:00
//csv.WriteLine();
//timeStamp.Ticks.WriteLine();
2023-06-13 10:53:17 +00:00
if (response.StatusCode != 200)
{
Console.WriteLine("ERROR: PUT");
var error = response.GetStringAsync();
Console.WriteLine(error);
}
}
2023-06-13 10:53:17 +00:00
}