1
mirror of https://github.com/home-assistant/core synced 2024-09-06 10:29:55 +02:00

Add binary sensor platform to Renault integration (#54750)

* Add binary sensor platform

* Add tests

* Simplify code

* Adjust descriptions

* Adjust tests

* Make "fuel" tests more explicit

* Updates for device registry checks
This commit is contained in:
epenet 2021-08-19 09:27:43 +02:00 committed by GitHub
parent e7fa3e727b
commit faec82ae8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 264 additions and 0 deletions

View File

@ -0,0 +1,58 @@
"""Support for Renault binary sensors."""
from __future__ import annotations
from renault_api.kamereon.enums import ChargeState, PlugState
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_PLUG,
BinarySensorEntity,
)
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 RenaultBatteryDataEntity, RenaultDataEntity
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[RenaultDataEntity] = []
for vehicle in proxy.vehicles.values():
if "battery" in vehicle.coordinators:
entities.append(RenaultPluggedInSensor(vehicle, "Plugged In"))
entities.append(RenaultChargingSensor(vehicle, "Charging"))
async_add_entities(entities)
class RenaultPluggedInSensor(RenaultBatteryDataEntity, BinarySensorEntity):
"""Plugged In binary sensor."""
_attr_device_class = DEVICE_CLASS_PLUG
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
if (not self.data) or (self.data.plugStatus is None):
return None
return self.data.get_plug_status() == PlugState.PLUGGED
class RenaultChargingSensor(RenaultBatteryDataEntity, BinarySensorEntity):
"""Charging binary sensor."""
_attr_device_class = DEVICE_CLASS_BATTERY_CHARGING
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
if (not self.data) or (self.data.chargingStatus is None):
return None
return self.data.get_charging_status() == ChargeState.CHARGE_IN_PROGRESS

View File

@ -7,6 +7,7 @@ CONF_KAMEREON_ACCOUNT_ID = "kamereon_account_id"
DEFAULT_SCAN_INTERVAL = 300 # 5 minutes
PLATFORMS = [
"binary_sensor",
"sensor",
]

View File

@ -1,4 +1,9 @@
"""Constants for the Renault integration tests."""
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_PLUG,
DOMAIN as BINARY_SENSOR_DOMAIN,
)
from homeassistant.components.renault.const import (
CONF_KAMEREON_ACCOUNT_ID,
CONF_LOCALE,
@ -19,6 +24,8 @@ from homeassistant.const import (
LENGTH_KILOMETERS,
PERCENTAGE,
POWER_KILO_WATT,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
TEMP_CELSIUS,
TIME_MINUTES,
@ -54,6 +61,20 @@ MOCK_VEHICLES = {
"cockpit": "cockpit_ev.json",
"hvac_status": "hvac_status.json",
},
BINARY_SENSOR_DOMAIN: [
{
"entity_id": "binary_sensor.plugged_in",
"unique_id": "vf1aaaaa555777999_plugged_in",
"result": STATE_ON,
"class": DEVICE_CLASS_PLUG,
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777999_charging",
"result": STATE_ON,
"class": DEVICE_CLASS_BATTERY_CHARGING,
},
],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.battery_autonomy",
@ -147,6 +168,20 @@ MOCK_VEHICLES = {
"charge_mode": "charge_mode_schedule.json",
"cockpit": "cockpit_ev.json",
},
BINARY_SENSOR_DOMAIN: [
{
"entity_id": "binary_sensor.plugged_in",
"unique_id": "vf1aaaaa555777999_plugged_in",
"result": STATE_OFF,
"class": DEVICE_CLASS_PLUG,
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777999_charging",
"result": STATE_OFF,
"class": DEVICE_CLASS_BATTERY_CHARGING,
},
],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.battery_autonomy",
@ -233,6 +268,20 @@ MOCK_VEHICLES = {
"charge_mode": "charge_mode_always.json",
"cockpit": "cockpit_fuel.json",
},
BINARY_SENSOR_DOMAIN: [
{
"entity_id": "binary_sensor.plugged_in",
"unique_id": "vf1aaaaa555777123_plugged_in",
"result": STATE_ON,
"class": DEVICE_CLASS_PLUG,
},
{
"entity_id": "binary_sensor.charging",
"unique_id": "vf1aaaaa555777123_charging",
"result": STATE_ON,
"class": DEVICE_CLASS_BATTERY_CHARGING,
},
],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.battery_autonomy",
@ -327,6 +376,7 @@ MOCK_VEHICLES = {
# Ignore, # charge-mode
],
"endpoints": {"cockpit": "cockpit_fuel.json"},
BINARY_SENSOR_DOMAIN: [],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.fuel_autonomy",

View File

@ -0,0 +1,155 @@
"""Tests for Renault binary sensors."""
from unittest.mock import patch
import pytest
from renault_api.kamereon import exceptions
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
from homeassistant.setup import async_setup_component
from . import (
check_device_registry,
setup_renault_integration_vehicle,
setup_renault_integration_vehicle_with_no_data,
setup_renault_integration_vehicle_with_side_effect,
)
from .const import MOCK_VEHICLES
from tests.common import mock_device_registry, mock_registry
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
async def test_binary_sensors(hass, vehicle_type):
"""Test for Renault binary sensors."""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
with patch("homeassistant.components.renault.PLATFORMS", [BINARY_SENSOR_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[BINARY_SENSOR_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"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == expected_entity["result"]
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
async def test_binary_sensor_empty(hass, vehicle_type):
"""Test for Renault binary sensors 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", [BINARY_SENSOR_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[BINARY_SENSOR_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"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys())
async def test_binary_sensor_errors(hass, vehicle_type):
"""Test for Renault binary sensors 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", [BINARY_SENSOR_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[BINARY_SENSOR_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"]
assert registry_entry.unit_of_measurement == expected_entity.get("unit")
assert registry_entry.device_class == expected_entity.get("class")
state = hass.states.get(entity_id)
assert state.state == STATE_UNAVAILABLE
async def test_binary_sensor_access_denied(hass):
"""Test for Renault binary sensors 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", [BINARY_SENSOR_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_binary_sensor_not_supported(hass):
"""Test for Renault binary sensors 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", [BINARY_SENSOR_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