diff --git a/homeassistant/components/teslemetry/__init__.py b/homeassistant/components/teslemetry/__init__.py index 2d35720d1b4..285aff1d0cf 100644 --- a/homeassistant/components/teslemetry/__init__.py +++ b/homeassistant/components/teslemetry/__init__.py @@ -7,6 +7,7 @@ from typing import Final from tesla_fleet_api import EnergySpecific, Teslemetry, VehicleSpecific from tesla_fleet_api.const import Scope from tesla_fleet_api.exceptions import ( + Forbidden, InvalidToken, SubscriptionRequired, TeslaFleetError, @@ -163,10 +164,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) - serial_number=str(site_id), ) + # Check live status endpoint works before creating its coordinator + try: + live_status = (await api.live_status())["response"] + except (InvalidToken, Forbidden, SubscriptionRequired) as e: + raise ConfigEntryAuthFailed from e + except TeslaFleetError as e: + raise ConfigEntryNotReady(e.message) from e + energysites.append( TeslemetryEnergyData( api=api, - live_coordinator=TeslemetryEnergySiteLiveCoordinator(hass, api), + live_coordinator=( + TeslemetryEnergySiteLiveCoordinator(hass, api, live_status) + if isinstance(live_status, dict) + else None + ), info_coordinator=TeslemetryEnergySiteInfoCoordinator( hass, api, product ), @@ -187,10 +200,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) - vehicle.coordinator.async_config_entry_first_refresh() for vehicle in vehicles ), - *( - energysite.live_coordinator.async_config_entry_first_refresh() - for energysite in energysites - ), *( energysite.info_coordinator.async_config_entry_first_refresh() for energysite in energysites diff --git a/homeassistant/components/teslemetry/binary_sensor.py b/homeassistant/components/teslemetry/binary_sensor.py index 29ebfea4db1..e7016fe4a91 100644 --- a/homeassistant/components/teslemetry/binary_sensor.py +++ b/homeassistant/components/teslemetry/binary_sensor.py @@ -193,6 +193,7 @@ async def async_setup_entry( ( # Energy Site Live TeslemetryEnergyLiveBinarySensorEntity(energysite, description) for energysite in entry.runtime_data.energysites + if energysite.live_coordinator for description in ENERGY_LIVE_DESCRIPTIONS if energysite.info_coordinator.data.get("components_battery") ), diff --git a/homeassistant/components/teslemetry/coordinator.py b/homeassistant/components/teslemetry/coordinator.py index 303a3250edf..d39402c622c 100644 --- a/homeassistant/components/teslemetry/coordinator.py +++ b/homeassistant/components/teslemetry/coordinator.py @@ -69,7 +69,9 @@ class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]): """Class to manage fetching energy site live status from the Teslemetry API.""" - def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None: + updated_once: bool + + def __init__(self, hass: HomeAssistant, api: EnergySpecific, data: dict) -> None: """Initialize Teslemetry Energy Site Live coordinator.""" super().__init__( hass, @@ -79,6 +81,12 @@ class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]) ) self.api = api + # Convert Wall Connectors from array to dict + data["wall_connectors"] = { + wc["din"]: wc for wc in (data.get("wall_connectors") or []) + } + self.data = data + async def _async_update_data(self) -> dict[str, Any]: """Update energy site data using Teslemetry API.""" diff --git a/homeassistant/components/teslemetry/diagnostics.py b/homeassistant/components/teslemetry/diagnostics.py index 7e9c8a9a5b0..fc601a58ae6 100644 --- a/homeassistant/components/teslemetry/diagnostics.py +++ b/homeassistant/components/teslemetry/diagnostics.py @@ -41,7 +41,9 @@ async def async_get_config_entry_diagnostics( ] energysites = [ { - "live": async_redact_data(x.live_coordinator.data, ENERGY_LIVE_REDACT), + "live": async_redact_data(x.live_coordinator.data, ENERGY_LIVE_REDACT) + if x.live_coordinator + else None, "info": async_redact_data(x.info_coordinator.data, ENERGY_INFO_REDACT), } for x in entry.runtime_data.energysites diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index f2126dddf4b..5178c543f1a 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -136,6 +136,8 @@ class TeslemetryEnergyLiveEntity(TeslemetryEntity): ) -> None: """Initialize common aspects of a Teslemetry Energy Site Live entity.""" + assert data.live_coordinator + self.api = data.api self._attr_unique_id = f"{data.id}-{key}" self._attr_device_info = data.device @@ -195,6 +197,8 @@ class TeslemetryWallConnectorEntity(TeslemetryEntity): ) -> None: """Initialize common aspects of a Teslemetry entity.""" + assert data.live_coordinator + self.api = data.api self.din = din self._attr_unique_id = f"{data.id}-{din}-{key}" diff --git a/homeassistant/components/teslemetry/models.py b/homeassistant/components/teslemetry/models.py index c2f50ab90df..547bda4be9b 100644 --- a/homeassistant/components/teslemetry/models.py +++ b/homeassistant/components/teslemetry/models.py @@ -50,7 +50,7 @@ class TeslemetryEnergyData: """Data for a vehicle in the Teslemetry integration.""" api: EnergySpecific - live_coordinator: TeslemetryEnergySiteLiveCoordinator + live_coordinator: TeslemetryEnergySiteLiveCoordinator | None info_coordinator: TeslemetryEnergySiteInfoCoordinator history_coordinator: TeslemetryEnergyHistoryCoordinator | None id: int diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index cf4be6e8cda..524d8579703 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -523,6 +523,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Teslemetry sensor platform from a config entry.""" + entities: list[SensorEntity] = [] for vehicle in entry.runtime_data.vehicles: for description in VEHICLE_DESCRIPTIONS: @@ -551,6 +552,7 @@ async def async_setup_entry( entities.extend( TeslemetryEnergyLiveSensorEntity(energysite, description) for energysite in entry.runtime_data.energysites + if energysite.live_coordinator for description in ENERGY_LIVE_DESCRIPTIONS if description.key in energysite.live_coordinator.data ) @@ -558,6 +560,7 @@ async def async_setup_entry( entities.extend( TeslemetryWallConnectorSensorEntity(energysite, din, description) for energysite in entry.runtime_data.energysites + if energysite.live_coordinator for din in energysite.live_coordinator.data.get("wall_connectors", {}) for description in WALL_CONNECTOR_DESCRIPTIONS ) diff --git a/tests/components/teslemetry/test_init.py b/tests/components/teslemetry/test_init.py index 3794ffb93d8..5481e6cc034 100644 --- a/tests/components/teslemetry/test_init.py +++ b/tests/components/teslemetry/test_init.py @@ -179,3 +179,14 @@ async def test_vehicle_stream( state = hass.states.get("binary_sensor.test_status") assert state.state == STATE_OFF + + +async def test_no_live_status( + hass: HomeAssistant, + mock_live_status: AsyncMock, +) -> None: + """Test coordinator refresh with an error.""" + mock_live_status.side_effect = AsyncMock({"response": ""}) + await setup_platform(hass) + + assert hass.states.get("sensor.energy_site_grid_power") is None