Motion sensor light control and dimming to previous state

Hi everyone,

I would like to set up a rule for my outside lights. I tried to copy, paste and build something based on the advise I found in the forum but I guess I am still too much of a noob to get things right …

My set up:

  • Dimmable light controlled as KNX dimmer thing
  • Motion sensor as a contact thing (via KNX group address)
  • Sun and brightness data via weather station or alternatively astro binding
  • OpenHAB 2.5

This is my objective:

  • The light should go on to a 25% dimm value in the morning at 6:45 if it is dark at that time and go off after sunrise. Same in the evening but from sunset to 11:30 p.m.
  • When the motion sensor is activated during the day it should send an alarm note. When it is dark it should dimm the light up to a 100 percent and then go back to the previous state: Either 25% or 0% or a manually set value.
  • It would be great to be able to override the automatic setting by pressing the light switch, i.e. if the switch was pressed the light will stay on until switched off regardless of motion sensor acitivity

What I did so far:

  • I created (copy pasted and tried to modify) rules I found in other posts
  • I tried to implement the Design Pattern “Time of Day” by @rlkoshak

This is my motion sensor rule - with slow dimming back to the previous state. It works one but I guess the problems start when the motion sensor is activated twice within the three minutes of full brightness. Then the previous state is set to 100% and the light simply stays on.

var Timer OU_Vorgarten_Timer = null
var int percent = 0
var Timer fade_Timer = null
var mem_OU_Podest_Light

rule "BWM Vordach mit Dimmer Memory und Fade"
when Item OU_Vordach_Motion changed to OPEN then
	{ if (OU_Podest_Light.state === null) { mem_OU_Podest_Light = 0 } else {mem_OU_Podest_Light = OU_Podest_Light.state}
     OU_Podest_Light.sendCommand(100)

if	(OU_Vorgarten_Timer === null)
        {
	     OU_Vorgarten_Timer = createTimer(now.plusMinutes(3))
           [ | 
           
        {
            if (OU_Podest_Light.state instanceof DecimalType)
                {
        percent = ((OU_Podest_Light.state as DecimalType)/5).intValue * 5 //round to 5
        fade_Timer = createTimer(now.plusMillis(400)) [|
            OU_Podest_Light.sendCommand(percent)
            if (percent > mem_OU_Podest_Light) {
                percent = percent - 10
                fade_Timer.reschedule(now.plusMillis(500))
                                               }
        ]
                }
        }                      
            OU_Vorgarten_Timer = null ]
	    }
         else	{ OU_Vorgarten_Timer.reschedule(now.plusMinutes(3))
        }	
    }
end

I found forum threads with persistance for dimmer value storage but I didn’t get these solutions to work. I guess one workaround could be to forget about the previous state and go back to either 25% or 0% based on the time of day. Then the remaining challenge would be to override the whole rule when the light is triggered by the switch.

For time/ brightness based activation I set up this rule:

import org.openhab.core.library.types.*
import org.openhab.model.script.actions.*
import org.joda.time.DateTime

var int percent = 0
var Timer fade_Timer = null


rule "Morgenlicht im Vorgarten AN"
	when
		Time cron "0 45 6 ? * MON-FRI *"
	then
		if (now.isBefore((astro_Sonnenaufgang.state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli))
		{
	            OU_Podest_Light.sendCommand(ON)
                OU_Carport_Light.sendCommand(ON)
                OU_FrontYard_Light.sendCommand(50)
		}
end

rule "Morgenlicht im Vorgarten AUS"
	when
		Channel "astro:sun:home:rise#event" triggered END
	then
		OU_Podest_Light.sendCommand(0)
    	        OU_Carport_Light.sendCommand(OFF)
                OU_FrontYard_Light.sendCommand(OFF)
end

rule "Abendlicht im Vorgarten AN"
when
  Channel 'astro:sun:home:civilDusk#event' triggered END
then
     	    if (OU_Podest_Light.state instanceof DecimalType) {
        percent = ((OU_Podest_Light.state as DecimalType)/5).intValue * 5 //round to 5
        fade_Timer = createTimer(now.plusMillis(500)) [|
            OU_Podest_Light.sendCommand(percent)
            if (percent < 40) {
                percent = percent + 5
                fade_Timer.reschedule(now.plusMillis(500))
            }
	     ]
    }	
end

rule "Abendlicht im Vorgarten AUS"
	when
		Time cron "0 30 23 ? * MON-FRI *"
	then
     	    if (OU_Podest_Light.state instanceof DecimalType) {
        percent = ((OU_Podest_Light.state as DecimalType)/5).intValue * 5 //round to 5
        fade_Timer = createTimer(now.plusMillis(500)) [|
            OU_Podest_Light.sendCommand(percent)
            if (percent > 0) {
                percent = percent - 5
                fade_Timer.reschedule(now.plusMillis(500))
            }
        ]
    }
end

And finally this is the Time of day rule from the Design Pattern. I have not implemented it in my rules because I receive this error:

2020-01-08 17:55:28.882 [WARN ] [rest.core.item.EnrichedItemDTOMapper] - Failed transforming the state ‘NULL’ on item ‘vTimeOfDay’ with pattern ‘MAP(weather.map):%s’: An error occurred while opening file.

val logName = "Time Of Day"

rule "Calculate time of day state" 
when
  System started or // run at system start in case the time changed when OH was offline
  Channel 'astro:sun:home:rise#event'    triggered START or
  Channel 'astro:sun:home:set#event'     triggered START or
  Channel 'astro:sun:minus90:set#event'  triggered START or
  Time cron "0 1 0 * * ? *" or // one minute after midnight so give Astro time to calculate the new day's times
  Time cron "0 0 6 * * ? *" or
  Time cron "0 0 23 * * ? *"
then

  logInfo(logName, "Calculating time of day...")

  // Calculate the times for the static tods and populate the associated Items
  // Update when changing static times
  // Jump to tomorrow and subtract to avoid problems at the change over to/from DST
  val morning_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(18)
  vMorning_Time.postUpdate(morning_start.toString) 

  val night_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(1)
  vNight_Time.postUpdate(night_start.toString)

  val bed_start = now.withTimeAtStartOfDay
  vBed_Time.postUpdate(bed_start.toString)

  // Convert the Astro Items to Joda DateTime
  val day_start = new DateTime(vSunrise_Time.state.toString) 
  val evening_start = new DateTime(vSunset_Time.state.toString)
  val afternoon_start = new DateTime(vEvening_Time.state.toString)

  // Calculate the current time of day
  var curr = "UNKNOWN"
  switch now {
  	case now.isAfter(morning_start)   && now.isBefore(day_start):       curr = "MORNING"
  	case now.isAfter(day_start)       && now.isBefore(afternoon_start): curr = "DAY"
  	case now.isAfter(afternoon_start) && now.isBefore(evening_start):   curr = "AFTERNOON"
  	case now.isAfter(evening_start)   && now.isBefore(night_start):     curr = "EVENING"
  	case now.isAfter(night_start):                                      curr = "NIGHT"
  	case now.isAfter(bed_start)       && now.isBefore(morning_start):   curr = "BED"
  }

  // Publish the current state
  logInfo(logName, "Die aktuelle Tageszeit ist: " + curr)
  vTimeOfDay.sendCommand(curr)
end

This is the item for time of day:

String vTimeOfDay "Aktuelle Tageszeit: [MAP(weather.map):%s]" <tod> (Bewegungsmelder)

DateTime vMorning_Time "Morgen [%1$tH:%1$tM]" <sunrise>
DateTime vSunrise_Time "Tag [%1$tH:%1$tM]" <sun> { channel="astro:sun:home:rise#start" }
DateTime vAfternoon_Time "Nachmittag [ %1$tH:%1$tM]" <sunset> { channel="astro:sun:minus90:set#start" }
DateTime vSunset_Time "Abend [%1$tH:%1$tM]" <sunset> { channel="astro:sun:home:set#start" }
DateTime vNight_Time "Nacht [%1$tH:%1$tM]" <moon>
DateTime vBed_Time "Schlaf [%1$tH:%1$tM]" <bedroom_blue>

I guess the experienced users will spot a lot of mistakes. Please let me know how to fix them.

Thank you and best regards, Max

I don’t think you ned persistence for this; you’re only really interested in storing a single value, the existing state just before you do something different to it.
Just copy that to an Item as you send a new command in response to motion.
Have the end-of-motion timer restore the value at end.

Thank you for your idea. Do you think this would not be overwritten and set to 100% if the rule is executed again while the motion sensor activated light is still on?

Yes, but you write your rule to guard against that. Is the light already full on - leave the store alone. Or, is the timer already running - leave the store alone. Or - is the store already populated - leave it alone (but remember to “blank” at end of timed period)