diff --git a/csharp/App/SaliMax/src/Topology.cs b/csharp/App/SaliMax/src/Topology.cs index 821764182..b33874c88 100644 --- a/csharp/App/SaliMax/src/Topology.cs +++ b/csharp/App/SaliMax/src/Topology.cs @@ -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); } }