Files
Smart-Dashboard/restricted/settings.php
T
adminandClaude Opus 5 deb81045d5 Skoda: Schluessel in den Einstellungen, Ladungen am Stueck, Wallbox in kW
- neuer Reiter "Fahrzeug": API-Schluessel eintragen und pruefen, ohne dass
  ein Wert je an den Browser zurueckgeht (restricted/skodaKeys.php)
- ajax/skoda.php trennt Ladungen am Zustand statt an 30 Minuten Pause und
  rahmt sie mit der Zeile davor und danach ein
- solarLog_skoda_ladepunkte.sql: Wallbox-Verlauf waehrend einer Ladung
- Kachelformat "leistung_kw" fuer Quellen, die schon Kilowatt melden
- Steckersymbol versteht evPlug als 1/0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 09:30:18 +02:00

259 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Einstellungsseite: Parameter, die von Hand gepflegt werden.
*
* Gegliedert in Reiter, einer je Thema - Preise und die Anzeigewerte der
* Home-Kacheln. Die Liste $reiter unten ist die einzige Stelle, die davon
* weiss: ein neues Thema ist ein Eintrag dort plus der Inhalt, alles Uebrige
* (Navigation, Umschalten, Merken des Reiters in der Adresse) ergibt sich
* daraus. Vorher stand jedes Thema als Karte untereinander; bei drei Themen
* waere die Seite ein Scrollband geworden.
*
* Innerhalb eines Reiters bleibt es bei Karten - eine je Tabelle bzw. je
* Etage.
*
* Die Zeilen zeichnet js/solar/settings.js aus dem Modell, das hier
* eingebettet wird - dieselbe Arbeitsteilung wie im Automatik-Editor. Sonst
* gaebe es die erste Zeile als PHP und jede weitere als JavaScript, und die
* beiden Fassungen liefen frueher oder spaeter auseinander. Aus demselben
* Grund kommen die Spaltenbreiten aus preisSpalten(): die Kopfzeile hier und
* die Felder dort muessen dieselben sein.
*/
require_once(__DIR__ . "/costs.php");
require_once(__DIR__ . "/kacheln.php");
require_once(__DIR__ . "/reiter.php");
// Nur wegen SKODA_APP_LINK - der Weg zu den Schlüsseln gehört neben das Feld,
// in das sie eingetragen werden, und der Link soll nur an einer Stelle stehen.
require_once(__DIR__ . "/skodaKeys.php");
/*
* Die Reiter. Reihenfolge ist Anzeigereihenfolge; der erste ist der offene,
* solange die Adresse nichts anderes sagt (#kacheln).
*/
$reiter = [
["id" => "preise", "titel" => "Preise", "symbol" => "bi-currency-euro"],
["id" => "kacheln", "titel" => "Home-Kacheln", "symbol" => "bi-grid-1x2"],
["id" => "geraete", "titel" => "Geräte", "symbol" => "bi-house-gear"],
["id" => "fahrzeug", "titel" => "Fahrzeug", "symbol" => "bi-ev-front"],
];
// "merken": der offene Reiter steht in der Adresse und uebersteht ein
// Neuladen - siehe reiterMerken() in js/solar/common.js.
$reiterOpt = ["name" => "einstellung", "merken" => true, "klasse" => "mb-3"];
$reiterOpt["aktiv"] = reiterAktiv($reiter, $reiterOpt);
$reihen = preisReihen();
$daten = [];
$fehler = "";
try {
foreach ($reihen as $key => $reihe) {
$daten[$key] = [
"felder" => $reihe["felder"],
"spalten" => preisSpalten($reihe["felder"]),
"zeilen" => preiseLaden($key),
];
}
} catch (Throwable $e) {
$fehler = $e->getMessage();
}
?>
<!--begin::App Main-->
<main class="app-main">
<!--begin::App Content Header-->
<div class="app-content-header">
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<h3 class="mb-0">Einstellungen</h3>
</div>
</div>
</div>
</div>
<!--end::App Content Header-->
<!--begin::App Content-->
<div class="app-content">
<div class="container-fluid">
<?php if ($fehler): ?>
<div class="alert alert-danger">Die Einstellungen sind nicht erreichbar: <?= htmlspecialchars($fehler) ?></div>
<?php endif; ?>
<!-- Reiter. Welcher offen ist, merkt sich die Adresse (#kacheln) -
ein Neuladen nach dem Speichern landet damit wieder dort. -->
<?= reiterLeiste($reiter, $reiterOpt) ?>
<div class="tab-content">
<?= reiterFlaecheAuf("preise", $reiterOpt) ?>
<?php foreach ($reihen as $key => $reihe): ?>
<?php $breiten = preisSpalten($reihe["felder"]); ?>
<div class="row">
<div class="col-12 col-xxl-9">
<div class="card mb-4 preis-karte" data-reihe="<?= htmlspecialchars($key) ?>">
<div class="card-header pb-2 pt-2">
<h3 class="card-title"><i class="bi <?= $reihe["symbol"] ?> me-2"></i><?= htmlspecialchars($reihe["titel"]) ?></h3>
<div class="card-tools">
<button type="button" class="btn btn-tool preis-neu" title="Zeitraum hinzufügen">
<i class="bi bi-plus-lg"></i>
</button>
<button type="button" class="btn btn-tool preis-zurueck" title="Änderungen verwerfen">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
</div>
</div>
<div class="card-body pb-2">
<p class="text-secondary small mb-3"><?= htmlspecialchars($reihe["hinweis"]) ?></p>
<!-- Spaltenköpfe nur auf breiten Schirmen; auf dem Handy trägt
jedes Feld seine eigene Beschriftung. -->
<div class="row g-2 d-none d-xl-flex preis-kopf text-secondary small fw-semibold">
<div class="col-xl-<?= $breiten["ab"] ?>">Gültig ab</div>
<div class="col-xl-<?= $breiten["bis"] ?>">Gültig bis</div>
<?php foreach (array_values($reihe["felder"]) as $i => $feld): ?>
<div class="col-xl-<?= $breiten["felder"][$i] ?>"><?= htmlspecialchars($feld["titel"]) ?></div>
<?php endforeach; ?>
<div class="col-xl-<?= $breiten["aktion"] ?>"></div>
</div>
<div class="preis-zeilen"></div>
<button type="button" class="btn btn-sm btn-outline-secondary mt-3 preis-neu">
<i class="bi bi-plus-lg me-1"></i>Zeitraum hinzufügen
</button>
</div>
<div class="card-footer d-flex align-items-center gap-3">
<button type="button" class="btn btn-primary preis-speichern">Speichern</button>
<span class="preis-hinweis small"></span>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<?= reiterFlaecheZu() ?><!-- /Preise -->
<!-- ================================================ Home-Kacheln ==== -->
<!-- Der Inhalt kommt aus js/solar/settings.js: eine Karte je Etage, darin
je Raum eine Zeile mit ihren Werten. Gezeichnet wird aus dem Modell,
das ajax/settings.php?action=kacheln liefert - dieselbe Arbeits-
teilung wie bei den Preisen. -->
<?= reiterFlaecheAuf("kacheln", $reiterOpt) ?>
<div class="row">
<div class="col-12 col-xxl-9">
<div class="card mb-4" id="kachelKarte">
<div class="card-header pb-2 pt-2">
<h3 class="card-title"><i class="bi bi-grid-1x2 me-2"></i>Werte auf den Kacheln</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" id="kachelNeuLaden"
title="Neu laden">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
</div>
</div>
<div class="card-body">
<p class="text-secondary small mb-3">
Jede Kachel im Grundriss zeigt bis zu drei Messwerte. Ohne eigene
Angabe sind das die Werte aus <code>restricted/rooms.php</code>
bei Räumen mit Thermostat Soll, Ist und Feuchte. Was hier gesetzt
wird, gilt stattdessen; „Standard“ nimmt es wieder zurück.
</p>
<div id="kachelListe">
<p class="text-secondary small mb-0">Wird geladen …</p>
</div>
</div>
</div>
</div>
</div>
<?= reiterFlaecheZu() ?><!-- /Home-Kacheln -->
<!-- ===================================================== Geräte ==== -->
<!-- Die Zuordnung Gerät -> Raum lag vorher als Modal auf der Home-Seite.
Sie gehört hierher: sie wird selten geändert, sie ist eine
Einstellung, und hier ist Platz für das, was daneben gehört - wie
lange ein Gerät nichts mehr gemeldet hat, in welchen Automatiken es
steckt, und der Weg, eine Karteileiche loszuwerden. -->
<?= reiterFlaecheAuf("geraete", $reiterOpt) ?>
<div class="row">
<div class="col-12 col-xxl-10">
<div class="card mb-4" id="geraeteKarte">
<div class="card-header pb-2 pt-2">
<h3 class="card-title"><i class="bi bi-house-gear me-2"></i>Geräte und Räume</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" id="geraeteNeuLaden" title="Neu laden">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
</div>
</div>
<div class="card-body">
<p class="text-secondary small mb-3">
Der Raum ordnet die Geräteliste im Automatik-Editor und bestimmt,
welche Messwerte bei einer Home-Kachel oben stehen. Er wird von Hand
vergeben und bleibt bei einem neuen Suchlauf erhalten der Suchlauf
legt nur an und aktualisiert, er löscht nichts. Abgebaute Geräte
bleiben deshalb stehen, bis sie hier entfernt werden.
</p>
<div id="geraeteListe">
<p class="text-secondary small mb-0">Wird geladen …</p>
</div>
</div>
</div>
</div>
</div>
<?= reiterFlaecheZu() ?><!-- /Geräte -->
<!-- =================================================== Fahrzeug ==== -->
<!-- Die Zugangsschlüssel der MyŠkoda-Anbindung. Sie laufen ab und
müssen dann getauscht werden - vorher hieß das: per SSH auf die NAS
und skoda.conf im Editor öffnen.
Die Zeilen zeichnet js/solar/settings.js aus dem, was
ajax/settings.php?action=skoda-keys liefert. Was dort NICHT
mitkommt, sind die Schlüssel selbst: die Seite steht im Heimnetz
ohne Anmeldung offen, und ein solcher Schlüssel steuert das
Fahrzeug von überall auf der Welt. Zu sehen ist nur, ob einer
hinterlegt ist und auf welche vier Zeichen er endet. -->
<?= reiterFlaecheAuf("fahrzeug", $reiterOpt) ?>
<div class="row">
<div class="col-12 col-xxl-8">
<div class="card mb-4" id="skodaKarte">
<div class="card-header pb-2 pt-2">
<h3 class="card-title"><i class="bi bi-key me-2"></i>Zugang zum Fahrzeug</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" id="skodaNeuLaden" title="Neu laden">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
</div>
</div>
<div class="card-body">
<p class="text-secondary small mb-3">
Die Schlüssel werden in der MyŠkoda-App erzeugt, gelten nur für die
dort ausgewählten Fahrzeuge und laufen nach einiger Zeit ab. Ein neuer
wird hier eingetragen und sofort einmal ausprobiert was die API
ablehnt, landet gar nicht erst in der Konfiguration.
</p>
<p class="mb-3">
<a class="btn btn-sm btn-outline-primary"
href="<?= SKODA_APP_LINK ?>" target="_blank" rel="noopener">
<i class="bi bi-box-arrow-up-right me-1"></i>Schlüssel in der MyŠkoda-App verwalten
</a>
</p>
<div id="skodaListe">
<p class="text-secondary small mb-0">Wird geladen …</p>
</div>
</div>
</div>
</div>
</div>
<?= reiterFlaecheZu() ?><!-- /Fahrzeug -->
</div><!-- /tab-content -->
</div>
</div>
<!--end::App Content-->
</main>
<!--end::App Main-->
<script>
// Ausgangsstand der Preistabellen. Werte in der Anzeigeeinheit (ct statt
// Euro); umgerechnet wird in restricted/costs.php - an genau einer Stelle.
const preisDaten = <?= json_encode($daten, JSON_UNESCAPED_UNICODE) ?>;
// Aelteste Messung in EnergyFlow. Beginnt eine Preisreihe spaeter, fehlt
// den Werten davor der Preis - dann warnt die Maske.
const ersteMessung = <?= json_encode(ersteMessung()) ?>;
</script>