UNPKG

iobroker.sprinklecontrol

Version:

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

975 lines (911 loc) 133 kB
'use strict'; /* Created with @iobroker/create-adapter v2.2.1 */ // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter const utils = require('@iobroker/adapter-core'); const schedule = require('node-schedule'); const SunCalc = require('suncalc'); // Load your modules here, e.g.: const sendMessageText = require('./lib/sendMessageText.js'); // sendMessageText const valveControl = require('./lib/valveControl.js').valveControl; // Steuerung der einzelnen Ventile const controlVoltage = require('./lib/valveControl.js').controlVoltage; // state der Steuerspannung const currentPumpUse = require('./lib/valveControl.js').currentPumpUse; // state der Pumpen const threadList = require('./lib/valveControl.js').threadList; // Auflistung aller aktiver Sprenger-Kreise const pressureReliefValve = require('./lib/valveControl.js').pressureReliefValve; // state des Druckentlastungsventil const myConfig = require('./lib/myConfig.js'); // myConfig → Speichern und abrufen von Konfigurationsdaten der Ventile const evaporation = require('./lib/evaporation.js'); // Berechnung der Verdunstung, Ermittlung der täglichen Höchsttemperatur, Speicherung der aktuellen Werte von Temperatur, Luftfeuchtigkeit, Helligkeit, Windgeschwindigkeit und Regenmenge const tools = require('./lib/tools.js').tools; // tools => laden von Hilfsfunktionen /** * The adapter instance */ let adapter; let publicHolidayStr; // ext. Adapter → Deutsche Feiertage let publicHolidayTomorrowStr; let weatherForecastTodayPfadStr; // Pfad zur Regenvorhersage in mm (Regenvorhersage - DasWetter.com) let weatherForecastTodayNum = 0; // heutige Regenvorhersage in mm let weatherForecastTomorrowNum = 0; // morgige Regenvorhersage in mm let addStartTimeSwitch = false; // Externer Schalter für Zusatzbewässerung let irrigationRestriction = false; // Schalter für Bewässerungseinschränkung let startTimeStr; let sunriseStr; let sunsetStr; let goldenHourEnd; let holidayStr; // switch => sprinklecontrol.*.control.Holiday (Holiday == true)=>Wochenendprogramm let autoOnOffStr; let kw; // akt. KW der Woche let timer, timerSleep; let today; // heutige Tag 0:So;1:Mo...6:Sa /* memo */ let ObjSprinkle = {}; /** * Starts the adapter instance * * @param {Partial<utils.AdapterOptions>} [options] */ function startAdapter(options) { // Create the adapter and define its methods return adapter = utils.adapter(Object.assign({}, options, { name: 'sprinklecontrol', // The ready callback is called when databases are connected and adapter received configuration. // start here! ready: main, // Main method defined below for readability // is called when adapter shuts down - callback has to be called under any circumstances! unload: (callback) => { try { adapter.log.info('cleaned everything up...'); clearTimeout(timer); clearTimeout(timerSleep); /*Startzeiten der Timer löschen*/ schedule.cancelJob('calcPosTimer'); schedule.cancelJob('sprinkleStartTime'); schedule.cancelJob('sprinkleSecondStartTime'); schedule.cancelJob('irrigationRestrictionOn'); schedule.cancelJob('irrigationRestrictionOff'); schedule.cancelJob('sprinkleAddStartTime'); /* alle Ventile und Aktoren deaktivieren */ valveControl.clearEntireList(); callback(); } catch (e) { callback(); } }, // is called if a subscribed state changes stateChange: async (id, state) => { try { // adapter.log.debug(`stateChange: ${id} (${state ? state.val : 'null'}) ack: ${state ? state.ack : 'null'}`); // The state was changed → Der Zustand wurde geändert if(state){ // Change in outside temperature → Änderung der Außentemperatur if (id === adapter.config.sensorOutsideTemperature) { /*Temperatur*/ if (!Number.isNaN(Number.parseFloat(state.val))) { // @ts-ignore 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))) { if (adapter.config.unitOfWindSpeed === 'm/s') { evaporation.setCurWindSpeed(parseFloat(state.val) * 3.6, state.lc); } else if (adapter.config.unitOfWindSpeed === 'km/h') { // HomeMatic: km/h evaporation.setCurWindSpeed(parseFloat(state.val), state.lc); } else { adapter.log.warn(`sensorWindSpeed => No unit selected; therefore, ${state.val} km/h is used. Please check your configuration!`); } } else { adapter.log.warn(`sensorWindSpeed => Wrong value: ${state.val}, Type: ${typeof state.val}`); } } // Drucksensor if (id === adapter.config.sensorPressure) { adapter.log.debug(`SensorPressure: ${ state?.val }, Type: ${ typeof state.val }`); let myPressure; if(typeof state.val === 'number') { myPressure = parseFloat(state.val); }else if(typeof state.val === 'string') { myPressure = parseFloat(state.val); }else if(typeof state.val === 'boolean') { (state.val === true) ? myPressure = 100 : myPressure = 0; } if(typeof myPressure !== "number" || myPressure > 0 || myPressure < 10 ) { valveControl.setSensorPressure(state.val); } else { adapter.log.warn(`SensorPressure (0...10 bar || true/false) => 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}`); } } // wenn in der config unter methodControlSM!== 'analog' oder 'bistable' eingegeben wurde, dann Bodenfeuchte-Sensor auslesen if (myConfig.config) { // @ts-ignore 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); } } } } } // Feiertagskalender if (adapter.config.publicHolidays === true) { if (id === `${adapter.config.publicHolInstance}.heute.boolean`) { publicHolidayStr = state.val; startTimeSprinkle(); } if (id === `${adapter.config.publicHolInstance}.morgen.boolean`) { publicHolidayTomorrowStr = state.val; startTimeSprinkle(); } } // Wettervorhersage if (adapter.config.weatherForecastService !== 'noWeatherData') { 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}.location_1.ForecastDaily.Day_2.Rain`) { 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(state.val); //fillLevelCistern = state.val || 0; } // Rückmeldungen mit ack === False if(state.ack === false){ // wenn (Holiday == true) ist, soll das Wochenendprogramm gefahren werden. if (id === `${adapter.namespace}.control.Holiday`) { 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`) { 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 === false) { valveControl.clearEntireList(); } startTimeSprinkle(); secondStartTimeSprinkle(); } // 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("autoStart"); } } // wenn (...sprinkleName.runningTime sich ändert) so wird der aktuelle Sprenger [sprinkleName] // bei == 0 gestoppt, > 1 gestartet || Zeit abgeändert if (myConfig.config && state.val !== '00:00') { 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) ? state.val : Math.round(60 * state.val) }]); adapter.setState(id, { val: state.val, ack: true }); } } } } const idSplit = id.split('.', 5); const _found = myConfig.config.find((d) => d.objectName === idSplit[3]); if (_found) { // wenn (...sprinkleName.autoOn == false[off]) so wird der aktuelle Sprenger [sprinkleName] // bei false nicht automatisch gestartet if (idSplit[4] === 'autoOn' && 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 }] ); adapter.setState(`sprinkle.${_found.objectName}.sprinklerState`, { val: 'off', ack: true }); } else if (_found.methodControlSM === 'fixDay') { await curNextFixDay(myConfig.config[_found.sprinkleID].sprinkleID, false).catch((e) => { adapter.log.warn(`main.autoOn: (${myConfig.config[_found.sprinkleID].sprinkleID}, false) ${e}`); }); } } // extBreak → Pause für den Spränger bis 0:05 if (idSplit[4] === `extBreak`) { _found.extBreak = state.val; // speichern unter myConfig.config.*.extBreak const _extBreak = await valveControl.extBreak(_found.sprinkleID, state.val).catch((e) => { adapter.log.warn(`main.extBreak: ${e}`); }); if (_extBreak?.name) adapter.log.info(`extBreak: ${_extBreak.name}, ${_extBreak.val ? 'on' : 'off'}`); if (_extBreak?.val === false) { /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost), < 6 > Cistern empty, <<< 7 >>> extBreak */ adapter.setState(`sprinkle.${_found.objectName}.sprinklerState`, { val: state.val ? 'extBreak' : 'off', ack: true }); } adapter.setState(id, { // Bestätigung val: state.val, ack: true }); } // postponeByOneDay → um einen Tag verschieben bei fixDay (twoNd & threeRd) if (idSplit[4] === `postponeByOneDay`) { myConfig.postponeByOneDay(_found.sprinkleID).catch((e) => { adapter.log.warn(`.postponeByOneDay: ${e}`); }); adapter.setState(id, { // Bestätigung val: false, ack: true }); } } // (state.ack === true) => Rückmeldung für das Schalten der Ventile, Druckentlastungsventil, Steuerspannung, Pumpen }else if (state.ack === true) { // Bestätigung für das Schalten der Ventile if (myConfig.config) { //adapter.log.info(`vor found: ${id} (${state.val}) => ack === ${state.ack}`); const found = myConfig.config.find((d) => d.control.idACK === id); //adapter.log.info(`nach found: ${id} => ${JSON.stringify(found)}`); if (found && (id === myConfig.config[found.sprinkleID].control.idACK)) { found.state = state; } } if (threadList) { const found = threadList.find((d) => d.control.idACK === id); if (found !== undefined && typeof found.controller.ackTrue === 'function') { found.controller.ackTrue(state); } } // Druckentlastungsventil if (id === pressureReliefValve?.control?.idACK && typeof pressureReliefValve.controller.ackTrue === 'function') { pressureReliefValve.controller.ackTrue(state); } // 24V Steuerspannung if (id === controlVoltage?.control?.idACK && typeof controlVoltage.controller.ackTrue === 'function') { controlVoltage.controller.ackTrue(state); } // Pumpe (aktuell verwendete Pumpe) if (id === currentPumpUse?.control?.idACK && typeof currentPumpUse.controller.ackTrue === 'function') { currentPumpUse.controller.ackTrue(state); } // The state was deleted // adapter.log.debug(`state ${id} deleted`); } } } catch (e) { adapter.log.error(`stateChange: ${e}`); } }, // Wenn Sie Nachrichten in Ihrem Adapter akzeptieren müssen, entkommentieren Sie den folgenden Block. // /** // * Über das Nachrichtenfeld wurde eine Nachricht an diese Instanz gesendet. Wird von E-Mail, Pushover, Text2Speech usw. verwendet. // * Für die Verwendung dieser Methode muss die Eigenschaft „common.messagebox“ in io-package.json auf true gesetzt sein // */ message: (obj) => { if (obj) { switch (obj.command) { case 'getTelegramUser': adapter.getForeignState(`${adapter.config.telegramInstance}.communicate.users`, (err, state) => { // @ts-ignore err && adapter.log.error(err); if (state && state.val) { try { adapter.log.debug(`getTelegramUser:${state.val}`); // @ts-ignore 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; } } } })); } // +++++++++++++++++ Get longitude an latitude from system config ++++++++++++++++++++ /**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', 'state'); if (obj?.common?.longitude && obj?.common?.latitude) { adapter.config.longitude = obj.common.longitude.toString(); adapter.config.latitude = obj.common.latitude.toString(); adapter.log.debug(`longitude: ${adapter.config.longitude} | latitude: ${adapter.config.latitude}`); } else { adapter.log.error('system settings cannot be called up. Please check configuration!'); } } 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 * - sprinkleID - Number of Array[0...] * - returnOn - true = Rückgabe des Wochentags; false = Schreiben in State /.actualSoilMoisture * - nextStart ['Sun','Mon','Tue','Wed','Thur','Fri','Sat'] * * @param sprinkleID * @param returnOn */ async function curNextFixDay (sprinkleID, returnOn) { try { const weekDayArray = myConfig.config[sprinkleID].fixDay.startFixDay; const objPfad = `sprinkle.${myConfig.config[sprinkleID].objectName}`; const weekday = ['Sun','Mon','Tue','Wed','Thur','Fri','Sat']; let found = false; let curDay = tools.formatTime().day; const d = new Date(); const curTime = `${zweiStellen(d.getHours())}:${zweiStellen(d.getMinutes())}`; function zweiStellen (s) { while (s.toString().length < 2) { s = `0${s}`; } return s; } if (curTime >= startTimeStr) { 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'; } } catch (error) { adapter.log.error(`Error curNextFixDay: ${error}`); } } /** * Neuen Start-Tag für Dreitage- und Zweitage-modus setzen * * @param {number} indexNr * @param {boolean} threeRd - Dreitage-modus Ja/Nein * true → Dreitage-modus (treeRD) * false → Zweitage-modus (twoNd) */ async function setNewDay (indexNr, threeRd) { const today = tools.formatTime().day; const nextStartDay = ((today + 1) > 6 ? 0 : (today + 1)); const objectName = myConfig.config[indexNr].objectName; const _actualSoilMoisture = await adapter.getStateAsync( `sprinkle.${objectName}.actualSoilMoisture` ).catch((e) => adapter.log.warn(`${objectName}.actualSoilMoisture fixDay setState ${e}`)); if (_actualSoilMoisture && (typeof _actualSoilMoisture.val === 'number')) { if ((_actualSoilMoisture.val >= 0) && (_actualSoilMoisture.val <= 6)) { if ((threeRd) && (_actualSoilMoisture.val === (((today + 3) > 6) ? 2 : (today + 3))) || (_actualSoilMoisture.val === (((today + 2) > 6) ? 1 : (today + 2))) || (_actualSoilMoisture.val === (((today + 1) > 6) ? 0 : (today + 1))) || (_actualSoilMoisture.val === today)) { myConfig.config[indexNr].fixDay.startFixDay[_actualSoilMoisture.val] = true; } else { myConfig.config[indexNr].fixDay.startFixDay[nextStartDay] = true; } } else { myConfig.config[indexNr].fixDay.startFixDay[nextStartDay] = true; } curNextFixDay(myConfig.config[indexNr].sprinkleID, false); } } /** * result Rain * - (aktuelle Wettervorhersage - Schwellwert der Regenberücksichtigung) wenn Sensor sich im Freien befindet * - (> 0) es regnet - Abbruch - * - (≤ 0) Start der Bewässerung * * @param {boolean} inGreenhouse - Sensor befindet sich im Gewächshaus * @returns {number} - resultierende Regenmenge */ function resRain (inGreenhouse) { return +((adapter.config.weatherForecastService !== 'noWeatherData' && !inGreenhouse) ? (((+ weatherForecastTodayNum) - parseFloat(adapter.config.thresholdRain)).toFixed(1)) : 0); } /** * Sets the status (of “instanceObjects” from io-package.json) to a defined value at startup * → Setzt den Status (von "instanceObjects" aus io-package.json) beim Start auf einen definierten Wert */ async function checkStates() { try { /** * return the saved objects under sprinkle.* * rückgabe der gespeicherten Objekte unter sprinkle.* */ const _list = await adapter.getForeignObjectsAsync(`${adapter.namespace}.sprinkle.*`, 'channel'); if (_list) { ObjSprinkle = _list; } /* control.Holiday */ const _holiday = await adapter.getStateAsync('control.Holiday'); if (!(_holiday?.val)) { adapter.setStateAsync('control.Holiday', { val: false, ack: true }); } /* control.autoOnOff */ const _autoOnOff = await adapter.getStateAsync('control.autoOnOff'); if (typeof _autoOnOff.val === 'boolean') { autoOnOffStr = _autoOnOff.val; } else { autoOnOffStr = true; adapter.setStateAsync('control.autoOnOff', { val: autoOnOffStr, ack: true }); } /* evaporation.ETpToday */ const _ETpToday = await adapter.getStateAsync('evaporation.ETpToday'); if (_ETpToday?.val) { evaporation.setETpTodayNum(+_ETpToday.val); } else { evaporation.setETpTodayNum(0); adapter.setStateAsync('evaporation.ETpToday', { val: 0, ack: true }); } /* evaporation.ETpYesterday */ const _ETpYesterday = await adapter.getStateAsync('evaporation.ETpYesterday'); if (_ETpYesterday?.val && typeof _ETpYesterday.val !== 'number') { adapter.setStateAsync('evaporation.ETpYesterday', { val: 0, ack: true }); } /* evaporation.dailyHighTemp */ const _dailyHighTemp = await adapter.getStateAsync('evaporation.dailyHighTemp'); if (_dailyHighTemp?.val && typeof _dailyHighTemp.val === 'number') { evaporation.setCurTemperatureMax(+_dailyHighTemp.val); } else { evaporation.setCurTemperatureMax(0); adapter.setStateAsync('evaporation.dailyHighTemp', { val: 0, ack: true }); } /* akt. kW ermitteln für history last week */ const _formatTime = tools.formatTime(); kw = _formatTime.kW; today = _formatTime.day; } catch (e) { adapter.log.error(`Error checkStates: ${e}`); } } /** * 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?.val && typeof _holiday.val === 'boolean') { holidayStr = _holiday.val; } /* switch autoOnOff */ const _autoOnOff = await adapter.getStateAsync('control.autoOnOff'); if (_autoOnOff?.val && typeof _autoOnOff.val === 'boolean') { autoOnOffStr = _autoOnOff.val; } /* wenn (addStartTimeSwitch == true) wird die zusätzliche Bewässerung aktiviert */ if (adapter.config.selectAddStartTime === 'withExternalSignal') { await adapter.subscribeStatesAsync('control.addStartTimeSwitch'); const _addStartTimeSwitch = await adapter.getStateAsync('control.addStartTimeSwitch'); if (_addStartTimeSwitch?.val && typeof _addStartTimeSwitch.val === 'boolean') { addStartTimeSwitch = _addStartTimeSwitch.val; } } /* Feiertage */ if (adapter.config.publicHolidays === true && (adapter.config.publicHolInstance !== undefined || adapter.config.publicHolInstance !== '')) { /* Feiertag Heute */ const _publicHolInstanceHeute = await adapter.getForeignStateAsync( `${adapter.config.publicHolInstance}.heute.boolean` );//.catch((e) => adapter.log.warn(`publicHolInstanceHeute: ${e}`)); if (_publicHolInstanceHeute?.val) { publicHolidayStr = _publicHolInstanceHeute.val; } /* Feiertag MORGEN */ const _publicHolInstanceMorgen = await adapter.getForeignStateAsync( `${adapter.config.publicHolInstance}.morgen.boolean` );//.catch((e) => adapter.log.warn(`publicHolInstanceMorgen: ${e}`)); if (_publicHolInstanceMorgen?.val) { publicHolidayTomorrowStr = _publicHolInstanceMorgen.val; } } if (adapter.config.weatherForecastService !== 'noWeatherData' && (weatherForecastTodayPfadStr !== undefined || weatherForecastTodayPfadStr !== '')) { /* Niederschlagsmenge HEUTE in mm */ const _weatherForInstanceToday = await adapter.getForeignStateAsync( weatherForecastTodayPfadStr ).catch((e) => adapter.log.warn(`weatherForInstanceToday(Pfad:${weatherForecastTodayPfadStr}): ${e}`)); if (_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 }); } if (adapter.config.weatherForInstance.length > 10) { /* Niederschlagsmenge MORGEN in mm */ const _weatherForInstance = await adapter.getForeignStateAsync( `${adapter.config.weatherForInstance}.location_1.ForecastDaily.Day_2.Rain` );//.catch((e) => adapter.log.warn(`weatherForInstance: ${e}`)); if (_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) { let _sprinklerState = 'off'; const _extBreak = await adapter.getForeignStateAsync(res.extBreakID); if (_extBreak && typeof _extBreak.val === 'boolean') { res.extBreak = _extBreak.val; if (_extBreak.val === true) { adapter.log.info(`get ${res.objectName}.extBreak = ${res.extBreak}`); } _sprinklerState = 'extBreak'; } const _autoOn = await adapter.getForeignStateAsync( res.autoOnID );//.catch((e) => adapter.log.warn(`autoOn: ${e}`)); if (_autoOn && typeof _autoOn.val === 'boolean') { res.autoOn = _autoOn.val; if (_autoOn.val === false) { adapter.log.info(`get ${res.objectName}.autoOn = ${res.autoOn}`); } _sprinklerState = 'off'; } await adapter.setStateAsync(`sprinkle.${res.objectName}.sprinklerState`, { val: _sprinklerState, ack: true }); } } /* Füllstand der Zisterne in % holen */ if (adapter.config.actualValueLevel){ const _actualValueLevel = await adapter.getForeignStateAsync( adapter.config.actualValueLevel );//.catch((e) => adapter.log.warn(`actualValueLevel: ${e}`)); if (_actualValueLevel?.val) { valveControl.setFillLevelCistern(_actualValueLevel.val); } } } 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') */ // @ts-ignore const calcPos = schedule.scheduleJob('calcPosTimer', '5 0 * * *', function() { // Berechnungen mittels SunCalc sunPos(); const _curTime = tools.formatTime(); today = _curTime.day; // History Daten aktualisieren, wenn eine neue Woche beginnt adapter.log.debug(`calcPos 0:05 old-KW: ${kw} new-KW: ${_curTime.kW} if: ${(kw !== _curTime.kW)}`); if (kw !== _curTime.kW) { const result = myConfig.config; if (result) { for(const i in result) { // eslint-disable-next-line no-prototype-builtins if (result.hasOwnProperty(i)) { const objectName = result[i].objectName; // @ts-ignore 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 }); } }); // @ts-ignore 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 }); } }); } } } kw = _curTime.kW; } // ETpToday, ETpYesterday, curTemperatureMax in evaporation aktualisieren da ein neuer Tag evaporation.setNewDay(); // Startzeit Festlegen → verzögert wegen Daten von SunCalc adapter.setTimeout(() => { startTimeSprinkle(); secondStartTimeSprinkle(); if (adapter.config.enableTimeBasedRestriction === true) { irrigationRestrictionOn(); irrigationRestrictionOff(); } addStartTimeSprinkle(); },1000); }); // Berechnung mittels sunCalc function sunPos() { let times; try{ // get today's sunlight times → Holen Sie sich die heutige Sonnenlichtzeit times = SunCalc.getTimes(new Date(), +adapter.config.latitude, +adapter.config.longitude); adapter.log.debug('calculate astrodata ...'); } catch (e) { adapter.log.error('cannot calculate astrodata ... please check your config for latitude und longitude!!'); } if (times) { // 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 irrigationRestrictionOn() { schedule.cancelJob('irrigationRestrictionOn'); if (adapter.config.enableTimeBasedRestriction === true) { const startOfInterruptionSplit = adapter.config.startOfInterruption.split(':'); const scheduleStartOfInterruption = schedule.scheduleJob('irrigationRestrictionOn', `${ startOfInterruptionSplit[1] } ${ startOfInterruptionSplit[0] } * * *`, function() { irrigationRestriction = true; valveControl.timeBasedRestriction(true); adapter.log.info(`Time-based irrigation restriction is enabled! (${adapter.config.startOfInterruption} - ${adapter.config.endOfInterruption})`); }); } } function irrigationRestrictionOff() { schedule.cancelJob('irrigationRestrictionOff'); const endOfInterruptionSplit = adapter.config.endOfInterruption.split(':'); const scheduleEndOfInterruption = schedule.scheduleJob('irrigationRestrictionOff', `${ endOfInterruptionSplit[1] } ${ endOfInterruptionSplit[0] } * * *`, function() { irrigationRestriction = false; valveControl.timeBasedRestriction(false); adapter.log.info('Time-based irrigation restrictions have been disabled!'); }); } function addStartTimeSprinkle() { schedule.cancelJob('sprinkleAddStartTime'); if (adapter.config.selectAddStartTime === 'greaterETpCurrent' || adapter.config.selectAddStartTime === 'greaterDailyMaxTemp' || adapter.config.selectAddStartTime === 'withExternalSignal' ) { const addStartTimeSplit = adapter.config.addWateringStartTime.split(':'); // @ts-ignore const scheduleAddStartTime = schedule.scheduleJob('sprinkleAddStartTime', `${ addStartTimeSplit[1] } ${ addStartTimeSplit[0] } * * *`, function() { // if (autoOnOff == false) => keine auto Start if (!autoOnOffStr) { sendMessageText.sendMessage('Irrigation not possible!\n (autoOnOff == false)'); schedule.cancelJob('sprinkleAddStartTime'); return; } // Zisterne leer → Abbruch if(valveControl.getIntBreakCisternPump()) { adapter.log.warn('Additional irrigation is not possible! The cistern is empty.'); sendMessageText.sendMessage('Additional irrigation is not possible!\n The cistern is empty.'); schedule.cancelJob('sprinkleAddStartTime'); return; } // @ts-ignore if (((adapter.config.selectAddStartTime === 'greaterETpCurrent') && (adapter.config.triggerAddStartTimeETpCur < evaporation.getETpTodayNum())) || ((adapter.config.selectAddStartTime === 'greaterDailyMaxTemp') && (adapter.config.triggerAddStartTimeTempMax < evaporation.getCurTemperatureMax())) || ((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 = []; 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.bistable.bool) { messageText += `<b>${res.objectName}</b> (${res.bistable.bool})\n` + ` ${res.extBreak ? 'extBreak ||' : 'START =>'} ${tools.addTime(Math.round(60 * res.addWateringTime), '')}\n`; memAddList.push({ auto: false, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * res.addWateringTime) }); } break; } case 'fixDay': { messageText += `<b>${res.objectName}</b>\n` + ` ${res.extBreak ? 'extBreak ||' : 'START =>'} ${tools.addTime(Math.round(60 * res.addWateringTime), '')}\n`; memAddList.push({ auto: false, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * res.addWateringTime) }); break; } case 'calculation': { messageText += `<b>${res.objectName}</b> ${res.calculation.pct}% (${res.calculation.pctTriggerIrrigation}%)\n` + ` ${res.extBreak ? 'extBreak ||' : 'START =>'} ${tools.addTime(Math.round(60 * res.addWateringTime), '')}\n`; memAddList.push({ auto: false, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * res.addWateringTime) }); break; } case 'analog': { if (res.analog.pct < res.analog.pctAddTriggersIrrigation) { messageText += `<b>${res.objectName}</b> ${res.analog.pct} %(${res.analog.pctAddTriggersIrrigation}%)\n` + ` ${res.extBreak ? 'extBreak ||' : 'START =>'} ${tools.addTime(Math.round(60 * res.addWateringTime), '')}\n`; memAddList.push({ auto: false, sprinkleID: res.sprinkleID, wateringTime: Math.round(60 * res.addWateringTime) }); } break; } } } else { adapter.log.debug(`${res.objectName}: autoOn (${res.autoOn}) && addWateringTime (${60 * res.addWateringTime} > 0) && resRain (${resRain(res.inGreenhouse)}) <= 0, if(${res.autoOn && (60 * res.addWateringTime > 0) && (resRain(res.inGreenhouse) <= 0)})`); } } valveControl.addList(memAddList); } if(!sendMessageText.onlySendError() && messageText.length > 0){ sendMessageText.sendMessage(messageText); } } else { // @ts-ignore adapter.log.debug(`greaterETpCurrent: ${(adapter.config.selectAddStartTime === 'greaterETpCurrent')} & ${(adapter.config.triggerAddStartTimeETpCur < evaporation.getETpTodayNum())}, withExternalSignal; ${(adapter.config.selectAddStartTime === 'withExternalSignal')} & ${addStartTimeSwitch}`); } adapter.setTimeout(()=>{ schedule.cancelJob('sprinkleAddStartTime'); }, 200); }); } } // Determination of the irrigation time => Bestimmung der Bewässerungszeit function startTimeSprinkle() { let startTimeSplit = []; let infoMessage; schedule.cancelJob('sprinkleSta