Add additional sensors to Teslemetry (#112555)

* Add more sensors

* Fix coverage

* Dont do this rename yet

* Fix case

* Update snapshot

* Add icons

* Remove unused icons

* Update snapshot

* Remove last_value logic from TimeSensor

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Update constant case

* Remove useless test

* Add refresh test back

* Add assertion to post coordinator refresh

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
Brett Adams 2024-03-16 20:54:37 +10:00 committed by GitHub
parent 470ef554d7
commit 219cb7a788
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1788 additions and 658 deletions

View file

@ -48,6 +48,11 @@ class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator
serial_number=vehicle.vin, serial_number=vehicle.vin,
) )
@property
def _value(self) -> Any | None:
"""Return a specific value from coordinator data."""
return self.coordinator.data.get(self.key)
async def wake_up_if_asleep(self) -> None: async def wake_up_if_asleep(self) -> None:
"""Wake up the vehicle if its asleep.""" """Wake up the vehicle if its asleep."""
async with self._wakelock: async with self._wakelock:

View file

@ -18,6 +18,15 @@
"battery_power": { "battery_power": {
"default": "mdi:home-battery" "default": "mdi:home-battery"
}, },
"charge_state_charging_state": {
"default": "mdi:ev-station",
"state": {
"disconnected": "mdi:connection",
"no_power": "mdi:power-plug-off-outline",
"starting": "mdi:play-circle",
"stopped": "mdi:stop-circle"
}
},
"drive_state_active_route_destination": { "drive_state_active_route_destination": {
"default": "mdi:routes" "default": "mdi:routes"
}, },

View file

@ -6,6 +6,7 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itertools import chain from itertools import chain
from typing import cast
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -27,10 +28,11 @@ from homeassistant.const import (
UnitOfTemperature, UnitOfTemperature,
UnitOfTime, UnitOfTime,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.util.variance import ignore_variance
from .const import DOMAIN from .const import DOMAIN
from .entity import ( from .entity import (
@ -40,13 +42,16 @@ from .entity import (
) )
from .models import TeslemetryEnergyData, TeslemetryVehicleData from .models import TeslemetryEnergyData, TeslemetryVehicleData
CHARGE_STATES = {
"Starting": "starting",
"Charging": "charging",
"Stopped": "stopped",
"Complete": "complete",
"Disconnected": "disconnected",
"NoPower": "no_power",
}
@callback SHIFT_STATES = {"P": "p", "D": "d", "R": "r", "N": "n"}
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) @dataclass(frozen=True, kw_only=True)
@ -57,11 +62,24 @@ class TeslemetrySensorEntityDescription(SensorEntityDescription):
VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
TeslemetrySensorEntityDescription(
key="charge_state_charging_state",
options=list(CHARGE_STATES.values()),
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: CHARGE_STATES.get(cast(str, value)),
),
TeslemetrySensorEntityDescription(
key="charge_state_battery_level",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
key="charge_state_usable_battery_level", key="charge_state_usable_battery_level",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY, device_class=SensorDeviceClass.BATTERY,
entity_registry_enabled_default=False,
), ),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
key="charge_state_charge_energy_added", key="charge_state_charge_energy_added",
@ -96,13 +114,16 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR, native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
device_class=SensorDeviceClass.SPEED, device_class=SensorDeviceClass.SPEED,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
),
TeslemetrySensorEntityDescription(
key="charge_state_conn_charge_cable",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
key="charge_state_minutes_to_full_charge", key="charge_state_fast_charger_type",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
value_fn=minutes_to_datetime, entity_registry_enabled_default=False,
), ),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
key="charge_state_battery_range", key="charge_state_battery_range",
@ -111,12 +132,29 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
device_class=SensorDeviceClass.DISTANCE, device_class=SensorDeviceClass.DISTANCE,
suggested_display_precision=1, suggested_display_precision=1,
), ),
TeslemetrySensorEntityDescription(
key="charge_state_est_battery_range",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfLength.MILES,
device_class=SensorDeviceClass.DISTANCE,
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
TeslemetrySensorEntityDescription(
key="charge_state_ideal_battery_range",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfLength.MILES,
device_class=SensorDeviceClass.DISTANCE,
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
key="drive_state_speed", key="drive_state_speed",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR, native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
device_class=SensorDeviceClass.SPEED, device_class=SensorDeviceClass.SPEED,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
value_fn=lambda value: value or 0,
), ),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
key="drive_state_power", key="drive_state_power",
@ -125,12 +163,13 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
value_fn=lambda value: value or 0,
), ),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
key="drive_state_shift_state", key="drive_state_shift_state",
options=["p", "d", "r", "n"], options=list(SHIFT_STATES.values()),
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
value_fn=lambda x: x.lower() if isinstance(x, str) else x, value_fn=lambda x: SHIFT_STATES.get(str(x), "p"),
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetrySensorEntityDescription( TeslemetrySensorEntityDescription(
@ -235,14 +274,27 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfLength.MILES, native_unit_of_measurement=UnitOfLength.MILES,
device_class=SensorDeviceClass.DISTANCE, device_class=SensorDeviceClass.DISTANCE,
), ),
TeslemetrySensorEntityDescription( )
@dataclass(frozen=True, kw_only=True)
class TeslemetryTimeEntityDescription(SensorEntityDescription):
"""Describes Teslemetry Sensor entity."""
variance: int
VEHICLE_TIME_DESCRIPTIONS: tuple[TeslemetryTimeEntityDescription, ...] = (
TeslemetryTimeEntityDescription(
key="charge_state_minutes_to_full_charge",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
variance=4,
),
TeslemetryTimeEntityDescription(
key="drive_state_active_route_minutes_to_arrival", key="drive_state_active_route_minutes_to_arrival",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
value_fn=minutes_to_datetime, variance=1,
),
TeslemetrySensorEntityDescription(
key="drive_state_active_route_destination",
entity_category=EntityCategory.DIAGNOSTIC,
), ),
) )
@ -322,6 +374,7 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription(key="island_status", device_class=SensorDeviceClass.ENUM),
) )
WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
@ -362,7 +415,12 @@ async def async_setup_entry(
for vehicle in data.vehicles for vehicle in data.vehicles
for description in VEHICLE_DESCRIPTIONS for description in VEHICLE_DESCRIPTIONS
), ),
( # Add energy sites ( # Add vehicles time sensors
TeslemetryVehicleTimeSensorEntity(vehicle, description)
for vehicle in data.vehicles
for description in VEHICLE_TIME_DESCRIPTIONS
),
( # Add energy site live
TeslemetryEnergySensorEntity(energysite, description) TeslemetryEnergySensorEntity(energysite, description)
for energysite in data.energysites for energysite in data.energysites
for description in ENERGY_DESCRIPTIONS for description in ENERGY_DESCRIPTIONS
@ -390,17 +448,36 @@ class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity):
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(vehicle, description.key) super().__init__(vehicle, description.key)
class TeslemetryVehicleTimeSensorEntity(TeslemetryVehicleEntity, SensorEntity):
"""Base class for Teslemetry vehicle metric sensors."""
entity_description: TeslemetryTimeEntityDescription
def __init__(
self,
data: TeslemetryVehicleData,
description: TeslemetryTimeEntityDescription,
) -> None:
"""Initialize the sensor."""
self.entity_description = description self.entity_description = description
self._get_timestamp = ignore_variance(
func=lambda value: dt_util.now() + timedelta(minutes=value),
ignored_variance=timedelta(minutes=description.variance),
)
super().__init__(data, description.key)
@property @property
def native_value(self) -> StateType | datetime: def native_value(self) -> datetime | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.entity_description.value_fn(self.get()) return self._get_timestamp(self._value)
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return if sensor is available.""" """Return the avaliability of the sensor."""
return super().available and self.get() is not None return isinstance(self._value, int | float) and self._value > 0
class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity): class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity):

View file

@ -32,32 +32,85 @@
} }
}, },
"sensor": { "sensor": {
"charge_state_usable_battery_level": { "battery_power": {
"name": "Battery level" "name": "Battery power"
},
"charge_state_battery_range": {
"name": "Battery range"
},
"charge_state_est_battery_range": {
"name": "Estimate battery range"
},
"charge_state_ideal_battery_range": {
"name": "Ideal battery range"
}, },
"charge_state_charge_energy_added": { "charge_state_charge_energy_added": {
"name": "Charge energy added" "name": "Charge energy added"
}, },
"charge_state_charge_rate": {
"name": "Charge rate"
},
"charge_state_charger_actual_current": {
"name": "Charger current"
},
"charge_state_charger_power": { "charge_state_charger_power": {
"name": "Charger power" "name": "Charger power"
}, },
"charge_state_charger_voltage": { "charge_state_charger_voltage": {
"name": "Charger voltage" "name": "Charger voltage"
}, },
"charge_state_charger_actual_current": { "charge_state_conn_charge_cable": {
"name": "Charger current" "name": "Charge cable"
}, },
"charge_state_charge_rate": { "charge_state_fast_charger_type": {
"name": "Charge rate" "name": "Fast charger type"
}, },
"charge_state_battery_range": { "charge_state_charging_state": {
"name": "Battery range" "name": "Charging",
"state": {
"starting": "Starting",
"charging": "Charging",
"disconnected": "Disconnected",
"stopped": "Stopped",
"complete": "Complete",
"no_power": "No power"
}
}, },
"charge_state_minutes_to_full_charge": { "charge_state_minutes_to_full_charge": {
"name": "Time to full charge" "name": "Time to full charge"
}, },
"drive_state_speed": { "charge_state_battery_level": {
"name": "Speed" "name": "Battery level"
},
"charge_state_usable_battery_level": {
"name": "Usable battery level"
},
"climate_state_driver_temp_setting": {
"name": "Driver temperature setting"
},
"climate_state_inside_temp": {
"name": "Inside temperature"
},
"climate_state_outside_temp": {
"name": "Outside temperature"
},
"climate_state_passenger_temp_setting": {
"name": "Passenger temperature setting"
},
"drive_state_active_route_destination": {
"name": "Destination"
},
"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_traffic_minutes_delay": {
"name": "Traffic delay"
}, },
"drive_state_power": { "drive_state_power": {
"name": "Power" "name": "Power"
@ -65,12 +118,39 @@
"drive_state_shift_state": { "drive_state_shift_state": {
"name": "Shift state", "name": "Shift state",
"state": { "state": {
"p": "Park",
"d": "Drive", "d": "Drive",
"r": "Reverse", "n": "Neutral",
"n": "Neutral" "p": "Park",
"r": "Reverse"
} }
}, },
"drive_state_speed": {
"name": "Speed"
},
"energy_left": {
"name": "Energy left"
},
"generator_power": {
"name": "Generator power"
},
"grid_power": {
"name": "Grid power"
},
"grid_services_power": {
"name": "Grid services power"
},
"load_power": {
"name": "Load power"
},
"percentage_charged": {
"name": "Percentage charged"
},
"solar_power": {
"name": "Solar power"
},
"total_pack_energy": {
"name": "Total pack energy"
},
"vehicle_state_odometer": { "vehicle_state_odometer": {
"name": "Odometer" "name": "Odometer"
}, },
@ -86,62 +166,8 @@
"vehicle_state_tpms_pressure_rr": { "vehicle_state_tpms_pressure_rr": {
"name": "Tire pressure rear right" "name": "Tire pressure rear right"
}, },
"climate_state_inside_temp": { "vin": {
"name": "Inside temperature" "name": "Vehicle"
},
"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": { "wall_connector_fault_state": {
"name": "Fault state code" "name": "Fault state code"
@ -149,8 +175,8 @@
"wall_connector_power": { "wall_connector_power": {
"name": "Power" "name": "Power"
}, },
"vin": { "wall_connector_state": {
"name": "Vehicle" "name": "State code"
} }
} }
} }

View file

@ -48,3 +48,18 @@ def assert_entities(
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
assert (state := hass.states.get(entity_entry.entity_id)) assert (state := hass.states.get(entity_entry.entity_id))
assert state == snapshot(name=f"{entity_entry.entity_id}-state") assert state == snapshot(name=f"{entity_entry.entity_id}-state")
def assert_entities_alt(
hass: HomeAssistant,
entry_id: str,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test that all entities match their alt snapshot."""
entity_entries = er.async_entries_for_config_entry(entity_registry, entry_id)
assert entity_entries
for entity_entry in entity_entries:
assert (state := hass.states.get(entity_entry.entity_id))
assert state == snapshot(name=f"{entity_entry.entity_id}-statealt")

View file

@ -12,6 +12,7 @@ WAKE_UP_ASLEEP = {"response": {"state": TeslemetryState.ASLEEP}, "error": None}
PRODUCTS = load_json_object_fixture("products.json", DOMAIN) PRODUCTS = load_json_object_fixture("products.json", DOMAIN)
VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN) VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN)
VEHICLE_DATA_ALT = load_json_object_fixture("vehicle_data_alt.json", DOMAIN)
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN) LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
RESPONSE_OK = {"response": {}, "error": None} RESPONSE_OK = {"response": {}, "error": None}

View file

@ -0,0 +1,279 @@
{
"response": {
"id": 1234,
"user_id": 1234,
"vehicle_id": 1234,
"vin": "VINVINVIN",
"color": null,
"access_type": "OWNER",
"granular_access": {
"hide_private": false
},
"tokens": ["abc", "def"],
"state": "online",
"in_service": false,
"id_s": "1234",
"calendar_enabled": true,
"api_version": 71,
"backseat_token": null,
"backseat_token_updated_at": null,
"ble_autopair_enrolled": false,
"charge_state": {
"battery_heater_on": false,
"battery_level": 77,
"battery_range": 266.87,
"charge_amps": 16,
"charge_current_request": 16,
"charge_current_request_max": 16,
"charge_enable_request": true,
"charge_energy_added": 0,
"charge_limit_soc": 80,
"charge_limit_soc_max": 100,
"charge_limit_soc_min": 50,
"charge_limit_soc_std": 80,
"charge_miles_added_ideal": 0,
"charge_miles_added_rated": 0,
"charge_port_cold_weather_mode": false,
"charge_port_color": "<invalid>",
"charge_port_door_open": true,
"charge_port_latch": "Engaged",
"charge_rate": 0,
"charger_actual_current": 0,
"charger_phases": null,
"charger_pilot_current": 16,
"charger_power": 0,
"charger_voltage": 2,
"charging_state": "Stopped",
"conn_charge_cable": "IEC",
"est_battery_range": 275.04,
"fast_charger_brand": "<invalid>",
"fast_charger_present": false,
"fast_charger_type": "ACSingleWireCAN",
"ideal_battery_range": 266.87,
"max_range_charge_counter": 0,
"minutes_to_full_charge": "bad value",
"not_enough_power_to_heat": null,
"off_peak_charging_enabled": false,
"off_peak_charging_times": "all_week",
"off_peak_hours_end_time": 900,
"preconditioning_enabled": false,
"preconditioning_times": "all_week",
"scheduled_charging_mode": "Off",
"scheduled_charging_pending": false,
"scheduled_charging_start_time": null,
"scheduled_charging_start_time_app": 600,
"scheduled_departure_time": 1704837600,
"scheduled_departure_time_minutes": 480,
"supercharger_session_trip_planner": false,
"time_to_full_charge": null,
"timestamp": null,
"trip_charging": false,
"usable_battery_level": 77,
"user_charge_enable_request": null
},
"climate_state": {
"allow_cabin_overheat_protection": true,
"auto_seat_climate_left": false,
"auto_seat_climate_right": false,
"auto_steering_wheel_heat": false,
"battery_heater": false,
"battery_heater_no_power": null,
"cabin_overheat_protection": "Off",
"cabin_overheat_protection_actively_cooling": false,
"climate_keeper_mode": "off",
"cop_activation_temperature": "High",
"defrost_mode": 0,
"driver_temp_setting": 22,
"fan_status": 0,
"hvac_auto_request": "On",
"inside_temp": 29.8,
"is_auto_conditioning_on": false,
"is_climate_on": false,
"is_front_defroster_on": false,
"is_preconditioning": false,
"is_rear_defroster_on": false,
"left_temp_direction": 251,
"max_avail_temp": 28,
"min_avail_temp": 15,
"outside_temp": 30,
"passenger_temp_setting": 22,
"remote_heater_control_enabled": false,
"right_temp_direction": 251,
"seat_heater_left": 0,
"seat_heater_rear_center": 0,
"seat_heater_rear_left": 0,
"seat_heater_rear_right": 0,
"seat_heater_right": 0,
"side_mirror_heaters": false,
"steering_wheel_heat_level": 0,
"steering_wheel_heater": false,
"supports_fan_only_cabin_overheat_protection": true,
"timestamp": 1705707520649,
"wiper_blade_heater": false
},
"drive_state": {
"active_route_latitude": 30.2226265,
"active_route_longitude": -97.6236871,
"active_route_miles_to_arrival": 0,
"active_route_minutes_to_arrival": 0,
"active_route_traffic_minutes_delay": 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
},
"gui_settings": {
"gui_24_hour_time": false,
"gui_charge_rate_units": "kW",
"gui_distance_units": "km/hr",
"gui_range_display": "Rated",
"gui_temperature_units": "C",
"gui_tirepressure_units": "Psi",
"show_range_units": false,
"timestamp": 1705707520649
},
"vehicle_config": {
"aux_park_lamps": "Eu",
"badge_version": 1,
"can_accept_navigation_requests": true,
"can_actuate_trunks": true,
"car_special_type": "base",
"car_type": "model3",
"charge_port_type": "CCS",
"cop_user_set_temp_supported": false,
"dashcam_clip_save_supported": true,
"default_charge_to_max": false,
"driver_assist": "TeslaAP3",
"ece_restrictions": false,
"efficiency_package": "M32021",
"eu_vehicle": true,
"exterior_color": "DeepBlue",
"exterior_trim": "Black",
"exterior_trim_override": "",
"has_air_suspension": false,
"has_ludicrous_mode": false,
"has_seat_cooling": false,
"headlamp_type": "Global",
"interior_trim_type": "White2",
"key_version": 2,
"motorized_charge_port": true,
"paint_color_override": "0,9,25,0.7,0.04",
"performance_package": "Base",
"plg": true,
"pws": true,
"rear_drive_unit": "PM216MOSFET",
"rear_seat_heaters": 1,
"rear_seat_type": 0,
"rhd": true,
"roof_color": "RoofColorGlass",
"seat_type": null,
"spoiler_type": "None",
"sun_roof_installed": null,
"supports_qr_pairing": false,
"third_row_seats": "None",
"timestamp": 1705707520649,
"trim_badging": "74d",
"use_range_badging": true,
"utc_offset": 36000,
"webcam_selfie_supported": true,
"webcam_supported": true,
"wheel_type": "Pinwheel18CapKit"
},
"vehicle_state": {
"api_version": 71,
"autopark_state_v2": "unavailable",
"calendar_supported": true,
"car_version": "2023.44.30.8 06f534d46010",
"center_display_state": 0,
"dashcam_clip_save_available": true,
"dashcam_state": "Recording",
"df": 0,
"dr": 0,
"fd_window": 0,
"feature_bitmask": "fbdffbff,187f",
"fp_window": 0,
"ft": 0,
"is_user_present": false,
"locked": false,
"media_info": {
"audio_volume": 2.6667,
"audio_volume_increment": 0.333333,
"audio_volume_max": 10.333333,
"media_playback_status": "Stopped",
"now_playing_album": "",
"now_playing_artist": "",
"now_playing_duration": 0,
"now_playing_elapsed": 0,
"now_playing_source": "Spotify",
"now_playing_station": "",
"now_playing_title": ""
},
"media_state": {
"remote_control_enabled": true
},
"notifications_supported": true,
"odometer": 6481.019282,
"parsed_calendar_supported": true,
"pf": 0,
"pr": 0,
"rd_window": 0,
"remote_start": false,
"remote_start_enabled": true,
"remote_start_supported": true,
"rp_window": 0,
"rt": 0,
"santa_mode": 0,
"sentry_mode": false,
"sentry_mode_available": true,
"service_mode": false,
"service_mode_plus": false,
"software_update": {
"download_perc": 0,
"expected_duration_sec": 2700,
"install_perc": 1,
"status": "",
"version": " "
},
"speed_limit_mode": {
"active": false,
"current_limit_mph": 69,
"max_limit_mph": 120,
"min_limit_mph": 50,
"pin_code_set": true
},
"timestamp": 1705707520649,
"tpms_hard_warning_fl": false,
"tpms_hard_warning_fr": false,
"tpms_hard_warning_rl": false,
"tpms_hard_warning_rr": false,
"tpms_last_seen_pressure_time_fl": 1705700812,
"tpms_last_seen_pressure_time_fr": 1705700793,
"tpms_last_seen_pressure_time_rl": 1705700794,
"tpms_last_seen_pressure_time_rr": 1705700823,
"tpms_pressure_fl": 2.775,
"tpms_pressure_fr": 2.8,
"tpms_pressure_rl": 2.775,
"tpms_pressure_rr": 2.775,
"tpms_rcp_front_value": 2.9,
"tpms_rcp_rear_value": 2.9,
"tpms_soft_warning_fl": false,
"tpms_soft_warning_fr": false,
"tpms_soft_warning_rl": false,
"tpms_soft_warning_rr": false,
"valet_mode": false,
"valet_pin_needed": false,
"vehicle_name": "Test",
"vehicle_self_test_progress": 0,
"vehicle_self_test_requested": false,
"webcam_available": true
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,19 @@
"""Test the Teslemetry sensor platform.""" """Test the Teslemetry sensor platform."""
from datetime import timedelta
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.teslemetry.coordinator import SYNC_INTERVAL
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import assert_entities, setup_platform from . import assert_entities, assert_entities_alt, setup_platform
from .const import VEHICLE_DATA_ALT
from tests.common import async_fire_time_changed
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
@ -17,6 +22,7 @@ async def test_sensors(
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
mock_vehicle_data,
) -> None: ) -> None:
"""Tests that the sensor entities are correct.""" """Tests that the sensor entities are correct."""
@ -25,3 +31,11 @@ async def test_sensors(
entry = await setup_platform(hass, [Platform.SENSOR]) entry = await setup_platform(hass, [Platform.SENSOR])
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
# Coordinator refresh
mock_vehicle_data.return_value = VEHICLE_DATA_ALT
freezer.tick(timedelta(seconds=SYNC_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot)