I recently ported my earlier Ikea triple-click hack from Rules DSL to Jython.
It’s much more responsive and elaborate than the original version, as it verifies that the items are grouped in triple-click association groups which belong to the gTripleToggle
group.
You can add one Switch Item to multiple triple-click association groups.
The script will gracefully manage many item/group configuration errors.
Ideally I could trigger a re-initialization if I’m able to see changes in the gTripleToggle
group.
Here’s the rule file (triple_click.py
):
"""
This rule implements triple-click support for ITEA TRADFRI remotes by using timers and click count.
"""
from core.rules import rule
from core.triggers import when
from core.actions import LogAction
import pprint
pp = pprint.PrettyPrinter(indent=4)
# Example using the createTimer Action
from core.actions import ScriptExecution
from org.joda.time import DateTime
# Keep track of rule initialization
initialized = False
# Triple-click item timers:
timers = {}
# Triple-click item click counters:
clicks = {}
# Final state if triple-click event occurred:
end_states = {}
# Triple-click subgroup items
group_items = {}
TRIPLE_TOGGLE_GROUP_NAME = "gTripleToggle"
MONITORING_TIME_SECONDS = 3
def myRule_initialize():
logTitle = "initialize()"
global initialized
LogAction.logDebug(
logTitle,
"AT START OF METHOD - intialized == {initialized}".format(initialized=str(initialized)),
)
global TRIPLE_TOGGLE_GROUP_NAME
global timers
global clicks
global end_states
global group_items
# Verify that item exists
if not itemRegistry.getItems(TRIPLE_TOGGLE_GROUP_NAME):
LogAction.logError(
logTitle,
"Item '{name}' does not exist! Please create this Group item.".format(
name=TRIPLE_TOGGLE_GROUP_NAME
),
)
return
# Verify thet item is of type Group
g = itemRegistry.getItem(TRIPLE_TOGGLE_GROUP_NAME)
if g.type != "Group":
LogAction.logError(
logTitle,
" Item '{name}' is of type '{type}', expecting 'Group'".format(
name=TRIPLE_TOGGLE_GROUP_NAME, type=g.type
),
)
return
# We're now okay to proceed, as the item named TRIPLE_TOGGLE_GROUP_NAME exists and is a Group
LogAction.logDebug(
logTitle,
"Item '{name}' is of type '{type}'".format(name=TRIPLE_TOGGLE_GROUP_NAME, type=g.type),
)
# Report if incorrect Item types are assigned as direct members of TRIPLE_TOGGLE_GROUP_NAME
tripleToggleGroupsErrors = list(item for item in g.getMembers() if item.type != "Group")
if tripleToggleGroupsErrors:
LogAction.logError(
logTitle,
"{count} Non-Group Item(s) found as direct members of '{name}'".format(
name=TRIPLE_TOGGLE_GROUP_NAME, count=len(tripleToggleGroupsErrors)
),
)
for item in tripleToggleGroupsErrors:
LogAction.logError(
logTitle,
"Item '{name}' is of type '{type}', expecting Group -- Item will be ignored".format(
name=item.name, type=item.type
),
)
# Now get all TripleToggle group names (they must be defined as direct members of TRIPLE_TOGGLE_GROUP_NAME and of thype Group)
tripleToggleGroups = list(item for item in g.getMembers() if item.type == "Group")
for item in tripleToggleGroups:
LogAction.logInfo(
logTitle, "Found TripleToggle [{type}] '{name}'".format(type=item.type, name=item.name)
)
# Process the members of the TripleToggle group
for item in (i for i in g.getAllMembers() if i.type != "Switch"):
LogAction.logError(
logTitle,
"Item '{name}' is of type '{type}', expecting 'Switch' -- Item will be ignored".format(
name=item.name, type=item.type
),
)
for item in (i for i in g.getAllMembers() if i.type == "Switch"):
LogAction.logInfo(logTitle, "[{type}] {name}".format(type=item.type, name=item.name))
# Determine the triple-click group(s) the Switch item belongs to:
item_triple_toggle_group_names = list(
group.name for group in tripleToggleGroups if group.name in item.getGroupNames()
)
item_triple_toggle_group_count = len(item_triple_toggle_group_names)
if item_triple_toggle_group_count == 0:
LogAction.logError(
logTitle,
"Item '{name}' belongs to 0 triple-click groups, probably direct descendant of '{groupname}' -- Item will be ignored".format(
name=item.name, groupname=TRIPLE_TOGGLE_GROUP_NAME
),
)
else:
if item_triple_toggle_group_count > 1:
LogAction.logWarn(
logTitle,
"Item '{name}' belongs to {count} triple-click groups: '{groups}'".format(
name=item.name,
count=str(item_triple_toggle_group_count),
groups="', '".join(item_triple_toggle_group_names),
),
)
else:
LogAction.logInfo(
logTitle,
"Item '{name}' belongs to 1 triple-click group(s): '{groups}'".format(
name=item.name, groups=item_triple_toggle_group_names
),
)
# Now initialize the Item:
group_items[item.name] = item_triple_toggle_group_names
timers[item.name] = None
clicks[item.name] = 0
end_states[item.name] = None
LogAction.logInfo(
logTitle,
"Item '{name}' of type '{type}' has been initialized".format(
type=item.type, name=item.name
),
)
# Initialization complete
initialized = True
LogAction.logDebug(
logTitle,
"AT END OF METHOD - intialized == {initialized}".format(initialized=str(initialized)),
)
@rule(
"TripleClick - System started", description="Initialize system state when the rule is reloaded"
)
@when("System started")
def SystemStarted(event):
logTitle = "SystemStarted()"
global initialized
myRule_initialize()
@rule("TripleClick - Click")
@when("Descendent of gTripleToggle changed")
def myRuleTripleClick_Clicked(event):
logTitle = "myRuleTripleClick_Clicked"
logPrefix = (
"(event is None): "
if event is None
else "Item '{name}' of type '{type}' with state '{state}': ".format(
name=event.itemName,
type=itemRegistry.getItem(event.itemName).type,
state=str(event.itemState),
)
)
LogAction.logDebug(logTitle, logPrefix + "At start of rule")
global initialized
if not initialized:
LogAction.logWarn(logTitle, logPrefix + "Not yet initialized - no action will be taken yet")
return
if event is None:
LogAction.logWarn(logTitle, logPrefix + "event == None")
return
if isinstance(event.itemState, UnDefType):
LogAction.logWarn(
logTitle,
logPrefix
+ "event item '{name} has state '{state}".format(
name=event.itemName, state=str(event.state)
),
)
return
# We're good to go
global timers
global clicks
global end_states
global group_items
LogAction.logDebug(
logTitle, logPrefix + "group_items = {list}".format(list=pp.pformat(group_items))
)
name = event.itemName
tripleToggleGroups = group_items.get(name)
# Bail out if Item conventions for this rule are not respected
if tripleToggleGroups is None:
LogAction.logError(
logTitle,
logPrefix
+ "Item '{name}' has no triple-click groups defined -- nothing to do".format(name=name),
)
return
LogAction.logDebug(
logTitle,
logPrefix
+ "AT START OF RULE - '{name}' (in group(s) '{groups}') has state '{state}' - starting the logic".format(
name=name, groups="', '".join(tripleToggleGroups), state=event.itemState
),
)
if timers.get(name) is None:
# Define the call-back that will be executed when the timer expires
def cb():
timers[name] = None
clicks[name] = 0
# We're using OH timers here as I want to reinitialize a running timer after each click (feature only available with OH timers)
timers[name] = ScriptExecution.createTimer(DateTime.now().plusSeconds(3), cb)
clicks[name] = 1
# Store the desired end state (current state of triggeringItem)
end_states[name] = str(event.itemState)
else:
cnt = clicks[name] + 1
stateInfo = end_states[name]
if cnt >= 3:
LogAction.logInfo(
logTitle,
logPrefix
+ u"{name} (end state will be {stateInfo}) toggle count: {count} ≥ 3 -- Switching {stateInfo} all associated items".format(
name=name, stateInfo=stateInfo, count=cnt
),
)
for g in tripleToggleGroups:
LogAction.logInfo(
logTitle,
logPrefix + "Processing items relating to group [{g}]".format(g=str(g)),
)
for i in itemRegistry.getItem(g).getAllMembers():
LogAction.logInfo(
logTitle,
logPrefix
+ "Processing item [{i}] of type [{t}] relating to group [{g}]".format(
i=i.name, t=i.type, g=str(g)
),
)
events.sendCommand(i, end_states[name])
if not timers[i.name] is None:
timers[i.name].cancel()
timers[i.name] = None
clicks[i.name] = 0
LogAction.logInfo(
logTitle,
logPrefix
+ "Processing item triple-toggle ended for item [{name}]".format(name=name),
)
else:
timers[name].reschedule(DateTime.now().plusSeconds(MONITORING_TIME_SECONDS))
clicks[name] = cnt
LogAction.logDebug(logTitle, logPrefix + "At end of rule")