├── .gitignore ├── README.md ├── custom_components └── variable │ ├── __init__.py │ ├── manifest.json │ └── services.yaml └── examples ├── counter.yaml ├── history.yaml ├── keypad.yaml └── timer.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hass-variables 2 | 3 | A Home Assistant component to declare and set/update variables (state). 4 | 5 | ## Install 6 | 7 | ### Manualy 8 | 9 | Copy `variable` folder in to your home-assistant `custom_components` folder 10 | 11 | ### Automaticaly with HACS 12 | 13 | In HACS settings, add a custom repository with: 14 | 15 | - URL: `https://github.com/rogro82/hass-variables` 16 | - type: `integration` 17 | 18 | Then the `variable` custom component will be installable through HACS and you will be able to follow the future updates. 19 | 20 | ## Configure 21 | 22 | Add the component `variable` to your configuration and declare the variables you want. 23 | 24 | ### Example configuration 25 | 26 | ```yaml 27 | variable: 28 | countdown_timer: 29 | value: 30 30 | attributes: 31 | friendly_name: 'Countdown' 32 | icon: mdi:alarm 33 | countdown_trigger: 34 | name: Countdown 35 | value: False 36 | light_scene: 37 | value: 'normal' 38 | attributes: 39 | previous: '' 40 | restore: true 41 | ``` 42 | 43 | A variable 'should' have a __value__ and can optionally have a __name__ and __attributes__, which can be used to specify additional values but can also be used to set internal attributes like icon, friendly_name etc. 44 | 45 | In case you want your variable to restore its value and attributes after restarting you can set __restore__ to true. 46 | 47 | ## Set variables from automations 48 | 49 | To update a variables value and/or its attributes you can use the service call `variable.set_variable` 50 | 51 | The following parameters can be used with this service: 52 | 53 | - __variable: string (required)__ 54 | The name of the variable to update 55 | - __value: any (optional)__ 56 | New value to set 57 | - __value_template: template (optional)__ 58 | New value to set from a template 59 | - __attributes: dictionary (optional)__ 60 | Attributes to set or update 61 | - __attributes_template: template (optional)__ 62 | Attributes to set or update from a template ( should return a json object ) 63 | - __replace_attributes: boolean ( optional )__ 64 | Replace or merge current attributes (default false = merge) 65 | 66 | ### Example service calls 67 | 68 | ```yaml 69 | action: 70 | - service: variable.set_variable 71 | data: 72 | variable: test_timer 73 | value: 30 74 | 75 | action: 76 | - service: variable.set_variable 77 | data: 78 | variable: last_motion 79 | value: "livingroom" 80 | attributes_template: > 81 | { 82 | "history_1": "{{ variable.state }}", 83 | "history_2": "{{ variable.attributes.history_1 }}", 84 | "history_3": "{{ variable.attributes.history_2 }}" 85 | } 86 | ``` 87 | 88 | ### Example timer automation 89 | 90 | ```yaml 91 | variable: 92 | test_timer: 93 | value: 0 94 | attributes: 95 | icon: mdi:alarm 96 | 97 | script: 98 | schedule_test_timer: 99 | sequence: 100 | - service: variable.set_variable 101 | data: 102 | variable: test_timer 103 | value: 30 104 | - service: automation.turn_on 105 | data: 106 | entity_id: automation.test_timer_countdown 107 | 108 | automation: 109 | - alias: test_timer_countdown 110 | initial_state: 'off' 111 | trigger: 112 | - platform: time_pattern 113 | seconds: '/1' 114 | action: 115 | - service: variable.set_variable 116 | data: 117 | variable: test_timer 118 | value_template: '{{ [((variable.state | int) - 1), 0] | max }}' 119 | 120 | - alias: test_timer_trigger 121 | trigger: 122 | platform: state 123 | entity_id: variable.test_timer 124 | to: '0' 125 | action: 126 | - service: automation.turn_off 127 | data: 128 | entity_id: automation.test_timer_countdown 129 | ``` 130 | 131 | More examples can be found in the examples folder. 132 | -------------------------------------------------------------------------------- /custom_components/variable/__init__.py: -------------------------------------------------------------------------------- 1 | """variable implementation for Homme Assistant.""" 2 | import asyncio 3 | import logging 4 | import json 5 | 6 | import voluptuous as vol 7 | 8 | from homeassistant.const import CONF_NAME, ATTR_ICON 9 | from homeassistant.helpers import config_validation as cv 10 | from homeassistant.exceptions import TemplateError 11 | from homeassistant.loader import bind_hass 12 | from homeassistant.helpers.entity_component import EntityComponent 13 | from homeassistant.helpers.restore_state import RestoreEntity 14 | 15 | _LOGGER = logging.getLogger(__name__) 16 | 17 | DOMAIN = "variable" 18 | ENTITY_ID_FORMAT = DOMAIN + ".{}" 19 | 20 | CONF_ATTRIBUTES = "attributes" 21 | CONF_VALUE = "value" 22 | CONF_RESTORE = "restore" 23 | 24 | ATTR_VARIABLE = "variable" 25 | ATTR_VALUE = "value" 26 | ATTR_VALUE_TEMPLATE = "value_template" 27 | ATTR_ATTRIBUTES = "attributes" 28 | ATTR_ATTRIBUTES_TEMPLATE = "attributes_template" 29 | ATTR_REPLACE_ATTRIBUTES = "replace_attributes" 30 | 31 | SERVICE_SET_VARIABLE = "set_variable" 32 | SERVICE_SET_VARIABLE_SCHEMA = vol.Schema( 33 | { 34 | vol.Required(ATTR_VARIABLE): cv.string, 35 | vol.Optional(ATTR_VALUE): cv.match_all, 36 | vol.Optional(ATTR_VALUE_TEMPLATE): cv.template, 37 | vol.Optional(ATTR_ATTRIBUTES): dict, 38 | vol.Optional(ATTR_ATTRIBUTES_TEMPLATE): cv.template, 39 | vol.Optional(ATTR_REPLACE_ATTRIBUTES): cv.boolean, 40 | } 41 | ) 42 | 43 | CONFIG_SCHEMA = vol.Schema( 44 | { 45 | DOMAIN: vol.Schema( 46 | { 47 | cv.slug: vol.Any( 48 | { 49 | vol.Optional(CONF_NAME): cv.string, 50 | vol.Optional(CONF_VALUE): cv.match_all, 51 | vol.Optional(CONF_ATTRIBUTES): dict, 52 | vol.Optional(CONF_RESTORE): cv.boolean, 53 | }, 54 | None, 55 | ) 56 | } 57 | ) 58 | }, 59 | extra=vol.ALLOW_EXTRA, 60 | ) 61 | 62 | 63 | @bind_hass 64 | def set_variable( 65 | hass, 66 | variable, 67 | value, 68 | value_template, 69 | attributes, 70 | attributes_template, 71 | replace_attributes, 72 | ): 73 | """Set input_boolean to True.""" 74 | hass.services.call( 75 | DOMAIN, 76 | SERVICE_SET_VARIABLE, 77 | { 78 | ATTR_VARIABLE: variable, 79 | ATTR_VALUE: value, 80 | ATTR_VALUE_TEMPLATE: value_template, 81 | ATTR_ATTRIBUTES: attributes, 82 | ATTR_ATTRIBUTES_TEMPLATE: attributes_template, 83 | ATTR_REPLACE_ATTRIBUTES: replace_attributes, 84 | }, 85 | ) 86 | 87 | 88 | async def async_setup(hass, config): 89 | """Set up variables.""" 90 | component = EntityComponent(_LOGGER, DOMAIN, hass) 91 | 92 | entities = [] 93 | 94 | for variable_id, variable_config in config[DOMAIN].items(): 95 | if not variable_config: 96 | variable_config = {} 97 | 98 | name = variable_config.get(CONF_NAME) 99 | value = variable_config.get(CONF_VALUE) 100 | attributes = variable_config.get(CONF_ATTRIBUTES) 101 | restore = variable_config.get(CONF_RESTORE, False) 102 | 103 | entities.append( 104 | Variable(variable_id, name, value, attributes, restore) 105 | ) 106 | 107 | @asyncio.coroutine 108 | def async_set_variable_service(call): 109 | """Handle calls to the set_variable service.""" 110 | entity_id = ENTITY_ID_FORMAT.format(call.data.get(ATTR_VARIABLE)) 111 | entity = component.get_entity(entity_id) 112 | 113 | if entity: 114 | target_variables = [entity] 115 | tasks = [ 116 | variable.async_set_variable( 117 | call.data.get(ATTR_VALUE), 118 | call.data.get(ATTR_VALUE_TEMPLATE), 119 | call.data.get(ATTR_ATTRIBUTES), 120 | call.data.get(ATTR_ATTRIBUTES_TEMPLATE), 121 | call.data.get(ATTR_REPLACE_ATTRIBUTES, False), 122 | ) 123 | for variable in target_variables 124 | ] 125 | if tasks: 126 | yield from asyncio.wait(tasks, loop=hass.loop) 127 | 128 | else: 129 | _LOGGER.warning("Failed to set unknown variable: %s", entity_id) 130 | 131 | hass.services.async_register( 132 | DOMAIN, 133 | SERVICE_SET_VARIABLE, 134 | async_set_variable_service, 135 | schema=SERVICE_SET_VARIABLE_SCHEMA, 136 | ) 137 | 138 | await component.async_add_entities(entities) 139 | return True 140 | 141 | 142 | class Variable(RestoreEntity): 143 | """Representation of a variable.""" 144 | 145 | def __init__(self, variable_id, name, value, attributes, restore): 146 | """Initialize a variable.""" 147 | self.entity_id = ENTITY_ID_FORMAT.format(variable_id) 148 | self._name = name 149 | self._value = value 150 | self._attributes = attributes 151 | self._restore = restore 152 | 153 | async def async_added_to_hass(self): 154 | """Run when entity about to be added.""" 155 | await super().async_added_to_hass() 156 | if self._restore is True: 157 | # If variable state have been saved. 158 | state = await self.async_get_last_state() 159 | if state: 160 | # restore state 161 | self._value = state.state 162 | # restore value 163 | self._attributes = state.attributes 164 | 165 | @property 166 | def should_poll(self): 167 | """If entity should be polled.""" 168 | return False 169 | 170 | @property 171 | def name(self): 172 | """Return the name of the variable.""" 173 | return self._name 174 | 175 | @property 176 | def icon(self): 177 | """Return the icon to be used for this entity.""" 178 | if self._attributes is not None: 179 | return self._attributes.get(ATTR_ICON) 180 | return None 181 | 182 | @property 183 | def state(self): 184 | """Return the state of the component.""" 185 | return self._value 186 | 187 | @property 188 | def state_attributes(self): 189 | """Return the state attributes.""" 190 | return self._attributes 191 | 192 | @asyncio.coroutine 193 | def async_set_variable( 194 | self, 195 | value, 196 | value_template, 197 | attributes, 198 | attributes_template, 199 | replace_attributes, 200 | ): 201 | """Update variable.""" 202 | current_state = self.hass.states.get(self.entity_id) 203 | updated_attributes = None 204 | updated_value = None 205 | 206 | if not replace_attributes and self._attributes is not None: 207 | updated_attributes = dict(self._attributes) 208 | 209 | if attributes is not None: 210 | if updated_attributes is not None: 211 | updated_attributes.update(attributes) 212 | else: 213 | updated_attributes = attributes 214 | 215 | elif attributes_template is not None: 216 | attributes_template.hass = self.hass 217 | 218 | try: 219 | attributes = json.loads( 220 | attributes_template.async_render( 221 | {"variable": current_state} 222 | ) 223 | ) 224 | 225 | if isinstance(attributes, dict): 226 | if updated_attributes is not None: 227 | updated_attributes.update(attributes) 228 | else: 229 | updated_attributes = attributes 230 | 231 | except TemplateError as ex: 232 | _LOGGER.error( 233 | "Could not render attribute_template %s: %s", 234 | self.entity_id, 235 | ex, 236 | ) 237 | 238 | if value is not None: 239 | updated_value = value 240 | 241 | elif value_template is not None: 242 | try: 243 | value_template.hass = self.hass 244 | updated_value = value_template.async_render( 245 | {"variable": current_state} 246 | ) 247 | except TemplateError as ex: 248 | _LOGGER.error( 249 | "Could not render value_template %s: %s", 250 | self.entity_id, 251 | ex, 252 | ) 253 | 254 | self._attributes = updated_attributes 255 | 256 | if updated_value is not None: 257 | self._value = updated_value 258 | 259 | yield from self.async_update_ha_state() 260 | -------------------------------------------------------------------------------- /custom_components/variable/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "variable", 3 | "name": "variable", 4 | "documentation": "https://github.com/rogro82/hass-variables", 5 | "requirements": [], 6 | "dependencies": [], 7 | "codeowners": [ 8 | "@rogro82" 9 | ] 10 | } -------------------------------------------------------------------------------- /custom_components/variable/services.yaml: -------------------------------------------------------------------------------- 1 | # Example services.yaml entry 2 | 3 | set_variable: 4 | # Description of the service 5 | description: Update a variables value and/or its attributes. 6 | # Different fields that your service accepts 7 | fields: 8 | # Key of the field 9 | variable: 10 | description: string (required) The name of the variable to update 11 | value: 12 | description: any (optional) New value to set 13 | value_template: 14 | description: template (optional) New value to set from a template 15 | attributes: 16 | description: dictionary (optional) Attributes to set or update 17 | attributes_template: 18 | description: template (optional) Attributes to set or update from a template ( should return a json object ) 19 | replace_attributes: 20 | description: boolean ( optional ) Replace or merge current attributes (default false = merge) 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/counter.yaml: -------------------------------------------------------------------------------- 1 | variable: 2 | test_counter: 3 | value: 0 4 | attributes: 5 | icon: mdi:alarm 6 | 7 | automation: 8 | - alias: test_counter 9 | initial_state: 'on' 10 | trigger: 11 | - platform: time 12 | seconds: '/1' 13 | action: 14 | - service: variable.set_variable 15 | data: 16 | variable: test_counter 17 | value_template: '{{ (variable.state | int) + 1 }}' 18 | -------------------------------------------------------------------------------- /examples/history.yaml: -------------------------------------------------------------------------------- 1 | variable: 2 | last_motion: 3 | value: 'unknown' 4 | restore: true 5 | 6 | script: 7 | update_last_motion: 8 | sequence: 9 | - service: variable.set_variable 10 | data: 11 | variable: last_motion 12 | attributes_template: > 13 | { 14 | "history_1": "{{ variable.state }}", 15 | "history_2": "{{ variable.attributes.history_1 }}", 16 | "history_3": "{{ variable.attributes.history_2 }}" 17 | } 18 | data_template: 19 | value: "{{ location }}" 20 | 21 | update_motion_hall: 22 | sequence: 23 | - service: script.update_last_motion 24 | data: 25 | location: 'hall' 26 | 27 | update_motion_livingroom: 28 | sequence: 29 | - service: script.update_last_motion 30 | data: 31 | location: 'livingroom' 32 | 33 | update_motion_toilet: 34 | sequence: 35 | - service: script.update_last_motion 36 | data: 37 | location: 'toilet' 38 | 39 | update_motion_bedroom: 40 | sequence: 41 | - service: script.update_last_motion 42 | data: 43 | location: 'bedroom' 44 | -------------------------------------------------------------------------------- /examples/keypad.yaml: -------------------------------------------------------------------------------- 1 | variable: 2 | keypad: 3 | value: '' 4 | attributes: 5 | icon: mdi:gesture-tap 6 | keypad_timer: 7 | value: 0 8 | attributes: 9 | icon: mdi:alarm 10 | 11 | input_boolean: 12 | keypad_toggle: 13 | 14 | script: 15 | update_keypad: 16 | sequence: 17 | - service: variable.set_variable 18 | data_template: 19 | variable: keypad 20 | attributes: 21 | last: "{{ number }}" 22 | - service: variable.set_variable 23 | data: 24 | variable: keypad 25 | value_template: '{{ variable.state }}{{ variable.attributes.last }}' 26 | - service: variable.set_variable 27 | data: 28 | variable: keypad_timer 29 | value: 10 30 | - service: automation.turn_on 31 | data: 32 | entity_id: automation.keypad_timer 33 | 34 | clear_keypad: 35 | sequence: 36 | - service: variable.set_variable 37 | data: 38 | variable: keypad 39 | value: '' 40 | 41 | enter_keypad_1: 42 | sequence: 43 | - service: script.update_keypad 44 | data: 45 | number: 1 46 | 47 | enter_keypad_2: 48 | sequence: 49 | - service: script.update_keypad 50 | data: 51 | number: 2 52 | 53 | enter_keypad_3: 54 | sequence: 55 | - service: script.update_keypad 56 | data: 57 | number: 3 58 | 59 | enter_keypad_4: 60 | sequence: 61 | - service: script.update_keypad 62 | data: 63 | number: 4 64 | 65 | automation: 66 | - alias: keypad_timer 67 | initial_state: 'off' 68 | trigger: 69 | - platform: time 70 | seconds: '/1' 71 | action: 72 | - service: variable.set_variable 73 | data: 74 | variable: keypad_timer 75 | value_template: '{{ [((variable.state | int) - 1), 0] | max }}' 76 | 77 | - alias: keypad_timeout 78 | trigger: 79 | platform: state 80 | entity_id: variable.keypad_timer 81 | to: '0' 82 | action: 83 | - service: script.clear_keypad 84 | - service: automation.turn_off 85 | data: 86 | entity_id: automation.keypad_timer 87 | 88 | - alias: keypad_validate 89 | trigger: 90 | platform: state 91 | entity_id: variable.keypad 92 | to: '1234' 93 | action: 94 | - service: input_boolean.toggle 95 | data: 96 | entity_id: input_boolean.keypad_toggle 97 | - service: script.clear_keypad 98 | -------------------------------------------------------------------------------- /examples/timer.yaml: -------------------------------------------------------------------------------- 1 | variable: 2 | test_timer: 3 | value: 0 4 | attributes: 5 | icon: mdi:alarm 6 | 7 | script: 8 | schedule_test_timer: 9 | sequence: 10 | - service: variable.set_variable 11 | data: 12 | variable: test_timer 13 | value: 30 14 | - service: automation.turn_on 15 | data: 16 | entity_id: automation.test_timer_countdown 17 | 18 | automation: 19 | - alias: test_timer_countdown 20 | initial_state: 'off' 21 | trigger: 22 | - platform: time_pattern 23 | seconds: '/1' 24 | action: 25 | - service: variable.set_variable 26 | data: 27 | variable: test_timer 28 | value_template: '{{ [((variable.state | int) - 1), 0] | max }}' 29 | 30 | - alias: test_timer_trigger 31 | trigger: 32 | platform: state 33 | entity_id: variable.test_timer 34 | to: '0' 35 | action: 36 | - service: automation.turn_off 37 | data: 38 | entity_id: automation.test_timer_countdown 39 | --------------------------------------------------------------------------------