diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index ca15a899c01..7dca3a312d6 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -41,7 +41,6 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_UNITS, STATE_CLASS_UNITS, SensorDeviceClass, - SensorStateClass, ) from homeassistant.components.switch import SwitchDeviceClass from homeassistant.config_entries import ( @@ -92,6 +91,7 @@ from homeassistant.helpers.hassio import is_hassio from homeassistant.helpers.json import json_dumps from homeassistant.helpers.selector import ( BooleanSelector, + DeviceClassSelectorConfig, FileSelector, FileSelectorConfig, NumberSelector, @@ -102,6 +102,9 @@ from homeassistant.helpers.selector import ( SelectSelector, SelectSelectorConfig, SelectSelectorMode, + SensorDeviceClassSelector, + SensorStateClassSelector, + SensorStateClassSelectorConfig, TemplateSelector, TemplateSelectorConfig, TextSelector, @@ -413,14 +416,7 @@ SUBENTRY_AVAILABILITY_SCHEMA = vol.Schema( ) # Sensor specific selectors -SENSOR_DEVICE_CLASS_SELECTOR = SelectSelector( - SelectSelectorConfig( - options=[device_class.value for device_class in SensorDeviceClass], - mode=SelectSelectorMode.DROPDOWN, - translation_key="device_class_sensor", - sort=True, - ) -) +SENSOR_DEVICE_CLASS_SELECTOR = SensorDeviceClassSelector(DeviceClassSelectorConfig()) BINARY_SENSOR_DEVICE_CLASS_SELECTOR = SelectSelector( SelectSelectorConfig( options=[device_class.value for device_class in BinarySensorDeviceClass], @@ -445,19 +441,9 @@ COVER_DEVICE_CLASS_SELECTOR = SelectSelector( sort=True, ) ) -SENSOR_STATE_CLASS_SELECTOR = SelectSelector( - SelectSelectorConfig( - options=[device_class.value for device_class in SensorStateClass], - mode=SelectSelectorMode.DROPDOWN, - translation_key=CONF_STATE_CLASS, - ) -) +SENSOR_STATE_CLASS_SELECTOR = SensorStateClassSelector(SensorStateClassSelectorConfig()) OPTIONS_SELECTOR = SelectSelector( - SelectSelectorConfig( - options=[], - custom_value=True, - multiple=True, - ) + SelectSelectorConfig(options=[], custom_value=True, multiple=True) ) SUGGESTED_DISPLAY_PRECISION_SELECTOR = NumberSelector( NumberSelectorConfig(mode=NumberSelectorMode.BOX, min=0, max=9) diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index 16652c498f3..59c30d195ec 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -817,66 +817,6 @@ "window": "[%key:component::cover::entity_component::window::name%]" } }, - "device_class_sensor": { - "options": { - "apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]", - "area": "[%key:component::sensor::entity_component::area::name%]", - "aqi": "[%key:component::sensor::entity_component::aqi::name%]", - "atmospheric_pressure": "[%key:component::sensor::entity_component::atmospheric_pressure::name%]", - "battery": "[%key:component::sensor::entity_component::battery::name%]", - "blood_glucose_concentration": "[%key:component::sensor::entity_component::blood_glucose_concentration::name%]", - "carbon_dioxide": "[%key:component::sensor::entity_component::carbon_dioxide::name%]", - "carbon_monoxide": "[%key:component::sensor::entity_component::carbon_monoxide::name%]", - "conductivity": "[%key:component::sensor::entity_component::conductivity::name%]", - "current": "[%key:component::sensor::entity_component::current::name%]", - "data_rate": "[%key:component::sensor::entity_component::data_rate::name%]", - "data_size": "[%key:component::sensor::entity_component::data_size::name%]", - "date": "[%key:component::sensor::entity_component::date::name%]", - "distance": "[%key:component::sensor::entity_component::distance::name%]", - "duration": "[%key:component::sensor::entity_component::duration::name%]", - "energy": "[%key:component::sensor::entity_component::energy::name%]", - "energy_distance": "[%key:component::sensor::entity_component::energy_distance::name%]", - "energy_storage": "[%key:component::sensor::entity_component::energy_storage::name%]", - "enum": "Enumeration", - "frequency": "[%key:component::sensor::entity_component::frequency::name%]", - "gas": "[%key:component::sensor::entity_component::gas::name%]", - "humidity": "[%key:component::sensor::entity_component::humidity::name%]", - "illuminance": "[%key:component::sensor::entity_component::illuminance::name%]", - "irradiance": "[%key:component::sensor::entity_component::irradiance::name%]", - "moisture": "[%key:component::sensor::entity_component::moisture::name%]", - "monetary": "[%key:component::sensor::entity_component::monetary::name%]", - "nitrogen_dioxide": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]", - "nitrogen_monoxide": "[%key:component::sensor::entity_component::nitrogen_monoxide::name%]", - "nitrous_oxide": "[%key:component::sensor::entity_component::nitrous_oxide::name%]", - "ozone": "[%key:component::sensor::entity_component::ozone::name%]", - "ph": "[%key:component::sensor::entity_component::ph::name%]", - "pm1": "[%key:component::sensor::entity_component::pm1::name%]", - "pm10": "[%key:component::sensor::entity_component::pm10::name%]", - "pm25": "[%key:component::sensor::entity_component::pm25::name%]", - "power": "[%key:component::sensor::entity_component::power::name%]", - "power_factor": "[%key:component::sensor::entity_component::power_factor::name%]", - "precipitation": "[%key:component::sensor::entity_component::precipitation::name%]", - "precipitation_intensity": "[%key:component::sensor::entity_component::precipitation_intensity::name%]", - "pressure": "[%key:component::sensor::entity_component::pressure::name%]", - "reactive_power": "[%key:component::sensor::entity_component::reactive_power::name%]", - "signal_strength": "[%key:component::sensor::entity_component::signal_strength::name%]", - "sound_pressure": "[%key:component::sensor::entity_component::sound_pressure::name%]", - "speed": "[%key:component::sensor::entity_component::speed::name%]", - "sulphur_dioxide": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]", - "temperature": "[%key:component::sensor::entity_component::temperature::name%]", - "timestamp": "[%key:component::sensor::entity_component::timestamp::name%]", - "volatile_organic_compounds": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]", - "volatile_organic_compounds_parts": "[%key:component::sensor::entity_component::volatile_organic_compounds_parts::name%]", - "voltage": "[%key:component::sensor::entity_component::voltage::name%]", - "volume": "[%key:component::sensor::entity_component::volume::name%]", - "volume_flow_rate": "[%key:component::sensor::entity_component::volume_flow_rate::name%]", - "volume_storage": "[%key:component::sensor::entity_component::volume_storage::name%]", - "water": "[%key:component::sensor::entity_component::water::name%]", - "weight": "[%key:component::sensor::entity_component::weight::name%]", - "wind_direction": "[%key:component::sensor::entity_component::wind_direction::name%]", - "wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]" - } - }, "device_class_switch": { "options": { "outlet": "[%key:component::switch::entity_component::outlet::name%]", @@ -916,14 +856,6 @@ "custom": "Custom" } }, - "state_class": { - "options": { - "measurement": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::measurement%]", - "measurement_angle": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::measurement_angle%]", - "total": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total%]", - "total_increasing": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total_increasing%]" - } - }, "supported_color_modes": { "options": { "onoff": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::onoff%]", diff --git a/homeassistant/components/sensor/strings.json b/homeassistant/components/sensor/strings.json index ecaeb2504d9..8e56da1cca1 100644 --- a/homeassistant/components/sensor/strings.json +++ b/homeassistant/components/sensor/strings.json @@ -327,5 +327,74 @@ "title": "The unit of {statistic_id} has changed", "description": "" } + }, + "selector": { + "device_class": { + "options": { + "apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]", + "area": "[%key:component::sensor::entity_component::area::name%]", + "aqi": "[%key:component::sensor::entity_component::aqi::name%]", + "atmospheric_pressure": "[%key:component::sensor::entity_component::atmospheric_pressure::name%]", + "battery": "[%key:component::sensor::entity_component::battery::name%]", + "blood_glucose_concentration": "[%key:component::sensor::entity_component::blood_glucose_concentration::name%]", + "carbon_dioxide": "[%key:component::sensor::entity_component::carbon_dioxide::name%]", + "carbon_monoxide": "[%key:component::sensor::entity_component::carbon_monoxide::name%]", + "conductivity": "[%key:component::sensor::entity_component::conductivity::name%]", + "current": "[%key:component::sensor::entity_component::current::name%]", + "data_rate": "[%key:component::sensor::entity_component::data_rate::name%]", + "data_size": "[%key:component::sensor::entity_component::data_size::name%]", + "date": "[%key:component::sensor::entity_component::date::name%]", + "distance": "[%key:component::sensor::entity_component::distance::name%]", + "duration": "[%key:component::sensor::entity_component::duration::name%]", + "energy": "[%key:component::sensor::entity_component::energy::name%]", + "energy_distance": "[%key:component::sensor::entity_component::energy_distance::name%]", + "energy_storage": "[%key:component::sensor::entity_component::energy_storage::name%]", + "enum": "Enumeration", + "frequency": "[%key:component::sensor::entity_component::frequency::name%]", + "gas": "[%key:component::sensor::entity_component::gas::name%]", + "humidity": "[%key:component::sensor::entity_component::humidity::name%]", + "illuminance": "[%key:component::sensor::entity_component::illuminance::name%]", + "irradiance": "[%key:component::sensor::entity_component::irradiance::name%]", + "moisture": "[%key:component::sensor::entity_component::moisture::name%]", + "monetary": "[%key:component::sensor::entity_component::monetary::name%]", + "nitrogen_dioxide": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]", + "nitrogen_monoxide": "[%key:component::sensor::entity_component::nitrogen_monoxide::name%]", + "nitrous_oxide": "[%key:component::sensor::entity_component::nitrous_oxide::name%]", + "ozone": "[%key:component::sensor::entity_component::ozone::name%]", + "ph": "[%key:component::sensor::entity_component::ph::name%]", + "pm1": "[%key:component::sensor::entity_component::pm1::name%]", + "pm10": "[%key:component::sensor::entity_component::pm10::name%]", + "pm25": "[%key:component::sensor::entity_component::pm25::name%]", + "power": "[%key:component::sensor::entity_component::power::name%]", + "power_factor": "[%key:component::sensor::entity_component::power_factor::name%]", + "precipitation": "[%key:component::sensor::entity_component::precipitation::name%]", + "precipitation_intensity": "[%key:component::sensor::entity_component::precipitation_intensity::name%]", + "pressure": "[%key:component::sensor::entity_component::pressure::name%]", + "reactive_power": "[%key:component::sensor::entity_component::reactive_power::name%]", + "signal_strength": "[%key:component::sensor::entity_component::signal_strength::name%]", + "sound_pressure": "[%key:component::sensor::entity_component::sound_pressure::name%]", + "speed": "[%key:component::sensor::entity_component::speed::name%]", + "sulphur_dioxide": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]", + "temperature": "[%key:component::sensor::entity_component::temperature::name%]", + "timestamp": "[%key:component::sensor::entity_component::timestamp::name%]", + "volatile_organic_compounds": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]", + "volatile_organic_compounds_parts": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]", + "voltage": "[%key:component::sensor::entity_component::voltage::name%]", + "volume": "[%key:component::sensor::entity_component::volume::name%]", + "volume_flow_rate": "[%key:component::sensor::entity_component::volume_flow_rate::name%]", + "volume_storage": "[%key:component::sensor::entity_component::volume_storage::name%]", + "water": "[%key:component::sensor::entity_component::water::name%]", + "weight": "[%key:component::sensor::entity_component::weight::name%]", + "wind_direction": "[%key:component::sensor::entity_component::wind_direction::name%]", + "wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]" + } + }, + "state_class": { + "options": { + "measurement": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::measurement%]", + "total": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total%]", + "total_increasing": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total_increasing%]" + } + } } } diff --git a/homeassistant/components/switch/strings.json b/homeassistant/components/switch/strings.json index b73cf8f849d..e2eac8fd89a 100644 --- a/homeassistant/components/switch/strings.json +++ b/homeassistant/components/switch/strings.json @@ -52,5 +52,13 @@ "name": "[%key:common::action::toggle%]", "description": "Toggles a switch on/off." } + }, + "selector": { + "device_class": { + "options": { + "outlet": "[%key:component::switch::entity_component::outlet::name%]", + "switch": "[%key:component::switch::title%]" + } + } } } diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 6f8df828c37..06c05f206b5 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -137,6 +137,13 @@ BASE_SELECTOR_CONFIG_SCHEMA = vol.Schema( } ) +CLASS_CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( + { + vol.Optional("multiple", default=False): cv.boolean, + vol.Optional("no_sort", default=False): cv.boolean, + } +) + class BaseSelectorConfig(TypedDict, total=False): """Class to common options of all selectors.""" @@ -1281,6 +1288,115 @@ class StatisticSelector(Selector[StatisticSelectorConfig]): return [vol.Schema(str)(val) for val in data] +class DeviceClassSelectorConfig(BaseSelectorConfig, total=False): + """Class to represent a device class selector config.""" + + device_classes: Sequence[StrEnum] | Sequence[str] + multiple: bool + no_sort: bool + + +class DeviceClassSelector(Selector[DeviceClassSelectorConfig]): + """Selector for an entity platform device class.""" + + selector_type: str + _default_device_classes: Callable[..., list[str]] + + CONFIG_SCHEMA: vol.Schema + + def __call__(self, data: Any) -> Any: + """Return valid device classes.""" + device_classes: Sequence[str] = [] + if config_device_classes := self.config.get( + "device_classes", self._default_device_classes() + ): + if isinstance(config_device_classes[0], StrEnum): + device_classes = [ + device_class.value + for device_class in cast(Sequence[StrEnum], config_device_classes) + ] + else: + device_classes = config_device_classes + + parent_schema: vol.In | vol.Any = vol.In(device_classes) + + if not self.config["multiple"]: + return parent_schema(vol.Schema(str)(data)) + if not isinstance(data, list): + raise vol.Invalid("Value should be a list") + return [parent_schema(vol.Schema(str)(val)) for val in data] + + +@SELECTORS.register("sensor_device_class") +class SensorDeviceClassSelector(DeviceClassSelector): + """Selector for a sensor device class.""" + + def _default_device_classes(self) -> list[str]: + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.sensor import SensorDeviceClass + + return [device_class.value for device_class in SensorDeviceClass] + + selector_type = "sensor_device_class" + + CONFIG_SCHEMA = CLASS_CONFIG_SCHEMA.extend( + { + vol.Optional( + "device_classes", + ): [vol.All(vol.Any([str], [StrEnum]))] + } + ) + + +class SensorStateClassSelectorConfig(BaseSelectorConfig, total=False): + """Class to represent a sensor state class selector config.""" + + state_classes: Sequence[StrEnum] | Sequence[str] + multiple: bool + no_sort: bool + + +@SELECTORS.register("sensor_state_class") +class SensorStateClassSelector(Selector[SensorStateClassSelectorConfig]): + """Selector for a sensor state class.""" + + selector_type = "sensor_state_class" + + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( + { + vol.Optional("state_classes"): [str], + vol.Optional("multiple", default=False): cv.boolean, + vol.Optional("no_sort", default=False): cv.boolean, + } + ) + + def __call__(self, data: Any) -> Any: + """Return valid device classes.""" + + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.sensor import SensorStateClass + + state_classes: Sequence[str] = [] + if config_state_classes := self.config.get( + "state_classes", [state_class.value for state_class in SensorStateClass] + ): + if isinstance(config_state_classes[0], StrEnum): + state_classes = [ + state_class.value + for state_class in cast(Sequence[StrEnum], config_state_classes) + ] + else: + state_classes = config_state_classes + + parent_schema: vol.In | vol.Any = vol.In(state_classes) + + if not self.config["multiple"]: + return parent_schema(vol.Schema(str)(data)) + if not isinstance(data, list): + raise vol.Invalid("Value should be a list") + return [parent_schema(vol.Schema(str)(val)) for val in data] + + class TargetSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a target selector config."""