Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
564280c
Deprecate min_max and migrate to group sensor
gjohansson-ST Apr 8, 2026
5bbfe69
import
gjohansson-ST Apr 10, 2026
7288d19
Mods
gjohansson-ST Apr 10, 2026
6e92ba2
Mods
gjohansson-ST Apr 14, 2026
26d8dfb
Mods
gjohansson-ST Apr 14, 2026
d4fca37
Mods
gjohansson-ST Apr 14, 2026
49ce8ed
Mods
gjohansson-ST Apr 14, 2026
f821952
Test
gjohansson-ST Apr 14, 2026
d25f3c6
Delete
gjohansson-ST Apr 14, 2026
f006283
Mods
gjohansson-ST Apr 14, 2026
44b0717
Mods
gjohansson-ST Apr 14, 2026
7f1846b
comments
gjohansson-ST Apr 14, 2026
6d4f5a4
Mods to repair flow
gjohansson-ST May 4, 2026
d037f33
Fix config flow
gjohansson-ST May 4, 2026
983df39
Fixes
gjohansson-ST May 4, 2026
787929f
Mods
gjohansson-ST May 4, 2026
46a05e4
mod
gjohansson-ST May 4, 2026
379c0b1
Fixes
gjohansson-ST May 13, 2026
3f69014
strings
gjohansson-ST May 13, 2026
00e8a6c
Fix string
gjohansson-ST May 13, 2026
9fd0f99
Fix flow
gjohansson-ST May 13, 2026
e131b21
async_config_entry_title
gjohansson-ST May 13, 2026
5079709
Mods
gjohansson-ST May 13, 2026
80733a2
Handle missing entity
gjohansson-ST May 13, 2026
f4bccbb
fix yaml description
gjohansson-ST May 13, 2026
d8b23df
Mods
gjohansson-ST May 13, 2026
ee9326d
Somewhat stable id
gjohansson-ST May 13, 2026
2e1c4e0
Remove from future
gjohansson-ST May 13, 2026
fbda661
Restore options flow test
gjohansson-ST May 13, 2026
7c6ff3f
Fix review comments
gjohansson-ST May 14, 2026
30176c6
Remove issue on config entry removal
gjohansson-ST May 21, 2026
9634953
Fixes
gjohansson-ST May 21, 2026
aad7b6c
docstring
gjohansson-ST May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions homeassistant/components/group/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
ATTR_OBJECT_ID,
ATTR_ORDER,
ATTR_REMOVE_ENTITIES,
CONF_GROUP_TYPE,
CONF_HIDE_MEMBERS,
CONF_IGNORE_NON_NUMERIC,
DATA_COMPONENT,
DOMAIN,
GROUP_ORDER,
Expand Down
8 changes: 4 additions & 4 deletions homeassistant/components/group/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from .binary_sensor import CONF_ALL, async_create_preview_binary_sensor
from .button import async_create_preview_button
from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC, DOMAIN
from .const import CONF_GROUP_TYPE, CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC, DOMAIN
from .cover import async_create_preview_cover
from .entity import GroupEntity
from .event import async_create_preview_event
Expand Down Expand Up @@ -180,7 +180,7 @@ async def light_switch_options_schema(

async def choose_options_step(options: dict[str, Any]) -> str:
"""Return next step_id for options flow according to group_type."""
return cast(str, options["group_type"])
return cast(str, options[CONF_GROUP_TYPE])


def set_group_type(
Expand All @@ -194,7 +194,7 @@ async def _set_group_type(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Add group type to user input."""
return {"group_type": group_type, **user_input}
return {CONF_GROUP_TYPE: group_type, **user_input}

return _set_group_type

Expand Down Expand Up @@ -430,7 +430,7 @@ def ws_start_preview(
config_entry = hass.config_entries.async_get_entry(config_entry_id)
if not config_entry:
raise HomeAssistantError
group_type = config_entry.options["group_type"]
group_type = config_entry.options[CONF_GROUP_TYPE]
name = config_entry.options["name"]
validated = PREVIEW_OPTIONS_SCHEMA[group_type](msg["user_input"])
entity_registry = er.async_get(hass)
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/group/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

CONF_HIDE_MEMBERS = "hide_members"
CONF_IGNORE_NON_NUMERIC = "ignore_non_numeric"
CONF_GROUP_TYPE = "group_type"

DOMAIN = "group"
DATA_COMPONENT: HassKey[EntityComponent[Group]] = HassKey(DOMAIN)
Expand Down
23 changes: 23 additions & 0 deletions homeassistant/components/min_max/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,30 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)

from .const import DOMAIN

PLATFORMS = [Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Min/Max from a config entry."""
async_create_issue(
hass,
DOMAIN,
f"migrate_to_group_sensor-{entry.entry_id}",
breaks_in_ha_version="2026.12.0",
is_fixable=True,
Comment thread
gjohansson-ST marked this conversation as resolved.
is_persistent=False,
severity=IssueSeverity.WARNING,
translation_key="migrate_to_group_sensor",
data={"entry_id": entry.entry_id},
)
Comment thread
gjohansson-ST marked this conversation as resolved.
Comment thread
gjohansson-ST marked this conversation as resolved.
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True
Expand All @@ -17,3 +35,8 @@ 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_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Remove a config entry."""
async_delete_issue(hass, DOMAIN, f"migrate_to_group_sensor-{entry.entry_id}")
16 changes: 10 additions & 6 deletions homeassistant/components/min_max/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
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.data_entry_flow import AbortFlow
from homeassistant.helpers import selector
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
SchemaFlowFormStep,
)
Expand Down Expand Up @@ -51,14 +53,16 @@
}
)

CONFIG_SCHEMA = vol.Schema(
{
vol.Required("name"): selector.TextSelector(),
}
).extend(OPTIONS_SCHEMA.schema)
CONFIG_SCHEMA = vol.Schema({})


async def migrate_to_groups(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Abort flow as migrate to groups."""
raise AbortFlow("migrated_to_groups")


CONFIG_FLOW = {
"user": SchemaFlowFormStep(CONFIG_SCHEMA),
"user": SchemaFlowFormStep(migrate_to_groups),
}
Comment thread
gjohansson-ST marked this conversation as resolved.

OPTIONS_FLOW = {
Expand Down
117 changes: 117 additions & 0 deletions homeassistant/components/min_max/repairs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Repairs platform for the Min/Max integration."""

from types import MappingProxyType
from typing import TYPE_CHECKING, Any, cast

import voluptuous as vol

from homeassistant import data_entry_flow
from homeassistant.components.group import (
CONF_ENTITIES,
CONF_GROUP_TYPE,
CONF_HIDE_MEMBERS,
CONF_IGNORE_NON_NUMERIC,
DOMAIN as GROUP_DOMAIN,
)
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import SOURCE_USER, ConfigEntry, ConfigEntryDisabler
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er

from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN


class MigrateToGroupSensorFlow(RepairsFlow):
"""Repair flow to migrate Min/Max helper to Group sensor."""

def __init__(self, entry: ConfigEntry) -> None:
"""Create flow."""
self.entry = entry
super().__init__()

async def async_step_init(

Check failure on line 33 in homeassistant/components/min_max/repairs.py

View workflow job for this annotation

GitHub Actions / Check pylint

E7403: Return type should be RepairsFlowResult in async_step_init (home-assistant-return-type)

Check failure on line 33 in homeassistant/components/min_max/repairs.py

View workflow job for this annotation

GitHub Actions / Check pylint

E7403: Return type should be RepairsFlowResult in async_step_init (home-assistant-return-type)
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
return await self.async_step_migrate()

async def async_step_migrate(

Check failure on line 39 in homeassistant/components/min_max/repairs.py

View workflow job for this annotation

GitHub Actions / Check pylint

E7403: Return type should be RepairsFlowResult in async_step_migrate (home-assistant-return-type)

Check failure on line 39 in homeassistant/components/min_max/repairs.py

View workflow job for this annotation

GitHub Actions / Check pylint

E7403: Return type should be RepairsFlowResult in async_step_migrate (home-assistant-return-type)
self, user_input: dict[str, Any] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the migration step of a fix flow."""
entity_reg = er.async_get(self.hass)
old_entity = entity_reg.async_get_entity_id(
SENSOR_DOMAIN, DOMAIN, self.entry.entry_id
)
if not old_entity:
return self.async_abort(reason="entity_not_found")

Comment thread
emontnemery marked this conversation as resolved.
if user_input is not None:
config = dict(self.entry.options)
config[CONF_ENTITIES] = config.pop(CONF_ENTITY_IDS)
config.pop(CONF_ROUND_DIGITS)
# Set group sensor defaults
config[CONF_HIDE_MEMBERS] = False
config[CONF_IGNORE_NON_NUMERIC] = False
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd think we should migrate to group default (False), the user can always change that if they want.

config[CONF_GROUP_TYPE] = SENSOR_DOMAIN

new_config_entry = ConfigEntry(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we use a normal config flow on the group domain to create the new entry, instead of adding it directly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a bit difficult as we need to unload min_max and change the entity before it starts to ensure we keep history etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sort of thing would normally be in the group integration's config flow with a custom initial step for the min_max adoption, but considering we'll be removing the code after 6 months, this is maybe OK.

data={},
discovery_keys=MappingProxyType({}),
domain=GROUP_DOMAIN,
minor_version=1,
options=config,
source=SOURCE_USER,
subentries_data=[],
title=self.entry.title,
unique_id=None,
version=1,
disabled_by=ConfigEntryDisabler.USER,
)

if not await self.hass.config_entries.async_unload(self.entry.entry_id):
return self.async_abort(reason="unload_failed")
await self.hass.config_entries.async_add(new_config_entry)
try:
entity_reg.async_update_entity_platform(
old_entity,
GROUP_DOMAIN,
new_config_entry_id=new_config_entry.entry_id,
new_unique_id=new_config_entry.entry_id,
)
except ValueError:
return self.async_abort(reason="entity_update_failed")
await self.hass.config_entries.async_set_disabled_by(
entry_id=new_config_entry.entry_id, disabled_by=None
)
await self.hass.config_entries.async_remove(self.entry.entry_id)

return self.async_create_entry(data={})
Comment on lines +85 to +90
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repairing the issue does that?!


entity_info = entity_reg.async_get(old_entity)
if TYPE_CHECKING:
assert entity_info
title = er.async_get_full_entity_name(self.hass, entity_info)

return self.async_show_form(
step_id="migrate",
data_schema=vol.Schema({}),
description_placeholders={"title": title},
)


async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, Any] | None,
) -> RepairsFlow:
"""Create flow."""
if data and (entry_id := data.get("entry_id")):
entry_id = cast(str, entry_id)
entry = hass.config_entries.async_get_entry(entry_id)
if TYPE_CHECKING:
assert entry
return MigrateToGroupSensorFlow(entry)

return ConfirmRepairFlow()
Comment on lines +108 to +117
38 changes: 38 additions & 0 deletions homeassistant/components/min_max/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
from __future__ import annotations

from datetime import datetime
import hashlib
import json
import logging
import statistics
from typing import Any

import voluptuous as vol

from homeassistant.components.group import CONF_ENTITIES
from homeassistant.components.sensor import (
Comment thread
gjohansson-ST marked this conversation as resolved.
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorDeviceClass,
Expand All @@ -20,6 +23,7 @@
ATTR_ENTITY_ID,
ATTR_UNIT_OF_MEASUREMENT,
CONF_NAME,
CONF_PLATFORM,
CONF_TYPE,
CONF_UNIQUE_ID,
STATE_UNAVAILABLE,
Expand All @@ -34,8 +38,10 @@
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
from homeassistant.util import yaml as yaml_util

from . import PLATFORMS
from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
Expand All @@ -53,6 +59,7 @@
ATTR_RANGE = "range"
ATTR_SUM = "sum"


ICON = "mdi:calculator"

SENSOR_TYPES = {
Expand Down Expand Up @@ -105,6 +112,36 @@ async def async_setup_entry(
)


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)
Comment thread
gjohansson-ST marked this conversation as resolved.
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_util.dump(platform_config)
yaml_config = yaml_config.replace("\n", "\n ")
yaml_config = "```yaml\nsensor:\n - platform: group\n " + yaml_config + "\n```"
Comment on lines +117 to +125
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above


def make_hash(config: dict[str, Any]) -> str:
d = hashlib.sha1(json.dumps(config, sort_keys=True).encode())
return d.hexdigest()

issue_id = f"yaml_deprecated-{make_hash(platform_config)}"
async_create_issue(
hass,
DOMAIN,
issue_id,
breaks_in_ha_version="2026.12.0",
Comment thread
gjohansson-ST marked this conversation as resolved.
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},
)
Comment thread
gjohansson-ST marked this conversation as resolved.
Comment thread
gjohansson-ST marked this conversation as resolved.
Comment thread
gjohansson-ST marked this conversation as resolved.
Comment thread
gjohansson-ST marked this conversation as resolved.


async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
Expand All @@ -119,6 +156,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)]
Expand Down
44 changes: 30 additions & 14 deletions homeassistant/components/min_max/strings.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
{
"config": {
"abort": {
"migrated_to_groups": "The Min/Max helper has been migrated to use Group sensors. Please use the Group helper instead."
},
"step": {
"user": {
"data": {
"entity_ids": "Input entities",
"name": "[%key:common::config_flow::data::name%]",
"round_digits": "Precision",
"type": "Statistic characteristic"
},
"data_description": {
"round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum."
},
"description": "Create a sensor that calculates a min, max, mean, median or sum from a list of input sensors.",
"description": "Min/Max helper has been deprecated, please use the Group helper instead.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this visible considering we immediately abort the flow?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I will remove it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have to keep it as the translation script requires it. It will anyhow be removed in 6 months

"title": "[%key:component::min_max::title%]"
}
}
},
"issues": {
"migrate_to_group_sensor": {
"fix_flow": {
"abort": {
"entity_not_found": "Entity could not be found as it has been removed, aborting the repair.",
"entity_update_failed": "Failed to update the entity to a Group sensor, please manually remove the obsolete entity.",
"unload_failed": "Failed to unload the Min/Max helper, please restart repairing the issue."
},
"step": {
"migrate": {
"description": "The Min/Max helper has been deprecated and {title} will be migrated to a Group sensor when you click submit to fix this repair.",
"title": "[%key:component::min_max::issues::migrate_to_group_sensor::title%]"
}
}
},
"title": "Min/Max helper has been deprecated"
},
"yaml_deprecated": {
"description": "The Min/Max helper has been deprecated and you should use Group sensors instead.\n\nReplace your Min/Max YAML configuration with this converted configuration:\n{yaml_config}\n\n**Note:**\n- `precision` is not supported in Group sensor, you need to update your entity options after migrating.\n- Group sensor supports setting `ignore_non_numeric` which defaults to `False` after migrating, change this to `True` to ignore input entities with invalid values.\n\nOnce you have replaced your YAML configuration, restart Home Assistant to use the Group helper instead.\n\nThe Group helper has more configuration possibilities. Refer to the documentation by clicking Learn More.",
"title": "[%key:component::min_max::issues::migrate_to_group_sensor::title%]"
}
},
Comment thread
gjohansson-ST marked this conversation as resolved.
"options": {
"step": {
"init": {
"data": {
"entity_ids": "[%key:component::min_max::config::step::user::data::entity_ids%]",
"round_digits": "[%key:component::min_max::config::step::user::data::round_digits%]",
"type": "[%key:component::min_max::config::step::user::data::type%]"
"entity_ids": "Input entities",
"round_digits": "Precision",
"type": "Statistic characteristic"
},
"data_description": {
"round_digits": "[%key:component::min_max::config::step::user::data_description::round_digits%]"
"round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum."
}
}
}
Expand Down
Loading
Loading