mirror of
https://github.com/home-assistant/core.git
synced 2026-10-06 14:29:21 -04:00
Fix Teslemetry windows cover going unknown after restart (#183468)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""Cover platform for Teslemetry integration."""
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from itertools import chain
|
||||
from typing import Any, override
|
||||
from typing import Any, Self, override
|
||||
|
||||
from tesla_fleet_api import firmware_at_least
|
||||
from tesla_fleet_api.const import (
|
||||
@@ -23,7 +24,7 @@ from homeassistant.components.cover import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
|
||||
|
||||
from . import TeslemetryConfigEntry
|
||||
from .entity import (
|
||||
@@ -175,6 +176,26 @@ class TeslemetryVehiclePollingWindowEntity(
|
||||
self._attr_is_closed = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class TeslemetryWindowsExtraStoredData(ExtraStoredData):
|
||||
"""Per-window closed flags stored with the windows cover state."""
|
||||
|
||||
fd: bool | None
|
||||
fp: bool | None
|
||||
rd: bool | None
|
||||
rp: bool | None
|
||||
|
||||
@override
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Return a dict representation of the window flags."""
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, restored: dict[str, Any]) -> Self:
|
||||
"""Initialize the window flags from a dict."""
|
||||
return cls(restored["fd"], restored["fp"], restored["rd"], restored["rp"])
|
||||
|
||||
|
||||
class TeslemetryStreamingWindowEntity(
|
||||
TeslemetryVehicleStreamEntity, TeslemetryWindowEntity, CoverRestoreEntity
|
||||
):
|
||||
@@ -200,6 +221,15 @@ class TeslemetryStreamingWindowEntity(
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""When entity is added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
if (extra_data := await self.async_get_last_extra_data()) is not None:
|
||||
windows = TeslemetryWindowsExtraStoredData.from_dict(extra_data.as_dict())
|
||||
self.fd = windows.fd
|
||||
self.fp = windows.fp
|
||||
self.rd = windows.rd
|
||||
self.rp = windows.rp
|
||||
elif self._attr_is_closed:
|
||||
# Without stored flags, closed still means every window was closed
|
||||
self.fd = self.fp = self.rd = self.rp = True
|
||||
self.async_on_remove(
|
||||
self.stream.async_add_listener(
|
||||
self._handle_stream_update,
|
||||
@@ -218,6 +248,12 @@ class TeslemetryStreamingWindowEntity(
|
||||
f"Adding field {signal} to {self.vehicle.vin}",
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def extra_restore_state_data(self) -> TeslemetryWindowsExtraStoredData:
|
||||
"""Return the per-window flags to restore."""
|
||||
return TeslemetryWindowsExtraStoredData(self.fd, self.fp, self.rd, self.rp)
|
||||
|
||||
def _handle_stream_update(self, data: dict[str, Any]) -> None:
|
||||
"""Update the entity attributes."""
|
||||
|
||||
|
||||
@@ -17,10 +17,11 @@ from homeassistant.components.cover import (
|
||||
SERVICE_STOP_COVER,
|
||||
CoverState,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.restore_state import async_get as async_get_restore_state
|
||||
|
||||
from . import assert_entities, setup_platform
|
||||
from .const import (
|
||||
@@ -36,6 +37,10 @@ from .const import (
|
||||
VEHICLE_DATA_NONE,
|
||||
)
|
||||
|
||||
from tests.common import mock_restore_cache_with_extra_data
|
||||
|
||||
WINDOWS_ENTITY_ID = "cover.test_windows"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_cover(
|
||||
@@ -572,3 +577,77 @@ async def test_cover_streaming(
|
||||
assert hass.states.get("cover.test_charge_port_door").state == "unknown"
|
||||
assert hass.states.get("cover.test_frunk").state == "unknown"
|
||||
assert hass.states.get("cover.test_trunk").state == "unknown"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("restored_state", "extra_data", "expected_state", "expected_extra_data"),
|
||||
[
|
||||
pytest.param(
|
||||
CoverState.CLOSED,
|
||||
{"fd": True, "fp": True, "rd": True, "rp": True},
|
||||
CoverState.CLOSED,
|
||||
{"fd": True, "fp": True, "rd": True, "rp": True},
|
||||
id="closed",
|
||||
),
|
||||
pytest.param(
|
||||
CoverState.OPEN,
|
||||
{"fd": False, "fp": True, "rd": True, "rp": True},
|
||||
CoverState.CLOSED,
|
||||
{"fd": True, "fp": True, "rd": True, "rp": True},
|
||||
id="open_window_closes",
|
||||
),
|
||||
pytest.param(
|
||||
CoverState.OPEN,
|
||||
{"fd": False, "fp": False, "rd": True, "rp": True},
|
||||
CoverState.OPEN,
|
||||
{"fd": True, "fp": False, "rd": True, "rp": True},
|
||||
id="other_window_still_open",
|
||||
),
|
||||
pytest.param(
|
||||
CoverState.CLOSED,
|
||||
None,
|
||||
CoverState.CLOSED,
|
||||
{"fd": True, "fp": True, "rd": True, "rp": True},
|
||||
id="no_extra_data_closed",
|
||||
),
|
||||
pytest.param(
|
||||
CoverState.OPEN,
|
||||
None,
|
||||
STATE_UNKNOWN,
|
||||
{"fd": True, "fp": None, "rd": None, "rp": None},
|
||||
id="no_extra_data_open",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_cover_streaming_windows_restore(
|
||||
hass: HomeAssistant,
|
||||
mock_add_listener: AsyncMock,
|
||||
restored_state: CoverState,
|
||||
extra_data: dict[str, bool] | None,
|
||||
expected_state: str,
|
||||
expected_extra_data: dict[str, bool | None],
|
||||
) -> None:
|
||||
"""Tests the windows cover restores its per-window state."""
|
||||
|
||||
mock_restore_cache_with_extra_data(
|
||||
hass, ((State(WINDOWS_ENTITY_ID, restored_state), extra_data),)
|
||||
)
|
||||
|
||||
await setup_platform(hass, [Platform.COVER])
|
||||
assert hass.states.get(WINDOWS_ENTITY_ID).state == restored_state
|
||||
|
||||
mock_add_listener.send(
|
||||
{
|
||||
"vin": VEHICLE_DATA_ALT["response"]["vin"],
|
||||
"data": {Signal.FD_WINDOW: "WindowStateClosed"},
|
||||
"createdAt": "2024-10-04T10:45:17.537Z",
|
||||
}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(WINDOWS_ENTITY_ID).state == expected_state
|
||||
stored_states = {
|
||||
stored.state.entity_id: stored
|
||||
for stored in async_get_restore_state(hass).async_get_stored_states()
|
||||
}
|
||||
assert stored_states[WINDOWS_ENTITY_ID].extra_data.as_dict() == expected_extra_data
|
||||
|
||||
Reference in New Issue
Block a user