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

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);
// }
}