implement circular current prevention
This commit is contained in:
parent
d88fc677b5
commit
dcc4bfa78a
|
@ -4,104 +4,172 @@ using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes;
|
||||||
using InnovEnergy.Lib.Time.Unix;
|
using InnovEnergy.Lib.Time.Unix;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
|
|
||||||
namespace InnovEnergy.App.SaliMax.Ess;
|
namespace InnovEnergy.App.SaliMax.Ess;
|
||||||
|
|
||||||
public static class Controller
|
public static class Controller
|
||||||
{
|
{
|
||||||
private static readonly UnixTimeSpan MaxTimeWithoutEoc = UnixTimeSpan.FromDays(7); // TODO: move to config
|
private static readonly UnixTimeSpan MaxTimeWithoutEoc = UnixTimeSpan.FromDays(7); // TODO: move to config
|
||||||
private static readonly TimeSpan CommunicationTimeout = TimeSpan.FromSeconds(10);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static EssMode SelectControlMode(this StatusRecord s)
|
public static EssMode SelectControlMode(this StatusRecord s)
|
||||||
{
|
{
|
||||||
return EssMode.OptimizeSelfConsumption;
|
//return EssMode.OptimizeSelfConsumption;
|
||||||
|
|
||||||
// return s.SystemState.Id != 16 ? EssMode.Off
|
return s.StateMachine.State != 16 ? EssMode.Off
|
||||||
// : s.MustHeatBatteries() ? EssMode.HeatBatteries
|
: s.MustHeatBatteries() ? EssMode.HeatBatteries
|
||||||
// : s.MustDoCalibrationCharge() ? EssMode.CalibrationCharge
|
: s.MustDoCalibrationCharge() ? EssMode.CalibrationCharge
|
||||||
// : s.MustReachMinSoc() ? EssMode.ReachMinSoc
|
: s.MustReachMinSoc() ? EssMode.ReachMinSoc
|
||||||
// : s.GridMeter is null ? EssMode.NoGridMeter
|
: s.GridMeter is null ? EssMode.NoGridMeter
|
||||||
// : EssMode.OptimizeSelfConsumption;
|
: EssMode.OptimizeSelfConsumption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static EssControl ControlEss(this StatusRecord s)
|
public static EssControl ControlEss(this StatusRecord s)
|
||||||
{
|
{
|
||||||
// var hasPreChargeAlarm = s.HasPreChargeAlarm();
|
|
||||||
//
|
|
||||||
// if (hasPreChargeAlarm)
|
|
||||||
// "PreChargeAlarm".Log();
|
|
||||||
|
|
||||||
var mode = s.SelectControlMode();
|
var mode = s.SelectControlMode();
|
||||||
|
|
||||||
|
mode.WriteLine();
|
||||||
|
|
||||||
if (mode is EssMode.Off or EssMode.NoGridMeter)
|
if (mode is EssMode.Off or EssMode.NoGridMeter)
|
||||||
return new EssControl(mode, EssLimit.NoLimit, PowerCorrection: 0, PowerSetpoint: 0);
|
return new EssControl(mode, EssLimit.NoLimit, PowerCorrection: 0, PowerSetpoint: 0);
|
||||||
|
|
||||||
var essDelta = s.ComputePowerDelta(mode);
|
var essDelta = s.ComputePowerDelta(mode);
|
||||||
|
|
||||||
var unlimitedControl = new EssControl(mode, EssLimit.NoLimit, essDelta, 0);
|
var unlimitedControl = new EssControl(mode, EssLimit.NoLimit, essDelta, 0);
|
||||||
|
|
||||||
var limitedControl = unlimitedControl
|
var limitedControl = unlimitedControl
|
||||||
.LimitChargePower(s)
|
.LimitChargePower(s)
|
||||||
.LimitDischargePower(s);
|
.LimitDischargePower(s)
|
||||||
|
.LimitInverterPower(s);
|
||||||
|
|
||||||
var currentPowerSetPoint = s.CurrentPowerSetPoint();
|
var currentPowerSetPoint = s.CurrentPowerSetPoint();
|
||||||
var setpoint = currentPowerSetPoint + limitedControl.PowerCorrection;
|
|
||||||
//var setpoint = -11000;
|
|
||||||
|
|
||||||
return limitedControl with { PowerSetpoint = setpoint };
|
var essControl = limitedControl with { PowerSetpoint = currentPowerSetPoint + limitedControl.PowerCorrection };
|
||||||
|
|
||||||
|
essControl.WriteLine();
|
||||||
|
s.Battery.Soc.WriteLine("Soc");
|
||||||
|
|
||||||
|
return essControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static EssControl LimitInverterPower(this EssControl control, StatusRecord s)
|
||||||
|
{
|
||||||
|
var powerDelta = control.PowerCorrection.Value;
|
||||||
|
|
||||||
|
var acDcs = s.AcDc.Devices;
|
||||||
|
|
||||||
|
var nInverters = acDcs.Count;
|
||||||
|
|
||||||
|
if (nInverters < 2)
|
||||||
|
return control; // current loop cannot happen
|
||||||
|
|
||||||
|
var nominalPower = acDcs.Average(d => d.Status.Nominal.Power);
|
||||||
|
var maxStep = nominalPower / 25;
|
||||||
|
|
||||||
|
var clampedPowerDelta = powerDelta.Clamp(-maxStep, maxStep);
|
||||||
|
|
||||||
|
var dcLimited = acDcs.Any(d => d.Status.PowerLimitedBy == PowerLimit.DcLink);
|
||||||
|
|
||||||
|
if (!dcLimited)
|
||||||
|
return control with { PowerCorrection = clampedPowerDelta };
|
||||||
|
|
||||||
|
var maxPower = acDcs.Max(d => d.Status.Ac.Power.Active.Value).WriteLine("Max");
|
||||||
|
var minPower = acDcs.Min(d => d.Status.Ac.Power.Active.Value).WriteLine("Min");
|
||||||
|
|
||||||
|
var powerDifference = maxPower - minPower;
|
||||||
|
|
||||||
|
if (powerDifference < maxStep)
|
||||||
|
return control with { PowerCorrection = clampedPowerDelta };
|
||||||
|
|
||||||
|
var correction = powerDifference / 4;
|
||||||
|
|
||||||
|
return s.AcDc.Dc.Voltage > s.Config.ReferenceDcBusVoltage
|
||||||
|
? control with { PowerCorrection = clampedPowerDelta.Clamp(-maxStep, -correction), LimitedBy = EssLimit.ChargeLimitedByMaxDcBusVoltage }
|
||||||
|
: control with { PowerCorrection = clampedPowerDelta.Clamp(correction, maxStep), LimitedBy = EssLimit.DischargeLimitedByMinDcBusVoltage };
|
||||||
|
}
|
||||||
|
|
||||||
|
// private static Double AdjustMaxChargePower(StatusRecord s, Double powerDelta)
|
||||||
|
// {
|
||||||
|
// var acDcs = s.AcDc.Devices;
|
||||||
|
//
|
||||||
|
// var nInverters = acDcs.Count;
|
||||||
|
//
|
||||||
|
// if (nInverters == 0)
|
||||||
|
// return 0; // no inverters present: we cannot charge at all
|
||||||
|
//
|
||||||
|
// var nominalPower = acDcs.Sum(d => d.Status.Nominal.Power);
|
||||||
|
//
|
||||||
|
// if (nInverters == 1)
|
||||||
|
// return powerDelta; // single inverter: current loop cannot happen
|
||||||
|
//
|
||||||
|
// acDcs.ForEach(d => d.Status.PowerLimitedBy.WriteLine());
|
||||||
|
//
|
||||||
|
// var dcLimited = acDcs.Any(d => d.Status.PowerLimitedBy == PowerLimit.DcLink);
|
||||||
|
//
|
||||||
|
// if (expr)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// var maxPowerDifference = nominalPower / 25;
|
||||||
|
//
|
||||||
|
// var maxPower = acDcs.Max(d => d.Status.Ac.Power.Active.Value).WriteLine("Max");
|
||||||
|
// var minPower = acDcs.Min(d => d.Status.Ac.Power.Active.Value).WriteLine("Min");
|
||||||
|
//
|
||||||
|
// var sum = acDcs.Sum(d => d.Status.Ac.Power.Active.Value);
|
||||||
|
//
|
||||||
|
// var powerDifference = maxPower - minPower;
|
||||||
|
//
|
||||||
|
// if (powerDifference > maxPowerDifference)
|
||||||
|
// ChargePower = sum - powerDifference / 4;
|
||||||
|
// else
|
||||||
|
// ChargePower += maxPowerDifference / 2;
|
||||||
|
//
|
||||||
|
// $"HACK : ChargePower {ChargePower} Difference: {powerDifference}".WriteLine();
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
private static EssControl LimitChargePower(this EssControl control, StatusRecord s)
|
private static EssControl LimitChargePower(this EssControl control, StatusRecord s)
|
||||||
{
|
{
|
||||||
var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower);
|
|
||||||
|
//var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower);
|
||||||
var maxBatteryChargePower = s.MaxBatteryChargePower();
|
var maxBatteryChargePower = s.MaxBatteryChargePower();
|
||||||
|
|
||||||
return control
|
return control
|
||||||
.LimitChargePower(maxInverterChargePower, EssLimit.ChargeLimitedByInverterPower)
|
//.LimitChargePower(, EssLimit.ChargeLimitedByInverterPower)
|
||||||
.LimitChargePower(maxBatteryChargePower, EssLimit.ChargeLimitedByBatteryPower);
|
.LimitChargePower(maxBatteryChargePower, EssLimit.ChargeLimitedByBatteryPower);
|
||||||
}
|
|
||||||
|
|
||||||
private static EssControl LimitChargePower(this EssControl control, Double controlDelta, EssLimit reason)
|
|
||||||
{
|
|
||||||
return control.PowerCorrection > controlDelta
|
|
||||||
? control with { LimitedBy = reason, PowerCorrection = controlDelta }
|
|
||||||
: control;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static EssControl LimitDischargePower(this EssControl control, StatusRecord s)
|
private static EssControl LimitDischargePower(this EssControl control, StatusRecord s)
|
||||||
{
|
{
|
||||||
var maxInverterDischargeDelta = s.ControlInverterPower(-s.Config.MaxInverterPower);
|
//var maxInverterDischargeDelta = s.ControlInverterPower(-s.Config.MaxInverterPower);
|
||||||
var maxBatteryDischargeDelta = s.Battery.Devices.Sum(b => b.MaxDischargePower);
|
var maxBatteryDischargeDelta = s.Battery.Devices.Sum(b => b.MaxDischargePower);
|
||||||
var keepMinSocLimitDelta = s.ControlBatteryPower(s.HoldMinSocPower());
|
var keepMinSocLimitDelta = s.ControlBatteryPower(s.HoldMinSocPower());
|
||||||
|
|
||||||
return control
|
return control
|
||||||
.LimitDischargePower(maxInverterDischargeDelta, EssLimit.DischargeLimitedByInverterPower)
|
// .LimitDischargePower(maxInverterDischargeDelta, EssLimit.DischargeLimitedByInverterPower)
|
||||||
.LimitDischargePower(maxBatteryDischargeDelta , EssLimit.DischargeLimitedByBatteryPower)
|
.LimitDischargePower(maxBatteryDischargeDelta , EssLimit.DischargeLimitedByBatteryPower)
|
||||||
.LimitDischargePower(keepMinSocLimitDelta , EssLimit.DischargeLimitedByMinSoc);
|
.LimitDischargePower(keepMinSocLimitDelta , EssLimit.DischargeLimitedByMinSoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static EssControl LimitDischargePower(this EssControl control, Double controlDelta, EssLimit reason)
|
|
||||||
{
|
|
||||||
return control.PowerCorrection < controlDelta
|
|
||||||
? control with { LimitedBy = reason, PowerCorrection = controlDelta }
|
|
||||||
: control;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static Double ComputePowerDelta(this StatusRecord s, EssMode mode) => mode switch
|
|
||||||
|
private static Double ComputePowerDelta(this StatusRecord s, EssMode mode)
|
||||||
{
|
{
|
||||||
EssMode.HeatBatteries => s.ControlInverterPower(s.Config.MaxInverterPower),
|
var chargePower = s.AcDc.Devices.Sum(d => d.Status.Nominal.Power.Value);
|
||||||
EssMode.CalibrationCharge => s.ControlInverterPower(s.Config.MaxInverterPower),
|
|
||||||
EssMode.ReachMinSoc => s.ControlInverterPower(s.Config.MaxInverterPower),
|
return mode switch
|
||||||
|
{
|
||||||
|
EssMode.HeatBatteries => s.ControlInverterPower(chargePower),
|
||||||
|
EssMode.ReachMinSoc => s.ControlInverterPower(chargePower),
|
||||||
|
EssMode.CalibrationCharge => s.ControlInverterPower(chargePower),
|
||||||
EssMode.OptimizeSelfConsumption => s.ControlGridPower(s.Config.GridSetPoint),
|
EssMode.OptimizeSelfConsumption => s.ControlGridPower(s.Config.GridSetPoint),
|
||||||
_ => throw new ArgumentException(null, nameof(mode))
|
_ => throw new ArgumentException(null, nameof(mode))
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Boolean HasPreChargeAlarm(this StatusRecord statusRecord)
|
private static Boolean HasPreChargeAlarm(this StatusRecord statusRecord)
|
||||||
|
@ -165,20 +233,14 @@ public static class Controller
|
||||||
return UnixTime.Now - statusRecord.Config.LastEoc > MaxTimeWithoutEoc;
|
return UnixTime.Now - statusRecord.Config.LastEoc > MaxTimeWithoutEoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Double DistributePower(this StatusRecord s, Double powerSetPoint)
|
|
||||||
{
|
|
||||||
var inverterPowerSetPoint = powerSetPoint / s.AcDc.Devices.Count;
|
|
||||||
return inverterPowerSetPoint.Clamp(-s.Config.MaxInverterPower, s.Config.MaxInverterPower);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static Double ControlGridPower(this StatusRecord status, Double targetPower)
|
public static Double ControlGridPower(this StatusRecord status, Double targetPower)
|
||||||
{
|
{
|
||||||
return ControlPower
|
return ControlPower
|
||||||
(
|
(
|
||||||
measurement: status.GridMeter!.Ac.Power.Active,
|
measurement : status.GridMeter!.Ac.Power.Active,
|
||||||
target: targetPower,
|
target : targetPower,
|
||||||
pConstant: status.Config.PConstant
|
pConstant : status.Config.PConstant
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,9 +248,9 @@ public static class Controller
|
||||||
{
|
{
|
||||||
return ControlPower
|
return ControlPower
|
||||||
(
|
(
|
||||||
measurement: status.AcDc.Ac.Power.Active,
|
measurement : status.AcDc.Ac.Power.Active,
|
||||||
target: targetInverterPower,
|
target : targetInverterPower,
|
||||||
pConstant: status.Config.PConstant
|
pConstant : status.Config.PConstant
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +273,7 @@ public static class Controller
|
||||||
if (batteries.Count == 0)
|
if (batteries.Count == 0)
|
||||||
return Double.NegativeInfinity;
|
return Double.NegativeInfinity;
|
||||||
|
|
||||||
var a = -2 * s.Config.SelfDischargePower * batteries.Count / s.Config.HoldSocZone;
|
var a = -2 * s.Config.BatterySelfDischargePower * batteries.Count / s.Config.HoldSocZone;
|
||||||
var b = -a * (s.Config.MinSoc + s.Config.HoldSocZone);
|
var b = -a * (s.Config.MinSoc + s.Config.HoldSocZone);
|
||||||
|
|
||||||
return batteries.Min(d => d.Soc.Value) * a + b;
|
return batteries.Min(d => d.Soc.Value) * a + b;
|
||||||
|
|
|
@ -8,7 +8,38 @@ public record EssControl
|
||||||
EssLimit LimitedBy,
|
EssLimit LimitedBy,
|
||||||
ActivePower PowerCorrection,
|
ActivePower PowerCorrection,
|
||||||
ActivePower PowerSetpoint
|
ActivePower PowerSetpoint
|
||||||
);
|
)
|
||||||
|
{
|
||||||
|
public EssControl LimitChargePower(Double controlDelta, EssLimit reason)
|
||||||
|
{
|
||||||
|
var overload = PowerCorrection - controlDelta;
|
||||||
|
|
||||||
|
if (overload <= 0)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
return this with
|
||||||
|
{
|
||||||
|
LimitedBy = reason,
|
||||||
|
PowerCorrection = controlDelta,
|
||||||
|
PowerSetpoint = PowerSetpoint - overload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public EssControl LimitDischargePower(Double controlDelta, EssLimit reason)
|
||||||
|
{
|
||||||
|
var overload = PowerCorrection - controlDelta;
|
||||||
|
|
||||||
|
if (overload >= 0)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
return this with
|
||||||
|
{
|
||||||
|
LimitedBy = reason,
|
||||||
|
PowerCorrection = controlDelta,
|
||||||
|
PowerSetpoint = PowerSetpoint - overload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ public enum EssLimit
|
||||||
DischargeLimitedByInverterPower,
|
DischargeLimitedByInverterPower,
|
||||||
ChargeLimitedByInverterPower,
|
ChargeLimitedByInverterPower,
|
||||||
ChargeLimitedByBatteryPower,
|
ChargeLimitedByBatteryPower,
|
||||||
|
ChargeLimitedByMaxDcBusVoltage,
|
||||||
|
DischargeLimitedByMinDcBusVoltage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Flurl.Http;
|
using Flurl.Http;
|
||||||
using InnovEnergy.App.SaliMax.Ess;
|
using InnovEnergy.App.SaliMax.Ess;
|
||||||
using InnovEnergy.App.SaliMax.SaliMaxRelays;
|
using InnovEnergy.App.SaliMax.SaliMaxRelays;
|
||||||
using InnovEnergy.App.SaliMax.System;
|
using InnovEnergy.App.SaliMax.System;
|
||||||
using InnovEnergy.App.SaliMax.SystemConfig;
|
using InnovEnergy.App.SaliMax.SystemConfig;
|
||||||
|
using InnovEnergy.App.SaliMax.VirtualDevices;
|
||||||
using InnovEnergy.Lib.Devices.AMPT;
|
using InnovEnergy.Lib.Devices.AMPT;
|
||||||
using InnovEnergy.Lib.Devices.Battery48TL;
|
using InnovEnergy.Lib.Devices.Battery48TL;
|
||||||
using InnovEnergy.Lib.Devices.EmuMeter;
|
using InnovEnergy.Lib.Devices.EmuMeter;
|
||||||
|
@ -19,9 +16,9 @@ using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
using InnovEnergy.Lib.Time.Unix;
|
using InnovEnergy.Lib.Time.Unix;
|
||||||
using InnovEnergy.Lib.Units;
|
using InnovEnergy.Lib.Units;
|
||||||
using InnovEnergy.Lib.Units.Composite;
|
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig;
|
using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig;
|
||||||
|
using AcPower = InnovEnergy.Lib.Units.Composite.AcPower;
|
||||||
using Exception = System.Exception;
|
using Exception = System.Exception;
|
||||||
|
|
||||||
#pragma warning disable IL2026
|
#pragma warning disable IL2026
|
||||||
|
@ -96,21 +93,137 @@ internal static class Program
|
||||||
var acDcDevices = new TruConvertAcDcDevices(TruConvertAcChannel);
|
var acDcDevices = new TruConvertAcDcDevices(TruConvertAcChannel);
|
||||||
var dcDcDevices = new TruConvertDcDcDevices(TruConvertDcChannel);
|
var dcDcDevices = new TruConvertDcDcDevices(TruConvertDcChannel);
|
||||||
var gridMeterDevice = new EmuMeterDevice(GridMeterChannel);
|
var gridMeterDevice = new EmuMeterDevice(GridMeterChannel);
|
||||||
var criticalLoadMeterDevice = new EmuMeterDevice(AcOutLoadChannel);
|
var acIslandLoadMeter = new EmuMeterDevice(AcOutLoadChannel);
|
||||||
var amptDevice = new AmptDevices(AmptChannel);
|
var amptDevice = new AmptDevices(AmptChannel);
|
||||||
var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel);
|
var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel);
|
||||||
|
|
||||||
StatusRecord ReadStatus() => new()
|
StatusRecord ReadStatus()
|
||||||
{
|
{
|
||||||
AcDc = acDcDevices.Read(),
|
var acDc = acDcDevices.Read();
|
||||||
DcDc = dcDcDevices.Read(),
|
var dcDc = dcDcDevices.Read();
|
||||||
Battery = batteryDevices.Read(),
|
var battery = batteryDevices.Read();
|
||||||
Relays = saliMaxRelaysDevice.Read(),
|
var relays = saliMaxRelaysDevice.Read();
|
||||||
CriticalLoad = criticalLoadMeterDevice.Read(),
|
var loadOnAcIsland = acIslandLoadMeter.Read();
|
||||||
GridMeter = gridMeterDevice.Read(),
|
var gridMeter = gridMeterDevice.Read();
|
||||||
Mppt = amptDevice.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
|
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
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
void WriteControl(StatusRecord r)
|
void WriteControl(StatusRecord r)
|
||||||
{
|
{
|
||||||
|
@ -130,40 +243,64 @@ internal static class Program
|
||||||
|
|
||||||
var t = UnixTime.FromTicks(UnixTime.Now.Ticks / 2 * 2);
|
var t = UnixTime.FromTicks(UnixTime.Now.Ticks / 2 * 2);
|
||||||
|
|
||||||
t.ToUtcDateTime().WriteLine();
|
//t.ToUtcDateTime().WriteLine();
|
||||||
|
|
||||||
var record = ReadStatus();
|
var record = ReadStatus();
|
||||||
|
|
||||||
|
var emuMeterRegisters = record.GridMeter;
|
||||||
|
if (emuMeterRegisters is not null)
|
||||||
|
{
|
||||||
|
emuMeterRegisters.Ac.Power.Active.WriteLine("Grid Active");
|
||||||
|
emuMeterRegisters.Ac.Power.Reactive.WriteLine("Grid Reactive");
|
||||||
|
}
|
||||||
|
|
||||||
record.AcDc.ResetAlarms();
|
record.AcDc.ResetAlarms();
|
||||||
record.DcDc.ResetAlarms();
|
record.DcDc.ResetAlarms();
|
||||||
|
|
||||||
|
record.ControlConstants();
|
||||||
|
|
||||||
record.ControlSystemState();
|
record.ControlSystemState();
|
||||||
|
|
||||||
|
Console.WriteLine($"{record.StateMachine.State}: {record.StateMachine.Message}");
|
||||||
|
|
||||||
var essControl = record.ControlEss();
|
var essControl = record.ControlEss();
|
||||||
|
|
||||||
record.Ess = essControl;
|
record.EssControl = essControl;
|
||||||
|
|
||||||
record.AcDc.SystemControl.ApplyDefaultSettings();
|
record.AcDc.SystemControl.ApplyDefaultSettings();
|
||||||
record.DcDc.SystemControl.ApplyDefaultSettings();
|
record.DcDc.SystemControl.ApplyDefaultSettings();
|
||||||
|
|
||||||
DistributePower(record, essControl);
|
DistributePower(record, essControl);
|
||||||
|
|
||||||
"===========================================".WriteLine();
|
|
||||||
|
|
||||||
WriteControl(record);
|
WriteControl(record);
|
||||||
|
|
||||||
await UploadCsv(record, t);
|
await UploadCsv(record, t);
|
||||||
|
|
||||||
var emuMeterRegisters = record.GridMeter;
|
record.Config.Save();
|
||||||
if (emuMeterRegisters is not null)
|
|
||||||
{
|
"===========================================".WriteLine();
|
||||||
emuMeterRegisters.Ac.Power.Active.WriteLine();
|
|
||||||
emuMeterRegisters.Ac.Power.Reactive.WriteLine();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ReSharper disable once FunctionNeverReturns
|
// ReSharper disable once FunctionNeverReturns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void DistributePower(StatusRecord record, EssControl essControl)
|
private static void DistributePower(StatusRecord record, EssControl essControl)
|
||||||
{
|
{
|
||||||
var nInverters = record.AcDc.Devices.Count;
|
var nInverters = record.AcDc.Devices.Count;
|
||||||
|
@ -174,8 +311,6 @@ internal static class Program
|
||||||
|
|
||||||
//var powerPerInverterPhase = AcPower.Null;
|
//var powerPerInverterPhase = AcPower.Null;
|
||||||
|
|
||||||
powerPerInverterPhase.WriteLine("powerPerInverterPhase");
|
|
||||||
|
|
||||||
record.AcDc.Devices.ForEach(d =>
|
record.AcDc.Devices.ForEach(d =>
|
||||||
{
|
{
|
||||||
d.Control.Ac.PhaseControl = PhaseControl.Asymmetric;
|
d.Control.Ac.PhaseControl = PhaseControl.Asymmetric;
|
||||||
|
@ -194,7 +329,7 @@ internal static class Program
|
||||||
sc.SystemConfig = AcDcAndDcDc;
|
sc.SystemConfig = AcDcAndDcDc;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
sc.CommunicationTimeout = TimeSpan.FromMinutes(10);
|
sc.CommunicationTimeout = TimeSpan.FromMinutes(2);
|
||||||
#else
|
#else
|
||||||
sc.CommunicationTimeout = TimeSpan.FromSeconds(10);
|
sc.CommunicationTimeout = TimeSpan.FromSeconds(10);
|
||||||
#endif
|
#endif
|
||||||
|
@ -234,13 +369,15 @@ internal static class Program
|
||||||
|
|
||||||
private static async Task UploadCsv(StatusRecord status, UnixTime timeStamp)
|
private static async Task UploadCsv(StatusRecord status, UnixTime timeStamp)
|
||||||
{
|
{
|
||||||
var csv = status.ToCsv();
|
timeStamp.WriteLine();
|
||||||
|
|
||||||
|
var csv = status.ToCsv().WriteLine();
|
||||||
var s3Path = timeStamp + ".csv";
|
var s3Path = timeStamp + ".csv";
|
||||||
var request = S3Config.CreatePutRequest(s3Path);
|
var request = S3Config.CreatePutRequest(s3Path);
|
||||||
var response = await request.PutAsync(new StringContent(csv));
|
var response = await request.PutAsync(new StringContent(csv));
|
||||||
|
|
||||||
csv.WriteLine();
|
//csv.WriteLine();
|
||||||
timeStamp.Ticks.WriteLine();
|
//timeStamp.Ticks.WriteLine();
|
||||||
|
|
||||||
if (response.StatusCode != 200)
|
if (response.StatusCode != 200)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue