How I have automated my lights

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
11 Likes

I just scanned the code and I’m a bit tired after a day of hiking, but my first thought is I see a lot of delicates code.

  • instead of having a switch statement with cases for evening and day and night where the only difference is the amount of time, just set the timer value in a global when the time of day change happens and simplify your rule to just use the value. You can even set the dinner value to 0 to handle the night case with the same createTimer code. See Design Pattern: How to Structure a Rule

  • as you expect this, consider Design Pattern: Associated Items and storing your time and dinner values using Design Pattern: Encoding and Accessing Values in Rules. Then you only need over rule you handle all of your motion sensor lights.

If I remember I’ll post something more when I get to a computer.

3 Likes

Thank you for sharing this with us !

For the bathroom (or any room with only one door that will be closed most of the time) a very useful sensor to have is a door sensor. If you know someone is inside and the door stays closed, then that someone must still be inside, even after the timer has expired. :slight_smile:

You seem to have thought about most edge cases… since how long is that system running and has it worked correctly for you ?

1 Like

@rikoshak Not that I don’t like hearing from you, but my previous post was mostly aimed @boconnor. (I am planning to implement more lighting automation and want to know what pitfalls/edge cases to think about.) :slight_smile:

Never mind then

Another thing that came to mind is typically the light is configured to go off a certain amount of time after the last motion was detected, meaning you only really track the ON states, not the OFF states of the motion sensor. As the person in the room moves around they keep rescheduling the timer until the room goes still for the given amount of time.

OK, now that I’m at a computer…

I’m glad to see either you came up with it independently or you are using the Design Pattern: Time Of Day.

So if I were to apply some of the above suggestions to make these rules more generic they would look something like (note, I’m not going to show all the Items involved).

import java.util.Map

val Map<String, Timer> timers = createHashMap // because you will want one timer per room
val Map<String, Timer> ctrlTimers = createHashMap

rule "Motion detected"
when
    Member of LightMotionSenors received update ON // members are named Room_Motion_Sensor
then
    if(triggeringItem.state == NULL) return; // we don't care about NULL

    val room = triggeringItem.name.split("_").get(0)
    val control = LightMotionControls.findFirst[ ctrl | ctrl.name == room + "_Motion_Control" ]
    if(control.state == OFF) return; // ignore the update if control is OFF

    // Get the values and associated Items
    val timer = timers.get(triggeringItem.name)
    val level = LightMotionLevels.members.findFirst[ ll | ll.name == room + "_" + HouseMode.state + "_Level" ].state as Number
    val timoutMins = (LightMotionTimouts.members.findFirst[ to | to.name == room + "_" + HouseMode.state + "_Timeout" ].state as Number).intValue
    val virtual = MotionLightsVirtual.members.findFirst[ v | v.name == room + "_Light_Virtual" ]
    val dimLevel = LightMotionDims.members.findFirst[ dim | dim.name == room + "_Dim" ].state as Number

    // Timer is already running
    if(timer !== null) {
        virtual.sendCommand(level) // undo the dim if necessary
        timer.reschedule(timoutMins) // goes off timeoutMins minutes after the last time the motion sensor detects motion
    }
    // No Timer was running
    else {
        virtual.sendCommand(level) // turn on the light

        // Create a timer to dimm then turn off the light
        timer.put(triggeringItem.name, createTimer(now.plusMinutes(timeoutMins), [ |

            // if already dimmed then turn off
            if(virtual.state == dimLevel) {
                virtual.sendCommand(OFF)
                timers.put(triggeringItem.name, null) // clear the timer
            }

            // dim the light and reset the timer for another minute
            else {
                virtual.sendCommand(level)
                timers.get(triggeringItem.name).reschedule(1)
            }
        ])
    }
end

rule "Disable motion control if light changed by switch"
when
    Member of Lights changed
then
    val virtual = MotionLightsVirtual.members.findFirst[ v | v.name == room + "_Light_Virtual" ]
    val control = LightMotionControls.findFirst[ ctrl | ctrl.name == room + "_Motion_Control" ]

    if(triggeringItem.state != virtual.state) {
        virtual.postUpdate(triggeringItem.state) // we don't want this to be treated as a new command
        control.sendCommand(OFF) // disable the motion sensor control of the light

        val int disabledTime = if(triggeringItem.state == 0) 10 else 60
        val timer = ctrlTimers.get(triggeringItem.name)
        if(timer !== null){
            timer.reschedule(now.plusMinutes(disabledTime))
        }
        else {
            ctrlTimers.put(triggeringItem.name, createTimer(now.plusMinutes(disabledTime), [ |
                control.sendCommand(ON)
                ctrlTimers.put(triggeringItem.name, null)
            ])
        }
    }
end

rule "Motion control resumed"
when
    Member of LightMotionControls received command ON
then
    val room = triggeringItem.name.split("_").get(0)
    val motion = LightMotionSenors.members.findFirst[ m | m.name == room + "_Motion_Sensor" ]
    val light = Lights.members.findFirst[ l | l.name == room + "_Motion_Light" ]
    val dimLevel = LightMotionDims.members.findFirst[ dim | dim.name == room + "_Dim" ].state as Number
    val virtual = MotionLightsVirtual.members.findFirst[ v | v.name == room + "_Light_Virtual" ]

    if(timers.get(motion.name) !== null || light.state == 0) return; // a timer is already set to handle the light or the light is already off

    if(light.state == dimLevel) return; // Light is already at the dim level

    timers.put(motion.name, createTimer(now.plusMinutes(1), [ |
        if(triggeringItem.state == OFF) virtual.sendCommand(OFF)
        timers.put(motion.name, null)
    ]) 
end

With the above rules you just need to add your Items to the proper Group, named following the format above (see Associated Items DP) and you never have to touch your Rules to add new lights/motion sensors. Also note that there is no duplicated code. The Timer is created exactly in one place.

As I mentioned before, to get the immediate OFF at night, set the Bathroom_night_Level to 0.

One advantage of putting all of this data into Items instead of global vals is you can adjust them on your sitemap instead of having them hard coded in your rules. You will want to use persistence on these Items though.

I tried to stick to the same behavior that you already have but make the rules work for all your rooms, not just the one room. You never want to have to copy and paste code.

And for the record, I’ve posted this elsewhere before but here it is again, my lighting rules:

val logName = "lights"

// Theory of operation: Turn off the light that are members of gLights_OFF_<TOD> and
// then turn on the lights that are members of gLights_ON_<TOD>. Reset the overrides.
rule "Set lights based on Time of Day"
when
  Item vTimeOfDay changed
then
  // reset overrides
  gLights_WEATHER_OVERRIDE.postUpdate(OFF)

  val offGroupName = "gLights_OFF_"+vTimeOfDay.state.toString
  val onGroupName = "gLights_ON_"+vTimeOfDay.state.toString

  logInfo(logName, "Turning off lights for " + offGroupName)
  val GroupItem offItems = gLights_OFF.members.filter[ g | g.name == offGroupName ].head as GroupItem
  offItems.members.filter[ l | l.state != OFF ].forEach[ l | l.sendCommand(OFF) ]

  logInfo(logName, "Turning on lights for " + onGroupName)
  val GroupItem onItems = gLights_ON.members.filter[ g| g.name == onGroupName ].head as GroupItem
  onItems.members.filter[ l | l.state != ON].forEach[ l | l.sendCommand(ON) ]

end

// Thoery of operation: If it is day time, turn on/off the weather lights when cloudy conditions
// change. Trigger the rule when it first becomes day so we can apply cloudy to lights then as well.
rule "Turn on lights when it is cloudy"
when
  Item vIsCloudy changed or
  Item vTimeOfDay changed
then
  // We only care about daytime and vIsCloudy isn't NULL
  if(vTimeOfDay.state != "DAY" || vIsCloudy.state == NULL) return;

  // give the side effects of time of day time to complete
  if(triggeringItem.name == "vTimeOfDay") Thread::sleep(500)

  logInfo(logName, "It is " + vTimeOfDay.state.toString + " and cloudy changed: " + vIsCloudy.state.toString +", adjusting lighting")

  // Apply the cloudy state to all the lights in the weather group
  gLights_ON_WEATHER.members.forEach[ l |

    val overrideName = l.name+"_Override"
    val override = gLights_WEATHER_OVERRIDE.members.findFirst[ o | o.name == overrideName ] as SwitchItem

    if(override.state != ON && l.state != vIsCloudy.state) l.sendCommand(vIsCloudy.state as OnOffType)

    if(override.state == ON) logInfo(logName, l.name + " is overridden")
  ]
end


// Theory of operation: any change in the relevant lights that occur more than five seconds after
// the change to DAY or after a change caused by cloudy is an override
rule "Watch for overrides"
when
  Member of gLights_ON_DAY changed
then
  // wait a minute before reacting after vTimeOfDay changes, ignore all other times of day
  if(vTimeOfDay.state != "DAY" || vTimeOfDay.lastUpdate("mapdb").isAfter(now.minusMinutes(1).millis)) return;

  // Assume any change to a light that occurs more than n seconds after time of day or cloudy is a manual override
  val n = 5
  val causedByClouds = vIsCloudy.lastUpdate("mapdb").isAfter(now.minusSeconds(n).millis)
  val causedByTime = vTimeOfDay.lastUpdate("mapdb").isAfter(now.minusSeconds(n).millis)

  if(!causedByClouds && !causedByTime) {
    logInfo(logName, "Manual light trigger detected, overriding cloudy control for " + triggeringItem.name)
    postUpdate(triggeringItem.name+"_Override", "ON")
  }
end
5 Likes

Wow. Thanks for having a look.

I won’t have time to look at this in detail for a few days but a brief look is very interesting. There’s a lot in there I don’t understand but when I get a chance I will sit down and figure it all out. I will probably need very different lighting behaviour in different rooms of the house so it will be interesting to see if I can genericise these rules enough to not have to copy/paste anything.

One point though, is that the Aeotec Multisensor 6s that I have only send out one ON signal when they initially detect motion. They won’t send out anything more until they haven’t detected any motion for a set period of time (in my case 60 seconds) at which point they send out a single OFF signal. They will send another ON signal the next time they detect motion after this. I believe this is a design decision to improve battery life. This means I do have to track the OFF states with these motion sensors.

Oh, and I did use your Time of Day design pattern in my rules. Thanks! In fact I’ve used an awful lot of what you have posted here and I’m very appreciative of it all. I’m not sure openHAB would be viable without all of the information you’ve posted.

I’ve only had this code running for a few days. So far no issues. I’m waiting for issues though - experience tells me they’re coming!

As for the door sensor - if there’s no-one else home…

No you don’t. Your timer just has to be longer than 60 seconds, which they are.

I think I have to disagree with this. If I start a 15 minute timer when I get an ON signal from the motion sensor, and then motion continues in that room for the next 15 minutes, there will never be another ON signal sent by my sensor and the light will turn off despite the motion sensor remaining activated.

Based on the behavior you described, and how EVERY other motion sensor out there works, you will receive the following events:

  • Motion detected, Item receives update ON
  • 60 seconds pass
  • Item receives update OFF which we can ignore because we don’t care
  • Some indeterminate time passes
  • Motion detected, Item receives update to ON

The OFF update doesn’t tell you anything. We are using received update so the rule will get triggered every time an ON update is sent to the Item. And you said that the ON will be sent after the Item goes to OFF which happens 60 seconds after motion is detected.

So you start a 15 minute Timer when you get an ON signal, and WHEN you see another ON signal you reschedule the Timer. Once the motion has stopped for 15 minutes then the Timer will go off and turn out the light (or dim then turn out in your case).

You can see this is happening in events.log if you don’t believe me.

The Timer and the sensor are completely independent. Your sensor will keep reporting motion detections as ON events. It just won’t do so faster than one ON event every minute.

I have some time today to look into this further. I have temporarily disabled my lighting rule and created the following rule.

var Timer testTimer = null

rule "test motion sensor"
when
  Item BathroomMotionSensor changed to ON
then
  BathroomLight.sendCommand(ON)
  testTimer?.cancel
  testTimer = createTimer(now.plusMinutes(2)) [|
    BathroomLight.sendCommand(OFF)
  ]
end

I then went into the bathroom and walked back and forth in front of the motion sensor for 2 minutes. After 2 minutes the light turned off despite continuous motion. Looking in the logs there were no additional ON signals sent from the motion sensor during that 2 minute period. I left the bathroom a few seconds after the light turned off and 60 seconds later an OFF signal was sent.

2018-06-05 10:49:38.165 [vent.ItemStateChangedEvent] - BathroomMotionSensor changed from OFF to ON
2018-06-05 10:49:38.860 [ome.event.ItemCommandEvent] - Item 'BathroomLight' received command ON
2018-06-05 10:49:38.874 [vent.ItemStateChangedEvent] - BathroomLight changed from 0 to 100
****I walk in front of the sensor for 2 minutes
2018-06-05 10:51:38.865 [ome.event.ItemCommandEvent] - Item 'BathroomLight' received command OFF
2018-06-05 10:51:38.874 [vent.ItemStateChangedEvent] - BathroomLight changed from 100 to 0
****I wait a few seconds and then leave the bathroom
2018-06-05 10:53:04.694 [vent.ItemStateChangedEvent] - BathroomMotionSensor changed from ON to OFF

I’ve also looked in the manual for the sensor and this is what it states;

The MultiSensor can send Basic Set Command to association group 1, which is setup via the Association Command Class, when the Motion Sensor detects movement to control the associated devices to “OPEN” state. After 4 minutes by default, if the Motion Sensor is not triggered again, the MultiSensor will send Basic Set Command to these devices to set them to their “CLOSE” state. However, if the Motion Sensor is triggered again within 4 minutes, the MultiSensor will reset the timing and start timing again.
The 4 minutes delay time can be changed through the usage of Z-Wave commands built into Z-Wave certified controllers/ gateways. (The specific Z-Wave command supporting this function is Configuration Command Class) Please consult the operation manual for these controllers/gateways for specific instructions on configuringthe MultiSensor.

(note that I have changed the 4 minute default delay to 1 minute on my sensor).

My interpretation of all this is that my sensors behave in a manner different to that which you have described. I’m happy to be corrected though if I am not understanding this correctly.

Now I’m going to set about understanding the rest of what you described here to cut my codebase down to size.

Indeed, the motion sensor behaves very differently from most I’ve seen. In this case you do need to trigger on the OFF command and subtract the timeout (i.e 4 minutes by default) to get the number of minutes since they last movement. Also make sure you have association group 1 configured as that is what will tell the motion detector to report to the controller so they binding can see it.

Thanks for confirming that. I was worried I was going crazy.

Just read the last few posts, but if remember right motion detectors have a beeing blind phase after the first trigger. And again if i remember right the Aen Multisensores have a blind phase of 4 minutes. so within 4 Minutes after the first trigger no retriggering is fired.

That is how it usually works. But, based on the quote from the manual Brad posted above, this particular sensor it doesn’t retrigger when motion is seen again like most sensors do. Instead it reschedules when it will send the OFF command. So, for example, if you are in a room moving around for 15 minutes, you will receive one ON command when movement first starts, then one OFF command at 19 minutes (15 minutes + 4 minutes after last movement).

@boconnor Thank you very much for creating this post, I am brand new to openhab and have the same sensors and switch’s you do. Was hoping you could post your edited working version of your rules, items etc. as I want to achieve what you have with my lights?

Sure. I’ve updated a lot since I first posted this taking a lot of Rich Koshak’s advice on-board. I now have a much more versatile set of rules with little need for repetition in the code.

There are two issues though. One is that it is a lot more complicated and I will probably need to annotate/comment the code to ensure it is understandable. Two is that I still have a few bugs to work out that I have been putting off getting to.

I’ll stop putting it off and try to sort out my bugs in the next few days and post what I have. Although I am about to receive my pre-ordered copy of Red Dead Redemption 2 so I may take a little longer…

lol cheers. as I’ve said just trying to set everything up, even if you post without comments I am not too concerned, i can always ask if I don’t understand something

I came across this code snippet whilst investigating my own lighting setup, and it looks like this is exactly what I am looking for. However when I implement the rules, I am getting an error thrown every time the reschedule method is called for the timer … so my lights turn on, but never off :slight_smile:

2019-03-24 16:08:57.405 [INFO ] [lipse.smarthome.model.script.openhab] - ...MotionLighting Triggered...
2019-03-24 16:08:57.406 [INFO ] [lipse.smarthome.model.script.openhab] - ...Reschedule Timer...
2019-03-24 16:08:57.407 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Motion detected': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.Timer.reschedule(org.joda.time.base.AbstractInstant) on instance: org.eclipse.smarthome.model.script.internal.actions.TimerImpl@737b0b44

Here’s the rule invoking this, based on rikoshak’s original code snippet…

import java.util.Map

val Map<String, Timer> timers = newHashMap
val Map<String, Timer> ctrlTimers = newHashMap

rule "Motion detected"
when
    Member of LightMotionSenors received update ON
then
    logInfo("openhab", "...MotionLighting Triggered...")
    if(triggeringItem.state == NULL) return; // we don't care about NULL

    val room = triggeringItem.name.split("_").get(0)
    val control = LightMotionControls.members.findFirst[ ctrl | ctrl.name == room + "_Motion_Control" ]
    if(control.state == OFF) return; // ignore the update if control is OFF

    // Get the values and associated Items
    val timer = timers.get(triggeringItem.name)
    val level = LightMotionLevels.members.findFirst[ ll | ll.name == room + "_" + HouseMode.state + "_Level" ].state as Number
    val timeoutMins = (LightMotionTimeouts.members.findFirst[ t | t.name == room + "_" + HouseMode.state + "_Timeout" ].state as Number).intValue
    val virtual = MotionLightsVirtual.members.findFirst[ v | v.name == room + "_Light_Virtual" ]
    val dimLevel = LightMotionDims.members.findFirst[ dim | dim.name == room + "_Dim" ].state as Number

    // Timer is already running
    if(timer !== null) {
        logInfo("openhab", "...Reschedule Timer...")
        virtual.sendCommand(level) // undo the dim if necessary
//        timer.reschedule(timeoutMins) // goes off timeoutMins minutes after the last time the motion sensor detects motion
        timers.get(triggeringItem.name).reschedule(timeoutMins)
    }
    // No Timer was running
    else {
        logInfo("openhab", "...Turn on light...")
        virtual.sendCommand(level) // turn on the light

        // Create a timer to dimm then turn off the light
        timers.put(triggeringItem.name, createTimer(now.plusMinutes(timeoutMins), [ |

            // if already dimmed then turn off
            if(virtual.state == dimLevel) {
                logInfo("openhab", "...Turn off light...")
                virtual.sendCommand(OFF)
                timers.put(triggeringItem.name, null) // clear the timer
            }

            // dim the light and reset the timer for another minute
            else {
                logInfo("openhab", "...Dim light...")
                virtual.sendCommand(level)
                timers.get(triggeringItem.name).reschedule(1)
            }
        ]))
    }
end

The original timer.reschedule(timeoutMins) code returns the same error - I was thinking that every other timer references the hash so this one should as well? But either way, any ideas/suggestions as to what the issue with ‘reschedule’ may be here?

Cheers,
Geoff