Anzeige fuer den Pi ist jetzt die Solar-Seite im Anzeige-Modus

Statt einer eigenen Vorlage mit nachgebauter Verdrahtung: ?action=anzeige
ist der Eintrag solar mit "anzeige" => true. header.php laesst Kopfleiste
und Menue weg und setzt die schmale Leiste (neu: Diagramme), footer.php
den Fuss; die Realtime-Karte fuellt einen Bildschirm, Diagramme folgen
darunter. Damit gehen am Display auch die Bedienfenster (Heizstab,
Wallboxen, Bewaesserung) und die Diagramme - alles aus solarMQTT.js.

anzeige.js bleibt fuer Leerlauf, Verbindungspunkt, Uhr und Neuladen;
restricted/anzeige.php, "eigen" in index.php und klickFilter entfallen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 20:22:35 +02:00
co-authored by Claude Opus 5
parent b8b2e66fd0
commit 2e66bdeff5
9 changed files with 159 additions and 193 deletions
+66 -85
View File
@@ -1,122 +1,103 @@
/* ==========================================================================
Vollbildanzeige (restricted/anzeige.php): Übersicht, Anlage und Speicher
für ein festes Display, z.B. den Raspberry Pi mit 800 x 480.
Anzeige-Modus der Solar-Seite (index.php?action=anzeige) für ein festes
Display, gebaut für den Raspberry Pi 3B+ mit 800 x 480.
Dieselben Bausteine wie auf der Solar-Seite, nur anders verdrahtet:
- immer quer (hochkantUnter: 0), das Display steht quer
- antippbar sind nur Kreise, die eine andere Ansicht öffnen; Bedienfenster
(Heizstab, Wallbox) gibt es hier nicht
- nach LEERLAUF ohne Berührung zurück zur Übersicht
- MQTT verbindet nach einem Abbruch still neu; der Punkt unten in der
Leiste zeigt den Zustand (grün verbunden, gelb keine frischen Werte,
rot getrennt). Kein alert() - am Display ist niemand, der ihn wegklickt.
- alle NEU_LADEN Stunden lädt die Seite neu, damit ein Browser, der
wochenlang läuft, keinen Speicher ansammelt und Änderungen am Code
ankommen
Die Seite selbst ist die Solar-Seite: solarMQTT.js zeichnet Übersicht,
Anlage, Speicher und Diagramme und öffnet die Bedienfenster. header.php
lässt Kopfleiste und Menü weg und setzt stattdessen die schmale Leiste
links; die Übersicht bleibt quer (energieflussStarten() in solarMQTT.js
fragt nach der Klasse "anzeige" am body).
Hier steht nur, was ein Display ohne Bediener braucht:
- Leiste: Übersicht, Anlage, Speicher, Diagramme
- nach LEERLAUF ohne Berührung zurück zur Übersicht, ganz nach oben
- Punkt in der Leiste: grün verbunden, gelb keine frischen Werte,
rot getrennt
- alle NEU_LADEN Stunden neu laden - ein Browser, der wochenlang läuft,
sammelt sonst Speicher an, und Änderungen am Code kämen nie an
========================================================================== */
var mqttData = {};
(function () {
"use strict";
const ADRESSE = "wss://mqtt.nas.el-wa.org:443"; // wie in solarMQTT.js
const LEERLAUF = 2 * 60000;
const NEU_LADEN = 6 * 3600000;
const TAKT = typeof anzeigeRuhig !== "undefined" && anzeigeRuhig ? 2000 : 1000;
const VERALTET = 30000;
const THEMEN = [
"solarManager/#",
"Gartenwasser/vorn/Timers", "Gartenwasser/vorn/status", "Gartenwasser/vorn/WateringToday",
"Gartenwasser/hinten/Timers", "Gartenwasser/hinten/status", "Gartenwasser/hinten/WateringToday",
];
const $ = id => document.getElementById(id);
let bild = null, anlage = null, speicher = null;
let leerlaufUhr = null, geplant = false, verbunden = false, letzteDaten = 0;
let leerlaufUhr = null;
let letzteNachricht = null, letzteZeit = 0;
function leisteSetzen(name) {
document.querySelectorAll("[data-anzeige]").forEach(b =>
b.setAttribute("aria-pressed", b.dataset.anzeige === name ? "true" : "false"));
}
function karte() {
return $("energieflussKarte");
}
/** Eine Ansicht der Realtime-Karte zeigen - über ihren eigenen Umschalter. */
function zeige(name) {
document.querySelectorAll("[data-anzeige]").forEach(b => b.setAttribute("aria-pressed", b.dataset.anzeige === name ? "true" : "false"));
document.querySelectorAll("[data-anzeige-flaeche]").forEach(f => { f.hidden = f.dataset.anzeigeFlaeche !== name; });
wachHalten();
if (name === "diagramme") {
// Die nächste Spalte nach der Realtime-Karte - dazwischen steht ein <script>.
let ziel = karte() && karte().closest("[class*=col-]").nextElementSibling;
while (ziel && !ziel.matches("[class*=col-]")) ziel = ziel.nextElementSibling;
if (ziel) ziel.scrollIntoView({ behavior: "instant", block: "start" });
leisteSetzen("diagramme");
return;
}
window.scrollTo({ top: 0, behavior: "instant" });
const knopf = document.querySelector('[data-enf-ansicht="' + name + '"]');
if (knopf) knopf.click();
leisteSetzen(name);
}
function wachHalten() {
clearTimeout(leerlaufUhr);
leerlaufUhr = setTimeout(() => zeige("fluss"), LEERLAUF);
leerlaufUhr = setTimeout(() => {
const offen = document.querySelector(".modal.show");
if (offen && window.bootstrap) bootstrap.Modal.getOrCreateInstance(offen).hide();
zeige("fluss");
}, LEERLAUF);
}
function zustand() {
const nachricht = window.mqttData && mqttData["solarManager/P_Load"];
if (nachricht && nachricht !== letzteNachricht) { letzteNachricht = nachricht; letzteZeit = Date.now(); }
const verbunden = typeof solarMqttClient !== "undefined" && solarMqttClient && solarMqttClient.connected;
const frisch = Date.now() - letzteZeit < VERALTET;
const punkt = $("anzeigeVerbindung");
const frisch = Date.now() - letzteDaten < VERALTET;
const klasse = "anzeige-punkt" + (verbunden && frisch ? " gut" : verbunden ? " warn" : "");
if (punkt.className !== klasse) {
punkt.className = klasse;
punkt.title = verbunden && frisch ? "verbunden" : verbunden ? "verbunden, aber keine frischen Werte" : "Verbindung getrennt wird neu aufgebaut";
punkt.title = verbunden && frisch ? "verbunden" : verbunden ? "verbunden, aber keine frischen Werte" : "Verbindung getrennt";
}
}
function uhr() {
const d = new Date();
const text = String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
if ($("anzeigeUhr").textContent !== text) $("anzeigeUhr").textContent = text;
}
function aktualisieren() {
if (geplant) return;
geplant = true;
setTimeout(() => {
geplant = false;
bild.aktualisieren(mqttData);
anlage.aktualisieren(mqttData);
speicher.aktualisieren(mqttData);
}, TAKT);
}
function verbinden() {
const client = mqtt.connect(ADRESSE, { rejectUnauthorized: false, reconnectPeriod: 5000, connectTimeout: 15000 });
client.on("connect", () => {
verbunden = true;
client.subscribe(THEMEN);
zustand();
});
const weg = () => { verbunden = false; zustand(); };
client.on("close", weg);
client.on("offline", weg);
client.on("error", () => {});
client.on("message", (topic, nachricht) => {
mqttData[topic] = nachricht;
if (topic.endsWith("/Timers")) wateringTimersLastSeen[topic] = Date.now();
if (topic === "solarManager/P_Load") {
letzteDaten = Date.now();
aktualisieren();
}
});
const uhr = String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
if ($("anzeigeUhr").textContent !== uhr) $("anzeigeUhr").textContent = uhr;
}
document.addEventListener("DOMContentLoaded", () => {
const bewaesserung = () => wateringSymboleSetzen(id => document.getElementById(id));
bild = new Energiefluss($("anzeige-fluss"), {
einstellungen: typeof energieflussEinstellungen !== "undefined" ? energieflussEinstellungen : {},
hochkantUnter: 0,
klickFilter: k => !!k.klick.ansicht,
onKlick: k => zeige(k.klick.ansicht),
onGezeichnet: bewaesserung,
});
bild.zeichnen();
anlage = new Anlage($("anzeige-anlage"), { hochkantUnter: 0 });
anlage.zeichnen();
speicher = new Speicher($("anzeige-speicher"));
speicher.zeichnen();
if (!document.body.classList.contains("anzeige")) return;
document.querySelectorAll("[data-anzeige]").forEach(b => b.addEventListener("click", () => zeige(b.dataset.anzeige)));
document.addEventListener("pointerdown", wachHalten, { passive: true });
// Klick auf Sonne oder Batterie wechselt über den Umschalter der Karte -
// die Leiste zieht nach.
document.querySelectorAll("[data-enf-ansicht]").forEach(b =>
b.addEventListener("click", () => leisteSetzen(b.dataset.enfAnsicht)));
window.addEventListener("scroll", () => {
const k = karte();
if (!k) return;
const unten = k.getBoundingClientRect().bottom < window.innerHeight / 2;
const aktiv = document.querySelector("[data-enf-ansicht].active");
leisteSetzen(unten ? "diagramme" : aktiv ? aktiv.dataset.enfAnsicht : "fluss");
}, { passive: true });
setInterval(() => { uhr(); zustand(); bewaesserung(); }, 1000);
uhr();
document.addEventListener("pointerdown", wachHalten, { passive: true });
wachHalten();
setInterval(zustand, 1000);
zustand();
verbinden();
setTimeout(() => location.reload(), NEU_LADEN);
});
})();
+2 -3
View File
@@ -8,7 +8,6 @@
onKlick(knoten), // Kreis mit klick-Ziel angetippt
onVerschoben(id, x, y), // nur beim Bearbeiten
onGezeichnet(), // nach jedem (Neu-)Zeichnen
klickFilter(knoten), // optional: welche Kreise antippbar sind
hochkantUnter: 720, // optional: px Breite, darunter hochkant (0 = nie)
});
bild.aktualisieren(mqttData);
@@ -222,9 +221,9 @@
}
}
/** Antippbar: hat ein Ziel, nicht im Bearbeiten, und opt.klickFilter lässt es zu. */
/** Antippbar: hat ein Ziel und wird nicht gerade bearbeitet. */
klickbar(k) {
return !!k.klick && !this.bearbeiten && (!this.opt.klickFilter || this.opt.klickFilter(k));
return !!k.klick && !this.bearbeiten;
}
hochkantGewuenscht() {
+4 -1
View File
@@ -196,7 +196,10 @@ function energieflussStarten() {
knopf.addEventListener("click", function () { zeigeAnsicht(knopf.dataset.enfAnsicht); });
});
// Anzeige-Modus (festes Display, js/solar/anzeige.js): immer quer.
const anzeigeModus = document.body.classList.contains("anzeige");
energiefluss = new Energiefluss(behaelter, {
hochkantUnter: anzeigeModus ? 0 : undefined,
einstellungen: typeof energieflussEinstellungen !== "undefined" ? energieflussEinstellungen : {},
onKlick: function (knoten) {
if (knoten.klick.ansicht) zeigeAnsicht(knoten.klick.ansicht);
@@ -211,7 +214,7 @@ function energieflussStarten() {
const anlageBehaelter = document.getElementById("anlage");
if (anlageBehaelter && typeof Anlage !== "undefined") {
anlage = new Anlage(anlageBehaelter);
anlage = new Anlage(anlageBehaelter, { hochkantUnter: anzeigeModus ? 0 : undefined });
anlage.zeichnen();
}
const speicherBehaelter = document.getElementById("speicher");