diff --git a/homeassistant/components/tesla_fleet/coordinator.py b/homeassistant/components/tesla_fleet/coordinator.py index dad592d3033..42b93352a6f 100644 --- a/homeassistant/components/tesla_fleet/coordinator.py +++ b/homeassistant/components/tesla_fleet/coordinator.py @@ -13,6 +13,7 @@ from tesla_fleet_api.exceptions import ( TeslaFleetError, VehicleOffline, ) +from tesla_fleet_api.ratecalculator import RateCalculator from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed @@ -20,7 +21,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import LOGGER, TeslaFleetState -VEHICLE_INTERVAL_SECONDS = 120 +VEHICLE_INTERVAL_SECONDS = 90 VEHICLE_INTERVAL = timedelta(seconds=VEHICLE_INTERVAL_SECONDS) VEHICLE_WAIT = timedelta(minutes=15) @@ -56,6 +57,7 @@ class TeslaFleetVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): updated_once: bool pre2021: bool last_active: datetime + rate: RateCalculator def __init__( self, hass: HomeAssistant, api: VehicleSpecific, product: dict @@ -65,27 +67,31 @@ class TeslaFleetVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): hass, LOGGER, name="Tesla Fleet Vehicle", - update_interval=timedelta(seconds=5), + update_interval=VEHICLE_INTERVAL, ) self.api = api self.data = flatten(product) self.updated_once = False self.last_active = datetime.now() + self.rate = RateCalculator(200, 86400, VEHICLE_INTERVAL_SECONDS, 3600, 5) async def _async_update_data(self) -> dict[str, Any]: """Update vehicle data using TeslaFleet API.""" - self.update_interval = VEHICLE_INTERVAL - try: # Check if the vehicle is awake using a non-rate limited API call - state = (await self.api.vehicle())["response"] - if state and state["state"] != TeslaFleetState.ONLINE: - self.data["state"] = state["state"] + if self.data["state"] != TeslaFleetState.ONLINE: + response = await self.api.vehicle() + self.data["state"] = response["response"]["state"] + + if self.data["state"] != TeslaFleetState.ONLINE: return self.data # This is a rated limited API call - data = (await self.api.vehicle_data(endpoints=ENDPOINTS))["response"] + self.rate.consume() + response = await self.api.vehicle_data(endpoints=ENDPOINTS) + data = response["response"] + except VehicleOffline: self.data["state"] = TeslaFleetState.ASLEEP return self.data @@ -103,6 +109,9 @@ class TeslaFleetVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): except TeslaFleetError as e: raise UpdateFailed(e.message) from e + # Calculate ideal refresh interval + self.update_interval = timedelta(seconds=self.rate.calculate()) + self.updated_once = True if self.api.pre2021 and data["state"] == TeslaFleetState.ONLINE: diff --git a/homeassistant/components/tesla_fleet/manifest.json b/homeassistant/components/tesla_fleet/manifest.json index 310d8940432..2acacab5065 100644 --- a/homeassistant/components/tesla_fleet/manifest.json +++ b/homeassistant/components/tesla_fleet/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/tesla_fleet", "iot_class": "cloud_polling", "loggers": ["tesla-fleet-api"], - "requirements": ["tesla-fleet-api==0.7.2"] + "requirements": ["tesla-fleet-api==0.7.3"] } diff --git a/homeassistant/components/teslemetry/manifest.json b/homeassistant/components/teslemetry/manifest.json index e241c7c4ae4..1780d9f0a10 100644 --- a/homeassistant/components/teslemetry/manifest.json +++ b/homeassistant/components/teslemetry/manifest.json @@ -7,5 +7,5 @@ "iot_class": "cloud_polling", "loggers": ["tesla-fleet-api"], "quality_scale": "platinum", - "requirements": ["tesla-fleet-api==0.7.2"] + "requirements": ["tesla-fleet-api==0.7.3"] } diff --git a/homeassistant/components/tessie/manifest.json b/homeassistant/components/tessie/manifest.json index e1908350e91..6059072c239 100644 --- a/homeassistant/components/tessie/manifest.json +++ b/homeassistant/components/tessie/manifest.json @@ -7,5 +7,5 @@ "iot_class": "cloud_polling", "loggers": ["tessie"], "quality_scale": "platinum", - "requirements": ["tessie-api==0.1.1", "tesla-fleet-api==0.7.2"] + "requirements": ["tessie-api==0.1.1", "tesla-fleet-api==0.7.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0481c9e2a84..80c3834db5a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2723,7 +2723,7 @@ temperusb==1.6.1 # homeassistant.components.tesla_fleet # homeassistant.components.teslemetry # homeassistant.components.tessie -tesla-fleet-api==0.7.2 +tesla-fleet-api==0.7.3 # homeassistant.components.powerwall tesla-powerwall==0.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 37677cb05e4..79aa747b117 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2136,7 +2136,7 @@ temperusb==1.6.1 # homeassistant.components.tesla_fleet # homeassistant.components.teslemetry # homeassistant.components.tessie -tesla-fleet-api==0.7.2 +tesla-fleet-api==0.7.3 # homeassistant.components.powerwall tesla-powerwall==0.5.2 diff --git a/tests/components/tesla_fleet/conftest.py b/tests/components/tesla_fleet/conftest.py index 4e4d7b406d5..ade2f6eb0a9 100644 --- a/tests/components/tesla_fleet/conftest.py +++ b/tests/components/tesla_fleet/conftest.py @@ -2,9 +2,10 @@ from __future__ import annotations +from collections.abc import Generator from copy import deepcopy import time -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import jwt import pytest @@ -83,7 +84,7 @@ async def setup_credentials(hass: HomeAssistant) -> None: @pytest.fixture(autouse=True) -def mock_products(): +def mock_products() -> Generator[AsyncMock]: """Mock Tesla Fleet Api products method.""" with patch( "homeassistant.components.tesla_fleet.TeslaFleetApi.products", @@ -93,7 +94,7 @@ def mock_products(): @pytest.fixture(autouse=True) -def mock_vehicle_state(): +def mock_vehicle_state() -> Generator[AsyncMock]: """Mock Tesla Fleet API Vehicle Specific vehicle method.""" with patch( "homeassistant.components.tesla_fleet.VehicleSpecific.vehicle", @@ -103,7 +104,7 @@ def mock_vehicle_state(): @pytest.fixture(autouse=True) -def mock_vehicle_data(): +def mock_vehicle_data() -> Generator[AsyncMock]: """Mock Tesla Fleet API Vehicle Specific vehicle_data method.""" with patch( "homeassistant.components.tesla_fleet.VehicleSpecific.vehicle_data", @@ -113,7 +114,7 @@ def mock_vehicle_data(): @pytest.fixture(autouse=True) -def mock_wake_up(): +def mock_wake_up() -> Generator[AsyncMock]: """Mock Tesla Fleet API Vehicle Specific wake_up method.""" with patch( "homeassistant.components.tesla_fleet.VehicleSpecific.wake_up", @@ -123,7 +124,7 @@ def mock_wake_up(): @pytest.fixture(autouse=True) -def mock_live_status(): +def mock_live_status() -> Generator[AsyncMock]: """Mock Teslemetry Energy Specific live_status method.""" with patch( "homeassistant.components.tesla_fleet.EnergySpecific.live_status", @@ -133,7 +134,7 @@ def mock_live_status(): @pytest.fixture(autouse=True) -def mock_site_info(): +def mock_site_info() -> Generator[AsyncMock]: """Mock Teslemetry Energy Specific site_info method.""" with patch( "homeassistant.components.tesla_fleet.EnergySpecific.site_info", diff --git a/tests/components/tesla_fleet/test_init.py b/tests/components/tesla_fleet/test_init.py index 4e6352efc6b..20bb6c66906 100644 --- a/tests/components/tesla_fleet/test_init.py +++ b/tests/components/tesla_fleet/test_init.py @@ -1,5 +1,7 @@ """Test the Tesla Fleet init.""" +from unittest.mock import AsyncMock + from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion @@ -102,18 +104,17 @@ async def test_vehicle_refresh_offline( mock_vehicle_state.reset_mock() mock_vehicle_data.reset_mock() - # Test the unlikely condition that a vehicle state is online but actually offline + # Then the vehicle goes offline mock_vehicle_data.side_effect = VehicleOffline freezer.tick(VEHICLE_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() - mock_vehicle_state.assert_called_once() + mock_vehicle_state.assert_not_called() mock_vehicle_data.assert_called_once() - mock_vehicle_state.reset_mock() mock_vehicle_data.reset_mock() - # Test the normal condition that a vehcile state is offline + # And stays offline mock_vehicle_state.return_value = VEHICLE_ASLEEP freezer.tick(VEHICLE_INTERVAL) async_fire_time_changed(hass) @@ -127,15 +128,15 @@ async def test_vehicle_refresh_offline( async def test_vehicle_refresh_error( hass: HomeAssistant, normal_config_entry: MockConfigEntry, - mock_vehicle_state, - side_effect, + mock_vehicle_data: AsyncMock, + side_effect: TeslaFleetError, freezer: FrozenDateTimeFactory, ) -> None: """Test coordinator refresh makes entity unavailable.""" await setup_platform(hass, normal_config_entry) - mock_vehicle_state.side_effect = side_effect + mock_vehicle_data.side_effect = side_effect freezer.tick(VEHICLE_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done()