Skip to content

[pull] dev from home-assistant:dev #782

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 4 commits into from
Jun 9, 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
43 changes: 43 additions & 0 deletions homeassistant/components/homee/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Diagnostics for homee integration."""

from typing import Any

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntry

from . import DOMAIN, HomeeConfigEntry

TO_REDACT = [CONF_PASSWORD, CONF_USERNAME, "latitude", "longitude", "wlan_ssid"]


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: HomeeConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""

return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"settings": async_redact_data(entry.runtime_data.settings.raw_data, TO_REDACT),
"devices": [{"node": node.raw_data} for node in entry.runtime_data.nodes],
}


async def async_get_device_diagnostics(
hass: HomeAssistant, entry: HomeeConfigEntry, device: DeviceEntry
) -> dict[str, Any]:
"""Return diagnostics for a device."""

# Extract node_id from the device identifiers
split_uid = next(
identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN
).split("-")
# Homee hub itself only has MAC as identifier and a node_id of -1
node_id = -1 if len(split_uid) < 2 else split_uid[1]

node = entry.runtime_data.get_node_by_id(int(node_id))
assert node is not None
return {
"homee node": node.raw_data,
}
45 changes: 37 additions & 8 deletions homeassistant/components/switch_as_x/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
from homeassistant.components.homeassistant import exposed_entities
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ENTITY_ID
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.core import Event, HomeAssistant, callback, valid_entity_id
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.event import async_track_entity_registry_updated_event

from .const import CONF_INVERT, CONF_TARGET_DOMAIN
from .const import CONF_INVERT, CONF_TARGET_DOMAIN, DOMAIN
from .light import LightSwitch

__all__ = ["LightSwitch"]
Expand Down Expand Up @@ -44,10 +44,12 @@ def async_add_to_device(

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
registry = er.async_get(hass)
entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)
try:
entity_id = er.async_validate_entity_id(registry, entry.options[CONF_ENTITY_ID])
entity_id = er.async_validate_entity_id(
entity_registry, entry.options[CONF_ENTITY_ID]
)
except vol.Invalid:
# The entity is identified by an unknown entity registry ID
_LOGGER.error(
Expand All @@ -68,24 +70,51 @@ async def async_registry_updated(
return

if "entity_id" in data["changes"]:
# Entity_id changed, reload the config entry
await hass.config_entries.async_reload(entry.entry_id)
# Entity_id changed, update or reload the config entry
if valid_entity_id(entry.options[CONF_ENTITY_ID]):
# If the entity is pointed to by an entity ID, update the entry
hass.config_entries.async_update_entry(
entry,
options={**entry.options, CONF_ENTITY_ID: data["entity_id"]},
)
else:
await hass.config_entries.async_reload(entry.entry_id)

if device_id and "device_id" in data["changes"]:
# If the tracked switch is no longer in the device, remove our config entry
# Handle the wrapped switch being moved to a different device or removed
# from the device
if (
not (entity_entry := registry.async_get(data[CONF_ENTITY_ID]))
not (entity_entry := entity_registry.async_get(data[CONF_ENTITY_ID]))
or not device_registry.async_get(device_id)
or entity_entry.device_id == device_id
):
# No need to do any cleanup
return

# The wrapped switch has been moved to a different device, update the
# switch_as_x entity and the device entry to include our config entry
switch_as_x_entity_id = entity_registry.async_get_entity_id(
entry.options[CONF_TARGET_DOMAIN], DOMAIN, entry.entry_id
)
if switch_as_x_entity_id:
# Update the switch_as_x entity to point to the new device (or no device)
entity_registry.async_update_entity(
switch_as_x_entity_id, device_id=entity_entry.device_id
)

if entity_entry.device_id is not None:
device_registry.async_update_device(
entity_entry.device_id, add_config_entry_id=entry.entry_id
)

device_registry.async_update_device(
device_id, remove_config_entry_id=entry.entry_id
)

# Reload the config entry so the switch_as_x entity is recreated with
# correct device info
await hass.config_entries.async_reload(entry.entry_id)

entry.async_on_unload(
async_track_entity_registry_updated_event(
hass, entity_id, async_registry_updated
Expand Down
1 change: 1 addition & 0 deletions tests/components/homee/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def get_attributes(attributes: list[Any]) -> list[AsyncMock]:
def attribute_by_type(type, instance=0) -> HomeeAttribute | None:
return {attr.type: attr for attr in mock_node.attributes}.get(type)

mock_node.raw_data = json_node
mock_node.get_attribute_by_type = attribute_by_type

return mock_node
Expand Down
11 changes: 11 additions & 0 deletions tests/components/homee/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def mock_config_entry() -> MockConfigEntry:
CONF_PASSWORD: TESTPASS,
},
unique_id=HOMEE_ID,
entry_id="test_entry_id",
)


Expand Down Expand Up @@ -68,5 +69,15 @@ def mock_homee() -> Generator[AsyncMock]:
homee.connected = True

homee.get_access_token.return_value = "test_token"
# Mock the Homee settings raw_data for diagnostics
homee.settings.raw_data = {
"uid": HOMEE_ID,
"homee_name": HOMEE_NAME,
"version": "1.2.3",
"mac_address": "00:05:55:11:ee:cc",
"wlan_ssid": "TestSSID",
"latitude": 52.5200,
"longitude": 13.4050,
}

yield homee
Loading