Table of Contents
Es kann eine Vielzahl von Werten geben, die bestimmte Schwellwerte nicht überschreiten sollen. Die Luftfeuchte in Räumen, den CO2-Gehalt, Batterie-Status, Grenzwerte in Hydroponik.
ObenHAB Items
Um Items, die überwacht werden sollen, zu identifizieren, will ich Tags verwenden. Wo möglich sollen es die vordefinierten semantischen Tags sein. Aber es gibt auch Werte, die darüber hinaus gehen. Außerdem ist das Einheiten-System von OpenHAB an dieser Stelle ungemein nützlich, da es Auch verschiedene Einheiten für den gleichen Sachverhalt miteinander vergleichen kann.
Die unten stehenden Beispiele habe ich aus ihrem Kontext gerissen. Sie sollen lediglich verdeutlichen, dass an das, was du schon hast, lediglich noch Tags und eine “tg” Trigger Group angefügt werden müssen und du im besten Fall sauber Einheiten definiert hast.
Für die Werte gilt die “tgWarnOnLimit”.
//Beispiel anhand von Wetterdaten
Number:Temperature OpenWeatherMapWetterinformationen_Current_Aussentemperatur "Aussentemperatur" (fgPersist,igOWMCurrentHEF,tgWarnOnLimit,tgCalc)["Measurement","Temperature"] { channel="openweathermap:weather-and-forecast:api:hef:current#temperature" }
Number:Pressure OpenWeatherMapWetterinformationen_Current_Luftdruck "Luftdruck" (fgPersist,igOWMCurrentHEF,tgWarnOnLimit)["Measurement","Pressure"] { channel="openweathermap:weather-and-forecast:api:hef:current#pressure" }
Number:Dimensionless OpenWeatherMapWetterinformationen_Current_Luftfeuchtigkeit "Luftfeuchtigkeit" (fgPersist,igOWMCurrentHEF,tgWarnOnLimit,tgCalc)["Measurement","Humidity"] { channel="openweathermap:weather-and-forecast:api:hef:current#humidity" }
Number:Speed OpenWeatherMapWetterinformationen_Current_Windgeschwindigkeit "Windgeschwindigkeit" (fgPersist,igOWMCurrentHEF,tgWarnOnLimit)["Measurement","Wind"] { channel="openweathermap:weather-and-forecast:api:hef:current#wind-speed" }
Number:Length OpenWeatherMapWetterinformationen_Current_Regen "Regen" (fgPersist,igOWMCurrentHEF,tgWarnOnLimit)["Measurement","Rain"] { channel="openweathermap:weather-and-forecast:api:hef:current#rain" }
Number:Temperature OpenWeatherMapWetterinformationen_Current_Taupunkt "Taupunkt [%.2f °C]" (fgPersist,igOWMCurrentHEF,tgWarnOnLimit)["Calculation","Dewpoint"] //complex transformaion via rule
//Beispiel anhand von eigenen Messwerten
Number:Dimensionless Arduino52_MQ2_CO "CO [%.2f ppm]" <smoke> (fgPersist,igArduino52,tgWarnOnLimit)["Measurement","CO"] //complex transformation via rule
Number:Temperature Arduino52_Temp "Temperatur (52) [%.2f °C]" <temperature> (fgPersist,igArduino52,igThermostatBueroAlex,sgOfficeAlex,tgWarnOnLimit)["Measurement","Temperature"] { alexa="CurrentTemperature", channel="mqtt:topic:myMosquitto:Arduino_52:temperature_C" }
Number:Temperature Arduino52_DewPoint "Taupukt [%.2f °C]" <rain> (fgPersist,igArduino52,sgOfficeAlex,tgWarnOnLimit)["Measurement","Dewpoint"] { channel="mqtt:topic:myMosquitto:Arduino_52:dew_point" }
Number:Pressure Arduino52_Druck_hPa "Luftdruck (52) [%.2f hPa]" <pressure> (fgPersist,igArduino52,tgWarnOnLimit)["Measurement","Pressure"] { channel="mqtt:topic:myMosquitto:Arduino_52:air_pressure_hPa" }
Number:Dimensionless Arduino52_LFeuch "Luftfeuchte [%.2f %%]" <humidity> (fgPersist,igArduino52,tgWarnOnLimit,sgOfficeAlex)["Measurement","Humidity"]{ alexa="RangeValue" [capabilityNames="Luftfeuchte,@Setting.Humidity", supportedRange="0:100:1", nonControllable=true], channel="mqtt:topic:myMosquitto:Arduino_52:humidity" }
Number:RadiationDoseEffective Arduino52_Rad_mmSvph "Rad [%.8f μSv]" (fgPersist,igArduino52,tgWarnOnLimit)["Measurement","Radiation","mmSvph"] { channel="mqtt:topic:myMosquitto:Arduino_52:rad_mmSvph" }
Number:RadiationDoseEffective Arduino52_Rad_mSvpy_Y "Rad (calc) [%.3f mSv]" (fgPersist,igArduino52,tgWarnOnLimit)["Measurement","Radiation","mSvpa"]
Number:RadiationDoseEffective Arduino52_Rad_mSvpy_m "Rad (calc) [%.3f mSv]" (fgPersist,igArduino52,tgWarnOnLimit)["Measurement","Radiation","mSvpm"]
OpenHAB rule
Zunächste definiere ich über viele Zeilen hinweg mögliche Grenzwerte. Nicht alle verwende ich schon, aber wenn ich es später tue, habe ich die Recherche hier schon dokumentiert. Auch hier stehen an den Variablen überall die Einheiten dran.
Es folgt eine Funktion, die die Warnmeldungen dann per Telegram raussendet und prüft, dass Meldungen nicht zu oft gesendet werden. In den meisten Beispielen verwende ich 180min als Mindestabstand. Dein Telegram musst du hier natürlich noch anpassen.
Und zuletzt folgen die Kriterien, an welchen Grenzwerten gewarnt werden soll.
import java.util.Map
import java.util.List
import org.openhab.core.model.script.ScriptServiceUtil
//1 Vol-% = 10.000 ppm | ml/m³
//Quellen
//https://www.air-q.com/grenzwerte
//https://www.abc-gefahren.de/dateien/ausbildung/elh/3_gsg_grenzwerte_gase.pdf
//--Lux--
val Lux_Ref_moonless_overcast_night_sky=0.0001|"lx"
val Lux_Ref_moonless_clear_night_sky_with_airglow=0.002|"lx"
val Lux_Ref_full_moon_on_a_clear_night=0.7|"lx" //0.27 - 1.0
val Lux_Ref_civil_twilight_under_clear_sky=3.4|"lx"
val Lux_Ref_corridor=100|"lx"
val Lux_Ref_bedroom=150|"lx"
val Lux_Ref_living_room=200|"lx"
val Lux_Ref_kitchen_bathroom=300|"lx"
val Lux_Ref_sunrise_sunset=400|"lx"
val Lux_Ref_office=500|"lx" //320 - 500
val Lux_Ref_overcast_day=1000|"lx"
val Lux_Ref_fully_daylight_without_direct_sun=17000|"lx" //10.000 - 25.000
val Lux_Ref_direct_sunlight=50000|"lx" //32.000 - 100.000
//--Radiation--
//https://www.bfs.de/DE/themen/ion/strahlenschutz/grenzwerte/grenzwerte.html//:~:text=Der%20Grenzwert%20f%C3%BCr%20die%20effektive,sind%20von%20diesen%20Begrenzungen%20ausgeschlossen.
//http://cholla.mmto.org/electronics/geiger/banggood/M4011.pdf
// Business : in milliSievert
val Rad_GW_business_year=20|"mSv"
val Rad_GW_emergency_case=250|"mSv"
val Rad_GW_business_lifetime=400|"mSv"
val Rad_GW_childbearing_women_per_month=2|"mSv"
// Privates : in milliSievert
val Rad_GW_underaged_and_unborn_people_per_year=1|"mSv"
val Rad_GW_organdosis_per_year=15|"mSv"
// Grenzwerte für kurze Zeiträume : in milliSievert
val Rad_GW_unborn_permanently_damaged=0.1|"mSv" //Unterer Schätzwert des Schwellenwerts für Schädigungen des Ungeborenen
val Rad_GW_reversable_illness=1|"mSv" //Bei akuter Exposition treten ab diesem Schwellenwert akute Strahleneffekte auf (zum Beispiel Kopfschmerzen, Übelkeit, Erbrechen)
val Rad_GW_potentially_deadly=3|"mSv" //Ohne medizinische Eingreifen sterben bei dieser Dosis 50 Prozent der exponierten Personen nach 3-6 Wochen, wenn es sich um eine in kurzer Zeit erfahrene Strahlenbelastung handelte (LD50)
val Rad_GW_deadly=8|"mSv" //Ohne entsprechende medizinische Behandlung bestehen nur geringe Überlebenschancen, wenn es sich um eine in kurzer Zeit erfahrene Strahlenbelastung handelte
val Rad_M4011_min_radiation_per_hour=20|"µSv" // / h
val Rad_M4011_max_radiation_per_hour=120|"µSv" // / h
//-- Humidity : Luftfeuchte in Prozent --
val Humidity_GW_lo=40.0|"%"
val Humidity_GW_hi=61.0|"%"
//-- Atemluft --
// Gefahrbeschreibung = ""
// explosiv = false
// untereExplosionsgrenze = null //Vol %
// HautresorptiverGefahrstoff = false //Schutzkleidung tragen!
// GewoehnungDesGeruchssinns = null
// Acute Exposure Guideline Levels
// AEGL1 = null //ppm, Schwelle zum Unwohlsein
// AEGL2 = null //ppm, Schwelle zur Einschränkung
// AEGL3 = null //ppm, Schwelle zur tödl. Wirkung
// ETW1 = null //ppm, Einsatztoleranzwert nach 1h Expositionszeit
// ETW4 = null //ppm, Einsatztoleranzwert nach 4h Expositionszeit
// MAK = null //ppm, max. Arbeitsplatzkonzentration
val Air_relGasdichte = 1
val Air_mol = 29.949 //g/mol
// GW = null //eigens verwendeter Grenzwert
//-- H2 : Wasserstoff --
val H2_Gefahrbeschreibung = "erstickend; explosiv"
val H2_untereExplosionsgrenze = 40000|"ppm"
val H2_relGasdichte = 0.06
val H2_mol = 2.02 //g/mol
val H2_GW = 40000|"ppm" //eigens verwendeter Grenzwert: Explosionsgefahr
//-- LPG : leicht verflüssigbare Kohlenwasserstoff-Verbindungen (CmHn) --
// Propan C3H8, Propen (Propylen) C3H6, Butan C4H10, Buten (Butylen) C4H8, Isobutan (Methylpropan) C4H10, Isobuten (Methylpropen) C4H8
val LPG_Gefahrbeschreibung = "erstickend / Wirkung auf Blut, Nerven und Zellen; explosiv"
val LPG_untereExplosionsgrenze = 14000|"ppm"
val LPG_MAK = 1000|"ppm" //ppm, max. Arbeitsplatzkonzentration
val LPG_relGasdichte = 2.1
val LPG_mol = 42.08 //g/mol -> Propen
val LPG_GW = 1000|"ppm" //ppm, eigens verwendeter Grenzwert: MAK
//-- CH4 : Methan --
val CH4_Gefahrbeschreibung = "erstickend; explosiv"
val CH4_untereExplosionsgrenze = 44000|"ppm"
val CH4_relGasdichte = 0.055
val CH4_mol = 16.04 //g/mol
val CH4_GW = 44000|"ppm" //ppm, eigens verwendeter Grenzwert: Explosionsgefahr
//-- CO : Kohlenstoffmonoxid --
val CO_Gefahrbeschreibung = "Wirkung auf Blut, Nerven und Zellen; explosiv"
val CO_untereExplosionsgrenze = 130000|"ppm"
// Acute Exposure Guideline Levels
val CO_AEGL2 = 33|"ppm" //ppm, Schwelle zur Einschränkung
val CO_ETW1 = 83|"ppm" //ppm, Einsatztoleranzwert nach 1h Expositionszeit
val CO_ETW4 = 33|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val CO_MAK = 30|"ppm" //ppm, max. Arbeitsplatzkonzentration
val CO_relGasdichte = 0.96
val CO_mol = 17.03 //g/mol
val CO_GW = 8|"ppm" //ppm, eigens verwendeter Grenzwert: Umweltbundesamt 8h-Mittelwert
//-- Smoke : Rauch --
//-- C3H8 : Propan --
val C3H8_Gefahrbeschreibung = "erstickend; explosiv"
val C3H8_untereExplosionsgrenze = 17000|"ppm"
val C3H8_MAK = 1000|"ppm" //ppm, max. Arbeitsplatzkonzentration
val C3H8_GW = 1000|"ppm" //ppm, eigens verwendeter Grenzwert: MAK
//-- C6H6 : Benzol --
val C6H6_Gefahrbeschreibung = "Wirkung auf Blut, Nerven und Zellen; explosiv"
val C6H6_untereExplosionsgrenze = 12000|"ppm"
val C6H6_HautresorptiverGefahrstoff = true //Schutzkleidung tragen!
// Acute Exposure Guideline Levels
val C6H6_AEGL1 = 18|"ppm" //ppm, Schwelle zum Unwohlsein
val C6H6_AEGL2 = 400|"ppm" //ppm, Schwelle zur Einschränkung
val C6H6_ETW4 = 20|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val C6H6_MAK = 1|"ppm" //ppm, max. Arbeitsplatzkonzentration
val C6H6_relGasdichte = 2.7
val C6H6_mol = 78.114 //g/mol
val C6H6_GW = 1|"ppm" //ppm, eigens verwendeter Grenzwert
//-- C6H14 : Hexan --
val C6H14_Gefahrbeschreibung = "Wirkung auf Blut, Nerven und Zellen; explosiv"
val C6H14_untereExplosionsgrenze = 11000|"ppm"
// Acute Exposure Guideline Levels
val C6H14_AEGL2 = 2900|"ppm" //ppm, Schwelle zur Einschränkung
val C6H14_ETW1 = 2900|"ppm" //ppm, Einsatztoleranzwert nach 1h Expositionszeit
val C6H14_ETW4 = 2900|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val C6H14_MAK = 50|"ppm" //ppm, max. Arbeitsplatzkonzentration
val C6H14_relGasdichte = 3.0
val C6H14_mol = 86.17 //g/mol
val C6H14_GW = 50|"ppm" //ppm, eigens verwendeter Grenzwert
//-- NOx : Stickstoffoxide --
val NOx_Gefahrbeschreibung = "reizend"
// Acute Exposure Guideline Levels
val NOx_AEGL1 = 0.5|"ppm" //ppm, Schwelle zum Unwohlsein
val NOx_AEGL2 = 8.2|"ppm" //ppm, Schwelle zur Einschränkung
val NOx_ETW1 = 12|"ppm" //ppm, Einsatztoleranzwert nach 1h Expositionszeit
val NOx_ETW4 = 8.2|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val NOx_MAK = 5|"ppm" //ppm, max. Arbeitsplatzkonzentration
val NOx_relGasdichte = 1.6
val NOx_mol = 46.01 //g/mol
val NOx_GW = 0.5|"ppm" //ppm, eigens verwendeter Grenzwert: AGS Grenzwert, längerfristige Exposition
//-- CL2 : Chlor --
val CL2_Gefahrbeschreibung = "reizend; explosiv"
// Acute Exposure Guideline Levels
val CL2_AEGL1 = 0.5|"ppm" //ppm, Schwelle zum Unwohlsein
val CL2_AEGL2 = 1|"ppm" //ppm, Schwelle zur Einschränkung
val CL2_ETW1 = 1|"ppm" //ppm, Einsatztoleranzwert nach 1h Expositionszeit
val CL2_ETW4 = 2|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val CL2_MAK = 0.5|"ppm" //ppm, max. Arbeitsplatzkonzentration
val CL2_relGasdichte = 2.5
val CL2_mol = 70.9 //g/mol
val CL2_GW = 0.5|"ppm" //ppm, eigens verwendeter Grenzwert
//-- O3 : Ozon --
// Acute Exposure Guideline Levels
val O3_MAK = 0.1|"ppm" //ppm, max. Arbeitsplatzkonzentration
val O3_relGasdichte = 1.78
val O3_mol = 48.00 //g/mol
val O3_GW = 0.06|"ppm" //ppm, eigens verwendeter Grenzwert: Umweltbundesamt
//https://www.engineeringtoolbox.com/air-contaminants-limits-d_1538.html
val O3_GW_mgm3=180|"mg/m³"
//-- CO2 : Kohlenstoffdioxid --
val CO2_Gefahrbeschreibung = "erstickend, Wirkung auf Blut, Nerven und Zellen"
val CO2_ETW4 = 10000|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val CO2_MAK = 5000|"ppm" //ppm, max. Arbeitsplatzkonzentration
val CO2_relGasdichte = 1.5
val CO2_mol = 44.01 //g/mol
val CO2_GW=1000|"ppm" //ppm, eigens verwendeter Grenzwert: Lüften-Schwellwert
val CO2_GW_MQ135=600|"ppm" //ppm, Mess-Ende des MQ135
//-- C2H5OH : Ethanol (evtl. auch andere Alkohole) --
val C2H5OH_Gefahrbeschreibung = "Wirkung auf Blut, Nerven und Zellen; explosiv"
val C2H5OH_untereExplosionsgrenze = 31000|"ppm"
val C2H5OH_ETW4 = 3000|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val C2H5OH_MAK = 500|"ppm" //ppm, max. Arbeitsplatzkonzentration
val C2H5OH_relGasdichte = 1.6
val C2H5OH_mol = 46.07 //g/mol
val C2H5OH_GW = 500|"ppm" //ppm, eigens verwendeter Grenzwert: MAK
//-- NH4 : Ammonium --
val NH4_mol = 18 //g/mol
//-- CH3 : Toluol --
val CH3_Gefahrbeschreibung = "Wirkung auf Blut, Nerven und Zellen; explosiv"
val CH3_untereExplosionsgrenze = 11000|"ppm"
// Acute Exposure Guideline Levels
val CH3_AEGL1 = 67|"ppm" //ppm, Schwelle zum Unwohlsein
val CH3_AEGL2 = 310|"ppm" //ppm, Schwelle zur Einschränkung
val CH3_ETW1 = 560|"ppm" //ppm, Einsatztoleranzwert nach 1h Expositionszeit
val CH3_ETW4 = 310|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val CH3_MAK = 50|"ppm" //ppm, max. Arbeitsplatzkonzentration
val CH3_relGasdichte = 3.2
val CH3_mol = 92.14 //g/mol
val CH3_GW = 50|"ppm" //ppm, eigens verwendeter Grenzwert: MAK
//-- CH3_2CO : Aceton --
val CH3_2CO_Gefahrbeschreibung = "Wirkung auf Blut, Nerven und Zellen; explosiv"
val CH3_2CO_untereExplosionsgrenze = 25000|"ppm"
// HautresorptiverGefahrstoff = null //Schutzkleidung tragen!
// GewoehnungDesGeruchssinns = null
// //Acute Exposure Guideline Levels
val CH3_2CO_AEGL1 = 200|"ppm" //ppm, Schwelle zum Unwohlsein
val CH3_2CO_AEGL2 = 1400|"ppm" //ppm, Schwelle zur Einschränkung
val CH3_2CO_ETW4 = 500|"ppm" //ppm, Einsatztoleranzwert nach 4h Expositionszeit
val CH3_2CO_MAK = 500|"ppm" //ppm, max. Arbeitsplatzkonzentration
val CH3_2CO_relGasdichte = 2.0
val CH3_2CO_mol = 58.08 //g/mol
val CH3_2CO_GW = 200|"ppm" //ppm, eigens verwendeter Grenzwert: AEGL1
// // --- Athmosphärische Warnungen ---
// // entsprechend https://www.hlnug.de/fileadmin/dokumente/luft/monatsberichte/2018/LHMB_2018-03.pdf
//
//-- PM10 : Feinstaub PM10 --
//https://www.umweltbundesamt.de/themen/luft/luftschadstoffe-im-ueberblick/feinstaub
val PM10_GW_mgm3=50|"mg/m³" //max 35x im Jahr Jahresmittel
val PM10_GW_mgm3_averagePerYear=40|"mg/m³" //im Jahresmittel
//-- PM2.5 : Feinstaub PM2.5 --
//https://www.umweltbundesamt.de/themen/luft/luftschadstoffe-im-ueberblick/feinstaub
val PM25_GW_mgm3_averagePerYear=25|"mg/m³" //im Jahresmittel
//-- NO2 : Stickstoffdioxid --
//https://www.engineeringtoolbox.com/air-contaminants-limits-d_1538.html
val NO2_GW_mgm3=40|"mg/m³"
//-- SO2 : Schwefeldioxid --
//https://www.engineeringtoolbox.com/air-contaminants-limits-d_1538.html
val SO2_GW_mgm3=20|"mg/m³"
var lastSent = <String, ZonedDateTime>newHashMap
val sendOncePerPeriod = [ GenericItem triggeringItem, Map<String, ZonedDateTime> lastSent, String message, int minutes |
if(!lastSent.containsKey(triggeringItem.name)){
lastSent.put(triggeringItem.name, now.minusMinutes(minutes +1))
}
var ZonedDateTime oldLastSent = lastSent.get(triggeringItem.name)
var tempTime = oldLastSent
if(tempTime.plusMinutes(minutes).isBefore(now)){
getActions("telegram","telegram:telegramBot:home_Bot").sendTelegram(-000000000L,message)
lastSent.put(triggeringItem.name, now)
}
]
rule "Sensor warn on limit"
when
Member of tgWarnOnLimit changed
then
//which room is affected due to the triggering item room?
//detect itemGroup (ig) of the item
val String igName = triggeringItem.getGroupNames.findFirst[ String groupName | groupName.startsWith("ig") ]
if(igName === null){
logInfo("Test", "triggeringItem: {}", triggeringItem )
}
val GroupItem igItem = ScriptServiceUtil.getItemRegistry.getItem(igName) as GroupItem
//detect semanticGroup of the itemGroup
val String sgName = igItem.getGroupNames.findFirst[ String groupName | groupName.startsWith("sg") ]
var List<String> roomList = newArrayList(sgName)
//Environment
val float dewpointEnvironment = (OpenWeatherMapWetterinformationen_Current_Taupunkt.state as Number).floatValue()
//do something with the rooms:
roomList.forEach[ String sgName |
val roomItem = ScriptServiceUtil.getItemRegistry.getItem(sgName) as GroupItem
//what states has the room?
val NumberItem dewpointItem = roomItem.allMembers.findFirst[ item | item.hasTag("Measurement") && item.hasTag("Dewpoint") ]
val float dewpoint = (dewpointItem.state as Number).floatValue
//val NumberItem humidityItem = roomItem.allMembers.findFirst[ item | item.hasTag("Measurement") && item.hasTag("Humidity") ]
//val float humidity = (humidityItem.state as Number).floatValue
//val NumberItem temperatureItem = roomItem.allMembers.findFirst[ item | item.hasTag("Measurement") && item.hasTag("Temperature") ]
//val float temperature = (temperatureItem.state as Number).floatValue
val int windows = roomItem.members.filter[ item | item.hasTag("Window")].size()
val int openWindows = roomItem.members.filter[ item | item.hasTag("Window") && (item.state as Number).intValue > 0].size()
//Was macht die Heizung?
//Was steht im Heizkalender?
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("Battery")){
if((triggeringItem.state as Number).intValue <= 20){
sendOncePerPeriod.apply(triggeringItem,lastSent,String::format("Batterie fast leer: %s in %s", triggeringItem.label , roomItem.label ), 180 )
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("Humidity")){
if(triggeringItem.state > Humidity_GW_hi){
if(
(windows == 0 && dewpoint >= dewpointEnvironment +2)
|| (openWindows == 0 && dewpoint >= dewpointEnvironment +2 )
){
sendOncePerPeriod.apply(triggeringItem,lastSent,String::format("Luftfeuchte hoch: in %s, dringend lüften!", roomItem.label ), 180 )
}
}
if(triggeringItem.state < Humidity_GW_lo){
if(
(windows == 0 && dewpoint <= dewpointEnvironment -2)
|| (openWindows == 0 && dewpoint <= dewpointEnvironment -2 )
){
sendOncePerPeriod.apply(triggeringItem,lastSent,String::format("Luftfeuchte niedrig: in %s, dringend lüften!", roomItem.label ), 180 )
}
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("CO2")){
if(triggeringItem.state > CO2_GW_MQ135){
if(
windows == 0
|| (openWindows == 0 && dewpoint >= dewpointEnvironment +3 )
){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, CO2_GW_MQ135 )
sendOncePerPeriod.apply(triggeringItem,lastSent,String::format("CO2 hoch: in %s, dringend lüften!", roomItem.label ), 180 )
}
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("Radiation")){
//Short-term Radiation
if( triggeringItem.hasTag("mmSvph") ){
if(triggeringItem.state > Rad_GW_childbearing_women_per_month){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, Rad_GW_childbearing_women_per_month )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Radioaktivität überschreitet kurzfristige Grenzwerte", 180 )
}
}
//Mid-term Radiation
if( triggeringItem.hasTag("mSvpm") ){
if(triggeringItem.state > Rad_GW_childbearing_women_per_month){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, Rad_GW_childbearing_women_per_month )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Radioaktivität überschreitet mittelfristige Grenzwerte", 180 )
}
}
//Long-term Radiation
if( triggeringItem.hasTag("mSvpa") ){
if(triggeringItem.state > Rad_GW_organdosis_per_year){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, Rad_GW_organdosis_per_year )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Radioaktivität überschreitet langfristige Grenzwerte", 180 )
}
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("O3")){
if(triggeringItem.state > O3_GW_mgm3){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, O3_GW_mgm3 )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Athmosphärische Warnung: O3 (Ozon) kritisch!", 180 )
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("PM10")){
if(triggeringItem.state > PM10_GW_mgm3){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, PM10_GW_mgm3 )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Athmosphärische Warnung: PM10 (Feinstaubpartikel 10µm) kritisch!", 180 )
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("PM25")){
if(triggeringItem.state > PM25_GW_mgm3_averagePerYear){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, PM25_GW_mgm3_averagePerYear )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Athmosphärische Warnung: PM2.5 (Feinstaubpartikel 2.5µm) kritisch!", 180 )
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("NO2")){
if(triggeringItem.state > NO2_GW_mgm3){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, NO2_GW_mgm3 )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Athmosphärische Warnung: NO2 (Stickstoffdioxid) kritisch!", 180 )
}
}
if(triggeringItem.hasTag("Measurement") && triggeringItem.hasTag("SO2")){
if(triggeringItem.state > SO2_GW_mgm3){
logInfo("Test", "triggeringItem: {}; Grenzwert: {}", triggeringItem, SO2_GW_mgm3 )
sendOncePerPeriod.apply(triggeringItem,lastSent,"Athmosphärische Warnung: SO2 (Stickstoffdioxid) kritisch!", 180 )
}
}
]
endEin Wort zu JS Automation und den Units. Es existiert ein Wrapper für die OpenHAB Units in den JS-Automations. Allerdings empfehle ich dringend Item-Werte auf eine Einheit zu normalisieren und dann als float ohne Einheiten weiterzubenutzen. Rechenoperationen werden derzeit (2026) auf unterschiedliche Weise verwendet.
Probleme werden deutlich an folgenden Beispielen
- Unterschiedliche Bezugspunkte bei der Verrechnung: Versuchst du, eine Durchschnittstemperatur aus zwei Werten (z.B. 18°C und 22°C) zu berechnen, scheint es eine gute Idee zu sein, beide zu addieren und durch 2 zu dividieren. Addition und Division werden allerdings zu unterschiedlichen Bezugspunkten (°C und Kelvin) verrechnet und so entsteht ein unvorhergesehener Wert, der deutlich vom erwarteten Wert (20°C) abweicht.
- Lesbarkeit: Die Rechenoperationen sind in JS nur über Methoden möglich, nicht mit +, -, *, /, … So entsteht schlecht lesbarer Code.
const { rules, triggers, items, actions, time, Quantity } = require('openhab');
// --- CONSTANTS ---
const TELEGRAM_CHAT_ID = -000000000;
const telegramAction = actions.get("telegram", "telegram:telegramBot:home_Bot");
// Limits (selection of the most important ones to keep the script clear)
const LIMITS = {
// 1 Vol-% = 10,000 ppm | ml/m³
// Sources
// https://www.air-q.com/grenzwerte
// https://www.abc-gefahren.de/dateien/ausbildung/elh/3_gsg_grenzwerte_gase.pdf
//--Lux--
Lux_Ref_moonless_overcast_night_sky: Quantity('0.0001 lx'),
Lux_Ref_moonless_clear_night_sky_with_airglow: Quantity('0.002 lx'),
Lux_Ref_full_moon_on_a_clear_night: Quantity('0.7 lx'), // 0.27 - 1.0
Lux_Ref_civil_twilight_under_clear_sky: Quantity('3.4 lx'),
Lux_Ref_corridor: Quantity('100 lx'),
Lux_Ref_bedroom: Quantity('150 lx'),
Lux_Ref_living_room: Quantity('200 lx'),
Lux_Ref_kitchen_bathroom: Quantity('300 lx'),
Lux_Ref_sunrise_sunset: Quantity('400 lx'),
Lux_Ref_office: Quantity('500 lx'), // 320 - 500
Lux_Ref_overcast_day: Quantity('1000 lx'),
Lux_Ref_fully_daylight_without_direct_sun: Quantity('17000 lx'), // 10,000 - 25,000
Lux_Ref_direct_sunlight: Quantity('50000 lx'), // 32,000 - 100,000
//-- AQI - Air Quality Index --
AQI_Ref_Good : 50.0,
AQI_Ref_Moderate : 100.0,
AQI_Ref_UnhealthyForSensitive : 150,
AQI_Ref_Unhealthy : 200,
AQI_Ref_VeryUnhealthy : 300,
//AQI_REF_Hazardous : 300+
//-- IAQ
//https://svach.lbl.gov/iaq-index-scores-indoor-air-quality/
IAQ_Ref_Better : 40.0,
IAQ_Ref_TypicalHome : 100.0,
IAQ_Ref_Worse : 140.0,
//--Radiation--
//https://www.bfs.de/DE/themen/ion/strahlenschutz/grenzwerte/grenzwerte.html//:~:text: Der%20Grenzwert%20f%C3%BCr%20die%20effektive,sind%20von%20diesen%20Begrenzungen%20ausgeschlossen.
//http://cholla.mmto.org/electronics/geiger/banggood/M4011.pdf
// Business : in milliSievert
Rad_GW_business_year: Quantity('20 mSv'),
Rad_GW_emergency_case: Quantity('250 mSv'),
Rad_GW_business_lifetime: Quantity('400 mSv'),
Rad_GW_childbearing_women_per_month: Quantity('2 mSv'),
// Private : in milliSievert
Rad_GW_underaged_and_unborn_people_per_year: Quantity('1 mSv'),
Rad_GW_organdosis_per_year: Quantity('15 mSv'),
// Limits for short periods : in milliSievert
Rad_GW_unborn_permanently_damaged: Quantity('0.1 mSv'), // Lower estimate of the threshold for harm to the unborn
Rad_GW_reversable_illness: Quantity('1 mSv'), // In case of acute exposure, acute radiation effects occur from this threshold (e.g. headache, nausea, vomiting)
Rad_GW_potentially_deadly: Quantity('3 mSv'), // Without medical intervention, 50 percent of exposed persons die at this dose after 3-6 weeks if it was a radiation exposure experienced in a short time (LD50)
Rad_GW_deadly: Quantity('8 mSv'), // Without appropriate medical treatment, there is little chance of survival if the radiation exposure was experienced in a short period of time
Rad_M4011_min_radiation_per_hour: Quantity('20 µSv'), // / h
Rad_M4011_max_radiation_per_hour: Quantity('120 µSv'), // / h
//-- Humidity : Air humidity in percent --
Humidity_GW_lo: Quantity('40.0 %'),
Humidity_GW_hi: Quantity('61.0 %'),
//-- Breathing air --
// Hazard description : ""
// explosive : false
// lower explosion limit : null //Vol %
// skin absorptive hazardous substance : false // Wear protective clothing!
// habituation of the sense of smell : null
// Acute Exposure Guideline Levels
// AEGL1 : null // ppm, threshold for discomfort
// AEGL2 : null // ppm, threshold for impairment
// AEGL3 : null // ppm, threshold for lethal effect
// ETW1 : null // ppm, exposure tolerance value after 1h exposure time
// ETW4 : null // ppm, exposure tolerance value after 4h exposure time
// MAK : null // ppm, max. workplace concentration
Air_relGasdichte : 1,
Air_mol : 29.949, //g/mol
// GW : null // self-used limit
//-- H2 : Hydrogen --
H2_Gefahrbeschreibung : "erstickend; explosiv",
H2_untereExplosionsgrenze : Quantity('40000 ppm'),
H2_relGasdichte : 0.06,
H2_mol : 2.02, //g/mol
H2_GW : Quantity('40000 ppm'), // self-used limit: explosion hazard
//-- LPG : easily liquefiable hydrocarbon compounds (CmHn) --
// Propane C3H8, propene (propylene) C3H6, butane C4H10, butene (butylene) C4H8, isobutane (methylpropane) C4H10, isobutene (methylpropene) C4H8
LPG_Gefahrbeschreibung : "erstickend / Wirkung auf Blut, Nerven und Zellen; explosiv",
LPG_untereExplosionsgrenze : Quantity('14000 ppm'),
LPG_MAK : Quantity('1000 ppm'), // ppm, max. workplace concentration
LPG_relGasdichte : 2.1,
LPG_mol : 42.08, //g/mol -> Propene
LPG_GW : Quantity('1000 ppm'), // ppm, self-used limit: MAK
//-- CH4 : Methane --
CH4_Gefahrbeschreibung : "erstickend; explosiv",
CH4_untereExplosionsgrenze : Quantity('44000 ppm'),
CH4_relGasdichte : 0.055,
CH4_mol : 16.04, //g/mol
CH4_GW : Quantity('44000 ppm'), // ppm, self-used limit: explosion hazard
//-- CO : Carbon monoxide --
CO_Gefahrbeschreibung : "Wirkung auf Blut, Nerven und Zellen; explosiv",
CO_untereExplosionsgrenze : Quantity('130000 ppm'),
// Acute Exposure Guideline Levels
CO_AEGL2 : Quantity('33 ppm'), // ppm, threshold for impairment
CO_ETW1 : Quantity('83 ppm'), // ppm, exposure tolerance value after 1h exposure time
CO_ETW4 : Quantity('33 ppm'), // ppm, exposure tolerance value after 4h exposure time
CO_MAK : Quantity('30 ppm'), // ppm, max. workplace concentration
CO_relGasdichte : 0.96,
CO_mol : 17.03, //g/mol
CO_GW : Quantity('8 ppm'), // ppm, self-used limit: Federal Environment Agency 8h average
CO_GW_mgm3 : Quantity('4 mg/m³'),
//-- Smoke : Smoke --
//-- C3H8 : Propane --
C3H8_Gefahrbeschreibung : "erstickend; explosiv",
C3H8_untereExplosionsgrenze : Quantity('17000 ppm'),
C3H8_MAK : Quantity('1000 ppm'), // ppm, max. workplace concentration
C3H8_GW : Quantity('1000 ppm'), // ppm, self-used limit: MAK
//-- C6H6 : Benzene --
C6H6_Gefahrbeschreibung : "Wirkung auf Blut, Nerven und Zellen; explosiv",
C6H6_untereExplosionsgrenze : Quantity('12000 ppm'),
C6H6_HautresorptiverGefahrstoff : true, // Wear protective clothing!
// Acute Exposure Guideline Levels
C6H6_AEGL1 : Quantity('18 ppm'), // ppm, threshold for discomfort
C6H6_AEGL2 : Quantity('400 ppm'), // ppm, threshold for impairment
C6H6_ETW4 : Quantity('20 ppm'), // ppm, exposure tolerance value after 4h exposure time
C6H6_MAK : Quantity('1 ppm'), // ppm, max. workplace concentration
C6H6_relGasdichte : 2.7,
C6H6_mol : 78.114, //g/mol
C6H6_GW : Quantity('1 ppm'), // ppm, self-used limit
//-- C6H14 : Hexane --
C6H14_Gefahrbeschreibung : "Wirkung auf Blut, Nerven und Zellen; explosiv",
C6H14_untereExplosionsgrenze : Quantity('11000 ppm'),
// Acute Exposure Guideline Levels
C6H14_AEGL2 : Quantity('2900 ppm'), // ppm, threshold for impairment
C6H14_ETW1 : Quantity('2900 ppm'), // ppm, exposure tolerance value after 1h exposure time
C6H14_ETW4 : Quantity('2900 ppm'), // ppm, exposure tolerance value after 4h exposure time
C6H14_MAK : Quantity('50 ppm'), // ppm, max. workplace concentration
C6H14_relGasdichte : 3.0,
C6H14_mol : 86.17, //g/mol
C6H14_GW : Quantity('50 ppm'), // ppm, self-used limit
//-- NOx : Nitrogen oxides --
NOx_Gefahrbeschreibung : "reizend",
// Acute Exposure Guideline Levels
NOx_AEGL1 : Quantity('0.5 ppm'), // ppm, threshold for discomfort
NOx_AEGL2 : Quantity('8.2 ppm'), // ppm, threshold for impairment
NOx_ETW1 : Quantity('12 ppm'), // ppm, exposure tolerance value after 1h exposure time
NOx_ETW4 : Quantity('8.2 ppm'), // ppm, exposure tolerance value after 4h exposure time
NOx_MAK : Quantity('5 ppm'), // ppm, max. workplace concentration
NOx_relGasdichte : 1.6,
NOx_mol : 46.01, //g/mol
NOx_GW : Quantity('0.5 ppm'), // ppm, self-used limit: AGS limit, longer-term exposure
//-- CL2 : Chlorine --
CL2_Gefahrbeschreibung : "reizend; explosiv",
// Acute Exposure Guideline Levels
CL2_AEGL1 : Quantity('0.5 ppm'), // ppm, threshold for discomfort
CL2_AEGL2 : Quantity('1 ppm'), // ppm, threshold for impairment
CL2_ETW1 : Quantity('1 ppm'), // ppm, exposure tolerance value after 1h exposure time
CL2_ETW4 : Quantity('2 ppm'), // ppm, exposure tolerance value after 4h exposure time
CL2_MAK : Quantity('0.5 ppm'), // ppm, max. workplace concentration
CL2_relGasdichte : 2.5,
CL2_mol : 70.9, //g/mol
CL2_GW : Quantity('0.5 ppm'), // ppm, self-used limit
//-- O3 : Ozone --
// Acute Exposure Guideline Levels
O3_MAK : Quantity('0.1 ppm'), // ppm, max. workplace concentration
O3_relGasdichte : 1.78,
O3_mol : 48.00, //g/mol
O3_GW : Quantity('0.06 ppm'), // ppm, self-used limit: Federal Environment Agency
//https://www.engineeringtoolbox.com/air-contaminants-limits-d_1538.html
O3_GW_mgm3: Quantity('180 mg/m³'),
//-- CO2 : Carbon dioxide --
CO2_Gefahrbeschreibung : "erstickend, Wirkung auf Blut, Nerven und Zellen",
CO2_ETW4 : Quantity('10000 ppm'), // ppm, exposure tolerance value after 4h exposure time
CO2_MAK : Quantity('5000 ppm'), // ppm, max. workplace concentration
CO2_relGasdichte : 1.5,
CO2_mol : 44.01, //g/mol
CO2_GW: Quantity('1000 ppm'), // ppm, self-used limit: ventilation threshold
CO2_GW_MQ135: Quantity('600 ppm'), // ppm, measuring end of MQ135
//-- C2H5OH : Ethanol (possibly also other alcohols) --
C2H5OH_Gefahrbeschreibung : "Wirkung auf Blut, Nerven und Zellen; explosiv",
C2H5OH_untereExplosionsgrenze : Quantity('31000 ppm'),
C2H5OH_ETW4 : Quantity('3000 ppm'), // ppm, exposure tolerance value after 4h exposure time
C2H5OH_MAK : Quantity('500 ppm'), // ppm, max. workplace concentration
C2H5OH_relGasdichte : 1.6,
C2H5OH_mol : 46.07, //g/mol
C2H5OH_GW : Quantity('500 ppm'), // ppm, self-used limit: MAK
//-- NH4 : Ammonium --
NH4_mol : 18, //g/mol
//-- CH3 : Toluene --
CH3_Gefahrbeschreibung : "Wirkung auf Blut, Nerven und Zellen; explosiv",
CH3_untereExplosionsgrenze : Quantity('11000 ppm'),
// Acute Exposure Guideline Levels
CH3_AEGL1 : Quantity('67 ppm'), // ppm, threshold for discomfort
CH3_AEGL2 : Quantity('310 ppm'), // ppm, threshold for impairment
CH3_ETW1 : Quantity('560 ppm'), // ppm, exposure tolerance value after 1h exposure time
CH3_ETW4 : Quantity('310 ppm'), // ppm, exposure tolerance value after 4h exposure time
CH3_MAK : Quantity('50 ppm'), // ppm, max. workplace concentration
CH3_relGasdichte : 3.2,
CH3_mol : 92.14, //g/mol
CH3_GW : Quantity('50 ppm'), // ppm, self-used limit: MAK
//-- CH3_2CO : Acetone --
CH3_2CO_Gefahrbeschreibung : "Wirkung auf Blut, Nerven und Zellen; explosiv",
CH3_2CO_untereExplosionsgrenze: Quantity('25000 ppm'),
// skin absorptive hazardous substance : null // Wear protective clothing!
// habituation of the sense of smell : null
// //Acute Exposure Guideline Levels
CH3_2CO_AEGL1 : Quantity('200 ppm'), // ppm, threshold for discomfort
CH3_2CO_AEGL2 : Quantity('1400 ppm'), // ppm, threshold for impairment
CH3_2CO_ETW4 : Quantity('500 ppm'), // ppm, exposure tolerance value after 4h exposure time
CH3_2CO_MAK : Quantity('500 ppm'), // ppm, max. workplace concentration
CH3_2CO_relGasdichte : 2.0,
CH3_2CO_mol : 58.08, //g/mol
CH3_2CO_GW : Quantity('200 ppm'), // ppm, self-used limit: AEGL1
// // --- Atmospheric warnings ---
// // according to https://www.hlnug.de/fileadmin/dokumente/luft/monatsberichte/2018/LHMB_2018-03.pdf
//
//-- PM10 : Particulate matter PM10 --
//https://www.umweltbundesamt.de/themen/luft/luftschadstoffe-im-ueberblick/feinstaub
PM10_GW_mgm3: Quantity('50 mg/m³'), // max 35x a year annual average
PM10_GW_mgm3_averagePerYear: Quantity('40 mg/m³'), // annual average
//-- PM2.5 : Particulate matter PM2.5 --
//https://www.umweltbundesamt.de/themen/luft/luftschadstoffe-im-ueberblick/feinstaub
PM25_GW_mgm3_averagePerYear: Quantity('25 mg/m³'), // annual average
//-- NO2 : Nitrogen dioxide --
//https://www.engineeringtoolbox.com/air-contaminants-limits-d_1538.html
NO2_GW_mgm3: Quantity('40 mg/m³'),
//-- SO2 : Sulfur dioxide --
//https://www.engineeringtoolbox.com/air-contaminants-limits-d_1538.html
SO2_GW_mgm3: Quantity('20 mg/m³'),
//-- AQI : General AQI value
AQI_GW: 100
};
// --- GLOBAL STORAGE (Persistence within the script lifecycle) ---
// In JS files, these variables are retained as long as the file is not reloaded.
const lastSent = new Map();
// --- HELPER FUNCTIONS ---
/**
* Sends a message only once per period
*/
const sendOncePerPeriod = (triggeringItem, message, minutes) => {
const now = time.ZonedDateTime.now();
const lastTime = lastSent.get(triggeringItem.name);
if (!lastTime || lastTime.plusMinutes(minutes).isBefore(now)) {
telegramAction.sendTelegram(TELEGRAM_CHAT_ID, message);
lastSent.set(triggeringItem.name, now);
}
};
/**
* Calculates the dew point
*/
const getDewpoint = (temp, hum) => {
let a = (temp >= 0) ? 7.5 : 7.6;
let b = (temp >= 0) ? 237.3 : 240.7;
const sdd = 6.1078 * Math.pow(10, (a * temp) / (b + temp));
const dd = (hum) * sdd;
const v = Math.log10(dd / 6.107);
return (b * v) / (a - v);
};
// --- RULES ---
/**
* Sensor limit monitoring (The main logic)
*/
rules.JSRule({
name: "Sensor warn on limit",
triggers: [triggers.GroupStateChangeTrigger('tgWarnOnLimit')],
execute: (event) => {
// which room is affected due to the triggering item room?
// detect itemGroup (ig) of the item
const triggeringItem = items.getItem(event.itemName);
const igName = triggeringItem.groupNames.find(g => g.startsWith("ig"));
const igItem = items.getItem(igName);
// detect semanticGroup of the itemGroup
const roomList = igItem.groupNames.filter(g => g.startsWith("sg"));
// do something with the rooms:
roomList.forEach(sgName => {
const roomItem = items.getItem(sgName);
// -- Room --
const dewpointRoomItem = roomItem.members.find(m => m.tags.includes("Measurement") && m.tags.includes("Dewpoint"));
const dewpointRoom = (dewpointRoomItem && dewpointRoomItem.state !== 'NULL') ? dewpointRoomItem.quantityState : Quantity('0 °C');
if(!dewpointRoomItem){
console.log("ERROR: No dewpoint-Item in " + sgName);
}
const temperatureRoomItem = roomItem.members.find(m => m.tags.includes("Measurement") && m.tags.includes("Temperature"));
const temperatureRoom = (temperatureRoomItem && temperatureRoomItem.state !== 'NULL') ? temperatureRoomItem.quantityState : Quantity('0 °C');
const windowItems = roomItem.members.filter(m => m.tags.includes("Window"));
const windows = windowItems.length;
const openWindows = windowItems.filter(m => m.numericState > 0).length;
// -- Environment --
const temperatureEnvironmentItem = items.getItem("siTemperatureEnvironment");
const temperatureEnvironment = temperatureEnvironmentItem.quantityState;
const dewpointEnvironmentItem = items.getItem("siDewpointEnvironment");
const dewpointEnvironment = dewpointEnvironmentItem.quantityState;
const now = time.ZonedDateTime.now();
const oneHourAgo = now.minusHours(1);
const oneDayAgo = now.minusHours(24);
// check radiators
if (triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("TempRadiator")) {
// --- What is the heater doing? (History of the last hour) ---
// .quantityState gives us the value with unit directly (e.g. 35 °C)
const radiatorTemperatureMax = triggeringItem.persistence.maximumSince(oneHourAgo).quantityState;
const radiatorTemperatureMin = triggeringItem.persistence.minimumSince(oneHourAgo).quantityState;
// --- What should it do? (Find setpoint in the same room) ---
const setpointItem = roomItem.members.find(m => m.tags.includes("Setpoint") && m.tags.includes("Temperature"));
const setpointValueMax = setpointItem.persistence.maximumSince(oneHourAgo).quantityState;
const setpointValueMin = setpointItem.persistence.minimumSince(oneHourAgo).quantityState;
// --- What is the outside temperature? ---
if (temperatureEnvironmentItem && temperatureEnvironmentItem.state !== 'NULL') {
const temperatureEnvironmentMax = temperatureEnvironmentItem.persistence.maximumSince(oneDayAgo).quantityState;
// --- What is the room temperature? ---
// We use the item 'temperatureRoomItem' here (must be defined in context, see previous example)
const roomTemperature = temperatureRoomItem.persistence.minimumSince(oneHourAgo).quantityState;
// LOGIC: If the heater is running even though it shouldn't
if (
// If the heater is running (>30°C && > outside temperature)
radiatorTemperatureMin.greaterThan('30 °C') &&
radiatorTemperatureMin.greaterThan(temperatureEnvironmentMax) &&
// and it shouldn't be (Setpoint for more than an hour less than (room temperature +2))
setpointValueMax.lessThan(roomTemperature.add('2 °C')) &&
// Only during the day, at night is setback temperature
(now.hour() < 23 && now.hour() >= 7)
) {
sendOncePerPeriod(
triggeringItem,
`Heizkörper zu warm in: ${roomItem.label}`,
180
);
}
} else {
console.error("siTemperatureEnvironment ist nicht verfügbar oder hat keinen Status.");
}
// LOGIC: Radiator too cold
if (
radiatorTemperatureMax.lessThan(radiatorTemperatureMin)
&& (now.hour() <= 23 && now.hour() >= 6)
) {
sendOncePerPeriod(
triggeringItem,
`Heizkörper zu kalt in: ${roomItem.label}`,
180
);
}
}
if (triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("Battery")) {
if (triggeringItem.numerictate <= 20) {
sendOncePerPeriod(
triggeringItem,
`Batterie fast leer: ${triggeringItem.label} in ${roomItem.label} (${triggeringItem.state})`,
480
);
}
}
if (triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("Humidity")) {
if (
temperatureEnvironmentItem && temperatureEnvironmentItem.state !== 'NULL'
&& dewpointEnvironmentItem && dewpointEnvironmentItem.state !== 'NULL'
) {
const avgTemp = temperatureRoomItem.persistence.averageSince(oneDayAgo).quantityState.toUnit("°C").float;
const maxTemp = temperatureRoomItem.persistence.maximumSince(oneDayAgo).quantityState.toUnit("°C").float;
const averageTemperatureRoom = (temperatureRoomItem) ? ((avgTemp + maxTemp) / 2) : 0.0;
const averageDewpointRoom = (temperatureRoomItem) ? getDewpoint(averageTemperatureRoom,0.54) : 0.0;
const weightTemperature = 1.0;
const weightDewpoint = 3.0;
const threshold = 120;
const differenceTemperatureRoom = temperatureRoom.toUnit("°C").float - averageTemperatureRoom;
const differenceTemperatureEnvironment = temperatureEnvironment.toUnit("°C").float - averageTemperatureRoom;
// Value of the temperature (exchange): Value of the outside temperature * need for temperature change (as weighting)
const valueOfTemperature = ( -1* differenceTemperatureRoom + differenceTemperatureEnvironment) * -1 * differenceTemperatureRoom; // makes the value quadratic. For a linear evaluation, it would have to be multiplied by the sign of differenceTemperatureRoom
const differenceDewpointRoom = dewpointRoom.toUnit("°C").float - averageDewpointRoom;
const differenceDewpointEnvironment = dewpointEnvironment.toUnit("°C").float - averageDewpointRoom;
const valueOfDewpoint = -1* ( -1* differenceDewpointRoom + differenceDewpointEnvironment) * differenceDewpointRoom;
const valueOfAir = valueOfTemperature * weightTemperature + valueOfDewpoint * weightDewpoint
if(now.hour() < 0 || now.hour() >= 6){ //daytime
if(windows == 0 && valueOfAir > threshold ){
sendOncePerPeriod(
triggeringItem,
`Guter Zeitpunkt die Fenster zu öffnen: in ${roomItem.label} (T=${temperatureRoom}, TP=${dewpointRoom} VS Draussen: T=${temperatureEnvironment}, TP=${dewpointEnvironment})`,
180
);
//logInfo("Test", "Guter Zeitpunkt die Fenster zu öffnen: in {} (T={}, TP={} VS Draussen: T={}, TP={}; Taupunktwert: -1* ( -1* {} + {}) * {} = {}))", roomItem.label, temperatureRoom, dewpointRoom, temperatureEnvironment.floatValue(), dewpointEnvironment, differenceDewpointRoom, differenceDewpointEnvironment, differenceDewpointRoom, valueOfDewpoint )
}
if(openWindows == 0 && valueOfAir > threshold ){
// Air too dry
//sendOncePerPeriod(
// triggeringItem,
// `Guter Zeitpunkt die Fenster zu öffnen: in ${roomItem.label} (T=${temperatureRoom}, TP=${dewpointRoom} VS Draussen: T=${temperatureEnvironment}, TP=${dewpointEnvironment})`,
// 360
//);
}
if(openWindows > 0 && valueOfAir < threshold ){
sendOncePerPeriod(
triggeringItem,
`Guter Zeitpunkt die Fenster zu schließen: in ${roomItem.label} (T=${temperatureRoom}, TP=${dewpointRoom} VS Draussen: T=${temperatureEnvironment}, TP=${dewpointEnvironment}, Value=${valueOfAir})`,
60
);
}
}
}
if(
triggeringItem.persistence.averageSince(now.minusDays(3)).numericState > 0.62
&& !roomItem.tags.includes("Garden")
&& !roomItem.tags.includes("Terrace")
&& !roomItem.tags.includes("Outdoor")
&& !roomItem.tags.includes("Garage")
){
// if(
// (windows == 0 && dewpoint >= dewpointEnvironment +2)
// || (openWindows == 0 && dewpoint >= dewpointEnvironment +2 )
// ){
sendOncePerPeriod(
triggeringItem,
`Luftfeuchte seit >3 Tagen über 62 Prozent: in ${roomItem.label}, dringend lüften!`,
720
);
// }
}
}
if (triggeringItem.tags.includes("Spirulina") && false) {
if (triggeringItem.tags.includes("EC")) {
}
if (triggeringItem.tags.includes("PH")) {
const averagePH = triggeringItem.persistence.averageSince(now.minusHours(1)).numericState;
if(averagePH < 9 && averagePH !== 'NULL'){
sendOncePerPeriod(
triggeringItem,
`Spirulina: PH-Wert zu niedrig (${averagePH}) - Höher konzentrierte Nährlösung hinzugeben`,
1440
);
}
if(averagePH > 12 ){
sendOncePerPeriod(
triggeringItem,
`Spirulina: PH-Wert zu hoch (${averagePH}) - Lösung kann verdünnt oder geerntet werden`,
2880
);
}
}
if (triggeringItem.tags.includes("Light")) {
const averageLight = triggeringItem.persistence.averageSince(now.minusDays(1)).numericState;
if(averageLight < 20 && averageLight !== 'NULL'){
//sendOncePerPeriod.apply(triggeringItem,lastSent,String::format("Spirulina: Zu wenig Licht (%s lx)", averageLight ), 1440 )
}
}
if (triggeringItem.tags.includes("WaterTemperature")) {
const maxWaterTemperature = triggeringItem.persistence.maximumSince(now.minusDays(1)).quantityState;
if(maxWaterTemperature.lessThan('20 °C') && maxWaterTemperature !== 'NULL'){
sendOncePerPeriod(
triggeringItem,
`Spirulina: Wasser zu kalt! (${maxWaterTemperature})`,
360
);
}
if(maxWaterTemperature.greaterThan('38 °C') && maxWaterTemperature !== 'NULL'){
sendOncePerPeriod(
triggeringItem,
`Spirulina: Wasser zu warm! (${maxWaterTemperature})`,
180
);
}
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("CO2")){
if(triggeringItem.quantityState.greaterThan(LIMITS.CO2_GW_MQ135)){
if(
windows == 0
|| (openWindows == 0 && dewpointRoom >= dewpointEnvironment.add('3 °C') )
){
sendOncePerPeriod(
triggeringItem,
`CO2 hoch in ${roomItem.label}, dringend lüften!`,
180
);
}
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("Radiation")){
//Short-term Radiation
if( triggeringItem.tags.includes("mmSvph") ){
if(triggeringItem.quantityState.greaterThan(LIMITS.Rad_GW_childbearing_women_per_month)){
sendOncePerPeriod(
triggeringItem,
`Radioaktivität überschreitet kurzfristige Grenzwerte (${triggeringItem.label}: ${triggeringItem.quantityState})`,
180
);
}
}
//Mid-term Radiation
if( triggeringItem.tags.includes("mSvpm") ){
if(triggeringItem.quantityState.greaterThan(LIMITS.Rad_GW_childbearing_women_per_month)){
sendOncePerPeriod(
triggeringItem,
`Radioaktivität überschreitet mittelfristige Grenzwerte (${triggeringItem.label}: ${triggeringItem.quantityState})`,
180
);
}
}
//Long-term Radiation
if( triggeringItem.tags.includes("mSvpa") ){
if(triggeringItem.quantityState.greaterThan(LIMITS.Rad_GW_organdosis_per_year)){
sendOncePerPeriod(
triggeringItem,
`Radioaktivität überschreitet langfristige Grenzwerte (${triggeringItem.label}: ${triggeringItem.quantityState})`,
180
);
}
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("AQI")){ // without unit
if(triggeringItem.numericState > 100){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: ${triggeringItem.name} kritisch! (${triggeringItem.numericState} AQI)`,
180
);
}
}
else{
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("O3")){
if(triggeringItem.quantityState.greaterThan(LIMITS.O3_GW_mgm3)){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: O3 (Ozon) kritisch! (${triggeringItem.quantityState} > ${LIMITS.O3_GW_mgm3})`,
180
);
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("CO")){
if(triggeringItem.quantityState.greaterThan(LIMITS.CO_GW_mgm3)){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: CO kritisch! (${triggeringItem.quantityState} > ${LIMITS.CO_GW_mgm3})`,
180
);
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("PM10")){
if(triggeringItem.quantityState.greaterThan(LIMITS.PM10_GW_mgm3)){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: PM10 (Feinstaubpartikel 10µm) kritisch! (${triggeringItem.quantityState} > ${LIMITS.PM10_GW_mgm3})`,
180
);
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("PM25")){
if(triggeringItem.quantityState.greaterThan(LIMITS.PM25_GW_mgm3_averagePerYear)){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: PM2.5 (Feinstaubpartikel 2.5µm) kritisch! (${triggeringItem.quantityState} > ${LIMITS.PM25_GW_mgm3_averagePerYear})`,
180
);
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("Air") && triggeringItem.tags.includes("IAQ")){
if(triggeringItem.quantityState.greaterThan(LIMITS.IAQ_Ref_Worse)){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: Sehr schlechte Luftqualität!!! (${triggeringItem.quantityState} > ${LIMITS.IAQ_Ref_Worse})`,
180
);
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("NO2")){
if(triggeringItem.quantityState.greaterThan(LIMITS.NO2_GW_mgm3)){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: NO2 (Stickstoffdioxid) kritisch! (${triggeringItem.quantityState} > ${LIMITS.NO2_GW_mgm3})`,
180
);
}
}
if(triggeringItem.tags.includes("Measurement") && triggeringItem.tags.includes("SO2")){
if(triggeringItem.quantityState.greaterThan(LIMITS.SO2_GW_mgm3)){
sendOncePerPeriod(
triggeringItem,
`Athmosphärische Warnung: SO2 (Stickstoffdioxid) kritisch! (${triggeringItem.quantityState} > ${LIMITS.SO2_GW_mgm3})`,
180
);
}
}
}
});
}
});
Pingback: Automatischer Taupunkt-Lüfter - Smarthome DIY - Heimautomatisierung selbst gemacht
Pingback: Automatic Dew Point Ventilation – Smart Home DIY - Smarthome DIY - Heimautomatisierung selbst gemacht