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
+13 -8
View File
@@ -192,15 +192,20 @@ genau die Topics, die auf den Kacheln stehen (`tileTopics()` in `rooms.php`).
| `settings` | `settings.php` | `settings.js`, `grundriss.js`, `energieflussKatalog.js`, `energiefluss.js`, `energieflussEinstellungen.js` | `settings.php?action=…` | — | | `settings` | `settings.php` | `settings.js`, `grundriss.js`, `energieflussKatalog.js`, `energiefluss.js`, `energieflussEinstellungen.js` | `settings.php?action=…` | — |
| `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — | | `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — |
| `einfuehrung` | `einfuehrung.php` | `einfuehrung.js` | nichts — Folien und Bilder stehen in der Seite | — | | `einfuehrung` | `einfuehrung.php` | `einfuehrung.js` | nichts — Folien und Bilder stehen in der Seite | — |
| `anzeige` | `anzeige.php` (ganze Seite, ohne `header.php`/`footer.php`) | `anlageKatalog.js`, `anlage.js`, `speicher.js`, `energieflussKatalog.js`, `energiefluss.js`, `bewaesserungAnzeige.js`, `anzeige.js` | `speicher` | `solarManager/#`, `Gartenwasser/#` | | `anzeige` | `solar.php` im Anzeige-Modus | wie `solar`, dazu `anzeige.js` | wie `solar` | wie `solar` |
**Vollbildanzeige** (`?action=anzeige`) — Übersicht, Anlage und Speicher für ein **Anzeige-Modus** (`?action=anzeige`) — die Solar-Seite für ein festes Display,
festes Display, gebaut für den Raspberry Pi 3B+ mit 800 × 480 im Kioskmodus: gebaut für den Raspberry Pi 3B+ mit 800 × 480 im Kioskmodus. In `index.php` eine
schmale Leiste links, immer quer, nach zwei Minuten ohne Berührung zurück zur Kopie des Eintrags `solar` mit `"anzeige" => true`: `header.php` lässt Kopfleiste
Übersicht, MQTT verbindet still neu (Punkt unten: grün/gelb/rot), alle sechs und Menü weg und setzt eine schmale Leiste (Übersicht, Anlage, Speicher,
Stunden lädt die Seite neu. Ohne weitere Parameter läuft sie „ruhig“ — nur die Diagramme), `footer.php` den Fuß; die Realtime-Karte füllt einen Bildschirm,
Flüsse bewegen sich, in groben Stufen — und ohne Mauszeiger; die Übersicht bleibt quer. Bedienfenster, Diagramme und Bewässerung kommen
`&animation=1` schaltet alle Animationen ein, `&maus=1` zeigt den Zeiger. unverändert aus `solarMQTT.js`. `anzeige.js` ergänzt nur: zurück zur Übersicht
nach zwei Minuten ohne Berührung (offene Fenster schließen), Verbindungspunkt
(grün/gelb/rot) und Uhr in der Leiste, Neuladen alle sechs Stunden. Ohne
weitere Parameter läuft sie „ruhig“ — nur die Flüsse bewegen sich, in groben
Stufen — und ohne Mauszeiger; `&animation=1` schaltet alle Animationen ein,
`&maus=1` zeigt den Zeiger.
Und dasselbe als Abhängigkeitsbild — von der Seite bis in den Speicher: Und dasselbe als Abhängigkeitsbild — von der Seite bis in den Speicher:
+35 -21
View File
@@ -3869,22 +3869,19 @@ svg.anl-plan.anl-hoch { max-height: none; }
} }
/* ========================================================================== /* ==========================================================================
Vollbildanzeige (restricted/anzeige.php, js/solar/anzeige.js), z.B. am Anzeige-Modus (index.php?action=anzeige, js/solar/anzeige.js): die
Raspberry Pi mit 800 x 480: schmale Leiste links, Ansicht auf voller Höhe. Solar-Seite auf einem festen Display, z.B. Raspberry Pi mit 800 x 480.
Leiste links; die Realtime-Karte füllt genau einen Bildschirm, die
Diagramme folgen darunter und rasten beim Wischen karteweise ein.
========================================================================== */ ========================================================================== */
body.anzeige { html:has(body.anzeige) { scroll-snap-type: y proximity; scrollbar-width: none; }
margin: 0; height: 100vh; overflow: hidden; html:has(body.anzeige)::-webkit-scrollbar { display: none; }
display: grid; grid-template-columns: 3.2rem minmax(0, 1fr); body.anzeige { -webkit-tap-highlight-color: transparent; }
background: var(--ton-grund, #191d20); color: var(--ton-schrift, #e8ede9);
font-family: "Poppins", system-ui, sans-serif; font-size: 14px;
-webkit-tap-highlight-color: transparent; user-select: none;
}
body.anzeige-ohne-maus, body.anzeige-ohne-maus,
body.anzeige-ohne-maus * { cursor: none !important; } body.anzeige-ohne-maus * { cursor: none !important; }
/* Die Seite lädt AdminLTE nicht - dessen Grundregeln fehlen hier. */
body.anzeige [hidden] { display: none !important; }
body.anzeige *, body.anzeige *::before, body.anzeige *::after { box-sizing: border-box; }
.anzeige .anzeige-leiste { .anzeige .anzeige-leiste {
position: fixed; left: 0; top: 0; bottom: 0; width: 3.2rem; z-index: 1030;
display: flex; flex-direction: column; align-items: center; gap: .4rem; padding: .5rem 0; display: flex; flex-direction: column; align-items: center; gap: .4rem; padding: .5rem 0;
background: var(--ton-flaeche, #23282c); border-right: 1px solid var(--ton-linie, rgba(138, 148, 158, .2)); background: var(--ton-flaeche, #23282c); border-right: 1px solid var(--ton-linie, rgba(138, 148, 158, .2));
} }
@@ -3898,14 +3895,26 @@ body.anzeige *, body.anzeige *::before, body.anzeige *::after { box-sizing: bord
.anzeige .anzeige-punkt.warn { background: #e8a33a; } .anzeige .anzeige-punkt.warn { background: #e8a33a; }
.anzeige .anzeige-punkt.gut { background: #6cbe58; } .anzeige .anzeige-punkt.gut { background: #6cbe58; }
.anzeige .anzeige-uhr { font-size: .72rem; font-weight: 600; color: var(--ton-schrift-2, #9aa6ad); font-variant-numeric: tabular-nums; } .anzeige .anzeige-uhr { font-size: .72rem; font-weight: 600; color: var(--ton-schrift-2, #9aa6ad); font-variant-numeric: tabular-nums; }
.anzeige .anzeige-inhalt { min-width: 0; height: 100vh; }
.anzeige .anzeige-flaeche { box-sizing: border-box; height: 100vh; padding: .35rem; overflow-y: auto; scrollbar-width: none; } .anzeige .app-wrapper { display: block; }
.anzeige .anzeige-flaeche::-webkit-scrollbar { display: none; } .anzeige .app-main { margin-left: 3.2rem; }
.anzeige .anzeige-flaeche[hidden] { display: none; } .anzeige .app-content-header { display: none; }
.anzeige #anzeige-fluss { overflow: hidden; padding: .2rem; } .anzeige .app-content { padding: 0 .35rem; }
.anzeige #anzeige-fluss svg.enf { width: 100%; height: 100%; max-height: none; } .anzeige .app-content .container-fluid { padding: 0; }
/* Anlage und Speicher: die Umbrüche der Solar-Seite richten sich nach der .anzeige .app-content .row { --bs-gutter-x: 0; }
Fensterbreite und würden am 800er-Display zu früh greifen. */ .anzeige .app-content .card { scroll-snap-align: start; }
/* Realtime-Karte: ein ganzer Bildschirm, ohne Kopf - die Leiste ersetzt ihn. */
.anzeige .card#energieflussKarte { height: 100vh; margin-bottom: .5rem !important; border: 0; border-radius: 0; background: transparent; }
.anzeige .card#energieflussKarte .card-header { display: none !important; }
.anzeige .card#energieflussKarte .card-body { height: 100%; padding: .2rem 0 !important; }
.anzeige #energiefluss { height: 100%; overflow: hidden; }
.anzeige #energiefluss svg.enf { width: 100%; height: 100%; max-height: none; }
.anzeige #anlage,
.anzeige #speicher { height: 100%; overflow-y: auto; scrollbar-width: none; }
/* Die Umbrüche von Anlage und Speicher richten sich nach der Fensterbreite
und würden am 800er-Display zu früh greifen. */
.anzeige .anl svg.anl-plan { max-height: calc(100vh - 7.5rem); } .anzeige .anl svg.anl-plan { max-height: calc(100vh - 7.5rem); }
.anzeige .spk .spk-kennzahlen { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .35rem; } .anzeige .spk .spk-kennzahlen { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .35rem; }
.anzeige .spk .spk-kachel { padding: .4rem .55rem .45rem; } .anzeige .spk .spk-kachel { padding: .4rem .55rem .45rem; }
@@ -3921,6 +3930,12 @@ body.anzeige *, body.anzeige *::before, body.anzeige *::after { box-sizing: bord
.anzeige .spk .spk-werte small { font-size: .62rem; } .anzeige .spk .spk-werte small { font-size: .62rem; }
.anzeige .spk .spk-gross b { font-size: 1.4rem; } .anzeige .spk .spk-gross b { font-size: 1.4rem; }
/* Bedienfenster: passen auf 480 px Höhe und lassen sich mit dem Finger bedienen. */
.anzeige .modal-dialog { margin: .4rem auto; }
.anzeige .modal-body { max-height: calc(100vh - 9rem); overflow-y: auto; }
.anzeige .modal .form-range { height: 2rem; }
.anzeige .modal .btn { min-height: 2.4rem; }
/* Ruhig (Vorgabe am Pi): alles Zierwerk aus, nur die Flüsse laufen - in /* Ruhig (Vorgabe am Pi): alles Zierwerk aus, nur die Flüsse laufen - in
groben Stufen, damit ein Raspberry Pi 3 nicht dauernd neu zeichnet. */ groben Stufen, damit ein Raspberry Pi 3 nicht dauernd neu zeichnet. */
.anzeige-ruhig *, .anzeige-ruhig *,
@@ -3932,7 +3947,6 @@ body.anzeige *, body.anzeige *::before, body.anzeige *::after { box-sizing: bord
.anzeige-ruhig .enf .enf-rueck .enf-strom { animation-direction: reverse !important; } .anzeige-ruhig .enf .enf-rueck .enf-strom { animation-direction: reverse !important; }
.anzeige-ruhig .enf .enf-still .enf-strom, .anzeige-ruhig .enf .enf-still .enf-strom,
.anzeige-ruhig .enf.enf-pause .enf-strom { animation: none !important; } .anzeige-ruhig .enf.enf-pause .enf-strom { animation: none !important; }
.anzeige-ruhig .enf .enf-strahlen,
.anzeige-ruhig .enf .enf-koerper { transition: none; } .anzeige-ruhig .enf .enf-koerper { transition: none; }
/* --- Karte: Umschalter Übersicht / Anlage -------------------------------- */ /* --- Karte: Umschalter Übersicht / Anlage -------------------------------- */
+9 -13
View File
@@ -120,13 +120,13 @@ if (isset($_GET["addUser"])) {
"scripts" => ["js/solar/logs.js"]], "scripts" => ["js/solar/logs.js"]],
"einfuehrung" => ["template" => "einfuehrung.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false, "einfuehrung" => ["template" => "einfuehrung.php", "mqtt" => false, "charts" => false, "meteogram" => false, "leaflet" => false,
"scripts" => ["js/solar/einfuehrung.js"]], "scripts" => ["js/solar/einfuehrung.js"]],
// Vollbild für ein festes Display (Raspberry Pi). "eigen": die Vorlage
// ist eine ganze Seite, ohne header.php und footer.php.
"anzeige" => ["template" => "anzeige.php", "eigen" => true, "mqtt" => true, "charts" => false, "meteogram" => false, "leaflet" => false,
"scripts" => ["js/solar/anlageKatalog.js", "js/solar/anlage.js", "js/solar/speicher.js",
"js/solar/energieflussKatalog.js", "js/solar/energiefluss.js",
"js/solar/bewaesserungAnzeige.js", "js/solar/anzeige.js"]],
]; ];
// Anzeige für ein festes Display (Raspberry Pi, 800 x 480): die Solar-Seite
// ohne Kopfleiste und Menue, mit schmaler Leiste - header.php und
// footer.php richten sich nach "anzeige", js/solar/anzeige.js macht den Rest.
$pages["anzeige"] = $pages["solar"];
$pages["anzeige"]["anzeige"] = true;
$pages["anzeige"]["scripts"][] = "js/solar/anzeige.js";
// Unbekannte oder fehlende Aktion faellt auf die Solar-Seite zurueck. // Unbekannte oder fehlende Aktion faellt auf die Solar-Seite zurueck.
if (!isset($_GET["action"]) || !isset($pages[$_GET["action"]])) { if (!isset($_GET["action"]) || !isset($pages[$_GET["action"]])) {
@@ -134,13 +134,9 @@ if (isset($_GET["addUser"])) {
} }
$page = $pages[$_GET["action"]]; $page = $pages[$_GET["action"]];
if (!empty($page["eigen"])) { include "restricted/header.php";
include "restricted/" . $page["template"]; include "restricted/" . $page["template"];
} else { include "restricted/footer.php";
include "restricted/header.php";
include "restricted/" . $page["template"];
include "restricted/footer.php";
}
} else { } else {
echo <<<ENDE echo <<<ENDE
<html> <html>
+66 -85
View File
@@ -1,122 +1,103 @@
/* ========================================================================== /* ==========================================================================
Vollbildanzeige (restricted/anzeige.php): Übersicht, Anlage und Speicher Anzeige-Modus der Solar-Seite (index.php?action=anzeige) für ein festes
für ein festes Display, z.B. den Raspberry Pi mit 800 x 480. Display, gebaut für den Raspberry Pi 3B+ mit 800 x 480.
Dieselben Bausteine wie auf der Solar-Seite, nur anders verdrahtet: Die Seite selbst ist die Solar-Seite: solarMQTT.js zeichnet Übersicht,
- immer quer (hochkantUnter: 0), das Display steht quer Anlage, Speicher und Diagramme und öffnet die Bedienfenster. header.php
- antippbar sind nur Kreise, die eine andere Ansicht öffnen; Bedienfenster lässt Kopfleiste und Menü weg und setzt stattdessen die schmale Leiste
(Heizstab, Wallbox) gibt es hier nicht links; die Übersicht bleibt quer (energieflussStarten() in solarMQTT.js
- nach LEERLAUF ohne Berührung zurück zur Übersicht fragt nach der Klasse "anzeige" am body).
- MQTT verbindet nach einem Abbruch still neu; der Punkt unten in der
Leiste zeigt den Zustand (grün verbunden, gelb keine frischen Werte, Hier steht nur, was ein Display ohne Bediener braucht:
rot getrennt). Kein alert() - am Display ist niemand, der ihn wegklickt. - Leiste: Übersicht, Anlage, Speicher, Diagramme
- alle NEU_LADEN Stunden lädt die Seite neu, damit ein Browser, der - nach LEERLAUF ohne Berührung zurück zur Übersicht, ganz nach oben
wochenlang läuft, keinen Speicher ansammelt und Änderungen am Code - Punkt in der Leiste: grün verbunden, gelb keine frischen Werte,
ankommen 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 () { (function () {
"use strict"; "use strict";
const ADRESSE = "wss://mqtt.nas.el-wa.org:443"; // wie in solarMQTT.js
const LEERLAUF = 2 * 60000; const LEERLAUF = 2 * 60000;
const NEU_LADEN = 6 * 3600000; const NEU_LADEN = 6 * 3600000;
const TAKT = typeof anzeigeRuhig !== "undefined" && anzeigeRuhig ? 2000 : 1000;
const VERALTET = 30000; 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); const $ = id => document.getElementById(id);
let bild = null, anlage = null, speicher = null; let leerlaufUhr = null;
let leerlaufUhr = null, geplant = false, verbunden = false, letzteDaten = 0; 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) { function zeige(name) {
document.querySelectorAll("[data-anzeige]").forEach(b => b.setAttribute("aria-pressed", b.dataset.anzeige === name ? "true" : "false")); if (name === "diagramme") {
document.querySelectorAll("[data-anzeige-flaeche]").forEach(f => { f.hidden = f.dataset.anzeigeFlaeche !== name; }); // Die nächste Spalte nach der Realtime-Karte - dazwischen steht ein <script>.
wachHalten(); 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() { function wachHalten() {
clearTimeout(leerlaufUhr); 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() { 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 punkt = $("anzeigeVerbindung");
const frisch = Date.now() - letzteDaten < VERALTET;
const klasse = "anzeige-punkt" + (verbunden && frisch ? " gut" : verbunden ? " warn" : ""); const klasse = "anzeige-punkt" + (verbunden && frisch ? " gut" : verbunden ? " warn" : "");
if (punkt.className !== klasse) { if (punkt.className !== klasse) {
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 d = new Date();
const text = String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0"); const uhr = String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
if ($("anzeigeUhr").textContent !== text) $("anzeigeUhr").textContent = text; if ($("anzeigeUhr").textContent !== uhr) $("anzeigeUhr").textContent = uhr;
}
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();
}
});
} }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const bewaesserung = () => wateringSymboleSetzen(id => document.getElementById(id)); if (!document.body.classList.contains("anzeige")) return;
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();
document.querySelectorAll("[data-anzeige]").forEach(b => b.addEventListener("click", () => zeige(b.dataset.anzeige))); 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); document.addEventListener("pointerdown", wachHalten, { passive: true });
uhr(); wachHalten();
setInterval(zustand, 1000);
zustand(); zustand();
verbinden();
setTimeout(() => location.reload(), NEU_LADEN); setTimeout(() => location.reload(), NEU_LADEN);
}); });
})(); })();
+2 -3
View File
@@ -8,7 +8,6 @@
onKlick(knoten), // Kreis mit klick-Ziel angetippt onKlick(knoten), // Kreis mit klick-Ziel angetippt
onVerschoben(id, x, y), // nur beim Bearbeiten onVerschoben(id, x, y), // nur beim Bearbeiten
onGezeichnet(), // nach jedem (Neu-)Zeichnen onGezeichnet(), // nach jedem (Neu-)Zeichnen
klickFilter(knoten), // optional: welche Kreise antippbar sind
hochkantUnter: 720, // optional: px Breite, darunter hochkant (0 = nie) hochkantUnter: 720, // optional: px Breite, darunter hochkant (0 = nie)
}); });
bild.aktualisieren(mqttData); 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) { klickbar(k) {
return !!k.klick && !this.bearbeiten && (!this.opt.klickFilter || this.opt.klickFilter(k)); return !!k.klick && !this.bearbeiten;
} }
hochkantGewuenscht() { hochkantGewuenscht() {
+4 -1
View File
@@ -196,7 +196,10 @@ function energieflussStarten() {
knopf.addEventListener("click", function () { zeigeAnsicht(knopf.dataset.enfAnsicht); }); 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, { energiefluss = new Energiefluss(behaelter, {
hochkantUnter: anzeigeModus ? 0 : undefined,
einstellungen: typeof energieflussEinstellungen !== "undefined" ? energieflussEinstellungen : {}, einstellungen: typeof energieflussEinstellungen !== "undefined" ? energieflussEinstellungen : {},
onKlick: function (knoten) { onKlick: function (knoten) {
if (knoten.klick.ansicht) zeigeAnsicht(knoten.klick.ansicht); if (knoten.klick.ansicht) zeigeAnsicht(knoten.klick.ansicht);
@@ -211,7 +214,7 @@ function energieflussStarten() {
const anlageBehaelter = document.getElementById("anlage"); const anlageBehaelter = document.getElementById("anlage");
if (anlageBehaelter && typeof Anlage !== "undefined") { if (anlageBehaelter && typeof Anlage !== "undefined") {
anlage = new Anlage(anlageBehaelter); anlage = new Anlage(anlageBehaelter, { hochkantUnter: anzeigeModus ? 0 : undefined });
anlage.zeichnen(); anlage.zeichnen();
} }
const speicherBehaelter = document.getElementById("speicher"); const speicherBehaelter = document.getElementById("speicher");
-60
View File
@@ -1,60 +0,0 @@
<?php
/**
* Vollbildanzeige für ein festes Display, gedacht für den Raspberry Pi mit
* dem 7-Zoll-Display (800 x 480) im Kioskmodus:
*
* index.php?action=anzeige ruhige Animationen, kein Mauszeiger
* index.php?action=anzeige&animation=1 alle Animationen (schnellere Geräte)
* index.php?action=anzeige&maus=1 Mauszeiger sichtbar (zum Einrichten)
*
* Eine eigene Seite ohne Kopf, Seitenleiste und die Karten der Solar-Seite:
* links eine schmale Leiste mit Übersicht, Anlage und Speicher, rechts die
* Ansicht auf voller Höhe. Gezeichnet wird mit denselben Bausteinen wie auf
* der Solar-Seite (energiefluss.js, anlage.js, speicher.js); verdrahtet
* werden sie von js/solar/anzeige.js.
*
* index.php bindet für diese Seite weder header.php noch footer.php ein
* ("eigen" in der Seitentabelle) - deshalb steht der Kopf hier selbst.
*/
require_once(__DIR__ . "/energiefluss.php");
$ruhig = empty($_GET["animation"]);
$maus = !empty($_GET["maus"]);
?>
<!doctype html>
<html lang="de" data-bs-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<title>Energiefluss</title>
<link rel="icon" type="image/png" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/fonts/font_poppins.css" />
<link rel="stylesheet" href="css/bootstrap-icons.min.css" />
<link rel="stylesheet" href="./css/solar.css?v=<?= filemtime("css/solar.css") ?>" />
</head>
<body class="anzeige<?= $ruhig ? " anzeige-ruhig" : "" ?><?= $maus ? "" : " anzeige-ohne-maus" ?>">
<nav class="anzeige-leiste" aria-label="Ansicht">
<button type="button" data-anzeige="fluss" aria-pressed="true" title="Übersicht"><i class="bi bi-diagram-3"></i></button>
<button type="button" data-anzeige="anlage" aria-pressed="false" title="Anlage"><i class="bi bi-grid-3x2-gap"></i></button>
<button type="button" data-anzeige="speicher" aria-pressed="false" title="Speicher"><i class="bi bi-battery-half"></i></button>
<div class="anzeige-fuss">
<span class="anzeige-punkt" id="anzeigeVerbindung" title="Verbindung wird aufgebaut"></span>
<span class="anzeige-uhr" id="anzeigeUhr"></span>
</div>
</nav>
<main class="anzeige-inhalt">
<div class="anzeige-flaeche" data-anzeige-flaeche="fluss" id="anzeige-fluss"></div>
<div class="anzeige-flaeche" data-anzeige-flaeche="anlage" id="anzeige-anlage" hidden></div>
<div class="anzeige-flaeche" data-anzeige-flaeche="speicher" id="anzeige-speicher" hidden></div>
</main>
<script>
// Abweichungen dieses Hauses vom Katalog, siehe restricted/energiefluss.php.
const energieflussEinstellungen = <?= json_encode((object)energieflussLaden(), JSON_UNESCAPED_UNICODE) ?>;
const anzeigeRuhig = <?= $ruhig ? "true" : "false" ?>;
</script>
<script src="js/mqtt.min.js"></script>
<?php foreach ($page["scripts"] as $script): ?>
<script src="<?= $script ?>?v=<?= filemtime($script) ?>"></script>
<?php endforeach; ?>
</body>
</html>
+2
View File
@@ -1,3 +1,4 @@
<?php if (empty($page["anzeige"])): ?>
<!--begin::Footer--> <!--begin::Footer-->
<footer class="app-footer"> <footer class="app-footer">
<!--begin::To the end--> <!--begin::To the end-->
@@ -12,6 +13,7 @@
<!--end::Copyright--> <!--end::Copyright-->
</footer> </footer>
<!--end::Footer--> <!--end::Footer-->
<?php endif; ?>
<div class="modal fade" id="modalEV" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"> <div class="modal fade" id="modalEV" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
+28 -2
View File
@@ -59,9 +59,34 @@ if (!isset($_GET["floor"]))
<!--end::Head--> <!--end::Head-->
<!--begin::Body--> <!--begin::Body-->
<body class="layout-fixed sidebar-expand-lg bg-body-tertiary" data-bs-theme="dark"> <?php
// Anzeige-Modus (?action=anzeige, siehe index.php): dieselbe Seite fuer ein
// festes Display, ohne Kopfleiste und Menue, dafuer mit schmaler Leiste.
// ?animation=1 schaltet alle Animationen ein, ?maus=1 den Mauszeiger.
$anzeige = !empty($page["anzeige"]);
$koerperKlassen = "layout-fixed sidebar-expand-lg bg-body-tertiary";
if ($anzeige) {
$koerperKlassen .= " anzeige" . (empty($_GET["animation"]) ? " anzeige-ruhig" : "")
. (empty($_GET["maus"]) ? " anzeige-ohne-maus" : "");
}
?>
<body class="<?= $koerperKlassen ?>" data-bs-theme="dark">
<?php if ($anzeige): ?>
<!-- Leiste der Anzeige; verdrahtet in js/solar/anzeige.js -->
<nav class="anzeige-leiste" aria-label="Ansicht">
<button type="button" data-anzeige="fluss" aria-pressed="true" title="Übersicht"><i class="bi bi-diagram-3"></i></button>
<button type="button" data-anzeige="anlage" aria-pressed="false" title="Anlage"><i class="bi bi-grid-3x2-gap"></i></button>
<button type="button" data-anzeige="speicher" aria-pressed="false" title="Speicher"><i class="bi bi-battery-half"></i></button>
<button type="button" data-anzeige="diagramme" aria-pressed="false" title="Diagramme"><i class="bi bi-graph-up"></i></button>
<div class="anzeige-fuss">
<span class="anzeige-punkt" id="anzeigeVerbindung" title="Verbindung wird aufgebaut"></span>
<span class="anzeige-uhr" id="anzeigeUhr"></span>
</div>
</nav>
<?php endif; ?>
<!--begin::App Wrapper--> <!--begin::App Wrapper-->
<div class="app-wrapper"> <div class="app-wrapper">
<?php if (!$anzeige): ?>
<!--begin::Header--> <!--begin::Header-->
<nav class="app-header navbar navbar-expand bg-body"> <nav class="app-header navbar navbar-expand bg-body">
<!--begin::Container--> <!--begin::Container-->
@@ -213,4 +238,5 @@ if (!isset($_GET["floor"]))
</div> </div>
<!--end::Sidebar Wrapper--> <!--end::Sidebar Wrapper-->
</aside> </aside>
<!--end::Sidebar--> <!--end::Sidebar-->
<?php endif; ?>