def when(target): """ This function decorator creates a ``triggers`` attribute in the decorated function, which is used by the ``rule`` decorator when creating the rule. The ``when`` decorator simplifies the use of many of the triggers in this module and allows for them to be used with natural language similar to what is used in the rules DSL. See :ref:`Guides/Rules:Decorators` for examples of how to use this decorator. Examples: .. code-block:: @when("Time cron 55 55 5 * * ?") @when("Item Test_String_1 changed from 'old test string' to 'new test string'") @when("Item gMotion_Sensors changed") @when("Member of gMotion_Sensors changed from ON to OFF") @when("Descendent of gContact_Sensors changed from OPEN to CLOSED") @when("Item Test_Switch_2 received update ON") @when("Item Test_Switch_1 received command OFF") @when("Item added") @when("Item removed") @when("Item updated") @when("Thing added") @when("Thing removed") @when("Thing updated") @when("Thing kodi:kodi:familyroom changed") @when("Thing kodi:kodi:familyroom changed from ONLINE to OFFLINE")# requires S1636, 2.5M2 or newer @when("Thing kodi:kodi:familyroom received update ONLINE")# requires S1636, 2.5M2 or newer @when("Channel astro:sun:local:eclipse#event triggered START")# must use a Channel of kind Trigger @when("System started")# requires S1566, 2.5M2 or newer ('System shuts down' has not been implemented) @when("Directory /opt/test [created, deleted, modified]")# requires S1566, 2.5M2 or newer @when("Subdirectory 'C:\My Stuff' [created]")# requires S1566, 2.5M2 or newer Args: target (string): the `rules DSL-like formatted trigger expression <https://www.openhab.org/docs/configuration/rules-dsl.html#rule-triggers>`_ to parse """ from os import path itemRegistry = scriptExtension.get("itemRegistry") # type: t_itemRegistry things = scriptExtension.get("things") # type: t_things from core.log import getLogger try: from org.openhab.core.thing import ChannelUID, ThingUID, ThingStatus from org.openhab.core.thing.type import ChannelKind except: from org.eclipse.smarthome.core.thing import ChannelUID, ThingUID, ThingStatus from org.eclipse.smarthome.core.thing.type import ChannelKind try: from org.eclipse.smarthome.core.types import TypeParser except: from org.openhab.core.types import TypeParser try: from org.quartz.CronExpression import isValidExpression except: # Quartz is removed in OH3, this needs to either impliment or match # functionality in `org.openhab.core.internal.scheduler.CronAdjuster` def isValidExpression(expr): import re expr = expr.strip() if expr.startswith("@"): return re.match( r"@(annually|yearly|monthly|weekly|daily|hourly|reboot)", expr) is not None parts = expr.split() if 6 <= len(parts) <= 7: for i in range(len(parts)): if not re.match( r"\?|(\*|\d+)(\/\d+)?|(\d+|\w{3})(\/|-)(\d+|\w{3})|((\d+|\w{3}),)*(\d+|\w{3})", parts[i]): return False return True return False LOG = getLogger(u"core.triggers") try: def item_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] if trigger_target in ["added", "removed", "updated"]: event_names = { "added": "ItemAddedEvent", "removed": "ItemRemovedEvent", "updated": "ItemUpdatedEvent" } trigger_name = "Item-{}".format( event_names.get(trigger_target)) function.triggers.append( ItemEventTrigger(event_names.get(trigger_target), trigger_name=trigger_name).trigger) else: item = itemRegistry.getItem(trigger_target) group_members = [] if target_type == "Member of": group_members = item.getMembers() elif target_type == "Descendent of": group_members = item.getAllMembers() else: group_members = [item] for member in group_members: trigger_name = "Item-{}-{}{}{}{}{}".format( member.name, trigger_type.replace(" ", "-"), "-from-{}".format(old_state) if old_state is not None else "", "-to-" if new_state is not None and trigger_type == "changed" else "", "-" if trigger_type == "received update" and new_state is not None else "", new_state if new_state is not None else "") trigger_name = validate_uid(trigger_name) if trigger_type == "received update": function.triggers.append( ItemStateUpdateTrigger( member.name, state=new_state, trigger_name=trigger_name).trigger) elif trigger_type == "received command": function.triggers.append( ItemCommandTrigger( member.name, command=new_state, trigger_name=trigger_name).trigger) else: function.triggers.append( ItemStateChangeTrigger( member.name, previous_state=old_state, state=new_state, trigger_name=trigger_name).trigger) LOG.trace(u"when: Created item_trigger: '{}'".format( trigger_name)) return function def cron_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append( CronTrigger(trigger_type, trigger_name=trigger_name).trigger) LOG.trace(u"when: Created cron_trigger: '{}'".format(trigger_name)) return function def system_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] #if trigger_target == "started": function.triggers.append( StartupTrigger(trigger_name=trigger_name).trigger) #else: # function.triggers.append(ShutdownTrigger(trigger_name=trigger_name).trigger) LOG.trace( u"when: Created system_trigger: '{}'".format(trigger_name)) return function def thing_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] if trigger_target in ["added", "removed", "updated"]: event_names = { "added": "ThingAddedEvent", "removed": "ThingRemovedEvent", "updated": "ThingUpdatedEvent" } function.triggers.append( ThingEventTrigger(event_names.get(trigger_target), trigger_name=trigger_name).trigger) elif new_state is not None or old_state is not None: if trigger_type == "changed": function.triggers.append( ThingStatusChangeTrigger( trigger_target, previous_status=old_state, status=new_state, trigger_name=trigger_name).trigger) else: function.triggers.append( ThingStatusUpdateTrigger( trigger_target, status=new_state, trigger_name=trigger_name).trigger) else: event_types = "ThingStatusInfoChangedEvent" if trigger_type == "changed" else "ThingStatusInfoEvent" function.triggers.append( ThingEventTrigger(event_types, trigger_target, trigger_name=trigger_name).trigger) LOG.trace( u"when: Created thing_trigger: '{}'".format(trigger_name)) return function def channel_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append( ChannelEventTrigger(trigger_target, event=new_state, trigger_name=trigger_name).trigger) LOG.trace( u"when: Created channel_trigger: '{}'".format(trigger_name)) return function def directory_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] event_kinds = [] if "created" in trigger_type: event_kinds.append(ENTRY_CREATE) if "deleted" in trigger_type: event_kinds.append(ENTRY_DELETE) if "modified" in trigger_type: event_kinds.append(ENTRY_MODIFY) if event_kinds == []: event_kinds = [ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY] function.triggers.append( DirectoryEventTrigger( trigger_target, event_kinds=event_kinds, watch_subdirectories=target_type == "Subdirectory", trigger_name=trigger_name).trigger) LOG.trace( u"when: Created channel_trigger: '{}'".format(trigger_name)) return function target_type = None trigger_target = None trigger_type = None old_state = None new_state = None trigger_name = None if isValidExpression(target): # a simple cron target was used, so add a default target_type and trigger_target (Time cron XXXXX) target_type = "Time" trigger_target = "cron" trigger_type = target trigger_name = "Time-cron-{}".format(target) else: from shlex import split input_list = split(target) if len(input_list) > 1: # target_type trigger_target [trigger_type] [from] [old_state] [to] [new_state] while input_list: if target_type is None: if " ".join(input_list[0:2]) in [ "Member of", "Descendent of" ]: target_type = " ".join(input_list[0:2]) input_list = input_list[2:] else: target_type = input_list.pop(0) elif trigger_target is None: if target_type == "System" and len(input_list) > 1: raise ValueError( u"when: \"{}\" could not be parsed. trigger_type '{}' is invalid for target_type 'System'. The only valid trigger_type is 'started'." .format(target, target_type)) # if " ".join(input_list[0:2]) == "shuts down": # trigger_target = "shuts down" else: trigger_target = input_list.pop(0) elif trigger_type is None: if "received" in " ".join(input_list[0:2]): if " ".join(input_list[0:2]) == "received update": if target_type in [ "Item", "Thing", "Member of", "Descendent of" ]: input_list = input_list[2:] trigger_type = "received update" else: raise ValueError( u"when: \"{}\" could not be parsed. 'received update' is invalid for target_type '{}'. The valid options are 'Item', 'Thing', 'Member of', or 'Descendent of'." .format(target, target_type)) elif " ".join( input_list[0:2]) == "received command": if target_type in [ "Item", "Member of", "Descendent of" ]: input_list = input_list[2:] trigger_type = "received command" else: raise ValueError( u"when: \"{}\" could not be parsed. 'received command' is invalid for target_type '{}'. The valid options are 'Item', 'Member of', or 'Descendent of'." .format(target, target_type)) else: raise ValueError( u"when: \"{}\" could not be parsed. '{}' is invalid for target_type '{}'. The valid options are 'received update' or 'received command'." .format(target, " ".join(input_list[0:2]), target_type)) elif input_list[0][0] == "[": if input_list[0][ -1] == "]": # if there are no spaces separating the event_kinds, it's ready to go trigger_type = input_list.pop(0).replace( " ", "").strip("[]'\"").split(",") else: event_kinds = input_list.pop(0).replace( " ", "").strip("[]'\"") found_closing_bracket = False while input_list and not found_closing_bracket: if input_list[0][-1] == "]": found_closing_bracket = True event_kinds = "{}{}".format( event_kinds, input_list.pop(0).replace( " ", "").strip("[]'\"")) trigger_type = event_kinds.split(",") if target_type in ["Directory", "Subdirectory"]: for event_kind in trigger_type: if event_kind not in [ "created", "deleted", "modified" ]: # ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY raise ValueError( u"when: \"{}\" could not be parsed. trigger_type '{}' is invalid for target_type '{}'. The valid options are 'created', 'deleted', or 'modified'." .format(target, trigger_type, target_type)) else: raise ValueError( u"when: \"{}\" could not be parsed. target_type '{}' is invalid for trigger_target '{}'. The valid options are 'Directory' or 'Subdirectory'." .format(target, target_type, trigger_target)) elif input_list[0] == "changed": if target_type in [ "Item", "Thing", "Member of", "Descendent of" ]: input_list.pop(0) trigger_type = "changed" else: raise ValueError( u"when: \"{}\" could not be parsed. 'changed' is invalid for target_type '{}'" .format(target, target_type)) elif input_list[0] == "triggered": if target_type == "Channel": trigger_type = input_list.pop(0) else: raise ValueError( u"when: \"{}\" could not be parsed. 'triggered' is invalid for target_type '{}'. The only valid option is 'Channel'." .format(target, target_type)) elif trigger_target == "cron": if target_type == "Time": if isValidExpression(" ".join(input_list)): trigger_type = " ".join(input_list) del input_list[:] else: raise ValueError( u"when: \"{}\" could not be parsed. '{}' is not a valid cron expression. See http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-06." .format(target, " ".join(input_list))) else: raise ValueError( u"when: \"{}\" could not be parsed. 'cron' is invalid for target_type '{}'" .format(target, target_type)) else: raise ValueError( u"when: \"{}\" could not be parsed because the trigger_type {}" .format( target, "is missing" if input_list[0] is None else "'{}' is invalid".format(input_list[0]))) else: if old_state is None and trigger_type == "changed" and input_list[ 0] == "from": input_list.pop(0) old_state = input_list.pop(0) elif new_state is None and trigger_type == "changed" and input_list[ 0] == "to": input_list.pop(0) new_state = input_list.pop(0) elif new_state is None and ( trigger_type == "received update" or trigger_type == "received command"): new_state = input_list.pop(0) elif new_state is None and target_type == "Channel": new_state = input_list.pop(0) elif input_list: # there are no more possible combinations, but there is more data raise ValueError( u"when: \"{}\" could not be parsed. '{}' is invalid for '{} {} {}'" .format(target, input_list, target_type, trigger_target, trigger_type)) else: # a simple Item target was used (just an Item name), so add a default target_type and trigger_type (Item XXXXX changed) if target_type is None: target_type = "Item" if trigger_target is None: trigger_target = target if trigger_type is None: trigger_type = "changed" # validate the inputs, and if anything isn't populated correctly throw an exception if target_type is None or target_type not in [ "Item", "Member of", "Descendent of", "Thing", "Channel", "System", "Time", "Directory", "Subdirectory" ]: raise ValueError( u"when: \"{}\" could not be parsed. target_type is missing or invalid. Valid target_type values are: Item, Member of, Descendent of, Thing, Channel, System, Time, Directory, and Subdirectory." .format(target)) elif target_type != "System" and trigger_target not in [ "added", "removed", "updated" ] and trigger_type is None: raise ValueError( u"when: \"{}\" could not be parsed because trigger_type cannot be None" .format(target)) elif target_type in ["Item", "Member of", "Descendent of" ] and trigger_target not in [ "added", "removed", "updated" ] and itemRegistry.getItems(trigger_target) == []: raise ValueError( u"when: \"{}\" could not be parsed because Item '{}' is not in the ItemRegistry" .format(target, trigger_target)) elif target_type in [ "Member of", "Descendent of" ] and itemRegistry.getItem(trigger_target).type != "Group": raise ValueError( u"when: \"{}\" could not be parsed because '{}' was specified, but '{}' is not a group" .format(target, target_type, trigger_target)) elif target_type == "Item" and trigger_target not in [ "added", "removed", "updated" ] and old_state is not None and trigger_type == "changed" and TypeParser.parseState( itemRegistry.getItem(trigger_target).acceptedDataTypes, old_state) is None: raise ValueError( u"when: \"{}\" could not be parsed because '{}' is not a valid state for '{}'" .format(target, old_state, trigger_target)) elif target_type == "Item" and trigger_target not in [ "added", "removed", "updated" ] and new_state is not None and ( trigger_type == "changed" or trigger_type == "received update") and TypeParser.parseState( itemRegistry.getItem(trigger_target).acceptedDataTypes, new_state) is None: raise ValueError( u"when: \"{}\" could not be parsed because '{}' is not a valid state for '{}'" .format(target, new_state, trigger_target)) elif target_type == "Item" and trigger_target not in [ "added", "removed", "updated" ] and new_state is not None and trigger_type == "received command" and TypeParser.parseCommand( itemRegistry.getItem(trigger_target).acceptedCommandTypes, new_state) is None: raise ValueError( u"when: \"{}\" could not be parsed because '{}' is not a valid command for '{}'" .format(target, new_state, trigger_target)) elif target_type == "Thing" and trigger_target not in [ "added", "removed", "updated" ] and things.get(ThingUID(trigger_target) ) is None: # returns null if Thing does not exist raise ValueError( u"when: \"{}\" could not be parsed because Thing '{}' is not in the ThingRegistry" .format(target, trigger_target)) elif target_type == "Thing" and old_state is not None and not hasattr( ThingStatus, old_state): raise ValueError( u"when: '{}' is not a valid Thing status".format(old_state)) elif target_type == "Thing" and new_state is not None and not hasattr( ThingStatus, new_state): raise ValueError( u"when: '{}' is not a valid Thing status".format(new_state)) elif target_type == "Thing" and trigger_target not in [ "added", "removed", "updated" ] and trigger_type is None: raise ValueError( u"when: \"{}\" could not be parsed. trigger_target '{}' is invalid for target_type 'Thing'. The only valid trigger_type values are 'added', 'removed', and 'updated'." .format(target, trigger_target)) elif target_type == "Channel" and things.getChannel( ChannelUID(trigger_target) ) is None: # returns null if Channel does not exist raise ValueError( u"when: \"{}\" could not be parsed because Channel '{}' does not exist" .format(target, trigger_target)) elif target_type == "Channel" and things.getChannel( ChannelUID(trigger_target)).kind != ChannelKind.TRIGGER: raise ValueError( u"when: \"{}\" could not be parsed because '{}' is not a trigger Channel" .format(target, trigger_target)) elif target_type == "System" and trigger_target != "started": # and trigger_target != "shuts down": raise ValueError( u"when: \"{}\" could not be parsed. trigger_target '{}' is invalid for target_type 'System'. The only valid trigger_type value is 'started'." .format(target, target_type )) # and 'shuts down'".format(target, target_type)) elif target_type in ["Directory", "Subdirectory" ] and not path.isdir(trigger_target): raise ValueError( u"when: \"{}\" could not be parsed. trigger_target '{}' does not exist or is not a directory." .format(target, target_type)) elif target_type in ["Directory", "Subdirectory"] and any( event_kind for event_kind in trigger_type if event_kind not in ["created", "deleted", "modified"]): raise ValueError( u"when: \"{}\" could not be parsed. trigger_target '{}' is invalid for target_type '{}'." .format(target, trigger_target, target_type)) LOG.trace( u"when: target: '{}', target_type: '{}', trigger_target: '{}', trigger_type: '{}', old_state: '{}', new_state: '{}'" .format(target, target_type, trigger_target, trigger_type, old_state, new_state)) trigger_name = validate_uid(trigger_name or target) if target_type in ["Item", "Member of", "Descendent of"]: return item_trigger elif target_type == "Thing": return thing_trigger elif target_type == "Channel": return channel_trigger elif target_type == "System": return system_trigger elif target_type == "Time": return cron_trigger elif target_type in ["Directory", "Subdirectory"]: return directory_trigger except ValueError as ex: LOG.warn(ex) def bad_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append(None) return function # If there was a problem with a trigger configuration, then add None # to the triggers attribute of the callback function, so that # core.rules.rule can identify that there was a problem and not start # the rule return bad_trigger except: import traceback LOG.warn(traceback.format_exc())
def when(target, target_type=None, trigger_type=None, old_state=None, new_state=None, event_types=None, trigger_name=None): try: def item_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] item = scope.itemRegistry.getItem(trigger_target) group_members = [] if target_type == "Member of": group_members = item.getMembers() elif target_type == "Descendent of": group_members = item.getAllMembers() else: group_members = [ item ] for member in group_members: trigger_name = "Item-{}-{}{}{}".format(member.name, trigger_type.replace(" ","-"), "-from-{}".format(old_state) if old_state else "", "-to-{}".format(new_state) if new_state else "") if trigger_type == "received update": function.triggers.append(ItemStateUpdateTrigger(member.name, state=new_state, triggerName=trigger_name).trigger) elif trigger_type == "received command": function.triggers.append(ItemCommandTrigger(member.name, command=new_state, triggerName=trigger_name).trigger) else: function.triggers.append(ItemStateChangeTrigger(member.name, previousState=old_state, state=new_state, triggerName=trigger_name).trigger) log.debug("when: Created item_trigger: [{}]".format(trigger_name)) return function def cron_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] trigger_name = "Cron-{}-{}".format(function.__name__, uuid.uuid1().hex) function.triggers.append(CronTrigger(trigger_type, triggerName=trigger_name).trigger) log.debug("when: Created cron_trigger: [{}]".format(trigger_name)) return function def system_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] if trigger_target == "started": function.triggers.append(StartupTrigger(triggerName=trigger_name).trigger) else: function.triggers.append(ShutdownTrigger(triggerName=trigger_name).trigger) log.debug("when: Created system_trigger: [{}]".format(trigger_name)) return function def channel_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append(ChannelEventTrigger(trigger_target, event=new_state, triggerName=trigger_name).trigger) log.debug("when: Created channel_trigger: [{}]".format(trigger_name)) return function def thing_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] event_types = "ThingStatusInfoChangedEvent" if (trigger_type == "changed") else "ThingStatusInfoEvent" function.triggers.append(ThingEventTrigger(trigger_target, event_types, triggerName=trigger_name).trigger) log.debug("when: Created thing_trigger: [{}]".format(trigger_name)) return function trigger_target = None if isValidExpression(target): target_type = "Time" trigger_target = "cron" trigger_type = target else: trigger_name = trigger_name or (target.replace(":","_").replace("#","_").replace(" ","-")) inputList = target.split(" ") if len(inputList) > 1: # target_type target [trigger_type] [from] [old_state] [to] [new_state] while len(inputList) > 0: if target_type is None: if " ".join(inputList[0:2]) in ["Member of", "Descendent of"]: target_type = " ".join(inputList[0:2]) inputList = inputList[2:] else: target_type = inputList.pop(0) elif trigger_target is None: if target_type == "System" and len(inputList) > 1: if " ".join(inputList[0:2]) == "shuts down": trigger_target = "shuts down" else: trigger_target = inputList.pop(0) elif trigger_type is None: if " ".join(inputList[0:2]) == "received update": if target_type in ["Item", "Thing", "Member of", "Descendent of"]: inputList = inputList[2:] trigger_type = "received update" else: raise ValueError("when: \"{}\" could not be parsed. \"received update\" is invalid for target_type \"{}\"".format(target, target_type)) elif " ".join(inputList[0:2]) == "received command": if target_type in ["Item", "Member of", "Descendent of"]: inputList = inputList[2:] trigger_type = "received command" else: raise ValueError("when: \"{}\" could not be parsed. \"received command\" is invalid for target_type \"{}\"".format(target, target_type)) elif inputList[0] == "changed": if target_type in ["Item", "Thing", "Member of", "Descendent of"]: inputList.pop(0) trigger_type = "changed" else: raise ValueError("when: \"{}\" could not be parsed. \"changed\" is invalid for target_type \"{}\"".format(target, target_type)) elif inputList[0] == "triggered": if target_type == "Channel": trigger_type = inputList.pop(0) else: raise ValueError("when: \"{}\" could not be parsed. \"triggered\" is invalid for target_type \"{}\"".format(target, target_type)) elif trigger_target == "cron": if target_type == "Time": if isValidExpression(" ".join(inputList)): trigger_type = " ".join(inputList) del inputList[:] else: raise ValueError("when: \"{}\" could not be parsed. \"{}\" is not a valid cron expression. See http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-06".format(target, " ".join(inputList))) else: raise ValueError("when: \"{}\" could not be parsed. \"cron\" is invalid for target_type \"{}\"".format(target, target_type)) else: raise ValueError("when: \"{}\" could not be parsed because the trigger_type {}".format(target, "is missing" if inputList[0] is None else "\"{}\" is invalid".format(inputList[0]))) else: if old_state is None and trigger_type == "changed" and inputList[0] == "from": inputList.pop(0) old_state = inputList.pop(0) elif new_state is None and trigger_type == "changed" and inputList[0] == "to": inputList.pop(0) new_state = inputList.pop(0) elif new_state is None and (trigger_type == "received update" or trigger_type == "received command"): new_state = inputList.pop(0) elif new_state is None and target_type == "Channel": new_state = inputList.pop(0) elif len(inputList) > 0:# there are no more possible combinations, but there is more data raise ValueError("when: \"{}\" could not be parsed. \"{}\" is invalid for \"{} {} {}\"".format(target, inputList, target_type, trigger_target, trigger_type)) else:# a simple Item target was used, so add default target_type and trigger_type (Item XXXXX changed) if target_type is None: target_type = "Item" if trigger_target is None: trigger_target = target if trigger_type is None: trigger_type = "changed" # validate the inputs, and if anything isn't populated correctly throw an exception if target_type is None or target_type not in ["Item", "Member of", "Descendent of", "Thing", "Channel", "System", "Time"]: raise ValueError("when: \"{}\" could not be parsed. target_type is missing or invalid. Valid target_type values are: Item, Member of, Descendent of, Thing, Channel, System, and Time.".format(target)) elif trigger_type is None: raise ValueError("when: \"{}\" could not be parsed because trigger_type cannot be None".format(target)) elif target_type in ["Item", "Member of", "Descendent of"] and scope.itemRegistry.getItem(trigger_target) is None:# throws ItemNotFoundException if item does not exist raise ValueError("when: \"{}\" could not be parsed because Item \"{}\" is not in the itemRegistry".format(target, trigger_target)) elif target_type in ["Member of", "Descendent of"] and scope.itemRegistry.getItem(trigger_target).type != "Group": raise ValueError("when: \"{}\" could not be parsed because \"{}\" was specified, but \"{}\" is not a group".format(target, target_type, trigger_target)) elif target_type == "Item" and old_state is not None and trigger_type == "changed" and not TypeParser.parseState(scope.itemRegistry.getItem(trigger_target).acceptedDataTypes, old_state): raise ValueError("when: \"{}\" could not be parsed because \"{}\" is not a valid state for \"{}\"".format(target, old_state, trigger_target)) elif target_type == "Item" and new_state is not None and (trigger_type == "changed" or trigger_type == "received update") and not TypeParser.parseState(scope.itemRegistry.getItem(trigger_target).acceptedDataTypes, new_state): raise ValueError("when: \"{}\" could not be parsed because \"{}\" is not a valid state for \"{}\"".format(target, new_state, trigger_target)) elif target_type == "Item" and new_state is not None and trigger_type == "received command" and not TypeParser.parseState(scope.itemRegistry.getItem(trigger_target).acceptedCommandTypes, new_state): raise ValueError("when: \"{}\" could not be parsed because \"{}\" is not a valid command for \"{}\"".format(target, new_state, trigger_target)) elif target_type == "Channel" and scope.things.getChannel(ChannelUID(trigger_target)) is None:# returns null if Channel does not exist raise ValueError("when: \"{}\" could not be parsed because Channel \"{}\" does not exist".format(target, trigger_target)) elif target_type == "Channel" and scope.things.getChannel(ChannelUID(trigger_target)).kind != ChannelKind.TRIGGER: raise ValueError("when: \"{}\" could not be parsed because Channel \"{}\" is not a trigger".format(target, trigger_target)) elif target_type == "Thing" and scope.things.get(ThingUID(trigger_target)) is None:# returns null if Thing does not exist raise ValueError("when: \"{}\" could not be parsed because Thing \"{}\" is not in the thingRegistry".format(target, trigger_target)) elif target_type == "Thing" and old_state and not hasattr(ThingStatus, old_state): raise ValueError("when: \"{}\" is not a valid Thing status".format(old_state)) elif target_type == "Thing" and new_state and not hasattr(ThingStatus, new_state): raise ValueError("when: \"{}\" is not a valid Thing status".format(new_state)) elif target_type == "Thing" and (old_state is not None or new_state is not None):# there is only an event trigger for Things, so old_state and new_state can't be used yet *****TO BE REMOVED***** raise ValueError("when: \"{}\" could not be parsed because rule triggers do not currently support checking the from/to status for Things".format(target)) elif target_type == "System" and trigger_target != "started" and trigger_target != "shuts down": raise ValueError("when: \"{}\" could not be parsed. trigger_target \"{}\" is invalid for target_type \"System\". Valid trigger_type values are \"started\" and \"shuts down\"".format(target, target_type)) #elif target_type == "System":# 'System shuts down' is not currently supported, and the 'System started' trigger needs to be reworked for the update API *****TO BE REMOVED***** # raise ValueError("when: \"{}\" could not be parsed because rule triggers do not currently support target_type \"System\"".format(target)) log.debug("when: target=[{}], target_type={}, trigger_target={}, trigger_type={}, old_state={}, new_state={}".format(target, target_type, trigger_target, trigger_type, old_state, new_state)) if target_type in ["Item", "Member of", "Descendent of"]: return item_trigger elif target_type == "Thing": return thing_trigger elif target_type == "Channel": return channel_trigger elif target_type == "System": return system_trigger elif target_type == "Time": return cron_trigger except Exception as e: import traceback log.error("when: Exception [{}]: [{}]".format(e, traceback.format_exc()))
def when(target, target_type=None, trigger_type=None, old_state=None, new_state=None, event_types=None, trigger_name=None): """openHAB DSL style trigger decorator. See :ref:`Guides/Rules:Extensions` for examples of how to use these extensions Examples: .. code-block:: @when("Item Test_Switch_1 received command OFF") @when("Item Test_Switch_2 received update ON") @when("Item gMotion_Sensors changed to ON") @when("Member of gMotion_Sensors changed to OFF") @when("Descendent of gContact_Sensors changed to OPEN") # Similar to 'Member of', but creates a trigger for each non-group sibling Item (think group_item.allMembers()) @when("Thing kodi:kodi:familyroom changed") # ThingStatusInfo (from <status> to <status>) cannot currently be used in triggers @when("Channel astro:sun:local:eclipse#event triggered START") @when("System started") # 'System started' requires S1566 or newer, and 'System shuts down' is not available @when("Time cron 55 55 5 * * ?") Args: target (str): Trigger expression to parse target_type (str): Target type ("Item", "Channel", etc.) trigger_type (str): Trigger type ("changed", "received command", etc.) old_state (str): Old state for "Item changed from" events new_state (str): New state for "changed to", "Item received update/command", and "Channel triggered" events event_types (str): Event type for GenericEventTrigger (keeps backwards compatibility with earlier versions) trigger_name (str): Name to assign to this trigger """ try: def item_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] item = scope.itemRegistry.getItem(trigger_target) group_members = [] if target_type == "Member of": group_members = item.getMembers() elif target_type == "Descendent of": group_members = item.getAllMembers() else: group_members = [item] for member in group_members: trigger_name = "Item-{}-{}{}{}{}{}".format( member.name, trigger_type.replace(" ", "-"), "-from-{}".format(old_state) if old_state is not None else "", "-to-" if new_state is not None and trigger_type == "changed" else "", "-" if trigger_type == "received update" and new_state is not None else "", new_state if new_state is not None else "") trigger_name = validate_uid(trigger_name) if trigger_type == "received update": function.triggers.append( ItemStateUpdateTrigger( member.name, state=new_state, triggerName=trigger_name).trigger) elif trigger_type == "received command": function.triggers.append( ItemCommandTrigger(member.name, command=new_state, triggerName=trigger_name).trigger) else: function.triggers.append( ItemStateChangeTrigger( member.name, previousState=old_state, state=new_state, triggerName=trigger_name).trigger) log.debug( "when: Created item_trigger: [{}]".format(trigger_name)) return function def cron_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append( CronTrigger(trigger_type, triggerName=trigger_name).trigger) log.debug("when: Created cron_trigger: [{}]".format(trigger_name)) return function def system_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] if trigger_target == "started": function.triggers.append( StartupTrigger(triggerName=trigger_name).trigger) else: function.triggers.append( ShutdownTrigger(triggerName=trigger_name).trigger) log.debug( "when: Created system_trigger: [{}]".format(trigger_name)) return function def channel_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append( ChannelEventTrigger(trigger_target, event=new_state, triggerName=trigger_name).trigger) log.debug( "when: Created channel_trigger: [{}]".format(trigger_name)) return function def thing_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] event_types = "ThingStatusInfoChangedEvent" if trigger_type == "changed" else "ThingStatusInfoEvent" function.triggers.append( ThingEventTrigger(trigger_target, event_types, triggerName=trigger_name).trigger) log.debug("when: Created thing_trigger: [{}]".format(trigger_name)) return function trigger_target = None if isValidExpression(target): # a simple cron target was used, so add a default target_type and trigger_target (Time cron XXXXX) target_type = "Time" trigger_target = "cron" trigger_type = target trigger_name = "Time-cron-{}".format(target) else: inputList = split(target) if len(inputList) > 1: # target_type target [trigger_type] [from] [old_state] [to] [new_state] while len(inputList) > 0: if target_type is None: if " ".join(inputList[0:2]) in [ "Member of", "Descendent of" ]: target_type = " ".join(inputList[0:2]) inputList = inputList[2:] else: target_type = inputList.pop(0) elif trigger_target is None: if target_type == "System" and len(inputList) > 1: if " ".join(inputList[0:2]) == "shuts down": trigger_target = "shuts down" else: trigger_target = inputList.pop(0) elif trigger_type is None: if " ".join(inputList[0:2]) == "received update": if target_type in [ "Item", "Thing", "Member of", "Descendent of" ]: inputList = inputList[2:] trigger_type = "received update" else: raise ValueError( "when: \"{}\" could not be parsed. \"received update\" is invalid for target_type \"{}\"" .format(target, target_type)) elif " ".join(inputList[0:2]) == "received command": if target_type in [ "Item", "Member of", "Descendent of" ]: inputList = inputList[2:] trigger_type = "received command" else: raise ValueError( "when: \"{}\" could not be parsed. \"received command\" is invalid for target_type \"{}\"" .format(target, target_type)) elif inputList[0] == "changed": if target_type in [ "Item", "Thing", "Member of", "Descendent of" ]: inputList.pop(0) trigger_type = "changed" else: raise ValueError( "when: \"{}\" could not be parsed. \"changed\" is invalid for target_type \"{}\"" .format(target, target_type)) elif inputList[0] == "triggered": if target_type == "Channel": trigger_type = inputList.pop(0) else: raise ValueError( "when: \"{}\" could not be parsed. \"triggered\" is invalid for target_type \"{}\"" .format(target, target_type)) elif trigger_target == "cron": if target_type == "Time": if isValidExpression(" ".join(inputList)): trigger_type = " ".join(inputList) del inputList[:] else: raise ValueError( "when: \"{}\" could not be parsed. \"{}\" is not a valid cron expression. See http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-06" .format(target, " ".join(inputList))) else: raise ValueError( "when: \"{}\" could not be parsed. \"cron\" is invalid for target_type \"{}\"" .format(target, target_type)) else: raise ValueError( "when: \"{}\" could not be parsed because the trigger_type {}" .format( target, "is missing" if inputList[0] is None else "\"{}\" is invalid".format(inputList[0]))) else: if old_state is None and trigger_type == "changed" and inputList[ 0] == "from": inputList.pop(0) old_state = inputList.pop(0) elif new_state is None and trigger_type == "changed" and inputList[ 0] == "to": inputList.pop(0) new_state = inputList.pop(0) elif new_state is None and ( trigger_type == "received update" or trigger_type == "received command"): new_state = inputList.pop(0) elif new_state is None and target_type == "Channel": new_state = inputList.pop(0) elif len( inputList ) > 0: # there are no more possible combinations, but there is more data raise ValueError( "when: \"{}\" could not be parsed. \"{}\" is invalid for \"{} {} {}\"" .format(target, inputList, target_type, trigger_target, trigger_type)) else: # a simple Item target was used, so add a default target_type and trigger_type (Item XXXXX changed) if target_type is None: target_type = "Item" if trigger_target is None: trigger_target = target if trigger_type is None: trigger_type = "changed" trigger_name = validate_uid(trigger_name or target) # validate the inputs, and if anything isn't populated correctly throw an exception if target_type is None or target_type not in [ "Item", "Member of", "Descendent of", "Thing", "Channel", "System", "Time" ]: raise ValueError( "when: \"{}\" could not be parsed. target_type is missing or invalid. Valid target_type values are: Item, Member of, Descendent of, Thing, Channel, System, and Time." .format(target)) elif target_type != "System" and trigger_type is None: raise ValueError( "when: \"{}\" could not be parsed because trigger_type cannot be None" .format(target)) elif target_type in [ "Item", "Member of", "Descendent of" ] and scope.itemRegistry.getItems(trigger_target) == []: raise ValueError( "when: \"{}\" could not be parsed because Item \"{}\" is not in the ItemRegistry" .format(target, trigger_target)) elif target_type in [ "Member of", "Descendent of" ] and scope.itemRegistry.getItem(trigger_target).type != "Group": raise ValueError( "when: \"{}\" could not be parsed because \"{}\" was specified, but \"{}\" is not a group" .format(target, target_type, trigger_target)) elif target_type == "Item" and old_state is not None and trigger_type == "changed" and TypeParser.parseState( scope.itemRegistry.getItem(trigger_target).acceptedDataTypes, old_state) is None: raise ValueError( "when: \"{}\" could not be parsed because \"{}\" is not a valid state for \"{}\"" .format(target, old_state, trigger_target)) elif target_type == "Item" and new_state is not None and ( trigger_type == "changed" or trigger_type == "received update" ) and TypeParser.parseState( scope.itemRegistry.getItem(trigger_target).acceptedDataTypes, new_state) is None: raise ValueError( "when: \"{}\" could not be parsed because \"{}\" is not a valid state for \"{}\"" .format(target, new_state, trigger_target)) elif target_type == "Item" and new_state is not None and trigger_type == "received command" and TypeParser.parseCommand( scope.itemRegistry.getItem( trigger_target).acceptedCommandTypes, new_state) is None: raise ValueError( "when: \"{}\" could not be parsed because \"{}\" is not a valid command for \"{}\"" .format(target, new_state, trigger_target)) elif target_type == "Channel" and scope.things.getChannel( ChannelUID(trigger_target) ) is None: # returns null if Channel does not exist raise ValueError( "when: \"{}\" could not be parsed because Channel \"{}\" does not exist" .format(target, trigger_target)) elif target_type == "Channel" and scope.things.getChannel( ChannelUID(trigger_target)).kind != ChannelKind.TRIGGER: raise ValueError( "when: \"{}\" could not be parsed because Channel \"{}\" is not a trigger" .format(target, trigger_target)) elif target_type == "Thing" and scope.things.get( ThingUID(trigger_target) ) is None: # returns null if Thing does not exist raise ValueError( "when: \"{}\" could not be parsed because Thing \"{}\" is not in the ThingRegistry" .format(target, trigger_target)) elif target_type == "Thing" and old_state and not hasattr( ThingStatus, old_state): raise ValueError( "when: \"{}\" is not a valid Thing status".format(old_state)) elif target_type == "Thing" and new_state and not hasattr( ThingStatus, new_state): raise ValueError( "when: \"{}\" is not a valid Thing status".format(new_state)) elif target_type == "Thing" and ( old_state is not None or new_state is not None ): # there is only an event trigger for Things, so old_state and new_state can't be used yet *****TO BE REMOVED***** raise ValueError( "when: \"{}\" could not be parsed because rule triggers do not currently support checking the from/to status for Things" .format(target)) elif target_type == "System" and trigger_target != "started": # and trigger_target != "shuts down": raise ValueError( "when: \"{}\" could not be parsed. trigger_target \"{}\" is invalid for target_type \"System\". The only valid trigger_type value is \"started\"" .format(target, target_type )) # and \"shuts down\"".format(target, target_type)) log.debug( "when: target=[{}], target_type={}, trigger_target={}, trigger_type={}, old_state={}, new_state={}" .format(target, target_type, trigger_target, trigger_type, old_state, new_state)) if target_type in ["Item", "Member of", "Descendent of"]: return item_trigger elif target_type == "Thing": return thing_trigger elif target_type == "Channel": return channel_trigger elif target_type == "System": return system_trigger elif target_type == "Time": return cron_trigger except ValueError as ve: log.warn(ve) def bad_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append(None) #log.warn("triggers = [{}]".format(function.triggers)) #log.warn("all None = [{}]".format(all(function.triggers))) #log.warn("count = [{}]".format(function.triggers.count(None))) #function.has_bad_trigger = True return function return bad_trigger except: import traceback log.debug(traceback.format_exc())
def when(target): """ This function decorator creates triggers attribute in the decorated function that is used by the ``rule`` decorator when creating a rule. The ``when`` decorator simplifies the use of many of the triggers in this module and allows for them to be used with natural language similar to what is used in the rules DSL. See :ref:`Guides/Rules:Decorators` for examples of how to use this decorator. Examples: .. code-block:: @when("Time cron 55 55 5 * * ?") @when("Item Test_String_1 changed from 'old test string' to 'new test string'") @when("Item gMotion_Sensors changed") @when("Member of gMotion_Sensors changed from ON to OFF") @when("Descendent of gContact_Sensors changed from OPEN to CLOSED") @when("Item Test_Switch_2 received update ON") @when("Item Test_Switch_1 received command OFF") @when("Thing kodi:kodi:familyroom changed") @when("Thing kodi:kodi:familyroom changed from ONLINE to OFFLINE")# requires S1636, 2.5M2 or newer @when("Thing kodi:kodi:familyroom received update ONLINE")# requires S1636, 2.5M2 or newer @when("Channel astro:sun:local:eclipse#event triggered START")# must use a Channel of kind Trigger @when("System started")# requires S1566, 2.5M2 or newer ('System shuts down' has not been implemented) Args: target (string): the `rules DSL-like formatted trigger expression <https://www.openhab.org/docs/configuration/rules-dsl.html#rule-triggers>`_ to parse """ try: def item_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] item = itemRegistry.getItem(trigger_target) group_members = [] if target_type == "Member of": group_members = item.getMembers() elif target_type == "Descendent of": group_members = item.getAllMembers() else: group_members = [item] for member in group_members: trigger_name = "Item-{}-{}{}{}{}{}".format( member.name, trigger_type.replace(" ", "-"), "-from-{}".format(old_state) if old_state is not None else "", "-to-" if new_state is not None and trigger_type == "changed" else "", "-" if trigger_type == "received update" and new_state is not None else "", new_state if new_state is not None else "") trigger_name = validate_uid(trigger_name) if trigger_type == "received update": function.triggers.append( ItemStateUpdateTrigger( member.name, state=new_state, trigger_name=trigger_name).trigger) elif trigger_type == "received command": function.triggers.append( ItemCommandTrigger(member.name, command=new_state, trigger_name=trigger_name).trigger) else: function.triggers.append( ItemStateChangeTrigger( member.name, previous_state=old_state, state=new_state, trigger_name=trigger_name).trigger) LOG.debug( u"when: Created item_trigger: '{}'".format(trigger_name)) return function def item_registry_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] event_names = { 'added': 'ItemAddedEvent', 'removed': 'ItemRemovedEvent', 'modified': 'ItemUpdatedEvent' } function.triggers.append( ItemRegistryTrigger(event_names.get(trigger_target))) LOG.debug(u"when: Created item_registry_trigger: '{}'".format( event_names.get(trigger_target))) return function def cron_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append( CronTrigger(trigger_type, trigger_name=trigger_name).trigger) LOG.debug(u"when: Created cron_trigger: '{}'".format(trigger_name)) return function def system_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] #if trigger_target == "started": function.triggers.append( StartupTrigger(trigger_name=trigger_name).trigger) #else: # function.triggers.append(ShutdownTrigger(trigger_name=trigger_name).trigger) LOG.debug( u"when: Created system_trigger: '{}'".format(trigger_name)) return function def thing_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] if new_state is not None or old_state is not None: if trigger_type == "changed": function.triggers.append( ThingStatusChangeTrigger( trigger_target, previous_status=old_state, status=new_state, trigger_name=trigger_name).trigger) else: function.triggers.append( ThingStatusUpdateTrigger( trigger_target, status=new_state, trigger_name=trigger_name).trigger) else: event_types = "ThingStatusInfoChangedEvent" if trigger_type == "changed" else "ThingStatusInfoEvent" function.triggers.append( ThingEventTrigger(trigger_target, event_types, trigger_name=trigger_name).trigger) LOG.debug( u"when: Created thing_trigger: '{}'".format(trigger_name)) return function def channel_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append( ChannelEventTrigger(trigger_target, event=new_state, trigger_name=trigger_name).trigger) LOG.debug( u"when: Created channel_trigger: '{}'".format(trigger_name)) return function target_type = None trigger_target = None trigger_type = None old_state = None new_state = None trigger_name = None if isValidExpression(target): # a simple cron target was used, so add a default target_type and trigger_target (Time cron XXXXX) target_type = "Time" trigger_target = "cron" trigger_type = target trigger_name = "Time-cron-{}".format(target) else: input_list = split(target) if len(input_list) > 1: # target_type trigger_target [trigger_type] [from] [old_state] [to] [new_state] while input_list: if target_type is None: if " ".join(input_list[0:2]) in [ "Member of", "Descendent of" ]: target_type = " ".join(input_list[0:2]) input_list = input_list[2:] else: target_type = input_list.pop(0) elif trigger_target is None: if target_type == "System" and len(input_list) > 1: if " ".join(input_list[0:2]) == "shuts down": trigger_target = "shuts down" else: trigger_target = input_list.pop(0) elif trigger_type is None: if " ".join(input_list[0:2]) == "received update": if target_type in [ "Item", "Thing", "Member of", "Descendent of" ]: input_list = input_list[2:] trigger_type = "received update" else: raise ValueError( u"when: '{}' could not be parsed. 'received update' is invalid for target_type '{}'" .format(target, target_type)) elif " ".join(input_list[0:2]) == "received command": if target_type in [ "Item", "Member of", "Descendent of" ]: input_list = input_list[2:] trigger_type = "received command" else: raise ValueError( u"when: '{}' could not be parsed. 'received command' is invalid for target_type '{}'" .format(target, target_type)) elif input_list[0] == "changed": if target_type in [ "Item", "Thing", "Member of", "Descendent of" ]: input_list.pop(0) trigger_type = "changed" else: raise ValueError( u"when: '{}' could not be parsed. 'changed' is invalid for target_type '{}'" .format(target, target_type)) elif input_list[0] == "triggered": if target_type == "Channel": trigger_type = input_list.pop(0) else: raise ValueError( u"when: '{}' could not be parsed. 'triggered' is invalid for target_type '{}'" .format(target, target_type)) elif trigger_target == "cron": if target_type == "Time": if isValidExpression(" ".join(input_list)): trigger_type = " ".join(input_list) del input_list[:] else: raise ValueError( u"when: '{}' could not be parsed. '{}' is not a valid cron expression. See http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-06" .format(target, " ".join(input_list))) else: raise ValueError( u"when: '{}' could not be parsed. 'cron' is invalid for target_type '{}'" .format(target, target_type)) else: raise ValueError( u"when: '{}' could not be parsed because the trigger_type {}" .format( target, "is missing" if input_list[0] is None else "'{}' is invalid".format(input_list[0]))) else: if old_state is None and trigger_type == "changed" and input_list[ 0] == "from": input_list.pop(0) old_state = input_list.pop(0) elif new_state is None and trigger_type == "changed" and input_list[ 0] == "to": input_list.pop(0) new_state = input_list.pop(0) elif new_state is None and ( trigger_type == "received update" or trigger_type == "received command"): new_state = input_list.pop(0) elif new_state is None and target_type == "Channel": new_state = input_list.pop(0) elif input_list: # there are no more possible combinations, but there is more data raise ValueError( u"when: '{}' could not be parsed. '{}' is invalid for '{} {} {}'" .format(target, input_list, target_type, trigger_target, trigger_type)) else: # a simple Item target was used (just an Item name), so add a default target_type and trigger_type (Item XXXXX changed) if target_type is None: target_type = "Item" if trigger_target is None: trigger_target = target if trigger_type is None: trigger_type = "changed" # validate the inputs, and if anything isn't populated correctly throw an exception if target_type is None or target_type not in [ "Item", "Member of", "Descendent of", "Thing", "Channel", "System", "Time" ]: raise ValueError( u"when: '{}' could not be parsed. target_type is missing or invalid. Valid target_type values are: Item, Member of, Descendent of, Thing, Channel, System, and Time." .format(target)) elif target_type != "System" and trigger_target not in [ "added", "removed", "modified" ] and trigger_type is None: raise ValueError( u"when: '{}' could not be parsed because trigger_type cannot be None" .format(target)) elif target_type in ["Item", "Member of", "Descendent of" ] and trigger_target not in [ "added", "removed", "modified" ] and itemRegistry.getItems(trigger_target) == []: raise ValueError( u"when: '{}' could not be parsed because Item '{}' is not in the ItemRegistry" .format(target, trigger_target)) elif target_type in [ "Member of", "Descendent of" ] and itemRegistry.getItem(trigger_target).type != "Group": raise ValueError( u"when: '{}' could not be parsed because '{}' was specified, but '{}' is not a group" .format(target, target_type, trigger_target)) elif target_type == "Item" and trigger_target not in [ "added", "removed", "modified" ] and old_state is not None and trigger_type == "changed" and TypeParser.parseState( itemRegistry.getItem(trigger_target).acceptedDataTypes, old_state) is None: raise ValueError( u"when: '{}' could not be parsed because '{}' is not a valid state for '{}'" .format(target, old_state, trigger_target)) elif target_type == "Item" and trigger_target not in [ "added", "removed", "modified" ] and new_state is not None and ( trigger_type == "changed" or trigger_type == "received update") and TypeParser.parseState( itemRegistry.getItem(trigger_target).acceptedDataTypes, new_state) is None: raise ValueError( u"when: '{}' could not be parsed because '{}' is not a valid state for '{}'" .format(target, new_state, trigger_target)) elif target_type == "Item" and trigger_target not in [ "added", "removed", "modified" ] and new_state is not None and trigger_type == "received command" and TypeParser.parseCommand( itemRegistry.getItem(trigger_target).acceptedCommandTypes, new_state) is None: raise ValueError( u"when: '{}' could not be parsed because '{}' is not a valid command for '{}'" .format(target, new_state, trigger_target)) elif target_type == "Thing" and things.get( ThingUID(trigger_target )) is None: # returns null if Thing does not exist raise ValueError( u"when: '{}' could not be parsed because Thing '{}' is not in the ThingRegistry" .format(target, trigger_target)) elif target_type == "Thing" and old_state is not None and not hasattr( ThingStatus, old_state): raise ValueError( u"when: '{}' is not a valid Thing status".format(old_state)) elif target_type == "Thing" and new_state is not None and not hasattr( ThingStatus, new_state): raise ValueError( u"when: '{}' is not a valid Thing status".format(new_state)) elif target_type == "Channel" and things.getChannel( ChannelUID(trigger_target) ) is None: # returns null if Channel does not exist raise ValueError( u"when: '{}' could not be parsed because Channel '{}' does not exist" .format(target, trigger_target)) # elif target_type == "Channel" and things.getChannel(ChannelUID(trigger_target)).kind != ChannelKind.TRIGGER: # raise ValueError(u"when: '{}' could not be parsed because '{}' is not a trigger Channel".format(target, trigger_target)) elif target_type == "System" and trigger_target != "started": # and trigger_target != "shuts down": raise ValueError( u"when: '{}' could not be parsed. trigger_target '{}' is invalid for target_type 'System'. The only valid trigger_type value is 'started'" .format(target, target_type )) # and 'shuts down'".format(target, target_type)) LOG.debug( u"when: target='{}', target_type='{}', trigger_target='{}', trigger_type='{}', old_state='{}', new_state='{}'" .format(target, target_type, trigger_target, trigger_type, old_state, new_state)) trigger_name = validate_uid(trigger_name or target) if target_type in ["Item", "Member of", "Descendent of"]: if trigger_target in ["added", "removed", "modified"]: return item_registry_trigger else: return item_trigger elif target_type == "Thing": return thing_trigger elif target_type == "Channel": return channel_trigger elif target_type == "System": return system_trigger elif target_type == "Time": return cron_trigger except ValueError as ex: LOG.warn(ex) def bad_trigger(function): if not hasattr(function, 'triggers'): function.triggers = [] function.triggers.append(None) return function # If there was a problem with a trigger configurationn, then add None # to the triggers attribute of the callback function, so that # core.rules.rule can identify that there was a problem and not start # the rule return bad_trigger except: import traceback LOG.warn(traceback.format_exc())