Extended Motion Sensor Rule: any Suggestions?

If I understand correctly, the manual/auto thing is similar to something I do in my rules.
Automated presence detection lighting, with timeouts, etc.
If someone comes along and intervenes with a manual switch - they don’t want that automatic action, thankyou.
So, a “manual off” would need to prohibit the motion triggered auto on - for a period. The user knows best.

My solution has a “Manual mode” Item for each lighting zone, I can detect manual events (I only have on/off so its easier) and set that. “Manual mode” prevents the usual auto-on AND auto-off from taking effect.
“Manual mode itself” expires after a time in the hours range, the effect of that is to allow auto to resume control and also prevent lights left on all night.

The same can be done with area triggers (including the timer), but it sounded to me like the requirement was for the manual override to be automatically detected :face_with_raised_eyebrow:.

Yes, that is what I’m talking about. My rules detect user intervention of fingers on switches. The actual mechanism for me is “unexpected” (from openHABs viewpoint) state changes. The user knows what they want, the auto should be blocked for a while. User doesn’t do anything special, just uses the wallswitch.

Thats right, If i set dome lights manually e.g. color scenes, i want it to stay there until i turn everything off.
The Problem with 100% is, that i have many lights and if i choose to have every lamp in the open living room/kitchen to 80% because i have visitors or so (easily with alexa or dimmer switches or the hue app, which is nicer to set color scenes) i dont want it to turn off after 30 seconds like normally in the kitchen. And i dont want 100 percent on every lamp. That is way too bright. Even in the bedroom 100 percent is way too much for reading, i only use it if i have to sort laundry or so. If i Change something, anything, colors, whatever, i do not want it to turn off after 30s,i do not want to take my phone and set a manual scene, and i want it to behave automaticslly after turning it off. In my opinion, its the easiest way for the user like that,
And i dont have to set it to manual by myself or check all switches etc.

For now, i will just go with my rule i think (if i ever get it to work…), maybe i will have some time in s few months to have a look into the new rule engine.

The hue sensor turns on for at least 15s, and stays on until 15s after the last movement.

Maybe i could change that ober zigbee directly, and thats a step i will take in the future. Porting the motion sensors off the philips hue bridge is the first step. The 15s are not that bad :slight_smile:

And thanks for all your support so far! Im really looking forward to this discussion.

I haven’t looked at your code closely yet, but in my much simpler system (I’ve only switches, no dimmers) I handle this with manual trigger detection and an override flag. If the light is changed manually, that light get’s excluded from all automations until the next time of day period (though that could be adjusted to some other event). In my Rules DSL version the override flag is an Item using Associated Items DP. In my JSR223 version I use metadata.

@openhabber, see Design Pattern: Motion Sensor Timer for an example. With JSR223 I’ve submitted a module that should make managing these timers a bit simpler as well. See https://github.com/openhab-scripters/openhab-helper-libraries/pull/233.

In the OP you asked for something you just need to configure rather than needing to code from scratch. JSR223 is pretty much your only option for that right now which is why we are all pushing that as an approach.

I know you and Scott have seen this, I’m just posting this example for future readers.

from core.rules import rule
from core.triggers import when
from time import sleep
from core.metadata import get_key_value, set_metadata
from core.actions import PersistenceExtensions
from org.joda.time import DateTime

@rule("Cloudy Lights",
      description="Turns ON or OFF some lights when it is cloudy during the day",
      tags=["lights"])
@when("Item vIsCloudy changed")
@when("Item vTimeOfDay changed")
def cloudy_lights(event):
    if (items["vTimeOfDay"] != StringType("DAY") or
        isinstance(items["vIsCloudy"], UnDefType)):
        return

    if event.itemName == "vTimeOfDay":
        sleep(0.5)

    cloudy_lights.log.info("It is {} and cloudy changed: {}"
                           .format(items["vTimeOfDay"], items["vIsCloudy"]))

    for light in [light for light in ir.getItem("gLights_ON_WEATHER").members if get_key_value(light.name, "Flags", "override") != ON]:
#   for light in filter(lambda light: get_key_value(light.name, "Flags", "override") != "ON", ir.getItem("gLights_ON_WEATHER").members):
        if items[light.name] != items["vIsCloudy"]:
            events.sendCommand(light, items["vIsCloudy"])

@rule("Lights Override",
      description=("Sets the Override flag when a light is manually changed "
                   "during the day"),
      tags=["lights"])
@when("Member of gLights_ON_WEATHER changed")
def override_lights(event):
    sleep(0.5) # Give pesistence a chance to catch up

    # Wait a minute before reacting after vTimeOfDay changes, ignore all other
    # times of day.
    if (items["vTimeOfDay"] != StringType("DAY") or
        (PersistenceExtensions.lastUpdate(ir.getItem("vTimeOfDay"), "mapdb")
            .isAfter(DateTime.now().minusSeconds(10)))):
        return

    if (not PersistenceExtensions.lastUpdate(ir.getItem("vIsCloudy"), "mapdb")
            .isAfter(DateTime.now().minusSeconds(5))):
        override_lights.log.info("Manual light trigger detected, overriding the"
                                 " light for auto control for {}."
                                 .format(event.itemName)))
        set_metadata(event.itemName, "Flags", { "override" : "ON" },
                     overwrite=False)

@rule("Reset Override",
      description="Change override flag when time of day changes",
      tags=["lights"])
@when("Item vTimeOfDay changed")
def reset_overrides(event):
    for light in ir.getItem("gLights_ON_WEATHER").members:
        set_metadata(light.name, "Flags", { "override" : "OFF" },
                     overwrite=False)

The Rules DSL version is a combination of Design Pattern: State Machine Driven Groups and Design Pattern: Manual Trigger Detection.

Hey everyone,

it seems to work now like i wanted, hurraay :smiley:

im giving you all code if somebody is interested. If you have some more suggestions, feel free =)

Motion.rules

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

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 PAR_DEADTIME_MANUAL_SECONDS = 20

val Log_An = false
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 Zeit_morgens = now.withTime(10, 0, 0, 0)
    val Zeit_mittags = now.withTime(13, 0, 0, 0)
    val Zeit_nachmittags = now.withTime(16, 0, 0, 0)
    val Zeit_nachts = now.withTime(22, 0, 0, 0)

    var curr_TOD = "UNKNOWN"
    switch now {
        case now.isAfter(Zeit_morgens) && now.isBefore(Zeit_mittags): curr_TOD = "MORGENS"
        case now.isAfter(Zeit_mittags) && now.isBefore(Zeit_nachmittags): curr_TOD = "MITTAGS"
        case now.isAfter(Zeit_nachmittags) && now.isBefore(Zeit_nachts): curr_TOD = "NACHMITTAG"
        default: curr_TOD = "NACHTS"
    }

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

rule "Motion"
when
    Member of gMotion_Sensor_Bewegung changed or
    Member of gMotion_Sensor_Lux received update
then
    // Raum bestimmen
    val raum_name = triggeringItem.name.split("_").get(1)
    val light_name = "gMotion_"+raum_name+"_Licht_Helligkeit"
    if(Log_An) logWarn("MOTION", raum_name)

 val motion_sensor = ScriptServiceUtil.getItemRegistry.getItem("gMotion_"+raum_name+"_Sensor_Bewegung")

    //wenn letzter Befehl von Manuell, 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. Wenn Manuell und Sensor ist inaktiv, dann ist auch nichts zu tun.
    if(last_update_type.get(raum_name) === null || last_update_type.get(raum_name) == "MANU"){       
        if(motion_sensor.state ==OFF){
            if(Log_An) logWarn("MOTION", "BEDINGUNG Bewegungsmelder aus und letzter Befehl manuell nicht OK")
            return;  
        }   

        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;
        }
    }

    // Parameter
    val par_lux_threshold = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_SP_Lux")
    val par_timeout_seconds = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_SP_Timeout")

    val bright_item = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_"+Time_of_Day.state.toString+"_Helligkeit")
    val par_sp_Helligkeit = if(bright_item === null) 0 else bright_item.state as Number
    val temp_item = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_"+Time_of_Day.state.toString+"_Farbtemperatur")
    val par_sp_Farbtemperatur = if(temp_item === null) 60 else temp_item.state as Number

   
    val lux_sensor = ScriptServiceUtil.getItemRegistry.getItem("gMotion_"+raum_name+"_Sensor_Lux")
    val light_Helligkeit = 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
        logWarn("MOTION", raum_name)
        if (timers.get(raum_name) !== null){
            timers.get(raum_name)?.cancel()
            timers.remove(raum_name)
        }
        // Exit wenn Licht an
        if(light_Helligkeit.state as Number >= 1) {
            if(Log_An) logWarn("MOTION", "BEDINGUNG Licht an nicht OK")
            return;
        }

        // check for UNDEF first
        val cur_lux = if(lux_sensor.state instanceof DecimalType) lux_sensor.state as Number else 1000.0
        val SP_lux = if(par_lux_threshold.state instanceof DecimalType) par_lux_threshold.state as Number else 0.0
        // Check if the room is already too bright
        if(cur_lux > SP_lux) {
            if(Log_An) logWarn("MOTION", "BEDINGUNG Helligkeit nicht OK")
            return;
        }

        // Check that the target Helligkeit is high enough
        if(par_sp_Helligkeit <= 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")

        // Command the light
        sendCommand(light_name, par_sp_Helligkeit.toString)
        createTimer(now.plusMillis(1000), [ | sendCommand("gMotion_"+raum_name+"_Licht_Farbtemperatur", par_sp_Farbtemperatur.toString)])
        //sendCommand("gMotion_"+raum_name+"_Licht_Farbtemperatur", par_sp_Farbtemperatur.toString)
        //createTimer(now.plusMillis(1000), [ | sendCommand(light_name, par_sp_Helligkeit.toString)]) 
        //createTimer(now.plusMillis(1500), [ | sendCommand("gMotion_"+raum_name+"_Licht_Farbtemperatur", par_sp_Farbtemperatur.toString)])
        //createTimer(now.plusMillis(2000), [ | sendCommand(light_name, par_sp_Helligkeit.toString)]) 
    }

    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;
        }

        var par_timeout = if(par_timeout_seconds.state instanceof DecimalType) (par_timeout_seconds.state as Number).intValue else 30
        if(Log_An) logWarn("MOTION", "AKTION AUS TIMER AKTIVIERT")
        timers.put(raum_name, createTimer(now.plusSeconds(par_timeout), [ |
            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


rule "Motion Cancel" //bei manuellem Eingriff
when
    Member of gMotion_Licht_Helligkeit changed
    or Member of gMotion_Licht_Farbtemperatur changed
then

//Raum bestimmen
val raum_name = triggeringItem.name.split("_").get(1)
if (Log_An) {logWarn("MOTIONCANCEL", raum_name)}
val new_State = triggeringItem.state as Number
val diff = (new_State) - (previousState as Number)
if(diff < 0.1 && diff>-0.1 && new_State != 0.0) {
    if (Log_An) {logWarn("MOTIONCANCEL","BEDINGUNG Mindestaenderung Helligkeit nicht OK")}
    return;
}

//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 letzte Aenderung von Automatik nicht OK")}
    return;
}

//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;
}

//Aktion
//Timer stoppen, wenn aktiv
if (timers.get(raum_name) !== null){
    timers.get(raum_name)?.cancel()
    timers.remove(raum_name)
}
last_update_time.put(raum_name,now)
last_update_type.put(raum_name,"MANU")

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

end

Motion.items

//Global
String Time_of_Day "Tageszeit"

//Overall Groups
Group: Dimmer:AVG gMotion_Licht_Helligkeit
Group: Dimmer:AVG gMotion_Licht_Farbtemperatur
Group: Switch:OR(ON,OFF) gMotion_Sensor_Bewegung
Group: Number:AVG gMotion_Sensor_Lux

//Flur
Group: Dimmer:AVG gMotion_Flur_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Flur_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Flur_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Flur_Sensor_Lux (gMotion_Sensor_Lux)

Number Motion_Flur_SP_Lux "Schwellwert Helligkeit Flur (lx)" (gMotion_SP_Lux)
Number Motion_Flur_SP_Timeout "Timeout Flur (s)" (gMotion_SP_Timeout)

Dimmer Motion_Flur_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Flur_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Flur_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Flur_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Flur_NACHMITTAG_Helligkeit "Helligkeit nachmittags"
Dimmer Motion_Flur_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Flur_NACHTS_Helligkeit "Helligkeit nachts"
Dimmer Motion_Flur_NACHTS_Farbtemperatur "Farbtemperatur nachts"

//Küche
Group: Dimmer:AVG gMotion_Kueche_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Kueche_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Kueche_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Kueche_Sensor_Lux (gMotion_Sensor_Lux)

Number Motion_Kueche_SP_Lux "Schwellwert Helligkeit Küche (lx)" (gMotion_SP_Lux)
Number Motion_Kueche_SP_Timeout "Timeout Küche (s)" (gMotion_SP_Timeout)

Dimmer Motion_Kueche_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Kueche_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Kueche_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Kueche_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Kueche_NACHMITTAG_Helligkeit "Helligkeit nachmittags"
Dimmer Motion_Kueche_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Kueche_NACHTS_Helligkeit "Helligkeit nachts"
Dimmer Motion_Kueche_NACHTS_Farbtemperatur "Farbtemperatur nachts"

//Bett
Group: Dimmer:AVG gMotion_Bett_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Bett_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Bett_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Bett_Sensor_Lux (gMotion_Sensor_Lux)

Number Motion_Bett_SP_Lux "Schwellwert Helligkeit Bett (lx)" (gMotion_SP_Lux)
Number Motion_Bett_SP_Timeout "Timeout Bett (s)" (gMotion_SP_Timeout)

Dimmer Motion_Bett_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Bett_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Bett_MITTAGS_Helligkeit "Helligkeit mittags" 
Dimmer Motion_Bett_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Bett_NACHMITTAG_Helligkeit "Helligkeit nachmittags" 
Dimmer Motion_Bett_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags" 
Dimmer Motion_Bett_NACHTS_Helligkeit "Helligkeit nachts"
Dimmer Motion_Bett_NACHTS_Farbtemperatur "Farbtemperatur nachts"

//Schlafzimmer
Group: Dimmer:AVG gMotion_Schlafzimmer_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Schlafzimmer_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Schlafzimmer_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Schlafzimmer_Sensor_Lux (gMotion_Sensor_Lux)
Group Motion_Schlafzimmer_SP_Helligkeit "Bewegungsmelder Schlafzimmer Helligkeit" (gMotion_SP_Helligkeit)
Group Motion_Schlafzimmer_SP_Farbtemperatur "Bewegungsmelder Schlafzimmer Farbtemperatur" (gMotion_SP_Farbtemperatur)

Number Motion_Schlafzimmer_SP_Lux "Schwellwert Helligkeit Schlafzimmer (lx)" (gMotion_SP_Lux)
Number Motion_Schlafzimmer_SP_Timeout "Timeout Schlafzimmer (s)" (gMotion_SP_Timeout)

Dimmer Motion_Schlafzimmer_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Schlafzimmer_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Schlafzimmer_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Schlafzimmer_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Schlafzimmer_NACHMITTAG_Helligkeit "Helligkeit nachmittags" 
Dimmer Motion_Schlafzimmer_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Schlafzimmer_NACHTS_Helligkeit "Helligkeit nachts" 
Dimmer Motion_Schlafzimmer_NACHTS_Farbtemperatur "Farbtemperatur nachts"

//Dekoration
Group: Dimmer:AVG gMotion_Dekoration_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Dekoration_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Dekoration_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Dekoration_Sensor_Lux (gMotion_Sensor_Lux)
Group Motion_Dekoration_SP_Helligkeit "Bewegungsmelder Dekoration Helligkeit" (gMotion_SP_Helligkeit)
Group Motion_Dekoration_SP_Farbtemperatur "Bewegungsmelder Dekoration Farbtemperatur" (gMotion_SP_Farbtemperatur)

Number Motion_Dekoration_SP_Lux "Schwellwert Helligkeit Dekoration (lx)" (gMotion_SP_Lux)
Number Motion_Dekoration_SP_Timeout "Timeout Dekoration (s)" (gMotion_SP_Timeout)

Dimmer Motion_Dekoration_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Dekoration_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Dekoration_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Dekoration_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Dekoration_NACHMITTAG_Helligkeit "Helligkeit nachmittags" 
Dimmer Motion_Dekoration_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Dekoration_NACHTS_Helligkeit "Helligkeit nachts" 
Dimmer Motion_Dekoration_NACHTS_Farbtemperatur "Farbtemperatur nachts"

For anyone interested:

my rule did not work as intended, because the hue binding is not very reliable with states. States did change to random values because they were reported while the lamp was dimming up, and like 2 Minutes later the right value was reported and the rule thought is was a manual change, which stopped the motion sensor from working.

I simplified many, many things, and it nearly works like it does before. Difference is, the override of the motion sensors does not happen automatically on change, because it does not work with hue. But i have the option to disable it per room in the sitemap, and a global setting, if the mode should switch to Automatic on lights off.
If i press my dimmer switches, they also change the mode to Manual.

Here are the updated versions:
items:

//Global
String Time_of_Day "Tageszeit [%s]"
Switch cMotion_Auto_Switch_if_Off "Bew. Meld. automatisch an bei Licht aus"

//Overall Groups
Group: Dimmer:AVG gMotion_Licht_Helligkeit
Group: Dimmer:AVG gMotion_Licht_Farbtemperatur
Group: Switch:OR(ON,OFF) gMotion_Sensor_Bewegung
Group: Number:AVG gMotion_Sensor_Lux
Group: Switch:OR(ON,OFF) gMotion_Mode //On=AUTO

//Flur
Group: Dimmer:AVG gMotion_Flur_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Flur_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Flur_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Flur_Sensor_Lux (gMotion_Sensor_Lux)

Switch gMotion_Flur_Mode (gMotion_Mode)

Number Motion_Flur_SP_Lux "Schwellwert Helligkeit Flur (lx)" (gMotion_SP_Lux)
Number Motion_Flur_SP_Timeout "Timeout Flur (s)" (gMotion_SP_Timeout)

Dimmer Motion_Flur_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Flur_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Flur_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Flur_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Flur_NACHMITTAG_Helligkeit "Helligkeit nachmittags"
Dimmer Motion_Flur_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Flur_NACHTS_Helligkeit "Helligkeit nachts"
Dimmer Motion_Flur_NACHTS_Farbtemperatur "Farbtemperatur nachts"

//Küche
Group: Dimmer:AVG gMotion_Kueche_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Kueche_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Kueche_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Kueche_Sensor_Lux (gMotion_Sensor_Lux)

Switch gMotion_Kueche_Mode (gMotion_Mode)

Number Motion_Kueche_SP_Lux "Schwellwert Helligkeit Küche (lx)" (gMotion_SP_Lux)
Number Motion_Kueche_SP_Timeout "Timeout Küche (s)" (gMotion_SP_Timeout)

Dimmer Motion_Kueche_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Kueche_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Kueche_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Kueche_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Kueche_NACHMITTAG_Helligkeit "Helligkeit nachmittags"
Dimmer Motion_Kueche_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Kueche_NACHTS_Helligkeit "Helligkeit nachts"
Dimmer Motion_Kueche_NACHTS_Farbtemperatur "Farbtemperatur nachts"


//Zusammenfassung ganzes Schlafzimmer
Group: Switch:OR(ON,OFF) gMotion_SchlafzimmerGesamt_Mode

//Bett
Group: Dimmer:AVG gMotion_Bett_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Bett_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Bett_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Bett_Sensor_Lux (gMotion_Sensor_Lux)

Switch gMotion_Bett_Mode (gMotion_Mode,gMotion_SchlafzimmerGesamt_Mode)

Number Motion_Bett_SP_Lux "Schwellwert Helligkeit Bett (lx)" (gMotion_SP_Lux)
Number Motion_Bett_SP_Timeout "Timeout Bett (s)" (gMotion_SP_Timeout)

Dimmer Motion_Bett_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Bett_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Bett_MITTAGS_Helligkeit "Helligkeit mittags" 
Dimmer Motion_Bett_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Bett_NACHMITTAG_Helligkeit "Helligkeit nachmittags" 
Dimmer Motion_Bett_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags" 
Dimmer Motion_Bett_NACHTS_Helligkeit "Helligkeit nachts"
Dimmer Motion_Bett_NACHTS_Farbtemperatur "Farbtemperatur nachts"



//Schlafzimmer
Group: Dimmer:AVG gMotion_Schlafzimmer_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Schlafzimmer_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Schlafzimmer_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Schlafzimmer_Sensor_Lux (gMotion_Sensor_Lux)

Switch gMotion_Schlafzimmer_Mode (gMotion_Mode,gMotion_SchlafzimmerGesamt_Mode)

Number Motion_Schlafzimmer_SP_Lux "Schwellwert Helligkeit Schlafzimmer (lx)" (gMotion_SP_Lux)
Number Motion_Schlafzimmer_SP_Timeout "Timeout Schlafzimmer (s)" (gMotion_SP_Timeout)

Dimmer Motion_Schlafzimmer_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Schlafzimmer_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Schlafzimmer_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Schlafzimmer_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Schlafzimmer_NACHMITTAG_Helligkeit "Helligkeit nachmittags" 
Dimmer Motion_Schlafzimmer_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Schlafzimmer_NACHTS_Helligkeit "Helligkeit nachts" 
Dimmer Motion_Schlafzimmer_NACHTS_Farbtemperatur "Farbtemperatur nachts"

//Dekoration
Group: Dimmer:AVG gMotion_Dekoration_Licht_Helligkeit (gMotion_Licht_Helligkeit)
Group: Dimmer:AVG gMotion_Dekoration_Licht_Farbtemperatur (gMotion_Licht_Farbtemperatur)
Group: Switch:OR(ON,OFF) gMotion_Dekoration_Sensor_Bewegung (gMotion_Sensor_Bewegung)
Group: Number:AVG gMotion_Dekoration_Sensor_Lux (gMotion_Sensor_Lux)

Switch gMotion_Dekoration_Mode (gMotion_Mode)

Number Motion_Dekoration_SP_Lux "Schwellwert Helligkeit Dekoration (lx)" (gMotion_SP_Lux)
Number Motion_Dekoration_SP_Timeout "Timeout Dekoration (s)" (gMotion_SP_Timeout)

Dimmer Motion_Dekoration_MORGENS_Helligkeit "Helligkeit morgens"
Dimmer Motion_Dekoration_MORGENS_Farbtemperatur "Farbtemperatur morgens"
Dimmer Motion_Dekoration_MITTAGS_Helligkeit "Helligkeit mittags"
Dimmer Motion_Dekoration_MITTAGS_Farbtemperatur "Farbtemperatur mittags"
Dimmer Motion_Dekoration_NACHMITTAG_Helligkeit "Helligkeit nachmittags" 
Dimmer Motion_Dekoration_NACHMITTAG_Farbtemperatur "Farbtemperatur nachmittags"
Dimmer Motion_Dekoration_NACHTS_Helligkeit "Helligkeit nachts" 
Dimmer Motion_Dekoration_NACHTS_Farbtemperatur "Farbtemperatur nachts"

rules:

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

val Hashtable<String, Timer> timers = new Hashtable<String, Timer>()

val Log_An = true

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 Zeit_morgens = now.withTime(10, 0, 0, 0)
    val Zeit_mittags = now.withTime(13, 0, 0, 0)
    val Zeit_nachmittags = now.withTime(16, 0, 0, 0)
    val Zeit_nachts = now.withTime(22, 0, 0, 0)

    var curr_TOD = "UNKNOWN"
    switch now {
        case now.isAfter(Zeit_morgens) && now.isBefore(Zeit_mittags): curr_TOD = "MORGENS"
        case now.isAfter(Zeit_mittags) && now.isBefore(Zeit_nachmittags): curr_TOD = "MITTAGS"
        case now.isAfter(Zeit_nachmittags) && now.isBefore(Zeit_nachts): curr_TOD = "NACHMITTAG"
        default: curr_TOD = "NACHTS"
    }

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

rule "Motion"
when
    Member of gMotion_Sensor_Bewegung changed
then
    // Raum bestimmen
    val raum_name = triggeringItem.name.split("_").get(1)
    if(Log_An) logWarn("MOTION", raum_name)

    val current_Mode = ScriptServiceUtil.getItemRegistry.getItem("gMotion_"+raum_name+"_Mode")    
   
    // Motion sensor changed to ON
    if(triggeringItem.state == ON){

        // Parameter
        val par_lux_threshold = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_SP_Lux")
        val bright_item = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_"+Time_of_Day.state.toString+"_Helligkeit")
        val par_sp_Helligkeit = if(bright_item === null) 0 else bright_item.state as Number
        val temp_item = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_"+Time_of_Day.state.toString+"_Farbtemperatur")
        val par_sp_Farbtemperatur = if(temp_item === null) 60 else temp_item.state as Number

        if(Log_An) logWarn("MOTION", "Sensor Aktiv")
        // Cancel the timer if it exists
        logWarn("MOTION", raum_name)
        if (timers.get(raum_name) !== null){
            timers.get(raum_name)?.cancel()
            timers.remove(raum_name)
        }
        // Exit wenn Licht an
        val light_Helligkeit = ScriptServiceUtil.getItemRegistry.getItem("gMotion_"+raum_name+"_Licht_Helligkeit")
        if(light_Helligkeit.state as Number >= 0.1) {
            if(Log_An) logWarn("MOTION", "BEDINGUNG Licht an nicht OK")
            return;
        }

        //Licht ist aus, wir wechseln zur Automatik wenn das Licht aus ist.
        if (current_Mode.state == OFF){
            //nur wenn aktiv 
            if (cMotion_Auto_Switch_if_Off.state == OFF) return;
            sendCommand("gMotion_"+raum_name+"_Mode","ON")
        }

        val lux_sensor = ScriptServiceUtil.getItemRegistry.getItem("gMotion_"+raum_name+"_Sensor_Lux")
        val cur_lux = if(lux_sensor.state instanceof DecimalType) lux_sensor.state as Number else 1000.0
        val SP_lux = if(par_lux_threshold.state instanceof DecimalType) par_lux_threshold.state as Number else 0.0
        // Check if the room is already too bright
        if(cur_lux > SP_lux) {
            if(Log_An) logWarn("MOTION", "BEDINGUNG Helligkeit nicht OK")
            return;
        }

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

        // Command the light
        createTimer(now.plusMillis(100), [ | 
		Thread::sleep(100)
        sendCommand("gMotion_"+raum_name+"_Licht_Helligkeit", par_sp_Helligkeit.toString)])

        createTimer(now.plusMillis(1000), [ | 
		Thread::sleep(100)
        sendCommand("gMotion_"+raum_name+"_Licht_Farbtemperatur", par_sp_Farbtemperatur.toString)])

    }

    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;
        }

        //wenn Modus Manuell, dann return
        if(current_Mode.state == OFF) return;

        //Parameter
        val par_timeout_seconds = ScriptServiceUtil.getItemRegistry.getItem("Motion_"+raum_name+"_SP_Timeout")
        var par_timeout = if(par_timeout_seconds.state instanceof DecimalType) (par_timeout_seconds.state as Number).intValue else 30
        if(Log_An) logWarn("MOTION", "AKTION AUS TIMER AKTIVIERT")
        timers.put(raum_name, createTimer(now.plusSeconds(par_timeout), [ |
			Thread::sleep(100)
            sendCommand("gMotion_"+raum_name+"_Licht_Helligkeit", "OFF") 
			Thread::sleep(500)
            sendCommand("gMotion_"+raum_name+"_Licht_Helligkeit", "OFF") 
            if(Log_An) logWarn("MOTION", "AKTION AUS")
        ]))
    }
end


rule "Motion Mode" //bei Moduswechsel zu Manual Timer löschen
when
    Member of gMotion_Mode changed
then

//Raum bestimmen
val raum_name = triggeringItem.name.split("_").get(1)
if (Log_An) {logWarn("MOTIONMODE", raum_name)}

//Mode auf AUTO gewechselt
if (triggeringItem.state == OFF && timers.get(raum_name) !== null){
        if (Log_An) {logWarn("MOTIONMODE","Manueller Modus gewählt, Timer wird gelöscht")}
        timers.get(raum_name)?.cancel()
        timers.remove(raum_name)
}
end```