iobroker.sprinklecontrol
Version:
Control of several sprinklers depending on weather conditions and pump performance.
273 lines (248 loc) • 11.2 kB
JavaScript
;
/**
* tools.js
* Hier werden allgemeine Funktionen bereitgestellt, die in verschiedenen Modulen des Bewässerungsplaners verwendet werden können.
* Diese Funktionen umfassen:
* - laterThanTime: Überprüft, ob eine angegebene Zeit in der Zukunft liegt.
* - trend: Berechnet einen Zwischenwert basierend auf zwei bekannten Punkten.
* - findAsync: Eine asynchrone Version der Array.find-Methode, die mit Promises arbeitet.
* - idStateControl: Ermittelt die ID-Adresse für State, ON_TIME, WORKING, PROCESS eines Homematic-Schaltaktors basierend auf der ID des Geräts und dem konfigurierten Schaltverhalten.
* - formatTime: Formatiert einen Zeitstempel in ein bestimmtes Format und gibt zusätzliche Informationen wie die Kalenderwoche und den Wochentag zurück.
* - addTime: Addiert zwei Zeitwerte, die entweder im Format "HH:MM:SS" oder als Sekunden angegeben werden können, und gibt das Ergebnis im gleichen Format zurück.
*
* Diese Funktionen sind so gestaltet, dass sie flexibel und wiederverwendbar sind, um die Entwicklung des Bewässerungsplaners zu erleichtern.
*/
const tools = {
/**
* laterThanTime Funktion
*
* @param {string} time - Zeit im Format "HH:MM"
* @returns {boolean} true, wenn die angegebene Zeit noch in der Zukunft liegt, andernfalls false die Zeitliegt in der Vergangenheit
*/
laterThanTime: function (time) {
const now = new Date();
const [hours, minutes] = time.split(':').map(Number);
const targetTime = new Date();
targetTime.setHours(hours, minutes, 0, 0);
return now < targetTime;
},
/**
* TREND - Zwischenwert ermitteln
*
* @param {number} a1 - Istwert a1 der Zeitachse
* @param {number} a2 - Istwert a2 der Zeitachse
* @param {number} b1 - Sollwert b1 der Wertachse
* @param {number} b2 - Sollwert b2 der Wertachse
* @param {number} sollA3 - Sollwert soll_A3 der Zeitachse
* @returns {number} Zwischenwert
*/
trend: function (a1,a2,b1,b2,sollA3) {
return (sollA3-a1)*(b2-b1)/(a2-a1)+b1;
},
// Signature of the callback
// type CallBackFind<T> = (
// value: T,
// index?: number,
// collection?: T[]
// ) => Promise<boolean>;
/**
* Async Find function
*
* You can use as follows
* const array = [1, 2, 3, 4];
* const output = await findAsync<number>(array, async (i) => {
* return Promise.resolve(i === 2);
* });
*
* @template T
* @param {T[]} elements
* @param {(element: T, index: number, elements: T[]) => Promise<boolean>} cb
* @returns {Promise<T | undefined>}
*/
findAsync: async function ( elements, cb) {
for (const [index, element] of elements.entries()) {
if (await cb(element, index, elements)) {
return element;
}
}
return undefined;
},
/**
* ID Homematic
* Hiermit kann die ID-Adresse State, ON_TIME, WORKING, PROCESS eines Homematic-Schaltaktors
* ermittelt werden, wenn die ID des Geräts bekannt ist.
*
* @param {object} adapter - Der ioBroker-Adapter, der die Funktion aufruft
* @param {string} id - ID des Geräts
* @returns {Promise} Promise, die die ID-Adresse zurückgibt
*/
idStateControl: async function (adapter, id) {
// "hm-rpc.0.MEQ1234567.3.STATE" => "hm-rpc.0.MEQ1234567.3"
const pfad = id.substring(0, id.lastIndexOf('.'));
switch (adapter.config.switchingBehavior) {
case "standard": {
// standard => "hm-rpc.0.MEQ1234567.3.STATE"
return {
idState: id,
idON_TIME: null,
idACK: id,
maker: "standard"
};
}
case "homematic": {
try {
const _state = await adapter.getForeignStateAsync(`${pfad}.STATE`);
const _ON_TIME = await adapter.getForeignStateAsync(`${pfad}.ON_TIME`);
const _WORKING = await adapter.getForeignStateAsync(`${pfad}.WORKING`);
const _PROCESS = await adapter.getForeignStateAsync(`${pfad}.PROCESS`);
if (typeof _state?.val !== 'boolean') throw new Error(`idStateControl: State ${pfad}.STATE nicht gefunden`);
if (_WORKING) {
return {
idState: _state ? `${pfad}.STATE` : null,
idON_TIME: _ON_TIME ? `${pfad}.ON_TIME` : null,
idACK: _WORKING ? `${pfad}.WORKING` : null,
maker: 'HM'
};
} else if (_PROCESS) {
return {
idState: _state ? `${pfad}.STATE` : null,
idON_TIME: _ON_TIME ? `${pfad}.ON_TIME` : null,
idACK: _PROCESS ? `${pfad}.PROCESS` : null,
maker: 'HmIP'
};
} else {
return {
idState: _state ? `${pfad}.STATE` : null,
idON_TIME: null,
idACK: _state ? `${pfad}.STATE` : null,
maker: 'standard'
};
}
} catch (error) {
adapter.log.error(`idStateControl: Fehler bei der Ermittlung der ID-Adresse für ${id}: ${error.message}`);
return {
idState: id,
idON_TIME: null,
idACK: id,
maker: 'unknown'
};
}
}
case "noResponse": {
// standard => "hm-rpc.0.MEQ1234567.3.STATE"
return {
idState: id,
idON_TIME: null,
idACK: null,
maker: "noResponse"
};
}
default: {
adapter.log.error(`„Zusätzliche Einstellungen“ => „Schaltverhalten anpassen“ enthielt keine Eingabe! Es wurde daher auf „Befehl ohne Rückmeldung“ gesetzt.`);
adapter.config.switchingBehavior = "noResponse";
return {
idState: id,
idON_TIME: null,
idACK: null,
maker: "default"
};
}
}
},
/**
* func Format Time
* → hier wird der übergebene Zeitstempel, myDate, in das angegebene Format, timeFormat, umgewandelt.
* Ist myDate nicht angegeben, so wird die aktuelle Zeit verwendet.
*
* @param {Date=} myDate - wenn nicht angegeben; dann wird aktuelles Datum verwendet
* @returns {{dayNr: number; kW: number; day: number; dayTime: string; past: boolean}}
* - kW: (number) Rückgabe der KW;
* - dayNr: (number) Tag des Jahres (Tagesnummer)
* - day: (number) Wochentag
* - dd.mm. hh:mm: (string) Rückgabe Datum und Zeit
* - past: true => (boolean) Heute schon vorbei
*/
formatTime: function (myDate) { // 'kW' 'dd.mm. hh:mm'
function zweiStellen (s) {
while (s.toString().length < 2) {
s = `0${ s }`;
}
return s;
}
const d = (myDate)? new Date(myDate):new Date();
const tag = zweiStellen(d.getDate());
const monat = zweiStellen(d.getMonth() + 1);
const stunde = zweiStellen(d.getHours());
const minute = zweiStellen(d.getMinutes());
const currentThursday = new Date(d.getTime() +(3-((d.getDay()+6) % 7)) * 86400000);
// At the beginning or end of a year the thursday could be in another year.
const yearOfThursday = currentThursday.getFullYear();
// Get first Thursday of the year
const firstThursday = new Date(new Date(yearOfThursday,0,4).getTime() +(3-((new Date(yearOfThursday,0,4).getDay()+6) % 7)) * 86400000);
const start = new Date(d.getFullYear(), 0, 0);
const diff = (+d - +start) + ((start.getTimezoneOffset() - d.getTimezoneOffset()) * 60 * 1000);
const oneDay = 1000 * 60 * 60 * 24;
const dayNr = Math.floor(diff / oneDay);
// +1 we start with week number 1
// +0.5 an easy and dirty way to round result (in combination with Math.floor)
const kW = Math.floor(1 + 0.5 + (currentThursday.getTime() - firstThursday.getTime()) / 86400000/7);
// Wochentag 0:So;1:Mo;...6:Sa
const day = d.getDay();
// 'dd.mm. hh:mm':
const dayTime = `${tag}.${monat} ${stunde}:${minute}`;
// true → Heute schon vorbei
const cd = new Date();
const cStunde = zweiStellen(cd.getHours());
const cMinute = zweiStellen(cd.getMinutes());
const past = ((stunde > cStunde) || ((stunde === cStunde) && (minute > cMinute)));
return {
dayNr: dayNr,
kW: kW,
day: day,
dayTime: dayTime,
past: past
};
},
/**
* func addTime (02:12:24 + 00:15) || (807) => 02:12:39
*
* @param time1 {string|number} z.B. 02:12:24 || 807 => 02:12:39
* @param time2 {string|number|undefined} z.B. 02:12:24 || 807 => 02:12:39 || undef.
* @returns {string} z.B. mm:ss || hh:mm:ss
*/
addTime: function (time1, time2){
const wert = string2seconds(time1) + string2seconds(time2);
return seconds2string(wert);
// private functions
function seconds2string(n){
n = Math.abs(n);
const h = Math.trunc(n / 3600);
const m = Math.trunc((n / 60 ) % 60);
const sec = Math.trunc(n % 60);
return (h === 0)?(`${frmt(m)}:${frmt(sec)}`):(`${frmt(h)}:${m}:${frmt(sec)}`);
} // end function seconds2string
function string2seconds(n) {
if(!n || (n === '--:--')) return 0;
if(Number.isInteger(n)) return n;
const tmp = n.split(':').reverse();
if(!tmp.length) tmp[0] = 0; // Sekunden
if(tmp.length < 2) tmp[1] = 0; // Minuten
if(tmp.length < 3) tmp[2] = 0; // Stunden
while(tmp[0] > 59) {
tmp[0] -= 60;
++tmp[1];
}
while(tmp[1] > 59) {
tmp[1] -= 60;
++tmp[2];
}
return (tmp[2] * 3600 + tmp[1] * 60 + 1 * tmp[0]);
} // string2seconds
function frmt(n) {
return n < 10 ? `0${ n }` : n;
}
} // end - function addTime
}
module.exports = {
tools
};