diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index 4a32ad16797..326244a7e4c 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -6,16 +6,18 @@ import voluptuous as vol from homeassistant.const import ( CONF_ICON, CONF_NAME, + SERVICE_RELOAD, + SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_TOGGLE, STATE_ON, ) -from homeassistant.loader import bind_hass import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service +from homeassistant.loader import bind_hass DOMAIN = "input_boolean" @@ -41,6 +43,8 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) + @bind_hass def is_on(hass, entity_id): @@ -52,6 +56,39 @@ async def async_setup(hass, config): """Set up an input boolean.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + entities = await _async_process_config(config) + + async def reload_service_handler(service_call): + """Remove all input booleans and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(conf) + if new_entities: + await component.async_add_entities(new_entities) + + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) + + component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") + + component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") + + component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") + + if entities: + await component.async_add_entities(entities) + + return True + + +async def _async_process_config(config): + """Process config and create list of entities.""" entities = [] for object_id, cfg in config[DOMAIN].items(): @@ -64,17 +101,7 @@ async def async_setup(hass, config): entities.append(InputBoolean(object_id, name, initial, icon)) - if not entities: - return False - - component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") - - component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") - - component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") - - await component.async_add_entities(entities) - return True + return entities class InputBoolean(ToggleEntity, RestoreEntity): diff --git a/homeassistant/components/input_boolean/services.yaml b/homeassistant/components/input_boolean/services.yaml index e49d46c9b86..e391c15d3a8 100644 --- a/homeassistant/components/input_boolean/services.yaml +++ b/homeassistant/components/input_boolean/services.yaml @@ -10,3 +10,6 @@ turn_on: description: Turns on an input boolean. fields: entity_id: {description: Entity id of the input boolean to turn on., example: input_boolean.notify_alerts} +reload: + description: Reload the input_boolean configuration. + diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index def8db9b35c..ed5e927c2ca 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -2,20 +2,22 @@ # pylint: disable=protected-access import asyncio import logging +from unittest.mock import patch -from homeassistant.core import CoreState, State, Context -from homeassistant.setup import async_setup_component -from homeassistant.components.input_boolean import is_on, CONF_INITIAL, DOMAIN +from homeassistant.components.input_boolean import CONF_INITIAL, DOMAIN, is_on from homeassistant.const import ( - STATE_ON, - STATE_OFF, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON, + SERVICE_RELOAD, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, ) +from homeassistant.core import Context, CoreState, State +from homeassistant.setup import async_setup_component from tests.common import mock_component, mock_restore_cache @@ -165,3 +167,71 @@ async def test_input_boolean_context(hass, hass_admin_user): assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id + + +async def test_reload(hass, hass_admin_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids()) + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + "test_1": None, + "test_2": {"name": "Hello World", "icon": "mdi:work", "initial": True}, + } + }, + ) + + _LOGGER.debug("ENTITIES: %s", hass.states.async_entity_ids()) + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_boolean.test_1") + state_2 = hass.states.get("input_boolean.test_2") + state_3 = hass.states.get("input_boolean.test_3") + + assert state_1 is not None + assert state_2 is not None + assert state_3 is None + assert STATE_ON == state_2.state + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "test_2": { + "name": "Hello World reloaded", + "icon": "mdi:work_reloaded", + "initial": False, + }, + "test_3": None, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_boolean.test_1") + state_2 = hass.states.get("input_boolean.test_2") + state_3 = hass.states.get("input_boolean.test_3") + + assert state_1 is None + assert state_2 is not None + assert state_3 is not None + + assert STATE_OFF == state_2.state + assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert "mdi:work_reloaded" == state_2.attributes.get(ATTR_ICON)