UNPKG

iobroker.sprinklecontrol

Version:

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

1,075 lines (1,001 loc) 112 kB
'use strict'; // Load your modules here, e.g.: => // Laden Sie Ihre Module hier, z.B. // const fs = require("fs"); const utils = require('@iobroker/adapter-core'); const schedule = require('node-schedule'); const SunCalc = require('suncalc'); const sendMessageText = require('./lib/sendMessageText.js'); // sendMessageText const valveControl = require('./lib/valveControl.js'); // Steuerung der einzelnen Ventile const myConfig = require('./lib/myConfig.js'); // myConfig → Speichern und abrufen von Konfigurationsdaten der Ventile const evaporation = require('./lib/evaporation.js'); const addTime = require('./lib/tools.js').addTime; const formatTime = require('./lib/tools.js').formatTime; //const trend = require('./lib/tools').trend; let adapter; const adapterName = require('./package.json').name.split('.').pop(); let publicHolidayStr = false; // Feiertag heute? let publicHolidayTomorrowStr = false; // Feiertag morgen? let weatherForecastTodayPfadStr = ''; // Pfad zur Regenvorhersage in mm let weatherForecastTodayNum = 0; // heutige Regenvorhersage in mm let weatherForecastTomorrowNum = 0; // morgige Regenvorhersage in mm let addStartTimeSwitch = false; // Externer Schalter für Zusatzbewässerung let startTimeStr = ''; // 06:00 let sunriseStr = ''; let sunsetStr = ''; let goldenHourEnd = ''; let holidayStr = false; // switch => sprinklecontrol.*.control.Holiday | (Holiday == true) => Wochenendprogramm let autoOnOffStr = true; let kwStr = ''; // akt. KW der Woche let timer, timerSleep; let today = 0; // heutige Tag 0:So;1:Mo...6:Sa /* memo */ let ObjSprinkle = {}; /* +++++++++++++++++++++++++++ Starts the adapter instance ++++++++++++++++++++++++++++++++ */ function startAdapter(options) { options = options || {}; Object.assign(options, { name: adapterName }); adapter = new utils.Adapter(options); // start here! adapter.on('ready', () => { // init createConfig myConfig.createConfig(adapter); // Hauptpumpe zur Bewässerung setzen valveControl.initValveControl(adapter); main(adapter); }); /** * +++++++++++++++++++++++++ is called when adapter shuts down +++++++++++++++++++++++++ * * @param {() => void}callback */ adapter.on('unload', (callback) => { /* Wird beim Stoppen nicht ausgeführt - Warum? if(adapter.config.notificationEnabled){ sendMessageText.sendMessage('sprinklerControl is shutting down'); adapter.log.info('sprinklerControl is shutting down'); } */ try { adapter.log.info('cleaned everything up...'); clearTimeout(timer); clearTimeout(timerSleep); /*Startzeiten der Timer löschen*/ schedule.cancelJob('calcPosTimer'); schedule.cancelJob('sprinkleStartTime'); schedule.cancelJob('sprinkleAddStartTime'); /* alle Ventile und Aktoren deaktivieren */ valveControl.clearEntireList(); callback(); } catch (err) { callback(err); } }); /** * ++++++++++++++++++ Answers when getTelegramUser calls from index_m ++++++++++++++++++ * -------------- Antwortet bei Aufrufen von getTelegramUser von index_m --------------- */ adapter.on ('message', (obj) => { if (obj) { switch (obj.command) { case 'getTelegramUser': adapter.getForeignState(`${adapter.config.telegramInstance }.communicate.users`, (err, state) => { err && adapter.log.error(err); if (state && state.val) { try { adapter.log.debug(`getTelegramUser: ${state.val}`); adapter.sendTo(obj.from, obj.command, JSON.parse(state.val), obj.callback); } catch (err) { err && adapter.log.error(err); adapter.log.error('Cannot parse stored user IDs from Telegram!'); } } }); break; } } }); /** * ++++++++++++++++++ is called if a subscribed object changes ++++++++++++++++++ * ---------- wird aufgerufen, wenn sich ein abonniertes Objekt ändert ---------- */ adapter.on('objectChange', (id, obj) => { if (obj) { adapter.log.info(`object ${id} changed: ${JSON.stringify(obj)}`); } else { // The object was deleted adapter.log.info(`object ${id} deleted`); } }); /** * ++++++++++++++++++ is called if a subscribed state changes ++++++++++++++++++ * --------- wird aufgerufen, wenn sich ein abonnierter Status ändert ---------- */ adapter.on('stateChange', (id, state) => { // The state was changed → Der Zustand wurde geändert adapter.log.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`); // Signale zum bestätigen (ack = true) - signals for confirmation if (state?.ack === false) { // wenn (Holiday == true) ist, soll das Wochenendprogramm gefahren werden. if (id === `${adapter.namespace}.control.Holiday`) { // @ts-ignore holidayStr = state.val; adapter.setState(id, { val: state.val, ack: true }); startTimeSprinkle(); } // wenn (addStartTimeSwitch == true) wird die zusätzliche Bewässerung aktiviert if (id === `${adapter.namespace}.control.addStartTimeSwitch` && typeof state.val === 'boolean') { addStartTimeSwitch = state.val; adapter.setState(id, { val: state.val, ack: true }); } // wenn (autoOnOff == false) so werden alle Sprenger nicht mehr automatisch gestartet. if ((id === `${adapter.namespace}.control.autoOnOff`)) { autoOnOffStr = state.val; adapter.log.info(`startAdapter: control.autoOnOff: ${state.val}`); adapter.setState(id, { val: state.val, ack: true }); if (!state.val) { valveControl.clearEntireList(); } startTimeSprinkle(); } // wenn (autoStart == true) so die automatische Bewässerung von Hand gestartet if (id === `${adapter.namespace}.control.autoStart`) { adapter.setState(id, { val: false, ack: true }); if (state.val === true) { // auto Start; startOfIrrigation(); } } // wenn (...sprinkleName.runningTime sich ändert) so wird der aktuelle Sprenger [sprinkleName] // bei == 0 gestoppt, > 1 gestartet if (myConfig.config) { const found = myConfig.config.find(d => d.objectID === id); if (found) { if (id === myConfig.config[found.sprinkleID].objectID) { if (!isNaN(state.val)) { valveControl.addList( [{ auto: false, // Handbetrieb sprinkleID: found.sprinkleID, wateringTime: (state.val <= 0) ? 0 : Math.round(60 * state.val) }]); adapter.setState(id, { val: (state.val <= 0) ? 0 : addTime(Math.round(60 * state.val)), ack: true }); } } } } // wenn (...sprinkleName.autoOn == false[off]) so wird der aktuelle Sprenger [sprinkleName] // bei false nicht automatisch gestartet if (myConfig.config && (typeof state.val === 'boolean')) { const found = myConfig.config.find(d => d.autoOnID === id); if (found && id === myConfig.config[found.sprinkleID].autoOnID) { myConfig.config[found.sprinkleID].autoOn = state.val; adapter.setState(id, { // Bestätigung val: state.val, ack: true }); adapter.log.info(`set ${found.objectName}.autoOn = ${state.val}, id: ${id}`); if (state.val === false) { valveControl.addList( [{ auto: false, sprinkleID: found.sprinkleID, wateringTime: 0 }] ); } } } // postponeByOneDay → um einen Tag verschieben bei fixDay (twoNd & threeRd) const idSplit = id.split('.', 5); if (idSplit[4] === `postponeByOneDay`) { const found = myConfig.config.find(d => d.objectName === idSplit[3]); if (found) { myConfig.postponeByOneDay(found.sprinkleID).catch((e) => { adapter.log.warn(`postponeByOneDay: ${e}`); }); adapter.setState(id, { val: false, ack: true }); } } } // Signale ohne Bestätigung - signals without confirmation // wenn in der config unter methodControlSM!== 'analog' oder 'bistable' eingegeben wurde, dann Bodenfeuchte-Sensor auslesen if (myConfig.config) { function filterByID(obj){ return (((obj.methodControlSM === 'analog') || (obj.methodControlSM === 'bistable')) && (obj.triggerSM === id)); } const filter = myConfig.config.filter(filterByID); if (filter) { for (const fil of filter) { if (id === myConfig.config[fil.sprinkleID].triggerSM){ // analog if (fil.methodControlSM === 'analog') { myConfig.setSoilMoistPct(fil.sprinkleID, state.val); } else if (fil.methodControlSM === 'bistable') { // bistable myConfig.setSoilMoistBool(fil.sprinkleID, state.val); } } } } } // Change in outside temperature → Änderung der Außentemperatur if (id === adapter.config.sensorOutsideTemperature) { /*Temperatur*/ if (!Number.isNaN(Number.parseFloat(state.val))) { evaporation.setCurTemperature(parseFloat(state.val), state.ts); } else { adapter.log.warn(`sensorOutsideTemperature => Wrong value: ${state.val}, Type: ${typeof state.val}`); } } // LuftFeuchtigkeit if (id === adapter.config.sensorOutsideHumidity) { if (!Number.isNaN(Number.parseFloat(state.val))) { evaporation.setCurHumidity(parseFloat(state.val), state.lc); } else { adapter.log.warn(`sensorOutsideHumidity => Wrong value: ${state.val}, Type: ${typeof state.val}`); } } // Helligkeit if (id === adapter.config.sensorBrightness) { if (!Number.isNaN(Number.parseFloat(state.val))) { evaporation.setCurIllumination(parseFloat(state.val), state.lc); } else { adapter.log.warn(`sensorBrightness => Wrong value: ${state.val}, Type: ${typeof state.val}`); } } // Windgeschwindigkeit if (id === adapter.config.sensorWindSpeed) { if (!Number.isNaN(Number.parseFloat(state.val))) { evaporation.setCurWindSpeed(parseFloat(state.val), state.lc); } else { adapter.log.warn(`sensorWindSpeed => Wrong value: ${state.val}, Type: ${typeof state.val}`); } } // Regencontainer // If the amount of rain is over 20 mm, the 'lastRainCounter' is overwritten and no calculation is carried out. => // * Wenn die Regenmenge mehr als 20 mm beträgt, wird der 'lastRainCounter' überschrieben und es wird keine Berechnung durchgeführt. if (id === adapter.config.sensorRainfall) { if (!Number.isNaN(Number.parseFloat(state.val))) { evaporation.setCurAmountOfRain(parseFloat(state.val)); } else { adapter.log.warn(`sensorRainfall => Wrong value: ${state.val}, Type: ${typeof state.val}`); } } // Feiertagskalender if (adapter.config.publicHolidays === true) { if (id === `${adapter.config.publicHolInstance }.heute.boolean`) { publicHolidayStr = state.val; startTimeSprinkle(); } if (id === `${adapter.config.publicHolInstance }.morgen.boolean`) { // @ts-ignore publicHolidayTomorrowStr = state.val; startTimeSprinkle(); } } // Wettervorhersage if (adapter.config.weatherForecast === true) { if (id === weatherForecastTodayPfadStr) { if (typeof state.val == 'string') { weatherForecastTodayNum = parseFloat(state.val); } else if (typeof state.val == 'number') { weatherForecastTodayNum = state.val; } else { weatherForecastTodayNum = 0; adapter.log.info(`StateChange => Wettervorhersage state.val ( ${state.val}; ${typeof state.val} ) kann nicht als Number verarbeitet werden`); } adapter.setState('info.rainToday', { val: weatherForecastTodayNum, ack: true }); } if (id === `${adapter.config.weatherForInstance }.NextDaysDetailed.Location_1.Day_2.rain_value`) { weatherForecastTomorrowNum = parseFloat(state.val); adapter.setState('info.rainTomorrow', { val: weatherForecastTomorrowNum, ack: true }); } } // Füllstand der Zisterne bei Statusänderung if (adapter.config.actualValueLevel && (id === adapter.config.actualValueLevel)) { valveControl.setFillLevelCistern(parseFloat(state.val) || 0); //fillLevelCistern = state.val || 0; } }); } // +++++++++++++++++ Get longitude an latitude from system config ++++++++++++++++++++ /**get longitude/latitude from system if not set or not valid * do not change if we have already a valid value * so we could use different settings compared to system if necessary * >>> * Längen- / Breitengrad vom System abrufen, falls nicht festgelegt oder ungültig * wird nicht geändert, wenn bereits ein gültiger Wert vorhanden ist, * daher können wir bei Bedarf andere Einstellungen als das System verwenden */ async function GetSystemData() { if (typeof adapter.config.longitude === undefined || adapter.config.longitude == null || adapter.config.longitude.length === 0 || isNaN(adapter.config.longitude) || typeof adapter.config.latitude === undefined || adapter.config.latitude == null || adapter.config.latitude.length === 0 || isNaN(adapter.config.latitude)) { try { const obj = await adapter.getForeignObjectAsync('system.config'); if (obj && obj.common && obj.common.longitude && obj.common.latitude) { adapter.config.longitude = obj.common.longitude; adapter.config.latitude = obj.common.latitude; adapter.log.debug(`longitude: ${adapter.config.longitude} | latitude: ${adapter.config.latitude}`); } else { adapter.log.error('system settings cannot be called up. Please check the geo data'); } } catch (err) { adapter.log.warn('system settings cannot be called up. Please check configuration!'); } } } /** * Schreiben des nächsten Starts in '.actualSoilMoisture' * oder Rückgabe des DayName für den nächsten Start * * @param sprinkleID - Number of Array[0...] * @param returnOn - true = Rückgabe des Wochentags; false = Schreiben in State /.actualSoilMoisture * @returns nextStart ['Sun','Mon','Tue','Wed','Thur','Fri','Sat'] */ function curNextFixDay (sprinkleID, returnOn) { const weekDayArray = myConfig.config[sprinkleID].startFixDay; const objPfad = `sprinkle.${myConfig.config[sprinkleID].objectName}`; const weekday = ['Sun','Mon','Tue','Wed','Thur','Fri','Sat']; let found = false; let curDay = formatTime(adapter, '', 'day'); if (startTimeStr <= formatTime(adapter, '', 'hh:mm')) { curDay++ } for ( let i=0; i<7; i++ ) { if (curDay > 6) { curDay = curDay - 7; } if (weekDayArray[curDay] === true) { found = true; if (returnOn) { return weekday[curDay]; } else { adapter.setState(`${objPfad}.actualSoilMoisture`, { val: curDay, ack: true }); } break; } curDay++; } if (returnOn && found === false) { return 'off'; } } // /** * Sets the status at start to a defined value * → Setzt den Status beim Start auf einen definierten Wert */ function checkStates() { /** * control.Holiday * * @param {string|null} err * @param {ioBroker.State|null|undefined} state */ adapter.getState('control.Holiday', (err, state) => { if (state && (state.val == null)) { adapter.setState('control.Holiday', { val: false, ack: true }); } }); /** * control.autoOnOff * * @param {string|null} err * @param {ioBroker.State|null|undefined} state */ adapter.getState('control.autoOnOff', (err, state) => { if (state && (typeof state.val === 'boolean')) { autoOnOffStr = state.val; } else { autoOnOffStr = true; adapter.setState('control.autoOnOff', { val: autoOnOffStr, ack: true }); } }); adapter.getState('evaporation.ETpToday', (err, state) => { if (state && (state.val == null)) { evaporation.setETpTodayNum(0); adapter.setState('evaporation.ETpToday', { val: 0, ack: true }); } else if (state) { evaporation.setETpTodayNum(parseFloat(state.val)); } }); adapter.getState('evaporation.ETpYesterday', (err, state) => { if (state && (state.val == null || state.val === false)) { adapter.setState('evaporation.ETpYesterday', { val: 0, ack: true }); } }); // akt. kW ermitteln für history last week kwStr = formatTime(adapter, '','kW'); today = formatTime(adapter,'', 'day'); } /** * aktuelle States checken nach dem Start (2000 ms) wenn alle Sprenger-Kreise angelegt wurden */ async function checkActualStates () { try { /** * switch Holiday * */ const _holiday = await adapter.getStateAsync('control.Holiday'); if (_holiday && _holiday.val && typeof _holiday.val === 'boolean') { holidayStr = _holiday.val; } /** * switch autoOnOff * */ const _autoOnOff = await adapter.getStateAsync('control.autoOnOff'); if (_autoOnOff && _autoOnOff.val && typeof _autoOnOff.val === 'boolean') { autoOnOffStr = _autoOnOff.val; } if (adapter.config.publicHolidays === true && (adapter.config.publicHolInstance !== 'none' || adapter.config.publicHolInstance !== '')) { /** * Feiertag Heute * */ const _publicHolInstanceHeute = await adapter.getForeignStateAsync( `${adapter.config.publicHolInstance }.heute.boolean` ).catch((e) => adapter.log.warn(e)); if (_publicHolInstanceHeute && _publicHolInstanceHeute.val) { publicHolidayStr = _publicHolInstanceHeute.val; } /** * Feiertag MORGEN * */ const _publicHolInstanceMorgen = await adapter.getForeignStateAsync( `${adapter.config.publicHolInstance }.morgen.boolean` ).catch((e) => adapter.log.warn(e)); if (_publicHolInstanceMorgen && _publicHolInstanceMorgen.val) { publicHolidayTomorrowStr = _publicHolInstanceMorgen.val; } } if (adapter.config.weatherForecast === true && (adapter.config.weatherForInstance !== 'none' || adapter.config.weatherForInstance !== '')) { /** * Niederschlagsmenge HEUTE in mm * */ const _weatherForInstanceToday = await adapter.getForeignStateAsync( weatherForecastTodayPfadStr ).catch((e) => adapter.log.warn(e)); if (_weatherForInstanceToday && _weatherForInstanceToday.val) { if (typeof _weatherForInstanceToday.val == 'string') { weatherForecastTodayNum = parseFloat(_weatherForInstanceToday.val); } else if (typeof _weatherForInstanceToday.val == 'number') { weatherForecastTodayNum = _weatherForInstanceToday.val; } else { weatherForecastTodayNum = 0; adapter.log.info(`checkActualStates => Wettervorhersage state.val ( ${_weatherForInstanceToday.val}); ${typeof _weatherForInstanceToday.val} kann nicht als Number verarbeitet werden`); } await adapter.setStateAsync('info.rainToday', { val: weatherForecastTodayNum, ack: true }); } /** * Niederschlagsmenge MORGEN in mm * */ const _weatherForInstance = await adapter.getForeignStateAsync( `${adapter.config.weatherForInstance }.NextDaysDetailed.Location_1.Day_2.rain_value` ).catch((e) => adapter.log.warn(e)); if (_weatherForInstance && _weatherForInstance.val) { weatherForecastTomorrowNum = _weatherForInstance.val; await adapter.setStateAsync('info.rainTomorrow', { val: weatherForecastTomorrowNum, ack: true }); } } // wenn (...sprinkleName.autoOn == false[off]) so wird der aktuelle Sprenger [sprinkleName] // bei false nicht automatisch gestartet /** * Abfrage von ...sprinkleName.autoOn * */ const result = myConfig.config; if (result) { for (const res of result) { /** * Abfrage ... .autoOn beim Start * */ const _autoOn = await adapter.getForeignStateAsync( res.autoOnID ); if (_autoOn && typeof _autoOn.val === 'boolean') { res.autoOn = _autoOn.val; if (_autoOn.val === false) { adapter.log.info(`get ${res.objectName}.autoOn = ${res.autoOn}`); } } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // +++++ Zustände der States aktualisieren +++++ // // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // .countdown beim Start auf '0' setzen adapter.setStateAsync(`sprinkle.${res.objectName}.countdown`,{ val:'0', ack:true }); // .runningTime beim Start auf '00:00' setzen adapter.setStateAsync(`sprinkle.${res.objectName}.runningTime`,{ val:'00:00', ack:true }); // .countdown beim Start auf '0' für (off) setzen adapter.setStateAsync(`sprinkle.${res.objectName}.sprinklerState`,{ val:0, ack:true }); } } if (adapter.config.actualValueLevel){ /** * Füllstand der Zisterne in % holen * */ const _actualValueLevel = await adapter.getForeignStateAsync( adapter.config.actualValueLevel ).catch((e) => adapter.log.warn(e)); if (_actualValueLevel && typeof parseFloat(_actualValueLevel.val) === 'number') { valveControl.setFillLevelCistern(parseFloat(_actualValueLevel.val)); } } /** * return the saved objects under sprinkle.* * rückgabe der gespeicherten Objekte unter sprinkle.* * */ const _list = await adapter.getForeignObjectsAsync(`${adapter.namespace }.sprinkle.*`, 'channel').catch((e) => adapter.log.warn(e)); if (_list) { ObjSprinkle = _list; } } catch (e) { adapter.log.warn(`sprinkleControl cannot check actual States ... Please check your sprinkleControl states: ${e}`); } } /** * at 0:05 start of StartTimeSprinkle * => um 0:05 start von StartTimeSprinkle * (..., '(s )m h d m wd') */ const calcPos = schedule.scheduleJob('calcPosTimer', '5 0 * * *', function() { // Berechnungen mittels SunCalc sunPos(); today = formatTime(adapter,'', 'day'); // History Daten aktualisieren, wenn eine neue Woche beginnt adapter.log.debug(`calcPos 0:05 old-KW: ${kwStr} new-KW: ${formatTime(adapter, '','kW')} if: ${(kwStr !== formatTime(adapter, '','kW'))}`); if (kwStr !== formatTime(adapter, '','kW')) { const result = myConfig.config; if (result) { for(const i in result) { if (Object.hasOwn(result, i)) { const objectName = result[i].objectName; adapter.getState(`sprinkle.${objectName}.history.curCalWeekConsumed`, (err, state) => { if (state) { adapter.setState(`sprinkle.${objectName}.history.lastCalWeekConsumed`, { val: state.val, ack: true }); adapter.setState(`sprinkle.${objectName}.history.curCalWeekConsumed`, { val: 0, ack: true }); } }); adapter.getState(`sprinkle.${objectName}.history.curCalWeekRunningTime`, (err, state) => { if (state) { adapter.setState(`sprinkle.${objectName}.history.lastCalWeekRunningTime`, { val: state.val, ack: true }); adapter.setState(`sprinkle.${objectName}.history.curCalWeekRunningTime`, { val: '00:00', ack: true }); } }); } } } kwStr = formatTime(adapter, '','kW'); } // ETpToday und ETpYesterday in evaporation aktualisieren da ein neuer Tag evaporation.setNewDay(); // Startzeit Festlegen → verzögert wegen Daten von SunCalc setTimeout(() => { startTimeSprinkle(); addStartTimeSprinkle(); },1000); }); // Berechnung mittels sunCalc function sunPos() { // get today's sunlight times → Holen Sie sich die heutige Sonnenlichtzeit const times = SunCalc.getTimes(new Date(), adapter.config.latitude, adapter.config.longitude); // format sunrise time from the Date object → Formatieren Sie die Sonnenaufgangszeit aus dem Date-Objekt sunriseStr = `${(`0${times.sunrise.getHours()}`).slice(-2)}:${(`0${times.sunrise.getMinutes()}`).slice(-2)}`; // format golden hour end time from the Date object → Formatiere golden hour end time aus dem Date-Objekt goldenHourEnd = `${(`0${times.goldenHourEnd.getHours()}`).slice(-2)}:${(`0${times.goldenHourEnd.getMinutes()}`).slice(-2)}`; // format sunset time from the Date object → formatieren Sie die Sonnenuntergangszeit aus dem Date-Objekt sunsetStr = sunsetStr = `${(`0${times.sunset.getHours()}`).slice(-2)}:${(`0${times.sunset.getMinutes()}`).slice(-2)}`; } function addStartTimeSprinkle() { schedule.cancelJob('sprinkleAddStartTime'); if (adapter.config.selectAddStartTime === 'greaterETpCurrent' || adapter.config.selectAddStartTime === 'withExternalSignal') { const addStartTimeSplit = adapter.config.addWateringStartTime.split(':'); const scheduleAddStartTime = schedule.scheduleJob('sprinkleAddStartTime', `${addStartTimeSplit[1]} ${addStartTimeSplit[0]} * * *`, function() { // if (autoOnOff == false) => keine auto Start if (!autoOnOffStr) { schedule.cancelJob('sprinkleAddStartTime'); return; } if (((adapter.config.selectAddStartTime === 'greaterETpCurrent') && (adapter.config.triggerAddStartTimeETpCur < evaporation.getETpTodayNum())) || (adapter.config.selectAddStartTime === 'withExternalSignal' && addStartTimeSwitch)) { let messageText = ''; // Filter enabled const result = myConfig.config.filter(d => d.enabled === true); if (result) { /** * Array zum flüchtigen Sammeln von Bewässerungsaufgaben * */ const memAddList = []; /** * result Rain * - (aktuelle Wettervorhersage - Schwellwert der Regenberücksichtigung) wenn Sensor sich im Freien befindet * - (> 0) es regnet - Abbruch - * - (≤ 0) Start der Bewässerung * * @param inGreenhouse - Sensor befindet sich im Gewächshaus * @returns - resultierende Regenmenge */ function resRain (inGreenhouse) { return (adapter.config.weatherForecast && !inGreenhouse) ? (((+ weatherForecastTodayNum) - parseFloat(adapter.config.thresholdRain)).toFixed(1)) : 0; } for(const res of result) { if (res.autoOn // Ventil aktiv && (res.addWateringTime > 0) // zusätzliche Bewässerung aktiv time > 0 && (resRain(res.inGreenhouse) <= 0)) { // keine Regenvorhersage switch (res.methodControlSM) { case 'bistable': { if (res.soilMoisture.bool) { messageText += `<b>${res.objectName}</b> (${res.soilMoisture.bool})\n` + ` START => ${addTime(Math.round(60 * res.addWateringTime), '')}\n`; memAddList.push({ auto: true, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * res.addWateringTime) }); } break; } case 'fixDay': { messageText += `<b>${res.objectName}</b>\n` + ` START => ${addTime(Math.round(60 * res.addWateringTime), '')}\n`; memAddList.push({ auto: true, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * res.addWateringTime) }); break; } case 'calculation': { const addCountdown = res.wateringTime * (res.soilMoisture.maxIrrigation - res.soilMoisture.val) / (res.soilMoisture.maxIrrigation - res.soilMoisture.triggersIrrigation) - res.wateringTime; adapter.log.debug(`addCountdown: ${addCountdown}, addWateringTime: ${res.addWateringTime}, if(${(addCountdown - res.addWateringTime) > 0})`); if ((addCountdown - res.addWateringTime) > 0) { messageText += `<b>${res.objectName}</b> ${res.soilMoisture.pct}% (${res.soilMoisture.pctTriggerIrrigation}%)\n` + ` START => ${addTime(Math.round(60 * addCountdown), '')}\n`; memAddList.push({ auto: true, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * addCountdown) }); } break; } case 'analog': { if (res.soilMoisture.pct < res.soilMoisture.pctAddTriggersIrrigation) { messageText += `<b>${res.objectName}</b> ${res.soilMoisture.pct} %(${res.soilMoisture.pctAddTriggersIrrigation}%)\n` + ` START => ${addTime(Math.round(60 * res.addWateringTime), '')}\n`; memAddList.push({ auto: true, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * res.addWateringTime) }); } break; } } } else { adapter.log.debug(`${res.objectName}: autoOn (${res.autoOn}) && addWateringTime (${res.addWateringTime} > 0) && resRain (${resRain(res.inGreenhouse)}) <= 0, if(${res.autoOn && (res.addWateringTime > 0) && (resRain(res.inGreenhouse) <= 0)})`); } } valveControl.addList(memAddList); } if(!sendMessageText.onlySendError() && messageText.length > 0){ sendMessageText.sendMessage(messageText); } } else { adapter.log.debug(`greaterETpCurrent: ${(adapter.config.selectAddStartTime === 'greaterETpCurrent')} & ${(adapter.config.triggerAddStartTimeETpCur < evaporation.getETpTodayNum())}, withExternalSignal; ${(adapter.config.selectAddStartTime === 'withExternalSignal')} & ${addStartTimeSwitch}`); } setTimeout(()=>{ schedule.cancelJob('sprinkleAddStartTime'); }, 200); }); } } // Determination of the irrigation time => Bestimmung der Bewässerungszeit function startTimeSprinkle() { let startTimeSplit = []; let infoMessage; let messageText = ''; schedule.cancelJob('sprinkleStartTime'); // if (autoOnOff == false) => keine auto Start if (!autoOnOffStr) { adapter.log.info(`Sprinkle: autoOnOff == Aus ( ${autoOnOffStr} )`); adapter.setState('info.nextAutoStart', { val: 'autoOnOff = off(0)', ack: true }); return; } /** * next start time (automatic) * → Berechnung des nächsten Starts (Automatik) * * @returns */ function nextStartTime () { let newStartTime; let run = 0; const curTime = new Date(); const myHours = checkTime(curTime.getHours()); const myMinutes = checkTime(curTime.getMinutes()); let myWeekday = curTime.getDay(); const myWeekdayStr = ['So','Mo','Di','Mi','Do','Fr','Sa']; const myTime = `${myHours}:${myMinutes}`; /** * aus 0...9 wird String 00...09 * * @param i * @returns */ function checkTime(i) { return (+i < 10) ? `0${i}` : i; } do { myWeekday += run; run++; if (myWeekday>6){ myWeekday=0; } // Start time variant according to configuration => Startzeitvariante gemäß Konfiguration switch(adapter.config.wateringStartTime) { case 'livingTime' : /*Startauswahl = festen Zeit*/ infoMessage = 'Start zur festen Zeit '; newStartTime = adapter.config.weekLiving; break; case 'livingSunrise' : /*Startauswahl = Sonnenaufgang*/ infoMessage = 'Start mit Sonnenaufgang '; // format sunrise time from the Date object newStartTime = addTime(sunriseStr, parseInt(adapter.config.timeShift)); break; case 'livingGoldenHourEnd' : /*Startauswahl = Ende der Golden Hour*/ infoMessage = 'Start zum Ende der Golden Hour '; // format goldenHourEnd time from the Date object newStartTime = goldenHourEnd; break; case 'livingSunset' : /*Startauswahl = Sonnenuntergang*/ infoMessage = 'Start mit Sonnenuntergang '; // format sunset time from the Date object newStartTime = addTime(sunsetStr, parseInt(adapter.config.timeShift)); break; } // Start am Wochenende →, wenn andere Zeiten verwendet werden soll if((adapter.config.publicWeekend) && ((myWeekday === 6) || (myWeekday === 0))){ infoMessage = 'Start am Wochenende '; newStartTime = adapter.config.weekEndLiving; } // Start an Feiertagen →, wenn Zeiten des Wochenendes verwendet werden soll if((adapter.config.publicHolidays) && (adapter.config.publicWeekend) && (((publicHolidayStr === true) && (run === 1)) // heute Feiertag && erster Durchlauf || ((publicHolidayTomorrowStr === true) && (run === 2)) // morgen Feiertag && zweiter Durchlauf || (holidayStr === true))) { // Urlaub infoMessage = 'Start am Feiertag '; newStartTime = adapter.config.weekEndLiving; } } while ((newStartTime <= myTime) && (run === 1)); const newStartTimeLong = `${myWeekdayStr[myWeekday]} ${newStartTime}`; /** * next Auto-Start * * @param {string|null} err * @param {ioBroker.State|null|undefined} state */ adapter.getState('info.nextAutoStart', (err, state) =>{ if (state) { if (state.val !== newStartTimeLong) { adapter.setState('info.nextAutoStart', { val: newStartTimeLong, ack: true }); // next Start Message if(!sendMessageText.onlySendError){ sendMessageText.sendMessage(`${infoMessage}(${myWeekdayStr[myWeekday]}) um ${newStartTime}`); } adapter.log.info(`${infoMessage} (${myWeekdayStr[myWeekday]}) um ${newStartTime}`); } } }); return newStartTime; } // startTimeStr = nextStartTime(); startTimeSplit = startTimeStr.split(':'); const scheduleStartTime = schedule.scheduleJob('sprinkleStartTime', `${startTimeSplit[1]} ${startTimeSplit[0]} * * *`, function() { startOfIrrigation(); setTimeout (() => { setTimeout(()=>{ nextStartTime(); }, 800); schedule.cancelJob('sprinkleStartTime'); }, 200); }); } const startOfIrrigation = () => { let messageText = ''; // Filter enabled const result = myConfig.config.filter(d => d.enabled === true); if (result) { /** * Array zum flüchtigen Sammeln von Bewässerungsaufgaben * */ const memAddList = []; /** * result Rain * - (aktuelle Wettervorhersage - Schwellwert der Regenberücksichtigung) wenn Sensor sich im Freien befindet * - (> 0) es regnet - Abbruch - * - (≤ 0) Start der Bewässerung * * @param inGreenhouse - Sensor befindet sich im Gewächshaus * @returns - resultierende Regenmenge */ function resRain (inGreenhouse) { return (adapter.config.weatherForecast && !inGreenhouse) ? (((+ weatherForecastTodayNum) - parseFloat(adapter.config.thresholdRain)).toFixed(1)) : 0; } // Bewässerung mit Zisterne? Füllstand anzeigen if (adapter.config.cisternSettings === true) messageText += `${valveControl.getStatusCistern()}\n`; for(const res of result) { let messageTextZeile1 = ''; let messageTextZeile2 = ''; messageTextZeile1 = `<b>${res.objectName}</b>`; switch (res.methodControlSM) { case 'bistable': messageTextZeile1 += ` (${res.soilMoisture.bool})`; break; case 'analog': messageTextZeile1 += ` ${res.soilMoisture.pct}% (${res.soilMoisture.pctTriggerIrrigation}%)`; break; case 'fixDay': //messageTextZeile1 += ` (${curNextFixDay(res.sprinkleID, true)})`; break; case 'calculation': messageTextZeile1 += ` ${res.soilMoisture.pct}% (${res.soilMoisture.pctTriggerIrrigation}%)`; break; } // Test Bodenfeuchte adapter.log.debug(`Bodenfeuchte: ${res.soilMoisture.val} <= ${res.soilMoisture.triggersIrrigation} AutoOn: ${res.autoOn}`); if (res.autoOn) { switch (res.methodControlSM) { // -- bistable -- Bodenfeuchte-Sensor mit 2-Punkt-Regler true und false -- // case 'bistable': if(res.soilMoisture.bool) { /* Wenn in der Config Regenvorhersage aktiviert: Startvorgang abbrechen, wenn der Regen den eingegebenen Schwellwert überschreitet. */ if (resRain(res.inGreenhouse) <= 0) { const curWateringTime = Math.round(60 * res.wateringTime * evaporation.timeExtension(res.wateringAdd)); memAddList.push({ auto: true, sprinkleID: res.sprinkleID, wateringTime: curWateringTime }); messageTextZeile2 += `START => ${addTime(curWateringTime, '')}`; } else if (adapter.config.weatherForecast) { /* Bewässerung unterdrückt da ausreichende Regenvorhersage */ messageTextZeile2 += `<i>Start verschoben, da heute ${weatherForecastTodayNum}mm Niederschlag</i>`; adapter.log.info(`${res.objectName}: Start verschoben, da Regenvorhersage für Heute ${weatherForecastTodayNum} mm [ ${resRain(res.inGreenhouse)} > 0 ]`); } } break; // --- analog -- Bodenfeuchte-Sensor im Wertebereich von 0 bis 100% --- // // -- Prozentuale Bodenfeuchte zu gering -- // case 'analog': if(res.soilMoisture.pct <= res.soilMoisture.pctTriggerIrrigation) { /* Wenn in der Config Regenvorhersage aktiviert: Startvorgang abbrechen, wenn der Regen den eingegebenen Schwellwert überschreitet. */ if (resRain(res.inGreenhouse) <= 0) { let countdown = res.wateringTime * (100 - res.soilMoisture.pct) / (100 - res.soilMoisture.pctTriggerIrrigation); // in min // Begrenzung der Bewässerungszeit auf dem in der Config eingestellten Überschreitung (in Prozent) if (countdown > (res.wateringTime * res.wateringAdd / 100)) { countdown = res.wateringTime * res.wateringAdd / 100; } memAddList.push({ auto: true, sprinkleID: res.sprinkleID, wateringTime: Math.round(60* countdown) }); messageTextZeile2 += `START => ${addTime(Math.round(60*countdown), '')}`; } else if (adapter.config.weatherForecast) { /* Bewässerung unterdrückt da ausreichende Regenvorhersage */ messageTextZeile2 += `<i>Start verschoben, da heute ${weatherForecastTodayNum}mm Niederschlag</i> `; adapter.log.info(`${res.objectName}: Start verschoben, da Regenvorhersage für Heute ${weatherForecastTodayNum} mm [ ${resRain(res.inGreenhouse)} > 0 ]`); } } break; // --- fixDay -- Start an festen Tagen ohne Sensoren --- // // -- Bewässerungstag erreicht // case 'fixDay': /* Wenn in der Config Regenvorhersage aktiviert: Startvorgang abbrechen, wenn der Regen den eingegebenen Schwellwert überschreitet. */ if (resRain(res.inGreenhouse) <= 0) { // Bewässerungstag erreicht if (res.startFixDay[today]) { const curWateringTime = Math.round(60 * res.wateringTime * evaporation.timeExtension(res.wateringAdd)); memAddList.push({ auto: true, sprinkleID: res.sprinkleID, wateringTime: curWateringTime }); messageTextZeile2 += `START => ${addTime(curWateringTime, '')}`; if (res.startDay === 'threeRd'){ // Next Start in 3 Tagen res.startFixDay[today] = false; res.startFixDay[(+ today + 3 > 6) ? (+ today-4) : (+ today+3)] = true; }else if (res.startDay === 'twoNd') { // Next Start in 2 Tagen res.startFixDay[today] = false; res.startFixDay[(+ today + 2 > 6) ? (+ today-5) : (+ today+2)] = true; } } } else if (adapter.config.weatherForecast){ messageTextZeile2 += `<i>Start verschoben, da heute ${weatherForecastTodayNum}mm Niederschlag</i>`; adapter.log.info(`${res.objectName}: Start verschoben, da Regenvorhersage für Heute ${weatherForecastTodayNum} mm [ ${resRain(false)} > 0 ]`); if ((res.startDay === 'threeRd') || (res.startDay === 'twoNd')) { let startDay = -1; res.startFixDay.forEach((item, index) => { if (item) { startDay = index;