Creating "mode switches" for seasonal activities

One unique thing about my setup, is that I have certain rules which only happen seasonally, or under certain circumstances. For instance, one of my outdoor modules controls the fountain in the summer, and Christmas lights in the winter. Each use case applies a different set of rules, and I can’t have my fountain timings setting off Christmas lights, or vice versa.

To make this work, my Items file includes several “mode” switches, not associated with physical things:

/**
 * Modes
 */
Switch  XmasMode      "Christmas Mode"  <settings>
Switch  FountainMode  "Fountain Mode"   <settings>

Then I created some “master” switches, switch groups, and of course the physical switch.

// Master Switches
Switch                    FountainMaster  "Fountain Master"            <water>
Switch                    XmasExtMaster   "Christmas Exterior Master"  <light>
// Switch Groups
Group:Switch:AND(ON,OFF)  gFountain       "Fountain"                   <water>
Group:Switch:AND(ON,OFF)  gXmasExt        "Christmas Exterior"         <light>
// Physical Switch
Switch  Courtyard_Outdoor_FountainXmas  "Fountain"  <water>  (gFountain, gXmasExt)  { channel="zwave:device:4ff68761:node27:switch_binary" }

The “master” switches have rules which check the state of the “mode” switch, and conditionally turn on/off the switch group:

/**
 * Fountain Master Switch
 */
rule "Fountain Master ON"
when
    Item FountainMaster received command ON
then
    if (FountainMode.state == ON) {
        sendCommand(gFountain, ON)
    }
end
rule "Fountain Master OFF"
when
    Item FountainMaster received command OFF
then
    if (FountainMode.state == ON) {
        sendCommand(gFountain, OFF)
    }
end

By doing it this way, my time-based rules can be really simple. They don’t include any conditional code, because they simply trigger the master switch which handles the conditional bits:

/**
 * Timers
 */
rule "Fountain ON Timer"
when
    // Sunday, 8:30 a.m.
    Time cron "0 30 8 ? * SUN *"
    or
    // Wednesday, 6:30 p.m.
    Time cron "0 30 18 ? * WED *"
then
    sendCommand(FountainMaster, ON)
end
rule "Fountain OFF Timer"
when
    // Every night at 9 p.m.
    Time cron "0 0 21 1/1 * ? *"
then
    sendCommand(FountainMaster, OFF)
end

Effectively, the “master” switches are emulating the notion of “scenes,” as they are called in the Vera world. I can automate scenes via rules as shown above, or include the master switches in my site map definition, to trigger them manually. Either way, I know that if Fountain Master is triggered, it’s only going to change the switch if Fountain Mode is enabled. That’s a really powerful abstraction that drastically simplifies rule creation.

You may be wondering why I bothered with the groups. Well, I have other mode-based master switches which control multiple devices. I left them out of my example for simplicity. But in those cases it’s nice to have the master switch control a group. So I always use a group for each mode, just for consistency’s sake.

3 Likes

Thank you.
Just a bit of advice,

Use the sendCommand method instead of the action when the item is known.
For example:
FountainMaster.sendCommand(ON) instead of sendCommand(FountainMaster, ON)

The reason for this can be found there: https://www.openhab.org/docs/configuration/rules-dsl.html#myitem-sendcommand-new-state-versus-sendcommand-myitem-new-state

Great post! Thanks for sharing!

This is a really nice use of proxy Items.

Here are some more advanced ways you can further simplify this code.

They can be made even simpler.

rule "Fountain Master"
when
    Item FountainMaster received command
then
    if(gFountain.state != receivedCommand) gFountain.sendCommand(receivedCommand)
end

I bet these could all be merged into a single Rule as well. Let’s make the names a little easier to split apart and rebuild.

Group:Switch gProxies
// Master Switches
Switch                    Fountain_Master  "Fountain Master"            <water> (gProxies)
Switch                    XmasExt_Master   "Christmas Exterior Master"  <light> (gProxies)
// Switch Groups
Group:Switch:AND(ON,OFF)  gFountain       "Fountain"                   <water>
Group:Switch:AND(ON,OFF)  gXmasExt        "Christmas Exterior"         <light>

Then you can combine all the Master rules into a single Rule

import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "Master"
when
    Member of gProxies received command
then
    val grp = ScriptServiceUtil.getItemRegistry.getItem("g"+triggeringItem.name.split("_").get(0))
    if(grp.state != receivedCommand) grp.sendCommand(receivedCommand)
end

This concept is documented in Design Pattern: Associated Items.

1 Like

Thanks for the tips! This definitely makes things more maintainable. Wish I would’ve known about it before I took time to write all those rules!

FYI, you probably already know this but for the sake of future visitors, the when clause actually needs to be:

when
    Member of gProxies received command

Now I just need to figure out a rule to do the opposite–so the Master switches update their state when the group changes!

1 Like

If you send a command to a Group, that command gets forwarded to all the members of that Group.

I’ve corrected my post above with the Member of trigger. I did mess that up.

1 Like

Let me clarify. I have the following rule to make sure my master switch always displays the correct status:

rule "Master Reflects Group"
when
    Member of gBuildingLights changed
then
    BuildingLights_Master.postUpdate(gBuildingLights.state)
end

I’d love to find a way to make a single rule for all masters, similar to how you used string splitting in your example. The pseudo-code would be like this:

rule "Master Reflects Group"
when
    // would love a way to not have to list them all
    Item gBuildingLights changed
    or
    Item gCourtyardLights changed
    or
    Item gParkingLights changed
then
    // how do I get this value?!?
    // triggeringItem returns the switch in the group, not the group itself...
    val grp = <<whichever group above triggered this rule>>
    val master = ScriptServiceUtil.getItemRegistry.getItem(grp.substring(1) + "_Master")
    master.postUpdate(grp.state)
end

I’m thinking this may not be possible, but just throwing it out there.

Maybe put the group in a group

rule "Master Reflects Group"
when
    Member of gMetaGroup changed
then
    val grp = triggeringItem
    val master = ScriptServiceUtil.getItemRegistry.getItem(grp.name.toString.substring(1) + "_Master")
    master.postUpdate(grp.state)
end
1 Like

With those rule Triggers, indeed, triggeringItem will be the Group that triggered the Rule.

You can make it shorter if you put your Lights Groups in their own Group.

rule "Master Reflects Group"
when
    Member of LightingGroups changed
then
    postUpdate(triggeringItem.name+"_Master", triggeringItem.state)
end

A Group is in all respects also an Item and Member of purposefully only works with immediate members of a Group, not subgroups, to support just this use case.

Oops, Vincent beat me to it.

1 Like

Only once in a while. Your code in the rule is a lot cleaner though.
His naming convention will need:

rule "Master Reflects Group"
when
    Member of gMetaGroup changed
then
    postUpdate(triggeringItem.name.toString.substring(1) + "_Master", triggeringItem.state)
end

Just to remove the g

2 Likes

You folks are wonderful.

For the sake of future visitors, here’s what I ended up with based on your examples.
I kept the value assignments, since it makes the code more readable to me. And I had to make one more change, since the postUpdate() action expects an Item instance, not a string; I had to use the getItem() method.

rule "Master Switches Mirror Groups"
when
    Member of gMasterGroups changed
then
    val grp = triggeringItem
    val master = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString.substring(1) + "_Master")
    master.postUpdate(grp.state)
end
1 Like