Durable one-shot Timers?

I have a few places where I’d like (in a script) to schedule an action to be performed at some point in the future, once. The typical scenario for this is to activate something for a period of time, but vary that period of time based on a calculation at the point of activation. An example of this is activating irrigation, but varying the time it’s running based on recent rainfall.

The only solution that I have seen to this is to implement some custom timing method in a scripting language such that the script runtime defers execution of the action, such as a setTimeout(...) in JavaScript. I am not a fan of this option because it’s not durable in the face of server, or even script restarts, at which point this state is lost (and deactivation may never happen). I guess one option is to place an upper bound on the period of activation by using the expire binding to at least prevent this case, but that seems like a workaround rather than the right solution.

I am thinking of creating a new type of trigger, which is a deferred-one-shot trigger, which allows specifying an action to be run, once, at a future time. If the trigger was missed (e.g. the system was down) then it would have the option to run at startup, and would delete itself once run (or expired, if missed and not set to run at startup in this case). Scripts could create future tasks against these triggers, which would survive restarts etc. However, before I do this, I wanted to ask if there is some existing option or approach that I am missing?

Thanks!

The approach I documented in my irrigation tutorials uses openHAB Timers. With this approach, the easiest thing to do is on a restart to just cancel everything and make sure all the valves are off. But you could persist the start and stop times of each of the valves and determine how long each one was on for today and restart the irrigation as desired, subtracting the time that already ran when OH restarts/the script is reloaded. You can see more at Design Pattern: Cascading Timers.

Another approach I could see is to store data in the Items metadata. If you are using managed Items (not .items files) the metadata will persist between restarts of OH. You store the start time in metadata on the valve and when OH comes back online trigger a rule to look at this metadata and it will see when the valve turned ON and turn off/recreate the timers as appropriate.

The GateKeeper DP implementation I have in Python is actually a good approach for scheduling a cascading timer type pattern like this. You can send it a function to execute and how long to wait before the next function passed to it can run (I’ve not yet written the JavaScript version but it’s not that complicated). That library could be enhanced perhaps to store the stuff that is queued up perhaps in an Item or Item’s metadata. That would centralize much of the recovery after an OH restart into one library. It might need to be simplified so instead of a function you’d only pass an Item name and command, but that should be fine for this type of application.

I’m a little ambivalent about creating yet another way to manage timing of events into openHAB core. We already have sleeps, timers, cron/time triggered rules, and expire. At the same time, having a way to preserve a timed event across openHAB restarts would be very useful. But I’d rather see one or more of these existing approaches (maybe expire or timers) enhanced over the creation of a whole new way. But this is coming strictly from trying to help users here on the forum as opposed to forming an opinion on technical merit. From that perspective, adding another way to control timing is going to only make it harder for these users who already have a challenge understanding and using what OH already provides.

Thanks very much for such a comprehensive answer!

I agree that simply turning off everything upon restart is probably the simplest solution. This is probably the best option for me right now.

I really don’t want to be messing with synthetic items and metadata to solve something which should really be straightforward: tbh I went down this path at first but once I ended up with such a large amount of items & logic it became too hard to figure out how things worked and interacted and I would regularly break things if I made changes. As you may know I’ve focused on more advanced JS scripting to replace all of the Rules DSL and synthetic item stuff for my system. This way I can be explicit about all the logic.

I completely understand your view on not adding to the existing complexity; that’s probably been one of my primarily goals at my workplace! The challenge for me as a consumer & contributor is to keep any PRs small in scope to actually get them accepted. Unfortunately this often means adding new, minimal & isolated functionality rather than enhanced existing stuff. I did have a quick look at Timers, however they are very simple and as well as having no storage, I’m not sure that the OH architecture would even have a place to store them? The closest thing I can think of is actually to implement a one-shot trigger as I originally suggested, then to rewire the Timer class to use this. Saying this, it would allow converging logic and code, although it would still mean that there were two interfaces (Timer class and explicit trigger) to it which would retain the UX complexity (although I guess we could then deprecate one of them).

ps. Not sure if you’re aware, but in the hue-emulation add-on there are actually a couple of additional trigger types which are effectively exactly what I suggested (one based on duration from creation, the other on an absolute point in time), although they don’t appear to have any explicit recovery options for if the event is missed. I took these to suggest that OH itself is lacking these triggers.

What about a trigger based on a DateTime Item? That might be the least complicated from a user’s perspective and perhaps the most flexible over all. When the DateTime Item changes, the rule gets scheduled to run at that time (assuming it’s in the future). The rule could be rescheduled to start at startup and rule load.

Then one would have a nice and simple interface. Restoring across restarts can be handled by Persistence.

The second half of the problem, knowing how long a valve was on while OH was offline, is still an exercise left to the user though. But that’s the case for all the proposals.

But that’s only implemented in the Hue binding. I presume it uses a standard Trigger Channel to kick off the rules.

I do want to address this part though. As I’ve implemented stuff like this before, there are no syntehtic/virtual/unbound Items involved usually. There is just a little bit of information tagged to the Item via metadata. And in this specific case, that little bit of information is only a date time stamp when the valve was last turned on. So I’m not actually talking about anything really complicated here that exists outside the rule. Just a timestamp. And that timestamp could be an Item (one per valve) or it can be Item metadata on the valve switch itself.

In fact, this over all approach is very similar to how the Python and JavaScript versions of my Time of Day implementations work, only somewhat simpler since there isn’t a lot of different sets of DateTime Items to keep track of, just the one set of Valve Items. When OH restarts and at midnight I trigger a rule to determine what type of day it is (using Ephemeris), selects the set of DateTime Items for that day type and then sets Timers for all those that are in the future.

That’s certainly an option, but it feels more complex to me to make a Trigger dependent on a (specific type of) item. I don’t think that any other triggers are. I would also want to include options such as ‘deleteAfterRun’ (default to true) and ‘runImmediatelyIfMissed’ (not sure about the default), which would make using an Item awkward (as the data would then either need to be split across the Item plus the Trigger). Maybe an option to substitute the point in time with data from an Item would be better?

It is implemented there, but it’s completely generic code not specific to Hue. The classes could literally be moved across to Core and used there. They are custom trigger types which trigger once at a specific time, no channels afaics. I assume that if you installed the hue-emulation binding you could probably use them in the UI (although I haven’t verified).

Fair enough, I see your point here. In this case I agree all that is required is metadata. I was thinking of extra DateTime items or similar.

I want to put my way here too. Maybe it is complex, but working perfect for me. Big advantage is, that you can control runtime length of the timer via a variable. This variable is an item which can be changed via setpoint in sitemap.

I covered everything in a function:
start of .rules file:

import org.eclipse.smarthome.model.script.ScriptServiceUtil
import java.util.HashMap
var HashMap<String, Timer> 			tTimersWithCancel = newHashMap()

Function:

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters:
// relatedItem = Item which should be changed if timer ends
// tTimersWithCancel = pointer to the timer variable
// timerlengthinseconds = length of timer in sec.
//   					  0 = timer should end before normal end but end action is executed
//  					 -1 = timer should end before normal end but end action is not executed
// newitemstate = State of the item after times end (end action)
//
val Procedures$Procedure4<GenericItem, HashMap<String, Timer>, Integer, String> startTimerWithCancel=[ relatedItem, tTimersWithCancel, timerlengthinseconds, newitemstate |

	// timer should end before normal end but end action is executed
	if(timerlengthinseconds == 0)
	{
		if(tTimersWithCancel.get(relatedItem.name) !== null)
			tTimersWithCancel.get(relatedItem.name).reschedule(now)
		return;
	}
	
	// timer should end before normal end but end action is not executed
	if(timerlengthinseconds < 0)
	{
		if(tTimersWithCancel.get(relatedItem.name) !== null)
		{
			tTimersWithCancel.get(relatedItem.name).cancel()
			tTimersWithCancel.put(relatedItem.name, null)
			tTimersWithCancel.remove(relatedItem.name)
		}
		return;
	}
	
	// Timer stated
	// wait a little bit, if two timers end on the same time not all of them are execute the commands
	Thread::sleep(100)

	// create the timer and do the action at the end of the timer
	if(tTimersWithCancel.get(relatedItem.name) === null || tTimersWithCancel.get(relatedItem.name).hasTerminated)
	{
		tTimersWithCancel.put(relatedItem.name, createTimer(now.plusSeconds(timerlengthinseconds)) [|
			tTimersWithCancel.get(relatedItem.name).cancel()
			tTimersWithCancel.put(relatedItem.name, null)
			tTimersWithCancel.remove(relatedItem.name)
			relatedItem.sendCommand(newitemstate.toString)
		])
	}

	// timer is running, just reschedule with full value of time
	else
	{
	tTimersWithCancel.get(relatedItem.name).reschedule(now.plusSeconds(timerlengthinseconds))
	}
]

How to use:

Start timer with :
startTimerWithCancel.apply(ITEMSWITCH1, tTimersWithCancel, 60, “OFF”)
This command will start a timer and after 60sec send command OFF to item ITEMSWITCH1

Extend timer time with :
startTimerWithCancel.apply(ITEMSWITCH1, tTimersWithCancel, 60, “OFF”)
This command will reschedule a running timer after 60sec send command OFF to item ITEMSWITCH1

Cancel timer but timer end command will run
startTimerWithCancel.apply(ITEMSWITCH1, tTimersWithCancel, 0, “OFF”)

Cancel timer but timer end command will not run
startTimerWithCancel.apply(ITEMSWITCH1, tTimersWithCancel, -1, “OFF”)

Real example, switch on light and switch it off after 60sec.:
ITEM:

Switch Dimmer02_Switch1      "Treppe EG/OG Dimmer [%s]"
Number nuTreppe_TimerTime	 "Treppe Bewegungslaufzeit [%d sec.]" 

in rule:
startTimerWithCancel.apply(Dimmer02_Switch1, tTimersWithCancel, (nuTreppe_TimerTime.state as Number).intValue, "OFF")

Right now the UI can’t handle Actions or anything else custom from bindings like that. There is not REST API end point to tell the UI that they exist and what their parameters are. The core actions are are hard coded in the UI. So this almost certainly would not be supported in the UI.

If I had a dime for every time someone here on the forum has asked for this very feature I could retire and do openHAB full time. :slight_smile: It is surprising to many new users that we can’t trigger a rule based on a DateTime Item’s state. So I don’t think it’s counter intuitive.

But over all, if I were king, I’d like to see a complete overhaul of scheduling in openHAB over all. We have too many ways to schedule stuff already: cron triggered rules, time of day triggered rules, sleeps, timers, expire, calendar bindings, astro binding, … They are all independent and solve their own little niche of the timing problem. But they don’t interact. There is no design for scheduling in openHAB. Each feature is bolted on to openHAB like barnacles on a ship. This will be yet another independent timing feature and as useful as it may be I don’t particularly like it for this reason.

But I also recognize that doing a complete overhaul of how scheduling works is a heavy lift and likely more than you would want to take on. But I like to style myself as the voice of the users. And as such, having yet another way to schedule things will add to complexity and confusion.