Add sensor platform to Teslemetry (#109088)
* Adding Energy * Adding Energy * Work in progress * Add fixtures * Add product info * Add sensors * Add icons * Update metadata * Use SensorEntityDescription for Energy * Use ENERGY_STORAGE * Add tests * Fix coverage * Update wall connector precision and units * Change devices * Fix serial number * Add icons and VIN to wall connector * Fix serial number again * Update snapshots * Use timestamp for minutes to arrival * Cleanup snapshot * Improvements * Update fixture * Add "code" to translations * Add "code" to snapshot * Use async_add_entities once * Disable a bunch of sensors * Ruff * Improve fixture and test coverage * Regen Snapshots * Add init to coordinatorpull/112267/head
parent
b195c3fa7b
commit
b5528de807
|
@ -2,7 +2,7 @@
|
|||
import asyncio
|
||||
from typing import Final
|
||||
|
||||
from tesla_fleet_api import Teslemetry, VehicleSpecific
|
||||
from tesla_fleet_api import EnergySpecific, Teslemetry, VehicleSpecific
|
||||
from tesla_fleet_api.exceptions import InvalidToken, PaymentRequired, TeslaFleetError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -12,12 +12,13 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
|||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .coordinator import TeslemetryVehicleDataCoordinator
|
||||
from .models import TeslemetryVehicleData
|
||||
from .coordinator import (
|
||||
TeslemetryEnergyDataCoordinator,
|
||||
TeslemetryVehicleDataCoordinator,
|
||||
)
|
||||
from .models import TeslemetryData, TeslemetryEnergyData, TeslemetryVehicleData
|
||||
|
||||
PLATFORMS: Final = [
|
||||
Platform.CLIMATE,
|
||||
]
|
||||
PLATFORMS: Final = [Platform.CLIMATE, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
@ -42,29 +43,48 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
raise ConfigEntryNotReady from e
|
||||
|
||||
# Create array of classes
|
||||
data = []
|
||||
vehicles: list[TeslemetryVehicleData] = []
|
||||
energysites: list[TeslemetryEnergyData] = []
|
||||
for product in products:
|
||||
if "vin" not in product:
|
||||
continue
|
||||
vin = product["vin"]
|
||||
|
||||
api = VehicleSpecific(teslemetry.vehicle, vin)
|
||||
coordinator = TeslemetryVehicleDataCoordinator(hass, api)
|
||||
data.append(
|
||||
TeslemetryVehicleData(
|
||||
api=api,
|
||||
coordinator=coordinator,
|
||||
vin=vin,
|
||||
if "vin" in product:
|
||||
vin = product["vin"]
|
||||
api = VehicleSpecific(teslemetry.vehicle, vin)
|
||||
coordinator = TeslemetryVehicleDataCoordinator(hass, api)
|
||||
vehicles.append(
|
||||
TeslemetryVehicleData(
|
||||
api=api,
|
||||
coordinator=coordinator,
|
||||
vin=vin,
|
||||
)
|
||||
)
|
||||
elif "energy_site_id" in product:
|
||||
site_id = product["energy_site_id"]
|
||||
api = EnergySpecific(teslemetry.energy, site_id)
|
||||
energysites.append(
|
||||
TeslemetryEnergyData(
|
||||
api=api,
|
||||
coordinator=TeslemetryEnergyDataCoordinator(hass, api),
|
||||
id=site_id,
|
||||
info=product,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Do all coordinator first refresh simultaneously
|
||||
# Do all coordinator first refreshes simultaneously
|
||||
await asyncio.gather(
|
||||
*(vehicle.coordinator.async_config_entry_first_refresh() for vehicle in data)
|
||||
*(
|
||||
vehicle.coordinator.async_config_entry_first_refresh()
|
||||
for vehicle in vehicles
|
||||
),
|
||||
*(
|
||||
energysite.coordinator.async_config_entry_first_refresh()
|
||||
for energysite in energysites
|
||||
),
|
||||
)
|
||||
|
||||
# Setup Platforms
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = TeslemetryData(
|
||||
vehicles, energysites
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
|
|
@ -26,7 +26,7 @@ async def async_setup_entry(
|
|||
|
||||
async_add_entities(
|
||||
TeslemetryClimateEntity(vehicle, TeslemetryClimateSide.DRIVER)
|
||||
for vehicle in data
|
||||
for vehicle in data.vehicles
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from tesla_fleet_api import VehicleSpecific
|
||||
from tesla_fleet_api import EnergySpecific, VehicleSpecific
|
||||
from tesla_fleet_api.exceptions import TeslaFleetError, VehicleOffline
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -14,19 +14,29 @@ from .const import LOGGER, TeslemetryState
|
|||
SYNC_INTERVAL = 60
|
||||
|
||||
|
||||
class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Class to manage fetching data from the Teslemetry API."""
|
||||
class TeslemetryDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Base class for Teslemetry Data Coordinators."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: VehicleSpecific) -> None:
|
||||
"""Initialize Teslemetry Data Update Coordinator."""
|
||||
name: str
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: VehicleSpecific | EnergySpecific
|
||||
) -> None:
|
||||
"""Initialize Teslemetry Vehicle Update Coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name="Teslemetry Vehicle",
|
||||
name=self.name,
|
||||
update_interval=timedelta(seconds=SYNC_INTERVAL),
|
||||
)
|
||||
self.api = api
|
||||
|
||||
|
||||
class TeslemetryVehicleDataCoordinator(TeslemetryDataCoordinator):
|
||||
"""Class to manage fetching data from the Teslemetry API."""
|
||||
|
||||
name = "Teslemetry Vehicle"
|
||||
|
||||
async def async_config_entry_first_refresh(self) -> None:
|
||||
"""Perform first refresh."""
|
||||
try:
|
||||
|
@ -65,3 +75,24 @@ class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||
else:
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
class TeslemetryEnergyDataCoordinator(TeslemetryDataCoordinator):
|
||||
"""Class to manage fetching data from the Teslemetry API."""
|
||||
|
||||
name = "Teslemetry Energy Site"
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update energy site data using Teslemetry API."""
|
||||
|
||||
try:
|
||||
data = await self.api.live_status()
|
||||
except TeslaFleetError as e:
|
||||
raise UpdateFailed(e.message) from e
|
||||
|
||||
# Convert Wall Connectors from array to dict
|
||||
data["response"]["wall_connectors"] = {
|
||||
wc["din"]: wc for wc in data["response"].get("wall_connectors", [])
|
||||
}
|
||||
|
||||
return data["response"]
|
||||
|
|
|
@ -10,12 +10,15 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
|||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MODELS, TeslemetryState
|
||||
from .coordinator import TeslemetryVehicleDataCoordinator
|
||||
from .models import TeslemetryVehicleData
|
||||
from .coordinator import (
|
||||
TeslemetryEnergyDataCoordinator,
|
||||
TeslemetryVehicleDataCoordinator,
|
||||
)
|
||||
from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
||||
|
||||
|
||||
class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator]):
|
||||
"""Parent class for Teslemetry Entities."""
|
||||
"""Parent class for Teslemetry Vehicle Entities."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
|
@ -74,3 +77,65 @@ class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator
|
|||
for key, value in args:
|
||||
self.coordinator.data[key] = value
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class TeslemetryEnergyEntity(CoordinatorEntity[TeslemetryEnergyDataCoordinator]):
|
||||
"""Parent class for Teslemetry Energy Entities."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
energysite: TeslemetryEnergyData,
|
||||
key: str,
|
||||
) -> None:
|
||||
"""Initialize common aspects of a Teslemetry entity."""
|
||||
super().__init__(energysite.coordinator)
|
||||
self.key = key
|
||||
self.api = energysite.api
|
||||
|
||||
self._attr_translation_key = key
|
||||
self._attr_unique_id = f"{energysite.id}-{key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, str(energysite.id))},
|
||||
manufacturer="Tesla",
|
||||
configuration_url="https://teslemetry.com/console",
|
||||
name=self.coordinator.data.get("site_name", "Energy Site"),
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class TeslemetryWallConnectorEntity(CoordinatorEntity[TeslemetryEnergyDataCoordinator]):
|
||||
"""Parent class for Teslemetry Wall Connector Entities."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
energysite: TeslemetryEnergyData,
|
||||
din: str,
|
||||
key: str,
|
||||
) -> None:
|
||||
"""Initialize common aspects of a Teslemetry entity."""
|
||||
super().__init__(energysite.coordinator)
|
||||
self.din = din
|
||||
self.key = key
|
||||
|
||||
self._attr_translation_key = key
|
||||
self._attr_unique_id = f"{energysite.id}-{din}-{key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, din)},
|
||||
manufacturer="Tesla",
|
||||
configuration_url="https://teslemetry.com/console",
|
||||
name="Wall Connector",
|
||||
via_device=(DOMAIN, str(energysite.id)),
|
||||
serial_number=din.split("-")[-1],
|
||||
)
|
||||
|
||||
@property
|
||||
def _value(self) -> int:
|
||||
"""Return a specific wall connector value from coordinator data."""
|
||||
return self.coordinator.data["wall_connectors"][self.din].get(self.key)
|
||||
|
|
|
@ -4,9 +4,20 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from tesla_fleet_api import VehicleSpecific
|
||||
from tesla_fleet_api import EnergySpecific, VehicleSpecific
|
||||
|
||||
from .coordinator import TeslemetryVehicleDataCoordinator
|
||||
from .coordinator import (
|
||||
TeslemetryEnergyDataCoordinator,
|
||||
TeslemetryVehicleDataCoordinator,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TeslemetryData:
|
||||
"""Data for the Teslemetry integration."""
|
||||
|
||||
vehicles: list[TeslemetryVehicleData]
|
||||
energysites: list[TeslemetryEnergyData]
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -17,3 +28,13 @@ class TeslemetryVehicleData:
|
|||
coordinator: TeslemetryVehicleDataCoordinator
|
||||
vin: str
|
||||
wakelock = asyncio.Lock()
|
||||
|
||||
|
||||
@dataclass
|
||||
class TeslemetryEnergyData:
|
||||
"""Data for a vehicle in the Teslemetry integration."""
|
||||
|
||||
api: EnergySpecific
|
||||
coordinator: TeslemetryEnergyDataCoordinator
|
||||
id: int
|
||||
info: dict[str, str]
|
||||
|
|
|
@ -0,0 +1,461 @@
|
|||
"""Sensor platform for Teslemetry integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import chain
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfLength,
|
||||
UnitOfPower,
|
||||
UnitOfPressure,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import (
|
||||
TeslemetryEnergyEntity,
|
||||
TeslemetryVehicleEntity,
|
||||
TeslemetryWallConnectorEntity,
|
||||
)
|
||||
from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
||||
|
||||
|
||||
@callback
|
||||
def minutes_to_datetime(value: StateType) -> datetime | None:
|
||||
"""Convert relative minutes into absolute datetime."""
|
||||
if isinstance(value, (int, float)) and value > 0:
|
||||
return dt_util.now() + timedelta(minutes=value)
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class TeslemetrySensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes Teslemetry Sensor entity."""
|
||||
|
||||
value_fn: Callable[[StateType], StateType | datetime] = lambda x: x
|
||||
|
||||
|
||||
VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_usable_battery_level",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_charge_energy_added",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_charger_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_charger_voltage",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_charger_actual_current",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_charge_rate",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||
device_class=SensorDeviceClass.SPEED,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_minutes_to_full_charge",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=minutes_to_datetime,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="charge_state_battery_range",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfLength.MILES,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_speed",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||
device_class=SensorDeviceClass.SPEED,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_shift_state",
|
||||
icon="mdi:car-shift-pattern",
|
||||
options=["p", "d", "r", "n"],
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
value_fn=lambda x: x.lower() if isinstance(x, str) else x,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="vehicle_state_odometer",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfLength.MILES,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
suggested_display_precision=0,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="vehicle_state_tpms_pressure_fl",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPressure.BAR,
|
||||
suggested_unit_of_measurement=UnitOfPressure.PSI,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
suggested_display_precision=1,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="vehicle_state_tpms_pressure_fr",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPressure.BAR,
|
||||
suggested_unit_of_measurement=UnitOfPressure.PSI,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
suggested_display_precision=1,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="vehicle_state_tpms_pressure_rl",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPressure.BAR,
|
||||
suggested_unit_of_measurement=UnitOfPressure.PSI,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
suggested_display_precision=1,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="vehicle_state_tpms_pressure_rr",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPressure.BAR,
|
||||
suggested_unit_of_measurement=UnitOfPressure.PSI,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
suggested_display_precision=1,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="climate_state_inside_temp",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="climate_state_outside_temp",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="climate_state_driver_temp_setting",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
suggested_display_precision=1,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="climate_state_passenger_temp_setting",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
suggested_display_precision=1,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_active_route_traffic_minutes_delay",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_active_route_energy_at_arrival",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_active_route_miles_to_arrival",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfLength.MILES,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_active_route_minutes_to_arrival",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=minutes_to_datetime,
|
||||
),
|
||||
TeslemetrySensorEntityDescription(
|
||||
key="drive_state_active_route_destination",
|
||||
icon="mdi:map-marker",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
|
||||
ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="solar_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
icon="mdi:solar-power",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="energy_left",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
icon="mdi:battery",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="total_pack_energy",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
icon="mdi:battery-high",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="percentage_charged",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
suggested_display_precision=2,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="battery_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
icon="mdi:home-battery",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="load_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
icon="mdi:power-plug",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="grid_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
icon="mdi:transmission-tower",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="grid_services_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
icon="mdi:transmission-tower",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="generator_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
icon="mdi:generator-stationary",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
)
|
||||
|
||||
WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="wall_connector_state",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
icon="mdi:ev-station",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="wall_connector_fault_state",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
icon="mdi:ev-station",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="wall_connector_power",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=2,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
icon="mdi:ev-station",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="vin",
|
||||
icon="mdi:car-electric",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the Teslemetry sensor platform from a config entry."""
|
||||
data = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
chain(
|
||||
( # Add vehicles
|
||||
TeslemetryVehicleSensorEntity(vehicle, description)
|
||||
for vehicle in data.vehicles
|
||||
for description in VEHICLE_DESCRIPTIONS
|
||||
),
|
||||
( # Add energy sites
|
||||
TeslemetryEnergySensorEntity(energysite, description)
|
||||
for energysite in data.energysites
|
||||
for description in ENERGY_DESCRIPTIONS
|
||||
if description.key in energysite.coordinator.data
|
||||
),
|
||||
( # Add wall connectors
|
||||
TeslemetryWallConnectorSensorEntity(energysite, din, description)
|
||||
for energysite in data.energysites
|
||||
for din in energysite.coordinator.data.get("wall_connectors", {})
|
||||
for description in WALL_CONNECTOR_DESCRIPTIONS
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity):
|
||||
"""Base class for Teslemetry vehicle metric sensors."""
|
||||
|
||||
entity_description: TeslemetrySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vehicle: TeslemetryVehicleData,
|
||||
description: TeslemetrySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(vehicle, description.key)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType | datetime:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.get())
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if sensor is available."""
|
||||
return super().available and self.get() is not None
|
||||
|
||||
|
||||
class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity):
|
||||
"""Base class for Teslemetry energy site metric sensors."""
|
||||
|
||||
entity_description: SensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
energysite: TeslemetryEnergyData,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(energysite, description.key)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.get()
|
||||
|
||||
|
||||
class TeslemetryWallConnectorSensorEntity(TeslemetryWallConnectorEntity, SensorEntity):
|
||||
"""Base class for Teslemetry energy site metric sensors."""
|
||||
|
||||
entity_description: SensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
energysite: TeslemetryEnergyData,
|
||||
din: str,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(
|
||||
energysite,
|
||||
din,
|
||||
description.key,
|
||||
)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self._value
|
|
@ -30,6 +30,128 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"charge_state_usable_battery_level": {
|
||||
"name": "Battery level"
|
||||
},
|
||||
"charge_state_charge_energy_added": {
|
||||
"name": "Charge energy added"
|
||||
},
|
||||
"charge_state_charger_power": {
|
||||
"name": "Charger power"
|
||||
},
|
||||
"charge_state_charger_voltage": {
|
||||
"name": "Charger voltage"
|
||||
},
|
||||
"charge_state_charger_actual_current": {
|
||||
"name": "Charger current"
|
||||
},
|
||||
"charge_state_charge_rate": {
|
||||
"name": "Charge rate"
|
||||
},
|
||||
"charge_state_battery_range": {
|
||||
"name": "Battery range"
|
||||
},
|
||||
"charge_state_minutes_to_full_charge": {
|
||||
"name": "Time to full charge"
|
||||
},
|
||||
"drive_state_speed": {
|
||||
"name": "Speed"
|
||||
},
|
||||
"drive_state_power": {
|
||||
"name": "Power"
|
||||
},
|
||||
"drive_state_shift_state": {
|
||||
"name": "Shift state",
|
||||
"state": {
|
||||
"p": "Park",
|
||||
"d": "Drive",
|
||||
"r": "Reverse",
|
||||
"n": "Neutral"
|
||||
}
|
||||
},
|
||||
"vehicle_state_odometer": {
|
||||
"name": "Odometer"
|
||||
},
|
||||
"vehicle_state_tpms_pressure_fl": {
|
||||
"name": "Tire pressure front left"
|
||||
},
|
||||
"vehicle_state_tpms_pressure_fr": {
|
||||
"name": "Tire pressure front right"
|
||||
},
|
||||
"vehicle_state_tpms_pressure_rl": {
|
||||
"name": "Tire pressure rear left"
|
||||
},
|
||||
"vehicle_state_tpms_pressure_rr": {
|
||||
"name": "Tire pressure rear right"
|
||||
},
|
||||
"climate_state_inside_temp": {
|
||||
"name": "Inside temperature"
|
||||
},
|
||||
"climate_state_outside_temp": {
|
||||
"name": "Outside temperature"
|
||||
},
|
||||
"climate_state_driver_temp_setting": {
|
||||
"name": "Driver temperature setting"
|
||||
},
|
||||
"climate_state_passenger_temp_setting": {
|
||||
"name": "Passenger temperature setting"
|
||||
},
|
||||
"drive_state_active_route_traffic_minutes_delay": {
|
||||
"name": "Traffic delay"
|
||||
},
|
||||
"drive_state_active_route_energy_at_arrival": {
|
||||
"name": "State of charge at arrival"
|
||||
},
|
||||
"drive_state_active_route_miles_to_arrival": {
|
||||
"name": "Distance to arrival"
|
||||
},
|
||||
"drive_state_active_route_minutes_to_arrival": {
|
||||
"name": "Time to arrival"
|
||||
},
|
||||
"drive_state_active_route_destination": {
|
||||
"name": "Destination"
|
||||
},
|
||||
"solar_power": {
|
||||
"name": "Solar power"
|
||||
},
|
||||
"energy_left": {
|
||||
"name": "Energy left"
|
||||
},
|
||||
"total_pack_energy": {
|
||||
"name": "Total pack energy"
|
||||
},
|
||||
"percentage_charged": {
|
||||
"name": "Percentage charged"
|
||||
},
|
||||
"battery_power": {
|
||||
"name": "Battery power"
|
||||
},
|
||||
"load_power": {
|
||||
"name": "Load power"
|
||||
},
|
||||
"grid_power": {
|
||||
"name": "Grid power"
|
||||
},
|
||||
"grid_services_power": {
|
||||
"name": "Grid services power"
|
||||
},
|
||||
"generator_power": {
|
||||
"name": "Generator power"
|
||||
},
|
||||
"wall_connector_state": {
|
||||
"name": "State code"
|
||||
},
|
||||
"wall_connector_fault_state": {
|
||||
"name": "Fault state code"
|
||||
},
|
||||
"wall_connector_power": {
|
||||
"name": "Power"
|
||||
},
|
||||
"vin": {
|
||||
"name": "Vehicle"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
"""Fixtures for Tessie."""
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from .const import PRODUCTS, RESPONSE_OK, VEHICLE_DATA, WAKE_UP_ONLINE
|
||||
from .const import LIVE_STATUS, PRODUCTS, RESPONSE_OK, VEHICLE_DATA, WAKE_UP_ONLINE
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
@ -55,3 +56,13 @@ def mock_request():
|
|||
return_value=RESPONSE_OK,
|
||||
) as mock_request:
|
||||
yield mock_request
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_live_status():
|
||||
"""Mock Teslemetry Energy Specific live_status method."""
|
||||
with patch(
|
||||
"homeassistant.components.teslemetry.EnergySpecific.live_status",
|
||||
side_effect=lambda: deepcopy(LIVE_STATUS),
|
||||
) as mock_live_status:
|
||||
yield mock_live_status
|
||||
|
|
|
@ -12,5 +12,6 @@ WAKE_UP_ASLEEP = {"response": {"state": TeslemetryState.ASLEEP}, "error": None}
|
|||
|
||||
PRODUCTS = load_json_object_fixture("products.json", DOMAIN)
|
||||
VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN)
|
||||
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
|
||||
|
||||
RESPONSE_OK = {"response": {}, "error": None}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"response": {
|
||||
"solar_power": 1185,
|
||||
"energy_left": 38896.47368421053,
|
||||
"total_pack_energy": 40727,
|
||||
"percentage_charged": 95.50537403739663,
|
||||
"backup_capable": true,
|
||||
"battery_power": 5060,
|
||||
"load_power": 6245,
|
||||
"grid_status": "Active",
|
||||
"grid_services_active": false,
|
||||
"grid_power": 0,
|
||||
"grid_services_power": 0,
|
||||
"generator_power": 0,
|
||||
"island_status": "on_grid",
|
||||
"storm_mode_active": false,
|
||||
"timestamp": "2024-01-01T00:00:00+00:00",
|
||||
"wall_connectors": [
|
||||
{
|
||||
"din": "abd-123",
|
||||
"wall_connector_state": 2,
|
||||
"wall_connector_fault_state": 2,
|
||||
"wall_connector_power": 0
|
||||
},
|
||||
{
|
||||
"din": "bcd-234",
|
||||
"wall_connector_state": 2,
|
||||
"wall_connector_fault_state": 2,
|
||||
"wall_connector_power": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -71,28 +71,50 @@
|
|||
"release_notes_supported": true
|
||||
},
|
||||
{
|
||||
"energy_site_id": 2345,
|
||||
"resource_type": "wall_connector",
|
||||
"id": "ID1234",
|
||||
"asset_site_id": "abcdef",
|
||||
"warp_site_number": "ID1234",
|
||||
"energy_site_id": 123456,
|
||||
"resource_type": "battery",
|
||||
"site_name": "Energy Site",
|
||||
"id": "ABC123",
|
||||
"gateway_id": "ABC123",
|
||||
"asset_site_id": "c0ffee",
|
||||
"warp_site_number": "GA123456",
|
||||
"energy_left": 23286.105263157893,
|
||||
"total_pack_energy": 40804,
|
||||
"percentage_charged": 57.068192488868476,
|
||||
"battery_type": "ac_powerwall",
|
||||
"backup_capable": true,
|
||||
"battery_power": 14990,
|
||||
"go_off_grid_test_banner_enabled": null,
|
||||
"storm_mode_enabled": null,
|
||||
"powerwall_onboarding_settings_set": null,
|
||||
"storm_mode_enabled": true,
|
||||
"powerwall_onboarding_settings_set": true,
|
||||
"powerwall_tesla_electric_interested_in": null,
|
||||
"vpp_tour_enabled": null,
|
||||
"sync_grid_alert_enabled": false,
|
||||
"breaker_alert_enabled": false,
|
||||
"sync_grid_alert_enabled": true,
|
||||
"breaker_alert_enabled": true,
|
||||
"components": {
|
||||
"battery": false,
|
||||
"solar": false,
|
||||
"grid": false,
|
||||
"load_meter": false,
|
||||
"battery": true,
|
||||
"battery_type": "ac_powerwall",
|
||||
"solar": true,
|
||||
"solar_type": "pv_panel",
|
||||
"grid": true,
|
||||
"load_meter": true,
|
||||
"market_type": "residential",
|
||||
"wall_connectors": [
|
||||
{ "device_id": "abcdef", "din": "12345", "is_active": true }
|
||||
{
|
||||
"device_id": "abc-123",
|
||||
"din": "123-abc",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"device_id": "bcd-234",
|
||||
"din": "234-bcd",
|
||||
"is_active": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"features": {}
|
||||
"features": {
|
||||
"rate_plan_manager_no_pricing_constraint": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"response": {
|
||||
"id": "1233-abcd",
|
||||
"site_name": "Site",
|
||||
"backup_reserve_percent": 0,
|
||||
"default_real_mode": "self_consumption",
|
||||
"installation_date": "2022-01-01T00:00:00+00:00",
|
||||
"user_settings": {
|
||||
"go_off_grid_test_banner_enabled": false,
|
||||
"storm_mode_enabled": true,
|
||||
"powerwall_onboarding_settings_set": true,
|
||||
"powerwall_tesla_electric_interested_in": false,
|
||||
"vpp_tour_enabled": true,
|
||||
"sync_grid_alert_enabled": true,
|
||||
"breaker_alert_enabled": false
|
||||
},
|
||||
"components": {
|
||||
"solar": true,
|
||||
"solar_type": "pv_panel",
|
||||
"battery": true,
|
||||
"grid": true,
|
||||
"backup": true,
|
||||
"gateway": "teg",
|
||||
"load_meter": true,
|
||||
"tou_capable": true,
|
||||
"storm_mode_capable": true,
|
||||
"flex_energy_request_capable": false,
|
||||
"car_charging_data_supported": false,
|
||||
"off_grid_vehicle_charging_reserve_supported": false,
|
||||
"vehicle_charging_performance_view_enabled": false,
|
||||
"vehicle_charging_solar_offset_view_enabled": false,
|
||||
"battery_solar_offset_view_enabled": true,
|
||||
"solar_value_enabled": true,
|
||||
"energy_value_header": "Energy Value",
|
||||
"energy_value_subheader": "Estimated Value",
|
||||
"energy_service_self_scheduling_enabled": true,
|
||||
"show_grid_import_battery_source_cards": true,
|
||||
"set_islanding_mode_enabled": true,
|
||||
"wifi_commissioning_enabled": true,
|
||||
"backup_time_remaining_enabled": true,
|
||||
"battery_type": "ac_powerwall",
|
||||
"configurable": true,
|
||||
"grid_services_enabled": false,
|
||||
"wall_connectors": [
|
||||
{
|
||||
"device_id": "123abc",
|
||||
"din": "abc123",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"device_id": "234bcd",
|
||||
"din": "bcd234",
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"disallow_charge_from_grid_with_solar_installed": true,
|
||||
"customer_preferred_export_rule": "pv_only",
|
||||
"net_meter_mode": "battery_ok",
|
||||
"system_alerts_enabled": true
|
||||
},
|
||||
"version": "23.44.0 eb113390",
|
||||
"battery_count": 3,
|
||||
"tou_settings": {
|
||||
"optimization_strategy": "economics",
|
||||
"schedule": [
|
||||
{
|
||||
"target": "off_peak",
|
||||
"week_days": [1, 0],
|
||||
"start_seconds": 0,
|
||||
"end_seconds": 3600
|
||||
},
|
||||
{
|
||||
"target": "peak",
|
||||
"week_days": [1, 0],
|
||||
"start_seconds": 3600,
|
||||
"end_seconds": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"nameplate_power": 15000,
|
||||
"nameplate_energy": 40500,
|
||||
"installation_time_zone": "",
|
||||
"max_site_meter_power_ac": 1000000000,
|
||||
"min_site_meter_power_ac": -1000000000,
|
||||
"vpp_backup_reserve_percent": 0
|
||||
}
|
||||
}
|
|
@ -112,10 +112,20 @@
|
|||
"wiper_blade_heater": false
|
||||
},
|
||||
"drive_state": {
|
||||
"active_route_latitude": -27.855946,
|
||||
"active_route_longitude": 153.345056,
|
||||
"active_route_latitude": 30.2226265,
|
||||
"active_route_longitude": -97.6236871,
|
||||
"active_route_miles_to_arrival": 0.039491,
|
||||
"active_route_minutes_to_arrival": 0.103577,
|
||||
"active_route_traffic_minutes_delay": 0,
|
||||
"power": 0,
|
||||
"gps_as_of": 1701129612,
|
||||
"heading": 185,
|
||||
"latitude": -30.222626,
|
||||
"longitude": -97.6236871,
|
||||
"native_latitude": -30.222626,
|
||||
"native_location_supported": 1,
|
||||
"native_longitude": -97.6236871,
|
||||
"native_type": "wgs",
|
||||
"power": -7,
|
||||
"shift_state": null,
|
||||
"speed": null,
|
||||
"timestamp": 1705707520649
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -55,10 +55,10 @@ async def test_other_failure(hass: HomeAssistant, mock_products) -> None:
|
|||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
# Coordinator
|
||||
# Vehicle Coordinator
|
||||
|
||||
|
||||
async def test_first_refresh(
|
||||
async def test_vehicle_first_refresh(
|
||||
hass: HomeAssistant,
|
||||
mock_wake_up,
|
||||
mock_vehicle_data,
|
||||
|
@ -88,14 +88,14 @@ async def test_first_refresh(
|
|||
mock_vehicle_data.assert_called_once()
|
||||
|
||||
|
||||
async def test_first_refresh_error(hass: HomeAssistant, mock_wake_up) -> None:
|
||||
async def test_vehicle_first_refresh_error(hass: HomeAssistant, mock_wake_up) -> None:
|
||||
"""Test first coordinator refresh with an error."""
|
||||
mock_wake_up.side_effect = TeslaFleetError
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_refresh_offline(
|
||||
async def test_vehicle_refresh_offline(
|
||||
hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory
|
||||
) -> None:
|
||||
"""Test coordinator refresh with an error."""
|
||||
|
@ -111,8 +111,18 @@ async def test_refresh_offline(
|
|||
mock_vehicle_data.assert_called_once()
|
||||
|
||||
|
||||
async def test_refresh_error(hass: HomeAssistant, mock_vehicle_data) -> None:
|
||||
async def test_vehicle_refresh_error(hass: HomeAssistant, mock_vehicle_data) -> None:
|
||||
"""Test coordinator refresh with an error."""
|
||||
mock_vehicle_data.side_effect = TeslaFleetError
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
# Test Energy Coordinator
|
||||
|
||||
|
||||
async def test_energy_refresh_error(hass: HomeAssistant, mock_live_status) -> None:
|
||||
"""Test coordinator refresh with an error."""
|
||||
mock_live_status.side_effect = TeslaFleetError
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
"""Test the Teslemetry sensor platform."""
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import assert_entities, setup_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_sensors(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Tests that the sensor entities are correct."""
|
||||
|
||||
freezer.move_to("2024-01-01 00:00:00+00:00")
|
||||
|
||||
entry = await setup_platform(hass, [Platform.SENSOR])
|
||||
|
||||
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
|
Loading…
Reference in New Issue