1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00

Add device tracker platform to Renault integration (#54745)

This commit is contained in:
epenet 2021-09-01 13:10:48 +02:00 committed by GitHub
parent 04a052a37d
commit bcf97cb308
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 343 additions and 35 deletions

View File

@ -1,5 +1,6 @@
"""Constants for the Renault component."""
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
DOMAIN = "renault"
@ -11,6 +12,7 @@ DEFAULT_SCAN_INTERVAL = 300 # 5 minutes
PLATFORMS = [
BINARY_SENSOR_DOMAIN,
DEVICE_TRACKER_DOMAIN,
SENSOR_DOMAIN,
]

View File

@ -0,0 +1,61 @@
"""Support for Renault device trackers."""
from __future__ import annotations
from renault_api.kamereon.models import KamereonVehicleLocationData
from homeassistant.components.device_tracker import SOURCE_TYPE_GPS
from homeassistant.components.device_tracker.config_entry import TrackerEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .renault_entities import RenaultDataEntity, RenaultEntityDescription
from .renault_hub import RenaultHub
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Renault entities from config entry."""
proxy: RenaultHub = hass.data[DOMAIN][config_entry.entry_id]
entities: list[RenaultDeviceTracker] = [
RenaultDeviceTracker(vehicle, description)
for vehicle in proxy.vehicles.values()
for description in DEVICE_TRACKER_TYPES
if description.coordinator in vehicle.coordinators
]
async_add_entities(entities)
class RenaultDeviceTracker(
RenaultDataEntity[KamereonVehicleLocationData], TrackerEntity
):
"""Mixin for device tracker specific attributes."""
@property
def latitude(self) -> float | None:
"""Return latitude value of the device."""
return self.coordinator.data.gpsLatitude if self.coordinator.data else None
@property
def longitude(self) -> float | None:
"""Return longitude value of the device."""
return self.coordinator.data.gpsLongitude if self.coordinator.data else None
@property
def source_type(self) -> str:
"""Return the source type of the device."""
return SOURCE_TYPE_GPS
DEVICE_TRACKER_TYPES: tuple[RenaultEntityDescription, ...] = (
RenaultEntityDescription(
key="location",
coordinator="location",
icon="mdi:car",
name="Location",
),
)

View File

@ -3,11 +3,12 @@ from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, Optional, cast
from typing import TYPE_CHECKING, Any, Optional, cast
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import as_utc, parse_datetime
from .renault_coordinator import T
from .renault_vehicle import RenaultVehicleProxy
@ -54,8 +55,19 @@ class RenaultDataEntity(CoordinatorEntity[Optional[T]], Entity):
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return the state attributes of this entity."""
last_update: str | None = None
if self.entity_description.coordinator == "battery":
last_update = self._get_data_attr("timestamp")
if last_update:
return {ATTR_LAST_UPDATE: last_update}
last_update = cast(str, self._get_data_attr("timestamp"))
elif self.entity_description.coordinator == "location":
last_update = cast(str, self._get_data_attr("lastUpdateTime"))
if last_update:
return {ATTR_LAST_UPDATE: _convert_to_utc_string(last_update)}
return None
def _convert_to_utc_string(value: str) -> str:
"""Convert date to UTC iso format."""
original_dt = parse_datetime(value)
if TYPE_CHECKING:
assert original_dt is not None
return as_utc(original_dt).isoformat()

View File

@ -138,6 +138,11 @@ COORDINATORS: tuple[RenaultCoordinatorDescription, ...] = (
key="hvac_status",
update_method=lambda x: x.get_hvac_status,
),
RenaultCoordinatorDescription(
endpoint="location",
key="location",
update_method=lambda x: x.get_location,
),
RenaultCoordinatorDescription(
endpoint="battery-status",
key="battery",

View File

@ -63,6 +63,11 @@ def get_fixtures(vehicle_type: str) -> dict[str, Any]:
if "hvac_status" in mock_vehicle["endpoints"]
else load_fixture("renault/no_data.json")
).get_attributes(schemas.KamereonVehicleHvacStatusDataSchema),
"location": schemas.KamereonVehicleDataResponseSchema.loads(
load_fixture(f"renault/{mock_vehicle['endpoints']['location']}")
if "location" in mock_vehicle["endpoints"]
else load_fixture("renault/no_data.json")
).get_attributes(schemas.KamereonVehicleLocationDataSchema),
}
@ -132,6 +137,9 @@ async def setup_renault_integration_vehicle(hass: HomeAssistant, vehicle_type: s
), patch(
"renault_api.renault_vehicle.RenaultVehicle.get_hvac_status",
return_value=mock_fixtures["hvac_status"],
), patch(
"renault_api.renault_vehicle.RenaultVehicle.get_location",
return_value=mock_fixtures["location"],
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
@ -181,6 +189,9 @@ async def setup_renault_integration_vehicle_with_no_data(
), patch(
"renault_api.renault_vehicle.RenaultVehicle.get_hvac_status",
return_value=mock_fixtures["hvac_status"],
), patch(
"renault_api.renault_vehicle.RenaultVehicle.get_location",
return_value=mock_fixtures["location"],
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
@ -229,6 +240,9 @@ async def setup_renault_integration_vehicle_with_side_effect(
), patch(
"renault_api.renault_vehicle.RenaultVehicle.get_hvac_status",
side_effect=side_effect,
), patch(
"renault_api.renault_vehicle.RenaultVehicle.get_location",
side_effect=side_effect,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@ -4,6 +4,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASS_PLUG,
DOMAIN as BINARY_SENSOR_DOMAIN,
)
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN
from homeassistant.components.renault.const import (
CONF_KAMEREON_ACCOUNT_ID,
CONF_LOCALE,
@ -33,6 +34,7 @@ from homeassistant.const import (
LENGTH_KILOMETERS,
PERCENTAGE,
POWER_KILO_WATT,
STATE_NOT_HOME,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
@ -77,6 +79,7 @@ MOCK_VEHICLES = {
"endpoints_available": [
True, # cockpit
True, # hvac-status
False, # location
True, # battery-status
True, # charge-mode
],
@ -92,23 +95,24 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_plugged_in",
"result": STATE_ON,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777999_charging",
"result": STATE_ON,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
],
DEVICE_TRACKER_DOMAIN: [],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.battery_autonomy",
"unique_id": "vf1aaaaa555777999_battery_autonomy",
"result": "141",
ATTR_ICON: "mdi:ev-station",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
@ -117,7 +121,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_battery_available_energy",
"result": "31",
ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
},
@ -126,7 +130,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_battery_level",
"result": "60",
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
},
@ -135,7 +139,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_battery_temperature",
"result": "20",
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
},
@ -152,14 +156,14 @@ MOCK_VEHICLES = {
"result": "charge_in_progress",
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE,
ATTR_ICON: "mdi:flash",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
{
"entity_id": "sensor.charging_power",
"unique_id": "vf1aaaaa555777999_charging_power",
"result": "0.027",
ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT,
},
@ -168,7 +172,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_charging_remaining_time",
"result": "145",
ATTR_ICON: "mdi:timer",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES,
},
@ -194,7 +198,7 @@ MOCK_VEHICLES = {
"result": "plugged",
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE,
ATTR_ICON: "mdi:power-plug",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
],
},
@ -209,6 +213,7 @@ MOCK_VEHICLES = {
"endpoints_available": [
True, # cockpit
False, # hvac-status
True, # location
True, # battery-status
True, # charge-mode
],
@ -216,6 +221,7 @@ MOCK_VEHICLES = {
"battery_status": "battery_status_not_charging.json",
"charge_mode": "charge_mode_schedule.json",
"cockpit": "cockpit_ev.json",
"location": "location.json",
},
BINARY_SENSOR_DOMAIN: [
{
@ -223,23 +229,32 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_plugged_in",
"result": STATE_OFF,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777999_charging",
"result": STATE_OFF,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
},
],
DEVICE_TRACKER_DOMAIN: [
{
"entity_id": "device_tracker.location",
"unique_id": "vf1aaaaa555777999_location",
"result": STATE_NOT_HOME,
ATTR_ICON: "mdi:car",
ATTR_LAST_UPDATE: "2020-02-18T16:58:38+00:00",
}
],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.battery_autonomy",
"unique_id": "vf1aaaaa555777999_battery_autonomy",
"result": "128",
ATTR_ICON: "mdi:ev-station",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
@ -248,7 +263,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_battery_available_energy",
"result": "0",
ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
},
@ -257,7 +272,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_battery_level",
"result": "50",
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
},
@ -266,7 +281,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_battery_temperature",
"result": STATE_UNKNOWN,
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
},
@ -283,14 +298,14 @@ MOCK_VEHICLES = {
"result": "charge_error",
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE,
ATTR_ICON: "mdi:flash-off",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
},
{
"entity_id": "sensor.charging_power",
"unique_id": "vf1aaaaa555777999_charging_power",
"result": STATE_UNKNOWN,
ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER,
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT,
},
@ -299,7 +314,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777999_charging_remaining_time",
"result": STATE_UNKNOWN,
ATTR_ICON: "mdi:timer",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES,
},
@ -317,7 +332,7 @@ MOCK_VEHICLES = {
"result": "unplugged",
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE,
ATTR_ICON: "mdi:power-plug-off",
ATTR_LAST_UPDATE: "2020-11-17T09:06:48+01:00",
ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00",
},
],
},
@ -332,6 +347,7 @@ MOCK_VEHICLES = {
"endpoints_available": [
True, # cockpit
False, # hvac-status
True, # location
True, # battery-status
True, # charge-mode
],
@ -339,6 +355,7 @@ MOCK_VEHICLES = {
"battery_status": "battery_status_charging.json",
"charge_mode": "charge_mode_always.json",
"cockpit": "cockpit_fuel.json",
"location": "location.json",
},
BINARY_SENSOR_DOMAIN: [
{
@ -346,23 +363,32 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777123_plugged_in",
"result": STATE_ON,
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777123_charging",
"result": STATE_ON,
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
],
DEVICE_TRACKER_DOMAIN: [
{
"entity_id": "device_tracker.location",
"unique_id": "vf1aaaaa555777123_location",
"result": STATE_NOT_HOME,
ATTR_ICON: "mdi:car",
ATTR_LAST_UPDATE: "2020-02-18T16:58:38+00:00",
}
],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.battery_autonomy",
"unique_id": "vf1aaaaa555777123_battery_autonomy",
"result": "141",
ATTR_ICON: "mdi:ev-station",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
},
@ -371,7 +397,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777123_battery_available_energy",
"result": "31",
ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
},
@ -380,7 +406,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777123_battery_level",
"result": "60",
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
},
@ -389,7 +415,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777123_battery_temperature",
"result": "20",
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
},
@ -406,14 +432,14 @@ MOCK_VEHICLES = {
"result": "charge_in_progress",
ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE,
ATTR_ICON: "mdi:flash",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
{
"entity_id": "sensor.charging_power",
"unique_id": "vf1aaaaa555777123_charging_power",
"result": "27.0",
ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER,
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT,
},
@ -422,7 +448,7 @@ MOCK_VEHICLES = {
"unique_id": "vf1aaaaa555777123_charging_remaining_time",
"result": "145",
ATTR_ICON: "mdi:timer",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES,
},
@ -456,7 +482,7 @@ MOCK_VEHICLES = {
"result": "plugged",
ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE,
ATTR_ICON: "mdi:power-plug",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16Z",
ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00",
},
],
},
@ -471,11 +497,24 @@ MOCK_VEHICLES = {
"endpoints_available": [
True, # cockpit
False, # hvac-status
True, # location
# Ignore, # battery-status
# Ignore, # charge-mode
],
"endpoints": {"cockpit": "cockpit_fuel.json"},
"endpoints": {
"cockpit": "cockpit_fuel.json",
"location": "location.json",
},
BINARY_SENSOR_DOMAIN: [],
DEVICE_TRACKER_DOMAIN: [
{
"entity_id": "device_tracker.location",
"unique_id": "vf1aaaaa555777123_location",
"result": STATE_NOT_HOME,
ATTR_ICON: "mdi:car",
ATTR_LAST_UPDATE: "2020-02-18T16:58:38+00:00",
}
],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.fuel_autonomy",

View File

@ -0,0 +1,164 @@
"""Tests for Renault sensors."""
from unittest.mock import patch
import pytest
from renault_api.kamereon import exceptions
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN
from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE
from homeassistant.const import ATTR_ICON, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from . import (
check_device_registry,
get_no_data_icon,
setup_renault_integration_vehicle,
setup_renault_integration_vehicle_with_no_data,
setup_renault_integration_vehicle_with_side_effect,
)
from .const import DYNAMIC_ATTRIBUTES, FIXED_ATTRIBUTES, MOCK_VEHICLES
from tests.common import mock_device_registry, mock_registry
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
async def test_device_trackers(hass: HomeAssistant, vehicle_type: str):
"""Test for Renault device trackers."""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]):
await setup_renault_integration_vehicle(hass, vehicle_type)
await hass.async_block_till_done()
mock_vehicle = MOCK_VEHICLES[vehicle_type]
check_device_registry(device_registry, mock_vehicle["expected_device"])
expected_entities = mock_vehicle[DEVICE_TRACKER_DOMAIN]
assert len(entity_registry.entities) == len(expected_entities)
for expected_entity in expected_entities:
entity_id = expected_entity["entity_id"]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
state = hass.states.get(entity_id)
assert state.state == expected_entity["result"]
for attr in FIXED_ATTRIBUTES + DYNAMIC_ATTRIBUTES:
assert state.attributes.get(attr) == expected_entity.get(attr)
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
async def test_device_tracker_empty(hass: HomeAssistant, vehicle_type: str):
"""Test for Renault device trackers with empty data from Renault."""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]):
await setup_renault_integration_vehicle_with_no_data(hass, vehicle_type)
await hass.async_block_till_done()
mock_vehicle = MOCK_VEHICLES[vehicle_type]
check_device_registry(device_registry, mock_vehicle["expected_device"])
expected_entities = mock_vehicle[DEVICE_TRACKER_DOMAIN]
assert len(entity_registry.entities) == len(expected_entities)
for expected_entity in expected_entities:
entity_id = expected_entity["entity_id"]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
state = hass.states.get(entity_id)
assert state.state == STATE_UNKNOWN
for attr in FIXED_ATTRIBUTES:
assert state.attributes.get(attr) == expected_entity.get(attr)
# Check dynamic attributes:
assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity)
assert ATTR_LAST_UPDATE not in state.attributes
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
async def test_device_tracker_errors(hass: HomeAssistant, vehicle_type: str):
"""Test for Renault device trackers with temporary failure."""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
invalid_upstream_exception = exceptions.InvalidUpstreamException(
"err.tech.500",
"Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway",
)
with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]):
await setup_renault_integration_vehicle_with_side_effect(
hass, vehicle_type, invalid_upstream_exception
)
await hass.async_block_till_done()
mock_vehicle = MOCK_VEHICLES[vehicle_type]
check_device_registry(device_registry, mock_vehicle["expected_device"])
expected_entities = mock_vehicle[DEVICE_TRACKER_DOMAIN]
assert len(entity_registry.entities) == len(expected_entities)
for expected_entity in expected_entities:
entity_id = expected_entity["entity_id"]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
state = hass.states.get(entity_id)
assert state.state == STATE_UNAVAILABLE
for attr in FIXED_ATTRIBUTES:
assert state.attributes.get(attr) == expected_entity.get(attr)
# Check dynamic attributes:
assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity)
assert ATTR_LAST_UPDATE not in state.attributes
async def test_device_tracker_access_denied(hass: HomeAssistant):
"""Test for Renault device trackers with access denied failure."""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
vehicle_type = "zoe_40"
access_denied_exception = exceptions.AccessDeniedException(
"err.func.403",
"Access is denied for this resource",
)
with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]):
await setup_renault_integration_vehicle_with_side_effect(
hass, vehicle_type, access_denied_exception
)
await hass.async_block_till_done()
mock_vehicle = MOCK_VEHICLES[vehicle_type]
check_device_registry(device_registry, mock_vehicle["expected_device"])
assert len(entity_registry.entities) == 0
async def test_device_tracker_not_supported(hass: HomeAssistant):
"""Test for Renault device trackers with not supported failure."""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
vehicle_type = "zoe_40"
not_supported_exception = exceptions.NotSupportedException(
"err.tech.501",
"This feature is not technically supported by this gateway",
)
with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]):
await setup_renault_integration_vehicle_with_side_effect(
hass, vehicle_type, not_supported_exception
)
await hass.async_block_till_done()
mock_vehicle = MOCK_VEHICLES[vehicle_type]
check_device_registry(device_registry, mock_vehicle["expected_device"])
assert len(entity_registry.entities) == 0

11
tests/fixtures/renault/location.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"data": {
"type": "Car",
"id": "VF1AAAAA555777999",
"attributes": {
"gpsLatitude": 48.1234567,
"gpsLongitude": 11.1234567,
"lastUpdateTime": "2020-02-18T16:58:38Z"
}
}
}