diff --git a/custom_components/pyscript/eval.py b/custom_components/pyscript/eval.py index 8d67727..a4b57e1 100644 --- a/custom_components/pyscript/eval.py +++ b/custom_components/pyscript/eval.py @@ -1167,9 +1167,15 @@ async def recurse_assign(self, lhs, val): return if not isinstance(var_name, str): raise NotImplementedError(f"unknown lhs type {lhs} (got {var_name}) in assign") - if var_name.find(".") >= 0: + dot_count = var_name.count(".") + if dot_count == 1: State.set(var_name, val) return + if dot_count == 2: + State.set_attr(var_name, val) + return + if dot_count > 0: + raise NotImplementedError("variable names may contain at most 2 dots") if self.curr_func and var_name in self.curr_func.global_names: self.global_sym_table[var_name] = val return diff --git a/custom_components/pyscript/state.py b/custom_components/pyscript/state.py index be378bc..b1c7220 100644 --- a/custom_components/pyscript/state.py +++ b/custom_components/pyscript/state.py @@ -98,12 +98,19 @@ def notify_var_get(cls, var_names, new_vars): return notify_vars @classmethod - def set(cls, var_name, value, new_attributes=None, **kwargs): + def set(cls, var_name, value=None, new_attributes=None, **kwargs): """Set a state variable and optional attributes in hass.""" if var_name.count(".") != 1: raise NameError(f"invalid name {var_name} (should be 'domain.entity')") - if new_attributes is None: + + state_value = None + if value is None or new_attributes is None: state_value = cls.hass.states.get(var_name) + + if value is None and state_value: + value = state_value.state + + if new_attributes is None: if state_value: new_attributes = state_value.attributes else: @@ -115,6 +122,18 @@ def set(cls, var_name, value, new_attributes=None, **kwargs): cls.notify_var_last[var_name] = str(value) cls.hass.states.async_set(var_name, value, new_attributes) + @classmethod + def set_attr(cls, var_attr_name, value): + """Set a state variable's attribute in hass.""" + parts = var_attr_name.split(".") + if len(parts) != 3: + raise NameError(f"invalid name {var_attr_name} (should be 'domain.entity.attr')") + + state_var_name = f"{parts[0]}.{parts[1]}" + attr_name = parts[2] + + cls.set(state_var_name, **{attr_name: value}) + @classmethod def exist(cls, var_name): """Check if a state variable value or attribute exists in hass.""" @@ -186,6 +205,7 @@ def register_functions(cls): functions = { "state.get": cls.get, "state.set": cls.set, + "state.set_attr": cls.set_attr, "state.names": cls.names, "state.get_attr": cls.get_attr, "pyscript.config": cls.pyscript_config, diff --git a/docs/reference.rst b/docs/reference.rst index ccee75c..f78ea84 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -92,14 +92,15 @@ you need their numeric value. State variables have attributes that can be accessed by adding the name of the attribute, as in ``DOMAIN.name.attr``. The attribute names and their meaning depend on the component that sets them, -so you will need to look at the State tab in the Developer Tools to see the available attributes. +so you will need to look at the State tab in the Developer Tools to see the available attributes. It is also +possible to set these attributes using ``DOMAIN.name.attr = value``. Starting in version 0.21, when you set a state variable, the existing attributes are not affected (they were previously removed). In cases where you need to compute the name of the state variable dynamically, or you need to set or -get the state attributes, you can use the built-in functions ``state.get()``, ``state.get_attr()`` -and ``state.set()``; see below. +get the state attributes, you can use the built-in functions ``state.get()``, ``state.get_attr()``, +``state.set_attr()`` and ``state.set()``; see below. The function ``state.names(domain=None)`` returns a list of all state variable names (ie, ``entity_id``\ s) of a domain. If ``domain`` is not specified, it returns all HASS state @@ -536,13 +537,16 @@ which you can’t do if you are directly assigning to the variable: ``state.names(domain=None)`` Returns a list of all state variable names (ie, ``entity_id``\ s) of a domain. If ``domain`` is not specified, it returns all HASS state variable (``entity_id``) names. -``state.set(name, value, new_attributes=None, **kwargs)`` +``state.set(name, value=None, new_attributes=None, **kwargs)`` Sets the state variable to the given value, with the optional attributes. The optional 3rd argument, ``new_attributes``, should be a ``dict`` and it will overwrite all the existing attributes if specified. If instead attributes are specified using keyword arguments, then other attributes will not be affected. If no optional arguments are provided, just the state variable - value is set and the attributes are not changed. To clear the attributes, set + value is set and the attributes are not changed. If no value is provided, just the state arguments + are set. To clear the attributes, set ``new_attributes={}``. +``state.set_attr(name, value)`` + Sets the state variable attribute to the given value. The name should look like ``DOMAIN.entity.attr`` Note that in HASS, all state variable values are coerced into strings. For example, if a state variable has a numeric value, you might want to convert it to a numeric type (eg, using ``int()`` or