iobroker.sprinklecontrol
Version:
Control of several sprinklers depending on weather conditions and pump performance.
373 lines (356 loc) • 22.2 kB
JavaScript
;
/*
info: log aufbau myConfig.js: #1.*
*/
const {formatTime} = require('./tools');
const trend = require('./tools').trend; // tools => laden von Hilfsfunktionen
// const formatTime = require('./tools').formatTime; // tools => laden von Hilfsfunktionen
let adapter;
/**
* Ermittlung der Bewässerungsmethode und der dazugehörigen Parameter
* Sensorabfrage für Bodenfeuchte-Sensoren beantragen (subscribeStates)
*
* @param {object} res - Objekt mit den Ventildaten aus der Config
* @param {string} res.methodControlSM - methodControlSM [Auswahlfeld in der Config].
* @param {string} res.triggerSM - triggerSM [Bodenfeuchte-Sensor in der Config].
* @param {string} res.analogZPct - analog Zero Percent [Auswahlfeld in der Config des Sprinklers > Bodenfeuchte-Sensor > analog > Wert bei 0 %].
* @param {string} res.analogOHPct - analog One Hundert Percent [Auswahlfeld in der Config des Sprinklers > Bodenfeuchte-Sensor > analog > Wert bei 100 %].
* @param {string} res.maxSoilMoistureIrrigation - maximale Bodenfeuchte nach der Bewässerung [Auswahlfeld in der Config des Sprinklers].
* @param {string} res.startDay - Auswahl (threeRd = Start im 3-Tages-Rhythmus,twoNd = Start im 2-Tages-Rhythmus, fixDay = Start an festen Tagen Sun-Sat).
* @param {boolean} res.sun - Sontag, Sunday (Sun).
* @param {boolean} res.mon - Montag, Monday (Mon).
* @param {boolean} res.tue - Dienstag, Tuesday (Tue) (Tues).
* @param {boolean} res.wed - Mittwoch, Wednesday (Wed).
* @param {boolean} res.thur - Donnerstag, Thursday (Thur) (Thurs).
* @param {boolean} res.fri - Freitag, Friday (Fri).
* @param {boolean} res.sat - Samstag, Saturday (Sat).
* @returns {{setMetConSM: string, setPct: (number|null), setStartDay: (string|null), setStartFixDay: boolean[]|null, setAnalogZPct: (number|null), setAnalogOHPct: (number|null), setTrigSM: (string|null), setVal: (number|null), setBool: (boolean|null)}}
*/
function getMetConSM(res) {
if (res.methodControlSM === 'bistable') {
// bistable (Bodenfeuchte-Sensor)
adapter.subscribeForeignStates(res.triggerSM);
return {
setStartDay: null,
setStartFixDay: null,
setMetConSM: 'bistable',
setTrigSM: res.triggerSM,
setAnalogZPct: null,
setAnalogOHPct: null,
setPct: null,
setVal: null,
setBool: null
};
} else if (res.methodControlSM === 'analog') {
// analog (Bodenfeuchte-Sensor)
adapter.subscribeForeignStates(res.triggerSM);
return {
setStartDay: null,
setStartFixDay: null,
setMetConSM: 'analog',
setTrigSM: res.triggerSM,
setAnalogZPct: parseFloat(res.analogZPct),
setAnalogOHPct: parseFloat(res.analogOHPct),
setPct: null,
setVal: null,
setBool: null
};
} else if (res.methodControlSM === 'fixDay') {
// start zur festen Zeit und Wochentag (ohne-Sensoren)
/**
* Wochentage an denen gestartet wird
*
*/
const startFixDay = [false, false, false, false, false, false, false]; // Sun, Mon, Tue, Wed, Thur, Fri, Sat
if(res.startDay === 'threeRd' || res.startDay === 'twoNd') {
//let today = formatTime(adapter,'', 'day');
//let nextStartDay = ((today + 1) > 6) ? 0 : (today + 1);
//startFixDay[nextStartDay] = true; // Start am nächsten Tag
} else if (res.startDay === 'fixDay') {
startFixDay[0] = res.sun;
startFixDay[1] = res.mon;
startFixDay[2] = res.tue;
startFixDay[3] = res.wed;
startFixDay[4] = res.thur;
startFixDay[5] = res.fri;
startFixDay[6] = res.sat;
}
return {
setStartDay: res.startDay,
setStartFixDay: startFixDay,
setMetConSM: 'fixDay',
setTrigSM: '',
setAnalogZPct: null,
setAnalogOHPct: null,
setPct: null,
setVal: null,
setBool: null
};
} else if (res.methodControlSM === 'calculation') {
// interne (Berechnung der Verdunstung)
return {
setStartDay: null,
setStartFixDay: null,
setMetConSM: 'calculation',
setTrigSM: '',
setAnalogZPct: null,
setAnalogOHPct: null,
setPct: 50,
setVal: parseFloat(res.maxSoilMoistureIrrigation) / 2,
setBool: null
};
} else {
// Notprogramm! In der SprinkleControl Konfiguration ist keine Bewässerungsart ausgewählt! Bitte anpassen.
adapter.log.warn('Emergency program! No irrigation type is selected in the SprinkleControl configuration! Please adjust.');
return {
setStartDay: 'twoNd',
setStartFixDay: [false, false, false, false, false, false, false], // Sun, Mon, Tue, Wed, Thur, Fri, Sat
setMetConSM: 'fixDay',
setTrigSM: '',
setAnalogZPct: null,
setAnalogOHPct: null,
setPct: null,
setVal: null,
setBool: null
};
}
}
const myConfig = {
config: [],
/**
*
* @param myAdapter - Kopie von Adapter main.js
*/
createConfig: (myAdapter) => {
adapter = myAdapter;
const result = adapter.config.events;
if (result) {
for (const res of result) {
/** Name des Bewässerungskreises */
let objectName = '';
if(res.sprinkleName !== '') {
objectName = res.sprinkleName.replace(/[.;, ]/g, '_');
} else if (res.sprinkleName === '') {
objectName = res.name.replace(/[.;, ]/g, '_');
}
const metConSM = getMetConSM(res);
/**
* Spränger hinzufügen
*/
const newEntry = {
/**
* Starttage in der Woche
* - 0(Sun); 1(Mon); 2(Tue); 3(Wed); 4(Thur); 5(Fri); 6(Sat)
*/
startFixDay: metConSM.setStartFixDay, // Sontag, Sunday (Sun)// Montag, Monday (Mon)// Dienstag, Tuesday (Tue) (Tues)// Mittwoch, Wednesday (Wed)// Donnerstag, Thursday (Thur) (Thurs)// Freitag, Friday (Fri)// Samstag, Saturday (Sat)
/**
* - Auswahl:
* - threeRd = Start im 3 Tages Rhythmus,
* - twoNd = Start im 2 Tages Rhythmus
* - fixDay = Start an festen Tagen Sun-Sat
*/
startDay: metConSM.setStartDay,
enabled: res.enabled || false,
booster: res.booster,
endIrrigation: res.endIrrigation,
autoOn: true,
autoOnID: `${adapter.namespace}.sprinkle.${objectName}.autoOn`, // sprinklecontrol.0.sprinkle.Rasenumrandung.autoOnID
objectName: objectName, // z.B. Rasenumrandung
objectID: `${adapter.namespace}.sprinkle.${objectName}.runningTime`, // sprinklecontrol.0.sprinkle.Rasenumrandung.runningTime
idState: res.name, // "hm-rpc.0.MEQ1234567.3.STATE"
updateStateTimerID: null, // Timer wird gelöscht wenn Rückmeldung erfolgte
sprinkleID: myConfig.config.length, // Array[0...]
wateringTime: parseInt(res.wateringTime), // ...min
wateringAdd: parseInt(res.wateringAdd), // 0 ... 200%
wateringInterval: ((60 * parseInt(res.wateringInterval)) || 0), // 5,10,15min
addWateringTime: (parseInt(res.addWateringTime) || 0), // ...min Zusatzbewässerung bei hohen Temperaturen
pipeFlow: parseInt(res.pipeFlow), // Wasserverbrauch des sprinkler-Kreises
methodControlSM: metConSM.setMetConSM, // Art der Kontrolle der Bodenfeuchte ('calculation'; 'bistable'; 'analog'; fixDay)
triggerSM: metConSM.setTrigSM, // Sensor für die Bodenfeuchte
inGreenhouse: res.inGreenhouse || false, // keine Wettervorhersage verwenden (Gewächshaus)
analogZPct: metConSM.setAnalogZPct, // analoger Sensor Wert bei 0% (analog zero percent)
analogOHPct: metConSM.setAnalogOHPct, // analoger Sensor Wert bei 100% (analog one hundert percent)
soilMoisture: {
val: metConSM.setVal, // Bodenfeuchte / Wassergehalt der oberen Bodenschicht (zB. 5 mm == 50%)
pct: metConSM.setPct, // Bodenfeuchte in % zB. 50% = maxSoilMoistureIrrigation) / 2
bool: metConSM.setBool, // Bodenfeuchtezustand trocken/feucht === true/false
min: parseFloat(res.maxSoilMoistureIrrigation) / 100, // (zB. 0,02 mm)
maxIrrigation: parseFloat(res.maxSoilMoistureIrrigation), // (zB. 10 mm)
maxRain: parseFloat(res.maxSoilMoistureIrrigation) /100 * (parseFloat(res.maxSoilMoistureRainPct) || 120), // (zB. 12 mm)
triggersIrrigation: parseFloat(res.maxSoilMoistureIrrigation) * parseInt(res.triggersIrrigation) / 100, // (zB. 50 % ==> 5 mm)
pctTriggerIrrigation: parseFloat(res.triggersIrrigation), // Auslöser der Bewässerung in % (zB. 50%)
pctAddTriggersIrrigation: (parseFloat(res.addTriggersIrrigation) < parseFloat(res.triggersIrrigation)) ? parseFloat(res.addTriggersIrrigation) : parseFloat(res.triggersIrrigation)
}
};
myConfig.config.push(newEntry);
if (newEntry.enabled) {
// Report a change in the status of the trigger IDs (.runningTime; .name) => Melden einer Änderung des Status der Trigger-IDs
adapter.subscribeStates(newEntry.objectID); // abonnieren der Statusänderungen des Objekts (reagieren auf 'runningTime' der einzelnen Bewässerungskreise)
adapter.subscribeStates(newEntry.autoOnID); // abonnieren der Statusänderungen des Objekts (reagieren auf 'autoOn' der einzelnen Bewässerungskreise)
// adapter.subscribeForeignStates(newEntry.idState); // abonnieren der Statusänderungen des Objekts (reagiert auf Änderung des 'Ventils' der einzelnen Bewässerungskreise zur Fehlerkontrolle bzw. Verbrauchsermittlung)
}
adapter.log.debug(`Config ${objectName} created (${newEntry.sprinkleID}) - ${JSON.stringify(myConfig.config[newEntry.sprinkleID])}`);
// kann nicht bewässern! Verbrauch höher als Hauptpumpenleistung
if (newEntry.pipeFlow > adapter.config.triggerMainPumpPower) {
adapter.log.warn(`${newEntry.objectName} cannot irrigate! Consumption (${newEntry.pipeFlow}) higher than pump capacity (${adapter.config.triggerMainPumpPower})`);
}
// kann nicht bewässern! Verbrauch höher als Zisternenpumpenleistung
if (adapter.config.cisternSettings && newEntry.pipeFlow > adapter.config.triggerCisternPumpPower) {
adapter.log.warn(`${newEntry.objectName} can not irrigate! Consumption (${newEntry.pipeFlow}) higher than cistern pump capacity (${adapter.config.triggerCisternPumpPower})`);
}
}
}
},
/**
* apply Evaporation
* → Verdunstung anwenden auf die einzelnen Sprenger kreise
*
* @param eTP - pot. Evapotranspiration nach Penman ETp in mm/d
*/
applyEvaporation: (eTP) => {
if (myConfig.config) {
for(const entry of myConfig.config) {
if (entry.methodControlSM === 'calculation'
&& !(entry.inGreenhouse && (eTP < 0))) { // nicht anwenden im Gewächshaus und Regen
const objectName = entry.objectName;
const pfadActSoiMoi = `sprinkle.${ objectName }.actualSoilMoisture`;
entry.soilMoisture.val -= eTP; // Abfrage => entry.soilMoisture.val
if (entry.soilMoisture.val < entry.soilMoisture.min) {
entry.soilMoisture.val = entry.soilMoisture.min;
} else if (entry.soilMoisture.val > entry.soilMoisture.maxRain) {
entry.soilMoisture.val = entry.soilMoisture.maxRain;
}
entry.soilMoisture.pct = Math.round(1000 * entry.soilMoisture.val / entry.soilMoisture.maxIrrigation) / 10; // Berechnung in %
adapter.log.debug(`apply Evaporation: ${objectName} => soilMoisture: ${entry.soilMoisture.val} soilMoisture: ${entry.soilMoisture.pct} %`);
adapter.setState(pfadActSoiMoi, {
val: entry.soilMoisture.pct,
ack: true
});
}
}
}
},
/**
* Bodenfeuchte (soilMoisture) setzen auf: => pct = 100%; val = maxIrrigation
*
* @param mySprinkleID - ID des Bewässerungskreises
*/
setSoilMoistPct100: (mySprinkleID) => {
myConfig.config[mySprinkleID].soilMoisture.val = myConfig.config[mySprinkleID].soilMoisture.maxIrrigation;
myConfig.config[mySprinkleID].soilMoisture.pct = 100;
adapter.setState(`sprinkle.${myConfig.config[mySprinkleID].objectName}.actualSoilMoisture`, {
val: myConfig.config[mySprinkleID].soilMoisture.pct,
ack: true
});
},
/**
* Speichern der Bodenfeuchtigkeit = bistabil (Bodenfeuchte-Sensor)
*
* @param mySprinkleID - ID des Bewässerungskreis
* @param newVal - neuer Wert vom Bodenfeuchte-Sensor
*/
setSoilMoistBool: (mySprinkleID, newVal) => {
if (myConfig.config[mySprinkleID].methodControlSM === 'bistable') {
if (typeof newVal === 'boolean') {
myConfig.config[mySprinkleID].soilMoisture.bool = newVal;
adapter.setState(`sprinkle.${[myConfig.config[mySprinkleID].objectName]}.actualSoilMoisture`, {
val: myConfig.config[mySprinkleID].soilMoisture.bool,
ack: true
});
} else {
adapter.log.warn(`The ${myConfig.config[mySprinkleID].objectName} soil moisture sensor does not provide a Boolean signal`);
}
} else {
adapter.log.warn(`Please check the signals and settings of the ${myConfig.config[mySprinkleID].objectName} soil moisture sensor`);
}
},
/**
* Speichern der Bodenfeuchtigkeit = analog (Bodenfeuchte-Sensor)
*
* @param mySprinkleID - ID des Bewässerungskreis
* @param newVal - neuer Wert vom Bodenfeuchte-Sensor
*/
setSoilMoistPct: (mySprinkleID, newVal) => {
if (myConfig.config[mySprinkleID].methodControlSM === 'analog') {
if (typeof parseFloat(newVal) === 'number') {
/**aktueller Wert des Bodenfeuchte-Sensor
* @type {number} myVal
*/
let myVal;
/**Reversible Eingang des Bodenfeuchte sensors
* @type {boolean} reverse -Eingang analogOHPct < analogZPct
*
*/
const reverse = (myConfig.config[mySprinkleID].analogOHPct < myConfig.config[mySprinkleID].analogZPct);
if (myConfig.config[mySprinkleID].analogOHPct === myConfig.config[mySprinkleID].analogZPct) {
adapter.log.warn(`${myConfig.config[mySprinkleID].objectName}: analog soil moisture sensor at 0% and at 100% => the values are the same`);
}
newVal = parseFloat(newVal);
if ((!reverse && newVal < myConfig.config[mySprinkleID].analogZPct)
|| (reverse && myConfig.config[mySprinkleID].analogZPct < newVal)) {
myVal = myConfig.config[mySprinkleID].analogZPct;
adapter.log.warn(`${myConfig.config[mySprinkleID].objectName} (${newVal}): analog soil moisture sensor at 0 % => ${reverse ? 'The range of values has been exceeded (reverse)' : 'The value range was undercut'}`);
} else if ((!reverse && newVal > myConfig.config[mySprinkleID].analogOHPct)
|| (reverse && myConfig.config[mySprinkleID].analogOHPct > newVal)) {
myVal = myConfig.config[mySprinkleID].analogOHPct;
adapter.log.warn(`${myConfig.config[mySprinkleID].objectName} (${newVal}): analog soil moisture sensor at 100 % => ${reverse ? 'The value range was undercut (reverse)' : 'The range of values has been exceeded'}`);
} else {
myVal = newVal;
}
myConfig.config[mySprinkleID].soilMoisture.pct = Math.round(10 * trend(myConfig.config[mySprinkleID].analogZPct, myConfig.config[mySprinkleID].analogOHPct, 0, 100, myVal)) / 10;
adapter.setState(`sprinkle.${[myConfig.config[mySprinkleID].objectName]}.actualSoilMoisture`, {
val: myConfig.config[mySprinkleID].soilMoisture.pct,
ack: true
});
} else {
adapter.log.warn(`The ${myConfig.config[mySprinkleID].objectName} soil moisture sensor does not provide a Number signal`);
}
} else {
adapter.log.warn(`Please check the signals and settings of the ${myConfig.config[mySprinkleID].objectName} soil moisture sensor`);
}
},
/**
* Bodenfeuchte (soilMoisture) erhöhen bis maxIrrigation (100%)
*
* @param mySprinkleID - ID des Bewässerungskreis
* @param addVal - soilMoisture.val wird um den Wert addVal erhöht
*/
addSoilMoistVal: (mySprinkleID, addVal) => {
if (myConfig.config[mySprinkleID].soilMoisture.val < myConfig.config[mySprinkleID].soilMoisture.maxIrrigation) {
myConfig.config[mySprinkleID].soilMoisture.val += addVal;
if (myConfig.config[mySprinkleID].soilMoisture.val > myConfig.config[mySprinkleID].soilMoisture.maxIrrigation) {
myConfig.config[mySprinkleID].soilMoisture.val = myConfig.config[mySprinkleID].soilMoisture.maxIrrigation;
}
}
myConfig.config[mySprinkleID].soilMoisture.pct = Math.round(1000 * myConfig.config[mySprinkleID].soilMoisture.val
/ myConfig.config[mySprinkleID].soilMoisture.maxIrrigation) / 10; // Berechnung in %
adapter.setState(`sprinkle.${myConfig.config[mySprinkleID].objectName}.actualSoilMoisture`, {
val: myConfig.config[mySprinkleID].soilMoisture.pct,
ack: true
});
},
postponeByOneDay: async (mySprinkleID) => {
if (myConfig.config[mySprinkleID].startDay === 'threeRd' || // Next Start in 3 Tagen
myConfig.config[mySprinkleID].startDay === 'twoNd') { // Next Start in 2 Tagen
const today = await formatTime(adapter,'', 'day');
const id = `${adapter.namespace}.sprinkle.${myConfig.config[mySprinkleID].objectName}.actualSoilMoisture`;
let curDay, nextDay;
/**
* Wert von actualSoilMoisture auslesen
*
*/
const _curDay = await adapter.getStateAsync(id).catch((e) => adapter.log.warn(`postponeByOneDay getStateAsync: ${e}`));
if (_curDay) {
curDay = ((_curDay.val >= 0) && (_curDay.val <= 6) && (typeof _curDay.val === 'number') ? _curDay.val : today);
myConfig.config[mySprinkleID].startFixDay[curDay] = false;
nextDay = (+ curDay + 1 > 6) ? (+ curDay-6) : (+ curDay+1);
myConfig.config[mySprinkleID].startFixDay[nextDay] = true;
adapter.setStateAsync(`${id}`, {
val: nextDay,
ack: true
}).catch((e) => adapter.log.warn(`postponeByOneDay setStateAsync: ${e}`));
}
}
}
};
module.exports = myConfig;