diff --git a/js/solar/homeMQTT.js b/js/solar/homeMQTT.js index 713aee1..e878bb5 100644 --- a/js/solar/homeMQTT.js +++ b/js/solar/homeMQTT.js @@ -72,9 +72,59 @@ const homeSVG = { * schneidet Nachkommastellen ab; ohne Angabe bleibt der Text so, wie das * Geraet ihn meldet. */ + /** + * Die Leistung benannter Wechselrichter, aufsummiert. + * + * Der SolarManager legt sie unter solarManager/inverters0, /inverters1 + * und so fort ab - die Nummer ist eine Position in einer Liste und sagt + * nichts darueber, welches Dach gemeint ist. Deshalb wird ueber + * invertersN/name gesucht: aendert sich die Reihenfolge, findet die + * Kachel ihren Wechselrichter trotzdem, statt stillschweigend das + * falsche Dach anzuzeigen. + */ + wechselrichterWert: function (mqttData, wert) { + const zweig = mqttData["solarManager"]; + if (!zweig) return null; + + const gesucht = wert.wechselrichter; + const feld = wert.feld || "p_AC"; + let summe = 0; + const gefunden = []; + const vorhanden = []; + + for (const schluessel in zweig) { + if (!schluessel.startsWith("inverters")) continue; + const wr = zweig[schluessel]; + if (!wr || typeof wr.name === "undefined") continue; + const name = String(wr.name); + vorhanden.push(name); + if (gesucht.indexOf(name) === -1) continue; + if (typeof wr[feld] === "undefined") continue; + summe += Number(wr[feld]); + gefunden.push(name); + } + + if (!gefunden.length) { + // Ein Name, den es nicht gibt, ergaebe sonst nur einen Strich auf + // der Kachel und keinen Hinweis, woran es liegt. + if (vorhanden.length && !homeSVG.gemeldet[gesucht.join("|")]) { + homeSVG.gemeldet[gesucht.join("|")] = true; + console.warn("Kachel: kein Wechselrichter namens " + gesucht.join(", ") + + ". Vorhanden sind: " + vorhanden.join(", ")); + } + return null; + } + return summe; + }, + + /** Merkt sich gemeldete Namen, damit die Konsole nicht zulaeuft. */ + gemeldet: {}, + kachelWert: function (mqttData, wert) { - const roh = homeSVG.ausTopic(mqttData, wert.topic); - if (typeof roh === "undefined") return null; + const roh = wert.wechselrichter + ? homeSVG.wechselrichterWert(mqttData, wert) + : homeSVG.ausTopic(mqttData, wert.topic); + if (roh === null || typeof roh === "undefined") return null; let text = String(roh); if (wert.pfad) { try { diff --git a/restricted/home.php b/restricted/home.php index 6cf0b0c..021df39 100644 --- a/restricted/home.php +++ b/restricted/home.php @@ -144,10 +144,21 @@ $wert): ?> + + + + + - " x="12" y="" + " + x="" y="" text-anchor="middle">– diff --git a/restricted/rooms.php b/restricted/rooms.php index 690c4bc..248b3bd 100644 --- a/restricted/rooms.php +++ b/restricted/rooms.php @@ -98,20 +98,38 @@ $rooms = [ // Zwei Carports, einer je Wohnpartei - deshalb dieselben Kuerzel wie die // Etagen. Die Wattpilot der EG-Partei steht dagegen in der Garage, siehe // restricted/config.php; am Carport OG haengt der go-e Charger. - ["floor" => "AG", "mqtt" => "Carport EG", "id" => "carport_eg", "x" => 156, "y" => 214, "werte" => []], + // Die PV des Carports haengt an mehreren Hoymiles. Sie werden ueber + // ihren Namen gesucht und aufsummiert, nicht ueber ihre Nummer: die + // Topics heissen solarManager/invertersN, und N ist eine Position in + // einer Liste. Aendert der SolarManager die Reihenfolge, zeigte eine + // fest eingetragene Nummer stillschweigend das falsche Dach an. + // + // Die Namen muessen genau so lauten wie unter invertersN/name. Passt + // einer nicht, schreibt homeMQTT.js die vorhandenen Namen in die + // Browser-Konsole - dann steht dort, was hier hingehoert. + ["floor" => "AG", "mqtt" => "Carport EG", "id" => "carport_eg", "x" => 156, "y" => 214, + "werte" => [ + ["wechselrichter" => ["Carport 1-6", "Carport 7-12", "Carport 13-16", + "Carport 17-19"], + "feld" => "p_AC", "einheit" => "W", "stellen" => 0, "icon" => "sun"], + ]], // Am Carport OG haengt der go-e Charger. Die Ladeleistung ist das // Einzige, was sich hier messen laesst - und sie beantwortet die Frage, // die man sich vor dem Carport stellt: laedt gerade jemand? ["floor" => "AG", "mqtt" => "Carport OG", "id" => "carport_og", "x" => 38, "y" => 155, "werte" => [ ["topic" => "solarManager/evPowerOG", "einheit" => "W", "stellen" => 0, - "gross" => true], + "icon" => "ev-station"], ]], // Veranda und Terrasse gibt es zweimal, je Wohnpartei eine. In der // Draufsicht laege das obere Paar genau auf dem unteren - deshalb die // Schraegsicht, siehe restricted/aussenplan.php. ["floor" => "AG", "mqtt" => "Veranda EG", "id" => "veranda_eg", "x" => 156, "y" => 132, "werte" => []], - ["floor" => "AG", "mqtt" => "Veranda OG", "id" => "veranda_og", "x" => 150, "y" => 92, "werte" => []], + ["floor" => "AG", "mqtt" => "Veranda OG", "id" => "veranda_og", "x" => 150, "y" => 92, + "werte" => [ + ["wechselrichter" => ["Veranda OG2-4", "Veranda UG/OG1"], + "feld" => "p_AC", "einheit" => "W", "stellen" => 0, "icon" => "sun"], + ]], ["floor" => "AG", "mqtt" => "Terrasse EG", "id" => "terrasse_eg", "x" => 212, "y" => 160, "werte" => []], ["floor" => "AG", "mqtt" => "Terrasse OG", "id" => "terrasse_og", "x" => 205, "y" => 122, "werte" => []], ["floor" => "AG", "mqtt" => "Pergola", "id" => "pergola", "x" => 339, "y" => 108, "werte" => []], @@ -120,7 +138,7 @@ $rooms = [ ["floor" => "AG", "mqtt" => "Garage", "id" => "garage", "x" => 103, "y" => 134, "werte" => [ ["topic" => "solarManager/evPower", "einheit" => "W", "stellen" => 0, - "gross" => true], + "icon" => "ev-station"], ]], // Der Garten ist kein Bauteil wie die anderen, aber ein Ort: hier haengen // der Regensensor und die beiden Ventilsteuerungen. @@ -131,7 +149,7 @@ $rooms = [ // laufende Tag - die Zahl, die man im Garten wissen will. Ein // laengeres Fenster ist ein geaenderter Pfad und sonst nichts. ["topic" => "Wetter/Regen", "pfad" => "tage1", "einheit" => "mm", - "stellen" => 1, "gross" => true], + "stellen" => 1, "icon" => "cloud-rain"], ]], ]; @@ -161,6 +179,28 @@ function floorLabel($floor) return $GLOBALS["floorLabels"][$floor] ?? $floor; } +/** + * Codepunkt eines Bootstrap-Icons für die Kacheln. + * + * Die Icons kommen als Schrift, nicht als Pfad: css/bootstrap-icons.min.css + * ist ohnehin eingebunden (restricted/header.php), und SVG-Text darf dieselbe + * Schriftfamilie benutzen wie HTML. Ein eingebettetes je Symbol wäre + * dieselbe Wirkung mit mehr Zeilen. + * + * Die Zahlen stehen in genau dieser CSS unter .bi-::before und sind von + * dort abgeschrieben - deshalb steht der Name daneben, sonst prüft das nie + * wieder jemand nach. + */ +function kachelIcon($name) +{ + $icons = [ + "ev-station" => "F83A", // Wallbox, Ladeleistung + "cloud-rain" => "F2B6", // Regenmenge + "sun" => "F5A2", // PV-Erzeugung + ]; + return isset($icons[$name]) ? mb_chr(hexdec($icons[$name]), "UTF-8") : ""; +} + /** * Etagen mit Grundriss - die, die im Menü und im SVG auftauchen. * @@ -225,7 +265,14 @@ function tileTopics() $zweige = []; foreach (roomsWithTile() as $room) { foreach ($room["werte"] as $wert) { - $zweige[explode("/", $wert["topic"])[0] . "/#"] = true; + // Ein Wert nennt entweder sein Topic oder einen Wechselrichter, + // den homeMQTT.js unter solarManager/invertersN sucht. Ohne diese + // Unterscheidung entstand aus dem fehlenden Topic ein "/#" - eine + // Anmeldung auf einen leeren Zweig. + $zweig = isset($wert["topic"]) + ? explode("/", $wert["topic"])[0] + : "solarManager"; + $zweige[$zweig . "/#"] = true; } } return array_keys($zweige);