Add Siemes and Millisiemens as additional units of conductivity and enable conversion between conductivity units (#118728)
This commit is contained in:
parent
818750dfd1
commit
0dd5391cd7
17 changed files with 112 additions and 16 deletions
|
@ -16,7 +16,12 @@ from homeassistant.components.sensor import (
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
|
from homeassistant.const import (
|
||||||
|
PERCENTAGE,
|
||||||
|
EntityCategory,
|
||||||
|
UnitOfConductivity,
|
||||||
|
UnitOfTemperature,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
@ -105,7 +110,8 @@ SENSORS: Final[list[FytaSensorEntityDescription]] = [
|
||||||
FytaSensorEntityDescription(
|
FytaSensorEntityDescription(
|
||||||
key="salinity",
|
key="salinity",
|
||||||
translation_key="salinity",
|
translation_key="salinity",
|
||||||
native_unit_of_measurement="mS/cm",
|
native_unit_of_measurement=UnitOfConductivity.MILLISIEMENS,
|
||||||
|
device_class=SensorDeviceClass.CONDUCTIVITY,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
FytaSensorEntityDescription(
|
FytaSensorEntityDescription(
|
||||||
|
|
|
@ -15,12 +15,12 @@ from homeassistant.components.sensor import (
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONDUCTIVITY,
|
|
||||||
DEGREE,
|
DEGREE,
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
Platform,
|
Platform,
|
||||||
UnitOfApparentPower,
|
UnitOfApparentPower,
|
||||||
|
UnitOfConductivity,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
UnitOfEnergy,
|
UnitOfEnergy,
|
||||||
|
@ -191,7 +191,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||||
),
|
),
|
||||||
"V_EC": SensorEntityDescription(
|
"V_EC": SensorEntityDescription(
|
||||||
key="V_EC",
|
key="V_EC",
|
||||||
native_unit_of_measurement=CONDUCTIVITY,
|
native_unit_of_measurement=UnitOfConductivity.MICROSIEMENS,
|
||||||
),
|
),
|
||||||
"V_VAR": SensorEntityDescription(
|
"V_VAR": SensorEntityDescription(
|
||||||
key="V_VAR",
|
key="V_VAR",
|
||||||
|
|
|
@ -18,6 +18,7 @@ from homeassistant.const import (
|
||||||
SIGNAL_STRENGTH_DECIBELS,
|
SIGNAL_STRENGTH_DECIBELS,
|
||||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
UnitOfApparentPower,
|
UnitOfApparentPower,
|
||||||
|
UnitOfConductivity,
|
||||||
UnitOfDataRate,
|
UnitOfDataRate,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
|
@ -120,6 +121,12 @@ class NumberDeviceClass(StrEnum):
|
||||||
Unit of measurement: `ppm` (parts per million)
|
Unit of measurement: `ppm` (parts per million)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
CONDUCTIVITY = "conductivity"
|
||||||
|
"""Conductivity.
|
||||||
|
|
||||||
|
Unit of measurement: `S/cm`, `mS/cm`, `µS/cm`
|
||||||
|
"""
|
||||||
|
|
||||||
CURRENT = "current"
|
CURRENT = "current"
|
||||||
"""Current.
|
"""Current.
|
||||||
|
|
||||||
|
@ -424,6 +431,7 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
|
||||||
NumberDeviceClass.BATTERY: {PERCENTAGE},
|
NumberDeviceClass.BATTERY: {PERCENTAGE},
|
||||||
NumberDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION},
|
NumberDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION},
|
||||||
NumberDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION},
|
NumberDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION},
|
||||||
|
NumberDeviceClass.CONDUCTIVITY: set(UnitOfConductivity),
|
||||||
NumberDeviceClass.CURRENT: set(UnitOfElectricCurrent),
|
NumberDeviceClass.CURRENT: set(UnitOfElectricCurrent),
|
||||||
NumberDeviceClass.DATA_RATE: set(UnitOfDataRate),
|
NumberDeviceClass.DATA_RATE: set(UnitOfDataRate),
|
||||||
NumberDeviceClass.DATA_SIZE: set(UnitOfInformation),
|
NumberDeviceClass.DATA_SIZE: set(UnitOfInformation),
|
||||||
|
|
|
@ -10,7 +10,6 @@ import voluptuous as vol
|
||||||
from homeassistant.components.recorder import get_instance, history
|
from homeassistant.components.recorder import get_instance, history
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONDUCTIVITY,
|
|
||||||
CONF_SENSORS,
|
CONF_SENSORS,
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
|
@ -18,6 +17,7 @@ from homeassistant.const import (
|
||||||
STATE_PROBLEM,
|
STATE_PROBLEM,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
|
UnitOfConductivity,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
|
@ -148,7 +148,7 @@ class Plant(Entity):
|
||||||
"max": CONF_MAX_MOISTURE,
|
"max": CONF_MAX_MOISTURE,
|
||||||
},
|
},
|
||||||
READING_CONDUCTIVITY: {
|
READING_CONDUCTIVITY: {
|
||||||
ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY,
|
ATTR_UNIT_OF_MEASUREMENT: UnitOfConductivity.MICROSIEMENS,
|
||||||
"min": CONF_MIN_CONDUCTIVITY,
|
"min": CONF_MIN_CONDUCTIVITY,
|
||||||
"max": CONF_MAX_CONDUCTIVITY,
|
"max": CONF_MAX_CONDUCTIVITY,
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,6 +28,7 @@ from homeassistant.helpers.typing import UNDEFINED, UndefinedType
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util.unit_conversion import (
|
from homeassistant.util.unit_conversion import (
|
||||||
BaseUnitConverter,
|
BaseUnitConverter,
|
||||||
|
ConductivityConverter,
|
||||||
DataRateConverter,
|
DataRateConverter,
|
||||||
DistanceConverter,
|
DistanceConverter,
|
||||||
DurationConverter,
|
DurationConverter,
|
||||||
|
@ -126,6 +127,7 @@ QUERY_STATISTICS_SUMMARY_SUM = (
|
||||||
|
|
||||||
|
|
||||||
STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = {
|
STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = {
|
||||||
|
**{unit: ConductivityConverter for unit in ConductivityConverter.VALID_UNITS},
|
||||||
**{unit: DataRateConverter for unit in DataRateConverter.VALID_UNITS},
|
**{unit: DataRateConverter for unit in DataRateConverter.VALID_UNITS},
|
||||||
**{unit: DistanceConverter for unit in DistanceConverter.VALID_UNITS},
|
**{unit: DistanceConverter for unit in DistanceConverter.VALID_UNITS},
|
||||||
**{unit: DurationConverter for unit in DurationConverter.VALID_UNITS},
|
**{unit: DurationConverter for unit in DurationConverter.VALID_UNITS},
|
||||||
|
@ -154,7 +156,7 @@ def mean(values: list[float]) -> float | None:
|
||||||
|
|
||||||
This is a very simple version that only works
|
This is a very simple version that only works
|
||||||
with a non-empty list of floats. The built-in
|
with a non-empty list of floats. The built-in
|
||||||
statistics.mean is more robust but is is almost
|
statistics.mean is more robust but is almost
|
||||||
an order of magnitude slower.
|
an order of magnitude slower.
|
||||||
"""
|
"""
|
||||||
return sum(values) / len(values)
|
return sum(values) / len(values)
|
||||||
|
|
|
@ -48,6 +48,7 @@ from .util import PERIOD_SCHEMA, get_instance, resolve_period
|
||||||
|
|
||||||
UNIT_SCHEMA = vol.Schema(
|
UNIT_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
|
vol.Optional("conductivity"): vol.In(DataRateConverter.VALID_UNITS),
|
||||||
vol.Optional("data_rate"): vol.In(DataRateConverter.VALID_UNITS),
|
vol.Optional("data_rate"): vol.In(DataRateConverter.VALID_UNITS),
|
||||||
vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS),
|
vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS),
|
||||||
vol.Optional("duration"): vol.In(DurationConverter.VALID_UNITS),
|
vol.Optional("duration"): vol.In(DurationConverter.VALID_UNITS),
|
||||||
|
|
|
@ -18,6 +18,7 @@ from homeassistant.const import (
|
||||||
SIGNAL_STRENGTH_DECIBELS,
|
SIGNAL_STRENGTH_DECIBELS,
|
||||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
UnitOfApparentPower,
|
UnitOfApparentPower,
|
||||||
|
UnitOfConductivity,
|
||||||
UnitOfDataRate,
|
UnitOfDataRate,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
|
@ -46,6 +47,7 @@ from homeassistant.helpers.deprecation import (
|
||||||
)
|
)
|
||||||
from homeassistant.util.unit_conversion import (
|
from homeassistant.util.unit_conversion import (
|
||||||
BaseUnitConverter,
|
BaseUnitConverter,
|
||||||
|
ConductivityConverter,
|
||||||
DataRateConverter,
|
DataRateConverter,
|
||||||
DistanceConverter,
|
DistanceConverter,
|
||||||
DurationConverter,
|
DurationConverter,
|
||||||
|
@ -137,6 +139,12 @@ class SensorDeviceClass(StrEnum):
|
||||||
Unit of measurement: `ppm` (parts per million)
|
Unit of measurement: `ppm` (parts per million)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
CONDUCTIVITY = "conductivity"
|
||||||
|
"""Conductivity.
|
||||||
|
|
||||||
|
Unit of measurement: `S/cm`, `mS/cm`, `µS/cm`
|
||||||
|
"""
|
||||||
|
|
||||||
CURRENT = "current"
|
CURRENT = "current"
|
||||||
"""Current.
|
"""Current.
|
||||||
|
|
||||||
|
@ -485,6 +493,7 @@ STATE_CLASSES: Final[list[str]] = [cls.value for cls in SensorStateClass]
|
||||||
|
|
||||||
UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = {
|
UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = {
|
||||||
SensorDeviceClass.ATMOSPHERIC_PRESSURE: PressureConverter,
|
SensorDeviceClass.ATMOSPHERIC_PRESSURE: PressureConverter,
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: ConductivityConverter,
|
||||||
SensorDeviceClass.CURRENT: ElectricCurrentConverter,
|
SensorDeviceClass.CURRENT: ElectricCurrentConverter,
|
||||||
SensorDeviceClass.DATA_RATE: DataRateConverter,
|
SensorDeviceClass.DATA_RATE: DataRateConverter,
|
||||||
SensorDeviceClass.DATA_SIZE: InformationConverter,
|
SensorDeviceClass.DATA_SIZE: InformationConverter,
|
||||||
|
@ -517,6 +526,7 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = {
|
||||||
SensorDeviceClass.BATTERY: {PERCENTAGE},
|
SensorDeviceClass.BATTERY: {PERCENTAGE},
|
||||||
SensorDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION},
|
SensorDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION},
|
||||||
SensorDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION},
|
SensorDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION},
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: set(UnitOfConductivity),
|
||||||
SensorDeviceClass.CURRENT: set(UnitOfElectricCurrent),
|
SensorDeviceClass.CURRENT: set(UnitOfElectricCurrent),
|
||||||
SensorDeviceClass.DATA_RATE: set(UnitOfDataRate),
|
SensorDeviceClass.DATA_RATE: set(UnitOfDataRate),
|
||||||
SensorDeviceClass.DATA_SIZE: set(UnitOfInformation),
|
SensorDeviceClass.DATA_SIZE: set(UnitOfInformation),
|
||||||
|
@ -591,6 +601,7 @@ DEVICE_CLASS_STATE_CLASSES: dict[SensorDeviceClass, set[SensorStateClass]] = {
|
||||||
SensorDeviceClass.BATTERY: {SensorStateClass.MEASUREMENT},
|
SensorDeviceClass.BATTERY: {SensorStateClass.MEASUREMENT},
|
||||||
SensorDeviceClass.CO: {SensorStateClass.MEASUREMENT},
|
SensorDeviceClass.CO: {SensorStateClass.MEASUREMENT},
|
||||||
SensorDeviceClass.CO2: {SensorStateClass.MEASUREMENT},
|
SensorDeviceClass.CO2: {SensorStateClass.MEASUREMENT},
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: {SensorStateClass.MEASUREMENT},
|
||||||
SensorDeviceClass.CURRENT: {SensorStateClass.MEASUREMENT},
|
SensorDeviceClass.CURRENT: {SensorStateClass.MEASUREMENT},
|
||||||
SensorDeviceClass.DATA_RATE: {SensorStateClass.MEASUREMENT},
|
SensorDeviceClass.DATA_RATE: {SensorStateClass.MEASUREMENT},
|
||||||
SensorDeviceClass.DATA_SIZE: set(SensorStateClass),
|
SensorDeviceClass.DATA_SIZE: set(SensorStateClass),
|
||||||
|
|
|
@ -41,6 +41,7 @@ CONF_IS_ATMOSPHERIC_PRESSURE = "is_atmospheric_pressure"
|
||||||
CONF_IS_BATTERY_LEVEL = "is_battery_level"
|
CONF_IS_BATTERY_LEVEL = "is_battery_level"
|
||||||
CONF_IS_CO = "is_carbon_monoxide"
|
CONF_IS_CO = "is_carbon_monoxide"
|
||||||
CONF_IS_CO2 = "is_carbon_dioxide"
|
CONF_IS_CO2 = "is_carbon_dioxide"
|
||||||
|
CONF_IS_CONDUCTIVITY = "is_conductivity"
|
||||||
CONF_IS_CURRENT = "is_current"
|
CONF_IS_CURRENT = "is_current"
|
||||||
CONF_IS_DATA_RATE = "is_data_rate"
|
CONF_IS_DATA_RATE = "is_data_rate"
|
||||||
CONF_IS_DATA_SIZE = "is_data_size"
|
CONF_IS_DATA_SIZE = "is_data_size"
|
||||||
|
@ -90,6 +91,7 @@ ENTITY_CONDITIONS = {
|
||||||
SensorDeviceClass.BATTERY: [{CONF_TYPE: CONF_IS_BATTERY_LEVEL}],
|
SensorDeviceClass.BATTERY: [{CONF_TYPE: CONF_IS_BATTERY_LEVEL}],
|
||||||
SensorDeviceClass.CO: [{CONF_TYPE: CONF_IS_CO}],
|
SensorDeviceClass.CO: [{CONF_TYPE: CONF_IS_CO}],
|
||||||
SensorDeviceClass.CO2: [{CONF_TYPE: CONF_IS_CO2}],
|
SensorDeviceClass.CO2: [{CONF_TYPE: CONF_IS_CO2}],
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: [{CONF_TYPE: CONF_IS_CONDUCTIVITY}],
|
||||||
SensorDeviceClass.CURRENT: [{CONF_TYPE: CONF_IS_CURRENT}],
|
SensorDeviceClass.CURRENT: [{CONF_TYPE: CONF_IS_CURRENT}],
|
||||||
SensorDeviceClass.DATA_RATE: [{CONF_TYPE: CONF_IS_DATA_RATE}],
|
SensorDeviceClass.DATA_RATE: [{CONF_TYPE: CONF_IS_DATA_RATE}],
|
||||||
SensorDeviceClass.DATA_SIZE: [{CONF_TYPE: CONF_IS_DATA_SIZE}],
|
SensorDeviceClass.DATA_SIZE: [{CONF_TYPE: CONF_IS_DATA_SIZE}],
|
||||||
|
@ -153,6 +155,7 @@ CONDITION_SCHEMA = vol.All(
|
||||||
CONF_IS_BATTERY_LEVEL,
|
CONF_IS_BATTERY_LEVEL,
|
||||||
CONF_IS_CO,
|
CONF_IS_CO,
|
||||||
CONF_IS_CO2,
|
CONF_IS_CO2,
|
||||||
|
CONF_IS_CONDUCTIVITY,
|
||||||
CONF_IS_CURRENT,
|
CONF_IS_CURRENT,
|
||||||
CONF_IS_DATA_RATE,
|
CONF_IS_DATA_RATE,
|
||||||
CONF_IS_DATA_SIZE,
|
CONF_IS_DATA_SIZE,
|
||||||
|
|
|
@ -40,6 +40,7 @@ CONF_ATMOSPHERIC_PRESSURE = "atmospheric_pressure"
|
||||||
CONF_BATTERY_LEVEL = "battery_level"
|
CONF_BATTERY_LEVEL = "battery_level"
|
||||||
CONF_CO = "carbon_monoxide"
|
CONF_CO = "carbon_monoxide"
|
||||||
CONF_CO2 = "carbon_dioxide"
|
CONF_CO2 = "carbon_dioxide"
|
||||||
|
CONF_CONDUCTIVITY = "conductivity"
|
||||||
CONF_CURRENT = "current"
|
CONF_CURRENT = "current"
|
||||||
CONF_DATA_RATE = "data_rate"
|
CONF_DATA_RATE = "data_rate"
|
||||||
CONF_DATA_SIZE = "data_size"
|
CONF_DATA_SIZE = "data_size"
|
||||||
|
@ -89,6 +90,7 @@ ENTITY_TRIGGERS = {
|
||||||
SensorDeviceClass.BATTERY: [{CONF_TYPE: CONF_BATTERY_LEVEL}],
|
SensorDeviceClass.BATTERY: [{CONF_TYPE: CONF_BATTERY_LEVEL}],
|
||||||
SensorDeviceClass.CO: [{CONF_TYPE: CONF_CO}],
|
SensorDeviceClass.CO: [{CONF_TYPE: CONF_CO}],
|
||||||
SensorDeviceClass.CO2: [{CONF_TYPE: CONF_CO2}],
|
SensorDeviceClass.CO2: [{CONF_TYPE: CONF_CO2}],
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: [{CONF_TYPE: CONF_CONDUCTIVITY}],
|
||||||
SensorDeviceClass.CURRENT: [{CONF_TYPE: CONF_CURRENT}],
|
SensorDeviceClass.CURRENT: [{CONF_TYPE: CONF_CURRENT}],
|
||||||
SensorDeviceClass.DATA_RATE: [{CONF_TYPE: CONF_DATA_RATE}],
|
SensorDeviceClass.DATA_RATE: [{CONF_TYPE: CONF_DATA_RATE}],
|
||||||
SensorDeviceClass.DATA_SIZE: [{CONF_TYPE: CONF_DATA_SIZE}],
|
SensorDeviceClass.DATA_SIZE: [{CONF_TYPE: CONF_DATA_SIZE}],
|
||||||
|
@ -153,6 +155,7 @@ TRIGGER_SCHEMA = vol.All(
|
||||||
CONF_BATTERY_LEVEL,
|
CONF_BATTERY_LEVEL,
|
||||||
CONF_CO,
|
CONF_CO,
|
||||||
CONF_CO2,
|
CONF_CO2,
|
||||||
|
CONF_CONDUCTIVITY,
|
||||||
CONF_CURRENT,
|
CONF_CURRENT,
|
||||||
CONF_DATA_RATE,
|
CONF_DATA_RATE,
|
||||||
CONF_DATA_SIZE,
|
CONF_DATA_SIZE,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"is_battery_level": "Current {entity_name} battery level",
|
"is_battery_level": "Current {entity_name} battery level",
|
||||||
"is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level",
|
"is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level",
|
||||||
"is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level",
|
"is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level",
|
||||||
|
"is_conductivity": "Current {entity_name} conductivity",
|
||||||
"is_current": "Current {entity_name} current",
|
"is_current": "Current {entity_name} current",
|
||||||
"is_data_rate": "Current {entity_name} data rate",
|
"is_data_rate": "Current {entity_name} data rate",
|
||||||
"is_data_size": "Current {entity_name} data size",
|
"is_data_size": "Current {entity_name} data size",
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
"battery_level": "{entity_name} battery level changes",
|
"battery_level": "{entity_name} battery level changes",
|
||||||
"carbon_monoxide": "{entity_name} carbon monoxide concentration changes",
|
"carbon_monoxide": "{entity_name} carbon monoxide concentration changes",
|
||||||
"carbon_dioxide": "{entity_name} carbon dioxide concentration changes",
|
"carbon_dioxide": "{entity_name} carbon dioxide concentration changes",
|
||||||
|
"conductivity": "{entity_name} conductivity changes",
|
||||||
"current": "{entity_name} current changes",
|
"current": "{entity_name} current changes",
|
||||||
"data_rate": "{entity_name} data rate changes",
|
"data_rate": "{entity_name} data rate changes",
|
||||||
"data_size": "{entity_name} data size changes",
|
"data_size": "{entity_name} data size changes",
|
||||||
|
@ -153,6 +155,9 @@
|
||||||
"carbon_dioxide": {
|
"carbon_dioxide": {
|
||||||
"name": "Carbon dioxide"
|
"name": "Carbon dioxide"
|
||||||
},
|
},
|
||||||
|
"conductivity": {
|
||||||
|
"name": "Conductivity"
|
||||||
|
},
|
||||||
"current": {
|
"current": {
|
||||||
"name": "Current"
|
"name": "Current"
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,11 +20,11 @@ from homeassistant.components.sensor import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||||
CONDUCTIVITY,
|
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
|
UnitOfConductivity,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
UnitOfMass,
|
UnitOfMass,
|
||||||
UnitOfPressure,
|
UnitOfPressure,
|
||||||
|
@ -53,7 +53,7 @@ SENSOR_DESCRIPTIONS = {
|
||||||
(DeviceClass.CONDUCTIVITY, Units.CONDUCTIVITY): SensorEntityDescription(
|
(DeviceClass.CONDUCTIVITY, Units.CONDUCTIVITY): SensorEntityDescription(
|
||||||
key=str(Units.CONDUCTIVITY),
|
key=str(Units.CONDUCTIVITY),
|
||||||
device_class=None,
|
device_class=None,
|
||||||
native_unit_of_measurement=CONDUCTIVITY,
|
native_unit_of_measurement=UnitOfConductivity.MICROSIEMENS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
|
|
@ -1129,8 +1129,21 @@ _DEPRECATED_MASS_POUNDS: Final = DeprecatedConstantEnum(
|
||||||
)
|
)
|
||||||
"""Deprecated: please use UnitOfMass.POUNDS"""
|
"""Deprecated: please use UnitOfMass.POUNDS"""
|
||||||
|
|
||||||
|
|
||||||
# Conductivity units
|
# Conductivity units
|
||||||
CONDUCTIVITY: Final = "µS/cm"
|
class UnitOfConductivity(StrEnum):
|
||||||
|
"""Conductivity units."""
|
||||||
|
|
||||||
|
SIEMENS = "S/cm"
|
||||||
|
MICROSIEMENS = "µS/cm"
|
||||||
|
MILLISIEMENS = "mS/cm"
|
||||||
|
|
||||||
|
|
||||||
|
_DEPRECATED_CONDUCTIVITY: Final = DeprecatedConstantEnum(
|
||||||
|
UnitOfConductivity.MICROSIEMENS,
|
||||||
|
"2025.6",
|
||||||
|
)
|
||||||
|
"""Deprecated: please use UnitOfConductivity.MICROSIEMENS"""
|
||||||
|
|
||||||
# Light units
|
# Light units
|
||||||
LIGHT_LUX: Final = "lx"
|
LIGHT_LUX: Final = "lx"
|
||||||
|
|
|
@ -10,6 +10,7 @@ from homeassistant.const import (
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
UNIT_NOT_RECOGNIZED_TEMPLATE,
|
UNIT_NOT_RECOGNIZED_TEMPLATE,
|
||||||
|
UnitOfConductivity,
|
||||||
UnitOfDataRate,
|
UnitOfDataRate,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
|
@ -169,6 +170,19 @@ class DistanceConverter(BaseUnitConverter):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ConductivityConverter(BaseUnitConverter):
|
||||||
|
"""Utility to convert electric current values."""
|
||||||
|
|
||||||
|
UNIT_CLASS = "conductivity"
|
||||||
|
NORMALIZED_UNIT = UnitOfConductivity.MICROSIEMENS
|
||||||
|
_UNIT_CONVERSION: dict[str | None, float] = {
|
||||||
|
UnitOfConductivity.MICROSIEMENS: 1,
|
||||||
|
UnitOfConductivity.MILLISIEMENS: 1e-3,
|
||||||
|
UnitOfConductivity.SIEMENS: 1e-6,
|
||||||
|
}
|
||||||
|
VALID_UNITS = set(UnitOfConductivity)
|
||||||
|
|
||||||
|
|
||||||
class ElectricCurrentConverter(BaseUnitConverter):
|
class ElectricCurrentConverter(BaseUnitConverter):
|
||||||
"""Utility to convert electric current values."""
|
"""Utility to convert electric current values."""
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ from homeassistant.components import plant
|
||||||
from homeassistant.components.recorder import Recorder
|
from homeassistant.components.recorder import Recorder
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONDUCTIVITY,
|
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
STATE_OK,
|
STATE_OK,
|
||||||
STATE_PROBLEM,
|
STATE_PROBLEM,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
|
UnitOfConductivity,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, State
|
from homeassistant.core import HomeAssistant, State
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
@ -79,7 +79,9 @@ async def test_low_battery(hass: HomeAssistant) -> None:
|
||||||
|
|
||||||
async def test_initial_states(hass: HomeAssistant) -> None:
|
async def test_initial_states(hass: HomeAssistant) -> None:
|
||||||
"""Test plant initialises attributes if sensor already exists."""
|
"""Test plant initialises attributes if sensor already exists."""
|
||||||
hass.states.async_set(MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY})
|
hass.states.async_set(
|
||||||
|
MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: UnitOfConductivity.MICROSIEMENS}
|
||||||
|
)
|
||||||
plant_name = "some_plant"
|
plant_name = "some_plant"
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
||||||
|
@ -98,7 +100,9 @@ async def test_update_states(hass: HomeAssistant) -> None:
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
||||||
)
|
)
|
||||||
hass.states.async_set(MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY})
|
hass.states.async_set(
|
||||||
|
MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: UnitOfConductivity.MICROSIEMENS}
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get(f"plant.{plant_name}")
|
state = hass.states.get(f"plant.{plant_name}")
|
||||||
assert state.state == STATE_PROBLEM
|
assert state.state == STATE_PROBLEM
|
||||||
|
@ -115,7 +119,9 @@ async def test_unavailable_state(hass: HomeAssistant) -> None:
|
||||||
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
||||||
)
|
)
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
MOISTURE_ENTITY, STATE_UNAVAILABLE, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY}
|
MOISTURE_ENTITY,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
{ATTR_UNIT_OF_MEASUREMENT: UnitOfConductivity.MICROSIEMENS},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get(f"plant.{plant_name}")
|
state = hass.states.get(f"plant.{plant_name}")
|
||||||
|
@ -132,13 +138,17 @@ async def test_state_problem_if_unavailable(hass: HomeAssistant) -> None:
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}}
|
||||||
)
|
)
|
||||||
hass.states.async_set(MOISTURE_ENTITY, 42, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY})
|
hass.states.async_set(
|
||||||
|
MOISTURE_ENTITY, 42, {ATTR_UNIT_OF_MEASUREMENT: UnitOfConductivity.MICROSIEMENS}
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get(f"plant.{plant_name}")
|
state = hass.states.get(f"plant.{plant_name}")
|
||||||
assert state.state == STATE_OK
|
assert state.state == STATE_OK
|
||||||
assert state.attributes[plant.READING_MOISTURE] == 42
|
assert state.attributes[plant.READING_MOISTURE] == 42
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
MOISTURE_ENTITY, STATE_UNAVAILABLE, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY}
|
MOISTURE_ENTITY,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
{ATTR_UNIT_OF_MEASUREMENT: UnitOfConductivity.MICROSIEMENS},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get(f"plant.{plant_name}")
|
state = hass.states.get(f"plant.{plant_name}")
|
||||||
|
|
|
@ -58,6 +58,7 @@ def test_matches_device_classes(device_class: SensorDeviceClass) -> None:
|
||||||
SensorDeviceClass.BATTERY: "CONF_IS_BATTERY_LEVEL",
|
SensorDeviceClass.BATTERY: "CONF_IS_BATTERY_LEVEL",
|
||||||
SensorDeviceClass.CO: "CONF_IS_CO",
|
SensorDeviceClass.CO: "CONF_IS_CO",
|
||||||
SensorDeviceClass.CO2: "CONF_IS_CO2",
|
SensorDeviceClass.CO2: "CONF_IS_CO2",
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: "CONF_IS_CONDUCTIVITY",
|
||||||
SensorDeviceClass.ENERGY_STORAGE: "CONF_IS_ENERGY",
|
SensorDeviceClass.ENERGY_STORAGE: "CONF_IS_ENERGY",
|
||||||
SensorDeviceClass.VOLUME_STORAGE: "CONF_IS_VOLUME",
|
SensorDeviceClass.VOLUME_STORAGE: "CONF_IS_VOLUME",
|
||||||
}.get(device_class, f"CONF_IS_{device_class.value.upper()}")
|
}.get(device_class, f"CONF_IS_{device_class.value.upper()}")
|
||||||
|
@ -66,6 +67,7 @@ def test_matches_device_classes(device_class: SensorDeviceClass) -> None:
|
||||||
# Ensure it has correct value
|
# Ensure it has correct value
|
||||||
constant_value = {
|
constant_value = {
|
||||||
SensorDeviceClass.BATTERY: "is_battery_level",
|
SensorDeviceClass.BATTERY: "is_battery_level",
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: "is_conductivity",
|
||||||
SensorDeviceClass.ENERGY_STORAGE: "is_energy",
|
SensorDeviceClass.ENERGY_STORAGE: "is_energy",
|
||||||
SensorDeviceClass.VOLUME_STORAGE: "is_volume",
|
SensorDeviceClass.VOLUME_STORAGE: "is_volume",
|
||||||
}.get(device_class, f"is_{device_class.value}")
|
}.get(device_class, f"is_{device_class.value}")
|
||||||
|
|
|
@ -62,6 +62,7 @@ def test_matches_device_classes(device_class: SensorDeviceClass) -> None:
|
||||||
SensorDeviceClass.BATTERY: "CONF_BATTERY_LEVEL",
|
SensorDeviceClass.BATTERY: "CONF_BATTERY_LEVEL",
|
||||||
SensorDeviceClass.CO: "CONF_CO",
|
SensorDeviceClass.CO: "CONF_CO",
|
||||||
SensorDeviceClass.CO2: "CONF_CO2",
|
SensorDeviceClass.CO2: "CONF_CO2",
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: "CONF_CONDUCTIVITY",
|
||||||
SensorDeviceClass.ENERGY_STORAGE: "CONF_ENERGY",
|
SensorDeviceClass.ENERGY_STORAGE: "CONF_ENERGY",
|
||||||
SensorDeviceClass.VOLUME_STORAGE: "CONF_VOLUME",
|
SensorDeviceClass.VOLUME_STORAGE: "CONF_VOLUME",
|
||||||
}.get(device_class, f"CONF_{device_class.value.upper()}")
|
}.get(device_class, f"CONF_{device_class.value.upper()}")
|
||||||
|
@ -70,6 +71,7 @@ def test_matches_device_classes(device_class: SensorDeviceClass) -> None:
|
||||||
# Ensure it has correct value
|
# Ensure it has correct value
|
||||||
constant_value = {
|
constant_value = {
|
||||||
SensorDeviceClass.BATTERY: "battery_level",
|
SensorDeviceClass.BATTERY: "battery_level",
|
||||||
|
SensorDeviceClass.CONDUCTIVITY: "conductivity",
|
||||||
SensorDeviceClass.ENERGY_STORAGE: "energy",
|
SensorDeviceClass.ENERGY_STORAGE: "energy",
|
||||||
SensorDeviceClass.VOLUME_STORAGE: "volume",
|
SensorDeviceClass.VOLUME_STORAGE: "volume",
|
||||||
}.get(device_class, device_class.value)
|
}.get(device_class, device_class.value)
|
||||||
|
|
|
@ -11,6 +11,7 @@ from homeassistant.const import (
|
||||||
CONCENTRATION_PARTS_PER_BILLION,
|
CONCENTRATION_PARTS_PER_BILLION,
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
|
UnitOfConductivity,
|
||||||
UnitOfDataRate,
|
UnitOfDataRate,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
|
@ -31,6 +32,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.util import unit_conversion
|
from homeassistant.util import unit_conversion
|
||||||
from homeassistant.util.unit_conversion import (
|
from homeassistant.util.unit_conversion import (
|
||||||
BaseUnitConverter,
|
BaseUnitConverter,
|
||||||
|
ConductivityConverter,
|
||||||
DataRateConverter,
|
DataRateConverter,
|
||||||
DistanceConverter,
|
DistanceConverter,
|
||||||
DurationConverter,
|
DurationConverter,
|
||||||
|
@ -57,6 +59,7 @@ INVALID_SYMBOL = "bob"
|
||||||
_ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = {
|
_ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = {
|
||||||
converter: sorted(converter.VALID_UNITS, key=lambda x: (x is None, x))
|
converter: sorted(converter.VALID_UNITS, key=lambda x: (x is None, x))
|
||||||
for converter in (
|
for converter in (
|
||||||
|
ConductivityConverter,
|
||||||
DataRateConverter,
|
DataRateConverter,
|
||||||
DistanceConverter,
|
DistanceConverter,
|
||||||
DurationConverter,
|
DurationConverter,
|
||||||
|
@ -77,6 +80,11 @@ _ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = {
|
||||||
|
|
||||||
# Dict containing all converters with a corresponding unit ratio.
|
# Dict containing all converters with a corresponding unit ratio.
|
||||||
_GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, float]] = {
|
_GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, float]] = {
|
||||||
|
ConductivityConverter: (
|
||||||
|
UnitOfConductivity.MICROSIEMENS,
|
||||||
|
UnitOfConductivity.MILLISIEMENS,
|
||||||
|
1000,
|
||||||
|
),
|
||||||
DataRateConverter: (
|
DataRateConverter: (
|
||||||
UnitOfDataRate.BITS_PER_SECOND,
|
UnitOfDataRate.BITS_PER_SECOND,
|
||||||
UnitOfDataRate.BYTES_PER_SECOND,
|
UnitOfDataRate.BYTES_PER_SECOND,
|
||||||
|
@ -122,6 +130,14 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo
|
||||||
_CONVERTED_VALUE: dict[
|
_CONVERTED_VALUE: dict[
|
||||||
type[BaseUnitConverter], list[tuple[float, str | None, float, str | None]]
|
type[BaseUnitConverter], list[tuple[float, str | None, float, str | None]]
|
||||||
] = {
|
] = {
|
||||||
|
ConductivityConverter: [
|
||||||
|
(5, UnitOfConductivity.SIEMENS, 5e3, UnitOfConductivity.MILLISIEMENS),
|
||||||
|
(5, UnitOfConductivity.SIEMENS, 5e6, UnitOfConductivity.MICROSIEMENS),
|
||||||
|
(5, UnitOfConductivity.MILLISIEMENS, 5e3, UnitOfConductivity.MICROSIEMENS),
|
||||||
|
(5, UnitOfConductivity.MILLISIEMENS, 5e-3, UnitOfConductivity.SIEMENS),
|
||||||
|
(5e6, UnitOfConductivity.MICROSIEMENS, 5e3, UnitOfConductivity.MILLISIEMENS),
|
||||||
|
(5e6, UnitOfConductivity.MICROSIEMENS, 5, UnitOfConductivity.SIEMENS),
|
||||||
|
],
|
||||||
DataRateConverter: [
|
DataRateConverter: [
|
||||||
(8e3, UnitOfDataRate.BITS_PER_SECOND, 8, UnitOfDataRate.KILOBITS_PER_SECOND),
|
(8e3, UnitOfDataRate.BITS_PER_SECOND, 8, UnitOfDataRate.KILOBITS_PER_SECOND),
|
||||||
(8e6, UnitOfDataRate.BITS_PER_SECOND, 8, UnitOfDataRate.MEGABITS_PER_SECOND),
|
(8e6, UnitOfDataRate.BITS_PER_SECOND, 8, UnitOfDataRate.MEGABITS_PER_SECOND),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue