Gemein enthaelt den Heizstab. Der Text zeigt weiter die volle Zahl, das Ringsegment Allgemeinstrom aber Gemein minus Heizstab - der hat sein eigenes Segment. Ist der Heizstab-Kreis aus, faellt sein Anteil wie bisher ins letzte Segment zurueck. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
309 lines
13 KiB
JavaScript
309 lines
13 KiB
JavaScript
/* ==========================================================================
|
|
Katalog der Energiefluss-Übersicht (Solar-Seite, Karte "Realtime").
|
|
|
|
Jeder Kreis steht hier genau einmal: wie er heißt, wie er aussieht, woran
|
|
er hängt, wo er steht und woher seine Werte kommen. Gezeichnet wird er von
|
|
js/solar/energiefluss.js, ohne dass der Renderer einen Kreis beim Namen
|
|
kennt. Ein neuer Kreis ist also ein neuer Eintrag hier - sonst nichts.
|
|
|
|
Was ein Haus davon abweichend einstellt (Kreis aus, eigener Name, eigene
|
|
Lage, Optionen), steht in homeMesh.energiefluss und wird unter
|
|
Einstellungen -> Übersicht gepflegt.
|
|
|
|
Felder eines Eintrags:
|
|
id feste Kennung, steht in der Datenbank
|
|
name Beschriftung unter dem Kreis
|
|
titel Bezeichnung in den Einstellungen, falls name dort missverständlich ist
|
|
symbol Schlüssel in ENERGIEFLUSS_SYMBOLE (energiefluss.js)
|
|
farbe Hauptfarbe: Ring, Symbol, Fluss
|
|
eltern Kennung des Kreises, mit dem er über einen Fluss verbunden
|
|
ist; null = frei stehend. Ist der Elternkreis aus, hängt der
|
|
Kreis am nächsten eingeschalteten darüber.
|
|
pflicht lässt sich nicht ausschalten (die Mitte des Bildes)
|
|
gross größerer Kreis
|
|
x, y Mitte in der Queransicht (Zeichenfläche etwa 1400 x 850)
|
|
skala Leistung in W, bei der der Ring voll ist
|
|
klick {ansicht: "anlage"} oder {modal: "heater"} - Ziel beim Antippen
|
|
optionen Vorgabe für die Optionen, die in den Einstellungen änderbar sind
|
|
zonen nur Bewässerung: die Anzeigen, die solarMQTT.js beschreibt
|
|
lesen(m, opt) liefert den Zustand aus den MQTT-Werten:
|
|
leistung W, bestimmt den Hauptwert und den Ring
|
|
wert Text statt der Leistung (optional)
|
|
neben kleine Zeile unter dem Wert
|
|
segmente [{w, farbe, knoten?}] statt eines einzigen Rings; knoten =
|
|
der Anteil gehört zu diesem Kreis und fällt, ist der aus, in
|
|
das letzte Segment
|
|
fluss W vom Elternkreis zu diesem hin; negativ = in Gegenrichtung
|
|
ruhe Kreis blass zeichnen (nichts los)
|
|
zeichen kleine Plaketten: [{art, text?, titel?}] - art siehe
|
|
ENERGIEFLUSS_ZEICHEN in energiefluss.js
|
|
zustand was das Symbol braucht: {an, stand, laden, temps}
|
|
|
|
Vorzeichen wie im Manager: P_Grid > 0 Bezug, P_Akku > 0 Entladen,
|
|
P_Load und og negativ.
|
|
========================================================================== */
|
|
|
|
const ENERGIEFLUSS_KATALOG = (function () {
|
|
"use strict";
|
|
|
|
const kw = w => (Math.abs(w) / 1000).toFixed(1).replace(".", ",");
|
|
|
|
/** Flächen des Sonnenrings aus anlageKatalog.js - fehlt der, feste Vorgabe. */
|
|
function flaechenAusAnlage() {
|
|
if (typeof ANLAGE_KATALOG === "undefined") {
|
|
return [
|
|
{ name: "Dach", von: 0, bis: 1, farbe: "#f7c000" },
|
|
{ name: "Veranda", von: 2, bis: 13, farbe: "#f78d00" },
|
|
{ name: "Carport", von: 14, bis: 33, farbe: "#f75a1e" },
|
|
];
|
|
}
|
|
return ANLAGE_KATALOG.flaechen.map(f => {
|
|
const eingaenge = [
|
|
...ANLAGE_KATALOG.platten.filter(p => p.flaeche === f.id).map(p => p.eingang),
|
|
...ANLAGE_KATALOG.strings.filter(s => s.flaeche === f.id).map(s => s.eingang),
|
|
];
|
|
return { name: f.name, von: Math.min(...eingaenge), bis: Math.max(...eingaenge), farbe: f.farbe };
|
|
}).filter(f => isFinite(f.von));
|
|
}
|
|
|
|
const wallboxZeichen = (modus, stecker, gesperrt) => {
|
|
const z = [];
|
|
if (modus === "Eco") z.push({ art: "eco", titel: "Eco-Modus" });
|
|
else if (modus === "Next Trip") z.push({ art: "uhr", titel: "Nächste Fahrt" });
|
|
else if (modus !== undefined) z.push({ art: "manuell", titel: "Standard-Modus" });
|
|
if (stecker) z.push({ art: "stecker", titel: "Fahrzeug angesteckt" });
|
|
if (gesperrt) z.push({ art: "schloss", titel: "Fahrzeug verriegelt" });
|
|
return z;
|
|
};
|
|
|
|
const knoten = [
|
|
// ---------------------------------------------------------------- Quellen
|
|
{
|
|
id: "pv", name: "Sonne", symbol: "sonne", farbe: "#f7c000",
|
|
eltern: "wr", x: 180, y: 130, skala: 15000,
|
|
klick: { ansicht: "anlage" },
|
|
// Welche Einträge von solarManager/P_PVn zu welcher Fläche gehören.
|
|
// Vorgabe aus dem Anlagenkatalog, damit die Zuordnung nur einmal
|
|
// gepflegt wird; in den Einstellungen überschreibbar.
|
|
optionen: { flaechen: flaechenAusAnlage() },
|
|
lesen(m, opt) {
|
|
const pvn = m.json("solarManager/P_PVn") || [];
|
|
const pv = m.zahl("solarManager/P_PV");
|
|
const flaechen = (opt.flaechen || []).map(f => {
|
|
let w = 0;
|
|
for (let i = f.von; i <= f.bis && i < pvn.length; i++) w += Number(pvn[i]) || 0;
|
|
return { w, farbe: f.farbe, name: f.name };
|
|
});
|
|
return {
|
|
leistung: pv,
|
|
segmente: flaechen.length ? flaechen : null,
|
|
neben: flaechen.map(f => kw(f.w)).join(" · "),
|
|
fluss: -pv,
|
|
ruhe: pv < 20,
|
|
zustand: { an: pv >= 20, stand: Math.min(1, pv / 10000) },
|
|
};
|
|
},
|
|
},
|
|
{
|
|
id: "netz", name: "Netz", symbol: "netz", farbe: "#9a9aab",
|
|
eltern: "wr", x: 100, y: 380, skala: 15000,
|
|
lesen(m) {
|
|
const netz = m.zahl("solarManager/P_Grid");
|
|
const a = n => (Math.round(Math.abs(m.zahl("solarManager/i_" + n + "evu")) * 10) / 10).toString().replace(".", ",");
|
|
return {
|
|
leistung: Math.abs(netz),
|
|
neben: a("l1") + "·" + a("l2") + "·" + a("l3") + " A",
|
|
fluss: -netz,
|
|
ruhe: Math.abs(netz) < 30,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
id: "akku", name: "Batterie", symbol: "akku", farbe: "#6cbe58",
|
|
eltern: "wr", x: 180, y: 630, skala: 15000,
|
|
klick: { ansicht: "speicher" },
|
|
lesen(m) {
|
|
const p = m.zahl("solarManager/P_Akku");
|
|
const soc = m.zahl("solarManager/SOC");
|
|
const grenze = m.zahl("solarManager/crgMaxPct");
|
|
const laden = p < -20;
|
|
const zeichen = [];
|
|
if (laden && grenze < 100) {
|
|
zeichen.push({ art: "uhr", text: Math.round(grenze) + " %", titel: "Ladeleistung begrenzt" });
|
|
}
|
|
return {
|
|
leistung: Math.abs(p),
|
|
neben: Math.round(soc) + " %" + (laden ? " · lädt" : p > 20 ? " · entlädt" : ""),
|
|
fluss: -p,
|
|
ruhe: Math.abs(p) < 20,
|
|
zeichen,
|
|
zustand: { stand: soc / 100, laden, an: Math.abs(p) >= 20 },
|
|
};
|
|
},
|
|
},
|
|
|
|
// ------------------------------------------------------------------ Mitte
|
|
{
|
|
id: "wr", name: "Autarkie", titel: "Wechselrichter (Autarkie)", symbol: "wechselrichter", farbe: "#b9c0c8",
|
|
eltern: null, pflicht: true, gross: true, x: 430, y: 380, skala: 100,
|
|
lesen(m) {
|
|
const aut = m.zahl("solarManager/autarky");
|
|
return {
|
|
leistung: aut,
|
|
wert: Math.round(aut) + " %",
|
|
neben: "Wirkungsgrad " + Math.round(m.zahl("solarManager/eff")) + " %",
|
|
segmente: [{ w: aut, farbe: "#6cbe58" }],
|
|
zustand: { an: m.zahl("solarManager/P_PV") >= 20 },
|
|
};
|
|
},
|
|
},
|
|
{
|
|
id: "verbrauch", name: "Verbrauch", symbol: "verbrauch", farbe: "#71afcd",
|
|
eltern: "wr", pflicht: true, gross: true, x: 770, y: 380, skala: 15000,
|
|
lesen(m) {
|
|
const last = -m.zahl("solarManager/P_Load");
|
|
const og = -m.zahl("solarManager/og");
|
|
const eg = m.zahl("solarManager/eg");
|
|
const ug = m.zahl("solarManager/ug");
|
|
const heiz = m.zahl("solarManager/pHeat");
|
|
// "Gemein" ist alles außer den Etagen - der Heizstab hängt mit daran.
|
|
// Im Text bleibt er drin, im Ring hat er sein eigenes Segment und wird
|
|
// deshalb vom Allgemeinstrom abgezogen; sonst zählte er doppelt.
|
|
const gemein = Math.max(0, last - ug - eg - og);
|
|
const allgemein = Math.max(0, gemein - heiz);
|
|
return {
|
|
leistung: last,
|
|
neben: "Gemein " + kw(gemein) + " kW",
|
|
segmente: [
|
|
{ w: og, farbe: "#70ccbb", knoten: "og" },
|
|
{ w: eg, farbe: "#70cc9c", knoten: "eg" },
|
|
{ w: ug, farbe: "#83a55b", knoten: "ug" },
|
|
{ w: heiz, farbe: "#c2614e", knoten: "heizstab" },
|
|
{ w: allgemein, farbe: "#71afcd" },
|
|
],
|
|
fluss: last,
|
|
};
|
|
},
|
|
},
|
|
|
|
// --------------------------------------------------------------- Etagen
|
|
{
|
|
id: "og", name: "Obergeschoss", symbol: "etageOben", farbe: "#70ccbb",
|
|
eltern: "verbrauch", x: 920, y: 70, skala: 15000,
|
|
lesen(m) {
|
|
const w = -m.zahl("solarManager/og");
|
|
return {
|
|
leistung: w, fluss: w, ruhe: w < 20,
|
|
zeichen: m.zahl("solarManager/heatOG") ? [{ art: "heizung", titel: "Heizung an" }] : [],
|
|
};
|
|
},
|
|
},
|
|
{
|
|
id: "eg", name: "Erdgeschoss", symbol: "etageMitte", farbe: "#70cc9c",
|
|
eltern: "verbrauch", x: 920, y: 670, skala: 15000,
|
|
lesen(m) {
|
|
const w = m.zahl("solarManager/eg");
|
|
const wb = m.zahl("solarManager/evPower") * 1000;
|
|
return {
|
|
leistung: w, fluss: w, ruhe: w < 20,
|
|
segmente: [
|
|
{ w: Math.max(0, w - wb), farbe: "#70cc9c" },
|
|
{ w: Math.min(w, wb), farbe: "#60afbf", knoten: "wallboxEG" },
|
|
],
|
|
zeichen: m.zahl("solarManager/heatEG") ? [{ art: "heizung", titel: "Heizung an" }] : [],
|
|
};
|
|
},
|
|
},
|
|
{
|
|
id: "ug", name: "Untergeschoss", symbol: "etageUnten", farbe: "#83a55b",
|
|
eltern: "verbrauch", x: 600, y: 670, skala: 15000,
|
|
lesen(m) {
|
|
const w = m.zahl("solarManager/ug");
|
|
return { leistung: w, fluss: w, ruhe: w < 20 };
|
|
},
|
|
},
|
|
|
|
// ----------------------------------------------------------- Verbraucher
|
|
{
|
|
id: "heizstab", name: "Heizstab", symbol: "speicher", farbe: "#c2614e",
|
|
eltern: "verbrauch", x: 1120, y: 380, skala: 6000,
|
|
klick: { modal: "heater" },
|
|
lesen(m) {
|
|
const w = m.zahl("solarManager/pHeat");
|
|
const oben = m.zahl("solarManager/t_buffT");
|
|
const mitte = m.zahl("solarManager/t_buffM");
|
|
const unten = m.zahl("solarManager/t_buffB");
|
|
const eco = m.text("solarManager/heatMode") === "eco";
|
|
const grad = t => (Math.round(t * 10) / 10).toString().replace(".", ",");
|
|
return {
|
|
leistung: w, fluss: w, ruhe: w < 20,
|
|
neben: grad(oben) + " · " + grad(mitte) + " °C",
|
|
zeichen: [eco ? { art: "eco", titel: "Automatik (Überschuss)" } : { art: "manuell", titel: "Feste Leistung" }],
|
|
zustand: { an: w >= 20, temps: [oben, mitte, unten] },
|
|
};
|
|
},
|
|
},
|
|
{
|
|
id: "wallboxOG", name: "Wallbox OG", symbol: "wallbox", farbe: "#4b71a6",
|
|
eltern: "og", x: 1220, y: 70, skala: 11000,
|
|
klick: { modal: "CarOG" },
|
|
lesen(m) {
|
|
const w = m.zahl("solarManager/evPowerOG") * 1000;
|
|
const stecker = m.zahl("solarManager/evPlugOG") > 0;
|
|
return {
|
|
leistung: w, fluss: w, ruhe: w < 50 && !stecker,
|
|
neben: w >= 50 ? "lädt" : stecker ? "angesteckt" : "kein Auto",
|
|
zeichen: wallboxZeichen(m.text("solarManager/evModeOG"), stecker, false),
|
|
zustand: { an: w >= 50 },
|
|
};
|
|
},
|
|
},
|
|
{
|
|
id: "wallboxEG", name: "Wallbox EG", symbol: "wallbox", farbe: "#60afbf",
|
|
eltern: "eg", x: 1220, y: 670, skala: 11000,
|
|
klick: { modal: "CarEG" },
|
|
lesen(m) {
|
|
const w = m.zahl("solarManager/evPower") * 1000;
|
|
const soc = m.zahl("solarManager/evSOC");
|
|
const tank = m.zahl("solarManager/evFuel");
|
|
// solarManager/evPlug kommt als 1/0; aeltere Manager schickten den
|
|
// Text der Wallbox ("no car", "charging", ...).
|
|
const roh = m.text("solarManager/evPlug");
|
|
const stecker = isNaN(Number(roh)) ? (roh !== undefined && roh !== "no car") : Number(roh) > 0;
|
|
let neben = Math.round(soc) + " %";
|
|
const rest = m.zahl("solarManager/carRemChrg");
|
|
if (w >= 50 && rest > -1 && m.text("solarManager/carRemChrg") !== undefined) {
|
|
const fertig = new Date(Date.now() + rest * 60000);
|
|
neben = "fertig " + String(fertig.getHours()).padStart(2, "0") + ":" + String(fertig.getMinutes()).padStart(2, "0");
|
|
}
|
|
const zeichen = wallboxZeichen(m.text("solarManager/evMode"), stecker, m.zahl("solarManager/evLock") > 0);
|
|
if (tank > 0) zeichen.push({ art: "tank", text: Math.round(tank) + " %", titel: "Tank" });
|
|
return {
|
|
leistung: w, fluss: w, ruhe: w < 50 && !stecker,
|
|
neben, zeichen,
|
|
zustand: { an: w >= 50, stand: soc / 100 },
|
|
};
|
|
},
|
|
},
|
|
|
|
// ------------------------------------------------------------- frei
|
|
{
|
|
id: "bewaesserung", name: "Bewässerung", symbol: "tropfen", farbe: "#4b8fd6",
|
|
eltern: null, x: 550, y: 70,
|
|
klick: { modal: "watering" },
|
|
// Ids wie in wateringZones (solarMQTT.js) - dort werden Farbe, Text und
|
|
// Tooltip gesetzt, hier nur die Stellen dafür angelegt.
|
|
zonen: [
|
|
{ name: "Tröge", zeichen: "\uF180", status: "wateringTroughStatus", symbol: "wateringTroughIcon" },
|
|
{ name: "Vorn", zeichen: "\uF3CE", status: "wateringFrontStatus", symbol: "wateringFrontIcon" },
|
|
{ name: "Beete", zeichen: "\uF3CD", status: "wateringBedsStatus", symbol: "wateringBedsIcon" },
|
|
],
|
|
lesen() {
|
|
return { wert: "" };
|
|
},
|
|
},
|
|
];
|
|
|
|
return { knoten };
|
|
})();
|