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

469 lines
20 KiB
C#

using System.Diagnostics.CodeAnalysis;
using InnovEnergy.App.SaliMax.Ess;
using InnovEnergy.Lib.Devices.Battery48TL;
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 │ └──────┘
// └──────┘ └──────┘
public static class Topology
{
public static TextBlock CreateTopology(this StatusRecord status)
{
// 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]
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.Ac.Power.Active;
var h = g;
var i = status.PvOnDc?.Dc.Power;
var k = status.DcDc.Dc.Link.Power;
var l = k;
var j = Sum(h, i, -k);
var d = Sum(f, g, -e);
var c = Sum(a, b, -d);
/////////////////////////////
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
);
// 730 V
}
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);
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);
////////////// 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)
.Apply(TextBlock.AlignLeft)
.TitleBox("AC/DC");
var gridFlow = Flow.Horizontal(h);
return TextBlock.AlignCenterVertical(inverterBox, gridFlow);
}
[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? l)
{
var dc48Voltage = status.DcDc.Dc.Battery.Voltage.ToDisplayString();
var busBox = TextBlock
.AlignLeft(dc48Voltage)
.TitleBox("DC/DC");
var busFlow = Flow.Horizontal(l);
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
var batteryBoxes = bat
.Devices
.Select(CreateBatteryBox)
.ToReadOnlyList();
var individualBatteries = batteryBoxes.Any()
? TextBlock.AlignLeft(batteryBoxes)
: TextBlock.Empty;
return TextBlock
.AlignCenterVertical
(
batteryAvgBox ,
individualBatteries
);
}
private static TextBlock CreateAveragedBatteryBox(Battery48TlRecords bat)
{
var voltage = bat.Dc.Voltage.ToDisplayString();
var soc = bat.Devices.Any() ? bat.Devices.Average(b => b.Soc).Percent().ToDisplayString() : "0";
var current = bat.Dc.Current.ToDisplayString();
var temp = bat.Temperature.ToDisplayString();
var heatingCurrent = bat.HeatingCurrent.ToDisplayString();
var alarms = bat.Alarms.Count + " Alarms";
var warnings = bat.Warnings.Count + " Warnings";
var nBatteries = bat.Devices.Count;
return TextBlock
.AlignLeft
(
voltage,
soc,
current,
temp,
heatingCurrent,
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() + " T",
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 ActivePower? Sum(ActivePower? e, ActivePower? f, ActivePower? g)
{
if (e is null || f is null || g is null)
return null;
return f + g + e;
}
private static TextBlock SwitchedFlow(Boolean? switchClosed, ActivePower? power)
{
return switchClosed is null ? TextBlock.FromString("??????????")
: !switchClosed.Value ? Switch.Open("K1")
: power is null ? TextBlock.FromString("??????????")
: Flow.Horizontal(power);
}
}