Skip to content

Commit

Permalink
Add select platform to Husqvarna Automower (#113816)
Browse files Browse the repository at this point in the history
* 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
Thomas55555 and joostlek authored Mar 21, 2024
1 parent b4c36d4 commit 6322135
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 3 deletions.
1 change: 1 addition & 0 deletions homeassistant/components/husqvarna_automower/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Platform.BINARY_SENSOR,
Platform.DEVICE_TRACKER,
Platform.LAWN_MOWER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/husqvarna_automower/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
70 changes: 70 additions & 0 deletions homeassistant/components/husqvarna_automower/select.py
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
17 changes: 14 additions & 3 deletions homeassistant/components/husqvarna_automower/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -79,6 +85,11 @@
"demo": "Demo"
}
}
},
"switch": {
"enable_schedule": {
"name": "Enable schedule"
}
}
}
}
102 changes: 102 additions & 0 deletions tests/components/husqvarna_automower/test_select.py
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

0 comments on commit 6322135

Please sign in to comment.