Remove stale Shelly BLU TRV devices (#145994)

* Remove stale Shelly BLU TRV devices

* Add test

* Remove config entry from device
This commit is contained in:
Shay Levy 2025-06-11 18:39:49 +03:00 committed by GitHub
parent e39edcc234
commit ab2fc4e9a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 131 additions and 3 deletions

View File

@ -64,6 +64,7 @@ from .utils import (
get_http_port,
get_rpc_scripts_event_types,
get_ws_context,
remove_stale_blu_trv_devices,
)
PLATFORMS: Final = [
@ -300,6 +301,7 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ShellyConfigEntry)
runtime_data.rpc_script_events = await get_rpc_scripts_event_types(
device, ignore_scripts=[BLE_SCRIPT_NAME]
)
remove_stale_blu_trv_devices(hass, device, entry)
except (DeviceConnectionError, MacAddressMismatchError, RpcCallError) as err:
await device.shutdown()
raise ConfigEntryNotReady(

View File

@ -61,8 +61,8 @@ rules:
reconfiguration-flow: done
repair-issues: done
stale-devices:
status: todo
comment: BLU TRV needs to be removed when un-paired
status: done
comment: BLU TRV is removed when un-paired
# Platinum
async-dependency: done

View File

@ -16,6 +16,7 @@ from aioshelly.const import (
DEFAULT_COAP_PORT,
DEFAULT_HTTP_PORT,
MODEL_1L,
MODEL_BLU_GATEWAY_G3,
MODEL_DIMMER,
MODEL_DIMMER_2,
MODEL_EM3,
@ -821,3 +822,32 @@ def get_block_device_info(
manufacturer="Shelly",
via_device=(DOMAIN, mac),
)
@callback
def remove_stale_blu_trv_devices(
hass: HomeAssistant, rpc_device: RpcDevice, entry: ConfigEntry
) -> None:
"""Remove stale BLU TRV devices."""
if rpc_device.model != MODEL_BLU_GATEWAY_G3:
return
dev_reg = dr.async_get(hass)
devices = dev_reg.devices.get_devices_for_config_entry_id(entry.entry_id)
config = rpc_device.config
blutrv_keys = get_rpc_key_ids(config, BLU_TRV_IDENTIFIER)
trv_addrs = [config[f"{BLU_TRV_IDENTIFIER}:{key}"]["addr"] for key in blutrv_keys]
for device in devices:
if not device.via_device_id:
# Device is not a sub-device, skip
continue
if any(
identifier[0] == DOMAIN and identifier[1] in trv_addrs
for identifier in device.identifiers
):
continue
LOGGER.debug("Removing stale BLU TRV device %s", device.name)
dev_reg.async_update_device(device.id, remove_config_entry_id=entry.entry_id)

View File

@ -260,6 +260,33 @@ MOCK_BLU_TRV_REMOTE_CONFIG = {
"meta": {},
},
},
{
"key": "blutrv:201",
"status": {
"id": 201,
"target_C": 17.1,
"current_C": 17.1,
"pos": 0,
"rssi": -60,
"battery": 100,
"packet_id": 58,
"last_updated_ts": 1734967725,
"paired": True,
"rpc": True,
"rsv": 61,
},
"config": {
"id": 201,
"addr": "f8:44:77:25:f0:de",
"name": "TRV-201",
"key": None,
"trv": "bthomedevice:201",
"temp_sensors": [],
"dw_sensors": [],
"override_delay": 30,
"meta": {},
},
},
],
"blutrv:200": {
"id": 0,
@ -272,6 +299,17 @@ MOCK_BLU_TRV_REMOTE_CONFIG = {
"name": "TRV-Name",
"local_name": "SBTR-001AEU",
},
"blutrv:201": {
"id": 1,
"enable": True,
"min_valve_position": 0,
"default_boost_duration": 1800,
"default_override_duration": 2147483647,
"default_override_target_C": 8,
"addr": "f8:44:77:25:f0:de",
"name": "TRV-201",
"local_name": "SBTR-001AEU",
},
}
@ -287,6 +325,17 @@ MOCK_BLU_TRV_REMOTE_STATUS = {
"battery": 100,
"errors": [],
},
"blutrv:201": {
"id": 0,
"pos": 0,
"steps": 0,
"current_C": 15.2,
"target_C": 17.1,
"schedule_rev": 0,
"rssi": -60,
"battery": 100,
"errors": [],
},
}

View File

@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, Mock, call, patch
from aioshelly.block_device import COAP
from aioshelly.common import ConnectionOptions
from aioshelly.const import MODEL_PLUS_2PM
from aioshelly.const import MODEL_BLU_GATEWAY_G3, MODEL_PLUS_2PM
from aioshelly.exceptions import (
DeviceConnectionError,
InvalidAuthError,
@ -38,6 +38,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.device_registry import DeviceRegistry, format_mac
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.setup import async_setup_component
from . import MOCK_MAC, init_integration, mutate_rpc_device_status
@ -606,3 +607,49 @@ async def test_ble_scanner_unsupported_firmware_fixed(
assert not issue_registry.async_get_issue(DOMAIN, issue_id)
assert len(issue_registry.issues) == 0
async def test_blu_trv_stale_device_removal(
hass: HomeAssistant,
mock_blu_trv: Mock,
entity_registry: EntityRegistry,
device_registry: DeviceRegistry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test BLU TRV removal of stale a device after un-pairing."""
trv_200_entity_id = "climate.trv_name"
trv_201_entity_id = "climate.trv_201"
monkeypatch.setattr(mock_blu_trv, "model", MODEL_BLU_GATEWAY_G3)
gw_entry = await init_integration(hass, 3, model=MODEL_BLU_GATEWAY_G3)
# verify that both trv devices are present
assert hass.states.get(trv_200_entity_id) is not None
trv_200_entry = entity_registry.async_get(trv_200_entity_id)
assert trv_200_entry
trv_200_device_entry = device_registry.async_get(trv_200_entry.device_id)
assert trv_200_device_entry
assert trv_200_device_entry.name == "TRV-Name"
assert hass.states.get(trv_201_entity_id) is not None
trv_201_entry = entity_registry.async_get(trv_201_entity_id)
assert trv_201_entry
trv_201_device_entry = device_registry.async_get(trv_201_entry.device_id)
assert trv_201_device_entry
assert trv_201_device_entry.name == "TRV-201"
# simulate un-pairing of trv 201 device
monkeypatch.delitem(mock_blu_trv.config, "blutrv:201")
monkeypatch.delitem(mock_blu_trv.status, "blutrv:201")
await hass.config_entries.async_reload(gw_entry.entry_id)
await hass.async_block_till_done()
# verify that trv 201 is removed
assert hass.states.get(trv_200_entity_id) is not None
assert device_registry.async_get(trv_200_entry.device_id) is not None
assert hass.states.get(trv_201_entity_id) is None
assert device_registry.async_get(trv_201_entry.device_id) is None