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

fix splitting of time ; value ; compat #722

Merged
merged 8 commits into from
Mar 1, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,6 @@ Die Koordinaten für einen Standort kann man z.B. auf http://www.mapcoordinates.
# - mysql:pymysql
# module_paths = /usr/local/python/lib # list of path-entries is possible

# Version 1.3: control type casting when assiging values to items
# assign_compatibility = latest # latest or compat_1.2 (compat_1.2 is default for shNG v1.3)

Es bietet sich an, die default-Datei nach smarthome.yaml zu kopieren und die Daten oben auf den eigenen Standort
anzupassen. Alternativ kann diese Anpassung später über das Admin Interface durchgeführt werden.

Expand Down
13 changes: 4 additions & 9 deletions doc/user/source/referenz/items/funktionen.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ genutzt werden können.
+--------------------------------+--------------------------------------------------------------------------------+
| **Funktion** | **Beschreibung** |
+================================+================================================================================+
| autotimer(time, value, compat) | Setzt einen Timer bei jedem Werte-Wechsel der Items. Angegeben wird die Zeit |
| autotimer(time, value) | Setzt einen Timer bei jedem Werte-Wechsel der Items. Angegeben wird die Zeit |
| | (**time**) die vergehen soll, bis das Item auf den Wert (**value**) gesetzt |
| | wird. Die Zeitangabe erfolgt in Sekunden. Eine Angabe der Dauer in Minuten |
| | ist wie in '10m' möglich. Die Bedeutung und Wirkungsweise von **compat** bitte |
| | auf der Seite |
| | :doc:`autotimer <./standard_attribute/autotimer>` |
| | nachlesen. |
| | ist wie in '10m' möglich. |
+--------------------------------+--------------------------------------------------------------------------------+
| fade(end, step, delta, caller, | Blendet das Item mit der definierten Schrittweite (int oder float) und |
| stop_fade, continue_fade, | timedelta (int oder float in Sekunden) auf einen angegebenen Wert auf oder |
Expand All @@ -40,10 +37,8 @@ genutzt werden können.
| return_parent() | Liefert den Item-Pfad des übergeordneten Items zurück. |
| | Aufruf: sh.item.return_parent() |
+--------------------------------+--------------------------------------------------------------------------------+
| timer(time, value, compat) | Funktioniert wir **autotimer()**, ausser dass die Aktion nur einmal ausgeführt |
| | wird. Die Bedeutung und Wirkungsweise von **compat** bitte auf der Seite |
| | :doc:`autotimer <./standard_attribute/autotimer>` |
| | nachlesen. |
| timer(time, value) | Funktioniert wir **autotimer()**, ausser dass die Aktion nur einmal ausgeführt |
| | wird. |
+--------------------------------+--------------------------------------------------------------------------------+


Expand Down
7 changes: 7 additions & 0 deletions doc/user/source/referenz/items/standard_attribute/crontab.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,20 @@ Im Admin Interface können die einzelnen Parametersätze durch ``|`` getrennt we
Durch Anhängen eines ``= value`` wird der entsprechende Wert ``value`` mitgesendet.
Das Beispiel setzt den Wert des Items täglich um Mitternacht auf ``20``:

**Ab SmartHomeNG v1.11** werden die Konfigurationsmöglichkeiten erweitert:

Für den **Wert** kann nun ein **eval** Ausdruck angegeben werden, der zur Laufzeit entsprechend neu evaluiert wird.
Dabei können auch Item Properties genutzt werden.

.. code-block:: yaml

crontab:
- '0 0 * * = 20'
- sunrise

crontab: '0 0 * * = sh.pfad.zum.item1() * 4 + sh.pfad.zum.item2.property.last_value'


Möchte man einen Wert im Minutentakt aktualisieren, ist es notwendig den Ausdruck ``* * * *`` unter Anführungszeichen zu setzen.

.. code-block:: yaml
Expand Down
24 changes: 15 additions & 9 deletions lib/item/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,21 +138,28 @@ def split_duration_value_string(value, ATTRIB_COMPAT_DEFAULT):
components are:
- time
- value
- compat
- possibly compat (obsolete, kept for backward compatibility)

:param value: raw attribute string containing duration, value (and compatibility)
:param value: raw attribute string containing duration, value
:return: three strings, representing time, value and compatibility attribute
"""
compat = ''

if value.find(ATTRIBUTE_SEPARATOR) >= 0:
time, __, attrvalue = value.partition(ATTRIBUTE_SEPARATOR)
attrvalue, __, compat = attrvalue.partition(ATTRIBUTE_SEPARATOR)
elif value.find('=') >= 0:
elif value.find('=') >= 0 and value[value.find('='):value.find('=') + 2] != '==':
time, __, attrvalue = value.partition('=')
attrvalue, __, compat = attrvalue.partition('=')
if attrvalue.find('=') >= 0 and (attrvalue.rfind('=') != attrvalue.rfind('==')) and (attrvalue.endswith('compat') or attrvalue.endswith('compat_1.2') or attrvalue.endswith('latest')):
attrvalue, __, compat = attrvalue.rpartition('=')
else:
time = value
attrvalue = None
compat = ''

# try to fix time if compat is (still) given:
time = time.strip()
if time.endswith('compat') or time.endswith('compat_1.2') or time.endswith('latest'):
time = time.removesuffix('compat').removesuffix('compat_1.2').removesuffix('latest').strip()[:-1]

time = time.strip()
if attrvalue is not None:
Expand All @@ -164,6 +171,7 @@ def split_duration_value_string(value, ATTRIB_COMPAT_DEFAULT):
# remove quotes, if present
if value != '' and ((value[0] == "'" and value[-1] == "'") or (value[0] == '"' and value[-1] == '"')):
value = value[1:-1]

return (time, attrvalue, compat)


Expand All @@ -181,11 +189,11 @@ def join_duration_value_string(time, value, compat=''):
"""
result = str(time)
if value != '' or compat != '':
result = result + ' ='
result = result + ' ' + ATTRIBUTE_SEPARATOR
if value != '':
result = result + ' ' + value
if compat != '':
result = result + ' = ' + compat
result = result + ' ' + ATTRIBUTE_SEPARATOR + ' ' + compat
return result


Expand All @@ -204,7 +212,6 @@ def json_serialize(obj):
return obj.isoformat()
raise TypeError("Type not serializable")


def json_obj_hook(json_dict):
"""
helper method for json deserialization
Expand Down Expand Up @@ -233,7 +240,6 @@ def cache_read(filename, tz, cformat=CACHE_FORMAT):

return (dt, value)


def cache_write(filename, value, cformat=CACHE_FORMAT):
try:
if cformat == CACHE_PICKLE:
Expand Down
13 changes: 9 additions & 4 deletions lib/item/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -1800,7 +1800,7 @@ def get_attr_time(self, attr: str) -> int | None:

var = getattr(self, f'_{attr}_time')
if var is None:
logger.debug(f'get_attr_time({attr}): item {self._path} has no member _{attr}_time. This is weird...')
logger.debug(f'get_attr_time({attr}): item {self._path} has no member _{attr}_time.')
return

if isinstance(var, int) or var is None:
Expand Down Expand Up @@ -1833,17 +1833,22 @@ def get_attr_time(self, attr: str) -> int | None:
except Exception as e:
logger.warning(f'error on evaluation {attr} time "{var}" for item {self._path}: {e}')

def get_attr_value(self, attr: str):
def get_attr_value(self, attr: str, value=None):
"""
return attribute value, possibly recalculated at call time

:param attr: attribute to calculate value for, e.g. cycle or autotimer
:type attr: str
"""
if attr not in ('cycle', 'autotimer'):
if attr not in ('cycle', 'autotimer', 'cron'):
return

var = getattr(self, f'_{attr}_value')
# only for cron, the value is stored in the scheduler instead of in the item
# so we just use the given value to proceed (eval)
if value is not None:
var = value
else:
var = getattr(self, f'_{attr}_value')
if var is None:
return

Expand Down
9 changes: 7 additions & 2 deletions lib/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None,
_value = None
else:
_value = _value.strip()
if obj.__class__.__name__ == 'Item':
_value = obj.get_stringwithabsolutepathes(_value, 'sh.', '(')
if desc.lower().startswith('init'):
details = desc
offset = 5 # default init offset
Expand Down Expand Up @@ -560,7 +562,7 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None,
self._scheduler[name] = {'prio': prio, 'obj': obj, 'source': source, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True}
if next is None:
self._next_time(name, offset)
except Exception as e:
except Exception:
raise
# logger.error(f"Exception: {e} while trying to add a new entry to scheduler")
finally:
Expand Down Expand Up @@ -781,12 +783,15 @@ def _task(self, name, obj, by, source, dest, value):
if isinstance(value, dict) and value.get('caller') == 'Autotimer':
src = 'autotimer'
value = obj.get_attr_value(src)
elif isinstance(source, dict) and source.get('source', '') == 'cron':
src = 'cron'

if value is None:
# re-set current item value. needs enforce_updates to work properly
value = obj()
else:
# get current (static or evaluated) value from item itself
value = obj.get_attr_value(src)
value = obj.get_attr_value(src, value)

# logger.debug(f'item {obj}: src = {src}, value = {value}')
if src == 'cycle':
Expand Down