Fix Teslemetry windows cover going unknown after restart (#183468)

This commit is contained in:
Brett Adams
2026-09-30 11:05:25 +02:00
committed by GitHub
parent 5e9452f76a
commit 2dfae25b65
2 changed files with 119 additions and 4 deletions
+38 -2
View File
@@ -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."""
+81 -2
View File
@@ -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