iobroker.sprinklecontrol
Version:
Control of several sprinklers depending on weather conditions and pump performance.
1,091 lines (1,021 loc) • 97.2 kB
JavaScript
'use strict';
const asyncTime = require('node:timers/promises');
// import {setTimeout} from 'timers/promises';
const myConfig = require('./myConfig.js');
const tools = require('./tools.js').tools;
const sendMessageText = require('./sendMessageText.js'); // sendMessageText
/**
* The adapter instance
*/
let adapter;
/**
* Thread-list
* → Auflistung aller aktiver Sprenger-Kreise
*/
const threadList = [];
/**
* Ventil mit dem größten Durchfluss (pipeFlow) für die Steuerung des Druckentlastungsventils
* - wird in der Funktion updateList() ermittelt
*/
const pressureReliefValve = {
enable: false,
name: '',
wateringTime: 10,
ac: {},
controller: {},
control: {
idState: undefined,
idON_TIME: undefined,
idACK: undefined,
maker: undefined
}
};
/**
* - bereit zum Boost (true: kein Boostventil aktive; false: BoostVentil aktive)
*/
let boostReady = true,
/**
* - Boost aktive
*/
boostOn = false,
/**
* maximal zulässige Anzahl der eingeschalteten Ventile
*/
maxParallel = 0,
/**
* Füllstand der Zisterne
*/
fillLevelCistern = 0,
/**
* Zeitliche Bewässerungseinschränkung EIN/AUS
* z.B. Behördenvorgabe, Sommerhitze + Trockenheit, usw.
*/
timeBasedRestrictionEn = false;
const updateListMarker = {
funcActive: false,
newStart: false,
switchingDistance: 5000,
cancelSwitchingDistance: {}
};
/**
* aktive Pumpendaten
* - enable: Pumpe ein/ausgeschaltet
* - pumpSwitching: Umschaltung aktive, während der Umschaltung von Zysterne auf Hauptpumpe bei aktiver Pumpe
* - name: Bezeichnung der Pumpe
* - idState: Objektname des State
* - id: ID des States unter Objecte im ioBroker info.cisternPump || info.mainPump
* - intBreak: Pause (Zisterne leer)
* - pumpCistern: ZisternenPumpe aktiv bei Verwendung zweier Pumpen
* - leadTime: Vorlaufzeit
* - cancelLeadTime: Vorlaufzeit abbrechen
* - pumpPower: Maximalleistung der Pumpe
* - restFlow: aktuelle LeistungsReserve der Pumpe
* - controller: Rückmeldung ack
* - ac: AbortController
*/
const currentPumpUse = {
enable: false,
pumpSwitching: false,
pumpReplacement: false,
name: '',
wateringTime: 0,
id: '',
pumpCistern: false,
intBreak: false,
leadTime: 0,
cancelLeadTime: {},
pumpPower: 0,
restFlow: 0,
ac: {},
controller: {},
control: {
idState: undefined,
idON_TIME: undefined,
idACK: undefined,
maker: undefined
}
};
let mainPumpControl = {
idState: undefined,
idON_TIME: undefined,
idACK: undefined,
maker: undefined
};
let cisternPumpControl = {
idState: undefined,
idON_TIME: undefined,
idACK: undefined,
maker: undefined
};
/**
* Steuerspannung 24V
* - enable: 24V ein/ausgeschaltet
* - name: Bezeichnung 24V
* - idState: Aktorerkennung "hm-rpc.0.MEQ1810129.1.STATE"
* - controller: controlle von Zeiten und Abbruchsignalen
*/
const controlVoltage = {
enable: false,
name: '24V',
wateringTime: 0,
ac: {},
controller: {},
control: {
idState: undefined,
idON_TIME: undefined,
idACK: undefined,
maker: undefined
}
};
/**
* Sensor Druck
* - enable: Sensor vorhanden
* - active: true = Sensor aktiv, false = Sensor inaktiv
* - setActive(true/false): Funktion zum setzen des Sensorstatus aktiv/inaktiv
* - inadequatePressure: true = Nicht ausreichender Druck, false = ausreichender Druck
* - name: Bezeichnung Sensor Druck
* - value: aktueller Wert des Sensors
* - state: aktueller Zustand des Sensors
* - ac: AbortController für die Überwachung der Sensoraktivität
*/
const sensorPressure = {
enable: false,
active: false,
setActive: async(val) => {
try {
sensorPressure.active = val;
if (val === true && sensorPressure.enable) {
await valveControl.setSensorPressure(sensorPressure.value);
} else {
if (sensorPressure?.ac !== null) {
await sensorPressure.ac.abort(); // AbortController zurücksetzen
sensorPressure.ac = null;
sensorPressure.inadequatePressure = false; // Drucksensor deaktiviert -> Druckverhältnisse wieder in Ordnung
}
}
} catch (error) {
adapter.log.error(`Error setting sensor pressure active ${val}: ${error}`);
}
},
inadequatePressure: false,
name: 'Sensor Pressure',
value: 0,
state: 0,
ac: null
};
/**
* Schaltabstand in ms
* - Schaltabstand zwischen den Ventilen
* - 250 ms - 240 000 ms (4 min)
*/
let switchingDistanceMS = 250;
/*==============================================================================================================================================*/
/* interne Funktionen */
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/**
* Schalten der Ventile
*
* @param {object} thread -Objekt des Ventils
* @param {boolean} val - neuer Zustand des Ventils
* @returns {Promise<string|undefined>}
*/
const setValve = async (thread, val) => {
// beim Schalten im guten Vertrauen (Befehl ohne Antwort)
if (adapter.config.switchingBehavior === 'noResponse') {
try {
await adapter.setForeignStateAsync(thread.control.idState, {
val: val,
ack: false
});
thread.enable = val;
if (thread.name !== 'Cistern pump' && thread.name !== 'Main pump' && thread.name !== '24V' && thread.name !== 'no Pump') {
adapter.setStateAsync(`sprinkle.${thread.name}.sprinklerState`, {
val: thread.state,
ack: true
});
adapter.setStateAsync(`sprinkle.${thread.name}.valveOn`, {
val: thread.enable,
ack: true
});
}
adapter.log.info(`setValve ${ thread.name }: ${ val }, ${ thread.wateringTime > 0 && val === true ? `${ tools.addTime(thread.wateringTime,'') }, ` : '' }`);
return thread.control.idState;
} catch (error) {
thread.enable = false;
thread.state = 'Error'
throw new Error(`setValve ${thread.name} error: ${error}`);
}
}
const startTime = new Date();
let result = undefined;
let updateStr = '';
thread.ac.acSetValveCancelTimeout = new AbortController;
/**
* Sprinkle geschaltet
*
* @param {{val:boolean,ack:boolean}} state
*/
thread.controller.ackTrue = (state) => {
if (
(thread.control.maker === 'standard') && (state?.val === val)
|| (thread.control.maker === 'HM') && (val ? state?.val === true : state?.val === false)
|| (thread.control.maker === 'HmIP') && (val ? state?.val === 1 : state?.val === 0)
) {
adapter.log.debug(`setValve ${thread.name} => ackTrue`);
result = thread.control.idState;
thread.enable === val ? updateStr = ' (update)' : thread.enable = val;
thread.ac.acSetValveCancelTimeout.abort();
} else {
adapter.log.debug(`setValve ${thread.name} => check ackTrue!
if => maker ${ thread.control.maker } === 'standard' && ${state?.val} === ${val}
|| ${thread.control.maker} === 'HM' && ${val} ? ${state?.val} === true : ${state?.val} === false
|| ${thread.control.maker} === 'HmIP' && ${val} ? ${state?.val} === 1 : ${state?.val} === 0}
`);
}
};
//adapter.log.info(`Set Valve (async () => {...}`);
try {
// ON_Time setzen wenn angegeben, damit das Ventil nach der angegebenen Zeit automatisch ausgeschaltet wird (z.B. wenn der Ausschaltbefehl nicht ausgeführt werden kann, z.B Funkstörung)
if (thread.control.idON_TIME !== null) {
await adapter.setForeignStateAsync(thread.control.idON_TIME, {
val: val ? Math.ceil(thread.wateringTime + 5) : 0,
ack: false
});
await asyncTime.setTimeout(200, undefined, undefined);
}
// Ventil ansteuern
const _setValve = await adapter.setForeignStateAsync(thread.control.idState, {
val: val,
ack: false
});
if (_setValve === thread.control.idState) { // Auftrag ausgeführt
await asyncTime.setTimeout(3000, undefined, { signal: thread.ac.acSetValveCancelTimeout.signal }); // max. 3s warten auf Rückmeldung ackTrue
const _getValve = await adapter.getForeignStateAsync(thread.control.idState);
if (_getValve?.val === val
) {
thread.enable === val ? updateStr = ' (update)' : thread.enable = val;;
return thread.control.idState;
} else {
throw new Error(` > was not switched! (${val}) Check the device! Reply: ${JSON.stringify(_getValve)}`);
}
} else {
throw new Error(` command could not be sent`);
}
} catch (error) {
// thread.controller.ackTrue wurde ausgelöst
if (error.name !== `AbortError`) {
await adapter.setForeignStateAsync(thread.control.idState, {
val: false,
ack: false
});
thread.enable = false;
thread.state = 'Error'
if (adapter.config.notificationEnabled) {
sendMessageText.sendMessage(`setValve ${thread.name} (${thread.control.idState}) ${error}`);
}
throw new Error(` > set Valve ${error}`);
}
} finally {
adapter.log.info(`setValve ${thread.name}:
${val},
${thread.control.maker !== 'standard' ? `${thread.control.maker}, ` : ''}
${ (thread.wateringTime > 0 && val === true && thread.name !== 'Cistern pump' && thread.name !== 'Main pump' && thread.name !== '24V')
? `${ tools.addTime(thread.wateringTime,'') }${ updateStr }, ` : ''}
processing time: ${(+new Date()) - +startTime}ms
`);
if (thread.ac.acSetValveCancelTimeout.aborted === false) thread.ac.acSetValveCancelTimeout.abort();
}
try {
if (thread.name !== 'Cistern pump' && thread.name !== 'Main pump' && thread.name !== '24V' && thread.name !== 'no Pump') {
await adapter.setStateAsync(`sprinkle.${thread.name}.sprinklerState`, {
val: thread.state,
ack: true
});
adapter.setStateAsync(`sprinkle.${thread.name}.valveOn`, {
val: thread.enable,
ack: true
});
}
} catch (error) {
adapter.log.error(`setValve ${thread.name} => Error setting sprinklerState or valveOn: ${error}`);
}
return result;
};
/**
* Sprinkle (name) delete
* → Ventil (name) löschen
*
* @param {Array.<{name: string}>} killList
* @returns {Promise}
*/
const delList = async (killList) => {
new Promise((resolve, reject) => {
const badList = [];
for (const name 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].name;
if ((name === entry) || bValveFound) {
if (name === 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: ${name} ( rest orders: ${threadList.length} )`);
}else{
badList.push(name);
}
}
if (badList.length > 0) {
reject(`delList: could not find ${JSON.stringify(badList)}`);
} else {
resolve;
}
});
}; // End delList
/**
* currentConsumption aktueller Verbrauch ermitteln
* - curFlow: aktuelle Restfördertleistung der Pumpe
* - parallel: aktuelle Anzahl der eingeschalteten Ventile
* - pumpRequired: Pumpe erforderlich (true/false)
*
* @param {boolean} write
* @returns {Promise<{curFlow:number, parallel:number, pumpRequired:boolean}>}
*/
const currentConsumption = async (write) => {
try {
let curFlow = (currentPumpUse.intBreak || sensorPressure.inadequatePressure) ? 0 : currentPumpUse.pumpPower, /* adapter.config.triggerMainPumpPower; */
/** aktuelle Anzahl der eingeschalteten Ventile */
parallel = 0,
pumpRequired = false;
adapter.log.debug(`currentConsumption curFlow: ${curFlow}, ${currentPumpUse.intBreak} => ${currentPumpUse.intBreak ? 0 : currentPumpUse.pumpPower}`);
//if (!currentPumpUse.intBreak
// && !sensorPressure.inadequatePressure
//) {
// ermitteln von curPipe und der Anzahl der parallelen Stränge
for (const entry of threadList){
if (entry.state === 'wait') pumpRequired = true; // state => wait
if (entry.enable === true // Ventil eingeschaltet
&& entry.extBreak === false // && nicht in der externen Pause (extBreak)
&& timeBasedRestrictionEn === false // && nicht in der zeitlichen Bewässerungspause
) {
curFlow -= entry.pipeFlow; // // ermitteln der RestFörderkapazität
parallel ++; // Anzahl der Bewässerungsstellen um 1 erhöhen
pumpRequired = true;
}
}
//}
// bei write schreiben der aktuellen Förderleistung der Pumpe und der Anzahl der parallelen Stränge
if (write || currentPumpUse.intBreak) {
adapter.setStateAsync('control.parallelOfMax', {
val: `${parallel} : ${maxParallel}`,
ack: true
});
adapter.setStateAsync('control.restFlow', {
val: `${curFlow} (${currentPumpUse.pumpPower} | ${currentPumpUse.name})`,
ack: true
});
}
return {
curFlow: curFlow,
parallel: parallel,
pumpRequired: pumpRequired
};
} catch (error) {
adapter.log.error(`currentConsumption Error: ${error}`);
return {
curFlow: 0,
parallel: 99,
pumpRequired: false
};
}
};
/** Interval-Beregnung aus */
const onOffTimeoutOff = async (entry) => {
entry.ac.acOnOffTimeoutOff = new AbortController;
try {
const res = await asyncTime.setTimeout(1000 * (entry.onOffTimeOff < 600 ? 600 : entry.onOffTimeOff),`time expired`, { signal: entry.ac.acOnOffTimeoutOff.signal }); // mindestens 600 sek Pause (10 min)
if (res === `time expired`) { // Zeit abgelaufen
entry.myBreak = false;
// Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost)
entry.state = 'wait';
adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
val: entry.state, // <<< 1 >>> wait
ack: true
});
updateList();
}
} catch (error) {
adapter.log.error(`onOffTimeoutOff error: ${error}`);
} finally {
if (entry.ac.acOnOffTimeoutOff.aborted === false) entry.ac.acOnOffTimeoutOff.abort();
}
};
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------------------------------------------------------------------------*/
/**
* Handling von Ventilen, Zeiten, Verbrauchsmengen im 1s Takt
*
* @param {object} entry
*/
const countSprinkleTime = async (entry) => {
try {
/* --- function beenden wenn ---*/
if ((boostOn && !(myConfig.config[entry.sprinkleID].booster) // boost-On && kein aktuelles Boost-Ventil
|| entry.extBreak === true // extBreak aktive
|| timeBasedRestrictionEn === true // zeitliche Bewässerungsbeschränkung aktiv
)
) {
return;
}
entry.count ++;
if ((entry.count < entry.wateringTime) // Zeit noch nicht abgelaufen?
&& (!entry.calcOn // Vergleich nur bei Berechnung der Verdunstung
|| !entry.autoOn // Vergleich nur bei Automatik
|| (myConfig.config[entry.sprinkleID].calculation.val < myConfig.config[entry.sprinkleID].calculation.maxIrrigation)) // Bodenfeuchte noch nicht erreicht? (z.B. beim Regen)
) { /* Zeit läuft */
adapter.setStateAsync(`sprinkle.${entry.name}.countdown`, {
val: tools.addTime(entry.wateringTime - entry.count, ''),
ack: true
});
/* Alle 15s die Bodenfeuchte anpassen */
if (entry.calcOn // Vergleich nur bei Berechnung der Verdunstung
&& entry.autoOn // Vergleich nur bei Automatik
&& !(entry.count % 15) // alle 15s ausführen
) {
myConfig.addSoilMoistVal(entry.sprinkleID, entry.soilMoisture15s);
}
/* Intervall-Beregnung wenn angegeben (onOffTimeOff > 0) */
if ((entry.onOffTimeOff > 0) && !(entry.count % entry.onOffTimeOn)) {
adapter.log.info(`Intervall-Beregnung, timeOn: ${entry.onOffTimeOn}s, count: ${entry.count}s, timeOff: ${entry.onOffTimeOff}s, count % onOffTime: ${entry.count % entry.onOffTimeOn}`);
const _setValveOnOff = await setValve(entry, false);
if (_setValveOnOff === entry.control.idState) {
entry.myBreak = true;
/* Zustand des Ventils im Thread <<< 3 = Pause >>> (0:off; 1:wait; 2:on; 3:break; 4:Boost(on); 5:off(Boost); 6:Cistern empty; 7:extBreak) */
entry.state = 'break';
adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
val: entry.state,
ack: true
});
clearInterval(entry.countdown);
entry.countdown = null;
currentConsumption(true);
onOffTimeoutOff(entry);
updateList();
}
}
} else { /* zeit abgelaufen => Ventil ausschalten */
/* 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].calculation.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;
boostOn = false;
adapter.log.debug(`ID: ${entry.name} UpdateList Sprinkle Off: boostReady = ${boostReady}`);
}
const _setValve = await setValve(entry, false);
/* Zustand des Ventils im Thread <<< 0 >>> off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
entry.state = 'off';
const _sprinklerState = await adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
val: entry.state,
ack: true
});
const _runningTime = adapter.setStateAsync(`sprinkle.${entry.name}.runningTime`, {
val: '00:00',
ack: true
});
const _countdown = adapter.setStateAsync(`sprinkle.${entry.name}.countdown`, {
val: '0',
ack: true
});
currentConsumption(true);
// currentConsumption(entry.pipeFlow, false);
Promise.all([
_setValve,
_sprinklerState,
_runningTime,
_countdown
]).then(async () => {
entry.killSprinkle = true;
/* Zeiten löschen */
clearInterval(entry.countdown);
entry.countdown = null;
// entry.ac.acOnOffTimeoutOff.abort(); /// ???
}).then(()=>{
updateList();
});
}
} catch (error) {
adapter.log.error(`countSprinkleTime(${entry.name}): ${error}`);
}
};
/**
* Timer zum ausschalten des Boost
*
* @param {object} entry
*/
const boostOnTimer = async (entry) => {
entry.ac.acBoostOnTimer = new AbortController;
const combinedSignal = AbortSignal.any([
entry.ac.acBoostOnTimer.signal
]);
try {
adapter.log.debug(`ID: ${entry.name} boostOnTimer time start`);
await asyncTime.setTimeout(30000, undefined, { signal: combinedSignal });
boostOn = false;
// Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost)
entry.state = 'on';
adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
val: entry.state,
ack: true
});
adapter.log.debug(`ID: ${entry.name} boostOnTimer time out: boostOn = ${boostOn}`);
updateList();
} catch (error) {
boostOn = false;
adapter.log.debug(`ID: ${entry.name} boostOnTimer time out: ERROR: ${error}`);
updateList();
}finally{
if (entry.ac.acBoostOnTimer.aborted === false) entry.ac.acBoostOnTimer.abort();
}
};
/**
* 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.
*/
const updateList = async () => {
/* 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;
}
const killList = [];
let consumption = {};
/* (timerOn = true) => Schaltabstand zwischen den Ventilen ein */
let timerOn = true;
// let switchingDistanceOn = false;
updateListMarker.funcActive = true;
/**
* Sortierfunktion mySortDescending absteigende Sortierung
*
* @param a
* @param b
* @returns {number}
*/
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 {number}
*/
function mySortAscending(a, b) {
return a.pipeFlow < b.pipeFlow ? -1 :
a.pipeFlow > b.pipeFlow ? 1 :
0;
}
// ermitteln der maximalen Laufzeit aller Ventile in der threadList
const sumOfWateringTime = Math.ceil(threadList.reduce(
(sum, entry) => sum + entry.wateringTime + (switchingDistanceMS / 1000), (currentPumpUse.leadTime + switchingDistanceMS) / 1000));
// Anpassung ON_Time wenn Laufzeit zu kurz
const adjustment = sumOfWateringTime > controlVoltage.wateringTime
&& adapter.config.switchingBehavior === 'homematic'
? true
: false;
/**
* Find das Ventil mit dem größten Durchfluss (pipeFlow) für die Steuerung des Druckentlastungsventils
* - Wenn die Bewässerung beendet wird, soll der Druck in den Leitungen durch kurzes Öffnen eines Druckentlastungsventils abgebaut werden,
* um die Lebensdauer des Systems zu erhöhen. Hierzu wird das Ventil mit dem Größten Durchfluss (pipeFlow) für 10 Sekunden angesteuert.
*/
function findPressureReliefValve() {
return myConfig.config.reduce((max, item) => {
return (item.extBreak === false
&& item.autoOn === true
&& max.pipeFlow > item.pipeFlow
) ? max : item;
}, myConfig.config[0]
);
}
/**
* Druckentlastungsventil ein wenn alle Ventile ihre Laufzeit beendet haben
* .killSprinkle = true
*/
function pressureReliefIO() {
return threadList.reduce((count, entry) => {
return (entry.myBreak === true // Intervall-Beregnung on
&& entry.killSprinkle === false // && Aufgabe noch nicht erledigt
) ? (count + 1) : count;
}, 0
);
}
/**
* Start der Steuerung der Pumpen und Ventile
*/
consumption = await currentConsumption(true);
if (consumption.pumpRequired === true) { // Pumpe erforderlich
// Spannungsversorgung einschalten bei Leistungsanforderung
try {
if (adapter.config.triggerControlVoltage
&& controlVoltage.enable === false
|| adjustment === true // Anpassung ON_Time wenn Laufzeit zu kurz, damit die 24V Versorgung während der Laufzeit nicht ausgeschaltet wird
) {
controlVoltage.wateringTime = sumOfWateringTime > controlVoltage.wateringTime ? sumOfWateringTime + 5 : controlVoltage.wateringTime; // Schaltzeit der 24V Versorgung entsprechend der maximalen Laufzeit aller Ventile in der threadList anpassen
const _controlVoltage = await setValve(controlVoltage, true);
if (_controlVoltage === controlVoltage.control.idState) {
adapter.setStateAsync(`info.supplyVoltage`, {
val: controlVoltage.enable,
ack: true
});
await asyncTime.setTimeout(switchingDistanceMS, undefined, undefined);
}
}
} catch (error) {
adapter.log.error(`Error trigger Control Voltage [${controlVoltage.control.idState}]: ${error}`);
}
// Pumpe einschalten bei Leistungsanforderung
try {
if (adapter.config.pumpSelection !== 'noPump'
&& currentPumpUse.enable === false
|| adjustment === true // Anpassung ON_Time wenn Laufzeit zu kurz, damit die Pumpe während der Laufzeit nicht ausgeschaltet wird
) {
currentPumpUse.wateringTime = sumOfWateringTime > currentPumpUse.wateringTime ? sumOfWateringTime : currentPumpUse.wateringTime; // Schaltzeit der Pumpe entsprechend der maximalen Laufzeit aller Ventile in der threadList anpassen
const _currentPumpUse = await setValve(currentPumpUse, true);
if (_currentPumpUse && _currentPumpUse === currentPumpUse.control.idState) {
timerOn = false;
adapter.setStateAsync(currentPumpUse.id, {
val: currentPumpUse.enable,
ack: true
});
await asyncTime.setTimeout(currentPumpUse.leadTime, undefined, undefined);
}
await sensorPressure.setActive(true); // Sensor Druck aktivieren, damit die Drucküberwachung startet
}
} catch (error) {
adapter.log.error(`Error trigger current Pump [${currentPumpUse.control.idState}]: ${error}`);
}
}
/* - wenn beim Umschalten der Pumpen die Förderleistung zu gering → Ventile deaktivieren - */
if (consumption.curFlow < 0) {
// aufsteigend sortieren nach der Verbrauchsmenge
threadList.sort(mySortAscending);
for await(const entry of threadList) {
entry.ac.acUpdateListPuOff = new AbortController; // acPumpeOff
try {
if (entry.enable // eingeschaltet
&& !entry.killSprinkle // && Aufgabe noch nicht erledigt
&& (consumption.curFlow < 0) // && Förderleistung der Pumpe zu gering
) {
entry.state = 'wait'; //1
const _setValve2 = await setValve(entry, false);
if (_setValve2 === entry.control.idState) {
consumption = await currentConsumption(true);
clearInterval(entry.countdown); // Zähler für Countdown, Verbrauchsmengen, usw. löschen
entry.countdown = null;
adapter.log.warn(`Set Valve ID: ${entry.name} Pump delivery rate too low, wait! curFlow ${consumption.curFlow} parallel: ${consumption.parallel}`);
await asyncTime.setTimeout(300, undefined, {signal: entry.ac.acUpdateListPuOff.signal});
}
}else{
entry.ac.acUpdateListPuOff.abort();
}
} catch (error) { // Fehler beim Reduzieren der Verbrauchsmenge
adapter.log.error(`Error reducing consumption amount: ${error}`);
} finally {
if (entry.ac.acUpdateListPuOff.aborted === false) entry.ac.acUpdateListPuOff.abort();
}
}
}
adapter.log.debug(`curFlow: ${consumption.curFlow}, parallel: ${consumption.parallel}`);
// absteigend sortieren nach der Verbrauchsmenge
threadList.sort(mySortDescending);
// einschalten der Bewässerungsventile nach Verbrauchsmenge und maximaler Anzahl
for await(const entry of threadList) {
try {
if (!entry.enable // ausgeschaltet
&& !entry.killSprinkle // && Aufgabe noch nicht erledigt
&& !entry.myBreak // && nicht in der Pause Interval-Beregnung
&& !entry.extBreak // && nicht in der externen Pause (extBreak)
&& !timeBasedRestrictionEn // && nicht in der zeitlichen Bewässerungsbeschränkung
&& (consumption.curFlow >= entry.pipeFlow) // && noch genügend Förderleistung der Pumpe
&& (consumption.parallel < maxParallel) // && maxParallel noch nicht erreicht
&& !boostOn // && Ventile nur einschalten, wenn kein Boost aktive
&& (boostReady || !(myConfig.config[entry.sprinkleID].booster)) // && wenn kein Boostventil aktive ist => alle Ventile ein ansonsten nur Ventile ohne Boostfunktion
){
entry.ac.acUpdateListOn = new AbortController;
timerOn === true ? await asyncTime.setTimeout(switchingDistanceMS, undefined, {signal: entry.ac.acUpdateListOn.signal}) : timerOn = true;
entry.state = myConfig.config[entry.sprinkleID].booster ? 'boost(on)' : 'on'; // state 4 => boost(on) : 2 => on
const _setValve3 = await setValve(entry, true);
if (_setValve3 === entry.control.idState) {
consumption = await currentConsumption(true);
if (myConfig.config[entry.sprinkleID].booster) {
boostReady = false;
boostOn = true;
boostOnTimer(entry);
adapter.log.debug(`ID: ${entry.name} UpdateList sprinkle On: boostReady = ${boostReady}`);
}
/* countdown starten */
if (!entry.startTime) {
entry.startTime = new Date();
}
adapter.log.debug(`!entry.countdown: ${!entry.countdown}, type: ${typeof(entry.countdown)}`);
if(!entry.countdown){
entry.countdown = setInterval(countSprinkleTime, 1000, entry); // 1000 = 1s
//entry.countdown = asyncTime.setInterval();
}
} else {
entry.ac.acUpdateListOn.abort();
entry.killSprinkle = true;
}
}
} catch (error) { //Fehler beim Einschalten der Bewässerungsventile
adapter.log.error(`Error turning on irrigation valves [${entry.name}]: ${error}`);
}
}
// Ausschalten der Ventile bei boostReady
if (boostOn) {
for await(const entry of threadList){
entry.ac.acUpdateListBoostOn = new AbortController;
try {
if(entry.enable // eingeschaltet
&& !entry.killSprinkle
&& !myConfig.config[entry.sprinkleID].booster
){
entry.state = 'off(Boost)'; //state => 5/off(boost)
const _setValve4 = await setValve(entry, false);
if (_setValve4 === entry.control.idState){
consumption = await currentConsumption(true);
clearInterval(entry.countdown);
entry.countdown = null;
await asyncTime.setTimeout(300, undefined, {signal: entry.ac.acUpdateListBoostOn.signal});
}
}
} catch (error) {
entry.ac.acUpdateListBoostOn.abort();
adapter.log.error(`Error when switching off the valves with boostReady [${entry.name}]: ${error}`);
} finally {
if (entry.ac.acUpdateListBoostOn.aborted === false) entry.ac.acUpdateListBoostOn.abort();
}
}
}
if (consumption.parallel === 0) { // Pumpe nicht mehr erforderlich | 0 Ventile an
// Pumpe ausschalten
if (currentPumpUse.enable === true) {
await sensorPressure.setActive(false).catch((error) => { // Sensor Druck deaktivieren, da keine Bewässerung mehr aktiv ist
adapter.log.error(`Error setting sensor pressure active false: ${error}`);
});
try {
const _currentPumpUse = await setValve(currentPumpUse, false);
if (_currentPumpUse === currentPumpUse.control.idState) {
adapter.setStateAsync(currentPumpUse.id,{
val: currentPumpUse.enable,
ack: true
});
}
await asyncTime.setTimeout(switchingDistanceMS, undefined, undefined);
} catch (error) {
adapter.log.error(`Error trigger current Pump [${ currentPumpUse.control.idState }]: ${error}`);
}
// Druckentlastungsventil ansteuern wenn in Konfiguration aktiv
if (adapter.config.pressureRelief === true // Druckentlastungsventil aktiviert
) {
try {
const empty = await findPressureReliefValve();
const _pressureReliefIO = await pressureReliefIO();
if (empty && _pressureReliefIO === 0) { // Ventil gefunden und alle Ventile haben ihre Laufzeit beendet
pressureReliefValve.name = empty.objectName;
pressureReliefValve.wateringTime = 10;
pressureReliefValve.pipeFlow = empty.pipeFlow;
pressureReliefValve.enable = false;
pressureReliefValve.control = empty.control;
adapter.log.info(`Pressure relief valve: ${pressureReliefValve.name} with pipeFlow ${pressureReliefValve.pipeFlow} l/h for 10s opened!`);
await setValve(pressureReliefValve, true);
await asyncTime.setTimeout(1000 * pressureReliefValve.wateringTime, undefined, undefined);
await setValve(pressureReliefValve, false);
await asyncTime.setTimeout(switchingDistanceMS, undefined, undefined);
}
} catch (error) {
adapter.log.error(`Error trigger pressure relief valve: ${error}`);
}
}
}
// Spannungsversorgung (24V) ausschalten
if(controlVoltage.enable === true) {
try {
const _controlVoltage = await setValve(controlVoltage, false);
if (_controlVoltage === controlVoltage.control.idState) {
adapter.setStateAsync(`info.supplyVoltage`, {
val: controlVoltage.enable,
ack: true
});
// await asyncTime.setTimeout(switchingDistanceMS, undefined, undefined);
}
} catch (error) {
adapter.log.error(`Error trigger Control Voltage [${controlVoltage.control.idState}]: ${error}`);
}
}
}
for await(const entry of threadList){
if(entry.killSprinkle
&& !entry.enable
){
killList.push(entry.name);
}
}
if (killList.length > 0) {
await delList(killList); // erledigte Bewässerungsaufgaben aus der threadList löschen
await asyncTime.setTimeout(50,undefined,undefined);
}
updateListMarker.funcActive = false;
if (updateListMarker.newStart === true) {
updateListMarker.newStart = false;
await asyncTime.setTimeout(50,undefined,undefined);
updateList();
}
}; // End updateList
/* --------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
/**
* +++++ Set the current pump for irrigation +++++
* → Festlegen der aktuellen Pumpe zur Bewässerung
*/
const setActualPump = async () => {
try {
switch(adapter.config.pumpSelection) {
case 'noPump':
case 'mainPump': {
/* Pumpe AUS => Zisternen-Bewässerung nicht aktiviert */
if (adapter.config.triggerCisternPump) {
await adapter.setStateAsync('info.cisternState', {
val: `Cistern settings are not active! ${(fillLevelCistern > 0) ? (` ${fillLevelCistern}% ${(adapter.config.triggerMinCisternLevel !== '') ? (` ${adapter.config.triggerMinCisternLevel}%`) : ('')}`):('')}`,
ack: true
});
}
break;
}
case 'cistern': {
// Zisterne unter Minimum
if (fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) {
if (!currentPumpUse.intBreak) {
currentPumpUse.intBreak = true;
adapter.log.info('Cistern empty => irrigation no longer possible');
if(!sendMessageText.onlySendError()){
sendMessageText.sendMessage('Cistern empty => irrigation no longer possible'); // Zisterne leer => Bewässerung nicht mehr möglich
}
}
// Pumpe ist eingeschaltet => ausschalten
if (currentPumpUse.enable) {
await setValve(currentPumpUse, false);
updateList(); // Wasserverbrauch an Pumpenleistung anpassen
}
// Zisterne über Minimum
} else if (fillLevelCistern > parseFloat(adapter.config.triggerOnCisternLevel)) {
if (currentPumpUse.intBreak) {
currentPumpUse.intBreak = false;
adapter.log.info('Cistern filled => irrigation can begin again'); // Zisterne gefüllt => Bewässerung kann wieder beginnen
if(!sendMessageText.onlySendError()){
sendMessageText.sendMessage('Cistern filled => irrigation can begin again');
}
}
// Wasserverbrauch an Pumpenleistung anpassen
updateList();
}
/* Info aktualisieren */
await adapter.setStateAsync('info.cisternState', {
val: `${ (currentPumpUse.intBreak === true) ? `Cistern empty: ${ fillLevelCistern } % (${ adapter.config.triggerOnCisternLevel } %)` : `Cistern filled: ${ fillLevelCistern } % (${ adapter.config.triggerMinCisternLevel } %)` }`,
ack: true
});
break;
}
case 'pumpAndCistern': {
if (currentPumpUse.enable) {
/* Bewässerungspumpen aktiv */
if ((fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) && (currentPumpUse.pumpCistern === true)) {
/* (Zisterne unter Minimum) && (ZisternenPumpe läuft) */
currentPumpUse.pumpSwitching = true;
sensorPressure.setActive(false); // Sensor Druck deaktivieren, da die Hauptpumpe ausgeschaltet wird und somit kein Druck mehr in den Leitungen ist
const _setValveCisternPumpOff = await setValve(currentPumpUse, false); // Pumpe Zisterne Aus
if (_setValveCisternPumpOff === currentPumpUse.control.idState) {
await adapter.setStateAsync(currentPumpUse.id, { // set State cisternPump === false
val: currentPumpUse.enable,
ack: true
});
} else {
throw new Error(`Error trigger cistern Pump off [${currentPumpUse.control.idState}]`);
}
currentPumpUse.pumpCistern = false;
currentPumpUse.name = 'Main pump';
currentPumpUse.control = { ...mainPumpControl };
currentPumpUse.id = 'info.mainPump';
currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
/* Hauptpumpe Ein */
const _setValveMainPumpOn = await setValve(currentPumpUse, true);
if (_setValveMainPumpOn === currentPumpUse.control.idState) {
await adapter.setStateAsync(currentPumpUse.id, { // set State cisternPump === false
val: currentPumpUse.enable,
ack: true
});
} else {
throw new Error(`Error trigger main Pump on [${currentPumpUse.control.idState}]`);
}
adapter.log.info('Pump change (cistern empty) Cistern pump off => main pump on');
if(!sendMessageText.onlySendError()){
sendMessageText.sendMessage('Pump change (cistern empty) Cistern pump off => main pump on');
}
currentPumpUse.pumpSwitching = false;
sensorPressure.setActive(true); // Sensor Druck aktivieren, da die Hauptpumpe eingeschaltet wird und somit wieder Druck in den Leitungen ist
updateList(); // Wasserverbrauch an Pumpenleistung anpassen
}
} else {
/* Bewässerungspumpen inaktiv */
if (fillLevelCistern > parseFloat(adapter.config.triggerOnCisternLevel)) {
/* Zisterne voll */
if (currentPumpUse.pumpCistern === false) {
currentPumpUse.pumpCistern = true;
currentPumpUse.name = 'Cistern pump';
currentPumpUse.id = 'info.cisternPump';
currentPumpUse.control = { ...cisternPumpControl };
currentPumpUse.pumpPower = parseInt(adapter.config.triggerCisternPumpPower);
await adapter.setStateAsync('control.restFlow', {
val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} | ${currentPumpUse.name})`,
ack: true
});
}
} else if (fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) {
/* Zisterne leer */
if (currentPumpUse.pumpCistern === true) {
currentPumpUse.pumpCistern = false;
currentPumpUse.name = 'Main pump';
currentPumpUse.id = 'info.mainPump';
currentPumpUse.control = { ...mainPumpControl };
currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
await adapter.setStateAsync('control.restFlow', {
val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} | ${currentPumpUse.name})`,
ack: true
});
}
}
}
/* Info aktualisieren */
await adapter.setStateAsync('info.cisternState', {
val: `${ (currentPumpUse.pumpCistern === false) ? `Cistern empty: ${ fillLevelCistern } % (${ adapter.config.triggerOnCisternLevel } %)` : `Cistern filled: ${ fillLevelCistern } % (${ adapter.config.triggerMinCisternLevel } %)` }`,
ack: true
});
break;
}
}
} catch (error) {
adapter.log.error(`setActualPump ${error}`);
}
}; // End setActualPump
/**
* 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.name}.history.lastConsumed`, {
val: Math.round(entry.litersPerSecond * entry.count),
ack: true
});
adapter.setState(`sprinkle.${entry.name}.history.lastRunningTime`, {
val: tools.addTime(entry.count, ''),
ack: true
});
const _formatTime = tools.formatTime(entry.startTime);
adapter.setState(`sprinkle.${entry.name}.history.lastOn`, {
val: _formatTime.dayTime,
ack: true
});
adapter.getState(`sprinkle.${entry.name}.history.curCalWeekConsumed`, (err, state) => {
if (state && state.val) {
adapter.setState(`sprinkle.${entry.name}.history.curCalWeekConsumed`, {
val: (+state.val) + Math.round(entry.litersPerSecond * entry.count),
ack: true
});
}
});
adapter.getState(`sprinkle.${entry.name}.history.curCalWeekRunningTime`, (err, state) => {
if (state && state.val) {
adapter.setState(`sprinkle.${entry.name}.history.curCalWeekRunningTime`, {
val: tools.addTime(+state.val, entry.count),
ack: true
});
}
});
} // End addConsumedAndTime
/*++++++++++++++++++