iobroker.sprinklecontrol
Version:
Control of several sprinklers depending on weather conditions and pump performance.
967 lines (913 loc) • 44.3 kB
JavaScript
;
const myConfig = require('./myConfig.js');
const addTime = require('./tools.js').addTime;
const formatTime = require('./tools').formatTime;
const sendMessageText = require('./sendMessageText.js'); // sendMessageText
/**
* The adapter instance
*/
let adapter;
/**
* Thread-list
* → Auflistung aller aktiver Sprenger-Kreise
*/
const threadList = [];
/** bereit für Boost */
let boostReady = true;
/** Boost ist aktive */
let boostOn = false;
/** Timer für die Länge des */
let boostListTimer;
/** maximal zulässige Anzahl der eingeschalteten Ventile */
let maxParallel = 0;
/** Füllstand der Zisterne */
let fillLevelCistern = 0;
let statusCistern = '';
/** Speicherort von setTimeout */
let idValveDelayTime;
const currentPumpUse = {
/** Pumpen aktive? */
enable: false,
/** Zisterne aktive? */
pumpCistern: false,
/** Pumpen-Bezeichnung; z.B. "hm-rpc.0.MEQ1810129.1.STATE" */
pumpName: '',
/** Pumpenleistung in l/h */
pumpPower: 0
};
const updateListMarker = {funcActive: false, newStart: false};
/*==============================================================================================================================================*/
/* interne Funktionen */
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/**
* Sprinkle (sprinkleName) delete
* → Ventil (sprinkleName) löschen
*
* @param killList - sprinkleName der zu löschenden Objekte
*/
function delList (killList) {
for(const sprinkleName of killList) {
let bValveFound = false; // Ventil gefunden
for(let counter = 0, // Loop über das Array
lastArray = (threadList.length - 1); // entsprechend der Anzahl der Eintragungen
counter <= lastArray;
counter++) {
const entry = threadList[counter].sprinkleName;
if ((sprinkleName === entry) || bValveFound) {
if (sprinkleName === entry) bValveFound = true;
if (counter !== lastArray) threadList[counter] = threadList[counter + 1];
}
}
/* If a valve is found, delete the last array (entry). Wenn Ventil gefunden letzten Array (Auftrag) löschen */
if (bValveFound) {
threadList.pop();
adapter.log.debug(`delList=> order deleted ID: ${sprinkleName} ( rest orders: ${threadList.length} )`);
}
}
} // End delList
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/**
* Activation of the booster (all other active valves are deactivated for the duration of the boost so that the sprinkler can be extended with the maximum possible pressure)
* => aktivierung des Boosters (alle anderen aktiven Ventile werden für die Zeit des Boosts deaktiviert um den maximalen möglichen Druck zum Ausfahren der Sprenger zu ermöglichen)
*
* @param sprinkleID
*/
function boostList (sprinkleID) {
boostReady = false;
boostOn = true;
for(const entry of threadList) {
if (entry.enabled) {
if (entry.sprinkleID === sprinkleID) { // Booster
/* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, < 3 > break, <<< 4 >>> Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 4,
ack: true
});
//valveState(entry, 'Boost(on)');
entry.times.boostTime2 = setTimeout(() => {
/* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 2,
ack: true
});
entry.times.boostTime2 = null;
},31000);
} else { // rest der Ventile
// in die Zwangspause (myBreak = true)
entry.times.boostTime1 = setTimeout(() => {
/* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), <<< 5 >>> off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 5,
ack: true
});
entry.myBreak = true;
// valveOnOff(entry, false, '#2.1 Set: off(Boost), ID: ');
entry.times.boostTime1 = null;
},250);
// aus der Zwangspause holen (myBreak = false)
entry.times.boostTime2 = setTimeout(() => {
/* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 2,
ack: true
});
// valveOnOff(entry, true, '#2.2 Set: on, ID: ');
entry.myBreak = false;
entry.times.boostTime2 = null;
},31000);
}
}
}
boostListTimer = setTimeout(() => {
boostOn = false;
updateList();
},32000);
} // End boostList
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/**
* If boostOn is ended by entering "runningTime = 0", normal operation should be restored. (Delete timer)
* → Wenn boostOn über die Eingabe "runningTime = 0" beendet wird, so soll zum Normalen ablauf wieder zurückgekehrt werden. (Löschen der Timer)
*
* @param sprinkleID
*/
function boostKill (sprinkleID) {
for(const entry of threadList) {
if (entry.enabled) {
if (entry.sprinkleID === sprinkleID) {
/* booster wird gekillt */
boostOn = false;
if(entry.times.boostTime2) {
clearTimeout(entry.times.boostTime2);
entry.times.boostTime2 = null;
}
} else {
/* normaler weiterbetrieb für den Rest */
if (entry.times.boostTime1) {
clearTimeout(entry.times.boostTime1);
entry.times.boostTime1 = null;
adapter.log.debug(`boostKill => ID: ${entry.sprinkleName} => boostTime2 (Ende) gelöscht)`);
}
if (entry.times.boostTime2) {
clearTimeout(entry.times.boostTime2);
entry.times.boostTime2 = null;
/* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 2,
ack: true
});
// valveOnOff(entry, true, '#2.3 Set: on, ID: ');
}
}
}
}
} // End boostKill
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/**
* Schaltintervall der Ventile, Schaltabstand ist in der Config hinterlegt
*
* @param delay wenn keine Zeit eingegeben ist, wird der Schaltabstand der Config verwendet
*/
const valveDelay = (delay = parseInt(adapter.config.switchingDistance)) => {
return new Promise (
resolve => idValveDelayTime = setTimeout (resolve, delay)
);
};
/**
* Ausschalten der Ventile mit Schaltabstand
*
* @param threadList Auflistung aller aktiven Sprenger-Kreise
* @param parallel aktuelle Anzahl der eingeschalteten Ventile
* @returns
*/
const switchTheValvesOffOn = async (threadList, parallel) => {
/**Sammlung von.sprinkleName die am Ende von updateList gelöscht werden
* @type {array} - killList
*/
const killList = [];
for (const entry of threadList) { // ausschalten der Ventile
if ((!entry.enabled // Ventile ausgeschaltet z.B. Intervall-Beregnung
|| entry.enabled && entry.myBreak // || in Pause z.B. Boost
|| entry.killSprinkle) // || Bewässerung erledigt
&& entry.enabled !== entry.enabledState // && Ventil nicht aktuell
) {
try {
adapter.setForeignStateAsync(entry.idState, {
val: false,
ack: false
});
entry.enabledState = entry.enabled;
/* Ventil aus threadList löschen → Aufgabe beendet und sind nicht in der Pause */
if (entry.killSprinkle) {
killList.push(entry.sprinkleName);
}
adapter.log.info(`Set (${myConfig.config[entry.sprinkleID].methodControlSM}) ID: ${entry.sprinkleName}, value: ${entry.enabled}`);
await valveDelay ();
} catch (error) {
adapter.log.error(`Error(ID: ${entry.sprinkleName}): ${error}`);
}
}
}
if (currentPumpUse.pumpName !== '') {
await setPumpOnOff(parallel > 0);
await valveDelay ();
}
if (adapter.config.triggerControlVoltage) {
setVoltageOnOff(parallel>0);
await valveDelay ();
}
for (const entry of threadList) { // einschalten der Ventile
if (entry.enabled // intern eingeschaltet
&& !entry.myBreak // && keine Pause
&& !entry.killSprinkle // Bewässerung noch nicht erledigt
&& entry.enabled !== entry.enabledState // && Ventil nicht aktuell
) {
try {
adapter.setForeignState(entry.idState, {
val: true,
ack: false
});
entry.enabledState = entry.enabled;
if (myConfig.config[entry.sprinkleID].booster) {
boostReady = false;
adapter.log.debug(`ID: ${entry.sprinkleName}UpdateList sprinkle On: boostReady = ${boostReady}`);
setTimeout(() => {
boostList(entry.sprinkleID);
}, 50);
}
/* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 2,
ack: true
});
// valveOnOff(entry, true, '#2.7 Set: on, ID: ');
/* countdown starten */
if (!entry.startTime) {
entry.startTime = new Date();
}
entry.countdown = setInterval(() => {
countSprinkleTime(entry);
}, 1000); // 1000 = 1s
adapter.log.info(`Set Valve (${myConfig.config[entry.sprinkleID].methodControlSM}) ID: ${entry.sprinkleName}, value: ${entry.enabled}, duration: ${addTime(entry.wateringTime)}`);
await valveDelay ();
} catch (error) {
adapter.log.error(`Error(ID: ${entry.sprinkleName}): ${error}`);
}
}
}
delList(killList); // erledigte Bewässerungsaufgaben aus der threadList löschen
};
/**
* Handling von Ventilen, Zeiten, Verbrauchsmengen im 1s Takt
*
* @param entry
*/
function countSprinkleTime(entry) {
/* --- function beenden wenn ---*/
if (boostOn && !(myConfig.config[entry.sprinkleID].booster) // boost-On && kein aktuelles Boost-Ventil
) {
return;
}
entry.count ++;
if ((entry.count < entry.wateringTime) // Zeit noch nicht abgelaufen?
&& (!entry.calcOn // alles ausser Berechnung der Verdunstung
|| !entry.autoOn // Handbetrieb
|| (myConfig.config[entry.sprinkleID].soilMoisture.val < myConfig.config[entry.sprinkleID].soilMoisture.maxIrrigation)) // Bodenfeuchte noch nicht erreicht? (z.B. beim Regen)
) { /* Zeit läuft */
adapter.setState(`sprinkle.${entry.sprinkleName}.countdown`, {
val: addTime(entry.wateringTime - entry.count),
ack: true
});
/* Alle 15s die Bodenfeuchte anpassen */
if (entry.calcOn // Vergleich nur bei Berechnung der Verdunstung
&& !(entry.count % 15) // alle 15s ausführen
) {
myConfig.addSoilMoistVal(entry.sprinkleID, entry.soilMoisture15s);
}
/* Intervall-Beregnung wenn angegeben (onOffTime > 0) */
if ((entry.onOffTime > 0) && !(entry.count % entry.onOffTime)) {
adapter.log.info(`Intervall-Beregnung, onOffTime: ${entry.onOffTime}, count: ${entry.count}`);
entry.enabled = false;
entry.myBreak = true;
/* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, <<< 3 >>> break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 3,
ack: true
});
updateList();
clearInterval(entry.countdown);
entry.onOffTimeoutOff = setTimeout(()=>{
entry.myBreak = false;
/* Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 1,
ack: true
});
updateList();
},1000 * (entry.onOffTime < 600 ? 600 : entry.onOffTime)); // 600 sek Pause (10 min)
}
} else { /* zeit abgelaufen => Ventil ausschalten */
/* Zustand des Ventils im Thread <<< 0 >>> off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
entry.enabled = false;
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 0,
ack: true
});
adapter.setState(`sprinkle.${entry.sprinkleName}.runningTime`, {
val: '0',
ack: true
});
adapter.setState(`sprinkle.${entry.sprinkleName}.countdown`, {
val: '0',
ack: true
});
/* Wenn in der Konfiguration Bodenfeuchte = 100% gesetzt ist und Auto-Bewässerung aktive, dann Bodenfeuchte = 100% setzen*/
if (entry.autoOn && entry.calcOn && myConfig.config[entry.sprinkleID].endIrrigation) {
myConfig.setSoilMoistPct100(entry.sprinkleID);
}
/* Verbrauchswerte in der Historie aktualisieren */
addConsumedAndTime(entry);
/* Booster zurücksetzen */
if (myConfig.config[entry.sprinkleID].booster) {
if (boostOn) {
boostKill(entry.sprinkleID);
}
boostReady = true;
adapter.log.debug(`ID: ${entry.sprinkleName} UpdateList Sprinkle Off: boostReady = ${boostReady}`);
}
/* Zeiten löschen */
clearInterval(entry.countdown);
/*clearTimeout(entry.onOffTimeoutOn);*/
clearTimeout(entry.onOffTimeoutOff);
/* Ventil aus threadList löschen → Aufgabe beendet */
//delList(entry.sprinkleName);
entry.killSprinkle = true;
updateList();
}
}
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/**
* Control of the active irrigation circuits so that the maximum pump capacity (l / h) is achieved and the maximum number of irrigation circuits is not exceeded.
* => Steuerung der aktiven Bewässerungskreise, damit die maximale Pumpenkapazität (l / h) erreicht wird und die maximale Anzahl der Bewässerungskreise nicht überschritten wird.
*/
function updateList () {
/* während des Boost eines Kreises ist ein zuschalten von Sprengern nicht möglich */
if (boostOn) {
return;
}
if (updateListMarker.funcActive === true) {
updateListMarker.newStart = true;
return;
}
updateListMarker.funcActive = true;
/** aktuelle Rest-Pumpenleistung */
let curFlow = currentPumpUse.pumpPower, /* adapter.config.triggerMainPumpPower; */
/** aktuelle Anzahl der eingeschalteten Ventile */
parallel = 0;
/**
* Sortierfunktion mySortDescending absteigende Sortierung
*
* @param a
* @param b
* @returns
*/
function mySortDescending(a, b) {
return (a.pipeFlow > b.pipeFlow) ? -1 :
(a.pipeFlow < b.pipeFlow) ? 1 :
0;
}
/**
* Sortierfunktion mySortAscending aufsteigende Sortierung
*
* @param a
* @param b
* @returns
*/
function mySortAscending(a, b) {
return (a.pipeFlow < b.pipeFlow) ? -1 :
(a.pipeFlow > b.pipeFlow) ? 1 :
0;
}
// ermitteln von curPipe und der anzahl der parallelen Stränge
for(const entry of threadList){
if (entry.enabled && !entry.killSprinkle) {
curFlow -= entry.pipeFlow; // // ermitteln der RestFörderkapazität
parallel ++; // Anzahl der Bewässerungsstellen um 1 erhöhen
}
}
if (curFlow < 0) {
/* - wenn beim Umschalten der Pumpen die Förderleistung zu gering → Ventile deaktivieren - */
// aufsteigend sortieren nach der Verbrauchsmenge
threadList.sort(mySortAscending);
for(const entry of threadList) {
if (entry.enabled // eingeschaltet
&& !entry.killSprinkle // && Aufgabe noch nicht erledigt
&& (curFlow < 0) // && Förderleistung der Pumpe zu gering
) {
entry.enabled = false; // ausgeschaltet merken
clearInterval(entry.countdown); // Zähler für Countdown, Verbrauchsmengen, usw. löschen
curFlow += entry.pipeFlow; // ermitteln der RestFörderkapazität
parallel--; // Anzahl der Bewässerungsstellen um 1 verringern
/* Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 1,
ack: true
});
// valveOnOff(entry, false, '#2.6 Set: wait, ID: ');
adapter.log.info(`Set Valve ID: ${entry.sprinkleName} Pump delivery rate too low, wait! curFlow ${curFlow} parallel: ${parallel}`);
}
}
}
// absteigend sortieren nach der Verbrauchsmenge
threadList.sort(mySortDescending);
// einschalten der Bewässerungsventile nach Verbrauchsmenge und maximaler Anzahl
for(const entry of threadList) {
// adapter.log.info(`Ventile Ein: enabled: ${!entry.enabled} && killSprinkle: ${!entry.killSprinkle} && myBreak: ${!entry.myBreak} && Flow: ${(curFlow >= entry.pipeFlow)} && ||: ${(parallel < maxParallel)} && noBoost: ${((boostReady) || !(myConfig.config[entry.sprinkleID].booster))}`);
if (!entry.enabled // ausgeschaltet
&& !entry.killSprinkle // && Aufgabe noch nicht erledigt
&& !entry.myBreak // && nicht in der Pause
&& (curFlow >= entry.pipeFlow) // && noch genügend Förderleistung der Pumpe
&& (parallel < maxParallel) // && maxParallel noch nicht erreicht
&& ((boostReady) || !(myConfig.config[entry.sprinkleID].booster)) // nur einer mit boostFunction darf aktive sein
) {
entry.enabled = true; // einschalten merken
curFlow -= entry.pipeFlow; // ermitteln der RestFörderkapazität
parallel++; // Anzahl der Bewässerungsstellen um 1 erhöhen
}
}
adapter.setState('control.parallelOfMax', {
val: `${parallel} : ${maxParallel}`,
ack: true
});
adapter.setState('control.restFlow', {
val: `${curFlow} (${currentPumpUse.pumpPower} ${currentPumpUse.pumpCistern ? 'Zisterne' : 'Grundwasser'})`,
ack: true
});
switchTheValvesOffOn(threadList, parallel).then(err => {
if (err) {
adapter.log.error(`Error - Set (false) err: ${err}`);
sendMessageText.sendMessage(`Error - Set (false) err: ${err}`);
}
});
updateListMarker.funcActive = false;
if (updateListMarker.newStart === true) {
updateListMarker.newStart = false;
setTimeout(updateList(), 50);
}
} // End updateList
/* --------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
/**
* +++++ Set the current pump for irrigation +++++
* → Festlegen der aktuellen Pumpe zur Bewässerung
*/
function setActualPump () {
if (adapter.config.cisternSettings === true) {
/* Zisternen-Bewässerung Einstellung in der config (2. Pumpe) aktiviert */
if (currentPumpUse.enable === true) {
/* Bewässerungspumpen aktiv */
if ((fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) && (currentPumpUse.pumpCistern === true)) {
/* (Zisterne unter Minimum) && (ZisternenPumpe läuft) */
adapter.setForeignState(currentPumpUse.pumpName, {
val: false,
ack: false
}); // Pumpe Zisterne Aus
if (currentPumpUse.pumpCistern === true && !sendMessageText.onlySendError()) {
sendMessageText.sendMessage(`Pump change (cistern empty) Cistern pump off => main pump on`);
}
currentPumpUse.pumpCistern = false;
currentPumpUse.pumpName = adapter.config.triggerMainPump || '';
currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
currentPumpUse.leadTime = parseInt(adapter.config.mainPumpLeadTime)*1000 || 1000;
adapter.setForeignState(currentPumpUse.pumpName, {
val: true,
ack: false
}); // Hauptpumpe Ein
adapter.log.info('Pump change (cistern empty) Cistern pump off => main pump on');
updateList(); // Wasserverbrauch an Pumpenleistung anpassen
}
if (fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) {
statusCistern = `Cistern empty: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel + 5} %)`;
adapter.setState('info.cisternState', {
val: statusCistern,
ack: true
});
} else {
statusCistern = `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`;
adapter.setState('info.cisternState', {
val: statusCistern,
ack: true
});
}
} else {
/* Bewässerungspumpen inaktiv */
if ((fillLevelCistern >= (parseFloat(adapter.config.triggerMinCisternLevel) + 5)) && (adapter.config.triggerCisternPump) && (adapter.config.triggerCisternPumpPower)) {
/* Zisterne voll && triggerCisternPump && triggerCisternPumpPower vorhanden*/
statusCistern = `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`;
adapter.setState('info.cisternState', {
val: statusCistern,
ack: true
});
if (currentPumpUse.pumpCistern === false && !sendMessageText.onlySendError()) {
sendMessageText.sendMessage(`Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`);
}
currentPumpUse.pumpCistern = true;
currentPumpUse.pumpName = adapter.config.triggerCisternPump || '';
currentPumpUse.pumpPower = parseInt(adapter.config.triggerCisternPumpPower);
currentPumpUse.leadTime = parseInt(adapter.config.cisternPumpLeadTime)*1000 || 1000;
adapter.setState('control.restFlow', {
val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} Zisterne)`,
ack: true
});
} else {
statusCistern = `Cistern empty: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel + 5} %)`;
adapter.setState('info.cisternState', {
val: statusCistern,
ack: true
});
currentPumpUse.pumpCistern = false;
currentPumpUse.pumpName = adapter.config.triggerMainPump || '';
currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
currentPumpUse.leadTime = parseInt(adapter.config.mainPumpLeadTime)*1000 || 1000;
adapter.setState('control.restFlow', {
val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} Grundwasser)`,
ack: true
});
}
}
} else {
/* Pumpe AUS => Zisternen-Bewässerung nicht aktiviert */
if (adapter.config.triggerCisternPump) {
statusCistern = `Cistern settings are not active!${(fillLevelCistern > 0)?(` level sensor: ${fillLevelCistern}%${(adapter.config.triggerMinCisternLevel !== '')?(`${adapter.config.triggerMinCisternLevel}%`):('')}`):('')}`;
adapter.setState('info.cisternState', {
val: statusCistern,
ack: true
});
}
}
} // End setActualPump
/**
* Switching the pump on or off
* => Ein bzw. ausschaltern der Pumpe
*
* @param pumpOnOff ; Pumpe on = true
*/
const setPumpOnOff = async (pumpOnOff) => {
clearTimeout(idValveDelayTime);
if (currentPumpUse.pumpName !== '') {
try {
const curPumpState = await adapter.getForeignStateAsync(currentPumpUse.pumpName);
if (curPumpState) {
if (pumpOnOff) {
if (curPumpState.val === false) {
adapter.setForeignState(currentPumpUse.pumpName, {
val: true,
ack: false
});
currentPumpUse.enable = true;
adapter.log.info(`Set (pump) on, wait ${currentPumpUse.leadTime}ms`);
await valveDelay(currentPumpUse.leadTime);
}
} else {
if (curPumpState.val !== false) {
adapter.setForeignState(currentPumpUse.pumpName, {
val: false,
ack: false
});
currentPumpUse.enable = false;
adapter.log.info('Set (pump) off');
}
}
}
} catch (error) {
adapter.log.error(`triggerMainPump ${currentPumpUse.pumpName} is not available (ist nicht erreichbar): ${error}`);
}
}
} // End setPumpOnOff
/**
* Switching the control voltage on or off
* => Ein bzw. ausschaltern der Steuerspannung
*
* @param voltageOnOff - Voltage on = true
*/
function setVoltageOnOff(voltageOnOff) {
if (adapter.config.triggerControlVoltage !== '') {
adapter.getForeignState(adapter.config.triggerControlVoltage, (err, state) => {
if (state) {
if (voltageOnOff) {
if (state.val === false) {
adapter.setForeignState(adapter.config.triggerControlVoltage, {
val: true,
ack: false
});
adapter.log.info('Set (voltage) on');
}
} else {
if (state.val !== false) {
adapter.setForeignState(adapter.config.triggerControlVoltage, {
val: false,
ack: false
});
adapter.log.info('Set (voltage) off');
}
}
} else if (err) {
adapter.log.error(`triggerControlVoltage is not available (ist nicht erreichbar): ${err}`);
}
});
}
}
/**
* Adding the consumption data to the history
* => Hinzufügen der Verbrauchsdaten zur History
*
* @param entry - array mit den Daten des aktiven Ventils
*/
function addConsumedAndTime(entry) {
adapter.setState(`sprinkle.${entry.sprinkleName}.history.lastConsumed`, {
val: Math.round(entry.litersPerSecond * entry.count),
ack: true
});
adapter.setState(`sprinkle.${entry.sprinkleName}.history.lastRunningTime`, {
val: addTime(entry.count),
ack: true
});
adapter.setState(`sprinkle.${entry.sprinkleName}.history.lastOn`, {
val: formatTime(adapter, entry.startTime, 'dd.mm. hh:mm'),
ack: true
});
adapter.getState(`sprinkle.${entry.sprinkleName}.history.curCalWeekConsumed`, (err, state) => {
if (state) {
adapter.setState(`sprinkle.${entry.sprinkleName}.history.curCalWeekConsumed`, {
val: (state.val) + Math.round(entry.litersPerSecond * entry.count),
ack: true
});
}
});
adapter.getState(`sprinkle.${entry.sprinkleName}.history.curCalWeekRunningTime`, (err, state) => {
if (state) {
adapter.setState(`sprinkle.${entry.sprinkleName}.history.curCalWeekRunningTime`, {
val: addTime(state.val, entry.count),
ack: true
});
}
});
} // End addConsumedAndTime
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
/* externe Funktionen */
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
/**
* -- externe Funktionen -> initValveControl - addList - clearEntireList - setFillLevelCistern --
*
*/
const valveControl = {
/**
* Initialize the start configuration of ventilControl
* => Initialisieren Sie die Startkonfiguration von ventilControl
*
* @param myAdapter
*/
initValveControl (myAdapter) {
adapter = myAdapter;
currentPumpUse.pumpCistern = false;
currentPumpUse.pumpName = adapter.config.triggerMainPump || '';
currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower) || 0;
currentPumpUse.leadTime = parseInt(adapter.config.mainPumpLeadTime)*1000 || 1000;
maxParallel = parseInt(adapter.config.maximumParallelValves);
/* Objekt control.restFlow befüllen */
adapter.setState('control.restFlow', {
val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} ${currentPumpUse.pumpCistern ? 'Zisterne' : 'Grundwasser'})`,
ack: true
});
/* Objekt control.parallelOfMax befüllen */
adapter.setState('control.parallelOfMax', {
val: `0 : ${adapter.config.maximumParallelValves}`,
ack: true
});
/* 24V ausschalter, wenn vorhanden */
if (adapter.config.triggerControlVoltage !== '') {
adapter.getForeignState('adapter.config.triggerControlVoltage', (err, state) => {
if (state && state.val === true) {
adapter.setForeignState(adapter.config.triggerControlVoltage, {
val: false,
ack: false
});
}
});
}
/* Pumpe ausschalter, wenn vorhanden */
if (adapter.config.triggerMainPump !== '') {
adapter.getForeignState('adapter.config.triggerMainPump', (err, state) => {
if (state && state.val === true) {
adapter.setForeignState(adapter.config.triggerMainPump, {
val: false,
ack: false
});
}
});
}
/* Pumpe (Zisterne) ausschalter, wenn vorhanden */
if (adapter.config.triggerCisternPump !== '') {
adapter.getForeignState('adapter.config.triggerCisternPump', (err, state) => {
if (state && state.val === true) {
adapter.setForeignState(adapter.config.triggerCisternPump, {
val: false,
ack: false
});
}
});
}
/* alle Ventile (.name = "hm-rpc.0.MEQ1234567.3.STATE") in einem definierten Zustand (false) versetzen*/
const result = adapter.config.events;
if (result) {
for (const res of result) {
adapter.getForeignState(res.name, (err, state) => {
if (state && state.val === true) {
adapter.setForeignState(res.name, {
val: false,
ack: false
});
}
});
}
}
}, // End initValveControl
/**
* Add Sprinkle→ Sprinkle hinzufügen
*
* @param sprinkleList
* @param sprinkleList[].auto - auto → Automatik == (true), Handbetrieb == (false)
* @param sprinkleList[].sprinkleID - sprinkleID → zugriff auf myConfig.config[sprinkleID]. xyz
* @param sprinkleList[].wateringTime - wateringTime → Bewässerungszeit in min
*/
addList (sprinkleList) {
//
for (const res of sprinkleList) {
const sprinkleName = myConfig.config[res.sprinkleID].objectName;
/**
* add done
* → hinzufügen erledigt (Sprenger bereits aktive)
*/
let addDone = false;
// schauen ob der Sprenger schon in der threadList ist
if (threadList) {
for (const entry of threadList) {
if (entry.sprinkleID === res.sprinkleID) {
if (entry.wateringTime === res.wateringTime) {
// adapter.setState('sprinkle.' + sprinkleName + '.runningTime', {val: addTime(wateringTime, ''), ack: false});
return;
}
entry.wateringTime = res.wateringTime;
entry.autoOn = res.auto; // auto: = true autostart; = false Handbetrieb
adapter.setState(`sprinkle.${sprinkleName}.runningTime`, {
val: addTime(res.wateringTime),
ack: true
});
addDone = true; // Sprinkle found
adapter.log.debug(`update ID: ${entry.sprinkleName} new time: ${addTime(res.wateringTime)}`);
// Wenn aktueller Spränger im BoostOn ausgeschaltet werden soll (ersten 30s)
if (boostOn
&& myConfig.config[res.sprinkleID].booster
&& entry.wateringTime <= 0
) {
boostKill(res.sprinkleID);
boostReady = true;
adapter.log.debug(`ID: ${res.sprinkleName} BoostKill Sprinkle Off: boostReady = ${boostReady}`);
}
break;
}
}
}
if (!addDone) {
if (res.wateringTime <= 0) {
adapter.setState(`sprinkle.${sprinkleName}.runningTime`, {
val: '0',
ack: true
});
return;
}
let pipeFlow = myConfig.config[res.sprinkleID].pipeFlow;
// Kontrolle des Ventilverbrauchs(muss kleiner als Pumpenleistung sein, da Ventil sonst nicht starten kann)
if (adapter.config.cisternSettings === true) {
if ((pipeFlow > adapter.config.triggerMainPumpPower) || (pipeFlow > adapter.config.triggerCisternPumpPower)) {
if (pipeFlow > adapter.config.triggerMainPumpPower) adapter.log.warn(`Emergency irrigation! ${sprinkleName}: Valve consumption > pump performance (main pump)`);
if (pipeFlow > adapter.config.triggerCisternPumpPower) adapter.log.warn(`Emergency irrigation! ${sprinkleName}: Valve consumption > pump performance (cistern pump)`);
pipeFlow = (adapter.config.triggerMainPumpPower < adapter.config.triggerCisternPumpPower) ? adapter.config.triggerMainPumpPower : adapter.config.triggerCisternPumpPower;
}
} else {
if (pipeFlow > adapter.config.triggerMainPumpPower) {
adapter.log.warn(`Emergency irrigation! ${sprinkleName}: Valve consumption > pump performance (main pump)`);
pipeFlow = adapter.config.triggerMainPumpPower;
}
}
const newThread = {
sprinkleID: res.sprinkleID, // Array[0...]
sprinkleName: sprinkleName, // z.B "Blumenbeet"
idState: myConfig.config[res.sprinkleID].idState, // z.B. "hm-rpc.0.MEQ1810129.1.STATE"
wateringTime: res.wateringTime, // Bewässerungszeit in s (Sekunden)
pipeFlow: pipeFlow, // Wasserverbrauch in l/h
count: 0, // Zähler im Sekundentakt
calcOn: (myConfig.config[res.sprinkleID].methodControlSM === 'calculation'), // Anpassung der Bodenfeuchte true/false
enabled: false, // Ventil softwaremäßig ein
enabledState: false, // Ventil hardwaremäßig ein
myBreak: false, // meine Pause
killSprinkle: false, // Löschauftrag ausführen am Ende in threadList
litersPerSecond: pipeFlow / 3600, // Wasserverbrauchsmenge pro Sekunde
onOffTime: myConfig.config[res.sprinkleID].wateringInterval,
autoOn: res.auto,
soilMoisture15s: 15 * (myConfig.config[res.sprinkleID].soilMoisture.maxIrrigation - myConfig.config[res.sprinkleID].soilMoisture.triggersIrrigation)
/ (60 * myConfig.config[res.sprinkleID].wateringTime),
times: {
boostTime1: null, // boost start in die Zwangspause schicken in 0,25s
boostTime2: null, // boost ende aus der Zwangspause holen nach 31s
}, // hinterlegen der verschiedenen Zeiten von timeout für gezieltes späteres löschen
id: threadList.length || 0,
};
threadList.push(newThread);
/* Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${sprinkleName}.sprinklerState`, {
val: 1,
ack: true
});
adapter.setState(`sprinkle.${sprinkleName}.runningTime`, {
val: addTime(res.wateringTime),
ack: true
});
adapter.log.debug(`ID: ${sprinkleName} new order created: ${JSON.stringify(threadList[newThread.id])}`);
}
}
updateList();
}, // End addList
/**
* switch off all devices, when close the adapter
* => Beim Beenden des adapters alles ausschalten
*/
clearEntireList () {
clearTimeout(idValveDelayTime);
setVoltageOnOff(false);
setPumpOnOff(false);
if (boostListTimer) {
boostReady = true;
boostOn = false;
clearTimeout(boostListTimer);
}
// let bValveFound = false; // Ventil gefunden
for (let counter = threadList.length - 1; // Loop über das Array
counter >= 0;
counter--) {
const entry = threadList[counter];
if (entry.enabledState) {
adapter.log.info(`Set Valve (SprinkleControl: off) ID: ${entry.sprinkleName}, value: false`);
adapter.setForeignState(entry.idState, {
val: false,
ack: false
});
}
/* Zustand des Ventils im Thread <<< 0 >>> off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
val: 0,
ack: true
});
adapter.setState(`sprinkle.${entry.sprinkleName}.runningTime`, {
val: '0',
ack: true
});
adapter.setState(`sprinkle.${entry.sprinkleName}.countdown`, {
val: '0',
ack: true
});
// valveOnOff(myEntry, false, '#2.0 Set: off, ID: ');
/* Verbrauchswerte in der Historie aktualisieren */
addConsumedAndTime(entry);
/* del timer countdown */
clearInterval(entry.countdown);
/* del timer onOffTimeoutOff */
clearTimeout(entry.onOffTimeoutOff);
/* del timer newThread.times.boostTime1 */
if (entry.times.boostTime1) {
clearTimeout(entry.times.boostTime1);
entry.times.boostTime1 = null;
}
/* del timer newThread.times.boostTime2 */
if (entry.times.boostTime2) {
clearTimeout(entry.times.boostTime2);
entry.times.boostTime2 = null;
}
adapter.log.debug(`order deleted Stop all ID: ${entry.sprinkleName} ( rest orders: ${threadList.length} )`);
threadList.pop(); // del last array
}
updateList();
}, // End clearEntireList
/**
* Änderungen des Füllstands setzen + Vorrang der Pumpe setzen
*
* @param levelCistern
*/
setFillLevelCistern (levelCistern) {
fillLevelCistern = (typeof levelCistern === 'number') ? levelCistern : 0;
setActualPump();
}, // End setFillLevelCistern
getStatusCistern () {
return statusCistern;
}
}; // End valveControl
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
module.exports = valveControl;