mirror of
https://github.com/home-assistant/core.git
synced 2026-05-04 11:54:35 +02:00
Fix Tesla update showing scheduled updates as installing (#158681)
This commit is contained in:
committed by
Bram Kragten
parent
d28d55c7db
commit
a697e63b8c
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from tesla_fleet_api.const import Scope
|
||||
@@ -24,6 +25,9 @@ SCHEDULED = "scheduled"
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
# Show scheduled update as installing if within this many seconds
|
||||
SCHEDULED_THRESHOLD_SECONDS = 120
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -69,12 +73,9 @@ class TeslaFleetUpdateEntity(TeslaFleetVehicleEntity, UpdateEntity):
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update the attributes of the entity."""
|
||||
|
||||
# Supported Features
|
||||
if self.scoped and self._value in (
|
||||
AVAILABLE,
|
||||
SCHEDULED,
|
||||
):
|
||||
# Only allow install when an update has been fully downloaded
|
||||
# Supported Features - only show install button if update is available
|
||||
# but not already scheduled
|
||||
if self.scoped and self._value == AVAILABLE:
|
||||
self._attr_supported_features = (
|
||||
UpdateEntityFeature.PROGRESS | UpdateEntityFeature.INSTALL
|
||||
)
|
||||
@@ -87,13 +88,9 @@ class TeslaFleetUpdateEntity(TeslaFleetVehicleEntity, UpdateEntity):
|
||||
# Remove build from version
|
||||
self._attr_installed_version = self._attr_installed_version.split(" ")[0]
|
||||
|
||||
# Latest Version
|
||||
if self._value in (
|
||||
AVAILABLE,
|
||||
SCHEDULED,
|
||||
INSTALLING,
|
||||
DOWNLOADING,
|
||||
WIFI_WAIT,
|
||||
# Latest Version - hide update if scheduled far in the future
|
||||
if self._value in (AVAILABLE, INSTALLING, DOWNLOADING, WIFI_WAIT) or (
|
||||
self._value == SCHEDULED and self._is_scheduled_soon()
|
||||
):
|
||||
self._attr_latest_version = self.coordinator.data[
|
||||
"vehicle_state_software_update_version"
|
||||
@@ -101,14 +98,24 @@ class TeslaFleetUpdateEntity(TeslaFleetVehicleEntity, UpdateEntity):
|
||||
else:
|
||||
self._attr_latest_version = self._attr_installed_version
|
||||
|
||||
# In Progress
|
||||
if self._value in (
|
||||
SCHEDULED,
|
||||
INSTALLING,
|
||||
):
|
||||
# In Progress - only show as installing if actually installing or
|
||||
# scheduled to start within 2 minutes
|
||||
if self._value == INSTALLING:
|
||||
self._attr_in_progress = True
|
||||
if install_perc := self.get("vehicle_state_software_update_install_perc"):
|
||||
self._attr_update_percentage = install_perc
|
||||
elif self._value == SCHEDULED and self._is_scheduled_soon():
|
||||
self._attr_in_progress = True
|
||||
self._attr_update_percentage = None
|
||||
else:
|
||||
self._attr_in_progress = False
|
||||
self._attr_update_percentage = None
|
||||
|
||||
def _is_scheduled_soon(self) -> bool:
|
||||
"""Check if a scheduled update is within the threshold to start."""
|
||||
scheduled_time_ms = self.get("vehicle_state_software_update_scheduled_time_ms")
|
||||
if scheduled_time_ms is None:
|
||||
return False
|
||||
# Convert milliseconds to seconds and compare to current time
|
||||
scheduled_time_sec = scheduled_time_ms / 1000
|
||||
return scheduled_time_sec - time.time() < SCHEDULED_THRESHOLD_SECONDS
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
"""Test the Tesla Fleet update platform."""
|
||||
|
||||
import copy
|
||||
import time
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.tesla_fleet.coordinator import VEHICLE_INTERVAL
|
||||
from homeassistant.components.tesla_fleet.update import INSTALLING
|
||||
from homeassistant.components.tesla_fleet.update import INSTALLING, SCHEDULED
|
||||
from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -19,6 +21,11 @@ from .const import COMMAND_OK, VEHICLE_DATA, VEHICLE_DATA_ALT
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
def _get_software_update(data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Get the software_update dict from vehicle data."""
|
||||
return data["response"]["vehicle_state"]["software_update"]
|
||||
|
||||
|
||||
async def test_update(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
@@ -70,14 +77,103 @@ async def test_update_services(
|
||||
)
|
||||
call.assert_called_once()
|
||||
|
||||
VEHICLE_INSTALLING = copy.deepcopy(VEHICLE_DATA)
|
||||
VEHICLE_INSTALLING["response"]["vehicle_state"]["software_update"]["status"] = ( # type: ignore[index]
|
||||
INSTALLING
|
||||
)
|
||||
mock_vehicle_data.return_value = VEHICLE_INSTALLING
|
||||
vehicle_installing = copy.deepcopy(VEHICLE_DATA)
|
||||
_get_software_update(vehicle_installing)["status"] = INSTALLING
|
||||
mock_vehicle_data.return_value = vehicle_installing
|
||||
freezer.tick(VEHICLE_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes["in_progress"] is True # type: ignore[union-attr]
|
||||
assert state is not None
|
||||
assert state.attributes["in_progress"] is True
|
||||
|
||||
|
||||
async def test_update_scheduled_far_future_not_in_progress(
|
||||
hass: HomeAssistant,
|
||||
normal_config_entry: MockConfigEntry,
|
||||
mock_vehicle_data: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Tests that a scheduled update far in the future is not shown as in_progress."""
|
||||
|
||||
await setup_platform(hass, normal_config_entry, [Platform.UPDATE])
|
||||
|
||||
entity_id = "update.test_update"
|
||||
|
||||
# Verify initial state (available) is not in_progress
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.attributes["in_progress"] is False
|
||||
|
||||
# Simulate update being scheduled for 1 hour in the future
|
||||
vehicle_scheduled = copy.deepcopy(VEHICLE_DATA)
|
||||
software_update = _get_software_update(vehicle_scheduled)
|
||||
software_update["status"] = SCHEDULED
|
||||
# Set scheduled time to 1 hour from now (well beyond threshold)
|
||||
software_update["scheduled_time_ms"] = int((time.time() + 3600) * 1000)
|
||||
mock_vehicle_data.return_value = vehicle_scheduled
|
||||
freezer.tick(VEHICLE_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Scheduled update far in future should NOT be in_progress
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.attributes["in_progress"] is False
|
||||
|
||||
|
||||
async def test_update_scheduled_soon_in_progress(
|
||||
hass: HomeAssistant,
|
||||
normal_config_entry: MockConfigEntry,
|
||||
mock_vehicle_data: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Tests that a scheduled update within threshold is shown as in_progress."""
|
||||
|
||||
await setup_platform(hass, normal_config_entry, [Platform.UPDATE])
|
||||
|
||||
entity_id = "update.test_update"
|
||||
|
||||
# Simulate update being scheduled within threshold (1 minute from now)
|
||||
vehicle_scheduled = copy.deepcopy(VEHICLE_DATA)
|
||||
software_update = _get_software_update(vehicle_scheduled)
|
||||
software_update["status"] = SCHEDULED
|
||||
# Set scheduled time to 1 minute from now (within 2 minute threshold)
|
||||
software_update["scheduled_time_ms"] = int((time.time() + 60) * 1000)
|
||||
mock_vehicle_data.return_value = vehicle_scheduled
|
||||
freezer.tick(VEHICLE_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Scheduled update within threshold should be in_progress
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.attributes["in_progress"] is True
|
||||
|
||||
|
||||
async def test_update_scheduled_no_time_not_in_progress(
|
||||
hass: HomeAssistant,
|
||||
normal_config_entry: MockConfigEntry,
|
||||
mock_vehicle_data: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Tests that a scheduled update without scheduled_time_ms is not in_progress."""
|
||||
|
||||
await setup_platform(hass, normal_config_entry, [Platform.UPDATE])
|
||||
|
||||
entity_id = "update.test_update"
|
||||
|
||||
# Simulate update being scheduled but without scheduled_time_ms
|
||||
vehicle_scheduled = copy.deepcopy(VEHICLE_DATA)
|
||||
_get_software_update(vehicle_scheduled)["status"] = SCHEDULED
|
||||
# No scheduled_time_ms field
|
||||
mock_vehicle_data.return_value = vehicle_scheduled
|
||||
freezer.tick(VEHICLE_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Scheduled update without time should NOT be in_progress
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.attributes["in_progress"] is False
|
||||
|
||||
Reference in New Issue
Block a user