├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README.md ├── customizer ├── __init__.py └── services.yaml └── pylintrc /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Report issues with [customizer component](https://github.com/andrey-git/home-assistant-customizer/blob/master/README.md) features here. 2 | 3 | For other issues with CustomUI please report at the main repository: https://github.com/andrey-git/home-assistant-custom-ui/issues 4 | -------------------------------------------------------------------------------- /.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 | 103 | # Idea 104 | .idea/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Home-assistant customizer comonent 2 | Custom component for [home-assistant](https://home-assistant.io) 3 | 4 | * Optionally loads CustomUI files. [HA 0.53+] 5 | * Specify a list of attributes to hide from `more-info` window. 6 | * Hide CustomUI - related attributes in `more-info` window. [HA 0.53+] 7 | * Add services for dynamic customization. 8 | * Set the width and number of UI columns (Requires CustomUI 20180112+) 9 | 10 | ## Installing 11 | Put `customizer` dir in `/custom_components/` 12 | 13 | ## Using 14 | `configuration.yaml` options (all optional): 15 | 16 | attribute | type | description | 17 | -- | -- | -- | 18 | custom_ui [HA 0.53+]| local / hosted / debug
version_tag | Whether to fetch CustomUI files.
`local` loads it from `/www/custom_ui/state-card-custom-ui.html`.
`hosted` loads it from https://raw.githubusercontent.com/andrey-git/home-assistant-custom-ui/master/state-card-custom-ui.html
`debug` loads it from https://raw.githubusercontent.com/andrey-git/home-assistant-custom-ui/master/state-card-custom-ui-dbg.html
version_tag (for example 20170830) loads a tagged version from 'https://github.com/andrey-git/home-assistant-custom-ui/releases/download/20170830/state-card-custom-ui.html' | 19 | hide_attributes | List of strings | List of attributes to hide from more-info popups. (Requires CustomUI) | 20 | columns| List of integers | Pixel widths at which to add the next column. Default value is [300, 600, 900, 1200] 21 | 22 | Example HA 0.53: 23 | ```yaml 24 | customizer: 25 | custom_ui: local 26 | hide_attributes: 27 | - node_id 28 | - value_index 29 | columns: [350, 700, 1050, 1400, 1850, 2100] 30 | ``` 31 | 32 | ## Services 33 | 34 | The component exposes a `set_attribute` service. 35 | It sets (or clears) persistent attribute of an entity overriding any value set in code. 36 | Like specifying it in `homeassistant -> customize` YAML section. 37 | 38 | 39 | Note that calling `homeassistant.reload_core_config` service or changing customization via Config panel will reset overrides to their yaml state. 40 | 41 | Service fields: 42 | 43 | name | description | example 44 | -- | -- | -- 45 | entity_id | Entity ID to set the attribute on | light.patio 46 | attribute | Name of the attribute to set | friendly_name 47 | value | (Optional) Value to set. Leave unspecified in order to clear set value. Note that when clearing attribute it will be empty (and not set-by-code) until next entity update | My light' 48 | 49 | For example you can call: 50 | ``` 51 | customizer.set_attribute({"entity_id": "light.patio", "attribute": "friendly_name", "value": "My Light"}) to change the light name 52 | ``` 53 | -------------------------------------------------------------------------------- /customizer/__init__.py: -------------------------------------------------------------------------------- 1 | """Customizer component. Bring extra customize features to home-assistant.""" 2 | 3 | import asyncio 4 | import logging 5 | import os 6 | 7 | import homeassistant.components.frontend as frontend 8 | import homeassistant.helpers.config_validation as cv 9 | from homeassistant.config import load_yaml_config_file, DATA_CUSTOMIZE 10 | from homeassistant.core import callback 11 | 12 | from homeassistant.helpers.entity import Entity 13 | from homeassistant.helpers.entity_component import EntityComponent 14 | from homeassistant.const import CONF_ENTITY_ID, MINOR_VERSION 15 | 16 | import voluptuous as vol 17 | 18 | _LOGGER = logging.getLogger(__name__) 19 | 20 | DOMAIN = 'customizer' 21 | DEPENDENCIES = ['frontend'] 22 | 23 | CONF_CUSTOM_UI = 'custom_ui' 24 | 25 | LOCAL = 'local' 26 | HOSTED = 'hosted' 27 | DEBUG = 'debug' 28 | 29 | CONF_HIDE_ATTRIBUTES = 'hide_attributes' 30 | 31 | CONF_ATTRIBUTE = 'attribute' 32 | CONF_VALUE = 'value' 33 | CONF_COLUMNS = 'columns' 34 | 35 | SERVICE_SET_ATTRIBUTE = 'set_attribute' 36 | SERVICE_SET_ATTRIBUTE_SCHEMA = vol.Schema({ 37 | vol.Required(CONF_ENTITY_ID): cv.entity_id, 38 | vol.Required(CONF_ATTRIBUTE): cv.string, 39 | vol.Optional(CONF_VALUE): cv.match_all, 40 | }) 41 | 42 | CONFIG_SCHEMA = vol.Schema({ 43 | DOMAIN: vol.Schema({ 44 | vol.Optional(CONF_CUSTOM_UI): cv.string, 45 | vol.Optional(CONF_COLUMNS): [int], 46 | vol.Optional(CONF_HIDE_ATTRIBUTES): 47 | vol.All(cv.ensure_list, [cv.string]), 48 | }) 49 | }, extra=vol.ALLOW_EXTRA) 50 | 51 | 52 | @asyncio.coroutine 53 | def async_setup(hass, config): 54 | """Set up customizer.""" 55 | custom_ui = config[DOMAIN].get(CONF_CUSTOM_UI) 56 | if MINOR_VERSION < 53 and custom_ui is not None: 57 | _LOGGER.warning('%s is only supported from Home Assistant 0.53', 58 | CONF_CUSTOM_UI) 59 | elif custom_ui is not None: 60 | def add_extra_html_url(base_url): 61 | """Add extra url using version-dependent function.""" 62 | if MINOR_VERSION >= 59: 63 | frontend.add_extra_html_url( 64 | hass, '{}.html'.format(base_url), False) 65 | frontend.add_extra_html_url( 66 | hass, '{}-es5.html'.format(base_url), True) 67 | else: 68 | frontend.add_extra_html_url(hass, '{}.html'.format(base_url)) 69 | 70 | if custom_ui == LOCAL: 71 | add_extra_html_url('/local/custom_ui/state-card-custom-ui') 72 | elif custom_ui == HOSTED: 73 | add_extra_html_url( 74 | 'https://raw.githubusercontent.com/andrey-git/' 75 | 'home-assistant-custom-ui/master/state-card-custom-ui') 76 | elif custom_ui == DEBUG: 77 | add_extra_html_url( 78 | 'https://raw.githubusercontent.com/andrey-git/' 79 | 'home-assistant-custom-ui/master/' 80 | 'state-card-custom-ui-dbg') 81 | else: 82 | add_extra_html_url( 83 | 'https://github.com/andrey-git/home-assistant-custom-ui/' 84 | 'releases/download/{}/state-card-custom-ui' 85 | .format(custom_ui)) 86 | 87 | component = EntityComponent(_LOGGER, DOMAIN, hass) 88 | yield from component.async_add_entities([CustomizerEntity(config[DOMAIN])]) 89 | 90 | @callback 91 | def set_attribute(call): 92 | """Set attribute override.""" 93 | data = call.data 94 | entity_id = data[CONF_ENTITY_ID] 95 | attribute = data[CONF_ATTRIBUTE] 96 | value = data.get(CONF_VALUE) 97 | overrides = hass.data[DATA_CUSTOMIZE].get(entity_id) 98 | state = hass.states.get(entity_id) 99 | state_attributes = dict(state.attributes) 100 | if value is None: 101 | if attribute in overrides: 102 | overrides.pop(attribute) 103 | if attribute in state_attributes: 104 | state_attributes.pop(attribute) 105 | else: 106 | overrides[attribute] = value 107 | state_attributes[attribute] = value 108 | hass.states.async_set(entity_id, state.state, state_attributes) 109 | 110 | if MINOR_VERSION >= 61: 111 | hass.services.async_register(DOMAIN, SERVICE_SET_ATTRIBUTE, 112 | set_attribute, 113 | SERVICE_SET_ATTRIBUTE_SCHEMA) 114 | else: 115 | descriptions = yield from hass.async_add_job( 116 | load_yaml_config_file, os.path.join( 117 | os.path.dirname(__file__), 'services.yaml')) 118 | hass.services.async_register(DOMAIN, SERVICE_SET_ATTRIBUTE, 119 | set_attribute, 120 | descriptions[SERVICE_SET_ATTRIBUTE], 121 | SERVICE_SET_ATTRIBUTE_SCHEMA) 122 | 123 | return True 124 | 125 | 126 | class CustomizerEntity(Entity): 127 | """Customizer entity class.""" 128 | 129 | def __init__(self, config): 130 | """Constructor that parses the config.""" 131 | self.hide_attributes = config.get(CONF_HIDE_ATTRIBUTES) 132 | self.columns = config.get(CONF_COLUMNS) 133 | 134 | @property 135 | def hidden(self): 136 | """Don't show the entity.""" 137 | return True 138 | 139 | @property 140 | def name(self): 141 | """Singleton name.""" 142 | return DOMAIN 143 | 144 | @property 145 | def state_attributes(self): 146 | """Return the state attributes.""" 147 | result = {} 148 | if self.hide_attributes: 149 | result[CONF_HIDE_ATTRIBUTES] = self.hide_attributes 150 | if self.columns: 151 | result[CONF_COLUMNS] = self.columns 152 | return result 153 | -------------------------------------------------------------------------------- /customizer/services.yaml: -------------------------------------------------------------------------------- 1 | set_attribute: 2 | description: > 3 | Set or clear persistent attribute of an entity overriding any value set in code. 4 | Note that calling homeassistant.reload_core_config will reset overrides to their yaml state. 5 | fields: 6 | entity_id: 7 | description: Entity ID to set the attribute on. 8 | example: light.patio 9 | attribute: 10 | description: Name of the attribute to set. 11 | example: friendly_name 12 | value: 13 | description: > 14 | (Optional) Value to set. Leave unspecified in order to clear set value. 15 | Note that when clearing attribute it will be empty (and not set-by-code) until next entity update. 16 | example: 'My light' 17 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | reports=no 3 | 4 | # Reasons disabled: 5 | # locally-disabled - it spams too much 6 | # duplicate-code - unavoidable 7 | # cyclic-import - doesn't test if both import on load 8 | # abstract-class-little-used - prevents from setting right foundation 9 | # abstract-class-not-used - is flaky, should not show up but does 10 | # unused-argument - generic callbacks and setup methods create a lot of warnings 11 | # global-statement - used for the on-demand requirement installation 12 | # redefined-variable-type - this is Python, we're duck typing! 13 | # too-many-* - are not enforced for the sake of readability 14 | # too-few-* - same as too-many-* 15 | # abstract-method - with intro of async there are always methods missing 16 | 17 | disable= 18 | abstract-class-little-used, 19 | abstract-class-not-used, 20 | abstract-method, 21 | cyclic-import, 22 | global-statement, 23 | locally-disabled, 24 | not-context-manager, 25 | redefined-variable-type, 26 | unused-argument 27 | 28 | --------------------------------------------------------------------------------