Skip to content

Commit f289516

Browse files
woody-boxclaude
andauthored
DS: sección «Común» auto-creada + interruptores globales de persianas (v0.98.0) (#138)
- Nuevo módulo auto-singleton shutter_common: la pantalla común de persianas (recuentos + sol) pasa a su propio dispositivo "Dynamic Shutter · Común", se crea con la primera persiana y se elimina con la última (async_remove_entry) - Interruptores globales que activan/desactivan una función en TODAS las persianas (subconjunto curado + Solo observar) + botón Reanudar automático; semántica a lo bruto con entidad de Aviso que lo explica con ejemplo - Sincronización bidireccional global<->por-persiana vía dispatcher; recuentos sin temporizador propio (re-arme por SIGNAL_DS_COVERS en el tick de cada persiana) - Retirada la lógica de adopción del propietario (v0.96.0), ya innecesaria - README: tabla de madurez honesta por módulo (rodaje real vs solo tests) Claude-Session: https://claude.ai/code/session_01HjFYRJN939VaxK75qjLgoW Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8bfcdf4 commit f289516

17 files changed

Lines changed: 535 additions & 102 deletions

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,37 @@ 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.0] — 2026-07-03
8+
9+
### Added
10+
- **DS · sección "Dynamic Shutter · Común".** La pantalla común de las persianas (recuentos
11+
abiertas/cerradas/entreabiertas + sol: amanecer, anochecer, azimut, elevación, día/noche)
12+
deja de estar anidada dentro de la primera persiana y pasa a su **propio dispositivo**.
13+
Se **crea sola** con la primera persiana y se elimina con la última — no hay que añadirla.
14+
Tus entity_ids no cambian (misma unique_id), así que no se rompen los dashboards.
15+
- **DS · interruptores globales de persianas** (en esa pantalla común). Mandos que activan o
16+
desactivan una función en **TODAS** las persianas a la vez: **Solo observar (todas)** — modo
17+
manual/automático global de la casa — más **Protección meteo**, **Escudo térmico**, **Escudo
18+
de sol directo**, **Aislamiento nocturno**, **Amanecer gradual**, **Sombreado geométrico** y
19+
**Limitación de pico**, y un botón **Reanudar automático (todas)**. Los muy por-persiana
20+
(privacidad, bloqueo, excluir de simulación, seguir movimientos manuales) siguen solo en cada
21+
persiana.
22+
- **Aviso de los globales**: cada interruptor global es un mando "a lo bruto" — al apagarlo se
23+
apaga esa función en todas y al encenderlo se enciende en todas; **no recuerda** el estado
24+
individual de cada persiana. Una entidad "Aviso" en la pantalla común lo explica con ejemplo
25+
(si el Escudo térmico está ON en 4 y OFF en otras 4, apagar y volver a encender el global lo
26+
deja ON en las 8).
27+
28+
### Changed
29+
- **README · tabla de madurez** actualizada (es/en): valoración honesta por módulo con el eje
30+
"rodaje en real vs solo tests" (DV/DS/DW maduros; DC mixto; Zonas beta; Energía experimental).
31+
32+
### Internal
33+
- Nuevo módulo auto-singleton `shutter_common` (config-flow interno, sin paso de UI) con su
34+
coordinator sin temporizador; los recuentos se re-arman por dispatcher desde el tick de cada
35+
persiana. Retirada la lógica de adopción del propietario (v0.96.0), ya innecesaria. Suite
36+
608 tests; paridad de traducciones. Bump minor por ser módulo/feature nueva.
37+
738
## [0.97.2] — 2026-07-03
839

940
### Changed

README.es.md

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,26 @@ entidades, automatización, histórico e interfaz. La lógica de decisión vive
145145

146146
## Estado del proyecto
147147

148-
Versión actual: **v0.72.0**.
149-
150-
| Área | Estado | Comentario |
151-
|------|--------|------------|
152-
| Instalación HACS | Beta | Instalable como integración personalizada |
153-
| Dynamic Climate (DC) | Beta | Clima por zona, biases, lead adaptativo, perfiles de instalación |
154-
| Dynamic Ventilation (DV) | Beta | Velocidad VMC por IAQ, humedad, punto de rocío |
155-
| Dynamic Shutter (DS) | Beta | Posición por fachada/sol/meteo; modos, simulación de presencia, pico global |
156-
| Dynamic Home (Zonas) | Beta | Zonas, modos de casa, confort, presencia, changeover, pausa maestra |
157-
| Dynamic Energy | Beta | Margen ICP, tarifa, escasez, totales kWh/€, presupuesto anti-pico |
158-
| Bus SDHB | Beta | Arbitraje de intenciones en memoria |
159-
| Config flow (UI) | Funcional | Alta + opciones por categoría; reconfigurar y clonar |
160-
| FV / batería / VE | Experimental | Campos presentes pero gateados; sin validar por el autor |
148+
Versión actual: **v0.98.0**. Proyecto personal, de un solo mantenedor y pre-1.0:
149+
nada es "final" en sentido de producción amplia, así que la escala es relativa.
150+
El eje clave **no es el número de tests** sino el **rodaje en hardware real**
151+
cuánto ha corrido cada módulo de verdad, no solo en los (606+) tests unitarios.
152+
153+
- **Beta (madura)** — bien cubierto por tests **y** en uso real diario.
154+
- **Beta** — completo y con tests, con rodaje real moderado.
155+
- **Experimental** — funciona pero con poca validación real, o con partes sin construir.
156+
157+
| Módulo | Madurez | Comentario |
158+
|--------|---------|------------|
159+
| Dynamic Ventilation (DV) | 🟢 Beta (madura) | Probado en real con tus relés; velocidad por IAQ + failsafe consolidados |
160+
| Dynamic Shutter (DS) | 🟢 Beta (madura) | Probado en real; endurecido en seguridad tras incidencias reales (dale rodaje) |
161+
| Dynamic Weather (DW) | 🟢 Beta (estable) | Espejo de proveedores + alertas; poca complejidad, poco riesgo |
162+
| Dynamic Climate (DC) | 🟡 Beta / Experimental | Núcleo (base+biases, condensación, changeover) funcional; caminos avanzados (multi-emisor, conducto compartido, anti-pico, anti-ciclado) solo en tests, poco rodaje real |
163+
| Dynamic Home (Zonas) | 🟡 Beta | Zonas, modos, presencia y changeover en uso; presets de confort menos ejercitados |
164+
| Dynamic Energy | 🟠 Experimental | El módulo más nuevo; núcleo (margen/tarifa/coste) testeado; FV/batería/VE diferidos y sin validar |
165+
| Bus SDHB | 🟢 Beta (estable) | Arbitraje de intenciones en memoria, desempate determinista |
166+
| Config flow (UI) | Funcional | Alta + opciones por categoría; reconfigurar y clonar; "Común" de persianas auto-creada |
167+
| FV / batería / VE | 🟠 Experimental | Campos presentes pero gateados; sin validar por el autor |
161168
| Dashboards de ejemplo | Pendiente | Aún no empaquetados |
162169
| Capturas | Pendiente | Por añadir |
163170

README.md

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,26 @@ dependency** (`*_engine.py`); the HA wrappers only translate state.
141141

142142
## Project status
143143

144-
Current release: **v0.72.0**.
145-
146-
| Area | Status | Notes |
147-
|------|--------|-------|
148-
| HACS install | Beta | Installable as a custom integration |
149-
| Dynamic Climate (DC) | Beta | Per-zone climate, biases, adaptive lead, install profiles |
150-
| Dynamic Ventilation (DV) | Beta | VMC speed by IAQ, humidity, dew point |
151-
| Dynamic Shutter (DS) | Beta | Position by facade/sun/weather; modes, presence sim, global peak |
152-
| Dynamic Home (Zones) | Beta | Zones, house modes, comfort, presence, changeover, master pause |
153-
| Dynamic Energy | Beta | ICP headroom, tariff, scarcity, kWh/€ totals, anti-peak budget |
154-
| SDHB bus | Beta | In-memory intent arbitration |
155-
| Config flow (UI) | Functional | Setup + options grouped by category; reconfigure & clone |
156-
| PV / battery / EV | Experimental | Fields present but gated; not validated by the author |
144+
Current release: **v0.98.0**. This is a personal, single-maintainer, pre-1.0
145+
project: nothing is "final" in a broad-production sense, so the scale below is
146+
relative. The key axis is **not test count** but **real-world soak** — how much
147+
each module has actually run on hardware, not just in the (606+) unit tests.
148+
149+
- **Beta (mature)** — well covered by tests **and** in daily real-world use.
150+
- **Beta** — complete and tested, with modest real-world soak.
151+
- **Experimental** — works but little real-world validation, or parts unbuilt.
152+
153+
| Module | Maturity | Notes |
154+
|--------|----------|-------|
155+
| Dynamic Ventilation (DV) | 🟢 Beta (mature) | Battle-tested on real relays; IAQ speed + failsafe proven |
156+
| Dynamic Shutter (DS) | 🟢 Beta (mature) | Battle-tested; safety-hardened after real incidents (give it soak time) |
157+
| Dynamic Weather (DW) | 🟢 Beta (stable) | Simple provider mirror + alerts; low complexity, low risk |
158+
| Dynamic Climate (DC) | 🟡 Beta / Experimental | Core (base+biases, condensation, changeover) functional; advanced paths (multi-emitter, shared duct, anti-peak, compressor anti-cycle) tested-only, little real-world use |
159+
| Dynamic Home (Zones) | 🟡 Beta | Zones, modes, presence, changeover in use; comfort presets less exercised |
160+
| Dynamic Energy | 🟠 Experimental | Newest module; core (headroom/tariff/cost) tested; PV/battery/EV deferred and unvalidated |
161+
| SDHB bus | 🟢 Beta (stable) | In-memory intent arbitration, deterministic tie-break |
162+
| Config flow (UI) | Functional | Setup + options by category; reconfigure & clone; shutter "Común" auto-created |
163+
| PV / battery / EV | 🟠 Experimental | Fields present but gated; not validated by the author |
157164
| Example dashboards | Pending | Not packaged yet |
158165
| Screenshots | Pending | To be added |
159166

custom_components/dynamic_home/__init__.py

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
DvCoordinator,
2323
EnergyCoordinator,
2424
SdhbHub,
25+
ShutterCommonCoordinator,
2526
WxCoordinator,
2627
ZonesCoordinator,
2728
)
@@ -42,9 +43,31 @@ def _platforms(entry: ConfigEntry) -> list[str]:
4243
return const.PLATFORMS_ZONES
4344
if module == const.MODULE_ENERGY:
4445
return const.PLATFORMS_ENERGY
46+
if module == const.MODULE_SHUTTER_COMMON:
47+
return const.PLATFORMS_SHUTTER_COMMON
4548
return const.PLATFORMS_VMC
4649

4750

51+
async def _ensure_common_entry(hass: HomeAssistant) -> None:
52+
"""Auto-create the "Dynamic Shutter · Común" singleton (once), if missing.
53+
54+
Called when a shutter is set up. Guarded by a flag (several shutters set up
55+
at once) and by the flow's unique-id singleton, so only one entry is created.
56+
On restart the entry already exists and this is a no-op.
57+
"""
58+
if any(e.data.get(const.CONF_MODULE) == const.MODULE_SHUTTER_COMMON
59+
for e in hass.config_entries.async_entries(const.DOMAIN)):
60+
return
61+
if hass.data[const.DOMAIN].get("_common_creating"):
62+
return
63+
hass.data[const.DOMAIN]["_common_creating"] = True
64+
try:
65+
await hass.config_entries.flow.async_init(
66+
const.DOMAIN, context={"source": "shutter_common"})
67+
finally:
68+
hass.data[const.DOMAIN].pop("_common_creating", None)
69+
70+
4871
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
4972
"""Set up Dynamic Home from a config entry."""
5073
hub: SdhbHub = hass.data.setdefault(const.DOMAIN, {}).setdefault(
@@ -79,6 +102,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
79102
elif module == const.MODULE_ENERGY:
80103
coordinator = EnergyCoordinator(hass, entry) # F34 house energy context
81104
coordinator.async_setup_listeners()
105+
elif module == const.MODULE_SHUTTER_COMMON:
106+
coordinator = ShutterCommonCoordinator(hass, entry) # shared screen + globals
82107
else:
83108
coordinator = DvCoordinator(hass, entry, hub)
84109
coordinator.async_setup_listeners()
@@ -92,9 +117,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
92117
await hass.config_entries.async_forward_entry_setups(entry, _platforms(entry))
93118
entry.async_on_unload(entry.add_update_listener(_async_options_updated))
94119
_async_register_services(hass)
120+
# First shutter -> make sure the shared "Dynamic Shutter · Común" entry exists.
121+
# Background task tied to the entry so it's cancelled on unload (no lingering).
122+
if module == const.MODULE_SHUTTER:
123+
entry.async_create_background_task(
124+
hass, _ensure_common_entry(hass), "dynamic_home ensure common")
95125
return True
96126

97127

128+
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
129+
"""A config entry was deleted (not just unloaded). If the LAST shutter is
130+
gone, remove the auto-created "Común" singleton too — it has no reason to
131+
linger without any shutter. Only fires on real removal, never on restart.
132+
"""
133+
if entry.data.get(const.CONF_MODULE) != const.MODULE_SHUTTER:
134+
return
135+
shutters = [e for e in hass.config_entries.async_entries(const.DOMAIN)
136+
if e.data.get(const.CONF_MODULE) == const.MODULE_SHUTTER
137+
and e.entry_id != entry.entry_id]
138+
if shutters:
139+
return
140+
for e in hass.config_entries.async_entries(const.DOMAIN):
141+
if e.data.get(const.CONF_MODULE) == const.MODULE_SHUTTER_COMMON:
142+
hass.async_create_task(hass.config_entries.async_remove(e.entry_id))
143+
144+
98145
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
99146
"""Unload a config entry."""
100147
unloaded = await hass.config_entries.async_unload_platforms(
@@ -123,16 +170,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
123170
ph = hass.data[const.DOMAIN].get("_peak_ds")
124171
if ph is not None:
125172
ph.clear(entry.entry_id)
126-
# This entry's adder is gone with its platform.
127-
adders = hass.data[const.DOMAIN].get("_ds_summary_adders") or {}
128-
adders.pop(entry.entry_id, None)
129-
# If this entry owned the house-wide shared sensors (counts + sun),
130-
# release the marker; the next tick of any surviving DS coordinator
131-
# adopts them (see DsCoordinator._async_update_data) — they used to
132-
# simply vanish until a restart.
133-
if (hass.data[const.DOMAIN].get(const.DATA_DS_SUMMARY_OWNER)
134-
== entry.entry_id):
135-
hass.data[const.DOMAIN].pop(const.DATA_DS_SUMMARY_OWNER, None)
136173
# A VMC must not leave repair issues for a removed entry (F08 filter,
137174
# free-cooling advisory).
138175
if isinstance(coordinator, DvCoordinator):
@@ -156,7 +193,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
156193
if not _entry_ids(hass):
157194
_async_unregister_services(hass)
158195
for key in ("_hub", "_anticycle", "_peak_dc", "_peak_ds",
159-
"_shared_emit", "_facades", "_ds_summary_adders"):
196+
"_shared_emit", "_facades"):
160197
hass.data[const.DOMAIN].pop(key, None)
161198
return unloaded
162199

custom_components/dynamic_home/button.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,37 @@
1515
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry,
1616
async_add_entities: AddEntitiesCallback) -> None:
1717
coordinator = hass.data[const.DOMAIN][entry.entry_id]
18-
if entry.data.get(const.CONF_MODULE) == const.MODULE_SHUTTER:
18+
module = entry.data.get(const.CONF_MODULE)
19+
if module == const.MODULE_SHUTTER:
1920
async_add_entities([ResumeAutoButton(coordinator, entry)])
2021
return
22+
if module == const.MODULE_SHUTTER_COMMON:
23+
async_add_entities([GlobalResumeAutoButton(hass, entry)])
24+
return
2125
async_add_entities([FilterResetButton(coordinator, entry)])
2226

2327

28+
class GlobalResumeAutoButton(ButtonEntity):
29+
"""Clears the manual hold on EVERY shutter (whole-house "back to auto")."""
30+
31+
_attr_has_entity_name = True
32+
_attr_translation_key = "global_resume_auto"
33+
_attr_icon = "mdi:autorenew"
34+
35+
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
36+
self._hass = hass
37+
self._attr_unique_id = f"{const.DOMAIN}_global_resume_auto"
38+
self._attr_device_info = DeviceInfo(
39+
identifiers={(const.DOMAIN, const.SHUTTERS_DEVICE_ID)},
40+
name="Dynamic Shutter · Común")
41+
42+
async def async_press(self) -> None:
43+
from .coordinator import DsCoordinator
44+
for co in self._hass.data.get(const.DOMAIN, {}).values():
45+
if isinstance(co, DsCoordinator):
46+
co.clear_manual_override()
47+
48+
2449
class ResumeAutoButton(ButtonEntity):
2550
"""Clears the manual hold so the shutter goes back to automatic control."""
2651

custom_components/dynamic_home/config_flow.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,18 @@ async def async_step_zones(self, user_input: dict[str, Any] | None = None):
226226
title=user_input[const.CONF_NAME], data=data)
227227
return self.async_show_form(step_id="zones", data_schema=STEP_ZONES_SCHEMA)
228228

229+
async def async_step_shutter_common(self, discovery_info=None):
230+
"""Auto-created singleton (no UI): the shared shutter screen + global
231+
switches. Kicked off by __init__ when the first shutter is set up, and
232+
removed with the last shutter — the user never adds it by hand.
233+
"""
234+
await self.async_set_unique_id("shutter_common_singleton")
235+
self._abort_if_unique_id_configured() # one common entry only
236+
return self.async_create_entry(
237+
title="Dynamic Shutter · Común",
238+
data={const.CONF_MODULE: const.MODULE_SHUTTER_COMMON,
239+
const.CONF_NAME: "Dynamic Shutter · Común"})
240+
229241
async def async_step_vmc(self, user_input: dict[str, Any] | None = None):
230242
if user_input is not None:
231243
await self.async_set_unique_id(f"vmc_{user_input[const.CONF_SW_PWR]}")

custom_components/dynamic_home/const.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
MODULE_WEATHER = "weather"
1313
MODULE_ZONES = "zones"
1414
MODULE_ENERGY = "energy"
15+
# Auto-created singleton: the shared shutter screen (counts + sun) and the global
16+
# shutter switches live here, in their own "Dynamic Shutter · Común" section.
17+
MODULE_SHUTTER_COMMON = "shutter_common"
1518

1619
# Short Dynamic Home tag per module. Prefixed onto the hardware mirrors and
1720
# suffixed onto the primary control entities (cover/climate/fan) so everything
@@ -32,15 +35,22 @@
3235
PLATFORMS_WEATHER: list[str] = ["weather", "binary_sensor", "sensor"]
3336
PLATFORMS_ZONES: list[str] = ["sensor", "select", "binary_sensor", "switch"]
3437
PLATFORMS_ENERGY: list[str] = ["sensor", "binary_sensor"]
38+
PLATFORMS_SHUTTER_COMMON: list[str] = ["sensor", "switch", "button"]
3539

3640
# Shared device that groups the bus-conflict sensors of every module (so the
3741
# whole bus is observable from one place in the HA UI, not scattered per entry).
3842
BUS_DEVICE_ID = "bus"
3943

40-
# Shared device + owner-entry marker for the house-wide shutter counts (how many
41-
# DS-managed covers are open/closed/ajar), created once by the first DS entry.
44+
# Shared device for the house-wide shutter screen (how many DS-managed covers are
45+
# open/closed/ajar, plus the sun sensors and the global switches). Owned by the
46+
# auto-created "Dynamic Shutter · Común" entry (MODULE_SHUTTER_COMMON).
4247
SHUTTERS_DEVICE_ID = "shutters_summary"
43-
DATA_DS_SUMMARY_OWNER = "_ds_summary_owner"
48+
# Dispatcher signal: a global shutter switch changed -> per-shutter toggles
49+
# re-write their UI state so they reflect the fan-out immediately.
50+
SIGNAL_DS_TOGGLES = f"{DOMAIN}_ds_toggles_changed"
51+
# Dispatcher signal: a DS coordinator ticked -> the house-wide count sensors
52+
# re-arm their cover tracking (catching shutters added/removed) and refresh.
53+
SIGNAL_DS_COVERS = f"{DOMAIN}_ds_covers_changed"
4454

4555
# --- Config entry keys: VMC (DV) hardware map ---
4656
CONF_NAME = "name"

custom_components/dynamic_home/coordinator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
from .coordinator_ds import DsCoordinator
1313
from .coordinator_dv import DvCoordinator
1414
from .coordinator_energy import EnergyCoordinator
15+
from .coordinator_shutter_common import ShutterCommonCoordinator
1516
from .coordinator_weather import WxCoordinator
1617
from .coordinator_zones import ZonesCoordinator
1718

1819
__all__ = ["DcCoordinator", "DsCoordinator", "DvCoordinator", "EnergyCoordinator",
19-
"WxCoordinator", "ZonesCoordinator", "SdhbHub"]
20+
"ShutterCommonCoordinator", "WxCoordinator", "ZonesCoordinator",
21+
"SdhbHub"]

custom_components/dynamic_home/coordinator_ds.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from homeassistant.config_entries import ConfigEntry
1515
from homeassistant.core import HomeAssistant
16+
from homeassistant.helpers.dispatcher import async_dispatcher_send
1617
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
1718
from homeassistant.util import dt as dt_util
1819

@@ -569,13 +570,6 @@ def _peak_gate(self, cfg: DsConfig, decision: DsDecision,
569570
return decision
570571

571572
async def _async_update_data(self) -> DsDecision:
572-
# Orphaned shared sensors (the owning DS entry unloaded): adopt them on
573-
# this entry's platform so the house counts / sun sensors live on.
574-
data = self.hass.data.get(const.DOMAIN, {})
575-
if (const.DATA_DS_SUMMARY_OWNER not in data
576-
and self.entry.entry_id in (data.get("_ds_summary_adders") or {})):
577-
from .sensor import adopt_shared_shutter_sensors
578-
adopt_shared_shutter_sensors(self.hass, prefer=self.entry.entry_id)
579573
cfg = self._cfg()
580574
cfg.privacy_pos_pct = int(self.privacy_pct)
581575
now_ts = dt_util.utcnow().timestamp()
@@ -676,6 +670,9 @@ async def _async_update_data(self) -> DsDecision:
676670
self.power_w = (meter if meter is not None
677671
else (cfg.est_w_motor if moved else 0.0))
678672
self._record_energy(now_ts)
673+
# Poke the house-wide count sensors (on the "Común" entry) so they re-arm
674+
# their cover tracking and refresh — no idle timer of their own.
675+
async_dispatcher_send(self.hass, const.SIGNAL_DS_COVERS)
679676
return decision
680677

681678
def _record_energy(self, now_ts: float) -> None:

0 commit comments

Comments
 (0)