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:
+149
-54
@@ -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}">></button>
|
||||
<button class="btn btn-outline-secondary" style="min-width: 40px;" type="button" id="btnOperator${nextID}" name="btnOperator${nextID}">></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}">></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}">></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 => {
|
||||
|
||||
Reference in New Issue
Block a user