modify Topology.cs to properly display missing/unreachable devices (mayor rewrite)

This commit is contained in:
ig 2023-08-25 17:22:02 +02:00
parent 9a400d992d
commit 41ccc63175
1 changed files with 322 additions and 155 deletions

View File

@ -1,8 +1,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using InnovEnergy.App.SaliMax.Ess;
using InnovEnergy.Lib.Devices.Battery48TL;
using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Units.Power;
using InnovEnergy.Lib.Utils;
@ -10,185 +8,351 @@ 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)
{
var islandTopology = status.CreateIslandTopology();
// 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);
// TODO: check ACDCs if AC is available and synced to find out if grid meter OR grid is unavailable
/////////////////////////////
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();
if (status.GridMeter is null) // no grid meter?
return islandTopology; // we're done
var gridTopology = status.CreateGridTopology();
return status.ConnectGridToIslandTopology(gridTopology, islandTopology);
return TextBlock.AlignCenterVertical
(
grid,
gridBus,
islandBus,
inverter,
dcBus,
dcDc,
batteries
);
// 730 V
}
private static TextBlock ConnectGridToIslandTopology(this StatusRecord status, TextBlock gridTopology, TextBlock islandTopology)
private static TextBlock CreateGridColumn(this StatusRecord status, ActivePower? a)
{
var inverterPower = status.AcDc.Ac.Power.Active;
var islandLoadPower = status.LoadOnAcIsland is not null
? status.LoadOnAcIsland.Ac.Power.Active
: 0; // TODO
// ┌─────────┐
// │ Grid │
// ├─────────┤ -10.3 kW
// │ -3205 W │<<<<<<<<<<
// │ -3507 W │ (a)
// │ -3605 W │ K1
// └─────────┘
ActivePower islandToGridBusPower = inverterPower + islandLoadPower;
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 gridToIslandConnection = SwitchedFlow(k2, islandToGridBusPower);
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
(
gridTopology,
gridToIslandConnection,
islandTopology
);
TextBlock.AlignCenterHorizontal(pvBox, pvFlow, busBox, loadFlow, loadBox),
busFlow
);
}
private static TextBlock CreateGridTopology(this StatusRecord status)
{
Debug.Assert(status.GridMeter is not null);
var gridBusColumn = status.CreateGridBusColumn();
var gridBox = status.GridMeter.CreateGridBox();
var k1 = status.Relays?.K1GridBusIsConnectedToGrid;
var gridPower = status.GridMeter.Ac.Power.Active;
var gridFlow = SwitchedFlow(k1, gridPower);
return TextBlock
.AlignCenterVertical
(
gridBox,
gridFlow,
gridBusColumn
);
}
private static TextBlock SwitchedFlow(Boolean? switchClosed, ActivePower power)
private static TextBlock CreateIslandBusColumn(this StatusRecord status,
ActivePower? e,
ActivePower? f,
ActivePower? g)
{
return switchClosed is null ? TextBlock.FromString("??????????")
: !switchClosed.Value ? Switch.Open("K1")
: Flow.Horizontal(power);
}
private static TextBlock CreateIslandTopology(this StatusRecord status)
{
var dcBatteryPower = status.DcDc.Dc.Battery.Power;
var dcdcPower = status.DcDc.Dc.Link.Power;
var inverterPower = status.AcDc.Ac.Power.Active;
var islandLoadPower = status.LoadOnAcIsland is not null
? status.LoadOnAcIsland.Ac.Power.Active
: 0; // TODO
var batteries = status.CreateBatteryColumn();
var dcBusColumn = status.CreateDcBusColumn();
var islandBusColumn = status.CreateIslandBusColumn(islandLoadPower);
var inverterBox = status.CreateInverterBox();
var dcDcBox = status.CreateDcDcBox();
return TextBlock
.AlignCenterVertical
(
islandBusColumn, status.InverterToIslandBusConnection(),
inverterBox , Flow.Horizontal(inverterPower), // inverter to DC bus
dcBusColumn , Flow.Horizontal(dcdcPower),
dcDcBox , Flow.Horizontal(dcBatteryPower),
batteries
);
// ┌────┐
// │ 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 InverterToIslandBusConnection(this StatusRecord status)
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(status.AcDc.Devices.Count);
.Take(nInverters);
if (k3S.All(s => s))
{
var inverterPower = status.AcDc.Ac.Power.Active;
return Flow.Horizontal(inverterPower);
}
if (k3S.Prepend(true).All(s => s)) // TODO: display when no ACDC present
return Flow.Horizontal(g);
return Switch.Open("K3");
}
private static TextBlock CreateGridBox(this IAc3Connection gridMeter)
{
return gridMeter
.Ac
.PhasePowersActive()
.TitleBox("Grid");
}
private static TextBlock CreateDcDcBox(this StatusRecord status)
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
.AlignLeft(dc48Voltage)
.TitleBox("DC/DC");
return TextBlock.AlignCenterVertical(busBox, busFlow);
}
private static TextBlock CreateInverterBox(this StatusRecord status)
private static TextBlock CreateDcBusColumn(this StatusRecord status,
ActivePower? i,
ActivePower? j,
ActivePower? k)
{
var inverterAcPhases = status
.AcDc
.Devices
.Select(d => d.Status.Ac.Power)
.ToReadOnlyList();
return TextBlock
.AlignLeft(inverterAcPhases)
.TitleBox("AC/DC");
}
private static TextBlock CreateGridBusColumn(this StatusRecord status)
{
Debug.Assert(status.GridMeter is not null);
var gridLoadPower = status.LoadOnAcGrid is not null
? status.LoadOnAcGrid.Power.Active
: 0; // TODO: show that LoadOnAcGrid is actually not available and not 0
return CreateTopologyColumn
(
"PV" , 0,
"Grid Bus", status.GridMeter.Ac.PhaseVoltages(),
"Load" , gridLoadPower
);
}
private static TextBlock CreateIslandBusColumn(this StatusRecord status, ActivePower islandLoadPower)
{
var islandBusPv = 0.W(); // TODO
// ┌────┐
// │ Pv │
// └────┘
// V
// V
// (i) 13.2 kW
// V
// V
// ┌────────┐
// │ Dc Bus │ 1008 W
// ├────────┤>>>>>>>>>>
// │ 776 V │ (k)
// └────────┘
// V
// V
// (j) 0 W
// V
// V
// ┌──────┐
// │ Load │
// └──────┘
//
return CreateTopologyColumn
(
"PV" , islandBusPv,
"Island Bus", status.AcDc.Ac.PhaseVoltages(),
"Load" , islandLoadPower
);
}
/////////////////// top ///////////////////
private static TextBlock CreateDcBusColumn(this StatusRecord status)
{
var dcBusLoad = 0.W(); // TODO
var pvOnDcPower = status.PvOnDc.Dc!.Power; // TODO !
var dcLinkVoltage = status.DcDc.Dc.Link.Voltage.ToDisplayString();
var mppt = status.PvOnDc;
return CreateTopologyColumn
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
(
"PV" , pvOnDcPower,
"DC Bus ", dcLinkVoltage,
"DC Load", dcBusLoad
TextBlock.AlignCenterHorizontal(pvBox, pvToBus, dcBusBox, busToLoad, loadBox),
busFlow
);
}
@ -242,23 +406,23 @@ public static class Topology
.TitleBox($"Battery x{nBatteries}");
}
private static TextBlock PhaseVoltages(this Ac3Bus ac)
private static TextBlock PhaseVoltages(Ac3Bus? ac)
{
return TextBlock.AlignLeft
(
ac.L1.Voltage.ToDisplayString(),
ac.L2.Voltage.ToDisplayString(),
ac.L3.Voltage.ToDisplayString()
ac?.L1.Voltage.ToDisplayString() ?? "???",
ac?.L2.Voltage.ToDisplayString() ?? "???",
ac?.L3.Voltage.ToDisplayString() ?? "???"
);
}
private static TextBlock PhasePowersActive(this Ac3Bus ac)
private static TextBlock PhasePowersActive(Ac3Bus? ac)
{
return TextBlock.AlignLeft
(
ac.L1.Power.Active.ToDisplayString(),
ac.L2.Power.Active.ToDisplayString(),
ac.L3.Power.Active.ToDisplayString()
ac?.L1.Power.Active.ToDisplayString() ?? "???",
ac?.L2.Power.Active.ToDisplayString() ?? "???",
ac?.L3.Power.Active.ToDisplayString() ?? "???"
);
}
@ -285,17 +449,20 @@ public static class Topology
return TextBlock.AlignCenterVertical(flow, box);
}
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")]
private static TextBlock CreateTopologyColumn(String pvTitle , ActivePower pvPower,
String busTitle , Object busData,
String loadTitle, ActivePower loadPower)
private static ActivePower? Sum(ActivePower? e, ActivePower? f, ActivePower? g)
{
var pvBox = TextBlock.FromString(pvTitle).Box();
var pvToBus = Flow.Vertical(pvPower);
var busBox = TextBlock.AlignLeft(busData).TitleBox(busTitle);
var busToLoad = Flow.Vertical(loadPower);
var loadBox = TextBlock.FromString(loadTitle).Box();
return TextBlock.AlignCenterHorizontal(pvBox, pvToBus, busBox, busToLoad, loadBox);
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);
}
}