Skip to content

Commit 155fc13

Browse files
Do not add derivative config entry to source device (home-assistant#148674)
Co-authored-by: Artur Pragacz <[email protected]>
1 parent 25f64a2 commit 155fc13

File tree

8 files changed

+301
-39
lines changed

8 files changed

+301
-39
lines changed

homeassistant/components/derivative/__init__.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@
1111
async_entity_id_to_device_id,
1212
async_remove_stale_devices_links_keep_entity_device,
1313
)
14-
from homeassistant.helpers.helper_integration import async_handle_source_entity_changes
14+
from homeassistant.helpers.helper_integration import (
15+
async_handle_source_entity_changes,
16+
async_remove_helper_config_entry_from_source_device,
17+
)
1518

1619
_LOGGER = logging.getLogger(__name__)
1720

1821

1922
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
2023
"""Set up Derivative from a config entry."""
2124

25+
# This can be removed in HA Core 2026.2
2226
async_remove_stale_devices_links_keep_entity_device(
2327
hass, entry.entry_id, entry.options[CONF_SOURCE]
2428
)
@@ -29,20 +33,16 @@ def set_source_entity_id_or_uuid(source_entity_id: str) -> None:
2933
options={**entry.options, CONF_SOURCE: source_entity_id},
3034
)
3135

32-
async def source_entity_removed() -> None:
33-
# The source entity has been removed, we need to clean the device links.
34-
async_remove_stale_devices_links_keep_entity_device(hass, entry.entry_id, None)
35-
3636
entry.async_on_unload(
3737
async_handle_source_entity_changes(
3838
hass,
39+
add_helper_config_entry_to_device=False,
3940
helper_config_entry_id=entry.entry_id,
4041
set_source_entity_id_or_uuid=set_source_entity_id_or_uuid,
4142
source_device_id=async_entity_id_to_device_id(
4243
hass, entry.options[CONF_SOURCE]
4344
),
4445
source_entity_id_or_uuid=entry.options[CONF_SOURCE],
45-
source_entity_removed=source_entity_removed,
4646
)
4747
)
4848
await hass.config_entries.async_forward_entry_setups(entry, (Platform.SENSOR,))
@@ -85,6 +85,20 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
8585
config_entry, options=new_options, version=1, minor_version=2
8686
)
8787

88+
if config_entry.minor_version < 3:
89+
# Remove the derivative config entry from the source device
90+
if source_device_id := async_entity_id_to_device_id(
91+
hass, config_entry.options[CONF_SOURCE]
92+
):
93+
async_remove_helper_config_entry_from_source_device(
94+
hass,
95+
helper_config_entry_id=config_entry.entry_id,
96+
source_device_id=source_device_id,
97+
)
98+
hass.config_entries.async_update_entry(
99+
config_entry, version=1, minor_version=3
100+
)
101+
88102
_LOGGER.debug(
89103
"Migration to configuration version %s.%s successful",
90104
config_entry.version,

homeassistant/components/derivative/config_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
142142
options_flow = OPTIONS_FLOW
143143

144144
VERSION = 1
145-
MINOR_VERSION = 2
145+
MINOR_VERSION = 3
146146

147147
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
148148
"""Return config entry title."""

homeassistant/components/derivative/sensor.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434
callback,
3535
)
3636
from homeassistant.helpers import config_validation as cv, entity_registry as er
37-
from homeassistant.helpers.device import async_device_info_to_link_from_entity
38-
from homeassistant.helpers.device_registry import DeviceInfo
37+
from homeassistant.helpers.device import async_entity_id_to_device
3938
from homeassistant.helpers.entity_platform import (
4039
AddConfigEntryEntitiesCallback,
4140
AddEntitiesCallback,
@@ -118,17 +117,13 @@ async def async_setup_entry(
118117
registry, config_entry.options[CONF_SOURCE]
119118
)
120119

121-
device_info = async_device_info_to_link_from_entity(
122-
hass,
123-
source_entity_id,
124-
)
125-
126120
if max_sub_interval_dict := config_entry.options.get(CONF_MAX_SUB_INTERVAL, None):
127121
max_sub_interval = cv.time_period(max_sub_interval_dict)
128122
else:
129123
max_sub_interval = None
130124

131125
derivative_sensor = DerivativeSensor(
126+
hass,
132127
name=config_entry.title,
133128
round_digits=int(config_entry.options[CONF_ROUND_DIGITS]),
134129
source_entity=source_entity_id,
@@ -137,7 +132,6 @@ async def async_setup_entry(
137132
unit_of_measurement=None,
138133
unit_prefix=config_entry.options.get(CONF_UNIT_PREFIX),
139134
unit_time=config_entry.options[CONF_UNIT_TIME],
140-
device_info=device_info,
141135
max_sub_interval=max_sub_interval,
142136
)
143137

@@ -152,6 +146,7 @@ async def async_setup_platform(
152146
) -> None:
153147
"""Set up the derivative sensor."""
154148
derivative = DerivativeSensor(
149+
hass,
155150
name=config.get(CONF_NAME),
156151
round_digits=config[CONF_ROUND_DIGITS],
157152
source_entity=config[CONF_SOURCE],
@@ -174,6 +169,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
174169

175170
def __init__(
176171
self,
172+
hass: HomeAssistant,
177173
*,
178174
name: str | None,
179175
round_digits: int,
@@ -184,11 +180,13 @@ def __init__(
184180
unit_time: UnitOfTime,
185181
max_sub_interval: timedelta | None,
186182
unique_id: str | None,
187-
device_info: DeviceInfo | None = None,
188183
) -> None:
189184
"""Initialize the derivative sensor."""
190185
self._attr_unique_id = unique_id
191-
self._attr_device_info = device_info
186+
self.device_entry = async_entity_id_to_device(
187+
hass,
188+
source_entity,
189+
)
192190
self._sensor_source_id = source_entity
193191
self._round_digits = round_digits
194192
self._attr_native_value = round(Decimal(0), round_digits)

homeassistant/helpers/device.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ def async_entity_id_to_device_id(
2121
return entity.device_id
2222

2323

24+
@callback
25+
def async_entity_id_to_device(
26+
hass: HomeAssistant,
27+
entity_id_or_uuid: str,
28+
) -> dr.DeviceEntry | None:
29+
"""Resolve the device entry for the entity id or entity uuid."""
30+
31+
if (device_id := async_entity_id_to_device_id(hass, entity_id_or_uuid)) is None:
32+
return None
33+
34+
return dr.async_get(hass).async_get(device_id)
35+
36+
2437
@callback
2538
def async_device_info_to_link_from_entity(
2639
hass: HomeAssistant,

homeassistant/helpers/helper_integration.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ def async_handle_source_entity_changes(
1919
set_source_entity_id_or_uuid: Callable[[str], None],
2020
source_device_id: str | None,
2121
source_entity_id_or_uuid: str,
22-
source_entity_removed: Callable[[], Coroutine[Any, Any, None]],
22+
source_entity_removed: Callable[[], Coroutine[Any, Any, None]] | None = None,
2323
) -> CALLBACK_TYPE:
2424
"""Handle changes to a helper entity's source entity.
2525
2626
The following changes are handled:
27-
- Entity removal: If the source entity is removed, the helper config entry
28-
is removed, and the helper entity is cleaned up.
27+
- Entity removal: If the source entity is removed:
28+
- If source_entity_removed is provided, it is called to handle the removal.
29+
- If source_entity_removed is not provided, The helper entity is updated to
30+
not link to any device.
2931
- Entity ID changed: If the source entity's entity ID changes and the source
3032
entity is identified by an entity ID, the set_source_entity_id_or_uuid is
3133
called. If the source entity is identified by a UUID, the helper config entry
@@ -52,7 +54,18 @@ async def async_registry_updated(
5254

5355
data = event.data
5456
if data["action"] == "remove":
55-
await source_entity_removed()
57+
if source_entity_removed:
58+
await source_entity_removed()
59+
else:
60+
for (
61+
helper_entity_entry
62+
) in entity_registry.entities.get_entries_for_config_entry_id(
63+
helper_config_entry_id
64+
):
65+
# Update the helper entity to link to the new device (or no device)
66+
entity_registry.async_update_entity(
67+
helper_entity_entry.entity_id, device_id=None
68+
)
5669

5770
if data["action"] != "update":
5871
return

0 commit comments

Comments
 (0)