diff --git a/homeassistant/components/husqvarna_automower/__init__.py b/homeassistant/components/husqvarna_automower/__init__.py index a6c9e46dbed7a4..03ab02429bb35e 100644 --- a/homeassistant/components/husqvarna_automower/__init__.py +++ b/homeassistant/components/husqvarna_automower/__init__.py @@ -21,6 +21,7 @@ Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.LAWN_MOWER, + Platform.SELECT, Platform.SENSOR, Platform.SWITCH, ] diff --git a/homeassistant/components/husqvarna_automower/icons.json b/homeassistant/components/husqvarna_automower/icons.json index a3e2bd6bb8bd02..65cc85bd09bf69 100644 --- a/homeassistant/components/husqvarna_automower/icons.json +++ b/homeassistant/components/husqvarna_automower/icons.json @@ -8,6 +8,11 @@ "default": "mdi:debug-step-into" } }, + "select": { + "headlight_mode": { + "default": "mdi:car-light-high" + } + }, "sensor": { "number_of_charging_cycles": { "default": "mdi:battery-sync-outline" diff --git a/homeassistant/components/husqvarna_automower/select.py b/homeassistant/components/husqvarna_automower/select.py new file mode 100644 index 00000000000000..e4376a1bca5ad8 --- /dev/null +++ b/homeassistant/components/husqvarna_automower/select.py @@ -0,0 +1,70 @@ +"""Creates a select entity for the headlight of the mower.""" + +import logging + +from aioautomower.exceptions import ApiException +from aioautomower.model import HeadlightModes + +from homeassistant.components.select import SelectEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import AutomowerDataUpdateCoordinator +from .entity import AutomowerControlEntity + +_LOGGER = logging.getLogger(__name__) + + +HEADLIGHT_MODES: list = [ + HeadlightModes.ALWAYS_OFF.lower(), + HeadlightModes.ALWAYS_ON.lower(), + HeadlightModes.EVENING_AND_NIGHT.lower(), + HeadlightModes.EVENING_ONLY.lower(), +] + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up select platform.""" + coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + AutomowerSelectEntity(mower_id, coordinator) + for mower_id in coordinator.data + if coordinator.data[mower_id].capabilities.headlights + ) + + +class AutomowerSelectEntity(AutomowerControlEntity, SelectEntity): + """Defining the headlight mode entity.""" + + _attr_options = HEADLIGHT_MODES + _attr_entity_category = EntityCategory.CONFIG + _attr_translation_key = "headlight_mode" + + def __init__( + self, + mower_id: str, + coordinator: AutomowerDataUpdateCoordinator, + ) -> None: + """Set up select platform.""" + super().__init__(mower_id, coordinator) + self._attr_unique_id = f"{mower_id}_headlight_mode" + + @property + def current_option(self) -> str: + """Return the current option for the entity.""" + return self.mower_attributes.headlight.mode.lower() + + async def async_select_option(self, option: str) -> None: + """Change the selected option.""" + try: + await self.coordinator.api.set_headlight_mode(self.mower_id, option.upper()) + except ApiException as exception: + raise HomeAssistantError( + f"Command couldn't be sent to the command queue: {exception}" + ) from exception diff --git a/homeassistant/components/husqvarna_automower/strings.json b/homeassistant/components/husqvarna_automower/strings.json index 4280ea097e85ff..8032c67040443e 100644 --- a/homeassistant/components/husqvarna_automower/strings.json +++ b/homeassistant/components/husqvarna_automower/strings.json @@ -37,9 +37,15 @@ "name": "Returning to dock" } }, - "switch": { - "enable_schedule": { - "name": "Enable schedule" + "select": { + "headlight_mode": { + "name": "Headlight mode", + "state": { + "always_on": "Always on", + "always_off": "Always off", + "evening_only": "Evening only", + "evening_and_night": "Evening and night" + } } }, "sensor": { @@ -79,6 +85,11 @@ "demo": "Demo" } } + }, + "switch": { + "enable_schedule": { + "name": "Enable schedule" + } } } } diff --git a/tests/components/husqvarna_automower/test_select.py b/tests/components/husqvarna_automower/test_select.py new file mode 100644 index 00000000000000..4283c7d379751b --- /dev/null +++ b/tests/components/husqvarna_automower/test_select.py @@ -0,0 +1,102 @@ +"""Tests for select platform.""" + +from datetime import timedelta +from unittest.mock import AsyncMock + +from aioautomower.exceptions import ApiException +from aioautomower.model import HeadlightModes +from aioautomower.utils import mower_list_to_dictionary_dataclass +from freezegun.api import FrozenDateTimeFactory +import pytest + +from homeassistant.components.husqvarna_automower.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from . import setup_integration +from .const import TEST_MOWER_ID + +from tests.common import ( + MockConfigEntry, + async_fire_time_changed, + load_json_value_fixture, +) + + +async def test_select_states( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test states of headlight mode select.""" + values = mower_list_to_dictionary_dataclass( + load_json_value_fixture("mower.json", DOMAIN) + ) + await setup_integration(hass, mock_config_entry) + state = hass.states.get("select.test_mower_1_headlight_mode") + assert state is not None + assert state.state == "evening_only" + + for state, expected_state in [ + ( + HeadlightModes.ALWAYS_OFF, + "always_off", + ), + (HeadlightModes.ALWAYS_ON, "always_on"), + (HeadlightModes.EVENING_AND_NIGHT, "evening_and_night"), + ]: + values[TEST_MOWER_ID].headlight.mode = state + mock_automower_client.get_status.return_value = values + freezer.tick(timedelta(minutes=5)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get("select.test_mower_1_headlight_mode") + assert state.state == expected_state + + +@pytest.mark.parametrize( + ("service"), + [ + ("always_on"), + ("always_off"), + ("evening_only"), + ("evening_and_night"), + ], +) +async def test_select_commands( + hass: HomeAssistant, + service: str, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test select commands for headlight mode.""" + await setup_integration(hass, mock_config_entry) + await hass.services.async_call( + domain="select", + service="select_option", + service_data={ + "entity_id": "select.test_mower_1_headlight_mode", + "option": service, + }, + blocking=True, + ) + mocked_method = mock_automower_client.set_headlight_mode + assert len(mocked_method.mock_calls) == 1 + + mocked_method.side_effect = ApiException("Test error") + with pytest.raises(HomeAssistantError) as exc_info: + await hass.services.async_call( + domain="select", + service="select_option", + service_data={ + "entity_id": "select.test_mower_1_headlight_mode", + "option": service, + }, + blocking=True, + ) + assert ( + str(exc_info.value) + == "Command couldn't be sent to the command queue: Test error" + ) + assert len(mocked_method.mock_calls) == 2