Skip to content

Commit ab2fc4e

Browse files
authored
Remove stale Shelly BLU TRV devices (home-assistant#145994)
* Remove stale Shelly BLU TRV devices * Add test * Remove config entry from device
1 parent e39edcc commit ab2fc4e

File tree

5 files changed

+131
-3
lines changed

5 files changed

+131
-3
lines changed

homeassistant/components/shelly/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
get_http_port,
6565
get_rpc_scripts_event_types,
6666
get_ws_context,
67+
remove_stale_blu_trv_devices,
6768
)
6869

6970
PLATFORMS: Final = [
@@ -300,6 +301,7 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ShellyConfigEntry)
300301
runtime_data.rpc_script_events = await get_rpc_scripts_event_types(
301302
device, ignore_scripts=[BLE_SCRIPT_NAME]
302303
)
304+
remove_stale_blu_trv_devices(hass, device, entry)
303305
except (DeviceConnectionError, MacAddressMismatchError, RpcCallError) as err:
304306
await device.shutdown()
305307
raise ConfigEntryNotReady(

homeassistant/components/shelly/quality_scale.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ rules:
6161
reconfiguration-flow: done
6262
repair-issues: done
6363
stale-devices:
64-
status: todo
65-
comment: BLU TRV needs to be removed when un-paired
64+
status: done
65+
comment: BLU TRV is removed when un-paired
6666

6767
# Platinum
6868
async-dependency: done

homeassistant/components/shelly/utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DEFAULT_COAP_PORT,
1717
DEFAULT_HTTP_PORT,
1818
MODEL_1L,
19+
MODEL_BLU_GATEWAY_G3,
1920
MODEL_DIMMER,
2021
MODEL_DIMMER_2,
2122
MODEL_EM3,
@@ -821,3 +822,32 @@ def get_block_device_info(
821822
manufacturer="Shelly",
822823
via_device=(DOMAIN, mac),
823824
)
825+
826+
827+
@callback
828+
def remove_stale_blu_trv_devices(
829+
hass: HomeAssistant, rpc_device: RpcDevice, entry: ConfigEntry
830+
) -> None:
831+
"""Remove stale BLU TRV devices."""
832+
if rpc_device.model != MODEL_BLU_GATEWAY_G3:
833+
return
834+
835+
dev_reg = dr.async_get(hass)
836+
devices = dev_reg.devices.get_devices_for_config_entry_id(entry.entry_id)
837+
config = rpc_device.config
838+
blutrv_keys = get_rpc_key_ids(config, BLU_TRV_IDENTIFIER)
839+
trv_addrs = [config[f"{BLU_TRV_IDENTIFIER}:{key}"]["addr"] for key in blutrv_keys]
840+
841+
for device in devices:
842+
if not device.via_device_id:
843+
# Device is not a sub-device, skip
844+
continue
845+
846+
if any(
847+
identifier[0] == DOMAIN and identifier[1] in trv_addrs
848+
for identifier in device.identifiers
849+
):
850+
continue
851+
852+
LOGGER.debug("Removing stale BLU TRV device %s", device.name)
853+
dev_reg.async_update_device(device.id, remove_config_entry_id=entry.entry_id)

tests/components/shelly/conftest.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,33 @@ def mock_white_light_set_state(
260260
"meta": {},
261261
},
262262
},
263+
{
264+
"key": "blutrv:201",
265+
"status": {
266+
"id": 201,
267+
"target_C": 17.1,
268+
"current_C": 17.1,
269+
"pos": 0,
270+
"rssi": -60,
271+
"battery": 100,
272+
"packet_id": 58,
273+
"last_updated_ts": 1734967725,
274+
"paired": True,
275+
"rpc": True,
276+
"rsv": 61,
277+
},
278+
"config": {
279+
"id": 201,
280+
"addr": "f8:44:77:25:f0:de",
281+
"name": "TRV-201",
282+
"key": None,
283+
"trv": "bthomedevice:201",
284+
"temp_sensors": [],
285+
"dw_sensors": [],
286+
"override_delay": 30,
287+
"meta": {},
288+
},
289+
},
263290
],
264291
"blutrv:200": {
265292
"id": 0,
@@ -272,6 +299,17 @@ def mock_white_light_set_state(
272299
"name": "TRV-Name",
273300
"local_name": "SBTR-001AEU",
274301
},
302+
"blutrv:201": {
303+
"id": 1,
304+
"enable": True,
305+
"min_valve_position": 0,
306+
"default_boost_duration": 1800,
307+
"default_override_duration": 2147483647,
308+
"default_override_target_C": 8,
309+
"addr": "f8:44:77:25:f0:de",
310+
"name": "TRV-201",
311+
"local_name": "SBTR-001AEU",
312+
},
275313
}
276314

277315

@@ -287,6 +325,17 @@ def mock_white_light_set_state(
287325
"battery": 100,
288326
"errors": [],
289327
},
328+
"blutrv:201": {
329+
"id": 0,
330+
"pos": 0,
331+
"steps": 0,
332+
"current_C": 15.2,
333+
"target_C": 17.1,
334+
"schedule_rev": 0,
335+
"rssi": -60,
336+
"battery": 100,
337+
"errors": [],
338+
},
290339
}
291340

292341

tests/components/shelly/test_init.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from aioshelly.block_device import COAP
77
from aioshelly.common import ConnectionOptions
8-
from aioshelly.const import MODEL_PLUS_2PM
8+
from aioshelly.const import MODEL_BLU_GATEWAY_G3, MODEL_PLUS_2PM
99
from aioshelly.exceptions import (
1010
DeviceConnectionError,
1111
InvalidAuthError,
@@ -38,6 +38,7 @@
3838
from homeassistant.core import HomeAssistant
3939
from homeassistant.helpers import issue_registry as ir
4040
from homeassistant.helpers.device_registry import DeviceRegistry, format_mac
41+
from homeassistant.helpers.entity_registry import EntityRegistry
4142
from homeassistant.setup import async_setup_component
4243

4344
from . import MOCK_MAC, init_integration, mutate_rpc_device_status
@@ -606,3 +607,49 @@ async def test_ble_scanner_unsupported_firmware_fixed(
606607

607608
assert not issue_registry.async_get_issue(DOMAIN, issue_id)
608609
assert len(issue_registry.issues) == 0
610+
611+
612+
async def test_blu_trv_stale_device_removal(
613+
hass: HomeAssistant,
614+
mock_blu_trv: Mock,
615+
entity_registry: EntityRegistry,
616+
device_registry: DeviceRegistry,
617+
monkeypatch: pytest.MonkeyPatch,
618+
) -> None:
619+
"""Test BLU TRV removal of stale a device after un-pairing."""
620+
trv_200_entity_id = "climate.trv_name"
621+
trv_201_entity_id = "climate.trv_201"
622+
623+
monkeypatch.setattr(mock_blu_trv, "model", MODEL_BLU_GATEWAY_G3)
624+
gw_entry = await init_integration(hass, 3, model=MODEL_BLU_GATEWAY_G3)
625+
626+
# verify that both trv devices are present
627+
assert hass.states.get(trv_200_entity_id) is not None
628+
trv_200_entry = entity_registry.async_get(trv_200_entity_id)
629+
assert trv_200_entry
630+
631+
trv_200_device_entry = device_registry.async_get(trv_200_entry.device_id)
632+
assert trv_200_device_entry
633+
assert trv_200_device_entry.name == "TRV-Name"
634+
635+
assert hass.states.get(trv_201_entity_id) is not None
636+
trv_201_entry = entity_registry.async_get(trv_201_entity_id)
637+
assert trv_201_entry
638+
639+
trv_201_device_entry = device_registry.async_get(trv_201_entry.device_id)
640+
assert trv_201_device_entry
641+
assert trv_201_device_entry.name == "TRV-201"
642+
643+
# simulate un-pairing of trv 201 device
644+
monkeypatch.delitem(mock_blu_trv.config, "blutrv:201")
645+
monkeypatch.delitem(mock_blu_trv.status, "blutrv:201")
646+
647+
await hass.config_entries.async_reload(gw_entry.entry_id)
648+
await hass.async_block_till_done()
649+
650+
# verify that trv 201 is removed
651+
assert hass.states.get(trv_200_entity_id) is not None
652+
assert device_registry.async_get(trv_200_entry.device_id) is not None
653+
654+
assert hass.states.get(trv_201_entity_id) is None
655+
assert device_registry.async_get(trv_201_entry.device_id) is None

0 commit comments

Comments
 (0)