mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Identify the active ZHA coordinator device in API responses (#74739)
* Remove deprecated zigpy properties * Create a `ZHADevice.is_active_coordinator` property * Add `@puddly` to the ZHA code owners * Create a `ZHAGateway.coordinator_ieee` shortcut property
This commit is contained in:
parent
14baaf4b67
commit
2986a2f01b
@ -1238,8 +1238,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/zeroconf/ @bdraco
|
||||
/homeassistant/components/zerproc/ @emlove
|
||||
/tests/components/zerproc/ @emlove
|
||||
/homeassistant/components/zha/ @dmulcahey @adminiuga
|
||||
/tests/components/zha/ @dmulcahey @adminiuga
|
||||
/homeassistant/components/zha/ @dmulcahey @adminiuga @puddly
|
||||
/tests/components/zha/ @dmulcahey @adminiuga @puddly
|
||||
/homeassistant/components/zodiac/ @JulienTant
|
||||
/tests/components/zodiac/ @JulienTant
|
||||
/homeassistant/components/zone/ @home-assistant/core
|
||||
|
@ -119,10 +119,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={
|
||||
(dr.CONNECTION_ZIGBEE, str(zha_gateway.application_controller.ieee))
|
||||
},
|
||||
identifiers={(DOMAIN, str(zha_gateway.application_controller.ieee))},
|
||||
connections={(dr.CONNECTION_ZIGBEE, str(zha_gateway.coordinator_ieee))},
|
||||
identifiers={(DOMAIN, str(zha_gateway.coordinator_ieee))},
|
||||
name="Zigbee Coordinator",
|
||||
manufacturer="ZHA",
|
||||
model=zha_gateway.radio_description,
|
||||
|
@ -1091,10 +1091,7 @@ def async_load_api(hass: HomeAssistant) -> None:
|
||||
zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
|
||||
ieee: EUI64 = service.data[ATTR_IEEE]
|
||||
zha_device: ZHADevice | None = zha_gateway.get_device(ieee)
|
||||
if zha_device is not None and (
|
||||
zha_device.is_coordinator
|
||||
and zha_device.ieee == zha_gateway.application_controller.ieee
|
||||
):
|
||||
if zha_device is not None and zha_device.is_active_coordinator:
|
||||
_LOGGER.info("Removing the coordinator (%s) is not allowed", ieee)
|
||||
return
|
||||
_LOGGER.info("Removing node %s", ieee)
|
||||
|
@ -17,6 +17,7 @@ import zigpy_znp.zigbee.application
|
||||
from homeassistant.const import Platform
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
ATTR_ACTIVE_COORDINATOR = "active_coordinator"
|
||||
ATTR_ARGS = "args"
|
||||
ATTR_ATTRIBUTE = "attribute"
|
||||
ATTR_ATTRIBUTE_ID = "attribute_id"
|
||||
|
@ -30,6 +30,7 @@ from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from . import channels
|
||||
from .const import (
|
||||
ATTR_ACTIVE_COORDINATOR,
|
||||
ATTR_ARGS,
|
||||
ATTR_ATTRIBUTE,
|
||||
ATTR_AVAILABLE,
|
||||
@ -252,12 +253,20 @@ class ZHADevice(LogMixin):
|
||||
|
||||
@property
|
||||
def is_coordinator(self) -> bool | None:
|
||||
"""Return true if this device represents the coordinator."""
|
||||
"""Return true if this device represents a coordinator."""
|
||||
if self._zigpy_device.node_desc is None:
|
||||
return None
|
||||
|
||||
return self._zigpy_device.node_desc.is_coordinator
|
||||
|
||||
@property
|
||||
def is_active_coordinator(self) -> bool:
|
||||
"""Return true if this device is the active coordinator."""
|
||||
if not self.is_coordinator:
|
||||
return False
|
||||
|
||||
return self.ieee == self.gateway.coordinator_ieee
|
||||
|
||||
@property
|
||||
def is_end_device(self) -> bool | None:
|
||||
"""Return true if this device is an end device."""
|
||||
@ -499,6 +508,7 @@ class ZHADevice(LogMixin):
|
||||
"""Get ZHA device information."""
|
||||
device_info: dict[str, Any] = {}
|
||||
device_info.update(self.device_info)
|
||||
device_info[ATTR_ACTIVE_COORDINATOR] = self.is_active_coordinator
|
||||
device_info["entities"] = [
|
||||
{
|
||||
"entity_id": entity_ref.reference_id,
|
||||
|
@ -178,9 +178,7 @@ class ZHAGateway:
|
||||
self.application_controller.add_listener(self)
|
||||
self.application_controller.groups.add_listener(self)
|
||||
self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
|
||||
self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(
|
||||
self.application_controller.ieee
|
||||
)
|
||||
self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(self.coordinator_ieee)
|
||||
self.async_load_devices()
|
||||
self.async_load_groups()
|
||||
|
||||
@ -189,7 +187,7 @@ class ZHAGateway:
|
||||
"""Restore ZHA devices from zigpy application state."""
|
||||
for zigpy_device in self.application_controller.devices.values():
|
||||
zha_device = self._async_get_or_create_device(zigpy_device, restored=True)
|
||||
if zha_device.ieee == self.application_controller.ieee:
|
||||
if zha_device.ieee == self.coordinator_ieee:
|
||||
self.coordinator_zha_device = zha_device
|
||||
delta_msg = "not known"
|
||||
if zha_device.last_seen is not None:
|
||||
@ -435,6 +433,11 @@ class ZHAGateway:
|
||||
)
|
||||
self.ha_entity_registry.async_remove(entry.entity_id)
|
||||
|
||||
@property
|
||||
def coordinator_ieee(self) -> EUI64:
|
||||
"""Return the active coordinator's IEEE address."""
|
||||
return self.application_controller.state.node_info.ieee
|
||||
|
||||
@property
|
||||
def devices(self) -> dict[EUI64, ZHADevice]:
|
||||
"""Return devices."""
|
||||
|
@ -76,7 +76,7 @@
|
||||
"known_devices": ["Bitron Video AV2010/10"]
|
||||
}
|
||||
],
|
||||
"codeowners": ["@dmulcahey", "@adminiuga"],
|
||||
"codeowners": ["@dmulcahey", "@adminiuga", "@puddly"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_esphomelib._tcp.local.",
|
||||
|
@ -6,7 +6,9 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import zigpy.profiles.zha
|
||||
import zigpy.types
|
||||
import zigpy.zcl.clusters.general as general
|
||||
import zigpy.zdo.types as zdo_t
|
||||
|
||||
from homeassistant.components.zha.core.const import (
|
||||
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
|
||||
@ -42,7 +44,7 @@ def required_platforms_only():
|
||||
def zigpy_device(zigpy_device_mock):
|
||||
"""Device tracker zigpy device."""
|
||||
|
||||
def _dev(with_basic_channel: bool = True):
|
||||
def _dev(with_basic_channel: bool = True, **kwargs):
|
||||
in_clusters = [general.OnOff.cluster_id]
|
||||
if with_basic_channel:
|
||||
in_clusters.append(general.Basic.cluster_id)
|
||||
@ -54,7 +56,7 @@ def zigpy_device(zigpy_device_mock):
|
||||
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.ON_OFF_SWITCH,
|
||||
}
|
||||
}
|
||||
return zigpy_device_mock(endpoints)
|
||||
return zigpy_device_mock(endpoints, **kwargs)
|
||||
|
||||
return _dev
|
||||
|
||||
@ -321,3 +323,31 @@ async def test_device_restore_availability(
|
||||
assert hass.states.get(entity_id).state == STATE_OFF
|
||||
else:
|
||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_device_is_active_coordinator(hass, zha_device_joined, zigpy_device):
|
||||
"""Test that the current coordinator is uniquely detected."""
|
||||
|
||||
current_coord_dev = zigpy_device(ieee="aa:bb:cc:dd:ee:ff:00:11", nwk=0x0000)
|
||||
current_coord_dev.node_desc = current_coord_dev.node_desc.replace(
|
||||
logical_type=zdo_t.LogicalType.Coordinator
|
||||
)
|
||||
|
||||
old_coord_dev = zigpy_device(ieee="aa:bb:cc:dd:ee:ff:00:12", nwk=0x0000)
|
||||
old_coord_dev.node_desc = old_coord_dev.node_desc.replace(
|
||||
logical_type=zdo_t.LogicalType.Coordinator
|
||||
)
|
||||
|
||||
# The two coordinators have different IEEE addresses
|
||||
assert current_coord_dev.ieee != old_coord_dev.ieee
|
||||
|
||||
current_coordinator = await zha_device_joined(current_coord_dev)
|
||||
stale_coordinator = await zha_device_joined(old_coord_dev)
|
||||
|
||||
# Ensure the current ApplicationController's IEEE matches our coordinator's
|
||||
current_coordinator.gateway.application_controller.state.node_info.ieee = (
|
||||
current_coord_dev.ieee
|
||||
)
|
||||
|
||||
assert current_coordinator.is_active_coordinator
|
||||
assert not stale_coordinator.is_active_coordinator
|
||||
|
Loading…
x
Reference in New Issue
Block a user