8000 Remove stale Shelly BLU TRV devices by thecode · Pull Request #145994 · home-assistant/core · GitHub
[go: up one dir, main page]

Skip to content

Remove stale Shelly BLU TRV devices #145994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions homeassistant/components/shelly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
get_http_port,
get_rpc_scripts_event_types,
get_ws_context,
remove_stale_blu_trv_devices,
)

PLATFORMS: Final = [
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/shelly/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions homeassistant/components/shelly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
DEFAULT_COAP_PORT,
DEFAULT_HTTP_PORT,
MODEL_1L,
MODEL_BLU_GATEWAY_G3,
MODEL_DIMMER,
MODEL_DIMMER_2,
MODEL_EM3,
Expand Down Expand Up @@ -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)
49 changes: 49 additions & 0 deletions tests/components/shelly/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,33 @@ def mock_white_light_set_state(
"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,
Expand All @@ -272,6 +299,17 @@ def mock_white_light_set_state(
"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",
},
}


Expand All @@ -287,6 +325,17 @@ def mock_white_light_set_state(
"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": [],
},
}


Expand Down
49 changes: 48 additions & 1 deletion tests/components/shelly/test_init.py
< 77EA td id="diff-69e9f252e2d010ea66971106acbeec81e06f95e69e7cbf55c1e6cbbd009eaae2R627" data-line-number="627" class="blob-num blob-num-addition js-linkable-line-number js-blob-rnum">
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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,
Expand Down Expand Up @@ -38,6 +38,7 @@
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
Expand Down Expand Up @@ -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
0