Hi all,
I’ve been slowly building my openHAB system for the last few months. I’ve built up my knowledge and understanding of the system from zero to almost semi-competent. I’ve tried a few home automation systems so far (mostly Vera and HomeSeer) and found shortcomings with both (especially Vera. Yuck.). OpenHAB had the steepest learning curve of the three by far, but now that I’ve (mostly) overcome it, I’m very very impressed with how flexible it is and how much can be achieved. I’m still learning though.
I’ve been working on setting up a fairly complicated (for me) automated motion-sensing lighting system throughout the house. I have used the bathroom as the prototype for this and I think I have reached a workable version 1.0. I thought I would post it here because a) it might be useful for somebody else, and b) someone might look at it and see bugs/gotchas that I’ve missed, or they may be able to suggest a more efficient way of achieving the same thing. For the last few days it seems to have worked perfectly, but I’m always looking for more edge cases. Once I’m confident that this is working well I will modify it for other rooms in the house.
It’s probably best if I first describe what I am trying to achieve.
I have a Fibaro Dimmer 2 z-wave dimmer with a single push-button switch on the wall. I also have an Aeotec Multisensor 6 sensor in the bathroom. I’m running openHAB 2.2 on a Raspberry Pi 3 with openHABian.
I have split the day into 3 ‘modes’; Day, Evening, and Night. The house cycles through these on a schedule set by sunrise/sunset and time of day. I will be making this more sophisticated in future to allow the user to switch more easily between modes (e.g. by voice command). The automated lighting will function differently depending on the mode. I also plan on adding an Away mode during which the motion-sensing lighting will be disabled (which is important as our cat tends to set off the motion sensors).
During evening mode, the light will turn on to full brightness when motion is detected by the sensor. The sensor sends a no motion detected signal 60 seconds after last detection of motion. When that signal is received a 14 minute timer is started. When this timer expires (15 minutes after last detection of motion) the bathroom light is dimmed to 10% and another 1 minute timer is started. When this timer expires the light is turned off. I added this dimming stage so that if somebody is in the bathroom and not moving enough to set off the motion sensor (e.g. having a prolonged period of contemplation while sitting) they get a warning to wave their arms around rather than being plunged into darkness.
During day mode the same rules apply as during evening mode except that it will only activate if the light level (detected by the motion sensor as well) is below a certain threshold. This means it will only activate on very dull days or at times close to sunset when evening mode has not yet been triggered.
During night mode the rule is slightly different. When motion is detected the light will turn on to minimum dimming level. After no motion is detected a 4 minute timer is started and the light is turned off when it expires.
As well as this, I have added the ability to override the motion control with the light switch. Any time openHAB controls the light it is done via a virtual dimmer. When the virtual dimmer is changed a rule immediately updates the real dimmer to the same value. If the light switch is used a rule detects that the virtual and real dimmers are set to different values and a switch for motion control is set to off. All of the earlier motion control rules check for this switch and will now be disabled. To reactivate motion control another timer is started. If the light has been turned off manually then motion control resumes in 10 minutes. If the light has been turned on or dimmed manually then motion control resumes in 60 minutes. I chose these times as I think it would be more annoying to be left in darkness unintentionally than in light. When motion control resumes, if there is no motion detected then the light will be dimmed and turned off after a further minute.
Finally, when the house mode changes, the light is adjusted to an appropriate level for the new mode depending on the current state of the light, the state of the motion sensor, and whether motion control is currently activated. A z-wave command is also sent to the dimmer to change the response to the light switch. During day or evening mode the light switch will turn the light on to full brightness. During night mode the light switch will turn on the minimum brightness.
Whew. Anyway, here’s the code. I think it’s mostly self-explanatory but I have added some comments to help explain what I’m doing. I’m very keen to learn and improve so please feel free to comment and criticise;
//housemode.items
String HouseMode "House mode"
String TimeOfDay "Current time of day [%s]"
DateTime SunriseTime "Sunrise [%1$tH:%1$tM]" { channel="astro:sun:home_with_limits:rise#start" }
DateTime SunsetTime "Sunset [%1$tH:%1$tM]" { channel="astro:sun:home:set#start" }
//bathroom.items
Dimmer BathroomLight "Bathroom Light [%d %%]" <light> (Lights) {channel="zwave:device:46feb3ec:node17:switch_dimmer"}
Dimmer BathroomLightVirtual "Bathroom Light Virtual [%d %%]" <light> (Lights)
Switch BathroomMotionSensor "Bathroom Motion Sensor[%s]" <motiondetector> {channel="zwave:device:46feb3ec:node11:sensor_binary"}
Number BathroomLightSensor "Bathroom Light Sensor [%d lux]" <sensor> {channel="zwave:device:46feb3ec:node11:sensor_luminance"}
Switch BathroomMotionControl "Bathroom Motion"
//housemode.rules
rule "Set Time of Day State"
when
System started or
Channel "astro:sun:home_with_limits:rise#event" triggered START or
Channel "astro:sun:home:set#event" triggered START or
Time cron "0 30 23 * * ?"
then
logInfo("house_mode.rules", "Checking time")
val long day_start = (SunriseTime.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli
val long evening_start = (SunsetTime.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli
val long night_start = now.withTimeAtStartOfDay.plusHours(23.5).millis
var curr = "evening"
switch now {
case now.isAfter(day_start) && now.isBefore(evening_start): curr = "day"
case now.isAfter(evening_start) && now.isBefore(night_start): curr = "evening"
}
logInfo("house_mode.rules", "Setting TimeOfDay to " + curr)
TimeOfDay.postUpdate(curr)
end
rule "Set house mode"
when
Item TimeOfDay changed
then
//TO DO - add logic to incorporate away state of house
logInfo("house_mode.rules", "Setting HouseMode to " + TimeOfDay.state)
HouseMode.postUpdate(TimeOfDay.state)
end
//bathroom_lighting.rules
var Timer bathroomMotionTimer = null
var Timer bathroomMotionControlTimer = null
var int motionControlResumeTime = null
val int bathroomDayLevel = 100 //default = 100
val int bathroomDayLightThreshold = 15 //default = 15
val int bathroomDayTimeoutMinutes = 14 //default = 14
val int bathroomDimLevel = 10 //default = 10
val int bathroomEveningLevel = 100 //default = 100
val int bathroomEveningTimeoutMinutes = 14 //default = 14
val int bathroomNightLevel = 1 //default = 1
val int bathroomNightTimeoutMinutes = 4 //default = 4
rule "Motion sensed in bathroom"
when
Item BathroomMotionSensor changed to ON
then
if (BathroomMotionControl.state != OFF) { //By using != OFF instead of == ON I don't have to initialise the BathroomMotionControl item on system startup
bathroomMotionTimer?.cancel
bathroomMotionTimer = null
switch (HouseMode.state) {
case 'day' : if (BathroomLightSensor.state < bathroomDayLightThreshold) {BathroomLightVirtual.sendCommand(bathroomDayLevel)}
case 'evening' : BathroomLightVirtual.sendCommand(bathroomDayLevel)
case 'night' : BathroomLightVirtual.sendCommand(bathroomNightLevel)
}
}
end
rule "No motion sensed in bathroom"
when
Item BathroomMotionSensor changed to OFF
then
bathroomMotionTimer?.cancel
switch (HouseMode.state) {
case 'day' : {
bathroomMotionTimer = createTimer(now.plusMinutes(bathroomDayTimeoutMinutes)) [| //Start a timer to dim the light after no motion detected
if (BathroomMotionControl.state != OFF) { BathroomLightVirtual.sendCommand(bathroomDimLevel) }
bathroomMotionTimer = createTimer(now.plusMinutes(1)) [| //Then start another timer to turn the light off a minute later
if (BathroomMotionControl.state != OFF) { BathroomLightVirtual.sendCommand(OFF) }
bathroomMotionTimer = null
]
]
}
case 'evening' : {
bathroomMotionTimer = createTimer(now.plusMinutes(bathroomEveningTimeoutMinutes)) [| //As per day mode
if (BathroomMotionControl.state != OFF) { BathroomLightVirtual.sendCommand(bathroomDimLevel) }
bathroomMotionTimer = createTimer(now.plusMinutes(1)) [|
if (BathroomMotionControl.state != OFF) { BathroomLightVirtual.sendCommand(OFF) }
bathroomMotionTimer = null
]
]
}
case 'night' : {
bathroomMotionTimer = createTimer(now.plusMinutes(bathroomNightTimeoutMinutes)) [| //Start a timer to just turn the light off
if (BathroomMotionControl.state != OFF) { BathroomLightVirtual.sendCommand(OFF) }
bathroomMotionTimer = null
]
}
}
end
rule "Update real light from virtual"
when
Item BathroomLightVirtual changed
then
BathroomLight.sendCommand(BathroomLightVirtual.state as PercentType)
end
rule "Disable motion control if light changed by switch"
when
Item BathroomLight changed
then
if (BathroomLight.state != BathroomLightVirtual.state) { //will be different if lightswitch used
BathroomLightVirtual.sendCommand(BathroomLight.state as PercentType) //update virtual dimmer to actual
BathroomMotionControl.sendCommand(OFF) //Turn off bathroom motion control
if (BathroomLight.state == 0) { motionControlResumeTime = 10 } //Resume motion control after 10 minutes if light turned off
else { motionControlResumeTime = 60 } //Resume motion control after 60 minutes if light turned to anything other than off
if (bathroomMotionControlTimer === null) { //Start timer to resume motion control
bathroomMotionControlTimer = createTimer(now.plusMinutes(motionControlResumeTime)) [|
BathroomMotionControl.sendCommand(ON)
bathroomMotionControlTimer = null
]
}
else {
bathroomMotionControlTimer.reschedule(now.plusMinutes(motionControlResumeTime))
}
}
end
rule "Motion control resumed"
when
Item BathroomMotionControl changed to ON
then
if (BathroomMotionSensor.state == OFF && BathroomLight.state > 0) { //If no motion is detected and the light is on when motion control is resumed
if (BathroomLight.state > bathroomDimLevel) { BathroomLightVirtual.sendCommand(bathroomDimLevel) } //Dim the light if it isn't already
bathroomMotionTimer = createTimer(now.plusMinutes(1)) [| //And start a timer to turn it off in 1 minute
if (BathroomMotionControl.state != OFF) { BathroomLightVirtual.sendCommand(OFF) }
bathroomMotionTimer = null
]
}
end
rule "HouseMode changes"
when
Item HouseMode changed
then
switch (HouseMode.state) {
case 'day' : {
sendHttpPutRequest("http://localhost:8080/rest/things/zwave:device:46feb3ec:node17/config", "application/json", "{'config_19_1':«bathroomDayLevel»}")
if (BathroomMotionSensor.state == ON && BathroomMotionControl.state != OFF && BathroomLight.state > 0 && BathroomLightSensor.state < bathroomDayLightThreshold) { BathroomLightVirtual.sendCommand(bathroomDayLevel) }
}
case 'evening' : {
sendHttpPutRequest("http://localhost:8080/rest/things/zwave:device:46feb3ec:node17/config", "application/json", "{'config_19_1':«bathroomDayLevel»}")
}
case 'night' : {
sendHttpPutRequest("http://localhost:8080/rest/things/zwave:device:46feb3ec:node17/config", "application/json", "{'config_19_1':«bathroomNightLevel»}")
if (BathroomMotionSensor.state == ON && BathroomMotionControl.state != OFF && BathroomLight.state > 0) { BathroomLightVirtual.sendCommand(bathroomNightLevel) }
else {
BathroomLightVirtual.sendCommand(OFF)
bathroomMotionTimer?.cancel
bathroomMotionTimer = null
}
}
}
end