UNPKG

iobroker.sprinklecontrol

Version:

Control of several sprinklers depending on weather conditions and pump performance.

1,091 lines (1,021 loc) 97.2 kB
'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 /*++++++++++++++++++