Automatic lighting control without timers

rules
persistence
light
Tags: #<Tag:0x00007fd31125fb48> #<Tag:0x00007fd31125f940> #<Tag:0x00007fd31125f6e8>

(Alex Forrow) #1

I’ve tried a few methods for doing automatic lighting control based on motion and contact sensors, and I’ve recently found a pattern for my rules that is working really well. It makes it simpler (but possibly not shorter). Maybe people are already doing this, either way very I’m interested to know thoughts.

The requirements are relatively simple: if certain conditions are met, turn the light on, otherwise turn it off. However evaluating those conditions can get complicated. Ordering in particular is a problem.

So instead my approach is:

  • Work out what inputs will affect the light, e.g. darkness, motion, contact
  • Rely on a persistence addon to handle cool-off periods (e.g. any motion in last X minutes)
  • Express the desired result as a single, clear if statement (e.g. isDark && (recent motion || recent door open))
  • Wrap it in rule that will evaluate this condition should any input change, plus trigger on a cron to reevaluate coof-off
  • Detect manual overrides, store as an item, and use as an additional input
var OnOffType expected_kitchen_table

rule "Kitchen table light control"
when
    Item Motion_Kitchen_Table received update or    
    Item Contact_Kitchen_Door received update or
    Item Dark_Indoors received update or
    Item Light_Kitchen_Override received update or
    Item Mode_Auto_Lights received update or
    Time cron "*/10 * * * * ?"
then
    // do nothing unless automatic lighting mode is enabled
    if (Mode_Auto_Lights.state == ON) {
        // if light has been overridden, simply set to overridden value
        if (Light_Kitchen_Override.state !== UNDEF && Light_Kitchen_Override.state !== NULL) {
            expected_kitchen_table = (Light_Kitchen_Override.state as OnOffType)
        } else {
            /* heart of the system - evaluate layers to determine if light should be on
             * 1. is it dark, AND
             * 2. motion detected recently, OR
             * 3. door open, or opened recently
             */
            if  (   (Dark_Indoors.state == ON) &&
                    (((Motion_Kitchen_Table.state == ON) || (Motion_Kitchen_Table.lastUpdate() as DateTime).plusMinutes(4).isAfter(now)) ||
                    ((Contact_Kitchen_Door.state == OPEN) || (Contact_Kitchen_Door.lastUpdate() as DateTime).plusMinutes(2).isAfter(now))
                    )) {
                expected_kitchen_table = ON
            } else {
                expected_kitchen_table = OFF
            }
        }

        // update actual item if different from expected
        if (Light_Kitchen_Table.state != expected_kitchen_table) {
            sendCommand(Light_Kitchen_Table, expected_kitchen_table)
        }
    }
end

/* Overrides can be detected simply by looking for updates to state that don't match
 * expected, previously evaluated value */
rule "Light_Kitchen_Table override detection"
when
    Item Light_Kitchen_Table received update
then
    if (Light_Kitchen_Table.state != expected_kitchen_table) {
        logInfo("override_kitchen", "Override detected using table switch")
        Light_Kitchen_Override.postUpdate(Light_Kitchen_Table.state)
    }
end

/* reset override item back to UNDEF once a reasonable amount of time has passed
 * I've recently switched from using expire binding because a) it works across restarts
 * and b) I can have different times depending if it was overridden to ON or OFF */
rule "Light_Kitchen_Table override reset"
when
    Time cron "*/10 * * * * ?"
then
    // is light overridden?
    if (Light_Kitchen_Override.state !== UNDEF && Light_Kitchen_Override.state !== NULL) {
        val DateTime lastUpdate = (Light_Kitchen_Override.lastUpdate() as DateTime)

        // if turned ON manually, reset to automatic after 10 minutes
        if (Light_Kitchen_Override.state == ON && lastUpdate.plusMinutes(10).isBefore(now)) {
            logInfo("override_kitchen", "ON override reset")
            Light_Kitchen_Override.postUpdate(UNDEF)
        }

        // if turned OFF manually, reset to automatic after 2 minutes
        if (Light_Kitchen_Override.state == OFF && lastUpdate.plusMinutes(2).isBefore(now)) {
            logInfo("override_kitchen", "OFF override reset")
            Light_Kitchen_Override.postUpdate(UNDEF)
        }
    }
end

With this system I have lights turning on automatically around my house based on various inputs, with no concern over what order they occur in. I can have multiple lights using the same inputs if required.

One caveat is that the persistence addon that stores the Override items must support storing UNDEF values. MapDB, for example, does not. I’m currently using a simple JSON on disk persistence addon I put together.

Let me know thoughts :slight_smile:


(YF) #2

Alex,

That works obviously but the logic becomes very specific. IMO, to control light there are three elements: the light switch itself, the triggering event and the timer to shut off. The triggering event is used to turn on the light and to renew the timer. Note the second part of the sentence; that essentially means that once you configure the timer, you only need to concern about the triggering event. It simplifies the requirement.

Now, for triggering event, in most cases they are motion sensors. However you can create a group of similar triggering events. E.g. I have a FF_Foyer_Motion_Sensor item; it is actually a Group but it is named that way for consistency. I don’t have a motion sensor yet, but I route the garage and front doors tripped events to that group. Later, when I have motion sensor, I will add it there as well. For your case, you can do the same thing. IMO, the key thing is to make the rules/items generic enough to lower your effort in adding new lights.

FYI, here are my current lights rules. All my rules do not require a persistence mechanism.


(Rich Koshak) #3

You can save an indent by return if the automode switch is off

if (Mode_Auto_Lights.state != ON) return;

Since you have three states to track for your override, why not just use a Number Item which will work with all of the persistence engines. Relying on a third party thrown together engine is a non-starter for most of us.

I’m also with YF. I’d like to see thisa little more generic. For example, the
Second two rules are likely to be identical for all your lights. Why not make them generic using something like Associated Items DP?

Similarly I can see some ways to make the first time more generic too which would let you add/remove lights simply through Group membership.


(Alex Forrow) #4

Thanks the feedback both!

I certainly agree that not being generic is a problem. There is definitely some quick improvements that could be made, particularly in regards to override detection and reset rules.

In regards to the core rule however, I possibly provided a bad example. I have other rooms that have dimmers and more complex logic around considering whether the house is in day/evening/sleep mode - I think making a generic rule for these would be more trouble that it’s worth - I’ll do some experimenting.

TBH I didn’t know of the pattern of replacing createTimer with the expire binding as an option. Isn’t there an issue if OpenHAB reloads/restarts while these are set ON? Will they still trigger?

In regards to the custom persistence service, I intend to rewrite as a OH2 addon and submit a Pull Request. I still feel having a usable NULL/UNDEF value is important.


(Rich Koshak) #5

You’d have the same problem with Timers. The fix for Expire timers is easy though:

Group:Switch ExpireTimers

Switch MyExpireTimer (ExpireTimers)
rule "Reset Expire Timers"
when
    System started
then
    ExpireTimers.members.forEach[ timer | timer.sendCommand(timer.state) ]
end

Requires the members of ExpireTimers to be saved to persistence and restored on startup.

Like I said. If this approach is only going to work with one persistence engine, even one that eventually becomes an official one, will be a non-starter for the majority of users.

And given that UNDEF/NULL are intended to represent

situations when item states do not have any defined value.

it seems to me that the correct behavior is to NOT save these states to persistence. But I’m not a maintainer so my opinion doesn’t count for much.

It also raises a question. Items get initialized to NULL which occurs when OH starts, the .items file gets changed, or you create the Item through the REST API (e.g. PaperUI). Should these NULL states be saved to persistence? It doesn’t seem correct to me to end up with a NULL value in persistence every time that I change my .items file.

Then there is the question about ordering the operations. For example, if you save a .items file and all those Items go to NULL, then the NULL gets saved to the DB, and then the NULL is what gets restored to the Item during restoreOnStartup instead of the value it had before the .items file was saved.

Then there is the challenge of how to deal with the persistence methods. For example, how should NULL be handled in a sumSince call? Just skip over the NULLs? Treat it as 0? Each one of the calls (minimumSince, averageSince, etc) have their own questions.

All of these questions are answerable, but the issue isn’t just one of saving the NULLs to the DB. Doing so effects pretty much everything that persistence does.


(YF) #6

@AlexForrow
Rich already addresses other points; I just want to touch on the generic aspect. One pattern that some people including myself use is the usage of common prefix in the item name. That mechanism allow you to define OPTIONAL attributes related to the light switch.

E.g.: if Kitchen_LightSwitch is the main item, you can have the following optional items:

  • Kitchen_LightSwitch_MotionSensor
  • Kitchen_LightSwitch_Illuminance
  • Kitchen_LightSwitch_TurnOffOtherLight
    and so on.

You can add more attributes as they become available. The rule continues to stay generic by making heavy use of groups (e.g. you have group of motion sensors, group of illuminance sensor,…) to determine if a capability is present for a each light switch. As you have additional dimension, the generic logic gets updated in such a way that old light switches that do not have the new capability continue to function. This is the mechanism I use to add illumiance sensor to one of my light switches 15 minutes ago.

As another example, say you would like to add dimmer capability to a light (this is something I haven’t done yet, but I plan to soon), you can add something like this:

  • Number Kitchen_LightSwitch_DimmingLevel
  • Number Kitchen_LightSwitch_DimmingStartHour
    and so on. The generic rule is again changed incrementally while retaining the existing functionalities.

I hope this helps.


(Rich Koshak) #7

This is written up in Design Pattern: Associated Items


(Alex Forrow) #8

OpenHAB seems to handle this very well. Only explicit updates (e.g. via postUpdate or expire binding) get dispatched to persistence addons, these intial NULL values do not.

Is this any different to a situation where values are missing from persisted storage for other reasons, e.g. a sensor did not report a value on expected schedule? Usually RRD databases would ignore nulls from calculations.

Ok, that’s cool. Thanks. And thanks @rlkoshak for writing up all these patterns.

I’m going to take this feedback on-board and do some iterating, continuing to explore this persistence-heavy approach.


(Rich Koshak) #9

The difference is the data isn’t missing. If I miss a value then there is just a hole in the DB there. But here there is a NULL there. So you need to decide a consistent way to handle that for all the different persistence methods. Do you skip the value? Do you use it as a value and add it into the calculation?

For example, if I have 10 entries in the DB and 5 of them are NULL, do I divide the sum by 5 and ignore the NULLs or do I divide by 10 and treat the NULLs as zeros?

That is a good precedent. Go with that.


(YF) #10

@rlkoshak Thanks for reminding me. That was actually what I read when I started out with OpenHab.