Skip to content

Commit 1f69daa

Browse files
woody-boxclaude
andauthored
DS: re-homar el dispositivo "Común" fuera de la primera persiana al actualizar (v0.98.1) (#141)
El dispositivo compartido de persianas (recuentos + sol, SHUTTERS_DEVICE_ID) existe desde la v0.90.0, cuando vivía anidado bajo la primera persiana. La v0.98.0 movió sus entidades a la entrada "Común" auto-creada, pero Home Assistant solo AÑADE entradas de configuración a un dispositivo (nunca poda la vieja), así que el dispositivo seguía mostrándose dentro de esa persiana en vez de como su propio hub. Al configurar la entrada Común se retira ahora cualquier enlace que no sea el suyo del dispositivo compartido (las entidades de la Común ya están adjuntas, así que el dispositivo nunca se colapsa y sus entity_ids no cambian). Idempotente: no hace nada en instalaciones nuevas ni una vez re-homado. Test de regresión por la vía real de recarga de la entrada, verificando que el enlace obsoleto se retira y la entidad compartida conserva su entity_id y su entrada. Claude-Session: https://claude.ai/code/session_01HjFYRJN939VaxK75qjLgoW Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8ecc8b5 commit 1f69daa

4 files changed

Lines changed: 92 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ Todas las versiones notables de la integración `custom_components/dynamic_home`
44
Formato basado en [Keep a Changelog](https://keepachangelog.com/es/1.1.0/) y
55
[SemVer](https://semver.org/lang/es/).
66

7+
## [0.98.1] — 2026-07-04
8+
9+
### Fixed
10+
- **DS · la sección "Dynamic Shutter · Común" seguía anidada dentro de la primera
11+
persiana** tras actualizar desde una versión anterior a la v0.98.0. El
12+
dispositivo compartido (recuentos + sol) existe desde la v0.90.0, cuando vivía
13+
colgado de la primera persiana; la v0.98.0 movió sus entidades a la entrada
14+
"Común" auto-creada, pero Home Assistant solo **añade** entradas de
15+
configuración a un dispositivo (nunca poda la vieja), así que el dispositivo
16+
seguía mostrándose **dentro** de esa persiana en vez de como su propio hub. Al
17+
arrancar, la entrada Común ahora **retira el enlace obsoleto** de la persiana y
18+
se queda como única dueña del dispositivo. Las entidades no se tocan: conservan
19+
su `entity_id`. Las instalaciones nuevas no se ven afectadas (el dispositivo
20+
nace ya bajo la Común).
21+
722
## [0.98.0] — 2026-07-03
823

924
### Added

custom_components/dynamic_home/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from homeassistant.config_entries import ConfigEntry
1212
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse
1313
from homeassistant.helpers import config_validation as cv
14+
from homeassistant.helpers import device_registry as dr
1415
from homeassistant.helpers import service as service_helper
1516

1617
from . import const
@@ -68,6 +69,28 @@ async def _ensure_common_entry(hass: HomeAssistant) -> None:
6869
hass.data[const.DOMAIN].pop("_common_creating", None)
6970

7071

72+
def _reassign_common_device(hass: HomeAssistant, entry: ConfigEntry) -> None:
73+
"""Re-home the shared "Dynamic Shutter · Común" device onto its own entry.
74+
75+
The shared shutter device (SHUTTERS_DEVICE_ID: counts + sun + note) predates
76+
v0.98.0, when it lived nested under the first shutter entry. v0.98.0 moved
77+
its entities to the auto-created Común singleton, but Home Assistant only ever
78+
*adds* config entries to a device — it never prunes the stale one — so the
79+
device keeps the old shutter in its ``config_entries`` and stays displayed
80+
*inside* that shutter instead of standing alone as its own hub. Drop every
81+
entry that isn't ours; the Común entities are already attached, so the device
82+
always keeps this entry and never collapses. Entity links are untouched.
83+
Idempotent: a no-op on fresh installs and once already re-homed.
84+
"""
85+
dev_reg = dr.async_get(hass)
86+
device = dev_reg.async_get_device(
87+
identifiers={(const.DOMAIN, const.SHUTTERS_DEVICE_ID)})
88+
if device is None:
89+
return
90+
for cfg_id in device.config_entries - {entry.entry_id}:
91+
dev_reg.async_update_device(device.id, remove_config_entry_id=cfg_id)
92+
93+
7194
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
7295
"""Set up Dynamic Home from a config entry."""
7396
hub: SdhbHub = hass.data.setdefault(const.DOMAIN, {}).setdefault(
@@ -115,6 +138,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
115138
hass.data[const.DOMAIN][entry.entry_id] = coordinator
116139

117140
await hass.config_entries.async_forward_entry_setups(entry, _platforms(entry))
141+
# The shared shutter device predates its own entry; on upgrade strip the stale
142+
# shutter link so it shows as its own "Común" hub, not nested (v0.98.1).
143+
if module == const.MODULE_SHUTTER_COMMON:
144+
_reassign_common_device(hass, entry)
118145
entry.async_on_unload(entry.add_update_listener(_async_options_updated))
119146
_async_register_services(hass)
120147
# First shutter -> make sure the shared "Dynamic Shutter · Común" entry exists.

custom_components/dynamic_home/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
"iot_class": "local_polling",
99
"issue_tracker": "https://github.com/woody-box/Dynamic-Home/issues",
1010
"requirements": [],
11-
"version": "0.98.0"
11+
"version": "0.98.1"
1212
}

tests/test_ds_integration.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,55 @@ async def test_common_entry_autocreated_and_removed_with_last_shutter(
13831383
if e.data.get(const.CONF_MODULE) == const.MODULE_SHUTTER_COMMON]
13841384

13851385

1386+
async def test_common_device_rehomed_off_shutter_on_upgrade(
1387+
hass: HomeAssistant) -> None:
1388+
# v0.98.1: after upgrading, the shared shutter device (counts + sun) keeps a
1389+
# stale link to the first shutter in its config_entries, so it stays nested
1390+
# under that shutter instead of standing alone. The shared entities already
1391+
# belong to the Común entry (v0.98.0); only the device link is stale. A
1392+
# reload of the Común entry must strip it without churning any entity.
1393+
from homeassistant.helpers import device_registry as dr
1394+
from homeassistant.helpers import entity_registry as er
1395+
_seed(hass)
1396+
ea = MockConfigEntry(domain=const.DOMAIN, data={**SHUTTER}, title="A")
1397+
ea.add_to_hass(hass)
1398+
# Pre-create the Común as a normal (reloadable) entry so the shutter's
1399+
# auto-create step finds it and we exercise the real setup path on reload.
1400+
common = MockConfigEntry(
1401+
domain=const.DOMAIN, title="Dynamic Shutter · Común",
1402+
unique_id="shutter_common_singleton",
1403+
data={const.CONF_NAME: "Común",
1404+
const.CONF_MODULE: const.MODULE_SHUTTER_COMMON})
1405+
common.add_to_hass(hass)
1406+
assert await hass.config_entries.async_setup(ea.entry_id)
1407+
await hass.async_block_till_done()
1408+
if common.state is not ConfigEntryState.LOADED:
1409+
assert await hass.config_entries.async_setup(common.entry_id)
1410+
await hass.async_block_till_done()
1411+
dev_reg = dr.async_get(hass)
1412+
reg = er.async_get(hass)
1413+
device = dev_reg.async_get_device(
1414+
identifiers={(const.DOMAIN, const.SHUTTERS_DEVICE_ID)})
1415+
eid = reg.async_get_entity_id(
1416+
"sensor", const.DOMAIN, f"{const.DOMAIN}_covers_open")
1417+
1418+
# Simulate the leftover pre-0.98 link: the shutter still pinned to the device.
1419+
dev_reg.async_update_device(device.id, add_config_entry_id=ea.entry_id)
1420+
assert ea.entry_id in dev_reg.async_get(device.id).config_entries
1421+
1422+
# Reload the Común entry -> its setup strips the stale shutter link.
1423+
assert await hass.config_entries.async_reload(common.entry_id)
1424+
await hass.async_block_till_done()
1425+
1426+
device = dev_reg.async_get_device(
1427+
identifiers={(const.DOMAIN, const.SHUTTERS_DEVICE_ID)})
1428+
assert device.config_entries == {common.entry_id} # only the Común owns it
1429+
# The shared entity was untouched: same entity_id, still on the Común entry.
1430+
assert reg.async_get_entity_id(
1431+
"sensor", const.DOMAIN, f"{const.DOMAIN}_covers_open") == eid
1432+
assert reg.async_get(eid).config_entry_id == common.entry_id
1433+
1434+
13861435
# --- v0.98.0: the "Común" screen + global switches ---
13871436
async def test_global_switch_fans_out_to_all_shutters(hass: HomeAssistant) -> None:
13881437
from homeassistant.helpers import entity_registry as er

0 commit comments

Comments
 (0)