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