Geraete hingen ueber den Text "Etage/Raum" an ihrem Raum, das Raum-Modal zerlegte "EG_Bad" am Unterstrich, und das Thermostat-Topic wurde aus Etage und Name zusammengesetzt. Ein umbenannter Raum haette still alle Geraete, seinen Temperaturregler und sein Heizsymbol verloren. - actors.room (Text) -> actors.room_id, Fremdschluessel auf rooms, beim Loeschen eines Raums SET NULL. Alle 59 Zuordnungen uebernommen. - automations.floor: ENUM(UG,EG,OG,AG) -> Verweis auf floors. Eine neue Etage kann damit auch Automatiken tragen. - room.php nimmt ?room=<Nummer>; Solltemperatur geht an rooms.thermostat + /changeSetTemp. - homeMQTT.js: Heizsymbol, Regler und Ist-Werte ueber mqttZweig(thermostat) statt mqttData.Raumtemp[Etage][Name]. - Raumvorschlaege in den Einstellungen: Messwerte unter dem Thermostat- Zweig eines Raums statt fest Raumtemp/<Etage>/<Name>. - Schluessel in raumListe(), Katalog und Maske ist die Raumnummer als Text. Geprueft gegen den Stand davor: Startseite gleich bis auf das Klickziel, jede der 25 Kacheln oeffnet ihren eigenen Raum; Geraeteliste, Vorschlaege, Editor-Katalog und die eigenen Geraete je Kachel gleich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
259 lines
13 KiB
PHP
259 lines
13 KiB
PHP
<?php
|
|
require_once(__DIR__ . "/rooms.php");
|
|
require_once(__DIR__ . "/reiter.php");
|
|
?>
|
|
<!--begin::App Main-->
|
|
<main class="app-main">
|
|
<!--begin::App Content Header-->
|
|
<div class="app-content-header">
|
|
<!--begin::Container-->
|
|
<div class="container-fluid">
|
|
<!--begin::Row-->
|
|
<div class="row">
|
|
<div class="col-sm-12">
|
|
<h3 class="mb-0">Home</h3>
|
|
</div>
|
|
</div>
|
|
<!--end::Row-->
|
|
</div>
|
|
<!--end::Container-->
|
|
</div>
|
|
<!--end::App Content Header-->
|
|
<!--begin::App Content-->
|
|
<div class="app-content">
|
|
<!--begin::Container-->
|
|
<div class="container-fluid">
|
|
<!--begin::Row-->
|
|
<div class="row">
|
|
<!-- Start col -->
|
|
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
|
<div class="card p-0 mb-4" style="width:100%">
|
|
<div class="card-header pb-0 pt-1">
|
|
<h3 class="card-title">Home</h3>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
|
<i class="bi bi-x-lg"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body pb-1">
|
|
<div lass="img-overlay-wrap">
|
|
<svg viewBox="0 0 400 300" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
|
|
|
<defs>
|
|
<g id="heater" filter="url(#shadow)" transform="translate(1.5,1),scale(0.2,0.2)" fill="#993311" stroke="#FF2211">
|
|
<path clip-path="M-22 2.24h42V22h-42z"
|
|
d="M16.543 8.028c-.023 1.503-.523 3.538-2.867 4.327.734-1.746.846-3.417.326-4.979-.695-2.097-3.014-3.735-4.557-4.627-.527-.306-1.203.074-1.193.683.02 1.112-.318 2.737-1.959 4.378C4.107 9.994 3 12.251 3 14.517 3 17.362 5 21 9 21c-4.041-4.041-1-7.483-1-7.483C8.846 19.431 12.988 21 15 21c1.711 0 5-1.25 5-6.448 0-3.133-1.332-5.511-2.385-6.899-.347-.458-1.064-.198-1.072.375" />
|
|
</g>
|
|
<g id="buffer" filter="url(#shadow)" transform="translate(18,1),scale(0.3,0.3)" fill="#ffffff">
|
|
<path d="M5 12.5a1.5 1.5 0 1 1-2-1.415V2.5a.5.5 0 0 1 1 0v8.585A1.5 1.5 0 0 1 5 12.5"/>
|
|
<path d="M1 2.5a2.5 2.5 0 0 1 5 0v7.55a3.5 3.5 0 1 1-5 0zM3.5 1A1.5 1.5 0 0 0 2 2.5v7.987l-.167.15a2.5 2.5 0 1 0 3.333 0L5 10.486V2.5A1.5 1.5 0 0 0 3.5 1m5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5m4.243 1.757a.5.5 0 0 1 0 .707l-.707.708a.5.5 0 1 1-.708-.708l.708-.707a.5.5 0 0 1 .707 0M8 5.5a.5.5 0 0 1 .5-.5 3 3 0 1 1 0 6 .5.5 0 0 1 0-1 2 2 0 0 0 0-4 .5.5 0 0 1-.5-.5M12.5 8a.5.5 0 0 1 .5-.5h1a.5.5 0 1 1 0 1h-1a.5.5 0 0 1-.5-.5m-1.172 2.828a.5.5 0 0 1 .708 0l.707.708a.5.5 0 0 1-.707.707l-.708-.707a.5.5 0 0 1 0-.708M8.5 12a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5"/>
|
|
</g>
|
|
</defs>
|
|
<g>
|
|
<?php
|
|
/*
|
|
* Ein Bild je Etage, dazu fuer jede Etage eine Blende.
|
|
* Ausgeschrieben waren das bei drei Etagen neun Zeilen
|
|
* und bei vier schon sechzehn - und jede neue Etage
|
|
* haette alle anderen mit angefasst.
|
|
*
|
|
* floorsWithPlan() liefert nur Etagen, deren Raeume
|
|
* Koordinaten haben. Eine Etage ohne angeklickte
|
|
* Kachelpositionen taucht hier also gar nicht erst auf.
|
|
*/
|
|
$etagen = floorsWithPlan();
|
|
?>
|
|
<?php /* Von unten nach oben gestapelt, wie bisher: waehrend der Blende
|
|
sind zwei Bilder gleichzeitig halb sichtbar, und dann soll das
|
|
obere Geschoss obenauf liegen. */ ?>
|
|
<?php foreach (array_reverse($etagen) as $etage): ?>
|
|
<g id="<?= $etage ?>img" <?php if ($_GET["floor"] != $etage) echo "opacity='0'"; ?> >
|
|
<?php foreach ($etagen as $andere): ?>
|
|
<animate begin="<?= $andere ?>.click" attributetype="CSS" attributeName="opacity"
|
|
to=<?= $andere === $etage ? "1.0" : "0.0" ?> dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<?php endforeach; ?>
|
|
<image href="<?= htmlspecialchars((string)etageBild($etage)) ?>" height="100%" width="100%" x=0 y=0 />
|
|
</g>
|
|
<?php endforeach; ?>
|
|
<?php
|
|
/*
|
|
* Groesse der Etagenschalter. Der Kreis ist im eigenen
|
|
* Koordinatensystem 88 Einheiten breit und reicht von 28
|
|
* bis 116 unter die Oberkante seiner Gruppe; alles andere
|
|
* folgt daraus. Sie waren einmal doppelt so gross und
|
|
* beherrschten damit die linke Seite - und das Bild des
|
|
* Aussengelaendes musste umso weiter einruecken, um sie
|
|
* freizuhalten.
|
|
*/
|
|
$mass = 0.38;
|
|
$kreisUnten = 116 * $mass;
|
|
|
|
/*
|
|
* Bei drei Etagen bleibt es bei den gewohnten 100
|
|
* Einheiten Abstand; ab der vierten ruecken sie zusammen,
|
|
* sonst faellt die unterste aus der 300 hohen viewBox.
|
|
*/
|
|
$abstand = count($etagen) > 1
|
|
? min(100, (295 - $kreisUnten) / (count($etagen) - 1))
|
|
: 0;
|
|
?>
|
|
<?php foreach ($etagen as $i => $etage): ?>
|
|
<g id="<?= $etage ?>" class="floorBtn<?= $_GET["floor"] == $etage ? " active" : "" ?>"
|
|
onclick="switchFloor('<?= $etage ?>')"
|
|
transform="translate(0,<?= round($i * $abstand, 1) ?>), scale(<?= $mass ?>)">
|
|
<!-- Die Beschriftung sitzt ueber text-anchor und
|
|
dominant-baseline im Kreismittelpunkt. Vorher stand
|
|
je Etage ein von Hand gesuchter Versatz im Markup
|
|
(62, 63, 64), und eine neue Etage brauchte einen
|
|
neuen. Aussehen und Zustand stehen in css/solar.css. -->
|
|
<circle cx="50" cy="72" r="44" />
|
|
<text x="50" y="72" text-anchor="middle"
|
|
dominant-baseline="central"><?= $etage ?></text>
|
|
</g>
|
|
<?php endforeach; ?>
|
|
<?php foreach (floorsWithPlan() as $floor): ?>
|
|
<g id="<?= $floor ?>_Info" <?php if ($floor != standardEtage()) echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
|
|
<?php foreach (array_map("mitWerten", roomsOnFloor($floor)) as $room): ?>
|
|
<?php
|
|
/*
|
|
* Grundlinien je nach Anzahl der Werte. Die Kachel ist 19 hoch; mit
|
|
* drei Werten sind es dieselben Zeilen wie eh und je (klein, gross,
|
|
* klein), mit weniger ruecken sie in die Mitte.
|
|
*/
|
|
$zeilen = [1 => [12], 2 => [9, 16], 3 => [5, 11.5, 16.5]];
|
|
$y = $zeilen[count($room["werte"])] ?? [];
|
|
?>
|
|
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)" onclick="openRoomView(<?= $room["nr"] ?>);" cursor="pointer">
|
|
<rect width="24" height="19" rx="5" fill="#0006" />
|
|
<?php if ($room["heizung"]): ?>
|
|
<use id="<?= $floor ?>_<?= $room["id"] ?>_heater" href="#heater" display="none" />
|
|
<use id="<?= $floor ?>_<?= $room["id"] ?>_buffer" href="#buffer" display="none" />
|
|
<?php endif; ?>
|
|
<text fill="#fff" style="font: 4px sans-serif;">
|
|
<?php if (!$room["werte"]): ?>
|
|
<?php
|
|
/*
|
|
* Kein Messwert: die Kachel traegt ihren Namen und oeffnet den Raum.
|
|
* Ein leeres Rechteck saehe aus wie ein Fehler.
|
|
*
|
|
* Lange Namen brechen am letzten Leerzeichen um. Die Kachel ist 24
|
|
* Einheiten breit, und "Terrasse OG" stand einzeilig links und rechts
|
|
* darueber hinaus. Aufgefallen ist das erst mit dem Aussengelaende:
|
|
* dort besteht jeder Raumname aus zwei Woertern.
|
|
*/
|
|
$luecke = strrpos($room["mqtt"], " ");
|
|
$teile = (strlen($room["mqtt"]) > 8 && $luecke !== false)
|
|
? [substr($room["mqtt"], 0, $luecke), substr($room["mqtt"], $luecke + 1)]
|
|
: [$room["mqtt"]];
|
|
$namenszeilen = count($teile) === 2 ? [9, 15.5] : [11.5];
|
|
?>
|
|
<?php foreach ($teile as $i => $teil): ?>
|
|
<tspan x="12" y="<?= $namenszeilen[$i] ?>" font-size="1.2em" text-anchor="middle"><?= htmlspecialchars($teil) ?></tspan>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
<?php foreach ($room["werte"] as $i => $wert): ?>
|
|
<?php $zeichen = kachelIcon($wert["icon"] ?? ""); ?>
|
|
<?php if ($zeichen !== ""): ?>
|
|
<!-- Ein Symbol vor der Zahl. "0 W" allein sagt nicht,
|
|
ob geladen, erzeugt oder verbraucht wird; die
|
|
Einheit beantwortet das nicht. Die Zahl rueckt
|
|
dafuer nach rechts, damit Symbol und Wert
|
|
zusammen mittig stehen. -->
|
|
<tspan x="4" y="<?= $y[$i] ?>" text-anchor="middle" opacity=".8"
|
|
style="font-family:bootstrap-icons"><?= $zeichen ?></tspan>
|
|
<?php endif; ?>
|
|
<!-- Der Platzhalter ist nur ein Strich: bevor der
|
|
erste Wert da ist, soll hier keine Einheit
|
|
stehen, die vielleicht gar nicht passt. -->
|
|
<tspan id="<?= $floor ?>_w<?= $i ?>_<?= $room["id"] ?>"
|
|
x="<?= $zeichen !== "" ? 15 : 12 ?>" y="<?= $y[$i] ?>"
|
|
<?= empty($wert["gross"]) ? "" : 'font-size="1.6em"' ?> text-anchor="middle">–</tspan>
|
|
<?php endforeach; ?>
|
|
</text>
|
|
</g>
|
|
<?php endforeach; ?>
|
|
</g>
|
|
<?php endforeach; ?>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- /.card -->
|
|
</div>
|
|
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
|
<div class="card p-0 mb-4" style="width:100%">
|
|
<div class="card-header pb-0 pt-1">
|
|
<h3 class="card-title">Automatismen</h3>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
|
<i class="bi bi-x-lg"></i>
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="card-body pb-1">
|
|
<?php
|
|
/*
|
|
* Eine Etage, ein Reiter. Gezeichnet von reiterLeiste() aus
|
|
* restricted/reiter.php - dieselbe Bauart wie im Raum-Modal und
|
|
* auf der Einstellungsseite. Frueher stand hier ein eigenes
|
|
* Markup samt eigenem switchTab() in homeMQTT.js; umgeschaltet
|
|
* wird jetzt von Bootstrap wie ueberall sonst.
|
|
*
|
|
* Der Name "actions" haelt die Element-Ids, an denen
|
|
* autoActionFuncs.js haengt: actions-OG, actions-OG-list.
|
|
*/
|
|
$autoReiter = array_map(function ($etage) {
|
|
return ["id" => $etage, "titel" => $etage,
|
|
"titel2" => floorLabel($etage)];
|
|
}, $floors);
|
|
$autoOpt = ["name" => "actions", "aktiv" => $_GET["floor"],
|
|
"klasse" => "mb-2"];
|
|
$autoOpt["aktiv"] = reiterAktiv($autoReiter, $autoOpt);
|
|
?>
|
|
<?= reiterLeiste($autoReiter, $autoOpt) ?>
|
|
<div class="tab-content" id="actions-tabContent">
|
|
<?php foreach ($floors as $etage): ?>
|
|
<?= reiterFlaecheAuf($etage, $autoOpt) ?>
|
|
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
|
|
refreshAutomations() in js/solar/autoActionFuncs.js -->
|
|
<div id="actions-<?= $etage ?>-list">Wird geladen...</div>
|
|
<button type="button" class="btn btn-success" title="Neue Automatik"
|
|
onclick="openAutoActionModal('?action=editor&floor=<?= $etage ?>')"><i class="bi bi-plus-square"></i></button>
|
|
<?= reiterFlaecheZu() ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- /.Start col -->
|
|
</div>
|
|
|
|
<!-- /.row (main row) -->
|
|
</div>
|
|
<!--end::Container-->
|
|
</div>
|
|
<!--end::App Content-->
|
|
</main>
|
|
<!--end::App Main-->
|
|
|
|
<script>
|
|
// Raumtabelle aus restricted/rooms.php - homeMQTT.js aktualisiert darueber
|
|
// die SVG-Kacheln, ohne die Raeume noch einmal aufzufuehren. In "werte"
|
|
// steht, welche Messwerte eine Kachel zeigt; homeTopics sind die Zweige,
|
|
// die der Browser dafuer beim Broker anmelden muss.
|
|
const homeRooms = <?= json_encode(roomsWithTile(), JSON_UNESCAPED_UNICODE) ?>;
|
|
const homeTopics = <?= json_encode(tileTopics(), JSON_UNESCAPED_UNICODE) ?>;
|
|
const homeStandardEtage = <?= json_encode(standardEtage()) ?>;
|
|
<?php if ($_GET["floor"] != standardEtage() && in_array($_GET["floor"], floorsWithPlan())): ?>
|
|
document.addEventListener('readystatechange', function () {
|
|
if (event.target.readyState === 'complete') {
|
|
switchFloor('<?= $_GET["floor"] ?>');
|
|
}
|
|
});
|
|
<?php endif; ?>
|
|
</script>
|