Enforce device class for gas and energy sensors used by energy dashboard (#56218)

* Enforce device class for gas and energy sensors used by energy dashboard

* Adjust tests
pull/54954/head
Erik Montnemery 2021-09-14 16:56:36 +02:00 committed by GitHub
parent aaa62dadec
commit bac55b78fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 128 additions and 27 deletions

View File

@ -1,12 +1,13 @@
"""Validate the energy preferences provide valid data."""
from __future__ import annotations
from collections.abc import Sequence
from collections.abc import Mapping, Sequence
import dataclasses
from typing import Any
from homeassistant.components import recorder, sensor
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ENERGY_KILO_WATT_HOUR,
ENERGY_WATT_HOUR,
STATE_UNAVAILABLE,
@ -19,14 +20,16 @@ from homeassistant.core import HomeAssistant, callback, valid_entity_id
from . import data
from .const import DOMAIN
ENERGY_USAGE_UNITS = (ENERGY_KILO_WATT_HOUR, ENERGY_WATT_HOUR)
ENERGY_USAGE_DEVICE_CLASSES = (sensor.DEVICE_CLASS_ENERGY,)
ENERGY_USAGE_UNITS = {
sensor.DEVICE_CLASS_ENERGY: (ENERGY_KILO_WATT_HOUR, ENERGY_WATT_HOUR)
}
ENERGY_UNIT_ERROR = "entity_unexpected_unit_energy"
GAS_USAGE_UNITS = (
ENERGY_WATT_HOUR,
ENERGY_KILO_WATT_HOUR,
VOLUME_CUBIC_METERS,
VOLUME_CUBIC_FEET,
)
GAS_USAGE_DEVICE_CLASSES = (sensor.DEVICE_CLASS_ENERGY, sensor.DEVICE_CLASS_GAS)
GAS_USAGE_UNITS = {
sensor.DEVICE_CLASS_ENERGY: (ENERGY_WATT_HOUR, ENERGY_KILO_WATT_HOUR),
sensor.DEVICE_CLASS_GAS: (VOLUME_CUBIC_METERS, VOLUME_CUBIC_FEET),
}
GAS_UNIT_ERROR = "entity_unexpected_unit_gas"
@ -59,7 +62,8 @@ class EnergyPreferencesValidation:
def _async_validate_usage_stat(
hass: HomeAssistant,
stat_value: str,
allowed_units: Sequence[str],
allowed_device_classes: Sequence[str],
allowed_units: Mapping[str, Sequence[str]],
unit_error: str,
result: list[ValidationIssue],
) -> None:
@ -106,19 +110,29 @@ def _async_validate_usage_stat(
ValidationIssue("entity_negative_state", stat_value, current_value)
)
unit = state.attributes.get("unit_of_measurement")
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
if device_class not in allowed_device_classes:
result.append(
ValidationIssue(
"entity_unexpected_device_class",
stat_value,
device_class,
)
)
else:
unit = state.attributes.get("unit_of_measurement")
if unit not in allowed_units:
result.append(ValidationIssue(unit_error, stat_value, unit))
if device_class and unit not in allowed_units.get(device_class, []):
result.append(ValidationIssue(unit_error, stat_value, unit))
state_class = state.attributes.get("state_class")
state_class = state.attributes.get(sensor.ATTR_STATE_CLASS)
supported_state_classes = [
allowed_state_classes = [
sensor.STATE_CLASS_MEASUREMENT,
sensor.STATE_CLASS_TOTAL,
sensor.STATE_CLASS_TOTAL_INCREASING,
]
if state_class not in supported_state_classes:
if state_class not in allowed_state_classes:
result.append(
ValidationIssue(
"entity_unexpected_state_class_total_increasing",
@ -236,6 +250,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_usage_stat(
hass,
flow["stat_energy_from"],
ENERGY_USAGE_DEVICE_CLASSES,
ENERGY_USAGE_UNITS,
ENERGY_UNIT_ERROR,
source_result,
@ -258,6 +273,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_usage_stat(
hass,
flow["stat_energy_to"],
ENERGY_USAGE_DEVICE_CLASSES,
ENERGY_USAGE_UNITS,
ENERGY_UNIT_ERROR,
source_result,
@ -282,6 +298,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_usage_stat(
hass,
source["stat_energy_from"],
GAS_USAGE_DEVICE_CLASSES,
GAS_USAGE_UNITS,
GAS_UNIT_ERROR,
source_result,
@ -304,6 +321,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_usage_stat(
hass,
source["stat_energy_from"],
ENERGY_USAGE_DEVICE_CLASSES,
ENERGY_USAGE_UNITS,
ENERGY_UNIT_ERROR,
source_result,
@ -313,6 +331,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_usage_stat(
hass,
source["stat_energy_from"],
ENERGY_USAGE_DEVICE_CLASSES,
ENERGY_USAGE_UNITS,
ENERGY_UNIT_ERROR,
source_result,
@ -320,6 +339,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_usage_stat(
hass,
source["stat_energy_to"],
ENERGY_USAGE_DEVICE_CLASSES,
ENERGY_USAGE_UNITS,
ENERGY_UNIT_ERROR,
source_result,
@ -331,6 +351,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_usage_stat(
hass,
device["stat_consumption"],
ENERGY_USAGE_DEVICE_CLASSES,
ENERGY_USAGE_UNITS,
ENERGY_UNIT_ERROR,
device_result,

View File

@ -45,7 +45,11 @@ async def test_validation(hass, mock_energy_manager):
hass.states.async_set(
f"sensor.{key}",
"123",
{"unit_of_measurement": "kWh", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "kWh",
"state_class": "total_increasing",
},
)
await mock_energy_manager.async_update(
@ -142,7 +146,11 @@ async def test_validation_device_consumption_entity_unexpected_unit(
hass.states.async_set(
"sensor.unexpected_unit",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "beers",
"state_class": "total_increasing",
},
)
assert (await validate.async_validate(hass)).as_dict() == {
@ -194,7 +202,11 @@ async def test_validation_solar(hass, mock_energy_manager):
hass.states.async_set(
"sensor.solar_production",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "beers",
"state_class": "total_increasing",
},
)
assert (await validate.async_validate(hass)).as_dict() == {
@ -227,12 +239,20 @@ async def test_validation_battery(hass, mock_energy_manager):
hass.states.async_set(
"sensor.battery_import",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "beers",
"state_class": "total_increasing",
},
)
hass.states.async_set(
"sensor.battery_export",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "beers",
"state_class": "total_increasing",
},
)
assert (await validate.async_validate(hass)).as_dict() == {
@ -282,12 +302,20 @@ async def test_validation_grid(hass, mock_energy_manager, mock_is_entity_recorde
hass.states.async_set(
"sensor.grid_consumption_1",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "beers",
"state_class": "total_increasing",
},
)
hass.states.async_set(
"sensor.grid_production_1",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "beers",
"state_class": "total_increasing",
},
)
assert (await validate.async_validate(hass)).as_dict() == {
@ -324,12 +352,20 @@ async def test_validation_grid_price_not_exist(hass, mock_energy_manager):
hass.states.async_set(
"sensor.grid_consumption_1",
"10.10",
{"unit_of_measurement": "kWh", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "kWh",
"state_class": "total_increasing",
},
)
hass.states.async_set(
"sensor.grid_production_1",
"10.10",
{"unit_of_measurement": "kWh", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "kWh",
"state_class": "total_increasing",
},
)
await mock_energy_manager.async_update(
{
@ -402,7 +438,11 @@ async def test_validation_grid_price_errors(
hass.states.async_set(
"sensor.grid_consumption_1",
"10.10",
{"unit_of_measurement": "kWh", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "kWh",
"state_class": "total_increasing",
},
)
hass.states.async_set(
"sensor.grid_price_1",
@ -454,18 +494,50 @@ async def test_validation_gas(hass, mock_energy_manager, mock_is_entity_recorded
"stat_energy_from": "sensor.gas_consumption_2",
"stat_cost": "sensor.gas_cost_2",
},
{
"type": "gas",
"stat_energy_from": "sensor.gas_consumption_3",
"stat_cost": "sensor.gas_cost_2",
},
{
"type": "gas",
"stat_energy_from": "sensor.gas_consumption_4",
"stat_cost": "sensor.gas_cost_2",
},
]
}
)
hass.states.async_set(
"sensor.gas_consumption_1",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "beers",
"state_class": "total_increasing",
},
)
hass.states.async_set(
"sensor.gas_consumption_2",
"10.10",
{"unit_of_measurement": "kWh", "state_class": "total_increasing"},
{
"device_class": "energy",
"unit_of_measurement": "kWh",
"state_class": "total_increasing",
},
)
hass.states.async_set(
"sensor.gas_consumption_3",
"10.10",
{
"device_class": "gas",
"unit_of_measurement": "",
"state_class": "total_increasing",
},
)
hass.states.async_set(
"sensor.gas_consumption_4",
"10.10",
{"unit_of_measurement": "beers", "state_class": "total_increasing"},
)
hass.states.async_set(
"sensor.gas_cost_2",
@ -488,6 +560,14 @@ async def test_validation_gas(hass, mock_energy_manager, mock_is_entity_recorded
},
],
[],
[],
[
{
"type": "entity_unexpected_device_class",
"identifier": "sensor.gas_consumption_4",
"value": None,
},
],
],
"device_consumption": [],
}