555 lines
23 KiB
C#
555 lines
23 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using InnovEnergy.App.SaliMax.Ess;
|
|
using InnovEnergy.App.SaliMax.VirtualDevices;
|
|
using InnovEnergy.Lib.Devices.AMPT;
|
|
using InnovEnergy.Lib.Devices.Battery48TL;
|
|
using InnovEnergy.Lib.Devices.EmuMeter;
|
|
using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
|
|
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
|
|
using InnovEnergy.Lib.Units;
|
|
using InnovEnergy.Lib.Units.Power;
|
|
using InnovEnergy.Lib.Utils;
|
|
using Ac3Bus = InnovEnergy.Lib.Units.Composite.Ac3Bus;
|
|
|
|
namespace InnovEnergy.App.SaliMax;
|
|
|
|
// ┌────┐ ┌────┐
|
|
// │ Pv │ │ Pv │ ┌────┐
|
|
// └────┘ └────┘ │ Pv │
|
|
// V V └────┘
|
|
// V V V
|
|
// (b) 0 W (e) 0 W V
|
|
// V V (i) 13.2 kW ┌────────────┐
|
|
// V V V │ Battery │
|
|
// ┌─────────┐ ┌──────────┐ ┌────────────┐ ┌─────────┐ V ├────────────┤
|
|
// │ Grid │ │ Grid Bus │ │ Island Bus │ │ AC/DC │ ┌────────┐ ┌───────┐ │ 52.3 V │
|
|
// ├─────────┤ -10.3 kW ├──────────┤ -11.7 kW ├────────────┤ -11.7 kW ├─────────┤ -11.7 kW │ Dc Bus │ 1008 W │ DC/DC │ 1008 W │ 99.1 % │
|
|
// │ -3205 W │<<<<<<<<<<│ 244 V │<<<<<<<<<<│ 244 V │<<<<<<<<<<│ -6646 W │<<<<<<<<<<├────────┤>>>>>>>>>>├───────┤>>>>>>>>>>│ 490 mA │
|
|
// │ -3507 W │ (a) │ 244 V │ (d) │ 244 V │ (g) │ -5071 W │ (h) │ 776 V │ (k) │ 56 V │ (l) │ 250 °C │
|
|
// │ -3605 W │ K1 │ 246 V │ K2 │ 246 V │ K3 └─────────┘ └────────┘ └───────┘ │ 445 A │
|
|
// └─────────┘ └──────────┘ └────────────┘ V │ 0 Warnings │
|
|
// V V V │ 0 Alarms │
|
|
// V V (j) 0 W └────────────┘
|
|
// (c) 1400 W (f) 0 W V
|
|
// V V V
|
|
// V V ┌──────┐
|
|
// ┌──────┐ ┌──────┐ │ Load │
|
|
// │ Load │ │ Load │ └──────┘
|
|
// └──────┘ └──────┘
|
|
|
|
|
|
// Calculated values: c,d & h
|
|
// ==========================
|
|
//
|
|
//
|
|
// AC side
|
|
// a + b - c - d = 0 [eq1]
|
|
// d + e - f - g = 0 [eq2]
|
|
//
|
|
// c & d are not measured!
|
|
//
|
|
// d = f + g - e [eq2]
|
|
// c = a + b - d [eq1]
|
|
//
|
|
// DC side
|
|
// h + i - j - k = 0 [eq3]
|
|
//
|
|
// g = h assuming no losses in ACDC
|
|
// k = l assuming no losses in DCDC
|
|
// j = h + i - k [eq3]
|
|
|
|
|
|
public static class Topology
|
|
{
|
|
|
|
public static TextBlock CreateTopologyTextBlock(this StatusRecord status)
|
|
{
|
|
var a = status.GridMeter?.Ac.Power.Active;
|
|
var b = status.PvOnAcGrid?.Power.Active;
|
|
var e = status.PvOnAcIsland?.Power.Active;
|
|
var f = status.LoadOnAcIsland?.Ac.Power.Active;
|
|
var g = status.AcDc.Dc.Power.Value;
|
|
var h = g;
|
|
var i = status.PvOnDc?.Dc.Power.Value;
|
|
var k = status.DcDc.Dc.Link.Power.Value;
|
|
var l = status.Battery is not null ? status.Battery.Dc.Power.Value : 0;
|
|
var j = status.LoadOnDc?.Power.Value;
|
|
var d = status.AcGridToAcIsland?.Power.Active;
|
|
var c = status.LoadOnAcGrid?.Power.Active;
|
|
|
|
/////////////////////////////
|
|
|
|
var grid = status.CreateGridColumn(a);
|
|
var gridBus = status.CreateGridBusColumn(b, c, d);
|
|
var islandBus = status.CreateIslandBusColumn(e, f, g);
|
|
var inverter = status.CreateInverterColumn(h);
|
|
var dcBus = status.CreateDcBusColumn(i, j, k);
|
|
var dcDc = status.CreateDcDcColumn(l);
|
|
var batteries = status.CreateBatteryColumn();
|
|
|
|
return TextBlock.AlignCenterVertical
|
|
(
|
|
grid,
|
|
gridBus,
|
|
islandBus,
|
|
inverter,
|
|
dcBus,
|
|
dcDc,
|
|
batteries
|
|
);
|
|
}
|
|
|
|
private static TextBlock CreateGridColumn(this StatusRecord status, ActivePower? a)
|
|
{
|
|
// ┌─────────┐
|
|
// │ Grid │
|
|
// ├─────────┤ -10.3 kW
|
|
// │ -3205 W │<<<<<<<<<<
|
|
// │ -3507 W │ (a)
|
|
// │ -3605 W │ K1
|
|
// └─────────┘
|
|
|
|
var gridMeterAc = status.GridMeter?.Ac;
|
|
var k1 = status.Relays?.K1GridBusIsConnectedToGrid;
|
|
|
|
var gridBox = PhasePowersActive(gridMeterAc).TitleBox("Grid");
|
|
var gridFlow = SwitchedFlow(k1, a, "K1");
|
|
|
|
return TextBlock.AlignCenterVertical(gridBox, gridFlow);
|
|
}
|
|
|
|
|
|
private static TextBlock CreateGridBusColumn(this StatusRecord status,
|
|
ActivePower? b,
|
|
ActivePower? c,
|
|
ActivePower? d)
|
|
{
|
|
|
|
// ┌────┐
|
|
// │ Pv │
|
|
// └────┘
|
|
// V
|
|
// V
|
|
// (b) 0 W
|
|
// V
|
|
// V
|
|
// ┌──────────┐
|
|
// │ Grid Bus │
|
|
// ├──────────┤ -11.7 kW
|
|
// │ 244 V │<<<<<<<<<<
|
|
// │ 244 V │ (d)
|
|
// │ 246 V │ K2
|
|
// └──────────┘
|
|
// V
|
|
// V
|
|
// (c) 1400 W
|
|
// V
|
|
// V
|
|
// ┌──────┐
|
|
// │ Load │
|
|
// └──────┘
|
|
|
|
|
|
////////////// top //////////////
|
|
|
|
var pvBox = TextBlock.FromString("PV").Box();
|
|
var pvFlow = Flow.Vertical(b);
|
|
|
|
////////////// center //////////////
|
|
|
|
// on IslandBus show voltages measured by inverter
|
|
// on GridBus show voltages measured by grid meter
|
|
// ought to be approx the same
|
|
|
|
var gridMeterAc = status.GridMeter?.Ac;
|
|
var k2 = status.Relays?.K2IslandBusIsConnectedToGridBus;
|
|
|
|
var busBox = PhaseVoltages(gridMeterAc).TitleBox("Grid Bus");
|
|
var busFlow = SwitchedFlow(k2, d, "K2");
|
|
|
|
////////////// bottom //////////////
|
|
|
|
var loadFlow = Flow.Vertical(c);
|
|
var loadBox = TextBlock.FromString("Load").Box();
|
|
|
|
////////////// assemble //////////////
|
|
|
|
return TextBlock.AlignCenterVertical
|
|
(
|
|
TextBlock.AlignCenterHorizontal(pvBox, pvFlow, busBox, loadFlow, loadBox),
|
|
busFlow
|
|
);
|
|
}
|
|
|
|
|
|
private static TextBlock CreateIslandBusColumn(this StatusRecord status,
|
|
ActivePower? e,
|
|
ActivePower? f,
|
|
ActivePower? g)
|
|
{
|
|
|
|
// ┌────┐
|
|
// │ Pv │
|
|
// └────┘
|
|
// V
|
|
// V
|
|
// (e) 0 W
|
|
// V
|
|
// V
|
|
// ┌────────────┐
|
|
// │ Island Bus │
|
|
// ├────────────┤ -11.7 kW
|
|
// │ 244 V │<<<<<<<<<<
|
|
// │ 244 V │ (g)
|
|
// │ 246 V │ K3
|
|
// └────────────┘
|
|
// V
|
|
// V
|
|
// (f) 0 W
|
|
// V
|
|
// V
|
|
// ┌──────┐
|
|
// │ Load │
|
|
// └──────┘
|
|
|
|
|
|
////////////// top //////////////
|
|
|
|
var pvBox = TextBlock.FromString("PV").Box();
|
|
var pvFlow = Flow.Vertical(e);
|
|
|
|
////////////// center //////////////
|
|
|
|
// on IslandBus show voltages measured by inverter
|
|
// on GridBus show voltages measured by grid meter
|
|
// ought to be approx the same
|
|
|
|
var inverterAc = status.AcDc.Ac;
|
|
var busBox = PhaseVoltages(inverterAc).TitleBox("Island Bus");
|
|
var busFlow = status.IslandBusToInverterConnection(g);
|
|
|
|
////////////// bottom //////////////
|
|
|
|
var loadFlow = Flow.Vertical(f);
|
|
var loadBox = TextBlock.FromString("Load").Box();
|
|
|
|
////////////// assemble //////////////
|
|
|
|
return TextBlock.AlignCenterVertical
|
|
(
|
|
TextBlock.AlignCenterHorizontal(pvBox, pvFlow, busBox, loadFlow, loadBox),
|
|
busFlow
|
|
);
|
|
}
|
|
|
|
|
|
|
|
private static TextBlock CreateInverterColumn(this StatusRecord status, ActivePower? h)
|
|
{
|
|
// ┌─────────┐
|
|
// │ AC/DC │
|
|
// ├─────────┤ -11.7 kW
|
|
// │ -6646 W │<<<<<<<<<<
|
|
// │ -5071 W │ (h)
|
|
// └─────────┘
|
|
|
|
var inverterBox = status
|
|
.AcDc
|
|
.Devices
|
|
.Select(d => d.Status.Ac.Power.Active)
|
|
.Apply(TextBlock.AlignLeft)
|
|
.TitleBox("AC/DC");
|
|
|
|
var dcFlow = Flow.Horizontal(h);
|
|
|
|
return TextBlock.AlignCenterVertical(inverterBox, dcFlow);
|
|
}
|
|
|
|
|
|
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
|
private static TextBlock IslandBusToInverterConnection(this StatusRecord status, ActivePower? g)
|
|
{
|
|
if (status.Relays is null)
|
|
return TextBlock.FromString("????????");
|
|
|
|
var nInverters = status.AcDc.Devices.Count;
|
|
|
|
var k3S = status
|
|
.Relays
|
|
.K3InverterIsConnectedToIslandBus
|
|
.Take(nInverters);
|
|
|
|
if (k3S.Prepend(true).All(s => s)) // TODO: display when no ACDC present
|
|
return Flow.Horizontal(g);
|
|
|
|
return Switch.Open("K3");
|
|
}
|
|
|
|
private static TextBlock CreateDcDcColumn(this StatusRecord status, ActivePower? p)
|
|
{
|
|
var dc48Voltage = status.DcDc.Dc.Battery.Voltage.ToDisplayString();
|
|
|
|
var busBox = TextBlock
|
|
.AlignLeft(dc48Voltage)
|
|
.TitleBox("DC/DC");
|
|
|
|
var busFlow = Flow.Horizontal(p);
|
|
|
|
return TextBlock.AlignCenterVertical(busBox, busFlow);
|
|
}
|
|
|
|
private static TextBlock CreateDcBusColumn(this StatusRecord status,
|
|
ActivePower? i,
|
|
ActivePower? j,
|
|
ActivePower? k)
|
|
{
|
|
// ┌────┐
|
|
// │ Pv │
|
|
// └────┘
|
|
// V
|
|
// V
|
|
// (i) 13.2 kW
|
|
// V
|
|
// V
|
|
// ┌────────┐
|
|
// │ Dc Bus │ 1008 W
|
|
// ├────────┤>>>>>>>>>>
|
|
// │ 776 V │ (k)
|
|
// └────────┘
|
|
// V
|
|
// V
|
|
// (j) 0 W
|
|
// V
|
|
// V
|
|
// ┌──────┐
|
|
// │ Load │
|
|
// └──────┘
|
|
//
|
|
|
|
|
|
/////////////////// top ///////////////////
|
|
|
|
var mppt = status.PvOnDc;
|
|
|
|
var nStrings = mppt is not null
|
|
? "x" + mppt.Strings.Count
|
|
: "?";
|
|
|
|
var pvBox = TextBlock.FromString($"PV {nStrings}").Box();
|
|
var pvToBus = Flow.Vertical(i);
|
|
|
|
/////////////////// center ///////////////////
|
|
|
|
var dcBusVoltage = status.DcDc.Dc.Link.Voltage;
|
|
|
|
var dcBusBox = dcBusVoltage
|
|
.ToDisplayString()
|
|
.Apply(TextBlock.FromString)
|
|
.TitleBox("DC Bus ");
|
|
|
|
var busFlow = Flow.Horizontal(k);
|
|
|
|
/////////////////// bottom ///////////////////
|
|
|
|
var busToLoad = Flow.Vertical(j);
|
|
var loadBox = TextBlock.FromString("DC Load").Box();
|
|
|
|
////////////// assemble //////////////
|
|
|
|
return TextBlock.AlignCenterVertical
|
|
(
|
|
TextBlock.AlignCenterHorizontal(pvBox, pvToBus, dcBusBox, busToLoad, loadBox),
|
|
busFlow
|
|
);
|
|
}
|
|
|
|
private static TextBlock CreateBatteryColumn(this StatusRecord status)
|
|
{
|
|
var bat = status.Battery;
|
|
var batteryAvgBox = CreateAveragedBatteryBox(bat);
|
|
|
|
return batteryAvgBox; // TODO: individualBatteries hidden atm
|
|
|
|
|
|
#pragma warning disable CS0162
|
|
var batteryBoxes = bat
|
|
.Devices
|
|
.Select(CreateBatteryBox)
|
|
.ToReadOnlyList();
|
|
|
|
var individualBatteries = batteryBoxes.Any()
|
|
? TextBlock.AlignLeft(batteryBoxes)
|
|
: TextBlock.Empty;
|
|
|
|
return TextBlock
|
|
.AlignCenterVertical
|
|
(
|
|
batteryAvgBox ,
|
|
individualBatteries
|
|
);
|
|
|
|
#pragma warning enable CS0162
|
|
}
|
|
|
|
private static TextBlock CreateAveragedBatteryBox(Battery48TlRecords? bat)
|
|
{
|
|
if (bat is null)
|
|
return TextBlock.AlignLeft("no battery").Box();
|
|
|
|
var voltage = bat.Dc.Voltage.ToDisplayString();
|
|
var soc = bat.Devices.Any() ? bat.Devices.Average(b => b.Soc).Percent().ToDisplayString() : "0"; // TODO
|
|
var current = bat.Dc.Current.ToDisplayString();
|
|
var busCurrent = bat.Devices.Any() ? bat.Devices.Sum(b => b.BusCurrent).A().ToDisplayString() : "0";
|
|
var temp = bat.Temperature.ToDisplayString();
|
|
var heatingPower = bat.HeatingPower.ToDisplayString();
|
|
var alarms = bat.Alarms.Count + " Alarms";
|
|
var warnings = bat.Warnings.Count + " Warnings";
|
|
var nBatteries = bat.Devices.Count;
|
|
|
|
return TextBlock
|
|
.AlignLeft
|
|
(
|
|
voltage,
|
|
soc,
|
|
current,
|
|
busCurrent,
|
|
temp,
|
|
heatingPower,
|
|
warnings,
|
|
alarms
|
|
)
|
|
.TitleBox($"Battery x{nBatteries}");
|
|
}
|
|
|
|
private static TextBlock PhaseVoltages(Ac3Bus? ac)
|
|
{
|
|
return TextBlock.AlignLeft
|
|
(
|
|
ac?.L1.Voltage.ToDisplayString() ?? "???",
|
|
ac?.L2.Voltage.ToDisplayString() ?? "???",
|
|
ac?.L3.Voltage.ToDisplayString() ?? "???"
|
|
);
|
|
}
|
|
|
|
private static TextBlock PhasePowersActive(Ac3Bus? ac)
|
|
{
|
|
return TextBlock.AlignLeft
|
|
(
|
|
ac?.L1.Power.Active.ToDisplayString() ?? "???",
|
|
ac?.L2.Power.Active.ToDisplayString() ?? "???",
|
|
ac?.L3.Power.Active.ToDisplayString() ?? "???"
|
|
);
|
|
}
|
|
|
|
private static TextBlock CreateBatteryBox(Battery48TlRecord battery, Int32 i)
|
|
{
|
|
var batteryWarnings = battery.Warnings.Any();
|
|
var batteryAlarms = battery.Alarms.Any();
|
|
|
|
var content = TextBlock.AlignLeft
|
|
(
|
|
battery.Dc.Voltage.ToDisplayString(),
|
|
battery.Soc.ToDisplayString(),
|
|
battery.Dc.Current.ToDisplayString() + " C/D",
|
|
battery.Temperatures.Cells.Average.ToDisplayString(),
|
|
battery.BusCurrent.ToDisplayString() ,
|
|
batteryWarnings,
|
|
batteryAlarms,
|
|
battery.HeatingCurrent.ToDisplayString() + " H"
|
|
);
|
|
|
|
var box = content.TitleBox($"Battery {i + 1}");
|
|
var flow = Flow.Horizontal(battery.Dc.Power);
|
|
|
|
return TextBlock.AlignCenterVertical(flow, box);
|
|
}
|
|
|
|
|
|
private static TextBlock SwitchedFlow(Boolean? switchClosed, ActivePower? power, String kx)
|
|
{
|
|
return switchClosed is null ? TextBlock.FromString("??????????")
|
|
: !switchClosed.Value ? Switch.Open(kx)
|
|
: power is null ? TextBlock.FromString("??????????")
|
|
: Flow.Horizontal(power);
|
|
}
|
|
|
|
|
|
public static AcPowerDevice? CalculateGridBusLoad(EmuMeterRegisters? gridMeter, AcPowerDevice? pvOnAcGrid, AcPowerDevice? gridBusToIslandBusPower)
|
|
{
|
|
var a = gridMeter ?.Ac.Power;
|
|
var b = pvOnAcGrid ?.Power;
|
|
var d = gridBusToIslandBusPower?.Power;
|
|
|
|
if (a is null || b is null || d is null)
|
|
return null;
|
|
|
|
var c = a + b - d; // [eq1]
|
|
|
|
return new AcPowerDevice { Power = c };
|
|
}
|
|
|
|
public static AcPowerDevice? CalculateGridBusToIslandBusPower(AcPowerDevice? pvOnAcIsland, EmuMeterRegisters? loadOnAcIsland, AcDcDevicesRecord? acDc)
|
|
{
|
|
var e = pvOnAcIsland ?.Power;
|
|
var f = loadOnAcIsland?.Ac.Power;
|
|
var g = acDc ?.Ac.Power;
|
|
|
|
if (e is null || f is null || g is null)
|
|
return null;
|
|
|
|
var d = f + g - e; // [eq2]
|
|
|
|
return new AcPowerDevice { Power = d };
|
|
}
|
|
|
|
public static DcPowerDevice? CalculateDcLoad(AcDcDevicesRecord? acDc, AmptStatus? pvOnDc, DcDcDevicesRecord? dcDc)
|
|
{
|
|
var h = acDc?.Dc.Power;
|
|
var i = pvOnDc?.Dc.Power;
|
|
var k = dcDc?.Dc.Link.Power;
|
|
|
|
if (h is null || i is null || k is null)
|
|
return null;
|
|
|
|
var j = h + i - k; // [eq3]
|
|
|
|
return new DcPowerDevice { Power = j};
|
|
}
|
|
|
|
// public static (AcPowerDevice? acGridToAcIsland, AcPowerDevice? loadOnAcGrid, DcPowerDevice? dcPowerDevice)
|
|
//
|
|
// CalculateEnergyFlow(EmuMeterRegisters? gridMeter,
|
|
// AcPowerDevice pvOnAcGrid,
|
|
// AcPowerDevice pvOnAcIsland,
|
|
// EmuMeterRegisters? loadOnAcIsland,
|
|
// AcDcDevicesRecord acDc,
|
|
// AmptStatus? pvOnDc,
|
|
// DcDcDevicesRecord dcDc)
|
|
// {
|
|
// var gridPower = gridMeter?.Ac.Power.Active;
|
|
// var islandLoadPower = loadOnAcIsland?.Ac.Power.Active;
|
|
// var inverterAcPower = acDc.Ac.Power.Active;
|
|
// var inverterDcPower = acDc.Dc.Power;
|
|
//
|
|
// var a = gridPower;
|
|
// var b = pvOnAcGrid.Power.Active;
|
|
// var e = pvOnAcIsland.Power.Active;
|
|
// var f = islandLoadPower;
|
|
// var g = inverterAcPower;
|
|
// var h = inverterDcPower;
|
|
// var i = pvOnDc?.Dc.Power;
|
|
// var k = dcDc.Dc.Link.Power;
|
|
// var j = Sum(h, i, -k);
|
|
// var d = Sum(f, g, -e);
|
|
// var c = Sum(a, b, -d);
|
|
//
|
|
// var acGridToAcIsland = d is null ? null : new AcPowerDevice { Power = d.Value };
|
|
// var loadOnAcGrid = c is null ? null : new AcPowerDevice { Power = c.Value };
|
|
// var dcPowerDevice = j is null ? null : new DcPowerDevice { Power = j };
|
|
//
|
|
// return (acGridToAcIsland, loadOnAcGrid, dcPowerDevice);
|
|
// }
|
|
|
|
|
|
}
|