diff --git a/homeassistant/components/min_max/__init__.py b/homeassistant/components/min_max/__init__.py index 9090de908fb5..532d5f50f4c2 100644 --- a/homeassistant/components/min_max/__init__.py +++ b/homeassistant/components/min_max/__init__.py @@ -1,10 +1,15 @@ """The min_max component.""" -from homeassistant.config_entries import ConfigEntry +import logging + +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from .const import DOMAIN + PLATFORMS = [Platform.SENSOR] +_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -17,3 +22,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + + +async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Migrate old entry.""" + _LOGGER.debug("Migrating from version %s.%s", entry.version, entry.minor_version) + + if entry.version > 2: + # This means the user has downgraded from a future version + return False + + if entry.version == 1: + # Migration to group sensor and remove config entry + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=entry.options + ) + ) + hass.config_entries.async_update_entry(entry, version=2) + + _LOGGER.debug( + "Migration to version %s.%s successful", + entry.version, + entry.minor_version, + ) + + return True diff --git a/homeassistant/components/min_max/config_flow.py b/homeassistant/components/min_max/config_flow.py index 98128b773bf9..f8922d0e2208 100644 --- a/homeassistant/components/min_max/config_flow.py +++ b/homeassistant/components/min_max/config_flow.py @@ -1,77 +1,19 @@ """Config flow for Min/Max integration.""" -from collections.abc import Mapping -from typing import Any, cast, override +from typing import Any -import voluptuous as vol +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN -from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.const import CONF_TYPE -from homeassistant.helpers import selector -from homeassistant.helpers.schema_config_entry_flow import ( - SchemaConfigFlowHandler, - SchemaFlowFormStep, -) - -from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN - -_STATISTIC_MEASURES = [ - "min", - "max", - "mean", - "median", - "last", - "range", - "sum", -] +from .const import DOMAIN -OPTIONS_SCHEMA = vol.Schema( - { - vol.Required(CONF_ENTITY_IDS): selector.EntitySelector( - selector.EntitySelectorConfig( - domain=[SENSOR_DOMAIN, NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], - multiple=True, - ), - ), - vol.Required(CONF_TYPE): selector.SelectSelector( - selector.SelectSelectorConfig( - options=_STATISTIC_MEASURES, translation_key=CONF_TYPE - ), - ), - vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector( - selector.NumberSelectorConfig( - min=0, max=6, mode=selector.NumberSelectorMode.BOX - ), - ), - } -) +class MinMaxConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for min_max integration.""" -CONFIG_SCHEMA = vol.Schema( - { - vol.Required("name"): selector.TextSelector(), - } -).extend(OPTIONS_SCHEMA.schema) + VERSION = 2 -CONFIG_FLOW = { - "user": SchemaFlowFormStep(CONFIG_SCHEMA), -} - -OPTIONS_FLOW = { - "init": SchemaFlowFormStep(OPTIONS_SCHEMA), -} - - -class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN): - """Handle a config or options flow for Min/Max.""" - - config_flow = CONFIG_FLOW - options_flow = OPTIONS_FLOW - options_flow_reloads = True - - @override - def async_config_entry_title(self, options: Mapping[str, Any]) -> str: - """Return config entry title.""" - return cast(str, options["name"]) if "name" in options else "" + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the user step.""" + return self.async_abort(reason="migrated_to_groups") diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index cc1f4b437012..ab996c6a252d 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -6,6 +6,7 @@ import statistics from typing import Any, override import voluptuous as vol +import yaml from homeassistant.components.sensor import ( PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA, @@ -13,11 +14,11 @@ from homeassistant.components.sensor import ( SensorEntity, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, + CONF_PLATFORM, CONF_TYPE, CONF_UNIQUE_ID, STATE_UNAVAILABLE, @@ -25,13 +26,11 @@ from homeassistant.const import ( ) from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, entity_registry as er +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import get_device_class -from homeassistant.helpers.entity_platform import ( - AddConfigEntryEntitiesCallback, - AddEntitiesCallback, -) +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType @@ -51,6 +50,8 @@ ATTR_LAST_ENTITY_ID = "last_entity_id" ATTR_RANGE = "range" ATTR_SUM = "sum" +CONF_ENTITIES = "entities" + ICON = "mdi:calculator" SENSOR_TYPES = { @@ -77,29 +78,27 @@ PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend( ) -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddConfigEntryEntitiesCallback, -) -> None: - """Initialize min/max/mean config entry.""" - registry = er.async_get(hass) - entity_ids = er.async_validate_entity_ids( - registry, config_entry.options[CONF_ENTITY_IDS] - ) - sensor_type = config_entry.options[CONF_TYPE] - round_digits = int(config_entry.options[CONF_ROUND_DIGITS]) - - async_add_entities( - [ - MinMaxSensor( - entity_ids, - config_entry.title, - sensor_type, - round_digits, - config_entry.entry_id, - ) - ] +async def yaml_deprecation_notice(hass: HomeAssistant, config: ConfigType) -> None: + """Raise repair issue for YAML configuration deprecation.""" + platform_config = config.copy() + platform_config[CONF_ENTITIES] = platform_config.pop(CONF_ENTITY_IDS) + platform_config.pop(CONF_ROUND_DIGITS) + platform_config.pop(CONF_PLATFORM) + if CONF_NAME not in platform_config: + platform_config[CONF_NAME] = f"{platform_config[CONF_TYPE]} sensor".capitalize() + yaml_config = yaml.dump(platform_config) + yaml_config = yaml_config.replace("\n", "\n ") + yaml_config = "```yaml\nsensor:\n - platform: group\n " + yaml_config + "\n```" + async_create_issue( + hass, + DOMAIN, + "yaml_deprecated", + breaks_in_ha_version="2026.12.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + learn_more_url="https://www.home-assistant.io/integrations/group/", + translation_key="yaml_deprecated", + translation_placeholders={"yaml_config": yaml_config}, ) @@ -117,6 +116,7 @@ async def async_setup_platform( unique_id = config.get(CONF_UNIQUE_ID) await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + await yaml_deprecation_notice(hass, config) async_add_entities( [MinMaxSensor(entity_ids, name, sensor_type, round_digits, unique_id)] diff --git a/homeassistant/components/min_max/strings.json b/homeassistant/components/min_max/strings.json index 760dace6b238..4d5be4827cf9 100644 --- a/homeassistant/components/min_max/strings.json +++ b/homeassistant/components/min_max/strings.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "migrated_to_groups": "The min/max integration has been migrated to use groups. Please use the group integration instead." + }, "step": { "user": { "data": { @@ -16,6 +19,12 @@ } } }, + "issues": { + "yaml_deprecated": { + "description": "The min/max integration has been migrated to use group sensor.\n\nReplace your Min/MaxYAML configuration with this converted configuration:\n{yaml_config}\n\nTo use the new configuration and replace your sensor with a group sensor, restart your Home Assistant.\n\nGroup sensors have more configuration possibilities, refer to the documentation by clicking on learn more.", + "title": "Min/Max YAML configuration is deprecated" + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/min_max/yaml.yaml b/homeassistant/components/min_max/yaml.yaml new file mode 100644 index 000000000000..4fe696d9ce3b --- /dev/null +++ b/homeassistant/components/min_max/yaml.yaml @@ -0,0 +1,26 @@ +# To be removed + +# min_max +sensor: + - platform: min_max + entity_ids: + - sensor.kitchen_temperature + - sensor.living_room_temperature + - sensor.office_temperature + type: max + name: Test + round_digits: 2 # Not exist in groups + unique_id: something_unique + # group + - platform: group + entities: # from entity_ids + - sensor.kitchen_temperature + - sensor.living_room_temperature + - sensor.office_temperature + type: max + name: Test + unique_id: something_unique + ignore_non_numeric: false # Not exist in min_max + unit_of_measurement: °C # Not exist in min_max + device_class: temperature # Not exist in min_max + state_class: measurement # Not exist in min_max