Add Select platform to Tessie (#105423)
* 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
This commit is contained in:
parent
126f0e4047
commit
dbb726f41f
5 changed files with 204 additions and 1 deletions
|
@ -14,7 +14,13 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import TessieDataUpdateCoordinator
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,15 @@ class TessieStatus(StrEnum):
|
||||||
ONLINE = "online"
|
ONLINE = "online"
|
||||||
|
|
||||||
|
|
||||||
|
class TessieSeatHeaterOptions(StrEnum):
|
||||||
|
"""Tessie seat heater options."""
|
||||||
|
|
||||||
|
OFF = "off"
|
||||||
|
LOW = "low"
|
||||||
|
MEDIUM = "medium"
|
||||||
|
HIGH = "high"
|
||||||
|
|
||||||
|
|
||||||
class TessieClimateKeeper(StrEnum):
|
class TessieClimateKeeper(StrEnum):
|
||||||
"""Tessie Climate Keeper Modes."""
|
"""Tessie Climate Keeper Modes."""
|
||||||
|
|
||||||
|
|
58
homeassistant/components/tessie/select.py
Normal file
58
homeassistant/components/tessie/select.py
Normal file
|
@ -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))
|
|
@ -102,6 +102,71 @@
|
||||||
"name": "Passenger temperature setting"
|
"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": {
|
"binary_sensor": {
|
||||||
"state": {
|
"state": {
|
||||||
"name": "Status"
|
"name": "Status"
|
||||||
|
|
65
tests/components/tessie/test_select.py
Normal file
65
tests/components/tessie/test_select.py
Normal file
|
@ -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
|
Loading…
Add table
Reference in a new issue