Skip to content

Instantly share code, notes, and snippets.

Last active Dec 3, 2017
What would you like to do?
OpenHAB2 - Generic sleep timer

OpenHAB2 - Generic sleep timer

The following describes the necessary Functions and Procedures to create a small "Sleep Timer Library" for OpenHAB (Don't know, if Library is the correct title, but you know what I mean ;-)). Using this approach, it's easy for you to create your own custom sleep timers.

NOTE: This stuff doesn't depend on any add-on.

Usage example

The usage example does not cover initialization or recovery on system start and demonstrates a VERY BASIC example. But it's not very hard for you to integrate and customize it to your needs ;-)

1. Create a rule, e.g. inside the file SleepTimer.rules and paste in the following code.

import org.eclipse.xtext.xbase.lib.Functions
import java.util.Map
import org.eclipse.xtext.xbase.lib.Procedures.Procedure0

var Map<String, Integer> gOldSleepTimerValues = newHashMap
var Map<String, Timer> gSleepTimers = newHashMap

var Functions$Function3<GenericItem, Integer, Map<String, Integer>, Integer> getNextSleepTimerValue =
[_sleepTimerItem, _sleepTimerValueInterval, _oldTimerValueMap |
	val String logPrefix = "SleepTimer::getNextSleepTimerValue()"
	// Get the current value of the number item
	var int currentValue = (_sleepTimerItem.state as Number).intValue
	// Get the old value of the number item.
	var int oldValue = _oldTimerValueMap.get(
	// If no old value exists, then initialize it with 0.
	if (oldValue == null)
		oldValue = 0
	// Get the change direction: +1 == UP, -1 == DOWN
	var int changeDirection = Math.signum(currentValue - oldValue).intValue
	// Calculate the multiplier to get the next sleep timer value.
	var int multiplier = (currentValue / _sleepTimerValueInterval)
	if( changeDirection == -1 && (currentValue % _sleepTimerValueInterval) != 0)
		multiplier += 1	
	var int newValue =  multiplier * _sleepTimerValueInterval
	// Store the new value inside the _oldTimerValueMap.
	_oldTimerValueMap.put(, newValue); 

	logInfo(logPrefix, "oldValue        : " + oldValue)
	logInfo(logPrefix, "currentValue    : " + currentValue)
	logInfo(logPrefix, "changeDirection : " + changeDirection)
	logInfo(logPrefix, "multiplier      : " + multiplier)
	logInfo(logPrefix, "nextValue       : " + newValue)
	return newValue

var Procedures$Procedure5<GenericItem, Integer, Map<String, Timer>, Procedures$Procedure1<Integer>, Procedures$Procedure0> startSleepTimer =
[_sleepTimerItem, _sleepTimerUpdateInterval, _sleepTimerMap, _intervalExpiredAction, _timerExpiredAction |
	val String logPrefix = "SleepTimer::startSleepTimer()"
	// Check if a timer for the Item is already running and cancel it.
	// NOTE: At this point it's possible that a synchronization problem occurs. If this condition is checked while
	//       the createSleepTimer routine updates the _sleepTimerMap, it's possible that the timer is not canceled.
	var Timer sleepTimer = _sleepTimerMap.get(;
	if (sleepTimer != null) {
		logInfo(logPrefix, "Old timer canceled")
	// Turn off the sleep timer, if the new state of the number item is set to 0, e.g. via UI.
	if(_sleepTimerItem.state as Number == 0) {
		logInfo(logPrefix, "Set timer value to 0 --> Timer turned off.")
	// Create the "createSleepTimer" function, which creates a respective sleep timer.
	// NOTE: I hate warnings, so I use Object as second generic argument instead of
	//       Functions$Function2<Integer, Functions$Function2, Timer>. As a consequence the
	//       argument _crealteSleepTimerFunction has to be casted to 
	//       Functions$Function2<Integer, Functions$Function2, Timer>.
	//       This is a way to suppress the warnings ;-)
	val Functions$Function2<Integer, Object, Timer> createSleepTimer = 
	[ _remainingTime, _createSleepTimerFunction |
		// DEBUGGING ->	
		logInfo(logPrefix, "Create timer using " + _remainingTime + " as remaining time.")
		val Functions$Function2<Integer, Object, Timer> castedCreateSleepTimerFunction =
			_createSleepTimerFunction as Functions$Function2<Integer, Object, Timer>
		if (_remainingTime <= 0) {
			// Apply the timer action and update the sleepTimerMap.
			_sleepTimerMap.put(, null)
			// DEBUGGING ->	
			logInfo(logPrefix, "Execute timer expired action.")
			// <- DEBUGGING
			if (_timerExpiredAction != null) {
			} else {
				// DEBUGGING ->	
				logInfo(logPrefix, "No timer expired action specified.")
				// <- DEBUGGING				
			return null;
		} else {
			return createTimer(now.plusSeconds(_sleepTimerUpdateInterval), [|
				// Update the remaining time.
				var int updatedRemainingTime = _remainingTime - 1
				// Execute the interval expired action.
				// DEBUGGING ->	
				logInfo(logPrefix, "Execute interval expired action.")
				// <- DEBUGGING
				if (_intervalExpiredAction != null) {
				} else {
					// Default interval expired action.
				// DEBUGGING ->	
					logInfo(logPrefix, "RemainingTime: " + updatedRemainingTime)
				// <- DEBUGGING
						castedCreateSleepTimerFunction.apply(updatedRemainingTime, castedCreateSleepTimerFunction)
	// Start recursive timer creation.
	val int remainingSleepTime = (_sleepTimerItem.state as Number).intValue
	_sleepTimerMap.put(, createSleepTimer.apply(remainingSleepTime, createSleepTimer))

2. Add a Number item inside an item file, e.g. SleepTimer.items

Number timer_number_item

3. Add e.g. a Setpoint to your sitemap file

// NOTE: minValue must be 0. Other values are not supported.
Setpoint item=timer_number_item label="My Timer [%d]" step=10 minValue=0 maxValue=500

4. Now add a new rule at the end of your rule file SleepTimer.rules

rule "My SleepTimer rule"
	Item timer_number_item received command
	// the increase/decrease value interval of the timer which MUST correspond to the step-attribute
	// of your Setpoint (in this case 10)
	val int timerValueInterval = 10
	// The update interval in seconds, i.e. in this case every 3 seconds the timer value is decremented by 1.
	val int timerUpdateIntervalSec = 3

	// Calculate the new value for the SleepTimer and post it as update.
	var int newValue = getNextSleepTimerValue.apply(timer_number_item, timerValueInterval, gOldSleepTimerValues)
	// Create the timer.
	startSleepTimer.apply(timer_number_item, timerUpdateIntervalSec, gSleepTimers,
	[ _remainingTime |
                // Update routine, which is called, if the timer is updated, in this case every 3 seconds.
                // The argument _remainingTime is specifies the remaining time of the timer ;-)

                // Here, we just update the number item.
	, [|
                // This is the timer expiration action. E.g. you can turn off a switch or just log it on the console.
                logInfo("MySleepTimer", "Timer expired.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment