Please see Design Pattern: What is a Design Pattern and How Do I Use Them to understand the scope and purpose of a design pattern. As with all DPs, the DP is not limited for the use demonstrated in the examples.
Problem Statement
This is going to be one of the more complex design patterns and it depends upon many other design patterns.
First of all, what is a state machine. For the purposes of this DP a state machine is a situation where when certain events occur a new state may be entered. A State Machine Primer with HABlladin, the openHAB Genie and Washing Machine State Machine are two great tutorials to get you started with a state machine. But an ultra simple state machine is actually Design Pattern: Time Of Day.
There are two ways to use the states from a state machine. First, one can change the behaviors by checking the current state of the machine and doing one thing instead of another based on the state. For example,
if(vTimeOfDay.state == "DAY") {
// do something
}
else if(vTimeOfDay.state == "EVENING") {
// do something else
}
The second is to do something in reaction to the state changes. For example
rule "It's night time"
when
Item vTimeOfDay changed to "NIGHT"
then
// do something
end
Often, the ādo somethingsā from above will be to send a bunch of commands or set a scene. the naive implementation would be a Switch statement or if else statements.
switch(vTimeOfDay.state.toString) {
case "MORNING": {
ItemOne.sendCommand(ON)
ItemTwo.sendCommand(ON)
ItemThree.sendCommand(ON)
}
case "DAY": {
ItemOne.sendCommand(OFF)
ItemTwo.sendCommand(ON)
ItemThree.sendCommand(OFF)
}
case "AFTERNOON": {
ItemOne.sendCommand(OFF)
ItemTwo.sendCommand(OFF)
ItemThree.sendCommand(ON)
}
// and so on
}
Is there a way to avoid this tedious and hard to maintain code?
Concept
Use the name of each state to establish Groups. For example, a Lights_ON_MORNING Group for the lights that are to come on in the morning as calculated by the Time of Day state machine. There might need to be more that one Group, for example Light_OFF_MORNING for the lights that are to turn OFF in the morning.
In your Rule, just send the command to the Group.
Simple Example
The state machine part of this example is the Time of Day DP. In this example, we want to control the lights based on the time of day states. The lights are simple Switches.
Items
Group:Switch:OR(ON, OFF) gLights_ON_MORNING
Group:Switch:OR(ON, OFF) gLights_OFF_MORNING
Group:Switch:OR(ON, OFF) gLights_ON_DAY
Group:Switch:OR(ON, OFF) gLights_OFF_DAY
Group:Switch:OR(ON, OFF) gLights_ON_AFTERNOON
Group:Switch:OR(ON, OFF) gLights_OFF_AFTERNOON
Group:Switch:OR(ON, OFF) gLights_ON_EVENING
Group:Switch:OR(ON, OFF) gLights_OFF_EVENING
Group:Switch:OR(ON, OFF) gLights_ON_NIGHT
Group:Switch:OR(ON, OFF) gLights_OFF_NIGHT
Group:Switch:OR(ON, OFF) gLights_ON_BED
Group:Switch:OR(ON, OFF) gLights_OFF_BED
Switch aFrontLamp "Front Room Lamp" (gLights_ON_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
Switch aFamilyLamp "Family Room Lamp" (gLights_ON_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
Switch aPorchLight "Front Porch" (gLights_OFF_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
Switch aMBR_Lights "Master Bedroom Lights" (gLights_ON_EVENING)
Python
from core.rules import rule
from core.triggers import when
@rule("Set lights based on Time of Day")
@when("Item vTimeOfDay changed")
def lights(event):
events.sendCommand("gLights_OFF_{}".format(items["vTimeOfDay"]), "OFF")
events.sendCommand("gLights_ON_{}".format(items["vTimeOfDay"]), "ON")
Rules DSL
// Theory of operation: Turn off the light that are members of gLights_OFF_<TOD> and
// then turn on the lights that are members of gLights_ON_<TOD>. Reset the overrides.
rule "Set lights based on Time of Day"
when
Item vTimeOfDay changed
then
sendCommand("gLights_OFF_"+vTimeOfDay.state.toString, "OFF")
sendCommand("gLights_ON_"+vTimeOfDay.state.toString, "ON")
end
Theory of Operation
If you look at the way the Items and Groups are defined, itās a table.
- | aFrontLamp | aFamilyLamp | aPorchLight | aMBR_Lights |
---|---|---|---|---|
MORNING | ON | ON | OFF | NA |
DAY | OFF | OFF | OFF | NA |
AFTERNOON | ON | ON | ON | NA |
EVENING | ON | ON | ON | ON |
NIGHT | OFF | OFF | OFF | NA |
BED | OFF | OFF | OFF | NA |
I strongly recommend establishing a table like this when trying to establish what you want to happen during the various states. If you have a lot of Items to control it might make more sense to put the Items as the rows and the states as the columns.
Armed with a table like this it is just a matter of adding the lights Items to the right Group. So, for example, we want the front lamp to turn on in the morning so we add it to gLights_ON_MORNING. It should be off during the day so we add it to gLights_OFF_DAY.
The Rule is ultra simple, just two lines of real code. When the time of day changes, we send OFF to the OFF Group for the current time of day state and we send ON to the Group for the current time of day state.
But with a configuration like this, changing the behaviors of the lights is a simple matter of changing Group membership. Need to add a new light? Just make sure to add it to the right Groups. Notice that aMBR_Lights is only a member of one of the Groups. Thatās fine too. There is nothing that says that the light needs to be controlled for all the times of day.
Complex Example
What if there is a mix of Item types? It is reasonable to expect that there are Dimmers or Color Items involved in a lighting example.
Items
We will again be using Design Pattern: Associated Items, only this time we will be using it to encode Dimmer and Color values for each light for each time of day. We will use the pattern <Light Name>_<Time of Day>
for the Items that store the state to be used for that light at that time of day.
Group:Switch:OR(ON, OFF) gLights
Group gSettings
Switch aFrontLamp "Front Room Lamp" (gLights)
Switch aFrontLamp_MORNING (gSettings)
Switch aFrontLamp_DAY (gSettings)
Switch aFrontLamp_AFTERNOON (gSettings)
Switch aFrontLamp_EVENING (gSettings)
Switch aFrontLamp_NIGHT (gSettings)
Switch aFrontLamp_BED (gSettings)
Dimmer aFamilyLamp "Family Room Lamp" (gLights)
Dimmer aFamilyLamp_MORNING (gSettings)
Dimmer aFamilyLamp_DAY (gSettings)
Dimmer aFamilyLamp_AFTERNOON (gSettings)
Dimmer aFamilyLamp_EVENING (gSettings)
Dimmer aFamilyLamp_NIGHT (gSettings)
Dimmer aFamilyLamp_BED (gSettings)
Color aPorchLight "Front Porch" (gLights)
Color aPorchLight_MORNING (gSettings)
Color aPorchLight_DAY (gSettings)
Color aPorchLight_AFTERNOON (gSettings)
Color aPorchLight_EVENING (gSettings)
Color aPorchLight_NIGHT (gSettings)
Color aPorchLight_BED (gSettings)
Populating the <Light Name>_<Time of Day>
Items is beyond the scope of this DP. See Design Pattern: Encoding and Accessing Values in Rules for some approaches.
Python
from core.rules import rule
from core.triggers import when
@rule("Set lights based on Time of Day")
@when("Item vTimeOfDay changed")
def lights(event):
for light in ir.getItem("gLights").members:
setting = items["{}_{}".format(light.name, items["vTimeOfDay"])]
events.sendCommand(light.name, setting)
Rules DSL
This rule will leverages the Associated Items DP.
rule "Set lights based on Time of Day"
when
Item vTimeOfDay changed
then
gLights.members.forEach[ light |
val setting = gSettings.members.findFirst[ s | s.name = light.name+"_"+vTimeOfDay.state.toString ]
if(setting !== null) light.sendCommand(setting.state)
]
end
Theory of Operation
Each Item has an associated setting Item for each time of day where it needs to change state. When the time of day changes, we loop through all of the lights, get the associated setting Item for that light and time of day and send that setting as a command to the light.
Advantages and Disadvantages
Advantages:
- Vastly reduced lines of Rules code and Rules complexity.
- Can modify behaviors for Items without modifying Rules.
- Can add new states without modifying Rules.
Disadvantages:
- Many more Groups and Items required.
- There is not way to configure this using a nice table format potentially leading to reconfiguration errors.
Related Design Patterns
Design Pattern | How Itās Used |
---|---|
Doing it smarter - help with rule code rollershutter automation | An example where the state from the state machine is used to drive different behaviors instead of being used as an event to cause something to happen |
A State Machine Primer with HABlladin, the openHAB Genie | Example state machine tutorial |
Washing Machine State Machine | An example state machine tutorial |
Design Pattern: Time Of Day | An example state machine tutorial, used to drive the examples |
Design Pattern: Associated Items | Used to sendCommands and get the states from associated Items based on Item names. |
Design Pattern: Encoding and Accessing Values in Rules | How to initially populated the settings Items in the complex example |