Files
Smart-Dashboard/tools/kachelpositionen.php
T
adminandClaude Opus 5 3636a134d6 Grundriss aus der Datenbank: Etagen und Raeume in homeMesh
Etagen, Raeume, Kachelpositionen und Kachelwerte standen als Tabelle in
restricted/rooms.php, die Werte zusaetzlich ueberschreibbar in
solarLog.kachelWerte. Fuer ein anderes Haus haette man den Quelltext
umschreiben muessen.

Jetzt liegen sie in homeMesh.floors und homeMesh.rooms
(homeMesh_grundriss.sql). rooms.php liest sie und gibt sie in der
bisherigen Form weiter; Startseite, Menue, Editor und Einstellungen sind
unveraendert - vorher und nachher als HTML verglichen, gleich bis auf
Livewerte und die zwei neuen Felder nr und thermostat in homeRooms.

- Vorgabe einer Kachel ist jetzt allein das Thermostat (rooms.thermostat):
  Soll, Ist, Feuchte - sonst nichts. Was bisher im Code als Vorgabe stand,
  gilt damit als selbst gesetzt.
- Grundrissbild je Etage (floors.bild) statt assets/img/<code>.png.
- Standard-Etage (floors.standard) statt "OG" an vier Stellen.
- meshDb() in eigener Datei, sonst Ring zwischen rooms.php und
  automations.php.
- solarLog_kacheln.sql entfaellt; die Tabelle kachelWerte liest niemand mehr.

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

215 lines
8.1 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
/**
* Messblatt für die Kachelpositionen im Grundriss.
*
* Die Kacheln der Home-Ansicht stehen in restricted/rooms.php als x/y in
* einer 400 × 300 großen Zeichenfläche. Wo eine neue hingehört, lässt sich
* an einem Bild nur schätzen - und der Bezugspunkt ist auch noch die linke
* obere Ecke, nicht die Mitte, die man eigentlich im Auge hat.
*
* Diese Seite nimmt beides ab: sie zeigt die Grundrisse mit Raster und den
* vorhandenen Kacheln in Originalgröße, hängt eine Geisterkachel an den
* Zeiger und schreibt darunter die Zeile, die in die Tabelle gehört.
*
* Aufruf: http://<host>/smart/tools/kachelpositionen.php
*
* Bewusst eine eigene Seite und kein Eintrag im Menü: sie wird gebraucht,
* wenn ein Raum dazukommt, und das ist selten.
*/
require_once(__DIR__ . "/../helper.php");
require_once(__DIR__ . "/../restricted/rooms.php");
if (!checkLogin()) {
http_response_code(403);
exit;
}
/** So groß ist eine Kachel im SVG - siehe restricted/home.php. */
const KACHEL_BREIT = 24;
const KACHEL_HOCH = 19;
/** So groß ist die Zeichenfläche. */
const PLAN_BREIT = 400;
const PLAN_HOCH = 300;
?><!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kachelpositionen</title>
<style>
:root {
--grund: #fbfbfa; --schrift: #1f2328; --leise: #6b7280;
--linie: #d8d8d4; --karte: #ffffff; --akzent: #00788f;
}
@media (prefers-color-scheme: dark) {
:root {
--grund: #16181c; --schrift: #e8eaed; --leise: #9aa0a6;
--linie: #2f333a; --karte: #1e2126; --akzent: #35b6cf;
}
}
body {
margin: 0; padding: 1.5rem 1rem 3rem;
background: var(--grund); color: var(--schrift);
font: 15px/1.55 -apple-system, "Segoe UI", Roboto, sans-serif;
}
.huelle { max-width: 900px; margin: 0 auto; }
h1 { font-size: 1.35rem; margin: 0 0 .3rem; }
.vorwort { color: var(--leise); margin: 0 0 1.5rem; max-width: 62ch; }
.vorwort code {
background: var(--karte); border: 1px solid var(--linie);
border-radius: .25rem; padding: .05rem .3rem; font-size: .88em;
}
.etage {
background: var(--karte); border: 1px solid var(--linie);
border-radius: .6rem; padding: .8rem; margin-bottom: 1.2rem;
}
h2 { font-size: .95rem; margin: 0 0 .5rem; font-weight: 600; }
h2 span { font-weight: 400; color: var(--leise); }
.plan {
display: block; width: 100%; height: auto; border-radius: .4rem;
cursor: crosshair; touch-action: none; background: #0b0d10;
}
.kachel { fill: rgba(0, 0, 0, .4); stroke: var(--akzent); stroke-width: .7; }
.anker { fill: var(--akzent); }
.kachelname { fill: #fff; font: 4px sans-serif; text-anchor: middle; opacity: .85; }
.achsen text { fill: #ffffffaa; font: 7px sans-serif; }
.geist { fill: rgba(255, 190, 60, .28); stroke: #ffbe3c; stroke-width: .8; }
.kreuz { stroke: #ffbe3c; stroke-width: .4; stroke-dasharray: 3 3; }
.ablesung {
margin: .6rem 0 0; font-family: ui-monospace, Consolas, monospace;
font-size: .88rem; color: var(--leise); min-height: 1.5em;
}
.ablesung b { color: var(--schrift); }
.ablesung .fest { color: var(--akzent); }
</style>
</head>
<body>
<div class="huelle">
<h1>Kachelpositionen im Grundriss</h1>
<p class="vorwort">
Die Zeichenfläche ist <?= PLAN_BREIT ?> &times; <?= PLAN_HOCH ?>. Eine Kachel ist
<?= KACHEL_BREIT ?> &times; <?= KACHEL_HOCH ?> groß, und <code>x</code>/<code>y</code>
in <code>restricted/rooms.php</code> ist ihre <b>linke obere Ecke</b> &ndash; die
Punkte an den vorhandenen Kacheln zeigen, wo dieser Bezugspunkt sitzt.
Zeig auf die Stelle, an der die <b>Mitte</b> der neuen Kachel liegen soll; das gelbe
Rechteck zeigt sie in Originalgröße, darunter stehen die Werte für die Tabelle.
Ein Klick friert die Ablesung ein.
</p>
<?php foreach ($floors as $floor): ?>
<?php
$mitKachel = roomsOnFloor($floor);
$ohneKachel = array_values(array_filter(allRooms(), function ($r) use ($floor) {
return $r["floor"] === $floor && !isset($r["x"]);
}));
?>
<section class="etage">
<h2><?= htmlspecialchars($floor) ?><?php if ($ohneKachel): ?>
<span>&middot; noch ohne Kachel:
<?= htmlspecialchars(implode(", ", array_column($ohneKachel, "mqtt"))) ?></span>
<?php endif; ?></h2>
<svg viewBox="0 0 <?= PLAN_BREIT ?> <?= PLAN_HOCH ?>" class="plan">
<defs>
<!-- Eigenes Raster je Etage: eine Kennung darf im Dokument nur
einmal vorkommen, sonst zeigt url(#...) immer auf dasselbe. -->
<pattern id="raster<?= $floor ?>" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M10 0H0V10" fill="none" stroke="#ffffff22" stroke-width=".4"/>
</pattern>
</defs>
<image href="../<?= htmlspecialchars((string)etageBild($floor)) ?>" x="0" y="0"
width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"/>
<rect x="0" y="0" width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"
fill="url(#raster<?= $floor ?>)"/>
<g class="achsen">
<?php for ($v = 50; $v < PLAN_BREIT; $v += 50): ?>
<text x="<?= $v ?>" y="9" text-anchor="middle"><?= $v ?></text>
<?php endfor; ?>
<?php for ($v = 50; $v < PLAN_HOCH; $v += 50): ?>
<text x="4" y="<?= $v + 3 ?>"><?= $v ?></text>
<?php endfor; ?>
</g>
<?php foreach ($mitKachel as $room): ?>
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)">
<rect class="kachel" width="<?= KACHEL_BREIT ?>" height="<?= KACHEL_HOCH ?>" rx="5"/>
<circle class="anker" r="1.6"/>
<text class="kachelname" x="<?= KACHEL_BREIT / 2 ?>" y="11">
<?= htmlspecialchars(mb_substr($room["mqtt"], 0, 8)) ?></text>
</g>
<?php endforeach; ?>
<g class="zeiger" style="display:none">
<rect class="geist" width="<?= KACHEL_BREIT ?>" height="<?= KACHEL_HOCH ?>" rx="5"/>
<line class="kreuz" x1="<?= -PLAN_BREIT ?>" x2="<?= PLAN_BREIT ?>"
y1="<?= KACHEL_HOCH / 2 ?>" y2="<?= KACHEL_HOCH / 2 ?>"/>
<line class="kreuz" y1="<?= -PLAN_HOCH ?>" y2="<?= PLAN_HOCH ?>"
x1="<?= KACHEL_BREIT / 2 ?>" x2="<?= KACHEL_BREIT / 2 ?>"/>
</g>
</svg>
<p class="ablesung">Zeiger über den Grundriss bewegen …</p>
</section>
<?php endforeach; ?>
</div>
<script>
// Muss zu den Konstanten oben passen - der Server schreibt sie hier hinein.
const BREIT = <?= KACHEL_BREIT ?>, HOCH = <?= KACHEL_HOCH ?>;
const PLAN_B = <?= PLAN_BREIT ?>, PLAN_H = <?= PLAN_HOCH ?>;
document.querySelectorAll(".etage").forEach(function (etage) {
const plan = etage.querySelector(".plan");
const zeiger = etage.querySelector(".zeiger");
const text = etage.querySelector(".ablesung");
let fest = false;
/**
* Vom Zeiger zur Kachel: gezeigt wird auf die Mitte, gebraucht wird die
* linke obere Ecke. Die Umrechnung gehoert hierher und nicht in den Kopf
* dessen, der misst.
*/
function stelle(ereignis) {
const r = plan.getBoundingClientRect();
const mx = (ereignis.clientX - r.left) / r.width * PLAN_B;
const my = (ereignis.clientY - r.top) / r.height * PLAN_H;
const x = Math.round(mx - BREIT / 2);
const y = Math.round(my - HOCH / 2);
zeiger.style.display = "";
zeiger.setAttribute("transform", "translate(" + x + "," + y + ")");
return { x: x, y: y, mx: Math.round(mx), my: Math.round(my) };
}
function zeile(p) {
return 'Mitte ' + p.mx + ',' + p.my + ' &nbsp;&rarr;&nbsp; <b>"x" =&gt; ' +
p.x + ', "y" =&gt; ' + p.y + '</b>';
}
plan.addEventListener("pointermove", function (e) {
if (fest) return;
text.innerHTML = zeile(stelle(e));
});
plan.addEventListener("pointerleave", function () {
if (fest) return;
zeiger.style.display = "none";
text.textContent = "Zeiger über den Grundriss bewegen …";
});
plan.addEventListener("click", function (e) {
const p = stelle(e);
fest = !fest;
text.innerHTML = fest
? '<span class="fest">festgehalten:</span> "x" =&gt; <b>' + p.x +
'</b>, "y" =&gt; <b>' + p.y + '</b> &nbsp;(Mitte ' + p.mx + ',' + p.my +
') &ndash; nochmal klicken zum Lösen'
: zeile(p);
});
});
</script>
</body>
</html>