Skip to content

Instantly share code, notes, and snippets.

@neutmute
Last active March 6, 2021 17:12
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 neutmute/6a8e44f300b2b5a4971630371cd0dd97 to your computer and use it in GitHub Desktop.
Save neutmute/6a8e44f300b2b5a4971630371cd0dd97 to your computer and use it in GitHub Desktop.
openhab universal dimmer
import org.openhab.core.model.script.ScriptServiceUtil
/*
# UniversalDimmer for OpenHAB 3
Reliably dim or colour lights or change volume of a target value over a set period of time
Thread: https://community.openhab.org/t/rule-to-slowly-fading-in-or-out-any-given-item-based-on-variables/54988/30?u=habau
Source: https://gist.github.com/neutmute/6a8e44f300b2b5a4971630371cd0dd97
Requires the RegEx transformation to be installed
*/
val java.util.Map<String, Timer> timerPool = newHashMap
// Uses linear regression to calculate the new value, send the comand and return the new value
var dimm = [GenericItem item, Number startingValue, Number targetValue, Number elapsedMs, Number periodMs |
var slope = ((targetValue - startingValue) / periodMs) as Number
var Number nextValue = ((slope * elapsedMs) + startingValue) as Number
item.sendCommand(nextValue)
nextValue
]
// universaldimmer.sendCommand("item_name,target-value,fade-time-ms")
rule "UniversalDimmer"
when
// syntax: <item name>,<target value>,<fade time>
//
// item name: item to fade
// target value: e.g. 100 for ON or 0 for OFF
// fade time: amount of time (in milliseconds) to reach the target value
//
// sample: lounge_light,100,10000
Item universaldimmer received command
then
var int timerTickEveryMs = 200;
val commandArray = receivedCommand.toString.split(",")
val itemName = commandArray.get(0) // Item to handle
var int targetValue = Integer::parseInt(commandArray.get(1)) // The final value we want
val int fadePeriodMs = Integer::parseInt(commandArray.get(2)) // How long to take to get there
val item = ScriptServiceUtil.getItemRegistry.getItem(itemName)
val itemType = item.getType()
// Use a non greedy regex to grab the first matching type as a percentType is an OnOffType too
val dataType = transform("REGEX", ".*?(PercentType|HSBType|OnOffType).*", item.getAcceptedDataTypes().toString())
if (itemType == "Group" && dataType == "OnOffType")
{
val groupItem = item as GroupItem;
logInfo("UniversalDimmer", String::format("Instructed to dim group '%s' that is On/Off type only. Acting on the child items", itemName))
groupItem.getAllMembers().forEach[i |
val dimmerArg = String.format("%s,%d,%d", i.name, targetValue, fadePeriodMs)
universaldimmer.sendCommand(dimmerArg)
]
return
}
// else: Groups with functions that are PercentType or HSBType can be acted on directly without recursion down their members
var int iteration = 0
var startedMillis = now.toInstant.toEpochMilli
if (item.state == NULL)
{
logInfo("UniversalDimmer", String::format("Cannot transition %s, state is null (dataType=%s)", itemName, dataType))
return
}
// Normalise the target value
if (targetValue > 100) {
targetValue = 100
} else if (targetValue < 0) {
targetValue = 0
}
// If a timer for this item is still on, kill it
var existingTimer = timerPool.get(itemName)
if (existingTimer !== null)
{
logInfo("UniversalDimmer", "Cancelling existing timer for " + itemName)
existingTimer.cancel
}
var int startingValue = 0
var bool shouldExit = false
switch(dataType)
{
case "HSBType":
startingValue = (item.state as HSBType).getBrightness().intValue
case "PercentType":
startingValue = (item.state as DecimalType).intValue
case "OnOffType":
{
// Someone is trying to dim a switch...what to do here is open to interpretation
var cmd = "OFF"
if (targetValue == 100) {
cmd = "ON"
}
logInfo("UniversalDimmer", String::format("UniversalDimmer was asked to set '%s' to %d but it is a switch. Setting to '%s' and exiting", itemName, targetValue, cmd));
item.sendCommand(cmd)
shouldExit = true
}
default:
{
logInfo("UniversalDimmer", String::format("UniversalDimmer doesn't know how to handle '%s' with dataType=%s", itemName, dataType));
shouldExit = true
}
}
if (shouldExit) {return}
logInfo("UniversalDimmer", String::format("Transition %s %s from %d => %d over %d ms", itemName, dataType, startingValue, targetValue, fadePeriodMs));
// create the timer
timerPool.put(itemName, createTimer(now.plusNanos(timerTickEveryMs * 1000000)) [|
var elapsedMs = now.toInstant.toEpochMilli - startedMillis
// Check if finished
var mustIterateToTarget = startingValue != targetValue;
if (elapsedMs <= fadePeriodMs && mustIterateToTarget) {
iteration = iteration + 1
var Number brightness = dimm.apply(item, startingValue, targetValue, elapsedMs, fadePeriodMs)
logInfo("UniversalDimmer", String::format("itemName=%s set to %.2f, iteration=%d, elapsedMs=%d", itemName, brightness, iteration, elapsedMs));
timerPool.get(itemName).reschedule(now.plusNanos(timerTickEveryMs * 1000000))
} else {
timerPool.remove(itemName)
// Time is up, ensure we land on the final value
item.sendCommand(targetValue)
logInfo("UniversalDimmer", String::format("Finished transitioning %s from %d => %d", itemName, startingValue, targetValue))
}
])
end
@neutmute
Copy link
Author

neutmute commented Jan 27, 2021

  • See revision 3 for OH2 version
  • Current OH3 version requires the RegEx transformation to be installed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment