Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stateengine Plugin: fixes and improvements #962

Merged
merged 17 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8d1ff2f
stateengine plugin: fix previously introduced issue logging problem
onkelandy Sep 22, 2024
86a1d4b
stateengine plugin: fix pass_state webif handling on first run
onkelandy Sep 22, 2024
eeaf0d1
stateengine plugin: minor improvements
onkelandy Sep 23, 2024
61ffec0
stateengine plugin: re-fix next conditionset when using released_by s…
onkelandy Sep 23, 2024
4e3cb99
stateengine plugin: improve logging (e.g. for handling released_by), …
onkelandy Sep 23, 2024
00a6737
stateengine plugin: fix and improve logging
onkelandy Sep 25, 2024
98c1461
stateengine plugin: introduce regex casting, used for conditionset co…
onkelandy Sep 25, 2024
1d52c52
stateengine plugin: fix/improve conversion of lists in items
onkelandy Sep 25, 2024
c49f322
stateengine plugin: correctly parse values in item, you can now also …
onkelandy Sep 25, 2024
f4814b7
stateengine plugin: fix string to list conversion
onkelandy Sep 26, 2024
d27ef45
stateengine plugin: reset issues list when setting a value (internal …
onkelandy Sep 26, 2024
8d3e1bb
stateengine plugin: fix logging of action count
onkelandy Sep 26, 2024
b1f977f
stateengine plugin: fix list actions
onkelandy Sep 26, 2024
f78a5b1
stateengine plugin: fix issue tracking for action definitions, minor …
onkelandy Sep 26, 2024
2f18d59
stateengine: re-fix time handling
onkelandy Sep 27, 2024
b8c4ee4
stateengine plugin: make it possible to set value of list item by se_…
onkelandy Sep 28, 2024
cb431df
stateengine plugin: minor internal code update
onkelandy Sep 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 100 additions & 60 deletions stateengine/StateEngineAction.py

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions stateengine/StateEngineActions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -567,7 +565,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
Expand Down
43 changes: 23 additions & 20 deletions stateengine/StateEngineItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -704,8 +703,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:
Expand Down Expand Up @@ -761,11 +760,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)
Expand Down Expand Up @@ -941,6 +935,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
Expand Down Expand Up @@ -1042,7 +1037,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

Expand Down Expand Up @@ -1204,7 +1199,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:
Expand All @@ -1214,11 +1209,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):
Expand Down Expand Up @@ -1330,14 +1325,15 @@ 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()

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 = []
Expand Down Expand Up @@ -1403,6 +1399,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, "_"))

Expand Down Expand Up @@ -1900,6 +1897,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:
Expand All @@ -1925,7 +1923,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
Expand Down Expand Up @@ -2112,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):
Expand Down
18 changes: 11 additions & 7 deletions stateengine/StateEngineLogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
12 changes: 8 additions & 4 deletions stateengine/StateEngineState.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -695,8 +698,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)
Expand All @@ -716,7 +720,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
Expand Down
28 changes: 22 additions & 6 deletions stateengine/StateEngineTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,30 @@ 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):
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)
flattened_elements = [element[0] if element[0] else element[1] for element in elements]
formatted_str = "[" + ", ".join(
["'" + element.strip(" '\"") + "'" for element in flattened_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}"') # If element contains single quote, wrap in double quotes
elif '"' in element:
formatted_elements.append(f"'{element}'") # If element contains double quote, wrap in single quotes
else:
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:
raise ValueError("Problem converting string to list: {}".format(ex))
Expand Down
Loading
Loading