Skip to content

Instantly share code, notes, and snippets.

@atschwarz
Last active December 3, 2017 01:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atschwarz/8ffc80492503e4c34ceb8a4797273457 to your computer and use it in GitHub Desktop.
Save atschwarz/8ffc80492503e4c34ceb8a4797273457 to your computer and use it in GitHub Desktop.
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(_sleepTimerItem.name)
	// 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(_sleepTimerItem.name, newValue); 

	// DEBUGGING ->
	logInfo(logPrefix, "oldValue        : " + oldValue)
	logInfo(logPrefix, "currentValue    : " + currentValue)
	logInfo(logPrefix, "changeDirection : " + changeDirection)
	logInfo(logPrefix, "multiplier      : " + multiplier)
	logInfo(logPrefix, "nextValue       : " + newValue)
	// <- DEBUGGING
	
	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(_sleepTimerItem.name);
	if (sleepTimer != null) {
		// DEBUGGING ->
		logInfo(logPrefix, "Old timer canceled")
		// <- DEBUGGING
		
		sleepTimer.cancel()
	}
	
	// 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.")
		return
	}
	
	// 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.")
		// <- DEBUGGING
		
		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(_sleepTimerItem.name, null)
			
			// DEBUGGING ->	
			logInfo(logPrefix, "Execute timer expired action.")
			// <- DEBUGGING
			
			if (_timerExpiredAction != null) {
				_timerExpiredAction.apply()
			} 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) {
					_intervalExpiredAction.apply(updatedRemainingTime)
				} else {
					// Default interval expired action.
					_sleepTimerItem.postUpdate(updatedRemainingTime)
				}
				
				// DEBUGGING ->	
					logInfo(logPrefix, "RemainingTime: " + updatedRemainingTime)
				// <- DEBUGGING
				
				_sleepTimerMap.put(
						_sleepTimerItem.name, 
						castedCreateSleepTimerFunction.apply(updatedRemainingTime, castedCreateSleepTimerFunction)
				)
			])
		} 
	]
	
	// Start recursive timer creation.
	val int remainingSleepTime = (_sleepTimerItem.state as Number).intValue
	_sleepTimerMap.put(_sleepTimerItem.name, 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"
when
	Item timer_number_item received command
then
	// 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)
	timer_number_item.postUpdate(newValue)
	
	// 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.
                timer_number_item.postUpdate(_remainingTime)
	]
	, [|
                // 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.")
        ])
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment