Last active
March 6, 2021 17:12
-
-
Save neutmute/6a8e44f300b2b5a4971630371cd0dd97 to your computer and use it in GitHub Desktop.
openhab universal dimmer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
RegEx
transformation to be installed