Regenbasierte automatische Gartenbewässerung + homeMesh-Anbindung für AutoAction-Regeleditor
- Neues stündliches Python-Script (restricted/gartenbewaesserung/auto_watering.py): holt Niederschlags- und Sonnenuntergangsdaten von Open-Meteo, wertet je Zone (Hochbeete/Tröge/Garten vorn) individuelle Regen-Schwellenwerte und Mindest-Bewässerungsdauern aus und startet bei Bedarf per MQTT den passenden Automatik-Modus der Ventilsteuerung im Zeitraum vor Sonnenuntergang; veröffentlicht den Regen-Status zusätzlich stündlich als retained MQTT-Nachricht je Zone. - Web-UI: neue Bewässerungs-Steuerung/-Anzeige (ajax/watering.php, js/solar/solarMQTT.js, assets/img/realtime.svg) inkl. Live-Status-Icons (aus/geplant/pausiert/aktiv per Farbe und Symbol). - AutoAction-Regeleditor (ajax/actorDetails.php, sensorDetails.php, fillActorDD.php, fillSensorDD.php, tahoma.php, AutoAction.php) liest Actor-/Sensor-Parameter jetzt live aus der homeMesh-Datenbank statt aus stark vereinfachten Altfeldern, inkl. zugehöriger Anpassungen am Device-Discovery-Tooling (restricted/deviceDiscovery/*, neues logic_module.py). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+20
-5
@@ -156,9 +156,10 @@ input[type='range']::-webkit-slider-runnable-track {
|
||||
</select>
|
||||
<label for="paramSelect1">Messwert</label>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary " type="button" id="btnOperator1" name="btnOperator1">></button>
|
||||
<button class="btn btn-outline-secondary" style="min-width: 40px;" type="button" id="btnOperator1" name="btnOperator1">></button>
|
||||
<div class="form-floating" id="valBlock1">
|
||||
<input type="number" class="form-control" id="threshold1" placeholder="0" value="0" name="threshold1"></input>
|
||||
<select class="form-select" id="thresholdOpt1" name="thresholdOpt1" placeholder="0" value="0"></select>
|
||||
<label for="threshold1">Wert/Schwelle</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -247,10 +248,24 @@ input[type='range']::-webkit-slider-runnable-track {
|
||||
</select>
|
||||
<label for="paramSelect1">Eigenschaft</label>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary " type="button" id="btnActOperator1" name="btnActOperator1">></button>
|
||||
<div class="form-floating" id="actValBlock1">
|
||||
<input type="number" class="form-control" id="actValue1" name="actValue1" placeholder="0" value="0"></input>
|
||||
<label for="actValue1">Sollwert</label>
|
||||
<button class="btn btn-outline-secondary" style="min-width: 40px;" type="button" id="btnActOperator1" name="btnActOperator1">></button>
|
||||
<div class="form-floating" id="act1ValBlock1">
|
||||
<input type="number" class="form-control" id="act1Value1" name="act1Value1" value="0"></input>
|
||||
<select class="form-select" id="act1ValueOpt1" name="act1ValueOpt1"></select>
|
||||
<label for="act1Value1">Wert 1</label>
|
||||
</div>
|
||||
<div class="form-floating" id="act2ValBlock1">
|
||||
<input type="number" class="form-control" id="act2Value1" name="act2Value1" value="0"></input>
|
||||
<label for="act2Value1">Wert 2</label>
|
||||
</div>
|
||||
<div class="form-floating" id="act3ValBlock1">
|
||||
<input type="number" class="form-control" id="act3Value1" name="act3Value1" value="0"></input>
|
||||
<label for="act3Value1">Wert 3</label>
|
||||
</div>
|
||||
<div class="form-floating" id="act4ValBlock1">
|
||||
<input type="number" class="form-control" id="act4Value1" name="act4Value1" value="0"></input>
|
||||
<label for="act4Value1">Wert 4</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+70
-1
@@ -1,5 +1,74 @@
|
||||
<?php
|
||||
require_once("../helper.php");
|
||||
if(isset($_GET["actorID"])){
|
||||
$actorID = intval($_GET["actorID"]);
|
||||
$qry = "SELECT command_name, command_url, parameter_type, min_value, max_value, possible_values, command_parameters.url AS parameter_url, parameter_name FROM actor_commands LEFT JOIN command_parameters ON actor_commands.id = command_id LEFT JOIN state_types ON parameter_type = state_types.id WHERE actor_id=".$actorID;
|
||||
$qry = "SELECT id, command_name, command_url FROM actor_commands WHERE actor_id = ".$actorID;
|
||||
$mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB);
|
||||
$result = mysqli_query($mysql, $qry);
|
||||
$operators = "";
|
||||
//while($row = $result->fetch_assoc()){
|
||||
// print_r($row);
|
||||
// echo "<br />";
|
||||
//}
|
||||
echo '{"commands": [';
|
||||
$iterated = 0;
|
||||
while($row = $result->fetch_assoc()){
|
||||
$items = "[]";
|
||||
$qry2 = "SELECT parameter_type, min_value, max_value, possible_values, command_parameters.url AS parameter_url, parameter_name FROM actor_commands LEFT JOIN command_parameters ON actor_commands.id = command_id LEFT JOIN state_types ON parameter_type = state_types.id WHERE command_id = ".$row["id"];
|
||||
$result2 = mysqli_query($mysql, $qry2);
|
||||
if($iterated++){
|
||||
echo ", ";
|
||||
}
|
||||
echo '{"name" : "'.$row["command_name"].'", "operators" : ["="], "parameters": [';
|
||||
$iterated2 = 0;
|
||||
$type = "hidden";
|
||||
while($row2 = $result2->fetch_assoc()){
|
||||
switch($row2["parameter_type"]){
|
||||
case "integer":
|
||||
if($row2["possible_values"]){
|
||||
$items = $row2["possible_values"];
|
||||
$type = "dropdown";
|
||||
}else{
|
||||
$type = "number";
|
||||
}
|
||||
break;
|
||||
case "float":
|
||||
$type = "number";
|
||||
break;
|
||||
case "time":
|
||||
$type = "time";
|
||||
break;
|
||||
case "bool":
|
||||
$type = "button";
|
||||
break;
|
||||
case "string":
|
||||
if($row2["possible_values"]){
|
||||
$items = $row2["possible_values"];
|
||||
$type = "dropdown";
|
||||
}else{
|
||||
$type = "text";
|
||||
}
|
||||
break;
|
||||
case "":
|
||||
$type = "hidden";
|
||||
default:
|
||||
$type = "text";
|
||||
break;
|
||||
}
|
||||
if($iterated2++){
|
||||
echo ", ";
|
||||
}
|
||||
echo '{"name" : "'.$row2["parameter_name"].'", "url" : "'.$row2["parameter_url"].'", "type" : "'.$type.'", "options" : '.$items.'}';
|
||||
}
|
||||
echo '], "type" : "'.$type.'"}';
|
||||
}
|
||||
echo "]}";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
require_once("../helper.php");
|
||||
if(isset($_GET["actorID"])){
|
||||
$ID = intval($_GET["actorID"]);
|
||||
$qry = "SELECT parameters FROM actors WHERE id=".$ID;
|
||||
@@ -8,5 +77,5 @@ if(isset($_GET["actorID"])){
|
||||
echo $result->fetch_array()[0];
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
?>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
require_once("../helper.php");
|
||||
$qry = "SELECT name, id FROM actors";
|
||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||
$qry = "SELECT name, actors.id FROM actors INNER JOIN actor_commands ON actor_id = actors.id GROUP BY actors.id";
|
||||
$mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB);
|
||||
$result = mysqli_query($mysql, $qry);
|
||||
|
||||
while($row = $result->fetch_assoc()){
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
require_once("../helper.php");
|
||||
$qry = "SELECT name, id FROM sensors";
|
||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||
$qry = "SELECT name, actors.id FROM actors INNER JOIN actor_states ON actor_id = actors.id GROUP BY actors.id";
|
||||
$mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB);
|
||||
$result = mysqli_query($mysql, $qry);
|
||||
|
||||
while($row = $result->fetch_assoc()){
|
||||
|
||||
+60
-3
@@ -2,10 +2,67 @@
|
||||
require_once("../helper.php");
|
||||
if(isset($_GET["sensorID"])){
|
||||
$sensorID = intval($_GET["sensorID"]);
|
||||
$qry = "SELECT parameters FROM sensors WHERE id=".$sensorID;
|
||||
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
|
||||
$qry = "SELECT state_name, url, state_types.type, unit, current_value, possible_values FROM actor_states LEFT JOIN state_types ON state_type = state_types.id WHERE actor_id=".$sensorID;
|
||||
$mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB);
|
||||
$result = mysqli_query($mysql, $qry);
|
||||
echo $result->fetch_array()[0];
|
||||
$operators = "";
|
||||
echo '{"parameters": [';
|
||||
$iterated = 0;
|
||||
$items = "[]";
|
||||
while($row = $result->fetch_assoc()){
|
||||
switch($row["type"]){
|
||||
case "integer":
|
||||
$operators = '["=","≠",">","<"]';
|
||||
$type = "number";
|
||||
break;
|
||||
case "float":
|
||||
$operators = '["=","≠",">","<"]';
|
||||
$type = "number";
|
||||
break;
|
||||
case "time":
|
||||
$operators = '["=","≠"]';
|
||||
$type = "time";
|
||||
break;
|
||||
case "deltatime":
|
||||
$operators = '["+","-"]';
|
||||
$type = "time";
|
||||
break;
|
||||
case "date":
|
||||
$operators = '["=","≠"]';
|
||||
$type = "date";
|
||||
break;
|
||||
case "datetime":
|
||||
$operators = '["=","≠"]';
|
||||
$type = "datetime-local";
|
||||
break;
|
||||
case "bool":
|
||||
$operators = '["= JA","= NEIN"]';
|
||||
$type = "button";
|
||||
break;
|
||||
case "string":
|
||||
$operators = '["=","≠"]';
|
||||
if($row["possible_values"]){
|
||||
$items = $row["possible_values"];
|
||||
$type = "dropdown";
|
||||
}else{
|
||||
$operators = '["=","≠"]';
|
||||
$type = "text";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$operators = '["=","≠"]';
|
||||
$type = "text";
|
||||
break;
|
||||
}
|
||||
if($iterated++){
|
||||
echo ", ";
|
||||
}
|
||||
if($row["current_value"]){
|
||||
$row["state_name"] .= " (=".$row["current_value"].")";
|
||||
}
|
||||
echo '{"name" : "'.$row["state_name"].'", "operators" : '.$operators.', "url" : "'.$row["url"].'", "type" : "'.$type.'", "options" : '.$items.'}';
|
||||
}
|
||||
echo "]}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
+9
-7
@@ -50,6 +50,7 @@ function filter_devs($arr,$filter,$prefix=""){
|
||||
foreach($arr as $key => $dev){
|
||||
$start = strpos(strtolower($dev["name"]),strtolower($filter));
|
||||
if($start !== false){
|
||||
$dev["name"] = str_replace("u_","",$dev["name"]);
|
||||
$ret[$i]["name"] = $prefix.substr($dev["name"],strlen($filter));
|
||||
$ret[$i]["id"] = $key;
|
||||
$i++;
|
||||
@@ -60,7 +61,7 @@ function filter_devs($arr,$filter,$prefix=""){
|
||||
|
||||
function searchForDevice($device){
|
||||
$dev = false;
|
||||
$devices = json_decode(file_get_contents('../restricted/tahoma_devices.json'), true);
|
||||
$devices = json_decode(file_get_contents($GLOBALS["tahoma_devlist"]), true);
|
||||
if (strlen($device) < 3 && intval($device) >= 0 && intval($device) < sizeof($devices)) {
|
||||
$dev = $devices[intval($device)];
|
||||
} else if ($key = array_search($device, array_column($devices, "name"))) {
|
||||
@@ -87,12 +88,13 @@ if ($_GET["action"] == "devlist") {
|
||||
echo $device["label"] . "(".$device["deviceURL"].")<br />";
|
||||
}
|
||||
}
|
||||
$file = fopen('../restricted/tahoma_devices.json', 'w');
|
||||
fwrite($file, json_encode($jalousien));
|
||||
fclose($file);
|
||||
//$file = fopen($tahoma_devlist, 'w');
|
||||
//fwrite($file, json_encode($jalousien));
|
||||
//fclose($file);
|
||||
echo json_encode($jalousien);
|
||||
}elseif ($_GET["action"] == "pos" && isset($_GET["device"])) {
|
||||
$dev = searchForDevice($_GET["device"])["id"];
|
||||
$devices = json_decode(file_get_contents('../restricted/tahoma_devices.json'), true);
|
||||
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
||||
$jalousien = array();
|
||||
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states";
|
||||
$ret = getSSLPage($url,$tahoma_token);
|
||||
@@ -115,7 +117,7 @@ if ($_GET["action"] == "devlist") {
|
||||
fastcgi_finish_request();
|
||||
}elseif ($_GET["action"] == "moving" && isset($_GET["device"])) {
|
||||
$dev = searchForDevice($_GET["device"])["id"];
|
||||
$devices = json_decode(file_get_contents('../restricted/tahoma_devices.json'), true);
|
||||
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
||||
$jalousien = array();
|
||||
$url = $url = 'https://gateway-'.$tahoma_PIN.':8443/enduser-mobile-web/1/enduserAPI/setup/devices/'.urlencode($dev)."/states/core:MovingState";
|
||||
$ret = getSSLPage($url,$tahoma_token);
|
||||
@@ -201,7 +203,7 @@ if ($_GET["action"] == "myactors") {
|
||||
$host = gethostbyaddr("192.168.179.32");
|
||||
$host = str_replace("-", "", strtolower(substr($host, 0, strpos($host, "."))));
|
||||
}
|
||||
$devices = json_decode(file_get_contents('../restricted/tahoma_devices.json'), true);
|
||||
$devices = json_decode(file_get_contents($tahoma_devlist), true);
|
||||
switch ($host) {
|
||||
case "tmpegbad":
|
||||
echo json_encode(filter_devs($devices, "bad "));
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
require_once("./phpMQTT.php");
|
||||
require_once("../helper.php");
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
if (!checkLogin()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(["success" => false, "message" => "Nicht angemeldet."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$topic = $_POST["topic"] ?? "";
|
||||
$payload = $_POST["payload"] ?? "";
|
||||
$allowedTopics = [
|
||||
"Gartenwasser/vorn/set",
|
||||
"Gartenwasser/vorn/auto",
|
||||
"Gartenwasser/hinten/set",
|
||||
"Gartenwasser/hinten/auto"
|
||||
];
|
||||
|
||||
if (!in_array($topic, $allowedTopics, true)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["success" => false, "message" => "Nicht erlaubtes MQTT-Topic."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (str_ends_with($topic, "/set")) {
|
||||
$command = json_decode($payload, true);
|
||||
$validValve = is_array($command)
|
||||
&& isset($command["valve"], $command["set"])
|
||||
&& in_array((int) $command["valve"], [1, 6], true)
|
||||
&& in_array($command["set"], ["open", "close"], true);
|
||||
if ($validValve && $command["set"] === "open") {
|
||||
$validValve = isset($command["timer"])
|
||||
&& is_numeric($command["timer"])
|
||||
&& (int) $command["timer"] >= 1
|
||||
&& (int) $command["timer"] <= 120;
|
||||
}
|
||||
if (!$validValve) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["success" => false, "message" => "Ungültiger Ventilbefehl."]);
|
||||
exit;
|
||||
}
|
||||
$payload = json_encode($command, JSON_THROW_ON_ERROR);
|
||||
} elseif (!in_array($payload, ["Hoch", "Vorn", "Trog", "Stop"], true)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["success" => false, "message" => "Ungültiger Automatikbefehl."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$mqtt = new Bluerhinos\phpMQTT("localhost", 1883, "WateringWeb-" . bin2hex(random_bytes(4)));
|
||||
if (!$mqtt->connect(true, null, "", "")) {
|
||||
http_response_code(503);
|
||||
echo json_encode(["success" => false, "message" => "MQTT-Broker nicht erreichbar."]);
|
||||
exit;
|
||||
}
|
||||
$mqtt->publish($topic, $payload, 0, false);
|
||||
$mqtt->close();
|
||||
} catch (Throwable $error) {
|
||||
http_response_code(503);
|
||||
echo json_encode(["success" => false, "message" => "MQTT-Fehler: " . $error->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
echo json_encode(["success" => true, "topic" => $topic, "payload" => $payload]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!checkLogin()) {
|
||||
exit;
|
||||
}
|
||||
|
||||
echo <<<HTML
|
||||
<div class="table-responsive p-3">
|
||||
<div id="watering-feedback" class="alert d-none mb-3" role="status"></div>
|
||||
<div class="mb-3">
|
||||
<h6>Automatik</h6>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-outline-primary" data-watering-auto-toggle data-watering-auto="Trog,Hoch" data-watering-topics="Gartenwasser/vorn,Gartenwasser/hinten" data-watering-label="Tröge und Hochbeete">Tröge und Hochbeete</button>
|
||||
<button type="button" class="btn btn-outline-primary" data-watering-auto-toggle data-watering-auto="Trog" data-watering-topic="Gartenwasser/vorn" data-watering-label="Tröge">Tröge</button>
|
||||
<button type="button" class="btn btn-outline-primary" data-watering-auto-toggle data-watering-auto="Hoch" data-watering-topic="Gartenwasser/hinten" data-watering-label="Hochbeete">Hochbeete</button>
|
||||
<button type="button" class="btn btn-outline-primary" data-watering-auto-toggle data-watering-auto="Vorn" data-watering-topic="Gartenwasser/vorn" data-watering-label="Garten vorn">Garten vorn</button>
|
||||
</div>
|
||||
</div>
|
||||
<h6>Manuell</h6>
|
||||
<table class="table align-middle mb-3">
|
||||
<thead><tr><th>Zone</th><th>Dauer (Min.)</th><th>Letzte Bewässerung</th><th>Dauer</th><th>Steuerung</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Tröge</td><td><input class="form-control" type="number" min="1" max="120" value="5" data-watering-timer="troughs"></td><td data-watering-last="troughs">--</td><td data-watering-duration="troughs">--</td><td><button type="button" class="btn btn-success" data-watering-toggle data-watering-zone="troughs">Ein</button></td></tr>
|
||||
<tr><td>Beete</td><td><input class="form-control" type="number" min="1" max="120" value="30" data-watering-timer="raisedBeds"></td><td data-watering-last="raisedBeds">--</td><td data-watering-duration="raisedBeds">--</td><td><button type="button" class="btn btn-success" data-watering-toggle data-watering-zone="raisedBeds">Ein</button></td></tr>
|
||||
<tr><td>Vorn</td><td><input class="form-control" type="number" min="1" max="120" value="30" data-watering-timer="frontGarden"></td><td data-watering-last="frontGarden">--</td><td data-watering-duration="frontGarden">--</td><td><button type="button" class="btn btn-success" data-watering-toggle data-watering-zone="frontGarden">Ein</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
HTML;
|
||||
Reference in New Issue
Block a user