Extended Motion Sensor Rule: any Suggestions?

Hey all,

Ive been looking quite some time for including my motion sensors into openhab.
At the moment, they are managed by hue.

Im thinking, there has to be something out there and I really do not want to start from scratch…

I found some Tutorials, but they are really basic.

i am using Philips hue Motion Sensors.

The ultimate Motion Sensor rule would have to include the following for me:

-only trigger lights if not already manually on(switch, alexa, app…), or switch back to the state before triggering (not every sensor)
-do not switch off lights if manually adjusted after triggering
-treshold for luminance
-multiple time slots: full light during day, nightlight during the night, and different turn off times per slot

Simple motion detectors just wont do it, exept for very simple things like the stairs.

Did anyone do anything like that? I thought I would just ask before doing it from scratch.

What bothers me the most is code reusability; I dont want to copy paste rules for every sensor, woulf be really nice to have like a configurable motion sensor module with input for the motion senslr, the brightness, some parameters and an output for the lights.

I appreciate your input!

Thanks,

Manuel

@rlkoshak is our code reusability guy :smiley:

This should be a good starting point:

For Rules DSL, copy and paste and edit is your only option.

The NGRE has the option for rule templates but no good way to create and distribute them yet. But that is in work.

JSR223 has a helper library that is getting off the ground that is starting to build up a library of reusable rules and modules. There are a hand full there already and I’ve around 10 more awaiting review, but none of them address your use case.

So you will either have to build this role from scratch it pice it together with piece part rules examples from the forum. For example:

1 Like

Thanks for your input! I will have a look i find the time for it.

I had a look. Most of the motion sensor stuff around here is pretty basic.
I made a rule today, which can do everything for one room (for now).
I wanted a rule which does not need to create items or proxy-items or things like that.

What bugs me is the if/elses per room so often, and the action

//Ausführen der jeweiligen Aktion
////Flur
if (Raum_Name=="Flur") {
    //Aktion an
    gLicht_Flur_Farbtemperatur.sendCommand(Par_Farbtemperatur)
    Thread::sleep(500)
    gLicht_Flur_Helligkeit.sendCommand(Par_Sollhelligkeit)    
}

has to be copied for every room…

i thought i share my rule, as i think many could have a use of it. It would be nice to get it to something everyone could use easily.

Any inputs? Feel free to modify or suggest.

I will wait a little for your input before extending it to my other 3 Motion sensors.

import java.util.Map
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
//intern
val Map<String, Timer> timers = newHashMap
val Map<String, DateTime> last_Auto_Update = newHashMap
val Map<String, String> Bewegungsmelder_Aktiv = newHashMap
//Damit wir nicht multithread auf die hashmaps zugreifen!
var Lock mylock = new ReentrantLock()
rule "Motion"
when
    Member of gSensor_Bewegung changed //Bewegungsmelder
    or Member of gSensor_Helligkeit changed //Helligkeit
then
var Trigger_Name = triggeringItem.name

//Parameter, die im Folgenden gesetzt werden, mit Standardwerten
var Par_Helligkeit = 5.0 //lx
var Par_Sollhelligkeit = 70.0 //%
var Par_Farbtemperatur = 70.0 //%
var Par_Timeout_Sekunden = 30 //s

//Istwert-Variablen, die im Folgenden gesetzt werden
var Ist_Helligkeit = 1000.0
var Ist_Bewegung = OFF
var Ist_Freigabe = "NEIN" //es wird nur aktiviert, wenn Freigabe auf "JA"
var Timer_Name = ""

//Sonstige
var Raum_Name = ""

//Zeiten
val Zeit_Morgens = now.withTimeAtStartOfDay.plusDays(1).minusHours(24-10) //10 Uhr
val Zeit_Mittags = now.withTimeAtStartOfDay.plusDays(1).minusHours(24-13) //13 Uhr
val Zeit_Nachmittags = now.withTimeAtStartOfDay.plusDays(1).minusHours(24-16) //16 Uhr
val Zeit_Nachts = now.withTimeAtStartOfDay.plusDays(1).minusHours(24-22) //22 Uhr

var Tageszeit = "UNBEKANNT"
switch now {
    case now.isAfter(Zeit_Morgens) && now.isBefore(Zeit_Mittags): Tageszeit = "MORGENS"
    case now.isAfter(Zeit_Mittags) && now.isBefore(Zeit_Nachmittags): Tageszeit = "MITTAGS"
    case now.isAfter(Zeit_Nachmittags) && now.isBefore(Zeit_Nachts): Tageszeit = "NACHMITTAG"
    default: Tageszeit = "NACHTS"
}
    ////Flur
    if (Trigger_Name.contains("Flur")) {
        Raum_Name="Flur"

        //nur wenn Lampen aus
        Ist_Helligkeit = (Sensor_Flur_Helligkeit.state as DecimalType).floatValue
        if (gLicht_Flur_Helligkeit.state <= 1.0) {
            Ist_Freigabe = "JA"
        }
        else{
            Ist_Freigabe = "NEIN"
        }

        //Parameter
        if (Tageszeit.contains("NACHTS") || Tageszeit.contains("MORGENS")){
            Par_Sollhelligkeit = 40.0
            Par_Farbtemperatur = 80.0
        }
        else if (Tageszeit.contains("NACHMITTAG")){
            Par_Sollhelligkeit = 100.0
            Par_Farbtemperatur = 60.0
        }
        else{
            Ist_Freigabe = "NEIN"
        }
        
        Par_Helligkeit = 1000.0
        Par_Timeout_Sekunden = 30

        //Zuordnung
        Timer_Name = Raum_Name

        if (Sensor_Flur_Bewegung.state == ON || Sensor_Flur2_Bewegung.state == ON){
            Ist_Bewegung= ON
        }
         
    }
    ////Schlafzimmer
    else if (Trigger_Name.contains("Schlafzimmer")){

        //Platzhalter
    }
    else {return;}

    //////////////////////////ALLGEMEIN////////////////////////////////////////////
    //Validierung 
    if (Ist_Helligkeit == UNDEF){ Ist_Helligkeit = 1000.0 }
    if (Ist_Bewegung != ON ){ Ist_Bewegung = OFF }
    if (Timer_Name == ""){ return; }
    
    //Bewegung aus? Dann Timer Starten
    if (Ist_Bewegung == OFF) {
        //nur wenn aktiv
        if (Bewegungsmelder_Aktiv.get(Raum_Name) === null || Bewegungsmelder_Aktiv.get(Raum_Name).contains("NEIN")){return;}
        //Timer starten
        //////////////////////////ALLGEMEIN ENDE//////////////////////////////////////
        ////Aktion aus
        //Flur
        
        if (Raum_Name=="Flur") {
            mylock.lock()
            if (timers.get(Raum_Name) !== null){
                timers.get(Raum_Name).cancel()
                timers.put(Raum_Name,null)
                timers.remove(Raum_Name)
            }

            timers.put(Raum_Name, createTimer(now.plusSeconds(Par_Timeout_Sekunden), [|
            logWarn("MOTION", "TIMER STOP")
                gLicht_Flur_Helligkeit.sendCommand(0)
            ]))
        mylock.unlock() 
        }
        return; //nichts sonst tun.
    }
    //////////////////////////ALLGEMEIN////////////////////////////////////////////
    //Timer canceln, wenn bereits aktiv,da wieder Bewegung
    mylock.lock()
    if( (timers.get(Raum_Name) !== null) && (!timers.get(Raum_Name).hasTerminated()) ){
       timers.get(Raum_Name).cancel()
       timers.put(Raum_Name,null)
       timers.remove(Raum_Name)
    }
    mylock.unlock()

    //Momentan ist der Timer inaktiv. Helligkeitswert gering genug, um zu aktivieren?
        if ((Ist_Helligkeit > Par_Helligkeit)) {
        return; //nichts tun.
    }
    //Freigabe zum Start des Vorgangs?
    if (!Ist_Freigabe.contains("JA")){
        return; //nichts tun.
    }

    //////////////////////////ALLGEMEIN ENDE//////////////////////////////////////

    //Wenn Sollhelligkeit quasi 0 ist, ist der Bewegungsmelder nicht aktiv.
    if (Par_Sollhelligkeit <= 0.1){
        return;
    }
    
    //Rücksetzen der Befehle durch Automatik verhindern
    mylock.lock()
    last_Auto_Update.put(Raum_Name,now)
    Bewegungsmelder_Aktiv.put(Raum_Name,"JA")
    mylock.unlock()
    Thread::sleep(100)
    
    //Ausführen der jeweiligen Aktion
    ////Flur
    if (Raum_Name=="Flur") {
        //Aktion an
        gLicht_Flur_Farbtemperatur.sendCommand(Par_Farbtemperatur)
        Thread::sleep(500)
        gLicht_Flur_Helligkeit.sendCommand(Par_Sollhelligkeit)    
    }

end

rule "Motion Cancel" //bei manuellem Eingriff
when
    Member of gLicht_Helligkeit changed
    or Member of gLicht_Farbtemperatur changed
then
    var Trigger_Name = triggeringItem.name
    var Raum_Name=""
    ////Flur
    if (Trigger_Name.contains("Flur")) {
        Raum_Name="Flur"
    }
    ////Schlafzimmer
    else if (Trigger_Name.contains("Schlafzimmer")){
        Raum_Name="Schlafzimmer"
    }
    else {
        return;
    }

    //////////////////////////ALLGEMEIN////////////////////////////////////////////
    //Bewegungsmelder auf inaktiv setzen wenn manuell überschrieben und eventuell gestarteten Timer löschen
    mylock.lock()
    //nur wenn Automatik gerade aktiv
    if (Bewegungsmelder_Aktiv.get(Raum_Name) === null || Bewegungsmelder_Aktiv.get(Raum_Name).contains("NEIN")){return;}

    //nur wenn nicht in den letzten 5 Sekunden gestartet durch die Automatik
    if (last_Auto_Update.get(Raum_Name) !== null && now.minusSeconds(5).isBefore(last_Auto_Update.get(Raum_Name))) {
        mylock.unlock()
        return;
    }
    
    if( (timers.get(Raum_Name) !== null) && (!timers.get(Raum_Name).hasTerminated()) ){
       timers.get(Raum_Name).cancel()
       timers.put(Raum_Name,null)
       timers.remove(Raum_Name)
    }
    Bewegungsmelder_Aktiv.put(Raum_Name,"NEIN")
    mylock.unlock()

    //////////////////////////ALLGEMEIN ENDE//////////////////////////////////////
end
1 Like

They are basic because they are posted with the intent that you will add to them and combine them with other posted examples.

Better to use

createTimer(now.plusMillis(500), [ | gLicht_Flur_Helligkeit.sendCommand(Par_Sollhelligkeit)])

over the Thread::sleep.

There are many approaches to avoid duplicated code. See Design Pattern: DRY, How Not to Repeat Yourself in Rules DSL. In particular, look at the Associated Items design pattern that let’s you access related Items based on their name. For example, replace all your copy and paste per room with:

    sendCommand("gLicht_"+Raum_Name+"_Farbtemerature", Par_Farbtemerature)
    sendCommand("gLicht_"+Raum_Name+"_Helligkeit", Par_Sollhelligkeit)

ReentrantLocks are super dangerous to use in Rules DSL. I cannot recommend their use under any circumstances.

Because you are almost certainly going to have other Rules that care about the time of day, it’s far better to code this as presented in the DP linked to above and put it in it’s own Rule and put the result into an Item. Then this Rule just needs to check the Item. It’s more efficient and easier to maintain.

See Associated Items DP (linked to in the DRY DP above) for how to name and easily extract the room name from the triggering Item. It also shows how to both sendCommand/postUpdate (examples above) and retrieve the state of an Item for which you’ve constructed it’s name.

These too could be put into Items and use Associated Items to get them by name. For something similar see Design Pattern: State Machine Driven Groups.

If you must use a lock, and you should go to great lengths to avoid it, at least put the code in a try/catch/finally so you have at least a chance that the lock get’s unlocked in case there is an error. In this case, use a HashTable instead of a HashMap. HashTable is thread safe and implements the locking for you. It’s safe to use library code that manages it’s own locks. In Rules DSL, certain errors can occur that never return to the Rule leaving your locks locked forever.

You can shorten the code that cancels and removes the timer to:

timers?.cancel
timers.put(Raum_Name, null)

Putting a null value removes the key so the remove is redundant.

But you don’t have to cancel and remove the Timer. Just reschedule it.

2 Likes

I’m on my phone so I can’t give you specific examples, but what you are trying to do can be accomplished using the lighting control system I made called Eos. It allows you to have motion activated scenes as well as scenes that adjust based on light level (or whatever other value you want). For your timed decay for the motion detection you would simply need to have the sensor only able to command the item to on, and then have the expire binding set it to off after a delay.

OP has a requirement which rules out use of expire, because it cannot be avoided or cancelled

With Eos if you adjust the lights manually then you have changed the scene, presumably to one that does not have the motion trigger configured.

@rlkoshak Wow, thank you for your review. I will rebuild my code tomorrow with your input, remove the lock and use hashtables for example.

And I will make it more group-based, so there is a master group for the lights, Motion Sensors and Brightness, and the rule finds the right item by itself.

I will leave the parameters in the rule for now, i dont want to have like 50 items to create just holding fixed values and make the selection here.

By the way, i could not find anything about thr variable scope inside timer bodies, so is it safe to do

sendCommand(“gLicht_”+Raum_Name+"_Farbtemerature)

inside a timer? So i can reuse the code for thr timer without Hardcoding the room names here?
Is it creating the cmd as a string during runtime and evaluating it on the timer expiring?

@CrazyIvan359: hmm, or should o have a look into that? Im not quite sure if it would do what i want completely, and how to accomplish that…

The lambda inherits the scope in which it is created. If it’s safe to do it before you create the Timer it’s safe to do it inside the Timer.

I know it will, with a little input from other automation parts, do exactly what you want because it’s already doing it in my setup.


Eos sets the lights in a group based on the scene for that group. Lets assume a simple setup with a scene called “on” that has all the lights on, and another called “off” that has all the lights off. You would then add your motion sensor item to the “off” scene, and when motion is active tell Eos to use the “on” scene.
If the scene is set to “off” and the motion item changes to ON, the “on” scene will be evaluated and the lights will be turned on. The person in the room pushes the switch which sets the scene to “on”. The lights won’t change because the scene being evaluated doesn’t change (because “off” + motion = “on”) but now the scene is “on” which has no motion settings and as such the lights won’t turn off when the motion item goes back to OFF.
See Motion Settings


Eos can get the luminance from an item you provide and use it with Threshold and Scaled scenes to adjust light levels dynamically.


I have accomplished this using a work in progress version of @rlkoshak new Time of Day script that runs on the new rule engine. It is still a work in progress and will be undergoing changes before it is added to the Helper Libraries. You can use any Time of Day setup you like.

I have scenes setup for rooms that require special actions based on time of day. Like our bedroom has the lights on softly in the morning and evening, but not during the day.

I have a rule that is triggered by the change of the ToD and it passively (postUpdate) sets the master scene as long as someone is home and awake. By only changing the master scene item (instead of sending a command) it does not change the scene of any other groups, so only groups with their scene already set to “parent” will use the new scene and groups that have a different scene set manually will not be changed.

When everyone is asleep I have the master scene set to “sleep”. I have the step lighting on the stairs setup with a “sleep” scene that is off by default but motion will turn them on. I also have a very dim nightlight on in the kitchen all the time (no motion sensors).

@CrazyEvan359
this seems very promising. Thank you.
One thing i dont see here: the luminance sensors are inside the motion sensors. it only makes sense reading them, if the lights are off.

So allthough scaling is nice, it doesnt fit that purpose. For example if its already bright in the morning, dont turn lights on. if its day and the curtains are closed, the TV lights should still go on.

Is that doable?

What happens to the scene, if I change the brightness via the hue-app or Alexa? Does it switch to “manual”?

For the Master Scenes: so you set one Master Scene to Morning/Day/Sleep, und defined individual Morning/Day/Sleep Scenes für each group/room?

And if you turn your lights off, they will be set to parent?

I think EOS would maybe be a good solution mid-term, but i think i dont have the time right now to change and test everything; is it compatible with 2.4 Release version?

I think that might be a project for the future…

@rlkoshak: can you give me a hint, where to find some documentation about hashtables? And why is everyone using hashmaps then?

I’m calculating values for each room based on Sun elevation, azimuth, and cloudiness information from the weather service. Eventually I would like to have my own luminance sensor outside.

I had thought about doing sensors in rooms but I always came to the same conclusion you have: the readings are invalid if any lights are on.

Is this common to have sensors in rooms? Perhaps it would be worth me adding support for this to Eos.


This is exactly what scaled scenes are for.


This could be accomplished with a scaled scene with settings for daytime (turn lights on dimly if luminance is very low) that also has a motion setting so that it plays a different scene if the curtains are closed (assuming they are part of your automation). The scene played if curtains are closed would have a the lights on at a low setting regardless of luminance and turn them up if luminance decreases.


You would need to create a proxy item for Alexa to control. As an example you could create a “fader” Dimmer and have Alexa control it to set the brightness. Create a scaled or threshold scene with its level source linked to this “fader” item. Create a rule that listens for commands to this “fader” item and when one is received it sets the room’s scene to the “fader” scene.


Yes, but not all rooms have a scene defined for each time of day. The default settings for undefined scenes will turn lights off, so I only have scenes defined where I don’t want the lights to be off.


Not necessarily. “parent” is a special scene where the group’s parent’s scene is used, which may not be “off”.


No. As it uses the openHAB Helper Libraries you will require a more recent version. They are quite stable though. I am running S1689 if I recall and I have no problems.

actually yes, im using the philips hue motion sensors. They have a luminance and a temperature sensor built in, are battery powered with a good lifetime, and cost around 35 Euro each.

It works pretty well, if you put down the shutters in your sleeping room, the night light will still turn on for example. I think its easier than evaluating everything manually what could influence the brightness in that room…

I will stick for my rule for now, but will keep an eye on EOS. It looks pretty nice.

This would be possible with Eos, albiet complex. A closed system like Hue will have detailed information about light output from each device, this level of information is difficult to have in an open system like openHAB.

I am open to suggestions though.

hue is not that complex. It just uses it as a condition if lights are off, the same i do now…it gives the brightness in lux.

https://docs.oracle.com/javase/8/docs/api/java/util/Hashtable.html

Because:

  • The use of hash maps/hash tables is IMHO a code smell. It indicates you are not using an approach that is well supported by the language. When ever I see either in use, my first thought is “they are doing it wrong.”
  • Most of the time it doesn’t matter whether the map is thread safe or not.
  • When you don’t need a thread safe collection, it is actually pretty expensive to use one; the non-thread safe version is a better choice.
  • Most users are unaware of these issues.

Well that is something I could add do Eos but seems somewhat useless. The lights just stay at the same brightness as the amount of light required in the room changes?