Skip to content

Commit

Permalink
Add Select platform to Tessie (home-assistant#105423)
Browse files Browse the repository at this point in the history
* Add select platform

* Add error coverage

* Fix case

* fix value

* Remove virtual key issue

* Add TessieSeatHeaterOptions enum and update TessieSeatHeaterSelectEntity options

* use ENUM in tests

* Porting other fixes

* Update entity
  • Loading branch information
Bre77 authored Dec 21, 2023
1 parent 126f0e4 commit dbb726f
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 1 deletion.
8 changes: 7 additions & 1 deletion homeassistant/components/tessie/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
from .const import DOMAIN
from .coordinator import TessieDataUpdateCoordinator

PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]

_LOGGER = logging.getLogger(__name__)

Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/tessie/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ class TessieStatus(StrEnum):
ONLINE = "online"


class TessieSeatHeaterOptions(StrEnum):
"""Tessie seat heater options."""

OFF = "off"
LOW = "low"
MEDIUM = "medium"
HIGH = "high"


class TessieClimateKeeper(StrEnum):
"""Tessie Climate Keeper Modes."""

Expand Down
58 changes: 58 additions & 0 deletions homeassistant/components/tessie/select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Select platform for Tessie integration."""
from __future__ import annotations

from tessie_api import set_seat_heat

from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN, TessieSeatHeaterOptions
from .entity import TessieEntity

SEAT_HEATERS = {
"climate_state_seat_heater_left": "front_left",
"climate_state_seat_heater_right": "front_right",
"climate_state_seat_heater_rear_left": "rear_left",
"climate_state_seat_heater_rear_center": "rear_center",
"climate_state_seat_heater_rear_right": "rear_right",
"climate_state_seat_heater_third_row_left": "third_row_left",
"climate_state_seat_heater_third_row_right": "third_row_right",
}


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Tessie select platform from a config entry."""
coordinators = hass.data[DOMAIN][entry.entry_id]

async_add_entities(
TessieSeatHeaterSelectEntity(coordinator, key)
for coordinator in coordinators
for key in SEAT_HEATERS
if key in coordinator.data
)


class TessieSeatHeaterSelectEntity(TessieEntity, SelectEntity):
"""Select entity for current charge."""

_attr_options = [
TessieSeatHeaterOptions.OFF,
TessieSeatHeaterOptions.LOW,
TessieSeatHeaterOptions.MEDIUM,
TessieSeatHeaterOptions.HIGH,
]

@property
def current_option(self) -> str | None:
"""Return the current selected option."""
return self._attr_options[self._value]

async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
level = self._attr_options.index(option)
await self.run(set_seat_heat, seat=SEAT_HEATERS[self.key], level=level)
self.set((self.key, level))
65 changes: 65 additions & 0 deletions homeassistant/components/tessie/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,71 @@
"name": "Passenger temperature setting"
}
},
"select": {
"climate_state_seat_heater_left": {
"name": "Seat heater left",
"state": {
"off": "[%key:common::state::off%]",
"low": "Low",
"medium": "Medium",
"high": "High"
}
},
"climate_state_seat_heater_right": {
"name": "Seat heater right",
"state": {
"off": "[%key:common::state::off%]",
"low": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::low%]",
"medium": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::medium%]",
"high": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::high%]"
}
},
"climate_state_seat_heater_rear_left": {
"name": "Seat heater rear left",
"state": {
"off": "[%key:common::state::off%]",
"low": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::low%]",
"medium": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::medium%]",
"high": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::high%]"
}
},
"climate_state_seat_heater_rear_center": {
"name": "Seat heater rear center",
"state": {
"off": "[%key:common::state::off%]",
"low": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::low%]",
"medium": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::medium%]",
"high": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::high%]"
}
},
"climate_state_seat_heater_rear_right": {
"name": "Seat heater rear right",
"state": {
"off": "[%key:common::state::off%]",
"low": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::low%]",
"medium": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::medium%]",
"high": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::high%]"
}
},
"climate_state_seat_heater_third_row_left": {
"name": "Seat heater third row left",
"state": {
"off": "[%key:common::state::off%]",
"low": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::low%]",
"medium": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::medium%]",
"high": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::high%]"
}
},
"climate_state_seat_heater_third_row_right": {
"name": "Seat heater third row right",
"state": {
"off": "[%key:common::state::off%]",
"low": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::low%]",
"medium": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::medium%]",
"high": "[%key:component::tessie::entity::select::climate_state_seat_heater_left::state::high%]"
}
}
},
"binary_sensor": {
"state": {
"name": "Status"
Expand Down
65 changes: 65 additions & 0 deletions tests/components/tessie/test_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Test the Tessie select platform."""
from unittest.mock import patch

import pytest

from homeassistant.components.select import (
DOMAIN as SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
)
from homeassistant.components.tessie.const import TessieSeatHeaterOptions
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, STATE_OFF
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

from .common import ERROR_UNKNOWN, TEST_RESPONSE, setup_platform


async def test_select(hass: HomeAssistant) -> None:
"""Tests that the select entity is correct."""

assert len(hass.states.async_all(SELECT_DOMAIN)) == 0

await setup_platform(hass)

assert len(hass.states.async_all(SELECT_DOMAIN)) == 5

entity_id = "select.test_seat_heater_left"
assert hass.states.get(entity_id).state == STATE_OFF

# Test changing select
with patch(
"homeassistant.components.tessie.select.set_seat_heat",
return_value=TEST_RESPONSE,
) as mock_set:
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{ATTR_ENTITY_ID: [entity_id], ATTR_OPTION: TessieSeatHeaterOptions.LOW},
blocking=True,
)
mock_set.assert_called_once()
assert mock_set.call_args[1]["seat"] == "front_left"
assert mock_set.call_args[1]["level"] == 1
assert hass.states.get(entity_id).state == TessieSeatHeaterOptions.LOW


async def test_errors(hass: HomeAssistant) -> None:
"""Tests unknown error is handled."""

await setup_platform(hass)
entity_id = "select.test_seat_heater_left"

# Test setting cover open with unknown error
with patch(
"homeassistant.components.tessie.select.set_seat_heat",
side_effect=ERROR_UNKNOWN,
) as mock_set, pytest.raises(HomeAssistantError) as error:
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{ATTR_ENTITY_ID: [entity_id], ATTR_OPTION: TessieSeatHeaterOptions.LOW},
blocking=True,
)
mock_set.assert_called_once()
assert error.from_exception == ERROR_UNKNOWN

0 comments on commit dbb726f

Please sign in to comment.