Dritte Ansicht der Realtime-Karte (auch per Klick auf die Batterie): Kennzahlen (Ladestand mit kWh und voll/leer um, Leistung, SOH, Zellspreizung, Temperaturen, Spannung, Durchsatz, Zustand), der Turm aus BMU und acht Modulen mit je 16 Zellbalken, das gewaehlte Modul mit Zellspannungen und Temperaturfuehlern, dazu Verlaeufe fuer Ladestand und Leistung (EnergyFlow) sowie Spreizung und Temperatur (byd) mit Zeitraum- wahl und Anzeige beim Ueberfahren. Neu: ajax/speicher.php. Nebenbei: Icon-Zeichen in energiefluss.js und im Katalog stehen als Escape (\uF...) statt als unsichtbare Zeichen im Quelltext. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
305 lines
13 KiB
JavaScript
305 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");
|
|
const gemein = Math.max(0, last - ug - eg - og);
|
|
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: gemein, 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 };
|
|
})();
|