From 5de420bad3a62a5f58db5365918c60c3645373bc Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 05:44:03 -0600 Subject: [PATCH 01/30] progress --- custom_components/pyscript/__init__.py | 6 +++--- custom_components/pyscript/state.py | 6 +++--- custom_components/pyscript/trigger.py | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index b386ba1..76dd4cc 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -40,7 +40,7 @@ from .function import Function from .global_ctx import GlobalContext, GlobalContextMgr from .jupyter_kernel import Kernel -from .state import State +from .state import State, StateVar from .trigger import TrigTime if sys.version_info[:2] >= (3, 8): @@ -208,8 +208,8 @@ async def state_changed(event): # state variable has been deleted new_val = None else: - new_val = event.data["new_state"].state - old_val = event.data["old_state"].state if event.data["old_state"] else None + new_val = StateVar(event.data.get('new_state')) + old_val = StateVar(event.data.get('old_state')) if event.data["old_state"] else None new_vars = {var_name: new_val, f"{var_name}.old": old_val} func_args = { "trigger_type": "state", diff --git a/custom_components/pyscript/state.py b/custom_components/pyscript/state.py index fdb67e8..2ce81c3 100644 --- a/custom_components/pyscript/state.py +++ b/custom_components/pyscript/state.py @@ -127,12 +127,12 @@ async def update(cls, new_vars, func_args): @classmethod def notify_var_get(cls, var_names, new_vars): """Return the most recent value of a state variable change.""" - notify_vars = {} + notify_vars = new_vars.copy() for var_name in var_names if var_names is not None else []: if var_name in cls.notify_var_last: notify_vars[var_name] = cls.notify_var_last[var_name] - elif var_name in new_vars: - notify_vars[var_name] = new_vars[var_name] + # elif var_name in new_vars: + # notify_vars[var_name] = new_vars[var_name] elif 1 <= var_name.count(".") <= 2 and not cls.exist(var_name): notify_vars[var_name] = None return notify_vars diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index bbc6689..011d2de 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -812,6 +812,30 @@ async def trigger_watch(self): if self.task_unique is not None: task_unique_func = Function.task_unique_factory(action_ast_ctx) + + _LOGGER.info( + "trigger %s with ident %s and any %s", + self.name, + self.state_trig_ident, + self.state_trig_ident_any, + ) + # + # check for changes to state or attributes + # + if notify_type == 'state': + trig_ident_change = False + for var in self.state_trig_ident: + var_pieces = var.split('.') + if len(var_pieces) == 2 and var == func_args['var_name']: + if func_args['value'] != func_args['old_value']: + trig_ident_change = True + elif len(var_pieces) == 3 and ".".join(var_pieces[0:2]) == func_args['var_name']: + if getattr(func_args['value'], var_pieces[2]) != getattr(func_args['old_value'], var_pieces[2]): + trig_ident_change = True + + if not trig_ident_change: + continue + # # check for @task_unique with kill_me=True # From bf0543b77962888a5dd21be8a8b1aebacb621301 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 05:44:03 -0600 Subject: [PATCH 02/30] progress --- custom_components/pyscript/__init__.py | 6 +++--- custom_components/pyscript/state.py | 6 +++--- custom_components/pyscript/trigger.py | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index 406694a..de62451 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -36,7 +36,7 @@ from .global_ctx import GlobalContext, GlobalContextMgr from .jupyter_kernel import Kernel from .requirements import install_requirements -from .state import State +from .state import State, StateVar from .trigger import TrigTime _LOGGER = logging.getLogger(LOGGER_PATH) @@ -193,8 +193,8 @@ async def state_changed(event): # state variable has been deleted new_val = None else: - new_val = event.data["new_state"].state - old_val = event.data["old_state"].state if event.data["old_state"] else None + new_val = StateVar(event.data.get('new_state')) + old_val = StateVar(event.data.get('old_state')) if event.data["old_state"] else None new_vars = {var_name: new_val, f"{var_name}.old": old_val} func_args = { "trigger_type": "state", diff --git a/custom_components/pyscript/state.py b/custom_components/pyscript/state.py index fdb67e8..2ce81c3 100644 --- a/custom_components/pyscript/state.py +++ b/custom_components/pyscript/state.py @@ -127,12 +127,12 @@ async def update(cls, new_vars, func_args): @classmethod def notify_var_get(cls, var_names, new_vars): """Return the most recent value of a state variable change.""" - notify_vars = {} + notify_vars = new_vars.copy() for var_name in var_names if var_names is not None else []: if var_name in cls.notify_var_last: notify_vars[var_name] = cls.notify_var_last[var_name] - elif var_name in new_vars: - notify_vars[var_name] = new_vars[var_name] + # elif var_name in new_vars: + # notify_vars[var_name] = new_vars[var_name] elif 1 <= var_name.count(".") <= 2 and not cls.exist(var_name): notify_vars[var_name] = None return notify_vars diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index bbc6689..011d2de 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -812,6 +812,30 @@ async def trigger_watch(self): if self.task_unique is not None: task_unique_func = Function.task_unique_factory(action_ast_ctx) + + _LOGGER.info( + "trigger %s with ident %s and any %s", + self.name, + self.state_trig_ident, + self.state_trig_ident_any, + ) + # + # check for changes to state or attributes + # + if notify_type == 'state': + trig_ident_change = False + for var in self.state_trig_ident: + var_pieces = var.split('.') + if len(var_pieces) == 2 and var == func_args['var_name']: + if func_args['value'] != func_args['old_value']: + trig_ident_change = True + elif len(var_pieces) == 3 and ".".join(var_pieces[0:2]) == func_args['var_name']: + if getattr(func_args['value'], var_pieces[2]) != getattr(func_args['old_value'], var_pieces[2]): + trig_ident_change = True + + if not trig_ident_change: + continue + # # check for @task_unique with kill_me=True # From 57c04b12b35fad69368c7e887a7c7a6c76d57e26 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 05:57:55 -0600 Subject: [PATCH 03/30] send strings in new_var --- custom_components/pyscript/__init__.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index de62451..bb0df40 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -192,15 +192,25 @@ async def state_changed(event): if "new_state" not in event.data or event.data["new_state"] is None: # state variable has been deleted new_val = None + new_val_obj = None else: - new_val = StateVar(event.data.get('new_state')) - old_val = StateVar(event.data.get('old_state')) if event.data["old_state"] else None + new_val = event.data['new_state'].state + new_val_obj = StateVar(event.data['new_state']) + + if "old_state" not in event.data or event.data["old_state"] is None: + # no previous state + old_val = None + old_val_obj = None + else: + old_val = event.data['old_state'].state + old_val_obj = StateVar(event.data['old_state']) + new_vars = {var_name: new_val, f"{var_name}.old": old_val} func_args = { "trigger_type": "state", "var_name": var_name, - "value": new_val, - "old_value": old_val, + "value": new_val_obj, + "old_value": old_val_obj, "context": event.context, } await State.update(new_vars, func_args) From 8941b003ab9366ef94109d1bc93f02a9a3df0050 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 06:09:53 -0600 Subject: [PATCH 04/30] issue when value/old_value is None --- custom_components/pyscript/trigger.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 011d2de..9227c0b 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -812,13 +812,6 @@ async def trigger_watch(self): if self.task_unique is not None: task_unique_func = Function.task_unique_factory(action_ast_ctx) - - _LOGGER.info( - "trigger %s with ident %s and any %s", - self.name, - self.state_trig_ident, - self.state_trig_ident_any, - ) # # check for changes to state or attributes # @@ -829,12 +822,14 @@ async def trigger_watch(self): if len(var_pieces) == 2 and var == func_args['var_name']: if func_args['value'] != func_args['old_value']: trig_ident_change = True - elif len(var_pieces) == 3 and ".".join(var_pieces[0:2]) == func_args['var_name']: - if getattr(func_args['value'], var_pieces[2]) != getattr(func_args['old_value'], var_pieces[2]): + elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == func_args['var_name']: + if func_args['value'] is None or func_args['old_value'] is None: + trig_ident_change= True + elif getattr(func_args['value'], var_pieces[2]) != getattr(func_args['old_value'], var_pieces[2]): trig_ident_change = True if not trig_ident_change: - continue + continue # # check for @task_unique with kill_me=True From 08f334d28fce62cf3107476f101de5c65f7e1c89 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 06:18:11 -0600 Subject: [PATCH 05/30] attrib change checking when new/old value is None --- custom_components/pyscript/trigger.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 9227c0b..2ea5593 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -823,9 +823,17 @@ async def trigger_watch(self): if func_args['value'] != func_args['old_value']: trig_ident_change = True elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == func_args['var_name']: - if func_args['value'] is None or func_args['old_value'] is None: - trig_ident_change= True - elif getattr(func_args['value'], var_pieces[2]) != getattr(func_args['old_value'], var_pieces[2]): + if func_args['value'] is None: + attrib_val = None + else: + attrib_val = getattr(func_args['value'], var_pieces[2]) + + if func_args['old_value'] is None: + attrib_old_val = None + else: + attrib_old_val = getattr(func_args['old_value'], var_pieces[2]) + + if attrib_old_val != attrib_val: trig_ident_change = True if not trig_ident_change: From 8f9a323e275ea26ef8288683e61b83515a49ac84 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 06:29:54 -0600 Subject: [PATCH 06/30] restore notify_var_get to original --- custom_components/pyscript/state.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/pyscript/state.py b/custom_components/pyscript/state.py index 2ce81c3..fdb67e8 100644 --- a/custom_components/pyscript/state.py +++ b/custom_components/pyscript/state.py @@ -127,12 +127,12 @@ async def update(cls, new_vars, func_args): @classmethod def notify_var_get(cls, var_names, new_vars): """Return the most recent value of a state variable change.""" - notify_vars = new_vars.copy() + notify_vars = {} for var_name in var_names if var_names is not None else []: if var_name in cls.notify_var_last: notify_vars[var_name] = cls.notify_var_last[var_name] - # elif var_name in new_vars: - # notify_vars[var_name] = new_vars[var_name] + elif var_name in new_vars: + notify_vars[var_name] = new_vars[var_name] elif 1 <= var_name.count(".") <= 2 and not cls.exist(var_name): notify_vars[var_name] = None return notify_vars From 7f8ffa0acf36d40e9564ebc36a127c00ba9b9a60 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 06:32:24 -0600 Subject: [PATCH 07/30] cleanup unused vars --- custom_components/pyscript/__init__.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index bb0df40..a9b748c 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -192,25 +192,21 @@ async def state_changed(event): if "new_state" not in event.data or event.data["new_state"] is None: # state variable has been deleted new_val = None - new_val_obj = None else: - new_val = event.data['new_state'].state - new_val_obj = StateVar(event.data['new_state']) + new_val = StateVar(event.data['new_state']) if "old_state" not in event.data or event.data["old_state"] is None: # no previous state old_val = None - old_val_obj = None else: - old_val = event.data['old_state'].state - old_val_obj = StateVar(event.data['old_state']) + old_val = StateVar(event.data['old_state']) new_vars = {var_name: new_val, f"{var_name}.old": old_val} func_args = { "trigger_type": "state", "var_name": var_name, - "value": new_val_obj, - "old_value": old_val_obj, + "value": new_val, + "old_value": old_val, "context": event.context, } await State.update(new_vars, func_args) From e5a0bfac7fc310c61b901285ad5028cf1cc79407 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 06:36:03 -0600 Subject: [PATCH 08/30] provide default when attribute doesn't exist --- custom_components/pyscript/trigger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 2ea5593..8d9ba4f 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -826,12 +826,12 @@ async def trigger_watch(self): if func_args['value'] is None: attrib_val = None else: - attrib_val = getattr(func_args['value'], var_pieces[2]) + attrib_val = getattr(func_args['value'], var_pieces[2], None) if func_args['old_value'] is None: attrib_old_val = None else: - attrib_old_val = getattr(func_args['old_value'], var_pieces[2]) + attrib_old_val = getattr(func_args['old_value'], var_pieces[2], None) if attrib_old_val != attrib_val: trig_ident_change = True From 3296f74a1215f938ecf4d54595915988a5b272db Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 06:40:40 -0600 Subject: [PATCH 09/30] cleanup change detecting logic --- custom_components/pyscript/trigger.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 8d9ba4f..c250b5d 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -823,16 +823,8 @@ async def trigger_watch(self): if func_args['value'] != func_args['old_value']: trig_ident_change = True elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == func_args['var_name']: - if func_args['value'] is None: - attrib_val = None - else: - attrib_val = getattr(func_args['value'], var_pieces[2], None) - - if func_args['old_value'] is None: - attrib_old_val = None - else: - attrib_old_val = getattr(func_args['old_value'], var_pieces[2], None) - + attrib_val = getattr(func_args['value'], var_pieces[2], None) + attrib_old_val = getattr(func_args['old_value'], var_pieces[2], None) if attrib_old_val != attrib_val: trig_ident_change = True From 8c2bd0e46eb1cdbddff11d7720249cbba810fb45 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 07:16:52 -0600 Subject: [PATCH 10/30] handle state_check_now correctly --- custom_components/pyscript/trigger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index c250b5d..a97b5aa 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -815,7 +815,9 @@ async def trigger_watch(self): # # check for changes to state or attributes # - if notify_type == 'state': + + # if "value" not in func_args, then we are state_check_now + if "value" in func_args and notify_type == 'state': trig_ident_change = False for var in self.state_trig_ident: var_pieces = var.split('.') From f06c0c4f566bef2960f03bfa9a6e0a343715a993 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 07:17:07 -0600 Subject: [PATCH 11/30] adjust test for StateVar --- tests/test_decorator_errors.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/test_decorator_errors.py b/tests/test_decorator_errors.py index 79d890d..778dfa8 100644 --- a/tests/test_decorator_errors.py +++ b/tests/test_decorator_errors.py @@ -194,15 +194,7 @@ def func_wrapup(): """Exception in line 1: 1 / pyscript.var1 ^ -TypeError: unsupported operand type(s) for /: 'int' and 'str'""" - in caplog.text - ) - - assert ( - """Exception in line 1: - 1 / pyscript.var1 - ^ -TypeError: unsupported operand type(s) for /: 'int' and 'str'""" +TypeError: unsupported operand type(s) for /: 'int' and 'StateVar'""" in caplog.text ) From c49310d1dc7292c010c0c1a582a8f1e6856cc5c0 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 14:15:55 -0600 Subject: [PATCH 12/30] add domain.entity.* tracking --- custom_components/pyscript/trigger.py | 33 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index a97b5aa..1facda3 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(LOGGER_PATH + ".trigger") -STATE_RE = re.compile(r"[a-zA-Z]\w*\.[a-zA-Z]\w*$") +STATE_RE = re.compile(r"[a-zA-Z]\w*\.[a-zA-Z]\w*(\.\*)?$") def dt_now(): @@ -739,7 +739,7 @@ async def trigger_watch(self): elif notify_type == "state": new_vars, func_args = notify_info - if "var_name" not in func_args or func_args["var_name"] not in self.state_trig_ident_any: + if "var_name" not in func_args or (func_args["var_name"] not in self.state_trig_ident_any and f"{func_args['var_name']}.*" not in self.state_trig_ident_any): if self.state_trig_eval: trig_ok = await self.state_trig_eval.eval(new_vars) exc = self.state_trig_eval.get_exception_long() @@ -819,16 +819,29 @@ async def trigger_watch(self): # if "value" not in func_args, then we are state_check_now if "value" in func_args and notify_type == 'state': trig_ident_change = False - for var in self.state_trig_ident: - var_pieces = var.split('.') - if len(var_pieces) == 2 and var == func_args['var_name']: - if func_args['value'] != func_args['old_value']: - trig_ident_change = True - elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == func_args['var_name']: - attrib_val = getattr(func_args['value'], var_pieces[2], None) - attrib_old_val = getattr(func_args['old_value'], var_pieces[2], None) + + # determine if the catchall has been requested in state_trig_ident_any + catch_all_entity = f"{func_args['var_name']}.*" + if catch_all_entity in self.state_trig_ident_any: + # catch all has been requested, check all attributes for change + for attribute in func_args['value'].__dict__: + if attribute in ['last_updated', 'last_changed']: + continue + attrib_val = getattr(func_args['value'], attribute, None) + attrib_old_val = getattr(func_args['old_value'], attribute, None) if attrib_old_val != attrib_val: trig_ident_change = True + else: + for var in self.state_trig_ident: + var_pieces = var.split('.') + if len(var_pieces) == 2 and var == func_args['var_name']: + if func_args['value'] != func_args['old_value']: + trig_ident_change = True + elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == func_args['var_name']: + attrib_val = getattr(func_args['value'], var_pieces[2], None) + attrib_old_val = getattr(func_args['old_value'], var_pieces[2], None) + if attrib_old_val != attrib_val: + trig_ident_change = True if not trig_ident_change: continue From d43c79d71ba7a16cd1cca1452fa12697f8b2994c Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 14:21:12 -0600 Subject: [PATCH 13/30] if we don't find d.e.* matches, keep looking normally. --- custom_components/pyscript/trigger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 1facda3..bd7622a 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -831,7 +831,8 @@ async def trigger_watch(self): attrib_old_val = getattr(func_args['old_value'], attribute, None) if attrib_old_val != attrib_val: trig_ident_change = True - else: + + if trig_ident_change == False: for var in self.state_trig_ident: var_pieces = var.split('.') if len(var_pieces) == 2 and var == func_args['var_name']: From 67cc67bf5e099a8bc2e44c55c47405347276035f Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 14:21:42 -0600 Subject: [PATCH 14/30] cleaner syntax --- custom_components/pyscript/trigger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index bd7622a..26b0717 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -832,7 +832,7 @@ async def trigger_watch(self): if attrib_old_val != attrib_val: trig_ident_change = True - if trig_ident_change == False: + if not trig_ident_change: for var in self.state_trig_ident: var_pieces = var.split('.') if len(var_pieces) == 2 and var == func_args['var_name']: From 5d1d4d869a62ee4e348f394256412e0bb0218919 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 14:50:00 -0600 Subject: [PATCH 15/30] let STATE_RE catch d.e.any --- custom_components/pyscript/trigger.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 26b0717..9e9d1b3 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(LOGGER_PATH + ".trigger") -STATE_RE = re.compile(r"[a-zA-Z]\w*\.[a-zA-Z]\w*(\.\*)?$") +STATE_RE = re.compile(r"[a-zA-Z]\w*\.[a-zA-Z]\w*(\.[a-zA-Z\*]?[a-zA-Z]*)?$") def dt_now(): @@ -739,7 +739,19 @@ async def trigger_watch(self): elif notify_type == "state": new_vars, func_args = notify_info - if "var_name" not in func_args or (func_args["var_name"] not in self.state_trig_ident_any and f"{func_args['var_name']}.*" not in self.state_trig_ident_any): + # check if any state_trig_ident_any starts with var_name + any_match = False + if "var_name" not in func_args: + any_match = True + else: + for check_var in self.state_trig_ident_any: + if check_var == func_args['var_name']: + any_match = True + break + if check_var.startswith(f"{func_args['var_name']}."): + any_match = True + break + if not any_match: if self.state_trig_eval: trig_ok = await self.state_trig_eval.eval(new_vars) exc = self.state_trig_eval.get_exception_long() From eeb6a6d22bd852ca97da7ea9e83f7e4e7ed756ad Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 14:52:06 -0600 Subject: [PATCH 16/30] logic correction --- custom_components/pyscript/trigger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 9e9d1b3..a3b620e 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -742,7 +742,7 @@ async def trigger_watch(self): # check if any state_trig_ident_any starts with var_name any_match = False if "var_name" not in func_args: - any_match = True + pass else: for check_var in self.state_trig_ident_any: if check_var == func_args['var_name']: From 32289348622eb8b70f93be837e03cc1f58af1517 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Thu, 5 Nov 2020 14:53:49 -0600 Subject: [PATCH 17/30] more readable --- custom_components/pyscript/trigger.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index a3b620e..861cda2 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -741,9 +741,7 @@ async def trigger_watch(self): # check if any state_trig_ident_any starts with var_name any_match = False - if "var_name" not in func_args: - pass - else: + if "var_name" in func_args: for check_var in self.state_trig_ident_any: if check_var == func_args['var_name']: any_match = True From 00de4899c83f8989b1be040a1a7555877b2a4293 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 05:25:09 -0600 Subject: [PATCH 18/30] properly determine all attributes --- custom_components/pyscript/trigger.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 861cda2..e1895d4 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -834,9 +834,8 @@ async def trigger_watch(self): catch_all_entity = f"{func_args['var_name']}.*" if catch_all_entity in self.state_trig_ident_any: # catch all has been requested, check all attributes for change - for attribute in func_args['value'].__dict__: - if attribute in ['last_updated', 'last_changed']: - continue + all_attributes = (set(func_args['value'].__dict__.keys()) | set(func_args['old_value'].__dict__.keys())) - {"last_updated", "last_changed"} + for attribute in all_attributes: attrib_val = getattr(func_args['value'], attribute, None) attrib_old_val = getattr(func_args['old_value'], attribute, None) if attrib_old_val != attrib_val: From c57a3114926ad103769a134df37870433cfbbb85 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 05:34:11 -0600 Subject: [PATCH 19/30] break out variables to reduce repetition --- custom_components/pyscript/trigger.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index e1895d4..d5fb404 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -827,29 +827,32 @@ async def trigger_watch(self): # # if "value" not in func_args, then we are state_check_now - if "value" in func_args and notify_type == 'state': + if notify_type == 'state' and all([(x in func_args) for x in ['value', 'old_value', 'var_name']]): trig_ident_change = False + value = func_args['value'] + old_value = func_args['old_value'] + var_name = func_args['var_name'] # determine if the catchall has been requested in state_trig_ident_any - catch_all_entity = f"{func_args['var_name']}.*" + catch_all_entity = f"{var_name}.*" if catch_all_entity in self.state_trig_ident_any: # catch all has been requested, check all attributes for change - all_attributes = (set(func_args['value'].__dict__.keys()) | set(func_args['old_value'].__dict__.keys())) - {"last_updated", "last_changed"} + all_attributes = (set(value.__dict__.keys()) | set(old_value.__dict__.keys())) - {"last_updated", "last_changed"} for attribute in all_attributes: - attrib_val = getattr(func_args['value'], attribute, None) - attrib_old_val = getattr(func_args['old_value'], attribute, None) + attrib_val = getattr(value, attribute, None) + attrib_old_val = getattr(old_value, attribute, None) if attrib_old_val != attrib_val: trig_ident_change = True if not trig_ident_change: for var in self.state_trig_ident: var_pieces = var.split('.') - if len(var_pieces) == 2 and var == func_args['var_name']: - if func_args['value'] != func_args['old_value']: + if len(var_pieces) == 2 and var == var_name: + if value != old_value: trig_ident_change = True - elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == func_args['var_name']: - attrib_val = getattr(func_args['value'], var_pieces[2], None) - attrib_old_val = getattr(func_args['old_value'], var_pieces[2], None) + elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: + attrib_val = getattr(value, var_pieces[2], None) + attrib_old_val = getattr(old_value, var_pieces[2], None) if attrib_old_val != attrib_val: trig_ident_change = True From a591ddc360fde067fdd6fc9a420b5c4ee797c880 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 06:21:48 -0600 Subject: [PATCH 20/30] break change check logic into functions and move above trig eval --- custom_components/pyscript/trigger.py | 117 ++++++++++++++------------ 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index d5fb404..2e16cca 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -644,6 +644,55 @@ def start(self): self.task = Function.create_task(self.trigger_watch()) _LOGGER.debug("trigger %s is active", self.name) + def ident_any_values_changed(self, func_args): + """Check for changes to state or attributes on ident any vars""" + value = func_args['value'] + old_value = func_args['old_value'] + var_name = func_args['var_name'] + + if var_name is None: + return False + + for check_var in self.state_trig_ident_any: + if check_var == var_name and old_value != value: + return True + + if check_var.startswith(f"{var_name}."): + var_pieces = check_var.split('.') + if len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: + if var_pieces[2] == "*": + # catch all has been requested, check all attributes for change + all_attributes = (set(value.__dict__.keys()) | set(old_value.__dict__.keys())) - {"last_updated", "last_changed"} + for attribute in all_attributes: + attrib_val = getattr(value, attribute, None) + attrib_old_val = getattr(old_value, attribute, None) + if attrib_old_val != attrib_val: + return True + else: + attrib_val = getattr(value, var_pieces[2], None) + attrib_old_val = getattr(old_value, var_pieces[2], None) + if attrib_old_val != attrib_val: + return True + return False + + def ident_values_changed(self, func_args): + """Check for changes to state or attributes on ident vars""" + value = func_args['value'] + old_value = func_args['old_value'] + var_name = func_args['var_name'] + for check_var in self.state_trig_ident: + var_pieces = check_var.split('.') + if len(var_pieces) == 2 and check_var == var_name: + if value != old_value: + return True + elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: + attrib_val = getattr(value, var_pieces[2], None) + attrib_old_val = getattr(old_value, var_pieces[2], None) + if attrib_old_val != attrib_val: + return True + + return False + async def trigger_watch(self): """Task that runs for each trigger, waiting for the next trigger and calling the function.""" @@ -739,25 +788,22 @@ async def trigger_watch(self): elif notify_type == "state": new_vars, func_args = notify_info - # check if any state_trig_ident_any starts with var_name - any_match = False - if "var_name" in func_args: - for check_var in self.state_trig_ident_any: - if check_var == func_args['var_name']: - any_match = True - break - if check_var.startswith(f"{func_args['var_name']}."): - any_match = True - break - if not any_match: - if self.state_trig_eval: - trig_ok = await self.state_trig_eval.eval(new_vars) - exc = self.state_trig_eval.get_exception_long() - if exc is not None: - self.state_trig_eval.get_logger().error(exc) - trig_ok = False - else: + # if func_args is not fully populated, then we are state_check_now so skip changed check + if all([(x in func_args) for x in ['value', 'old_value', 'var_name']]): + # check for changes to ident vars + if not self.ident_any_values_changed(func_args) and not self.ident_values_changed(func_args): + # nothing changed so no need to evaluate trigger + continue + + if self.state_trig_eval: + trig_ok = await self.state_trig_eval.eval(new_vars) + exc = self.state_trig_eval.get_exception_long() + if exc is not None: + self.state_trig_eval.get_logger().error(exc) trig_ok = False + else: + trig_ok = False + if self.state_hold_dur is not None: if trig_ok: if not state_trig_waiting: @@ -822,42 +868,7 @@ async def trigger_watch(self): if self.task_unique is not None: task_unique_func = Function.task_unique_factory(action_ast_ctx) - # - # check for changes to state or attributes - # - - # if "value" not in func_args, then we are state_check_now - if notify_type == 'state' and all([(x in func_args) for x in ['value', 'old_value', 'var_name']]): - trig_ident_change = False - value = func_args['value'] - old_value = func_args['old_value'] - var_name = func_args['var_name'] - # determine if the catchall has been requested in state_trig_ident_any - catch_all_entity = f"{var_name}.*" - if catch_all_entity in self.state_trig_ident_any: - # catch all has been requested, check all attributes for change - all_attributes = (set(value.__dict__.keys()) | set(old_value.__dict__.keys())) - {"last_updated", "last_changed"} - for attribute in all_attributes: - attrib_val = getattr(value, attribute, None) - attrib_old_val = getattr(old_value, attribute, None) - if attrib_old_val != attrib_val: - trig_ident_change = True - - if not trig_ident_change: - for var in self.state_trig_ident: - var_pieces = var.split('.') - if len(var_pieces) == 2 and var == var_name: - if value != old_value: - trig_ident_change = True - elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: - attrib_val = getattr(value, var_pieces[2], None) - attrib_old_val = getattr(old_value, var_pieces[2], None) - if attrib_old_val != attrib_val: - trig_ident_change = True - - if not trig_ident_change: - continue # # check for @task_unique with kill_me=True From cc9615d8cbb4992a2b6b2ea2ddfca84e58320322 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 06:53:45 -0600 Subject: [PATCH 21/30] deal with old_value/value is None --- custom_components/pyscript/trigger.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 2e16cca..5977491 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -662,7 +662,12 @@ def ident_any_values_changed(self, func_args): if len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: if var_pieces[2] == "*": # catch all has been requested, check all attributes for change - all_attributes = (set(value.__dict__.keys()) | set(old_value.__dict__.keys())) - {"last_updated", "last_changed"} + all_attributes = set() + if value is not None: + all_attributes |= set(value.__dict__.keys()) + if old_value is not None: + all_attributes |= set(old_value.__dict__.keys()) + all_attributes -= {"last_updated", "last_changed"} for attribute in all_attributes: attrib_val = getattr(value, attribute, None) attrib_old_val = getattr(old_value, attribute, None) From 50a083a0f2d6b4ff894d70f00d22cc3341404042 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 06:54:47 -0600 Subject: [PATCH 22/30] skip ident change check if also in ident_any --- custom_components/pyscript/trigger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 5977491..039eeee 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -686,6 +686,8 @@ def ident_values_changed(self, func_args): old_value = func_args['old_value'] var_name = func_args['var_name'] for check_var in self.state_trig_ident: + if check_var in self.state_trig_ident_any: + continue var_pieces = check_var.split('.') if len(var_pieces) == 2 and check_var == var_name: if value != old_value: From 2497f0a5d1c5ef51943081f483b5befe74689504 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 06:55:22 -0600 Subject: [PATCH 23/30] correct trig eval logic --- custom_components/pyscript/trigger.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 039eeee..4964d25 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -795,21 +795,18 @@ async def trigger_watch(self): elif notify_type == "state": new_vars, func_args = notify_info - # if func_args is not fully populated, then we are state_check_now so skip changed check - if all([(x in func_args) for x in ['value', 'old_value', 'var_name']]): - # check for changes to ident vars - if not self.ident_any_values_changed(func_args) and not self.ident_values_changed(func_args): - # nothing changed so no need to evaluate trigger + if not self.ident_any_values_changed(func_args): + if not self.ident_values_changed(func_args): continue - - if self.state_trig_eval: - trig_ok = await self.state_trig_eval.eval(new_vars) - exc = self.state_trig_eval.get_exception_long() - if exc is not None: - self.state_trig_eval.get_logger().error(exc) + + if self.state_trig_eval: + trig_ok = await self.state_trig_eval.eval(new_vars) + exc = self.state_trig_eval.get_exception_long() + if exc is not None: + self.state_trig_eval.get_logger().error(exc) + trig_ok = False + else: trig_ok = False - else: - trig_ok = False if self.state_hold_dur is not None: if trig_ok: From ba79200603cb9f28493d3eccf1d5ecbb6ce8b219 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 06:55:45 -0600 Subject: [PATCH 24/30] formatting --- custom_components/pyscript/trigger.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 4964d25..88a4ff9 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -872,8 +872,6 @@ async def trigger_watch(self): if self.task_unique is not None: task_unique_func = Function.task_unique_factory(action_ast_ctx) - - # # check for @task_unique with kill_me=True # From b063ff6cf9d76256e117b667c5c51f945262d216 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 07:49:33 -0600 Subject: [PATCH 25/30] debugging and pass tests --- custom_components/pyscript/trigger.py | 68 +++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 88a4ff9..2c68d6d 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -646,15 +646,23 @@ def start(self): def ident_any_values_changed(self, func_args): """Check for changes to state or attributes on ident any vars""" - value = func_args['value'] - old_value = func_args['old_value'] - var_name = func_args['var_name'] + value = func_args.get('value') + old_value = func_args.get('old_value') + var_name = func_args.get('var_name') if var_name is None: + _LOGGER.debug( + "%s ident_any change not detected because no var_name", + self.name, + ) return False for check_var in self.state_trig_ident_any: if check_var == var_name and old_value != value: + _LOGGER.debug( + "%s ident_any change detected at state", + self.name, + ) return True if check_var.startswith(f"{var_name}."): @@ -672,32 +680,74 @@ def ident_any_values_changed(self, func_args): attrib_val = getattr(value, attribute, None) attrib_old_val = getattr(old_value, attribute, None) if attrib_old_val != attrib_val: + _LOGGER.debug( + "%s ident_any change detected in * at %s", + self.name, + attribute, + ) return True else: attrib_val = getattr(value, var_pieces[2], None) attrib_old_val = getattr(old_value, var_pieces[2], None) if attrib_old_val != attrib_val: + _LOGGER.debug( + "%s ident_any change detected at %s", + self.name, + var_pieces[2], + ) return True + + _LOGGER.debug( + "%s no ident_any change detected", + self.name, + ) return False def ident_values_changed(self, func_args): """Check for changes to state or attributes on ident vars""" - value = func_args['value'] - old_value = func_args['old_value'] - var_name = func_args['var_name'] + value = func_args.get('value') + old_value = func_args.get('old_value') + var_name = func_args.get('var_name') + + if var_name is None: + _LOGGER.debug( + "%s ident changes detected because no var_name", + self.name, + ) + return True + for check_var in self.state_trig_ident: - if check_var in self.state_trig_ident_any: - continue + # if check_var in self.state_trig_ident_any: + # _LOGGER.debug( + # "%s ident change skipping %s because also ident_any", + # self.name, + # check_var, + # ) + # continue var_pieces = check_var.split('.') if len(var_pieces) == 2 and check_var == var_name: if value != old_value: + _LOGGER.debug( + "%s ident change detected at state", + self.name + ) return True elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: attrib_val = getattr(value, var_pieces[2], None) attrib_old_val = getattr(old_value, var_pieces[2], None) if attrib_old_val != attrib_val: + _LOGGER.debug( + "%s ident change detected at attribute %s", + self.name, + var_pieces[2] + ) return True + _LOGGER.debug( + "%s no ident change detected", + self.name, + ) + return False async def trigger_watch(self): @@ -798,7 +848,7 @@ async def trigger_watch(self): if not self.ident_any_values_changed(func_args): if not self.ident_values_changed(func_args): continue - + if self.state_trig_eval: trig_ok = await self.state_trig_eval.eval(new_vars) exc = self.state_trig_eval.get_exception_long() From a18e253bfb859b3d88bcf33c6e8eaea48bb9983c Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 07:59:08 -0600 Subject: [PATCH 26/30] make state_check_now logic more readable --- custom_components/pyscript/trigger.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 2c68d6d..a606e10 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -711,19 +711,19 @@ def ident_values_changed(self, func_args): if var_name is None: _LOGGER.debug( - "%s ident changes detected because no var_name", + "%s ident changes not detected because no var_name", self.name, ) - return True + return False for check_var in self.state_trig_ident: - # if check_var in self.state_trig_ident_any: - # _LOGGER.debug( - # "%s ident change skipping %s because also ident_any", - # self.name, - # check_var, - # ) - # continue + if check_var in self.state_trig_ident_any: + _LOGGER.debug( + "%s ident change skipping %s because also ident_any", + self.name, + check_var, + ) + continue var_pieces = check_var.split('.') if len(var_pieces) == 2 and check_var == var_name: if value != old_value: @@ -846,7 +846,8 @@ async def trigger_watch(self): new_vars, func_args = notify_info if not self.ident_any_values_changed(func_args): - if not self.ident_values_changed(func_args): + # if var_name not in func_args we are state_check_now + if "var_name" in func_args and not self.ident_values_changed(func_args): continue if self.state_trig_eval: From 729f52227050c81eec865b3b0899ba613f476fce Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 19:48:14 -0600 Subject: [PATCH 27/30] better STATE_RE --- custom_components/pyscript/trigger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index a606e10..b52cf46 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(LOGGER_PATH + ".trigger") -STATE_RE = re.compile(r"[a-zA-Z]\w*\.[a-zA-Z]\w*(\.[a-zA-Z\*]?[a-zA-Z]*)?$") +STATE_RE = re.compile(r"[a-zA-Z]\w*\.[a-zA-Z]\w*(\.(([a-zA-Z]\w*)|\*))?$") def dt_now(): From 0da11b98a978ca54121bc2773edaa94f27803183 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 19:55:35 -0600 Subject: [PATCH 28/30] make and use STATE_VIRTUAL_ATTRS --- custom_components/pyscript/state.py | 1 + custom_components/pyscript/trigger.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/pyscript/state.py b/custom_components/pyscript/state.py index fdb67e8..4b7cb82 100644 --- a/custom_components/pyscript/state.py +++ b/custom_components/pyscript/state.py @@ -12,6 +12,7 @@ _LOGGER = logging.getLogger(LOGGER_PATH + ".state") +STATE_VIRTUAL_ATTRS = {"last_updated", "last_changed"} class StateVar(str): """Class for representing the value and attributes of a state variable.""" diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index b52cf46..cbe213b 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -17,7 +17,7 @@ from .eval import AstEval from .event import Event from .function import Function -from .state import State +from .state import State, STATE_VIRTUAL_ATTRS _LOGGER = logging.getLogger(LOGGER_PATH + ".trigger") @@ -675,7 +675,7 @@ def ident_any_values_changed(self, func_args): all_attributes |= set(value.__dict__.keys()) if old_value is not None: all_attributes |= set(old_value.__dict__.keys()) - all_attributes -= {"last_updated", "last_changed"} + all_attributes -= STATE_VIRTUAL_ATTRS for attribute in all_attributes: attrib_val = getattr(value, attribute, None) attrib_old_val = getattr(old_value, attribute, None) From 6d57457778d0f7253cdb5a053676c51a4bda7b0d Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 20:09:22 -0600 Subject: [PATCH 29/30] first pass at wait_until and global ident checks --- custom_components/pyscript/trigger.py | 197 +++++++++++--------------- 1 file changed, 82 insertions(+), 115 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index cbe213b..06d8cd4 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -48,6 +48,74 @@ def parse_time_offset(offset_str): return value * scale +def ident_any_values_changed(func_args, ident): + """Check for changes to state or attributes on ident any vars""" + value = func_args.get('value') + old_value = func_args.get('old_value') + var_name = func_args.get('var_name') + + if var_name is None: + return False + + for check_var in ident: + if check_var == var_name and old_value != value: + return True + + if check_var.startswith(f"{var_name}."): + var_pieces = check_var.split('.') + if len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: + if var_pieces[2] == "*": + # catch all has been requested, check all attributes for change + all_attributes = set() + if value is not None: + all_attributes |= set(value.__dict__.keys()) + if old_value is not None: + all_attributes |= set(old_value.__dict__.keys()) + all_attributes -= STATE_VIRTUAL_ATTRS + for attribute in all_attributes: + attrib_val = getattr(value, attribute, None) + attrib_old_val = getattr(old_value, attribute, None) + if attrib_old_val != attrib_val: + return True + else: + attrib_val = getattr(value, var_pieces[2], None) + attrib_old_val = getattr(old_value, var_pieces[2], None) + if attrib_old_val != attrib_val: + return True + + return False + +def ident_values_changed(func_args, ident): + """Check for changes to state or attributes on ident vars""" + value = func_args.get('value') + old_value = func_args.get('old_value') + var_name = func_args.get('var_name') + + if var_name is None: + return False + + for check_var in ident: + # if check_var in self.state_trig_ident_any: + # _LOGGER.debug( + # "%s ident change skipping %s because also ident_any", + # self.name, + # check_var, + # ) + # continue + var_pieces = check_var.split('.') + if len(var_pieces) == 2 and check_var == var_name: + if value != old_value: + return True + elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: + attrib_val = getattr(value, var_pieces[2], None) + attrib_old_val = getattr(old_value, var_pieces[2], None) + if attrib_old_val != attrib_val: + return True + + return False + + + class TrigTime: """Class for trigger time functions.""" @@ -254,13 +322,18 @@ async def wait_until( new_vars, func_args = None, {} state_trig_ok = False - if func_args.get("var_name", "") in state_trig_ident_any: - state_trig_ok = True - elif state_trig_eval: - state_trig_ok = await state_trig_eval.eval(new_vars) - exc = state_trig_eval.get_exception_obj() - if exc is not None: - break + + if not ident_any_values_changed(func_args, state_trig_ident_any): + # if var_name not in func_args we are state_check_now + if "var_name" in func_args and not ident_values_changed(func_args, state_trig): + continue + + if state_trig_eval: + state_trig_ok = await state_trig_eval.eval(new_vars) + exc = state_trig_eval.get_exception_obj() + if exc is not None: + break + if state_hold is not None: if state_trig_ok: if not state_trig_waiting: @@ -644,112 +717,6 @@ def start(self): self.task = Function.create_task(self.trigger_watch()) _LOGGER.debug("trigger %s is active", self.name) - def ident_any_values_changed(self, func_args): - """Check for changes to state or attributes on ident any vars""" - value = func_args.get('value') - old_value = func_args.get('old_value') - var_name = func_args.get('var_name') - - if var_name is None: - _LOGGER.debug( - "%s ident_any change not detected because no var_name", - self.name, - ) - return False - - for check_var in self.state_trig_ident_any: - if check_var == var_name and old_value != value: - _LOGGER.debug( - "%s ident_any change detected at state", - self.name, - ) - return True - - if check_var.startswith(f"{var_name}."): - var_pieces = check_var.split('.') - if len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: - if var_pieces[2] == "*": - # catch all has been requested, check all attributes for change - all_attributes = set() - if value is not None: - all_attributes |= set(value.__dict__.keys()) - if old_value is not None: - all_attributes |= set(old_value.__dict__.keys()) - all_attributes -= STATE_VIRTUAL_ATTRS - for attribute in all_attributes: - attrib_val = getattr(value, attribute, None) - attrib_old_val = getattr(old_value, attribute, None) - if attrib_old_val != attrib_val: - _LOGGER.debug( - "%s ident_any change detected in * at %s", - self.name, - attribute, - ) - return True - else: - attrib_val = getattr(value, var_pieces[2], None) - attrib_old_val = getattr(old_value, var_pieces[2], None) - if attrib_old_val != attrib_val: - _LOGGER.debug( - "%s ident_any change detected at %s", - self.name, - var_pieces[2], - ) - return True - - _LOGGER.debug( - "%s no ident_any change detected", - self.name, - ) - return False - - def ident_values_changed(self, func_args): - """Check for changes to state or attributes on ident vars""" - value = func_args.get('value') - old_value = func_args.get('old_value') - var_name = func_args.get('var_name') - - if var_name is None: - _LOGGER.debug( - "%s ident changes not detected because no var_name", - self.name, - ) - return False - - for check_var in self.state_trig_ident: - if check_var in self.state_trig_ident_any: - _LOGGER.debug( - "%s ident change skipping %s because also ident_any", - self.name, - check_var, - ) - continue - var_pieces = check_var.split('.') - if len(var_pieces) == 2 and check_var == var_name: - if value != old_value: - _LOGGER.debug( - "%s ident change detected at state", - self.name - ) - return True - elif len(var_pieces) == 3 and f"{var_pieces[0]}.{var_pieces[1]}" == var_name: - attrib_val = getattr(value, var_pieces[2], None) - attrib_old_val = getattr(old_value, var_pieces[2], None) - if attrib_old_val != attrib_val: - _LOGGER.debug( - "%s ident change detected at attribute %s", - self.name, - var_pieces[2] - ) - return True - - _LOGGER.debug( - "%s no ident change detected", - self.name, - ) - - return False - async def trigger_watch(self): """Task that runs for each trigger, waiting for the next trigger and calling the function.""" @@ -845,9 +812,9 @@ async def trigger_watch(self): elif notify_type == "state": new_vars, func_args = notify_info - if not self.ident_any_values_changed(func_args): + if not ident_any_values_changed(func_args, self.state_trig_ident_any): # if var_name not in func_args we are state_check_now - if "var_name" in func_args and not self.ident_values_changed(func_args): + if "var_name" in func_args and not ident_values_changed(func_args, self.state_trig_ident): continue if self.state_trig_eval: From 0d4e32c710f675da3b6ea202e81b39a2383f5491 Mon Sep 17 00:00:00 2001 From: Daniel Lashua Date: Fri, 6 Nov 2020 20:18:58 -0600 Subject: [PATCH 30/30] correct logic --- custom_components/pyscript/trigger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 06d8cd4..b60cd7b 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -321,12 +321,12 @@ async def wait_until( else: new_vars, func_args = None, {} - state_trig_ok = False + state_trig_ok = True if not ident_any_values_changed(func_args, state_trig_ident_any): # if var_name not in func_args we are state_check_now if "var_name" in func_args and not ident_values_changed(func_args, state_trig): - continue + state_trig_ok = False if state_trig_eval: state_trig_ok = await state_trig_eval.eval(new_vars)