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:
+66
-85
@@ -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);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user