diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index e2b7f41dc..9b2ae9a9e 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -82,6 +82,8 @@ def __init__(self, abitem, name: str): self._function = None self.__template = None self._action_status = {} + self._retrigger_issue = None + self._suspend_issue = None self.__queue = abitem.queue def update_delay(self, value): @@ -260,7 +262,6 @@ 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': 'unknown', 'action': self._function}]}} - # self._action_status = _issue if check_value: check_value.set_cast(check_item.cast) if check_mindelta: @@ -273,14 +274,12 @@ def check_getitem_fromeval(self, check_item, check_value=None, check_mindelta=No self._log_develop("Got no item from eval on {} with initial item {}", self._function, self.__item) except Exception as ex: _issue = {self._name: {'issue': ex, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} - # self._action_status = _issue # raise Exception("Problem evaluating item '{}' from eval: {}".format(check_item, ex)) self._log_error("Problem evaluating item '{}' from eval: {}", check_item, ex) check_item = None if item is None: _issue = {self._name: {'issue': ['Item {} from eval not existing'.format(check_item)], 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} - # self._action_status = _issue # raise Exception("Problem evaluating item '{}' from eval. It does not exist.".format(check_item)) self._log_error("Problem evaluating item '{}' from eval. It does not exist", check_item) check_item = None @@ -359,7 +358,6 @@ def check_complete(self, item_state, check_item, check_status, check_mindelta, c if self._abitem.id == check_item.property.path: self._caller += '_self' if _issue[self._name].get('issue') not in [[], [None], None]: - # self._action_status = _issue self._log_develop("Issue with {} action {}", action_type, _issue) else: _issue = {self._name: {'issue': None, @@ -440,7 +438,6 @@ def _update_repeat_webif(value: bool): _validitem = True except Exception as ex: _validitem = False - #self._log_develop("action issues {}", self._action_status) self._log_decrease_indent() if not self._can_execute(state): self._log_decrease_indent() @@ -561,6 +558,7 @@ def _waitforexecute(self, state, actionname: str, namevar: str = "", repeat_text else: value = None self._abitem.add_scheduler_entry(self._scheduler_name) + self.update_webif_actionstatus(state, self._name, 'Scheduled') self._se_plugin.scheduler_add(self._scheduler_name, self._delayed_execute, value={'actionname': actionname, 'namevar': self._name, 'repeat_text': repeat_text, 'value': value, @@ -791,6 +789,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if returnvalue: return value self._log_info("{0}: Setting values by attribute '{1}'.{2}", actionname, self.__byattr, repeat_text) + self.update_webif_actionstatus(state, self._name, 'True') source = self.set_source(current_condition, previous_condition, previousstate_condition) for item in self.itemsApi.find_items(self.__byattr): self._log_info("\t{0} = {1}", item.property.path, item.conf[self.__byattr]) @@ -855,6 +854,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if returnvalue: return value + self.update_webif_actionstatus(state, self._name, 'True') self._log_info("{0}: Triggering logic '{1}' using value '{2}'.{3}", actionname, self.__logic, value, repeat_text) add_logics = 'logics.{}'.format(self.__logic) if not self.__logic.startswith('logics.') else self.__logic self._sh.trigger(add_logics, by=self._caller, source=self._name, value=value) @@ -937,10 +937,12 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if previousstate_condition: self._log_debug("Running eval {0} based on previous state's conditionset {1}", self.__eval, previousstate_condition) eval(self.__eval) + self.update_webif_actionstatus(state, self._name, 'True') self._log_decrease_indent() except Exception as ex: self._log_decrease_indent() text = "{0}: Problem evaluating '{1}': {2}." + self.update_webif_actionstatus(state, self._name, 'False', 'Problem evaluating: {}'.format(ex)) self._log_error(text.format(actionname, StateEngineTools.get_eval_name(self.__eval), ex)) else: try: @@ -954,9 +956,11 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if previousstate_condition: self._log_debug("Running eval {0} based on previous state's conditionset {1}", self.__eval, previousstate_condition) self.__eval() + self.update_webif_actionstatus(state, self._name, 'True') self._log_decrease_indent() except Exception as ex: self._log_decrease_indent() + self.update_webif_actionstatus(state, self._name, 'False', 'Problem calling: {}'.format(ex)) text = "{0}: Problem calling '{0}': {1}." self._log_error(text.format(actionname, StateEngineTools.get_eval_name(self.__eval), ex)) @@ -1057,6 +1061,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if value is None: self._log_debug("{0}: Value is None", actionname) + self.update_webif_actionstatus(state, self._name, 'False', 'Value is None') return if returnvalue: @@ -1068,6 +1073,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s # noinspection PyCallingNonCallable delta = float(abs(self.__item() - value)) if delta < mindelta: + self.update_webif_actionstatus(state, self._name, 'False') text = "{0}: Not setting '{1}' to '{2}' because delta '{3:.2}' is lower than mindelta '{4}'" self._log_debug(text, actionname, self.__item.property.path, value, delta, mindelta) return @@ -1097,6 +1103,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s self._log_debug("{0}: New value differs from old value, no force required.", actionname) self._log_decrease_indent() self._log_debug("{0}: Set '{1}' to '{2}'.{3}", actionname, self.__item.property.path, value, repeat_text) + self.update_webif_actionstatus(state, self._name, 'True') # noinspection PyCallingNonCallable self.__item(value, caller=self._caller, source=source) @@ -1190,61 +1197,84 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s self._log_increase_indent() if self.__special == "suspend": self.suspend_execute(state, current_condition, previous_condition, previousstate_condition) + if self._suspend_issue in ["", [], None, [None]]: + self.update_webif_actionstatus(state, self._name, 'True') + else: + self.update_webif_actionstatus(state, self._name, 'False', self._suspend_issue) self._log_decrease_indent() elif self.__special == "retrigger": + if self._retrigger_issue in ["", [], None, [None]]: + self.update_webif_actionstatus(state, self._name, 'True') + else: + self.update_webif_actionstatus(state, self._name, 'False', self._retrigger_issue) # noinspection PyCallingNonCallable self._abitem.update_state(self.__value, self._caller) #self.__value(True, caller=self._caller) self._log_decrease_indent() else: self._log_decrease_indent() + self.update_webif_actionstatus(state, self._name, 'False', 'Unknown special value {}'.format(self.__special)) raise ValueError("{0}: Unknown special value '{1}'!".format(actionname, self.__special)) self._log_debug("Special action {0}: done", self.__special) def suspend_get_value(self, value): _issue = {self._name: {'issue': None, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} if value is None: - _issue = {self._name: {'issue': 'Special action suspend requires arguments', 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} + text = 'Special action suspend requires arguments' + _issue = {self._name: {'issue': text, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} self._action_status = _issue - raise ValueError("Action {0}: Special action 'suspend' requires arguments!".format(self._name)) + self._suspend_issue = text + raise ValueError("Action {0}: {1}".format(self._name, text)) suspend, manual = StateEngineTools.partition_strip(value, ",") if suspend is None or manual is None: - _issue = {self._name: {'issue': 'Special action suspend requires two arguments', 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} + text = "Special action 'suspend' requires two arguments (separated by a comma)!" + _issue = {self._name: {'issue': text, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} self._action_status = _issue - raise ValueError("Action {0}: Special action 'suspend' requires two arguments (separated by a comma)!".format(self._name)) + self._suspend_issue = text + raise ValueError("Action {0}: {1}".format(self._name, text)) suspend_item, _issue = self._abitem.return_item(suspend) _issue = {self._name: {'issue': _issue, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} if suspend_item is None: - _issue = {self._name: {'issue': 'Suspend item not found', 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} + text = "Suspend item '{}' not found!".format(suspend) + _issue = {self._name: {'issue': text, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} self._action_status = _issue - raise ValueError("Action {0}: Suspend item '{1}' not found!".format(self._name, suspend)) + self._suspend_issue = text + raise ValueError("Action {0}: {1}".format(self._name, text)) manual_item, _issue = self._abitem.return_item(manual) + self._suspend_issue = _issue _issue = {self._name: {'issue': _issue, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} if manual_item is None: - _issue = {self._name: {'issue': 'Manual item {} not found'.format(manual), 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} + text = 'Manual item {} not found'.format(manual) + _issue = {self._name: {'issue': text, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} self._action_status = _issue - raise ValueError("Action {0}: Manual item '{1}' not found!".format(self._name, manual)) + self._suspend_issue = text + raise ValueError("Action {0}: {1}".format(self._name, text)) self._action_status = _issue return [suspend_item, manual_item.property.path] def retrigger_get_value(self, value): if value is None: - _issue = {self._name: {'issue': 'Special action retrigger requires item', 'issueorigin': [{'state': 'retrigger', 'action': 'retrigger'}]}} + text = 'Special action retrigger requires item' + _issue = {self._name: {'issue': text, 'issueorigin': [{'state': 'retrigger', 'action': 'retrigger'}]}} self._action_status = _issue - raise ValueError("Action {0}: Special action 'retrigger' requires item".format(self._name)) + self._retrigger_issue = text + raise ValueError("Action {0}: {1}".format(self._name, text)) se_item, __ = StateEngineTools.partition_strip(value, ",") se_item, _issue = self._abitem.return_item(se_item) + self._retrigger_issue = _issue _issue = {self._name: {'issue': _issue, 'issueorigin': [{'state': 'retrigger', 'action': 'retrigger'}]}} self._action_status = _issue if se_item is None: - _issue = {self._name: {'issue': 'Retrigger item {} not found'.format(se_item), 'issueorigin': [{'state': 'retrigger', 'action': 'retrigger'}]}} + text = 'Retrigger item {} not found'.format(se_item) + _issue = {self._name: {'issue': text, 'issueorigin': [{'state': 'retrigger', 'action': 'retrigger'}]}} self._action_status = _issue - raise ValueError("Action {0}: Retrigger item '{1}' not found!".format(self._name, se_item)) + self._retrigger_issue = text + raise ValueError("Action {0}: {1}".format(self._name, text)) return se_item def suspend_execute(self, state=None, current_condition=None, previous_condition=None, previousstate_condition=None): diff --git a/stateengine/StateEngineDefaults.py b/stateengine/StateEngineDefaults.py index 5486edeca..6b4a24500 100755 --- a/stateengine/StateEngineDefaults.py +++ b/stateengine/StateEngineDefaults.py @@ -36,8 +36,11 @@ logger = None +plugin_version = 0 + def write_to_log(logger): + logger.info("Plugin {0} Version {1}".format(plugin_identification, plugin_version)) logger.info("StateEngine default suntracking offset = {0}".format(suntracking_offset)) logger.info("StateEngine default suntracking lamella open value = {0}".format(lamella_open_value)) logger.info("StateEngine default startup delay = {0}".format(startup_delay)) diff --git a/stateengine/StateEngineFunctions.py b/stateengine/StateEngineFunctions.py index dc66feee0..a8df0f13e 100755 --- a/stateengine/StateEngineFunctions.py +++ b/stateengine/StateEngineFunctions.py @@ -24,7 +24,9 @@ import re from . import StateEngineLogger from . import StateEngineTools +from . import StateEngineDefaults from lib.item import Items +from ast import literal_eval class SeFunctions: @@ -64,6 +66,39 @@ def __get_lock(self, lock_id): # If the original caller/source should be considered, the method returns the inverted value of the item. # Otherwise, the method returns the current value of the item, so that no change will be made def manual_item_update_eval(self, item_id, caller=None, source=None): + def check_include_exclude(entry_type): + conf_entry = item.conf["se_manual_{}".format(entry_type)] + if isinstance(conf_entry, str): + if ',' in conf_entry or conf_entry.startswith("["): + try: + new_conf_entry = literal_eval(conf_entry) + if isinstance(new_conf_entry, list): + conf_entry = new_conf_entry + except Exception: + conf_entry = [conf_entry, ] + else: + conf_entry = [conf_entry, ] + elif not isinstance(conf_entry, list): + elog.error("Item '{0}', Attribute 'se_manual_{1}': Value must be a string or a list!", item_id, entry_type) + return retval_no_trigger + elog.info("checking manual {0} values: {1}", entry_type, conf_entry) + elog.increase_indent() + + # If current value is in list -> Return "Trigger" + for e in conf_entry: + e = re.compile(e, re.IGNORECASE) + result = e.match(original) + elog.info("Checking regex result {}", result) + if result is not None: + elog.info("{0}: matching.", e) + elog.decrease_indent() + returnvalue = retval_trigger if entry_type == "include" else retval_no_trigger + elog.info("Writing value {0}", returnvalue) + return returnvalue + elog.info("{0}: not matching", e) + elog.decrease_indent() + return None + item = self.itemsApi.return_item(item_id) if item is None: self.logger.error("manual_item_update_eval: item {0} not found!".format(item_id)) @@ -97,7 +132,7 @@ def manual_item_update_eval(self, item_id, caller=None, source=None): elog.info("get_caller({0}, {1}): original trigger by {2}:{3}", caller, source, original_caller, original_source) original = "{}:{}".format(original_caller, original_source) - entry = re.compile("Stateengine Plugin", re.IGNORECASE) + entry = re.compile(StateEngineDefaults.plugin_identification, re.IGNORECASE) result = entry.match(original) if result is not None: elog.info("Manual item updated by Stateengine Plugin. Ignoring change and writing value {}", @@ -105,81 +140,23 @@ def manual_item_update_eval(self, item_id, caller=None, source=None): return retval_no_trigger if "se_manual_on" in item.conf: - # get list of include entries - include = item.conf["se_manual_on"] - if isinstance(include, str): - include = [include, ] - elif not isinstance(include, list): - elog.error("Item '{0}', Attribute 'se_manual_on': Value must be a string or a list!", item_id) - return retval_no_trigger - elog.info("checking manual on values: {0}", include) - elog.increase_indent() - - # If current value is in list -> Return "Trigger" - for entry in include: - entry = re.compile(entry, re.IGNORECASE) - result = entry.match(original) - elog.info("Checking regex result {}", result) - if result is not None: - elog.info("{0}: matching.", entry) - elog.decrease_indent() - elog.info("Writing value {0}", retval_no_trigger) - return retval_no_trigger - elog.info("{0}: not matching", entry) - elog.decrease_indent() + returnvalue = check_include_exclude("on") + if returnvalue is not None: + return returnvalue if "se_manual_exclude" in item.conf: - # get list of exclude entries - exclude = item.conf["se_manual_exclude"] - - if isinstance(exclude, str): - exclude = [exclude, ] - elif not isinstance(exclude, list): - elog.error("Item '{0}', Attribute 'se_manual_exclude': Value must be a string or a list!", item_id) - return retval_no_trigger - elog.info("checking exclude values: {0}", exclude) - elog.increase_indent() - - # If current value is in list -> Return "NoTrigger" - for entry in exclude: - entry = re.compile(entry, re.IGNORECASE) - result = entry.match(original) - elog.info("Checking regex result {}", result) - if result is not None: - elog.info("{0}: matching.", entry) - elog.decrease_indent() - elog.info("Writing value {0}", retval_no_trigger) - return retval_no_trigger - elog.info("{0}: not matching", entry) - elog.decrease_indent() + returnvalue = check_include_exclude("exclude") + if returnvalue is not None: + return returnvalue if "se_manual_include" in item.conf: - # get list of include entries - include = item.conf["se_manual_include"] - if isinstance(include, str): - include = [include, ] - elif not isinstance(include, list): - elog.error("Item '{0}', Attribute 'se_manual_include': Value must be a string or a list!", item_id) + returnvalue = check_include_exclude("include") + if returnvalue is not None: + return returnvalue + else: + # Current value not in list -> Return "No Trigger + elog.info("No include values matching. Writing value {0}", retval_no_trigger) return retval_no_trigger - elog.info("checking include values: {0}", include) - elog.increase_indent() - - # If current value is in list -> Return "Trigger" - for entry in include: - entry = re.compile(entry, re.IGNORECASE) - result = entry.match(original) - elog.info("Checking regex result {}", result) - if result is not None: - elog.info("{0}: matching.", entry) - elog.decrease_indent() - elog.info("Writing value {0}", retval_no_trigger) - return retval_trigger - elog.info("{0}: not matching", entry) - elog.decrease_indent() - - # Current value not in list -> Return "No Trigger - elog.info("No include values matching. Writing value {0}", retval_no_trigger) - return retval_no_trigger else: # No include-entries -> return "Trigger" elog.info("No include limitation. Writing value {0}", retval_trigger) diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index 669898e55..bedbe383a 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -113,6 +113,12 @@ def laststate(self): _returnvalue = None if self.__laststate_item_id is None else self.__laststate_item_id.property.value return _returnvalue + @property + def laststate_releasedby(self): + _returnvalue = None if self.__laststate_item_id is None \ + else self.__release_info.get(self.__laststate_item_id.property.value) + return _returnvalue + @property def previousstate(self): _returnvalue = None if self.__previousstate_item_id is None else self.__previousstate_item_id.property.value @@ -181,6 +187,7 @@ def __init__(self, smarthome, item, se_plugin): self.__shtime = Shtime.get_instance() self.__se_plugin = se_plugin self.__active_schedulers = [] + self.__release_info = {} self.__default_instant_leaveaction = StateEngineValue.SeValue(self, "Default Instant Leave Action", False, "bool") self.__instant_leaveaction = StateEngineValue.SeValue(self, "Instant Leave Action", False, "num") try: @@ -385,17 +392,24 @@ def show_issues_summary(self): self.__unused_attributes = filtered_dict self.__logger.info("".ljust(80, "_")) - + issues = 0 if self.__config_issues: + issues += 1 self.__log_issues('config entries') if self.__unused_attributes: + issues += 1 self.__log_issues('attributes') if self.__action_status: + issues += 1 self.__log_issues('actions') if self.__state_issues: + issues += 1 self.__log_issues('states') if self.__struct_issues: + issues += 1 self.__log_issues('structs') + if issues == 0: + self.__logger.info("No configuration issues found. Congratulations ;)") def update_leave_action(self, default_instant_leaveaction): default_instant_leaveaction_value = default_instant_leaveaction.get() @@ -892,6 +906,9 @@ def update_can_release_list(): self.__logger.debug("Inserted copy of state {}", relevant_state.id) _checkedentries.append(entry) self.__logger.info("State {} can currently get released by: {}", new_state.id, _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.info("".ljust(80, "_")) return all_released_by diff --git a/stateengine/StateEngineState.py b/stateengine/StateEngineState.py index 06d17186f..bfdddb35a 100755 --- a/stateengine/StateEngineState.py +++ b/stateengine/StateEngineState.py @@ -84,7 +84,7 @@ def releasedby(self): @releasedby.setter def releasedby(self, value): - self.__releasedby.set(value) + self.__releasedby.set(value, "", True, None, False) @property def can_release(self): @@ -92,7 +92,7 @@ def can_release(self): @can_release.setter def can_release(self, value): - self.__can_release.set(value) + self.__can_release.set(value, "", True, None, False) @property def has_released(self): @@ -100,7 +100,7 @@ def has_released(self): @has_released.setter def has_released(self, value): - self.__has_released.set(value) + self.__has_released.set(value, "", True, None, False) @property def was_releasedby(self): @@ -108,7 +108,7 @@ def was_releasedby(self): @was_releasedby.setter def was_releasedby(self, value): - self.__was_releasedby.set(value) + self.__was_releasedby.set(value, "", True, None, False) @property def is_copy_for(self): @@ -116,7 +116,13 @@ def is_copy_for(self): @is_copy_for.setter def is_copy_for(self, value): - self.__is_copy_for.set(value) + if value: + webif_id = value.id + else: + webif_id = None + _key_copy = ['{}'.format(self.id), 'is_copy_for'] + self._abitem.update_webif(_key_copy, webif_id) + self.__is_copy_for.set(value, "", True, None, False) # Constructor # abitem: parent SeItem instance @@ -199,7 +205,8 @@ def write_to_log(self): 'actions_enter_or_stay': {}, 'actions_stay': {}, 'actions_leave': {}, - 'leave': False, 'enter': False, 'stay': False}) + 'leave': False, 'enter': False, 'stay': False, + 'is_copy_for': None, 'releasedby': None}) self._log_decrease_indent() self._log_info("Finished Web Interface Update") @@ -313,10 +320,10 @@ def refill(self): def update_releasedby_internal(self, states=None): if states == []: - _returnvalue, _returntype, _issue = self.__releasedby.set([None]) + _returnvalue, _returntype, _issue = self.__releasedby.set([None], "", True, None, False) elif states: self._log_develop("Setting releasedby to {}", states) - _returnvalue, _returntype, _issue = self.__releasedby.set(states) + _returnvalue, _returntype, _issue = self.__releasedby.set(states, "", True, None, False) self._log_develop("returnvalue {}", _returnvalue) else: _returnvalue, _returntype, _, _issue = self.__releasedby.set_from_attr(self.__item, "se_released_by") @@ -324,9 +331,9 @@ def update_releasedby_internal(self, states=None): def update_can_release_internal(self, states): if states == []: - _returnvalue, _returntype, _issue = self.__can_release.set([None]) + _returnvalue, _returntype, _issue = self.__can_release.set([None], "", True, None, False) elif states: - _returnvalue, _returntype, _issue = self.__can_release.set(states) + _returnvalue, _returntype, _issue = self.__can_release.set(states, "", True, None, False) else: _returnvalue, _returntype, _issue = [None], [None], None return _returnvalue, _returntype, _issue @@ -498,9 +505,8 @@ def update_action_status(action_status, actiontype): if child_name == "enter" or child_name.startswith("enter_"): _conditioncount += 1 _unused_attributes, _used_attributes = self.__conditions.update(child_name, child_item, parent_item) - if _conditioncount == 1: - self.__unused_attributes = copy(_unused_attributes) - self.__used_attributes = copy(_used_attributes) + self.__unused_attributes = copy(_unused_attributes) + self.__used_attributes = copy(_used_attributes) for item in self.__unused_attributes.keys(): if 'issue' in self.__unused_attributes[item].keys(): if not self.__unused_attributes[item].get('issueorigin'): @@ -508,6 +514,7 @@ def update_action_status(action_status, actiontype): entry = {'state': self.id, 'conditionset': child_name} if entry not in self.__unused_attributes[item].get('issueorigin'): self.__unused_attributes[item]['issueorigin'].append(entry) + self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) except ValueError as ex: raise ValueError("Condition {0} error: {1}".format(child_name, ex)) diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index 329125021..1ca824350 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -155,8 +155,9 @@ 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, item=None): - value = copy.deepcopy(value) + def set(self, value, name="", reset=True, item=None, copyvalue=True): + if copyvalue is True: + value = copy.copy(value) if reset: self.__resetvalue() if isinstance(value, list): @@ -372,6 +373,7 @@ def set(self, value, name="", reset=True, item=None): self.__issues = StateEngineTools.flatten_list(self.__issues) self.__listorder = StateEngineTools.flatten_list(self.__listorder) self.__type_listorder = StateEngineTools.flatten_list(self.__type_listorder) + del value return self.__listorder, self.__type_listorder, self.__issues # Set cast function diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index b1b38b36d..a40a5a300 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -135,7 +135,8 @@ def _check_webif_conditions(action_dict, condition_to_meet: str, conditionset: s else " ({} not met)".format(condition_info) if not condition_met\ else " (no repeat)" if _repeat is False and originaltype == 'actions_stay'\ else " (delay: {})".format(_delay) if _delay > 0\ - else " (wrong delay!)" if _delay < 0\ + else " (cancel delay!)" if _delay == -1 \ + else " (wrong delay!)" if _delay < -1 \ else " (delta {} < {})".format(_delta, _mindelta) if cond_delta and cond1 and cond2\ else "" action1 = action_dict.get('function') @@ -157,13 +158,17 @@ def _check_webif_conditions(action_dict, condition_to_meet: str, conditionset: s cond_enter = originaltype == 'actions_enter' and self.__states[state].get('enter') is True cond_stay = originaltype == 'actions_stay' and self.__states[state].get('stay') is True active = True if (cond_enter or cond_stay) and cond1 else False - success_info = '' \ + success_info = '' \ if _issue is not None and active \ - else '' \ + else '' \ if (_success == 'False' or not condition_met) and active \ - else '' \ + else '' \ + if _success == 'Scheduled' and active \ + else '' \ + if _success == 'True' and active and _delay > 0 \ + else '' \ if _success == 'True' and active \ - else '' + else '' if not action2 == 'None': actionlabel += '{} {} {} {}'.format(fontcolor, action1, action2, action3, additionaltext) actionlabel += '{}'.format(success_info) @@ -342,7 +347,7 @@ def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, c if self.__nodes.get('{}_{}_state_actions_enter_edge'.format(state, conditionset)) is None: self.__nodes['{}_{}_state_{}_edge'.format(state, conditionset, action_type)] = \ pydotplus.Edge(self.__nodes['{}_{}'.format(state, conditionset)], self.__nodes['{}_{}_state_{}'.format( - state, conditionset, action_type)], style='bold', taillabel=" True", tooltip='first enter') + state, conditionset, action_type)], style='bold', taillabel=" True", tooltip='first enter') self.__graph.add_edge(self.__nodes['{}_{}_state_{}_edge'.format(state, conditionset, action_type)]) else: self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_{}_state_actions_enter'.format(state, conditionset)], @@ -405,10 +410,12 @@ def drawgraph(self, filename): new_y -= 1 * self.__scalefactor position = '{},{}!'.format(0, new_y) #self._log_debug('state: {} {}',state, position) + self.__nodes[state] = pydotplus.Node(state, pos=position, pin=True, notranslate=True, style="filled", fillcolor=color, shape="ellipse", label='<
' - '
{}
{}
>'.format(state, self.__states[state]['name'])) + '{}>'.format( + state, self.__states[state]['name'])) position = '{},{}!'.format(0.5, new_y) self.__nodes['{}_right'.format(state)] = pydotplus.Node('{}_right'.format(state), pos=position, shape="square", width="0", label="") @@ -522,11 +529,16 @@ def drawgraph(self, filename): self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_{}'.format(state, conditionset)], self.__nodes['{}_{}_right'.format(state, conditionset)], style='bold', taillabel=" True", tooltip='action on enter')) - + if self.__states[state].get('is_copy_for'): + xlabel = "can currently release {}\n\r".format(self.__states[state].get('is_copy_for')) + elif self.__states[state].get('releasedby'): + xlabel = "can currently get released by {}\n\r".format(self.__states[state].get('releasedby')) + else: + xlabel = "" if j == 0: self.__graph.add_edge(pydotplus.Edge(self.__nodes[state], self.__nodes['{}_right'.format(state)], - style='bold', color='black', dir='none', - edgetooltip='check first conditionset')) + style='bold', color='black', dir='none', + xlabel=xlabel, edgetooltip='check first conditionset')) self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_right'.format(state)], self.__nodes['{}_{}'.format(state, conditionset)], style='bold', color='black', tooltip='check first conditionset')) @@ -572,7 +584,7 @@ def drawgraph(self, filename): self.__graph.add_node(self.__nodes['{}_actions_leave'.format(state)]) self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_leave'.format(state)], self.__nodes['{}_actions_leave'.format(state)], style='bold', - taillabel=" True", tooltip='run leave actions')) + taillabel=" True", tooltip='run leave actions')) previous_state = state diff --git a/stateengine/__init__.py b/stateengine/__init__.py index 58ae6f5a3..dd10a4dad 100755 --- a/stateengine/__init__.py +++ b/stateengine/__init__.py @@ -96,6 +96,8 @@ def __init__(self, sh): StateEngineDefaults.suntracking_offset = self.get_parameter_value("lamella_offset") StateEngineDefaults.lamella_open_value = self.get_parameter_value("lamella_open_value") + StateEngineDefaults.plugin_identification = self.get_fullname() + StateEngineDefaults.plugin_version = self.PLUGIN_VERSION StateEngineDefaults.write_to_log(self.logger) StateEngineCurrent.init(self.get_sh()) @@ -236,7 +238,7 @@ def get_graph(self, abitem, graphtype='link'): change_datetime = datetime.fromtimestamp(change_timestamp) formatted_date = change_datetime.strftime('%H:%M:%S, %d. %B') except Exception: - pass + formatted_date = "Unbekannt" return f'
{self.translate("Letzte Aktualisierung:")} {formatted_date}
\ \ diff --git a/stateengine/locale.yaml b/stateengine/locale.yaml index 4c8194174..3e79465ce 100755 --- a/stateengine/locale.yaml +++ b/stateengine/locale.yaml @@ -26,3 +26,4 @@ plugin_translations: 'ist noch nicht initialisiert.': {'de': '=', 'en': 'is not initialized yet.'} 'Die erste Evaluierung ist geplant für:': {'de': '=', 'en': 'The first evaluation is planned for:'} 'Letzte Aktualisierung:': {'de': '=', 'en': 'Last Update:'} + 'Potenziell Released': {'de': '=', 'en': 'Potential Released'} diff --git a/stateengine/plugin.yaml b/stateengine/plugin.yaml index bd0aa0444..e91ad8795 100755 --- a/stateengine/plugin.yaml +++ b/stateengine/plugin.yaml @@ -804,8 +804,8 @@ item_structs: remark: Adapt the se_manual_exclude the way you need it #se_manual_include: KNX:* Force manual mode based on source se_manual_exclude: - - database:* - - init:* + - database:.* + - init:.* retrigger: remark: Item to retrigger the rule set evaluation @@ -1000,8 +1000,8 @@ item_structs: remark: Adapt the se_manual_exclude the way you need it #se_manual_include: KNX:* Force manual mode based on source se_manual_exclude: - - database:* - - init:* + - database:.* + - init:.* retrigger: remark: Item to retrigger the rule set evaluation diff --git a/stateengine/webif/__init__.py b/stateengine/webif/__init__.py index 1c3021bc5..b53855a6e 100755 --- a/stateengine/webif/__init__.py +++ b/stateengine/webif/__init__.py @@ -107,9 +107,18 @@ def get_data_html(self, dataSet=None): # get the new data data = {} for item in self.plugin.get_items(): + laststate = item.laststate_name + laststate = "-" if laststate in ["", None] else laststate conditionset = item.lastconditionset_name - conditionset = "-" if conditionset == "" else conditionset - data.update({item.id: {'laststate': item.laststate_name, 'lastconditionset': conditionset}}) + conditionset = "-" if conditionset in ["", None] else conditionset + ll = item.logger.log_level_as_num + if item.laststate_releasedby in [None, []]: + lsr = "-" + else: + lsr = [entry.split('.')[-1] for entry in item.laststate_releasedby] + data.update({item.id: {'laststate': laststate, + 'lastconditionset': conditionset, 'log_level': ll, + 'laststate_releasedby': lsr}}) try: return json.dumps(data) except Exception as e: diff --git a/stateengine/webif/static/img/visualisations/sign_delay.png b/stateengine/webif/static/img/visualisations/sign_delay.png new file mode 100644 index 000000000..eaa0cda82 Binary files /dev/null and b/stateengine/webif/static/img/visualisations/sign_delay.png differ diff --git a/stateengine/webif/static/img/visualisations/sign_false.png b/stateengine/webif/static/img/visualisations/sign_false.png index 6d6433cc1..8f86cde95 100644 Binary files a/stateengine/webif/static/img/visualisations/sign_false.png and b/stateengine/webif/static/img/visualisations/sign_false.png differ diff --git a/stateengine/webif/static/img/visualisations/sign_scheduled.png b/stateengine/webif/static/img/visualisations/sign_scheduled.png new file mode 100644 index 000000000..a59cc00b7 Binary files /dev/null and b/stateengine/webif/static/img/visualisations/sign_scheduled.png differ diff --git a/stateengine/webif/static/img/visualisations/sign_true.png b/stateengine/webif/static/img/visualisations/sign_true.png index c77f8557e..26ff619ee 100644 Binary files a/stateengine/webif/static/img/visualisations/sign_true.png and b/stateengine/webif/static/img/visualisations/sign_true.png differ diff --git a/stateengine/webif/static/img/visualisations/sign_warn.png b/stateengine/webif/static/img/visualisations/sign_warn.png index e58ab170e..3084bec58 100644 Binary files a/stateengine/webif/static/img/visualisations/sign_warn.png and b/stateengine/webif/static/img/visualisations/sign_warn.png differ diff --git a/stateengine/webif/templates/index.html b/stateengine/webif/templates/index.html index 183d5d6f1..e52c915f4 100755 --- a/stateengine/webif/templates/index.html +++ b/stateengine/webif/templates/index.html @@ -26,12 +26,6 @@ }; } -