Extended Motion Sensor Rule: any Suggestions?

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?

hmm.
I edited many things. I implemented Hashtables like you said.
Unfortunately, i get “null” errors, for example here:

try {
    mylock.lock()
    if (timers.get(Raum_Name) !== null){
            timers.get(Raum_Name).cancel()
            timers.put(Raum_Name, null)   
    }
}
catch(Throwable t) {
    logError("Error", "Keine Ahnung was passiert ist..." + t.toString)
    timers.put(Raum_Name, null) 
}
finally {
    mylock.unlock() 
}

Error:
17:58:19.613 [ERROR] [untime.internal.engine.RuleEngineImpl] - Rule ‘Motion’: null

It doesnt matter if I have locks or not, it was just a try.
Any ideas?

import java.util.Hashtable
import org.eclipse.smarthome.model.script.ScriptServiceUtil
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
////GLOBALE PARAMETER/////
val String[] Raumliste = newArrayList('Flur','Kueche','Bett')
val Log_An = true
//Textbausteine(Prefix + Raumname + Suffix)
val Light_Prefix = "gMotion_"
val Light_Suffix_Brightness = "_Licht_Helligkeit"
val Light_Suffix_Temperature = "_Licht_Farbtemperatur"

val Motion_Sensor_Prefix = "gMotion_"
val Motion_Sensor_Suffix = "_Sensor_Bewegung"

val Lux_Sensor_Prefix = "gMotion_"
val Lux_Sensor_Suffix = "_Sensor_Lux"
////GLOBALE PARAMETER/////


//intern
val Hashtable<String, Timer> timers = new Hashtable<String, Timer>()
val Hashtable<String, DateTime> last_Update_Time = new Hashtable<String, DateTime>()
val Hashtable<String, String> last_Update_Type = new Hashtable<String, String>()
val Hashtable<String, Number> last_Update_Brightness = new Hashtable<String, Number>()
val Hashtable<String, Number> last_Update_Temperature = new Hashtable<String, Number>()
var Lock mylock = new ReentrantLock()
rule "Motion"
when
    Member of gMotion_Sensor_Bewegung changed //Bewegungsmelder
    or Member of gMotion_Sensor_Lux received update //Helligkeit
then
///////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"
}
///////Zeiten////////

///////RAUM bestimmen/////
var Trigger_Name = triggeringItem.name
var Raum_Name = ""
//Raum bestimmen
val i = Raumliste.iterator
while (i.hasNext){
    val str = i.next
    if (Trigger_Name.contains(str)){
        Raum_Name = str
    }
}
if (Raum_Name == "") {return;} //Raum nicht in der Raumliste
///////RAUM bestimmen/////
if (Log_An) {logWarn("MOTION", Raum_Name)}
///////PARAMETER//////////
    //Standardwerte
    var Par_Lux_Threshold = 5.0 //lx
    var Par_SP_Brightness = 70.0 //%
    var Par_SP_Temperature = 70.0 //%
    var Par_Timeout_Seconds = 30 //s
    var Par_Deadtime_Manual_Seconds = 20 //Totzeit für Bewegungsmelder nach man. Bedienung
    //Individuelle Werte
    switch (Raum_Name){
        case "Flur":{
            Par_Lux_Threshold = 40.0
            Par_Timeout_Seconds = 10

            switch(Tageszeit){
                case "MORGENS",
                case "NACHTS":{
                    Par_SP_Brightness = 30.0
                    Par_SP_Temperature = 80.0
                }
                case "NACHMITTAG":{
                    Par_SP_Brightness = 100.0
                    Par_SP_Temperature = 60.0 
                }
                default:{
                    Par_SP_Brightness = 0.0
                    Par_SP_Temperature = 60.0                 
                }
            }
        }
        ////Küche
        case "Kueche": {
            Par_Lux_Threshold = 180.0
            Par_Timeout_Seconds = 60

            switch(Tageszeit){
                case "MORGENS",
                case "NACHTS":{
                    Par_SP_Brightness = 50.0
                    Par_SP_Temperature = 80.0
                }
                case "NACHMITTAG":{
                    Par_SP_Brightness = 100.0
                    Par_SP_Temperature = 60.0 
                }
                default:{
                    Par_SP_Brightness = 100.0
                    Par_SP_Temperature = 40.0                 
                }
            }
        }
        ////Küche
        case "Bett": {
            Par_Lux_Threshold = 300.0
            Par_Timeout_Seconds = 0
            Par_Deadtime_Manual_Seconds = 0
            switch(Tageszeit){
                case "MORGENS",
                case "NACHTS":{
                    Par_SP_Brightness = 20.0
                    Par_SP_Temperature = 100.0
                }
                case "NACHMITTAG":{
                    Par_SP_Brightness = 100.0
                    Par_SP_Temperature = 60.0 
                }
                default:{
                    Par_SP_Brightness = 100.0
                    Par_SP_Temperature = 50.0                 
                }
            }
        }
    }

///////PARAMETER//////////
if (Log_An) {logWarn("MOTION", "Parameter bestimmt")}
///////Items bestimmen////
val GenericItem Motion_Sensor = ScriptServiceUtil.getItemRegistry?.getItem(Motion_Sensor_Prefix + Raum_Name + Motion_Sensor_Suffix) as GenericItem 
val GenericItem Lux_Sensor = ScriptServiceUtil.getItemRegistry?.getItem(Lux_Sensor_Prefix + Raum_Name + Lux_Sensor_Suffix) as GenericItem 
val GenericItem Light_Brightness = ScriptServiceUtil.getItemRegistry?.getItem(Light_Prefix + Raum_Name + Light_Suffix_Brightness) as GenericItem 
///////Items bestimmen////
if (Log_An) {logWarn("MOTION", "Items bestimmt")}
///////MOTION AN//////////
if (Motion_Sensor.state == ON){
    if (Log_An) {logWarn("MOTION", "Sensor Aktiv")}
    //Timer canceln, wenn bereits aktiv,da wieder Bewegung
    
    try {
        mylock.lock()
        if (timers.get(Raum_Name) !== null){
                timers.get(Raum_Name).cancel()
                timers.put(Raum_Name, null)   
        }
    }
    catch(Throwable t) {
        logError("Error", "Keine Ahnung was passiert ist..." + t.toString)
        timers.put(Raum_Name, null) 
    }
    finally {
        mylock.unlock() 
    }
    

    if (Log_An) {logWarn("MOTION", "Timer gecancelt")}
    ///////BEDINGUNGEN ZUM ANSCHALTEN////
    //nur wenn Lampen aus
    if (Light_Brightness.state >= 1.0) {
        if (Log_An) {logWarn("MOTION", "BEDINGUNG Licht an nicht OK")}
        return;
    }
    if (Log_An) {logWarn("MOTION", "BEDINGUNG Licht aus OK")}
    //wenn letzter Befehl von Automatik, dann nur wenn letzter Befehl älter als X Sekunden (Da der Sensor bis zu 15 Sekunden lang noch AN senden kann)
    //verhindert, dass das Licht nach dem Ausschalten direkt wieder an geht
    if (last_Update_Type.get(Raum_Name) === null || last_Update_Type.get(Raum_Name).contains("MANU")){
        if (last_Update_Time.get(Raum_Name) !== null && now.minusSeconds(Par_Deadtime_Manual_Seconds).isBefore(last_Update_Time.get(Raum_Name))) {
        
        if (Log_An) {logWarn("MOTION","BEDINGUNG Totzeit nach manuell aus nicht OK")}
        return;
        }
    }
    if (Log_An) {logWarn("MOTION","BEDINGUNG Totzeit nach manuell aus OK")}


    //Helligkeitswert gering genug, um zu aktivieren?
    var Cur_Lux = (Lux_Sensor.state as DecimalType).floatValue
    if (Cur_Lux == UNDEF){ Cur_Lux = (1000.0).floatValue }

    if ((Cur_Lux > Par_Lux_Threshold)) {
        if (Log_An) {logWarn("MOTION","BEDINGUNG Helligkeit nicht OK")}
        return; //nichts tun.
    }
    if (Log_An) {logWarn("MOTION","BEDINGUNG Helligkeit OK")}
    //Wenn Sollhelligkeit quasi 0 ist, ist der Bewegungsmelder auch nicht aktiv.
    if (Par_SP_Brightness <= 0.1){
        if (Log_An) {logWarn("MOTION","BEDINGUNG Sensor Sollhelligkeit >0 nicht OK")}
        return;
    }
    if (Log_An) {logWarn("MOTION","BEDINGUNG Sensor Sollhelligkeit >0 OK")}
    ///////BEDINGUNGEN ZUM ANSCHALTEN////

    ///////AKTION AN//////
    //Rücksetzen der Befehle durch Automatik verhindern
    
    last_Update_Time.put(Raum_Name,now)
    last_Update_Type.put(Raum_Name,"AUTO")
    
    Thread::sleep(100)

    //Ausführen der jeweiligen Aktion
    //Aktion an
    sendCommand(Light_Prefix+Raum_Name+Light_Suffix_Brightness,Par_SP_Brightness.toString())
    createTimer(now.plusMillis(300), [ | sendCommand(Light_Prefix+Raum_Name+Light_Suffix_Temperature,Par_SP_Temperature.toString())])
    createTimer(now.plusMillis(600), [ | sendCommand(Light_Prefix+Raum_Name+Light_Suffix_Brightness,Par_SP_Brightness.toString())])
    createTimer(now.plusMillis(900), [ | sendCommand(Light_Prefix+Raum_Name+Light_Suffix_Temperature,Par_SP_Temperature.toString())])
    if (Log_An) {logWarn("MOTION","AKTION AN")}
    ///////AKTION AN//////
}
///////MOTION AN//////
///////MOTION AUS//////////
else {
    if (Log_An) {logWarn("MOTION", "Sensor Inaktiv")}
    ///////BEDINGUNGEN ZUM AUSCHALTEN/////
    //nur wenn letzte Aktion nicht Manuell war
    if (last_Update_Type.get(Raum_Name) === null || last_Update_Type.get(Raum_Name).contains("MANU")){
        if (Log_An) {logWarn("MOTION","BEDINGUNG letzte Aktion Automatik nicht OK")}
        return;
    }
    if (Log_An) {logWarn("MOTION","BEDINGUNG letzte Aktion Automatik OK")}
    //nur wenn Timer nicht vorhanden
    
    if (timers.get(Raum_Name) !== null) {
        if (Log_An) {logWarn("MOTION","BEDINGUNG Timer noch nicht vorhanden nicht OK")}
        return;
    }
    if (Log_An) {logWarn("MOTION","BEDINGUNG Timer noch nicht vorhanden OK")}
    ///////BEDINGUNGEN ZUM AUSCHALTEN/////

    ///////AKTION AUS//////
    ////Aktion aus
        if (Log_An) {logWarn("MOTION","AKTION AUS TIMER AKTIVIERT")}
        timers.put(Raum_Name, createTimer(now.plusSeconds(Par_Timeout_Seconds), [|
        
        last_Update_Time.put(Raum_Name,now)
        last_Update_Type.put(Raum_Name,"AUTO")
        
        sendCommand(Light_Prefix+Raum_Name+Light_Suffix_Brightness,0.toString())
        if (Log_An) {logWarn("MOTION","AKTION AUS")}
        ]))
    ///////AKTION AUS//////
     
}
///////MOTION AUS//////////
end

rule "Motion Cancel" //bei manuellem Eingriff
when
    Member of gMotion_Licht_Helligkeit changed
    or Member of gMotion_Licht_Farbtemperatur changed
then
///////RAUM bestimmen/////
var Trigger_Name = triggeringItem.name
var Raum_Name = ""
//Raum bestimmen
val i = Raumliste.iterator
while (i.hasNext){
    val str = i.next
    if (Trigger_Name.contains(str)){
        Raum_Name = str
    }
}
if (Raum_Name == "") {return;} //Raum nicht in der Raumliste
///////RAUM bestimmen/////
if (Log_An) {logWarn("MOTIONCANCEL", Raum_Name)}
///////BEDINGUNG ABBRUCH///////

//Bedingung Mindeständerung für Abbruch oder Lampen aus
if (triggeringItem.state == UNDEF ||triggeringItem.state == NULL ){
    return;
}
val Current_Value = (triggeringItem.state as DecimalType).floatValue
if (triggeringItem.name.contains(Light_Suffix_Brightness)){
    if (last_Update_Brightness.get(Raum_Name) === null){
        last_Update_Brightness.put(Raum_Name,Current_Value)
    }

    if (Current_Value+0.1 <= last_Update_Brightness.get(Raum_Name) 
    || Current_Value-0.1 >= last_Update_Brightness.get(Raum_Name)
    || Current_Value <=0.1){
        last_Update_Brightness.put(Raum_Name,Current_Value)
    }
    else{
        if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG Mindestaenderung Helligkeit nicht OK")}
        return;
    }
    if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG Mindestaenderung Helligkeit OK")}
    
}
else{
    if (last_Update_Temperature.get(Raum_Name) === null){
        last_Update_Temperature.put(Raum_Name,Current_Value)
    }

    if (Current_Value+0.1 <= last_Update_Temperature.get(Raum_Name) 
    || Current_Value-0.1 >= last_Update_Temperature.get(Raum_Name)
    || Current_Value <=0.1){
        last_Update_Temperature.put(Raum_Name,Current_Value)
    }
    else{
        if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG Mindestaenderung Farbtemp nicht OK")}
        return;
    }
    if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG Mindestaenderung Farbtemp OK")}
}

//nur wenn letzter Befehl von Automatik
if (last_Update_Type.get(Raum_Name) === null || last_Update_Type.get(Raum_Name).contains("MANU")){
    if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG Timer von Automatik nicht OK")}
    return;
}
if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG Timer von Automatik OK")}
//nur wenn letzter Befehl älter als 5 Sekunden
if (last_Update_Time.get(Raum_Name) !== null && now.minusSeconds(5).isBefore(last_Update_Time.get(Raum_Name))) {
    if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG letzter Befehl alt genug nicht OK")}
    
    return;
}
if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG letzter Befehl alt genug OK")}

///////BEDINGUNG ABBRUCH///////

///////AKTION ABBRUCH///////
//Timer stoppen, wenn aktiv
if (timers.get(Raum_Name) !== null){
    timers.get(Raum_Name)?.cancel()
    timers.put(Raum_Name, null)
}
last_Update_Time.put(Raum_Name,now)
last_Update_Type.put(Raum_Name,"MANU")


if (Log_An) {logWarn("MOTIONCANCEL","MOTION UNTERBROCHEN")}

///////AKTION ABBRUCH///////
end

And this is why ReentrantLocks are dangerous. I’m going to bet that your finally never ran and your lock never got unlocked because of the error. Notice your logError never ran.

Unfortunately, null errors mean lots of different things in Rules DSL. Most often it indicates a type error (e.g. you try to cast a Number Item’s state to Number but the Item’s state is NULL or UNDEF).

You will need to add logging statements to your Rule to figure out which line it’s failing on and then check the value of everything being used on that line to see if something is unexpected.

I also get them without locks, i tried it before…
It fails for example on one of those after the check for null…
timers.get(Raum_Name).cancel()
timers.put(Raum_Name, null

I have no idea why…

Are you certain that Raum_Name is not null at that point?

Honestly, I’ve no idea what is causing the problem but this code is so long and complicated I’m more inclined to fix the whole thing using Rules DSL best practices rather than trying to figure this part out.

import org.eclipse.smarthome.model.script.ScriptServiceUtil
import java.util.Map

val Map<String, Timer> timers = createHashMap
val Map<String, DateTime> last_update_time = createHashMap
val Map<String, String> last_update_type = createHashMap
val Map<String, Number> last_update_brightness = createHashMap
val Map<String, Number> last_update_temperature = createHashMap

val PAR_DEADTIME_MANUAL_SECONDS = 20
val PAR_LUX_THRESHOLD = 5.0 //lx

// Move the time of day stuff to it's own Rule.
rule "Time of Day"
when
    System started or
    Time cron "0 0 10 * * ? *" or
    Time cron "0 0 13 * * ? *" or
    Time cron "0 0 16 * * ? *" or
    Time cron "0 0 22 * * ? *"
then
    val ziet_morgens = now.withTime(10, 0, 0, 0)
    val ziet_mittags = now.withTime(13, 0, 0, 0)
    val ziet_nachmittags = now.withTime(16, 0, 0, 0)
    val ziet_nachts = now.withTime(22, 0, 0, 0)

    var curr_tod = "UNKNOWN"
    switch now {
        case now.isAfter(ziet_morgens) && now.isBefore(ziet_mittags): curr_tod = "MORGENS"
        case now.isAfter(ziet_mittags) && now.isBefore(ziet_nachmittags): curr_tod = "MITTAGS"
        case now.isAfter(ziet_nachmittags) && now.isBefore(ziet_nachts): curr_tod = "NACHMITTAG"
        case now.isAfter(ziet_nachts): curr_tod == "NACHTS"
    }

    if(curr_tod == "UNKNOWN") logError("TOD", "Error calculating time of day!")

    if(TimeOfDay.state.toString != curr_tod){
        logInfo("TOD", "Transitioning time of day to " + curr_tod)
        TimeOfDay.postUpdate(curr_tod)
    }
end

rule "Motion"
when
    Member of gMotion_Sensor_Bewegung changed or
    Member of gMotion_Sensor_Lux received update
then
    // don't see your Item names so I'll assume it follows the pattern <something>_<room name>_<other stuff>
    val raum_name = triggeringItem.name.split("_").get(1)
    val light_name = "gMotion_"+raum_name+"_Licht_Helligkeit"
    if(Log_An) logWarn("MOTION", raum_name)

    // Consider using Items and persistence for last_update stuff. I'll leave them as maps for now
    // Fail fast, both cases exit in this situation, just put the check once at the top
    if(last_update_type.get(raum_name) === null || last_update_type.get(raum_name) == "MANU"){
        if(last_update_time.get(raum_name) !== null && if(now.minusSeconds(PAR_DEADTIME_MANUAL_SECONDS).isBefore(last_update_time.get(raum_name))){
            if(Log_An) logWarn("MOTION", "BEDINGUNG Totzeit nach manuell aus nicht OK")
            return;
        }
    }

    // I recommend putting the Lux and Timeout parameters in Items which will give you the 
    // opportunity to adjust them from your sitemap instead of requiring changing code.
    val par_lux_threshold = ScriptServiceUtil.getItemRegistry.getItem(raum_name+"_Lux")
    val par_timeout_seconds = ScriptServiceUtil.getItemRegistry.getItem(raum_name+"_Timeout_Seconds")

    // Similarly, put the time of day brightness and temps into Items
    val bright_item = ScriptServiceUtil.getItemRegistry.getItem(raum_name+"_"+TimeOfDay.state.toString+"_Brightness")
    val par_sp_brightness = if(bright_item == null) 0 else bright_item.state as Number
    val temp_item = ScriptServiceUtil.getItemRegistry.getItem(raum_name+"_"+TimeOfDay.state.toString+"_Temperature")
    val par_sp_temperature = if(temp_item == null) 60 else temp_item.state as Number

    val motion_sensor = ScriptServiceUtil.getItem.getItemRegistry.getItem("gMotion_"+raum_name+"_Sensor_Bewegung")
    val lux_sensor = ScriptServiceUtil.getItem.getItemRegistry.getItem("gMotion_"+raum_name+"_Sensor_Lux")
    val light_brightness = ScriptServiceUtil.getItemRegistry.getItem(light_name)

    // Motion sensor is ON
    if(motion_sensor.state == ON){
        if(Log_An) logWarn("MOTION", "Sensor Aktiv")
        // Cancel the timer if it exists
        timers.get(raum_name)?.cancel
        timers.put(raum_name, null)

        // Exit if brightness is over 1
        if(light_brightness.state as Number >= 1) {
            if(Log_An) logWarn("MOTION", "BEDINGUNG Licht an nicht OK")
            return;
        }

        // You will get an error if you try to cast an UNDEF to DecimalType. You have to check for UNDEF first
        // Don't use primitives except where necessary
        val cur_lux = if(Lux_Sensor.state instanceof DecimalType) lux_sensor.state as Number else 1000

        // Check if the room is already too bright
        if(cur_lux > PAR_LUX_THRESHOLD) {
            if(Log_An) logWarn("MOTION", "BEDINGUNG Helligkeit nicht OK")
            return;
        }

        // Check that the target brightness is high enough
        if(par_sp_brightness <= 0.1){
            if(Log_An) logWarn("MOTION", "BEDINGUNG Sensor Sollhelligkeit > 0 nicht OK")
            return;
        }

        // Record the update
        last_update_time.put(raum_name, now)
        last_update_type.put(raum_name, "AUTO")

        // why sleep? There is no reason to sleep here.

        // Command the light
        sendCommand(light_name, par_sp_brightness)
        createTimer(now.plusMillis(300), [ | sendCommand("gMotion_"+raum_name+"_Licht_Farbtemperature", par_sp_temperature.toString)
        // Why do you have to sendCommand twice?
    }

    else {
        if(Log_An) logWarn("MOTION", "Sensor Inaktiv")

        if(timers.get(raum_name) !== null){
            if(Log_An) logWarn("MOTION", "BEDINGUNG Timer noch nicht vorhanden nicht OK")
            return;
        }

        if(Log_An) logWarn("MOTION", "AKTOIN AUS TIMER AKTIVIERT")
        timers.put(raum_name, createTimer(now.plusSeconds(par_timeout_seconds.intValue), [ |
            last_update_time.put(raum_name, now)
            last_update_type.put(raum_name, "AUTO")
            sendCommand(light_name, "OFF") // you can send ON/OFF to Dimmer Items
            if(Log_An) logWarn("MOTION", "AKTION AUS")
        ])
    }
end
3 Likes

Wow, this is amazing! I will definitely try it out.
Thank you!

I triggered the lights multiple times, because philips seems to ignore some commands sometimes if it cant senf it to the bulb at that time so the cmd gets lost. In rare cases, the light stays off, has the wrong color or brightness.

By the way, is there a simpler method in my motion cancel rule to have some deadband, by which the item triggers on change?
The brightness sometimes changes like 0.001% by itself, triggers the rule 5 times, and cancels my motion sensor allthough nothing happened. Is there nothing like item x changed by 0.1 or more as trigger?

Theoretically there is something more missing…if motion cancel cancels the timer, and the light goes off, it should reevaluate the rule once after deadtime_seconds for that room Can i somehow trigger an update to an item (the motion sensor) without changing its state? Im afraid writing on to it, because if the motion sensor changes to off right before the state would then be wrong.
The motion sensor only Updates to on if moving once, and off once 16 seconds or so after the last movement. I want the lights to come back on after the deadtime, even if there was motion the whole time (no trigger because the state does not change). Tl;dr i want to push an update to the item without changing its state so the rule gets triggerer on that room.

Thank you so much again!

Some words about EOS: the scaling by sensor is nice, we have that in our Office. But at home i want darker lights at night, and maximum brightness if it is too dark, or just some ambient light. And i want it to go on if the shutters are down as well, etc.
So, the position inside the room is better thsn outside, because it measures brightness where it is needed. The downside is, you can only read it while the lights are off (thats what i do) or implement some regulator with the brightness as feedback. Im not sure if that would be overkill… but it would be the most precise brightness control possible. :slight_smile:

This can be done using the time of day mechanism I explained above.


This can be done by using the shutters open/close as a motion trigger to change the scene being evaluated, also explained above.


This is possible, but how would you measure the feedback? Openhab allows you to connect any kind of light you want, so brightness from each light cannot be known. I cannot think of a way to do this without requiring many adjustments by the user, which I think would make undesirable to use.

Do you mean hysteresis? The only approach I know of is the set of if/elif statements. I’ve submitted a generic implementation of that to the JSR223 Helper Libraries though. https://github.com/openhab-scripters/openhab-helper-libraries/pull/222 For Rules DSL, you just have to do it yourself though.

No. You would have to do this yourself. It’s not hard though.

rule "Brightness changes"
when
    Item MyBrightnessItem changed
then
    val diff = (newState as Number) - (previousState as Number)
    if(diff < 0.001) return;

    // do stuff
end

newState and previousState are implicit variables that only exist for Rules triggered by a changed trigger.

MyItem.postUpdate(MyItem.state)

That will update the Item with it’s current state. If your Rule is triggered with received update the Rule will trigger as a result.

You can determine this experimentally. It would be tedious but possible.