Beim Anlegen einer Automatik denkt man in Raeumen ("im Bad soll ..."), nicht
in Geraetearten. Ein Raummodell gibt es schon - restricted/rooms.php, die
einzige Quelle fuer die Home-Ansicht -, nur fehlte die Zuordnung Geraet ->
Raum.
Die neue Spalte actors.room haelt sie als "Etage/Raum" (OG/Bad), dazu
"aussen" fuer Carport, Garage und Regensensor. Etage und Raum zusammen,
weil "Bad" allein auf drei Etagen vorkommt. Discovery fasst die Spalte nicht
an - der Upsert setzt nur type und name -, die Zuordnung ueberlebt also
jeden Suchlauf.
Zugeordnet wird ueber das Haus-Symbol in der Karte "Automatismen". Wo der
Raum aus dem MQTT-Topic hervorgeht ("Raumtemp/OG/Bad/Temp[degC]"), schlaegt
die Ansicht ihn vor und uebernimmt ihn auf Knopfdruck - das sind die
dreizehn Thermostate, ohne jedes Raten. Bei allen anderen wird bewusst nicht
geraten: "Bad Links" sagt nichts darueber, auf welcher der drei Etagen
dieses Bad liegt. Noch nicht zugeordnete Geraete stehen in der Tabelle oben.
Die Geraeteauswahl im Editor gruppiert danach nach Raum, in der Reihenfolge
aus rooms.php. Solange niemand zugeordnet hat, bleibt es bei der Gruppierung
nach Geraeteart - eine einzige Gruppe "Nicht zugeordnet" waere ein
Rueckschritt. Und weil auch nach der ersten Zuordnung noch viel ohne Raum
ist, wird dieser Rest weiter nach Geraeteart unterteilt ("Ohne Raum ·
Aussenjalousie (11)").
Anders als bei den Gerätearten behalten Raeume mit nur einem Geraet ihre
Ueberschrift: "OG · Bad" ist auch mit einem einzigen Thermostat genau die
Zeile, die man sucht - und mehr als eines haengt selten in einem Raum.
Die dreizehn Thermostate sind bereits zugeordnet, weil das ihre eigenen
Topics hergeben; alles andere steht auf "-" und will von Hand vergeben
werden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
211 lines
14 KiB
PHP
211 lines
14 KiB
PHP
<?php require_once(__DIR__ . "/rooms.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="floorBtn">
|
|
<path fill="transparent" stroke-width="4"
|
|
d="M 99.86386438745993 22.00011880076356 A 50 50 0 1 0 100 22" />
|
|
</g>
|
|
<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>
|
|
<g id="UGimg" <?php if($_GET["floor"] != "UG"){echo "opacity='0'";} ?> >
|
|
<animate begin="UG.click" attributetype="CSS" attributeName="opacity" to=1.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<animate begin="EG.click" attributetype="CSS" attributeName="opacity" to=0.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<animate begin="OG.click" attributetype="CSS" attributeName="opacity" to=0.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<image href="assets/img/UG.png" height="100%" width="100%" x=0
|
|
y=0 />
|
|
</g>
|
|
<g id="EGimg" <?php if($_GET["floor"] != "EG"){echo "opacity='0'";} ?> >
|
|
<animate begin="EG.click" attributetype="CSS" attributeName="opacity" to=1.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<animate begin="OG.click" attributetype="CSS" attributeName="opacity" to=0.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<animate begin="UG.click" attributetype="CSS" attributeName="opacity" to=0.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<image href="assets/img/EG.png" height="100%" width="100%" x=0
|
|
y=0 />
|
|
|
|
</g>
|
|
<g id="OGimg" <?php if($_GET["floor"] != "OG"){echo "opacity='0'";} ?> >
|
|
<animate begin="OG.click" attributetype="CSS" attributeName="opacity" to=1.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<animate begin="EG.click" attributetype="CSS" attributeName="opacity" to=0.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<animate begin="UG.click" attributetype="CSS" attributeName="opacity" to=0.0 dur="0.7s"
|
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
|
<image href="assets/img/OG.png" border="1px solid white" height="100%" width="100%" x=0
|
|
y=0 />
|
|
</g>
|
|
<g id="OG" onclick="switchFloor('OG')" xcursor="pointer" transform="translate(0,0), scale(0.50,0.50)">
|
|
<animate id="showOG" begin=click attributetype="CSS" attributeName="opacity" from=0 to=1
|
|
dur="0.1s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
fill="freeze">
|
|
</animate>
|
|
<use href="#floorBtn" stroke="#aa7713" />
|
|
<text fill="#aa7713" style="font: 3em sans-serif;" transform="translate(62,90)">OG</text>
|
|
</g>
|
|
<g id="EG" onclick="switchFloor('EG')" cursor="pointer" transform="translate(0,100), scale(0.50,0.50)">
|
|
<animate id="showEG" begin=click attributetype="CSS" attributeName="opacity" from=0 to=1
|
|
dur="0.1s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
fill="freeze"></animate>
|
|
<use href="#floorBtn" stroke="#aa7713" />
|
|
<text fill="#aa7713" style="font: 3em sans-serif;" transform="translate(64,90)">EG</text>
|
|
</g>
|
|
<g id="UG" onclick="switchFloor('UG')" cursor="pointer" transform="translate(0,200), scale(0.50,0.50)">
|
|
<animate id="showUG" begin=click attributetype="CSS" attributeName="opacity" from=0 to=1
|
|
dur="0.1s" repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1"
|
|
fill="freeze"></animate>
|
|
<use href="#floorBtn" stroke="#aa7713" />
|
|
<text fill="#aa7713" style="font: 3em sans-serif;" transform="translate(63,90)">UG</text>
|
|
</g>
|
|
<?php foreach ($floors as $floor): ?>
|
|
<g id="<?= $floor ?>_Info" <?php if ($floor != "OG") echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
|
|
<?php foreach (roomsOnFloor($floor) as $room): ?>
|
|
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)" onclick="openHeaterSettings('<?= $floor ?>_<?= $room["mqtt"] ?>');" cursor="pointer">
|
|
<rect width="24" height="19" rx="5" fill="#0006" />
|
|
<use id="<?= $floor ?>_<?= $room["id"] ?>_heater" href="#heater" display="none" />
|
|
<use id="<?= $floor ?>_<?= $room["id"] ?>_buffer" href="#buffer" display="none" />
|
|
<text fill="#fff" style="font: 4px sans-serif;">
|
|
<tspan id="<?= $floor ?>_pres_<?= $room["id"] ?>" y="5" x="12" text-anchor="middle">---- hPa</tspan>
|
|
<tspan id="<?= $floor ?>_tmp_<?= $room["id"] ?>" x="12" dy="1em" font-size="1.6em" text-anchor="middle">--,- °C</tspan>
|
|
<tspan id="<?= $floor ?>_hum_<?= $room["id"] ?>" x="12" dy="1.2em" text-anchor="middle">-- %rF</tspan>
|
|
</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">
|
|
<!-- Zuordnung Geraet -> Raum: sie ordnet die Geraeteliste im
|
|
Editor nach Raeumen statt nach Geraeteart. -->
|
|
<button type="button" class="btn btn-tool" title="Geräte den Räumen zuordnen"
|
|
onclick="openRoomModal()">
|
|
<i class="bi bi-house-gear"></i>
|
|
</button>
|
|
<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 class="card-header p-0 pt-1">
|
|
<ul class="nav nav-tabs" id="actions-tab" role="tablist">
|
|
<li class="nav-item">
|
|
<a class="nav-link <?php if($_GET["floor"] == "OG") echo "active"; ?>" id="actions-OG-tab"href="#actions-OG" onclick="switchTab('actions-OG')" aria-controls="actions-OG" aria-selected="true">OG</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link <?php if($_GET["floor"] == "EG") echo "active"; ?>" id="actions-EG-tab" href="#actions-EG" onclick="switchTab('actions-EG')" aria-controls="actions-EG" aria-selected="false">EG</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link <?php if($_GET["floor"] == "UG") echo "active"; ?>" id="actions-UG-tab" href="#actions-UG" onclick="switchTab('actions-UG')" aria-controls="actions-UG" aria-selected="false">UG</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="tab-content" id="actions-tabContent">
|
|
<div class="tab-pane fade <?php if($_GET["floor"] == "OG") echo "active show"; ?>" id="actions-OG" role="tabpanel" aria-labelledby="actions-OG-tab">
|
|
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
|
|
refreshAutomations() in js/solar/autoActionFuncs.js -->
|
|
<div id="actions-OG-list">Wird geladen...</div>
|
|
<button type="button" class="btn btn-success" title="Neue Automatik"
|
|
onclick="openAutoActionModal('?action=editor&floor=OG')"><i class="bi bi-plus-square"></i></button>
|
|
</div>
|
|
<div class="tab-pane fade <?php if($_GET["floor"] == "EG") echo "active show"; ?>" id="actions-EG" role="tabpanel" aria-labelledby="actions-EG-tab">
|
|
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
|
|
refreshAutomations() in js/solar/autoActionFuncs.js -->
|
|
<div id="actions-EG-list">Wird geladen...</div>
|
|
<button type="button" class="btn btn-success" title="Neue Automatik"
|
|
onclick="openAutoActionModal('?action=editor&floor=EG')"><i class="bi bi-plus-square"></i></button>
|
|
</div>
|
|
<div class="tab-pane fade <?php if($_GET["floor"] == "UG") echo "active show"; ?>" id="actions-UG" role="tabpanel" aria-labelledby="actions-UG-tab">
|
|
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
|
|
refreshAutomations() in js/solar/autoActionFuncs.js -->
|
|
<div id="actions-UG-list">Wird geladen...</div>
|
|
<button type="button" class="btn btn-success" title="Neue Automatik"
|
|
onclick="openAutoActionModal('?action=editor&floor=UG')"><i class="bi bi-plus-square"></i></button>
|
|
</div>
|
|
</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.
|
|
const homeRooms = <?= json_encode($rooms) ?>;
|
|
<?php if ($_GET["floor"] != "OG" && in_array($_GET["floor"], $floors)): ?>
|
|
document.addEventListener('readystatechange', function () {
|
|
if (event.target.readyState === 'complete') {
|
|
switchFloor('<?= $_GET["floor"] ?>');
|
|
}
|
|
});
|
|
<?php endif; ?>
|
|
</script>
|