iobroker.sprinklecontrol
Version:
Control of several sprinklers depending on weather conditions and pump performance.
1,075 lines (1,001 loc) • 112 kB
JavaScript
'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;