iobroker.sprinklecontrol
Version:
Control of several sprinklers depending on weather conditions and pump performance.
975 lines (911 loc) • 133 kB
JavaScript
'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