Skip to content

Commit

Permalink
Merge pull request smarthomeNG#833 from onkelandy/stateengine
Browse files Browse the repository at this point in the history
Stateengine Plugin: new se_stateorder functionality, list items, more updates
  • Loading branch information
onkelandy authored Oct 20, 2023
2 parents b9874a0 + 6c5d09e commit 50ba058
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 258 deletions.
10 changes: 8 additions & 2 deletions stateengine/StateEngineEval.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def get_relative_item(self, subitem_id):
# See description of StateEngineItem.SeItem.return_item for details
def get_relative_itemvalue(self, subitem_id):
self._eval_lock.acquire()
returnvalue = []
self._log_debug("Executing method 'get_relative_itemvalue({0})'", subitem_id)
try:
if self._abitem._initstate and subitem_id == '..state_name':
Expand All @@ -163,12 +164,13 @@ def get_relative_itemvalue(self, subitem_id):
else:
item, issue = self._abitem.return_item(subitem_id)
returnvalue = item.property.value
self._log_debug("Return item value '{0}' for item {1}", returnvalue, item.property.path)
returnvalue = StateEngineTools.convert_str_to_list(returnvalue)
self._log_debug("Return item value '{0}' for item {1}", returnvalue, subitem_id)
except Exception as ex:
returnvalue = None
self._log_warning("Problem evaluating value of '{0}': {1}", subitem_id, ex)
finally:
self._eval_lock.release()
returnvalue = returnvalue[0] if len(returnvalue) == 1 else None if len(returnvalue) == 0 else returnvalue
return returnvalue

# Return the property of an item related to the StateEngine Object Item
Expand All @@ -192,6 +194,10 @@ def get_relative_itemproperty(self, subitem_id, prop):
self._abitem.return_item(self._abitem._initstate.id)[0].property.path, returnvalue)
else:
returnvalue = getattr(item.property, prop)
if prop == "value":
returnvalue = StateEngineTools.convert_str_to_list(returnvalue)
returnvalue = returnvalue[0] if len(returnvalue) == 1 else None if len(
returnvalue) == 0 else returnvalue
self._log_debug("Return item property {0} from {1}: {2}", prop, item.property.path, returnvalue)
except Exception as ex:
returnvalue = None
Expand Down
440 changes: 261 additions & 179 deletions stateengine/StateEngineItem.py

Large diffs are not rendered by default.

62 changes: 52 additions & 10 deletions stateengine/StateEngineState.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ def releasedby(self):
def releasedby(self, value):
self.__releasedby.set(value, "", True, None, False)

@property
def order(self):
return self.__order.get()

@order.setter
def order(self, value):
self.__order.set(value, "", True, None, False)

@property
def can_release(self):
return self.__can_release.get()
Expand Down Expand Up @@ -155,6 +163,7 @@ def __init__(self, abitem, item_state):
self.__actions_enter = StateEngineActions.SeActions(self._abitem)
self.__actions_stay = StateEngineActions.SeActions(self._abitem)
self.__actions_leave = StateEngineActions.SeActions(self._abitem)
self.__order = StateEngineValue.SeValue(self._abitem, "State Order", False, "num")
self._log_increase_indent()
try:
self.__fill(self.__item, 0)
Expand Down Expand Up @@ -190,12 +199,11 @@ def write_to_log(self):
self._abitem.set_variable("current.state_name", self.name)
self._abitem.set_variable("current.state_id", self.id)
self.__text.write_to_logger()
self.__order.write_to_logger()
self.__is_copy_for.write_to_logger()
self.__releasedby.write_to_logger()
self.__can_release.write_to_logger()
if self.__use_done:
_log_se_use = self.__use_done[0] if len(self.__use_done) == 1 else self.__use_done
self._log_info("State configuration extended by se_use: {}", _log_se_use)
self.__use.write_to_logger()

self._log_info("Updating Web Interface...")
self._log_increase_indent()
Expand Down Expand Up @@ -247,6 +255,24 @@ def write_to_log(self):
self._abitem.set_variable("current.state_id", "")
self._log_decrease_indent()

def update_order(self, value=None):
if isinstance(value, list):
if len(value) > 1:
_default_value = self.__order.get()
self._log_warning("se_stateorder for item {} can not be defined as a list"
" ({}). Using default value {}.", self.id, value, _default_value)
value = _default_value
elif len(value) == 1:
value = value[0]
if value is None and "se_stateorder" in self.__item.conf:
_, _, _, _issue = self.__order.set_from_attr(self.__item, "se_stateorder")
elif value is not None:
_, _, _issue = self.__order.set(value, "", True, None, False)
else:
_issue = [None]

return _issue

# run actions when entering the state
# item_allow_repeat: Is repeating actions generally allowed for the item?
def run_enter(self, allow_item_repeat: bool):
Expand Down Expand Up @@ -351,6 +377,18 @@ def update_name(self, item_state, recursion_depth=0):
self.__name = self.text
return self.__name

def __fill_list(self, item_states, recursion_depth, se_use=None):
for i, element in enumerate(item_states):
if element == self.state_item:
self._log_info("Use element {} is same as current state - Ignoring.", element)
elif element is not None and element not in self.__use_done:
try:
_use = se_use[i]
except Exception:
_use = element
self.__fill(element, recursion_depth, _use)
self.__use_done.append(element)

# Read configuration from item and populate data in class
# item_state: item to read from
# recursion_depth: current recursion_depth (recursion is canceled after five levels)
Expand All @@ -370,7 +408,6 @@ def update_action_status(action_status, actiontype):
if action_status is None:
return
action_status = StateEngineTools.flatten_list(action_status)
#self._log_debug("Action status: {}", action_status)
if isinstance(action_status, list):
for e in action_status:
update_action_status(e, actiontype)
Expand Down Expand Up @@ -436,7 +473,7 @@ def update_action_status(action_status, actiontype):
{item_state.property.path: {'issue': _issue, 'attribute': 'se_use'}})
self._log_warning("{} - ignoring.", _issue)
else:
_use = [_use] if not isinstance(_use, list) else StateEngineTools.flatten_list(_use)
_use = [_use] if not isinstance(_use, list) else _use
_returntype = [_returntype] if not isinstance(_returntype, list) else _returntype
cleaned_use_list = []
for i, element in enumerate(_use):
Expand Down Expand Up @@ -476,15 +513,20 @@ def update_action_status(action_status, actiontype):
_path = _configvalue[i]
self._log_info("se_use {} defined by item/eval. Even if current result is not valid, "
"entry will be re-evaluated on next state evaluation.", _path)
if _path not in cleaned_use_list:
if _path is not None and _path not in cleaned_use_list:
cleaned_use_list.append(_path)
self.__use_done.append(_path)
if _path is None:
pass
elif _fill and element not in self.__use_done:
elif element == self.state_item:
self._log_info("Use element {} is same as current state - Ignoring.", _name)
elif _fill and element is not None and element not in self.__use_done:
self._log_develop("Adding element {} to state fill function.", _name)
self.__use_done.append(element)
self.__fill(element, recursion_depth + 1, _name)

if isinstance(_name, list):
self.__fill_list(element, recursion_depth + 1, _name)
else:
self.__use_done.append(element)
self.__fill(element, recursion_depth + 1, _name)
self.__use.set(cleaned_use_list)

# Get action sets and condition sets
Expand Down
42 changes: 42 additions & 0 deletions stateengine/StateEngineTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,48 @@ def partition_strip(value, splitchar):
return part1.strip(), part2.strip()


# return list representation of string
# value: list as string
# returns: list or original value
def convert_str_to_list(value):
if isinstance(value, str) and ("," in value and value.startswith("[")):
value = value.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]
formatted_str = "[" + ", ".join(
["'" + element.strip(" '\"") + "'" for element in flattened_elements]) + "]"
return literal_eval(formatted_str)
except Exception as ex:
raise ValueError("Problem converting string to list: {}".format(ex))
elif isinstance(value, list):
return value
else:
return [value]

# return dict representation of string
# value: OrderedDict as string
# returns: OrderedDict or original value
def convert_str_to_dict(value):
if isinstance(value, str) and value.startswith("["):
value = re.split('(, (?![^(]*\)))', value.strip(']['))
value = [s for s in value if s != ', ']
result = []
for s in value:
m = re.match(r'^OrderedDict\((.+)\)$', s)
if m:
result.append(dict(literal_eval(m.group(1))))
else:
result.append(literal_eval(s))
value = result
else:
return value
try:
return literal_eval(value)
except Exception as ex:
raise ValueError("Problem converting string to OrderedDict: {}".format(ex))

# return string representation of eval function
# eval_func: eval function
# returns: string representation
Expand Down
Loading

0 comments on commit 50ba058

Please sign in to comment.