[Edit] Adde link to Time of Day State Machine
For years now I’ve let this Design Pattern post get out of hand so I’m going to rein it in a bit now. It’s really expanded away from what it’s originally intended to be (i.e. a way to illustrate how to code a time based state machine) into a thread that is mostly about the example, Time of Day. It’s partially my fault for naming the thread “Time of Day”. But there are better alternatives for Time of Day in specific and time based state machines in general now so I’m going to close this down.
So here’s the way this thread will work going forward.
-
If you want a simple implementation of the Time of Day example to use in your system see Creating Capabilities with Rule Templates: Time of Day. It is easy to install and instantiate and doesn’t require messing with any code. You can build it all in the UI using the Marketplace (OH 3.2 M2 or later required). Problems or questions should be posted there.
-
If you want a more capable implementation of Time of Day that uses Ephemeris to know what type of day it is (and therefore have a different state machine for each type of day) see Time Based State Machine which can be installed from the marketplace. See openhab-rules-tools/ephem_tod at main · rkoshak/openhab-rules-tools · GitHub for the raw code, though I don’t plan on supporting those going forward. OH 3 users should look at the JavaScript implementation and OH 2 users should look at the Python implementation. If you have problems or questions please open a new thread.
-
If you have a Rules DSL version of the code below and need help with it, see 1. As far as I’m concerned the Rules DSL version is deprecated and no longer supported (at least by me). If you need to port the Rules DSL version below to OH 3 or are having some other problem with it, it will be faster to start over and use 1 than it will be to fix the existing code.
-
If you are looking for a way to set up a time based state machine, see 1. Hopefully that thread both illustrates how to set it up but also makes it clear how you can adjust it to apply to other use cases.
-
If you want to use the time of day as an example to help you write your own code see below. But be aware that the code below is no longer supported and likely will need to be adjusted to work. However, I recommend looking at 1 also for a more recent example to inspire your own code.
Everything below this point is deprecated as of October 26th, 2021.
Please see Design Pattern: What is a Design Pattern and How Do I Use Them for an explanation of what a DP is and how to use them.
Problem Statement
Often in home automation one has certain rules they want to run at certain times or have the home automation exhibit different behaviors for different times of day. The naive approach would be to do the time comparisons inline in each Rule that cares about the time of day. This DP provides an alternative approach.
This DP is particularly suitable for tracking any sequence of events that are denoted by a starting event, not just time. In fact, this DP is really just a simple state machine and can be applied to any similar problem. Other examples include tracking important dates, seasons of the year, controlling an irrigation system (the end of one zone triggers the next zone), etc.
Concept
The overall concept is to create a String Item that represents the current state in the sequence. Rules will check to determine what that state is to determine their behavior or trigger when the String Item changes. One or more additional Rules are responsible for calculating and populating the String Item based on events.
NOTE: The naive approach to implementing this Design pattern is to use Switches, one for each possible state. However, in practice using Switches makes the code more complicated than it needs to be, causes the proliferation of unnecessary Items, and ultimately results in an implementation that is less able to scale when it comes time to add new times of day.
Simple Example: Tracking the Time Periods in a Day
Library implementations
JavaScript MainUI Rule
There is a MainUI Rule JavaScript implementation located at GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules.. You can import it by creating a new rule in MainUI, click on the code tab and paste the contents of ephemTimeOfDay.yml into the form. Switch back to the design tab and set a “Schedule” tag and the rule will show up in the calendar under Schedule.
You just create a TimeOfDay
String Item which will hold the current state and create a TimesOfDay
Group to hold all the time of day DateTime Items.
Group:DateTime TimesOfDay
String TimeOfDay "Current time of day [%s]"
// Default day, initialization for JavaScript should be done thgrough MainUI. See https://community.openhab.org/t/oh-3-examples-how-to-boot-strap-the-state-of-an-item/108234
DateTime Default_Morning (TimesOfDay) { etod="MORNING"[type="default"] }
DateTime Default_Day (TimesOfDay) { channel="astro:sun:set120:set#start", etod="DAY"[type="default"] }
DateTime Default_Evening (TimesOfDay) { channel="astro:sun:local:set#start", etod="EVENING"[type="default"] }
DateTime Default_Night (TimesOfDay) { etod="NIGHT"[type="default"] }
DateTime Default_Bed (TimesOfDay) { etod="BED"[type="default"] }
// Weekend day, notice that not all the states are listed, the unlisted states are skipped
DateTime Weekend_Day (TimesOfDay) { channel="astro:sun:set120:set#start", etod="DAY"[type="weekend"] }
DateTime Weekend_Evening (TimesOfDay) { channel="astro:sun:local:set#start", etod="EVENING"[type="weekend"] }
DateTime Default_Bed (TimesOfDay) { etod="BED"[type="weekend"] }
// Custom dayset
DateTime Trash_Morning (TimesOfDay) { etod="MORNING"[type="dayset", set="trash"] }
DateTime Trash_Trashtime (TimesOfDay) { etod="TRASH"[type="dayset", set="trash"]}
DateTime Trash_Day (TimesOfDay) { channel="astro:sun:set120:set#start", etod="DAY"[type="dayset", set="trash"] }
DateTime Trash_Evening (TimesOfDay) { channel="astro:sun:local:set#start", etod="EVENING"[type="dayset", set="trash"] }
DateTime Trash_Night (TimesOfDay) { etod="NIGHT"[type="dayset", set="trash"] }
DateTime Trash_Bed (TimesOfDay) { etod="BED"[type="dayset", set="trash"] }
// Default holiday
DateTime Weekend_Day (TimesOfDay) { channel="astro:sun:set120:set#start", etod="DAY"[type="holiday"] }
DateTime Weekend_Evening (TimesOfDay) { channel="astro:sun:local:set#start", etod="EVENING"[type="holiday"] }
DateTime Default_Bed (TimesOfDay) { etod="BED"[type="holiday"] }
// Custom holiday
DateTime Weekend_Day (TimesOfDay) { channel="astro:sun:set120:set#start", etod="DAY"[type="custom", file="/openhab/conf/services/custom1.xml"] }
DateTime Weekend_Evening (TimesOfDay) { channel="astro:sun:local:set#start", etod="EVENING"[type="custom", file="/openhab/conf/services/custom1.xml"] }
DateTime Default_Bed (TimesOfDay) { etod="BED"[type="custom", file="/openhab/conf/services/custom1.xml"] }
See the readme in the repo for more details.
Python
There is an implementation of tracking time periods in the day that supports a different set of time periods depending on the type of day (e.g. weekend, holiday, etc.) located at GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules.. This is a reusable Rule that lets you define the time periods through Item metadata.
The below examples also includes use of the init_items library (see openhab-rules-tools link above) to boot strap static times not based on something like Astro.
// Default day, notice the use of init_items to initialize the state of the Item.
DateTime Default_Morning { init="2020-06-01T06:00:00", etod="MORNING"[type="default"] }
DateTime Default_Day { channel="astro:sun:set120:set#start", etod="DAY"[type="default"] }
DateTime Default_Evening { channel="astro:sun:local:set#start", etod="EVENING"[type="default"] }
DateTime Default_Night { init="23:00:00", etod="NIGHT"[type="default"] }
DateTime Default_Bed { init="00:02:00", etod="BED"[type="default"] }
// Weekend day, notice that not all the states are listed.
DateTime Weekend_Day { channel="astro:sun:set120:set#start", etod="DAY"[type="weekend"] }
DateTime Weekend_Evening { channel="astro:sun:local:set#start", etod="EVENING"[type="weekend"] }
DateTime Default_Bed { init="00:02:00", etod="BED"[type="weekend"] }
// Custom dayset, defined in [Ephemeris](https://www.openhab.org/docs/configuration/actions.html#ephemeris).
DateTime Trash_Morning { init="06:00:00", etod="MORNING"[type="dayset", set="trash"] }
DateTime Trash_Trashtime { init="07:00:00", etod="TRASH"[type="dayset", set="trash"]}
DateTime Trash_Day { channel="astro:sun:set120:set#start", etod="DAY"[type="dayset", set="trash"] }
DateTime Trash_Evening { channel="astro:sun:local:set#start", etod="EVENING"[type="dayset", set="trash"] }
DateTime Trash_Night { init="23:00:00", etod="NIGHT"[type="dayset", set="trash"] }
DateTime Trash_Bed { init="00:02:00", etod="BED"[type="dayset", set="trash"] }
// Default holiday from Ephemeris.
DateTime Weekend_Day { channel="astro:sun:set120:set#start", etod="DAY"[type="holiday"] }
DateTime Weekend_Evening { channel="astro:sun:local:set#start", etod="EVENING"[type="holiday"] }
DateTime Default_BEd { init="00:02:00", etod="BED"[type="holiday"] }
// Custom holiday defined in Ephemeris.
DateTime Weekend_Day { channel="astro:sun:set120:set#start", etod="DAY"[type="custom", file="/openhab/conf/services/custom1.xml"] }
DateTime Weekend_Evening { channel="astro:sun:local:set#start", etod="EVENING"[type="custom", file="/openhab/conf/services/custom1.xml"] }
DateTime Default_BEd { init="00:02:00", etod="BED"[type="custom", file="/openhab/conf/services/custom1.xml"] }
The rule will automatically create a TimeOfDay Item if it doesn’t already exist. See the library readme for more details.
Rules DSL
In this example we will be tracking the time of day. There will be six states: MORNING, DAY, AFTERNOON, EVENING, NIGHT and BED. Some of the boundaries are statically defined while others are based on sunrise and sunset.
- MORNING: 06:00 to sunrise; Note during some times of year sunrise is before 06:00
- DAY: Sunrise to 90 minutes before sunset
- EVENING: 90 minutes before sunset to 23:00
- NIGHT: 23:00 to 00:100:
- BED: 00:00 to 06:00
Things
The Astro binding is used to generate the events for sunrise and sunset. The Astro binding supports using an offset but it effects all events for that Thing. For example, if you use an offset of -90 minutes, you cannot get an event at sunset -90 and at sunset from the same Thing so you may need to define more than one Astro Thing depending on where you want to put the boundaries.
There are also two ways to define an offset for the Astro binding. You can define the offset on the Thing or you can add 15 degrees to the latitude to add 60 minutes.
Items
String vTimeOfDay "Current Time of Day [MAP(weather.map):%s]" <tod>
DateTime vMorning_Time "Morning [%1$tH:%1$tM]" <sunrise>
DateTime vSunrise_Time "Day [%1$tH:%1$tM]" <sun> { channel="astro:sun:home:rise#start" }
DateTime vSunset_Time "Evening [%1$tH:%1$tM]" <sunset> { channel="astro:sun:home:set#start" }
DateTime vNight_Time "Night [%1$tH:%1$tM]" <moon>
DateTime vBed_Time "Bed [%1$tH:%1$tM]" <bedroom_blue>
DateTime vEvening_Time "Afternoon [ %1$tH:%1$tM]" <sunset> { channel="astro:sun:minus90:set#start" }
See Items | openHAB for creating the tod icons applied to vTimeOfDay.
Rules
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, "Calculated time of day is " + curr)
vTimeOfDay.sendCommand(curr)
end
// Examples for use of vTimeOfDay
rule "Day time started"
when
Item vTimeOfDay changed to "DAY" // does not work prior to OH 2.3 Release
then
// do stuff when DAY starts
end
rule "Some rule"
when
// some trigger
then
if(vTimeOfDay.state != "BED") return;
// do stuff to do when it isn't BED time
end
Adding a new time of day is as simple as adding a new trigger to the rule to fire when that time of day starts, calculate the DateTime for today when that time period starts, and then adding a new case to the switch statement to determine whether the current time is between when this new time period starts and the next one starts. The isBefore test for the previous time period would have to be updated as well.
Theory of Operation
There is a DateTime Item for the start of each time of day. In the above those DateTimes are statically populated that are not linked to an Astro channel. This can be expanded using one of the Alarm Clock examples or CalDav binding to populate these DateTime Items if desired.
There is also vTimeOfDay which represents the current state.
There is a Rule that gets triggered at the start of each new time of day and it gets triggered at system start. The Rule calulates the current time and determines what the current state of vTimeOfDay should be based on the current Time.
Advantages and Limitations
The major advantage of this approach is it centralizes all of your time of day calculations into one place. This allows:
- avoidance of duplicated code
- avoidance of typos and coding errors scattered through the code
- ease of debugging
- simpler Rules logic for Rules that care about Time of Day as now they only need to test vTimeOfDay for the current state
The major limitation of this approach is that it does not support overlapping time periods well. It could be expanded to handle that through the addition of additional Time Of Day Items or setting vTimeOfDay to a list of Strings (e.g. “Evening, Twilight”) but that adds complications to the TimeOfDay rule and any Rule that depends upon TimeOfDay.
Complex Example
In this example, the design pattern is applied to a simple three zone irrigation system. The first trigger is time based and the subsequent triggers and states are defined by the end of watering the zones.
The below also excludes error checking and likely has typos and is intended for illustration only. In particular, if OH restarts or reloads the rules file for some reason, the Irrigation will jump to the next Zone no matter how long the previous zone has been watering and it will not make sure the previous zone turned off. Handling these edge cases is beyond the scope of this writing.
Items
String Irrigation
Group gZones
Switch Zone1 (gZones)
Switch Zone2 (gZones)
Switch Zone3 (gZones)
Rules:
rule "Irrigation control"
when
System started or
Time cron "0 0 6 * * ? *" or
Member of gZones changed to OFF
then
val currState = Irrigation.state.toString
val nextState = "Done"
if(currState == NULL || currState == "Done") nextState = "Zone1"
else if(currState == "Zone1") nextState = "Zone2"
else if(currState == "Zone2") nextState = "Zone3"
// If it is "Zone3" the next state is already set to "Done"
Irrigation.sendCommand(nextState)
end
rule "Zone management"
when
Item Irrigation received command
then
sendCommand(receivedCommand.toString, "ON")
createTimer(now.plusMinutes(5), [ | sendCommand(receivedCommand.toString, "OFF") ]
end
Theory of Operation
Each Zone is represented by an Item and there is a controlling String Item to represent the state of the Irrigation.
The Rule that calculates the irrigation state gets triggered based on Time, at System started, and when any member of gZones turns OFF. Based on the current state stored in Irrigation the next state is calculated and commanded.
There is a companion Rule that is driven by the Irrigation state that sends the ON command to the zone represented by the state of Irrigation and sets a Timer to turn it OFF in five minutes.
Other implementations
Python version of the Rules DSL above
See Migrating DSL Rules to JSR223/Javascript with a case example for an example of this DP in JavaScript using JSR223 Rules.
Another version that includes more controls for scenes
See Mode (Time of Day) — openHAB Helper Libraries documentation for another approach to implement this DP using Python Rules.
A Python version that uses Ephemeris, Groups, and Item Metadata
The following is a third version which I’ve written but have since re-implemented with the library linked to above. I leave it here for another example of the DP. As such, it is slightly different in use and configuration. This version is no longer maintained and left for illustrative purposed only. I’ve an attempt to incorporate the new Ephemeris capability to allow for the definition of different start times based on the type of day (e.g. different start times for weekends and holidays from work days).
Configuration
This reusable Rule requires configuration through configuration.py and changes to your Items.
configuration.py
tod_group = "TimeOfDay_StartTimes"
tod_item = "vTimeOfDay"
You define the name of the Group that contains all of the start time Items and the name of the Item which represents the current time of day.
Items
To make this Rule reusable the approach has been adjusted to move away from an event driven approach to an Item based approach. Rather than requiring users to define Rule triggers for each starting event they now only need to define DateTime Items for each starting time. These Items can be populated from any source including Astro, CalDav, HABPanel widgets, Rules, and any other source.
I’ve also implemented a way to initialize Items with a statically defined date time using Item metadata (see Design Pattern: Using Item Metadata as an Alternative to Several DPs for details). Any Item that is NULL or UNDEF when the rule is triggered will be updated with the date time stored in the ToD start_time metadata value. Only those Items that need to be initialized need to have this metadata value.
In addition, each DateTime needs the name of the time of day state that the Item represents the start of. This too is stored in Item metadata, the ToD tod_state metadata value.
String vTimeOfDay "Current Time of Day [MAP(weather.map):%s]" <tod>
Group TimeOfDay_StartTimes
DateTime vMorning_Time "Morning [%1$tH:%1$tM]" <sunrise> (TimeOfDay_StartTimes)
{ ToD="init"[start_time="06:00",tod_state="MORNING"] }
DateTime vDay_Time "Day [%1$tH:%1$tM]" <sun> (TimeOfDay_StartTimes)
{ channel="astro:sun:local:rise#start", ToD="init"[tod_state="DAY"] }
DateTime vAfternoon_Time "Afternoon [ %1$tH:%1$tM]" <sunset> (TimeOfDay_StartTimes)
{ channel="astro:sun:set120:set#start", ToD="init"[tod_state="AFTERNOON"] }
DateTime vEvening_Time "Evening [%1$tH:%1$tM]" <sunset> (TimeOfDay_StartTimes)
{ channel="astro:sun:local:set#start", ToD="init"[tod_state="EVENING"] }
DateTime vNight_Time "Night [%1$tH:%1$tM]" <moon> (TimeOfDay_StartTimes)
{ ToD="init"[start_time="23:00", tod_state="NIGHT"] }
DateTime vBed_Time "Bed [%1$tH:%1$tM]" <bedroom_blue> (TimeOfDay_StartTimes)
{ ToD="init"[start_time="00:02",tod_state="BED"] }
Take note that those Items above that are not linked to a binding to get initialized and updated with a time have a start_time value defined.
Python
from core.rules import rule
from core.triggers import when
from org.joda.time import DateTime
from core.utils import sendCommandCheckFirst
from core.metadata import get_key_value
from time import sleep
from core.actions import ScriptExecution
from configuration import tod_group, tod_item
tod_timers = {}
def tod_init(log):
"""
Initializes any member of tod_group with the time in it's metadata if it's
state is UnDefType. This depends upon the rule loading delay from the Helper
Libraries to ensure that restoreOnStartup and Astro have finished populating
the Items prior to this function executing.
"""
log.info("About to initialize Time of Day Items")
now = DateTime.now()
# Loop through the undefined Items.
init_done = False
for start_time in [t for t in ir.getItem(tod_group).members
if isinstance(t.state, UnDefType)]:
# extract the initialization string from metadata.
t = get_key_value(start_time.name, "ToD", "start_time")
# Log an error if there is no such metadata.
if t is None: log.error("{} is uninitialized and doesn't have metadata!"
.format(start_time.name))
# Parse the init string to HH:MM:SS:MS (SS:MS are both optional).
else:
time_parts = t.split(':')
num_parts = len(time_parts)
if num_parts < 2: log.error("{} is malformed metadata to initialize"
" {}".format(t, start_time.name))
else:
events.postUpdate(start_time,
str(now.withTime(int(time_parts[0]),
int(time_parts[1]),
int(time_parts[2]) if num_parts > 2 else 0,
int(time_parts[3]) if num_parts > 3 else 0)))
init_done = True
if init_done: sleep(0.2) # Give the Items a chance to update.
log.info("Done initializing Time of Day")
@log_traceback
def clear_timers():
# Clear out all the existing timers.
for name, timer in tod_timers.items():
new_tod.log.debug("Cleaning out timer for {}.".format(name))
if timer is not None and not timer.hasTerminated():
timer.cancel()
del tod_timers[name]
@log_traceback
def tod_transition(state, log):
log.info("Transitioning time of day from {} to {}."
.format(items[tod_item],state))
events.sendCommand(tod_item, state)
@rule("New Time of Day",
description="Generic implementation state machine driven by time",
tags=["designpattern"])
@when("System started")
@when("Time cron 0 1 0 * * ? *")
@when("Member of {} changed".format(tod_group))
def new_tod(event):
"""
Time of Day: A time based state machine which commands a state Item
(tod_item in configuration) with the current time of day as a String.
To define the state machine, create a DateTime Item which will be populated
with the start times for each of the time of day states. Each of these Items
must be made members of the tod_group Group.
The values stored in the Items can come from anywhere but the two most
common will be from Astro or statically defined. For Astro, just link the
Item to the appropriate Astro Channel. For statically defined the time is
defined and the Item initialized through metadata.
The expected metadata is : Tod="init"[start_time="HH:MM:SS:MS", tod_state="EXAMPLE"]
where
- HH is hours
- MM is minutes
- SS is seconds and optional
- MS is milliseconds and optional
- EXAMPLE is the State String
All members of tod_group are required to have a tod_state metadata entry.
Only static Items require the start_time metadata entry.
This Rule triggeres at system started, one minute after midnight, and if any
member of tod_group changes. When the Rule triggers, it creates timers to go
off at the indicated times.
"""
tod_init(new_tod.log)
now = DateTime.now()
clear_timers()
# Create timers for all the members of tod_group.
most_recent_time = now.minusDays(1)
most_recent_state = str(items[tod_item])
for start_time in ir.getItem(tod_group).members:
item_time = DateTime(str(start_time.state))
trigger_time = now.withTime(item_time.getHourOfDay(),
item_time.getMinuteOfHour(),
item_time.getSecondOfMinute(),
item_time.getMillisOfSecond())
# Update the Item if it's still got yesterday's date.
if item_time.isBefore(trigger_time):
events.postUpdate(start_time, str(trigger_time))
state = get_key_value(start_time.name, "ToD", "tod_state")
# If there is no state we can't use this Item.
if state is None:
new_tod.log.error("{} does not have tod_state metadata!"
.format(start_time.name))
# If we have already passed this time, keep track of the most recent
# time and state.
elif (trigger_time.isBefore(now)
and trigger_time.isAfter(most_recent_time)):
most_recent_time = trigger_time
most_recent_state = state
new_tod.log.debug("The most recent state is now {}".format(state))
# Create future timers
elif trigger_time.isAfter(now):
new_tod.log.debug("Setting timer for Item {}, Time {}, and State {}"
".".format(start_time.name, trigger_time, state))
tod_timers[start_time.name] = ScriptExecution.createTimer(
trigger_time,
lambda s=state: tod_transition(s,
new_tod.log))
else:
new_tod.log.debug("{} is in the past but there is a more recent "
"state".format(state))
# Command the time of day to the current state
new_tod.log.info("Time of day is now {}".format(most_recent_state))
sendCommandCheckFirst(tod_item, most_recent_state)
def scriptUnloaded():
clear_timers()
Theory of Operation
The rule triggers when OH starts, a minute after midnight, and whenever a member of tod_group changes. (TODO: Can I trigger a Rule on the change in membership of tod_group?).
First the rule initializes any UNDEF/NULL Item using it’s metadata. Then it clears out any Timers that may be hanging around. Unlike the Rules DSL version above which is Rule Trigger driven, this Rule is Timer driven.
Next the rule loops through all the members of tod_group.
If the DateTime isn’t for today, we update the Item with today’s date.
Then get the name of the state from metadata.
Log an error if the Item lacks the needed metadata. If the Item’s state is before now, keep track of which ever state occurred most recently. If the Item’s state is in the future, create a Timer to transition the tod_state Item to the new state at that time.
Finally, command the tod_state with the most_recent_state.
When the script is unloaded, all Timers are cancelled to keep stray timers from hanging around.
Related Design Patterns
Design Pattern | How It’s Used |
---|---|
Design Pattern: Unbound Item (aka Virtual Item) | vTimeOfDay, Irrigation, and other Items are examples of a Virtual Item |
Design Pattern: Separation of Behaviors | This DP is a specific implementation of Separation of Behaviors |
A State Machine Primer with HABlladin, the openHAB Genie - #6 by jswim788 | This DP is a simplified implementation of a state machine |
Design Pattern: Using Item Metadata as an Alternative to Several DPs | The Python versions use metadata to define the times of day. |
Edit: A near complete rewrite to match formatting of other DP postings and reflect changes in OH 2.3. Eliminated examples for prior versions of OH.
Edit: Use toString instead of going through the long set of calls to get a millis to convert DateTimeType to DateTime
Edit. Remove call to millis in the switch statement.
Edit: Resorted the headings and added a reference to the openhab-rules-tools implementation.