Der Ring wuchs linear bis zur Skala - bei 15 kW Vollausschlag war ein Geschoss mit 300 W ein Strich von zwei Grad. Jetzt bekommt das untere Zehntel knapp ein Drittel des Rings, der volle Ausschlag bleibt bei skala. Bei mehreren Segmenten wird ueber die Summe gerechnet, sonst waere die Skala nicht additiv. Die Punkte laufen jetzt von 2,4 bis 13 statt 3,5 bis 10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
319 lines
14 KiB
JavaScript
319 lines
14 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)
|
||
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: "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) + " %";
|
||
// 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 };
|
||
})();
|