Skip to content

Commit

Permalink
stateengine plugin: introduce delta attribute for single actions, int…
Browse files Browse the repository at this point in the history
…roduce minagedelta to run actions in a specific interval only
  • Loading branch information
onkelandy committed Sep 18, 2024
1 parent 928ae60 commit 27abe95
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 18 deletions.
114 changes: 97 additions & 17 deletions stateengine/StateEngineAction.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from . import StateEngineEval
from . import StateEngineValue
from . import StateEngineDefaults
from . import StateEngineCurrent
import datetime
from lib.shtime import Shtime
import re
Expand Down Expand Up @@ -77,6 +78,8 @@ def __init__(self, abitem, name: str):
self.previousstate_conditionset = StateEngineValue.SeValue(self._abitem, "previousstate_conditionset", True, "str")
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")
self._agedelta = 0
self._scheduler_name = None
self._function = None
self.__template = None
Expand Down Expand Up @@ -111,6 +114,20 @@ def update_instanteval(self, value):
'issueorigin': [{'state': 'unknown', 'action': self._function}]}}
return _issue

def update_mindelta(self, value):
self._log_warning("Mindelta is only relevant for set (force) actions - ignoring")
_issue = {self._name: {'issue': 'Mindelta not relevant for this action type', 'attribute': 'mindelta',
'issueorigin': [{'state': 'unknown', 'action': self._function}]}}
return _issue

def update_minagedelta(self, value):
if self._minagedelta is None:
self._minagedelta = StateEngineValue.SeValue(self._abitem, "minagedelta", False, "num")
_, _, _issue, _ = self._minagedelta.set(value)
_issue = {self._name: {'issue': _issue, 'attribute': 'minagedelta',
'issueorigin': [{'state': 'unknown', 'action': self._function}]}}
return _issue

def update_repeat(self, value):
if self.__repeat is None:
self.__repeat = StateEngineValue.SeValue(self._abitem, "repeat", False, "bool")
Expand Down Expand Up @@ -300,7 +317,37 @@ def check_getitem_fromeval(self, check_item, check_value=None, check_mindelta=No
'issueorigin': [{'state': 'unknown', 'action': self._function}]}}
return check_item, check_value, check_mindelta, _issue

def check_complete(self, state, check_item, check_status, check_mindelta, check_value, action_type, evals_items=None, use=None):
def eval_minagedelta(self, actioninfo, state):
lastrun = self._abitem.last_run.get(self._name)
if not lastrun:
return False
if not self._minagedelta.is_empty():
minagedelta = self._minagedelta.get()
try:
minagedelta = float(minagedelta)
except Exception:
self._log_warning("{0}: minagedelta {1} seems to be no number.", self._name, minagedelta)
minagedelta = 0.0
self._agedelta = float((datetime.datetime.now() - lastrun).total_seconds())
self._info_dict.update({'agedelta': self._agedelta, 'minagedelta': str(minagedelta)})
_key = [self._state.id, self._action_type, self._name]
self._abitem.update_webif(_key, self._info_dict, True)
if self._agedelta < minagedelta:
text = "{0}: {1} because age delta '{2:.2f}' is lower than minagedelta '{3:.2f}'."
self._log_debug(text, self.name, actioninfo, self._agedelta, minagedelta)
self.update_webif_actionstatus(state, self._name, 'False', None,
f"(age delta '{self._agedelta:.2f}' &#60; '{minagedelta:.2f})")
return True
else:
text = "{0}: Proceeding as age delta '{1:.2f}' is higher than minagedelta '{2:.2f}'."
self.update_webif_actionstatus(state, self._name, 'True', None,
f"(age delta '{self._agedelta:.2f}' &#62; '{minagedelta:.2f})")
self._log_debug(text, self.name, self._agedelta, minagedelta)
return False
else:
return False

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}]}}
try:
Expand Down Expand Up @@ -350,6 +397,11 @@ def check_complete(self, state, check_item, check_status, check_mindelta, check_
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)
Expand All @@ -371,7 +423,7 @@ def check_complete(self, state, check_item, check_status, check_mindelta, check_
_issue = {self._name: {'issue': None,
'issueorigin': [{'state': state.id, 'action': self._function}]}}

return check_item, check_status, check_mindelta, check_value, _issue
return check_item, check_status, check_mindelta, check_minagedelta, check_value, _issue

# Execute action (considering delay, etc)
# is_repeat: Indicate if this is a repeated action without changing the state
Expand Down Expand Up @@ -526,7 +578,7 @@ def _update_repeat_webif(value: bool):
except Exception:
pass

actionname = "Action '{0}'".format(self._name) if delay == 0 else "Delayed Action ({0} seconds) '{1}'".format(
actionname = "Action '{0}'".format(self._name) if delay == 0 else "Delayed Action ({0} seconds) '{1}'.".format(
delay, self._scheduler_name)
_delay_info = 0
if delay is None:
Expand Down Expand Up @@ -670,8 +722,9 @@ def write_to_logger(self):
self._log_debug("item is not defined! Check log file.")
item = None
mindelta = self._mindelta.write_to_logger() or 0
minagedelta = self._minagedelta.write_to_logger() or 0
value = self._value.write_to_logger()
self._info_dict.update({'item': item, 'mindelta': str(mindelta), 'delta': str(self._delta), 'value': str(value)})
self._info_dict.update({'item': item, 'mindelta': str(mindelta), 'minagedelta': str(minagedelta), 'agedelta': str(self._agedelta), 'delta': str(self._delta), 'value': str(value)})
_key = [self._state.id, self._action_type, self._name]
self._abitem.update_webif(_key, self._info_dict, True)
return value
Expand All @@ -684,8 +737,8 @@ def complete(self, state, action_type, evals_items=None, use=None):
self._abitem.set_variable('current.state_name', state.name)
self._action_type = action_type
self._state = state
self._item, self._status, self._mindelta, self._value, _issue = self.check_complete(
state, self._item, self._status, self._mindelta, self._value, "set/force", evals_items, use)
self._item, self._status, self._mindelta, self._minagedelta, self._value, _issue = self.check_complete(
state, self._item, self._status, self._mindelta, self._minagedelta, self._value, "set/force", evals_items, use)
self._action_status = _issue

self._abitem.set_variable('current.action_name', '')
Expand Down Expand Up @@ -729,7 +782,9 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s
if returnvalue:
self._log_decrease_indent()
return value

minagedelta = self.eval_minagedelta(f"Not setting {self._item.property.path} to {value}", state)
if minagedelta:
return
if not self._mindelta.is_empty():
mindelta = float(self._mindelta.get())
try:
Expand Down Expand Up @@ -766,13 +821,22 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s
def _force_set(self, actionname, item, value, source):
pass

def update_mindelta(self, value):
if self._mindelta is None:
self._mindelta = StateEngineValue.SeValue(self._abitem, "mindelta", False, "num")
_, _, _issue, _ = self._mindelta.set(value)
_issue = {self._name: {'issue': _issue, 'attribute': 'mindelta',
'issueorigin': [{'state': 'unknown', 'action': self._function}]}}
return _issue

def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition, previous_condition, previousstate_condition, next_condition):
self._log_decrease_indent()
self._log_debug("{0}: Set '{1}' to '{2}'{3}", actionname, item.property.path, value, repeat_text)
self._log_debug("{0}: Set '{1}' to '{2}'.{3}", actionname, item.property.path, value, repeat_text)
pat = r"(?:[^,(]*)\'(.*?)\'"
self.update_webif_actionstatus(state, re.findall(pat, actionname)[0], 'True')
# noinspection PyCallingNonCallable
item(value, caller=self._caller, source=source)
self._abitem.last_run = {self._name: datetime.datetime.now()}
self._item = self._eval_item


Expand Down Expand Up @@ -816,20 +880,20 @@ def _force_set(self, actionname, item, value, source):
current_value = item()
if current_value == value:
if self._item._type == 'bool':
self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, item.property.path, not value)
self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, not value)
item(not value, caller=self._caller, source=source)
elif self._item._type == 'str':
if value != '':
self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, item.property.path, '')
self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, '')
item('', caller=self._caller, source=source)
else:
self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, item.property.path, '-')
self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, '-')
item('-', caller=self._caller, source=source)
elif self._item._type == 'num':
self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, item.property.path, current_value+0.1)
self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, current_value+0.1)
item(current_value+0.1, caller=self._caller, source=source)
else:
self._log_warning("{0}: Force not implemented for item type '{1}'", actionname, item._type)
self._log_warning("{0}: Force not implemented for item type '{1}'.", actionname, item._type)
else:
self._log_debug("{0}: New value differs from old value, no force required.", actionname)

Expand Down Expand Up @@ -884,12 +948,16 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s
self._abitem.set_variable('current.action_name', namevar)
if returnvalue:
return value
minagedelta = self.eval_minagedelta(f"Not setting values by attribute {self.__byattr}", state)
if minagedelta:
return
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, next_condition)
for item in self._sh.find_items(self.__byattr):
self._log_info("\t{0} = {1}", item.property.path, item.conf[self.__byattr])
item(item.conf[self.__byattr], caller=self._caller, source=source)
self._abitem.last_run = {self._name: datetime.datetime.now()}


# Class representing a single "se_trigger" action
Expand Down Expand Up @@ -957,13 +1025,17 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s

if returnvalue:
return value
minagedelta = self.eval_minagedelta(f"Not triggering logic {self.__logic}", state)
if minagedelta:
return
self._info_dict.update({'value': str(value)})
_key = [self._state.id, self._action_type, self._name]
self._abitem.update_webif(_key, self._info_dict, True)
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)
self._abitem.last_run = {self._name: datetime.datetime.now()}


# Class representing a single "se_run" action
Expand Down Expand Up @@ -1022,15 +1094,18 @@ def write_to_logger(self):
def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None):
def log_conditions():
if current_condition:
self._log_debug("Running eval {0} based on conditionset {1}", self.__eval, current_condition)
self._log_debug("Running eval {0} based on conditionset {1}.", self.__eval, current_condition)
if previous_condition:
self._log_debug("Running eval {0} based on previous conditionset {1}", self.__eval, previous_condition)
self._log_debug("Running eval {0} based on previous conditionset {1}.", self.__eval, previous_condition)
if previousstate_condition:
self._log_debug("Running eval {0} based on previous state's conditionset {1}", self.__eval,
self._log_debug("Running eval {0} based on previous state's conditionset {1}.", self.__eval,
previousstate_condition)
if next_condition:
self._log_debug("Running eval {0} based on next conditionset {1}", self.__eval, next_condition)
self._log_debug("Running eval {0} based on next conditionset {1}.", self.__eval, next_condition)

minagedelta = self.eval_minagedelta(f"Not running eval {self.__eval}", state)
if minagedelta:
return
self._abitem.set_variable('current.action_name', namevar)
self._log_increase_indent()
eval_result = ''
Expand Down Expand Up @@ -1068,6 +1143,7 @@ def log_conditions():
self.update_webif_actionstatus(state, self._name, 'False', 'Problem calling: {}'.format(ex))
text = "{0}: Problem calling '{0}': {1}."
self._log_error(text, actionname, StateEngineTools.get_eval_name(self.__eval), ex)
self._abitem.last_run = {self._name: datetime.datetime.now()}
self._info_dict.update({'value': str(eval_result)})
_key = [self._state.id, self._action_type, self._name]
self._abitem.update_webif(_key, self._info_dict, True)
Expand Down Expand Up @@ -1137,6 +1213,9 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s
self._abitem.set_variable('current.action_name', namevar)
if returnvalue:
return None
minagedelta = self.eval_minagedelta(f"Not executing special action {self.__special}", state)
if minagedelta:
return
try:
_log_value = self.__value.property.path
except Exception:
Expand Down Expand Up @@ -1165,6 +1244,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s
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)
self._abitem.last_run = {self._name: datetime.datetime.now()}

def suspend_get_value(self, value):
_issue = {self._name: {'issue': None, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}}
Expand Down
40 changes: 39 additions & 1 deletion stateengine/StateEngineActions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def __init__(self, abitem):
self.__actions = {}
self.__action_type = None
self.__state = None
self.__unassigned_mindeltas = {}
self.__unassigned_minagedeltas = {}
self.__unassigned_delays = {}
self.__unassigned_repeats = {}
self.__unassigned_instantevals = {}
Expand Down Expand Up @@ -83,6 +85,22 @@ def update(self, attribute, value):
else:
_issue = self.__actions[name].update_delay(value)
return _count, _issue
elif func == "se_mindelta":
# set mindelta
if name not in self.__actions:
# If we do not have the action yet (delay-attribute before action-attribute), ...
self.__unassigned_mindeltas[name] = value
else:
_issue = self.__actions[name].update_mindelta(value)
return _count, _issue
elif func == "se_minagedelta":
# set minagedelta
if name not in self.__actions:
# If we do not have the action yet (delay-attribute before action-attribute), ...
self.__unassigned_minagedeltas[name] = value
else:
_issue = self.__actions[name].update_minagedelta(value)
return _count, _issue
elif func == "se_instanteval":
# set instant calculation
if name not in self.__actions:
Expand Down Expand Up @@ -273,6 +291,18 @@ def __ensure_action_exists(self, func, name):
_issue_list.append(_issue)
del self.__unassigned_instantevals[name]

if name in self.__unassigned_mindeltas:
_issue = action.update_mindelta(self.__unassigned_mindeltas[name])
if _issue:
_issue_list.append(_issue)
del self.__unassigned_mindeltas[name]

if name in self.__unassigned_minagedeltas:
_issue = action.update_minagedelta(self.__unassigned_minagedeltas[name])
if _issue:
_issue_list.append(_issue)
del self.__unassigned_minagedeltas[name]

if name in self.__unassigned_repeats:
_issue = action.update_repeat(self.__unassigned_repeats[name])
if _issue:
Expand Down Expand Up @@ -330,7 +360,7 @@ def remove_action(e):
self._log_warning("Ignoring action {0} because: {1}", name, e)

parameter = {'function': None, 'force': None, 'repeat': None, 'delay': 0, 'order': None, 'nextconditionset': None, 'conditionset': None,
'previousconditionset': None, 'previousstate_conditionset': None, 'mode': None, 'instanteval': None}
'previousconditionset': None, 'previousstate_conditionset': None, 'mode': None, 'instanteval': None, 'mindelta': None, 'minagedelta': None}
_issue = None
_issue_list = []
# value_list needs to be string or list
Expand Down Expand Up @@ -474,6 +504,14 @@ def remove_action(e):
_issue = self.__actions[name].update_repeat(parameter['repeat'])
if _issue:
_issue_list.append(_issue)
if parameter['mindelta'] is not None:
_issue = self.__actions[name].update_mindelta(parameter['mindelta'])
if _issue:
_issue_list.append(_issue)
if parameter['minagedelta'] is not None:
_issue = self.__actions[name].update_minagedelta(parameter['minagedelta'])
if _issue:
_issue_list.append(_issue)
if parameter['delay'] != 0:
_issue = self.__actions[name].update_delay(parameter['delay'])
if _issue:
Expand Down

0 comments on commit 27abe95

Please sign in to comment.