From 430052d3fa5654bd59fa2a5bd9f2e422efdbb39c Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 29 Jan 2024 06:35:15 +0000 Subject: [PATCH 1/5] Add loop limit and test coverage --- homeassistant/components/teslemetry/entity.py | 14 ++++++-- tests/components/teslemetry/test_climate.py | 32 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index c8fbc5910d8006..7eb81988715e00 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -3,6 +3,9 @@ import asyncio from typing import Any +from tesla_fleet_api.exceptions import TeslaFleetError + +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -45,11 +48,18 @@ def __init__( async def wake_up_if_asleep(self) -> None: """Wake up the vehicle if its asleep.""" async with self._wakelock: + wait = 0 while self.coordinator.data["state"] != TeslemetryState.ONLINE: - state = (await self.api.wake_up())["response"]["state"] + try: + state = (await self.api.wake_up())["response"]["state"] + except TeslaFleetError as err: + raise HomeAssistantError(str(err)) from err self.coordinator.data["state"] = state if state != TeslemetryState.ONLINE: - await asyncio.sleep(5) + wait += 5 + if wait >= 15: # Give up after 30 seconds total + raise HomeAssistantError("Could not wake up vehicle") + await asyncio.sleep(wait) def get(self, key: str | None = None, default: Any | None = None) -> Any: """Return a specific value from coordinator data.""" diff --git a/tests/components/teslemetry/test_climate.py b/tests/components/teslemetry/test_climate.py index ede38a695e20de..1ee00cb5fcb96c 100644 --- a/tests/components/teslemetry/test_climate.py +++ b/tests/components/teslemetry/test_climate.py @@ -26,6 +26,7 @@ from homeassistant.helpers import entity_registry as er from . import assert_entities, setup_platform +from .const import WAKE_UP_ASLEEP, WAKE_UP_ONLINE from tests.common import async_fire_time_changed @@ -108,7 +109,7 @@ async def test_errors( async def test_asleep_or_offline( - hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory + hass: HomeAssistant, mock_vehicle_data, mock_wake_up, freezer: FrozenDateTimeFactory ) -> None: """Tests asleep is handled.""" @@ -124,7 +125,34 @@ async def test_asleep_or_offline( await hass.async_block_till_done() mock_vehicle_data.assert_called_once() - # Run a command that will wake up the vehicle, but not immediately + # Run a command but fail trying to wake up the vehicle + mock_wake_up.side_effect = InvalidCommand + with pytest.raises(HomeAssistantError) as error: + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + assert error + mock_wake_up.side_effect = None + + # Run a command but timeout trying to wake up the vehicle + mock_wake_up.return_value = WAKE_UP_ASLEEP + with patch( + "homeassistant.components.teslemetry.entity.asyncio.sleep" + ), pytest.raises(HomeAssistantError) as error: + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + assert error + mock_wake_up.return_value = WAKE_UP_ONLINE + + # Run a command and wake up the vehicle immediately + await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: [entity_id]}, blocking=True ) From bd5b71527f9b04a2b534758e20e8f0856334808c Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 29 Jan 2024 06:58:07 +0000 Subject: [PATCH 2/5] Add assertions --- tests/components/teslemetry/test_climate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/components/teslemetry/test_climate.py b/tests/components/teslemetry/test_climate.py index 1ee00cb5fcb96c..62d67891d4fd51 100644 --- a/tests/components/teslemetry/test_climate.py +++ b/tests/components/teslemetry/test_climate.py @@ -124,6 +124,7 @@ async def test_asleep_or_offline( async_fire_time_changed(hass) await hass.async_block_till_done() mock_vehicle_data.assert_called_once() + mock_wake_up.reset_mock() # Run a command but fail trying to wake up the vehicle mock_wake_up.side_effect = InvalidCommand @@ -136,6 +137,8 @@ async def test_asleep_or_offline( ) assert error mock_wake_up.side_effect = None + mock_wake_up.assert_called_once() + mock_wake_up.reset_mock() # Run a command but timeout trying to wake up the vehicle mock_wake_up.return_value = WAKE_UP_ASLEEP @@ -149,11 +152,13 @@ async def test_asleep_or_offline( blocking=True, ) assert error + mock_wake_up.assert_called() + mock_wake_up.reset_mock() mock_wake_up.return_value = WAKE_UP_ONLINE # Run a command and wake up the vehicle immediately - await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: [entity_id]}, blocking=True ) await hass.async_block_till_done() + mock_wake_up.assert_called_once() From d5f061e8d0e5fee43e3a2f4eba665345461c597d Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Fri, 16 Feb 2024 08:21:33 +1000 Subject: [PATCH 3/5] Update entity.py --- homeassistant/components/teslemetry/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index 0a39eecc6cde82..d39772f6864a24 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -57,7 +57,7 @@ async def wake_up_if_asleep(self) -> None: self.coordinator.data["state"] = state if state != TeslemetryState.ONLINE: wait += 5 - if wait >= 15: # Give up after 30 seconds total + if wait >= 20: # Give up after 45 seconds total raise HomeAssistantError("Could not wake up vehicle") await asyncio.sleep(wait) From fab2be2fc5e6fdf2822a1a9e448da560f0fe2318 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 22 Feb 2024 02:38:44 +0000 Subject: [PATCH 4/5] Improved wake_up --- homeassistant/components/teslemetry/entity.py | 20 ++++++++++++------- tests/components/teslemetry/conftest.py | 10 ++++++++++ tests/components/teslemetry/test_climate.py | 16 ++++++++++++--- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index d39772f6864a24..2d67e3d2389339 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -48,18 +48,24 @@ def __init__( async def wake_up_if_asleep(self) -> None: """Wake up the vehicle if its asleep.""" async with self._wakelock: - wait = 0 + times = 0 while self.coordinator.data["state"] != TeslemetryState.ONLINE: try: - state = (await self.api.wake_up())["response"]["state"] - except TeslaFleetError as err: - raise HomeAssistantError(str(err)) from err + if times == 0: + cmd = await self.api.wake_up() + else: + cmd = await self.api.vehicle() + state = cmd["response"]["state"] + except TeslaFleetError as e: + raise HomeAssistantError(str(e)) from e + except TypeError as e: + raise HomeAssistantError("Invalid response from Teslemetry") from e self.coordinator.data["state"] = state if state != TeslemetryState.ONLINE: - wait += 5 - if wait >= 20: # Give up after 45 seconds total + times += 1 + if times >= 4: # Give up after 30 seconds total raise HomeAssistantError("Could not wake up vehicle") - await asyncio.sleep(wait) + await asyncio.sleep(times * 5) def get(self, key: str | None = None, default: Any | None = None) -> Any: """Return a specific value from coordinator data.""" diff --git a/tests/components/teslemetry/conftest.py b/tests/components/teslemetry/conftest.py index 0fc279eaa215a0..8c1fe070dde913 100644 --- a/tests/components/teslemetry/conftest.py +++ b/tests/components/teslemetry/conftest.py @@ -37,6 +37,16 @@ def mock_wake_up(): yield mock_wake_up +@pytest.fixture(autouse=True) +def mock_vehicle(): + """Mock Tesla Fleet API Vehicle Specific vehicle method.""" + with patch( + "homeassistant.components.teslemetry.VehicleSpecific.vehicle", + return_value=WAKE_UP_ONLINE, + ) as mock_vehicle: + yield mock_vehicle + + @pytest.fixture(autouse=True) def mock_request(): """Mock Tesla Fleet API Vehicle Specific class.""" diff --git a/tests/components/teslemetry/test_climate.py b/tests/components/teslemetry/test_climate.py index 62d67891d4fd51..2e791f68b93598 100644 --- a/tests/components/teslemetry/test_climate.py +++ b/tests/components/teslemetry/test_climate.py @@ -109,7 +109,11 @@ async def test_errors( async def test_asleep_or_offline( - hass: HomeAssistant, mock_vehicle_data, mock_wake_up, freezer: FrozenDateTimeFactory + hass: HomeAssistant, + mock_vehicle_data, + mock_wake_up, + mock_vehicle, + freezer: FrozenDateTimeFactory, ) -> None: """Tests asleep is handled.""" @@ -136,12 +140,14 @@ async def test_asleep_or_offline( blocking=True, ) assert error - mock_wake_up.side_effect = None mock_wake_up.assert_called_once() + + mock_wake_up.side_effect = None mock_wake_up.reset_mock() # Run a command but timeout trying to wake up the vehicle mock_wake_up.return_value = WAKE_UP_ASLEEP + mock_vehicle.return_value = WAKE_UP_ASLEEP with patch( "homeassistant.components.teslemetry.entity.asyncio.sleep" ), pytest.raises(HomeAssistantError) as error: @@ -152,9 +158,13 @@ async def test_asleep_or_offline( blocking=True, ) assert error - mock_wake_up.assert_called() + mock_wake_up.assert_called_once() + mock_vehicle.assert_called() + mock_wake_up.reset_mock() + mock_vehicle.reset_mock() mock_wake_up.return_value = WAKE_UP_ONLINE + mock_vehicle.return_value = WAKE_UP_ONLINE # Run a command and wake up the vehicle immediately await hass.services.async_call( From 78d31da8cbadfb3f17c372e2ac5320320327066f Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 22 Feb 2024 09:23:10 +0000 Subject: [PATCH 5/5] Remove extra catch --- homeassistant/components/teslemetry/entity.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index 2d67e3d2389339..024d0603e7e835 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -58,8 +58,6 @@ async def wake_up_if_asleep(self) -> None: state = cmd["response"]["state"] except TeslaFleetError as e: raise HomeAssistantError(str(e)) from e - except TypeError as e: - raise HomeAssistantError("Invalid response from Teslemetry") from e self.coordinator.data["state"] = state if state != TeslemetryState.ONLINE: times += 1