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:
puddly 2022-07-11 14:19:30 -04:00 committed by GitHub
parent 14baaf4b67
commit 2986a2f01b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 57 additions and 18 deletions

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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"

View File

@ -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,

View File

@ -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."""

View File

@ -76,7 +76,7 @@
"known_devices": ["Bitron Video AV2010/10"]
}
],
"codeowners": ["@dmulcahey", "@adminiuga"],
"codeowners": ["@dmulcahey", "@adminiuga", "@puddly"],
"zeroconf": [
{
"type": "_esphomelib._tcp.local.",

View File

@ -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