Wenn dein Tagesablauf menschlich ist und sich häufig Änderungen ergeben wie unterschiedliche Anwesenheitszeiten, Urlaub, Ausflüge, Schichtarbeit, … dann willst du dein Heizsystem dem vielleicht auch ebenso dynamisch anpassen können. Vielleicht hast du auch ab und zu Änderungen in der Hardware und baust dein Smarthome gerade erst auf. Dann passieren in den Regeln schnell mal Fehler.
Im Folgenden will ich dir ein Beispiel vorstellen, wie eine Regel deine Heizungen ganz im Griff hat, abhängig von semantischen Gruppen.
notwendige Gruppen
Für das Konzept sind zwei Typen von Gruppen notwendig.
- semantische Gruppen: Du hast dein Zuhause bereits mit semantischen Gruppen definiert
- Triggergruppen: Deine Items müssen wissen, dass sie die Regel auslösen müssen. Daher fassen wir sie in Triggergruppen zusammen
- Item-Gruppen: Items, die zu einem Gegenstand als Gruppe zugeordnet werden
Gruppen sind technisch immer die gleichen Arten Gruppen. Die unterschiedliche Bezeichnung ist lediglich aufgrund der Funktion, die sie erfüllen sollen, gewählt. Das hilft beim Verstehen und Nachvollziehen.
/etc/openhab/items/semantic-model.items
//functional Groups
Group fgPersist // for Items to preserve their history
Group:Number:COUNT(OPEN) fgIntrusion "Intrusion [%s]" // All window- and doorsensors
Group fgRadiatorBoost // Radiator Boost
Group:DateTime:EARLIEST fgFaultDetection // Fault or disturbance detection for wireless connections (as "last seen")
//trigger groups
Group tgHeatingEvents // events, that will change temperature to a custom setpoint like a calendar evenent
Group tgWindow // Window- or door contact sensors
Group tgMotionDetection // PIR Sensors
Group tgHausAus // a special button to tell my home, that I will go to sleep now
Group tgWatchdogEvents // alarm system events
Group tgLightshow // dynamic scene picker
Group tgLSchange // dynamic scene influencer
Group tgLSinterrupt // dynamic scene interruptor
Group tgWarnOnLimit // Gas- and Rad-Sensors for warning
Group tgCalc // Item triggers a calculations for another Item (like Temperature or Humidity for Dewpoint)
// Semantic Groups
Group sgWacholderweg "Zuhause" ["Location"]
Group sgIndoor "Indoor" ["Indoor"]
Group sgHouse "Haus" <group> (sgIndoor) ["House"] { alexa="Other" }
Group sgGroundFloor "EG" <groundfloor> (sgHouse) ["GroundFloor"]
Group sgBedroom "Schlafzimmer" <bedroom> (sgGroundFloor) ["Bedroom"] { alexa="Other" }
Group sgCorridorEG "Flur" <corridor> (sgGroundFloor) ["Corridor"] { alexa="Other" }
Group sgKitchen "Küche" <kitchen> (sgGroundFloor) ["Kitchen"] { alexa="Other" }
Group sgBathroom "Bad" <bath> (sgGroundFloor) ["Bathroom"] { alexa="Other" }
Group sgLivingRoom "Wohnzimmer" (sgGroundFloor) ["LivingRoom"] { alexa="Other" }
Group sgCellar "Keller" <cellar> (sgHouse) ["Cellar"] { alexa="Other" }
Group sgCorridorCellar "Flur Keller" <corridor> (sgCellar) ["Corridor"]
Group sgGarage "Garage" <garagedoor> (sgCellar) ["Garage"]
Group sgOutdoor "Outdoor" ["Outdoor"]
Group sgEnvironment "Draussen" <flow> (sgOutdoor) ["Garden"] { alexa="Other" }
Group sgGarden "Garten" <garden> (sgOutdoor) ["Garden"]
Group sgTerrace "Terrasse" <terrace> (sgOutdoor) ["Terrace"] { alexa="Other" }
mögliche beeinflussende Items
tgHeatingEvents
Die Kalender-Items basieren auf dem OpenHAB ical-Modul. So kann aus einem Google-Kalender die Beschreibung als Zahl ausgelesen werden und als Setpoint an ein Thermostat übermittelt werden. Die items werden zu einer itemGroup pro Kalender zusammengefasst. diese itemGroup wird zu einem Raum mittels semantischer Gruppe zugewiesen. Die semantischen Tags („Webservice“) geben dem System Aufschluss darüber, worum es sich bei der Gruppe handelt.
Wenn sich der Start-Zeitpunkt oder der Titel ändert, soll damit die Heizregel ausgelöst werden. Das wird mit der Triggergruppe „tgHeatingEvents“ deklariert.
/etc/openhab/items/calendars.items
Group:Switch:COUNT(ON) igCalHeatingWohnzimmer "Heizprofil Wohnzimmer" (sgLivingRoom)["WebService"]
Switch KalenderHeizprofilWohnzimmer_CurrentEventPresence "Current Event Presence" (igCalHeatingWohnzimmer) { channel="icalendar:calendar:Wohnzimmer:current_presence" }
Number:Temperature KalenderHeizprofilWohnzimmer_CurrentEventTitle "Current Event Title [%.1f °C]" (igCalHeatingWohnzimmer,tgHeatingEvents)["Setpoint","Temperature"] { channel="icalendar:calendar:Wohnzimmer:current_title" }
DateTime KalenderHeizprofilWohnzimmer_CurrentEventStart "Current Event Start" (igCalHeatingWohnzimmer,tgHeatingEvents)["Status","Timestamp"] { channel="icalendar:calendar:Wohnzimmer:current_start" }
DateTime KalenderHeizprofilWohnzimmer_CurrentEventEnd "Current Event End" (igCalHeatingWohnzimmer) { channel="icalendar:calendar:Wohnzimmer:current_end" }
Group:Switch:COUNT(ON) igCalHeatingBedroom "Heizprofil Schlafzimmer" (sgBedroom)["WebService"]
Switch KalenderHeizprofilSchlafzimmer_CurrentEventPresence "Current Event Presence" (igCalHeatingBedroom) { channel="icalendar:calendar:Schlafzimmer:current_presence" }
Number:Temperature KalenderHeizprofilSchlafzimmer_CurrentEventTitle "Current Event Title [%.1f °C]" (igCalHeatingBedroom,tgHeatingEvents)["Setpoint","Temperature"] { channel="icalendar:calendar:Schlafzimmer:current_title" }
DateTime KalenderHeizprofilSchlafzimmer_CurrentEventStart "Current Event Start" (igCalHeatingBedroom,tgHeatingEvents)["Status","Timestamp"] { channel="icalendar:calendar:Schlafzimmer:current_start" }
DateTime KalenderHeizprofilSchlafzimmer_CurrentEventEnd "Current Event End" (igCalHeatingBedroom) { channel="icalendar:calendar:Schlafzimmer:current_end" }
// ... eventually more calendars like these ...
tgWindow
Dabei handelt es sich um simple Items, die den Status von Fenstern und Türen aufzeigen. Ich hab mich für Sensoren auf Zigbee-Basis und MQTT entschieden. Die Batterien halten ewig.
Der Kontakt- und Batterie-Status reichen für die Zwecke vollkommen aus. Auch diese beiden Zustände werden zu einer itemGroup zusammengefasst. Diese itemGroup wird als „Window“ getaggt und einem Raum zugeordnet. die fg-Gruppen sind an anderer Stelle interessant, jedoch nicht für die Heizregel. Auch diese Items müssen sauber getaggt werden.
Wenn sich ein Fenster öffnet, soll die Heizung in dem Raum abgeschaltet werden.
/etc/openhab/items/zigbee-sensors.items
Group:Number:COUNT(OPEN) igWindowSensor_LivingRoom "Fenster Wohnzimmer [%s]" <Window> (sgLivingRoom)["Window"]
Contact WindowSensor_LivingRoom_contact "Kontakt [%s]" (fgPersist,igWindowSensor_LivingRoom,fgIntrusion,tgWindow)["OpenState","Opening"] { channel="mqtt:topic:myMosquitto:WindowSensor_LivingRoom:contact" }
Number:Dimensionless WindowSensor_LivingRoom_battery "Batterie [%.1f %%]" <Battery> (igWindowSensor_LivingRoom)["Battery","Measurement","Energy"] { channel="mqtt:topic:myMosquitto:WindowSensor_LivingRoom:battery" }
// ... eventually more sensors like these ...
tgHausAus (optional)
Das ist mein Knopf am Bett. Oder die Software-Variante als Switch-Item. Der soll das Haus abschalten, wenn ich ins Bett gehe und darauf drücke.
Tritt ein neues Ereignis ein, wie etwa eine Kalendergesteuerte Temperaturänderung, wird der Modus abgelöst.
/etc/openhab/items/hue.items
Group igSmartButton1 "Haus-Aus-Knopf" (sgBedroom)["RemoteControl"]
String SmartButton1_DimmerSwitch (igSmartButton1,tgHausAus)["Status"] { channel="mqtt:topic:myMosquitto:SmartButton1:action" } //on, off, skip_backward, skip_forward, press, hold, release
Number:Dimensionless SmartButton1_Battery "Batterie [%.1f %%]" <Battery> (igSmartButton1)["Battery","Measurement","Energy"] { channel="mqtt:topic:myMosquitto:SmartButton1:battery" }
Switch SmartButton1_manual "Haus" (igSmartButton1,tgHausAus)["Switch"] { expire="2s,state=ON", alexa="Switch" }
tgWatchdogEvents (optional)
Das sind Events, die durch das Alarmsystem hervorgerufen werden. Hier bietet es sich an, das manuelle Einschalten der Alarmanlage das Heizsystem des Hauses in einen Urlaubsmodus zu schicken.
Heizungs-Items
Group igThermostatWohnzimmer "Wohnzimmer Heizung" <radiator> (sgLivingRoom)["RadiatorControl"] { synonyms="Wohnzimmer Thermostat", alexa="Thermostat" }
Number:Temperature ThermostatWohnzimmer1_Temperature "Aktuelle Temp. (l) [%.1f °C]" <Temperature> (fgPersist,igThermostatWohnzimmer)["Measurement","Temperature"] { channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer1:local_temperature" }
Number:Temperature ThermostatWohnzimmer1_SetTemperature "Soll-Temp. (l) [%.1f °C]" <Temperature> (fgPersist,igThermostatWohnzimmer,sgLivingRoom)["Setpoint","Temperature"]{ alexa="TargetTemperature", channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer1:current_heating_setpoint", channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer2:current_heating_setpoint" }
Number:Temperature ThermostatWohnzimmer1_SetTemperatureRO "Soll-Temperatur (RO) [%.1f °C]" <Temperature> (igThermostatWohnzimmer)["Measurement","SetpointRO"] { channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer1:current_heating_setpoint" }
String ThermostatWohnzimmer1_RadiatorMode "Radiator mode [%s]" (igThermostatWohnzimmer) { expire="1s,manual", channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer1:preset" }
Number:Dimensionless ThermostatWohnzimmer1_Battery "Battery level [%.1f %%]" <Battery> (igThermostatWohnzimmer)["Battery","Measurement","Energy"] { alexa="BatteryLevel", channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer1:battery" }
Number:Dimensionless ThermostatWohnzimmer1_ValvePosition "Ventilposition [%.1f %%]" (fgPersist,igThermostatWohnzimmer)["OpenLevel","Opening"] { channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer1:valve_position" }
Switch ThermostatWohnzimmer1_Boost "Boost" (igThermostatWohnzimmer,fgRadiatorBoost)["Switch","Opening"] { channel="mqtt:topic:myMosquitto:ThermostatWohnzimmer1:boost" }
OpenHAB Rule DSL
Sind die Gruppen und Items sauber definiert, kann die Regel wie folgt (oder auf deine Bedürfnisse angepasst) verwendet werden. Zunächst werden die Bedingungen eines Ereignisses ermittelt. Erst im letzten Bereich werden diese dann zu einer Änderung des Sollwertes zusammengesetzt.
import org.openhab.core.model.script.ScriptServiceUtil
import java.util.List
import java.util.Map
var Map<String, QuantityType<Temperature>> setpoints = newHashMap
//it is possible, that the signal gets lost. So try it again, till the not commanded item reacts to a change
//yes, i could set the "noautoupdate"-option. But alexa then will answer wrong temperature, when i ask... so: duplicate item
val setRadiator = [ NumberItem radiator, QuantityType<Temperature> setpoint, Map<String, Timer> setpoints |
//when setpoint is changed, send it multiple times because sometimes the zigbee-signal gets lost
val String igName = radiator.getGroupNames.findFirst[ String groupName | groupName.startsWith("ig") ]
val GroupItem igItem = ScriptServiceUtil.getItemRegistry.getItem(igName) as GroupItem
val NumberItem setpointROitem = igItem.members.findFirst[ item | item.hasTag("Measurement") && item.hasTag("SetpointRO") ]
setpoints.put(radiator.name,setpoint)
//2s, 4s, 8s, 16s ... make the timespan bigger between the tryouts
createTimer(now, [ |
var long sleep = 2000
for(var i=0; i<10 && (setpointROitem.state == NULL || (setpointROitem.state as QuantityType<Temperature>) != setpoints.get(radiator.name)); i++) {
radiator.sendCommand(setpoint)
sleep *= 2
Thread::sleep(sleep)
}
])
]
rule "heating"
when
Member of tgHeatingEvents changed
or Member of tgWindow changed
or Member of tgHausAus changed
or Member of tgWatchdogEvents changed
then {
//find the affected rooms
var List<String> roomList = newArrayList()
//which rooms shall be affected due to the trigger?
if(triggeringItem.name == "SmartButton1_DimmerSwitch" || triggeringItem.name == "SmartButton1_manual"){
//which rooms shall be shut down, when I go to sleep and press my home-off-button
roomList = newArrayList("sgOfficeJulia","sgKitchen","sgLivingRoom","sgOfficeAlex")
}
else if(triggeringItem.name == "WatchdogManual"){
//which rooms shall be shut down, when I activate the alarm system manually?
roomList = newArrayList("sgBedroom","sgOfficeJulia","sgKitchen","sgLivingRoom","sgOfficeAlex")
}
else{
//which room is affected due to the triggering item room?
//detect itemGroup (ig) of the item
val igName = triggeringItem.getGroupNames.findFirst[ String groupName | groupName.startsWith("ig") ]
val GroupItem igItem = ScriptServiceUtil.getItemRegistry.getItem(igName) as GroupItem
//detect semanticGroup of the itemGroup
val sgName = igItem.getGroupNames.findFirst[ String groupName | groupName.startsWith("sg") ]
roomList = newArrayList(sgName)
}
//do something with the rooms:
roomList.forEach[ String sgName |
val GroupItem room = ScriptServiceUtil.getItemRegistry.getItem(sgName) as GroupItem
//what states has the room?
//window open?
var window = CLOSED
room.members.forEach[ NumberItem thing |
if( thing.tags.contains("Window") || thing.tags.contains("FrontDoor") || thing.tags.contains("BackDoor") ){
if(thing.state != NULL){
if( (thing.state as Number).intValue > 0){
window = OPEN
}
}
}
]
//holiday or manual alarm is on?
var watchdog = if(WatchdogManual.state == ON) true else false
//home-off-button?
var hausAusKnopf = if(triggeringItem.name == "SmartButton1_DimmerSwitch" || triggeringItem.name == "SmartButton1_manual") true else false
//is there a heating event in the calendar for that room?
var QuantityType<Temperature> calendarSetpoint = 0|°C
room.members.forEach[ GroupItem gItem |
if(gItem.tags.contains("WebService")){
gItem.members.forEach[ NumberItem item |
if( item.tags.contains("Setpoint") && item.tags.contains("Temperature") ){
calendarSetpoint = if(item.state != UNDEF && item.state >= 6|°C) (item.state as QuantityType<Temperature>) else 17|°C
}
]
}
]
//set the temperature
room.members.forEach[ GroupItem gItem |
if(gItem.tags.contains("RadiatorControl")){
gItem.members.forEach[ NumberItem radiator |
if( radiator.tags.contains("Setpoint") && radiator.tags.contains("Temperature") ){
if(window == OPEN){
setRadiator.apply(radiator,6|°C,setpoints)
//logInfo("Test", "Set window OPEN: {}", triggeringItem)
}
else if(watchdog){
setRadiator.apply(radiator,6|°C,setpoints)
//logInfo("Test", "Set watchdog: {}", triggeringItem)
}
else if(hausAusKnopf && 17|°C < (radiator.state as QuantityType<Temperature>)){
setRadiator.apply(radiator,17|°C,setpoints)
//logInfo("Test", "Set Haus-Aus: {}", triggeringItem)
}
else if(calendarSetpoint > 0|°C){
setRadiator.apply(radiator,calendarSetpoint,setpoints)
//logInfo("Test", "Set calendar: {}", triggeringItem)
}
else if(17|°C < (radiator.state as QuantityType<Temperature>)){
setRadiator.apply(radiator,17|°C,setpoints)
//logInfo("Test", "Set default: {}", triggeringItem)
}
}
]
}
]
]
}
end
Pingback: OpenHAB: Wenn man vergisst das Fenster zu schließen - Smarthome DIY - Heimautomatisierung selbst gemacht