Rule to slowly fading in or out any given item based on variables

Tags: #<Tag:0x00007f617e0b9f28> #<Tag:0x00007f617e0b9de8>

Hi,

I designed a rule to slowly fade in or out any light. One can pas a command including

  • the item
  • the direction
  • how long shall the fading go
  • how big shall the steps be
    to the rule.

Example:

universaldimmer.sendCommand("Dimmer,IN,6000,12,dim_DR_TE_Terrasse_Links")

This will fade in the Dimmer dim_DR_TE_Terrasse_Links within 6 seconds using steps of 12%.

Item:

String universaldimmer	"Trigges a rule to fade in/out a light"

The rule

import org.eclipse.smarthome.model.script.ScriptServiceUtil

var Timer fade_Timer = null

// will dimm the item and report back the new brightness
var dimm = [ GenericItem myitem, String mytype, String mydirection, Number mystep  |
	var int mybrightness = 0

  	//get brightness
	if (mytype == "Color") {
		mybrightness = (myitem.state as HSBType).getBrightness().intValue
	} else if (mytype == "Dimmer") {
		mybrightness= (myitem.state as DecimalType).intValue
	}

	//calculate new brightness
	if ( mydirection == "IN") {

		mybrightness = mybrightness + mystep.intValue
		// do not dimm over 100
		if ( mybrightness > 100 ) {
			mybrightness = 100
		}
	} else {
		mybrightness = mybrightness - mystep.intValue
		// do not dimm under 0
		if ( mybrightness < 0 ) mybrightness = 0
	}
	logInfo("Universaldimmer", "I will dimm "+ mydirection + " " + myitem.label + " (" + mytype + ") to " + mybrightness)
	
	// set new brightness
	myitem.sendCommand(mybrightness)
	
	// return new brightness
	mybrightness
]


rule "Universaldimmer"
when
    Item universaldimmer received command	//expect e.g. Color,IN,120000,12,col_E1_ES_Hue_Go_Farbe
then
	val buffer						= receivedCommand.toString.split(",")
	val mytype						= receivedCommand.toString.split(',').get(0)					// Color or Dimmer
	val mydirection					= receivedCommand.toString.split(',').get(1)					// IN or OUT 
	val int myfadetime				= Integer::parseInt(buffer.get(2))					 			// time for fading
	val int mystep					= Integer::parseInt(buffer.get(3))								// step for fading
	val int mytime					= Math::round(myfadetime * mystep / 100) 						// calculate timer
	val myitem_name					= receivedCommand.toString.split(',').get(4)					// Item to handle
	val myitem						= ScriptServiceUtil.getItemRegistry.getItem(myitem_name)

	//If a timer is still on kill it
	fade_Timer?.cancel

	// fade and return new brightness
	var int mybrightness = dimm.apply(myitem, mytype, mydirection, mystep)

	fade_Timer = createTimer(now.plusMillis(mytime)) [|
		
		//Check if finishes
		if (((mybrightness < 100 ) && (mydirection == "IN")) || ((mybrightness > 0) && (mydirection == "OUT")))  {
			// if not finished dim again 
			mybrightness = dimm.apply(myitem, mytype, mydirection, mystep)

			// rescheduling
			fade_Timer.reschedule(now.plusMillis(mytime))
		}
    ]

end

Best regards
Peter

3 Likes

Hi,

I don’t want to edit the above as someone might need it as it is. I now have a new version, where you can decide the target-value instead just “IN” or “OUT”. You can still use 0 instead of “OUT” and 100 instead of “IN”.

Example:

universaldimmer.sendCommand("Dimmer,80,6000,12,dim_DR_TE_Terrasse_Links")

This will fade in the Dimmer dim_DR_TE_Terrasse_Links to 80% using steps of 12% in about 6 seconds.

Item:

String universaldimmer	"Trigges a rule to fade in/out a light"

Rule:

import org.eclipse.smarthome.model.script.ScriptServiceUtil

var Timer fade_Timer = null

// will dimm the item and report back the new brightness
var dimm = [ GenericItem myitem, String mytype, Number mytarget, Number mystep  |
	var int mybrightness = 0

  	//get brightness
	if (mytype == "Color") {
		mybrightness = (myitem.state as HSBType).getBrightness().intValue
	} else if (mytype == "Dimmer") {
		mybrightness= (myitem.state as DecimalType).intValue
	} 

	//calculate new brightness
	if ( mytarget.intValue > mybrightness ) {

		mybrightness = mybrightness + mystep.intValue
		// do not dimm over  mytarget
		if ( mybrightness >  mytarget.intValue ) mybrightness =  mytarget.intValue

	} else {
		mybrightness = mybrightness - mystep.intValue
		// do not dimm under  mytarget
		if ( mybrightness <  mytarget.intValue ) mybrightness =  mytarget.intValue
	}
	logInfo("Universaldimmer", "I will dimm " + myitem.label + " (" + mytype + ") to " + mybrightness)
	
	// set new brightness
	myitem.sendCommand(mybrightness)
	
	// return new brightness
	mybrightness
]


rule "Universaldimmer"
when
    Item universaldimmer received command	//expect e.g. Color,100,120000,12,col_E1_ES_Hue_Go_Farbe
then
	val buffer			= receivedCommand.toString.split(",")
	val mytype			= receivedCommand.toString.split(',').get(0)					// Color or Dimmer
	var int mytarget	= Integer::parseInt(buffer.get(1))								// where to go to 
	val int myfadetime	= Integer::parseInt(buffer.get(2))					 			// time for fading
	val int mystep		= Integer::parseInt(buffer.get(3))								// step for fading
	val myitem_name		= receivedCommand.toString.split(',').get(4)					// Item to handle
	val myitem			= ScriptServiceUtil.getItemRegistry.getItem(myitem_name)
	var int mytime		= 0

	// avoid that the target cannot be reaced
	if		( mytarget > 100 )	mytarget = 100
	else if ( mytarget < 0 )	mytarget = 0
	
	//If a timer is still on kill it
	fade_Timer?.cancel

	// fade
	var int mybrightness = dimm.apply(myitem, mytype, mytarget, mystep)
	
	// calculate timesteps
	if ( mybrightness > mytarget ) {
		mytime		= Math::round(myfadetime * mystep / ((mybrightness + mystep ) - mytarget))
	} else {
		mytime		= Math::round(myfadetime * mystep / (mytarget - (mybrightness + mystep)))
	}
	
	// create the timer
	fade_Timer = createTimer(now.plusMillis(mytime)) [|
		
		//Check if finishes
		if (mybrightness != mytarget )  {
			// if not finished dim again 
			mybrightness = dimm.apply(myitem, mytype, mytarget, mystep)

			// rescheduling
			fade_Timer.reschedule(now.plusMillis(mytime))
		}
    ]

end

Best regards
Peter

3 Likes

smart script, works perfect!

This is incredibly helpful for a new user - just getting started with all of this.

I do have a question about usage, though: it doesn’t appear that calls to this rule can overlap - that is, if I have one bulb fading in over a long period of time (20 minutes), and I try to use this function to fade in a different bulb, starting 10 minutes into that process, the function stops fading the first bulb and just fades in the second.

Is there a straightforward way to cause the rule to run as a “second instantiation” and not cut off the first, or would I need to duplicate the universal dimmer rule for each bulb I want to simultaneously dim?

Welcome to the openHAB forum!

If you look into the rule “Universaldimmer” you will see that the author decided to kill any still running timer. That is the reason for this behaviour.

I just modified the rule to allow simultaneously dim for different items:

import org.eclipse.smarthome.model.script.ScriptServiceUtil

val java.util.Map<String, Timer> timerPool = newHashMap

// will dimm the item and report back the new brightness
var dimm = [GenericItem item, String type, Number targetValue, Number stepSize |
	var int itemBrightness = 0

	// get brightness
	if (type == "Color") {
		itemBrightness = (item.state as HSBType).getBrightness().intValue
	} else if (type == "Dimmer") {
		itemBrightness = (item.state as DecimalType).intValue
	}

	// calculate new brightness
	if (targetValue.intValue > itemBrightness) {

		itemBrightness = itemBrightness + stepSize.intValue
		// do not dimm above targetValue
		if (itemBrightness > targetValue.intValue) {
			itemBrightness = targetValue.intValue
		}
	} else {

		itemBrightness = itemBrightness - stepSize.intValue
		// do not dimm below targetValue
		if (itemBrightness < targetValue.intValue) {
			itemBrightness = targetValue.intValue
		}
	}
	logInfo("home.universaldimmer", "Dimm " + item.name + " (" + type + ") to " + itemBrightness)
	
	// set new brightness
	item.sendCommand(itemBrightness)
	
	// return new brightness
	itemBrightness
]

rule "Universaldimmer"
when
	// syntax: <type>,<target value>,<fade time>,<step size>,<item name>
	// 
	// type: Color or Dimmer
	// target value: e.g. 100 for ON or 0 for OFF
	// fade time: amount of time (in milli seconds) to reach the target value
	// step size: amount of steps to reach the target value
	// item name: item to fade
	//
	// sample: Color,100,10000,12,col_E1_ES_Hue_Go_Farbe
	Item universaldimmer received command
then

	val buffer			= receivedCommand.toString.split(",")
	val type			= buffer.get(0)						// Color or Dimmer
	var int targetValue	= Integer::parseInt(buffer.get(1))	// where to go to
	val int fadeTime	= Integer::parseInt(buffer.get(2))	// time for fading
	val int stepSize	= Integer::parseInt(buffer.get(3))	// step for fading
	val itemName		= buffer.get(4)						// Item to handle
	val item			= ScriptServiceUtil.getItemRegistry.getItem(itemName)
	var int rescheduleTime	= 0

	logInfo("home.universaldimmer", "Item to dimm: " + itemName)

	// avoid that the target cannot be reaced
	if (targetValue > 100) {
		targetValue = 100
	} else if (targetValue < 0) {
		targetValue = 0
	}

	// If a timer is still on kill it
	timerPool.get(itemName)?.cancel

	// fade
	var int brightness = dimm.apply(item, type, targetValue, stepSize)

	if (brightness > targetValue) {
		rescheduleTime = Math::round(fadeTime * stepSize / ((brightness + stepSize ) - targetValue))
	} else {
		rescheduleTime = Math::round(fadeTime * stepSize / (targetValue - (brightness + stepSize)))
	}

	// create the timer
	timerPool.put(itemName, createTimer(now.plusMillis(rescheduleTime)) [|
		
		// Check if finishes
		if (brightness != targetValue) {
			// if not finished dim again 
			brightness = dimm.apply(item, type, targetValue, stepSize)

			// rescheduling
			timerPool.get(itemName).reschedule(now.plusMillis(rescheduleTime))
		} else {
			timerPool.remove(itemName)
		}
	])

end
1 Like

Hi, any way to make the steps smaller than 1? I tried 0.5 but had an error? Thanks! :slight_smile:

You’d have to replace all theint and intValue references to their float counterparts.