mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Move Trunks from Button to Cover in Tessie (#106448)
* Move trunks from buttons to covers * Add tests * Cleanup snapshot * Use Constants * StrEnum to IntEnum
This commit is contained in:
parent
2fe982c7f3
commit
91aea843fc
@ -9,8 +9,6 @@ from tessie_api import (
|
||||
enable_keyless_driving,
|
||||
flash_lights,
|
||||
honk,
|
||||
open_close_rear_trunk,
|
||||
open_front_trunk,
|
||||
trigger_homelink,
|
||||
wake,
|
||||
)
|
||||
@ -49,12 +47,6 @@ DESCRIPTIONS: tuple[TessieButtonEntityDescription, ...] = (
|
||||
TessieButtonEntityDescription(
|
||||
key="boombox", func=lambda: boombox, icon="mdi:volume-high"
|
||||
),
|
||||
TessieButtonEntityDescription(
|
||||
key="frunk", func=lambda: open_front_trunk, icon="mdi:car"
|
||||
),
|
||||
TessieButtonEntityDescription(
|
||||
key="trunk", func=lambda: open_close_rear_trunk, icon="mdi:car-back"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Constants used by Tessie integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from enum import IntEnum, StrEnum
|
||||
|
||||
DOMAIN = "tessie"
|
||||
|
||||
@ -46,3 +46,10 @@ class TessieUpdateStatus(StrEnum):
|
||||
INSTALLING = "installing"
|
||||
WIFI_WAIT = "downloading_wifi_wait"
|
||||
SCHEDULED = "scheduled"
|
||||
|
||||
|
||||
class TessieCoverStates(IntEnum):
|
||||
"""Tessie Cover states."""
|
||||
|
||||
CLOSED = 0
|
||||
OPEN = 1
|
||||
|
@ -6,6 +6,8 @@ from typing import Any
|
||||
from tessie_api import (
|
||||
close_charge_port,
|
||||
close_windows,
|
||||
open_close_rear_trunk,
|
||||
open_front_trunk,
|
||||
open_unlock_charge_port,
|
||||
vent_windows,
|
||||
)
|
||||
@ -19,7 +21,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, TessieCoverStates
|
||||
from .coordinator import TessieStateUpdateCoordinator
|
||||
from .entity import TessieEntity
|
||||
|
||||
@ -31,10 +33,12 @@ async def async_setup_entry(
|
||||
data = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
Entity(vehicle.state_coordinator)
|
||||
for Entity in (
|
||||
klass(vehicle.state_coordinator)
|
||||
for klass in (
|
||||
TessieWindowEntity,
|
||||
TessieChargePortEntity,
|
||||
TessieFrontTrunkEntity,
|
||||
TessieRearTrunkEntity,
|
||||
)
|
||||
for vehicle in data
|
||||
)
|
||||
@ -54,30 +58,30 @@ class TessieWindowEntity(TessieEntity, CoverEntity):
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed or not."""
|
||||
return (
|
||||
self.get("vehicle_state_fd_window") == 0
|
||||
and self.get("vehicle_state_fp_window") == 0
|
||||
and self.get("vehicle_state_rd_window") == 0
|
||||
and self.get("vehicle_state_rp_window") == 0
|
||||
self.get("vehicle_state_fd_window") == TessieCoverStates.CLOSED
|
||||
and self.get("vehicle_state_fp_window") == TessieCoverStates.CLOSED
|
||||
and self.get("vehicle_state_rd_window") == TessieCoverStates.CLOSED
|
||||
and self.get("vehicle_state_rp_window") == TessieCoverStates.CLOSED
|
||||
)
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open windows."""
|
||||
await self.run(vent_windows)
|
||||
self.set(
|
||||
("vehicle_state_fd_window", 1),
|
||||
("vehicle_state_fp_window", 1),
|
||||
("vehicle_state_rd_window", 1),
|
||||
("vehicle_state_rp_window", 1),
|
||||
("vehicle_state_fd_window", TessieCoverStates.OPEN),
|
||||
("vehicle_state_fp_window", TessieCoverStates.OPEN),
|
||||
("vehicle_state_rd_window", TessieCoverStates.OPEN),
|
||||
("vehicle_state_rp_window", TessieCoverStates.OPEN),
|
||||
)
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Close windows."""
|
||||
await self.run(close_windows)
|
||||
self.set(
|
||||
("vehicle_state_fd_window", 0),
|
||||
("vehicle_state_fp_window", 0),
|
||||
("vehicle_state_rd_window", 0),
|
||||
("vehicle_state_rp_window", 0),
|
||||
("vehicle_state_fd_window", TessieCoverStates.CLOSED),
|
||||
("vehicle_state_fp_window", TessieCoverStates.CLOSED),
|
||||
("vehicle_state_rd_window", TessieCoverStates.CLOSED),
|
||||
("vehicle_state_rp_window", TessieCoverStates.CLOSED),
|
||||
)
|
||||
|
||||
|
||||
@ -105,3 +109,52 @@ class TessieChargePortEntity(TessieEntity, CoverEntity):
|
||||
"""Close windows."""
|
||||
await self.run(close_charge_port)
|
||||
self.set((self.key, False))
|
||||
|
||||
|
||||
class TessieFrontTrunkEntity(TessieEntity, CoverEntity):
|
||||
"""Cover entity for the charge port."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.DOOR
|
||||
_attr_supported_features = CoverEntityFeature.OPEN
|
||||
|
||||
def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator, "vehicle_state_ft")
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed or not."""
|
||||
return self._value == TessieCoverStates.CLOSED
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open front trunk."""
|
||||
await self.run(open_front_trunk)
|
||||
self.set((self.key, TessieCoverStates.OPEN))
|
||||
|
||||
|
||||
class TessieRearTrunkEntity(TessieEntity, CoverEntity):
|
||||
"""Cover entity for the charge port."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.DOOR
|
||||
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||
|
||||
def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator, "vehicle_state_rt")
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed or not."""
|
||||
return self._value == TessieCoverStates.CLOSED
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open rear trunk."""
|
||||
if self._value == TessieCoverStates.CLOSED:
|
||||
await self.run(open_close_rear_trunk)
|
||||
self.set((self.key, TessieCoverStates.OPEN))
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Close rear trunk."""
|
||||
if self._value == TessieCoverStates.OPEN:
|
||||
await self.run(open_close_rear_trunk)
|
||||
self.set((self.key, TessieCoverStates.CLOSED))
|
||||
|
@ -123,7 +123,9 @@
|
||||
},
|
||||
"charge_state_charge_port_door_open": {
|
||||
"name": "Charge port door"
|
||||
}
|
||||
},
|
||||
"vehicle_state_ft": { "name": "Frunk" },
|
||||
"vehicle_state_rt": { "name": "Trunk" }
|
||||
},
|
||||
"select": {
|
||||
"climate_state_seat_heater_left": {
|
||||
@ -267,9 +269,7 @@
|
||||
"honk": { "name": "Honk horn" },
|
||||
"trigger_homelink": { "name": "Homelink" },
|
||||
"enable_keyless_driving": { "name": "Keyless driving" },
|
||||
"boombox": { "name": "Play fart" },
|
||||
"frunk": { "name": "Open frunk" },
|
||||
"trunk": { "name": "Open/Close trunk" }
|
||||
"boombox": { "name": "Play fart" }
|
||||
},
|
||||
"switch": {
|
||||
"charge_state_charge_enable_request": {
|
||||
|
57
tests/components/tessie/snapshots/test_cover.ambr
Normal file
57
tests/components/tessie/snapshots/test_cover.ambr
Normal file
@ -0,0 +1,57 @@
|
||||
# serializer version: 1
|
||||
# name: test_covers[cover.test_charge_port_door-open_unlock_charge_port-close_charge_port][cover.test_charge_port_door]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'Test Charge port door',
|
||||
'supported_features': <CoverEntityFeature: 3>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.test_charge_port_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'open',
|
||||
})
|
||||
# ---
|
||||
# name: test_covers[cover.test_frunk-open_front_trunk-False][cover.test_frunk]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'Test Frunk',
|
||||
'supported_features': <CoverEntityFeature: 1>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.test_frunk',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_covers[cover.test_trunk-open_close_rear_trunk-open_close_rear_trunk][cover.test_trunk]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'Test Trunk',
|
||||
'supported_features': <CoverEntityFeature: 3>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.test_trunk',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_covers[cover.test_vent_windows-vent_windows-close_windows][cover.test_vent_windows]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'window',
|
||||
'friendly_name': 'Test Vent windows',
|
||||
'supported_features': <CoverEntityFeature: 3>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.test_vent_windows',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
@ -2,6 +2,7 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
DOMAIN as COVER_DOMAIN,
|
||||
@ -17,78 +18,57 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from .common import ERROR_UNKNOWN, TEST_RESPONSE, TEST_RESPONSE_ERROR, setup_platform
|
||||
|
||||
|
||||
async def test_window(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "openfunc", "closefunc"),
|
||||
[
|
||||
("cover.test_vent_windows", "vent_windows", "close_windows"),
|
||||
("cover.test_charge_port_door", "open_unlock_charge_port", "close_charge_port"),
|
||||
("cover.test_frunk", "open_front_trunk", False),
|
||||
("cover.test_trunk", "open_close_rear_trunk", "open_close_rear_trunk"),
|
||||
],
|
||||
)
|
||||
async def test_covers(
|
||||
hass: HomeAssistant,
|
||||
entity_id: str,
|
||||
openfunc: str,
|
||||
closefunc: str,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Tests that the window cover entity is correct."""
|
||||
|
||||
await setup_platform(hass)
|
||||
|
||||
entity_id = "cover.test_vent_windows"
|
||||
assert hass.states.get(entity_id).state == STATE_CLOSED
|
||||
assert hass.states.get(entity_id) == snapshot(name=entity_id)
|
||||
|
||||
# Test open windows
|
||||
with patch(
|
||||
"homeassistant.components.tessie.cover.vent_windows",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_set:
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_OPEN_COVER,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_set.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == STATE_OPEN
|
||||
if openfunc:
|
||||
with patch(
|
||||
f"homeassistant.components.tessie.cover.{openfunc}",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_open:
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_OPEN_COVER,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_open.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == STATE_OPEN
|
||||
|
||||
# Test close windows
|
||||
with patch(
|
||||
"homeassistant.components.tessie.cover.close_windows",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_set:
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_CLOSE_COVER,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_set.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == STATE_CLOSED
|
||||
|
||||
|
||||
async def test_charge_port(hass: HomeAssistant) -> None:
|
||||
"""Tests that the charge port cover entity is correct."""
|
||||
|
||||
await setup_platform(hass)
|
||||
|
||||
entity_id = "cover.test_charge_port_door"
|
||||
assert hass.states.get(entity_id).state == STATE_OPEN
|
||||
|
||||
# Test close charge port
|
||||
with patch(
|
||||
"homeassistant.components.tessie.cover.close_charge_port",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_set:
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_CLOSE_COVER,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_set.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == STATE_CLOSED
|
||||
|
||||
# Test open charge port
|
||||
with patch(
|
||||
"homeassistant.components.tessie.cover.open_unlock_charge_port",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_set:
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_OPEN_COVER,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_set.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == STATE_OPEN
|
||||
if closefunc:
|
||||
with patch(
|
||||
f"homeassistant.components.tessie.cover.{closefunc}",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_close:
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_CLOSE_COVER,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_close.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == STATE_CLOSED
|
||||
|
||||
|
||||
async def test_errors(hass: HomeAssistant) -> None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user