Align number and sensor device classes (#81909)

* Align number and sensor device classes

* Add tests

* Tweak tests
This commit is contained in:
Erik Montnemery 2022-11-17 14:00:28 +01:00 committed by GitHub
parent 18e30e7c06
commit b6586d5c34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 293 additions and 23 deletions

View file

@ -55,8 +55,249 @@ _LOGGER = logging.getLogger(__name__)
class NumberDeviceClass(StrEnum): class NumberDeviceClass(StrEnum):
"""Device class for numbers.""" """Device class for numbers."""
# temperature (C/F) # NumberDeviceClass should be aligned with SensorDeviceClass
APPARENT_POWER = "apparent_power"
"""Apparent power.
Unit of measurement: `VA`
"""
AQI = "aqi"
"""Air Quality Index.
Unit of measurement: `None`
"""
BATTERY = "battery"
"""Percentage of battery that is left.
Unit of measurement: `%`
"""
CO = "carbon_monoxide"
"""Carbon Monoxide gas concentration.
Unit of measurement: `ppm` (parts per million)
"""
CO2 = "carbon_dioxide"
"""Carbon Dioxide gas concentration.
Unit of measurement: `ppm` (parts per million)
"""
CURRENT = "current"
"""Current.
Unit of measurement: `A`
"""
DISTANCE = "distance"
"""Generic distance.
Unit of measurement: `LENGTH_*` units
- SI /metric: `mm`, `cm`, `m`, `km`
- USCS / imperial: `in`, `ft`, `yd`, `mi`
"""
ENERGY = "energy"
"""Energy.
Unit of measurement: `Wh`, `kWh`, `MWh`, `GJ`
"""
FREQUENCY = "frequency"
"""Frequency.
Unit of measurement: `Hz`, `kHz`, `MHz`, `GHz`
"""
GAS = "gas"
"""Gas.
Unit of measurement: ``, `ft³`
"""
HUMIDITY = "humidity"
"""Relative humidity.
Unit of measurement: `%`
"""
ILLUMINANCE = "illuminance"
"""Illuminance.
Unit of measurement: `lx`, `lm`
"""
MOISTURE = "moisture"
"""Moisture.
Unit of measurement: `%`
"""
MONETARY = "monetary"
"""Amount of money.
Unit of measurement: ISO4217 currency code
See https://en.wikipedia.org/wiki/ISO_4217#Active_codes for active codes
"""
NITROGEN_DIOXIDE = "nitrogen_dioxide"
"""Amount of NO2.
Unit of measurement: `µg/`
"""
NITROGEN_MONOXIDE = "nitrogen_monoxide"
"""Amount of NO.
Unit of measurement: `µg/`
"""
NITROUS_OXIDE = "nitrous_oxide"
"""Amount of N2O.
Unit of measurement: `µg/`
"""
OZONE = "ozone"
"""Amount of O3.
Unit of measurement: `µg/`
"""
PM1 = "pm1"
"""Particulate matter <= 0.1 μm.
Unit of measurement: `µg/`
"""
PM10 = "pm10"
"""Particulate matter <= 10 μm.
Unit of measurement: `µg/`
"""
PM25 = "pm25"
"""Particulate matter <= 2.5 μm.
Unit of measurement: `µg/`
"""
POWER_FACTOR = "power_factor"
"""Power factor.
Unit of measurement: `%`
"""
POWER = "power"
"""Power.
Unit of measurement: `W`, `kW`
"""
PRECIPITATION_INTENSITY = "precipitation_intensity"
"""Precipitation intensity.
Unit of measurement: UnitOfVolumetricFlux
- SI /metric: `mm/d`, `mm/h`
- USCS / imperial: `in/d`, `in/h`
"""
PRESSURE = "pressure"
"""Pressure.
Unit of measurement:
- `mbar`, `cbar`, `bar`
- `Pa`, `hPa`, `kPa`
- `inHg`
- `psi`
"""
REACTIVE_POWER = "reactive_power"
"""Reactive power.
Unit of measurement: `var`
"""
SIGNAL_STRENGTH = "signal_strength"
"""Signal strength.
Unit of measurement: `dB`, `dBm`
"""
SPEED = "speed"
"""Generic speed.
Unit of measurement: `SPEED_*` units or `UnitOfVolumetricFlux`
- SI /metric: `mm/d`, `mm/h`, `m/s`, `km/h`
- USCS / imperial: `in/d`, `in/h`, `ft/s`, `mph`
- Nautical: `kn`
"""
SULPHUR_DIOXIDE = "sulphur_dioxide"
"""Amount of SO2.
Unit of measurement: `µg/`
"""
TEMPERATURE = "temperature" TEMPERATURE = "temperature"
"""Temperature.
Unit of measurement: `°C`, `°F`
"""
VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
"""Amount of VOC.
Unit of measurement: `µg/`
"""
VOLTAGE = "voltage"
"""Voltage.
Unit of measurement: `V`
"""
VOLUME = "volume"
"""Generic volume.
Unit of measurement: `VOLUME_*` units
- SI / metric: `mL`, `L`, ``
- USCS / imperial: `fl. oz.`, `ft³`, `gal` (warning: volumes expressed in
USCS/imperial units are currently assumed to be US volumes)
"""
WATER = "water"
"""Water.
Unit of measurement:
- SI / metric: ``, `L`
- USCS / imperial: `ft³`, `gal` (warning: volumes expressed in
USCS/imperial units are currently assumed to be US volumes)
"""
WEIGHT = "weight"
"""Generic weight, represents a measurement of an object's mass.
Weight is used instead of mass to fit with every day language.
Unit of measurement: `MASS_*` units
- SI / metric: `µg`, `mg`, `g`, `kg`
- USCS / imperial: `oz`, `lb`
"""
WIND_SPEED = "wind_speed"
"""Wind speed.
Unit of measurement: `SPEED_*` units
- SI /metric: `m/s`, `km/h`
- USCS / imperial: `ft/s`, `mph`
- Nautical: `kn`
"""
DEVICE_CLASSES_SCHEMA: Final = vol.All(vol.Lower, vol.Coerce(NumberDeviceClass)) DEVICE_CLASSES_SCHEMA: Final = vol.All(vol.Lower, vol.Coerce(NumberDeviceClass))

View file

@ -88,6 +88,30 @@ SCAN_INTERVAL: Final = timedelta(seconds=30)
class SensorDeviceClass(StrEnum): class SensorDeviceClass(StrEnum):
"""Device class for sensors.""" """Device class for sensors."""
# Non-numerical device classes
DATE = "date"
"""Date.
Unit of measurement: `None`
ISO8601 format: https://en.wikipedia.org/wiki/ISO_8601
"""
DURATION = "duration"
"""Fixed duration.
Unit of measurement: `d`, `h`, `min`, `s`
"""
TIMESTAMP = "timestamp"
"""Timestamp.
Unit of measurement: `None`
ISO8601 format: https://en.wikipedia.org/wiki/ISO_8601
"""
# Numerical device classes, these should be aligned with NumberDeviceClass
APPARENT_POWER = "apparent_power" APPARENT_POWER = "apparent_power"
"""Apparent power. """Apparent power.
@ -124,14 +148,6 @@ class SensorDeviceClass(StrEnum):
Unit of measurement: `A` Unit of measurement: `A`
""" """
DATE = "date"
"""Date.
Unit of measurement: `None`
ISO8601 format: https://en.wikipedia.org/wiki/ISO_8601
"""
DISTANCE = "distance" DISTANCE = "distance"
"""Generic distance. """Generic distance.
@ -140,12 +156,6 @@ class SensorDeviceClass(StrEnum):
- USCS / imperial: `in`, `ft`, `yd`, `mi` - USCS / imperial: `in`, `ft`, `yd`, `mi`
""" """
DURATION = "duration"
"""Fixed duration.
Unit of measurement: `d`, `h`, `min`, `s`
"""
ENERGY = "energy" ENERGY = "energy"
"""Energy. """Energy.
@ -295,14 +305,6 @@ class SensorDeviceClass(StrEnum):
Unit of measurement: `°C`, `°F` Unit of measurement: `°C`, `°F`
""" """
TIMESTAMP = "timestamp"
"""Timestamp.
Unit of measurement: `None`
ISO8601 format: https://en.wikipedia.org/wiki/ISO_8601
"""
VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
"""Amount of VOC. """Amount of VOC.

View file

@ -14,6 +14,7 @@ from homeassistant.components.number import (
NumberEntity, NumberEntity,
NumberEntityDescription, NumberEntityDescription,
) )
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
@ -848,3 +849,20 @@ async def test_custom_unit_change(
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
assert float(state.state) == pytest.approx(float(default_value)) assert float(state.state) == pytest.approx(float(default_value))
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == default_unit assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == default_unit
def test_device_classes_aligned():
"""Make sure all sensor device classes are also available in NumberDeviceClass."""
non_numeric_device_classes = {
SensorDeviceClass.DATE,
SensorDeviceClass.DURATION,
SensorDeviceClass.TIMESTAMP,
}
for device_class in SensorDeviceClass:
if device_class in non_numeric_device_classes:
continue
assert hasattr(NumberDeviceClass, device_class.name)
assert getattr(NumberDeviceClass, device_class.name).value == device_class.value

View file

@ -5,6 +5,7 @@ from decimal import Decimal
import pytest import pytest
from pytest import approx from pytest import approx
from homeassistant.components.number import NumberDeviceClass
from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
@ -927,3 +928,11 @@ async def test_unit_conversion_priority_legacy_conversion_removed(
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
assert float(state.state) == approx(float(original_value)) assert float(state.state) == approx(float(original_value))
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == original_unit assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == original_unit
def test_device_classes_aligned():
"""Make sure all number device classes are also available in SensorDeviceClass."""
for device_class in NumberDeviceClass:
assert hasattr(SensorDeviceClass, device_class.name)
assert getattr(SensorDeviceClass, device_class.name).value == device_class.value