1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 08:26:41 +01:00

Source Tessie phantom drain and battery sensors from state data (#165970)

This commit is contained in:
Brett Adams
2026-03-19 17:24:32 +10:00
committed by GitHub
parent 6a1e7c1cca
commit 886b6b08ac
14 changed files with 83 additions and 308 deletions

View File

@@ -13,7 +13,7 @@ from tesla_fleet_api.exceptions import (
TeslaFleetError,
)
from tesla_fleet_api.tessie import Tessie
from tessie_api import get_battery, get_state_of_all_vehicles
from tessie_api import get_state_of_all_vehicles
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN, Platform
@@ -28,7 +28,6 @@ from homeassistant.helpers.device_registry import DeviceInfo
from .const import DOMAIN, MODELS
from .coordinator import (
TessieBatteryHealthCoordinator,
TessieEnergyHistoryCoordinator,
TessieEnergySiteInfoCoordinator,
TessieEnergySiteLiveCoordinator,
@@ -74,25 +73,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
except ClientError as e:
raise ConfigEntryNotReady from e
try:
batteries = await asyncio.gather(
*(
get_battery(
session=session,
api_key=api_key,
vin=vehicle["vin"],
)
for vehicle in state_of_all_vehicles["results"]
if vehicle["last_state"] is not None
)
)
except ClientResponseError as e:
if e.status == HTTPStatus.UNAUTHORIZED:
raise ConfigEntryAuthFailed from e
raise ConfigEntryError("Setup failed, unable to get battery data") from e
except ClientError as e:
raise ConfigEntryNotReady from e
vehicles = [
TessieVehicleData(
vin=vehicle["vin"],
@@ -103,13 +83,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
vin=vehicle["vin"],
data=vehicle["last_state"],
),
battery_coordinator=TessieBatteryHealthCoordinator(
hass,
entry,
api_key=api_key,
vin=vehicle["vin"],
data=battery,
),
device=DeviceInfo(
identifiers={(DOMAIN, vehicle["vin"])},
manufacturer="Tesla",
@@ -126,15 +99,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
serial_number=vehicle["vin"],
),
)
for vehicle, battery in zip(
(
v
for v in state_of_all_vehicles["results"]
if v["last_state"] is not None
),
batteries,
strict=True,
)
for vehicle in state_of_all_vehicles["results"]
if vehicle["last_state"] is not None
]
# Energy Sites

View File

@@ -11,7 +11,7 @@ from aiohttp import ClientResponseError
from tesla_fleet_api.const import TeslaEnergyPeriod
from tesla_fleet_api.exceptions import InvalidToken, MissingToken, TeslaFleetError
from tesla_fleet_api.tessie import EnergySite
from tessie_api import get_battery, get_state
from tessie_api import get_state
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
@@ -87,48 +87,6 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
return flatten(vehicle)
class TessieBatteryHealthCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching battery health data from the Tessie API."""
config_entry: TessieConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: TessieConfigEntry,
api_key: str,
vin: str,
data: dict[str, Any],
) -> None:
"""Initialize Tessie Battery Health coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name="Tessie Battery Health",
update_interval=timedelta(seconds=TESSIE_SYNC_INTERVAL),
)
self.api_key = api_key
self.vin = vin
self.session = async_get_clientsession(hass)
self.data = data
async def _async_update_data(self) -> dict[str, Any]:
"""Update battery health data using Tessie API."""
try:
data = await get_battery(
session=self.session,
api_key=self.api_key,
vin=self.vin,
)
except ClientResponseError as e:
if e.status == HTTPStatus.UNAUTHORIZED:
raise ConfigEntryAuthFailed from e
raise UpdateFailed from e
return data
class TessieEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching energy site live status from the Tessie API."""

View File

@@ -35,7 +35,6 @@ async def async_get_config_entry_diagnostics(
vehicles = [
{
"data": async_redact_data(x.data_coordinator.data, VEHICLE_REDACT),
"battery": x.battery_coordinator.data,
}
for x in entry.runtime_data.vehicles
]

View File

@@ -12,7 +12,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, TRANSLATED_ERRORS
from .coordinator import (
TessieBatteryHealthCoordinator,
TessieEnergyHistoryCoordinator,
TessieEnergySiteInfoCoordinator,
TessieEnergySiteLiveCoordinator,
@@ -24,7 +23,6 @@ from .models import TessieEnergyData, TessieVehicleData
class TessieBaseEntity(
CoordinatorEntity[
TessieStateUpdateCoordinator
| TessieBatteryHealthCoordinator
| TessieEnergySiteInfoCoordinator
| TessieEnergySiteLiveCoordinator
| TessieEnergyHistoryCoordinator
@@ -37,15 +35,16 @@ class TessieBaseEntity(
def __init__(
self,
coordinator: TessieStateUpdateCoordinator
| TessieBatteryHealthCoordinator
| TessieEnergySiteInfoCoordinator
| TessieEnergySiteLiveCoordinator
| TessieEnergyHistoryCoordinator,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie entity."""
self.key = key
self.data_key = data_key or key
self._attr_translation_key = key
super().__init__(coordinator)
self._async_update_attrs()
@@ -53,11 +52,11 @@ class TessieBaseEntity(
@property
def _value(self) -> Any:
"""Return value from coordinator data."""
return self.coordinator.data.get(self.key)
return self.coordinator.data.get(self.data_key)
def get(self, key: str | None = None, default: Any | None = None) -> Any:
"""Return a specific value from coordinator data."""
return self.coordinator.data.get(key or self.key, default)
return self.coordinator.data.get(key or self.data_key, default)
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
@@ -76,6 +75,7 @@ class TessieEntity(TessieBaseEntity):
self,
vehicle: TessieVehicleData,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie vehicle entity."""
self.vin = vehicle.vin
@@ -84,12 +84,7 @@ class TessieEntity(TessieBaseEntity):
self._attr_unique_id = f"{vehicle.vin}-{key}"
self._attr_device_info = vehicle.device
super().__init__(vehicle.data_coordinator, key)
@property
def _value(self) -> Any:
"""Return value from coordinator data."""
return self.coordinator.data.get(self.key)
super().__init__(vehicle.data_coordinator, key, data_key)
def set(self, *args: Any) -> None:
"""Set a value in coordinator data."""
@@ -133,29 +128,14 @@ class TessieEnergyEntity(TessieBaseEntity):
data: TessieEnergyData,
coordinator: TessieEnergySiteInfoCoordinator | TessieEnergySiteLiveCoordinator,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie energy site entity."""
self.api = data.api
self._attr_unique_id = f"{data.id}-{key}"
self._attr_device_info = data.device
super().__init__(coordinator, key)
class TessieBatteryEntity(TessieBaseEntity):
"""Parent class for Tessie battery health entities."""
def __init__(
self,
vehicle: TessieVehicleData,
key: str,
) -> None:
"""Initialize common aspects of a Tessie battery health entity."""
self.vin = vehicle.vin
self._attr_unique_id = f"{vehicle.vin}-{key}"
self._attr_device_info = vehicle.device
super().__init__(vehicle.battery_coordinator, key)
super().__init__(coordinator, key, data_key)
class TessieEnergyHistoryEntity(TessieBaseEntity):
@@ -165,13 +145,14 @@ class TessieEnergyHistoryEntity(TessieBaseEntity):
self,
data: TessieEnergyData,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Tessie energy history entity."""
self.api = data.api
self._attr_unique_id = f"{data.id}-{key}"
self._attr_device_info = data.device
assert data.history_coordinator
super().__init__(data.history_coordinator, key)
super().__init__(data.history_coordinator, key, data_key)
class TessieWallConnectorEntity(TessieBaseEntity):
@@ -182,6 +163,7 @@ class TessieWallConnectorEntity(TessieBaseEntity):
data: TessieEnergyData,
din: str,
key: str,
data_key: str | None = None,
) -> None:
"""Initialize common aspects of a Teslemetry entity."""
self.din = din
@@ -194,7 +176,7 @@ class TessieWallConnectorEntity(TessieBaseEntity):
serial_number=din.rsplit("-", maxsplit=1)[-1],
)
assert data.live_coordinator
super().__init__(data.live_coordinator, key)
super().__init__(data.live_coordinator, key, data_key)
@property
def _value(self) -> int:

View File

@@ -9,7 +9,6 @@ from tesla_fleet_api.tessie import EnergySite
from homeassistant.helpers.device_registry import DeviceInfo
from .coordinator import (
TessieBatteryHealthCoordinator,
TessieEnergyHistoryCoordinator,
TessieEnergySiteInfoCoordinator,
TessieEnergySiteLiveCoordinator,
@@ -42,6 +41,5 @@ class TessieVehicleData:
"""Data for a Tessie vehicle."""
data_coordinator: TessieStateUpdateCoordinator
battery_coordinator: TessieBatteryHealthCoordinator
device: DeviceInfo
vin: str

View File

@@ -41,7 +41,6 @@ from .const import (
TessieWallConnectorStates,
)
from .entity import (
TessieBatteryEntity,
TessieEnergyEntity,
TessieEnergyHistoryEntity,
TessieEntity,
@@ -62,6 +61,7 @@ def minutes_to_datetime(value: StateType) -> datetime | None:
class TessieSensorEntityDescription(SensorEntityDescription):
"""Describes Tessie Sensor entity."""
data_key: str | None = None
value_fn: Callable[[StateType], StateType | datetime] = lambda x: x
available_fn: Callable[[StateType], bool] = lambda _: True
@@ -142,6 +142,14 @@ DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
TessieSensorEntityDescription(
key="phantom_drain_percent",
data_key="charge_state_phantom_drain",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=2,
),
TessieSensorEntityDescription(
key="charge_state_energy_remaining",
state_class=SensorStateClass.MEASUREMENT,
@@ -150,6 +158,51 @@ DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=2,
),
TessieSensorEntityDescription(
key="lifetime_energy_used",
data_key="charge_state_lifetime_energy_used",
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="pack_current",
data_key="charge_state_pack_current",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
device_class=SensorDeviceClass.CURRENT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="pack_voltage",
data_key="charge_state_pack_voltage",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="module_temp_min",
data_key="charge_state_module_temp_min",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="module_temp_max",
data_key="charge_state_module_temp_max",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="charge_state_conn_charge_cable",
entity_category=EntityCategory.DIAGNOSTIC,
@@ -290,57 +343,6 @@ DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
),
)
BATTERY_DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
TessieSensorEntityDescription(
key="phantom_drain_percent",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=2,
),
TessieSensorEntityDescription(
key="lifetime_energy_used",
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="pack_current",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
device_class=SensorDeviceClass.CURRENT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="pack_voltage",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="module_temp_min",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
TessieSensorEntityDescription(
key="module_temp_max",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=1,
),
)
ENERGY_LIVE_DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
TessieSensorEntityDescription(
key="solar_power",
@@ -494,12 +496,6 @@ async def async_setup_entry(
for vehicle in entry.runtime_data.vehicles
for description in DESCRIPTIONS
),
( # Add vehicle battery health
TessieBatteryHealthSensorEntity(vehicle, description)
for vehicle in entry.runtime_data.vehicles
for description in BATTERY_DESCRIPTIONS
if description.key in vehicle.battery_coordinator.data
),
( # Add energy site info
TessieEnergyInfoSensorEntity(energysite, description)
for energysite in entry.runtime_data.energysites
@@ -545,7 +541,7 @@ class TessieVehicleSensorEntity(TessieEntity, SensorEntity):
) -> None:
"""Initialize the sensor."""
self.entity_description = description
super().__init__(vehicle, description.key)
super().__init__(vehicle, description.key, description.data_key)
@property
def native_value(self) -> StateType | datetime:
@@ -558,25 +554,6 @@ class TessieVehicleSensorEntity(TessieEntity, SensorEntity):
return super().available and self.entity_description.available_fn(self.get())
class TessieBatteryHealthSensorEntity(TessieBatteryEntity, SensorEntity):
"""Sensor entity for Tessie battery health data."""
entity_description: TessieSensorEntityDescription
def __init__(
self,
vehicle: TessieVehicleData,
description: TessieSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
self.entity_description = description
super().__init__(vehicle, description.key)
def _async_update_attrs(self) -> None:
"""Update the attributes of the sensor."""
self._attr_native_value = self.entity_description.value_fn(self._value)
class TessieEnergyLiveSensorEntity(TessieEnergyEntity, SensorEntity):
"""Base class for Tessie energy site sensor entity."""

View File

@@ -19,7 +19,6 @@ from tests.common import MockConfigEntry, load_json_object_fixture
# Tessie library
TEST_STATE_OF_ALL_VEHICLES = load_json_object_fixture("vehicles.json", DOMAIN)
TEST_VEHICLE_STATE_ONLINE = load_json_object_fixture("online.json", DOMAIN)
TEST_VEHICLE_BATTERY = load_json_object_fixture("battery.json", DOMAIN)
TEST_RESPONSE = {"result": True}
TEST_RESPONSE_ERROR = {"result": False, "reason": "reason_why"}

View File

@@ -15,7 +15,6 @@ from .common import (
SCOPES,
SITE_INFO,
TEST_STATE_OF_ALL_VEHICLES,
TEST_VEHICLE_BATTERY,
TEST_VEHICLE_STATE_ONLINE,
)
@@ -32,22 +31,6 @@ def mock_get_state():
yield mock_get_state
@pytest.fixture(autouse=True)
def mock_get_battery():
"""Mock get_battery function."""
with (
patch(
"homeassistant.components.tessie.get_battery",
return_value=TEST_VEHICLE_BATTERY,
) as mock_get_battery,
patch(
"homeassistant.components.tessie.coordinator.get_battery",
new=mock_get_battery,
),
):
yield mock_get_battery
@pytest.fixture(autouse=True)
def mock_get_state_of_all_vehicles():
"""Mock get_state_of_all_vehicles function."""

View File

@@ -1,13 +0,0 @@
{
"timestamp": 1704067200,
"battery_level": 73,
"battery_range": 250.5,
"ideal_battery_range": 280.2,
"phantom_drain_percent": 0.5,
"energy_remaining": 55.2,
"lifetime_energy_used": 12345.6,
"pack_current": -0.6,
"pack_voltage": 390.1,
"module_temp_min": 22.5,
"module_temp_max": 24
}

View File

@@ -54,6 +54,12 @@
"off_peak_charging_enabled": false,
"off_peak_charging_times": "all_week",
"off_peak_hours_end_time": 900,
"lifetime_energy_used": 12345.6,
"module_temp_max": 24,
"module_temp_min": 22.5,
"pack_current": -0.6,
"pack_voltage": 390.1,
"phantom_drain": 0.5,
"preconditioning_enabled": false,
"preconditioning_times": "all_week",
"scheduled_charging_mode": "StartAt",

View File

@@ -161,19 +161,6 @@
]),
'vehicles': list([
dict({
'battery': dict({
'battery_level': 73,
'battery_range': 250.5,
'energy_remaining': 55.2,
'ideal_battery_range': 280.2,
'lifetime_energy_used': 12345.6,
'module_temp_max': 24,
'module_temp_min': 22.5,
'pack_current': -0.6,
'pack_voltage': 390.1,
'phantom_drain_percent': 0.5,
'timestamp': 1704067200,
}),
'data': dict({
'access_type': 'OWNER',
'api_version': 67,

View File

@@ -2074,7 +2074,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '24',
'state': '18',
})
# ---
# name: test_sensors[sensor.test_battery_module_temperature_min-entry]
@@ -2132,7 +2132,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '22.5',
'state': '16.5',
})
# ---
# name: test_sensors[sensor.test_battery_pack_current-entry]
@@ -2190,7 +2190,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '-0.6',
'state': 'unknown',
})
# ---
# name: test_sensors[sensor.test_battery_pack_voltage-entry]
@@ -2248,7 +2248,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '390.1',
'state': 'unknown',
})
# ---
# name: test_sensors[sensor.test_battery_range-entry]
@@ -3247,7 +3247,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '12345.6',
'state': '20505.629',
})
# ---
# name: test_sensors[sensor.test_odometer-entry]
@@ -3481,7 +3481,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.5',
'state': 'unknown',
})
# ---
# name: test_sensors[sensor.test_power-entry]

View File

@@ -80,51 +80,6 @@ async def test_coordinator_connection(
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
async def test_coordinator_battery_update(
hass: HomeAssistant, mock_get_battery, freezer: FrozenDateTimeFactory
) -> None:
"""Tests that the battery coordinator handles updates."""
await setup_platform(hass, [Platform.SENSOR])
mock_get_battery.reset_mock()
freezer.tick(WAIT)
async_fire_time_changed(hass)
await hass.async_block_till_done()
mock_get_battery.assert_called_once()
async def test_coordinator_battery_auth(
hass: HomeAssistant, mock_get_battery, freezer: FrozenDateTimeFactory
) -> None:
"""Tests that the battery coordinator handles auth errors."""
await setup_platform(hass, [Platform.SENSOR])
mock_get_battery.reset_mock()
mock_get_battery.side_effect = ERROR_AUTH
freezer.tick(WAIT)
async_fire_time_changed(hass)
await hass.async_block_till_done()
mock_get_battery.assert_called_once()
async def test_coordinator_battery_error(
hass: HomeAssistant, mock_get_battery, freezer: FrozenDateTimeFactory
) -> None:
"""Tests that the battery coordinator handles client errors."""
await setup_platform(hass, [Platform.SENSOR])
mock_get_battery.reset_mock()
mock_get_battery.side_effect = ERROR_UNKNOWN
freezer.tick(WAIT)
async_fire_time_changed(hass)
await hass.async_block_till_done()
mock_get_battery.assert_called_once()
assert hass.states.get("sensor.test_phantom_drain").state == STATE_UNAVAILABLE
async def test_coordinator_live_error(
hass: HomeAssistant, mock_live_status, freezer: FrozenDateTimeFactory
) -> None:

View File

@@ -2,7 +2,6 @@
from unittest.mock import patch
import pytest
from tesla_fleet_api.exceptions import TeslaFleetError
from homeassistant.config_entries import ConfigEntryState
@@ -51,27 +50,6 @@ async def test_connection_failure(
assert entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.parametrize(
("side_effect", "expected_state"),
[
(ERROR_AUTH, ConfigEntryState.SETUP_ERROR),
(ERROR_UNKNOWN, ConfigEntryState.SETUP_ERROR),
(ERROR_CONNECTION, ConfigEntryState.SETUP_RETRY),
],
)
async def test_battery_setup_failure(
hass: HomeAssistant,
mock_get_battery,
side_effect: Exception,
expected_state: ConfigEntryState,
) -> None:
"""Test init with a battery API error."""
mock_get_battery.side_effect = side_effect
entry = await setup_platform(hass)
assert entry.state is expected_state
async def test_products_error(hass: HomeAssistant) -> None:
"""Test init with a fleet error on products."""