From 8d1ff2fdf69a9d13ae285b9332c85125dae7bae9 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 22 Sep 2024 20:33:51 +0200 Subject: [PATCH 01/17] stateengine plugin: fix previously introduced issue logging problem --- stateengine/StateEngineItem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index 40a9f32d2..38417aeda 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -1204,7 +1204,7 @@ def list_issues(v): self.__logger.info("has the following issues:") self.__logger.increase_indent() for i, e in enumerate(_issuelist): - _attr = "" if _attrlist is None or not _attrlist[i] else "attribute {}: ".format(_attrlist[i]) + _attr = "" if _attrlist is None or not isinstance(_attrlist, list) or not _attrlist[i] else "attribute {}: ".format(_attrlist[i]) if isinstance(e, dict): print_readable_dict(_attr, e) else: @@ -1214,11 +1214,11 @@ def list_issues(v): if isinstance(_issuelist[0], dict): self.__logger.info("has the following issues:") self.__logger.increase_indent() - _attr = "" if _attrlist is None or not _attrlist[0] else "attribute {}: ".format(_attrlist[0]) + _attr = "" if _attrlist is None or not isinstance(_attrlist, list) or not _attrlist[0] else "attribute {}: ".format(_attrlist[0]) print_readable_dict(_attr, _issuelist[0]) self.__logger.decrease_indent() else: - _attr = "" if _attrlist is None or not _attrlist[0] else " for attribute {}".format(_attrlist[0]) + _attr = "" if _attrlist is None or not isinstance(_attrlist, list) or not _attrlist[0] else " for attribute {}".format(_attrlist[0]) self.__logger.info("has the following issue{}: {}", _attr, _issuelist[0]) else: if isinstance(_issuelist, dict): From 86a1d4b7ffa5cf071ace6d268df0a5367e84b721 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 22 Sep 2024 20:44:29 +0200 Subject: [PATCH 02/17] stateengine plugin: fix pass_state webif handling on first run --- stateengine/StateEngineItem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index 38417aeda..23026c6ae 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -704,8 +704,8 @@ def update_current_to_empty(d): _leaveactions_run = True elif result is False and last_state != state and state.actions_pass.count() > 0: _pass_state = state - state.run_pass(self.__pass_repeat.get(state, False), self.__repeat_actions.get()) - _key_pass = ['{}'.format(last_state.id), 'pass'] + _pass_state.run_pass(self.__pass_repeat.get(state, False), self.__repeat_actions.get()) + _key_pass = ['{}'.format(_pass_state.id), 'pass'] self.update_webif(_key_pass, True) self.__pass_repeat.update({state: True}) if result is True: From eeaf0d1c5f1686df88c0a5a7be68f0084a2d3c30 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 23 Sep 2024 06:39:57 +0200 Subject: [PATCH 03/17] stateengine plugin: minor improvements --- stateengine/StateEngineAction.py | 4 ++-- stateengine/StateEngineActions.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index 04679281e..2cd578b44 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -42,7 +42,7 @@ def function(self): def action_status(self): return self._action_status - # Cast function for delay + # Cast function for delay and other time based attributes # value: value to cast @staticmethod def __cast_seconds(value): @@ -59,7 +59,7 @@ def __cast_seconds(value): elif isinstance(value, float): return int(value) else: - raise ValueError("Can not cast delay value {0} to int!".format(value)) + raise ValueError("Can not cast value {0} to int!".format(value)) # Initialize the action # abitem: parent SeItem instance diff --git a/stateengine/StateEngineActions.py b/stateengine/StateEngineActions.py index 92cf17fcb..05f2b340a 100755 --- a/stateengine/StateEngineActions.py +++ b/stateengine/StateEngineActions.py @@ -567,7 +567,7 @@ def remove_action(e): _issue_list.append(_issue) if _action: self.__actions[name] = _action - self._log_debug("Handle combined issuelist {}", _issue_list) + # self._log_develop("Handle combined issuelist {}", _issue_list) return _issue_list # noinspection PyMethodMayBeStatic From 61ffec0233e8c5aa5948641b474811f2ee7fa67c Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 23 Sep 2024 06:41:34 +0200 Subject: [PATCH 04/17] stateengine plugin: re-fix next conditionset when using released_by states --- stateengine/StateEngineItem.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index 23026c6ae..e628ac0ec 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -761,11 +761,6 @@ def update_current_to_empty(d): "State is a copy and therefore just releasing {}. Skipping state actions, running leave actions " "of last state, then retriggering.", new_state.is_copy_for.id) if last_state is not None and self.__ab_alive: - _, self.__nextconditionset_name = self.__update_check_can_enter(last_state, _instant_leaveaction, False) - if self.__nextconditionset_name: - self.__nextconditionset_id = f"{state.state_item.property.path}.{self.__nextconditionset_name}" - else: - self.__nextconditionset_id = "" self.set_variable('next.conditionset_id', self.__nextconditionset_id) self.set_variable('next.conditionset_name', self.__nextconditionset_name) self.__logger.develop("Current variables: {}", self.__variables) From 4e3cb99a56268b25ec5629271007c31761019ac9 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 23 Sep 2024 06:43:03 +0200 Subject: [PATCH 05/17] stateengine plugin: improve logging (e.g. for handling released_by), introducing internal prefix for log messages --- stateengine/StateEngineItem.py | 8 ++++++-- stateengine/StateEngineLogger.py | 18 +++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index e628ac0ec..f0d5b9a3c 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -936,6 +936,7 @@ def update_can_release_list(): self.__logger.info("".ljust(80, "_")) self.__logger.info("Handling released_by attributes") + self.__logger.increase_indent("Handling released_by... ") can_release = {} all_released_by = {} skip_copy = True @@ -1037,7 +1038,7 @@ def update_can_release_list(): self.__release_info = {new_state.id: _can_release_list} _key_releasedby = ['{}'.format(new_state.id), 'releasedby'] self.update_webif(_key_releasedby, _can_release_list) - + self.__logger.increase_indent("") self.__logger.info("".ljust(80, "_")) return all_released_by @@ -1333,6 +1334,7 @@ def __reorder_states(self, init=True): _reordered_states = [] self.__logger.info("".ljust(80, "_")) self.__logger.info("Recalculating state order. Current order: {}", self.__states) + self.__logger.increase_indent() _copied_states = {} _add_order = 0 _changed_orders = [] @@ -1398,6 +1400,7 @@ def __reorder_states(self, init=True): else: _reorder_webif[state.id] = self.__webif_infos[state.id] self.__webif_infos = _reorder_webif + self.__logger.decrease_indent() self.__logger.info("Recalculated state order. New order: {}", self.__states) self.__logger.info("".ljust(80, "_")) @@ -1895,6 +1898,7 @@ def update_can_release_list(): self.__logger.info("".ljust(80, "_")) self.__logger.info("Initializing released_by attributes") + self.__logger.increase_indent() can_release = {} state_dict = {state.id: state for state in self.__states} for state in self.__states: @@ -1920,7 +1924,7 @@ def update_can_release_list(): self.__config_issues.update({state.id: {'issue': _issuelist, 'attribute': 'se_released_by'}}) state.update_releasedby_internal(_convertedlist) self.__update_can_release(can_release, state) - + self.__logger.decrease_indent() self.__logger.info("".ljust(80, "_")) # log item data diff --git a/stateengine/StateEngineLogger.py b/stateengine/StateEngineLogger.py index a398a6b15..645bf020a 100755 --- a/stateengine/StateEngineLogger.py +++ b/stateengine/StateEngineLogger.py @@ -131,6 +131,7 @@ def __init__(self, item, manual=False): self.__name = 'stateengine.{}'.format(item.property.path) self.__section = item.property.path.replace(".", "_").replace("/", "") self.__indentlevel = 0 + self.__indentprefix = "" if manual: self.__log_level_as_num = 2 else: @@ -151,7 +152,10 @@ def update_logfile(self): # Increase indentation level # by: number of levels to increase def increase_indent(self, by=1): - self.__indentlevel += by + if isinstance(by, int): + self.__indentlevel += by + else: + self.__indentprefix = by # Decrease indentation level # by: number of levels to decrease @@ -170,7 +174,7 @@ def log(self, level, text, *args): indent = "\t" * self.__indentlevel if args: text = text.format(*args) - logtext = "{0}{1} {2}\r\n".format(datetime.datetime.now(), indent, text) + logtext = "{0} {1}{2}{3}\r\n".format(datetime.datetime.now(), self.__indentprefix, indent, text) try: with open(self.__filename, mode="a", encoding="utf-8") as f: f.write(logtext) @@ -194,7 +198,7 @@ def header(self, text): def info(self, text, *args): self.log(1, text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.info(text) @@ -205,7 +209,7 @@ def info(self, text, *args): def debug(self, text, *args): self.log(2, text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.debug(text) @@ -216,7 +220,7 @@ def debug(self, text, *args): def develop(self, text, *args): self.log(3, "DEV: " + text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.log(StateEngineDefaults.VERBOSE, text) @@ -228,7 +232,7 @@ def develop(self, text, *args): def warning(self, text, *args): self.log(1, "WARNING: " + text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.warning(text) @@ -240,7 +244,7 @@ def warning(self, text, *args): def error(self, text, *args): self.log(1, "ERROR: " + text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.error(text) From 00a67378e54d633092817f9ad48f9e1bf4ce573d Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 01:03:34 +0200 Subject: [PATCH 06/17] stateengine plugin: fix and improve logging --- stateengine/StateEngineAction.py | 19 +++++++++++-------- stateengine/StateEngineItem.py | 18 +++++++++++------- stateengine/StateEngineValue.py | 19 +++++++++++-------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index 2cd578b44..69250609d 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -429,6 +429,8 @@ def execute(self, is_repeat: bool, allow_item_repeat: bool, state): # check if any conditiontype is met or not # condition: type of condition 'conditionset'/'previousconditionset'/'previousstate_conditionset'/'nextconditionset' def _check_condition(condition: str): + self._log_debug("Checking {}", condition) + self._log_increase_indent() _conditions_met_count = 0 _conditions_necessary_count = 0 _condition_to_meet = None @@ -451,23 +453,24 @@ def _check_condition(condition: str): _updated_current_condition = self._abitem.get_variable("next.conditionset_id") if _current_condition == '' else _current_condition _condition_to_meet = _condition_to_meet if isinstance(_condition_to_meet, list) else [_condition_to_meet] _condition_met = [] + self._log_decrease_indent() for cond in _condition_to_meet: - if cond is not None: + if cond is not None and condition not in _conditions_met_type: _conditions_necessary_count += 1 _orig_cond = cond try: - cond = re.compile(cond) _matching = cond.fullmatch(_updated_current_condition) if _matching: - self._log_debug("Given {} {} matches current one: {}", condition, _orig_cond, _updated_current_condition) + self._log_debug("Given {} '{}' matches current one: '{}'", condition, _orig_cond.pattern, _updated_current_condition) _condition_met.append(_updated_current_condition) _conditions_met_count += 1 + _conditions_met_type.append(condition) else: - self._log_debug("Given {} {} not matching current one: {}", condition, _orig_cond, _updated_current_condition) - self.update_webif_actionstatus(state, self._name, 'False', None, f"({condition} {_orig_cond} not met)") + self._log_debug("Given {} '{}' not matching current one: '{}'", condition, _orig_cond.pattern, _updated_current_condition) + self.update_webif_actionstatus(state, self._name, 'False', None, f"({condition} {_orig_cond.pattern} not met)") except Exception as ex: if cond is not None: - self._log_warning("Given {} {} is not a valid regex: {}", condition, _orig_cond, ex) + self._log_warning("Given {} '{}' is not a valid regex: {}", condition, _orig_cond.pattern, ex) return _condition_met, _conditions_met_count, _conditions_necessary_count # update web interface with delay info @@ -499,11 +502,9 @@ def _update_repeat_webif(value: bool): self._log_increase_indent() try: self._getitem_fromeval() - self._log_decrease_indent() _validitem = True except Exception: _validitem = False - self._log_decrease_indent() if not self._can_execute(state): self._log_decrease_indent() return @@ -513,6 +514,7 @@ def _update_repeat_webif(value: bool): previous_condition_met = None previousstate_condition_met = None next_condition_met = None + _conditions_met_type = [] if not self.conditionset.is_empty(): current_condition_met, cur_conditions_met, cur_condition_necessary = _check_condition('conditionset') conditions_met += cur_conditions_met @@ -529,6 +531,7 @@ def _update_repeat_webif(value: bool): next_condition_met, next_conditions_met, next_conditionset_necessary = _check_condition('nextconditionset') conditions_met += next_conditions_met condition_necessary += min(1, next_conditionset_necessary) + self._log_decrease_indent() self._log_develop("Action '{0}': conditions met: {1}, necessary {2}.", self._name, conditions_met, condition_necessary) if conditions_met < condition_necessary: self._log_info("Action '{0}': Skipping because not all conditions are met.", self._name) diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index f0d5b9a3c..407dba038 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -223,18 +223,17 @@ def __init__(self, smarthome, item, se_plugin): self.__name = str(self.__item) self.__itemClass = Item # initialize logging - + self.__logger.log_level_as_num = 2 self.__log_level = StateEngineValue.SeValue(self, "Log Level", False, "num") _default_log_level = self.__logger.default_log_level.get() _returnvalue, _returntype, _using_default, _issue, _ = self.__log_level.set_from_attr(self.__item, "se_log_level", - _default_log_level) + default_value=_default_log_level) self.__using_default_log_level = _using_default _returnvalue = self.__log_level.get() if isinstance(_returnvalue, list) and len(_returnvalue) == 1: _returnvalue = _returnvalue[0] - self.__logger.log_level_as_num = 2 _startup_log_level = self.__logger.startup_log_level.get() @@ -266,13 +265,13 @@ def __init__(self, smarthome, item, se_plugin): # get startup delay self.__startup_delay = StateEngineValue.SeValue(self, "Startup Delay", False, "num") - self.__startup_delay.set_from_attr(self.__item, "se_startup_delay", StateEngineDefaults.startup_delay) + self.__startup_delay.set_from_attr(self.__item, "se_startup_delay", default_value=StateEngineDefaults.startup_delay) self.__startup_delay_over = False # Init suspend settings self.__default_suspend_time = StateEngineDefaults.suspend_time.get() self.__suspend_time = StateEngineValue.SeValue(self, "Suspension time on manual changes", False, "num") - self.__suspend_time.set_from_attr(self.__item, "se_suspend_time", self.__default_suspend_time) + self.__suspend_time.set_from_attr(self.__item, "se_suspend_time", default_value=self.__default_suspend_time) # Init laststate and previousstate items/values self.__config_issues = {} @@ -459,7 +458,7 @@ def update_leave_action(self, default_instant_leaveaction): self.__default_instant_leaveaction = default_instant_leaveaction _returnvalue_leave, _returntype_leave, _using_default_leave, _issue, _ = self.__instant_leaveaction.set_from_attr( - self.__item, "se_instant_leaveaction", default_instant_leaveaction) + self.__item, "se_instant_leaveaction", default_value=default_instant_leaveaction) if len(_returnvalue_leave) > 1: self.__logger.warning("se_instant_leaveaction for item {} can not be defined as a list" @@ -2111,7 +2110,12 @@ def get_update_original_source(self): # return value of variable def get_variable(self, varname): - return self.__variables[varname] if varname in self.__variables else "(Unknown variable '{0}'!)".format(varname) + if varname not in self.__variables: + returnvalue = "(Unknown variable '{0}'!)".format(varname) + self.__logger.warning("Issue when getting variable {}".format(returnvalue)) + else: + returnvalue = self.__variables[varname] + return returnvalue # set value of variable def set_variable(self, varname, value): diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index 0e14b1296..b4ff3a3bb 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -115,7 +115,7 @@ def set_from_attr(self, item, attribute_name, default_value=None, reset=True, at value = default_value _using_default = True self._log_develop("Processing value from attribute name {0}, reset {1}, type {2}: using default value {3}", - attribute_name, reset, value, attr_type) + attribute_name, reset, attr_type, value) value_list = [] if value is not None and isinstance(value, list) and attr_type is not None: for i, entry in enumerate(value): @@ -475,6 +475,7 @@ def get_type(self): # Write condition to logger def write_to_logger(self): + returnvalues = [] if self.__template is not None: self._log_info("{0}: Using template(s) {1}", self.__name, self.__template) if self.__value is not None: @@ -484,7 +485,7 @@ def write_to_logger(self): self._log_debug("{0}: {1} ({2})", self.__name, i, type(i)) else: self._log_debug("{0}: {1} ({2})", self.__name, self.__value, type(self.__value)) - return self.__value + returnvalues.append(self.__value) if self.__regex is not None: if isinstance(self.__regex, list): for i in self.__regex: @@ -492,7 +493,7 @@ def write_to_logger(self): self._log_debug("{0} from regex: {1}", self.__name, i) else: self._log_debug("{0} from regex: {1}", self.__name, self.__regex) - return f"regex:{self.__regex}" + returnvalues.append(f"regex:{self.__regex}") if self.__struct is not None: if isinstance(self.__struct, list): for i in self.__struct: @@ -501,7 +502,7 @@ def write_to_logger(self): else: self._log_debug("{0} from struct: {1}", self.__name, self.__struct.property.path) - return self.__struct + returnvalues.append(self.__struct) if self.__item is not None: _original_listorder = self.__listorder.copy() items = [] @@ -517,14 +518,14 @@ def write_to_logger(self): items = self.__get_from_item() self._log_debug("Currently item results in {}", items) self.__listorder = _original_listorder - return items + returnvalues.append(items) if self.__eval is not None: self._log_debug("{0} from eval: {1}", self.__name, self.__eval) _original_listorder = self.__listorder.copy() eval_result = self.__get_eval() self._log_debug("Currently eval results in {}. ", eval_result) self.__listorder = _original_listorder - return eval_result + returnvalues.append(eval_result) if self.__varname is not None: if isinstance(self.__varname, list): for i in self.__varname: @@ -535,8 +536,10 @@ def write_to_logger(self): _original_listorder = self.__listorder.copy() var_result = self.__get_from_variable() self.__listorder = _original_listorder - return var_result - return None + returnvalues.append(var_result) + returnvalues = StateEngineTools.flatten_list(returnvalues) + returnvalues = returnvalues[0] if len(returnvalues) == 1 else None if len(returnvalues) == 0 else returnvalues + return returnvalues # Get Text (similar to logger text) # prefix: Prefix for text From 98c146134fc93f5701a9ff10c6cd1a275ad62586 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 01:05:36 +0200 Subject: [PATCH 07/17] stateengine plugin: introduce regex casting, used for conditionset comparison for actions --- stateengine/StateEngineAction.py | 8 ++++---- stateengine/StateEngineValue.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index 69250609d..70099d79d 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -74,10 +74,10 @@ def __init__(self, abitem, name: str): self.__delay = StateEngineValue.SeValue(self._abitem, "delay") self.__repeat = None self.__instanteval = None - self.nextconditionset = StateEngineValue.SeValue(self._abitem, "nextconditionset", True, "str") - self.conditionset = StateEngineValue.SeValue(self._abitem, "conditionset", True, "str") - self.previousconditionset = StateEngineValue.SeValue(self._abitem, "previousconditionset", True, "str") - self.previousstate_conditionset = StateEngineValue.SeValue(self._abitem, "previousstate_conditionset", True, "str") + self.nextconditionset = StateEngineValue.SeValue(self._abitem, "nextconditionset", True, "regex") + self.conditionset = StateEngineValue.SeValue(self._abitem, "conditionset", True, "regex") + self.previousconditionset = StateEngineValue.SeValue(self._abitem, "previousconditionset", True, "regex") + self.previousstate_conditionset = StateEngineValue.SeValue(self._abitem, "previousstate_conditionset", True, "regex") self.__mode = StateEngineValue.SeValue(self._abitem, "mode", True, "str") self.__order = StateEngineValue.SeValue(self._abitem, "order", False, "num") self._minagedelta = StateEngineValue.SeValue(self._abitem, "minagedelta") diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index b4ff3a3bb..244689933 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -54,7 +54,7 @@ def __init__(self, abitem, name, allow_value_list=False, value_type=None): self.__varname = None self.__template = None self.__issues = [] - self.__get_issues = {'cast_item': [], 'eval': [], 'regex': [], 'struct': [], 'var': [], 'item': []} + self.__get_issues = {'cast_item': [], 'cast_regex': [], 'eval': [], 'regex': [], 'struct': [], 'var': [], 'item': []} self._additional_sources = [] self.itemsApi = Items.get_instance() self.__itemClass = Item @@ -64,6 +64,8 @@ def __init__(self, abitem, name, allow_value_list=False, value_type=None): self.__valid_valuetypes = ["value", "regex", "eval", "var", "item", "template", "struct"] if value_type == "str": self.__cast_func = StateEngineTools.cast_str + elif value_type == "regex": + self.__cast_func = self.cast_regex elif value_type == "num": self.__cast_func = StateEngineTools.cast_num elif value_type == "item": @@ -564,6 +566,31 @@ def get_text(self, prefix=None, suffix=None): value = value if suffix is None else value + suffix return value + # cast a value as regex. Throws ValueError if cast is not possible + # value: value to cast + # returns: value as regex + def cast_regex(self, value): + try: + _issue_dict = {} + _returnvalue = value + if isinstance(value, str): + try: + _returnvalue = re.compile(value, re.IGNORECASE) + except Exception as ex: + _issue = "Issue converting {} to regex: {}".format(value, ex) + _issue_dict = {str(value): _issue} + self._log_error(_issue) + if _issue_dict and _issue_dict not in self.__get_issues['cast_regex']: + self.__get_issues['cast_regex'].append(_issue_dict) + return _returnvalue + except Exception as ex: + _issue = "Can't cast {0} to regex! {1}".format(value, ex) + _issue_dict = {str(value): _issue} + if _issue_dict not in self.__get_issues['cast_regex']: + self.__get_issues['cast_regex'].append(_issue_dict) + self._log_error(_issue) + return value + # cast a value as item. Throws ValueError if cast is not possible # value: value to cast # returns: value as item or struct From 1d52c52aa08ab69fca2063f4487f58cc5e200d2b Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 01:06:23 +0200 Subject: [PATCH 08/17] stateengine plugin: fix/improve conversion of lists in items --- stateengine/StateEngineTools.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/stateengine/StateEngineTools.py b/stateengine/StateEngineTools.py index a29968b0a..2a892ceaa 100755 --- a/stateengine/StateEngineTools.py +++ b/stateengine/StateEngineTools.py @@ -323,8 +323,16 @@ def convert_str_to_list(value, force=True): try: elements = re.findall(r"'([^']+)'|([^,]+)", value) flattened_elements = [element[0] if element[0] else element[1] for element in elements] - formatted_str = "[" + ", ".join( - ["'" + element.strip(" '\"") + "'" for element in flattened_elements]) + "]" + formatted_elements = [] + for element in flattened_elements: + element = element.strip(" '\"") + if "'" in element: + formatted_elements.append(f'"{element}"') + elif '"' in element: + formatted_elements.append(f"'{element}'") + else: + formatted_elements.append(f"'{element}'") + formatted_str = "[" + ", ".join(formatted_elements) + "]" return literal_eval(formatted_str) except Exception as ex: raise ValueError("Problem converting string to list: {}".format(ex)) From c49f322aec0d361a65dce125c52628ac0fdc075a Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 01:07:09 +0200 Subject: [PATCH 09/17] stateengine plugin: correctly parse values in item, you can now also define regex, eval, etc. in an item(value)! --- stateengine/StateEngineValue.py | 338 +++++++++++++++++++------------- 1 file changed, 204 insertions(+), 134 deletions(-) diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index 244689933..4cb873448 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -164,11 +164,13 @@ def __resetvalue(self): # Set value # value: string indicating value or source of value # name: name of object ("time" is being handled differently) - def set(self, value, name="", reset=True, copyvalue=True): + def set(self, value, name="", reset=True, copyvalue=True, returnvalue=False): + if copyvalue is True: value = copy.copy(value) if reset: self.__resetvalue() + returnvalues = [] if isinstance(value, list): source = [] field_value = [] @@ -184,7 +186,8 @@ def set(self, value, name="", reset=True, copyvalue=True): f = val source.append(s) field_value.append(f) - self.__listorder.append("{}:{}".format(s, f)) + if not returnvalue: + self.__listorder.append("{}:{}".format(s, f)) if field_value[i] == "": source[i] = "value" field_value[i] = value[i] @@ -199,10 +202,11 @@ def set(self, value, name="", reset=True, copyvalue=True): "will be handled the same as the item type, e.g. string, bool, etc.", _issue, self.__valid_valuetypes, field_value[i]) source[i] = "value" - self.__type_listorder.append(source[i]) - self.__orig_listorder.append(val) - if source[i] == "value": - self.__listorder[i] = value[i] + if not returnvalue: + self.__type_listorder.append(source[i]) + self.__orig_listorder.append(val) + if source[i] == "value": + self.__listorder[i] = value[i] if source[i] == "template": if self.__template is None: self.__template = [] @@ -211,11 +215,14 @@ def set(self, value, name="", reset=True, copyvalue=True): if _template is not None: try: source[i], field_value[i] = StateEngineTools.partition_strip(_template, ":") - if val in self.__listorder and field_value[i] in self._abitem.templates: + if not returnvalue and val in self.__listorder and field_value[i] in self._abitem.templates: self.__listorder[self.__listorder.index(val)] = self._abitem.templates.get(field_value[i]) + elif returnvalue and val in returnvalues and field_value[i] in self._abitem.templates: + returnvalues[returnvalues.index(val)] = self._abitem.templates.get(field_value[i]) except Exception as ex: self._abitem.updatetemplates(field_value[i], None) - self.__listorder = [i for i in self.__listorder if i != val] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != val] self._log_warning("Removing template {}: {}", field_value[i], ex) val, field_value[i], source[i] = None, None, None else: @@ -223,7 +230,8 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue not in self.__issues: self.__issues.append(_issue) self._log_warning(_issue) - self.__listorder = [i for i in self.__listorder if i != val] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != val] source[i], field_value[i], val = None, None, None try: if isinstance(self.__template, list) and len(self.__template) == 1: @@ -233,17 +241,21 @@ def set(self, value, name="", reset=True, copyvalue=True): elif isinstance(value, str): source, field_value = StateEngineTools.partition_strip(value, ":") - self.__listorder.append("{}{}{}".format(source, ":" if field_value else "", field_value)) + if not returnvalue: + self.__listorder.append("{}{}{}".format(source, ":" if field_value else "", field_value)) if source == "template": self.__template = field_value _template = self._abitem.templates.get(self.__template) if _template is not None: try: source, field_value = StateEngineTools.partition_strip(_template, ":") - if value in self.__listorder and field_value in self._abitem.templates: + if not returnvalue and value in self.__listorder and field_value in self._abitem.templates: self.__listorder[self.__listorder.index(value)] = self._abitem.templates[self.__template] + elif returnvalue and value in returnvalues and field_value in self._abitem.templates: + returnvalues[returnvalues.index(value)] = self._abitem.templates[self.__template] except Exception as ex: - self.__listorder = [i for i in self.__listorder if i != value] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != value] source, field_value, value = None, None, None self._abitem.updatetemplates(self.__template, None) self._log_warning("Removing template {}: {}", self.__template, ex) @@ -252,7 +264,8 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue not in self.__issues: self.__issues.append(_issue) self._log_warning(_issue) - self.__listorder = [i for i in self.__listorder if i != value] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != value] source, field_value, value = None, None, None try: cond1 = source.lstrip('-').replace('.', '', 1).isdigit() @@ -274,10 +287,11 @@ def set(self, value, name="", reset=True, copyvalue=True): "will be handled the same as the item type, e.g. string, bool, etc.", _issue, self.__valid_valuetypes, field_value) source = "value" - if source == "value": - self.__listorder = [field_value] - self.__type_listorder.append(source) - self.__orig_listorder.append(value) + if not returnvalue: + if source == "value": + self.__listorder = [field_value] + self.__type_listorder.append(source) + self.__orig_listorder.append(value) else: source = "value" field_value = value @@ -316,8 +330,9 @@ def set(self, value, name="", reset=True, copyvalue=True): elif field_value[i] == "": field_value[i] = s s = "value" - self.__value = [] if self.__value is None else [self.__value] if not isinstance(self.__value, - list) else self.__value + if not returnvalue: + self.__value = [] if self.__value is None else [self.__value] if not isinstance(self.__value, + list) else self.__value if s == "value": cond3 = isinstance(field_value[i], str) and field_value[i].lstrip('-').replace('.', '', 1).isdigit() if cond3: @@ -330,10 +345,14 @@ def set(self, value, name="", reset=True, copyvalue=True): _value, _issue = self.__do_cast(field_value[i]) if _issue not in [[], None, [None], self.__issues]: self.__issues.append(_issue) - self.__value.append(_value) + if not returnvalue: + self.__value.append(_value) + else: + returnvalues.append(_value) else: self.__value.append(None) - self.__item = [] if self.__item is None else [self.__item] if not isinstance(self.__item, list) else self.__item + if not returnvalue: + self.__item = [] if self.__item is None else [self.__item] if not isinstance(self.__item, list) else self.__item if s == "item": _item, _issue = self._abitem.return_item(field_value[i]) if _issue not in [[], None, [None], self.__issues]: @@ -341,35 +360,47 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) self.__issues.append(_issue) - self.__item.append(None if s != "item" else self.__absolute_item(_item, field_value[i])) - self.__eval = [] if self.__eval is None else [self.__eval] if not isinstance(self.__eval, list) else self.__eval - self.__eval.append(None if s != "eval" else field_value[i]) - self.__regex = [] if self.__regex is None else [self.__regex] if not isinstance(self.__regex, list) else self.__regex - self.__regex.append(None if s != "regex" else field_value[i]) - self.__struct = [] if self.__struct is None else [self.__struct] if not isinstance(self.__struct, list) else self.__struct - self.__struct.append(None if s != "struct" else StateEngineStructs.create(self._abitem, field_value[i])) - self.__varname = [] if self.__varname is None else [self.__varname] if not isinstance(self.__varname, list) else self.__varname - self.__varname.append(None if s != "var" else field_value[i]) - - if self.__item: - self.__item = [i for i in self.__item if i is not None] - self.__item = self.__item[0] if len(self.__item) == 1 else None if len(self.__item) == 0 else self.__item - if self.__eval: - self.__eval = [i for i in self.__eval if i is not None] - self.__eval = self.__eval[0] if len(self.__eval) == 1 else None if len(self.__eval) == 0 else self.__eval - if self.__regex: - self.__regex = [i for i in self.__regex if i is not None] - self.__regex = self.__regex[0] if len(self.__regex) == 1 else None if len(self.__regex) == 0 else self.__regex - if self.__struct: - self.__struct = [i for i in self.__struct if i is not None] - self.__struct = None if len(self.__struct) == 0 else self.__struct - if self.__varname: - self.__varname = [i for i in self.__varname if i is not None] - self.__varname = self.__varname[0] if len(self.__varname) == 1 else None if len(self.__varname) == 0 else self.__varname - if self.__value: - self.__value = [i for i in self.__value if i is not None] - self.__value = self.__value[0] if len(self.__value) == 1 else None if len(self.__value) == 0 else self.__value - + returnvalues.append(_item) + if not returnvalue: + self.__item.append(None if s != "item" else self.__absolute_item(_item, field_value[i])) + self.__eval = [] if self.__eval is None else [self.__eval] if not isinstance(self.__eval, list) else self.__eval + self.__eval.append(None if s != "eval" else field_value[i]) + self.__regex = [] if self.__regex is None else [self.__regex] if not isinstance(self.__regex, list) else self.__regex + self.__regex.append(None if s != "regex" else field_value[i]) + self.__struct = [] if self.__struct is None else [self.__struct] if not isinstance(self.__struct, list) else self.__struct + self.__struct.append(None if s != "struct" else StateEngineStructs.create(self._abitem, field_value[i])) + self.__varname = [] if self.__varname is None else [self.__varname] if not isinstance(self.__varname, list) else self.__varname + self.__varname.append(None if s != "var" else field_value[i]) + else: + if s == "item": + returnvalues.append(self.__get_from_item(field_value[i])) + elif s == "struct": + returnvalues.append(self.__get_from_struct(field_value[i])) + elif s == "eval": + returnvalues.append(self.__get_eval(field_value[i])) + elif s == "regex": + returnvalues.append(self.__get_from_regex(field_value[i])) + elif s == "var": + returnvalues.append(self.__get_from_variable(field_value[i])) + if not returnvalue: + if self.__item: + self.__item = [i for i in self.__item if i is not None] + self.__item = self.__item[0] if len(self.__item) == 1 else None if len(self.__item) == 0 else self.__item + if self.__eval: + self.__eval = [i for i in self.__eval if i is not None] + self.__eval = self.__eval[0] if len(self.__eval) == 1 else None if len(self.__eval) == 0 else self.__eval + if self.__regex: + self.__regex = [i for i in self.__regex if i is not None] + self.__regex = self.__regex[0] if len(self.__regex) == 1 else None if len(self.__regex) == 0 else self.__regex + if self.__struct: + self.__struct = [i for i in self.__struct if i is not None] + self.__struct = None if len(self.__struct) == 0 else self.__struct + if self.__varname: + self.__varname = [i for i in self.__varname if i is not None] + self.__varname = self.__varname[0] if len(self.__varname) == 1 else None if len(self.__varname) == 0 else self.__varname + if self.__value: + self.__value = [i for i in self.__value if i is not None] + self.__value = self.__value[0] if len(self.__value) == 1 else None if len(self.__value) == 0 else self.__value else: if source == "item": _item, _issue = self._abitem.return_item(field_value) @@ -378,11 +409,24 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) self.__issues.append(_issue) - self.__item = None if source != "item" else self.__absolute_item(_item, field_value) - self.__eval = None if source != "eval" else field_value - self.__regex = None if source != "regex" else field_value - self.__struct = None if source != "struct" else StateEngineStructs.create(self._abitem, field_value) - self.__varname = None if source != "var" else field_value + returnvalues.append(_item) + if not returnvalue: + self.__item = None if source != "item" else self.__absolute_item(_item, field_value) + self.__eval = None if source != "eval" else field_value + self.__regex = None if source != "regex" else field_value + self.__struct = None if source != "struct" else StateEngineStructs.create(self._abitem, field_value) + self.__varname = None if source != "var" else field_value + else: + if source == "item": + returnvalues.append(self.__get_from_item(field_value)) + elif source == "struct": + returnvalues.append(self.__get_from_struct(field_value)) + elif source == "eval": + returnvalues.append(self.__get_eval(field_value)) + elif source == "regex": + returnvalues.append(self.__get_from_regex(field_value)) + elif source == "var": + returnvalues.append(self.__get_from_variable(field_value)) if source == "value": if isinstance(field_value, list) and not self.__allow_value_list: raise ValueError("{0}: value_in is not allowed, problem with {1}. Allowed = {2}".format( @@ -394,16 +438,24 @@ def set(self, value, name="", reset=True, copyvalue=True): field_value = True elif isinstance(field_value, str) and field_value.lower() in ['false', 'no']: field_value = False - self.__value, _issue = self.__do_cast(field_value) + if not returnvalue: + self.__value, _issue = self.__do_cast(field_value) + else: + val, _issue = self.__do_cast(field_value) + returnvalues.append(val) if _issue not in [[], None, [None], self.__issues]: self.__issues.append(_issue) else: self.__value = None self.__issues = StateEngineTools.flatten_list(self.__issues) + del value + if returnvalue: + returnvalues = StateEngineTools.flatten_list(returnvalues) + returnvalues = returnvalues[0] if len(returnvalues) == 1 else None if len(returnvalues) == 0 else returnvalues + return returnvalues, self.__issues self.__listorder = StateEngineTools.flatten_list(self.__listorder) self.__type_listorder = StateEngineTools.flatten_list(self.__type_listorder) self.__orig_listorder = StateEngineTools.flatten_list(self.__orig_listorder) - del value return self.__listorder, self.__type_listorder, self.__issues, self.__orig_listorder # Set cast function @@ -718,10 +770,12 @@ def __do_cast(self, value, item_id=None): return value, _issue # Determine value by using a struct - def __get_from_struct(self): + def __get_from_struct(self, struct_get=None): values = [] - if isinstance(self.__struct, list): - for val in self.__struct: + if struct_get is None: + struct_get = self.__struct + if isinstance(struct_get, list): + for val in struct_get: if val is not None: _newvalue, _issue = self.__do_cast(val) _issue_dict = {val: _issue} @@ -731,28 +785,28 @@ def __get_from_struct(self): if 'struct:{}'.format(val.property.path) in self.__listorder: self.__listorder[self.__listorder.index('struct:{}'.format(val.property.path))] = _newvalue else: - if self.__struct is not None: - _newvalue, _issue = self.__do_cast(self.__struct) - _issue_dict = {self.__struct: _issue} + if struct_get is not None: + _newvalue, _issue = self.__do_cast(struct_get) + _issue_dict = {struct_get: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['struct']: self.__get_issues['struct'].append(_issue_dict) - if 'struct:{}'.format(self.__regex) in self.__listorder: - self.__listorder[self.__listorder.index('struct:{}'.format(self.__struct))] = _newvalue + if 'struct:{}'.format(struct_get) in self.__listorder: + self.__listorder[self.__listorder.index('struct:{}'.format(struct_get))] = _newvalue values = _newvalue if values: return values try: - _newvalue, _issue = self.__do_cast(self.__struct) + _newvalue, _issue = self.__do_cast(struct_get) _issue_dict = {_newvalue: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['struct']: self.__get_issues['struct'].append(_issue_dict) - if 'struct:{}'.format(self.__struct) in self.__listorder: - self.__listorder[self.__listorder.index('struct:{}'.format(self.__struct))] = _newvalue + if 'struct:{}'.format(struct_get) in self.__listorder: + self.__listorder[self.__listorder.index('struct:{}'.format(struct_get))] = _newvalue values = _newvalue except Exception as ex: - values = self.__struct + values = struct_get _issue = "Problem while getting from struct '{0}': {1}.".format(values, ex) _issue_dict = {values: _issue} if _issue_dict not in self.__get_issues['struct']: @@ -761,28 +815,30 @@ def __get_from_struct(self): return values # Determine value by regular expression - def __get_from_regex(self): - if isinstance(self.__regex, list): + def __get_from_regex(self, regex_get=None): + if regex_get is None: + regex_get = self.__regex + if isinstance(regex_get, list): values = [] - for val in self.__regex: + for val in regex_get: _newvalue = re.compile(val, re.IGNORECASE) values.append(_newvalue) if 'regex:{}'.format(val) in self.__listorder: self.__listorder[self.__listorder.index('regex:{}'.format(val))] = _newvalue else: - _newvalue = re.compile(self.__regex, re.IGNORECASE) - if 'regex:{}'.format(self.__regex) in self.__listorder: - self.__listorder[self.__listorder.index('regex:{}'.format(self.__regex))] = _newvalue + _newvalue = re.compile(regex_get, re.IGNORECASE) + if 'regex:{}'.format(regex_get) in self.__listorder: + self.__listorder[self.__listorder.index('regex:{}'.format(regex_get))] = _newvalue values = _newvalue if values is not None: return values try: - _newvalue = re.compile(self.__regex, re.IGNORECASE) - if 'regex:{}'.format(self.__regex) in self.__listorder: - self.__listorder[self.__listorder.index('regex:{}'.format(self.__regex))] = _newvalue + _newvalue = re.compile(regex_get, re.IGNORECASE) + if 'regex:{}'.format(regex_get) in self.__listorder: + self.__listorder[self.__listorder.index('regex:{}'.format(regex_get))] = _newvalue values = _newvalue except Exception as ex: - values = self.__regex + values = regex_get _issue = "Problem while creating regex '{0}': {1}.".format(values, ex) _issue_dict = {values: _issue} if _issue_dict not in self.__get_issues['regex']: @@ -791,7 +847,7 @@ def __get_from_regex(self): return values # Determine value by executing eval-function - def __get_eval(self): + def __get_eval(self, eval_get=None): # noinspection PyUnusedLocal sh = self._sh # noinspection PyUnusedLocal @@ -802,37 +858,44 @@ def __get_eval(self): "get_variable('next.", 'get_variable("next.' ] - if isinstance(self.__eval, str): - self.__eval = StateEngineTools.parse_relative(self.__eval, 'sh.', ['()', '.property.']) - if "stateengine_eval" in self.__eval or "se_eval" in self.__eval: + set_eval = False + if eval_get is None: + eval_get = self.__eval + set_eval = True + if isinstance(eval_get, str): + if set_eval: + self.__eval = StateEngineTools.parse_relative(eval_get, 'sh.', ['()', '.property.']) + else: + eval_get = StateEngineTools.parse_relative(eval_get, 'sh.', ['()', '.property.']) + if "stateengine_eval" in eval_get or "se_eval" in eval_get: # noinspection PyUnusedLocal stateengine_eval = se_eval = StateEngineEval.SeEval(self._abitem) - self._log_debug("Checking eval: {0}", self.__eval) - if self.__eval in self._abitem.cache: + self._log_debug("Checking eval: {0}", eval_get) + if eval_get in self._abitem.cache: self._log_increase_indent() - result = self._abitem.cache.get(self.__eval) + result = self._abitem.cache.get(eval_get) self._log_debug("Loading eval from cache: {}", result) self._log_decrease_indent() - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [result] + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [result] return result self._log_increase_indent() try: - _newvalue, _issue = self.__do_cast(eval(self.__eval)) - _issue_dict = {StateEngineTools.get_eval_name(self.__eval): _issue} + _newvalue, _issue = self.__do_cast(eval(eval_get)) + _issue_dict = {StateEngineTools.get_eval_name(eval_get): _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [_newvalue] + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [_newvalue] values = _newvalue self._log_decrease_indent() self._log_debug("Eval result: {0} ({1}).", values, type(values)) - if not any(pattern in self.__eval for pattern in patterns): - self._abitem.cache = {self.__eval: values} + if not any(pattern in eval_get for pattern in patterns): + self._abitem.cache = {eval_get: values} self._log_increase_indent() except Exception as ex: self._log_decrease_indent() - _name = StateEngineTools.get_eval_name(self.__eval) + _name = StateEngineTools.get_eval_name(eval_get) _issue = "Problem evaluating '{0}': {1}.".format(_name, ex) _issue_dict = {_name: _issue} if _issue_dict not in self.__get_issues['eval']: @@ -843,14 +906,14 @@ def __get_eval(self): finally: self._log_decrease_indent() else: - if isinstance(self.__eval, list): + if isinstance(eval_get, list): values = [] - for val in self.__eval: + for val in eval_get: try: val = val.replace("\n", "") except Exception: pass - self._log_debug("Checking eval {0} from list {1}.", val, self.__eval) + self._log_debug("Checking eval {0} from list {1}.", val, eval_get) self._log_increase_indent() if val in self._abitem.cache: result = self._abitem.cache.get(val) @@ -909,31 +972,31 @@ def __get_eval(self): self._abitem.cache = {val: value} self._log_decrease_indent() else: - self._log_debug("Checking eval (no str, no list): {0}.", self.__eval) - if self.__eval in self._abitem.cache: + self._log_debug("Checking eval (no str, no list): {0}.", eval_get) + if eval_get in self._abitem.cache: self._log_increase_indent() - result = self._abitem.cache.get(self.__eval) + result = self._abitem.cache.get(eval_get) self._log_debug("Loading eval (no str, no list) from cache: {}", result) self._log_decrease_indent() - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [result] + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [result] return result try: self._log_increase_indent() - _newvalue, _issue = self.__do_cast(self.__eval()) + _newvalue, _issue = self.__do_cast(eval_get()) _issue_dict = {_newvalue: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [_newvalue] + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [_newvalue] values = _newvalue self._log_decrease_indent() self._log_debug("Eval result (no str, no list): {0}.", values) - self._abitem.cache = {self.__eval: values} + self._abitem.cache = {eval_get: values} self._log_increase_indent() except Exception as ex: self._log_decrease_indent() - _name = StateEngineTools.get_eval_name(self.__eval) + _name = StateEngineTools.get_eval_name(eval_get) _issue = "Problem evaluating '{0}': {1}.".format(_name, ex) self._log_warning(_issue) self._log_increase_indent() @@ -944,10 +1007,12 @@ def __get_eval(self): return values # Determine value from item - def __get_from_item(self): - if isinstance(self.__item, list): + def __get_from_item(self, get_item=None): + if get_item is None: + get_item = self.__item + if isinstance(get_item, list): values = [] - for val in self.__item: + for val in get_item: _new_values = [] if val is None: _newvalue = None @@ -960,7 +1025,9 @@ def __get_from_item(self): checked_entry = checked_entry if isinstance(checked_entry, list) else [checked_entry] for entry in checked_entry: - _newvalue, _issue = self.__do_cast(entry) + # _newvalue, _issue = self.__do_cast(entry) + _newvalue, _issue = self.set(entry, reset=False, returnvalue=True) + self._log_develop("Return from set from item list: {}, issue {}", _newvalue, _issue) _issue_dict = {entry: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) @@ -981,78 +1048,81 @@ def __get_from_item(self): if values is not None: return values else: - if self.__item is None: + if get_item is None: return None try: - checked_entry = StateEngineTools.convert_str_to_list(self.__item.property.value) + checked_entry = StateEngineTools.convert_str_to_list(get_item.property.value) except Exception as ex: self._log_warning("While getting from item: {}", ex) checked_entry = [] checked_entry = checked_entry if isinstance(checked_entry, list) else [checked_entry] _new_values = [] for entry in checked_entry: - _newvalue, _issue = self.__do_cast(entry) + #_newvalue, _issue = self.__do_cast(entry) + _newvalue, _issue = self.set(entry, reset=False, returnvalue=True) + self._log_develop("Return from set from item: {}, issue {}, listorder {}", _newvalue, _issue, self.__listorder) _issue_dict = {entry: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) if _newvalue is not None: _new_values.append(_newvalue) _new_values = _new_values[0] if len(_new_values) == 1 else None if len(_new_values) == 0 else [_new_values] - search_item = 'item:{}'.format(self.__item) + search_item = 'item:{}'.format(get_item) if search_item in self.__listorder: index = self.__listorder.index(search_item) self.__listorder[index] = _new_values - if self.__item in self.__listorder: - index = self.__listorder.index(self.__item) + if get_item in self.__listorder: + index = self.__listorder.index(get_item) self.__listorder[index] = _new_values values = _new_values if values is not None: return values try: - _newvalue = self.__item.property.path - search_item = 'item:{}'.format(self.__item) + _newvalue = get_item.property.path + search_item = 'item:{}'.format(get_item) if search_item in self.__listorder: index = self.__listorder.index(search_item) self.__listorder[index] = _newvalue values = _newvalue except Exception as ex: - values = self.__item + values = get_item _issue = "Problem while reading item path '{0}': {1}.".format(values, ex) self._log_info(_issue) - _newvalue, _issue = self.__do_cast(values) + #_newvalue, _issue = self.__do_cast(values) + _newvalue, _issue = self.set(values, reset=False, returnvalue=True) + self._log_develop("Return from set from item end: {}, issue {}", _newvalue, _issue) _issue_dict = {_newvalue: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) return _newvalue # Determine value from variable - def __get_from_variable(self): + def __get_from_variable(self, var_get=None): def update_value(varname): value = self._abitem.get_variable(varname) new_value, _issue = self.__do_cast(value) new_value = 'var:{}'.format(varname) if new_value == '' else new_value - if isinstance(new_value, str) and 'Unknown variable' in new_value: + if isinstance(new_value, str) and '(Unknown variable' in new_value: _issue = "There is a problem with your variable {}".format(new_value) - self._log_warning(_issue) new_value = '' _issue_dict = {varname: _issue} - if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['var']: + if _issue_dict not in self.__get_issues['var']: self.__get_issues['var'].append(_issue_dict) - self._log_debug("Checking variable '{0}', value {1} from list {2}", - varname, new_value, self.__listorder) if 'var:{}'.format(varname) in self.__listorder: self.__listorder[self.__listorder.index('var:{}'.format(varname))] = new_value return new_value - + _issue = "" values = [] - - if isinstance(self.__varname, list): - for var in self.__varname: + if var_get is None: + var_get = self.__varname + if isinstance(var_get, list): + for var in var_get: self._log_debug("Checking variable in loop '{0}', value {1} from list {2}", var, values[-1], self.__listorder) values.append(update_value(var)) else: - values = update_value(self.__varname) - self._log_debug("Variable result: {0}", values) + values = update_value(var_get) + + self._log_debug("Variable result: '{}'.", values) return values From f4814b7fce27a99e01e64077addfb517a51ed3a1 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 10:47:52 +0200 Subject: [PATCH 10/17] stateengine plugin: fix string to list conversion --- stateengine/StateEngineTools.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/stateengine/StateEngineTools.py b/stateengine/StateEngineTools.py index 2a892ceaa..8be8bb0b2 100755 --- a/stateengine/StateEngineTools.py +++ b/stateengine/StateEngineTools.py @@ -317,21 +317,25 @@ def partition_strip(value, splitchar): # value: list as string # returns: list or original value def convert_str_to_list(value, force=True): - if isinstance(value, str) and (value[:1] == '[' and value[-1:] == ']'): - value = value.strip("[]") + if isinstance(value, str): + value = value.strip() + if value.startswith('[') and value.endswith(']'): + value = value[1:-1].strip() if isinstance(value, str) and "," in value: try: - elements = re.findall(r"'([^']+)'|([^,]+)", value) - flattened_elements = [element[0] if element[0] else element[1] for element in elements] + elements = re.findall(r"'([^']*)'|\"([^\"]*)\"|([^,]+)", value) + flattened_elements = [element[0] if element[0] else (element[1] if element[1] else element[2].strip()) for + element in elements] + flattened_elements = [element.strip() for element in flattened_elements] formatted_elements = [] for element in flattened_elements: element = element.strip(" '\"") if "'" in element: - formatted_elements.append(f'"{element}"') + formatted_elements.append(f'"{element}"') # If element contains single quote, wrap in double quotes elif '"' in element: - formatted_elements.append(f"'{element}'") + formatted_elements.append(f"'{element}'") # If element contains double quote, wrap in single quotes else: - formatted_elements.append(f"'{element}'") + formatted_elements.append(f"'{element}'") # Default case, wrap in single quotes formatted_str = "[" + ", ".join(formatted_elements) + "]" return literal_eval(formatted_str) except Exception as ex: From d27ef45d91315bac4f9d23eb9eae0af3a8aa7dc6 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 10:48:18 +0200 Subject: [PATCH 11/17] stateengine plugin: reset issues list when setting a value (internal use only) --- stateengine/StateEngineValue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index 4cb873448..344db0d01 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -165,7 +165,7 @@ def __resetvalue(self): # value: string indicating value or source of value # name: name of object ("time" is being handled differently) def set(self, value, name="", reset=True, copyvalue=True, returnvalue=False): - + self.__issues = [] if copyvalue is True: value = copy.copy(value) if reset: From 8d3e1bbf1d9a85524f6272a87120fc128aaaf22f Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 21:02:30 +0200 Subject: [PATCH 12/17] stateengine plugin: fix logging of action count --- stateengine/StateEngineState.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stateengine/StateEngineState.py b/stateengine/StateEngineState.py index 905a969f8..7d0147125 100755 --- a/stateengine/StateEngineState.py +++ b/stateengine/StateEngineState.py @@ -695,8 +695,9 @@ def filter_issues(input_dict): for attribute in child_item.conf: self._log_develop("Filling state with {} action named {} for state {} with config {}", child_name, attribute, state.id, child_item.conf) action_method.update_action_details(self, action_type) - _action_counts[action_name] += 1 - _, _action_status = action_method.update(attribute, child_item.conf.get(attribute)) + _result = action_method.update(attribute, child_item.conf.get(attribute)) + _action_counts[action_name] += _result[0] if _result else 0 + _action_status = _result[1] if _action_status: update_action_status(action_name, _action_status) self._abitem.update_action_status(self.__action_status) @@ -716,7 +717,7 @@ def filter_issues(input_dict): update_action_status("enter_or_stay", _action_status) self._abitem.update_action_status(self.__action_status) - _total_actioncount = _action_counts["enter"] + _action_counts["stay"] + _action_counts["enter_or_stay"] + _action_counts["leave"] + _total_actioncount = _action_counts["enter"] + _action_counts["stay"] + _action_counts["enter_or_stay"] + _action_counts["leave"] + _action_counts["pass"] self.update_name(item_state, recursion_depth) # Complete condition sets and actions at the end From b1f977ff5b9c0cfc14f6b3aa673ccc05dff41148 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 21:02:56 +0200 Subject: [PATCH 13/17] stateengine plugin: fix list actions --- stateengine/StateEngineAction.py | 129 ++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 46 deletions(-) diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index 70099d79d..d6653982a 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -287,10 +287,7 @@ def check_getitem_fromeval(self, check_item, check_value=None, check_mindelta=No check_item, _issue = self._abitem.return_item(item) _issue = { self._name: {'issue': _issue, 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} - if check_value: - check_value.set_cast(check_item.cast) - if check_mindelta: - check_mindelta.set_cast(check_item.cast) + check_item, check_mindelta = self._cast_stuff(check_item, check_mindelta, check_value) self._scheduler_name = "{}-SeItemDelayTimer".format(check_item.property.path) if self._abitem.id == check_item.property.path: self._caller += '_self' @@ -343,6 +340,12 @@ def eval_minagedelta(self, actioninfo, state): else: return False + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + def check_complete(self, state, check_item, check_status, check_mindelta, check_minagedelta, check_value, action_type, evals_items=None, use=None): _issue = {self._name: {'issue': None, 'issueorigin': [{'state': state.id, 'action': self._function}]}} @@ -380,45 +383,12 @@ def check_complete(self, state, check_item, check_status, check_mindelta, check_ if check_item is None and _issue[self._name].get('issue') is None: _issue = {self._name: {'issue': ['Item not defined in rules section'], 'issueorigin': [{'state': state.id, 'action': self._function}]}} - # missing status in action: Try to find it. - if check_status is None: - status = StateEngineTools.find_attribute(self._sh, state, "se_status_" + self._name, 0, use) - if status is not None: - check_status, _issue = self._abitem.return_item(status) - _issue = {self._name: {'issue': _issue, - 'issueorigin': [{'state': state.id, 'action': self._function}]}} - - if check_mindelta.is_empty(): - mindelta = StateEngineTools.find_attribute(self._sh, state, "se_mindelta_" + self._name, 0, use) - if mindelta is not None: - check_mindelta.set(mindelta) if check_minagedelta.is_empty(): minagedelta = StateEngineTools.find_attribute(self._sh, state, "se_minagedelta_" + self._name, 0, use) if minagedelta is not None: check_minagedelta.set(minagedelta) - - if check_status is not None: - check_value.set_cast(check_status.cast) - check_mindelta.set_cast(check_status.cast) - self._scheduler_name = "{}-SeItemDelayTimer".format(check_status.property.path) - if self._abitem.id == check_status.property.path: - self._caller += '_self' - elif check_status is None: - if isinstance(check_item, str): - pass - elif check_item is not None: - check_value.set_cast(check_item.cast) - check_mindelta.set_cast(check_item.cast) - self._scheduler_name = "{}-SeItemDelayTimer".format(check_item.property.path) - if self._abitem.id == check_item.property.path: - self._caller += '_self' - if _issue[self._name].get('issue') not in [[], [None], None]: - self._log_develop("Issue with {} action {}", action_type, _issue) - else: - _issue = {self._name: {'issue': None, - 'issueorigin': [{'state': state.id, 'action': self._function}]}} - + check_item, check_status, check_mindelta, check_value, _issue = self._get_status(check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue) return check_item, check_status, check_mindelta, check_minagedelta, check_value, _issue # Execute action (considering delay, etc) @@ -568,7 +538,9 @@ def _update_repeat_webif(value: bool): self._log_increase_indent() if _validitem: delay = 0 if self.__delay.is_empty() else self.__delay.get() - plan_next = self._se_plugin.scheduler_return_next(self._scheduler_name) + plan_next = None + if self._scheduler_name: + plan_next = self._se_plugin.scheduler_return_next(self._scheduler_name) if plan_next is not None and plan_next > self.shtime.now() or delay == -1: self._log_info("Action '{0}: Removing previous delay timer '{1}'.", self._name, self._scheduler_name) self._se_plugin.scheduler_remove(self._scheduler_name) @@ -684,6 +656,51 @@ def __init__(self, abitem, name: str): self._value = StateEngineValue.SeValue(self._abitem, "value") self._mindelta = StateEngineValue.SeValue(self._abitem, "mindelta", False, "num") + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + # missing status in action: Try to find it. + if check_status is None: + status = StateEngineTools.find_attribute(self._sh, state, "se_status_" + self._name, 0, use) + if status is not None: + check_status, _issue = self._abitem.return_item(status) + _issue = {self._name: {'issue': _issue, + 'issueorigin': [{'state': state.id, 'action': self._function}]}} + + if check_mindelta.is_empty(): + mindelta = StateEngineTools.find_attribute(self._sh, state, "se_mindelta_" + self._name, 0, use) + if mindelta is not None: + check_mindelta.set(mindelta) + + if check_status is not None: + self._log_develop("Casting value {} to status {}", check_value, check_status) + check_value.set_cast(check_status.cast) + check_mindelta.set_cast(check_status.cast) + self._scheduler_name = "{}-SeItemDelayTimer".format(check_status.property.path) + if self._abitem.id == check_status.property.path: + self._caller += '_self' + elif check_status is None: + if isinstance(check_item, str): + pass + elif check_item is not None: + self._log_develop("Casting value {} to item {}", check_value, check_item) + check_value.set_cast(check_item.cast) + check_mindelta.set_cast(check_item.cast) + self._scheduler_name = "{}-SeItemDelayTimer".format(check_item.property.path) + if self._abitem.id == check_item.property.path: + self._caller += '_self' + if _issue[self._name].get('issue') not in [[], [None], None]: + self._log_develop("Issue with {} action {}", action_type, _issue) + else: + _issue = {self._name: {'issue': None, + 'issueorigin': [{'state': state.id, 'action': self._function}]}} + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + if check_value: + check_value.set_cast(check_item.cast) + if check_mindelta: + check_mindelta.set_cast(check_item.cast) + return check_item, check_mindelta + def _getitem_fromeval(self): if self._item is None: return @@ -1329,12 +1346,17 @@ def __repr__(self): return "SeAction Add {}".format(self._name) def write_to_logger(self): - SeActionBase.write_to_logger(self) SeActionSetItem.write_to_logger(self) + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): - value = value if isinstance(value, list) else [value] self._log_debug("{0}: Add '{1}' to '{2}'.{3}", actionname, value, item.property.path, repeat_text) + value = value if isinstance(value, list) else [value] value = item.property.value + value self.update_webif_actionstatus(state, self._name, 'True') # noinspection PyCallingNonCallable @@ -1354,9 +1376,14 @@ def __repr__(self): return "SeAction RemoveFirst {}".format(self._name) def write_to_logger(self): - SeActionBase.write_to_logger(self) SeActionSetItem.write_to_logger(self) + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): currentvalue = item.property.value value = value if isinstance(value, list) else [value] @@ -1385,9 +1412,14 @@ def __repr__(self): return "SeAction RemoveLast {}".format(self._name) def write_to_logger(self): - SeActionBase.write_to_logger(self) SeActionSetItem.write_to_logger(self) + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): currentvalue = item.property.value value = value if isinstance(value, list) else [value] @@ -1418,17 +1450,22 @@ def __repr__(self): return "SeAction RemoveAll {}".format(self._name) def write_to_logger(self): - SeActionBase.write_to_logger(self) SeActionSetItem.write_to_logger(self) + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): currentvalue = item.property.value value = value if isinstance(value, list) else [value] for v in value: try: currentvalue = [i for i in currentvalue if i != v] - self._log_debug("{0}: Remove all '{1}' from '{2}'.{3}", - actionname, v, item.property.path, repeat_text) + self._log_debug("{0}: Remove all '{1}' from '{2}', value is now {3}.{4}", + actionname, v, item.property.path, currentvalue, repeat_text) except Exception as ex: self._log_warning("{0}: Remove all '{1}' from '{2}' failed: {3}", actionname, value, item.property.path, ex) From f78a5b17ddac733dd9597cedc44b47ae0e46e771 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 26 Sep 2024 22:10:36 +0200 Subject: [PATCH 14/17] stateengine plugin: fix issue tracking for action definitions, minor updates --- stateengine/StateEngineItem.py | 2 +- stateengine/StateEngineState.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index 407dba038..b171a8823 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -1325,7 +1325,7 @@ def list_issues(v): self.__logger.info("") for entry, value in to_check: if 'issue' not in value: - text = "Definition {} not used in any action or condition.".format(entry) + text = "Definition {} not used in any action or condition.".format(value.get('attribute', entry)) self.__logger.info("{}", text) self.__logger.decrease_indent() diff --git a/stateengine/StateEngineState.py b/stateengine/StateEngineState.py index 7d0147125..b2ab0221a 100755 --- a/stateengine/StateEngineState.py +++ b/stateengine/StateEngineState.py @@ -242,7 +242,7 @@ def write_to_log(self): 'actions_stay': {}, 'actions_leave': {}, 'actions_pass': {}, - 'leave': False, 'enter': False, 'stay': False, + 'leave': False, 'enter': False, 'stay': False, 'pass': False, 'is_copy_for': None, 'releasedby': None}) self._log_decrease_indent() self._log_info("Finished Web Interface Update") @@ -573,6 +573,7 @@ def update_unused(used_attributes, attrib_type, attrib_name): used_attributes[nested_entry].update({attrib_type: attrib_name}) used_attributes[nested_entry].update(nested_dict) self.__used_attributes.update(used_attributes) + self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) def update_action_status(actn_type, action_status): def filter_issues(input_dict): @@ -613,8 +614,10 @@ def filter_issues(input_dict): # Add 'used in' and update with existing data flattened_dict[inner_key]['used in'] = key flattened_dict[inner_key].update(nested_dict) + self.__used_attributes = deepcopy(flattened_dict) self.__action_status = filter_issues(self.__action_status) + self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) if isinstance(state, SeState): item_state = state.state_item From 2f18d59a56293e414180fa7f91f08da49a70cca8 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Fri, 27 Sep 2024 20:54:52 +0200 Subject: [PATCH 15/17] stateengine: re-fix time handling --- stateengine/StateEngineValue.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index 344db0d01..1bfc943d3 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -64,20 +64,28 @@ def __init__(self, abitem, name, allow_value_list=False, value_type=None): self.__valid_valuetypes = ["value", "regex", "eval", "var", "item", "template", "struct"] if value_type == "str": self.__cast_func = StateEngineTools.cast_str + self.__cast = "str" elif value_type == "regex": self.__cast_func = self.cast_regex + self.__cast = "regex" elif value_type == "num": self.__cast_func = StateEngineTools.cast_num + self.__cast = "num" elif value_type == "item": self.__cast_func = self.cast_item + self.__cast = "item" elif value_type == "bool": self.__cast_func = StateEngineTools.cast_bool + self.__cast = "bool" elif value_type == "time": self.__cast_func = StateEngineTools.cast_time + self.__cast = "time" elif value_type == "list": self.__cast_func = StateEngineTools.cast_list + self.__cast = "list" else: self.__cast_func = None + self.__cast = None def __repr__(self): return "{}".format(self.get()) @@ -156,6 +164,7 @@ def __resetvalue(self): self.__struct = None self.__varname = None self.__template = None + self.__cast = None self._additional_sources = [] self.__listorder = [] self.__type_listorder = [] @@ -170,6 +179,8 @@ def set(self, value, name="", reset=True, copyvalue=True, returnvalue=False): value = copy.copy(value) if reset: self.__resetvalue() + if name: + self.__cast = name returnvalues = [] if isinstance(value, list): source = [] @@ -273,7 +284,7 @@ def set(self, value, name="", reset=True, copyvalue=True, returnvalue=False): except Exception: cond1 = False cond2 = False - if name == "time" and cond1 and cond2: + if (name == "time" or self.__cast == "time") and cond1 and cond2: field_value = value source = "value" elif field_value == "": @@ -324,7 +335,7 @@ def set(self, value, name="", reset=True, copyvalue=True, returnvalue=False): except Exception: cond1 = False cond2 = False - if name == "time" and cond1 and cond2: + if (name == "time" or self.__cast == "time") and cond1 and cond2: field_value[i] = '{}:{}'.format(source[i], field_value[i]) s = "value" elif field_value[i] == "": From b8c4ee469a1356d4ba40fefe1ce7998851effae4 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sat, 28 Sep 2024 16:28:53 +0200 Subject: [PATCH 16/17] stateengine plugin: make it possible to set value of list item by se_set_..: [foo, bar] --- stateengine/StateEngineValue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index 1bfc943d3..3c0678e6a 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -726,7 +726,7 @@ def __do_cast(self, value, item_id=None): try: _newvalue = element if element == 'novalue' else self.__cast_func(element) except Exception as ex: - _newvalue = None + _newvalue = element _issue = "Problem casting element '{0}': {1}.".format(element, ex) self._log_warning(_issue) valuelist.append(_newvalue) @@ -777,7 +777,7 @@ def __do_cast(self, value, item_id=None): self._log_debug("Original casting of {} to {} failed. New cast is now: {}.", value, self.__cast_func, type(value)) return value, _issue - return None, _issue + return value, _issue return value, _issue # Determine value by using a struct From cb431df32e7334227b70b834d7350850f1d68a5b Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sat, 28 Sep 2024 16:30:33 +0200 Subject: [PATCH 17/17] stateengine plugin: minor internal code update --- stateengine/StateEngineActions.py | 6 ++---- stateengine/StateEngineTools.py | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stateengine/StateEngineActions.py b/stateengine/StateEngineActions.py index 05f2b340a..75a64de47 100755 --- a/stateengine/StateEngineActions.py +++ b/stateengine/StateEngineActions.py @@ -82,8 +82,7 @@ def update(self, attribute, value): return _count, _issue elif isinstance(value, str): value = ":".join(map(str.strip, value.split(":"))) - if value[:1] == '[' and value[-1:] == ']': - value = StateEngineTools.convert_str_to_list(value, False) + value = StateEngineTools.convert_str_to_list(value, False) if name in self.__actions: self.__actions[name].update_action_details(self.__state, self.__action_type) if func == "se_delay": @@ -388,8 +387,7 @@ def remove_action(e): entry = list("{!s}:{!s}".format(k, v) for (k, v) in entry.items())[0] key, val = StateEngineTools.partition_strip(entry, ":") val = ":".join(map(str.strip, val.split(":"))) - if val[:1] == '[' and val[-1:] == ']': - val = StateEngineTools.convert_str_to_list(val, False) + val = StateEngineTools.convert_str_to_list(val, False) if key == "function": parameter[key] = StateEngineTools.cast_str(val) elif key == "force": diff --git a/stateengine/StateEngineTools.py b/stateengine/StateEngineTools.py index 8be8bb0b2..b423c9c6a 100755 --- a/stateengine/StateEngineTools.py +++ b/stateengine/StateEngineTools.py @@ -318,9 +318,13 @@ def partition_strip(value, splitchar): # returns: list or original value def convert_str_to_list(value, force=True): if isinstance(value, str): + orig_value = value value = value.strip() if value.startswith('[') and value.endswith(']'): value = value[1:-1].strip() + else: + return orig_value + if isinstance(value, str) and "," in value: try: elements = re.findall(r"'([^']*)'|\"([^\"]*)\"|([^,]+)", value)