Hey all,
I came up with a function which might be useful to others.
The use case is:
- you have Number item which works as a scene switch - i.e. each value of the item corresponds to one scene
- you want to control these scenarios through Switch items, e.g. because you want to be able to use Alexa speech commands with these switches
Everything is contained in one Python file and can be controlled by adding specific tags to an item - I tried to keep the interface clean.
The setup is working for me, but I’d say I’m only 80% done - see the TODOs in the description.
I would be very interested in your feedback - what could be improved?
What did I miss?
Are there better approaches in OpenHab / Python to accomplish what I did?
Cheers,
-bastian
EDIT: the script below is outdated. Please use the script from the latest comment below on this page which has the full script.
from core.jsr223.scope import events, itemRegistry, OnOffType, ON, OFF
from core.rules import rule
from core.triggers import when
from core.items import add_item
from core.utils import post_update_if_different
#from personal.logger import Logger
"""
Usage Scenario:
- you have Number item which works as a scene switch - i.e. each value
of the item corresponds to one scene
- you want to control these scenarios through Switch items, e.g. because
you want to be able to use Alexa speech commands with these switches
This script allows just that, with minimal effort on your side.
Usage:
You need to add two tags to the Number item:
1. "hl:multitoggle"
This tag must always be set exactly as shown.
2. "hl:config={0: 'Off', 1: 'First Scene Name', 2: 'Second scene name'}"
The part contained in curly brackets must be a valid Pyhton dictionary,
it may contain as many scene-items as you need.
The key must be integer indexes in ascending order, the value becomes
the label of the corresponding scene switch and hence also the keyword
for Alexa for that switch.
The first dictionary item with 0 as key is considered to be an Off-state
for the scenario.
Example:
Number NMediaScene "Media Scene [%s]" ["hl:multitoggle", "hl:config={0: 'Off', 1: 'Music', 2: 'Fire TV', 3: 'PS4'}"]
Function:
- setting the Number item to a specific scene, will turn the Switch
associated to that number to ON and all others to OFF.
- setting a Switch to ON will set the Number item to the associated
int value
- setting any Switch except the first one to OFF is considered switching
the whole scenario to Off-state and thus sets the Numer item to 0
- setting the first Switch to OFF is senseless with regards to the
concept, as it would mean "switch off the Off state" - therefore the
system prevents this by setting the switch back to ON immidiately
TODO: clarify, how to remove created items if the multitoggle base item
(Number) does not exist anymore
workaround: delete manually in Paper UI
TODO: clarify, how to work with changes of the multitoggle base item
(Number) (currently, items are deleted and recreated on each run)
TODO: ensure that spaces work in scene names
workaround: don't use spaces
TODO: check if unicode chars work in scene names
workaround: don't use unicod chars
TODO: check what happens to the Alexa items when elements are re-created
(i.e. do you have to do a new search for items or not)
"""
def add_multitoggle_switch(name, label, tags=[], state="OFF", overwrite=True):
if itemRegistry.getItems(name) and overwrite:
itemRegistry.remove(name)
# create the switch - this will raise an exception if the Switch exists and overwrite=False
new_toggle_switch = add_item(item_name, item_type="Switch", label=label, tags=tags)
# set switched initially to OFF
events.postUpdate(new_toggle_switch, "OFF")
return new_toggle_switch
def multitoggle_trigger_generator(group_list):
"""Decorator which generates a trigger for the multitoggle rule."""
def wrapper(function):
for group in group_list:
when("Member of {} received command".format(group))(function)
return function
return wrapper
#my_log = Logger(initial_entry=False)
# naming definitions
ns_helper_library = "hl"
multitoggle_tag = "multitoggle"
multitoggle_tag_off_state = "offstate"
config_tag = "config"
multitoggle_Group_prefix = "GMT"
multitoggle_switch_prefix = "SMT"
multitoggle_groups = []
#my_log.inspect_var(itemRegistry.getItemsByTag("hl:multitoggle"), "itemRegistry.byTag")
#my_log.inspect_var(itemRegistry.getItemsByTagAndType("Number", "hl:multitoggle"), "itemRegistry.byTagAndType")
for toggle_as_number in itemRegistry.getItemsByTagAndType("Number", ns_helper_library + ":" + multitoggle_tag):
# iterate over all toggle-items
#my_log.message("Setting up multitoggle for {}".format(toggle_as_number.name))
# add a group - works as a handle to the switches
group_name = multitoggle_Group_prefix + toggle_as_number.name
if itemRegistry.getItems(group_name):
itemRegistry.remove(group_name)
toggle_group = add_item(group_name, item_type="Group", tags=[ns_helper_library + ":" + multitoggle_tag], label=group_name)
#store the new multitoggle group
multitoggle_groups.append(group_name)
# include the original item in the group
toggle_group.addMember(toggle_as_number)
# grab the config info from the correct label
for tag in toggle_as_number.tags:
if tag.find(ns_helper_library + ":" + config_tag) != -1:
toggle_options = eval(tag[tag.find("{"):])
# construct a switch item for the first value - the off-state
key, value = toggle_options.items()[0]
item_name = multitoggle_switch_prefix + toggle_as_number.name + "_" + str(key)
new_toggle_switch = add_multitoggle_switch(item_name, value, ["Switchable", ns_helper_library + ":" + multitoggle_tag_off_state])
toggle_group.addMember(new_toggle_switch)
for key, value in toggle_options.items()[1:]:
# construct a switch item for the remaining scene values
item_name = multitoggle_switch_prefix + toggle_as_number.name + "_" + str(key)
new_toggle_switch = add_multitoggle_switch(item_name, value, ["Switchable"])
toggle_group.addMember(new_toggle_switch)
# sync switch state to number item
if not isinstance(toggle_as_number.state, UnDefType) and toggle_as_number.state.intValue() >= 0:
events.postUpdate(multitoggle_switch_prefix + toggle_as_number.name + "_" + str(toggle_as_number.state.intValue()), "ON")
@rule("Multitoggle", description="Auto generated rule to handle multitoggle.")
@multitoggle_trigger_generator(multitoggle_groups)
# @when("Member of {} received command".format("GMTNTryoutAAA"))
def multitoggle(event):
"""Auto generated rule to handle multitoggle."""
#my_log = Logger(multitoggle, event)
# detect the multi toggle group, where the change happened
multitoggle_group_name = itemRegistry.getItem(event.itemName).groupNames[0]
multitoggle_group = itemRegistry.getItem(multitoggle_group_name)
if not multitoggle_group.hasTag(ns_helper_library + ":" + multitoggle_tag):
raise Exception("group is expected to have the tag <{}>".format(ns_helper_library + ":" + multitoggle_tag))
if itemRegistry.getItem(event.itemName).getType() == "Switch":
toggle_as_number = filter(lambda item: item.getType() == "Number", multitoggle_group.members)[0]
if event.itemCommand == OnOffType.OFF:
if itemRegistry.getItem(event.itemName).hasTag(ns_helper_library + ":" + multitoggle_tag_off_state):
# it's the first switch, which may never be off, as the switch itself represents the off-state
events.postUpdate(event.itemName, "ON")
else:
# it's not the first switch, so the first switch needs to be set to ON
first_switch = filter(lambda item: item.hasTag(ns_helper_library + ":" + multitoggle_tag_off_state), multitoggle_group.members)[0]
events.postUpdate(first_switch, "ON")
events.sendCommand(toggle_as_number, first_switch.name[first_switch.name.rfind("_")+1:])
elif event.itemCommand == OnOffType.ON:
# set all switches of that group to OFF, except the one which triggered the change
for item in filter(lambda item: item.getType() == "Switch", multitoggle_group.members):
if item.name != event.itemName:
post_update_if_different(item, "OFF")
# set the numeric item to the number of the switch, which triggered to ON
events.sendCommand(toggle_as_number, event.itemName[event.itemName.rfind("_")+1:])
elif itemRegistry.getItem(event.itemName).getType() == "Number":
# set all switches of that group to OFF, except the switch which corresponds to the selected option
for item in filter(lambda item: item.getType() == "Switch", multitoggle_group.members):
if item.name[item.name.rfind("_")+1:] == str(event.itemCommand):
post_update_if_different(item, "ON")
else:
post_update_if_different(item, "OFF")
Example item:
Number NMyMediaScene
"Media Scene [%s]" ["hl:multitoggle", "hl:config={0: 'Off', 1: 'Fernsehen', 2: 'PS4', 3: 'Musik'}"]