diff --git a/homeassistant/components/command_line/__init__.py b/homeassistant/components/command_line/__init__.py index 906e28052da..6f536bf4744 100644 --- a/homeassistant/components/command_line/__init__.py +++ b/homeassistant/components/command_line/__init__.py @@ -46,12 +46,15 @@ from homeassistant.const import ( CONF_UNIQUE_ID, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, + SERVICE_RELOAD, Platform, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant, ServiceCall from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.helpers.entity_platform import async_get_platforms +from homeassistant.helpers.reload import async_integration_yaml_config +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN @@ -163,14 +166,39 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up Command Line from yaml config.""" - command_line_config: list[dict[str, dict[str, Any]]] = config.get(DOMAIN, []) + + async def _reload_config(call: Event | ServiceCall) -> None: + """Reload Command Line.""" + reload_config = await async_integration_yaml_config(hass, "command_line") + reset_platforms = async_get_platforms(hass, "command_line") + for reset_platform in reset_platforms: + _LOGGER.debug("Reload resetting platform: %s", reset_platform.domain) + await reset_platform.async_reset() + if not reload_config: + return + await async_load_platforms(hass, reload_config.get(DOMAIN, []), reload_config) + + async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config) + + await async_load_platforms(hass, config.get(DOMAIN, []), config) + + return True + + +async def async_load_platforms( + hass: HomeAssistant, + command_line_config: list[dict[str, dict[str, Any]]], + config: ConfigType, +) -> None: + """Load platforms from yaml.""" if not command_line_config: - return True + return _LOGGER.debug("Full config loaded: %s", command_line_config) load_coroutines: list[Coroutine[Any, Any, None]] = [] platforms: list[Platform] = [] + reload_configs: list[tuple] = [] for platform_config in command_line_config: for platform, _config in platform_config.items(): if (mapped_platform := PLATFORM_MAPPING[platform]) not in platforms: @@ -180,6 +208,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: platform_config, PLATFORM_MAPPING[platform], ) + reload_configs.append((PLATFORM_MAPPING[platform], _config)) load_coroutines.append( discovery.async_load_platform( hass, @@ -190,10 +219,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) ) - await async_setup_reload_service(hass, DOMAIN, platforms) - if load_coroutines: _LOGGER.debug("Loading platforms: %s", platforms) await asyncio.gather(*load_coroutines) - - return True diff --git a/tests/components/command_line/fixtures/configuration.yaml b/tests/components/command_line/fixtures/configuration.yaml index f210b640338..43e6f641966 100644 --- a/tests/components/command_line/fixtures/configuration.yaml +++ b/tests/components/command_line/fixtures/configuration.yaml @@ -1,6 +1,7 @@ -cover: - - platform: command_line - covers: - from_yaml: - command_state: "echo closed" - value_template: "{{ value }}" +command_line: + - "binary_sensor": + "name": "Test" + "command": "echo 1" + "payload_on": "1" + "payload_off": "0" + "command_timeout": 15 diff --git a/tests/components/command_line/fixtures/configuration_empty.yaml b/tests/components/command_line/fixtures/configuration_empty.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index e0187b80f41..ac0a33fc7a9 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -9,7 +9,7 @@ from unittest.mock import patch import pytest -from homeassistant import config as hass_config, setup +from homeassistant import setup from homeassistant.components.command_line import DOMAIN from homeassistant.components.command_line.cover import CommandCover from homeassistant.components.cover import DOMAIN as COVER_DOMAIN, SCAN_INTERVAL @@ -21,7 +21,6 @@ from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, - SERVICE_RELOAD, SERVICE_STOP_COVER, ) from homeassistant.core import HomeAssistant @@ -29,7 +28,7 @@ from homeassistant.helpers import entity_registry as er import homeassistant.helpers.issue_registry as ir import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed, get_fixture_path +from tests.common import async_fire_time_changed async def test_no_covers_platform_yaml( @@ -214,45 +213,6 @@ async def test_state_value(hass: HomeAssistant) -> None: assert entity_state.state == "closed" -@pytest.mark.parametrize( - "get_config", - [ - { - "command_line": [ - { - "cover": { - "command_state": "echo open", - "value_template": "{{ value }}", - "name": "Test", - } - } - ] - } - ], -) -async def test_reload(hass: HomeAssistant, load_yaml_integration: None) -> None: - """Verify we can reload command_line covers.""" - - entity_state = hass.states.get("cover.test") - assert entity_state - assert entity_state.state == "unknown" - - yaml_path = get_fixture_path("configuration.yaml", "command_line") - with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): - await hass.services.async_call( - "command_line", - SERVICE_RELOAD, - {}, - blocking=True, - ) - await hass.async_block_till_done() - - assert len(hass.states.async_all()) == 1 - - assert not hass.states.get("cover.test") - assert hass.states.get("cover.from_yaml") - - @pytest.mark.parametrize( "get_config", [ diff --git a/tests/components/command_line/test_init.py b/tests/components/command_line/test_init.py index 06d7b8c41dc..53f985961f3 100644 --- a/tests/components/command_line/test_init.py +++ b/tests/components/command_line/test_init.py @@ -2,12 +2,17 @@ from __future__ import annotations from datetime import timedelta +from unittest.mock import patch -from homeassistant.const import STATE_ON, STATE_OPEN +import pytest + +from homeassistant import config as hass_config +from homeassistant.components.command_line.const import DOMAIN +from homeassistant.const import SERVICE_RELOAD, STATE_ON, STATE_OPEN from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, get_fixture_path async def test_setup_config(hass: HomeAssistant, load_yaml_integration: None) -> None: @@ -25,3 +30,55 @@ async def test_setup_config(hass: HomeAssistant, load_yaml_integration: None) -> assert state_sensor.state == "5" assert state_cover.state == STATE_OPEN assert state_switch.state == STATE_ON + + +async def test_reload_service( + hass: HomeAssistant, load_yaml_integration: None, caplog: pytest.LogCaptureFixture +) -> None: + """Test reload serviice.""" + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10)) + await hass.async_block_till_done() + + state_binary_sensor = hass.states.get("binary_sensor.test") + state_sensor = hass.states.get("sensor.test") + assert state_binary_sensor.state == STATE_ON + assert state_sensor.state == "5" + + caplog.clear() + + yaml_path = get_fixture_path("configuration.yaml", "command_line") + with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + assert "Loading config" in caplog.text + + state_binary_sensor = hass.states.get("binary_sensor.test") + state_sensor = hass.states.get("sensor.test") + assert state_binary_sensor.state == STATE_ON + assert not state_sensor + + caplog.clear() + + yaml_path = get_fixture_path("configuration_empty.yaml", "command_line") + with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + state_binary_sensor = hass.states.get("binary_sensor.test") + state_sensor = hass.states.get("sensor.test") + assert not state_binary_sensor + assert not state_sensor + + assert "Loading config" not in caplog.text