-
-
Notifications
You must be signed in to change notification settings - Fork 30.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add select platform to Husqvarna Automower (#113816)
* Add select platform to Husqvarna Automower * docstring * address review * pin headlight_modes list * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker <[email protected]> * Apply review --------- Co-authored-by: Joost Lekkerkerker <[email protected]>
- Loading branch information
1 parent
b4c36d4
commit 6322135
Showing
5 changed files
with
192 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |