- 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>
390 lines
16 KiB
JavaScript
390 lines
16 KiB
JavaScript
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 = 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 = document.getElementById("btnOperator"+id);
|
|
valBlockID =document.getElementById("valBlock"+id);
|
|
valElemID = document.getElementById("threshold"+id);
|
|
valOptID = document.getElementById("thresholdOpt"+id);
|
|
val2BlockID = null;
|
|
}
|
|
if(type =="hidden"){
|
|
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{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function fillSensorDD(ID){
|
|
var elem = document.getElementById("sensorSelect"+ID);
|
|
fetch("./ajax/fillSensorDD.php", {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-From-Modal': 'a',
|
|
'Requested-With-Ajax': 'ajax'
|
|
}
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
elem.innerHTML = html;
|
|
elem.addEventListener("change", arrangeSensorInputs);
|
|
document.getElementById("paramSelect"+ID).addEventListener("change", changeValueType);
|
|
elem.dispatchEvent(new Event('change'));
|
|
})
|
|
.catch(error => {
|
|
elem.innerHTML = "";
|
|
});
|
|
}
|
|
|
|
function fillActorDD(ID){
|
|
var elem = document.getElementById("actorSelect"+ID);
|
|
fetch("./ajax/fillActorDD.php", {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-From-Modal': 'a',
|
|
'Requested-With-Ajax': 'ajax'
|
|
}
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
elem.innerHTML = html;
|
|
elem.addEventListener("change", arrangeActorInputs);
|
|
document.getElementById("actParamSelect"+ID).addEventListener("change", changeValueType);
|
|
document.getElementById("actorSelect"+ID).dispatchEvent(new Event('change'));
|
|
})
|
|
.catch(error => {
|
|
elem.innerHTML = "";
|
|
});
|
|
}
|
|
|
|
function changeAllIDs (parentNode, newID) {
|
|
for (var i = 0; i < parentNode.childNodes.length; i++) {
|
|
var child = parentNode.childNodes[i];
|
|
changeAllIDs(child, newID);
|
|
}
|
|
if(parentNode.id){
|
|
parentNode.id = parentNode.id.replace(/\d+$/, newID);
|
|
}
|
|
if(parentNode.dataset !== undefined){
|
|
if(parentNode.dataset.delid)
|
|
parentNode.dataset.delid = parentNode.dataset.delid.replace(/\d+$/, newID);
|
|
}
|
|
}
|
|
|
|
function delAutoEntry(event){
|
|
ID = event.currentTarget.dataset.delid;
|
|
document.getElementById(ID).remove();
|
|
ID = ID.replace(/\d+$/, function(n){ return ++n });
|
|
while(sensor= document.getElementById(ID)){
|
|
changeAllIDs(sensor, ID.match(/\d+$/)-1);
|
|
ID = ID.replace(/\d+$/, function(n){ return ++n });
|
|
}
|
|
}
|
|
function addSensor(event){
|
|
var t = document.getElementById('sensorsList').children;
|
|
//get the second last element (ignore "add" button)
|
|
nextID = Number(t[t.length-1].id.replace("sensorSettings","")) + 1;
|
|
var div = document.createElement('div');
|
|
div.className = "input-group mt-3";
|
|
div.id = "sensorSettings"+String(nextID);
|
|
div.innerHTML = `<button class='btn btn-outline-secondary' type='button' id='btnLogic${nextID}' name='btnLogic${nextID}' data-tglstates='["und","oder"]' >und</button>
|
|
<div class="form-floating">
|
|
<select class="form-select" id="sensorSelect${nextID}" name="sensorSelect${nextID}" aria-label="Default select example">
|
|
</select>
|
|
<label>Sensor</label>
|
|
</div>
|
|
<div class="form-floating" id="paramBlock${nextID}">
|
|
<select class="form-select" id="paramSelect${nextID}" name="paramSelect${nextID}" aria-label="Default select example">
|
|
</select>
|
|
<label>Messwert</label>
|
|
</div>
|
|
<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>
|
|
</div>`;
|
|
document.getElementById("sensorsList").appendChild(div);
|
|
fillSensorDD(nextID);
|
|
}
|
|
|
|
function addActor(event){
|
|
var t = document.getElementById('actorsList').children;
|
|
//get the second last element (ignore "add" button)
|
|
nextID = Number(t[t.length-1].id.replace("actorSettings","")) + 1;
|
|
var div = document.createElement('div');
|
|
div.className = "input-group mt-3";
|
|
div.id = "actorSettings"+String(nextID);
|
|
div.innerHTML = `<div class="form-floating">
|
|
<select class="form-select" id="actorSelect${nextID}" name="actorSelect${nextID}" aria-label="Default select example">
|
|
</select>
|
|
<label>Aktor</label>
|
|
</div>
|
|
<div class="form-floating" id="actParamBlock${nextID}">
|
|
<select class="form-select" id="actParamSelect${nextID}" name="actParamSelect${nextID}" aria-label="Default select example">
|
|
</select>
|
|
<label>Eigenschaft</label>
|
|
</div>
|
|
<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);
|
|
/*"afterend",`<div class='input-group mb-3' id='actorSettings${nextID}'>
|
|
<div class="form-floating">
|
|
<select class="form-select" id="actorSelect${nextID}" aria-label="Default select example">
|
|
</select>
|
|
<label>Aktor</label>
|
|
</div>
|
|
<div class="form-floating" id="actParamBlock${nextID}">
|
|
<select class="form-select" id="actParamSelect${nextID}" aria-label="Default select example">
|
|
</select>
|
|
<label>Eigenschaft</label>
|
|
</div>
|
|
<button class="btn btn-outline-secondary " type="button" id="btnActOperator${nextID}">></button>
|
|
<div class="form-floating" id="actValBlock${nextID}">
|
|
<input type="number" class="form-control" id="actValue${nextID}" placeholder="0" value="0"></input>
|
|
<label>Sollwert</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>
|
|
</div>`);*/
|
|
fillActorDD(nextID);
|
|
}
|
|
|
|
function arrangeSensorInputs(event){
|
|
ID = Number(event.currentTarget.id.replace("sensorSelect",""));
|
|
sensorID = document.getElementById("sensorSelect"+ID).value;
|
|
|
|
fetch("./ajax/sensorDetails.php?sensorID="+sensorID, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-From-Modal': 'a',
|
|
'Requested-With-Ajax': 'ajax'
|
|
}
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
params = JSON.parse(html);
|
|
if(params.parameters.length == 1){
|
|
document.getElementById("paramBlock"+ID).hidden = true;
|
|
}else{
|
|
document.getElementById("paramBlock"+ID).hidden = false;
|
|
}
|
|
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 => {
|
|
alert(error);
|
|
document.getElementById("paramSelect"+ID).innerHTML = "";
|
|
});
|
|
}
|
|
|
|
function arrangeActorInputs(event){
|
|
ID = Number(event.currentTarget.id.replace("actorSelect",""));
|
|
actorID = document.getElementById("actorSelect"+ID).value;
|
|
|
|
fetch("./ajax/actorDetails.php?actorID="+actorID, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-From-Modal': 'a',
|
|
'Requested-With-Ajax': 'ajax'
|
|
}
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
params = JSON.parse(html);
|
|
document.getElementById("actParamSelect"+ID).innerHTML = "";
|
|
if(params.commands.length == 1){
|
|
document.getElementById("actParamBlock"+ID).hidden = true;
|
|
}else{
|
|
document.getElementById("actParamBlock"+ID).hidden = false;
|
|
}
|
|
|
|
clearOptions("actParamSelect"+ID);
|
|
addOptions("actParamSelect"+ID,params.commands);
|
|
document.getElementById("actParamSelect"+ID).dispatchEvent(new Event('change'));
|
|
})
|
|
.catch(error => {
|
|
document.getElementById("actParamSelect"+ID).innerHTML = "";
|
|
});
|
|
}
|
|
|
|
|
|
function tglOperators(event){
|
|
btn = event.currentTarget;
|
|
operators = JSON.parse(btn.dataset.tglstates);
|
|
current = btn.innerHTML;
|
|
btn.innerHTML = operators[(operators.indexOf(current)+1) % operators.length];
|
|
}
|
|
|
|
function loadAutomatic(params){
|
|
if(params.search("action=new")>0){
|
|
fillSensorDD("1");
|
|
fillActorDD("1");
|
|
}
|
|
}
|
|
|
|
function openAutoActionModal(params) {
|
|
let contentURL = "./ajax/AutoAction.php"+params;
|
|
if(params.search("action=new")>0)
|
|
document.getElementById("modal-title").innerHTML = "Neue Automatik anlegen";
|
|
else
|
|
document.getElementById("modal-title").innerHTML = "Automatik Einstellungen";
|
|
modalBodyElement = document.getElementById('modal-body');
|
|
modalBodyElement.innerHTML = loadingHTML("Wird geladen...");
|
|
document.getElementById("modalSaveBtn").addEventListener("click", submitFormAjax);
|
|
document.getElementById("modalSaveBtn").contentURL = contentURL;
|
|
modalEV.show();
|
|
fetch(contentURL, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-From-Modal': 'a',
|
|
'Requested-With-Ajax': 'ajax'
|
|
}
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
modalBodyElement.innerHTML = html;
|
|
loadAutomatic(params);
|
|
document.getElementById("btnAddSensor").addEventListener("click", addSensor);
|
|
document.getElementById("btnAddActor").addEventListener("click", addActor);
|
|
})
|
|
.catch(error => {
|
|
modalBodyElement.innerHTML += error.message;
|
|
});
|
|
} |