Files
Smart-Dashboard/js/solar/energieflussKatalog.js
T
adminandClaude Opus 5 a50905bb6c Heizstab kann ganz aus
Dritte Betriebsart neben Automatik und Fest: Aus schickt eine feste Leistung
von null (setMode=-1). In der Automatik liefe der Heizstab beim naechsten
Ueberschuss wieder an. Erkannt wird der Zustand daran, dass das Geraet
feste Betriebsart, aber keine Leistung meldet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:08:13 +02:00

320 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ==========================================================================
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)
hochX, hochY Mitte in der Hochkant-Ansicht, falls das blosse Tauschen
von x und y den Kreis dort ungünstig stellt (optional)
skala Leistung in W, bei der der Ring voll ist. Dazwischen ist die
Skala gestaucht (ringAnteil() in energiefluss.js): kleine
Leistungen bekommen mehr Ring als ihr Anteil
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;
je Zone bild (ZONENBILDER in energiefluss.js) oder zeichen
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",
// Hochkant steht das UG dort, wo quer der Heizstab sitzt, und umgekehrt:
// so stehen die drei Geschosse untereinander.
eltern: "verbrauch", x: 600, y: 670, hochX: 380, hochY: 1120, 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, hochX: 670, hochY: 600, 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: w >= 20 ? "Feste Leistung" : "Aus" }],
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) + "%";
// Restladezeit in Minuten laut Fahrzeug (gatherSkodaData.py). Das
// frühere carRemChrg schickt der Manager nicht mehr.
const rest = m.zahl("solarManager/evRemChrgTime");
if (w >= 50 && rest > 0) {
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: [
// bild: Symbol aus ZONENBILDER (energiefluss.js); zeichen: Zeichen aus Bootstrap Icons
{ name: "Tröge", bild: "kuebel", status: "wateringTroughStatus", symbol: "wateringTroughIcon" },
{ name: "Vorn", bild: "garten", status: "wateringFrontStatus", symbol: "wateringFrontIcon" },
{ name: "Beete", bild: "hochbeet", status: "wateringBedsStatus", symbol: "wateringBedsIcon" },
],
lesen() {
return { wert: "" };
},
},
];
return { knoten };
})();