How I have automated my lights

@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

Think I found the problem… After a bit of trial and error, and re-reading the DP notes a few times, needs to be:

timer.reschedule(now.plusMinutes(timeoutMins))

Instead of:

timer.reschedule(timeoutMins)

Seems to be working now…

1 Like

@trentw11. Sorry I didn’t post my code. I was too unhappy with it to put it online and have simply had too many other responsibilities in life to work on it.

Having said that, I have had more time for the last few weeks and have had enough time to completely rebuild my z-wave network and re-write my code. It isn’t quite finished, but it is looking very promising. I will be able to post it in a week or two.

@boconnor did you ever have time to update your code? I’m working on motion control for lighting in a house I just finished building and would love to see where you ended up with yours. It seems like what you’re doing is exactly what I’m looking to accomplish. I’d love to see the updated product! :slight_smile:

Here is a similar but more modern and robust version…

It is with great shame that I admit that I have not. I just seem to be in a stage of life where tinkering on things like this is a luxury I just don’t have enough time for. I have a z-wave network that doesn’t quite work reliably and when I have had time to work on openHAB it has been spent trying to get my stupid z-wave system actually working reliably. Hopefully over the holidays I will solve that and get back to my lighting code, but don’t hold your breath.

Sorry.

I would love to see the final product. I’m also tinkering arround with the aeotec sensor and a light bulb in my bedroom.
Which is also very special situation because the light isn’t allowed to switch on when I’m asleep but due to my movements (while im sleeping) it tends to switch on.
My GF is getting frustrated from this :stuck_out_tongue: I’m playing arround with time schedules and the sensitivity but so far it’s a nightmare.

@boconnor NO shame there! This is a hobby after all, right? :slight_smile: I actually got it working, with a few exceptions, based on @rlkoshak’s Groups example from post#6 above. I’ve still got to figure out how to work around the different behavior of the Aeotec Multisensor, as I’m using the same sensor you are. I’ve also got to take a few minutes and work out a bug with my TimeOfDay rule. I set it up on day 2 of my journey with OH and clearly didn’t understand it at that point. I’ll have the TOD thing worked out this morning, as for the Multisensor, we’ll see… :slight_smile:

@5iver Thanks Scott. I’d come across this a few times but haven’t really looked at it yet. I definitely will check it out too. I plan to finish working on the current exercise if for no other reason than to learn from it, and then at some point in the future give your method a shot. :slight_smile:

1 Like

@robbie_demaegdt I’ll post my version with items/rules/sensor settings when it get it debugged. I plan to eventually add a “Goodnight” switch, or something like that, to be sure this doesn’t occur. My wife would be quite angry if I let that happen, lol.

If you’re using the example code from post#6, then it “should” be as simple as setting your NIGHT levels to 0 for that particular light. Haven’t quite got there yet to test at that level of detail though. Stay tuned…

@boconnor and @robbie_demaegdt as promised here’s my finished product.

Sensor: Aeotec Multisensor 6
Parameter 3: Motion Sensor Reset Timeout - 10 seconds

Items

Dimmer MFMBRCloset_Dimmer          "Master Closet Light Dimmer [%d %%]"  <slider>   (MF_MstrBed, GSW_Lights, GSW_IndoorLights, InsideMotionLights, mapdbPersist, InfluxDB)      {channel="zwave:device:15d73a75:node17:switch_dimmer"}
Dimmer MFMBRCloset_Motion_Light    "MBR Closet Motion Light"                        (InsideMotionLights)                                                                        {channel="zwave:device:15d73a75:node17:switch_dimmer"}
Switch MFMBRCloset_Motion_Override "MBR Closet Motion Control Override"             (LightMotionControls, mapdbPersist, InfluxDB)
Switch MFMBRCloset_Motion_Pause    "MBR Closet Motion Control Pause"                (LightMotionControls, mapdbPersist, InfluxDB)
Switch MFMBRCloset_Motion_Sensor   "Master Bath Motion"                  <motion>   (LightMotionSensors, mapdbPersist, InfluxDB)                                                {channel="zwave:device:15d73a75:node20:alarm_motion"}
Number MFMBRCloset_DAY_Level       "Daytime lighting level [%s]"         <light>    (LightMotionLevels, mapdbPersist)  
Number MFMBRCloset_EVENING_Level   "Evening lighting level [%s]"         <light>    (LightMotionLevels, mapdbPersist)  
Number MFMBRCloset_NIGHT_Level     "Night lighting level [%s]"           <light>    (LightMotionLevels, mapdbPersist)  
Number MFMBRCloset_DAY_Timeout     "Daytime lighting timeout [%s]"       <light>    (LightMotionTimeouts, mapdbPersist)  
Number MFMBRCloset_EVENING_Timeout "Evening lighting timeout [%s]"       <light>    (LightMotionTimeouts, mapdbPersist)  
Number MFMBRCloset_NIGHT_Timeout   "Night lighting timeout [%s]"         <light>    (LightMotionTimeouts, mapdbPersist)
Dimmer MFMBRCloset_Light_Virtual   "Virtual light switch"                           (MotionLightsVirtual, mapdbPersist)
Number MFMBRCloset_Dim             "dim level for motion lighting [%s]"             (LightMotionDims, mapdbPersist)

Rules

import java.util.Map


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

rule "Motion detected"
when
    Member of LightMotionSensors changed to ON // members are named Room_Motion_Sensor
then
    logInfo("Rule Info", "Step 1 of motion detected")
    if(triggeringItem.state == null) return; // we don't care about NULL

    val room = triggeringItem.name.split("_").get(0)
    val pause = LightMotionControls.members.findFirst[ p | p.name == room + "_Motion_Pause" ] //Items named room_Motion_Pause go in the LightMotionControls Group
    val override = LightMotionControls.members.findFirst[ ov | ov.name == room + "_Motion_Override"] //Items named room_Motion_Overide go in the LightMotionControls Group
    logInfo("Rule Info", "Step 2 of motion detected"+" room: "+ room +" control: "+ pause)
    if(pause.state == ON || override.state == ON) return; // ignore the update if pause or override is ON

    // Get the values and associated Items
    val timer = timers.get(triggeringItem.name)
    val level = LightMotionLevels.members.findFirst[ ll | ll.name == room + "_" + TimeOfDay.state + "_Level" ].state as Number
    val timeoutMins = (LightMotionTimeouts.members.findFirst[ tv | tv.name == room + "_" + TimeOfDay.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
    logInfo("Rule Info", "Step 3 of motion detected" +" timer: "+  timer +" level: "+ level +" timeoutMins: "+ timeoutMins +" virtual: "+ virtual +" dimLevel: "+ dimLevel)
    // Timer is already running
    if(timer !== null && triggeringItem.state == ON) {
        logInfo("Rule Info", "Step 4 of motion detected rule.  Send "+ level +" to "+ virtual +" and reschedule the timer")
        virtual.sendCommand(level) // undo the dim if necessary
        timer.reschedule(now.plusMinutes(timeoutMins)) // goes off timeoutMins minutes after the last time the motion sensor detects motion
    }
    // No Timer was running
    else {
        logInfo("Rule Info", "Step 5 of motion detected rule.  No light timer running for this room.  Turn on " + room + " lights")
        virtual.sendCommand(level) // turn on the light

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

            // if already dimmed then turn off
            if(virtual.state == dimLevel) {
                logInfo("Rule Info", "Step 6 of motion detected rule.  Turn off light after dim-before-off sequence. " + triggeringItem.name + " = " + triggeringItem.state)
                logInfo("Rule Info", "Step 6 of motion detected rule: virtual: "+ virtual.state +" dimLevel: "+ dimLevel + " triggeringItem.state: "+ triggeringItem.state )
                virtual.sendCommand(OFF)
                timers.get(triggeringItem.name).cancel // cancel the timer
                //timers.put(triggeringItem.name, null)  // clear the timer
                
            }

            // dim the light and reset the timer for another minute
            else {
                logInfo("Rule Info", "Step 7 of motion detected rule.  Begin dim-before-off sequence.")
                logInfo("Rule Info", "Step 7 of motion detected rule: virtual: "+ virtual.state +" dimLevel: "+ dimLevel +" timer: "+ timers.get(triggeringItem.name) +" now: "+ now)
                virtual.sendCommand(dimLevel)
                timers.get(triggeringItem.name).reschedule(now.plusMinutes(1))
                }
            ])
        )}
end

rule "Pause motion control if light changed by switch"
when
    Member of InsideMotionLights changed  
then
    val room = triggeringItem.name.split("_").get(0)
    val virtual = MotionLightsVirtual.members.findFirst[ v | v.name == room + "_Light_Virtual" ] //Items named room_Light_Virtual go in the MotionLightsVirtual Group
    val pause = LightMotionControls.members.findFirst[ p | p.name == room + "_Motion_Pause" ]  //Items named room_Motion_Pause go in the LightMotionControls Group
    logInfo("Rule Info", "Step 1 of pause motion control" +" room: "+ room +" virtual: "+ virtual +" control: "+ pause)
    if(triggeringItem.state != virtual.state) {
        logInfo("Rule Info", "Step 2 of pause motion control rule.  Set ROOM_Motion_Control to Pause")
        virtual.postUpdate(triggeringItem.state) // we don't want this to be treated as a new command
        pause.sendCommand(ON) // temporarily disable the motion sensor control of the light

        val int disabledTime = if(triggeringItem.state == 0) 10 else 60 //If light is OFF, set to 10 minutes.  If ON, set to 60 minutes.
        val timer = ctrlTimers.get(triggeringItem.name)
        if(timer !== null){
            logInfo("Rule Info", "Step 3 of pause motion control rule.  Add "+ disabledTime +" time to Motion_Control pause for "+ room)
            timer.reschedule(now.plusMinutes(disabledTime))
        } 
        else {
            ctrlTimers.put(triggeringItem.name, createTimer(now.plusMinutes(disabledTime), [ |
                logInfo("Rule Info", "Step 4 of pause motion control rule.  Pause Motion Control for "+ room)
                pause.sendCommand(OFF)
                ctrlTimers.put(triggeringItem.name, null)
            ])
            )}
    }
end

rule "Motion control resume"
when
    Member of LightMotionControls received command OFF
then
    val room = triggeringItem.name.split("_").get(0)
    val motion = LightMotionSensors.members.findFirst[ m | m.name == room + "_Motion_Sensor" ]
    val light = InsideMotionLights.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" ]
    logInfo("Rule Info", "Step 1 of motion control resume" +" room: "+ room +" motion: "+ motion +" light: "+ light +" dimLevel: "+ dimLevel +" virtual: "+ 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
    logInfo("Rule Info", "Step 2 of motion control resume")
    if(light.state == dimLevel) return; // Light is already at the dim level
    logInfo("Rule Info", "Step 3 of motion control resume")
    timers.put(motion.name, createTimer(now.plusMinutes(1), [ |
        if(triggeringItem.state == ON) virtual.sendCommand(OFF)
        timers.put(motion.name, null)
    ]) 
    )
end

rule "Update real from virtual"
when
    Member of MotionLightsVirtual changed  
then
    val room = triggeringItem.name.split("_").get(0)
    val virtual = MotionLightsVirtual.members.findFirst[ v | v.name == room + "_Light_Virtual" ]
    val light = InsideMotionLights.members.findFirst[ l | l.name == room + "_Motion_Light" ]
    logInfo("Rule Info", "Step 1 update real from virtual rule.  "+" room: "+ room +" virtual: "+ virtual +" control: "+ light)
    light.sendCommand(virtual.state.toString)
end

This works pretty much the same as the OP with a couple of minor tweaks. I ended up removing a couple of the “HouseMode” items and reducing my TimeOfDay states to Day,Evening, and Night. I also changed motion control to “pause” and added an “Override” mode as well. This way, if i want to “pause” the motion control from the switch i can do so BUT i can also completely override motion control via a switch on my sitemap. I intend to use this switch for functions like “party mode” or “Bedtime”. For example, you could tell Alexa “Goodnight” and have a routine that turns on this Override and whatever other stuff you might want the house to do at bedtime.

I learned a LOT in this exercise and I’ve still got plenty left to learn so any feedback/advice would be greatly appreciated.

I also want to thank @rlkoshak and @5iver ! You two gentlemen are of great value to this community!

2 Likes

I am new to this and came via the route of changing my alarm. I replaced the old alarm system with Konnected for my alarm so I could use all my existing PIR’s and Smoke detectors from my old alarm. As a result of this all the PIR’s are on my system now, so I put Sonoff minis behind my switches and hooked the two, so I can use the alarm PIR’s for my lights when the alarm is off.

I chose a slightly different logic to the original poster. My logic is follows;

I wanted the lights to work differently based on being used with the switch vs with the PIR. I wanted them to behave more normal with the switch, mostly because my wife is a little averse to home automation! :slight_smile: and just be quick and easy for PIR, so they work a little differently.

I have 2 timeouts, one if activated via the PIR, and one if activated via the switch. Both can me changed via the system, but have defaults of around 3 mins idle for the PIR and 30 mins for switch.

If you walk into a room and don’t hit the switch, the PIR will turn the light on, it will keep it on until it detects no movement for 3 mins, then turn off. while it keeps detecting movement it will obviously keep on. This is the kind of use where you just walk into a room to do something a while, then go out again.

If you walk in and turn the light on via the switch, it turns the light on with a longer timeout, of 30 mins. The use assumption being that if you turn it on you will prob be in there longer.

My wife found it very annoying that the light would go off and she needed to do something to turn it back on, and I can see the point, so having a far longer timeout via the switch makes it appear a more normal use of a switch

If you turn the light off and then on again at the switch within 2 seconds. this locks the light on and the PIR will then never turn it off.

If the PIR turns the light off and it sees movement it will turn it straight back on again.

However, if you turn the light off via the switch, the PIR it locked out for a peroid of time from turning the light back on. It is very annoying if you turn the light off, move in the room, and it comes back on again immediately. Another thing that made my wife want me to remove everything!! :slight_smile:

None of the automation kicks in at all, until the astro sunset is reached.

I think this seems to work pretty well and was nice use of the existing alarm system.

2 Likes

@boconnor just wanted to say thanks for posting this. I remember seeing your post a while back and it gave me some great ideas on how we could make automated lighting even better.

I really liked your idea of overriding motion control, so I incorporated it using zwave scenes and the expire binding to create a more simplified version of manual control. It’s working beautifully so far. By using scenes, you can have a much cleaner base of maintenance since you can get rid of proxy items and you can set the manual override time in a centralized location.

Not all zwave switches support scenes, but it’s becoming more and more common.

I’m glad I can help someone. I’m usually the one in desperate need of help around here.

I have some spare time over the next 8 weeks or so thanks to a pandemic induced lack of work.I’m actually hoping to get a chance to get back to the code for this and produce something polished and relatively bug-free. I’ll post it when I get a chance.

Your idea sounds good, but I found the expire binding to be too inflexible for my purposes. I use it in a few simple cases, but not being able to vary the duration of the timers ruined them for me. I’d love it if this binding could be extended somehow in the future. I also have never really gotten my head around the idea of scenes and how they work. I’m not sure whether the scene functionality on my Fibaro dimmers could be coaxed to work in this way. There does appear to be a scene number channel so maybe? I might have a play, but in the meantime I’m stuck with my complicated logic and proxies.

Those Inovelli switch/dimmers look very nice. And astonishingly cheap too. Z-wave home automation seems like a much cheaper hobby in the US than it does here.

Sorry to hear about your recent increase in availability. I guess the bright side is getting to play with OH.

Yes, I really love these devices. They’re a small company run by enthusiasts who really care about their products, so we’re lucky to have them. The CEO himself is active on reddit and other home automation discussion boards. I’ve tried zwave dimmers from a dozen different brands over the last 7 years or so, and these are my favorite so far.

Z-wave scenes are not well-documented outside of dedicated controllers, so I was in the same boat until I had some hands on time with some devices that offered them.

As it turns out, for switches and dimmers which support scenes (I’ve tried homeseer and inovelli), it’s pretty simple. The device sends an update to your zwave network based on action triggers defined by the manufacturer. For the OH zwave binding, this is received as a separate channel for the device.

In other words, if you single-tap up, the switch will report a status of “1.0” If you double-tap up, it will send out a “1.1”. If you double-tap down, it will send out “2.1”, and so on. You’ll have to look at the documentation for each device to see how many scenes they support since each device is different.

Scenes are perfect for this use case because we can now capture a hardware press without having to create extra proxy items.

If you link the model number, I can check the cd-jackson DB to see if your specific fibaro switch supports the scene channel for sending scene info the same way.

They’re FGD-212 dimmers. Looking through the manual it does seem like they can be configured to report scene information in the way you have described. I suspect I will be able to find a way to dramatically simplify my code (and possibly delete a huge pile of proxy items!), which will be fantastic.

My ‘free time’ starts in a few days so will work on it starting then.

I checked out the xml for the FGD212 and yes, it does appear it will support scenes in the zwave binding as well. The relevant channel is here:

  <channel id="scene_number" typeId="scene_number">
    <label>Scene Number</label>
    <properties>
      <property name="binding:*:DecimalType">COMMAND_CLASS_SCENE_ACTIVATION</property>
    </properties>
  </channel>

I’m sure you’ve already got this in mind, but here’s a friendly reminder just in case: before a full rewrite of your rules, make sure to check the performance of this zwave function to make sure it’s working to your standards.

You’ll probably want to test usability, accuracy, latency on a few switches first.

Don’t worry. It’ll all be done on a bench with a spare dimmer before I take it near anything already up and running and working.