Zeilenenden vereinheitlichen: LF ueberall ausser im Fremdcode
Bisher entschied jede Arbeitsstation fuer sich, welche Zeilenenden ein Commit bekommt: Git fuer Windows setzt core.autocrlf=input in seiner System-Konfiguration, die NAS setzt nichts. Weil der Bestand ausserdem in sich gemischt war - 34 PHP-Dateien mit LF, 30 mit CRLF -, gab es gar kein richtiges Zeilenende, an das ein Werkzeug sich haette halten koennen. Jede Ergaenzung mit LF in einer CRLF-Datei ergab eine gemischte Datei; helper.php und js/solar/homeMQTT.js waren bereits so entstanden. .gitattributes macht die Zeilenenden zur Eigenschaft des Repositorys. LF, weil dieses Verzeichnis der Web-Ordner der NAS ist und von Linux ausgeliefert wird - und weil Git fuer Windows mit "input" ohnehin schon dorthin zeigt. restricted/WebAuthn bleibt ausgenommen und damit byteweise so, wie die Bibliothek geliefert wurde; darunter liegen 292 Zertifikate. Von den 47 geaenderten Dateien wurde nachgerechnet keine einzige im Inhalt angefasst - der Vergleich HEAD-ohne-CR gegen Index ist ueberall gleich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
# Zeilenenden gehoeren zum Repository, nicht zur Maschine.
|
||||||
|
#
|
||||||
|
# Ohne diese Datei entscheidet jede Arbeitsstation fuer sich: Git fuer
|
||||||
|
# Windows setzt core.autocrlf=input in seiner System-Konfiguration, die NAS
|
||||||
|
# setzt nichts. Dasselbe Repository, zwei Verhalten - und weil der Bestand
|
||||||
|
# selbst gemischt war (34 PHP-Dateien mit LF, 30 mit CRLF), gab es kein
|
||||||
|
# richtiges Zeilenende, an das ein Werkzeug sich haette halten koennen.
|
||||||
|
# Jede Ergaenzung mit LF in einer CRLF-Datei ergab eine gemischte Datei.
|
||||||
|
#
|
||||||
|
# LF und nicht CRLF, weil dieses Verzeichnis der Web-Ordner der NAS ist und
|
||||||
|
# von Linux ausgeliefert wird.
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Eingekaufter Fremdcode bleibt byteweise so, wie er geliefert wurde - dann
|
||||||
|
# bleibt ein Update dagegen vergleichbar. Darunter liegen auch 292
|
||||||
|
# Zertifikate, die niemand anfassen sollte.
|
||||||
|
restricted/WebAuthn/** -text
|
||||||
+15
-15
@@ -1,16 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once("restricted/mysql.php");
|
require_once("restricted/mysql.php");
|
||||||
require_once("helper.php");
|
require_once("helper.php");
|
||||||
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
||||||
if(!mysqli_query($mysql,"DELETE FROM addUser WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){
|
if(!mysqli_query($mysql,"DELETE FROM addUser WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){
|
||||||
echo mysqli_error($mysql);
|
echo mysqli_error($mysql);
|
||||||
}
|
}
|
||||||
if(isLocal()){
|
if(isLocal()){
|
||||||
$accessKey = strval(random_int(0,99999999));
|
$accessKey = strval(random_int(0,99999999));
|
||||||
if(!mysqli_query($mysql,"INSERT INTO addUser SET accesskey=".$accessKey.", datetime=NOW();")){
|
if(!mysqli_query($mysql,"INSERT INTO addUser SET accesskey=".$accessKey.", datetime=NOW();")){
|
||||||
echo mysqli_error($mysql);
|
echo mysqli_error($mysql);
|
||||||
}
|
}
|
||||||
header('Location: https://nas.el-wa.org/smart?addUser='.$accessKey);
|
header('Location: https://nas.el-wa.org/smart?addUser='.$accessKey);
|
||||||
echo 'https://nas.el-wa.org/smart?addUser='.$accessKey;
|
echo 'https://nas.el-wa.org/smart?addUser='.$accessKey;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
+161
-161
@@ -1,162 +1,162 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
if(!isset($_GET["TO"])){
|
if(!isset($_GET["TO"])){
|
||||||
$_GET["TO"] = 12;
|
$_GET["TO"] = 12;
|
||||||
}
|
}
|
||||||
$_GET["TO"] = intval($_GET["TO"]);
|
$_GET["TO"] = intval($_GET["TO"]);
|
||||||
if(!isset($_GET["FROM"])){
|
if(!isset($_GET["FROM"])){
|
||||||
$_GET["FROM"] = -24;
|
$_GET["FROM"] = -24;
|
||||||
}
|
}
|
||||||
$_GET["FROM"] = intval($_GET["FROM"]);
|
$_GET["FROM"] = intval($_GET["FROM"]);
|
||||||
//-totalConsumption-PL1_EV*1000-PL2_EV*1000-PL3_EV*1000-PL1_EVog*1000-PL2_EVog*1000-PL3_EVog*1000+PL1_OG+PL2_OG+PL3_OG-heaterPwr AS 'UG',
|
//-totalConsumption-PL1_EV*1000-PL2_EV*1000-PL3_EV*1000-PL1_EVog*1000-PL2_EVog*1000-PL3_EVog*1000+PL1_OG+PL2_OG+PL3_OG-heaterPwr AS 'UG',
|
||||||
$consQuery = "SELECT
|
$consQuery = "SELECT
|
||||||
UNIX_TIMESTAMP(EnergyFlow.datetime) AS time,
|
UNIX_TIMESTAMP(EnergyFlow.datetime) AS time,
|
||||||
pvP AS 'Solarleistung',
|
pvP AS 'Solarleistung',
|
||||||
soc AS Ladestand,
|
soc AS Ladestand,
|
||||||
-totalConsumption-PL1_EV*1000-PL2_EV*1000-PL3_EV*1000-PL1_EVog*1000-PL2_EVog*1000-PL3_EVog*1000+PL1_OG+PL2_OG+PL3_OG-heaterPwr-PL1_UG-PL2_UG-PL3_UG-PL1_EG-PL2_EG-PL3_EG AS 'Gemein',
|
-totalConsumption-PL1_EV*1000-PL2_EV*1000-PL3_EV*1000-PL1_EVog*1000-PL2_EVog*1000-PL3_EVog*1000+PL1_OG+PL2_OG+PL3_OG-heaterPwr-PL1_UG-PL2_UG-PL3_UG-PL1_EG-PL2_EG-PL3_EG AS 'Gemein',
|
||||||
(PL1_UG+PL2_UG+PL3_UG) AS 'UG',
|
(PL1_UG+PL2_UG+PL3_UG) AS 'UG',
|
||||||
(PL1_EG+PL2_EG+PL3_EG) AS 'EG',
|
(PL1_EG+PL2_EG+PL3_EG) AS 'EG',
|
||||||
-(PL1_OG+PL2_OG+PL3_OG) AS 'OG',
|
-(PL1_OG+PL2_OG+PL3_OG) AS 'OG',
|
||||||
(PL1_EV+PL2_EV+PL3_EV)*1000 AS 'Auto UG',
|
(PL1_EV+PL2_EV+PL3_EV)*1000 AS 'Auto UG',
|
||||||
(PL1_EVog+PL2_EVog+PL3_EVog)*1000 AS 'Auto OG',
|
(PL1_EVog+PL2_EVog+PL3_EVog)*1000 AS 'Auto OG',
|
||||||
heaterPwr AS 'Heizstab',
|
heaterPwr AS 'Heizstab',
|
||||||
IF(battP<0, -battP, 0) AS Batterieladung,
|
IF(battP<0, -battP, 0) AS Batterieladung,
|
||||||
gridPfeed AS Einspeisung
|
gridPfeed AS Einspeisung
|
||||||
FROM solarLog.EnergyFlow
|
FROM solarLog.EnergyFlow
|
||||||
WHERE EnergyFlow.datetime BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
WHERE EnergyFlow.datetime BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
||||||
ORDER BY EnergyFlow.datetime";
|
ORDER BY EnergyFlow.datetime";
|
||||||
$simQuery = "SELECT
|
$simQuery = "SELECT
|
||||||
UNIX_TIMESTAMP(simPower.period_end) AS time,
|
UNIX_TIMESTAMP(simPower.period_end) AS time,
|
||||||
power*1000 AS 'Vorhersage'
|
power*1000 AS 'Vorhersage'
|
||||||
FROM solarLog.simPower
|
FROM solarLog.simPower
|
||||||
WHERE simPower.period_end BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
WHERE simPower.period_end BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
||||||
ORDER BY simPower.period_end";
|
ORDER BY simPower.period_end";
|
||||||
$linecolors["Solarleistung"] = "#FFFF00";
|
$linecolors["Solarleistung"] = "#FFFF00";
|
||||||
$linecolors["Gemein"] = "#FF9900";
|
$linecolors["Gemein"] = "#FF9900";
|
||||||
$linecolors["UG"] = "#FF8800";
|
$linecolors["UG"] = "#FF8800";
|
||||||
$linecolors["EG"] = "#FF6600";
|
$linecolors["EG"] = "#FF6600";
|
||||||
$linecolors["OG"] = "#FF4400";
|
$linecolors["OG"] = "#FF4400";
|
||||||
$linecolors["Auto UG"] = "#00aaFF";
|
$linecolors["Auto UG"] = "#00aaFF";
|
||||||
$linecolors["Auto OG"] = "#0044FF";
|
$linecolors["Auto OG"] = "#0044FF";
|
||||||
$linecolors["Heizstab"] = "#FF0000";
|
$linecolors["Heizstab"] = "#FF0000";
|
||||||
$linecolors["Batterieladung"] = "#00aa00";
|
$linecolors["Batterieladung"] = "#00aa00";
|
||||||
$linecolors["Einspeisung"] = "#b0b0b0";
|
$linecolors["Einspeisung"] = "#b0b0b0";
|
||||||
$linecolors["Ladestand"] = "#00aa00";
|
$linecolors["Ladestand"] = "#00aa00";
|
||||||
$linecolors["Vorhersage"] = "#2222FF";
|
$linecolors["Vorhersage"] = "#2222FF";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||||
$result = mysqli_query($mysql, $consQuery);
|
$result = mysqli_query($mysql, $consQuery);
|
||||||
$simRes = mysqli_query($mysql,$simQuery);
|
$simRes = mysqli_query($mysql,$simQuery);
|
||||||
if(!$result){
|
if(!$result){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
$obj = (object)[]; // Cast empty array to object
|
$obj = (object)[]; // Cast empty array to object
|
||||||
$obj->labels = [];
|
$obj->labels = [];
|
||||||
$obj->datasets = [];
|
$obj->datasets = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$filled = 0;
|
$filled = 0;
|
||||||
if ($simRes->num_rows > 1) {
|
if ($simRes->num_rows > 1) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
$row1 = $simRes->fetch_assoc();
|
$row1 = $simRes->fetch_assoc();
|
||||||
$dataset->borderColor = $linecolors["Vorhersage"];
|
$dataset->borderColor = $linecolors["Vorhersage"];
|
||||||
$dataset->backgroundColor = $linecolors["Vorhersage"]."55";
|
$dataset->backgroundColor = $linecolors["Vorhersage"]."55";
|
||||||
$dataset->borderWidth=1.5;
|
$dataset->borderWidth=1.5;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
$dataset->stack = "sim";
|
$dataset->stack = "sim";
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
$dataset->label = "Vorhersage";
|
$dataset->label = "Vorhersage";
|
||||||
/*$pt = (object)[];
|
/*$pt = (object)[];
|
||||||
$pt->x = $row1["time"]*1000;
|
$pt->x = $row1["time"]*1000;
|
||||||
$pt->y = $row1["Vorhersage"];
|
$pt->y = $row1["Vorhersage"];
|
||||||
$dataset->data[] = clone $pt;
|
$dataset->data[] = clone $pt;
|
||||||
while ($row1 = $simRes->fetch_assoc()) {
|
while ($row1 = $simRes->fetch_assoc()) {
|
||||||
$pt = (object)[];
|
$pt = (object)[];
|
||||||
$pt->x = $row1["time"]*1000 + 30*60*1000;
|
$pt->x = $row1["time"]*1000 + 30*60*1000;
|
||||||
$pt->y = $row1["Vorhersage"];
|
$pt->y = $row1["Vorhersage"];
|
||||||
$dataset->data[] = clone $pt;
|
$dataset->data[] = clone $pt;
|
||||||
}*/
|
}*/
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
$rownext = $simRes->fetch_assoc();
|
$rownext = $simRes->fetch_assoc();
|
||||||
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
||||||
}
|
}
|
||||||
if ($result->num_rows > 1) {
|
if ($result->num_rows > 1) {
|
||||||
$ii = 1;
|
$ii = 1;
|
||||||
$row = $result->fetch_assoc();
|
$row = $result->fetch_assoc();
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$dataset->borderColor = $linecolors[$name];
|
$dataset->borderColor = $linecolors[$name];
|
||||||
$dataset->backgroundColor = $linecolors[$name]."55";
|
$dataset->backgroundColor = $linecolors[$name]."55";
|
||||||
$dataset->borderWidth=1;
|
$dataset->borderWidth=1;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
if ($name == "Solarleistung") {
|
if ($name == "Solarleistung") {
|
||||||
$dataset->stack = "SolarPwr";
|
$dataset->stack = "SolarPwr";
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
} else if ($name == "Ladestand") {
|
} else if ($name == "Ladestand") {
|
||||||
$dataset->stack = "Charge";
|
$dataset->stack = "Charge";
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y1';
|
$dataset->yAxisID = 'y1';
|
||||||
} else {
|
} else {
|
||||||
$dataset->stack = "Consumers";
|
$dataset->stack = "Consumers";
|
||||||
if ($filled == 0) {
|
if ($filled == 0) {
|
||||||
$filled = 1;
|
$filled = 1;
|
||||||
$dataset->fill = "origin";
|
$dataset->fill = "origin";
|
||||||
} else {
|
} else {
|
||||||
$dataset->fill = "-1";
|
$dataset->fill = "-1";
|
||||||
}
|
}
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
}
|
}
|
||||||
$dataset->label = $name;
|
$dataset->label = $name;
|
||||||
|
|
||||||
$dataset->data[] = $value;
|
$dataset->data[] = $value;
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
$ii++;
|
$ii++;
|
||||||
} else {
|
} else {
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ($row = $result->fetch_assoc()) {
|
while ($row = $result->fetch_assoc()) {
|
||||||
$ii = 1;
|
$ii = 1;
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$obj->datasets[$ii]->data[] = $value;
|
$obj->datasets[$ii]->data[] = $value;
|
||||||
$ii++;
|
$ii++;
|
||||||
} else {
|
} else {
|
||||||
if(($value * 1000) < $nextSimTimestamp){
|
if(($value * 1000) < $nextSimTimestamp){
|
||||||
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
||||||
}else{
|
}else{
|
||||||
$row1 = $rownext;
|
$row1 = $rownext;
|
||||||
$rownext = $simRes->fetch_assoc();
|
$rownext = $simRes->fetch_assoc();
|
||||||
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
||||||
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
||||||
}
|
}
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$obj->labels[] = $nextSimTimestamp; //Draw future forecast
|
$obj->labels[] = $nextSimTimestamp; //Draw future forecast
|
||||||
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
||||||
while($rownext = $simRes->fetch_assoc()){
|
while($rownext = $simRes->fetch_assoc()){
|
||||||
$obj->labels[] = $rownext["time"]*1000 + 30*60*1000;
|
$obj->labels[] = $rownext["time"]*1000 + 30*60*1000;
|
||||||
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//header('Content-Type: application/json');
|
//header('Content-Type: application/json');
|
||||||
echo json_encode($obj);
|
echo json_encode($obj);
|
||||||
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
||||||
?>
|
?>
|
||||||
+171
-171
@@ -1,171 +1,171 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
|
|
||||||
$consEstQuery = "SELECT
|
$consEstQuery = "SELECT
|
||||||
UNIX_TIMESTAMP(DATE_ADD(datetime, INTERVAL 28 DAY)) AS 'time',
|
UNIX_TIMESTAMP(DATE_ADD(datetime, INTERVAL 28 DAY)) AS 'time',
|
||||||
SUM(-(totalConsumption + heaterPwr)/48) AS 'Vorraussichtl. Verbrauch'
|
SUM(-(totalConsumption + heaterPwr)/48) AS 'Vorraussichtl. Verbrauch'
|
||||||
FROM EnergyFlow
|
FROM EnergyFlow
|
||||||
WHERE
|
WHERE
|
||||||
DATE(datetime) >= DATE(DATE_SUB(NOW(),INTERVAL 28 DAY)) And DATE(datetime) != DATE(NOW())
|
DATE(datetime) >= DATE(DATE_SUB(NOW(),INTERVAL 28 DAY)) And DATE(datetime) != DATE(NOW())
|
||||||
GROUP BY WEEKDAY(datetime)
|
GROUP BY WEEKDAY(datetime)
|
||||||
ORDER BY datetime";
|
ORDER BY datetime";
|
||||||
|
|
||||||
$prodEstQuery = "SELECT UNIX_TIMESTAMP(CONVERT_TZ(period_End,'GMT','Europe/Berlin')) AS 'time',
|
$prodEstQuery = "SELECT UNIX_TIMESTAMP(CONVERT_TZ(period_End,'GMT','Europe/Berlin')) AS 'time',
|
||||||
SUM(power*500) AS 'Vorhersage'
|
SUM(power*500) AS 'Vorhersage'
|
||||||
FROM simPower
|
FROM simPower
|
||||||
WHERE DATE(CONVERT_TZ(period_End,'GMT','Europe/Berlin')) >= DATE(DATE_SUB(NOW(),INTERVAL 7 DAY))
|
WHERE DATE(CONVERT_TZ(period_End,'GMT','Europe/Berlin')) >= DATE(DATE_SUB(NOW(),INTERVAL 7 DAY))
|
||||||
GROUP BY DAY(CONVERT_TZ(period_End,'GMT','Europe/Berlin'))
|
GROUP BY DAY(CONVERT_TZ(period_End,'GMT','Europe/Berlin'))
|
||||||
ORDER BY period_End;";
|
ORDER BY period_End;";
|
||||||
|
|
||||||
$prodRealQuery = "SELECT
|
$prodRealQuery = "SELECT
|
||||||
UNIX_TIMESTAMP(datetime) AS 'time',
|
UNIX_TIMESTAMP(datetime) AS 'time',
|
||||||
SUM(pvP/12) AS 'Tatsächliche Erzeugung',
|
SUM(pvP/12) AS 'Tatsächliche Erzeugung',
|
||||||
SUM(-totalConsumption/12) AS 'Tatsächlicher Verbrauch'
|
SUM(-totalConsumption/12) AS 'Tatsächlicher Verbrauch'
|
||||||
FROM EnergyFlow
|
FROM EnergyFlow
|
||||||
WHERE
|
WHERE
|
||||||
DATE(datetime) >= DATE(DATE_SUB(NOW(),INTERVAL 7 DAY))
|
DATE(datetime) >= DATE(DATE_SUB(NOW(),INTERVAL 7 DAY))
|
||||||
GROUP BY DAY(datetime)
|
GROUP BY DAY(datetime)
|
||||||
ORDER BY datetime";
|
ORDER BY datetime";
|
||||||
|
|
||||||
|
|
||||||
$linecolors["Tatsächliche Erzeugung"] = "#cccc00";
|
$linecolors["Tatsächliche Erzeugung"] = "#cccc00";
|
||||||
$linecolors["Tatsächlicher Verbrauch"] = "#EE9900";
|
$linecolors["Tatsächlicher Verbrauch"] = "#EE9900";
|
||||||
$linecolors["Vorraussichtl. Verbrauch"] = "#BB4400";
|
$linecolors["Vorraussichtl. Verbrauch"] = "#BB4400";
|
||||||
$linecolors["Auto UG"] = "#00aaFF";
|
$linecolors["Auto UG"] = "#00aaFF";
|
||||||
$linecolors["Auto OG"] = "#0044FF";
|
$linecolors["Auto OG"] = "#0044FF";
|
||||||
$linecolors["Heizstab"] = "#FF0000";
|
$linecolors["Heizstab"] = "#FF0000";
|
||||||
$linecolors["Batterieladung"] = "#00aa00";
|
$linecolors["Batterieladung"] = "#00aa00";
|
||||||
$linecolors["Einspeisung"] = "#b0b0b0";
|
$linecolors["Einspeisung"] = "#b0b0b0";
|
||||||
$linecolors["Ladestand"] = "#00aa00";
|
$linecolors["Ladestand"] = "#00aa00";
|
||||||
$linecolors["Vorhersage"] = "#4444FF";
|
$linecolors["Vorhersage"] = "#4444FF";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||||
$consEst = mysqli_query($mysql, $consEstQuery);
|
$consEst = mysqli_query($mysql, $consEstQuery);
|
||||||
$prodEst = mysqli_query($mysql,$prodEstQuery);
|
$prodEst = mysqli_query($mysql,$prodEstQuery);
|
||||||
$prodReal = mysqli_query($mysql,$prodRealQuery);
|
$prodReal = mysqli_query($mysql,$prodRealQuery);
|
||||||
if(!$consEst){
|
if(!$consEst){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
if(!$prodEst){
|
if(!$prodEst){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
if(!$prodRealQuery){
|
if(!$prodRealQuery){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
|
|
||||||
$obj = (object)[]; // Cast empty array to object
|
$obj = (object)[]; // Cast empty array to object
|
||||||
$obj->labels = [];
|
$obj->labels = [];
|
||||||
$obj->datasets = [];
|
$obj->datasets = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$filled = 0;
|
$filled = 0;
|
||||||
|
|
||||||
if($consEst->num_rows > 1){
|
if($consEst->num_rows > 1){
|
||||||
$row = $consEst->fetch_assoc();
|
$row = $consEst->fetch_assoc();
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$dataset->borderColor = $linecolors[$name];
|
$dataset->borderColor = $linecolors[$name];
|
||||||
$dataset->backgroundColor = $linecolors[$name]."66";
|
$dataset->backgroundColor = $linecolors[$name]."66";
|
||||||
$dataset->borderWidth=1;
|
$dataset->borderWidth=1;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
$dataset->stack = $name;
|
$dataset->stack = $name;
|
||||||
//$dataset->fill = "none";
|
//$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
$dataset->label = $name;
|
$dataset->label = $name;
|
||||||
for($i=0;$i<7;$i++){
|
for($i=0;$i<7;$i++){
|
||||||
$dataset->data[] = NULL;
|
$dataset->data[] = NULL;
|
||||||
}
|
}
|
||||||
$dataset->data[] = $value;
|
$dataset->data[] = $value;
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ($row = $consEst->fetch_assoc()) {
|
while ($row = $consEst->fetch_assoc()) {
|
||||||
$ii = 0;
|
$ii = 0;
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$obj->datasets[$ii]->data[] = $value;
|
$obj->datasets[$ii]->data[] = $value;
|
||||||
$ii++;
|
$ii++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($prodEst->num_rows > 1) {
|
if ($prodEst->num_rows > 1) {
|
||||||
$row = $prodEst->fetch_assoc();
|
$row = $prodEst->fetch_assoc();
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$dataset->borderColor = $linecolors[$name];
|
$dataset->borderColor = $linecolors[$name];
|
||||||
$dataset->backgroundColor = $linecolors[$name]."55";
|
$dataset->backgroundColor = $linecolors[$name]."55";
|
||||||
$dataset->borderWidth=1;
|
$dataset->borderWidth=1;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
$dataset->stack = $name;
|
$dataset->stack = $name;
|
||||||
//$dataset->fill = "none";
|
//$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
$dataset->label = $name;
|
$dataset->label = $name;
|
||||||
$dataset->data[] = $value;
|
$dataset->data[] = $value;
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
} else {
|
} else {
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ($row = $prodEst->fetch_assoc()) {
|
while ($row = $prodEst->fetch_assoc()) {
|
||||||
$ii = 1;
|
$ii = 1;
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$obj->datasets[$ii]->data[] = $value;
|
$obj->datasets[$ii]->data[] = $value;
|
||||||
$ii++;
|
$ii++;
|
||||||
} else {
|
} else {
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($prodReal->num_rows > 1){
|
if($prodReal->num_rows > 1){
|
||||||
$row = $prodReal->fetch_assoc();
|
$row = $prodReal->fetch_assoc();
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$dataset->borderColor = $linecolors[$name];
|
$dataset->borderColor = $linecolors[$name];
|
||||||
$dataset->backgroundColor = $linecolors[$name]."55";
|
$dataset->backgroundColor = $linecolors[$name]."55";
|
||||||
$dataset->borderWidth=1;
|
$dataset->borderWidth=1;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
$dataset->stack = $name;
|
$dataset->stack = $name;
|
||||||
//$dataset->fill = "none";
|
//$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
$dataset->label = $name;
|
$dataset->label = $name;
|
||||||
$dataset->data[] = $value;
|
$dataset->data[] = $value;
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ($row = $prodReal->fetch_assoc()) {
|
while ($row = $prodReal->fetch_assoc()) {
|
||||||
$ii = 2;
|
$ii = 2;
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$obj->datasets[$ii]->data[] = $value;
|
$obj->datasets[$ii]->data[] = $value;
|
||||||
$ii++;
|
$ii++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//header('Content-Type: application/json');
|
//header('Content-Type: application/json');
|
||||||
echo json_encode($obj);
|
echo json_encode($obj);
|
||||||
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
||||||
|
|||||||
+105
-105
@@ -1,105 +1,105 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
|
|
||||||
$heatQuery = "SELECT
|
$heatQuery = "SELECT
|
||||||
UNIX_TIMESTAMP(Heater.datetime) AS time,
|
UNIX_TIMESTAMP(Heater.datetime) AS time,
|
||||||
PufferU AS 'Speicher unten',
|
PufferU AS 'Speicher unten',
|
||||||
PufferM AS 'Speicher mitte',
|
PufferM AS 'Speicher mitte',
|
||||||
PufferO AS 'Speicher oben',
|
PufferO AS 'Speicher oben',
|
||||||
|
|
||||||
|
|
||||||
thermeVLfb AS 'Therme Vorlauf Fußboden',
|
thermeVLfb AS 'Therme Vorlauf Fußboden',
|
||||||
thermeRL AS 'Therme Rücklauf',
|
thermeRL AS 'Therme Rücklauf',
|
||||||
heaterVL AS 'Heizstab Vorlauf',
|
heaterVL AS 'Heizstab Vorlauf',
|
||||||
heaterRL AS 'Heizstab Rücklauf',
|
heaterRL AS 'Heizstab Rücklauf',
|
||||||
fbVL AS 'Fußboden Vorlauf',
|
fbVL AS 'Fußboden Vorlauf',
|
||||||
fbRL AS 'Fußboden Rücklauf'
|
fbRL AS 'Fußboden Rücklauf'
|
||||||
FROM Heater
|
FROM Heater
|
||||||
WHERE Heater.datetime BETWEEN DATE_SUB(NOW(),INTERVAL 24 HOUR) and NOW()
|
WHERE Heater.datetime BETWEEN DATE_SUB(NOW(),INTERVAL 24 HOUR) and NOW()
|
||||||
ORDER BY Heater.datetime";
|
ORDER BY Heater.datetime";
|
||||||
|
|
||||||
$waterQuery = "SELECT UNIX_TIMESTAMP(wasser.datetime) AS time, rate AS 'Wasserverbrauch'
|
$waterQuery = "SELECT UNIX_TIMESTAMP(wasser.datetime) AS time, rate AS 'Wasserverbrauch'
|
||||||
FROM solarLog.wasser
|
FROM solarLog.wasser
|
||||||
WHERE wasser.datetime BETWEEN DATE_SUB(NOW(),INTERVAL 24 HOUR) and NOW()
|
WHERE wasser.datetime BETWEEN DATE_SUB(NOW(),INTERVAL 24 HOUR) and NOW()
|
||||||
ORDER BY wasser.datetime";
|
ORDER BY wasser.datetime";
|
||||||
|
|
||||||
$linecolors["Speicher oben"] = "#FF5500";
|
$linecolors["Speicher oben"] = "#FF5500";
|
||||||
$linecolors["Speicher mitte"] = "#FFaa00";
|
$linecolors["Speicher mitte"] = "#FFaa00";
|
||||||
$linecolors["Speicher unten"] = "#FFFF00";
|
$linecolors["Speicher unten"] = "#FFFF00";
|
||||||
$linecolors["Therme Vorlauf Fußboden"] = "#bb0000";
|
$linecolors["Therme Vorlauf Fußboden"] = "#bb0000";
|
||||||
$linecolors["Therme Rücklauf"] = "#ee0000";
|
$linecolors["Therme Rücklauf"] = "#ee0000";
|
||||||
$linecolors["Heizstab Vorlauf"] = "#9900bb";
|
$linecolors["Heizstab Vorlauf"] = "#9900bb";
|
||||||
$linecolors["Heizstab Rücklauf"] = "#8800aa";
|
$linecolors["Heizstab Rücklauf"] = "#8800aa";
|
||||||
$linecolors["Fußboden Vorlauf"] = "#00FF00";
|
$linecolors["Fußboden Vorlauf"] = "#00FF00";
|
||||||
$linecolors["Fußboden Rücklauf"] = "#00aa00";
|
$linecolors["Fußboden Rücklauf"] = "#00aa00";
|
||||||
$linecolors["Wasserverbrauch"] = "#2222FF";
|
$linecolors["Wasserverbrauch"] = "#2222FF";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||||
$result = mysqli_query($mysql, $heatQuery);
|
$result = mysqli_query($mysql, $heatQuery);
|
||||||
$simRes = mysqli_query($mysql,$waterQuery);
|
$simRes = mysqli_query($mysql,$waterQuery);
|
||||||
if(!$result){
|
if(!$result){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
$obj = (object)[]; // Cast empty array to object
|
$obj = (object)[]; // Cast empty array to object
|
||||||
$obj->labels = [];
|
$obj->labels = [];
|
||||||
$obj->datasets = [];
|
$obj->datasets = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$filled = 0;
|
$filled = 0;
|
||||||
|
|
||||||
if ($result->num_rows > 1) {
|
if ($result->num_rows > 1) {
|
||||||
$ii = 0;
|
$ii = 0;
|
||||||
$row = $result->fetch_assoc();
|
$row = $result->fetch_assoc();
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$dataset->borderColor = $linecolors[$name];
|
$dataset->borderColor = $linecolors[$name];
|
||||||
$dataset->backgroundColor = $linecolors[$name]."22";
|
$dataset->backgroundColor = $linecolors[$name]."22";
|
||||||
$dataset->borderWidth=2;
|
$dataset->borderWidth=2;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
if(strpos($name,"Speicher") === false) {
|
if(strpos($name,"Speicher") === false) {
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
} else {
|
} else {
|
||||||
// $dataset->stack = "Consumers";
|
// $dataset->stack = "Consumers";
|
||||||
if ($filled == 0) {
|
if ($filled == 0) {
|
||||||
$filled = 1;
|
$filled = 1;
|
||||||
$dataset->fill = "origin";
|
$dataset->fill = "origin";
|
||||||
} else {
|
} else {
|
||||||
$dataset->fill = "-1";
|
$dataset->fill = "-1";
|
||||||
}
|
}
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
}
|
}
|
||||||
$dataset->label = $name;
|
$dataset->label = $name;
|
||||||
|
|
||||||
$dataset->data[] = $value;
|
$dataset->data[] = $value;
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
$ii++;
|
$ii++;
|
||||||
} else {
|
} else {
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ($row = $result->fetch_assoc()) {
|
while ($row = $result->fetch_assoc()) {
|
||||||
$ii = 0;
|
$ii = 0;
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$obj->datasets[$ii]->data[] = $value;
|
$obj->datasets[$ii]->data[] = $value;
|
||||||
$ii++;
|
$ii++;
|
||||||
} else {
|
} else {
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//header('Content-Type: application/json');
|
//header('Content-Type: application/json');
|
||||||
echo json_encode($obj);
|
echo json_encode($obj);
|
||||||
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
||||||
|
|||||||
+164
-164
@@ -1,165 +1,165 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
if(!isset($_GET["TO"])){
|
if(!isset($_GET["TO"])){
|
||||||
$_GET["TO"] = 12;
|
$_GET["TO"] = 12;
|
||||||
}
|
}
|
||||||
$_GET["TO"] = intval($_GET["TO"]);
|
$_GET["TO"] = intval($_GET["TO"]);
|
||||||
if(!isset($_GET["FROM"])){
|
if(!isset($_GET["FROM"])){
|
||||||
$_GET["FROM"] = -24;
|
$_GET["FROM"] = -24;
|
||||||
}
|
}
|
||||||
$_GET["FROM"] = intval($_GET["FROM"]);
|
$_GET["FROM"] = intval($_GET["FROM"]);
|
||||||
$consQuery = "SELECT
|
$consQuery = "SELECT
|
||||||
UNIX_TIMESTAMP(EnergyFlow.datetime) AS time,
|
UNIX_TIMESTAMP(EnergyFlow.datetime) AS time,
|
||||||
pvP AS Solarleistung,
|
pvP AS Solarleistung,
|
||||||
IF(-totalConsumption - IF(gridP>0, gridP, 0) - IF(battP>0, battP, 0) > 0, -totalConsumption - IF(gridP>0, gridP, 0) - IF(battP>0, battP, 0), 0) AS Direktverbrauch,
|
IF(-totalConsumption - IF(gridP>0, gridP, 0) - IF(battP>0, battP, 0) > 0, -totalConsumption - IF(gridP>0, gridP, 0) - IF(battP>0, battP, 0), 0) AS Direktverbrauch,
|
||||||
IF(battP>0, battP, 0) AS Batteriebezug,
|
IF(battP>0, battP, 0) AS Batteriebezug,
|
||||||
gridPcons AS Netzbezug,
|
gridPcons AS Netzbezug,
|
||||||
-totalConsumption AS Verbrauch,".
|
-totalConsumption AS Verbrauch,".
|
||||||
//IF(battP<0, -battP, 0) AS Batterieladung,
|
//IF(battP<0, -battP, 0) AS Batterieladung,
|
||||||
"soc AS Ladestand
|
"soc AS Ladestand
|
||||||
FROM solarLog.EnergyFlow
|
FROM solarLog.EnergyFlow
|
||||||
WHERE EnergyFlow.datetime BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
WHERE EnergyFlow.datetime BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
||||||
ORDER BY EnergyFlow.datetime";
|
ORDER BY EnergyFlow.datetime";
|
||||||
|
|
||||||
$simQuery = "SELECT
|
$simQuery = "SELECT
|
||||||
UNIX_TIMESTAMP(simPower.period_end) AS time,
|
UNIX_TIMESTAMP(simPower.period_end) AS time,
|
||||||
power*1000 AS 'Vorhersage'
|
power*1000 AS 'Vorhersage'
|
||||||
FROM solarLog.simPower
|
FROM solarLog.simPower
|
||||||
WHERE simPower.period_end BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
WHERE simPower.period_end BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"])." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
||||||
ORDER BY simPower.period_end";
|
ORDER BY simPower.period_end";
|
||||||
$linecolors["Solarleistung"] = "#FFFF00";
|
$linecolors["Solarleistung"] = "#FFFF00";
|
||||||
$linecolors["Direktverbrauch"] = "#FFcc00";
|
$linecolors["Direktverbrauch"] = "#FFcc00";
|
||||||
$linecolors["Verbrauch"] = "#FFaa44";
|
$linecolors["Verbrauch"] = "#FFaa44";
|
||||||
$linecolors["Auto UG"] = "#00aaFF";
|
$linecolors["Auto UG"] = "#00aaFF";
|
||||||
$linecolors["Auto OG"] = "#0044FF";
|
$linecolors["Auto OG"] = "#0044FF";
|
||||||
$linecolors["Netzbezug"] = "#FF0000";
|
$linecolors["Netzbezug"] = "#FF0000";
|
||||||
$linecolors["Batteriebezug"] = "#00aa00";
|
$linecolors["Batteriebezug"] = "#00aa00";
|
||||||
$linecolors["Batterieladung"] = "#0033aa";
|
$linecolors["Batterieladung"] = "#0033aa";
|
||||||
$linecolors["Einspeisung"] = "#b0b0b0";
|
$linecolors["Einspeisung"] = "#b0b0b0";
|
||||||
$linecolors["Ladestand"] = "#00aa00";
|
$linecolors["Ladestand"] = "#00aa00";
|
||||||
$linecolors["Vorhersage"] = "#2222FF";
|
$linecolors["Vorhersage"] = "#2222FF";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
|
|
||||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||||
$result = mysqli_query($mysql, $consQuery);
|
$result = mysqli_query($mysql, $consQuery);
|
||||||
$simRes = mysqli_query($mysql,$simQuery);
|
$simRes = mysqli_query($mysql,$simQuery);
|
||||||
if(!$result){
|
if(!$result){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
$obj = (object)[]; // Cast empty array to object
|
$obj = (object)[]; // Cast empty array to object
|
||||||
$obj->labels = [];
|
$obj->labels = [];
|
||||||
$obj->datasets = [];
|
$obj->datasets = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$filled = 0;
|
$filled = 0;
|
||||||
if ($simRes->num_rows > 1) {
|
if ($simRes->num_rows > 1) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
$row1 = $simRes->fetch_assoc();
|
$row1 = $simRes->fetch_assoc();
|
||||||
$dataset->borderColor = $linecolors["Vorhersage"];
|
$dataset->borderColor = $linecolors["Vorhersage"];
|
||||||
$dataset->backgroundColor = $linecolors["Vorhersage"]."55";
|
$dataset->backgroundColor = $linecolors["Vorhersage"]."55";
|
||||||
$dataset->borderWidth=1.5;
|
$dataset->borderWidth=1.5;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
$dataset->stack = "sim";
|
$dataset->stack = "sim";
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
$dataset->label = "Vorhersage";
|
$dataset->label = "Vorhersage";
|
||||||
/*$pt = (object)[];
|
/*$pt = (object)[];
|
||||||
$pt->x = $row["time"]*1000;
|
$pt->x = $row["time"]*1000;
|
||||||
$pt->y = $row["Vorhersage"];
|
$pt->y = $row["Vorhersage"];
|
||||||
$dataset->data[] = clone $pt;
|
$dataset->data[] = clone $pt;
|
||||||
while ($row = $simRes->fetch_assoc()) {
|
while ($row = $simRes->fetch_assoc()) {
|
||||||
$pt = (object)[];
|
$pt = (object)[];
|
||||||
$pt->x = $row["time"]*1000 + 30*60*1000;
|
$pt->x = $row["time"]*1000 + 30*60*1000;
|
||||||
$pt->y = $row["Vorhersage"];
|
$pt->y = $row["Vorhersage"];
|
||||||
$dataset->data[] = clone $pt;
|
$dataset->data[] = clone $pt;
|
||||||
}*/
|
}*/
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
$rownext = $simRes->fetch_assoc();
|
$rownext = $simRes->fetch_assoc();
|
||||||
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
||||||
}
|
}
|
||||||
if ($result->num_rows > 1) {
|
if ($result->num_rows > 1) {
|
||||||
$ii = 1;
|
$ii = 1;
|
||||||
$row = $result->fetch_assoc();
|
$row = $result->fetch_assoc();
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$dataset->borderColor = $linecolors[$name];
|
$dataset->borderColor = $linecolors[$name];
|
||||||
$dataset->backgroundColor = $linecolors[$name]."22";
|
$dataset->backgroundColor = $linecolors[$name]."22";
|
||||||
$dataset->borderWidth=1;
|
$dataset->borderWidth=1;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
if ($name == "Solarleistung") {
|
if ($name == "Solarleistung") {
|
||||||
$dataset->stack = "SolarPwr";
|
$dataset->stack = "SolarPwr";
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
}else if ($name == "Verbrauch") {
|
}else if ($name == "Verbrauch") {
|
||||||
$dataset->stack = "ConsPwr";
|
$dataset->stack = "ConsPwr";
|
||||||
$dataset->fill = "1";
|
$dataset->fill = "1";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
}else if ($name == "Batterieladung") {
|
}else if ($name == "Batterieladung") {
|
||||||
$dataset->stack = "ConsPwr";
|
$dataset->stack = "ConsPwr";
|
||||||
$dataset->fill = "-1";
|
$dataset->fill = "-1";
|
||||||
$dataset->backgroundColor = $linecolors[$name]."77";
|
$dataset->backgroundColor = $linecolors[$name]."77";
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
} else if ($name == "Ladestand") {
|
} else if ($name == "Ladestand") {
|
||||||
$dataset->stack = "Charge";
|
$dataset->stack = "Charge";
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y1';
|
$dataset->yAxisID = 'y1';
|
||||||
} else {
|
} else {
|
||||||
$dataset->stack = "Consumers";
|
$dataset->stack = "Consumers";
|
||||||
if ($filled == 0) {
|
if ($filled == 0) {
|
||||||
$filled = 1;
|
$filled = 1;
|
||||||
$dataset->fill = "origin";
|
$dataset->fill = "origin";
|
||||||
} else {
|
} else {
|
||||||
$dataset->fill = "-1";
|
$dataset->fill = "-1";
|
||||||
}
|
}
|
||||||
$dataset->yAxisID = 'y';
|
$dataset->yAxisID = 'y';
|
||||||
}
|
}
|
||||||
$dataset->label = $name;
|
$dataset->label = $name;
|
||||||
|
|
||||||
$dataset->data[] = $value;
|
$dataset->data[] = $value;
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
$ii++;
|
$ii++;
|
||||||
} else {
|
} else {
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ($row = $result->fetch_assoc()) {
|
while ($row = $result->fetch_assoc()) {
|
||||||
$ii = 1;
|
$ii = 1;
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
if ($name != "time") {
|
if ($name != "time") {
|
||||||
$obj->datasets[$ii]->data[] = $value;
|
$obj->datasets[$ii]->data[] = $value;
|
||||||
$ii++;
|
$ii++;
|
||||||
} else {
|
} else {
|
||||||
if(($value * 1000) < $nextSimTimestamp){
|
if(($value * 1000) < $nextSimTimestamp){
|
||||||
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
||||||
}else{
|
}else{
|
||||||
$row1 = $rownext;
|
$row1 = $rownext;
|
||||||
$rownext = $simRes->fetch_assoc();
|
$rownext = $simRes->fetch_assoc();
|
||||||
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
$nextSimTimestamp = $rownext["time"]*1000 + 30*60*1000;
|
||||||
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
$obj->datasets[0]->data[] = $row1["Vorhersage"];
|
||||||
}
|
}
|
||||||
$obj->labels[] = $value * 1000;
|
$obj->labels[] = $value * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$obj->labels[] = $nextSimTimestamp; //Draw future forecast
|
$obj->labels[] = $nextSimTimestamp; //Draw future forecast
|
||||||
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
||||||
while($rownext = $simRes->fetch_assoc()){
|
while($rownext = $simRes->fetch_assoc()){
|
||||||
$obj->labels[] = $rownext["time"]*1000 + 30*60*1000;
|
$obj->labels[] = $rownext["time"]*1000 + 30*60*1000;
|
||||||
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
$obj->datasets[0]->data[] = $rownext["Vorhersage"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//header('Content-Type: application/json');
|
//header('Content-Type: application/json');
|
||||||
echo json_encode($obj);
|
echo json_encode($obj);
|
||||||
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
||||||
?>
|
?>
|
||||||
+211
-211
@@ -1,211 +1,211 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
require_once("../restricted/costs.php");
|
require_once("../restricted/costs.php");
|
||||||
if(!isset($_GET["year"])){
|
if(!isset($_GET["year"])){
|
||||||
$_GET["year"] = date("Y");
|
$_GET["year"] = date("Y");
|
||||||
}else{
|
}else{
|
||||||
$_GET["year"] = intval($_GET["year"]);
|
$_GET["year"] = intval($_GET["year"]);
|
||||||
}
|
}
|
||||||
if($_GET["year"] < 2018 || $_GET["year"] > date("Y")){
|
if($_GET["year"] < 2018 || $_GET["year"] > date("Y")){
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
if(isset($_GET["type"])){
|
if(isset($_GET["type"])){
|
||||||
if(strtolower($_GET["type"]) =="lastyear"){
|
if(strtolower($_GET["type"]) =="lastyear"){
|
||||||
$_GET["year"] = date("Y")-1;
|
$_GET["year"] = date("Y")-1;
|
||||||
}elseif(strtolower($_GET["type"]) =="prelastyear"){
|
}elseif(strtolower($_GET["type"]) =="prelastyear"){
|
||||||
$_GET["year"] = date("Y")-2;
|
$_GET["year"] = date("Y")-2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Jahresstatistik als eine Abfrage - jetzt auf stats_daily.
|
* Die Jahresstatistik als eine Abfrage - jetzt auf stats_daily.
|
||||||
*
|
*
|
||||||
* Gerechnet wird nicht mehr ueber die Rohdaten aus EnergyFlow, sondern ueber
|
* Gerechnet wird nicht mehr ueber die Rohdaten aus EnergyFlow, sondern ueber
|
||||||
* die Tageswerte. Zwei Gruende:
|
* die Tageswerte. Zwei Gruende:
|
||||||
*
|
*
|
||||||
* 1. Die Rohdaten werden nach zwoelf Monaten ausgeduennt bzw. ins
|
* 1. Die Rohdaten werden nach zwoelf Monaten ausgeduennt bzw. ins
|
||||||
* Stundenarchiv verschoben (solarlog-rollup.sh). Eine Kachel, die auf
|
* Stundenarchiv verschoben (solarlog-rollup.sh). Eine Kachel, die auf
|
||||||
* EnergyFlow rechnet, zeigt fuer alte Jahre danach zu wenig an.
|
* EnergyFlow rechnet, zeigt fuer alte Jahre danach zu wenig an.
|
||||||
* 2. Ein Jahr sind 365 Zeilen statt rund 105.000.
|
* 2. Ein Jahr sind 365 Zeilen statt rund 105.000.
|
||||||
*
|
*
|
||||||
* Die Tarife bleiben unveraendert: je Zeile ein Stichtag, das Ende holt
|
* Die Tarife bleiben unveraendert: je Zeile ein Stichtag, das Ende holt
|
||||||
* LEAD() beim naechsten. Weil ein Stichtag ein DATUM ist, kann ein Tag nie
|
* LEAD() beim naechsten. Weil ein Stichtag ein DATUM ist, kann ein Tag nie
|
||||||
* zwei Tarife haben - die Verdichtung auf Tage verliert hier also nichts.
|
* zwei Tarife haben - die Verdichtung auf Tage verliert hier also nichts.
|
||||||
*
|
*
|
||||||
* Was in stats_daily vorberechnet sein MUSS, weil es eine Bedingung je
|
* Was in stats_daily vorberechnet sein MUSS, weil es eine Bedingung je
|
||||||
* Messwert auswertet und sich aus Tagessummen nicht rekonstruieren laesst:
|
* Messwert auswertet und sich aus Tagessummen nicht rekonstruieren laesst:
|
||||||
*
|
*
|
||||||
* eigenverbrauch_kwh pv - Einspeisung - Batterieladung
|
* eigenverbrauch_kwh pv - Einspeisung - Batterieladung
|
||||||
* - Heizstab + Batterieentladung. Dazu addiert
|
* - Heizstab + Batterieentladung. Dazu addiert
|
||||||
* wird pv_fehlbetrag_kwh: bei Ausfall einzelner
|
* wird pv_fehlbetrag_kwh: bei Ausfall einzelner
|
||||||
* Wechselrichter fehlt deren Beitrag in pv, alle
|
* Wechselrichter fehlt deren Beitrag in pv, alle
|
||||||
* Senken sind dagegen gemessen - die Differenz
|
* Senken sind dagegen gemessen - die Differenz
|
||||||
* faellt sonst zu klein oder negativ aus.
|
* faellt sonst zu klein oder negativ aus.
|
||||||
* heizstab_solar_kwh Heizstab, anteilig nach autonomy
|
* heizstab_solar_kwh Heizstab, anteilig nach autonomy
|
||||||
* auto_*_solar_kwh Wallbox, anteilig nach autonomy - dem vom
|
* auto_*_solar_kwh Wallbox, anteilig nach autonomy - dem vom
|
||||||
* Geraet in feiner Aufloesung gerechneten
|
* Geraet in feiner Aufloesung gerechneten
|
||||||
* Nicht-Netz-Anteil der Zufuhr
|
* Nicht-Netz-Anteil der Zufuhr
|
||||||
*
|
*
|
||||||
* Einheiten sind in stats_daily bereits bereinigt: PL*_EV und PL*_EVog
|
* Einheiten sind in stats_daily bereits bereinigt: PL*_EV und PL*_EVog
|
||||||
* liefern Kilowatt, alle uebrigen Leistungsspalten Watt. In der alten
|
* liefern Kilowatt, alle uebrigen Leistungsspalten Watt. In der alten
|
||||||
* Abfrage stand das als /12 gegen /12000 nebeneinander.
|
* Abfrage stand das als /12 gegen /12000 nebeneinander.
|
||||||
*
|
*
|
||||||
* Die Solaranteile teilen jetzt durch NULLIF(...): ohne Ladung im Jahr kam
|
* Die Solaranteile teilen jetzt durch NULLIF(...): ohne Ladung im Jahr kam
|
||||||
* vorher 0/0 heraus.
|
* vorher 0/0 heraus.
|
||||||
*
|
*
|
||||||
* "Ersparnis Batterie" rechnet den Nutzen netto: was die entladene Energie
|
* "Ersparnis Batterie" rechnet den Nutzen netto: was die entladene Energie
|
||||||
* aus dem Netz gekostet haette, abzueglich der Verguetung, die fuer den zum
|
* aus dem Netz gekostet haette, abzueglich der Verguetung, die fuer den zum
|
||||||
* Laden verwendeten Strom entgangen ist. Brutto waere die Kachel zu
|
* Laden verwendeten Strom entgangen ist. Brutto waere die Kachel zu
|
||||||
* freundlich - der Speicher ist nicht umsonst zu fuellen.
|
* freundlich - der Speicher ist nicht umsonst zu fuellen.
|
||||||
*
|
*
|
||||||
* Achtung beim Lesen: "Verbrauchsersparnis" ist der GESAMTE Eigenverbrauch
|
* Achtung beim Lesen: "Verbrauchsersparnis" ist der GESAMTE Eigenverbrauch
|
||||||
* zum Strompreis. "Ersparnis Batterie" und "Ersparnis Solarladung" sind
|
* zum Strompreis. "Ersparnis Batterie" und "Ersparnis Solarladung" sind
|
||||||
* Teilmengen davon, nach Herkunft bzw. Verwendung herausgegriffen. Sie
|
* Teilmengen davon, nach Herkunft bzw. Verwendung herausgegriffen. Sie
|
||||||
* duerfen nicht addiert werden.
|
* duerfen nicht addiert werden.
|
||||||
*
|
*
|
||||||
* Der Grundpreis fehlt weiterhin bewusst - er haengt an den Tagen des
|
* Der Grundpreis fehlt weiterhin bewusst - er haengt an den Tagen des
|
||||||
* Jahres, nicht an einem Messwert, und kommt aus grundpreisImJahr() dazu.
|
* Jahres, nicht an einem Messwert, und kommt aus grundpreisImJahr() dazu.
|
||||||
*/
|
*/
|
||||||
function statistikAbfrage($jahr)
|
function statistikAbfrage($jahr)
|
||||||
{
|
{
|
||||||
return "WITH
|
return "WITH
|
||||||
preis AS (SELECT active_date, cost, gain,
|
preis AS (SELECT active_date, cost, gain,
|
||||||
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM gridCosts),
|
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM gridCosts),
|
||||||
gas AS (SELECT active_date, cost, kwhPerLitre,
|
gas AS (SELECT active_date, cost, kwhPerLitre,
|
||||||
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM gasCosts),
|
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM gasCosts),
|
||||||
sprit AS (SELECT active_date, cost, lPer100km, kwhPer100km,
|
sprit AS (SELECT active_date, cost, lPer100km, kwhPer100km,
|
||||||
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM fuelCosts)
|
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM fuelCosts)
|
||||||
SELECT
|
SELECT
|
||||||
SUM(d.netz_bezug_kwh) AS 'Stromverbrauch',
|
SUM(d.netz_bezug_kwh) AS 'Stromverbrauch',
|
||||||
SUM(d.netz_bezug_kwh * preis.cost) AS 'Stromkosten',
|
SUM(d.netz_bezug_kwh * preis.cost) AS 'Stromkosten',
|
||||||
SUM((d.eigenverbrauch_kwh + IFNULL(d.pv_fehlbetrag_kwh,0)) * preis.cost) AS 'Verbrauchsersparnis',
|
SUM((d.eigenverbrauch_kwh + IFNULL(d.pv_fehlbetrag_kwh,0)) * preis.cost) AS 'Verbrauchsersparnis',
|
||||||
SUM(d.netz_einsp_kwh * preis.gain) AS 'Einspeisevergütung',
|
SUM(d.netz_einsp_kwh * preis.gain) AS 'Einspeisevergütung',
|
||||||
SUM(d.heizstab_solar_kwh * gas.cost / NULLIF(gas.kwhPerLitre,0)) AS 'Ersparnis Heizung',
|
SUM(d.heizstab_solar_kwh * gas.cost / NULLIF(gas.kwhPerLitre,0)) AS 'Ersparnis Heizung',
|
||||||
SUM(d.batt_entladen_kwh * preis.cost - d.batt_laden_kwh * preis.gain) AS 'Ersparnis Batterie',
|
SUM(d.batt_entladen_kwh * preis.cost - d.batt_laden_kwh * preis.gain) AS 'Ersparnis Batterie',
|
||||||
SUM(d.autarkie_avg * d.stunden_erfasst) / NULLIF(SUM(d.stunden_erfasst),0) AS 'Ø Autarkie',
|
SUM(d.autarkie_avg * d.stunden_erfasst) / NULLIF(SUM(d.stunden_erfasst),0) AS 'Ø Autarkie',
|
||||||
SUM(d.auto_eg_kwh) AS 'Autoladung ges. EG',
|
SUM(d.auto_eg_kwh) AS 'Autoladung ges. EG',
|
||||||
SUM(d.auto_eg_solar_kwh) / NULLIF(SUM(d.auto_eg_kwh),0) * 100 AS 'Autoladung Solar EG',
|
SUM(d.auto_eg_solar_kwh) / NULLIF(SUM(d.auto_eg_kwh),0) * 100 AS 'Autoladung Solar EG',
|
||||||
SUM(d.auto_eg_solar_kwh * preis.cost) AS 'Ersparnis Solarladung EG',
|
SUM(d.auto_eg_solar_kwh * preis.cost) AS 'Ersparnis Solarladung EG',
|
||||||
SUM(d.auto_eg_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)) AS 'Benzin gespart EG',
|
SUM(d.auto_eg_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)) AS 'Benzin gespart EG',
|
||||||
SUM(d.auto_eg_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0) * sprit.cost) AS 'Ersparnis Benzin EG',
|
SUM(d.auto_eg_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0) * sprit.cost) AS 'Ersparnis Benzin EG',
|
||||||
SUM(d.auto_og_kwh) AS 'Autoladung ges. OG',
|
SUM(d.auto_og_kwh) AS 'Autoladung ges. OG',
|
||||||
SUM(d.auto_og_solar_kwh) / NULLIF(SUM(d.auto_og_kwh),0) * 100 AS 'Autoladung Solar OG',
|
SUM(d.auto_og_solar_kwh) / NULLIF(SUM(d.auto_og_kwh),0) * 100 AS 'Autoladung Solar OG',
|
||||||
SUM(d.auto_og_solar_kwh * preis.cost) AS 'Ersparnis Solarladung OG',
|
SUM(d.auto_og_solar_kwh * preis.cost) AS 'Ersparnis Solarladung OG',
|
||||||
SUM(d.auto_og_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)) AS 'Benzin gespart OG',
|
SUM(d.auto_og_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)) AS 'Benzin gespart OG',
|
||||||
SUM(d.auto_og_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0) * sprit.cost) AS 'Ersparnis Benzin OG'
|
SUM(d.auto_og_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0) * sprit.cost) AS 'Ersparnis Benzin OG'
|
||||||
FROM stats_daily d
|
FROM stats_daily d
|
||||||
JOIN preis ON d.tag >= preis.active_date AND (preis.folgt IS NULL OR d.tag < preis.folgt)
|
JOIN preis ON d.tag >= preis.active_date AND (preis.folgt IS NULL OR d.tag < preis.folgt)
|
||||||
LEFT JOIN gas ON d.tag >= gas.active_date AND (gas.folgt IS NULL OR d.tag < gas.folgt)
|
LEFT JOIN gas ON d.tag >= gas.active_date AND (gas.folgt IS NULL OR d.tag < gas.folgt)
|
||||||
LEFT JOIN sprit ON d.tag >= sprit.active_date AND (sprit.folgt IS NULL OR d.tag < sprit.folgt)
|
LEFT JOIN sprit ON d.tag >= sprit.active_date AND (sprit.folgt IS NULL OR d.tag < sprit.folgt)
|
||||||
WHERE YEAR(d.tag) = " . intval($jahr) . ";";
|
WHERE YEAR(d.tag) = " . intval($jahr) . ";";
|
||||||
}
|
}
|
||||||
|
|
||||||
$Query = statistikAbfrage($_GET["year"]);
|
$Query = statistikAbfrage($_GET["year"]);
|
||||||
$PrevQuery = statistikAbfrage($_GET["year"] - 1);
|
$PrevQuery = statistikAbfrage($_GET["year"] - 1);
|
||||||
|
|
||||||
// Reihenfolge wie in der Abfrage, mit "Verg.+Einsp. Strom" an Stelle 4.
|
// Reihenfolge wie in der Abfrage, mit "Verg.+Einsp. Strom" an Stelle 4.
|
||||||
$units = Array("kWh","€","€","€","€","€","€","%",
|
$units = Array("kWh","€","€","€","€","€","€","%",
|
||||||
"kWh","%","€","L","€",
|
"kWh","%","€","L","€",
|
||||||
"kWh","%","€","L","€");
|
"kWh","%","€","L","€");
|
||||||
// Nur bei Verbrauch und Kosten ist weniger besser.
|
// Nur bei Verbrauch und Kosten ist weniger besser.
|
||||||
$LessIsBetter = array_merge([true, true], array_fill(0, count($units) - 2, false));
|
$LessIsBetter = array_merge([true, true], array_fill(0, count($units) - 2, false));
|
||||||
|
|
||||||
function array_insert($array,$values,$offset) {
|
function array_insert($array,$values,$offset) {
|
||||||
return array_slice($array, 0, $offset, true) + $values + array_slice($array, $offset, NULL, true);
|
return array_slice($array, 0, $offset, true) + $values + array_slice($array, $offset, NULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
|
|
||||||
$html = "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
|
$html = "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
|
||||||
$mysql = solarDb();
|
$mysql = solarDb();
|
||||||
$Res = mysqli_query($mysql,$Query);
|
$Res = mysqli_query($mysql,$Query);
|
||||||
$ResPrev = mysqli_query($mysql,$PrevQuery);
|
$ResPrev = mysqli_query($mysql,$PrevQuery);
|
||||||
if(!$Res || !$ResPrev){
|
if(!$Res || !$ResPrev){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
if ($Res->num_rows > 0) {
|
if ($Res->num_rows > 0) {
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$row = $Res->fetch_assoc();
|
$row = $Res->fetch_assoc();
|
||||||
$rowPrev = $ResPrev->fetch_assoc();
|
$rowPrev = $ResPrev->fetch_assoc();
|
||||||
// Der Grundpreis faellt unabhaengig vom Verbrauch an und laesst sich
|
// Der Grundpreis faellt unabhaengig vom Verbrauch an und laesst sich
|
||||||
// deshalb nicht aus den Messwerten summieren.
|
// deshalb nicht aus den Messwerten summieren.
|
||||||
$row["Stromkosten"] += grundpreisImJahr($_GET["year"]);
|
$row["Stromkosten"] += grundpreisImJahr($_GET["year"]);
|
||||||
$rowPrev["Stromkosten"] += grundpreisImJahr($_GET["year"] - 1);
|
$rowPrev["Stromkosten"] += grundpreisImJahr($_GET["year"] - 1);
|
||||||
//$row["Vergütung+Einsparung Strom"] = $row["Einspeisevergütung"]+$row["Verbrauchsersparnis"];
|
//$row["Vergütung+Einsparung Strom"] = $row["Einspeisevergütung"]+$row["Verbrauchsersparnis"];
|
||||||
$row = array_insert($row,["Verg.+Einsp. Strom" => $row["Einspeisevergütung"]+$row["Verbrauchsersparnis"]],4);
|
$row = array_insert($row,["Verg.+Einsp. Strom" => $row["Einspeisevergütung"]+$row["Verbrauchsersparnis"]],4);
|
||||||
$rowPrev = array_insert($rowPrev,["Verg.+Einsp. Strom" => $rowPrev["Einspeisevergütung"]+$rowPrev["Verbrauchsersparnis"]],4);
|
$rowPrev = array_insert($rowPrev,["Verg.+Einsp. Strom" => $rowPrev["Einspeisevergütung"]+$rowPrev["Verbrauchsersparnis"]],4);
|
||||||
$i = 0;
|
$i = 0;
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
if(str_starts_with($name,"Autoladung ges.")){
|
if(str_starts_with($name,"Autoladung ges.")){
|
||||||
$html .= "</div><hr class='mt-3 mb-3 border-light' />";
|
$html .= "</div><hr class='mt-3 mb-3 border-light' />";
|
||||||
$html .= "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
|
$html .= "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
|
||||||
}
|
}
|
||||||
/*$html .= "<div class='col-12 col-sm-4 col-md-2 col-xl-1'>
|
/*$html .= "<div class='col-12 col-sm-4 col-md-2 col-xl-1'>
|
||||||
<div class='info-box'>
|
<div class='info-box'>
|
||||||
<div class='info-box-content'>
|
<div class='info-box-content'>
|
||||||
<span class='info-box-text'>".$name."</span>
|
<span class='info-box-text'>".$name."</span>
|
||||||
<span class='info-box-number'>".
|
<span class='info-box-number'>".
|
||||||
number_format(floatval($value),2,",",".")
|
number_format(floatval($value),2,",",".")
|
||||||
."<small>".$units[$i++]."</small>
|
."<small>".$units[$i++]."</small>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.info-box-content -->
|
<!-- /.info-box-content -->
|
||||||
</div>
|
</div>
|
||||||
</div>";*/
|
</div>";*/
|
||||||
$html .= "<div class='col'>
|
$html .= "<div class='col'>
|
||||||
<div class='card stats border-0'>
|
<div class='card stats border-0'>
|
||||||
<div class='card-body bg-dark bg-gradient rounded-top pb-1 pt-1'>
|
<div class='card-body bg-dark bg-gradient rounded-top pb-1 pt-1'>
|
||||||
".$name."
|
".$name."
|
||||||
</div>
|
</div>
|
||||||
<div class='card-footer text-center'>";
|
<div class='card-footer text-center'>";
|
||||||
if($_GET["year"] != date("Y")){
|
if($_GET["year"] != date("Y")){
|
||||||
if($LessIsBetter[$i]){
|
if($LessIsBetter[$i]){
|
||||||
if($value == 0)
|
if($value == 0)
|
||||||
$dev=100;
|
$dev=100;
|
||||||
else
|
else
|
||||||
$dev = $rowPrev[$name]/$value;
|
$dev = $rowPrev[$name]/$value;
|
||||||
//$first = round($rowPrev[$name]*10/$rowPrev[$name]); //allow for 10% deviation for displaying no tendency
|
//$first = round($rowPrev[$name]*10/$rowPrev[$name]); //allow for 10% deviation for displaying no tendency
|
||||||
//$second = round($value); //allow for 10% deviation for displaying no tendency
|
//$second = round($value); //allow for 10% deviation for displaying no tendency
|
||||||
$arrowGood = "down";
|
$arrowGood = "down";
|
||||||
$arrowBad = "up";
|
$arrowBad = "up";
|
||||||
}else{
|
}else{
|
||||||
if($rowPrev[$name] == 0)
|
if($rowPrev[$name] == 0)
|
||||||
$dev=100;
|
$dev=100;
|
||||||
else
|
else
|
||||||
$dev = $value/$rowPrev[$name];
|
$dev = $value/$rowPrev[$name];
|
||||||
//$first = round($value); //allow for 10% deviation for displaying no tendency
|
//$first = round($value); //allow for 10% deviation for displaying no tendency
|
||||||
//$second = round($rowPrev[$name]); //allow for 10% deviation for displaying no tendency
|
//$second = round($rowPrev[$name]); //allow for 10% deviation for displaying no tendency
|
||||||
$arrowGood = "up";
|
$arrowGood = "up";
|
||||||
$arrowBad = "down";
|
$arrowBad = "down";
|
||||||
}
|
}
|
||||||
if($dev > 1.05){
|
if($dev > 1.05){
|
||||||
$html .= "<span class='float-right text-success-emphasis'>
|
$html .= "<span class='float-right text-success-emphasis'>
|
||||||
<i class='bi bi-arrow-".$arrowGood."' style='font-size: 0.9em;'></i> ";
|
<i class='bi bi-arrow-".$arrowGood."' style='font-size: 0.9em;'></i> ";
|
||||||
}elseif($dev < 0.95){
|
}elseif($dev < 0.95){
|
||||||
$html .= "<span class='float-right text-danger-emphasis'>
|
$html .= "<span class='float-right text-danger-emphasis'>
|
||||||
<i class='bi bi-arrow-".$arrowBad."' style='font-size: 0.9em;'></i> ";
|
<i class='bi bi-arrow-".$arrowBad."' style='font-size: 0.9em;'></i> ";
|
||||||
}else{
|
}else{
|
||||||
$html .= "<span class='float-right'>
|
$html .= "<span class='float-right'>
|
||||||
<i class='bi bi-arrow-right' style='font-size: 0.9em;'></i> ";
|
<i class='bi bi-arrow-right' style='font-size: 0.9em;'></i> ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
$html .= "<span class='float-right'>";
|
$html .= "<span class='float-right'>";
|
||||||
}
|
}
|
||||||
$html .= number_format(floatval($value),2,",",".")
|
$html .= number_format(floatval($value),2,",",".")
|
||||||
."<small> ".$units[$i]."</small></span></div>
|
."<small> ".$units[$i]."</small></span></div>
|
||||||
<!-- /.info-box-content -->
|
<!-- /.info-box-content -->
|
||||||
</div>
|
</div>
|
||||||
</div>";
|
</div>";
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$html .= "</div>";
|
$html .= "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//header('Content-Type: application/json');
|
//header('Content-Type: application/json');
|
||||||
echo $html;
|
echo $html;
|
||||||
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
||||||
|
|||||||
+65
-65
@@ -1,65 +1,65 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
if(!isset($_GET["TO"])){
|
if(!isset($_GET["TO"])){
|
||||||
$_GET["TO"] = 12;
|
$_GET["TO"] = 12;
|
||||||
}
|
}
|
||||||
$_GET["TO"] = intval($_GET["TO"]);
|
$_GET["TO"] = intval($_GET["TO"]);
|
||||||
if(!isset($_GET["FROM"])){
|
if(!isset($_GET["FROM"])){
|
||||||
$_GET["FROM"] = -24;
|
$_GET["FROM"] = -24;
|
||||||
}
|
}
|
||||||
$_GET["FROM"] = intval($_GET["FROM"]);
|
$_GET["FROM"] = intval($_GET["FROM"]);
|
||||||
|
|
||||||
$Query = "SELECT
|
$Query = "SELECT
|
||||||
UNIX_TIMESTAMP(CONCAT(date,' ',sunrise)) AS sunrise,
|
UNIX_TIMESTAMP(CONCAT(date,' ',sunrise)) AS sunrise,
|
||||||
UNIX_TIMESTAMP(CONCAT(date,' ',sunset)) AS sunset
|
UNIX_TIMESTAMP(CONCAT(date,' ',sunset)) AS sunset
|
||||||
FROM solarLog.daylight
|
FROM solarLog.daylight
|
||||||
WHERE date BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"]-24)." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
WHERE date BETWEEN DATE_ADD(NOW(),INTERVAL ".($_GET["FROM"]-24)." HOUR) and DATE_ADD(NOW(),INTERVAL ".$_GET["TO"]." HOUR)
|
||||||
ORDER BY date";
|
ORDER BY date";
|
||||||
|
|
||||||
$linecolors["Solarleistung"] = "#FFFF00";
|
$linecolors["Solarleistung"] = "#FFFF00";
|
||||||
$linecolors["UG"] = "#FFaa00";
|
$linecolors["UG"] = "#FFaa00";
|
||||||
$linecolors["OG"] = "#FF4400";
|
$linecolors["OG"] = "#FF4400";
|
||||||
$linecolors["Auto UG"] = "#00aaFF";
|
$linecolors["Auto UG"] = "#00aaFF";
|
||||||
$linecolors["Auto OG"] = "#0044FF";
|
$linecolors["Auto OG"] = "#0044FF";
|
||||||
$linecolors["Heizstab"] = "#FF0000";
|
$linecolors["Heizstab"] = "#FF0000";
|
||||||
$linecolors["Batterieladung"] = "#00aa00";
|
$linecolors["Batterieladung"] = "#00aa00";
|
||||||
$linecolors["Einspeisung"] = "#b0b0b0";
|
$linecolors["Einspeisung"] = "#b0b0b0";
|
||||||
$linecolors["Ladestand"] = "#00aa00";
|
$linecolors["Ladestand"] = "#00aa00";
|
||||||
$linecolors["Vorhersage"] = "#2222FF";
|
$linecolors["Vorhersage"] = "#2222FF";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||||
$result = mysqli_query($mysql, $Query);
|
$result = mysqli_query($mysql, $Query);
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
echo "Error:<br>" . mysqli_error($mysql) . "<br />";
|
echo "Error:<br>" . mysqli_error($mysql) . "<br />";
|
||||||
}
|
}
|
||||||
//$obj[] = (object)[]; // Cast empty array to object
|
//$obj[] = (object)[]; // Cast empty array to object
|
||||||
|
|
||||||
while ($row = $result->fetch_assoc()) {
|
while ($row = $result->fetch_assoc()) {
|
||||||
$ii = 1;
|
$ii = 1;
|
||||||
$anno = (object)[];
|
$anno = (object)[];
|
||||||
if($row["sunrise"] < (time()+$_GET["FROM"]*60*60)){
|
if($row["sunrise"] < (time()+$_GET["FROM"]*60*60)){
|
||||||
$anno->xMin = (time()+$_GET["FROM"]*60*60)*1000;
|
$anno->xMin = (time()+$_GET["FROM"]*60*60)*1000;
|
||||||
}else{
|
}else{
|
||||||
$anno->xMin = $row["sunrise"]*1000;
|
$anno->xMin = $row["sunrise"]*1000;
|
||||||
}
|
}
|
||||||
if($row["sunset"] > (time()+$_GET["TO"]*60*60)){
|
if($row["sunset"] > (time()+$_GET["TO"]*60*60)){
|
||||||
$anno->xMax = round(time()+$_GET["TO"]*60*60)*1000;
|
$anno->xMax = round(time()+$_GET["TO"]*60*60)*1000;
|
||||||
}else{
|
}else{
|
||||||
$anno->xMax = $row["sunset"]*1000;
|
$anno->xMax = $row["sunset"]*1000;
|
||||||
}
|
}
|
||||||
$anno->borderWidth = 0;
|
$anno->borderWidth = 0;
|
||||||
if($row["sunset"] > (time()+$_GET["FROM"]*60*60)){
|
if($row["sunset"] > (time()+$_GET["FROM"]*60*60)){
|
||||||
$obj[] = clone $anno;
|
$obj[] = clone $anno;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//header('Content-Type: application/json');
|
//header('Content-Type: application/json');
|
||||||
echo json_encode($obj);
|
echo json_encode($obj);
|
||||||
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
||||||
|
|||||||
+65
-65
@@ -1,65 +1,65 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
|
|
||||||
$waterQuery = "SELECT UNIX_TIMESTAMP(datetime) AS time, rate AS 'Wasserverbrauch'
|
$waterQuery = "SELECT UNIX_TIMESTAMP(datetime) AS time, rate AS 'Wasserverbrauch'
|
||||||
FROM solarLog.wasser
|
FROM solarLog.wasser
|
||||||
WHERE wasser.datetime BETWEEN DATE_SUB(NOW(),INTERVAL 24 HOUR) and NOW()
|
WHERE wasser.datetime BETWEEN DATE_SUB(NOW(),INTERVAL 24 HOUR) and NOW()
|
||||||
ORDER BY wasser.datetime";
|
ORDER BY wasser.datetime";
|
||||||
|
|
||||||
$linecolors["Speicher oben"] = "#FF5500";
|
$linecolors["Speicher oben"] = "#FF5500";
|
||||||
$linecolors["Speicher mitte"] = "#FFaa00";
|
$linecolors["Speicher mitte"] = "#FFaa00";
|
||||||
$linecolors["Speicher unten"] = "#FFFF00";
|
$linecolors["Speicher unten"] = "#FFFF00";
|
||||||
$linecolors["Therme Vorlauf Fußboden"] = "#bb0000";
|
$linecolors["Therme Vorlauf Fußboden"] = "#bb0000";
|
||||||
$linecolors["Therme Rücklauf"] = "#ee0000";
|
$linecolors["Therme Rücklauf"] = "#ee0000";
|
||||||
$linecolors["Heizstab Vorlauf"] = "#9900bb";
|
$linecolors["Heizstab Vorlauf"] = "#9900bb";
|
||||||
$linecolors["Heizstab Rücklauf"] = "#8800aa";
|
$linecolors["Heizstab Rücklauf"] = "#8800aa";
|
||||||
$linecolors["Fußboden Vorlauf"] = "#00FF00";
|
$linecolors["Fußboden Vorlauf"] = "#00FF00";
|
||||||
$linecolors["Fußboden Rücklauf"] = "#00aa00";
|
$linecolors["Fußboden Rücklauf"] = "#00aa00";
|
||||||
$linecolors["Wasserverbrauch"] = "#2222FF";
|
$linecolors["Wasserverbrauch"] = "#2222FF";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||||
$waterRes = mysqli_query($mysql,$waterQuery);
|
$waterRes = mysqli_query($mysql,$waterQuery);
|
||||||
if(!$waterRes){
|
if(!$waterRes){
|
||||||
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
$obj = (object)[]; // Cast empty array to object
|
$obj = (object)[]; // Cast empty array to object
|
||||||
$obj->labels = [];
|
$obj->labels = [];
|
||||||
$obj->datasets = [];
|
$obj->datasets = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$filled = 0;
|
$filled = 0;
|
||||||
if ($waterRes->num_rows > 1) {
|
if ($waterRes->num_rows > 1) {
|
||||||
$dataset = (object)[];
|
$dataset = (object)[];
|
||||||
$row = $waterRes->fetch_assoc();
|
$row = $waterRes->fetch_assoc();
|
||||||
$dataset->borderColor = $linecolors["Wasserverbrauch"];
|
$dataset->borderColor = $linecolors["Wasserverbrauch"];
|
||||||
$dataset->backgroundColor = $linecolors["Wasserverbrauch"]."00";
|
$dataset->backgroundColor = $linecolors["Wasserverbrauch"]."00";
|
||||||
$dataset->borderWidth=2;
|
$dataset->borderWidth=2;
|
||||||
$dataset->pointRadius= 0;
|
$dataset->pointRadius= 0;
|
||||||
$dataset->pointHoverRadius= 5;
|
$dataset->pointHoverRadius= 5;
|
||||||
$dataset->tension=0.2;
|
$dataset->tension=0.2;
|
||||||
$dataset->fill = "none";
|
$dataset->fill = "none";
|
||||||
$dataset->yAxisID = 'y1';
|
$dataset->yAxisID = 'y1';
|
||||||
$dataset->label = "Wasserverbrauch";
|
$dataset->label = "Wasserverbrauch";
|
||||||
$pt = (object)[];
|
$pt = (object)[];
|
||||||
$pt->x = $row["time"]*1000;
|
$pt->x = $row["time"]*1000;
|
||||||
$pt->y = $row["Wasserverbrauch"];
|
$pt->y = $row["Wasserverbrauch"];
|
||||||
$dataset->data[] = clone $pt;
|
$dataset->data[] = clone $pt;
|
||||||
while ($row = $waterRes->fetch_assoc()) {
|
while ($row = $waterRes->fetch_assoc()) {
|
||||||
$pt = (object)[];
|
$pt = (object)[];
|
||||||
$pt->x = $row["time"]*1000;
|
$pt->x = $row["time"]*1000;
|
||||||
$pt->y = $row["Wasserverbrauch"];
|
$pt->y = $row["Wasserverbrauch"];
|
||||||
$dataset->data[] = clone $pt;
|
$dataset->data[] = clone $pt;
|
||||||
}
|
}
|
||||||
$obj->datasets[] = clone $dataset;
|
$obj->datasets[] = clone $dataset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//header('Content-Type: application/json');
|
//header('Content-Type: application/json');
|
||||||
echo json_encode($obj);
|
echo json_encode($obj);
|
||||||
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|
||||||
|
|||||||
+57
-57
@@ -1,57 +1,57 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
|
|
||||||
|
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
$close = 0;
|
$close = 0;
|
||||||
$hostname = "192.168.179.169";
|
$hostname = "192.168.179.169";
|
||||||
|
|
||||||
|
|
||||||
if (isset($_POST["setMode"]) || isset($_GET["setMode"])) {
|
if (isset($_POST["setMode"]) || isset($_GET["setMode"])) {
|
||||||
//send new value
|
//send new value
|
||||||
$_POST["setMode"] = intval($_POST["setMode"]) + intval($_GET["setMode"]);
|
$_POST["setMode"] = intval($_POST["setMode"]) + intval($_GET["setMode"]);
|
||||||
if($_POST["setMode"] > 0 && $_POST["setMode"] < 60){
|
if($_POST["setMode"] > 0 && $_POST["setMode"] < 60){
|
||||||
$pwr = $_POST["setMode"]*100;
|
$pwr = $_POST["setMode"]*100;
|
||||||
$headers[] = "GET /set?mode=man&pwr=".$pwr." HTTP/1.1";
|
$headers[] = "GET /set?mode=man&pwr=".$pwr." HTTP/1.1";
|
||||||
$headers[] = "Host: ".$hostname;
|
$headers[] = "Host: ".$hostname;
|
||||||
$headers[] = "";
|
$headers[] = "";
|
||||||
$remote = fsockopen("tcp://".$hostname, 80, $errno, $errstr, 5);
|
$remote = fsockopen("tcp://".$hostname, 80, $errno, $errstr, 5);
|
||||||
fwrite($remote, implode("\r\n", $headers)."\r\n");
|
fwrite($remote, implode("\r\n", $headers)."\r\n");
|
||||||
$file = '';
|
$file = '';
|
||||||
$file .= fread($remote, 1024);
|
$file .= fread($remote, 1024);
|
||||||
fclose($remote);
|
fclose($remote);
|
||||||
}elseif($_POST["setMode"] == 0){
|
}elseif($_POST["setMode"] == 0){
|
||||||
$headers[] = "GET /set?mode=eco&pwr=0 HTTP/1.1";
|
$headers[] = "GET /set?mode=eco&pwr=0 HTTP/1.1";
|
||||||
$headers[] = "Host: ".$hostname;
|
$headers[] = "Host: ".$hostname;
|
||||||
$headers[] = "";
|
$headers[] = "";
|
||||||
$remote = fsockopen("tcp://".$hostname, 80, $errno, $errstr, 5);
|
$remote = fsockopen("tcp://".$hostname, 80, $errno, $errstr, 5);
|
||||||
fwrite($remote, implode("\r\n", $headers)."\r\n");
|
fwrite($remote, implode("\r\n", $headers)."\r\n");
|
||||||
$file = '';
|
$file = '';
|
||||||
$file .= fread($remote, 1024);
|
$file .= fread($remote, 1024);
|
||||||
fclose($remote);
|
fclose($remote);
|
||||||
}
|
}
|
||||||
$close = 1;
|
$close = 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$close = 1;
|
$close = 1;
|
||||||
}
|
}
|
||||||
if (!$close) {
|
if (!$close) {
|
||||||
echo <<<ENDE
|
echo <<<ENDE
|
||||||
<!--begin::Form-->
|
<!--begin::Form-->
|
||||||
<form id="heater_form">
|
<form id="heater_form">
|
||||||
<div class="col-sm-11">
|
<div class="col-sm-11">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label class="form-label" for="addedCharge">Leistung: </label>
|
<label class="form-label" for="addedCharge">Leistung: </label>
|
||||||
<div style="font-size: 15px;color: #297195;display: inline-block;" id="modal-slider-label">0</div>
|
<div style="font-size: 15px;color: #297195;display: inline-block;" id="modal-slider-label">0</div>
|
||||||
<input type="range" id="modal-slider" name="setMode" class="form-range range-color-track" min="0" max="60" />
|
<input type="range" id="modal-slider" name="setMode" class="form-range range-color-track" min="0" max="60" />
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<span class="max-amount f-16 font-weight-normal darkGray-color text-right mt-1">Auto</span>
|
<span class="max-amount f-16 font-weight-normal darkGray-color text-right mt-1">Auto</span>
|
||||||
<span class="max-amount f-16 font-weight-normal darkGray-color text-right mt-1">6.0 kW</span>
|
<span class="max-amount f-16 font-weight-normal darkGray-color text-right mt-1">6.0 kW</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
ENDE;
|
ENDE;
|
||||||
}
|
}
|
||||||
|
|||||||
+671
-671
File diff suppressed because it is too large
Load Diff
+252
-252
@@ -1,252 +1,252 @@
|
|||||||
<?php
|
<?php
|
||||||
set_time_limit(60);
|
set_time_limit(60);
|
||||||
ob_start();
|
ob_start();
|
||||||
require_once("../helper.php");
|
require_once("../helper.php");
|
||||||
require_once("../restricted/tahoma_EG.php");
|
require_once("../restricted/tahoma_EG.php");
|
||||||
|
|
||||||
// Ohne diese Pruefung liessen sich Rollladen von beliebiger Stelle abfragen
|
// Ohne diese Pruefung liessen sich Rollladen von beliebiger Stelle abfragen
|
||||||
// und verfahren - das Tahoma-Token liegt serverseitig, ein Angreifer braucht
|
// und verfahren - das Tahoma-Token liegt serverseitig, ein Angreifer braucht
|
||||||
// es also gar nicht.
|
// es also gar nicht.
|
||||||
if (!checkLogin()) {
|
if (!checkLogin()) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
//67ebd23e3a61763386d9
|
//67ebd23e3a61763386d9
|
||||||
function getSSLPage($url, $tahoma_token)
|
function getSSLPage($url, $tahoma_token)
|
||||||
{
|
{
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', "Authorization: Bearer ".$tahoma_token));
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', "Authorization: Bearer ".$tahoma_token));
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
|
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
$result = curl_exec($ch);
|
$result = curl_exec($ch);
|
||||||
if (curl_errno($ch)) {
|
if (curl_errno($ch)) {
|
||||||
$error_msg = curl_error($ch);
|
$error_msg = curl_error($ch);
|
||||||
//print $error_msg;
|
//print $error_msg;
|
||||||
}
|
}
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function postSSLPage($url, $body, $tahoma_token)
|
function postSSLPage($url, $body, $tahoma_token)
|
||||||
{
|
{
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', "Authorization: Bearer ".$tahoma_token));
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', "Authorization: Bearer ".$tahoma_token));
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
|
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
$result = curl_exec($ch);
|
$result = curl_exec($ch);
|
||||||
if (curl_errno($ch)) {
|
if (curl_errno($ch)) {
|
||||||
$error_msg = curl_error($ch);
|
$error_msg = curl_error($ch);
|
||||||
//print $error_msg;
|
//print $error_msg;
|
||||||
}
|
}
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function filter_devs($arr,$filter,$prefix=""){
|
function filter_devs($arr,$filter,$prefix=""){
|
||||||
$ret = Array();
|
$ret = Array();
|
||||||
$i = 0;
|
$i = 0;
|
||||||
foreach($arr as $key => $dev){
|
foreach($arr as $key => $dev){
|
||||||
$start = strpos(strtolower($dev["name"]),strtolower($filter));
|
$start = strpos(strtolower($dev["name"]),strtolower($filter));
|
||||||
if($start !== false){
|
if($start !== false){
|
||||||
$dev["name"] = str_replace("u_","",$dev["name"]);
|
$dev["name"] = str_replace("u_","",$dev["name"]);
|
||||||
$ret[$i]["name"] = $prefix.substr($dev["name"],strlen($filter));
|
$ret[$i]["name"] = $prefix.substr($dev["name"],strlen($filter));
|
||||||
$ret[$i]["id"] = $key;
|
$ret[$i]["id"] = $key;
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchForDevice($device){
|
function searchForDevice($device){
|
||||||
$dev = false;
|
$dev = false;
|
||||||
$devices = json_decode(file_get_contents($GLOBALS["tahoma_devlist"]), true);
|
$devices = json_decode(file_get_contents($GLOBALS["tahoma_devlist"]), true);
|
||||||
if (strlen($device) < 3 && intval($device) >= 0 && intval($device) < sizeof($devices)) {
|
if (strlen($device) < 3 && intval($device) >= 0 && intval($device) < sizeof($devices)) {
|
||||||
$dev = $devices[intval($device)];
|
$dev = $devices[intval($device)];
|
||||||
} else if ($key = array_search($device, array_column($devices, "name"))) {
|
} else if ($key = array_search($device, array_column($devices, "name"))) {
|
||||||
$dev = $devices[$key];
|
$dev = $devices[$key];
|
||||||
} else if ($key = array_search($device, array_column($devices, "id"))) {
|
} else if ($key = array_search($device, array_column($devices, "id"))) {
|
||||||
$dev = $devices[$key];
|
$dev = $devices[$key];
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $dev;
|
return $dev;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($_GET["action"] == "devlist") {
|
if ($_GET["action"] == "devlist") {
|
||||||
$jalousien = array();
|
$jalousien = array();
|
||||||
$url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices';
|
$url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices';
|
||||||
$ret = getSSLPage($url,$tahoma_token);
|
$ret = getSSLPage($url,$tahoma_token);
|
||||||
$devices = json_decode($ret, true);
|
$devices = json_decode($ret, true);
|
||||||
$i = 0;
|
$i = 0;
|
||||||
foreach ($devices as $device) {
|
foreach ($devices as $device) {
|
||||||
if ($device["controllableName"] == "io:ExteriorVenetianBlindIOComponent") {
|
if ($device["controllableName"] == "io:ExteriorVenetianBlindIOComponent") {
|
||||||
$jalousien[$i]["name"] = $device["label"];
|
$jalousien[$i]["name"] = $device["label"];
|
||||||
$jalousien[$i]["id"] = $device["deviceURL"];
|
$jalousien[$i]["id"] = $device["deviceURL"];
|
||||||
$i = $i + 1;
|
$i = $i + 1;
|
||||||
echo $device["label"] . "(".$device["deviceURL"].")<br />";
|
echo $device["label"] . "(".$device["deviceURL"].")<br />";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//$file = fopen($tahoma_devlist, 'w');
|
//$file = fopen($tahoma_devlist, 'w');
|
||||||
//fwrite($file, json_encode($jalousien));
|
//fwrite($file, json_encode($jalousien));
|
||||||
//fclose($file);
|
//fclose($file);
|
||||||
echo json_encode($jalousien);
|
echo json_encode($jalousien);
|
||||||
}elseif ($_GET["action"] == "pos" && isset($_GET["device"])) {
|
}elseif ($_GET["action"] == "pos" && isset($_GET["device"])) {
|
||||||
$dev = searchForDevice($_GET["device"])["id"];
|
$dev = searchForDevice($_GET["device"])["id"];
|
||||||
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
||||||
$jalousien = array();
|
$jalousien = array();
|
||||||
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states";
|
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states";
|
||||||
$ret = getSSLPage($url,$tahoma_token);
|
$ret = getSSLPage($url,$tahoma_token);
|
||||||
$states = json_decode($ret, true);
|
$states = json_decode($ret, true);
|
||||||
$out = "{";
|
$out = "{";
|
||||||
foreach ($states as $state){
|
foreach ($states as $state){
|
||||||
if ($state["name"] == "core:SlateOrientationState")
|
if ($state["name"] == "core:SlateOrientationState")
|
||||||
$out .= "\"rotation\":".$state["value"].",";
|
$out .= "\"rotation\":".$state["value"].",";
|
||||||
if ($state["name"] == "core:ClosureState")
|
if ($state["name"] == "core:ClosureState")
|
||||||
$out .= "\"position\":".$state["value"].",";
|
$out .= "\"position\":".$state["value"].",";
|
||||||
}
|
}
|
||||||
$out = substr($out,0,-1);
|
$out = substr($out,0,-1);
|
||||||
$out .= "}";
|
$out .= "}";
|
||||||
echo $out;
|
echo $out;
|
||||||
header('Connection: close');
|
header('Connection: close');
|
||||||
header('Content-Length: '.ob_get_length());
|
header('Content-Length: '.ob_get_length());
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
@ob_flush();
|
@ob_flush();
|
||||||
flush();
|
flush();
|
||||||
fastcgi_finish_request();
|
fastcgi_finish_request();
|
||||||
}elseif ($_GET["action"] == "moving" && isset($_GET["device"])) {
|
}elseif ($_GET["action"] == "moving" && isset($_GET["device"])) {
|
||||||
$dev = searchForDevice($_GET["device"])["id"];
|
$dev = searchForDevice($_GET["device"])["id"];
|
||||||
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
||||||
$jalousien = array();
|
$jalousien = array();
|
||||||
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states/core:MovingState";
|
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states/core:MovingState";
|
||||||
$ret = getSSLPage($url,$tahoma_token);
|
$ret = getSSLPage($url,$tahoma_token);
|
||||||
$moving = json_decode($ret, true);
|
$moving = json_decode($ret, true);
|
||||||
if($moving["value"])
|
if($moving["value"])
|
||||||
echo "true";
|
echo "true";
|
||||||
else
|
else
|
||||||
echo "false";
|
echo "false";
|
||||||
header('Connection: close');
|
header('Connection: close');
|
||||||
header('Content-Length: '.ob_get_length());
|
header('Content-Length: '.ob_get_length());
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
@ob_flush();
|
@ob_flush();
|
||||||
flush();
|
flush();
|
||||||
fastcgi_finish_request();
|
fastcgi_finish_request();
|
||||||
}elseif ($_GET["action"] == "move" && isset($_GET["pos"]) && isset($_GET["angle"]) && isset($_GET["device"])) {
|
}elseif ($_GET["action"] == "move" && isset($_GET["pos"]) && isset($_GET["angle"]) && isset($_GET["device"])) {
|
||||||
$angle = $_GET["angle"];
|
$angle = $_GET["angle"];
|
||||||
$pos = $_GET["pos"];
|
$pos = $_GET["pos"];
|
||||||
if ($pos < 0 || $pos > 100) {
|
if ($pos < 0 || $pos > 100) {
|
||||||
echo "Position out of Range";
|
echo "Position out of Range";
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
if ($angle < 0 || $angle > 100) {
|
if ($angle < 0 || $angle > 100) {
|
||||||
echo "Angle out of Range";
|
echo "Angle out of Range";
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
$dev = searchForDevice($_GET["device"])["id"];
|
$dev = searchForDevice($_GET["device"])["id"];
|
||||||
if($dev == false){
|
if($dev == false){
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
header('Connection: close');
|
header('Connection: close');
|
||||||
header('Content-Length: '.ob_get_length());
|
header('Content-Length: '.ob_get_length());
|
||||||
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states/core:MovingState";
|
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states/core:MovingState";
|
||||||
$ret = json_decode(getSSLPage($url,$tahoma_token),true);
|
$ret = json_decode(getSSLPage($url,$tahoma_token),true);
|
||||||
|
|
||||||
$action = array();
|
$action = array();
|
||||||
$action["label"] = "myAction";
|
$action["label"] = "myAction";
|
||||||
$action["actions"] = array();
|
$action["actions"] = array();
|
||||||
$action["actions"][0]["deviceURL"] = $dev;
|
$action["actions"][0]["deviceURL"] = $dev;
|
||||||
$action["actions"][0]["commands"] = array();
|
$action["actions"][0]["commands"] = array();
|
||||||
if(!isset($ret["value"])){
|
if(!isset($ret["value"])){
|
||||||
$ret = json_decode(getSSLPage($url,$tahoma_token),true);
|
$ret = json_decode(getSSLPage($url,$tahoma_token),true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isset($ret["value"]) || $ret["value"] == false){
|
if(!isset($ret["value"]) || $ret["value"] == false){
|
||||||
$url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/exec/apply';
|
$url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/exec/apply';
|
||||||
echo "start";
|
echo "start";
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
@ob_flush();
|
@ob_flush();
|
||||||
flush();
|
flush();
|
||||||
fastcgi_finish_request();
|
fastcgi_finish_request();
|
||||||
$action["actions"][0]["commands"][0]["name"] = "setClosureAndOrientation";
|
$action["actions"][0]["commands"][0]["name"] = "setClosureAndOrientation";
|
||||||
$action["actions"][0]["commands"][0]["parameters"][0] = intval($pos);
|
$action["actions"][0]["commands"][0]["parameters"][0] = intval($pos);
|
||||||
$action["actions"][0]["commands"][0]["parameters"][1] = intval($angle);
|
$action["actions"][0]["commands"][0]["parameters"][1] = intval($angle);
|
||||||
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token));
|
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token));
|
||||||
if(!isset($res["execId"])){
|
if(!isset($res["execId"])){
|
||||||
sleep(1);
|
sleep(1);
|
||||||
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token)); //2nd try
|
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token)); //2nd try
|
||||||
}
|
}
|
||||||
}elseif($ret["value"] == true){
|
}elseif($ret["value"] == true){
|
||||||
$url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/exec/apply';
|
$url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/exec/apply';
|
||||||
echo "stop";
|
echo "stop";
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
@ob_flush();
|
@ob_flush();
|
||||||
flush();
|
flush();
|
||||||
fastcgi_finish_request();
|
fastcgi_finish_request();
|
||||||
$action["actions"][0]["commands"][0]["name"] = "stop";
|
$action["actions"][0]["commands"][0]["name"] = "stop";
|
||||||
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token));
|
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token));
|
||||||
if(!isset($res["execId"])){
|
if(!isset($res["execId"])){
|
||||||
sleep(1);
|
sleep(1);
|
||||||
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token)); //2nd try
|
$res = json_decode(postSSLPage($url,json_encode($action),$tahoma_token)); //2nd try
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($_GET["action"] == "myactors") {
|
if ($_GET["action"] == "myactors") {
|
||||||
if(isset($_GET["filter"])){
|
if(isset($_GET["filter"])){
|
||||||
$host = $_GET["filter"];
|
$host = $_GET["filter"];
|
||||||
$host = str_replace("-", "", strtolower($host));
|
$host = str_replace("-", "", strtolower($host));
|
||||||
}else{
|
}else{
|
||||||
$host = gethostbyaddr("192.168.179.32");
|
$host = gethostbyaddr("192.168.179.32");
|
||||||
$host = str_replace("-", "", strtolower(substr($host, 0, strpos($host, "."))));
|
$host = str_replace("-", "", strtolower(substr($host, 0, strpos($host, "."))));
|
||||||
}
|
}
|
||||||
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
||||||
switch ($host) {
|
switch ($host) {
|
||||||
case "tmpegbad":
|
case "tmpegbad":
|
||||||
echo json_encode(filter_devs($devices, "bad "));
|
echo json_encode(filter_devs($devices, "bad "));
|
||||||
break;
|
break;
|
||||||
case "tmpegwozi":
|
case "tmpegwozi":
|
||||||
echo json_encode(filter_devs($devices, "wozi "));
|
echo json_encode(filter_devs($devices, "wozi "));
|
||||||
break;
|
break;
|
||||||
case "tmpegflorian": //flori
|
case "tmpegflorian": //flori
|
||||||
$ret = filter_devs($devices, "florian ","Flori ");
|
$ret = filter_devs($devices, "florian ","Flori ");
|
||||||
$ret = array_merge($ret, filter_devs($devices, "magdalena ","Magdalena "));
|
$ret = array_merge($ret, filter_devs($devices, "magdalena ","Magdalena "));
|
||||||
echo json_encode($ret);
|
echo json_encode($ret);
|
||||||
break;
|
break;
|
||||||
case "tmpegmagdalena":
|
case "tmpegmagdalena":
|
||||||
echo json_encode(filter_devs($devices, "magdalena "));
|
echo json_encode(filter_devs($devices, "magdalena "));
|
||||||
break;
|
break;
|
||||||
case "tmpegschlafzimmer":
|
case "tmpegschlafzimmer":
|
||||||
echo json_encode(filter_devs($devices, "schlafzimmer "));
|
echo json_encode(filter_devs($devices, "schlafzimmer "));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$ret2=Array();
|
$ret2=Array();
|
||||||
//$ret2[0]["name"] = $host;
|
//$ret2[0]["name"] = $host;
|
||||||
//$ret2[0]["id"] = 99;
|
//$ret2[0]["id"] = 99;
|
||||||
$ret = filter_devs($devices, "");
|
$ret = filter_devs($devices, "");
|
||||||
//$ret = array_merge($ret, $ret2);
|
//$ret = array_merge($ret, $ret2);
|
||||||
echo json_encode($ret);
|
echo json_encode($ret);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
header('Connection: close');
|
header('Connection: close');
|
||||||
header('Content-Length: '.ob_get_length());
|
header('Content-Length: '.ob_get_length());
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
@ob_flush();
|
@ob_flush();
|
||||||
flush();
|
flush();
|
||||||
fastcgi_finish_request();
|
fastcgi_finish_request();
|
||||||
|
|
||||||
|
|
||||||
//echo postSSLPage($url,json_encode($action));
|
//echo postSSLPage($url,json_encode($action));
|
||||||
}
|
}
|
||||||
|
|||||||
+143
-143
@@ -1,144 +1,144 @@
|
|||||||
/* devanagari */
|
/* devanagari */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
src: url(pxiByp8kv8JHgFVrLFj_Z11lFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLFj_Z11lFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
src: url(pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
src: url(pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
/* devanagari */
|
/* devanagari */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: url(pxiByp8kv8JHgFVrLDz8Z11lFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLDz8Z11lFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: url(pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: url(pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
/* devanagari */
|
/* devanagari */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url(pxiEyp8kv8JHgFVrJJbecmNE.woff2) format('woff2');
|
src: url(pxiEyp8kv8JHgFVrJJbecmNE.woff2) format('woff2');
|
||||||
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url(pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2');
|
src: url(pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2');
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url(pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2');
|
src: url(pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
/* devanagari */
|
/* devanagari */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url(pxiByp8kv8JHgFVrLEj6Z11lFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLEj6Z11lFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url(pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url(pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
/* devanagari */
|
/* devanagari */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: url(pxiByp8kv8JHgFVrLCz7Z11lFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLCz7Z11lFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: url(pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: url(pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
/* devanagari */
|
/* devanagari */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
src: url(pxiByp8kv8JHgFVrLDD4Z11lFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLDD4Z11lFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
src: url(pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2) format('woff2');
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Poppins';
|
font-family: 'Poppins';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
src: url(pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2) format('woff2');
|
src: url(pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#fff"
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#fff"
|
||||||
class="bi bi-heart-arrow" viewBox="0 0 16 16">
|
class="bi bi-heart-arrow" viewBox="0 0 16 16">
|
||||||
<path
|
<path
|
||||||
d="M6.707 9h4.364c-.536 1.573 2.028 3.806 4.929-.5-2.9-4.306-5.465-2.073-4.929-.5H6.707L4.854 6.146a.5.5 0 1 0-.708.708L5.293 8h-.586L2.854 6.146a.5.5 0 1 0-.708.708L3.293 8h-.586L.854 6.146a.5.5 0 1 0-.708.708L1.793 8.5.146 10.146a.5.5 0 0 0 .708.708L2.707 9h.586l-1.147 1.146a.5.5 0 0 0 .708.708L4.707 9h.586l-1.147 1.146a.5.5 0 0 0 .708.708z" />
|
d="M6.707 9h4.364c-.536 1.573 2.028 3.806 4.929-.5-2.9-4.306-5.465-2.073-4.929-.5H6.707L4.854 6.146a.5.5 0 1 0-.708.708L5.293 8h-.586L2.854 6.146a.5.5 0 1 0-.708.708L3.293 8h-.586L.854 6.146a.5.5 0 1 0-.708.708L1.793 8.5.146 10.146a.5.5 0 0 0 .708.708L2.707 9h.586l-1.147 1.146a.5.5 0 0 0 .708.708L4.707 9h.586l-1.147 1.146a.5.5 0 0 0 .708.708z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 491 B |
+286
-286
@@ -1,287 +1,287 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" style="border:0px solid gray;background-color: #212529;" viewBox="0 0 630 750" width= "100%" height= "100%">
|
<svg xmlns="http://www.w3.org/2000/svg" style="border:0px solid gray;background-color: #212529;" viewBox="0 0 630 750" width= "100%" height= "100%">
|
||||||
|
|
||||||
|
|
||||||
<g style="fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stroke:#666666;fill:#666666;" stroke-width="10" transform="scale (2) translate(0 -25)">
|
<g style="fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stroke:#666666;fill:#666666;" stroke-width="10" transform="scale (2) translate(0 -25)">
|
||||||
<path
|
<path
|
||||||
id="Puffer"
|
id="Puffer"
|
||||||
d="m 227,68 c 0,0 0,-28 -74,-28 -75,0 -75,27 -75,27 V 353 c 0,0 10,31 76,31 66,0 75,-32 75,-32 z"
|
d="m 227,68 c 0,0 0,-28 -74,-28 -75,0 -75,27 -75,27 V 353 c 0,0 10,31 76,31 66,0 75,-32 75,-32 z"
|
||||||
style="fill:#212529;fill-opacity:1;stroke-width:2;" />
|
style="fill:#212529;fill-opacity:1;stroke-width:2;" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="3"
|
ry="3"
|
||||||
rx="1"
|
rx="1"
|
||||||
cy="112"
|
cy="112"
|
||||||
cx="211"
|
cx="211"
|
||||||
id="path902" />
|
id="path902" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="5"
|
ry="5"
|
||||||
rx="3"
|
rx="3"
|
||||||
cy="163"
|
cy="163"
|
||||||
cx="211"
|
cx="211"
|
||||||
id="path904" />
|
id="path904" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="5"
|
ry="5"
|
||||||
rx="3"
|
rx="3"
|
||||||
cy="282"
|
cy="282"
|
||||||
cx="210"
|
cx="210"
|
||||||
id="path904-6" />
|
id="path904-6" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="5"
|
ry="5"
|
||||||
rx="3"
|
rx="3"
|
||||||
cy="345"
|
cy="345"
|
||||||
cx="213"
|
cx="213"
|
||||||
id="path904-2" />
|
id="path904-2" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="3"
|
ry="3"
|
||||||
rx="1"
|
rx="1"
|
||||||
cy="332"
|
cy="332"
|
||||||
cx="213"
|
cx="213"
|
||||||
id="path902-4" />
|
id="path902-4" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="3"
|
ry="3"
|
||||||
rx="1"
|
rx="1"
|
||||||
cy="121"
|
cy="121"
|
||||||
cx="100"
|
cx="100"
|
||||||
id="path902-0" />
|
id="path902-0" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="3"
|
ry="3"
|
||||||
rx="1"
|
rx="1"
|
||||||
cy="164"
|
cy="164"
|
||||||
cx="99" />
|
cx="99" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="3"
|
ry="3"
|
||||||
rx="1"
|
rx="1"
|
||||||
cy="175"
|
cy="175"
|
||||||
cx="99"
|
cx="99"
|
||||||
id="path902-8" />
|
id="path902-8" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="3"
|
ry="3"
|
||||||
rx="1"
|
rx="1"
|
||||||
cy="223"
|
cy="223"
|
||||||
cx="98"
|
cx="98"
|
||||||
id="path902-2" />
|
id="path902-2" />
|
||||||
<ellipse
|
<ellipse
|
||||||
ry="3"
|
ry="3"
|
||||||
rx="1"
|
rx="1"
|
||||||
cy="257"
|
cy="257"
|
||||||
cx="98"
|
cx="98"
|
||||||
id="path902-07" />
|
id="path902-07" />
|
||||||
<path
|
<path
|
||||||
id="heatweRLtube1"
|
id="heatweRLtube1"
|
||||||
d="m 211,282 h 95 v -44"
|
d="m 211,282 h 95 v -44"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="path976"
|
id="path976"
|
||||||
d="m 212,163 h 58"
|
d="m 212,163 h 58"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="path978"
|
id="path978"
|
||||||
d="M 32,140 H 2 v 61 H 32 Z"
|
d="M 32,140 H 2 v 61 H 32 Z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path980"
|
id="path980"
|
||||||
d="M 99,121 H 9 v 18"
|
d="M 99,121 H 9 v 18"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="path982"
|
id="path982"
|
||||||
d="M 33,175 H 98"
|
d="M 33,175 H 98"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="path984"
|
id="path984"
|
||||||
d="m 8,201 v 21 H 98"
|
d="m 8,201 v 21 H 98"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="fbVLtube"
|
id="fbVLtube"
|
||||||
d="m 99,164 h 7 V 244 H 8 v 23"
|
d="m 99,164 h 7 V 244 H 8 v 23"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="fbRLtube"
|
id="fbRLtube"
|
||||||
d="m 8,302 v 32 H 107 v -79 h -9"
|
d="m 8,302 v 32 H 107 v -79 h -9"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="path990"
|
id="path990"
|
||||||
d="m 211,112 h 39 V 68"
|
d="m 211,112 h 39 V 68"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="path992"
|
id="path992"
|
||||||
d="M 26,151 H 8 v 2 H 6 v 4 l -1,1 v 30 H 28 v -34 l -1,-1 v -4 z"
|
d="M 26,151 H 8 v 2 H 6 v 4 l -1,1 v 30 H 28 v -34 l -1,-1 v -4 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path994"
|
id="path994"
|
||||||
d="M 28,167 H 5"
|
d="M 28,167 H 5"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path996"
|
id="path996"
|
||||||
d="M 5,161 H 28"
|
d="M 5,161 H 28"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path998"
|
id="path998"
|
||||||
d="M 27,156 H 7"
|
d="M 27,156 H 7"
|
||||||
style="fill:none;stroke-width:1px;s" />
|
style="fill:none;stroke-width:1px;s" />
|
||||||
<path
|
<path
|
||||||
id="path1000"
|
id="path1000"
|
||||||
d="m 10,152 1,2 H 24 l 1,-2 z"
|
d="m 10,152 1,2 H 24 l 1,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1002"
|
id="path1002"
|
||||||
d="m 24,167 v 5 h 4"
|
d="m 24,167 v 5 h 4"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1004"
|
id="path1004"
|
||||||
d="M 47,268 H 1 v 34 H 47 v -34 z"
|
d="M 47,268 H 1 v 34 H 47 v -34 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1006"
|
id="path1006"
|
||||||
d="m 8,269 v 2 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 -0,1 1,1 1,1 h 31 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 v 4"
|
d="m 8,269 v 2 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 0,1 1,1 1,1 H 40 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 c 0,0 -1,0 -1,1 -0,1 1,1 1,1 h 31 c 0,0 1,0 1,1 0,1 -1,1 -1,1 H 8 v 4"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1008"
|
id="path1008"
|
||||||
d="M 294,238 Z"
|
d="M 294,238 Z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="heizerRLtube2"
|
id="heizerRLtube2"
|
||||||
d="m 295,238 h 10"
|
d="m 295,238 h 10"
|
||||||
style="fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
style="fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
<path
|
<path
|
||||||
id="path1012"
|
id="path1012"
|
||||||
d="M 270,163 Z"
|
d="M 270,163 Z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="heizregler"
|
id="heizregler"
|
||||||
d="m 282,247 v 7 c 0,0 0,4 7,4 6,0 7,-4 7,-4 v -7 c 0,0 -0,3 -8,3 -7,0 -6,-3 -6,-3 z"
|
d="m 282,247 v 7 c 0,0 0,4 7,4 6,0 7,-4 7,-4 v -7 c 0,0 -0,3 -8,3 -7,0 -6,-3 -6,-3 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="Heizstab"
|
id="Heizstab"
|
||||||
d="m 293,246 v -74 c 0,0 -0,-2 -4,-2 -4,0 -4,2 -4,2 v 74 c 0,0 -0,1 4,1 4,-0 4,-1 4,-1 z"
|
d="m 293,246 v -74 c 0,0 -0,-2 -4,-2 -4,0 -4,2 -4,2 v 74 c 0,0 -0,1 4,1 4,-0 4,-1 4,-1 z"
|
||||||
style="fill:#212529;stroke-width:1px;" />
|
style="fill:#212529;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1020"
|
id="path1020"
|
||||||
d="m 270,163 h 19 v 5"
|
d="m 270,163 h 19 v 5"
|
||||||
style="fill:none;" />
|
style="fill:none;" />
|
||||||
<path
|
<path
|
||||||
id="path1022"
|
id="path1022"
|
||||||
d="m 282,247 c 0,0 -0,-2 2,-2"
|
d="m 282,247 c 0,0 -0,-2 2,-2"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1024"
|
id="path1024"
|
||||||
d="m 296,247 c 0,0 0,-2 -3,-3"
|
d="m 296,247 c 0,0 0,-2 -3,-3"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path976-2"
|
id="path976-2"
|
||||||
d="m 213,332 h 75"
|
d="m 213,332 h 75"
|
||||||
style="fill:none;stroke-width:4;" />
|
style="fill:none;stroke-width:4;" />
|
||||||
<path
|
<path
|
||||||
id="path1043"
|
id="path1043"
|
||||||
d="m 240,338 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
d="m 240,338 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-9"
|
id="path1043-9"
|
||||||
d="m 240,169 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
d="m 240,169 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-5"
|
id="path1043-5"
|
||||||
d="m 65,228 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
d="m 65,228 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-3"
|
id="path1043-3"
|
||||||
d="m 65,250 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
d="m 65,250 0,-2 -7,3 6,3 -0,-2 4,-0 -0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-94"
|
id="path1043-94"
|
||||||
d="m 253,288 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
d="m 253,288 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-94-5"
|
id="path1043-94-5"
|
||||||
d="m 236,103 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
d="m 236,103 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-94-2"
|
id="path1043-94-2"
|
||||||
d="m 65,342 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
d="m 65,342 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-94-6"
|
id="path1043-94-6"
|
||||||
d="m 65,126 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
d="m 65,126 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1043-94-9"
|
id="path1043-94-9"
|
||||||
d="m 65,180 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
d="m 65,180 -0,-2 7,3 -6,3 0,-2 -4,-0 0,-2 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1123"
|
id="path1123"
|
||||||
d="m 253,52 h 9 c 0,0 3,-0 5,1 1,1 1,5 1,5 l -0,3 c 0,0 2,0 3,1 1,1 1,3 1,3 h -11 c 0,0 -0,-2 1,-4 1,-1 2,-1 2,-1 v -3 c 0,0 0,-2 -1,-3 -1,-0 -2,-0 -2,-0 h -9"
|
d="m 253,52 h 9 c 0,0 3,-0 5,1 1,1 1,5 1,5 l -0,3 c 0,0 2,0 3,1 1,1 1,3 1,3 h -11 c 0,0 -0,-2 1,-4 1,-1 2,-1 2,-1 v -3 c 0,0 0,-2 -1,-3 -1,-0 -2,-0 -2,-0 h -9"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1125"
|
id="path1125"
|
||||||
d="m 266,68 -0,2"
|
d="m 266,68 -0,2"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1127"
|
id="path1127"
|
||||||
d="m 266,73 v 1"
|
d="m 266,73 v 1"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1129"
|
id="path1129"
|
||||||
d="m 268,68 v 2"
|
d="m 268,68 v 2"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1131"
|
id="path1131"
|
||||||
d="m 268,73 v 1"
|
d="m 268,73 v 1"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1133"
|
id="path1133"
|
||||||
d="m 271,69 v 2"
|
d="m 271,69 v 2"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1135"
|
id="path1135"
|
||||||
d="M 271,74 V 75"
|
d="M 271,74 V 75"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1137"
|
id="path1137"
|
||||||
d="m 285,314 h -4 v 12 h 4"
|
d="m 285,314 h -4 v 12 h 4"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1139"
|
id="path1139"
|
||||||
d="m 280,318 h -4 v -5 h -4 v 5 h -4 c 0,0 -1,-0 -2,1 -0,1 -0,4 -0,4 0,0 -0,3 2,3 2,0 2,-2 2,-3 0,-1 2,-1 2,-1 l 9,0"
|
d="m 280,318 h -4 v -5 h -4 v 5 h -4 c 0,0 -1,-0 -2,1 -0,1 -0,4 -0,4 0,0 -0,3 2,3 2,0 2,-2 2,-3 0,-1 2,-1 2,-1 l 9,0"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
<path
|
<path
|
||||||
id="path1141"
|
id="path1141"
|
||||||
d="m 278,312 h -9 c -1,0 -2,-1 0,-1 2,0 2,1 4,1 2,0 3,-1 5,-1 2,0 0,1 -0,1 z"
|
d="m 278,312 h -9 c -1,0 -2,-1 0,-1 2,0 2,1 4,1 2,0 3,-1 5,-1 2,0 0,1 -0,1 z"
|
||||||
style="fill:none;stroke-width:1px;" />
|
style="fill:none;stroke-width:1px;" />
|
||||||
</g>
|
</g>
|
||||||
<g style="font-size: 28px; font-weight: 600; font-family: sans-serif; text-anchor: middle; fill: rgb(200, 210, 210); letter-spacing: 1px;" >
|
<g style="font-size: 28px; font-weight: 600; font-family: sans-serif; text-anchor: middle; fill: rgb(200, 210, 210); letter-spacing: 1px;" >
|
||||||
<text id="PufferOtxt" dy="200" dx="320">
|
<text id="PufferOtxt" dy="200" dx="320">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="PufferMtxt" dy="400" dx="320">
|
<text id="PufferMtxt" dy="400" dx="320">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="PufferUtxt" dy="600" dx="320">
|
<text id="PufferUtxt" dy="600" dx="320">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="heaterRL" dy="505" dx="530" style="font-size: 22px;">
|
<text id="heaterRL" dy="505" dx="530" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="heaterVL" dy="260" dx="530" style="font-size: 22px;">
|
<text id="heaterVL" dy="260" dx="530" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="fbVL" dy="465" dx="70" style="font-size: 22px;">
|
<text id="fbVL" dy="465" dx="70" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="fbRL" dy="605" dx="70" style="font-size: 22px;">
|
<text id="fbRL" dy="605" dx="70" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="thermeRL" dy="385" dx="70" style="font-size: 22px;">
|
<text id="thermeRL" dy="385" dx="70" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="thermeVLfb" dy="290" dx="110" style="font-size: 22px;">
|
<text id="thermeVLfb" dy="290" dx="110" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="thermeVLww" dy="180" dx="70" style="font-size: 22px;">
|
<text id="thermeVLww" dy="180" dx="70" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
<text id="triacLbl" dy="350" dx="515" style="font-size: 22px;">
|
<text id="triacLbl" dy="350" dx="515" style="font-size: 22px;">
|
||||||
Triac:
|
Triac:
|
||||||
</text>
|
</text>
|
||||||
<text id="triac" dy="375" dx="515" style="font-size: 22px;">
|
<text id="triac" dy="375" dx="515" style="font-size: 22px;">
|
||||||
--.- °C
|
--.- °C
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 7.8 KiB |
+892
-892
File diff suppressed because it is too large
Load Diff
+365
-365
@@ -1,366 +1,366 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Lukas Buchs
|
* Copyright (C) 2022 Lukas Buchs
|
||||||
* license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
|
* license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
|
||||||
*
|
*
|
||||||
* Server test script for WebAuthn library. Saves new registrations in session.
|
* Server test script for WebAuthn library. Saves new registrations in session.
|
||||||
*
|
*
|
||||||
* JAVASCRIPT | SERVER
|
* JAVASCRIPT | SERVER
|
||||||
* ------------------------------------------------------------
|
* ------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* REGISTRATION
|
* REGISTRATION
|
||||||
*
|
*
|
||||||
* window.fetch -----------------> getCreateArgs
|
* window.fetch -----------------> getCreateArgs
|
||||||
* |
|
* |
|
||||||
* navigator.credentials.create <-------------'
|
* navigator.credentials.create <-------------'
|
||||||
* |
|
* |
|
||||||
* '-------------------------> processCreate
|
* '-------------------------> processCreate
|
||||||
* |
|
* |
|
||||||
* alert ok or fail <----------------'
|
* alert ok or fail <----------------'
|
||||||
*
|
*
|
||||||
* ------------------------------------------------------------
|
* ------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* VALIDATION
|
* VALIDATION
|
||||||
*
|
*
|
||||||
* window.fetch ------------------> getGetArgs
|
* window.fetch ------------------> getGetArgs
|
||||||
* |
|
* |
|
||||||
* navigator.credentials.get <----------------'
|
* navigator.credentials.get <----------------'
|
||||||
* |
|
* |
|
||||||
* '-------------------------> processGet
|
* '-------------------------> processGet
|
||||||
* |
|
* |
|
||||||
* alert ok or fail <----------------'
|
* alert ok or fail <----------------'
|
||||||
*
|
*
|
||||||
* ------------------------------------------------------------
|
* ------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once './restricted/WebAuthn/src/WebAuthn.php';
|
require_once './restricted/WebAuthn/src/WebAuthn.php';
|
||||||
require_once("./helper.php"); // startet die Session, liefert isLocal() und mysql.php
|
require_once("./helper.php"); // startet die Session, liefert isLocal() und mysql.php
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// read get argument and post body
|
// read get argument and post body
|
||||||
$fn = filter_input(INPUT_GET, 'fn');
|
$fn = filter_input(INPUT_GET, 'fn');
|
||||||
|
|
||||||
// Registrierung und Loeschen von Anmeldedaten nur nach einem gueltigen
|
// Registrierung und Loeschen von Anmeldedaten nur nach einem gueltigen
|
||||||
// Einmalschluessel aus addUser.php (checkAdduser setzt mayRegister) oder
|
// Einmalschluessel aus addUser.php (checkAdduser setzt mayRegister) oder
|
||||||
// aus dem lokalen Netz. Ohne diese Wache genuegen zwei Aufrufe von
|
// aus dem lokalen Netz. Ohne diese Wache genuegen zwei Aufrufe von
|
||||||
// beliebiger Stelle, um einen eigenen Passkey fuer das fest hinterlegte
|
// beliebiger Stelle, um einen eigenen Passkey fuer das fest hinterlegte
|
||||||
// Konto anzulegen - der Einmalschluessel schuetzte nur die Anzeige in
|
// Konto anzulegen - der Einmalschluessel schuetzte nur die Anzeige in
|
||||||
// index.php, nicht diesen Endpunkt.
|
// index.php, nicht diesen Endpunkt.
|
||||||
if ($fn === 'getCreateArgs' || $fn === 'processCreate' || $fn === 'clearRegistrations') {
|
if ($fn === 'getCreateArgs' || $fn === 'processCreate' || $fn === 'clearRegistrations') {
|
||||||
$freigegeben = isset($_SESSION['mayRegister'])
|
$freigegeben = isset($_SESSION['mayRegister'])
|
||||||
&& (time() - $_SESSION['mayRegister']) < 600;
|
&& (time() - $_SESSION['mayRegister']) < 600;
|
||||||
if (!$freigegeben && !isLocal()) {
|
if (!$freigegeben && !isLocal()) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode(['success' => false, 'msg' => 'Registrierung nicht freigegeben.']));
|
print(json_encode(['success' => false, 'msg' => 'Registrierung nicht freigegeben.']));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$requireResidentKey = false;
|
$requireResidentKey = false;
|
||||||
$userVerification = false;
|
$userVerification = false;
|
||||||
$formats = [];
|
$formats = [];
|
||||||
$msg="";
|
$msg="";
|
||||||
|
|
||||||
$formats[] = 'android-key';
|
$formats[] = 'android-key';
|
||||||
$formats[] = 'android-safetynet';
|
$formats[] = 'android-safetynet';
|
||||||
$formats[] = 'apple';
|
$formats[] = 'apple';
|
||||||
$formats[] = 'fido-u2f';
|
$formats[] = 'fido-u2f';
|
||||||
$formats[] = 'none';
|
$formats[] = 'none';
|
||||||
$formats[] = 'packed';
|
$formats[] = 'packed';
|
||||||
$formats[] = 'tpm';
|
$formats[] = 'tpm';
|
||||||
$userId = "E071229F004A4CDE";
|
$userId = "E071229F004A4CDE";
|
||||||
$userName = "SmartHomeWagner";
|
$userName = "SmartHomeWagner";
|
||||||
$userDisplayName = "2025Bym0";
|
$userDisplayName = "2025Bym0";
|
||||||
|
|
||||||
$post = trim(file_get_contents('php://input'));
|
$post = trim(file_get_contents('php://input'));
|
||||||
if ($post) {
|
if ($post) {
|
||||||
$post = json_decode($post, null, 512, JSON_THROW_ON_ERROR);
|
$post = json_decode($post, null, 512, JSON_THROW_ON_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rpId = "nas.el-wa.org";
|
$rpId = "nas.el-wa.org";
|
||||||
if ($rpId === false) {
|
if ($rpId === false) {
|
||||||
throw new Exception('invalid relying party ID');
|
throw new Exception('invalid relying party ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
// cross-platform: true, if type internal is not allowed
|
// cross-platform: true, if type internal is not allowed
|
||||||
// false, if only internal is allowed
|
// false, if only internal is allowed
|
||||||
// null, if internal and cross-platform is allowed
|
// null, if internal and cross-platform is allowed
|
||||||
$crossPlatformAttachment = null;
|
$crossPlatformAttachment = null;
|
||||||
|
|
||||||
// new Instance of the server library.
|
// new Instance of the server library.
|
||||||
// make sure that $rpId is the domain name.
|
// make sure that $rpId is the domain name.
|
||||||
$WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $rpId, $formats);
|
$WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $rpId, $formats);
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/isrg-root-x2.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/isrg-root-x2.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/solo.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/solo.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/solokey_f1.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/solokey_f1.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/solokey_r1.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/solokey_r1.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/apple.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/apple.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/yubico.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/yubico.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/hypersecu.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/hypersecu.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/globalSign.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/globalSign.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/googleHardware.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/googleHardware.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/microsoftTpmCollection.pem');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/microsoftTpmCollection.pem');
|
||||||
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/mds');
|
$WebAuthn->addRootCertificates('./restricted/WebAuthn/rootCertificates/mds');
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// request for create arguments
|
// request for create arguments
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
if ($fn === 'getCreateArgs') {
|
if ($fn === 'getCreateArgs') {
|
||||||
$createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, 60*4, $requireResidentKey, $userVerification, $crossPlatformAttachment);
|
$createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, 60*4, $requireResidentKey, $userVerification, $crossPlatformAttachment);
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($createArgs));
|
print(json_encode($createArgs));
|
||||||
|
|
||||||
// save challange to session. you have to deliver it to processGet later.
|
// save challange to session. you have to deliver it to processGet later.
|
||||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// request for get arguments
|
// request for get arguments
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
} else if ($fn === 'getGetArgs') {
|
} else if ($fn === 'getGetArgs') {
|
||||||
$ids = [];
|
$ids = [];
|
||||||
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
||||||
$result = mysqli_query($mysql,"SELECT credentialId FROM users WHERE userId = '".base64_encode($userId)."';");
|
$result = mysqli_query($mysql,"SELECT credentialId FROM users WHERE userId = '".base64_encode($userId)."';");
|
||||||
if(!$result){
|
if(!$result){
|
||||||
$msg = "Error:<br>".mysqli_error($mysql)."<br />";
|
$msg = "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
if ($result->num_rows > 0) {
|
if ($result->num_rows > 0) {
|
||||||
while($row = $result->fetch_assoc()) {
|
while($row = $result->fetch_assoc()) {
|
||||||
$ids[] = base64_decode($row["credentialId"]);
|
$ids[] = base64_decode($row["credentialId"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if ($requireResidentKey) {
|
if ($requireResidentKey) {
|
||||||
if (!isset($_SESSION['registrations']) || !is_array($_SESSION['registrations']) || count($_SESSION['registrations']) === 0) {
|
if (!isset($_SESSION['registrations']) || !is_array($_SESSION['registrations']) || count($_SESSION['registrations']) === 0) {
|
||||||
throw new Exception('we do not have any registrations in session to check the registration');
|
throw new Exception('we do not have any registrations in session to check the registration');
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// load registrations from session stored there by processCreate.
|
// load registrations from session stored there by processCreate.
|
||||||
// normaly you have to load the credential Id's for a username
|
// normaly you have to load the credential Id's for a username
|
||||||
// from the database.
|
// from the database.
|
||||||
if (isset($_SESSION['registrations']) && is_array($_SESSION['registrations'])) {
|
if (isset($_SESSION['registrations']) && is_array($_SESSION['registrations'])) {
|
||||||
foreach ($_SESSION['registrations'] as $reg) {
|
foreach ($_SESSION['registrations'] as $reg) {
|
||||||
if ($reg->userId === $userId) {
|
if ($reg->userId === $userId) {
|
||||||
$ids[] = $reg->credentialId;
|
$ids[] = $reg->credentialId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
if (count($ids) === 0) {
|
if (count($ids) === 0) {
|
||||||
throw new Exception('no registrations in session for userId ' . $userId);
|
throw new Exception('no registrations in session for userId ' . $userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
$getArgs = $WebAuthn->getGetArgs($ids, 60*4, true, true, true, true, true, $userVerification);
|
$getArgs = $WebAuthn->getGetArgs($ids, 60*4, true, true, true, true, true, $userVerification);
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($getArgs));
|
print(json_encode($getArgs));
|
||||||
|
|
||||||
// save challange to session. you have to deliver it to processGet later.
|
// save challange to session. you have to deliver it to processGet later.
|
||||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// process create
|
// process create
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
} else if ($fn === 'processCreate') {
|
} else if ($fn === 'processCreate') {
|
||||||
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
||||||
$clientDataJSON = !empty($post->clientDataJSON) ? base64_decode($post->clientDataJSON) : null;
|
$clientDataJSON = !empty($post->clientDataJSON) ? base64_decode($post->clientDataJSON) : null;
|
||||||
$attestationObject = !empty($post->attestationObject) ? base64_decode($post->attestationObject) : null;
|
$attestationObject = !empty($post->attestationObject) ? base64_decode($post->attestationObject) : null;
|
||||||
$challenge = $_SESSION['challenge'] ?? null;
|
$challenge = $_SESSION['challenge'] ?? null;
|
||||||
|
|
||||||
// processCreate returns data to be stored for future logins.
|
// processCreate returns data to be stored for future logins.
|
||||||
// in this example we store it in the php session.
|
// in this example we store it in the php session.
|
||||||
// Normally you have to store the data in a database connected
|
// Normally you have to store the data in a database connected
|
||||||
// with the username.
|
// with the username.
|
||||||
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, $userVerification === 'required', true, false);
|
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, $userVerification === 'required', true, false);
|
||||||
|
|
||||||
// add user infos
|
// add user infos
|
||||||
$data->userId = $userId;
|
$data->userId = $userId;
|
||||||
$data->userName = $userName;
|
$data->userName = $userName;
|
||||||
$data->userDisplayName = $userDisplayName;
|
$data->userDisplayName = $userDisplayName;
|
||||||
//set Null to 0
|
//set Null to 0
|
||||||
$data->signatureCounter ??= 0;
|
$data->signatureCounter ??= 0;
|
||||||
/*
|
/*
|
||||||
if (!isset($_SESSION['registrations']) || !array_key_exists('registrations', $_SESSION) || !is_array($_SESSION['registrations'])) {
|
if (!isset($_SESSION['registrations']) || !array_key_exists('registrations', $_SESSION) || !is_array($_SESSION['registrations'])) {
|
||||||
$_SESSION['registrations'] = [];
|
$_SESSION['registrations'] = [];
|
||||||
}*/
|
}*/
|
||||||
if(!mysqli_query($mysql,"INSERT INTO users SET userId = '".base64_encode($data->userId)."', credentialId = '".base64_encode($data->credentialId)."', credentialPublicKey = '".base64_encode($data->credentialPublicKey)."', signatureCounter = '".base64_encode($data->signatureCounter)."', name = '".mysqli_real_escape_string($mysql,filter_input(INPUT_GET, 'name'))."';")){
|
if(!mysqli_query($mysql,"INSERT INTO users SET userId = '".base64_encode($data->userId)."', credentialId = '".base64_encode($data->credentialId)."', credentialPublicKey = '".base64_encode($data->credentialPublicKey)."', signatureCounter = '".base64_encode($data->signatureCounter)."', name = '".mysqli_real_escape_string($mysql,filter_input(INPUT_GET, 'name'))."';")){
|
||||||
$msg = "Error:<br>".mysqli_error($mysql)."<br />";
|
$msg = "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if ($data->rootValid === false) {
|
if ($data->rootValid === false) {
|
||||||
$msg = 'registration ok, but certificate does not match any of the selected root ca.';
|
$msg = 'registration ok, but certificate does not match any of the selected root ca.';
|
||||||
}
|
}
|
||||||
// $msg = "Data: ".json_last_error();//json_encode($data);//'registration success.';
|
// $msg = "Data: ".json_last_error();//json_encode($data);//'registration success.';
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
$_SESSION['registrations'][] = $data;
|
$_SESSION['registrations'][] = $data;
|
||||||
*/
|
*/
|
||||||
$return = new stdClass();
|
$return = new stdClass();
|
||||||
$return->success = true;
|
$return->success = true;
|
||||||
$return->msg = $msg;
|
$return->msg = $msg;
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($return));
|
print(json_encode($return));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// proccess get
|
// proccess get
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
} else if ($fn === 'processGet') {
|
} else if ($fn === 'processGet') {
|
||||||
$clientDataJSON = !empty($post->clientDataJSON) ? base64_decode($post->clientDataJSON) : null;
|
$clientDataJSON = !empty($post->clientDataJSON) ? base64_decode($post->clientDataJSON) : null;
|
||||||
$authenticatorData = !empty($post->authenticatorData) ? base64_decode($post->authenticatorData) : null;
|
$authenticatorData = !empty($post->authenticatorData) ? base64_decode($post->authenticatorData) : null;
|
||||||
$signature = !empty($post->signature) ? base64_decode($post->signature) : null;
|
$signature = !empty($post->signature) ? base64_decode($post->signature) : null;
|
||||||
$userHandle = !empty($post->userHandle) ? base64_decode($post->userHandle) : null;
|
$userHandle = !empty($post->userHandle) ? base64_decode($post->userHandle) : null;
|
||||||
$id = !empty($post->id) ? base64_decode($post->id) : null;
|
$id = !empty($post->id) ? base64_decode($post->id) : null;
|
||||||
$challenge = $_SESSION['challenge'] ?? '';
|
$challenge = $_SESSION['challenge'] ?? '';
|
||||||
$credentialPublicKey = null;
|
$credentialPublicKey = null;
|
||||||
|
|
||||||
// looking up correspondending public key of the credential id
|
// looking up correspondending public key of the credential id
|
||||||
// you should also validate that only ids of the given user name
|
// you should also validate that only ids of the given user name
|
||||||
// are taken for the login.
|
// are taken for the login.
|
||||||
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
$mysql = new mysqli($mysql_server,$mysql_user,$mysql_pass,$mysql_db);
|
||||||
$result = mysqli_query($mysql,"SELECT credentialPublicKey, userId, name FROM users WHERE credentialId = '".base64_encode($id)."';");
|
$result = mysqli_query($mysql,"SELECT credentialPublicKey, userId, name FROM users WHERE credentialId = '".base64_encode($id)."';");
|
||||||
if(!$result){
|
if(!$result){
|
||||||
$msg = "Error:<br>".mysqli_error($mysql)."<br />";
|
$msg = "Error:<br>".mysqli_error($mysql)."<br />";
|
||||||
}
|
}
|
||||||
if ($result->num_rows > 0) {
|
if ($result->num_rows > 0) {
|
||||||
$row = $result->fetch_assoc();
|
$row = $result->fetch_assoc();
|
||||||
$credentialPublicKey = base64_decode($row["credentialPublicKey"]);
|
$credentialPublicKey = base64_decode($row["credentialPublicKey"]);
|
||||||
$reg = (object) ['userId' => base64_decode($row["userId"])];
|
$reg = (object) ['userId' => base64_decode($row["userId"])];
|
||||||
$username = $row["name"];
|
$username = $row["name"];
|
||||||
} /*else {
|
} /*else {
|
||||||
if (isset($_SESSION['registrations']) && is_array($_SESSION['registrations'])) {
|
if (isset($_SESSION['registrations']) && is_array($_SESSION['registrations'])) {
|
||||||
foreach ($_SESSION['registrations'] as $reg) {
|
foreach ($_SESSION['registrations'] as $reg) {
|
||||||
if ($reg->credentialId === $id) {
|
if ($reg->credentialId === $id) {
|
||||||
$credentialPublicKey = $reg->credentialPublicKey;
|
$credentialPublicKey = $reg->credentialPublicKey;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
if ($credentialPublicKey === null) {
|
if ($credentialPublicKey === null) {
|
||||||
throw new Exception('Public Key for credential ID not found!');
|
throw new Exception('Public Key for credential ID not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have resident key, we have to verify that the userHandle is the provided userId at registration
|
// if we have resident key, we have to verify that the userHandle is the provided userId at registration
|
||||||
if ($requireResidentKey && $userHandle !== hex2bin($reg->userId)) {
|
if ($requireResidentKey && $userHandle !== hex2bin($reg->userId)) {
|
||||||
throw new \Exception('userId doesnt match (is ' . bin2hex($userHandle) . ' but expect ' . $reg->userId . ')');
|
throw new \Exception('userId doesnt match (is ' . bin2hex($userHandle) . ' but expect ' . $reg->userId . ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the get request. throws WebAuthnException if it fails
|
// process the get request. throws WebAuthnException if it fails
|
||||||
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, $userVerification === 'required');
|
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, $userVerification === 'required');
|
||||||
|
|
||||||
$return = new stdClass();
|
$return = new stdClass();
|
||||||
$return->success = true;
|
$return->success = true;
|
||||||
$authKey = strval(random_int(0,99999999));
|
$authKey = strval(random_int(0,99999999));
|
||||||
$result = mysqli_query($mysql,"UPDATE users SET authKey=".$authKey.", lastAuth=NOW() WHERE credentialId = '".base64_encode($id)."';");
|
$result = mysqli_query($mysql,"UPDATE users SET authKey=".$authKey.", lastAuth=NOW() WHERE credentialId = '".base64_encode($id)."';");
|
||||||
$_SESSION["Logged"] = true;
|
$_SESSION["Logged"] = true;
|
||||||
$_SESSION["user"] = $username;
|
$_SESSION["user"] = $username;
|
||||||
$_SESSION["authKey"] = $authKey;
|
$_SESSION["authKey"] = $authKey;
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($return));
|
print(json_encode($return));
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// proccess clear registrations
|
// proccess clear registrations
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
} else if ($fn === 'clearRegistrations') {
|
} else if ($fn === 'clearRegistrations') {
|
||||||
$_SESSION['registrations'] = null;
|
$_SESSION['registrations'] = null;
|
||||||
$_SESSION['challenge'] = null;
|
$_SESSION['challenge'] = null;
|
||||||
|
|
||||||
$return = new stdClass();
|
$return = new stdClass();
|
||||||
$return->success = true;
|
$return->success = true;
|
||||||
$return->msg = 'all registrations deleted';
|
$return->msg = 'all registrations deleted';
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($return));
|
print(json_encode($return));
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// display stored data as HTML
|
// display stored data as HTML
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
} /*else if ($fn === 'getStoredDataHtml') {
|
} /*else if ($fn === 'getStoredDataHtml') {
|
||||||
$html = '<!DOCTYPE html>' . "\n";
|
$html = '<!DOCTYPE html>' . "\n";
|
||||||
$html .= '<html><head><style>tr:nth-child(even){background-color: #f2f2f2;}</style></head>';
|
$html .= '<html><head><style>tr:nth-child(even){background-color: #f2f2f2;}</style></head>';
|
||||||
$html .= '<body style="font-family:sans-serif">';
|
$html .= '<body style="font-family:sans-serif">';
|
||||||
if (isset($_SESSION['registrations']) && is_array($_SESSION['registrations'])) {
|
if (isset($_SESSION['registrations']) && is_array($_SESSION['registrations'])) {
|
||||||
$html .= '<p>There are ' . count($_SESSION['registrations']) . ' registrations in this session:</p>';
|
$html .= '<p>There are ' . count($_SESSION['registrations']) . ' registrations in this session:</p>';
|
||||||
foreach ($_SESSION['registrations'] as $reg) {
|
foreach ($_SESSION['registrations'] as $reg) {
|
||||||
$html .= '<table style="border:1px solid black;margin:10px 0;">';
|
$html .= '<table style="border:1px solid black;margin:10px 0;">';
|
||||||
foreach ($reg as $key => $value) {
|
foreach ($reg as $key => $value) {
|
||||||
|
|
||||||
if (is_bool($value)) {
|
if (is_bool($value)) {
|
||||||
$value = $value ? 'yes' : 'no';
|
$value = $value ? 'yes' : 'no';
|
||||||
|
|
||||||
} else if (is_null($value)) {
|
} else if (is_null($value)) {
|
||||||
$value = 'null';
|
$value = 'null';
|
||||||
|
|
||||||
} else if (is_object($value)) {
|
} else if (is_object($value)) {
|
||||||
$value = chunk_split(strval($value), 64);
|
$value = chunk_split(strval($value), 64);
|
||||||
|
|
||||||
} else if (is_string($value) && strlen($value) > 0 && htmlspecialchars($value, ENT_QUOTES) === '') {
|
} else if (is_string($value) && strlen($value) > 0 && htmlspecialchars($value, ENT_QUOTES) === '') {
|
||||||
$value = chunk_split(bin2hex($value), 64);
|
$value = chunk_split(bin2hex($value), 64);
|
||||||
}
|
}
|
||||||
$html .= '<tr><td>' . htmlspecialchars($key) . '</td><td style="font-family:monospace;">' . nl2br(htmlspecialchars($value)) . '</td>';
|
$html .= '<tr><td>' . htmlspecialchars($key) . '</td><td style="font-family:monospace;">' . nl2br(htmlspecialchars($value)) . '</td>';
|
||||||
}
|
}
|
||||||
$html .= '</table>';
|
$html .= '</table>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$html .= '<p>There are no registrations.</p>';
|
$html .= '<p>There are no registrations.</p>';
|
||||||
}
|
}
|
||||||
$html .= '</body></html>';
|
$html .= '</body></html>';
|
||||||
|
|
||||||
header('Content-Type: text/html');
|
header('Content-Type: text/html');
|
||||||
print $html;
|
print $html;
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// get root certs from FIDO Alliance Metadata Service
|
// get root certs from FIDO Alliance Metadata Service
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
} */else if ($fn === 'queryFidoMetaDataService') {
|
} */else if ($fn === 'queryFidoMetaDataService') {
|
||||||
|
|
||||||
$mdsFolder = './restricted/WebAuthn/rootCertificates/mds';
|
$mdsFolder = './restricted/WebAuthn/rootCertificates/mds';
|
||||||
$success = false;
|
$success = false;
|
||||||
$msg = null;
|
$msg = null;
|
||||||
|
|
||||||
// fetch only 1x / 24h
|
// fetch only 1x / 24h
|
||||||
$lastFetch = \is_file($mdsFolder . '/lastMdsFetch.txt') ? \strtotime(\file_get_contents($mdsFolder . '/lastMdsFetch.txt')) : 0;
|
$lastFetch = \is_file($mdsFolder . '/lastMdsFetch.txt') ? \strtotime(\file_get_contents($mdsFolder . '/lastMdsFetch.txt')) : 0;
|
||||||
if ($lastFetch + (3600*48) < \time()) {
|
if ($lastFetch + (3600*48) < \time()) {
|
||||||
$cnt = $WebAuthn->queryFidoMetaDataService($mdsFolder);
|
$cnt = $WebAuthn->queryFidoMetaDataService($mdsFolder);
|
||||||
$success = true;
|
$success = true;
|
||||||
\file_put_contents($mdsFolder . '/lastMdsFetch.txt', date('r'));
|
\file_put_contents($mdsFolder . '/lastMdsFetch.txt', date('r'));
|
||||||
$msg = 'successfully queried FIDO Alliance Metadata Service - ' . $cnt . ' certificates downloaded.';
|
$msg = 'successfully queried FIDO Alliance Metadata Service - ' . $cnt . ' certificates downloaded.';
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$msg = 'Fail: last fetch was at ' . date('r', $lastFetch) . ' - fetch only 1x every 48h';
|
$msg = 'Fail: last fetch was at ' . date('r', $lastFetch) . ' - fetch only 1x every 48h';
|
||||||
}
|
}
|
||||||
|
|
||||||
$return = new stdClass();
|
$return = new stdClass();
|
||||||
$return->success = $success;
|
$return->success = $success;
|
||||||
$return->msg = $msg;
|
$return->msg = $msg;
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($return));
|
print(json_encode($return));
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable $ex) {
|
} catch (Throwable $ex) {
|
||||||
$return = new stdClass();
|
$return = new stdClass();
|
||||||
$return->success = false;
|
$return->success = false;
|
||||||
$return->msg = $ex->getMessage();
|
$return->msg = $ex->getMessage();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($return));
|
print(json_encode($return));
|
||||||
}
|
}
|
||||||
Vendored
+6
-6
File diff suppressed because one or more lines are too long
Vendored
+4
-4
File diff suppressed because one or more lines are too long
+132
-132
@@ -1,96 +1,96 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
require_once("restricted/mysql.php");
|
require_once("restricted/mysql.php");
|
||||||
$_SESSION["local"] =false;
|
$_SESSION["local"] =false;
|
||||||
|
|
||||||
// Netze, die als lokal gelten und damit ohne Anmeldung Zugriff bekommen.
|
// Netze, die als lokal gelten und damit ohne Anmeldung Zugriff bekommen.
|
||||||
// Bewusst eine sichtbare Liste statt eines Zeichenkettenvergleichs.
|
// Bewusst eine sichtbare Liste statt eines Zeichenkettenvergleichs.
|
||||||
const LOCAL_NETWORKS = ["192.168.179.0/24"];
|
const LOCAL_NETWORKS = ["192.168.179.0/24"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Liegt $ip im Netz $cidr? Verglichen wird binaer (inet_pton), also
|
* Liegt $ip im Netz $cidr? Verglichen wird binaer (inet_pton), also
|
||||||
* unabhaengig von der Schreibweise: "2001:db8::" und "2001:0db8:0000::"
|
* unabhaengig von der Schreibweise: "2001:db8::" und "2001:0db8:0000::"
|
||||||
* sind dieselbe Adresse.
|
* sind dieselbe Adresse.
|
||||||
*/
|
*/
|
||||||
function ipInNetwork($ip, $cidr)
|
function ipInNetwork($ip, $cidr)
|
||||||
{
|
{
|
||||||
$teile = explode("/", $cidr, 2);
|
$teile = explode("/", $cidr, 2);
|
||||||
$adresse = @inet_pton($ip);
|
$adresse = @inet_pton($ip);
|
||||||
$netz = @inet_pton($teile[0]);
|
$netz = @inet_pton($teile[0]);
|
||||||
if ($adresse === false || $netz === false || strlen($adresse) !== strlen($netz)) {
|
if ($adresse === false || $netz === false || strlen($adresse) !== strlen($netz)) {
|
||||||
return false; // ungueltig oder verschiedene Adressfamilien
|
return false; // ungueltig oder verschiedene Adressfamilien
|
||||||
}
|
}
|
||||||
// Praefixlaenge streng pruefen. Ohne das macht ein Tippfehler in
|
// Praefixlaenge streng pruefen. Ohne das macht ein Tippfehler in
|
||||||
// LOCAL_NETWORKS die Funktion fail-open: "/-5" etwa liefert eine
|
// LOCAL_NETWORKS die Funktion fail-open: "/-5" etwa liefert eine
|
||||||
// Nullmaske, und dann liegt jede Adresse in jedem Netz.
|
// Nullmaske, und dann liegt jede Adresse in jedem Netz.
|
||||||
if (isset($teile[1])) {
|
if (isset($teile[1])) {
|
||||||
if (!ctype_digit($teile[1])) {
|
if (!ctype_digit($teile[1])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$bits = intval($teile[1]);
|
$bits = intval($teile[1]);
|
||||||
if ($bits > strlen($adresse) * 8) {
|
if ($bits > strlen($adresse) * 8) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$bits = strlen($adresse) * 8;
|
$bits = strlen($adresse) * 8;
|
||||||
}
|
}
|
||||||
$ganzeBytes = intdiv($bits, 8);
|
$ganzeBytes = intdiv($bits, 8);
|
||||||
$restBits = $bits % 8;
|
$restBits = $bits % 8;
|
||||||
if ($ganzeBytes > 0 && strncmp($adresse, $netz, $ganzeBytes) !== 0) {
|
if ($ganzeBytes > 0 && strncmp($adresse, $netz, $ganzeBytes) !== 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($restBits === 0) {
|
if ($restBits === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$maske = chr((0xFF << (8 - $restBits)) & 0xFF);
|
$maske = chr((0xFF << (8 - $restBits)) & 0xFF);
|
||||||
return ($adresse[$ganzeBytes] & $maske) === ($netz[$ganzeBytes] & $maske);
|
return ($adresse[$ganzeBytes] & $maske) === ($netz[$ganzeBytes] & $maske);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kommt die Anfrage aus dem Heimnetz? Nur dann gilt sie ohne Anmeldung als
|
* Kommt die Anfrage aus dem Heimnetz? Nur dann gilt sie ohne Anmeldung als
|
||||||
* berechtigt.
|
* berechtigt.
|
||||||
*
|
*
|
||||||
* REMOTE_ADDR stammt aus der TCP-Verbindung und ist nicht faelschbar.
|
* REMOTE_ADDR stammt aus der TCP-Verbindung und ist nicht faelschbar.
|
||||||
* Weiterleitungs-Header wie X-Forwarded-For duerfen hier bewusst NICHT
|
* Weiterleitungs-Header wie X-Forwarded-For duerfen hier bewusst NICHT
|
||||||
* herangezogen werden - sie kann jeder Aufrufer frei setzen.
|
* herangezogen werden - sie kann jeder Aufrufer frei setzen.
|
||||||
*/
|
*/
|
||||||
function isLocal()
|
function isLocal()
|
||||||
{
|
{
|
||||||
$_SESSION["local"] = false;
|
$_SESSION["local"] = false;
|
||||||
$remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
|
$remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
|
||||||
$server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '';
|
$server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '';
|
||||||
|
|
||||||
if (filter_var($remote, FILTER_VALIDATE_IP) === false) {
|
if (filter_var($remote, FILTER_VALIDATE_IP) === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auf IPv6 abgebildete IPv4-Adressen (::ffff:192.168.179.44) auf ihre
|
// Auf IPv6 abgebildete IPv4-Adressen (::ffff:192.168.179.44) auf ihre
|
||||||
// IPv4-Form zurueckfuehren. Ein Server mit Dual-Stack-Socket meldet
|
// IPv4-Form zurueckfuehren. Ein Server mit Dual-Stack-Socket meldet
|
||||||
// LAN-Clients so - ohne diesen Schritt faellt das Heimnetz durch.
|
// LAN-Clients so - ohne diesen Schritt faellt das Heimnetz durch.
|
||||||
if (preg_match('/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i', $remote, $treffer)) {
|
if (preg_match('/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i', $remote, $treffer)) {
|
||||||
$remote = $treffer[1];
|
$remote = $treffer[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
$lokal = false;
|
$lokal = false;
|
||||||
foreach (LOCAL_NETWORKS as $netz) {
|
foreach (LOCAL_NETWORKS as $netz) {
|
||||||
if (ipInNetwork($remote, $netz)) {
|
if (ipInNetwork($remote, $netz)) {
|
||||||
$lokal = true;
|
$lokal = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gleiches IPv6-/64 wie der Server. Das Praefix vergibt der Provider und
|
// Gleiches IPv6-/64 wie der Server. Das Praefix vergibt der Provider und
|
||||||
// es kann sich aendern, deshalb aus SERVER_ADDR abgeleitet statt fest.
|
// es kann sich aendern, deshalb aus SERVER_ADDR abgeleitet statt fest.
|
||||||
if (!$lokal
|
if (!$lokal
|
||||||
&& filter_var($remote, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false
|
&& filter_var($remote, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false
|
||||||
&& filter_var($server, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
|
&& filter_var($server, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
|
||||||
$lokal = substr(inet_pton($remote), 0, 8) === substr(inet_pton($server), 0, 8);
|
$lokal = substr(inet_pton($remote), 0, 8) === substr(inet_pton($server), 0, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION["local"] = $lokal;
|
$_SESSION["local"] = $lokal;
|
||||||
return $lokal;
|
return $lokal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kleine HTTP-GET-Anfrage im Heimnetz, Antwort als JSON-Array.
|
* Kleine HTTP-GET-Anfrage im Heimnetz, Antwort als JSON-Array.
|
||||||
* Ersetzt die frueher in carOG.php, heater.php und AutoAction.php je einzeln
|
* Ersetzt die frueher in carOG.php, heater.php und AutoAction.php je einzeln
|
||||||
@@ -106,43 +106,43 @@ function httpGetJson($url){
|
|||||||
return json_decode($response, true);
|
return json_decode($response, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkLogin(){
|
function checkLogin(){
|
||||||
$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]);
|
$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]);
|
||||||
if(isLocal()){
|
if(isLocal()){
|
||||||
$_SESSION["local"] =true;
|
$_SESSION["local"] =true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(isset($_SESSION["authKey"])){
|
if(isset($_SESSION["authKey"])){
|
||||||
$res = mysqli_query($mysql,"SELECT id FROM users WHERE lastAuth > DATE_SUB(NOW(), INTERVAL 2 DAY) AND authKey = '".mysqli_real_escape_string($mysql,$_SESSION["authKey"])."' AND name = '".mysqli_real_escape_string($mysql,$_SESSION["user"])."';");
|
$res = mysqli_query($mysql,"SELECT id FROM users WHERE lastAuth > DATE_SUB(NOW(), INTERVAL 2 DAY) AND authKey = '".mysqli_real_escape_string($mysql,$_SESSION["authKey"])."' AND name = '".mysqli_real_escape_string($mysql,$_SESSION["user"])."';");
|
||||||
if(!$res){
|
if(!$res){
|
||||||
echo mysqli_error($mysql);
|
echo mysqli_error($mysql);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(mysqli_num_rows($res) == 1){
|
if(mysqli_num_rows($res) == 1){
|
||||||
return isset($_SESSION["Logged"]);
|
return isset($_SESSION["Logged"]);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAdduser(){
|
function checkAdduser(){
|
||||||
$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]);
|
$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]);
|
||||||
if(!mysqli_query($mysql,"DELETE FROM addUser WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){
|
if(!mysqli_query($mysql,"DELETE FROM addUser WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){
|
||||||
echo mysqli_error($mysql);
|
echo mysqli_error($mysql);
|
||||||
}
|
}
|
||||||
$result = mysqli_query($mysql,"SELECT * FROM addUser WHERE accesskey='".mysqli_real_escape_string($mysql,$_GET["addUser"])."';");
|
$result = mysqli_query($mysql,"SELECT * FROM addUser WHERE accesskey='".mysqli_real_escape_string($mysql,$_GET["addUser"])."';");
|
||||||
if(!$result){
|
if(!$result){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($result->num_rows > 0) {
|
if ($result->num_rows > 0) {
|
||||||
// Freigabe fuer die Passkey-Registrierung vermerken. authServer.php
|
// Freigabe fuer die Passkey-Registrierung vermerken. authServer.php
|
||||||
// prueft dieses Merkmal; ohne das waeren getCreateArgs/processCreate
|
// prueft dieses Merkmal; ohne das waeren getCreateArgs/processCreate
|
||||||
// direkt aufrufbar und der Einmalschluessel wirkungslos.
|
// direkt aufrufbar und der Einmalschluessel wirkungslos.
|
||||||
$_SESSION["mayRegister"] = time();
|
$_SESSION["mayRegister"] = time();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
Vendored
+6
-6
File diff suppressed because one or more lines are too long
+148
-148
@@ -1,149 +1,149 @@
|
|||||||
async function checkRegistration() {
|
async function checkRegistration() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
|
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
|
||||||
throw new Error('Browser not supported.');
|
throw new Error('Browser not supported.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// get check args
|
// get check args
|
||||||
let rep = await window.fetch('authServer.php?fn=getGetArgs' + getGetParams(), {method:'GET',cache:'no-cache'});
|
let rep = await window.fetch('authServer.php?fn=getGetArgs' + getGetParams(), {method:'GET',cache:'no-cache'});
|
||||||
const getArgs = await rep.json();
|
const getArgs = await rep.json();
|
||||||
|
|
||||||
// error handling
|
// error handling
|
||||||
if (getArgs.success === false) {
|
if (getArgs.success === false) {
|
||||||
throw new Error(getArgs.msg);
|
throw new Error(getArgs.msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace binary base64 data with ArrayBuffer. a other way to do this
|
// replace binary base64 data with ArrayBuffer. a other way to do this
|
||||||
// is the reviver function of JSON.parse()
|
// is the reviver function of JSON.parse()
|
||||||
recursiveBase64StrToArrayBuffer(getArgs);
|
recursiveBase64StrToArrayBuffer(getArgs);
|
||||||
|
|
||||||
// check credentials with hardware
|
// check credentials with hardware
|
||||||
const cred = await navigator.credentials.get(getArgs);
|
const cred = await navigator.credentials.get(getArgs);
|
||||||
|
|
||||||
// create object for transmission to server
|
// create object for transmission to server
|
||||||
const authenticatorAttestationResponse = {
|
const authenticatorAttestationResponse = {
|
||||||
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
||||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||||
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
||||||
signature: cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null,
|
signature: cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null,
|
||||||
userHandle: cred.response.userHandle ? arrayBufferToBase64(cred.response.userHandle) : null
|
userHandle: cred.response.userHandle ? arrayBufferToBase64(cred.response.userHandle) : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// send to server
|
// send to server
|
||||||
rep = await window.fetch('authServer.php?fn=processGet' + getGetParams(), {
|
rep = await window.fetch('authServer.php?fn=processGet' + getGetParams(), {
|
||||||
method:'POST',
|
method:'POST',
|
||||||
body: JSON.stringify(authenticatorAttestationResponse),
|
body: JSON.stringify(authenticatorAttestationResponse),
|
||||||
cache:'no-cache'
|
cache:'no-cache'
|
||||||
});
|
});
|
||||||
const authenticatorAttestationServerResponse = await rep.json();
|
const authenticatorAttestationServerResponse = await rep.json();
|
||||||
|
|
||||||
// check server response
|
// check server response
|
||||||
if (authenticatorAttestationServerResponse.success) {
|
if (authenticatorAttestationServerResponse.success) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
/// window.alert(authenticatorAttestationServerResponse.msg || 'login success');
|
/// window.alert(authenticatorAttestationServerResponse.msg || 'login success');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(authenticatorAttestationServerResponse.msg);
|
throw new Error(authenticatorAttestationServerResponse.msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
//reloadServerPreview();
|
//reloadServerPreview();
|
||||||
window.alert(err.message || 'unknown error occured');
|
window.alert(err.message || 'unknown error occured');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryFidoMetaDataService() {
|
function queryFidoMetaDataService() {
|
||||||
window.fetch('authServer.php?fn=queryFidoMetaDataService' + getGetParams(), {method:'GET',cache:'no-cache'}).then(function(response) {
|
window.fetch('authServer.php?fn=queryFidoMetaDataService' + getGetParams(), {method:'GET',cache:'no-cache'}).then(function(response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
|
|
||||||
}).then(function(json) {
|
}).then(function(json) {
|
||||||
if (json.success) {
|
if (json.success) {
|
||||||
window.alert(json.msg);
|
window.alert(json.msg);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(json.msg);
|
throw new Error(json.msg);
|
||||||
}
|
}
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
window.alert(err.message || 'unknown error occured');
|
window.alert(err.message || 'unknown error occured');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert RFC 1342-like base64 strings to array buffer
|
* convert RFC 1342-like base64 strings to array buffer
|
||||||
* @param {mixed} obj
|
* @param {mixed} obj
|
||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
function recursiveBase64StrToArrayBuffer(obj) {
|
function recursiveBase64StrToArrayBuffer(obj) {
|
||||||
let prefix = '=?BINARY?B?';
|
let prefix = '=?BINARY?B?';
|
||||||
let suffix = '?=';
|
let suffix = '?=';
|
||||||
if (typeof obj === 'object') {
|
if (typeof obj === 'object') {
|
||||||
for (let key in obj) {
|
for (let key in obj) {
|
||||||
if (typeof obj[key] === 'string') {
|
if (typeof obj[key] === 'string') {
|
||||||
let str = obj[key];
|
let str = obj[key];
|
||||||
if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {
|
if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {
|
||||||
str = str.substring(prefix.length, str.length - suffix.length);
|
str = str.substring(prefix.length, str.length - suffix.length);
|
||||||
|
|
||||||
let binary_string = window.atob(str);
|
let binary_string = window.atob(str);
|
||||||
let len = binary_string.length;
|
let len = binary_string.length;
|
||||||
let bytes = new Uint8Array(len);
|
let bytes = new Uint8Array(len);
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
bytes[i] = binary_string.charCodeAt(i);
|
bytes[i] = binary_string.charCodeAt(i);
|
||||||
}
|
}
|
||||||
obj[key] = bytes.buffer;
|
obj[key] = bytes.buffer;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
recursiveBase64StrToArrayBuffer(obj[key]);
|
recursiveBase64StrToArrayBuffer(obj[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a ArrayBuffer to Base64
|
* Convert a ArrayBuffer to Base64
|
||||||
* @param {ArrayBuffer} buffer
|
* @param {ArrayBuffer} buffer
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
function arrayBufferToBase64(buffer) {
|
function arrayBufferToBase64(buffer) {
|
||||||
let binary = '';
|
let binary = '';
|
||||||
let bytes = new Uint8Array(buffer);
|
let bytes = new Uint8Array(buffer);
|
||||||
let len = bytes.byteLength;
|
let len = bytes.byteLength;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
binary += String.fromCharCode( bytes[ i ] );
|
binary += String.fromCharCode( bytes[ i ] );
|
||||||
}
|
}
|
||||||
return window.btoa(binary);
|
return window.btoa(binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get URL parameter
|
* Get URL parameter
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
function getGetParams() {
|
function getGetParams() {
|
||||||
let url = '';
|
let url = '';
|
||||||
|
|
||||||
url += '&apple=1';
|
url += '&apple=1';
|
||||||
url += '&yubico=1';
|
url += '&yubico=1';
|
||||||
url += '&solo=1';
|
url += '&solo=1';
|
||||||
url += '&hypersecu=1';
|
url += '&hypersecu=1';
|
||||||
url += '&google=1';
|
url += '&google=1';
|
||||||
url += 'µsoft=1';
|
url += 'µsoft=1';
|
||||||
url += '&mds=1';
|
url += '&mds=1';
|
||||||
|
|
||||||
url += '&requireResidentKey=0';
|
url += '&requireResidentKey=0';
|
||||||
|
|
||||||
url += '&type_usb=1';
|
url += '&type_usb=1';
|
||||||
url += '&type_nfc=1';
|
url += '&type_nfc=1';
|
||||||
url += '&type_ble=1';
|
url += '&type_ble=1';
|
||||||
url += '&type_int=1';
|
url += '&type_int=1';
|
||||||
url += '&type_hybrid=1';
|
url += '&type_hybrid=1';
|
||||||
|
|
||||||
url += '&fmt_android-key=1';
|
url += '&fmt_android-key=1';
|
||||||
url += '&fmt_android-safetynet=1';
|
url += '&fmt_android-safetynet=1';
|
||||||
url += '&fmt_apple=1';
|
url += '&fmt_apple=1';
|
||||||
url += '&fmt_fido-u2f=1';
|
url += '&fmt_fido-u2f=1';
|
||||||
url += '&fmt_none=0' ;
|
url += '&fmt_none=0' ;
|
||||||
url += '&fmt_packed=1';
|
url += '&fmt_packed=1';
|
||||||
url += '&fmt_tpm=1';
|
url += '&fmt_tpm=1';
|
||||||
url += '&userVerification=discouraged';
|
url += '&userVerification=discouraged';
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
Vendored
+6
-6
File diff suppressed because one or more lines are too long
Vendored
+13
-13
File diff suppressed because one or more lines are too long
Vendored
+6
-6
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
/*! https://github.com/FranBar1966/bootstrap-5-modal-dynamic - License in the terms described in the LICENSE file */
|
/*! https://github.com/FranBar1966/bootstrap-5-modal-dynamic - License in the terms described in the LICENSE file */
|
||||||
function startDynamicModal(){document.body.removeEventListener("click",dynamicModalHandler),document.body.addEventListener("click",dynamicModalHandler)}function dynamicModalHandler(e){const t=e.target.closest(".modal-dynamic");if(!t)return;e.target.closest("a")&&e.preventDefault();const a=e.target.getAttribute("href"),d=e.target.dataset.template||"#modalTemplate";let o=document.querySelector(a);if(!o){const e=document.querySelector(d);e&&(o=e.cloneNode(!0),o.id=a.substring(1),document.body.appendChild(o))}if(!o)return;const n=e.target.dataset.class,r=e.target.dataset.title,s=e.target.dataset.header,l=e.target.dataset.noheader,c=e.target.dataset.url,i=e.target.dataset.footer,u=e.target.dataset.nofooter,m=e.target.dataset.width||"",y=e.target.dataset.backdrop||"false",h=!e.target.dataset.keyboard||"true"===e.target.dataset.keyboard;if(n&&o.classList.add(...n.split(" ")),s){const e=o.querySelector(".modal-header"),t=document.querySelector(s);e&&t&&(e.innerHTML=t.innerHTML)}if(l){const e=o.querySelector(".modal-header");e&&e.classList.add("hidden","d-none")}if(r){const e=o.querySelector(".modal-title");e&&(e.innerHTML=r)}if(i){const e=o.querySelector(".modal-footer"),t=document.querySelector(i);e&&t&&(e.innerHTML=t.innerHTML)}if(u){const e=o.querySelector(".modal-footer");e&&e.classList.add("hidden","d-none")}if(m){const e=o.querySelector(".modal-dialog");if(e){const t=isNaN(m)||""===m?m:m+"px";e.style.maxWidth=t,e.style.width="auto"}}let f=bootstrap.Modal.getInstance(o);f||(f=new bootstrap.Modal(o,{keyboard:h,backdrop:y})),o.addEventListener("hidden.bs.modal",e=>{o.remove()}),o.addEventListener("shown.bs.modal",e=>{o.focus()}),f.show();const g=o.querySelector(".modal-body");if(c.startsWith("#")){const e=document.querySelector(c);g.innerHTML=e?e.innerHTML:"ERROR: Content not found"}else fetch(c,{method:"GET",headers:{"X-Requested-From-Modal":a.substring(1),"Requested-With-Ajax":"ajax"}}).then(e=>e.text()).then(e=>{g.innerHTML=e,window.dispatchEvent(new CustomEvent("neutralFetchCompleted",{detail:{element:o,url:c}}))}).catch(e=>{g.innerHTML=e.message,window.dispatchEvent(new CustomEvent("neutralFetchError",{detail:{element:o,url:c}}))})}startDynamicModal(),window.addEventListener("neutralFetchCompleted",()=>{startDynamicModal()});
|
function startDynamicModal(){document.body.removeEventListener("click",dynamicModalHandler),document.body.addEventListener("click",dynamicModalHandler)}function dynamicModalHandler(e){const t=e.target.closest(".modal-dynamic");if(!t)return;e.target.closest("a")&&e.preventDefault();const a=e.target.getAttribute("href"),d=e.target.dataset.template||"#modalTemplate";let o=document.querySelector(a);if(!o){const e=document.querySelector(d);e&&(o=e.cloneNode(!0),o.id=a.substring(1),document.body.appendChild(o))}if(!o)return;const n=e.target.dataset.class,r=e.target.dataset.title,s=e.target.dataset.header,l=e.target.dataset.noheader,c=e.target.dataset.url,i=e.target.dataset.footer,u=e.target.dataset.nofooter,m=e.target.dataset.width||"",y=e.target.dataset.backdrop||"false",h=!e.target.dataset.keyboard||"true"===e.target.dataset.keyboard;if(n&&o.classList.add(...n.split(" ")),s){const e=o.querySelector(".modal-header"),t=document.querySelector(s);e&&t&&(e.innerHTML=t.innerHTML)}if(l){const e=o.querySelector(".modal-header");e&&e.classList.add("hidden","d-none")}if(r){const e=o.querySelector(".modal-title");e&&(e.innerHTML=r)}if(i){const e=o.querySelector(".modal-footer"),t=document.querySelector(i);e&&t&&(e.innerHTML=t.innerHTML)}if(u){const e=o.querySelector(".modal-footer");e&&e.classList.add("hidden","d-none")}if(m){const e=o.querySelector(".modal-dialog");if(e){const t=isNaN(m)||""===m?m:m+"px";e.style.maxWidth=t,e.style.width="auto"}}let f=bootstrap.Modal.getInstance(o);f||(f=new bootstrap.Modal(o,{keyboard:h,backdrop:y})),o.addEventListener("hidden.bs.modal",e=>{o.remove()}),o.addEventListener("shown.bs.modal",e=>{o.focus()}),f.show();const g=o.querySelector(".modal-body");if(c.startsWith("#")){const e=document.querySelector(c);g.innerHTML=e?e.innerHTML:"ERROR: Content not found"}else fetch(c,{method:"GET",headers:{"X-Requested-From-Modal":a.substring(1),"Requested-With-Ajax":"ajax"}}).then(e=>e.text()).then(e=>{g.innerHTML=e,window.dispatchEvent(new CustomEvent("neutralFetchCompleted",{detail:{element:o,url:c}}))}).catch(e=>{g.innerHTML=e.message,window.dispatchEvent(new CustomEvent("neutralFetchError",{detail:{element:o,url:c}}))})}startDynamicModal(),window.addEventListener("neutralFetchCompleted",()=>{startDynamicModal()});
|
||||||
Vendored
+6
-6
File diff suppressed because one or more lines are too long
+859
-859
File diff suppressed because it is too large
Load Diff
+193
-193
@@ -1,193 +1,193 @@
|
|||||||
var mqttData = {};
|
var mqttData = {};
|
||||||
|
|
||||||
const solarMQTT = {
|
const solarMQTT = {
|
||||||
getMQTT: function () {
|
getMQTT: function () {
|
||||||
const id = Math.random().toString(36).substring(7);
|
const id = Math.random().toString(36).substring(7);
|
||||||
const topic = "#";
|
const topic = "#";
|
||||||
const connection = "wss://mqtt.nas.el-wa.org:443"
|
const connection = "wss://mqtt.nas.el-wa.org:443"
|
||||||
mqttsolarTreeDone = false;
|
mqttsolarTreeDone = false;
|
||||||
// const connection = "ws://username:password@37.97.203.138:8083" // Works
|
// const connection = "ws://username:password@37.97.203.138:8083" // Works
|
||||||
// const connection = "wss://public:public@public.cloud.shiftr.io" // Works
|
// const connection = "wss://public:public@public.cloud.shiftr.io" // Works
|
||||||
const client = mqtt.connect(connection, {
|
const client = mqtt.connect(connection, {
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("message", messageReceived);
|
client.on("message", messageReceived);
|
||||||
client.on("connect", function () {
|
client.on("connect", function () {
|
||||||
client.subscribe("solarManager/#");
|
client.subscribe("solarManager/#");
|
||||||
client.subscribe("wattpilot/properties/lmo/state");
|
client.subscribe("wattpilot/properties/lmo/state");
|
||||||
client.subscribe("wattpilot/properties/ftt/state");
|
client.subscribe("wattpilot/properties/ftt/state");
|
||||||
client.subscribe("wattpilot/properties/fte/state");
|
client.subscribe("wattpilot/properties/fte/state");
|
||||||
client.subscribe("wattpilot/properties/amp/state");
|
client.subscribe("wattpilot/properties/amp/state");
|
||||||
client.subscribe("wattpilot/properties/car/state");
|
client.subscribe("wattpilot/properties/car/state");
|
||||||
client.subscribe("go-eCharger/270003/amp");
|
client.subscribe("go-eCharger/270003/amp");
|
||||||
client.subscribe("go-eCharger/270003/ate");
|
client.subscribe("go-eCharger/270003/ate");
|
||||||
client.subscribe("go-eCharger/270003/lmo");
|
client.subscribe("go-eCharger/270003/lmo");
|
||||||
client.subscribe("go-eCharger/270003/att");
|
client.subscribe("go-eCharger/270003/att");
|
||||||
client.subscribe("go-eCharger/270003/car");
|
client.subscribe("go-eCharger/270003/car");
|
||||||
client.subscribe("weatherStation/#");
|
client.subscribe("weatherStation/#");
|
||||||
});
|
});
|
||||||
client.on("error", function (error) {
|
client.on("error", function (error) {
|
||||||
//alert("MQTT Error: " + error);
|
//alert("MQTT Error: " + error);
|
||||||
});
|
});
|
||||||
client.on('end', function () {
|
client.on('end', function () {
|
||||||
setTimeout(getMQTT, 5000);
|
setTimeout(getMQTT, 5000);
|
||||||
alert("MQTT Disconnected, try to reconnect in 5 secs.");
|
alert("MQTT Disconnected, try to reconnect in 5 secs.");
|
||||||
})
|
})
|
||||||
|
|
||||||
function getNestedProp(obj, path) {
|
function getNestedProp(obj, path) {
|
||||||
return path.split('/').reduce((acc, key) => acc && acc[key], obj);
|
return path.split('/').reduce((acc, key) => acc && acc[key], obj);
|
||||||
}
|
}
|
||||||
function setNestedProp(obj, path, value) {
|
function setNestedProp(obj, path, value) {
|
||||||
var schema = obj; // a moving reference to internal objects within obj
|
var schema = obj; // a moving reference to internal objects within obj
|
||||||
var pList = path.split('/');
|
var pList = path.split('/');
|
||||||
var len = pList.length;
|
var len = pList.length;
|
||||||
for (var i = 0; i < len - 1; i++) {
|
for (var i = 0; i < len - 1; i++) {
|
||||||
var elem = pList[i];
|
var elem = pList[i];
|
||||||
if (!schema[elem]) schema[elem] = {}
|
if (!schema[elem]) schema[elem] = {}
|
||||||
schema = schema[elem];
|
schema = schema[elem];
|
||||||
}
|
}
|
||||||
|
|
||||||
schema[pList[len - 1]] = value;
|
schema[pList[len - 1]] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function messageReceived(topic, message) {
|
function messageReceived(topic, message) {
|
||||||
setNestedProp(mqttData, topic, message);
|
setNestedProp(mqttData, topic, message);
|
||||||
if (topic == "solarManager/P_Load") {
|
if (topic == "solarManager/P_Load") {
|
||||||
setTimeout(function () { solarSVG.updateValuesMQTT(mqttData) }, 200); //give the object tree some time to build up and receive all values
|
setTimeout(function () { solarSVG.updateValuesMQTT(mqttData) }, 200); //give the object tree some time to build up and receive all values
|
||||||
} else if (topic == "weatherStation/windDeg") {
|
} else if (topic == "weatherStation/windDeg") {
|
||||||
setTimeout(function () { updateWeatherCards(name => mqttData["weatherStation"]?.[name]) }, 200);
|
setTimeout(function () { updateWeatherCards(name => mqttData["weatherStation"]?.[name]) }, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const solarSVG = {
|
const solarSVG = {
|
||||||
updateCnt: 99,
|
updateCnt: 99,
|
||||||
updateValuesMQTT: function (mqttData) {
|
updateValuesMQTT: function (mqttData) {
|
||||||
if (this.updateCnt > 10) {
|
if (this.updateCnt > 10) {
|
||||||
this.updateCnt = 0;
|
this.updateCnt = 0;
|
||||||
var obj = document.querySelector("object");
|
var obj = document.querySelector("object");
|
||||||
var htmlNode = obj.contentDocument;
|
var htmlNode = obj.contentDocument;
|
||||||
htmlNode.getElementById("PufferOtxt").innerHTML = mqttData["solarManager"]["t_buffT"] + " °C";
|
htmlNode.getElementById("PufferOtxt").innerHTML = mqttData["solarManager"]["t_buffT"] + " °C";
|
||||||
htmlNode.getElementById("PufferMtxt").innerHTML = mqttData["solarManager"]["t_buffM"] + " °C";
|
htmlNode.getElementById("PufferMtxt").innerHTML = mqttData["solarManager"]["t_buffM"] + " °C";
|
||||||
htmlNode.getElementById("PufferUtxt").innerHTML = mqttData["solarManager"]["t_buffB"] + " °C";
|
htmlNode.getElementById("PufferUtxt").innerHTML = mqttData["solarManager"]["t_buffB"] + " °C";
|
||||||
htmlNode.getElementById("heaterVL").innerHTML = mqttData["solarManager"]["t_heatVL"] + " °C";
|
htmlNode.getElementById("heaterVL").innerHTML = mqttData["solarManager"]["t_heatVL"] + " °C";
|
||||||
htmlNode.getElementById("heaterRL").innerHTML = mqttData["solarManager"]["t_heatRL"] + " °C";
|
htmlNode.getElementById("heaterRL").innerHTML = mqttData["solarManager"]["t_heatRL"] + " °C";
|
||||||
htmlNode.getElementById("thermeVLfb").innerHTML = mqttData["solarManager"]["t_gasVLu"] + " °C";
|
htmlNode.getElementById("thermeVLfb").innerHTML = mqttData["solarManager"]["t_gasVLu"] + " °C";
|
||||||
htmlNode.getElementById("thermeVLww").innerHTML = mqttData["solarManager"]["t_gasVLo"] + " °C";
|
htmlNode.getElementById("thermeVLww").innerHTML = mqttData["solarManager"]["t_gasVLo"] + " °C";
|
||||||
htmlNode.getElementById("thermeRL").innerHTML = mqttData["solarManager"]["t_gasRL"] + " °C";
|
htmlNode.getElementById("thermeRL").innerHTML = mqttData["solarManager"]["t_gasRL"] + " °C";
|
||||||
htmlNode.getElementById("fbVL").innerHTML = mqttData["solarManager"]["t_fbVL"] + " °C";
|
htmlNode.getElementById("fbVL").innerHTML = mqttData["solarManager"]["t_fbVL"] + " °C";
|
||||||
htmlNode.getElementById("fbRL").innerHTML = mqttData["solarManager"]["t_fbRL"] + " °C";
|
htmlNode.getElementById("fbRL").innerHTML = mqttData["solarManager"]["t_fbRL"] + " °C";
|
||||||
htmlNode.getElementById("triac").innerHTML = mqttData["solarManager"]["t_triac"] + " °C";
|
htmlNode.getElementById("triac").innerHTML = mqttData["solarManager"]["t_triac"] + " °C";
|
||||||
}
|
}
|
||||||
this.updateCnt++;
|
this.updateCnt++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var chartSettings = {
|
var chartSettings = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
options: {
|
options: {
|
||||||
animation: true,
|
animation: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
annotation: {
|
annotation: {
|
||||||
common: { type: 'box', drawTime: 'beforeDatasetsDraw', yScaleID: 'y-axis-0', backgroundColor: 'rgba(255, 255, 255, 0.05)', init: true },
|
common: { type: 'box', drawTime: 'beforeDatasetsDraw', yScaleID: 'y-axis-0', backgroundColor: 'rgba(255, 255, 255, 0.05)', init: true },
|
||||||
annotations: []
|
annotations: []
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: 'nearest',
|
position: 'nearest',
|
||||||
pointStyle: "circle",
|
pointStyle: "circle",
|
||||||
boxWidth: 4,
|
boxWidth: 4,
|
||||||
usePointStyle: true,
|
usePointStyle: true,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: function (context) {
|
label: function (context) {
|
||||||
let label = context.dataset.label || '';
|
let label = context.dataset.label || '';
|
||||||
if (label) {
|
if (label) {
|
||||||
label += ': ';
|
label += ': ';
|
||||||
}
|
}
|
||||||
if (context.dataset.yAxisID == "y1") {
|
if (context.dataset.yAxisID == "y1") {
|
||||||
label += Math.round(context.parsed.y * 10) / 10 + " " + "L/min";
|
label += Math.round(context.parsed.y * 10) / 10 + " " + "L/min";
|
||||||
} else {
|
} else {
|
||||||
if (context.parsed.y !== null) {
|
if (context.parsed.y !== null) {
|
||||||
ret = scale(Math.round(context.parsed.y), false);
|
ret = scale(Math.round(context.parsed.y), false);
|
||||||
label += ret[0] + " " + ret[1] + "°C";
|
label += ret[0] + " " + ret[1] + "°C";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return label;
|
return label;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
position: "bottom",
|
position: "bottom",
|
||||||
labels: {
|
labels: {
|
||||||
pointStyleWidth: 10,
|
pointStyleWidth: 10,
|
||||||
usePointStyle: true,
|
usePointStyle: true,
|
||||||
pointStyle: "line",
|
pointStyle: "line",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
interaction: {
|
interaction: {
|
||||||
intersect: false,
|
intersect: false,
|
||||||
mode: 'index',
|
mode: 'index',
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
adapters: {
|
adapters: {
|
||||||
date: {
|
date: {
|
||||||
locale: "DE-de"
|
locale: "DE-de"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
|
|
||||||
},
|
},
|
||||||
type: 'timestack',
|
type: 'timestack',
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
stacked: false,
|
stacked: false,
|
||||||
display: true,
|
display: true,
|
||||||
position: 'left',
|
position: 'left',
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: value => `${value} °C`,
|
callback: value => `${value} °C`,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: "Temperatur"
|
text: "Temperatur"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
y1: {
|
y1: {
|
||||||
stacked: false,
|
stacked: false,
|
||||||
display: true,
|
display: true,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: value => `${value} L/min`,
|
callback: value => `${value} L/min`,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: "Wasserverbrauch"
|
text: "Wasserverbrauch"
|
||||||
},
|
},
|
||||||
data:{}
|
data:{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var chartData = {};
|
var chartData = {};
|
||||||
const heatChart = new Chart(
|
const heatChart = new Chart(
|
||||||
document.querySelector('#heat-chart'),
|
document.querySelector('#heat-chart'),
|
||||||
Object.assign({}, chartSettings)
|
Object.assign({}, chartSettings)
|
||||||
);
|
);
|
||||||
const waterChart = new Chart(
|
const waterChart = new Chart(
|
||||||
document.querySelector('#water-chart'),
|
document.querySelector('#water-chart'),
|
||||||
Object.assign({}, chartSettings)
|
Object.assign({}, chartSettings)
|
||||||
);
|
);
|
||||||
|
|
||||||
document.addEventListener('readystatechange', function () {
|
document.addEventListener('readystatechange', function () {
|
||||||
if (event.target.readyState === "complete") {
|
if (event.target.readyState === "complete") {
|
||||||
solarMQTT.getMQTT();
|
solarMQTT.getMQTT();
|
||||||
getData(heatChart, 'ajax/getHeaterData.php', { sunrise: { FROM: -24, TO: 0 } });
|
getData(heatChart, 'ajax/getHeaterData.php', { sunrise: { FROM: -24, TO: 0 } });
|
||||||
getData(waterChart, 'ajax/getWaterData.php', { sunrise: { FROM: -24, TO: 0 } });
|
getData(waterChart, 'ajax/getWaterData.php', { sunrise: { FROM: -24, TO: 0 } });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+152
-152
@@ -1,152 +1,152 @@
|
|||||||
tooltipLabel = function (context) {
|
tooltipLabel = function (context) {
|
||||||
let label = context.dataset.label || '';
|
let label = context.dataset.label || '';
|
||||||
if (label) {
|
if (label) {
|
||||||
label += ': ';
|
label += ': ';
|
||||||
}
|
}
|
||||||
if (context.dataset.yAxisID == "y1") {
|
if (context.dataset.yAxisID == "y1") {
|
||||||
label += Math.round(context.parsed.y * 10) / 10 + " " + "%";
|
label += Math.round(context.parsed.y * 10) / 10 + " " + "%";
|
||||||
} else {
|
} else {
|
||||||
if (context.parsed.y !== null) {
|
if (context.parsed.y !== null) {
|
||||||
ret = scale(Math.round(context.parsed.y), false);
|
ret = scale(Math.round(context.parsed.y), false);
|
||||||
label += ret[0] + " " + ret[1] + "Wh";
|
label += ret[0] + " " + ret[1] + "Wh";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return label;
|
return label;
|
||||||
};
|
};
|
||||||
|
|
||||||
tooltipFooter = function (tooltipItems){
|
tooltipFooter = function (tooltipItems){
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
|
||||||
tooltipItems.forEach(function(tooltipItem) {
|
tooltipItems.forEach(function(tooltipItem) {
|
||||||
if (tooltipItem.dataset.yAxisID != "y1") {
|
if (tooltipItem.dataset.yAxisID != "y1") {
|
||||||
sum += tooltipItem.parsed.y;
|
sum += tooltipItem.parsed.y;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ret = scale(Math.round(sum), false);
|
ret = scale(Math.round(sum), false);
|
||||||
sum = ret[0] + " " + ret[1] + "Wh";
|
sum = ret[0] + " " + ret[1] + "Wh";
|
||||||
return 'Summe: ' + sum;
|
return 'Summe: ' + sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
legendLabels = function(chart){
|
legendLabels = function(chart){
|
||||||
const datasets = chart.data.datasets;
|
const datasets = chart.data.datasets;
|
||||||
const {
|
const {
|
||||||
labels: {
|
labels: {
|
||||||
usePointStyle,
|
usePointStyle,
|
||||||
pointStyle,
|
pointStyle,
|
||||||
textAlign,
|
textAlign,
|
||||||
color
|
color
|
||||||
}
|
}
|
||||||
} = chart.legend.options;
|
} = chart.legend.options;
|
||||||
return chart._getSortedDatasetMetas().map((meta) => {
|
return chart._getSortedDatasetMetas().map((meta) => {
|
||||||
const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
|
const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
|
||||||
const borderWidth = Chart.helpers.toPadding(style.borderWidth);
|
const borderWidth = Chart.helpers.toPadding(style.borderWidth);
|
||||||
ret = scale(Math.round(arraySum(datasets[meta.index].data)), false);
|
ret = scale(Math.round(arraySum(datasets[meta.index].data)), false);
|
||||||
ret2 = scale(Math.round(datasets[meta.index].data[datasets[meta.index].data.length-1]), false);
|
ret2 = scale(Math.round(datasets[meta.index].data[datasets[meta.index].data.length-1]), false);
|
||||||
return {
|
return {
|
||||||
text: datasets[meta.index].label + " Σ " + ret[0]+" "+ret[1]+"Wh",//+ " Last: " + ret2[0]+" "+ret2[1]+"W",
|
text: datasets[meta.index].label + " Σ " + ret[0]+" "+ret[1]+"Wh",//+ " Last: " + ret2[0]+" "+ret2[1]+"W",
|
||||||
fillStyle: style.backgroundColor,
|
fillStyle: style.backgroundColor,
|
||||||
fontColor: color,
|
fontColor: color,
|
||||||
hidden: !meta.visible,
|
hidden: !meta.visible,
|
||||||
lineCap: style.borderCapStyle,
|
lineCap: style.borderCapStyle,
|
||||||
lineDash: style.borderDash,
|
lineDash: style.borderDash,
|
||||||
lineDashOffset: style.borderDashOffset,
|
lineDashOffset: style.borderDashOffset,
|
||||||
lineJoin: style.borderJoinStyle,
|
lineJoin: style.borderJoinStyle,
|
||||||
lineWidth: (borderWidth.width + borderWidth.height) / 4,
|
lineWidth: (borderWidth.width + borderWidth.height) / 4,
|
||||||
strokeStyle: style.borderColor,
|
strokeStyle: style.borderColor,
|
||||||
pointStyle: pointStyle || style.pointStyle,
|
pointStyle: pointStyle || style.pointStyle,
|
||||||
rotation: style.rotation,
|
rotation: style.rotation,
|
||||||
textAlign: textAlign || style.textAlign,
|
textAlign: textAlign || style.textAlign,
|
||||||
borderRadius: 0, // TODO: v4, default to style.borderRadius
|
borderRadius: 0, // TODO: v4, default to style.borderRadius
|
||||||
datasetIndex: meta.index
|
datasetIndex: meta.index
|
||||||
};
|
};
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
Chart.defaults.plugins.tooltip.callbacks.footer = tooltipFooter;
|
Chart.defaults.plugins.tooltip.callbacks.footer = tooltipFooter;
|
||||||
Chart.defaults.plugins.tooltip.callbacks.label = tooltipLabel;
|
Chart.defaults.plugins.tooltip.callbacks.label = tooltipLabel;
|
||||||
Chart.defaults.plugins.legend.labels.generateLabels = legendLabels;
|
Chart.defaults.plugins.legend.labels.generateLabels = legendLabels;
|
||||||
|
|
||||||
var forecastChartSettings = {
|
var forecastChartSettings = {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
interaction: {
|
interaction: {
|
||||||
intersect: false,
|
intersect: false,
|
||||||
mode: 'index',
|
mode: 'index',
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
display: true,
|
display: true,
|
||||||
min: 0,
|
min: 0,
|
||||||
suggestedMax: 1000,
|
suggestedMax: 1000,
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: value => `${value / 1000} kWh`,
|
callback: value => `${value / 1000} kWh`,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var decadeChartSettings = {
|
var decadeChartSettings = {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
interaction: {
|
interaction: {
|
||||||
intersect: false,
|
intersect: false,
|
||||||
mode: 'index',
|
mode: 'index',
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
display: true,
|
display: true,
|
||||||
min: 0,
|
min: 0,
|
||||||
suggestedMax: 1000000,
|
suggestedMax: 1000000,
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: value => `${value / 1000000} MWh`,
|
callback: value => `${value / 1000000} MWh`,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var chartData = {};
|
var chartData = {};
|
||||||
|
|
||||||
const consChart = new Chart(
|
const consChart = new Chart(
|
||||||
document.querySelector('#consumption-chart'),
|
document.querySelector('#consumption-chart'),
|
||||||
Object.assign({}, forecastChartSettings)
|
Object.assign({}, forecastChartSettings)
|
||||||
);
|
);
|
||||||
const prodChart = new Chart(
|
const prodChart = new Chart(
|
||||||
document.querySelector('#production-chart'),
|
document.querySelector('#production-chart'),
|
||||||
Object.assign({}, forecastChartSettings)
|
Object.assign({}, forecastChartSettings)
|
||||||
);
|
);
|
||||||
const consChartYear = new Chart(
|
const consChartYear = new Chart(
|
||||||
document.querySelector('#consumption-chart-year'),
|
document.querySelector('#consumption-chart-year'),
|
||||||
Object.assign({}, decadeChartSettings)
|
Object.assign({}, decadeChartSettings)
|
||||||
);
|
);
|
||||||
const prodChartYear = new Chart(
|
const prodChartYear = new Chart(
|
||||||
document.querySelector('#production-chart-year'),
|
document.querySelector('#production-chart-year'),
|
||||||
Object.assign({}, decadeChartSettings)
|
Object.assign({}, decadeChartSettings)
|
||||||
);
|
);
|
||||||
const consChartDecade = new Chart(
|
const consChartDecade = new Chart(
|
||||||
document.querySelector('#consumption-chart-decade'),
|
document.querySelector('#consumption-chart-decade'),
|
||||||
Object.assign({}, decadeChartSettings)
|
Object.assign({}, decadeChartSettings)
|
||||||
);
|
);
|
||||||
const prodChartDecade = new Chart(
|
const prodChartDecade = new Chart(
|
||||||
document.querySelector('#production-chart-decade'),
|
document.querySelector('#production-chart-decade'),
|
||||||
Object.assign({}, decadeChartSettings)
|
Object.assign({}, decadeChartSettings)
|
||||||
);
|
);
|
||||||
document.addEventListener('readystatechange', function () {
|
document.addEventListener('readystatechange', function () {
|
||||||
if (event.target.readyState === "complete") {
|
if (event.target.readyState === "complete") {
|
||||||
getData(prodChart, 'ajax/energyHistory.php?series=prod&range=month');
|
getData(prodChart, 'ajax/energyHistory.php?series=prod&range=month');
|
||||||
getData(consChart, 'ajax/energyHistory.php?series=cons&range=month');
|
getData(consChart, 'ajax/energyHistory.php?series=cons&range=month');
|
||||||
getData(prodChartYear, 'ajax/energyHistory.php?series=prod&range=year');
|
getData(prodChartYear, 'ajax/energyHistory.php?series=prod&range=year');
|
||||||
getData(consChartYear, 'ajax/energyHistory.php?series=cons&range=year');
|
getData(consChartYear, 'ajax/energyHistory.php?series=cons&range=year');
|
||||||
getData(prodChartDecade, 'ajax/energyHistory.php?series=prod&range=decade');
|
getData(prodChartDecade, 'ajax/energyHistory.php?series=prod&range=decade');
|
||||||
getData(consChartDecade, 'ajax/energyHistory.php?series=cons&range=decade');
|
getData(consChartDecade, 'ajax/energyHistory.php?series=cons&range=decade');
|
||||||
getStats("Stats-Year","ajax/getStats.php?type=ThisYear");
|
getStats("Stats-Year","ajax/getStats.php?type=ThisYear");
|
||||||
getStats("Stats-Lastyear","ajax/getStats.php?type=LastYear");
|
getStats("Stats-Lastyear","ajax/getStats.php?type=LastYear");
|
||||||
getStats("Stats-Prelastyear","ajax/getStats.php?type=PreLastYear");
|
getStats("Stats-Prelastyear","ajax/getStats.php?type=PreLastYear");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+422
-422
@@ -1,59 +1,59 @@
|
|||||||
|
|
||||||
const homeMQTT = {
|
const homeMQTT = {
|
||||||
getMQTT: function () {
|
getMQTT: function () {
|
||||||
const id = Math.random().toString(36).substring(7);
|
const id = Math.random().toString(36).substring(7);
|
||||||
const topic = "#";
|
const topic = "#";
|
||||||
const connection = "wss://mqtt.nas.el-wa.org:443"
|
const connection = "wss://mqtt.nas.el-wa.org:443"
|
||||||
mqttsolarTreeDone = false;
|
mqttsolarTreeDone = false;
|
||||||
// const connection = "ws://username:password@37.97.203.138:8083" // Works
|
// const connection = "ws://username:password@37.97.203.138:8083" // Works
|
||||||
// const connection = "wss://public:public@public.cloud.shiftr.io" // Works
|
// const connection = "wss://public:public@public.cloud.shiftr.io" // Works
|
||||||
const client = mqtt.connect(connection, {
|
const client = mqtt.connect(connection, {
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("message", messageReceived);
|
client.on("message", messageReceived);
|
||||||
client.on("connect", function () {
|
client.on("connect", function () {
|
||||||
// Welche Zweige gebraucht werden, sagt die Raumtabelle: seit eine
|
// Welche Zweige gebraucht werden, sagt die Raumtabelle: seit eine
|
||||||
// Kachel jeden Messwert zeigen kann, reicht Raumtemp allein nicht
|
// Kachel jeden Messwert zeigen kann, reicht Raumtemp allein nicht
|
||||||
// mehr. homeTopics kommt aus restricted/rooms.php.
|
// mehr. homeTopics kommt aus restricted/rooms.php.
|
||||||
homeTopics.forEach(function (topic) { client.subscribe(topic); });
|
homeTopics.forEach(function (topic) { client.subscribe(topic); });
|
||||||
});
|
});
|
||||||
client.on("error", function (error) {
|
client.on("error", function (error) {
|
||||||
//alert("MQTT Error: " + error);
|
//alert("MQTT Error: " + error);
|
||||||
});
|
});
|
||||||
client.on('end', function () {
|
client.on('end', function () {
|
||||||
setTimeout(getMQTT, 5000);
|
setTimeout(getMQTT, 5000);
|
||||||
alert("MQTT Disconnected, try to reconnect in 5 secs.");
|
alert("MQTT Disconnected, try to reconnect in 5 secs.");
|
||||||
})
|
})
|
||||||
|
|
||||||
function getNestedProp(obj, path) {
|
function getNestedProp(obj, path) {
|
||||||
return path.split('/').reduce((acc, key) => acc && acc[key], obj);
|
return path.split('/').reduce((acc, key) => acc && acc[key], obj);
|
||||||
}
|
}
|
||||||
function setNestedProp(obj, path, value) {
|
function setNestedProp(obj, path, value) {
|
||||||
var schema = obj; // a moving reference to internal objects within obj
|
var schema = obj; // a moving reference to internal objects within obj
|
||||||
var pList = path.split('/');
|
var pList = path.split('/');
|
||||||
var len = pList.length;
|
var len = pList.length;
|
||||||
for (var i = 0; i < len - 1; i++) {
|
for (var i = 0; i < len - 1; i++) {
|
||||||
var elem = pList[i];
|
var elem = pList[i];
|
||||||
if (!schema[elem]) schema[elem] = {}
|
if (!schema[elem]) schema[elem] = {}
|
||||||
schema = schema[elem];
|
schema = schema[elem];
|
||||||
}
|
}
|
||||||
|
|
||||||
schema[pList[len - 1]] = value;
|
schema[pList[len - 1]] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function messageReceived(topic, message) {
|
function messageReceived(topic, message) {
|
||||||
setNestedProp(mqttData, topic, message);
|
setNestedProp(mqttData, topic, message);
|
||||||
setTimeout(function () { homeSVG.updateValuesMQTT(mqttData) }, 200); //give the object tree some time to build up and receive all values
|
setTimeout(function () { homeSVG.updateValuesMQTT(mqttData) }, 200); //give the object tree some time to build up and receive all values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const homeSVG = {
|
const homeSVG = {
|
||||||
updateCnt: 99,
|
updateCnt: 99,
|
||||||
fillElementArray: function () {
|
fillElementArray: function () {
|
||||||
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Eine Nachricht aus dem Baum holen. mqttData ist nach den Teilen des
|
* Eine Nachricht aus dem Baum holen. mqttData ist nach den Teilen des
|
||||||
* Topics verschachtelt, siehe setNestedProp() weiter oben.
|
* Topics verschachtelt, siehe setNestedProp() weiter oben.
|
||||||
@@ -116,369 +116,369 @@ const homeSVG = {
|
|||||||
if (typeof (values["mode"]) != "undefined")
|
if (typeof (values["mode"]) != "undefined")
|
||||||
el(room.id + "_buffer").setAttribute("display", values["mode"] == "Overheating" ? "" : "none");
|
el(room.id + "_buffer").setAttribute("display", values["mode"] == "Overheating" ? "" : "none");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentFloor = "OG";
|
var currentFloor = "OG";
|
||||||
|
|
||||||
function addClass(el, classNameToAdd){
|
function addClass(el, classNameToAdd){
|
||||||
el.className += ' ' + classNameToAdd;
|
el.className += ' ' + classNameToAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeClass(el, classNameToRemove){
|
function removeClass(el, classNameToRemove){
|
||||||
var elClass = ' ' + el.className + ' ';
|
var elClass = ' ' + el.className + ' ';
|
||||||
while(elClass.indexOf(' ' + classNameToRemove + ' ') !== -1){
|
while(elClass.indexOf(' ' + classNameToRemove + ' ') !== -1){
|
||||||
elClass = elClass.replace(' ' + classNameToRemove + ' ', '');
|
elClass = elClass.replace(' ' + classNameToRemove + ' ', '');
|
||||||
}
|
}
|
||||||
el.className = elClass;
|
el.className = elClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Die Etagen stehen nicht mehr im Code: was es gibt, sagen die Reiter, die
|
// Die Etagen stehen nicht mehr im Code: was es gibt, sagen die Reiter, die
|
||||||
// home.php aus der Raumtabelle gebaut hat. Vorher standen OG, EG und UG hier
|
// home.php aus der Raumtabelle gebaut hat. Vorher standen OG, EG und UG hier
|
||||||
// sechsmal, und eine vierte Etage haette sechs neue Zeilen gebraucht.
|
// sechsmal, und eine vierte Etage haette sechs neue Zeilen gebraucht.
|
||||||
function switchTab(newtab){
|
function switchTab(newtab){
|
||||||
const newContent = document.getElementById(newtab);
|
const newContent = document.getElementById(newtab);
|
||||||
const newTabBtn = document.getElementById(newtab+"-tab");
|
const newTabBtn = document.getElementById(newtab+"-tab");
|
||||||
document.querySelectorAll("#actions-tab .nav-link").forEach(function(btn){
|
document.querySelectorAll("#actions-tab .nav-link").forEach(function(btn){
|
||||||
removeClass(btn,"active");
|
removeClass(btn,"active");
|
||||||
});
|
});
|
||||||
document.querySelectorAll("#actions-tabContent .tab-pane").forEach(function(pane){
|
document.querySelectorAll("#actions-tabContent .tab-pane").forEach(function(pane){
|
||||||
removeClass(pane,"active show");
|
removeClass(pane,"active show");
|
||||||
});
|
});
|
||||||
addClass(newTabBtn,"active");
|
addClass(newTabBtn,"active");
|
||||||
addClass(newContent,"active show");
|
addClass(newContent,"active show");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function switchFloor(floor){
|
function switchFloor(floor){
|
||||||
if(currentFloor == floor)
|
if(currentFloor == floor)
|
||||||
return;
|
return;
|
||||||
const targetIn = document.getElementById(floor+'_Info');
|
const targetIn = document.getElementById(floor+'_Info');
|
||||||
const targetOut = document.getElementById(currentFloor+'_Info');
|
const targetOut = document.getElementById(currentFloor+'_Info');
|
||||||
var blendIn = new KeyframeEffect(
|
var blendIn = new KeyframeEffect(
|
||||||
targetIn, [{opacity: '0'},{opacity: '100'}],
|
targetIn, [{opacity: '0'},{opacity: '100'}],
|
||||||
{
|
{
|
||||||
duration: 500,
|
duration: 500,
|
||||||
easing: "ease-in-out",
|
easing: "ease-in-out",
|
||||||
fill: "forwards",
|
fill: "forwards",
|
||||||
iterations: 1,
|
iterations: 1,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
var blendOut = new KeyframeEffect(
|
var blendOut = new KeyframeEffect(
|
||||||
targetOut, [{opacity: '100'},{opacity: '0'}],
|
targetOut, [{opacity: '100'},{opacity: '0'}],
|
||||||
{
|
{
|
||||||
duration: 500,
|
duration: 500,
|
||||||
easing: "ease-in-out",
|
easing: "ease-in-out",
|
||||||
fill: "forwards",
|
fill: "forwards",
|
||||||
iterations: 1,
|
iterations: 1,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
var inAnim = new Animation(blendIn,document.timeline);
|
var inAnim = new Animation(blendIn,document.timeline);
|
||||||
var outAnim = new Animation(blendOut,document.timeline);
|
var outAnim = new Animation(blendOut,document.timeline);
|
||||||
targetIn.setAttribute("display","");
|
targetIn.setAttribute("display","");
|
||||||
outAnim.onfinish= (event) => {
|
outAnim.onfinish= (event) => {
|
||||||
targetOut.setAttribute("display","none");
|
targetOut.setAttribute("display","none");
|
||||||
};
|
};
|
||||||
inAnim.play();
|
inAnim.play();
|
||||||
outAnim.play();
|
outAnim.play();
|
||||||
currentFloor = floor;
|
currentFloor = floor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var modalEV = new bootstrap.Modal(document.getElementById('modalEV'), {
|
var modalEV = new bootstrap.Modal(document.getElementById('modalEV'), {
|
||||||
keyboard: false
|
keyboard: false
|
||||||
});
|
});
|
||||||
var offcanvas = new bootstrap.Offcanvas(document.getElementById('offcanvas'), {
|
var offcanvas = new bootstrap.Offcanvas(document.getElementById('offcanvas'), {
|
||||||
keyboard: false
|
keyboard: false
|
||||||
});
|
});
|
||||||
|
|
||||||
var mqttData = {};
|
var mqttData = {};
|
||||||
|
|
||||||
function openHeaterSettings(heater){
|
function openHeaterSettings(heater){
|
||||||
openRoomView(heater);
|
openRoomView(heater);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('readystatechange', function () {
|
document.addEventListener('readystatechange', function () {
|
||||||
if (event.target.readyState === "complete") {
|
if (event.target.readyState === "complete") {
|
||||||
homeMQTT.getMQTT();
|
homeMQTT.getMQTT();
|
||||||
mountMeteogram();
|
mountMeteogram();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alles, was in einem Raum haengt, auf einer Seite: Heizung, Rollladen,
|
* Alles, was in einem Raum haengt, auf einer Seite: Heizung, Rollladen,
|
||||||
* Licht. Der Grundriss zeigt Raeume, also sollte ein Klick darauf den Raum
|
* Licht. Der Grundriss zeigt Raeume, also sollte ein Klick darauf den Raum
|
||||||
* oeffnen - vorher kam nur der Temperaturregler, die Rollladen im selben
|
* oeffnen - vorher kam nur der Temperaturregler, die Rollladen im selben
|
||||||
* Zimmer waren von hier gar nicht erreichbar.
|
* Zimmer waren von hier gar nicht erreichbar.
|
||||||
*
|
*
|
||||||
* Welche Geraete dazugehoeren, sagt die Raumzuordnung (actors.room).
|
* Welche Geraete dazugehoeren, sagt die Raumzuordnung (actors.room).
|
||||||
*/
|
*/
|
||||||
function openRoomView(raum) {
|
function openRoomView(raum) {
|
||||||
const teile = raum.toString().split("_");
|
const teile = raum.toString().split("_");
|
||||||
document.getElementById("modal-title").innerHTML =
|
document.getElementById("modal-title").innerHTML =
|
||||||
teile[0] + " · " + teile.slice(1).join(" ")
|
teile[0] + " · " + teile.slice(1).join(" ")
|
||||||
.replace("ue", "ü").replace("ae", "ä").replace("oe", "ö");
|
.replace("ue", "ü").replace("ae", "ä").replace("oe", "ö");
|
||||||
|
|
||||||
// Der Speichern-Knopf gehoert allen Modals gemeinsam. Ein Klon ohne
|
// Der Speichern-Knopf gehoert allen Modals gemeinsam. Ein Klon ohne
|
||||||
// Zuhoerer stellt sicher, dass nicht der Handler eines vorher
|
// Zuhoerer stellt sicher, dass nicht der Handler eines vorher
|
||||||
// geoeffneten Modals mitfeuert; im Raum-Modal bleibt er weg. Hier wird
|
// geoeffneten Modals mitfeuert; im Raum-Modal bleibt er weg. Hier wird
|
||||||
// nichts gesammelt und dann abgeschickt - jeder Rollladen, jeder
|
// nichts gesammelt und dann abgeschickt - jeder Rollladen, jeder
|
||||||
// Schalter und jede Lampe wirkt sofort, und die Solltemperatur tut es
|
// Schalter und jede Lampe wirkt sofort, und die Solltemperatur tut es
|
||||||
// jetzt auch. Ein Knopf, der nur fuer einen der Reiter gilt, waere in
|
// jetzt auch. Ein Knopf, der nur fuer einen der Reiter gilt, waere in
|
||||||
// allen anderen eine Falle.
|
// allen anderen eine Falle.
|
||||||
const alt = document.getElementById("modalSaveBtn");
|
const alt = document.getElementById("modalSaveBtn");
|
||||||
const btn = alt.cloneNode(true);
|
const btn = alt.cloneNode(true);
|
||||||
alt.replaceWith(btn);
|
alt.replaceWith(btn);
|
||||||
btn.hidden = true;
|
btn.hidden = true;
|
||||||
|
|
||||||
const koerper = document.getElementById("modal-body");
|
const koerper = document.getElementById("modal-body");
|
||||||
koerper.innerHTML = loadingHTML("Wird geladen...");
|
koerper.innerHTML = loadingHTML("Wird geladen...");
|
||||||
modalEV.show();
|
modalEV.show();
|
||||||
|
|
||||||
fetch("./ajax/room.php?room=" + encodeURIComponent(raum), {
|
fetch("./ajax/room.php?room=" + encodeURIComponent(raum), {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: { "X-Requested-From-Modal": "a", "Requested-With-Ajax": "ajax" }
|
headers: { "X-Requested-From-Modal": "a", "Requested-With-Ajax": "ajax" }
|
||||||
})
|
})
|
||||||
.then(a => a.text())
|
.then(a => a.text())
|
||||||
.then(html => {
|
.then(html => {
|
||||||
koerper.innerHTML = html;
|
koerper.innerHTML = html;
|
||||||
|
|
||||||
const slider = document.getElementById("modal-slider");
|
const slider = document.getElementById("modal-slider");
|
||||||
if (slider) {
|
if (slider) {
|
||||||
bindModalSlider(slider, document.getElementById("modal-slider-label"),
|
bindModalSlider(slider, document.getElementById("modal-slider-label"),
|
||||||
wert => wert + " °C");
|
wert => wert + " °C");
|
||||||
const stand = mqttData["Raumtemp"]?.[teile[0]]?.[teile[1]]?.["Set Temp[degC]"];
|
const stand = mqttData["Raumtemp"]?.[teile[0]]?.[teile[1]]?.["Set Temp[degC]"];
|
||||||
slider.value = typeof stand !== "undefined" ? stand : 10;
|
slider.value = typeof stand !== "undefined" ? stand : 10;
|
||||||
// Nur "input": das zeichnet die Anzeige, schickt aber nichts.
|
// Nur "input": das zeichnet die Anzeige, schickt aber nichts.
|
||||||
slider.dispatchEvent(new Event("input"));
|
slider.dispatchEvent(new Event("input"));
|
||||||
// "change" kommt beim Loslassen - wie bei den Rollladenreglern.
|
// "change" kommt beim Loslassen - wie bei den Rollladenreglern.
|
||||||
slider.addEventListener("change", () => sendeSolltemperatur(raum));
|
slider.addEventListener("change", () => sendeSolltemperatur(raum));
|
||||||
}
|
}
|
||||||
|
|
||||||
raumBedienungAktivieren(koerper);
|
raumBedienungAktivieren(koerper);
|
||||||
raumMesswerteAktualisieren(teile[0], teile[1]);
|
raumMesswerteAktualisieren(teile[0], teile[1]);
|
||||||
})
|
})
|
||||||
.catch(fehler => { koerper.innerHTML = "Konnte den Raum nicht laden: " + fehler.message; });
|
.catch(fehler => { koerper.innerHTML = "Konnte den Raum nicht laden: " + fehler.message; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Bedienelemente des Raum-Modals mit ihren Kommandos verbinden.
|
* Die Bedienelemente des Raum-Modals mit ihren Kommandos verbinden.
|
||||||
*
|
*
|
||||||
* Welches Geraet welches Element bekommt, entscheidet der Server
|
* Welches Geraet welches Element bekommt, entscheidet der Server
|
||||||
* (restricted/roomControls.php); hier haengen nur die Zuhoerer an den
|
* (restricted/roomControls.php); hier haengen nur die Zuhoerer an den
|
||||||
* data-Attributen, die er dabei setzt.
|
* data-Attributen, die er dabei setzt.
|
||||||
*/
|
*/
|
||||||
function raumBedienungAktivieren(koerper) {
|
function raumBedienungAktivieren(koerper) {
|
||||||
koerper.querySelectorAll("button[data-cmd]").forEach(k => {
|
koerper.querySelectorAll("button[data-cmd]").forEach(k => {
|
||||||
k.addEventListener("click", () => schalte(k));
|
k.addEventListener("click", () => schalte(k));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schieberegler: waehrend des Ziehens nur die Anzeige, geschickt wird
|
// Schieberegler: waehrend des Ziehens nur die Anzeige, geschickt wird
|
||||||
// erst beim Loslassen. Sonst bekaeme eine Jalousie bei jedem Pixel ein
|
// erst beim Loslassen. Sonst bekaeme eine Jalousie bei jedem Pixel ein
|
||||||
// neues Ziel und kaeme aus dem Anfahren nicht mehr heraus.
|
// neues Ziel und kaeme aus dem Anfahren nicht mehr heraus.
|
||||||
koerper.querySelectorAll(".raum-jalousie").forEach(lamellenZeichnen);
|
koerper.querySelectorAll(".raum-jalousie").forEach(lamellenZeichnen);
|
||||||
koerper.querySelectorAll(".raum-regler").forEach(regler => {
|
koerper.querySelectorAll(".raum-regler").forEach(regler => {
|
||||||
const schieber = regler.querySelector("input[type=range]");
|
const schieber = regler.querySelector("input[type=range]");
|
||||||
reglerAnzeigen(regler);
|
reglerAnzeigen(regler);
|
||||||
schieber.addEventListener("input", () => reglerAnzeigen(regler));
|
schieber.addEventListener("input", () => reglerAnzeigen(regler));
|
||||||
schieber.addEventListener("change", () => {
|
schieber.addEventListener("change", () => {
|
||||||
const params = {};
|
const params = {};
|
||||||
params[regler.dataset.param] = schieber.value;
|
params[regler.dataset.param] = schieber.value;
|
||||||
raumKommando(Number(regler.dataset.cmd), params, regler);
|
raumKommando(Number(regler.dataset.cmd), params, regler);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Kippschalter: An und Aus sind zwei getrennte Kommandos. Kennt das
|
// Kippschalter: An und Aus sind zwei getrennte Kommandos. Kennt das
|
||||||
// Geraet nur "umschalten", tut es auch das - dann stimmt die Stellung
|
// Geraet nur "umschalten", tut es auch das - dann stimmt die Stellung
|
||||||
// hinterher, weil wir sie selbst gesetzt haben.
|
// hinterher, weil wir sie selbst gesetzt haben.
|
||||||
koerper.querySelectorAll(".raum-schalter").forEach(schalter => {
|
koerper.querySelectorAll(".raum-schalter").forEach(schalter => {
|
||||||
const kasten = schalter.querySelector("input[type=checkbox]");
|
const kasten = schalter.querySelector("input[type=checkbox]");
|
||||||
const beschriftung = schalter.querySelector("label");
|
const beschriftung = schalter.querySelector("label");
|
||||||
kasten.addEventListener("change", () => {
|
kasten.addEventListener("change", () => {
|
||||||
const ziel = kasten.checked ? schalter.dataset.ein : schalter.dataset.aus;
|
const ziel = kasten.checked ? schalter.dataset.ein : schalter.dataset.aus;
|
||||||
const cmd = ziel || schalter.dataset.um;
|
const cmd = ziel || schalter.dataset.um;
|
||||||
beschriftung.textContent = kasten.checked ? "An" : "Aus";
|
beschriftung.textContent = kasten.checked ? "An" : "Aus";
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
raumKommando(Number(cmd), {}, schalter);
|
raumKommando(Number(cmd), {}, schalter);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Farbe: der Browser bringt seinen Farbwaehler mit, das Geraet will drei
|
// Farbe: der Browser bringt seinen Farbwaehler mit, das Geraet will drei
|
||||||
// Zahlen 0-255. Zerlegt wird deshalb erst hier.
|
// Zahlen 0-255. Zerlegt wird deshalb erst hier.
|
||||||
koerper.querySelectorAll(".raum-farbe").forEach(feld => {
|
koerper.querySelectorAll(".raum-farbe").forEach(feld => {
|
||||||
const eingabe = feld.querySelector("input[type=color]");
|
const eingabe = feld.querySelector("input[type=color]");
|
||||||
eingabe.addEventListener("change", () => {
|
eingabe.addEventListener("change", () => {
|
||||||
const hex = eingabe.value;
|
const hex = eingabe.value;
|
||||||
const params = {};
|
const params = {};
|
||||||
params[feld.dataset.red] = parseInt(hex.substr(1, 2), 16);
|
params[feld.dataset.red] = parseInt(hex.substr(1, 2), 16);
|
||||||
params[feld.dataset.green] = parseInt(hex.substr(3, 2), 16);
|
params[feld.dataset.green] = parseInt(hex.substr(3, 2), 16);
|
||||||
params[feld.dataset.blue] = parseInt(hex.substr(5, 2), 16);
|
params[feld.dataset.blue] = parseInt(hex.substr(5, 2), 16);
|
||||||
raumKommando(Number(feld.dataset.cmd), params, feld);
|
raumKommando(Number(feld.dataset.cmd), params, feld);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auswahllisten, die ohne zusaetzlichen Knopf schalten - Effekt, Preset.
|
// Auswahllisten, die ohne zusaetzlichen Knopf schalten - Effekt, Preset.
|
||||||
koerper.querySelectorAll("select.raum-sofort").forEach(auswahl => {
|
koerper.querySelectorAll("select.raum-sofort").forEach(auswahl => {
|
||||||
auswahl.addEventListener("change", () => {
|
auswahl.addEventListener("change", () => {
|
||||||
const params = {};
|
const params = {};
|
||||||
params[auswahl.dataset.param] = auswahl.value;
|
params[auswahl.dataset.param] = auswahl.value;
|
||||||
raumKommando(Number(auswahl.dataset.cmd), params, auswahl);
|
raumKommando(Number(auswahl.dataset.cmd), params, auswahl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anzeige, gefuellte Bahn und Lamellenbild eines Reglers nachziehen.
|
* Anzeige, gefuellte Bahn und Lamellenbild eines Reglers nachziehen.
|
||||||
*
|
*
|
||||||
* Was 0 % und 100 % bedeuten, steht bei den senkrechten Reglern als Wort
|
* Was 0 % und 100 % bedeuten, steht bei den senkrechten Reglern als Wort
|
||||||
* an ihren Enden ("oben"/"unten", "auf"/"zu"); eine Zahl steht dort nicht
|
* an ihren Enden ("oben"/"unten", "auf"/"zu"); eine Zahl steht dort nicht
|
||||||
* mehr - das Bild daneben zeigt beim Ziehen mehr als sie.
|
* mehr - das Bild daneben zeigt beim Ziehen mehr als sie.
|
||||||
*/
|
*/
|
||||||
function reglerAnzeigen(regler) {
|
function reglerAnzeigen(regler) {
|
||||||
const schieber = regler.querySelector("input[type=range]");
|
const schieber = regler.querySelector("input[type=range]");
|
||||||
const anzeige = regler.querySelector(".raum-regler-wert");
|
const anzeige = regler.querySelector(".raum-regler-wert");
|
||||||
const wert = Number(schieber.value);
|
const wert = Number(schieber.value);
|
||||||
|
|
||||||
if (anzeige) {
|
if (anzeige) {
|
||||||
anzeige.textContent = wert + (schieber.dataset.einheit || "");
|
anzeige.textContent = wert + (schieber.dataset.einheit || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Der gefuellte Anteil der Bahn - dieselbe Eigenschaft, aus der auch
|
// Der gefuellte Anteil der Bahn - dieselbe Eigenschaft, aus der auch
|
||||||
// bindModalSlider() in common.js den Temperaturregler faerbt.
|
// bindModalSlider() in common.js den Temperaturregler faerbt.
|
||||||
const min = Number(schieber.min);
|
const min = Number(schieber.min);
|
||||||
const max = Number(schieber.max);
|
const max = Number(schieber.max);
|
||||||
schieber.style.setProperty("--background-size",
|
schieber.style.setProperty("--background-size",
|
||||||
((wert - min) * 100 / (max - min)) + "%");
|
((wert - min) * 100 / (max - min)) + "%");
|
||||||
|
|
||||||
lamellenZeichnen(regler.closest(".raum-jalousie"));
|
lamellenZeichnen(regler.closest(".raum-jalousie"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Der Stand eines der beiden Regler einer Jalousie, als Anteil 0..1. */
|
/** Der Stand eines der beiden Regler einer Jalousie, als Anteil 0..1. */
|
||||||
function reglerAnteil(jalousie, rolle, ersatz) {
|
function reglerAnteil(jalousie, rolle, ersatz) {
|
||||||
const schieber = jalousie.querySelector("[data-rolle=" + rolle + "] input[type=range]");
|
const schieber = jalousie.querySelector("[data-rolle=" + rolle + "] input[type=range]");
|
||||||
if (!schieber) return ersatz;
|
if (!schieber) return ersatz;
|
||||||
const min = Number(schieber.min);
|
const min = Number(schieber.min);
|
||||||
return (Number(schieber.value) - min) / (Number(schieber.max) - min);
|
return (Number(schieber.value) - min) / (Number(schieber.max) - min);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Das Fenster zeichnen, wie die Jalousie davor gerade steht.
|
* Das Fenster zeichnen, wie die Jalousie davor gerade steht.
|
||||||
*
|
*
|
||||||
* Zwei Zahlen in einem Bild. Die Hoehe schneidet ab - gezeichnet wird nur
|
* Zwei Zahlen in einem Bild. Die Hoehe schneidet ab - gezeichnet wird nur
|
||||||
* der Teil des Fensters, den die Jalousie bedeckt, mit der Endschiene an
|
* der Teil des Fensters, den die Jalousie bedeckt, mit der Endschiene an
|
||||||
* seiner Unterkante. Die Neigung bestimmt, wie dick eine Lamelle von vorn
|
* seiner Unterkante. Die Neigung bestimmt, wie dick eine Lamelle von vorn
|
||||||
* erscheint: w * sin(Winkel). Bei "auf" sieht man nur ihre Kante und viel
|
* erscheint: w * sin(Winkel). Bei "auf" sieht man nur ihre Kante und viel
|
||||||
* Luft dazwischen, bei "zu" deckt sie den Abstand zur naechsten
|
* Luft dazwischen, bei "zu" deckt sie den Abstand zur naechsten
|
||||||
* vollstaendig ab - genau das, was das Licht abhaelt. Von Mitte zu Mitte
|
* vollstaendig ab - genau das, was das Licht abhaelt. Von Mitte zu Mitte
|
||||||
* sind es 10 Einheiten, die Lamelle ist 11 tief; ab etwa 65 % schliesst
|
* sind es 10 Einheiten, die Lamelle ist 11 tief; ab etwa 65 % schliesst
|
||||||
* das Bild, so wie es die Jalousie auch tut.
|
* das Bild, so wie es die Jalousie auch tut.
|
||||||
*
|
*
|
||||||
* Ein Rollladen ohne Neigung bekommt volle Lamellenhoehe - er ist ja ein
|
* Ein Rollladen ohne Neigung bekommt volle Lamellenhoehe - er ist ja ein
|
||||||
* geschlossener Panzer.
|
* geschlossener Panzer.
|
||||||
*/
|
*/
|
||||||
function lamellenZeichnen(jalousie) {
|
function lamellenZeichnen(jalousie) {
|
||||||
if (!jalousie) return;
|
if (!jalousie) return;
|
||||||
const bild = jalousie.querySelector(".raum-lamellen");
|
const bild = jalousie.querySelector(".raum-lamellen");
|
||||||
if (!bild) return;
|
if (!bild) return;
|
||||||
|
|
||||||
const hoch = bild.viewBox.baseVal.height;
|
const hoch = bild.viewBox.baseVal.height;
|
||||||
const abdeckung = hoch * reglerAnteil(jalousie, "position", 1);
|
const abdeckung = hoch * reglerAnteil(jalousie, "position", 1);
|
||||||
const kippung = reglerAnteil(jalousie, "neigung", 1);
|
const kippung = reglerAnteil(jalousie, "neigung", 1);
|
||||||
const dicke = Math.max(1.6, 11 * Math.sin(kippung * Math.PI / 2));
|
const dicke = Math.max(1.6, 11 * Math.sin(kippung * Math.PI / 2));
|
||||||
|
|
||||||
bild.querySelector(".raum-abdeckung").setAttribute("height", abdeckung);
|
bild.querySelector(".raum-abdeckung").setAttribute("height", abdeckung);
|
||||||
bild.querySelectorAll(".raum-lamelle").forEach(lamelle => {
|
bild.querySelectorAll(".raum-lamelle").forEach(lamelle => {
|
||||||
const mitte = Number(lamelle.dataset.mitte);
|
const mitte = Number(lamelle.dataset.mitte);
|
||||||
lamelle.setAttribute("height", dicke);
|
lamelle.setAttribute("height", dicke);
|
||||||
lamelle.setAttribute("y", mitte - dicke / 2);
|
lamelle.setAttribute("y", mitte - dicke / 2);
|
||||||
});
|
});
|
||||||
// Die Endschiene sitzt auf der Unterkante und bleibt im Bild, auch wenn
|
// Die Endschiene sitzt auf der Unterkante und bleibt im Bild, auch wenn
|
||||||
// die Jalousie ganz oben steht.
|
// die Jalousie ganz oben steht.
|
||||||
const schiene = bild.querySelector(".raum-endschiene");
|
const schiene = bild.querySelector(".raum-endschiene");
|
||||||
const dick = schiene.height.baseVal.value;
|
const dick = schiene.height.baseVal.value;
|
||||||
schiene.setAttribute("y", Math.max(0, Math.min(hoch - dick, abdeckung - dick)));
|
schiene.setAttribute("y", Math.max(0, Math.min(hoch - dick, abdeckung - dick)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Ist-Werte im Heizungsteil aus dem laufenden MQTT-Strom fuellen.
|
* Die Ist-Werte im Heizungsteil aus dem laufenden MQTT-Strom fuellen.
|
||||||
*
|
*
|
||||||
* Der Browser hoert ohnehin schon auf Raumtemp/#, und ein Wert aus der
|
* Der Browser hoert ohnehin schon auf Raumtemp/#, und ein Wert aus der
|
||||||
* Datenbank waere neben dem Regler nur ein Stand von vorhin.
|
* Datenbank waere neben dem Regler nur ein Stand von vorhin.
|
||||||
*/
|
*/
|
||||||
function raumMesswerteAktualisieren(etage, raum) {
|
function raumMesswerteAktualisieren(etage, raum) {
|
||||||
const werte = mqttData["Raumtemp"]?.[etage]?.[raum];
|
const werte = mqttData["Raumtemp"]?.[etage]?.[raum];
|
||||||
if (!werte) return;
|
if (!werte) return;
|
||||||
document.querySelectorAll("#roomView [data-mqtt]").forEach(feld => {
|
document.querySelectorAll("#roomView [data-mqtt]").forEach(feld => {
|
||||||
const wert = werte[feld.dataset.mqtt];
|
const wert = werte[feld.dataset.mqtt];
|
||||||
if (typeof wert !== "undefined") feld.textContent = wert;
|
if (typeof wert !== "undefined") feld.textContent = wert;
|
||||||
});
|
});
|
||||||
const heizt = document.querySelector("#roomView [data-mqtt-heizung]");
|
const heizt = document.querySelector("#roomView [data-mqtt-heizung]");
|
||||||
if (heizt) heizt.classList.toggle("d-none", String(werte["Heating"]) !== "true");
|
if (heizt) heizt.classList.toggle("d-none", String(werte["Heating"]) !== "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ein Kommando des angeklickten Knopfes ausfuehren. */
|
/** Ein Kommando des angeklickten Knopfes ausfuehren. */
|
||||||
function schalte(knopf) {
|
function schalte(knopf) {
|
||||||
const zeile = knopf.closest("[data-cmdrow]");
|
const zeile = knopf.closest("[data-cmdrow]");
|
||||||
const params = {};
|
const params = {};
|
||||||
if (zeile) {
|
if (zeile) {
|
||||||
zeile.querySelectorAll("[data-param]").forEach(f => { params[f.dataset.param] = f.value; });
|
zeile.querySelectorAll("[data-param]").forEach(f => { params[f.dataset.param] = f.value; });
|
||||||
}
|
}
|
||||||
raumKommando(Number(knopf.dataset.cmd), params, knopf);
|
raumKommando(Number(knopf.dataset.cmd), params, knopf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ein Kommando schicken und die Karte so lange sperren.
|
* Ein Kommando schicken und die Karte so lange sperren.
|
||||||
*
|
*
|
||||||
* Ein "Zu" an einer Jalousie dauert ueber eine Minute (die Mechanik will
|
* Ein "Zu" an einer Jalousie dauert ueber eine Minute (die Mechanik will
|
||||||
* zweimal geschwenkt werden, siehe restricted/commands.php). Ohne sichtbare
|
* zweimal geschwenkt werden, siehe restricted/commands.php). Ohne sichtbare
|
||||||
* Sperre tippt man in der Zeit dreimal nach.
|
* Sperre tippt man in der Zeit dreimal nach.
|
||||||
*/
|
*/
|
||||||
function raumKommando(cmdId, params, element) {
|
function raumKommando(cmdId, params, element) {
|
||||||
const karte = element.closest(".raum-geraet") || element;
|
const karte = element.closest(".raum-geraet") || element;
|
||||||
karte.classList.add("raum-warte");
|
karte.classList.add("raum-warte");
|
||||||
raumRequest("command", { command_id: cmdId, params: params })
|
raumRequest("command", { command_id: cmdId, params: params })
|
||||||
.then(antwort => {
|
.then(antwort => {
|
||||||
karte.classList.remove("raum-warte");
|
karte.classList.remove("raum-warte");
|
||||||
raumMeldung(antwort);
|
raumMeldung(antwort);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die neue Solltemperatur schicken.
|
* Die neue Solltemperatur schicken.
|
||||||
*
|
*
|
||||||
* Das Modal bleibt danach offen: frueher schloss es sich, weil der
|
* Das Modal bleibt danach offen: frueher schloss es sich, weil der
|
||||||
* Speichern-Knopf das Ende der Bedienung war. Jetzt ist das Verschieben
|
* Speichern-Knopf das Ende der Bedienung war. Jetzt ist das Verschieben
|
||||||
* des Reglers die Bedienung, und wer dreimal nachjustiert, will nicht
|
* des Reglers die Bedienung, und wer dreimal nachjustiert, will nicht
|
||||||
* dreimal neu aufmachen.
|
* dreimal neu aufmachen.
|
||||||
*/
|
*/
|
||||||
function sendeSolltemperatur(raum) {
|
function sendeSolltemperatur(raum) {
|
||||||
const slider = document.getElementById("modal-slider");
|
const slider = document.getElementById("modal-slider");
|
||||||
if (!slider) return;
|
if (!slider) return;
|
||||||
const karte = slider.closest(".raum-geraet");
|
const karte = slider.closest(".raum-geraet");
|
||||||
if (karte) karte.classList.add("raum-warte");
|
if (karte) karte.classList.add("raum-warte");
|
||||||
raumRequest("temp", { room: raum, temp: Number(slider.value) })
|
raumRequest("temp", { room: raum, temp: Number(slider.value) })
|
||||||
.then(antwort => {
|
.then(antwort => {
|
||||||
if (karte) karte.classList.remove("raum-warte");
|
if (karte) karte.classList.remove("raum-warte");
|
||||||
raumMeldung(antwort);
|
raumMeldung(antwort);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function raumRequest(action, body) {
|
function raumRequest(action, body) {
|
||||||
return fetch("./ajax/room.php?action=" + action, {
|
return fetch("./ajax/room.php?action=" + action, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json", "Requested-With-Ajax": "ajax" },
|
headers: { "Content-Type": "application/json", "Requested-With-Ajax": "ajax" },
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
})
|
})
|
||||||
.then(a => a.json())
|
.then(a => a.json())
|
||||||
.catch(fehler => ({ error: fehler.message }));
|
.catch(fehler => ({ error: fehler.message }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Rueckmeldung im Modal statt als alert - man schaltet oft mehrmals. */
|
/** Rueckmeldung im Modal statt als alert - man schaltet oft mehrmals. */
|
||||||
function raumMeldung(antwort) {
|
function raumMeldung(antwort) {
|
||||||
const ziel = document.getElementById("roomFeedback");
|
const ziel = document.getElementById("roomFeedback");
|
||||||
if (!ziel) return;
|
if (!ziel) return;
|
||||||
ziel.className = "small mt-2 " + (antwort.error ? "text-danger" : "text-body-secondary");
|
ziel.className = "small mt-2 " + (antwort.error ? "text-danger" : "text-body-secondary");
|
||||||
ziel.textContent = antwort.error ? antwort.error : "✓ " + antwort.ok;
|
ziel.textContent = antwort.error ? antwort.error : "✓ " + antwort.ok;
|
||||||
}
|
}
|
||||||
|
|||||||
+1172
-1172
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,95 +1,95 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Base Module Interface
|
Base Module Interface
|
||||||
Definiert die einheitliche Schnittstelle für alle Gerätemodule
|
Definiert die einheitliche Schnittstelle für alle Gerätemodule
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import List, Dict, Tuple
|
from typing import List, Dict, Tuple
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseModule(ABC):
|
class BaseModule(ABC):
|
||||||
"""
|
"""
|
||||||
Abstrakte Basisklasse für alle Gerätemodule
|
Abstrakte Basisklasse für alle Gerätemodule
|
||||||
|
|
||||||
Jedes Modul muss nur discover() implementieren und gibt
|
Jedes Modul muss nur discover() implementieren und gibt
|
||||||
eine Liste von (actors, sensors) zurück.
|
eine Liste von (actors, sensors) zurück.
|
||||||
Die Datenbank-Logik bleibt im Hauptscript.
|
Die Datenbank-Logik bleibt im Hauptscript.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
"""
|
"""
|
||||||
Initialisiert das Modul
|
Initialisiert das Modul
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: Config-Objekt mit allen Einstellungen
|
config: Config-Objekt mit allen Einstellungen
|
||||||
"""
|
"""
|
||||||
self.config = config
|
self.config = config
|
||||||
self.module_name = self.__class__.__name__.replace('Module', '')
|
self.module_name = self.__class__.__name__.replace('Module', '')
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""
|
"""
|
||||||
Führt Device Discovery durch
|
Führt Device Discovery durch
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (actors, sensors) mit Listen von Dicts:
|
Tuple (actors, sensors) mit Listen von Dicts:
|
||||||
|
|
||||||
Actor Dict Format:
|
Actor Dict Format:
|
||||||
{
|
{
|
||||||
'type': str, # z.B. 'RollerShutter'
|
'type': str, # z.B. 'RollerShutter'
|
||||||
'name': str, # z.B. 'Wohnzimmer Rollo'
|
'name': str, # z.B. 'Wohnzimmer Rollo'
|
||||||
'url': str, # Eindeutige ID/URL
|
'url': str, # Eindeutige ID/URL
|
||||||
'commands': [ # Liste von Commands
|
'commands': [ # Liste von Commands
|
||||||
{
|
{
|
||||||
'command': str,
|
'command': str,
|
||||||
'parameters': [
|
'parameters': [
|
||||||
{
|
{
|
||||||
'name': str,
|
'name': str,
|
||||||
'type': str,
|
'type': str,
|
||||||
'min': float (optional),
|
'min': float (optional),
|
||||||
'max': float (optional),
|
'max': float (optional),
|
||||||
'url': str, # Eindeutige ID/URL
|
'url': str, # Eindeutige ID/URL
|
||||||
'values': list (optional)
|
'values': list (optional)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'states': [ # Liste von States
|
'states': [ # Liste von States
|
||||||
{
|
{
|
||||||
'name': str,
|
'name': str,
|
||||||
'type': int/str,
|
'type': int/str,
|
||||||
'current_value': any,
|
'current_value': any,
|
||||||
'unit': str (optional)
|
'unit': str (optional)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Sensor Dict Format:
|
Sensor Dict Format:
|
||||||
{
|
{
|
||||||
'type': str, # z.B. 'TemperatureSensor'
|
'type': str, # z.B. 'TemperatureSensor'
|
||||||
'name': str, # z.B. 'Außentemperatur'
|
'name': str, # z.B. 'Außentemperatur'
|
||||||
'url': str, # Eindeutige ID/URL
|
'url': str, # Eindeutige ID/URL
|
||||||
'states': [ # Liste von States
|
'states': [ # Liste von States
|
||||||
{
|
{
|
||||||
'name': str,
|
'name': str,
|
||||||
'type': int/str,
|
'type': int/str,
|
||||||
'current_value': any,
|
'current_value': any,
|
||||||
'url': str, # Eindeutige ID/URL
|
'url': str, # Eindeutige ID/URL
|
||||||
'unit': str (optional)
|
'unit': str (optional)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
"""Prüft ob Modul aktiviert ist"""
|
"""Prüft ob Modul aktiviert ist"""
|
||||||
return True # Override in Subklassen falls nötig
|
return True # Override in Subklassen falls nötig
|
||||||
|
|
||||||
def get_name(self) -> str:
|
def get_name(self) -> str:
|
||||||
"""Gibt Modulname zurück"""
|
"""Gibt Modulname zurück"""
|
||||||
return self.module_name
|
return self.module_name
|
||||||
|
|||||||
@@ -1,238 +1,238 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Gartenwasser Module
|
Gartenwasser Module
|
||||||
Die beiden Ventilsteuerungen und der Regen - fest beschrieben, nicht gesucht
|
Die beiden Ventilsteuerungen und der Regen - fest beschrieben, nicht gesucht
|
||||||
KEINE Datenbank-Operationen!
|
KEINE Datenbank-Operationen!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Tuple
|
from typing import List, Dict, Tuple
|
||||||
from modules.base_module import BaseModule
|
from modules.base_module import BaseModule
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Topic, auf das gatherRainData.py im SolarManager seine Regensummen legt.
|
# Topic, auf das gatherRainData.py im SolarManager seine Regensummen legt.
|
||||||
# Dasselbe Topic steht dort noch einmal im Quelltext. Bewusst kein
|
# Dasselbe Topic steht dort noch einmal im Quelltext. Bewusst kein
|
||||||
# Konfigurationseintrag: er müsste in beiden Repositorien gleich lauten, und
|
# Konfigurationseintrag: er müsste in beiden Repositorien gleich lauten, und
|
||||||
# eine Einstellung, die man an zwei Orten gleich halten muss, ist keine.
|
# eine Einstellung, die man an zwei Orten gleich halten muss, ist keine.
|
||||||
REGEN_TOPIC = "Wetter/Regen"
|
REGEN_TOPIC = "Wetter/Regen"
|
||||||
|
|
||||||
# Welche Regenfenster als Messwert im Editor auftauchen. gatherRainData.py
|
# Welche Regenfenster als Messwert im Editor auftauchen. gatherRainData.py
|
||||||
# veröffentlicht immer tage1 bis tage7; welche davon jemand braucht, entscheidet
|
# veröffentlicht immer tage1 bis tage7; welche davon jemand braucht, entscheidet
|
||||||
# allein diese Liste. Die drei Zonen der alten auto_watering.py brauchten 1, 2
|
# allein diese Liste. Die drei Zonen der alten auto_watering.py brauchten 1, 2
|
||||||
# und 5 Tage - der Rest steht da, damit eine neue Zone keinen Eingriff im
|
# und 5 Tage - der Rest steht da, damit eine neue Zone keinen Eingriff im
|
||||||
# SolarManager kostet.
|
# SolarManager kostet.
|
||||||
REGEN_FENSTER = [1, 2, 3, 5, 7]
|
REGEN_FENSTER = [1, 2, 3, 5, 7]
|
||||||
|
|
||||||
# Die beiden ESP32-Ventilsteuerungen. Sie melden sich nicht per
|
# Die beiden ESP32-Ventilsteuerungen. Sie melden sich nicht per
|
||||||
# Home-Assistant-Discovery, das MQTT-Modul findet sie also nicht.
|
# Home-Assistant-Discovery, das MQTT-Modul findet sie also nicht.
|
||||||
#
|
#
|
||||||
# topic Wurzel aller Topics dieser Steuerung
|
# topic Wurzel aller Topics dieser Steuerung
|
||||||
# modi Nutzlast des /auto-Topics -> Beschriftung im Editor. Dieselben
|
# modi Nutzlast des /auto-Topics -> Beschriftung im Editor. Dieselben
|
||||||
# Klartexte stehen in der Whitelist von ajax/watering.php.
|
# Klartexte stehen in der Whitelist von ajax/watering.php.
|
||||||
# zonen Ventilnummer -> Name der Zone. Eine Steuerung bedient mehrere
|
# zonen Ventilnummer -> Name der Zone. Eine Steuerung bedient mehrere
|
||||||
# Zonen; "vorn" zum Beispiel Ventil 1 für die Tröge und Ventil 6
|
# Zonen; "vorn" zum Beispiel Ventil 1 für die Tröge und Ventil 6
|
||||||
# für den Garten. Aus jeder Zone werden zwei Messwerte.
|
# für den Garten. Aus jeder Zone werden zwei Messwerte.
|
||||||
STEUERUNGEN = [
|
STEUERUNGEN = [
|
||||||
{
|
{
|
||||||
"name": "Gartenwasser vorn",
|
"name": "Gartenwasser vorn",
|
||||||
"topic": "Gartenwasser/vorn",
|
"topic": "Gartenwasser/vorn",
|
||||||
"modi": [{"Trog": "Tröge"}, {"Vorn": "Garten vorn"}, {"Stop": "Stopp"}],
|
"modi": [{"Trog": "Tröge"}, {"Vorn": "Garten vorn"}, {"Stop": "Stopp"}],
|
||||||
"zonen": [(1, "Tröge"), (6, "Garten vorn")],
|
"zonen": [(1, "Tröge"), (6, "Garten vorn")],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Gartenwasser hinten",
|
"name": "Gartenwasser hinten",
|
||||||
"topic": "Gartenwasser/hinten",
|
"topic": "Gartenwasser/hinten",
|
||||||
"modi": [{"Hoch": "Hochbeete"}, {"Stop": "Stopp"}],
|
"modi": [{"Hoch": "Hochbeete"}, {"Stop": "Stopp"}],
|
||||||
"zonen": [(6, "Hochbeete")],
|
"zonen": [(6, "Hochbeete")],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class GartenwasserModule(BaseModule):
|
class GartenwasserModule(BaseModule):
|
||||||
"""
|
"""
|
||||||
Gartenwasser Modul - Implementiert BaseModule Interface
|
Gartenwasser Modul - Implementiert BaseModule Interface
|
||||||
|
|
||||||
Anders als die übrigen Module sucht dieses nichts, es schreibt drei feste
|
Anders als die übrigen Module sucht dieses nichts, es schreibt drei feste
|
||||||
Geräte hin - wie das Logic-Modul auch:
|
Geräte hin - wie das Logic-Modul auch:
|
||||||
|
|
||||||
* Die Ventilsteuerungen sprechen MQTT, aber ohne Home-Assistant-
|
* Die Ventilsteuerungen sprechen MQTT, aber ohne Home-Assistant-
|
||||||
Discovery. Ohne dieses Modul kennt die Datenbank sie nicht, und im
|
Discovery. Ohne dieses Modul kennt die Datenbank sie nicht, und im
|
||||||
Automatik-Editor lässt sich die Bewässerung nicht auswählen.
|
Automatik-Editor lässt sich die Bewässerung nicht auswählen.
|
||||||
* "Regen" ist gar kein Gerät, sondern das, was gatherRainData.py im
|
* "Regen" ist gar kein Gerät, sondern das, was gatherRainData.py im
|
||||||
SolarManager von Open-Meteo holt.
|
SolarManager von Open-Meteo holt.
|
||||||
|
|
||||||
Von Hand in die Datenbank geschrieben würden die Zeilen zwar jeden
|
Von Hand in die Datenbank geschrieben würden die Zeilen zwar jeden
|
||||||
Discovery-Lauf überleben (es wird nur mit ON DUPLICATE KEY UPDATE
|
Discovery-Lauf überleben (es wird nur mit ON DUPLICATE KEY UPDATE
|
||||||
geschrieben, nie gelöscht), aber niemand wüsste mehr, woher sie kommen.
|
geschrieben, nie gelöscht), aber niemand wüsste mehr, woher sie kommen.
|
||||||
|
|
||||||
Zusammen ersetzt das restricted/gartenbewaesserung/auto_watering.py: die
|
Zusammen ersetzt das restricted/gartenbewaesserung/auto_watering.py: die
|
||||||
Zeitfenster werden Bedingungen auf Sonnenauf- und -untergang, die
|
Zeitfenster werden Bedingungen auf Sonnenauf- und -untergang, die
|
||||||
Regenschwellen Bedingungen auf "Regen ... Tage", und geschaltet wird über
|
Regenschwellen Bedingungen auf "Regen ... Tage", und geschaltet wird über
|
||||||
das Kommando "Automatik".
|
das Kommando "Automatik".
|
||||||
|
|
||||||
Zwei Zusagen der Ventilsteuerung stecken in den Messwerten unten, und ohne
|
Zwei Zusagen der Ventilsteuerung stecken in den Messwerten unten, und ohne
|
||||||
sie stimmt die Automatik nicht mehr:
|
sie stimmt die Automatik nicht mehr:
|
||||||
|
|
||||||
* LastWatering trägt je Ventil ein "daysSinceWatering<n>" - die Anzahl
|
* LastWatering trägt je Ventil ein "daysSinceWatering<n>" - die Anzahl
|
||||||
Tage, nicht den Zeitstempel. Das Feld fehlt, solange für ein Ventil
|
Tage, nicht den Zeitstempel. Das Feld fehlt, solange für ein Ventil
|
||||||
keine Bewässerung bekannt ist.
|
keine Bewässerung bekannt ist.
|
||||||
* WateringToday wird um Mitternacht zurückgesetzt.
|
* WateringToday wird um Mitternacht zurückgesetzt.
|
||||||
* Running sagt retained, ob gerade etwas läuft, und fällt über eine
|
* Running sagt retained, ob gerade etwas läuft, und fällt über eine
|
||||||
Last-Will-Nachricht auch dann auf false, wenn die Steuerung wegbricht.
|
Last-Will-Nachricht auch dann auf false, wenn die Steuerung wegbricht.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
"""Prüft ob Gartenwasser aktiviert ist"""
|
"""Prüft ob Gartenwasser aktiviert ist"""
|
||||||
return self.config.gartenwasser_enable
|
return self.config.gartenwasser_enable
|
||||||
|
|
||||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""
|
"""
|
||||||
Erzeugt die beiden Ventilsteuerungen als Actors und den Regen als Sensor
|
Erzeugt die beiden Ventilsteuerungen als Actors und den Regen als Sensor
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (actors, sensors)
|
Tuple (actors, sensors)
|
||||||
"""
|
"""
|
||||||
logger.info("\n" + "=" * 60)
|
logger.info("\n" + "=" * 60)
|
||||||
logger.info("GARTENWASSER-GERÄTE WERDEN ERZEUGT")
|
logger.info("GARTENWASSER-GERÄTE WERDEN ERZEUGT")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
actors = [self._steuerung(s) for s in STEUERUNGEN]
|
actors = [self._steuerung(s) for s in STEUERUNGEN]
|
||||||
sensors = [self._regen()]
|
sensors = [self._regen()]
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _steuerung(steuerung: Dict) -> Dict:
|
def _steuerung(steuerung: Dict) -> Dict:
|
||||||
"""
|
"""
|
||||||
Eine Ventilsteuerung: ein Kommando, zwei Messwerte je Zone.
|
Eine Ventilsteuerung: ein Kommando, zwei Messwerte je Zone.
|
||||||
|
|
||||||
Das Kommando trägt seinen Klartext im Parameter und nicht im Namen.
|
Das Kommando trägt seinen Klartext im Parameter und nicht im Namen.
|
||||||
Ein Kommando ohne Parameter schickt im Runner eine leere Nutzlast
|
Ein Kommando ohne Parameter schickt im Runner eine leere Nutzlast
|
||||||
(siehe MQTTTransport.senden), der Modus muss also der Parameter sein -
|
(siehe MQTTTransport.senden), der Modus muss also der Parameter sein -
|
||||||
im Editor liest sich das dann als "Automatik Modus = Hochbeete".
|
im Editor liest sich das dann als "Automatik Modus = Hochbeete".
|
||||||
"""
|
"""
|
||||||
topic = steuerung["topic"]
|
topic = steuerung["topic"]
|
||||||
|
|
||||||
# Läuft an dieser Steuerung gerade etwas? Der Wächter für jede Regel,
|
# Läuft an dieser Steuerung gerade etwas? Der Wächter für jede Regel,
|
||||||
# die hier etwas anstoßen will - die Zonen einer Steuerung hängen an
|
# die hier etwas anstoßen will - die Zonen einer Steuerung hängen an
|
||||||
# derselben Leitung, zwei gleichzeitig gibt es nicht.
|
# derselben Leitung, zwei gleichzeitig gibt es nicht.
|
||||||
#
|
#
|
||||||
# Gelesen wird das aus Running und nicht aus Timers: Timers ist nicht
|
# Gelesen wird das aus Running und nicht aus Timers: Timers ist nicht
|
||||||
# retained und wird nur im Sekundentakt gesendet, solange etwas läuft.
|
# retained und wird nur im Sekundentakt gesendet, solange etwas läuft.
|
||||||
# Der Runner sähe im Ruhezustand gar nichts und behielte nach einem
|
# Der Runner sähe im Ruhezustand gar nichts und behielte nach einem
|
||||||
# Lauf den zuletzt gesehenen Wert - also genau das Gegenteil von
|
# Lauf den zuletzt gesehenen Wert - also genau das Gegenteil von
|
||||||
# verlässlich. Running ist retained und fällt über eine
|
# verlässlich. Running ist retained und fällt über eine
|
||||||
# Last-Will-Nachricht auch dann auf false, wenn die Steuerung mitten
|
# Last-Will-Nachricht auch dann auf false, wenn die Steuerung mitten
|
||||||
# im Gießen wegbricht.
|
# im Gießen wegbricht.
|
||||||
states = [{
|
states = [{
|
||||||
"name": "Bewässerung läuft",
|
"name": "Bewässerung läuft",
|
||||||
"url": topic + "/Running",
|
"url": topic + "/Running",
|
||||||
"value_path": "running",
|
"value_path": "running",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
}, {
|
}, {
|
||||||
# Klartext der laufenden Automatik ("Hoch", "Trog", "Vorn"), leer
|
# Klartext der laufenden Automatik ("Hoch", "Trog", "Vorn"), leer
|
||||||
# wenn von Hand oder gar nicht bewässert wird.
|
# wenn von Hand oder gar nicht bewässert wird.
|
||||||
"name": "Laufender Modus",
|
"name": "Laufender Modus",
|
||||||
"url": topic + "/Running",
|
"url": topic + "/Running",
|
||||||
"value_path": "mode",
|
"value_path": "mode",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
}]
|
}]
|
||||||
|
|
||||||
for valve, zone in steuerung["zonen"]:
|
for valve, zone in steuerung["zonen"]:
|
||||||
if len(steuerung["zonen"]) > 1:
|
if len(steuerung["zonen"]) > 1:
|
||||||
# Bei einer Steuerung mit nur einer Zone wäre das derselbe Wert
|
# Bei einer Steuerung mit nur einer Zone wäre das derselbe Wert
|
||||||
# wie "Bewässerung läuft" - ein zweiter Messwert, der nie etwas
|
# wie "Bewässerung läuft" - ein zweiter Messwert, der nie etwas
|
||||||
# anderes sagt, macht die Auswahl nur länger.
|
# anderes sagt, macht die Auswahl nur länger.
|
||||||
states.append({
|
states.append({
|
||||||
"name": "Bewässerung läuft " + zone,
|
"name": "Bewässerung läuft " + zone,
|
||||||
"url": topic + "/Running",
|
"url": topic + "/Running",
|
||||||
"value_path": "running%d" % valve,
|
"value_path": "running%d" % valve,
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
})
|
})
|
||||||
states.append({
|
states.append({
|
||||||
# Ersetzt die Zwölf-Stunden-Sperre von auto_watering.py:
|
# Ersetzt die Zwölf-Stunden-Sperre von auto_watering.py:
|
||||||
# "heute noch nicht bewässert" heißt hier schlicht "= 0".
|
# "heute noch nicht bewässert" heißt hier schlicht "= 0".
|
||||||
#
|
#
|
||||||
# Dass das trägt, liegt an der Steuerung: sie setzt den Wert um
|
# Dass das trägt, liegt an der Steuerung: sie setzt den Wert um
|
||||||
# Mitternacht zurück. Die Nachricht ist retained, und der Runner
|
# Mitternacht zurück. Die Nachricht ist retained, und der Runner
|
||||||
# sieht nur die Zahl hinter dem value_path - den Tag, den die
|
# sieht nur die Zahl hinter dem value_path - den Tag, den die
|
||||||
# Nutzlast als "wateringDay" mitführt, kann er nicht prüfen.
|
# Nutzlast als "wateringDay" mitführt, kann er nicht prüfen.
|
||||||
# Ohne den Reset stünde nach einer bewässerungsfreien Nacht die
|
# Ohne den Reset stünde nach einer bewässerungsfreien Nacht die
|
||||||
# Summe von gestern als "heute" da, und die Zone bliebe trocken.
|
# Summe von gestern als "heute" da, und die Zone bliebe trocken.
|
||||||
"name": "Bewässert heute " + zone,
|
"name": "Bewässert heute " + zone,
|
||||||
"url": topic + "/WateringToday",
|
"url": topic + "/WateringToday",
|
||||||
"value_path": "wateringDurationTodaySecs%d" % valve,
|
"value_path": "wateringDurationTodaySecs%d" % valve,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"unit": "s",
|
"unit": "s",
|
||||||
})
|
})
|
||||||
states.append({
|
states.append({
|
||||||
# Der Trockenheits-Notlauf (max_dry_days in auto_watering.py).
|
# Der Trockenheits-Notlauf (max_dry_days in auto_watering.py).
|
||||||
# Ein Zeitstempel nützte hier nichts: der Runner vergleicht
|
# Ein Zeitstempel nützte hier nichts: der Runner vergleicht
|
||||||
# Datumsangaben nur gegen einen festen Wert, "länger als fünf
|
# Datumsangaben nur gegen einen festen Wert, "länger als fünf
|
||||||
# Tage her" ist damit nicht formulierbar. Die Steuerung liefert
|
# Tage her" ist damit nicht formulierbar. Die Steuerung liefert
|
||||||
# deshalb gleich die Anzahl Tage - dafür wurde ihre Firmware
|
# deshalb gleich die Anzahl Tage - dafür wurde ihre Firmware
|
||||||
# erweitert, das Feld gab es vorher nicht.
|
# erweitert, das Feld gab es vorher nicht.
|
||||||
#
|
#
|
||||||
# Für ein Ventil, das noch nie lief, lässt sie das Feld weg
|
# Für ein Ventil, das noch nie lief, lässt sie das Feld weg
|
||||||
# statt eine 0 zu schicken. Eine 0 hieße "heute bewässert" und
|
# statt eine 0 zu schicken. Eine 0 hieße "heute bewässert" und
|
||||||
# würde den Notlauf für immer unterdrücken; ohne Feld bleibt
|
# würde den Notlauf für immer unterdrücken; ohne Feld bleibt
|
||||||
# der Messwert leer, und die Bedingung greift schlicht nicht.
|
# der Messwert leer, und die Bedingung greift schlicht nicht.
|
||||||
"name": "Tage seit Bewässerung " + zone,
|
"name": "Tage seit Bewässerung " + zone,
|
||||||
"url": topic + "/LastWatering",
|
"url": topic + "/LastWatering",
|
||||||
"value_path": "daysSinceWatering%d" % valve,
|
"value_path": "daysSinceWatering%d" % valve,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"unit": "Tage",
|
"unit": "Tage",
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"type": "Bewässerung",
|
"type": "Bewässerung",
|
||||||
"name": steuerung["name"],
|
"name": steuerung["name"],
|
||||||
"url": "mqtt://" + topic,
|
"url": "mqtt://" + topic,
|
||||||
"commands": [{
|
"commands": [{
|
||||||
"command": "Automatik",
|
"command": "Automatik",
|
||||||
"url": topic + "/auto",
|
"url": topic + "/auto",
|
||||||
"parameters": [{
|
"parameters": [{
|
||||||
"name": "Modus",
|
"name": "Modus",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"url": topic + "/auto",
|
"url": topic + "/auto",
|
||||||
"values": steuerung["modi"],
|
"values": steuerung["modi"],
|
||||||
}],
|
}],
|
||||||
}],
|
}],
|
||||||
"states": states,
|
"states": states,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _regen() -> Dict:
|
def _regen() -> Dict:
|
||||||
"""
|
"""
|
||||||
Der Regen als Gerät: je Zeitraum ein Messwert, dazu der Stand.
|
Der Regen als Gerät: je Zeitraum ein Messwert, dazu der Stand.
|
||||||
|
|
||||||
"Regen heute" ist der bereits gefallene Regen des laufenden Tages, die
|
"Regen heute" ist der bereits gefallene Regen des laufenden Tages, die
|
||||||
übrigen zählen ihn plus die abgeschlossenen Tage davor - so hat auch
|
übrigen zählen ihn plus die abgeschlossenen Tage davor - so hat auch
|
||||||
auto_watering.py gerechnet.
|
auto_watering.py gerechnet.
|
||||||
|
|
||||||
Der Stand ist kein Messwert zum Vergleichen, sondern zum Nachsehen: im
|
Der Stand ist kein Messwert zum Vergleichen, sondern zum Nachsehen: im
|
||||||
Editor steht er hinter dem Namen ("= 04.09.2026 09:00:00") und verrät,
|
Editor steht er hinter dem Namen ("= 04.09.2026 09:00:00") und verrät,
|
||||||
ob der Sammler noch läuft. Bleibt er länger aus, leert gatherRainData.py
|
ob der Sammler noch läuft. Bleibt er länger aus, leert gatherRainData.py
|
||||||
die Zahlen von sich aus, und die Bewässerung setzt aus.
|
die Zahlen von sich aus, und die Bewässerung setzt aus.
|
||||||
"""
|
"""
|
||||||
states = [{
|
states = [{
|
||||||
"name": "Regen heute" if tage == 1 else "Regen %d Tage" % tage,
|
"name": "Regen heute" if tage == 1 else "Regen %d Tage" % tage,
|
||||||
"url": REGEN_TOPIC,
|
"url": REGEN_TOPIC,
|
||||||
"value_path": "tage%d" % tage,
|
"value_path": "tage%d" % tage,
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
} for tage in REGEN_FENSTER]
|
} for tage in REGEN_FENSTER]
|
||||||
|
|
||||||
states.append({
|
states.append({
|
||||||
"name": "Stand",
|
"name": "Stand",
|
||||||
"url": REGEN_TOPIC,
|
"url": REGEN_TOPIC,
|
||||||
"value_path": "stand",
|
"value_path": "stand",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"type": "Wetter",
|
"type": "Wetter",
|
||||||
"name": "Regen",
|
"name": "Regen",
|
||||||
"url": "mqtt://" + REGEN_TOPIC,
|
"url": "mqtt://" + REGEN_TOPIC,
|
||||||
"states": states,
|
"states": states,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
WLED Module
|
WLED Module
|
||||||
Enthält NUR Logik-spezifische Geräte-Discovery Logik
|
Enthält NUR Logik-spezifische Geräte-Discovery Logik
|
||||||
KEINE Datenbank-Operationen!
|
KEINE Datenbank-Operationen!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import socket
|
import socket
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
from modules.base_module import BaseModule
|
from modules.base_module import BaseModule
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LogicModule(BaseModule):
|
class LogicModule(BaseModule):
|
||||||
"""
|
"""
|
||||||
Logic Modul - Implementiert BaseModule Interface
|
Logic Modul - Implementiert BaseModule Interface
|
||||||
Gibt nur Actors zurück, KEINE DB-Operationen
|
Gibt nur Actors zurück, KEINE DB-Operationen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
"""Prüft ob Logic aktiviert ist"""
|
"""Prüft ob Logic aktiviert ist"""
|
||||||
return self.config.logic_enable
|
return self.config.logic_enable
|
||||||
|
|
||||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""
|
"""
|
||||||
Erzeugt Einträge für Sensorunabhängige Funktionen (Timer, Sonnenauf/untergang, Uhrzeiten, etc.)
|
Erzeugt Einträge für Sensorunabhängige Funktionen (Timer, Sonnenauf/untergang, Uhrzeiten, etc.)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (actors, sensors) - WLED sind immer Actors
|
Tuple (actors, sensors) - WLED sind immer Actors
|
||||||
"""
|
"""
|
||||||
logger.info("\n" + "=" * 60)
|
logger.info("\n" + "=" * 60)
|
||||||
logger.info("LOGIC-GERÄTE WERDEN ERZEUGT")
|
logger.info("LOGIC-GERÄTE WERDEN ERZEUGT")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
actors = []
|
actors = []
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
states = []
|
states = []
|
||||||
states.append({
|
states.append({
|
||||||
'name': 'Uhrzeit',
|
'name': 'Uhrzeit',
|
||||||
'url': 'time',
|
'url': 'time',
|
||||||
'type': 'time',
|
'type': 'time',
|
||||||
'current_value': '13:45'
|
'current_value': '13:45'
|
||||||
})
|
})
|
||||||
states.append({
|
states.append({
|
||||||
'name': 'Datum',
|
'name': 'Datum',
|
||||||
'url': 'date',
|
'url': 'date',
|
||||||
'type': 'date',
|
'type': 'date',
|
||||||
'current_value': '20.03.2026'
|
'current_value': '20.03.2026'
|
||||||
})
|
})
|
||||||
states.append({
|
states.append({
|
||||||
'name': 'Sonnenaufgang',
|
'name': 'Sonnenaufgang',
|
||||||
'url': 'sunrise',
|
'url': 'sunrise',
|
||||||
'type': 'deltatime',
|
'type': 'deltatime',
|
||||||
'current_value': '00:00'
|
'current_value': '00:00'
|
||||||
})
|
})
|
||||||
states.append({
|
states.append({
|
||||||
'name': 'Sonnenuntergang',
|
'name': 'Sonnenuntergang',
|
||||||
'url': 'sunset',
|
'url': 'sunset',
|
||||||
'type': 'deltatime',
|
'type': 'deltatime',
|
||||||
'current_value': '00:00'
|
'current_value': '00:00'
|
||||||
})
|
})
|
||||||
|
|
||||||
sensors.append({'type': 'LOGIC',
|
sensors.append({'type': 'LOGIC',
|
||||||
'name': "Zeitpunkt",
|
'name': "Zeitpunkt",
|
||||||
'url': f"Logic",
|
'url': f"Logic",
|
||||||
'commands': [],
|
'commands': [],
|
||||||
'states': states})
|
'states': states})
|
||||||
|
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,479 +1,479 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Shelly Module
|
Shelly Module
|
||||||
Enthält NUR Shelly-spezifische Geräte-Discovery Logik
|
Enthält NUR Shelly-spezifische Geräte-Discovery Logik
|
||||||
KEINE Datenbank-Operationen!
|
KEINE Datenbank-Operationen!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
import socket
|
import socket
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
from modules.base_module import BaseModule
|
from modules.base_module import BaseModule
|
||||||
|
|
||||||
# Logging konfigurieren
|
# Logging konfigurieren
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ShellyDiscovery:
|
class ShellyDiscovery:
|
||||||
"""Klasse zum Entdecken von Shelly-Geräten im Netzwerk"""
|
"""Klasse zum Entdecken von Shelly-Geräten im Netzwerk"""
|
||||||
|
|
||||||
SHELLY_MDNS_SERVICE = "_http._tcp.local."
|
SHELLY_MDNS_SERVICE = "_http._tcp.local."
|
||||||
COMMON_PORTS = [80]
|
COMMON_PORTS = [80]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.devices = []
|
self.devices = []
|
||||||
|
|
||||||
|
|
||||||
def scan_network(self, network: str = None, max_threads: int = 50) -> List[str]:
|
def scan_network(self, network: str = None, max_threads: int = 50) -> List[str]:
|
||||||
"""Scannt das Netzwerk nach Shelly-Geräten"""
|
"""Scannt das Netzwerk nach Shelly-Geräten"""
|
||||||
if network is None:
|
if network is None:
|
||||||
try:
|
try:
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
s.connect(("8.8.8.8", 80))
|
s.connect(("8.8.8.8", 80))
|
||||||
local_ip = s.getsockname()[0]
|
local_ip = s.getsockname()[0]
|
||||||
s.close()
|
s.close()
|
||||||
network_prefix = '.'.join(local_ip.split('.')[:-1])
|
network_prefix = '.'.join(local_ip.split('.')[:-1])
|
||||||
except:
|
except:
|
||||||
logger.warning("Konnte lokale IP nicht ermitteln, verwende 192.168.1.x")
|
logger.warning("Konnte lokale IP nicht ermitteln, verwende 192.168.1.x")
|
||||||
network_prefix = "192.168.1"
|
network_prefix = "192.168.1"
|
||||||
else:
|
else:
|
||||||
network_prefix = '.'.join(network.split('.')[:3])
|
network_prefix = '.'.join(network.split('.')[:3])
|
||||||
|
|
||||||
logger.info(f"Scanne Netzwerk {network_prefix}.0/24 nach Shelly-Geräten...")
|
logger.info(f"Scanne Netzwerk {network_prefix}.0/24 nach Shelly-Geräten...")
|
||||||
|
|
||||||
def check_ip(ip):
|
def check_ip(ip):
|
||||||
device = ShellyDiscovery.is_shelly_device(ip)
|
device = ShellyDiscovery.is_shelly_device(ip)
|
||||||
if device:
|
if device:
|
||||||
return device
|
return device
|
||||||
return None
|
return None
|
||||||
|
|
||||||
shelly_devices = []
|
shelly_devices = []
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
|
||||||
futures = [executor.submit(check_ip, f"{network_prefix}.{i}")
|
futures = [executor.submit(check_ip, f"{network_prefix}.{i}")
|
||||||
for i in range(1, 255)]
|
for i in range(1, 255)]
|
||||||
|
|
||||||
for future in concurrent.futures.as_completed(futures):
|
for future in concurrent.futures.as_completed(futures):
|
||||||
result = future.result()
|
result = future.result()
|
||||||
if result:
|
if result:
|
||||||
shelly_devices.append(result)
|
shelly_devices.append(result)
|
||||||
#logger.info(f"Shelly-Gerät gefunden: {result}")
|
#logger.info(f"Shelly-Gerät gefunden: {result}")
|
||||||
|
|
||||||
return shelly_devices
|
return shelly_devices
|
||||||
"""def scan_network(self, start_ip: int = 2, end_ip: int = 254, timeout: float = 0.3) -> List[str]:
|
"""def scan_network(self, start_ip: int = 2, end_ip: int = 254, timeout: float = 0.3) -> List[str]:
|
||||||
|
|
||||||
active_hosts = []
|
active_hosts = []
|
||||||
logger.info(f"Scanne Netzwerk {self.network_range}.{start_ip}-{end_ip}...")
|
logger.info(f"Scanne Netzwerk {self.network_range}.{start_ip}-{end_ip}...")
|
||||||
|
|
||||||
for i in range(start_ip, end_ip + 1):
|
for i in range(start_ip, end_ip + 1):
|
||||||
ip = f"{self.network_range}.{i}"
|
ip = f"{self.network_range}.{i}"
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
sock.settimeout(timeout)
|
sock.settimeout(timeout)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = sock.connect_ex((ip, 80))
|
result = sock.connect_ex((ip, 80))
|
||||||
if result == 0:
|
if result == 0:
|
||||||
active_hosts.append(ip)
|
active_hosts.append(ip)
|
||||||
logger.debug(f"Host gefunden: {ip}")
|
logger.debug(f"Host gefunden: {ip}")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
logger.info(f"{len(active_hosts)} aktive Hosts gefunden")
|
logger.info(f"{len(active_hosts)} aktive Hosts gefunden")
|
||||||
return active_hosts
|
return active_hosts
|
||||||
"""
|
"""
|
||||||
def is_shelly_device(ip: str) -> Optional[Dict]:
|
def is_shelly_device(ip: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Prüft ob ein Host ein Shelly-Gerät ist
|
Prüft ob ein Host ein Shelly-Gerät ist
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ip: IP-Adresse des Hosts
|
ip: IP-Adresse des Hosts
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Device Info Dict wenn Shelly, sonst None
|
Device Info Dict wenn Shelly, sonst None
|
||||||
"""
|
"""
|
||||||
#logger.info(f"Suche Shelly Getät unter: {ip}")
|
#logger.info(f"Suche Shelly Getät unter: {ip}")
|
||||||
try:
|
try:
|
||||||
# Versuche Gen2 API (neuere Shelly-Geräte)
|
# Versuche Gen2 API (neuere Shelly-Geräte)
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"http://{ip}/rpc/Shelly.GetDeviceInfo",
|
f"http://{ip}/rpc/Shelly.GetDeviceInfo",
|
||||||
timeout=2
|
timeout=2
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
logger.info(f"Shelly Gen2 Gerät gefunden: {ip} - {data.get('name', 'Unknown')}")
|
logger.info(f"Shelly Gen2 Gerät gefunden: {ip} - {data.get('name', 'Unknown')}")
|
||||||
return {
|
return {
|
||||||
'ip': ip,
|
'ip': ip,
|
||||||
'generation': 2,
|
'generation': 2,
|
||||||
'info': data
|
'info': data
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Versuche Gen1 API (ältere Shelly-Geräte)
|
# Versuche Gen1 API (ältere Shelly-Geräte)
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"http://{ip}/settings",
|
f"http://{ip}/settings",
|
||||||
timeout=2
|
timeout=2
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if 'device' in data and (data['device']['type'].startswith('SHELLY') or data['device']['type'].startswith('SHSW')):
|
if 'device' in data and (data['device']['type'].startswith('SHELLY') or data['device']['type'].startswith('SHSW')):
|
||||||
logger.info(f"Shelly Gen1 Gerät gefunden: {ip} - {data['device'].get('type', 'Unknown')}")
|
logger.info(f"Shelly Gen1 Gerät gefunden: {ip} - {data['device'].get('type', 'Unknown')}")
|
||||||
return {
|
return {
|
||||||
'ip': ip,
|
'ip': ip,
|
||||||
'generation': 1,
|
'generation': 1,
|
||||||
'info': data
|
'info': data
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_device_status(self, device: Dict) -> Optional[Dict]:
|
def get_device_status(self, device: Dict) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Holt den Status eines Shelly-Geräts
|
Holt den Status eines Shelly-Geräts
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
device: Device Info Dictionary
|
device: Device Info Dictionary
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Status Dictionary oder None
|
Status Dictionary oder None
|
||||||
"""
|
"""
|
||||||
ip = device['ip']
|
ip = device['ip']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if device['generation'] == 2:
|
if device['generation'] == 2:
|
||||||
# Gen2 Status
|
# Gen2 Status
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"http://{ip}/rpc/Shelly.GetStatus",
|
f"http://{ip}/rpc/Shelly.GetStatus",
|
||||||
timeout=2
|
timeout=2
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
# Gen1 Status
|
# Gen1 Status
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"http://{ip}/status",
|
f"http://{ip}/status",
|
||||||
timeout=2
|
timeout=2
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Abrufen des Status von {ip}: {e}")
|
logger.error(f"Fehler beim Abrufen des Status von {ip}: {e}")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def discover_devices(self) -> List[Dict]:
|
def discover_devices(self) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Entdeckt alle Shelly-Geräte im Netzwerk
|
Entdeckt alle Shelly-Geräte im Netzwerk
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Liste von Shelly-Geräten mit Status
|
Liste von Shelly-Geräten mit Status
|
||||||
"""
|
"""
|
||||||
self.devices = self.scan_network()
|
self.devices = self.scan_network()
|
||||||
|
|
||||||
for idx, device in enumerate(self.devices):
|
for idx, device in enumerate(self.devices):
|
||||||
status = self.get_device_status(device)
|
status = self.get_device_status(device)
|
||||||
self.devices[idx]['status'] = status
|
self.devices[idx]['status'] = status
|
||||||
|
|
||||||
logger.info(f"Insgesamt {len(self.devices)} Shelly-Geräte entdeckt")
|
logger.info(f"Insgesamt {len(self.devices)} Shelly-Geräte entdeckt")
|
||||||
return self.devices
|
return self.devices
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# MODULE WRAPPER
|
# MODULE WRAPPER
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
class ShellyModule(BaseModule):
|
class ShellyModule(BaseModule):
|
||||||
"""
|
"""
|
||||||
Shelly Modul - Implementiert BaseModule Interface
|
Shelly Modul - Implementiert BaseModule Interface
|
||||||
Gibt Actors/Sensors zurück, KEINE DB-Operationen
|
Gibt Actors/Sensors zurück, KEINE DB-Operationen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
"""Prüft ob Shelly aktiviert ist"""
|
"""Prüft ob Shelly aktiviert ist"""
|
||||||
return self.config.shelly_enable
|
return self.config.shelly_enable
|
||||||
|
|
||||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""
|
"""
|
||||||
Führt Shelly Discovery durch
|
Führt Shelly Discovery durch
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (actors, sensors)
|
Tuple (actors, sensors)
|
||||||
"""
|
"""
|
||||||
logger.info("\n" + "=" * 60)
|
logger.info("\n" + "=" * 60)
|
||||||
logger.info("SHELLY-GERÄTE WERDEN GESUCHT")
|
logger.info("SHELLY-GERÄTE WERDEN GESUCHT")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
actors = []
|
actors = []
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
discovery = ShellyDiscovery()
|
discovery = ShellyDiscovery()
|
||||||
devices = discovery.discover_devices()
|
devices = discovery.discover_devices()
|
||||||
|
|
||||||
if not devices:
|
if not devices:
|
||||||
logger.info("Keine Shelly-Geräte gefunden")
|
logger.info("Keine Shelly-Geräte gefunden")
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|
||||||
logger.info(f"{len(devices)} Shelly-Geräte gefunden")
|
logger.info(f"{len(devices)} Shelly-Geräte gefunden")
|
||||||
|
|
||||||
# Verarbeite jedes Gerät
|
# Verarbeite jedes Gerät
|
||||||
for device in devices:
|
for device in devices:
|
||||||
if device['generation'] == 2:
|
if device['generation'] == 2:
|
||||||
device_actors, device_sensors = self._parse_gen2_device(device)
|
device_actors, device_sensors = self._parse_gen2_device(device)
|
||||||
else:
|
else:
|
||||||
device_actors, device_sensors = self._parse_gen1_device(device)
|
device_actors, device_sensors = self._parse_gen1_device(device)
|
||||||
|
|
||||||
actors.extend(device_actors)
|
actors.extend(device_actors)
|
||||||
sensors.extend(device_sensors)
|
sensors.extend(device_sensors)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"✗ Shelly Discovery Fehler: {e}")
|
logger.error(f"✗ Shelly Discovery Fehler: {e}")
|
||||||
|
|
||||||
logger.info(f"Shelly: {len(actors)} Aktoren, {len(sensors)} Sensoren gefunden")
|
logger.info(f"Shelly: {len(actors)} Aktoren, {len(sensors)} Sensoren gefunden")
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|
||||||
def _parse_gen2_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
|
def _parse_gen2_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""Parst Gen2 Shelly-Gerät"""
|
"""Parst Gen2 Shelly-Gerät"""
|
||||||
actors = []
|
actors = []
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
info = device.get('info', {})
|
info = device.get('info', {})
|
||||||
status = device.get('status', {})
|
status = device.get('status', {})
|
||||||
ip = device['ip']
|
ip = device['ip']
|
||||||
|
|
||||||
device_name = info.get('name', f"Shelly_{info.get('id', ip)}")
|
device_name = info.get('name', f"Shelly_{info.get('id', ip)}")
|
||||||
device_model = info.get('model', 'Unknown')
|
device_model = info.get('model', 'Unknown')
|
||||||
|
|
||||||
# Switches als Aktoren
|
# Switches als Aktoren
|
||||||
switch_count = sum(1 for key in status.keys() if key.startswith('switch:'))
|
switch_count = sum(1 for key in status.keys() if key.startswith('switch:'))
|
||||||
for i in range(switch_count):
|
for i in range(switch_count):
|
||||||
switch_data = status.get(f'switch:{i}', {})
|
switch_data = status.get(f'switch:{i}', {})
|
||||||
|
|
||||||
actors.append({
|
actors.append({
|
||||||
'type': f'ShellySwitch_{device_model}'.replace(' ', '_'),
|
'type': f'ShellySwitch_{device_model}'.replace(' ', '_'),
|
||||||
'name': f"{device_name}_Switch_{i}",
|
'name': f"{device_name}_Switch_{i}",
|
||||||
'url': f"http://{ip}/rpc/Switch.Set?id={i}",
|
'url': f"http://{ip}/rpc/Switch.Set?id={i}",
|
||||||
'commands': [
|
'commands': [
|
||||||
{'command': 'turn_on', 'url': 'on=true', 'parameters': []},
|
{'command': 'turn_on', 'url': 'on=true', 'parameters': []},
|
||||||
{'command': 'turn_off', 'url': 'on=false', 'parameters': []},
|
{'command': 'turn_off', 'url': 'on=false', 'parameters': []},
|
||||||
{'command': 'toggle', 'parameters': []}
|
{'command': 'toggle', 'parameters': []}
|
||||||
],
|
],
|
||||||
'states': [
|
'states': [
|
||||||
{
|
{
|
||||||
'name': 'output',
|
'name': 'output',
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
'url': 'output',
|
'url': 'output',
|
||||||
'current_value': switch_data.get('output', False)
|
'current_value': switch_data.get('output', False)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
# Temperatursensoren
|
# Temperatursensoren
|
||||||
temp_count = sum(1 for key in status.keys() if key.startswith('temperature:'))
|
temp_count = sum(1 for key in status.keys() if key.startswith('temperature:'))
|
||||||
for i in range(temp_count):
|
for i in range(temp_count):
|
||||||
temp_data = status.get(f'temperature:{i}', {})
|
temp_data = status.get(f'temperature:{i}', {})
|
||||||
|
|
||||||
sensors.append({
|
sensors.append({
|
||||||
'type': 'Temperatur',
|
'type': 'Temperatur',
|
||||||
'name': f"{device_name}_Temp_{i}",
|
'name': f"{device_name}_Temp_{i}",
|
||||||
'url': f"http://{ip}/rpc/Temperature.GetStatus?id={i}",
|
'url': f"http://{ip}/rpc/Temperature.GetStatus?id={i}",
|
||||||
'states': [
|
'states': [
|
||||||
{
|
{
|
||||||
'name': 'temperature',
|
'name': 'temperature',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'current_value': temp_data.get('tC'),
|
'current_value': temp_data.get('tC'),
|
||||||
'url': f"tC",
|
'url': f"tC",
|
||||||
'unit': '°C'
|
'unit': '°C'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
# Energy-Meter
|
# Energy-Meter
|
||||||
em_count = sum(1 for key in status.keys() if key.startswith('em:'))
|
em_count = sum(1 for key in status.keys() if key.startswith('em:'))
|
||||||
for i in range(em_count):
|
for i in range(em_count):
|
||||||
em_data = status.get(f'em:{i}', {})
|
em_data = status.get(f'em:{i}', {})
|
||||||
sensors.append({
|
sensors.append({
|
||||||
'type': 'Stromzähler',
|
'type': 'Stromzähler',
|
||||||
'name': f"{device_name}_EM_{i}",
|
'name': f"{device_name}_EM_{i}",
|
||||||
'url': f"http://{ip}/rpc/em.GetStatus?id={i}",
|
'url': f"http://{ip}/rpc/em.GetStatus?id={i}",
|
||||||
'states': []
|
'states': []
|
||||||
})
|
})
|
||||||
if(em_data.get('a_voltage') is not None):
|
if(em_data.get('a_voltage') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Spannung Phase A',
|
'name': 'Spannung Phase A',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'a_voltage',
|
'url': 'a_voltage',
|
||||||
'current_value': em_data.get('a_voltage'),
|
'current_value': em_data.get('a_voltage'),
|
||||||
'unit': 'V'})
|
'unit': 'V'})
|
||||||
if(em_data.get('b_voltage') is not None):
|
if(em_data.get('b_voltage') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Spannung Phase B',
|
'name': 'Spannung Phase B',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'b_voltage',
|
'url': 'b_voltage',
|
||||||
'current_value': em_data.get('b_voltage'),
|
'current_value': em_data.get('b_voltage'),
|
||||||
'unit': 'V'})
|
'unit': 'V'})
|
||||||
if(em_data.get('c_voltage') is not None):
|
if(em_data.get('c_voltage') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Spannung Phase C',
|
'name': 'Spannung Phase C',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'c_voltage',
|
'url': 'c_voltage',
|
||||||
'current_value': em_data.get('c_voltage'),
|
'current_value': em_data.get('c_voltage'),
|
||||||
'unit': 'V'})
|
'unit': 'V'})
|
||||||
if(em_data.get('a_current') is not None):
|
if(em_data.get('a_current') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Strom Phase A',
|
'name': 'Strom Phase A',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'a_current',
|
'url': 'a_current',
|
||||||
'current_value': em_data.get('a_current'),
|
'current_value': em_data.get('a_current'),
|
||||||
'unit': 'A'})
|
'unit': 'A'})
|
||||||
if(em_data.get('b_current') is not None):
|
if(em_data.get('b_current') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Strom Phase B',
|
'name': 'Strom Phase B',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'b_current',
|
'url': 'b_current',
|
||||||
'current_value': em_data.get('b_current'),
|
'current_value': em_data.get('b_current'),
|
||||||
'unit': 'A'})
|
'unit': 'A'})
|
||||||
if(em_data.get('c_current') is not None):
|
if(em_data.get('c_current') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Strom Phase C',
|
'name': 'Strom Phase C',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'c_current',
|
'url': 'c_current',
|
||||||
'current_value': em_data.get('c_current'),
|
'current_value': em_data.get('c_current'),
|
||||||
'unit': 'A'})
|
'unit': 'A'})
|
||||||
if(em_data.get('a_act_power') is not None):
|
if(em_data.get('a_act_power') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Wirkleistung Phase A',
|
'name': 'Wirkleistung Phase A',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'a_act_power',
|
'url': 'a_act_power',
|
||||||
'current_value': em_data.get('a_act_power'),
|
'current_value': em_data.get('a_act_power'),
|
||||||
'unit': 'W'})
|
'unit': 'W'})
|
||||||
if(em_data.get('b_act_power') is not None):
|
if(em_data.get('b_act_power') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Wirkleistung Phase B',
|
'name': 'Wirkleistung Phase B',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'b_act_power',
|
'url': 'b_act_power',
|
||||||
'current_value': em_data.get('b_act_power'),
|
'current_value': em_data.get('b_act_power'),
|
||||||
'unit': 'W'})
|
'unit': 'W'})
|
||||||
if(em_data.get('c_act_power') is not None):
|
if(em_data.get('c_act_power') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Wirkleistung Phase C',
|
'name': 'Wirkleistung Phase C',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'c_act_power',
|
'url': 'c_act_power',
|
||||||
'current_value': em_data.get('c_act_power'),
|
'current_value': em_data.get('c_act_power'),
|
||||||
'unit': 'W'})
|
'unit': 'W'})
|
||||||
if(em_data.get('a_aprt_power') is not None):
|
if(em_data.get('a_aprt_power') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Scheinleistung Phase A',
|
'name': 'Scheinleistung Phase A',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'a_aprt_power',
|
'url': 'a_aprt_power',
|
||||||
'current_value': em_data.get('a_aprt_power'),
|
'current_value': em_data.get('a_aprt_power'),
|
||||||
'unit': 'VA'})
|
'unit': 'VA'})
|
||||||
if(em_data.get('b_aprt_power') is not None):
|
if(em_data.get('b_aprt_power') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Scheinleistung Phase B',
|
'name': 'Scheinleistung Phase B',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'b_aprt_power',
|
'url': 'b_aprt_power',
|
||||||
'current_value': em_data.get('b_aprt_power'),
|
'current_value': em_data.get('b_aprt_power'),
|
||||||
'unit': 'VA'})
|
'unit': 'VA'})
|
||||||
if(em_data.get('c_aprt_power') is not None):
|
if(em_data.get('c_aprt_power') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Scheinleistung Phase C',
|
'name': 'Scheinleistung Phase C',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'c_aprt_power',
|
'url': 'c_aprt_power',
|
||||||
'current_value': em_data.get('c_aprt_power'),
|
'current_value': em_data.get('c_aprt_power'),
|
||||||
'unit': 'VA'})
|
'unit': 'VA'})
|
||||||
if(em_data.get('a_freq') is not None):
|
if(em_data.get('a_freq') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Frequenz Phase A',
|
'name': 'Frequenz Phase A',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'a_freq',
|
'url': 'a_freq',
|
||||||
'current_value': em_data.get('a_freq'),
|
'current_value': em_data.get('a_freq'),
|
||||||
'unit': 'Hz'})
|
'unit': 'Hz'})
|
||||||
if(em_data.get('b_freq') is not None):
|
if(em_data.get('b_freq') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Frequenz Phase B',
|
'name': 'Frequenz Phase B',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'b_freq',
|
'url': 'b_freq',
|
||||||
'current_value': em_data.get('b_freq'),
|
'current_value': em_data.get('b_freq'),
|
||||||
'unit': 'Hz'})
|
'unit': 'Hz'})
|
||||||
if(em_data.get('c_freq') is not None):
|
if(em_data.get('c_freq') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Frequenz Phase C',
|
'name': 'Frequenz Phase C',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'c_freq',
|
'url': 'c_freq',
|
||||||
'current_value': em_data.get('c_freq'),
|
'current_value': em_data.get('c_freq'),
|
||||||
'unit': 'Hz'})
|
'unit': 'Hz'})
|
||||||
if(em_data.get('total_act_power') is not None):
|
if(em_data.get('total_act_power') is not None):
|
||||||
sensors[len(sensors)-1]['states'].append({
|
sensors[len(sensors)-1]['states'].append({
|
||||||
'name': 'Wirkleistung gesamt',
|
'name': 'Wirkleistung gesamt',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'url': 'total_act_power',
|
'url': 'total_act_power',
|
||||||
'current_value': em_data.get('total_act_power'),
|
'current_value': em_data.get('total_act_power'),
|
||||||
'unit': 'W'})
|
'unit': 'W'})
|
||||||
|
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|
||||||
def _parse_gen1_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
|
def _parse_gen1_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""Parst Gen1 Shelly-Gerät"""
|
"""Parst Gen1 Shelly-Gerät"""
|
||||||
actors = []
|
actors = []
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
info = device.get('info', {})
|
info = device.get('info', {})
|
||||||
status = device.get('status', {})
|
status = device.get('status', {})
|
||||||
ip = device['ip']
|
ip = device['ip']
|
||||||
|
|
||||||
device_name = info.get('name', f"Shelly_{info.get('type', ip)}")
|
device_name = info.get('name', f"Shelly_{info.get('type', ip)}")
|
||||||
device_type = info['device'].get('type', 'Unknown')
|
device_type = info['device'].get('type', 'Unknown')
|
||||||
|
|
||||||
# Relays als Aktoren
|
# Relays als Aktoren
|
||||||
relays = status.get('relays', [])
|
relays = status.get('relays', [])
|
||||||
for i, relay in enumerate(relays):
|
for i, relay in enumerate(relays):
|
||||||
actors.append({
|
actors.append({
|
||||||
'type': f'ShellyRelay_{device_type}'.replace(' ', '_'),
|
'type': f'ShellyRelay_{device_type}'.replace(' ', '_'),
|
||||||
'name': f"{device_name}_Relay_{i}",
|
'name': f"{device_name}_Relay_{i}",
|
||||||
'url': f"http://{ip}/relay/{i}",
|
'url': f"http://{ip}/relay/{i}",
|
||||||
'commands': [
|
'commands': [
|
||||||
{'command': 'turn_on', 'url': 'turn=on', 'parameters': []},
|
{'command': 'turn_on', 'url': 'turn=on', 'parameters': []},
|
||||||
{'command': 'turn_off', 'url': 'turn=off', 'parameters': []},
|
{'command': 'turn_off', 'url': 'turn=off', 'parameters': []},
|
||||||
{'command': 'toggle', 'url': 'turn=toggle', 'parameters': []}
|
{'command': 'toggle', 'url': 'turn=toggle', 'parameters': []}
|
||||||
],
|
],
|
||||||
'states': [
|
'states': [
|
||||||
{
|
{
|
||||||
'name': 'ison',
|
'name': 'ison',
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
'url': 'ison',
|
'url': 'ison',
|
||||||
'current_value': relay.get('ison', False)
|
'current_value': relay.get('ison', False)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
# Temperatursensoren
|
# Temperatursensoren
|
||||||
temp_data = status.get('tmp', {})
|
temp_data = status.get('tmp', {})
|
||||||
if temp_data and 'tC' in temp_data:
|
if temp_data and 'tC' in temp_data:
|
||||||
sensors.append({
|
sensors.append({
|
||||||
'type': 'Temperatur',
|
'type': 'Temperatur',
|
||||||
'name': f"{device_name}_Temp",
|
'name': f"{device_name}_Temp",
|
||||||
'url': f"http://{ip}/status",
|
'url': f"http://{ip}/status",
|
||||||
'states': [
|
'states': [
|
||||||
{
|
{
|
||||||
'name': 'temperature',
|
'name': 'temperature',
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'current_value': temp_data.get('tC'),
|
'current_value': temp_data.get('tC'),
|
||||||
'unit': '°C'
|
'unit': '°C'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
@@ -1,445 +1,445 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Tahoma Module
|
Tahoma Module
|
||||||
Enthält NUR Tahoma-spezifische Geräte-Discovery Logik
|
Enthält NUR Tahoma-spezifische Geräte-Discovery Logik
|
||||||
KEINE Datenbank-Operationen!
|
KEINE Datenbank-Operationen!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import urllib3
|
import urllib3
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
from modules.base_module import BaseModule
|
from modules.base_module import BaseModule
|
||||||
|
|
||||||
# SSL-Warnungen deaktivieren
|
# SSL-Warnungen deaktivieren
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TahomaAPI:
|
class TahomaAPI:
|
||||||
"""Original TahomaAPI Klasse - unverändert"""
|
"""Original TahomaAPI Klasse - unverändert"""
|
||||||
|
|
||||||
def __init__(self, gateway_ip: str, api_token: str):
|
def __init__(self, gateway_ip: str, api_token: str):
|
||||||
self.base_url = f"https://{gateway_ip}:8443/enduser-mobile-web/1/enduserAPI"
|
self.base_url = f"https://{gateway_ip}:8443/enduser-mobile-web/1/enduserAPI"
|
||||||
self.headers = {
|
self.headers = {
|
||||||
"Authorization": f"Bearer {api_token}",
|
"Authorization": f"Bearer {api_token}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_setup(self) -> Optional[Dict]:
|
def get_setup(self) -> Optional[Dict]:
|
||||||
try:
|
try:
|
||||||
url = f"{self.base_url}/setup"
|
url = f"{self.base_url}/setup"
|
||||||
response = requests.get(url, headers=self.headers, verify=False, timeout=10)
|
response = requests.get(url, headers=self.headers, verify=False, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logger.error(f"Fehler beim Abrufen der Setup-Daten: {e}")
|
logger.error(f"Fehler beim Abrufen der Setup-Daten: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_devices(self) -> List[Dict]:
|
def get_devices(self) -> List[Dict]:
|
||||||
setup = self.get_setup()
|
setup = self.get_setup()
|
||||||
if not setup:
|
if not setup:
|
||||||
return []
|
return []
|
||||||
devices = setup.get('devices', [])
|
devices = setup.get('devices', [])
|
||||||
logger.info(f"{len(devices)} Tahoma-Geräte gefunden")
|
logger.info(f"{len(devices)} Tahoma-Geräte gefunden")
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
|
||||||
class DeviceClassifier:
|
class DeviceClassifier:
|
||||||
"""Original DeviceClassifier - unverändert"""
|
"""Original DeviceClassifier - unverändert"""
|
||||||
|
|
||||||
ACTOR_TYPES = {
|
ACTOR_TYPES = {
|
||||||
'RollerShutter': 'Rollladen',
|
'RollerShutter': 'Rollladen',
|
||||||
'ExteriorScreen': 'Außenrollo',
|
'ExteriorScreen': 'Außenrollo',
|
||||||
'Awning': 'Markise',
|
'Awning': 'Markise',
|
||||||
'ExteriorVenetianBlind':'Außenjalousie',
|
'ExteriorVenetianBlind':'Außenjalousie',
|
||||||
'Blind': 'Jalousie',
|
'Blind': 'Jalousie',
|
||||||
'GarageDoor': 'Garagentor',
|
'GarageDoor': 'Garagentor',
|
||||||
'Window': 'Fenster',
|
'Window': 'Fenster',
|
||||||
'Light': 'Beleuchtung',
|
'Light': 'Beleuchtung',
|
||||||
'OnOff': 'Schalter',
|
'OnOff': 'Schalter',
|
||||||
'DimmableLight': 'Dimmbare Beleuchtung',
|
'DimmableLight': 'Dimmbare Beleuchtung',
|
||||||
'HeatingSystem': 'Heizungssystem',
|
'HeatingSystem': 'Heizungssystem',
|
||||||
'Valve': 'Ventil',
|
'Valve': 'Ventil',
|
||||||
'Switch': 'Schalter',
|
'Switch': 'Schalter',
|
||||||
'Door': 'Tür',
|
'Door': 'Tür',
|
||||||
'Curtain': 'Vorhang',
|
'Curtain': 'Vorhang',
|
||||||
'VenetianBlind': 'Jalousie',
|
'VenetianBlind': 'Jalousie',
|
||||||
'PergolaScreen': 'Pergola-Rollo'
|
'PergolaScreen': 'Pergola-Rollo'
|
||||||
}
|
}
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'TemperatureSensor': 'Temperatur',
|
'TemperatureSensor': 'Temperatur',
|
||||||
'LightSensor': 'Licht',
|
'LightSensor': 'Licht',
|
||||||
'HumiditySensor': 'Feuchtigkeit',
|
'HumiditySensor': 'Feuchtigkeit',
|
||||||
'ContactSensor': 'Kontakt',
|
'ContactSensor': 'Kontakt',
|
||||||
'OccupancySensor': 'Anwesenheit',
|
'OccupancySensor': 'Anwesenheit',
|
||||||
'SmokeSensor': 'Rauch',
|
'SmokeSensor': 'Rauch',
|
||||||
'WaterDetectionSensor': 'Wasser',
|
'WaterDetectionSensor': 'Wasser',
|
||||||
'WindowHandle': 'Fenstergriff',
|
'WindowHandle': 'Fenstergriff',
|
||||||
'MotionSensor': 'Bewegung',
|
'MotionSensor': 'Bewegung',
|
||||||
'SunSensor': 'Sonne',
|
'SunSensor': 'Sonne',
|
||||||
'WindSensor': 'Wind',
|
'WindSensor': 'Wind',
|
||||||
'RainSensor': 'Regen',
|
'RainSensor': 'Regen',
|
||||||
'ConsumptionSensor': 'Verbrauch'
|
'ConsumptionSensor': 'Verbrauch'
|
||||||
}
|
}
|
||||||
|
|
||||||
STATE_NAMES = {
|
STATE_NAMES = {
|
||||||
"core:BatteryLevelState": 'Ladestand',
|
"core:BatteryLevelState": 'Ladestand',
|
||||||
"core:BatteryState": 'Batteriezustand',
|
"core:BatteryState": 'Batteriezustand',
|
||||||
"core:ClosureState": 'Position',
|
"core:ClosureState": 'Position',
|
||||||
"core:CommandLockLevelsState": 'Gesperrt',
|
"core:CommandLockLevelsState": 'Gesperrt',
|
||||||
"core:Memorized1OrientationState": 'Gespeicherte Neigung',
|
"core:Memorized1OrientationState": 'Gespeicherte Neigung',
|
||||||
"core:Memorized1PositionState": 'Gespeicherte Position',
|
"core:Memorized1PositionState": 'Gespeicherte Position',
|
||||||
"core:MovingState": 'Fährt gerade',
|
"core:MovingState": 'Fährt gerade',
|
||||||
"core:OpenClosedState": 'Geöffnet/Geschlossen',
|
"core:OpenClosedState": 'Geöffnet/Geschlossen',
|
||||||
"core:PriorityLockTimerState": 'Verriegelungstimer',
|
"core:PriorityLockTimerState": 'Verriegelungstimer',
|
||||||
"core:DiscreteRSSILevelState": 'RSSI',
|
"core:DiscreteRSSILevelState": 'RSSI',
|
||||||
"core:SlateOrientationState": 'Lamellenausrichtung',
|
"core:SlateOrientationState": 'Lamellenausrichtung',
|
||||||
"core:StatusState": 'Status',
|
"core:StatusState": 'Status',
|
||||||
"core:LuminanceState": 'Helligkeit',
|
"core:LuminanceState": 'Helligkeit',
|
||||||
"core:SmokeState": 'Rauch',
|
"core:SmokeState": 'Rauch',
|
||||||
"core:SunEnergyState": 'Sonnenenergie',
|
"core:SunEnergyState": 'Sonnenenergie',
|
||||||
"core:TemperatureState": 'Temperatur',
|
"core:TemperatureState": 'Temperatur',
|
||||||
"io:MaintenanceRadioPartBatteryState": 'Ladestand Funkmodul',
|
"io:MaintenanceRadioPartBatteryState": 'Ladestand Funkmodul',
|
||||||
"io:MaintenanceSensorPartBatteryState": 'Ladestand Sensor',
|
"io:MaintenanceSensorPartBatteryState": 'Ladestand Sensor',
|
||||||
"core:SensorDefectState": 'Sensor defekt'
|
"core:SensorDefectState": 'Sensor defekt'
|
||||||
}
|
}
|
||||||
|
|
||||||
STATE_ENUMS = {
|
STATE_ENUMS = {
|
||||||
"core:BatteryLevelState": ["full","normal","low","verylow"],
|
"core:BatteryLevelState": ["full","normal","low","verylow"],
|
||||||
"core:BatteryState": ["full","normal","low","verylow"],
|
"core:BatteryState": ["full","normal","low","verylow"],
|
||||||
"core:ClosureState": '',
|
"core:ClosureState": '',
|
||||||
"core:CommandLockLevelsState": '',
|
"core:CommandLockLevelsState": '',
|
||||||
"core:Memorized1OrientationState": '',
|
"core:Memorized1OrientationState": '',
|
||||||
"core:Memorized1PositionState": '',
|
"core:Memorized1PositionState": '',
|
||||||
"core:MovingState": ["true","false"],
|
"core:MovingState": ["true","false"],
|
||||||
"core:OpenClosedState": ["open","close"],
|
"core:OpenClosedState": ["open","close"],
|
||||||
"core:PriorityLockTimerState": '',
|
"core:PriorityLockTimerState": '',
|
||||||
"core:DiscreteRSSILevelState": ["good","normal","low"],
|
"core:DiscreteRSSILevelState": ["good","normal","low"],
|
||||||
"core:SlateOrientationState": '',
|
"core:SlateOrientationState": '',
|
||||||
"core:StatusState": ["available","unavailable"],
|
"core:StatusState": ["available","unavailable"],
|
||||||
"core:TargetClosureState": '',
|
"core:TargetClosureState": '',
|
||||||
"core:LuminanceState": 'Helligkeit',
|
"core:LuminanceState": 'Helligkeit',
|
||||||
"core:SmokeState": ["notDetected","detected"],
|
"core:SmokeState": ["notDetected","detected"],
|
||||||
"core:SunEnergyState": '',
|
"core:SunEnergyState": '',
|
||||||
"core:TemperatureState": '',
|
"core:TemperatureState": '',
|
||||||
"io:MaintenanceRadioPartBatteryState": ["full","normal","low","verylow"],
|
"io:MaintenanceRadioPartBatteryState": ["full","normal","low","verylow"],
|
||||||
"io:MaintenanceSensorPartBatteryState": ["full","normal","low","verylow"],
|
"io:MaintenanceSensorPartBatteryState": ["full","normal","low","verylow"],
|
||||||
"core:SensorDefectState": ["true","false"]
|
"core:SensorDefectState": ["true","false"]
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
"core:TargetClosureState": 'Zielposition',
|
"core:TargetClosureState": 'Zielposition',
|
||||||
"io:PriorityLockOriginatorState": 'Sperrendes Gerät',
|
"io:PriorityLockOriginatorState": 'Sperrendes Gerät',
|
||||||
"core:ManufacturerDiagnosticsState": 'Diagnose',
|
"core:ManufacturerDiagnosticsState": 'Diagnose',
|
||||||
"core:OpenClosedUnknownState": 'Statusanzeige wenn unbekannt',
|
"core:OpenClosedUnknownState": 'Statusanzeige wenn unbekannt',
|
||||||
"core:ManufacturerSettingsState": 'Einstellungen',
|
"core:ManufacturerSettingsState": 'Einstellungen',
|
||||||
"core:RSSILevelState": 'RSSI',
|
"core:RSSILevelState": 'RSSI',
|
||||||
"core:NameState": 'Name',
|
"core:NameState": 'Name',
|
||||||
"core:SecuredPositionState": 'Sicherer Zustand',
|
"core:SecuredPositionState": 'Sicherer Zustand',
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_sensor_type_translation(cls, sensor):
|
def get_sensor_type_translation(cls, sensor):
|
||||||
return cls.SENSOR_TYPES.get(sensor, sensor)
|
return cls.SENSOR_TYPES.get(sensor, sensor)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_actor_type_translation(cls, actor_type):
|
def get_actor_type_translation(cls, actor_type):
|
||||||
return cls.ACTOR_TYPES.get(actor_type, actor_type)
|
return cls.ACTOR_TYPES.get(actor_type, actor_type)
|
||||||
|
|
||||||
PARAMETER_TYPES = {
|
PARAMETER_TYPES = {
|
||||||
0: "bool",
|
0: "bool",
|
||||||
1: "integer", # Integer value
|
1: "integer", # Integer value
|
||||||
2: "float", # Floating point number
|
2: "float", # Floating point number
|
||||||
3: "string", # Text string
|
3: "string", # Text string
|
||||||
4: "undefined",
|
4: "undefined",
|
||||||
6: "bool",
|
6: "bool",
|
||||||
11: "array"
|
11: "array"
|
||||||
}
|
}
|
||||||
|
|
||||||
TAHOMA_COMMANDS = {
|
TAHOMA_COMMANDS = {
|
||||||
"setClosure": 'Position',
|
"setClosure": 'Position',
|
||||||
"setClosureAndOrientation": "Position+Neigung",
|
"setClosureAndOrientation": "Position+Neigung",
|
||||||
"setOrientation": "Neigung",
|
"setOrientation": "Neigung",
|
||||||
"up": "Auf",
|
"up": "Auf",
|
||||||
"down": "Zu",
|
"down": "Zu",
|
||||||
"my": "my-Position anfahren",
|
"my": "my-Position anfahren",
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
"refresh": "Aktualisieren",
|
"refresh": "Aktualisieren",
|
||||||
"wink": "Winken",
|
"wink": "Winken",
|
||||||
"setMyPosition": "my-Position einstellen",
|
"setMyPosition": "my-Position einstellen",
|
||||||
"on": "Anschalten",
|
"on": "Anschalten",
|
||||||
"off": "Ausschalten",
|
"off": "Ausschalten",
|
||||||
"toggle": "Toggle",
|
"toggle": "Toggle",
|
||||||
"setIntensity": "Intensität",
|
"setIntensity": "Intensität",
|
||||||
"setColor": "Farbe",
|
"setColor": "Farbe",
|
||||||
"setColorTemperature": "Farbtemperatur",
|
"setColorTemperature": "Farbtemperatur",
|
||||||
"setTargetTemperature": "Solltemperatur",
|
"setTargetTemperature": "Solltemperatur",
|
||||||
"setMode": "Modus",
|
"setMode": "Modus",
|
||||||
"pulse": "Impuls",
|
"pulse": "Impuls",
|
||||||
"setLevel": "Wert",
|
"setLevel": "Wert",
|
||||||
"trigger": "Trigger",
|
"trigger": "Trigger",
|
||||||
}
|
}
|
||||||
# Tahoma Commands mit Parametern
|
# Tahoma Commands mit Parametern
|
||||||
TAHOMA_COMMAND_PARAMS = {
|
TAHOMA_COMMAND_PARAMS = {
|
||||||
"setClosure": [{"name": "Position","url":"0", "type": "integer", "min": 0, "max": 100}],
|
"setClosure": [{"name": "Position","url":"0", "type": "integer", "min": 0, "max": 100}],
|
||||||
"setClosureAndOrientation": [
|
"setClosureAndOrientation": [
|
||||||
{"name": "Position", "url":"0", "type": "integer", "min": 0, "max": 100},
|
{"name": "Position", "url":"0", "type": "integer", "min": 0, "max": 100},
|
||||||
{"name": "Neigung", "url":"1", "type": "integer", "min": 0, "max": 100}
|
{"name": "Neigung", "url":"1", "type": "integer", "min": 0, "max": 100}
|
||||||
],
|
],
|
||||||
"setOrientation": [{"name": "Neigung", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
"setOrientation": [{"name": "Neigung", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
||||||
"up": [], "down": [], "my": [], "stop": [], "refresh": [], "wink":[],
|
"up": [], "down": [], "my": [], "stop": [], "refresh": [], "wink":[],
|
||||||
"setMyPosition": [{"name": "Position", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
"setMyPosition": [{"name": "Position", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
||||||
"on": [], "off": [], "toggle": [],
|
"on": [], "off": [], "toggle": [],
|
||||||
"setIntensity": [{"name": "Helligkeit", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
"setIntensity": [{"name": "Helligkeit", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
||||||
"setColor": [
|
"setColor": [
|
||||||
{"name": "Farbton", "url":"0", "type": "integer", "min": 0, "max": 360},
|
{"name": "Farbton", "url":"0", "type": "integer", "min": 0, "max": 360},
|
||||||
{"name": "Sättigung", "url":"1", "type": "integer", "min": 0, "max": 100}
|
{"name": "Sättigung", "url":"1", "type": "integer", "min": 0, "max": 100}
|
||||||
],
|
],
|
||||||
"setColorTemperature": [{"name": "Farbtemperatur", "url":"0", "type": "integer", "min": 2000, "max": 6500}],
|
"setColorTemperature": [{"name": "Farbtemperatur", "url":"0", "type": "integer", "min": 2000, "max": 6500}],
|
||||||
"setTargetTemperature": [{"name": "Temperatur", "url":"0", "type": "float", "min": 5.0, "max": 30.0}],
|
"setTargetTemperature": [{"name": "Temperatur", "url":"0", "type": "float", "min": 5.0, "max": 30.0}],
|
||||||
"setMode": [{"name": "Betriebsart", "url":"0", "type": "string"}],
|
"setMode": [{"name": "Betriebsart", "url":"0", "type": "string"}],
|
||||||
"pulse": [{"name": "Impulsdauer", "url":"0", "type": "integer", "min": 1, "max": 3600}],
|
"pulse": [{"name": "Impulsdauer", "url":"0", "type": "integer", "min": 1, "max": 3600}],
|
||||||
"setLevel": [{"name": "Ausgangslevel", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
"setLevel": [{"name": "Ausgangslevel", "url":"0", "type": "integer", "min": 0, "max": 100}],
|
||||||
"trigger": [],
|
"trigger": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_actor(cls, device: Dict) -> bool:
|
def is_actor(cls, device: Dict) -> bool:
|
||||||
"""Prüft ob Gerät ein Aktor ist"""
|
"""Prüft ob Gerät ein Aktor ist"""
|
||||||
device_type = device.get('definition').get('uiClass', '')
|
device_type = device.get('definition').get('uiClass', '')
|
||||||
|
|
||||||
if device_type in cls.ACTOR_TYPES:
|
if device_type in cls.ACTOR_TYPES:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
commands = device.get('definition', {}).get('commands', [])
|
commands = device.get('definition', {}).get('commands', [])
|
||||||
if commands:
|
if commands:
|
||||||
command_names = [cmd.get('commandName', '') for cmd in commands]
|
command_names = [cmd.get('commandName', '') for cmd in commands]
|
||||||
actor_commands = {'open', 'close', 'on', 'off', 'up', 'down', 'setPosition', 'dim'}
|
actor_commands = {'open', 'close', 'on', 'off', 'up', 'down', 'setPosition', 'dim'}
|
||||||
if any(cmd in actor_commands for cmd in command_names):
|
if any(cmd in actor_commands for cmd in command_names):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_sensor(cls, device: Dict) -> bool:
|
def is_sensor(cls, device: Dict) -> bool:
|
||||||
"""Prüft ob Gerät ein Sensor ist"""
|
"""Prüft ob Gerät ein Sensor ist"""
|
||||||
device_type = device.get('definition').get('uiClass', '')
|
device_type = device.get('definition').get('uiClass', '')
|
||||||
|
|
||||||
if device_type in cls.SENSOR_TYPES:
|
if device_type in cls.SENSOR_TYPES:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
states = device.get('states', [])
|
states = device.get('states', [])
|
||||||
commands = device.get('definition', {}).get('commands', [])
|
commands = device.get('definition', {}).get('commands', [])
|
||||||
|
|
||||||
if states and len(states) > 0 and len(commands) <= 1:
|
if states and len(states) > 0 and len(commands) <= 1:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extract_actor_data(cls, device: Dict) -> tuple:
|
def extract_actor_data(cls, device: Dict) -> tuple:
|
||||||
"""Extrahiert Commands und States aus Aktor"""
|
"""Extrahiert Commands und States aus Aktor"""
|
||||||
commands = []
|
commands = []
|
||||||
states = []
|
states = []
|
||||||
|
|
||||||
cmd_definitions = device.get('definition', {}).get('commands', [])
|
cmd_definitions = device.get('definition', {}).get('commands', [])
|
||||||
|
|
||||||
for cmd in cmd_definitions:
|
for cmd in cmd_definitions:
|
||||||
command_url = cmd.get('commandName', '')
|
command_url = cmd.get('commandName', '')
|
||||||
command_name = cls.TAHOMA_COMMANDS.get(command_url, command_url)
|
command_name = cls.TAHOMA_COMMANDS.get(command_url, command_url)
|
||||||
cmd_params = cls.TAHOMA_COMMAND_PARAMS.get(command_url, "Not in List")
|
cmd_params = cls.TAHOMA_COMMAND_PARAMS.get(command_url, "Not in List")
|
||||||
if(cmd_params != "Not in List"): #only append command, if it is on of the listed commands, to prevent flooding the DB with bullshit.
|
if(cmd_params != "Not in List"): #only append command, if it is on of the listed commands, to prevent flooding the DB with bullshit.
|
||||||
command_entry = {
|
command_entry = {
|
||||||
'command': command_name,
|
'command': command_name,
|
||||||
'url': command_url,
|
'url': command_url,
|
||||||
'parameters': []
|
'parameters': []
|
||||||
}
|
}
|
||||||
|
|
||||||
for cmd_param in cmd_params:
|
for cmd_param in cmd_params:
|
||||||
param_detail = {'name': cmd_param.get('name', '')}
|
param_detail = {'name': cmd_param.get('name', '')}
|
||||||
|
|
||||||
if 'type' in cmd_param:
|
if 'type' in cmd_param:
|
||||||
param_detail['type'] = cmd_param['type']
|
param_detail['type'] = cmd_param['type']
|
||||||
if 'min' in cmd_param:
|
if 'min' in cmd_param:
|
||||||
param_detail['min'] = cmd_param['min']
|
param_detail['min'] = cmd_param['min']
|
||||||
if 'max' in cmd_param:
|
if 'max' in cmd_param:
|
||||||
param_detail['max'] = cmd_param['max']
|
param_detail['max'] = cmd_param['max']
|
||||||
if 'values' in cmd_param:
|
if 'values' in cmd_param:
|
||||||
param_detail['values'] = cmd_param['values']
|
param_detail['values'] = cmd_param['values']
|
||||||
if 'url' in cmd_param:
|
if 'url' in cmd_param:
|
||||||
param_detail['url'] = cmd_param['url']
|
param_detail['url'] = cmd_param['url']
|
||||||
if param_detail['name']:
|
if param_detail['name']:
|
||||||
command_entry['parameters'].append(param_detail)
|
command_entry['parameters'].append(param_detail)
|
||||||
|
|
||||||
commands.append(command_entry)
|
commands.append(command_entry)
|
||||||
|
|
||||||
# States extrahieren
|
# States extrahieren
|
||||||
state_definitions = device.get('states', [])
|
state_definitions = device.get('states', [])
|
||||||
for state in state_definitions:
|
for state in state_definitions:
|
||||||
state_url = state.get('name', '')
|
state_url = state.get('name', '')
|
||||||
state_name = cls.STATE_NAMES.get(state_url, '')
|
state_name = cls.STATE_NAMES.get(state_url, '')
|
||||||
state_enums = cls.STATE_ENUMS.get(state_url,'')
|
state_enums = cls.STATE_ENUMS.get(state_url,'')
|
||||||
if state_name:
|
if state_name:
|
||||||
state_entry = {
|
state_entry = {
|
||||||
'name': state_name,
|
'name': state_name,
|
||||||
'url': state_url,
|
'url': state_url,
|
||||||
'type': cls.PARAMETER_TYPES.get(state.get('type', 4)),
|
'type': cls.PARAMETER_TYPES.get(state.get('type', 4)),
|
||||||
'values': state_enums
|
'values': state_enums
|
||||||
}
|
}
|
||||||
if 'value' in state:
|
if 'value' in state:
|
||||||
state_entry['current_value'] = state['value']
|
state_entry['current_value'] = state['value']
|
||||||
states.append(state_entry)
|
states.append(state_entry)
|
||||||
|
|
||||||
return commands, states
|
return commands, states
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extract_sensor_data(cls, device: Dict) -> list:
|
def extract_sensor_data(cls, device: Dict) -> list:
|
||||||
"""Extrahiert States aus Sensor"""
|
"""Extrahiert States aus Sensor"""
|
||||||
states = []
|
states = []
|
||||||
|
|
||||||
state_definitions = device.get('states', [])
|
state_definitions = device.get('states', [])
|
||||||
for state in state_definitions:
|
for state in state_definitions:
|
||||||
state_url = state.get('name', '')
|
state_url = state.get('name', '')
|
||||||
state_name = cls.STATE_NAMES.get(state_url, '')
|
state_name = cls.STATE_NAMES.get(state_url, '')
|
||||||
state_enums = cls.STATE_ENUMS.get(state_url,'')
|
state_enums = cls.STATE_ENUMS.get(state_url,'')
|
||||||
if state_name:
|
if state_name:
|
||||||
state_entry = {
|
state_entry = {
|
||||||
'name': state_name,
|
'name': state_name,
|
||||||
'url': state_url,
|
'url': state_url,
|
||||||
'type': cls.PARAMETER_TYPES.get(state.get('type', 4)),
|
'type': cls.PARAMETER_TYPES.get(state.get('type', 4)),
|
||||||
'values': state_enums
|
'values': state_enums
|
||||||
}
|
}
|
||||||
if 'value' in state:
|
if 'value' in state:
|
||||||
state_entry['current_value'] = state['value']
|
state_entry['current_value'] = state['value']
|
||||||
states.append(state_entry)
|
states.append(state_entry)
|
||||||
|
|
||||||
return states
|
return states
|
||||||
|
|
||||||
|
|
||||||
class TahomaModule(BaseModule):
|
class TahomaModule(BaseModule):
|
||||||
"""
|
"""
|
||||||
Tahoma Modul - Implementiert BaseModule Interface
|
Tahoma Modul - Implementiert BaseModule Interface
|
||||||
Gibt nur Actors/Sensors zurück, KEINE DB-Operationen
|
Gibt nur Actors/Sensors zurück, KEINE DB-Operationen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
"""Prüft ob Tahoma aktiviert ist"""
|
"""Prüft ob Tahoma aktiviert ist"""
|
||||||
return (self.config.tahoma_enable and
|
return (self.config.tahoma_enable and
|
||||||
self.config.tahoma_ip and
|
self.config.tahoma_ip and
|
||||||
self.config.tahoma_token)
|
self.config.tahoma_token)
|
||||||
|
|
||||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""
|
"""
|
||||||
Führt Tahoma Discovery durch
|
Führt Tahoma Discovery durch
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (actors, sensors) - Listen von Dicts im vereinheitlichten Format
|
Tuple (actors, sensors) - Listen von Dicts im vereinheitlichten Format
|
||||||
"""
|
"""
|
||||||
logger.info("\n" + "=" * 60)
|
logger.info("\n" + "=" * 60)
|
||||||
logger.info("TAHOMA-GERÄTE WERDEN ABGERUFEN")
|
logger.info("TAHOMA-GERÄTE WERDEN ABGERUFEN")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
actors = []
|
actors = []
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
# TahomaAPI initialisieren
|
# TahomaAPI initialisieren
|
||||||
tahoma = TahomaAPI(self.config.tahoma_ip, self.config.tahoma_token)
|
tahoma = TahomaAPI(self.config.tahoma_ip, self.config.tahoma_token)
|
||||||
devices = tahoma.get_devices()
|
devices = tahoma.get_devices()
|
||||||
|
|
||||||
if not devices:
|
if not devices:
|
||||||
logger.warning("Keine Tahoma-Geräte gefunden")
|
logger.warning("Keine Tahoma-Geräte gefunden")
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|
||||||
# Geräte gruppieren (Original-Logik)
|
# Geräte gruppieren (Original-Logik)
|
||||||
device_groups = {}
|
device_groups = {}
|
||||||
standalone_devices = []
|
standalone_devices = []
|
||||||
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
device_url = device.get('deviceURL', '')
|
device_url = device.get('deviceURL', '')
|
||||||
match = re.match(r'(.+)#(\d+)$', device_url)
|
match = re.match(r'(.+)#(\d+)$', device_url)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
base_url = match.group(1)
|
base_url = match.group(1)
|
||||||
if base_url not in device_groups:
|
if base_url not in device_groups:
|
||||||
device_groups[base_url] = []
|
device_groups[base_url] = []
|
||||||
device_groups[base_url].append(device)
|
device_groups[base_url].append(device)
|
||||||
else:
|
else:
|
||||||
standalone_devices.append(device)
|
standalone_devices.append(device)
|
||||||
|
|
||||||
# Gruppierte Geräte verarbeiten
|
# Gruppierte Geräte verarbeiten
|
||||||
for base_url, group_devices in device_groups.items():
|
for base_url, group_devices in device_groups.items():
|
||||||
main_device = None
|
main_device = None
|
||||||
for dev in group_devices:
|
for dev in group_devices:
|
||||||
if dev.get('deviceURL', '').endswith('#1'):
|
if dev.get('deviceURL', '').endswith('#1'):
|
||||||
main_device = dev
|
main_device = dev
|
||||||
break
|
break
|
||||||
|
|
||||||
if not main_device and group_devices:
|
if not main_device and group_devices:
|
||||||
main_device = group_devices[0]
|
main_device = group_devices[0]
|
||||||
|
|
||||||
main_name = main_device.get('label', 'Unbekannt') if main_device else 'Unbekannt'
|
main_name = main_device.get('label', 'Unbekannt') if main_device else 'Unbekannt'
|
||||||
|
|
||||||
for device in group_devices:
|
for device in group_devices:
|
||||||
if(device.get('label','').startswith("IO (") == False):
|
if(device.get('label','').startswith("IO (") == False):
|
||||||
actor, sensor = self._process_device(device, device.get('label',main_name))
|
actor, sensor = self._process_device(device, device.get('label',main_name))
|
||||||
else:
|
else:
|
||||||
actor, sensor = self._process_device(device, main_name)
|
actor, sensor = self._process_device(device, main_name)
|
||||||
if actor:
|
if actor:
|
||||||
actors.append(actor)
|
actors.append(actor)
|
||||||
if sensor:
|
if sensor:
|
||||||
sensors.append(sensor)
|
sensors.append(sensor)
|
||||||
|
|
||||||
# Standalone Geräte verarbeiten
|
# Standalone Geräte verarbeiten
|
||||||
for device in standalone_devices:
|
for device in standalone_devices:
|
||||||
device_name = device.get('label', 'Unbekannt')
|
device_name = device.get('label', 'Unbekannt')
|
||||||
actor, sensor = self._process_device(device, device_name)
|
actor, sensor = self._process_device(device, device_name)
|
||||||
if actor:
|
if actor:
|
||||||
actors.append(actor)
|
actors.append(actor)
|
||||||
if sensor:
|
if sensor:
|
||||||
sensors.append(sensor)
|
sensors.append(sensor)
|
||||||
|
|
||||||
logger.info(f"Tahoma: {len(actors)} Aktoren, {len(sensors)} Sensoren gefunden")
|
logger.info(f"Tahoma: {len(actors)} Aktoren, {len(sensors)} Sensoren gefunden")
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|
||||||
def _process_device(self, device: Dict, device_name: str) -> Tuple[Optional[Dict], Optional[Dict]]:
|
def _process_device(self, device: Dict, device_name: str) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||||
"""
|
"""
|
||||||
Verarbeitet ein einzelnes Gerät
|
Verarbeitet ein einzelnes Gerät
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (actor_dict or None, sensor_dict or None)
|
Tuple (actor_dict or None, sensor_dict or None)
|
||||||
"""
|
"""
|
||||||
device_url = device.get('deviceURL', '')
|
device_url = device.get('deviceURL', '')
|
||||||
device_type = device.get('definition').get('uiClass', 'Unknown')
|
device_type = device.get('definition').get('uiClass', 'Unknown')
|
||||||
|
|
||||||
is_actor = DeviceClassifier.is_actor(device)
|
is_actor = DeviceClassifier.is_actor(device)
|
||||||
is_sensor = DeviceClassifier.is_sensor(device)
|
is_sensor = DeviceClassifier.is_sensor(device)
|
||||||
|
|
||||||
actor = None
|
actor = None
|
||||||
sensor = None
|
sensor = None
|
||||||
|
|
||||||
if is_actor:
|
if is_actor:
|
||||||
commands, states = DeviceClassifier.extract_actor_data(device)
|
commands, states = DeviceClassifier.extract_actor_data(device)
|
||||||
device_type = DeviceClassifier.get_actor_type_translation(device_type)
|
device_type = DeviceClassifier.get_actor_type_translation(device_type)
|
||||||
actor = {
|
actor = {
|
||||||
'type': device_type,
|
'type': device_type,
|
||||||
'name': device_name,
|
'name': device_name,
|
||||||
'url': device_url,
|
'url': device_url,
|
||||||
'commands': commands,
|
'commands': commands,
|
||||||
'states': states
|
'states': states
|
||||||
}
|
}
|
||||||
|
|
||||||
elif is_sensor:
|
elif is_sensor:
|
||||||
states = DeviceClassifier.extract_sensor_data(device)
|
states = DeviceClassifier.extract_sensor_data(device)
|
||||||
device_type = DeviceClassifier.get_sensor_type_translation(device_type)
|
device_type = DeviceClassifier.get_sensor_type_translation(device_type)
|
||||||
sensor = {
|
sensor = {
|
||||||
'type': device_type,
|
'type': device_type,
|
||||||
'name': device_name,
|
'name': device_name,
|
||||||
'url': device_url,
|
'url': device_url,
|
||||||
'states': states
|
'states': states
|
||||||
}
|
}
|
||||||
|
|
||||||
return actor, sensor
|
return actor, sensor
|
||||||
|
|||||||
@@ -1,316 +1,316 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
WLED Module
|
WLED Module
|
||||||
Enthält NUR WLED-spezifische Geräte-Discovery Logik
|
Enthält NUR WLED-spezifische Geräte-Discovery Logik
|
||||||
KEINE Datenbank-Operationen!
|
KEINE Datenbank-Operationen!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import socket
|
import socket
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
from modules.base_module import BaseModule
|
from modules.base_module import BaseModule
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WLEDDiscovery:
|
class WLEDDiscovery:
|
||||||
"""Original WLEDDiscovery Klasse - unverändert"""
|
"""Original WLEDDiscovery Klasse - unverändert"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def discover_devices(timeout: int = 5) -> List[str]:
|
def discover_devices(timeout: int = 5) -> List[str]:
|
||||||
"""Sucht nach WLED-Geräten via mDNS"""
|
"""Sucht nach WLED-Geräten via mDNS"""
|
||||||
try:
|
try:
|
||||||
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
|
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
|
||||||
import time
|
import time
|
||||||
|
|
||||||
class WLEDListener(ServiceListener):
|
class WLEDListener(ServiceListener):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.devices = []
|
self.devices = []
|
||||||
|
|
||||||
def add_service(self, zc, type_, name):
|
def add_service(self, zc, type_, name):
|
||||||
info = zc.get_service_info(type_, name)
|
info = zc.get_service_info(type_, name)
|
||||||
if info:
|
if info:
|
||||||
addresses = [socket.inet_ntoa(addr) for addr in info.addresses]
|
addresses = [socket.inet_ntoa(addr) for addr in info.addresses]
|
||||||
for addr in addresses:
|
for addr in addresses:
|
||||||
if addr not in self.devices:
|
if addr not in self.devices:
|
||||||
self.devices.append(addr)
|
self.devices.append(addr)
|
||||||
logger.info(f"WLED-Gerät gefunden: {name} ({addr})")
|
logger.info(f"WLED-Gerät gefunden: {name} ({addr})")
|
||||||
|
|
||||||
def remove_service(self, zc, type_, name):
|
def remove_service(self, zc, type_, name):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def update_service(self, zc, type_, name):
|
def update_service(self, zc, type_, name):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
zeroconf = Zeroconf()
|
zeroconf = Zeroconf()
|
||||||
listener = WLEDListener()
|
listener = WLEDListener()
|
||||||
browser = ServiceBrowser(zeroconf, "_http._tcp.local.", listener)
|
browser = ServiceBrowser(zeroconf, "_http._tcp.local.", listener)
|
||||||
|
|
||||||
logger.info(f"Suche nach WLED-Geräten (Timeout: {timeout}s)...")
|
logger.info(f"Suche nach WLED-Geräten (Timeout: {timeout}s)...")
|
||||||
time.sleep(timeout)
|
time.sleep(timeout)
|
||||||
|
|
||||||
zeroconf.close()
|
zeroconf.close()
|
||||||
|
|
||||||
# Nur WLED-Geräte filtern
|
# Nur WLED-Geräte filtern
|
||||||
wled_devices = []
|
wled_devices = []
|
||||||
for ip in listener.devices:
|
for ip in listener.devices:
|
||||||
if WLEDDiscovery.is_wled_device(ip):
|
if WLEDDiscovery.is_wled_device(ip):
|
||||||
wled_devices.append(ip)
|
wled_devices.append(ip)
|
||||||
|
|
||||||
logger.info(f"{len(wled_devices)} WLED-Geräte gefunden")
|
logger.info(f"{len(wled_devices)} WLED-Geräte gefunden")
|
||||||
return wled_devices
|
return wled_devices
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.warning("zeroconf nicht installiert. Verwende Netzwerk-Scan...")
|
logger.warning("zeroconf nicht installiert. Verwende Netzwerk-Scan...")
|
||||||
return WLEDDiscovery.scan_network()
|
return WLEDDiscovery.scan_network()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler bei WLED-Discovery: {e}")
|
logger.error(f"Fehler bei WLED-Discovery: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def scan_network(network: str = None, max_threads: int = 50) -> List[str]:
|
def scan_network(network: str = None, max_threads: int = 50) -> List[str]:
|
||||||
"""Scannt das Netzwerk nach WLED-Geräten"""
|
"""Scannt das Netzwerk nach WLED-Geräten"""
|
||||||
if network is None:
|
if network is None:
|
||||||
try:
|
try:
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
s.connect(("8.8.8.8", 80))
|
s.connect(("8.8.8.8", 80))
|
||||||
local_ip = s.getsockname()[0]
|
local_ip = s.getsockname()[0]
|
||||||
s.close()
|
s.close()
|
||||||
network_prefix = '.'.join(local_ip.split('.')[:-1])
|
network_prefix = '.'.join(local_ip.split('.')[:-1])
|
||||||
except:
|
except:
|
||||||
logger.warning("Konnte lokale IP nicht ermitteln, verwende 192.168.1.x")
|
logger.warning("Konnte lokale IP nicht ermitteln, verwende 192.168.1.x")
|
||||||
network_prefix = "192.168.1"
|
network_prefix = "192.168.1"
|
||||||
else:
|
else:
|
||||||
network_prefix = '.'.join(network.split('.')[:3])
|
network_prefix = '.'.join(network.split('.')[:3])
|
||||||
|
|
||||||
logger.info(f"Scanne Netzwerk {network_prefix}.0/24 nach WLED-Geräten...")
|
logger.info(f"Scanne Netzwerk {network_prefix}.0/24 nach WLED-Geräten...")
|
||||||
|
|
||||||
def check_ip(ip):
|
def check_ip(ip):
|
||||||
if WLEDDiscovery.is_wled_device(ip):
|
if WLEDDiscovery.is_wled_device(ip):
|
||||||
return ip
|
return ip
|
||||||
return None
|
return None
|
||||||
|
|
||||||
wled_devices = []
|
wled_devices = []
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
|
||||||
futures = [executor.submit(check_ip, f"{network_prefix}.{i}")
|
futures = [executor.submit(check_ip, f"{network_prefix}.{i}")
|
||||||
for i in range(1, 255)]
|
for i in range(1, 255)]
|
||||||
|
|
||||||
for future in concurrent.futures.as_completed(futures):
|
for future in concurrent.futures.as_completed(futures):
|
||||||
result = future.result()
|
result = future.result()
|
||||||
if result:
|
if result:
|
||||||
wled_devices.append(result)
|
wled_devices.append(result)
|
||||||
logger.info(f"WLED-Gerät gefunden: {result}")
|
logger.info(f"WLED-Gerät gefunden: {result}")
|
||||||
|
|
||||||
return wled_devices
|
return wled_devices
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_wled_device(ip: str, timeout: float = 2.0) -> bool:
|
def is_wled_device(ip: str, timeout: float = 2.0) -> bool:
|
||||||
"""Prüft ob IP ein WLED-Gerät ist"""
|
"""Prüft ob IP ein WLED-Gerät ist"""
|
||||||
try:
|
try:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"http://{ip}/json/info",
|
f"http://{ip}/json/info",
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
headers={'User-Agent': 'DeviceDiscovery/1.0'}
|
headers={'User-Agent': 'DeviceDiscovery/1.0'}
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
return 'ver' in data or 'name' in data
|
return 'ver' in data or 'name' in data
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class WLEDAPI:
|
class WLEDAPI:
|
||||||
"""Original WLEDAPI Klasse - unverändert"""
|
"""Original WLEDAPI Klasse - unverändert"""
|
||||||
|
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.base_url = f"http://{ip}"
|
self.base_url = f"http://{ip}"
|
||||||
|
|
||||||
def get_info(self) -> Optional[Dict]:
|
def get_info(self) -> Optional[Dict]:
|
||||||
try:
|
try:
|
||||||
response = requests.get(f"{self.base_url}/json/info", timeout=2)
|
response = requests.get(f"{self.base_url}/json/info", timeout=2)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Abrufen der WLED-Info von {self.ip}: {e}")
|
logger.error(f"Fehler beim Abrufen der WLED-Info von {self.ip}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_presets(self) -> Optional[List]:
|
def get_presets(self) -> Optional[List]:
|
||||||
try:
|
try:
|
||||||
response = requests.get(f"{self.base_url}/presets.json", timeout=2)
|
response = requests.get(f"{self.base_url}/presets.json", timeout=2)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
presets_data = response.json()
|
presets_data = response.json()
|
||||||
preset_list = []
|
preset_list = []
|
||||||
if isinstance(presets_data, dict):
|
if isinstance(presets_data, dict):
|
||||||
for preset_id, preset_data in presets_data.items():
|
for preset_id, preset_data in presets_data.items():
|
||||||
preset_name = preset_data.get('n', f'Preset {preset_id}')
|
preset_name = preset_data.get('n', f'Preset {preset_id}')
|
||||||
preset_list.append({int(preset_id): preset_name})
|
preset_list.append({int(preset_id): preset_name})
|
||||||
return preset_list
|
return preset_list
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Abrufen der WLED-Presets von {self.ip}: {e}")
|
logger.error(f"Fehler beim Abrufen der WLED-Presets von {self.ip}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_effects(self) -> Optional[List]:
|
def get_effects(self) -> Optional[List]:
|
||||||
try:
|
try:
|
||||||
response = requests.get(f"{self.base_url}/json/eff", timeout=2)
|
response = requests.get(f"{self.base_url}/json/eff", timeout=2)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
eff_data = response.json()
|
eff_data = response.json()
|
||||||
eff_list = []
|
eff_list = []
|
||||||
for eff_id, eff_name in enumerate(eff_data):
|
for eff_id, eff_name in enumerate(eff_data):
|
||||||
if not eff_name:
|
if not eff_name:
|
||||||
eff_name = f"Effect {eff_id}"
|
eff_name = f"Effect {eff_id}"
|
||||||
eff_list.append({int(eff_id): eff_name})
|
eff_list.append({int(eff_id): eff_name})
|
||||||
return eff_list
|
return eff_list
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Abrufen der WLED-Effects von {self.ip}: {e}")
|
logger.error(f"Fehler beim Abrufen der WLED-Effects von {self.ip}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_state(self) -> Optional[Dict]:
|
def get_state(self) -> Optional[Dict]:
|
||||||
try:
|
try:
|
||||||
response = requests.get(f"{self.base_url}/json/state", timeout=2)
|
response = requests.get(f"{self.base_url}/json/state", timeout=2)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Abrufen des WLED-State von {self.ip}: {e}")
|
logger.error(f"Fehler beim Abrufen des WLED-State von {self.ip}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_device_data(self) -> Optional[Dict]:
|
def get_device_data(self) -> Optional[Dict]:
|
||||||
"""Erstellt Geräte-Dict im vereinheitlichten Format"""
|
"""Erstellt Geräte-Dict im vereinheitlichten Format"""
|
||||||
info = self.get_info()
|
info = self.get_info()
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
|
|
||||||
if not info:
|
if not info:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
name = info.get('name', f"WLED {self.ip}")
|
name = info.get('name', f"WLED {self.ip}")
|
||||||
preset_values = self.get_presets()
|
preset_values = self.get_presets()
|
||||||
eff_values = self.get_effects()
|
eff_values = self.get_effects()
|
||||||
|
|
||||||
commands = [
|
commands = [
|
||||||
{'command': 'An', 'url': '{"on":true}', 'parameters': []},
|
{'command': 'An', 'url': '{"on":true}', 'parameters': []},
|
||||||
{'command': 'Aus', 'url': '{"on":false}', 'parameters': []},
|
{'command': 'Aus', 'url': '{"on":false}', 'parameters': []},
|
||||||
{
|
{
|
||||||
'command': 'Helligkeit', 'url': '{"bri":%brightness%}',
|
'command': 'Helligkeit', 'url': '{"bri":%brightness%}',
|
||||||
'parameters': [
|
'parameters': [
|
||||||
{'name': 'brightness', 'type': 'integer', 'min': 0, 'max': 255}
|
{'name': 'brightness', 'type': 'integer', 'min': 0, 'max': 255}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'command': 'Farbe', 'url': '{"seg":[{"col":[[%red%,%green%,%blue%]]}]}',
|
'command': 'Farbe', 'url': '{"seg":[{"col":[[%red%,%green%,%blue%]]}]}',
|
||||||
'parameters': [
|
'parameters': [
|
||||||
{'name': 'red', 'type': 'integer', 'min': 0, 'max': 255},
|
{'name': 'red', 'type': 'integer', 'min': 0, 'max': 255},
|
||||||
{'name': 'green', 'type': 'integer', 'min': 0, 'max': 255},
|
{'name': 'green', 'type': 'integer', 'min': 0, 'max': 255},
|
||||||
{'name': 'blue', 'type': 'integer', 'min': 0, 'max': 255}
|
{'name': 'blue', 'type': 'integer', 'min': 0, 'max': 255}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'command': 'Effekt', 'url': '{"seg":[{"fx":%effect%}]}',
|
'command': 'Effekt', 'url': '{"seg":[{"fx":%effect%}]}',
|
||||||
'parameters': [
|
'parameters': [
|
||||||
{'name': 'effect', 'type': 'integer', 'min': 0, 'max': 255, 'values': eff_values}
|
{'name': 'effect', 'type': 'integer', 'min': 0, 'max': 255, 'values': eff_values}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'command': 'Preset', 'url': '{"ps":%preset%}',
|
'command': 'Preset', 'url': '{"ps":%preset%}',
|
||||||
'parameters': [
|
'parameters': [
|
||||||
{'name': 'preset', 'type': 'integer', 'min': 1, 'max': 250, 'values': preset_values}
|
{'name': 'preset', 'type': 'integer', 'min': 1, 'max': 250, 'values': preset_values}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
states = []
|
states = []
|
||||||
if state:
|
if state:
|
||||||
states.append({
|
states.append({
|
||||||
'name': 'An',
|
'name': 'An',
|
||||||
'url': 'on',
|
'url': 'on',
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
'current_value': state.get('on', False)
|
'current_value': state.get('on', False)
|
||||||
})
|
})
|
||||||
states.append({
|
states.append({
|
||||||
'name': 'Helligkeit',
|
'name': 'Helligkeit',
|
||||||
'url': 'bri',
|
'url': 'bri',
|
||||||
'type': 'integer',
|
'type': 'integer',
|
||||||
'current_value': state.get('bri', 0)
|
'current_value': state.get('bri', 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
segments = state.get('seg', [])
|
segments = state.get('seg', [])
|
||||||
if segments and len(segments) > 0:
|
if segments and len(segments) > 0:
|
||||||
colors = segments[0].get('col', [[0,0,0]])
|
colors = segments[0].get('col', [[0,0,0]])
|
||||||
if colors and len(colors) > 0:
|
if colors and len(colors) > 0:
|
||||||
states.append({
|
states.append({
|
||||||
'name': 'Farbe',
|
'name': 'Farbe',
|
||||||
'url': 'seg[0].col[0]',
|
'url': 'seg[0].col[0]',
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'current_value': colors[0]
|
'current_value': colors[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'type': 'WLED',
|
'type': 'WLED',
|
||||||
'name': name,
|
'name': name,
|
||||||
'url': f"wled://{self.ip}",
|
'url': f"wled://{self.ip}",
|
||||||
'commands': commands,
|
'commands': commands,
|
||||||
'states': states
|
'states': states
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class WLEDModule(BaseModule):
|
class WLEDModule(BaseModule):
|
||||||
"""
|
"""
|
||||||
WLED Modul - Implementiert BaseModule Interface
|
WLED Modul - Implementiert BaseModule Interface
|
||||||
Gibt nur Actors zurück, KEINE DB-Operationen
|
Gibt nur Actors zurück, KEINE DB-Operationen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
"""Prüft ob WLED aktiviert ist"""
|
"""Prüft ob WLED aktiviert ist"""
|
||||||
return self.config.wled_enable
|
return self.config.wled_enable
|
||||||
|
|
||||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||||
"""
|
"""
|
||||||
Führt WLED Discovery durch
|
Führt WLED Discovery durch
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (actors, sensors) - WLED sind immer Actors
|
Tuple (actors, sensors) - WLED sind immer Actors
|
||||||
"""
|
"""
|
||||||
logger.info("\n" + "=" * 60)
|
logger.info("\n" + "=" * 60)
|
||||||
logger.info("WLED-GERÄTE WERDEN GESUCHT")
|
logger.info("WLED-GERÄTE WERDEN GESUCHT")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
actors = []
|
actors = []
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
# Discovery
|
# Discovery
|
||||||
wled_ips = WLEDDiscovery.discover_devices(timeout=self.config.wled_discovery_timeout)
|
wled_ips = WLEDDiscovery.discover_devices(timeout=self.config.wled_discovery_timeout)
|
||||||
|
|
||||||
# Manuelle IPs hinzufügen
|
# Manuelle IPs hinzufügen
|
||||||
if self.config.wled_manual_ips:
|
if self.config.wled_manual_ips:
|
||||||
logger.info(f"Füge {len(self.config.wled_manual_ips)} manuelle WLED-IPs hinzu...")
|
logger.info(f"Füge {len(self.config.wled_manual_ips)} manuelle WLED-IPs hinzu...")
|
||||||
for manual_ip in self.config.wled_manual_ips:
|
for manual_ip in self.config.wled_manual_ips:
|
||||||
if manual_ip not in wled_ips:
|
if manual_ip not in wled_ips:
|
||||||
if WLEDDiscovery.is_wled_device(manual_ip):
|
if WLEDDiscovery.is_wled_device(manual_ip):
|
||||||
wled_ips.append(manual_ip)
|
wled_ips.append(manual_ip)
|
||||||
logger.info(f"✓ Manuelles WLED-Gerät: {manual_ip}")
|
logger.info(f"✓ Manuelles WLED-Gerät: {manual_ip}")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠ {manual_ip} ist kein WLED-Gerät")
|
logger.warning(f"⚠ {manual_ip} ist kein WLED-Gerät")
|
||||||
|
|
||||||
if not wled_ips:
|
if not wled_ips:
|
||||||
logger.info("Keine WLED-Geräte gefunden")
|
logger.info("Keine WLED-Geräte gefunden")
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|
||||||
logger.info(f"{len(wled_ips)} WLED-Geräte gefunden")
|
logger.info(f"{len(wled_ips)} WLED-Geräte gefunden")
|
||||||
|
|
||||||
# Gerätedaten abrufen
|
# Gerätedaten abrufen
|
||||||
for ip in wled_ips:
|
for ip in wled_ips:
|
||||||
try:
|
try:
|
||||||
wled = WLEDAPI(ip)
|
wled = WLEDAPI(ip)
|
||||||
device_data = wled.get_device_data()
|
device_data = wled.get_device_data()
|
||||||
|
|
||||||
if device_data:
|
if device_data:
|
||||||
actors.append(device_data)
|
actors.append(device_data)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠ Konnte keine Daten von WLED {ip} abrufen")
|
logger.warning(f"⚠ Konnte keine Daten von WLED {ip} abrufen")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"✗ Fehler beim Verarbeiten von WLED {ip}: {e}")
|
logger.error(f"✗ Fehler beim Verarbeiten von WLED {ip}: {e}")
|
||||||
|
|
||||||
logger.info(f"WLED: {len(actors)} Aktoren gefunden")
|
logger.info(f"WLED: {len(actors)} Aktoren gefunden")
|
||||||
return actors, sensors
|
return actors, sensors
|
||||||
|
|||||||
+207
-207
@@ -1,208 +1,208 @@
|
|||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<!--begin::Head-->
|
<!--begin::Head-->
|
||||||
<?php
|
<?php
|
||||||
// Die Raumtabelle traegt die Etagen - das Menue baut daraus seine Punkte.
|
// Die Raumtabelle traegt die Etagen - das Menue baut daraus seine Punkte.
|
||||||
// Hier und nicht erst in home.php: der Kopf wird zuerst eingebunden.
|
// Hier und nicht erst in home.php: der Kopf wird zuerst eingebunden.
|
||||||
require_once(__DIR__ . "/rooms.php");
|
require_once(__DIR__ . "/rooms.php");
|
||||||
|
|
||||||
// $_GET["action"] ist von index.php bereits auf eine gueltige Seite gesetzt.
|
// $_GET["action"] ist von index.php bereits auf eine gueltige Seite gesetzt.
|
||||||
if (!isset($_GET["floor"]))
|
if (!isset($_GET["floor"]))
|
||||||
$_GET["floor"] = "OG";
|
$_GET["floor"] = "OG";
|
||||||
?>
|
?>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<title>Smarthome control</title>
|
<title>Smarthome control</title>
|
||||||
<!--begin::Accessibility Meta Tags-->
|
<!--begin::Accessibility Meta Tags-->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
<meta name="theme-color" content="#007bff" media="(prefers-color-scheme: light)" />
|
<meta name="theme-color" content="#007bff" media="(prefers-color-scheme: light)" />
|
||||||
<meta name="theme-color" content="#1a1a1a" media="(prefers-color-scheme: dark)" />
|
<meta name="theme-color" content="#1a1a1a" media="(prefers-color-scheme: dark)" />
|
||||||
<link rel="icon" type="image/png" href="assets/img/favicon.png">
|
<link rel="icon" type="image/png" href="assets/img/favicon.png">
|
||||||
<!--end::Accessibility Meta Tags-->
|
<!--end::Accessibility Meta Tags-->
|
||||||
|
|
||||||
<!--begin::Primary Meta Tags-->
|
<!--begin::Primary Meta Tags-->
|
||||||
<meta name="title" content="Smarthome control" />
|
<meta name="title" content="Smarthome control" />
|
||||||
<meta name="author" content="ColorlibHQ" />
|
<meta name="author" content="ColorlibHQ" />
|
||||||
<meta name="description" content="Smarthome control panel by m0." />
|
<meta name="description" content="Smarthome control panel by m0." />
|
||||||
<meta name="keywords" content="smarthome dashboard, admin panel" />
|
<meta name="keywords" content="smarthome dashboard, admin panel" />
|
||||||
<!--end::Primary Meta Tags-->
|
<!--end::Primary Meta Tags-->
|
||||||
|
|
||||||
<!--begin::Accessibility Features-->
|
<!--begin::Accessibility Features-->
|
||||||
<!-- Skip links will be dynamically added by accessibility.js -->
|
<!-- Skip links will be dynamically added by accessibility.js -->
|
||||||
<meta name="supported-color-schemes" content="light dark" />
|
<meta name="supported-color-schemes" content="light dark" />
|
||||||
<link rel="preload" href="./css/adminlte.min.css?v=2" as="style" />
|
<link rel="preload" href="./css/adminlte.min.css?v=2" as="style" />
|
||||||
<link rel="stylesheet" href="./css/solar.css?v=<?= filemtime("css/solar.css") ?>" />
|
<link rel="stylesheet" href="./css/solar.css?v=<?= filemtime("css/solar.css") ?>" />
|
||||||
<!--end::Accessibility Features-->
|
<!--end::Accessibility Features-->
|
||||||
|
|
||||||
<!--begin::Fonts-->
|
<!--begin::Fonts-->
|
||||||
<link rel="stylesheet" href="assets/fonts/font_poppins.css" media="print" onload="this.media='all'" />
|
<link rel="stylesheet" href="assets/fonts/font_poppins.css" media="print" onload="this.media='all'" />
|
||||||
<!--end::Fonts-->
|
<!--end::Fonts-->
|
||||||
|
|
||||||
<!--begin::Third Party Plugin(Bootstrap Icons)-->
|
<!--begin::Third Party Plugin(Bootstrap Icons)-->
|
||||||
<link rel="stylesheet" href="css/bootstrap-icons.min.css" />
|
<link rel="stylesheet" href="css/bootstrap-icons.min.css" />
|
||||||
<!--end::Third Party Plugin(Bootstrap Icons)-->
|
<!--end::Third Party Plugin(Bootstrap Icons)-->
|
||||||
|
|
||||||
<!--begin::Required Plugin(AdminLTE)-->
|
<!--begin::Required Plugin(AdminLTE)-->
|
||||||
<link rel="stylesheet" href="./css/adminlte.min.css?v=2" />
|
<link rel="stylesheet" href="./css/adminlte.min.css?v=2" />
|
||||||
<!--end::Required Plugin(AdminLTE)-->
|
<!--end::Required Plugin(AdminLTE)-->
|
||||||
|
|
||||||
<!--begin::Karte (Leaflet) - nur auf Seiten mit Karte-->
|
<!--begin::Karte (Leaflet) - nur auf Seiten mit Karte-->
|
||||||
<?php if ($page["leaflet"]): ?>
|
<?php if ($page["leaflet"]): ?>
|
||||||
<link rel="stylesheet" href="css/leaflet.css" />
|
<link rel="stylesheet" href="css/leaflet.css" />
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<!--end::Karte-->
|
<!--end::Karte-->
|
||||||
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<!--end::Head-->
|
<!--end::Head-->
|
||||||
<!--begin::Body-->
|
<!--begin::Body-->
|
||||||
|
|
||||||
<body class="layout-fixed sidebar-expand-lg bg-body-tertiary" data-bs-theme="dark">
|
<body class="layout-fixed sidebar-expand-lg bg-body-tertiary" data-bs-theme="dark">
|
||||||
<!--begin::App Wrapper-->
|
<!--begin::App Wrapper-->
|
||||||
<div class="app-wrapper">
|
<div class="app-wrapper">
|
||||||
<!--begin::Header-->
|
<!--begin::Header-->
|
||||||
<nav class="app-header navbar navbar-expand bg-body">
|
<nav class="app-header navbar navbar-expand bg-body">
|
||||||
<!--begin::Container-->
|
<!--begin::Container-->
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!--begin::Start Navbar Links-->
|
<!--begin::Start Navbar Links-->
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" data-lte-toggle="sidebar" href="#" role="button">
|
<a class="nav-link" data-lte-toggle="sidebar" href="#" role="button">
|
||||||
<i class="bi bi-list"></i>
|
<i class="bi bi-list"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<!--end::Start Navbar Links-->
|
<!--end::Start Navbar Links-->
|
||||||
|
|
||||||
<!--begin::End Navbar Links-->
|
<!--begin::End Navbar Links-->
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
<!--begin::Fullscreen Toggle-->
|
<!--begin::Fullscreen Toggle-->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#" data-lte-toggle="fullscreen">
|
<a class="nav-link" href="#" data-lte-toggle="fullscreen">
|
||||||
<i data-lte-icon="maximize" class="bi bi-arrows-fullscreen"></i>
|
<i data-lte-icon="maximize" class="bi bi-arrows-fullscreen"></i>
|
||||||
<i data-lte-icon="minimize" class="bi bi-fullscreen-exit" style="display: none"></i>
|
<i data-lte-icon="minimize" class="bi bi-fullscreen-exit" style="display: none"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php
|
<?php
|
||||||
if ($_SESSION["local"] == true)
|
if ($_SESSION["local"] == true)
|
||||||
echo "<li class='nav-item'><a class='nav-link' href='addUser.php'><i data-lte-icon='Add user' class='bi bi-person-add'></i></a></li>";
|
echo "<li class='nav-item'><a class='nav-link' href='addUser.php'><i data-lte-icon='Add user' class='bi bi-person-add'></i></a></li>";
|
||||||
?>
|
?>
|
||||||
<!--end::Fullscreen Toggle-->
|
<!--end::Fullscreen Toggle-->
|
||||||
</ul>
|
</ul>
|
||||||
<!--end::End Navbar Links-->
|
<!--end::End Navbar Links-->
|
||||||
</div>
|
</div>
|
||||||
<!--end::Container-->
|
<!--end::Container-->
|
||||||
</nav>
|
</nav>
|
||||||
<!--end::Header-->
|
<!--end::Header-->
|
||||||
<!--begin::Sidebar-->
|
<!--begin::Sidebar-->
|
||||||
<aside class="app-sidebar bg-body-secondary shadow">
|
<aside class="app-sidebar bg-body-secondary shadow">
|
||||||
<!--begin::Sidebar Brand-->
|
<!--begin::Sidebar Brand-->
|
||||||
<div class="sidebar-brand">
|
<div class="sidebar-brand">
|
||||||
<!--begin::Brand Link-->
|
<!--begin::Brand Link-->
|
||||||
<a href="./index.php" class="brand-link">
|
<a href="./index.php" class="brand-link">
|
||||||
<!--begin::Brand Image-->
|
<!--begin::Brand Image-->
|
||||||
<img src="./assets/img/AdminLTELogo.png" alt="Logo"
|
<img src="./assets/img/AdminLTELogo.png" alt="Logo"
|
||||||
class="brand-image opacity-75 shadow" />
|
class="brand-image opacity-75 shadow" />
|
||||||
<!--end::Brand Image-->
|
<!--end::Brand Image-->
|
||||||
<!--begin::Brand Text-->
|
<!--begin::Brand Text-->
|
||||||
<span class="brand-text fw-light">Smart controller</span>
|
<span class="brand-text fw-light">Smart controller</span>
|
||||||
<!--end::Brand Text-->
|
<!--end::Brand Text-->
|
||||||
</a>
|
</a>
|
||||||
<!--end::Brand Link-->
|
<!--end::Brand Link-->
|
||||||
</div>
|
</div>
|
||||||
<!--end::Sidebar Brand-->
|
<!--end::Sidebar Brand-->
|
||||||
<!--begin::Sidebar Wrapper-->
|
<!--begin::Sidebar Wrapper-->
|
||||||
<div class="sidebar-wrapper">
|
<div class="sidebar-wrapper">
|
||||||
<nav class="mt-2">
|
<nav class="mt-2">
|
||||||
<!--begin::Sidebar Menu-->
|
<!--begin::Sidebar Menu-->
|
||||||
<ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" role="navigation"
|
<ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" role="navigation"
|
||||||
aria-label="Main navigation" data-accordion="false" id="navigation">
|
aria-label="Main navigation" data-accordion="false" id="navigation">
|
||||||
<li class="nav-item menu-open">
|
<li class="nav-item menu-open">
|
||||||
<a href="#" class="nav-link active">
|
<a href="#" class="nav-link active">
|
||||||
<i class="nav-icon bi bi-speedometer"></i>
|
<i class="nav-icon bi bi-speedometer"></i>
|
||||||
<p>
|
<p>
|
||||||
Dashboards
|
Dashboards
|
||||||
<i class="nav-arrow bi bi-chevron-right"></i>
|
<i class="nav-arrow bi bi-chevron-right"></i>
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
<ul class="nav nav-treeview ps-4">
|
<ul class="nav nav-treeview ps-4">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=solar" class="nav-link<?php if($_GET["action"]=="solar"){echo " active";} ?>">
|
<a href="?action=solar" class="nav-link<?php if($_GET["action"]=="solar"){echo " active";} ?>">
|
||||||
<i class="nav-icon bi bi-brightness-alt-high"></i>
|
<i class="nav-icon bi bi-brightness-alt-high"></i>
|
||||||
<p>Solar</p>
|
<p>Solar</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=heat" class="nav-link <?php if($_GET["action"]=="heat"){echo " active";} ?>">
|
<a href="?action=heat" class="nav-link <?php if($_GET["action"]=="heat"){echo " active";} ?>">
|
||||||
<i class="nav-icon bi bi-fire"></i>
|
<i class="nav-icon bi bi-fire"></i>
|
||||||
<p>Heizung</p>
|
<p>Heizung</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=history" class="nav-link <?php if($_GET["action"]=="history"){echo " active";} ?>">
|
<a href="?action=history" class="nav-link <?php if($_GET["action"]=="history"){echo " active";} ?>">
|
||||||
<i class="nav-icon bi bi-graph-up"></i>
|
<i class="nav-icon bi bi-graph-up"></i>
|
||||||
<p>Historie</p>
|
<p>Historie</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=weather" class="nav-link <?php if($_GET["action"]=="weather"){echo " active";} ?>">
|
<a href="?action=weather" class="nav-link <?php if($_GET["action"]=="weather"){echo " active";} ?>">
|
||||||
<i class="nav-icon bi bi-cloud-sun"></i>
|
<i class="nav-icon bi bi-cloud-sun"></i>
|
||||||
<p>Wetter</p>
|
<p>Wetter</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=skoda" class="nav-link <?php if($_GET["action"]=="skoda"){echo " active";} ?>">
|
<a href="?action=skoda" class="nav-link <?php if($_GET["action"]=="skoda"){echo " active";} ?>">
|
||||||
<i class="nav-icon bi bi-ev-front"></i>
|
<i class="nav-icon bi bi-ev-front"></i>
|
||||||
<p>Škoda</p>
|
<p>Škoda</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item menu-open">
|
<li class="nav-item menu-open">
|
||||||
<a href="#" class="nav-link active">
|
<a href="#" class="nav-link active">
|
||||||
<i class="nav-icon bi bi-house"></i>
|
<i class="nav-icon bi bi-house"></i>
|
||||||
<p>
|
<p>
|
||||||
Home
|
Home
|
||||||
<i class="nav-arrow bi bi-chevron-right"></i>
|
<i class="nav-arrow bi bi-chevron-right"></i>
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
<!-- Nur Etagen mit Grundriss: ein Menuepunkt auf das
|
<!-- Nur Etagen mit Grundriss: ein Menuepunkt auf das
|
||||||
Aussengelaende fuehrte auf eine leere Zeichenflaeche.
|
Aussengelaende fuehrte auf eine leere Zeichenflaeche.
|
||||||
Bekommt dessen erster Raum eine Kachel, steht es hier
|
Bekommt dessen erster Raum eine Kachel, steht es hier
|
||||||
von selbst. -->
|
von selbst. -->
|
||||||
<ul class="nav nav-treeview ps-4">
|
<ul class="nav nav-treeview ps-4">
|
||||||
<?php foreach (floorsWithPlan() as $etage): ?>
|
<?php foreach (floorsWithPlan() as $etage): ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=home&floor=<?= $etage ?>" title="<?= htmlspecialchars(floorLabel($etage)) ?>"
|
<a href="?action=home&floor=<?= $etage ?>" title="<?= htmlspecialchars(floorLabel($etage)) ?>"
|
||||||
class="nav-link <?php if($_GET["action"]=="home" && $_GET["floor"] == $etage){echo " active";} ?>">
|
class="nav-link <?php if($_GET["action"]=="home" && $_GET["floor"] == $etage){echo " active";} ?>">
|
||||||
<p> <?= $etage ?></p>
|
<p> <?= $etage ?></p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<!-- Einstellungen stehen fuer sich: kein Dashboard,
|
<!-- Einstellungen stehen fuer sich: kein Dashboard,
|
||||||
sondern die Parameter dahinter. Genauso die
|
sondern die Parameter dahinter. Genauso die
|
||||||
Protokolle - sie zeigen nicht das Haus, sondern
|
Protokolle - sie zeigen nicht das Haus, sondern
|
||||||
die Prozesse, die es steuern. -->
|
die Prozesse, die es steuern. -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=settings" class="nav-link<?php if($_GET["action"]=="settings"){echo " active";} ?>">
|
<a href="?action=settings" class="nav-link<?php if($_GET["action"]=="settings"){echo " active";} ?>">
|
||||||
<i class="nav-icon bi bi-gear"></i>
|
<i class="nav-icon bi bi-gear"></i>
|
||||||
<p>Einstellungen</p>
|
<p>Einstellungen</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="?action=logs" class="nav-link<?php if($_GET["action"]=="logs"){echo " active";} ?>">
|
<a href="?action=logs" class="nav-link<?php if($_GET["action"]=="logs"){echo " active";} ?>">
|
||||||
<i class="nav-icon bi bi-journal-text"></i>
|
<i class="nav-icon bi bi-journal-text"></i>
|
||||||
<p>Protokolle</p>
|
<p>Protokolle</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<!--end::Sidebar Menu-->
|
<!--end::Sidebar Menu-->
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<!--end::Sidebar Wrapper-->
|
<!--end::Sidebar Wrapper-->
|
||||||
</aside>
|
</aside>
|
||||||
<!--end::Sidebar-->
|
<!--end::Sidebar-->
|
||||||
+1572
-1572
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+235
-235
@@ -1,235 +1,235 @@
|
|||||||
<?php require_once(__DIR__ . "/rooms.php"); ?>
|
<?php require_once(__DIR__ . "/rooms.php"); ?>
|
||||||
<!--begin::App Main-->
|
<!--begin::App Main-->
|
||||||
<main class="app-main">
|
<main class="app-main">
|
||||||
<!--begin::App Content Header-->
|
<!--begin::App Content Header-->
|
||||||
<div class="app-content-header">
|
<div class="app-content-header">
|
||||||
<!--begin::Container-->
|
<!--begin::Container-->
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!--begin::Row-->
|
<!--begin::Row-->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h3 class="mb-0">Home</h3>
|
<h3 class="mb-0">Home</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--end::Row-->
|
<!--end::Row-->
|
||||||
</div>
|
</div>
|
||||||
<!--end::Container-->
|
<!--end::Container-->
|
||||||
</div>
|
</div>
|
||||||
<!--end::App Content Header-->
|
<!--end::App Content Header-->
|
||||||
<!--begin::App Content-->
|
<!--begin::App Content-->
|
||||||
<div class="app-content">
|
<div class="app-content">
|
||||||
<!--begin::Container-->
|
<!--begin::Container-->
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!--begin::Row-->
|
<!--begin::Row-->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Start col -->
|
<!-- Start col -->
|
||||||
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
<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 p-0 mb-4" style="width:100%">
|
||||||
<div class="card-header pb-0 pt-1">
|
<div class="card-header pb-0 pt-1">
|
||||||
<h3 class="card-title">Home</h3>
|
<h3 class="card-title">Home</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
||||||
<i class="bi bi-x-lg"></i>
|
<i class="bi bi-x-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body pb-1">
|
<div class="card-body pb-1">
|
||||||
<div lass="img-overlay-wrap">
|
<div lass="img-overlay-wrap">
|
||||||
<svg viewBox="0 0 400 300" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 400 300" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
<defs>
|
<defs>
|
||||||
<g id="floorBtn">
|
<g id="floorBtn">
|
||||||
<path fill="transparent" stroke-width="4"
|
<path fill="transparent" stroke-width="4"
|
||||||
d="M 99.86386438745993 22.00011880076356 A 50 50 0 1 0 100 22" />
|
d="M 99.86386438745993 22.00011880076356 A 50 50 0 1 0 100 22" />
|
||||||
</g>
|
</g>
|
||||||
<g id="heater" filter="url(#shadow)" transform="translate(1.5,1),scale(0.2,0.2)" fill="#993311" stroke="#FF2211">
|
<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"
|
<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" />
|
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>
|
||||||
<g id="buffer" filter="url(#shadow)" transform="translate(18,1),scale(0.3,0.3)" fill="#ffffff">
|
<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="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"/>
|
<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>
|
</g>
|
||||||
</defs>
|
</defs>
|
||||||
<g>
|
<g>
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* Ein Bild je Etage, dazu fuer jede Etage eine Blende.
|
* Ein Bild je Etage, dazu fuer jede Etage eine Blende.
|
||||||
* Ausgeschrieben waren das bei drei Etagen neun Zeilen
|
* Ausgeschrieben waren das bei drei Etagen neun Zeilen
|
||||||
* und bei vier schon sechzehn - und jede neue Etage
|
* und bei vier schon sechzehn - und jede neue Etage
|
||||||
* haette alle anderen mit angefasst.
|
* haette alle anderen mit angefasst.
|
||||||
*
|
*
|
||||||
* floorsWithPlan() liefert nur Etagen, deren Raeume
|
* floorsWithPlan() liefert nur Etagen, deren Raeume
|
||||||
* Koordinaten haben. Eine Etage ohne angeklickte
|
* Koordinaten haben. Eine Etage ohne angeklickte
|
||||||
* Kachelpositionen taucht hier also gar nicht erst auf.
|
* Kachelpositionen taucht hier also gar nicht erst auf.
|
||||||
*/
|
*/
|
||||||
$etagen = floorsWithPlan();
|
$etagen = floorsWithPlan();
|
||||||
?>
|
?>
|
||||||
<?php /* Von unten nach oben gestapelt, wie bisher: waehrend der Blende
|
<?php /* Von unten nach oben gestapelt, wie bisher: waehrend der Blende
|
||||||
sind zwei Bilder gleichzeitig halb sichtbar, und dann soll das
|
sind zwei Bilder gleichzeitig halb sichtbar, und dann soll das
|
||||||
obere Geschoss obenauf liegen. */ ?>
|
obere Geschoss obenauf liegen. */ ?>
|
||||||
<?php foreach (array_reverse($etagen) as $etage): ?>
|
<?php foreach (array_reverse($etagen) as $etage): ?>
|
||||||
<g id="<?= $etage ?>img" <?php if ($_GET["floor"] != $etage) echo "opacity='0'"; ?> >
|
<g id="<?= $etage ?>img" <?php if ($_GET["floor"] != $etage) echo "opacity='0'"; ?> >
|
||||||
<?php foreach ($etagen as $andere): ?>
|
<?php foreach ($etagen as $andere): ?>
|
||||||
<animate begin="<?= $andere ?>.click" attributetype="CSS" attributeName="opacity"
|
<animate begin="<?= $andere ?>.click" attributetype="CSS" attributeName="opacity"
|
||||||
to=<?= $andere === $etage ? "1.0" : "0.0" ?> dur="0.7s"
|
to=<?= $andere === $etage ? "1.0" : "0.0" ?> dur="0.7s"
|
||||||
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<image href="assets/img/<?= $etage ?>.png" height="100%" width="100%" x=0 y=0 />
|
<image href="assets/img/<?= $etage ?>.png" height="100%" width="100%" x=0 y=0 />
|
||||||
</g>
|
</g>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* Der Kreis ist 50 hoch und sitzt 11 unter der Oberkante
|
* Der Kreis ist 50 hoch und sitzt 11 unter der Oberkante
|
||||||
* seiner Gruppe. Bei drei Etagen bleibt es bei den
|
* seiner Gruppe. Bei drei Etagen bleibt es bei den
|
||||||
* gewohnten 100 Einheiten Abstand; ab der vierten ruecken
|
* gewohnten 100 Einheiten Abstand; ab der vierten ruecken
|
||||||
* sie zusammen, sonst faellt die unterste aus der 300
|
* sie zusammen, sonst faellt die unterste aus der 300
|
||||||
* hohen viewBox heraus.
|
* hohen viewBox heraus.
|
||||||
*/
|
*/
|
||||||
$abstand = count($etagen) > 1
|
$abstand = count($etagen) > 1
|
||||||
? min(100, (295 - 61) / (count($etagen) - 1))
|
? min(100, (295 - 61) / (count($etagen) - 1))
|
||||||
: 0;
|
: 0;
|
||||||
// Von Hand mittig gerueckt, je nach Breite der Buchstaben.
|
// Von Hand mittig gerueckt, je nach Breite der Buchstaben.
|
||||||
$textX = ["OG" => 62, "EG" => 64, "UG" => 63, "AG" => 62];
|
$textX = ["OG" => 62, "EG" => 64, "UG" => 63, "AG" => 62];
|
||||||
?>
|
?>
|
||||||
<?php foreach ($etagen as $i => $etage): ?>
|
<?php foreach ($etagen as $i => $etage): ?>
|
||||||
<g id="<?= $etage ?>" onclick="switchFloor('<?= $etage ?>')" cursor="pointer"
|
<g id="<?= $etage ?>" onclick="switchFloor('<?= $etage ?>')" cursor="pointer"
|
||||||
transform="translate(0,<?= round($i * $abstand, 1) ?>), scale(0.50,0.50)">
|
transform="translate(0,<?= round($i * $abstand, 1) ?>), scale(0.50,0.50)">
|
||||||
<animate id="show<?= $etage ?>" begin=click attributetype="CSS" attributeName="opacity"
|
<animate id="show<?= $etage ?>" begin=click attributetype="CSS" attributeName="opacity"
|
||||||
from=0 to=1 dur="0.1s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
from=0 to=1 dur="0.1s" repeatCount="1" calcMode="spline" keyTimes="0;1"
|
||||||
keySplines=".1,0,.27,1" fill="freeze"></animate>
|
keySplines=".1,0,.27,1" fill="freeze"></animate>
|
||||||
<use href="#floorBtn" stroke="#aa7713" />
|
<use href="#floorBtn" stroke="#aa7713" />
|
||||||
<text fill="#aa7713" style="font: 3em sans-serif;"
|
<text fill="#aa7713" style="font: 3em sans-serif;"
|
||||||
transform="translate(<?= $textX[$etage] ?? 62 ?>,90)"><?= $etage ?></text>
|
transform="translate(<?= $textX[$etage] ?? 62 ?>,90)"><?= $etage ?></text>
|
||||||
</g>
|
</g>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php foreach (floorsWithPlan() as $floor): ?>
|
<?php foreach (floorsWithPlan() as $floor): ?>
|
||||||
<g id="<?= $floor ?>_Info" <?php if ($floor != "OG") echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
|
<g id="<?= $floor ?>_Info" <?php if ($floor != "OG") echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
|
||||||
<?php foreach (array_map("mitWerten", roomsOnFloor($floor)) as $room): ?>
|
<?php foreach (array_map("mitWerten", roomsOnFloor($floor)) as $room): ?>
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* Grundlinien je nach Anzahl der Werte. Die Kachel ist 19 hoch; mit
|
* Grundlinien je nach Anzahl der Werte. Die Kachel ist 19 hoch; mit
|
||||||
* drei Werten sind es dieselben Zeilen wie eh und je (klein, gross,
|
* drei Werten sind es dieselben Zeilen wie eh und je (klein, gross,
|
||||||
* klein), mit weniger ruecken sie in die Mitte.
|
* klein), mit weniger ruecken sie in die Mitte.
|
||||||
*/
|
*/
|
||||||
$zeilen = [1 => [12], 2 => [9, 16], 3 => [5, 11.5, 16.5]];
|
$zeilen = [1 => [12], 2 => [9, 16], 3 => [5, 11.5, 16.5]];
|
||||||
$y = $zeilen[count($room["werte"])] ?? [];
|
$y = $zeilen[count($room["werte"])] ?? [];
|
||||||
?>
|
?>
|
||||||
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)" onclick="openHeaterSettings('<?= $floor ?>_<?= $room["mqtt"] ?>');" cursor="pointer">
|
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)" onclick="openHeaterSettings('<?= $floor ?>_<?= $room["mqtt"] ?>');" cursor="pointer">
|
||||||
<rect width="24" height="19" rx="5" fill="#0006" />
|
<rect width="24" height="19" rx="5" fill="#0006" />
|
||||||
<?php if ($room["heizung"]): ?>
|
<?php if ($room["heizung"]): ?>
|
||||||
<use id="<?= $floor ?>_<?= $room["id"] ?>_heater" href="#heater" display="none" />
|
<use id="<?= $floor ?>_<?= $room["id"] ?>_heater" href="#heater" display="none" />
|
||||||
<use id="<?= $floor ?>_<?= $room["id"] ?>_buffer" href="#buffer" display="none" />
|
<use id="<?= $floor ?>_<?= $room["id"] ?>_buffer" href="#buffer" display="none" />
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<text fill="#fff" style="font: 4px sans-serif;">
|
<text fill="#fff" style="font: 4px sans-serif;">
|
||||||
<?php if (!$room["werte"]): ?>
|
<?php if (!$room["werte"]): ?>
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* Kein Messwert: die Kachel traegt ihren Namen und oeffnet den Raum.
|
* Kein Messwert: die Kachel traegt ihren Namen und oeffnet den Raum.
|
||||||
* Ein leeres Rechteck saehe aus wie ein Fehler.
|
* Ein leeres Rechteck saehe aus wie ein Fehler.
|
||||||
*
|
*
|
||||||
* Lange Namen brechen am letzten Leerzeichen um. Die Kachel ist 24
|
* Lange Namen brechen am letzten Leerzeichen um. Die Kachel ist 24
|
||||||
* Einheiten breit, und "Terrasse OG" stand einzeilig links und rechts
|
* Einheiten breit, und "Terrasse OG" stand einzeilig links und rechts
|
||||||
* darueber hinaus. Aufgefallen ist das erst mit dem Aussengelaende:
|
* darueber hinaus. Aufgefallen ist das erst mit dem Aussengelaende:
|
||||||
* dort besteht jeder Raumname aus zwei Woertern.
|
* dort besteht jeder Raumname aus zwei Woertern.
|
||||||
*/
|
*/
|
||||||
$luecke = strrpos($room["mqtt"], " ");
|
$luecke = strrpos($room["mqtt"], " ");
|
||||||
$teile = (strlen($room["mqtt"]) > 8 && $luecke !== false)
|
$teile = (strlen($room["mqtt"]) > 8 && $luecke !== false)
|
||||||
? [substr($room["mqtt"], 0, $luecke), substr($room["mqtt"], $luecke + 1)]
|
? [substr($room["mqtt"], 0, $luecke), substr($room["mqtt"], $luecke + 1)]
|
||||||
: [$room["mqtt"]];
|
: [$room["mqtt"]];
|
||||||
$namenszeilen = count($teile) === 2 ? [9, 15.5] : [11.5];
|
$namenszeilen = count($teile) === 2 ? [9, 15.5] : [11.5];
|
||||||
?>
|
?>
|
||||||
<?php foreach ($teile as $i => $teil): ?>
|
<?php foreach ($teile as $i => $teil): ?>
|
||||||
<tspan x="12" y="<?= $namenszeilen[$i] ?>" font-size="1.2em" text-anchor="middle"><?= htmlspecialchars($teil) ?></tspan>
|
<tspan x="12" y="<?= $namenszeilen[$i] ?>" font-size="1.2em" text-anchor="middle"><?= htmlspecialchars($teil) ?></tspan>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php foreach ($room["werte"] as $i => $wert): ?>
|
<?php foreach ($room["werte"] as $i => $wert): ?>
|
||||||
<!-- Der Platzhalter ist nur ein Strich: bevor der
|
<!-- Der Platzhalter ist nur ein Strich: bevor der
|
||||||
erste Wert da ist, soll hier keine Einheit
|
erste Wert da ist, soll hier keine Einheit
|
||||||
stehen, die vielleicht gar nicht passt. -->
|
stehen, die vielleicht gar nicht passt. -->
|
||||||
<tspan id="<?= $floor ?>_w<?= $i ?>_<?= $room["id"] ?>" x="12" y="<?= $y[$i] ?>"
|
<tspan id="<?= $floor ?>_w<?= $i ?>_<?= $room["id"] ?>" x="12" y="<?= $y[$i] ?>"
|
||||||
<?= empty($wert["gross"]) ? "" : 'font-size="1.6em"' ?> text-anchor="middle">–</tspan>
|
<?= empty($wert["gross"]) ? "" : 'font-size="1.6em"' ?> text-anchor="middle">–</tspan>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</g>
|
</g>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.card -->
|
<!-- /.card -->
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
<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 p-0 mb-4" style="width:100%">
|
||||||
<div class="card-header pb-0 pt-1">
|
<div class="card-header pb-0 pt-1">
|
||||||
<h3 class="card-title">Automatismen</h3>
|
<h3 class="card-title">Automatismen</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
<!-- Zuordnung Geraet -> Raum: sie ordnet die Geraeteliste im
|
<!-- Zuordnung Geraet -> Raum: sie ordnet die Geraeteliste im
|
||||||
Editor nach Raeumen statt nach Geraeteart. -->
|
Editor nach Raeumen statt nach Geraeteart. -->
|
||||||
<button type="button" class="btn btn-tool" title="Geräte den Räumen zuordnen"
|
<button type="button" class="btn btn-tool" title="Geräte den Räumen zuordnen"
|
||||||
onclick="openRoomModal()">
|
onclick="openRoomModal()">
|
||||||
<i class="bi bi-house-gear"></i>
|
<i class="bi bi-house-gear"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
||||||
<i class="bi bi-x-lg"></i>
|
<i class="bi bi-x-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body pb-1">
|
<div class="card-body pb-1">
|
||||||
<div class="card-header p-0 pt-1">
|
<div class="card-header p-0 pt-1">
|
||||||
<ul class="nav nav-tabs" id="actions-tab" role="tablist">
|
<ul class="nav nav-tabs" id="actions-tab" role="tablist">
|
||||||
<?php foreach ($floors as $etage): $offen = $_GET["floor"] == $etage; ?>
|
<?php foreach ($floors as $etage): $offen = $_GET["floor"] == $etage; ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?php if ($offen) echo "active"; ?>" id="actions-<?= $etage ?>-tab"
|
<a class="nav-link <?php if ($offen) echo "active"; ?>" id="actions-<?= $etage ?>-tab"
|
||||||
href="#actions-<?= $etage ?>" onclick="switchTab('actions-<?= $etage ?>')"
|
href="#actions-<?= $etage ?>" onclick="switchTab('actions-<?= $etage ?>')"
|
||||||
title="<?= htmlspecialchars(floorLabel($etage)) ?>"
|
title="<?= htmlspecialchars(floorLabel($etage)) ?>"
|
||||||
aria-controls="actions-<?= $etage ?>" aria-selected="<?= $offen ? "true" : "false" ?>"><?= $etage ?></a>
|
aria-controls="actions-<?= $etage ?>" aria-selected="<?= $offen ? "true" : "false" ?>"><?= $etage ?></a>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" id="actions-tabContent">
|
<div class="tab-content" id="actions-tabContent">
|
||||||
<?php foreach ($floors as $etage): ?>
|
<?php foreach ($floors as $etage): ?>
|
||||||
<div class="tab-pane fade <?php if ($_GET["floor"] == $etage) echo "active show"; ?>"
|
<div class="tab-pane fade <?php if ($_GET["floor"] == $etage) echo "active show"; ?>"
|
||||||
id="actions-<?= $etage ?>" role="tabpanel" aria-labelledby="actions-<?= $etage ?>-tab">
|
id="actions-<?= $etage ?>" role="tabpanel" aria-labelledby="actions-<?= $etage ?>-tab">
|
||||||
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
|
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
|
||||||
refreshAutomations() in js/solar/autoActionFuncs.js -->
|
refreshAutomations() in js/solar/autoActionFuncs.js -->
|
||||||
<div id="actions-<?= $etage ?>-list">Wird geladen...</div>
|
<div id="actions-<?= $etage ?>-list">Wird geladen...</div>
|
||||||
<button type="button" class="btn btn-success" title="Neue Automatik"
|
<button type="button" class="btn btn-success" title="Neue Automatik"
|
||||||
onclick="openAutoActionModal('?action=editor&floor=<?= $etage ?>')"><i class="bi bi-plus-square"></i></button>
|
onclick="openAutoActionModal('?action=editor&floor=<?= $etage ?>')"><i class="bi bi-plus-square"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.Start col -->
|
<!-- /.Start col -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- /.row (main row) -->
|
<!-- /.row (main row) -->
|
||||||
</div>
|
</div>
|
||||||
<!--end::Container-->
|
<!--end::Container-->
|
||||||
</div>
|
</div>
|
||||||
<!--end::App Content-->
|
<!--end::App Content-->
|
||||||
</main>
|
</main>
|
||||||
<!--end::App Main-->
|
<!--end::App Main-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Raumtabelle aus restricted/rooms.php - homeMQTT.js aktualisiert darueber
|
// Raumtabelle aus restricted/rooms.php - homeMQTT.js aktualisiert darueber
|
||||||
// die SVG-Kacheln, ohne die Raeume noch einmal aufzufuehren. In "werte"
|
// die SVG-Kacheln, ohne die Raeume noch einmal aufzufuehren. In "werte"
|
||||||
// steht, welche Messwerte eine Kachel zeigt; homeTopics sind die Zweige,
|
// steht, welche Messwerte eine Kachel zeigt; homeTopics sind die Zweige,
|
||||||
// die der Browser dafuer beim Broker anmelden muss.
|
// die der Browser dafuer beim Broker anmelden muss.
|
||||||
const homeRooms = <?= json_encode(roomsWithTile(), JSON_UNESCAPED_UNICODE) ?>;
|
const homeRooms = <?= json_encode(roomsWithTile(), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
const homeTopics = <?= json_encode(tileTopics(), JSON_UNESCAPED_UNICODE) ?>;
|
const homeTopics = <?= json_encode(tileTopics(), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
<?php if ($_GET["floor"] != "OG" && in_array($_GET["floor"], floorsWithPlan())): ?>
|
<?php if ($_GET["floor"] != "OG" && in_array($_GET["floor"], floorsWithPlan())): ?>
|
||||||
document.addEventListener('readystatechange', function () {
|
document.addEventListener('readystatechange', function () {
|
||||||
if (event.target.readyState === 'complete') {
|
if (event.target.readyState === 'complete') {
|
||||||
switchFloor('<?= $_GET["floor"] ?>');
|
switchFloor('<?= $_GET["floor"] ?>');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
//$tahoma_PIN = "2040-3358-7811"; //NEU
|
//$tahoma_PIN = "2040-3358-7811"; //NEU
|
||||||
$tahoma_PIN = "1215-2900-8489"; //CLASSIC
|
$tahoma_PIN = "1215-2900-8489"; //CLASSIC
|
||||||
//$tahoma_token = "693e87db02b3667c519d"; //NEU
|
//$tahoma_token = "693e87db02b3667c519d"; //NEU
|
||||||
$tahoma_token = "67ebd23e3a61763386d9"; //CLASSIC
|
$tahoma_token = "67ebd23e3a61763386d9"; //CLASSIC
|
||||||
$tahoma_devlist = "../restricted/tahoma_devices_classic.json";
|
$tahoma_devlist = "../restricted/tahoma_devices_classic.json";
|
||||||
?>
|
?>
|
||||||
Reference in New Issue
Block a user