NEWS
JS für Überwachung der Luftfeuchtigkeit in Räumen
-
Hallo zusammen,
ich möchte euch heute mein neues ioBroker-Skript vorstellen, das Lüftungsempfehlungen basierend auf dem Raumklima gibt.
Das Skript liest für mehrere Räume die aktuellen Sensorwerte (relative Luftfeuchte und Temperatur) ein und berechnet daraus zwei wichtige Empfehlungen:Feste Schwellenwert-Berechnung:
Hier wird geprüft, ob die relative Luftfeuchte in einem Raum einen definierten Schwellenwert (z. B. 57 % oder individuell pro Raum) erreicht oder überschritten hat. Überschreitet der Wert den Schwellenwert, wird empfohlen, den Raum zu lüften.Absolute Luftfeuchte-Berechnung:
Mittels der Formel
AH = (6.112 * exp((17.67 * T) / (T + 243.5)) * RH * 2.1674) / (T + 273.15)
wird die absolute Luftfeuchte (in g/m³) sowohl für die Innen- als auch die Außenluft berechnet. Für die Außendaten wird zusätzlich der Luftdruck als Korrekturfaktor einbezogen, sodass
AH_out = AH * (P / 1013.25)
entsteht. Vergleicht man nun die absolute Luftfeuchte im Raum (AH_in) mit der Außenluft (AH_out) – ggf. unter Berücksichtigung eines Offsets –, kann ermittelt werden, ob Lüften die Luftfeuchte senken würde.Das Skript ist dabei sehr modular aufgebaut und lässt sich über eine zentrale Konfiguration (config) steuern:
Aktivierung einzelner Berechnungen:
Ihr könnt separat die feste Schwellenwert-Berechnung und die absolute Luftfeuchte-Berechnung ein- bzw. ausschalten.Raumdefinition:
Für jeden Raum sind die zugehörigen Sensoren (Datenpunkte für Temperatur und Luftfeuchte) und die Ziel-Datenpunkte (in die die Empfehlung geschrieben wird) hinterlegt. Zudem kann pro Raum ein individueller Schwellenwert definiert werden, andernfalls wird der globale Wert genutzt.Sensorverfügbarkeit und Sensoralter:
Optional prüft das Skript, ob alle Sensoren erreichbar sind und ob sie innerhalb eines einstellbaren Zeitraums (z. B. 3 Stunden) aktualisiert wurden. Fehlende oder veraltete Sensorwerte werden mit Warnmeldungen (grafisch hervorgehoben durch das Symbol️) im Debug-Log ausgegeben.
Zeitgesteuerte Ausführung:
Um eine effiziente Auswertung zu gewährleisten, wird das Skript je nach Tageszeit unterschiedlich oft ausgeführt:
Zwischen 06:00 und 22:59 läuft es alle 10 Minuten.
Zwischen 23:00 und 05:59 wird es alle 30 Minuten ausgeführt.Zusätzlich wird das Skript beim Start einmalig initial ausgeführt.
Debug-Log:
Mit aktiviertem Debug-Modus werden sämtliche Schritte und Berechnungen – inklusive der Sensorprüfungen und Altersermittlungen – detailliert im ioBroker-Log ausgegeben. Dies erleichtert die Fehlersuche und Anpassung an eure Umgebung.Das Skript ist so konzipiert, dass es flexibel anpassen lässt. Alle wichtigen Parameter (wie Schwellenwerte, Offsets, Sensor-Alter) sind zentral einstellbar, sodass ihr schnell und unkompliziert Änderungen vornehmen könnt.
/** * Skript: Lüftungsempfehlung basierend auf Raumklima * * Beschreibung: * Dieses Skript liest für mehrere Räume die aktuellen Sensorwerte für relative Luftfeuchte und Temperatur ein. * Es berechnet daraus zum einen: * - Eine Lüftungsempfehlung auf Basis eines festen Schwellenwertes (z. B. 57 % relative Luftfeuchte). * - Eine Empfehlung, ob eine Lüftung durch Austausch der Luft die absolute Luftfeuchte senken würde. * * Für die absolute Luftfeuchte wird folgende Formel verwendet: * AH = (6.112 * exp((17.67 * T)/(T + 243.5)) * RH * 2.1674) / (T + 273.15) * Für Außensensoren wird zusätzlich der Luftdruck als Korrekturfaktor einbezogen: * AH_out = AH * (P / 1013.25) * * Beide Berechnungen können über die Konfiguration (enableFixedCalculation und * enableAbsoluteHumidityCalculation) ein- bzw. ausgeschaltet werden. * * Für jeden Raum werden die ermittelten Empfehlungen (true/false) in die definierten Datenpunkte geschrieben. * * Zusätzlich: * - Es gibt einen Debug-Log, der detaillierte Informationen zu den Berechnungen ausgibt, * wenn debug auf true gesetzt ist. * - Optional wird geprüft, ob alle Sensoren erreichbar sind. Fehlende Sensoren werden im Debug-Log ausgegeben. * - Zusätzlich wird (optional) überprüft, ob Sensoren länger als ein einstellbarer Zeitraum (in Stunden) * keinen aktualisierten Wert gesendet haben. Diese Warnung wird grafisch (z. B. mit "⚠️") im Debug-Log angezeigt. * * Die Berechnungen werden je nach Tageszeit unterschiedlich oft ausgeführt: * - Zwischen 06:00 und 22:59 alle 10 Minuten * - Zwischen 23:00 und 05:59 alle 30 Minuten */ // ============================= // Konfiguration // ============================= var config = { // Berechnungen ein- bzw. ausschalten: enableFixedCalculation: true, // Aktiviert die Berechnung der Lüftungsempfehlung basierend auf einem festen RH-Schwellenwert. enableAbsoluteHumidityCalculation: true, // Aktiviert die Berechnung, ob Lüften (Austausch mit Außenluft) die absolute Luftfeuchte senkt. globalRHThreshold: 40, // Global einstellbarer Schwellenwert für relative Luftfeuchte in %. absoluteHumidityOffset: 0, // Optionaler Offset in g/m³, der beim Vergleich der absoluten Luftfeuchte berücksichtigt wird. debug: true, // Schaltet die ausführliche Debug-Ausgabe an (true) oder aus (false). // Optionale Prüfung der Sensorverfügbarkeit: enableSensorCheck: true, // Optionale Prüfung des Sensoralters: // Es wird geprüft, ob ein Sensor länger als maxAgeHours keinen neuen Wert gesendet hat. sensorAgeCheck: { enabled: true, maxAgeHours: 3 // Maximale erlaubte Zeitspanne (in Stunden) ohne Aktualisierung. }, // Definition der Räume: rooms: { "Wohnzimmer": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Wohnzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Wohnzimmer", threshold: null // Falls null, wird der globale Schwellenwert verwendet. }, "Arbeitszimmer": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Arbeitszimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Arbeitszimmer", threshold: null }, "Badezimmer": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Badezimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Badezimmer", threshold: null }, "Buegelzimmer": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Buegelzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Buegelzimmer", threshold: null }, "Essflur": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Essflur", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Essflur", threshold: null }, "Schlafzimmer": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Schlafzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Schlafzimmer", threshold: null }, "Waschkueche": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Waschkueche", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Waschkueche", threshold: null }, "GG-Kueche": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Kueche", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Kueche", threshold: null }, "GG-Schlafzimmer": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Schlafzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Schlafzimmer", threshold: null }, "GG-Wohnzimmer": { enabled: true, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Wohnzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Wohnzimmer", threshold: null }, "GG-Bad": { enabled: false, indoorRH: "Euer.Datenpunkt.des.Objekts.Feuchtigkeit", indoorTemp: "Euer.Datenpunkt.des.Objekts.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Bad", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Bad", threshold: null } }, // Definition der Außensensoren: outside: { rh: "Euer.Datenpunkt.des.Objekts.Luftfeuchte", temp: "Euer.Datenpunkt.des.Objekts.Temperatur", pressure: "Euer.Datenpunkt.des.Objekts.Luftdruck" } }; // ============================= // Funktion: calcAbsoluteHumidity // ============================= /** * Berechnet die absolute Luftfeuchte (g/m³) basierend auf Temperatur, relativer Luftfeuchte * und optional dem Luftdruck. * * Formel: * AH = (6.112 * exp((17.67 * T) / (T + 243.5)) * RH * 2.1674) / (T + 273.15) * * Falls der optionale Parameter "pressure" (Luftdruck in hPa) übergeben wird, wird der Wert * als Korrekturfaktor (bezogen auf 1013.25 hPa) einbezogen: * AH = AH * (pressure / 1013.25) * * @param {number} T - Temperatur in °C. * @param {number} RH - Relative Luftfeuchte in %. * @param {number} [pressure] - (Optional) Luftdruck in hPa. * @returns {number} Berechnete absolute Luftfeuchte in g/m³. */ function calcAbsoluteHumidity(T, RH, pressure) { var AH = (6.112 * Math.exp((17.67 * T) / (T + 243.5)) * RH * 2.1674) / (T + 273.15); if (pressure !== undefined && pressure > 0) { AH = AH * (pressure / 1013.25); } return AH; } // ============================= // Funktion: checkSensorAge // ============================= /** * Prüft, ob ein Sensor seit mehr als der konfigurierten Zeit (in Stunden) keinen neuen Wert gesendet hat. * * Falls der Sensor zu alt ist, wird eine Warnung mit grafischer Kennzeichnung (⚠️) im Debug-Log ausgegeben. * * @param {object} sensorState - Der Zustand des Sensors, der u.a. den Zeitstempel (ts) enthalten sollte. * @param {string} sensorName - Bezeichner des Sensors (z. B. Datenpunktname), der in der Warnung angezeigt wird. */ function checkSensorAge(sensorState, sensorName) { if (config.sensorAgeCheck && config.sensorAgeCheck.enabled && sensorState && sensorState.ts) { var now = Date.now(); var age = (now - sensorState.ts) / 3600000; // Zeitdifferenz in Stunden if (age > config.sensorAgeCheck.maxAgeHours) { // Grafische Kennzeichnung mit dem Warnsymbol ⚠️ if (config.debug) { log("⚠️⚠️⚠️ Sensor " + sensorName + " hat seit " + age.toFixed(2) + " Stunden keinen neuen Wert gesendet!", "warn"); } } } } // ============================= // Funktion: runCalculation // ============================= /** * Führt die vollständige Berechnung der Lüftungsempfehlungen durch. * * Ablauf: * 1. Außensensoren auslesen und (optional) auf Verfügbarkeit sowie das Sensoralter prüfen. * 2. Berechne die absolute Luftfeuchte für die Außenluft. * 3. Für jeden aktivierten Raum: * a. Raum-Sensoren (Temperatur und RH) auslesen, optional deren Verfügbarkeit und Alter prüfen. * b. Berechne die absolute Luftfeuchte im Raum. * c. Falls aktiviert: Vergleiche den gemessenen RH-Wert mit dem Schwellenwert und speichere das Ergebnis in fixedDP. * d. Falls aktiviert: Vergleiche die absolute Luftfeuchte im Raum (AH_in) mit der Außenluft (AH_out) (plus Offset) * und speichere das Ergebnis in reduceDP. * * Bei fehlenden kritischen Sensoren (sowohl außen als auch pro Raum) wird eine Warnung im Debug-Log ausgegeben * und die Berechnung (bzw. für den Raum) abgebrochen. */ function runCalculation() { // Außensensoren auslesen var outsideTempState = getState(config.outside.temp); var outsideRHState = getState(config.outside.rh); var outsidePressureState = getState(config.outside.pressure); // Optionale Prüfung der Außensensoren (Verfügbarkeit) if (config.enableSensorCheck) { var missingOutsideSensors = []; if (!outsideTempState) missingOutsideSensors.push("Außentemperatur (" + config.outside.temp + ")"); if (!outsideRHState) missingOutsideSensors.push("Außen RH (" + config.outside.rh + ")"); if (!outsidePressureState) missingOutsideSensors.push("Außen Druck (" + config.outside.pressure + ")"); if (missingOutsideSensors.length > 0) { log("Fehlende Außensensoren: " + missingOutsideSensors.join(", "), "warn"); } } // Sensor-Alter-Prüfung für Außensensoren if (config.sensorAgeCheck && config.sensorAgeCheck.enabled) { checkSensorAge(outsideTempState, config.outside.temp); checkSensorAge(outsideRHState, config.outside.rh); checkSensorAge(outsidePressureState, config.outside.pressure); } // Kritische Außensensoren müssen vorhanden sein; andernfalls wird die Berechnung abgebrochen. if (!outsideTempState || !outsideRHState || !outsidePressureState) { log("Kritische Außensensoren nicht vollständig verfügbar! Berechnung wird abgebrochen.", "error"); return; } // Umwandlung der Außensensorwerte in Zahlen var outsideTemp = parseFloat(outsideTempState.val); var outsideRH = parseFloat(outsideRHState.val); var outsidePressure = parseFloat(outsidePressureState.val); var AH_out = calcAbsoluteHumidity(outsideTemp, outsideRH, outsidePressure); // Debug-Ausgabe der Außensensorwerte und der berechneten absoluten Luftfeuchte if (config.debug) { log("Außenwerte: Temp=" + outsideTemp + "°C, RH=" + outsideRH + "%, Druck=" + outsidePressure + " hPa, AH_out=" + AH_out.toFixed(2) + " g/m³"); } // Iteration über alle Räume for (var roomName in config.rooms) { var room = config.rooms[roomName]; if (!room.enabled) { if (config.debug) log("Raum " + roomName + " ist deaktiviert."); continue; } // Raum-Sensoren auslesen var indoorTempState = getState(room.indoorTemp); var indoorRHState = getState(room.indoorRH); // Optionale Prüfung der Raum-Sensoren (Verfügbarkeit) if (config.enableSensorCheck) { var missingRoomSensors = []; if (!indoorTempState) missingRoomSensors.push("Temperatur (" + room.indoorTemp + ")"); if (!indoorRHState) missingRoomSensors.push("Luftfeuchte (" + room.indoorRH + ")"); if (missingRoomSensors.length > 0) { log("Fehlende Sensoren für " + roomName + ": " + missingRoomSensors.join(", "), "warn"); } } // Sensor-Alter-Prüfung für Raum-Sensoren if (config.sensorAgeCheck && config.sensorAgeCheck.enabled) { checkSensorAge(indoorTempState, room.indoorTemp + " in " + roomName); checkSensorAge(indoorRHState, room.indoorRH + " in " + roomName); } // Falls kritische Sensoren im Raum fehlen, wird dieser übersprungen. if (!indoorTempState || !indoorRHState) { if (config.debug) log("Berechnung für " + roomName + " wird übersprungen, da Sensoren fehlen.", "warn"); continue; } // Umwandlung der Raumwerte in Zahlen und Berechnung der absoluten Luftfeuchte im Raum var indoorTemp = parseFloat(indoorTempState.val); var indoorRH = parseFloat(indoorRHState.val); var AH_in = calcAbsoluteHumidity(indoorTemp, indoorRH); // ----------------------------- // Feste Schwellenwert-Berechnung (Fixed Calculation) // ----------------------------- if (config.enableFixedCalculation) { var threshold = (room.threshold !== null) ? room.threshold : config.globalRHThreshold; var fixedRecommendation = (indoorRH >= threshold); setState(room.fixedDP, fixedRecommendation, true); if (config.debug) { log("[" + roomName + "] Fixed Calculation: RH_in=" + indoorRH + "%, Threshold=" + threshold + " -> Empfehlung Lüften: " + fixedRecommendation); } } // ----------------------------- // Berechnung, ob Lüften die absolute Luftfeuchte senken kann (Absolute Humidity Calculation) // ----------------------------- if (config.enableAbsoluteHumidityCalculation) { var reduceRec = (AH_in > (AH_out + config.absoluteHumidityOffset)); setState(room.reduceDP, reduceRec, true); if (config.debug) { log("[" + roomName + "] Absolute Humidity Calculation: AH_in=" + AH_in.toFixed(2) + " g/m³, AH_out=" + AH_out.toFixed(2) + " g/m³, Offset=" + config.absoluteHumidityOffset + " -> Lüften senkt Luftfeuchte: " + reduceRec); } } } } // ============================= // Zeitgesteuerte Ausführung (Scheduling) // ============================= // // Der erste Cron-Job führt runCalculation() alle 10 Minuten im Zeitfenster von 06:00 bis 22:59 aus. // Der zweite Cron-Job führt runCalculation() alle 30 Minuten im Zeitfenster von 23:00 bis 05:59 aus. // Beim Laden des Skripts wird runCalculation() einmalig initial aufgerufen. // Von 06:00 bis 22:59 alle 10 Minuten: schedule("*/10 6-22 * * *", function() { runCalculation(); if (config.debug) log("runCalculation ausgelöst (10-Minuten-Intervall)", "info"); }); // Von 23:00 bis 05:59 alle 30 Minuten: schedule("*/30 23,0-5 * * *", function() { runCalculation(); if (config.debug) log("runCalculation ausgelöst (30-Minuten-Intervall)", "info"); }); // Initialer Aufruf beim Laden des Skripts: runCalculation();
Um noch die Datenpunkte "onthefly" erstellen zu können, dieses Anpassbare Script:
/** * Skript: Ziel-Datenpunkte erstellen * * Beschreibung: * Dieses Skript erstellt alle in der Konfiguration definierten Ziel-Datenpunkte, * in denen später die Lüftungsempfehlungen abgelegt werden. * Für jeden Raum werden zwei Datenpunkte erzeugt: * - fixedDP: Für die Lüftungsempfehlung basierend auf einem festen Schwellenwert der relativen Luftfeuchte. * - reduceDP: Für die Empfehlung, ob Lüften die absolute Luftfeuchte senken kann. * * Die Datenpunkte werden als Boolean (true/false) erstellt. * * Hinweis: Falls ein Datenpunkt bereits existiert, wird er nicht erneut erstellt. * * Konfiguration: * Die Räume und die zugehörigen Datenpunkt-Namen werden in der Variable "config" definiert. */ // ============================= // Konfiguration // ============================= var config = { rooms: { "Wohnzimmer": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Wohnzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Wohnzimmer" }, "Arbeitszimmer": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Arbeitszimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Arbeitszimmer" }, "Badezimmer": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Badezimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Badezimmer" }, "Buegelzimmer": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Buegelzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Buegelzimmer" }, "Essflur": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Essflur", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Essflur" }, "Schlafzimmer": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Schlafzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Schlafzimmer" }, "Waschkueche": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Waschkueche", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Waschkueche" }, "GG-Kueche": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Kueche", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Kueche" }, "GG-Schlafzimmer": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Schlafzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Schlafzimmer" }, "GG-Wohnzimmer": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Wohnzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Wohnzimmer" }, "GG-Bad": { fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Bad", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Bad" } } }; // Optionaler Debug-Modus (true = ausführliche Logausgaben) var debug = true; /** * Erstellt einen Datenpunkt, falls dieser noch nicht existiert. * * @param {string} dp - Der vollständige Name des Datenpunkts. * @param {object} options - Optionen für den Datenpunkt (z. B. Typ, Rolle, Beschreibung). */ function createTargetState(dp, options) { if (!existsState(dp)) { // Erstelle den Datenpunkt mit initialem Wert false. createState(dp, false, options); if (debug) log("Datenpunkt erstellt: " + dp, "info"); } else { if (debug) log("Datenpunkt existiert bereits: " + dp, "info"); } } // ============================= // Hauptteil: Ziel-Datenpunkte erstellen // ============================= for (var roomName in config.rooms) { var room = config.rooms[roomName]; // Erstelle den Datenpunkt für die feste Schwellenwert-Berechnung (fixedDP) createTargetState(room.fixedDP, { name: "Lüftungsempfehlung (Fester Wert) für " + roomName, type: "boolean", role: "indicator" }); // Erstelle den Datenpunkt für die absolute Luftfeuchte-Berechnung (reduceDP) createTargetState(room.reduceDP, { name: "Lüftungsempfehlung (LF senken möglich) für " + roomName, type: "boolean", role: "indicator" }); } log("Alle Ziel-Datenpunkte wurden überprüft und ggf. erstellt.", "info");
Ihr müsst natürlich die Sensoren durch eure ersetzen und ggf. andere gewünschte Einstellungen anpassen.
Bei Fragen oder Optimierungsideen, gerne her damit
-
wo ist der Unterschied zum "alten" Skript https://forum.iobroker.net/post/890189 ?
-
@homoran
Gute Frage, das kenne ich nicht.
Müsstest du vergleichen. -
Da ich denke, das ich mir noch die absolute Luftfeuchtigkeit mit anzeigen möchte,
werden hiermit entsprechenden Datenpunkte erzeugt:/** * Skript: Anlage von Zieldatenpunkten für Absolute Luftfeuchtigkeit * * Beschreibung: * Dieses Skript erstellt automatisch die Zieldatenpunkte für die absolute Luftfeuchtigkeit * in den einzelnen Räumen. Der Datenpunktpfad entspricht dem Muster: * * 0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.<Raumname> * * Dabei werden die folgenden Optionen vergeben: * - name: "<Raumname>-Absolute-LF" * - type: "number" * - read: true * - write: true * - def: 0 * * Falls ein Datenpunkt bereits existiert, wird er nicht erneut erstellt. */ // Liste der Raumbezeichnungen, für die der Datenpunkt angelegt werden soll var rooms = [ "Wohnzimmer", "Arbeitszimmer", "Badezimmer", "Buegelzimmer", "Essflur", "Schlafzimmer", "Waschkueche", "GG-Kueche", "GG-Schlafzimmer", "GG-Wohnzimmer", "GG-Bad", "Aussen-Nord" ]; // Optionale Debug-Ausgabe (true = ausführliche Logmeldungen) var debug = true; /** * Erstellt einen Datenpunkt, falls dieser noch nicht existiert. * * @param {string} dp - Der vollständige Datenpunktpfad. * @param {object} options - Optionen für den Datenpunkt, z. B.: * { name: "<Raumname>-Absolute-LF", type: "number", read: true, write: true, def: 0 } */ function createTargetState(dp, options) { if (!existsState(dp)) { // Erstelle den Datenpunkt mit dem Default-Wert und den übergebenen Optionen. createState(dp, options.def, { name: options.name, type: options.type, read: options.read, write: options.write }); if (debug) log("Datenpunkt erstellt: " + dp, "info"); } else { if (debug) log("Datenpunkt existiert bereits: " + dp, "info"); } } // Iteriere über alle Räume und erstelle die entsprechenden Zieldatenpunkte rooms.forEach(function(roomName) { // Erstelle den Datenpunktpfad var dpPath = "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit." + roomName; // Definiere die Optionen für diesen Datenpunkt var options = { name: roomName + "-Absolute-LF", type: "number", read: true, write: true, def: 0 }; // Erstelle den Datenpunkt, falls er noch nicht existiert createTargetState(dpPath, options); }); log("Anlage der Zieldatenpunkte für Absolute Luftfeuchtigkeit abgeschlossen.", "info");
-
@gismoh sagte in JS für Überwachung der Luftfeuchtigkeit in Räumen:
das kenne ich nicht.
ist wahrscheinlich zu alt
stammt aus den ersten ioBroker Tagen.läuft aber mit allem pipapo absolut stabil
-
@homoran
"Meins" muss noch getestet werden, bin dort auch noch nicht fertig.
Momentan füge ich noch den Taupunkt hinzu, um zu warnen, wann es zu Schimmel kommen kann - so zumindest die Idee.
Edit:
Somit kann man z.B. den Heizkörper automatisch aufdrehen lassen (oder nur warnen) wenn es kritisch wird.
Möchte so Energie sparen, aber relativ "Save". -
@gismoh sagte in JS für Überwachung der Luftfeuchtigkeit in Räumen:
Momentan füge ich noch den Taupunkt hinzu, um zu warnen, wann es zu Schimmel kommen kann - so zumindest die Idee.
hast du den nicht schon in den Rechnungen drin?
für die Werkstatt hab ich noch zusätzlich den "Taupunkt" für 80% und 70% Feuchte, da dies eigentlich schon die Grenzwerte für Schimmelbildung sind
-
@homoran
Den möchte ich genauer Berechnen und ausgeben lassen.
Der ist nicht nur von der Temeratur oder der Luftfeuchtig abhängig, sondern koaliert mit beiden Werten
(so zumindest mein Gedanke).Edit: Ja, sehe ich gerade, hast du ja bereits drin
-
@gismoh sagte in JS für Überwachung der Luftfeuchtigkeit in Räumen:
@homoran
Den möchte ich genauer Berechnen und ausgeben lassen.
Der ist nicht nur von der Temeratur oder der Luftfeuchtig abhängig, sondern koaliert mit beiden Werten
(so zumindest mein Gedanke).Edit: Ja, sehe ich gerade, hast du ja bereits drin
Nur der View ist von mir, die Daten kommen alle aus dem verlinkten Script.
Da gibt es dann auch noch differenzierte Lüftungsempfehlung mit Auskühlschutz usw. -
@homoran
Die Lüftungsempfehlungen kommen auch noch, dies ist dafür die "Vorarbeit".
Wenn ich nun mal dransitze, ziehe ich es nun auch durchHatte ein altes Script in Benutzung, mit welchem ich nicht mehr zufrieden war, deswegen wollte ich einfach mal schauen, was nun dabei rauskommt
-
@gismoh
Habe nun noch eine Berechnung hinzugefügt, in welcher der Taupunkt der einzelnen Räume berechnet wird.
Dieser ist wichtig um Schimmel zu vermeiden, vor allem, für wenig genutzte Räume, welche auch nicht oder wenig beheizt werden.Da das Raumthermostat meist nicht an der kältesten Stelle des Raums misst wurde eine Temperaturkorrektur für die Taupunktberechnung hinzugefügt.
Wenn also in einem Raum das hier eingebundene Thermometer z.B. 19 Grad ermittelt und die kälteste Stelle im Raum mit 16 Grad gemessen wird, wird die Korrektur für diesen Raum mit "+3" eingegeben.
/** * Skript: Lüftungsempfehlung basierend auf Raumklima inkl. Speicherung von absoluter Luftfeuchte und Taupunkt (nur für Räume) * * Beschreibung: * Dieses Skript liest für mehrere Räume die aktuellen Sensorwerte (relative Luftfeuchte und Temperatur) * ein und berechnet daraus: * - Eine Lüftungsempfehlung basierend auf einem festen Schwellenwert (z. B. 57 % RH). * - Ob Lüften (Austausch mit Außenluft) die absolute Luftfeuchte senken kann. * - Die absolute Luftfeuchte (AH) mit der Formel: * AH = (6.112 * exp((17.67 * T)/(T + 243.5)) * RH * 2.1674) / (T + 273.15) * Für Außensensoren wird zusätzlich der Luftdruck als Korrekturfaktor einbezogen: * AH_out = AH * (P / 1013.25) * - Den Taupunkt (in °C) mittels: * γ = ln(RH/100) + (17.67 * (T_corr))/(243.5 + T_corr) * Taupunkt = (243.5 * γ)/(17.67 - γ) * Dabei wird T_corr = T + taupunktTempCorrection berechnet. * Die temperaturbezogene Korrektur (taupunktTempCorrection) kann für jeden Raum separat eingestellt werden. * Die Berechnung des Taupunkts erfolgt **nur für Räume**. * * Die Ergebnisse werden in definierte Datenpunkte geschrieben: * - fixedDP: Empfehlung basierend auf festem Schwellenwert (true/false). * - reduceDP: Empfehlung, ob Lüften die Luftfeuchte senken kann (true/false). * - absDP: Absolute Luftfeuchte (in g/m³). * - taupunktDP: Taupunkt (in °C) – **nur für Räume**. * * Zusätzlich: * - Optional wird geprüft, ob Sensoren erreichbar sind und ob sie zu alt sind. * - Die Speicherung der berechneten absoluten Luftfeuchte und des Taupunkts ist optional (saveAbsoluteHumidity, saveDewPoint). * * Zeitgesteuerte Ausführung: * - Zwischen 06:00 und 22:59 wird das Skript alle 10 Minuten ausgeführt. * - Zwischen 23:00 und 05:59 alle 30 Minuten. */ // ============================= // Konfiguration // ============================= var config = { // Berechnungen ein- bzw. ausschalten: enableFixedCalculation: true, // Empfehlung basierend auf festem RH-Schwellenwert. enableAbsoluteHumidityCalculation: true, // Empfehlung, ob Lüften die absolute Luftfeuchte senkt. globalRHThreshold: 40, // Globaler RH-Schwellenwert in %. absoluteHumidityOffset: 0, // Offset in g/m³ für den Vergleich der absoluten Luftfeuchte. debug: true, // Aktiviert ausführliche Debug-Ausgabe. // Optionale Speicherung der berechneten absoluten Luftfeuchte und des Taupunkts. saveAbsoluteHumidity: true, // Speicherung der absoluten Luftfeuchte. saveDewPoint: true, // Speicherung des Taupunkts (nur für Räume). // Global einstellbare Temperaturkorrektur für die Taupunktberechnung (wird verwendet, falls kein Raumwert definiert ist) taupunktTempCorrection: +3, // Optionale Prüfung der Sensorverfügbarkeit: enableSensorCheck: true, // Optionale Prüfung des Sensoralters: sensorAgeCheck: { enabled: true, maxAgeHours: 3 // Maximale Zeitspanne (in Stunden) ohne Aktualisierung. }, // Definition der Räume: // Für jeden Raum können zusätzlich eigene Einstellungen für die Temperaturkorrektur (taupunktTempCorrection) definiert werden. // Die "taupunktTempCorrection" ist der Wert, um wieviel Grad wärmer die Temperatur am Erfassungsgerät ist, im Verhältnis zur Kühlsten Stelle im Raum (minimierung des Schimmelrisiko) rooms: { "Wohnzimmer": { enabled: true, indoorRH: "alias.0.Wohnzimmer.Raumklima.Sonoff.Luftfeuchte", indoorTemp: "alias.0.Wohnzimmer.Raumklima.Sonoff.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Wohnzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Wohnzimmer", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Wohnzimmer", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.Wohnzimmer", threshold: null, taupunktTempCorrection: +3 // Korrektur für dieses Zimmer (kann individuell angepasst werden) }, "Arbeitszimmer": { enabled: true, indoorRH: "alias.0.Arbeitszimmer.Raumklima.Sonoff.Luftfeuchte", indoorTemp: "alias.0.Arbeitszimmer.Raumklima.Sonoff.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Arbeitszimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Arbeitszimmer", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Arbeitszimmer", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.Arbeitszimmer", threshold: null, taupunktTempCorrection: +3 }, "Badezimmer": { enabled: true, indoorRH: "alias.0.Badezimmer.Luftfeuchte", indoorTemp: "alias.0.Badezimmer.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Badezimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Badezimmer", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Badezimmer", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.Badezimmer", threshold: null, taupunktTempCorrection: +3 }, "Buegelzimmer": { enabled: true, indoorRH: "alias.0.Bügelzimmer.Luftfeuchte.Sonoff", indoorTemp: "alias.0.Bügelzimmer.Temperatur.Sonoff", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Buegelzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Buegelzimmer", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Buegelzimmer", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.Buegelzimmer", threshold: null, taupunktTempCorrection: +3 }, "Essflur": { enabled: true, indoorRH: "alias.0.Essflur.Raumklima.Aqara-BLE.Luftfeuchte", indoorTemp: "alias.0.Essflur.Raumklima.Aqara-BLE.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Essflur", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Essflur", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Essflur", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.Essflur", threshold: null, taupunktTempCorrection: +3 }, "Schlafzimmer": { enabled: true, indoorRH: "alias.0.Schlafzimmer.Raumklima.Sonoff.Luftfeuchte", indoorTemp: "alias.0.Schlafzimmer.Raumklima.Sonoff.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Schlafzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Schlafzimmer", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Schlafzimmer", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.Schlafzimmer", threshold: null, taupunktTempCorrection: +3 }, "Waschkueche": { enabled: true, indoorRH: "alias.0.Waschküche.Raumklima.Aqara.Luftfeuchte", indoorTemp: "alias.0.Waschküche.Raumklima.Aqara.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.Waschkueche", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.Waschkueche", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Waschkueche", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.Waschkueche", threshold: null, taupunktTempCorrection: +3 }, "GG-Kueche": { enabled: true, indoorRH: "alias.0.ELW-West.Küche.Raumklima.Aqara.Luftfeuchte", indoorTemp: "alias.0.ELW-West.Küche.Raumklima.Aqara.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Kueche", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Kueche", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.GG-Kueche", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.GG-Kueche", threshold: null, taupunktTempCorrection: +3 }, "GG-Schlafzimmer": { enabled: true, indoorRH: "alias.0.ELW-West.Schlafzimmer.Raumklima.Aqara.Luftfeuchte", indoorTemp: "alias.0.ELW-West.Schlafzimmer.Raumklima.Aqara.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Schlafzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Schlafzimmer", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.GG-Schlafzimmer", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.GG-Schlafzimmer", threshold: null, taupunktTempCorrection: +3 }, "GG-Wohnzimmer": { enabled: true, indoorRH: "alias.0.ELW-West.Wohnzimmer.Raumklima.Sonoff.Luftfeuchte", indoorTemp: "alias.0.ELW-West.Wohnzimmer.Raumklima.Sonoff.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Wohnzimmer", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Wohnzimmer", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.GG-Wohnzimmer", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.GG-Wohnzimmer", threshold: null, taupunktTempCorrection: +3 }, "GG-Bad": { enabled: false, indoorRH: "alias.0.GG-Bad.Raumklima.Sonoff.Luftfeuchte", indoorTemp: "alias.0.GG-Bad.Raumklima.Sonoff.Temperatur", fixedDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.FesterWert-ueberschritten.GG-Bad", reduceDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.LF-Senken-moeglich.GG-Bad", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.GG-Bad", taupunktDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.GG-Bad", threshold: null, taupunktTempCorrection: +3 } }, // Definition der Außensensoren: outside: { rh: "alias.0.Außen-Nord.Raumklima.Aqara.Luftfeuchte", temp: "alias.0.Außen-Nord.Raumklima.Aqara.Temperatur", pressure: "alias.0.Außen-Nord.Raumklima.Aqara.Luftdruck", absDP: "0_userdata.0.EigeneDatenpunkte.Raumklima.Luefungsempfehlung.AbsoluteLuftfeuchtigkeit.Aussen-Nord" // Taupunkt für außen wird bewusst nicht berechnet. } }; // ============================= // Funktion: calcAbsoluteHumidity // ============================= /** * Berechnet die absolute Luftfeuchte (g/m³) basierend auf Temperatur, relativer Luftfeuchte * und optional dem Luftdruck. * * Formel: * AH = (6.112 * exp((17.67 * T) / (T + 243.5)) * RH * 2.1674) / (T + 273.15) * * Falls der optionale Parameter "pressure" (Luftdruck in hPa) übergeben wird, wird der Wert * als Korrekturfaktor (bezogen auf 1013.25 hPa) einbezogen: * AH = AH * (pressure / 1013.25) * * @param {number} T - Temperatur in °C. * @param {number} RH - Relative Luftfeuchte in %. * @param {number} [pressure] - (Optional) Luftdruck in hPa. * @returns {number} Berechnete absolute Luftfeuchte in g/m³. */ function calcAbsoluteHumidity(T, RH, pressure) { var AH = (6.112 * Math.exp((17.67 * T) / (T + 243.5)) * RH * 2.1674) / (T + 273.15); if (pressure !== undefined && pressure > 0) { AH = AH * (pressure / 1013.25); } return AH; } // ============================= // Funktion: calcDewPoint // ============================= /** * Berechnet den Taupunkt (in °C) basierend auf Temperatur (T) und relativer Luftfeuchte (RH). * * Für die Berechnung des Taupunkts wird eine temperaturbezogene Korrektur angewendet, * die für jeden Raum separat eingestellt werden kann. Falls für einen Raum keine Korrektur * definiert ist, wird der globale Standardwert (config.taupunktTempCorrection) verwendet. * * Formel: * T_corr = T + taupunktTempCorrection * γ = ln(RH/100) + (17.67 * T_corr) / (243.5 + T_corr) * Taupunkt = (243.5 * γ) / (17.67 - γ) * * @param {number} T - Gemessene Temperatur in °C. * @param {number} RH - Relative Luftfeuchte in %. * @param {number} tauCorrection - Temperaturkorrektur (in °C) für die Taupunktberechnung. * @returns {number} Berechneter Taupunkt in °C. */ function calcDewPoint(T, RH, tauCorrection) { var T_corr = T + tauCorrection; var gamma = Math.log(RH / 100) + (17.67 * T_corr) / (243.5 + T_corr); var dewPoint = (243.5 * gamma) / (17.67 - gamma); return dewPoint; } // ============================= // Funktion: checkSensorAge // ============================= /** * Prüft, ob ein Sensor seit mehr als der konfigurierten Zeit (in Stunden) keinen neuen Wert gesendet hat. * * Falls der Sensor zu alt ist, wird eine Warnung mit grafischer Kennzeichnung (⚠️) im Debug-Log ausgegeben. * * @param {object} sensorState - Der Zustand des Sensors, der u.a. den Zeitstempel (ts) enthalten sollte. * @param {string} sensorName - Bezeichner des Sensors, der in der Warnung angezeigt wird. */ function checkSensorAge(sensorState, sensorName) { if (config.sensorAgeCheck && config.sensorAgeCheck.enabled && sensorState && sensorState.ts) { var now = Date.now(); var age = (now - sensorState.ts) / 3600000; // Zeitdifferenz in Stunden if (age > config.sensorAgeCheck.maxAgeHours) { if (config.debug) { log("⚠️⚠️⚠️ Sensor " + sensorName + " hat seit " + age.toFixed(2) + " Stunden keinen neuen Wert gesendet!", "warn"); } } } } // ============================= // Funktion: runCalculation // ============================= /** * Führt die vollständige Berechnung der Lüftungsempfehlungen durch. * * Ablauf: * 1. Außensensoren auslesen, optional auf Verfügbarkeit und Sensoralter prüfen. * 2. Berechne die absolute Luftfeuchte für die Außenluft (AH_out). * Optional: Speichere AH_out in den entsprechenden Zieldatenpunkten. * 3. Für jeden aktivierten Raum: * a. Raum-Sensoren (Temperatur und RH) auslesen, optional auf Verfügbarkeit und Sensoralter prüfen. * b. Berechne die absolute Luftfeuchte im Raum (AH_in) und den Taupunkt (Raum). * Dabei wird für die Taupunktberechnung die temperaturbezogene Korrektur (raumbezogen oder global) angewendet. * c. Optional: Speichere AH_in und den Taupunkt in den entsprechenden Zieldatenpunkten. * d. Falls aktiviert: Vergleiche den gemessenen RH-Wert mit dem Schwellenwert und speichere das Ergebnis in fixedDP. * e. Falls aktiviert: Vergleiche AH_in mit AH_out (plus Offset) und speichere das Ergebnis in reduceDP. * * Bei fehlenden kritischen Sensoren wird die Berechnung (bzw. für den Raum) abgebrochen. */ function runCalculation() { // Außensensoren auslesen var outsideTempState = getState(config.outside.temp); var outsideRHState = getState(config.outside.rh); var outsidePressureState = getState(config.outside.pressure); // Optionale Prüfung der Außensensoren (Verfügbarkeit) if (config.enableSensorCheck) { var missingOutsideSensors = []; if (!outsideTempState) missingOutsideSensors.push("Außentemperatur (" + config.outside.temp + ")"); if (!outsideRHState) missingOutsideSensors.push("Außen RH (" + config.outside.rh + ")"); if (!outsidePressureState) missingOutsideSensors.push("Außen Druck (" + config.outside.pressure + ")"); if (missingOutsideSensors.length > 0) { log("Fehlende Außensensoren: " + missingOutsideSensors.join(", "), "warn"); } } // Sensor-Alter-Prüfung für Außensensoren if (config.sensorAgeCheck && config.sensorAgeCheck.enabled) { checkSensorAge(outsideTempState, config.outside.temp); checkSensorAge(outsideRHState, config.outside.rh); checkSensorAge(outsidePressureState, config.outside.pressure); } // Falls kritische Außensensoren fehlen, wird die Berechnung abgebrochen. if (!outsideTempState || !outsideRHState || !outsidePressureState) { log("Kritische Außensensoren nicht vollständig verfügbar! Berechnung wird abgebrochen.", "error"); return; } // Umwandlung der Außensensorwerte in Zahlen und Berechnung von AH_out var outsideTemp = parseFloat(outsideTempState.val); var outsideRH = parseFloat(outsideRHState.val); var outsidePressure = parseFloat(outsidePressureState.val); var AH_out = calcAbsoluteHumidity(outsideTemp, outsideRH, outsidePressure); // Debug-Ausgabe für Außensensoren if (config.debug) { log("Außenwerte: Temp=" + outsideTemp + "°C, RH=" + outsideRH + "%, Druck=" + outsidePressure + " hPa, AH_out=" + AH_out.toFixed(2) + " g/m³", "info"); } // Optionale Speicherung der absoluten Luftfeuchte für außen (Aussen-Nord) if (config.saveAbsoluteHumidity) { setState(config.outside.absDP, AH_out, true); if (config.debug) log("Absolute Luftfeuchte außen gespeichert in " + config.outside.absDP + ": " + AH_out.toFixed(2) + " g/m³", "info"); } // Hinweis: Der Taupunkt für außen wird bewusst nicht berechnet. // Iteration über alle Räume for (var roomName in config.rooms) { var room = config.rooms[roomName]; if (!room.enabled) { if (config.debug) log("Raum " + roomName + " ist deaktiviert.", "info"); continue; } // Raum-Sensoren auslesen var indoorTempState = getState(room.indoorTemp); var indoorRHState = getState(room.indoorRH); // Optionale Prüfung der Raum-Sensoren (Verfügbarkeit) if (config.enableSensorCheck) { var missingRoomSensors = []; if (!indoorTempState) missingRoomSensors.push("Temperatur (" + room.indoorTemp + ")"); if (!indoorRHState) missingRoomSensors.push("Luftfeuchte (" + room.indoorRH + ")"); if (missingRoomSensors.length > 0) { log("Fehlende Sensoren für " + roomName + ": " + missingRoomSensors.join(", "), "warn"); } } // Sensor-Alter-Prüfung für Raum-Sensoren if (config.sensorAgeCheck && config.sensorAgeCheck.enabled) { checkSensorAge(indoorTempState, room.indoorTemp + " in " + roomName); checkSensorAge(indoorRHState, room.indoorRH + " in " + roomName); } // Falls kritische Raum-Sensoren fehlen, wird der Raum übersprungen. if (!indoorTempState || !indoorRHState) { if (config.debug) log("Berechnung für " + roomName + " wird übersprungen, da Sensoren fehlen.", "warn"); continue; } // Umwandlung der Raumwerte in Zahlen und Berechnung von AH_in var indoorTemp = parseFloat(indoorTempState.val); var indoorRH = parseFloat(indoorRHState.val); var AH_in = calcAbsoluteHumidity(indoorTemp, indoorRH); // Optionale Speicherung der absoluten Luftfeuchte im Raum if (config.saveAbsoluteHumidity && room.absDP) { setState(room.absDP, AH_in, true); if (config.debug) log("Absolute Luftfeuchte in " + roomName + " gespeichert in " + room.absDP + ": " + AH_in.toFixed(2) + " g/m³", "info"); } // Berechnung des Taupunkts im Raum: // Für die Berechnung wird die temperaturbezogene Korrektur verwendet, die für den Raum separat definiert sein kann. var tauCorrection = (typeof room.taupunktTempCorrection !== 'undefined') ? room.taupunktTempCorrection : config.taupunktTempCorrection; var taupunkt = calcDewPoint(indoorTemp, indoorRH, tauCorrection); if (config.saveDewPoint && room.taupunktDP) { setState(room.taupunktDP, taupunkt, true); if (config.debug) log("Taupunkt in " + roomName + " gespeichert in " + room.taupunktDP + ": " + taupunkt.toFixed(2) + " °C", "info"); } // ----------------------------- // Feste Schwellenwert-Berechnung (Fixed Calculation) // ----------------------------- if (config.enableFixedCalculation) { var threshold = (room.threshold !== null) ? room.threshold : config.globalRHThreshold; var fixedRecommendation = (indoorRH >= threshold); setState(room.fixedDP, fixedRecommendation, true); if (config.debug) { log("[" + roomName + "] Fixed Calculation: RH_in=" + indoorRH + "%, Threshold=" + threshold + " -> Empfehlung Lüften: " + fixedRecommendation, "info"); } } // ----------------------------- // Berechnung, ob Lüften die absolute Luftfeuchte senken kann (Absolute Humidity Calculation) // ----------------------------- if (config.enableAbsoluteHumidityCalculation) { var reduceRec = (AH_in > (AH_out + config.absoluteHumidityOffset)); setState(room.reduceDP, reduceRec, true); if (config.debug) { log("[" + roomName + "] Absolute Humidity Calculation: AH_in=" + AH_in.toFixed(2) + " g/m³, AH_out=" + AH_out.toFixed(2) + " g/m³, Offset=" + config.absoluteHumidityOffset + " -> Lüften senkt Luftfeuchte: " + reduceRec, "info"); } } } } // ============================= // Zeitgesteuerte Ausführung (Scheduling) // ============================= // // Ausführung im Tag-/Nachtmodus: // - Zwischen 06:00 und 22:59 alle 10 Minuten // - Zwischen 23:00 und 05:59 alle 30 Minuten // Beim Laden des Skripts wird runCalculation() initial aufgerufen. // Von 06:00 bis 22:59 alle 10 Minuten: schedule("*/10 6-22 * * *", function() { runCalculation(); if (config.debug) log("runCalculation ausgelöst (10-Minuten-Intervall)", "info"); }); // Von 23:00 bis 05:59 alle 30 Minuten: schedule("*/30 23,0-5 * * *", function() { runCalculation(); if (config.debug) log("runCalculation ausgelöst (30-Minuten-Intervall)", "info"); }); // Initialer Aufruf beim Laden des Skripts: runCalculation();
Skript um die Datenpunkte für die Taupunkte hinzu zufügen:
/** * Skript: Anlage der Zieldatenpunkte für Taupunkte * * Beschreibung: * Dieses Skript erstellt automatisch alle Zieldatenpunkte für die Taupunkte in den einzelnen Räumen. * Der Datenpunktpfad entspricht dem Muster: * * 0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt.<Raumname> * * Die zugehörigen Optionen lauten: * - name: "<Raumname>-Taupunkt" * - type: "number" * - read: true * - write: true * - def: 0 * * Falls ein Datenpunkt bereits existiert, wird er nicht erneut erstellt. */ // Liste der Raumbezeichnungen, für die ein Taupunkt-Datenpunkt angelegt werden soll. var rooms = [ "Wohnzimmer", "Arbeitszimmer", "Badezimmer", "Buegelzimmer", "Essflur", "Schlafzimmer", "Waschkueche", "GG-Kueche", "GG-Schlafzimmer", "GG-Wohnzimmer", "GG-Bad" ]; // Optionale Debug-Ausgabe (true = ausführliche Logmeldungen) var debug = true; /** * Erstellt einen Datenpunkt, falls dieser noch nicht existiert. * * @param {string} dp - Der vollständige Datenpunktpfad. * @param {object} options - Optionen für den Datenpunkt, z.B.: * { name: "<Raumname>-Taupunkt", type: "number", read: true, write: true, def: 0, unit: °C } */ function createTargetState(dp, options) { if (!existsState(dp)) { // Datenpunkt anlegen und initial auf den Default-Wert setzen. createState(dp, options.def, { name: options.name, type: options.type, read: options.read, write: options.write }); if (debug) log("Datenpunkt erstellt: " + dp, "info"); } else { if (debug) log("Datenpunkt existiert bereits: " + dp, "info"); } } // Iteriere über alle Räume und erstelle die entsprechenden Taupunkt-Datenpunkte. rooms.forEach(function(roomName) { var dpPath = "0_userdata.0.EigeneDatenpunkte.Raumklima.Taupunkt." + roomName; var options = { name: roomName + "-Taupunkt", type: "number", read: true, write: true, def: 0 }; createTargetState(dpPath, options); }); log("Anlage der Zieldatenpunkte für Taupunkte abgeschlossen.", "info");
-
@homoran
Wie erkennst du das gekippte Fenster?
Arbeite selber mit den Aqara Sensoren, die können es so nicht, oder ich müsste ggf. einen zweiten je Fenster hinzufügen - dafür bin ich allerdings zu kniepig. -
@gismoh sagte in JS für Überwachung der Luftfeuchtigkeit in Räumen:
Wie erkennst du das gekippte Fenster?
Homematic Drehgriffsensoren!
-
-
Entweder mit einem Sensor direkt am Griff, dann kann man aber auch nicht sicher sein, ob das Fenster gekippt ist oder nur die Stellung des Griffs zutrifft.
-
Oder aber mit zwei Sensoren an einem kippbaren Fenster (sicherste Lösung).
-
Oder, da gekippte Fenster ohnehin sinnfrei sind, weil ein gekipptes Fenster immer im kriminalistischen Sinn offen ist, einfach die Kippfunktion deaktivieren und das Umfeld auf Stoßlüftung einschwören.
-
-
Der zweite Punkt ist mir aktuell nicht nur zu teuer, sondern auch zu unansehnlich (zwei Sensoren am Fenster).
Die Sensoren für den ersten Punkt muss ich mir noch ansehen.
Beim dritten Punkt musste ich gerade laut lachen....
Wo meine Schwiegereltern noch die alten Fenster hatten, wo es einen separaten Hebel für die Kippfunktion gab,
hatte ich denen diesen einfach an alle Fenster demontiert.
Diese waren zwar am schimpfen über mich, ABER, hatten seit dem keinen Schimmel mehr an den Fenster Laibungen XD -
@meister-mopper sagte in JS für Überwachung der Luftfeuchtigkeit in Räumen:
- Oder aber mit zwei Sensoren an einem kippbaren Fenster (sicherste Lösung).
Alternativ Shelly BLU Door/Window. Die haben Neigungswinkelmessung, über die Kipp erkannt wird und zusätzlich auch noch Helligkeit mit an Bord.
-
@samson71
Die Shelly´s würden mir ganz gut gefallen, wenn se nicht über Bluetooth gehen würden. Gerade die braune Version würde passen. Hoffentlich bringen se dies auch noch z.B. mit Zigbee raus. -
@gismoh
Du musst dafür im ioBroker direkt kein BT haben. Die kannst Du über BT z.B. an einen Shelly PM o.ä. koppeln. Darüber bekommst die dann zu fassen. Vorteil von den Dingern ist die günstige und langlebige CR2032 Batterie. Das hat mich bisher an den klassischen Batterie Aktoren von Shelly gestört. Die laufen meist nur mit den relativ teuren CR123 und direkt erhältlich sind sie auch nicht überall, wenn man mal akut eine braucht. -
@homoran sagte in JS für Überwachung der Luftfeuchtigkeit in Räumen:
@gismoh sagte in JS für Überwachung der Luftfeuchtigkeit in Räumen:
Momentan füge ich noch den Taupunkt hinzu, um zu warnen, wann es zu Schimmel kommen kann - so zumindest die Idee.
hast du den nicht schon in den Rechnungen drin?
für die Werkstatt hab ich noch zusätzlich den "Taupunkt" für 80% und 70% Feuchte, da dies eigentlich schon die Grenzwerte für Schimmelbildung sind
Hi, welches Widget hast Du für die Anzeige Ja/nein beim lüften genutzt.
Bekomme es einfach nicht hin mit dem Text bzw finde wohl nicht das Richtige.EDIT: hat sich erledigt, habe es hinbekommen