Files
Smart-Dashboard/restricted/skoda.php
T
adminandClaude Opus 5 d285ec27a0 Einstellungsseite, Skoda-Seite und Karte
Sammelstand der offenen Arbeit im Dashboard.

Einstellungen: eine Eingabemaske fuer die Energiepreise unter
restricted/settings.php, dazu costs.php als Modell und ajax/settings.php
als Endpunkt. Die Preise stehen jetzt in gridCosts, gasCosts und fuelCosts
statt fest im Quelltext von getStats.php; die Jahresstatistik rechnet ueber
LEAD() den jeweils gueltigen Zeitraum aus und schlaegt den anteiligen
Grundpreis auf. Eingegeben wird in gewohnten Einheiten - l/100 km,
kWh/100 km, Euro je Liter -, umgerechnet wird beim Speichern.
solarLog_costs.sql beschreibt die Umstellung der Tabellen.

Skoda: eigene Seite mit Live-Werten ueber MQTT und Historie ueber
ajax/skoda.php, Kommandos ueber ajax/skodaCmd.php.

Karte: Leaflet mit einem eigenen Kachel-Zwischenspeicher (ajax/tile.php),
dessen Ablage unter tiles/ nicht ins Repository gehoert.

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

384 lines
19 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
/*
* Škoda-Dashboard.
*
* Zwei Datenwege, bewusst getrennt:
*
* MQTT Ladestand, Verriegelung, Reichweite, Ladezustand und
* Wallbox-Leistung kommen laufend über solarManager/# und
* aktualisieren die Anzeige sofort.
*
* ajax/skoda.php Alles, was nur in der Tabelle steht - Türen, Fenster,
* Licht, Kilometerstand, Position, Klima - sowie sämtliche
* Auswertungen. Wird beim Laden und danach minütlich geholt.
*
* Solange kein Fahrzeug angebunden ist, bleibt die Tabelle leer; jeder
* Abschnitt zeigt dann seinen Leerzustand statt eines Fehlers.
*
* Die Fahrzeuggrafik ist eine maßstäbliche Draufsicht des Enyaq:
* 4648 x 1877 mm, Radstand 2765 mm, daraus das Seitenverhältnis 2,48:1 und
* die Lage der Achsen. Kein Foto, sondern ein Schema - die Teile sollen
* ihren Zustand zeigen, nicht das Auto porträtieren.
*/
?>
<!--begin::App Main-->
<main class="app-main">
<div class="app-content-header">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-sm-8">
<h3 class="mb-0">Škoda</h3>
</div>
<div class="col-sm-4 text-sm-end">
<span class="skoda-alter text-secondary" id="skodaAlter">&nbsp;</span>
</div>
</div>
</div>
</div>
<div class="app-content">
<div class="container-fluid">
<!-- ================================================ Zustand ===== -->
<div class="row">
<!-- Das Fahrzeug als Anzeigeelement. Jedes Teil trägt seinen Zustand
selbst: offene Türen und Klappen leuchten bernstein, Licht gelb,
der Ladeanschluss pulst beim Laden. Die Zeile darunter benennt
dasselbe in Worten und hebt beim Überfahren das zugehörige Teil
hervor - Farbe allein soll die Aussage nicht tragen. -->
<div class="col-xxl-4 col-lg-5 d-flex align-items-stretch">
<div class="card mb-4 p-0 w-100">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Fahrzeug</h3>
<div class="card-tools">
<span class="badge text-bg-secondary" id="skodaSchloss"></span>
</div>
</div>
<div class="card-body d-flex flex-column align-items-center pt-2">
<svg id="skodaAuto" class="skoda-auto" viewBox="0 0 224 500"
role="img" aria-label="Draufsicht des Fahrzeugs mit dem Zustand von Türen, Fenstern, Klappen und Licht">
<!-- Räder. Vorderachse bei 860 mm, Hinterachse bei 3625 mm
ab Bugspitze - der Radstand von 2765 mm maßstäblich. -->
<g class="rad">
<rect x="5" y="68" width="21" height="58" rx="9"/>
<rect x="198" y="68" width="21" height="58" rx="9"/>
<rect x="5" y="362" width="21" height="58" rx="9"/>
<rect x="198" y="362" width="21" height="58" rx="9"/>
</g>
<!-- Karosserie -->
<path class="karosserie" d="M112 6
C150 6, 198 16, 204 50 C209 90, 212 160, 212 250
C212 340, 209 410, 204 450 C198 484, 150 494, 112 494
C74 494, 26 484, 20 450 C15 410, 12 340, 12 250
C12 160, 15 90, 20 50 C26 16, 74 6, 112 6 Z"/>
<!-- Tech-Deck Face: die glänzend schwarze Blende über die
volle Bugbreite, die den Enyaq seit der Überarbeitung
kennzeichnet. Von oben sieht man ihre Oberkante. -->
<path class="techdeck" d="M112 7
C150 7, 197 17, 203 49 L194 53 C187 27, 148 18, 112 18
C76 18, 37 27, 30 53 L21 49 C27 17, 74 7, 112 7 Z"/>
<!-- Scheinwerfer, vier Elemente je Seite wie beim Original,
dazu die Rückleuchten als durchgehendes Band. -->
<g id="teil-lights" class="teil teil-licht" data-teil="lights">
<rect x="36" y="30" width="9" height="13" rx="4"/>
<rect x="48" y="27" width="9" height="13" rx="4"/>
<rect x="60" y="25" width="9" height="13" rx="4"/>
<rect x="72" y="24" width="9" height="13" rx="4"/>
<rect x="143" y="24" width="9" height="13" rx="4"/>
<rect x="155" y="25" width="9" height="13" rx="4"/>
<rect x="167" y="27" width="9" height="13" rx="4"/>
<rect x="179" y="30" width="9" height="13" rx="4"/>
<rect class="rueck" x="40" y="462" width="52" height="13" rx="6"/>
<rect class="rueck" x="132" y="462" width="52" height="13" rx="6"/>
<title>Beleuchtung</title>
</g>
<!-- Motorhaube mit angedeuteter Mittelsicke -->
<g id="teil-bonnet" class="teil" tabindex="0" data-teil="bonnet">
<path d="M31 152 L193 152 L189 62 C169 40, 55 40, 35 62 Z"/>
<title>Motorhaube</title>
</g>
<path class="sicke" d="M112 60 L112 150"/>
<!-- Windschutzscheibe, Dach, Heckscheibe -->
<path class="scheibe" d="M34 156 L190 156 L178 200 L46 200 Z"/>
<rect class="dach" x="46" y="200" width="132" height="136" rx="10"/>
<!-- Panoramaglasdach: beim Enyaq nahezu die ganze Dachfläche -->
<g id="teil-sunroof" class="teil" tabindex="0" data-teil="sunroof">
<rect x="62" y="206" width="100" height="120" rx="8"/>
<title>Panoramadach</title>
</g>
<!-- Dachreling -->
<g class="reling">
<rect x="48" y="204" width="9" height="134" rx="4"/>
<rect x="167" y="204" width="9" height="134" rx="4"/>
</g>
<path class="scheibe" d="M46 336 L178 336 L190 382 L34 382 Z"/>
<rect class="spoiler" x="52" y="330" width="120" height="9" rx="4"/>
<!-- Heckklappe -->
<g id="teil-trunk" class="teil" tabindex="0" data-teil="trunk">
<path d="M34 384 L190 384 L186 458 C168 478, 56 478, 38 458 Z"/>
<title>Heckklappe</title>
</g>
<!-- Türen, jeweils mit Seitenfenster darin -->
<g id="teil-doors" class="teil" data-teil="doors">
<g tabindex="0"><rect x="12" y="202" width="23" height="74" rx="6"/><title>Tür vorn links</title></g>
<g tabindex="0"><rect x="12" y="278" width="23" height="66" rx="6"/><title>Tür hinten links</title></g>
<g tabindex="0"><rect x="189" y="202" width="23" height="74" rx="6"/><title>Tür vorn rechts</title></g>
<g tabindex="0"><rect x="189" y="278" width="23" height="66" rx="6"/><title>Tür hinten rechts</title></g>
</g>
<g id="teil-windows" class="teil teil-fenster" data-teil="windows">
<rect x="17" y="210" width="13" height="58" rx="4"/>
<rect x="17" y="286" width="13" height="50" rx="4"/>
<rect x="194" y="210" width="13" height="58" rx="4"/>
<rect x="194" y="286" width="13" height="50" rx="4"/>
<title>Seitenfenster</title>
</g>
<!-- Außenspiegel -->
<g class="spiegel">
<rect x="1" y="194" width="16" height="10" rx="5"/>
<rect x="207" y="194" width="16" height="10" rx="5"/>
</g>
<!-- ============================================================
Steuerung: die beiden anklickbaren Stellen.
Bedient wird das, worum es geht - der Ladeanschluss für
das Laden, der Innenraum für Klima und Lüftung. Beide
tragen einen Ring als Hinweis darauf, dass sie mehr sind
als Anzeige, und öffnen ein Modal mit den Befehlen.
Sie stehen bewusst NICHT in der Zustandszeile darunter:
die zeigt, was ist. Diese beiden ändern etwas.
============================================================ -->
<!-- Ladeanschluss. Beim Enyaq sitzt die Klappe am hinteren
rechten Kotflügel; sie pulst, solange geladen wird. -->
<g id="teil-plug" class="teil teil-stecker steuer" tabindex="0"
role="button" data-steuer="laden" aria-label="Laden steuern">
<circle class="ring" cx="203" cy="408" r="17"/>
<circle cx="203" cy="408" r="11"/>
<path d="M198 402 l6 5 -4 0 4 6"/>
<title>Laden steuern</title>
</g>
<!-- Innenraum, auf der Windschutzscheibe: Klima und Lüftung. -->
<g id="steuer-klima" class="steuer" tabindex="0"
role="button" data-steuer="klima" aria-label="Klima und Lüftung steuern">
<circle class="ring" cx="112" cy="178" r="19"/>
<circle class="flaeche" cx="112" cy="178" r="13"/>
<path class="glyphe" d="M112 170 v10 M108 174 h8 M105 185 q7 -4 14 0"/>
<title>Klima und Lüftung steuern</title>
</g>
</svg>
<div class="skoda-teile" id="skodaTeile"></div>
<p class="skoda-hinweis">
Ladeanschluss und Innenraum antippen, um zu steuern
</p>
</div>
</div>
</div>
<!-- Batterie, Reichweite, Ladung -->
<div class="col-xxl-8 col-lg-7 d-flex align-items-stretch">
<div class="card mb-4 p-0 w-100">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Batterie und Ladung</h3>
<div class="card-tools">
<span class="badge text-bg-secondary" id="skodaLadeZustand"></span>
</div>
</div>
<div class="card-body">
<div class="row align-items-center g-3">
<!-- Ladestandsring. Der Strich auf dem Ring ist das
eingestellte Ladeziel - eine Ladung, die dort endet, ist
kein Fehler, sondern Absicht. -->
<div class="col-md-5 text-center">
<svg class="skoda-ring" viewBox="0 0 200 200" role="img" aria-labelledby="skodaSocText">
<circle class="ring-bahn" cx="100" cy="100" r="82"/>
<circle class="ring-wert" id="skodaRing" cx="100" cy="100" r="82"/>
<line class="ring-ziel" id="skodaZiel" x1="100" y1="8" x2="100" y2="26"/>
<text id="skodaSocText" class="ring-zahl" x="100" y="98"></text>
<text class="ring-einheit" x="100" y="126">Prozent</text>
</svg>
</div>
<div class="col-md-7">
<div class="skoda-werte">
<div class="skoda-wert"><span class="k">Reichweite</span><span class="v" id="skodaReichweite"></span></div>
<div class="skoda-wert"><span class="k">Ladeleistung</span><span class="v" id="skodaLeistung"></span></div>
<div class="skoda-wert"><span class="k">Restzeit</span><span class="v" id="skodaRestzeit"></span></div>
<div class="skoda-wert"><span class="k">Ladeziel</span><span class="v" id="skodaZielText"></span></div>
<div class="skoda-wert"><span class="k">Kilometerstand</span><span class="v" id="skodaKm"></span></div>
<div class="skoda-wert"><span class="k">Klima</span><span class="v" id="skodaKlima"></span></div>
</div>
<!-- Woher der Strom gerade kommt: der Balken teilt die
Ladeleistung in den Anteil, den die PV deckt, und den
Rest aus dem Netz. -->
<div class="skoda-quelle mt-3" id="skodaQuelleBox">
<div class="d-flex justify-content-between small text-secondary mb-1">
<span>Ladestrom</span><span id="skodaQuelleText"></span>
</div>
<div class="skoda-balken">
<div class="teil-pv" id="skodaAnteilPV"></div>
<div class="teil-netz" id="skodaAnteilNetz"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ================================================= Ladungen ==== -->
<div class="row">
<div class="col-xl-8 d-flex align-items-stretch">
<div class="card mb-4 p-0 w-100">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Verlauf der letzten Ladung</h3>
<div class="card-tools">
<span class="text-secondary small" id="skodaKurveZeit"></span>
</div>
</div>
<div class="card-body pb-1">
<div class="chart-container"><canvas id="skodaKurve"></canvas></div>
</div>
</div>
</div>
<div class="col-xl-4 d-flex align-items-stretch">
<div class="card mb-4 p-0 w-100">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Ladungen</h3>
</div>
<div class="card-body p-0">
<div class="skoda-liste" id="skodaLadungen"></div>
</div>
</div>
</div>
</div>
<!-- ============================================== Akkugesundheit == -->
<div class="row">
<div class="col-xl-8 d-flex align-items-stretch">
<div class="card mb-4 p-0 w-100">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Akkugesundheit</h3>
<div class="card-tools">
<span class="text-secondary small">kWh je 100 Ladestandsprozent</span>
</div>
</div>
<div class="card-body pb-1">
<div class="chart-container"><canvas id="skodaGesundheit"></canvas></div>
</div>
</div>
</div>
<div class="col-xl-4 d-flex align-items-stretch">
<div class="card mb-4 p-0 w-100">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Befund</h3>
</div>
<div class="card-body">
<div class="skoda-befund" id="skodaBefund">
<p class="text-secondary mb-0">Noch keine ausgewerteten Ladungen.</p>
</div>
<p class="text-secondary small mb-0 mt-3">
Gemessen wird am Wallbox-Zähler, die Ladeverluste sind also
enthalten. Absolut ist der Wert eine Näherung — aussagekräftig
ist sein Verlauf, denn ein alternder Akku bräuchte <em>weniger</em>
Kilowattstunden für dieselben hundert Prozentpunkte.
</p>
</div>
</div>
</div>
</div>
<!-- ============================================ Standort ========== -->
<!-- Karte auf Leaflet. Die Kacheln kommen nicht unmittelbar von
OpenStreetMap, sondern über ajax/tile.php aus dem eigenen
Zwischenspeicher auf dem NAS - siehe dort, warum.
Die Fahrtlinien sind gestrichelt, und zwar mit Absicht: die API
liefert nur die letzte bekannte PARKposition, also Start und Ziel.
Die tatsächlich gefahrene Strecke kennt niemand, und eine
durchgezogene Linie auf einer Straßenkarte würde genau das
behaupten. -->
<div class="row">
<div class="col-xl-7 d-flex align-items-stretch">
<div class="card mb-4 p-0 w-100">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Standort und Fahrten</h3>
<div class="card-tools">
<button type="button" id="skodaKarteHeim" class="btn btn-tool"
title="Auf den aktuellen Standort zentrieren">
<i class="bi bi-geo-alt"></i>
</button>
<a href="#" id="skodaOsmLink" class="btn btn-tool" target="_blank"
rel="noopener noreferrer" title="Auf OpenStreetMap öffnen">
<i class="bi bi-box-arrow-up-right"></i>
</a>
</div>
</div>
<div class="card-body p-2">
<div id="skodaKarte" class="skoda-karte"></div>
<div class="d-flex justify-content-between align-items-center px-2 pt-2 pb-1">
<span class="text-secondary small" id="skodaStandort"></span>
<span class="skoda-kartenlegende">
<span><i class="pkt jetzt"></i>jetzt</span>
<span><i class="pkt halt"></i>Halt</span>
<span><i class="lin"></i>Luftlinie</span>
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-5 d-flex align-items-stretch">
<!-- Die Klasse zeigt-kwh / zeigt-prozent auf dieser Kachel
entscheidet, welche Einheit die Zeilen anzeigen. Beide Werte
stehen im Markup, umgeschaltet wird allein per CSS - so muss
beim Wechsel nichts neu gebaut werden, und die Auswahl
übersteht auch die minütliche Auffrischung der Liste. -->
<div class="card mb-4 p-0 w-100 zeigt-kwh" id="skodaFahrtenKachel">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Fahrten</h3>
<div class="card-tools d-flex align-items-center gap-2">
<!-- Text kommt aus zeigeVerbrauchsbasis(): der Verbrauch in
Kilowattstunden ist nicht gemessen, sondern aus dem
Ladestand und der eigenen Ladehistorie gerechnet. Womit
genau, steht im Titel dieses Elements. -->
<span class="text-secondary small" id="skodaVerbrauchBasis"></span>
<span class="skoda-umschalter" id="skodaEinheit" role="group"
aria-label="Einheit des Verbrauchs">
<button type="button" data-einheit="kwh" aria-pressed="true">kWh</button>
<button type="button" data-einheit="prozent" aria-pressed="false">%</button>
</span>
</div>
</div>
<div class="card-body p-0">
<div class="skoda-liste" id="skodaFahrten"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!--end::App Main-->