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:
2026-08-26 20:19:27 +02:00
co-authored by Claude Sonnet 5
parent 4de95c1a29
commit 29f752eafe
22 changed files with 1693 additions and 234 deletions
+149 -54
View File
@@ -1,23 +1,129 @@
function addOptions(dropdownID, opts){
opts.forEach((opt, index) => {
var option = document.createElement("option");
if(opt.name){ //parameter could be command in case of actor.
option.text = opt.name;
option.value = opt.url;
if(opt.operators)
option.setAttribute("data-tglstates",JSON.stringify(opt.operators));
if(opt.type)
option.setAttribute("data-type",opt.type);
if(opt.options)
option.setAttribute("data-parameters",JSON.stringify([opt]));
if(opt.parameters){ //we have an actor with more than one parameters, then the parameters are actually nested inside the command
option.setAttribute("data-parameters",JSON.stringify(opt.parameters));
}
}else if(typeof opt === 'object'){
option.value = Object.keys(opt)[0];
option.text = opt[option.value];
}else {
option.text = opt;
option.value = opt;
}
if(typeof dropdownID === 'object'){
dropdownID.appendChild(option);
}else{
document.getElementById(dropdownID).appendChild(option);
}
});
}
function clearOptions(dropdownID){
if(typeof dropdownID === 'object'){
dropdownID.innerHTML = "";
}else{
document.getElementById(dropdownID).innerHTML = "";
}
}
function changeValueType(event){
dropdown = event.currentTarget;
type = dropdown.options[dropdown.selectedIndex].dataset.type;
id = dropdown.id.match(/\d+$/);
if(dropdown.id.search("act") > -1){
opBtnID = "btnActOperator"+id;
valBlockID = "actValBlock"+id;
valElemID = "actValue"+id;
opBtnID = document.getElementById("btnActOperator"+id);
valBlockID = document.getElementById("act1ValBlock"+id);
valElemID = document.getElementById("act1Value"+id);
valOptID = document.getElementById("act1ValueOpt"+id);
val2BlockID = document.getElementById("act2ValBlock"+id);
val3BlockID = document.getElementById("act3ValBlock"+id);
val4BlockID = document.getElementById("act4ValBlock"+id);
}else{
opBtnID = "btnOperator"+id;
valBlockID ="valBlock"+id;
valElemID = "threshold"+id;
opBtnID = document.getElementById("btnOperator"+id);
valBlockID =document.getElementById("valBlock"+id);
valElemID = document.getElementById("threshold"+id);
valOptID = document.getElementById("thresholdOpt"+id);
val2BlockID = null;
}
if(type =="hidden"){
document.getElementById(opBtnID).hidden = true;
document.getElementById(valBlockID).hidden = true;
opBtnID.hidden = true;
valBlockID.hidden = true;
if(val2BlockID){ //if this is an actor hide all additional inputs, as only one dropdown is supported
val2BlockID.hidden = true;
val3BlockID.hidden = true;
val4BlockID.hidden = true;
}
}else{
document.getElementById(opBtnID).hidden = false;
document.getElementById(valBlockID).hidden = false;
document.getElementById(valElemID).type = type;
opBtnID.setAttribute("data-tglstates",dropdown.options[dropdown.selectedIndex].dataset.tglstates);
opBtnID.innerHTML = JSON.parse(dropdown.options[dropdown.selectedIndex].dataset.tglstates)[0];
opBtnID.hidden = false;
valBlockID.hidden = false;
if(type == "dropdown"){
valElemID.type = "text";
valOptID.hidden = false;
valElemID.hidden = true;
if(val2BlockID){ //if this is an actor hide all additional inputs, as only one dropdown is supported
val2BlockID.hidden = true;
val3BlockID.hidden = true;
val4BlockID.hidden = true;
}
clearOptions(valOptID);
addOptions(valOptID,JSON.parse(dropdown.options[dropdown.selectedIndex].dataset.parameters)[0].options);
}else if(type == "button" || type == "bool"){
valElemID.type = "text";
valOptID.hidden = true;
valBlockID.hidden = true;
if(val2BlockID){ //if this is an actor hide all additional inputs, as only one dropdown is supported
val2BlockID.hidden = true;
val3BlockID.hidden = true;
val4BlockID.hidden = true;
}
}else{
valElemID.type = type;
if(type == "time")
valElemID.value = "00:00";
else if(type == "date")
valElemID.value = new Date().toISOString().split('T')[0]
else if(type == "text")
valElemID.value = "";
else
valElemID.value = "0";
valOptID.hidden = true;
valElemID.hidden = false;
if(val2BlockID){
parameters = JSON.parse(dropdown.options[dropdown.selectedIndex].dataset.parameters);
val2BlockID.hidden = true;
val3BlockID.hidden = true;
val4BlockID.hidden = true;
theAssociatedLabel = valElemID.parentNode.querySelector("label[for='" + valElemID.id + "']");
theAssociatedLabel.innerHTML = parameters[0].name;
switch(parameters.length){
case 4:
theAssociatedLabel = val4BlockID.querySelector("label[for='" + val4BlockID.id.replace("Block","ue") + "']");
theAssociatedLabel.innerHTML = parameters[3].name;
val4BlockID.hidden = false;
case 3:
theAssociatedLabel = val3BlockID.querySelector("label[for='" + val3BlockID.id.replace("Block","ue") + "']");
theAssociatedLabel.innerHTML = parameters[2].name;
val3BlockID.hidden = false;
case 2:
theAssociatedLabel = val2BlockID.querySelector("label[for='" + val2BlockID.id.replace("Block","ue") + "']");
theAssociatedLabel.innerHTML = parameters[1].name;
val2BlockID.hidden = false;
}
}
}
}
}
@@ -104,9 +210,10 @@ function changeValueType(event){
</select>
<label>Messwert</label>
</div>
<button class="btn btn-outline-secondary " type="button" id="btnOperator${nextID}" name="btnOperator${nextID}">&gt;</button>
<button class="btn btn-outline-secondary" style="min-width: 40px;" type="button" id="btnOperator${nextID}" name="btnOperator${nextID}">&gt;</button>
<div class="form-floating" id="valBlock${nextID}">
<input type="number" class="form-control" id="threshold${nextID}" name="threshold${nextID}" placeholder="0" value="0"></input>
<select class="form-select" id="thresholdOpt${nextID}" name="thresholdOpt${nextID}" placeholder="0" value="0"></select>
<label>Wert/Schwelle</label>
</div>
<button class='btn btn-outline-danger' type='button' id='btnDel${nextID}' data-delid="sensorSettings${nextID}" onclick="delAutoEntry(event)"><i class="bi bi-trash3"></i></button>
@@ -132,11 +239,24 @@ function changeValueType(event){
</select>
<label>Eigenschaft</label>
</div>
<button class="btn btn-outline-secondary " type="button" id="btnActOperator${nextID}" name="btnActOperator${nextID}">&gt;</button>
<div class="form-floating" id="actValBlock${nextID}">
<input type="number" class="form-control" id="actValue${nextID}" name="actValue${nextID}" placeholder="0" value="0"></input>
<label>Sollwert</label>
<button class="btn btn-outline-secondary" style="min-width: 40px;" type="button" id="btnActOperator${nextID}" name="btnActOperator${nextID}">&gt;</button>
<div class="form-floating" id="act1ValBlock${nextID}">
<input type="number" class="form-control" id="act1Value${nextID}" name="act1Value${nextID}" placeholder="0" value="0"></input>
<select class="form-select" id="act1ValueOpt${nextID}" name="act1ValueOpt${nextID}"></select>
<label for="act1Value${nextID}">Wert 1</label>
</div>
<div class="form-floating" id="act2ValBlock{nextID}">
<input type="number" class="form-control" id="act2Value${nextID}" name="act2Value${nextID}" value="0"></input>
<label for="act2Value${nextID}">Wert 2</label>
</div>
<div class="form-floating" id="act3ValBlock{nextID}">
<input type="number" class="form-control" id="act3Value${nextID}" name="act3Value${nextID}" value="0"></input>
<label for="act3Value${nextID}">Wert 3</label>
</div>
<div class="form-floating" id="act4ValBlock{nextID}">
<input type="number" class="form-control" id="act4Value${nextID}" name="act4Value${nextID}" value="0"></input>
<label for="act4Value${nextID}">Wert 4</label>
</div>
<button class='btn btn-outline-danger' type='button' id='btnActDel${nextID}' data-delid="actorSettings${nextID}" onclick="delAutoEntry(event)"><i class="bi bi-trash3"></i></button>`;
document.getElementById("actorsList").appendChild(div);
@@ -175,25 +295,17 @@ function changeValueType(event){
.then(response => response.text())
.then(html => {
params = JSON.parse(html);
document.getElementById("paramSelect"+ID).innerHTML = "";
if(params.Parameters.length == 1){
if(params.parameters.length == 1){
document.getElementById("paramBlock"+ID).hidden = true;
}else{
document.getElementById("paramBlock"+ID).hidden = false;
}
params.Parameters.forEach((param, index) => {
var option = document.createElement("option");
option.text = param.name;
option.value = index;
option.setAttribute("data-type",param.type);
document.getElementById("paramSelect"+ID).appendChild(option);
document.getElementById("btnOperator"+ID).setAttribute("data-tglstates",JSON.stringify(param.operators));
document.getElementById("btnOperator"+ID).innerHTML = param.operators[0];
document.getElementById("btnOperator"+ID).addEventListener("click", tglOperators);
if(document.getElementById("btnLogic"+ID))
document.getElementById("btnLogic"+ID).addEventListener("click", tglOperators);
document.getElementById("threshold"+ID).setAttribute("type",param.type);
});
document.getElementById("btnOperator"+ID).addEventListener("click", tglOperators);
clearOptions("paramSelect"+ID);
addOptions("paramSelect"+ID,params.parameters);
if(document.getElementById("btnLogic"+ID))
document.getElementById("btnLogic"+ID).addEventListener("click", tglOperators);
document.getElementById("paramSelect"+ID).dispatchEvent(new Event('change'));
})
.catch(error => {
@@ -204,9 +316,9 @@ function changeValueType(event){
function arrangeActorInputs(event){
ID = Number(event.currentTarget.id.replace("actorSelect",""));
sensorID = document.getElementById("actorSelect"+ID).value;
actorID = document.getElementById("actorSelect"+ID).value;
fetch("./ajax/actorDetails.php?actorID="+sensorID, {
fetch("./ajax/actorDetails.php?actorID="+actorID, {
method: 'GET',
headers: {
'X-Requested-From-Modal': 'a',
@@ -217,31 +329,14 @@ function changeValueType(event){
.then(html => {
params = JSON.parse(html);
document.getElementById("actParamSelect"+ID).innerHTML = "";
if(params.Parameters.length == 1){
if(params.commands.length == 1){
document.getElementById("actParamBlock"+ID).hidden = true;
}else{
document.getElementById("actParamBlock"+ID).hidden = false;
}
params.Parameters.forEach((param, index) => {
var option = document.createElement("option");
option.text = param.name;
option.value = index;
option.setAttribute("data-type",param.type);
document.getElementById("actParamSelect"+ID).appendChild(option);
if(param.operators === undefined){
document.getElementById("btnActOperator"+ID).dataset.tglstates = "[\"=\"]";
document.getElementById("btnActOperator"+ID).innerHTML = "=";
}else{
document.getElementById("btnActOperator"+ID).dataset.tglstates = JSON.stringify(param.operators);
document.getElementById("btnActOperator"+ID).innerHTML = param.operators[0];
}
document.getElementById("btnActOperator"+ID).addEventListener("click", tglOperators);
if(document.getElementById("btnActLogic"+ID)){
document.getElementById("btnActLogic"+ID).addEventListener("click", tglOperators);
}
//if(document.getElementById("actParamSelect"+ID).value == param.id)
// document.getElementById("actValue"+ID).setAttribute("type",param.type);
});
clearOptions("actParamSelect"+ID);
addOptions("actParamSelect"+ID,params.commands);
document.getElementById("actParamSelect"+ID).dispatchEvent(new Event('change'));
})
.catch(error => {