mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Bump ZHA to 2.0.0 (#174586)
This commit is contained in:
@@ -77,6 +77,8 @@ from zha.zigbee.device import (
|
||||
ZHAEvent,
|
||||
)
|
||||
from zha.zigbee.group import Group, GroupInfo, GroupMember
|
||||
import zhaquirks
|
||||
import zhaquirks.legacy
|
||||
from zigpy.config import (
|
||||
CONF_DATABASE,
|
||||
CONF_DEVICE,
|
||||
@@ -312,6 +314,36 @@ class ZHADeviceProxy(EventBase):
|
||||
self._unsubs: list[Callable[[], None]] = []
|
||||
self._unsubs.append(self.device.on_all_events(self._handle_event_protocol))
|
||||
|
||||
@callback
|
||||
def async_rebind_device(self, device: Device) -> None:
|
||||
"""Repoint this proxy to a replacement device object after a re-interview."""
|
||||
for unsub in self._unsubs:
|
||||
unsub()
|
||||
|
||||
self._unsubs.clear()
|
||||
|
||||
self.device = device
|
||||
self._unsubs.append(self.device.on_all_events(self._handle_event_protocol))
|
||||
self.attach_event_handlers()
|
||||
|
||||
@callback
|
||||
def attach_event_handlers(self) -> None:
|
||||
"""Attach event handlers to the ZHA device."""
|
||||
device_registry = dr.async_get(self.gateway_proxy.hass)
|
||||
|
||||
# Sync the device's firmware version into the device registry
|
||||
def update_sw_version(event: DeviceFirmwareInfoUpdatedEvent) -> None:
|
||||
"""Update software version in device registry."""
|
||||
device_registry.async_update_device(
|
||||
self.device_id, sw_version=event.new_firmware_version
|
||||
)
|
||||
|
||||
self._unsubs.append(
|
||||
self.device.on_event(
|
||||
DeviceFirmwareInfoUpdatedEvent.event_type, update_sw_version
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def device_id(self) -> str:
|
||||
"""Return the HA device registry device id."""
|
||||
@@ -726,11 +758,24 @@ class ZHAGatewayProxy(EventBase):
|
||||
def handle_device_fully_initialized(self, event: DeviceFullInitEvent) -> None:
|
||||
"""Handle a device fully initialized event."""
|
||||
zha_device = self.gateway.get_device(event.device_info.ieee)
|
||||
|
||||
# If ZHA swaps out the underlying device object, we need to update the proxy to
|
||||
# point to the new one. The replacement is initialized silently
|
||||
# (its entity-added events are suppressed), so we repoint the proxy and re-add
|
||||
# its entities here; the old entities were already removed via teardown events
|
||||
# on the previous object.
|
||||
device_proxy = self.device_proxies.get(zha_device.ieee)
|
||||
swapped_device = (
|
||||
device_proxy is not None and device_proxy.device is not zha_device
|
||||
)
|
||||
|
||||
zha_device_proxy = self._async_get_or_create_device_proxy(zha_device)
|
||||
if swapped_device:
|
||||
zha_device_proxy.async_rebind_device(zha_device)
|
||||
|
||||
device_info = zha_device_proxy.zha_device_info
|
||||
device_info[DEVICE_PAIRING_STATUS] = event.device_info.pairing_status.name
|
||||
if event.new_join:
|
||||
if event.new_join or swapped_device:
|
||||
self._create_entity_metadata(zha_device_proxy)
|
||||
async_dispatcher_send(self.hass, SIGNAL_ADD_ENTITIES)
|
||||
async_dispatcher_send(
|
||||
@@ -870,19 +915,7 @@ class ZHAGatewayProxy(EventBase):
|
||||
sw_version=zha_device.firmware_version,
|
||||
)
|
||||
zha_device_proxy.device_id = device_registry_device.id
|
||||
|
||||
def update_sw_version(event: DeviceFirmwareInfoUpdatedEvent) -> None:
|
||||
"""Update software version in device registry."""
|
||||
device_registry.async_update_device(
|
||||
device_registry_device.id,
|
||||
sw_version=event.new_firmware_version,
|
||||
)
|
||||
|
||||
self._unsubs.append(
|
||||
zha_device.on_event(
|
||||
DeviceFirmwareInfoUpdatedEvent.event_type, update_sw_version
|
||||
)
|
||||
)
|
||||
zha_device_proxy.attach_event_handlers()
|
||||
|
||||
return zha_device_proxy
|
||||
|
||||
@@ -1363,6 +1396,10 @@ def create_zha_config(hass: HomeAssistant, ha_zha_data: HAZHAData) -> ZHAData:
|
||||
quirks_config: QuirksConfiguration = QuirksConfiguration(
|
||||
enabled=ha_zha_data.yaml_config.get(CONF_ENABLE_QUIRKS, True),
|
||||
custom_quirks_path=ha_zha_data.yaml_config.get(CONF_CUSTOM_QUIRKS_PATH),
|
||||
setup_function=zhaquirks.setup,
|
||||
uninitialized_packet_handler=(
|
||||
zhaquirks.legacy.handle_message_from_uninitialized_sender
|
||||
),
|
||||
)
|
||||
overrides_config: dict[str, DeviceOverridesConfiguration] = {}
|
||||
overrides: dict[str, dict[str, Any]] = cast(
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"universal_silabs_flasher",
|
||||
"serialx"
|
||||
],
|
||||
"requirements": ["zha==1.4.1"],
|
||||
"requirements": ["zha==2.0.0", "zha-quirks==2.0.0"],
|
||||
"usb": [
|
||||
{
|
||||
"description": "*2652*",
|
||||
|
||||
@@ -45,13 +45,18 @@ from .helpers import get_zha_data
|
||||
RECOMMENDED_RADIOS = (
|
||||
RadioType.ezsp,
|
||||
RadioType.znp,
|
||||
RadioType.ziggurat,
|
||||
RadioType.deconz,
|
||||
)
|
||||
|
||||
# Only the common radio types will be autoprobed, ordered by new device popularity.
|
||||
# XBee takes too long to probe since it scans through all possible bauds and likely has
|
||||
# very few users to begin with.
|
||||
AUTOPROBE_RADIOS = RECOMMENDED_RADIOS
|
||||
AUTOPROBE_RADIOS = (
|
||||
RadioType.ezsp,
|
||||
RadioType.znp,
|
||||
RadioType.deconz,
|
||||
)
|
||||
|
||||
CONNECT_DELAY_S = 1.0
|
||||
RETRY_DELAY_S = 1.0
|
||||
|
||||
Generated
+4
-1
@@ -3466,7 +3466,10 @@ zeroconf==0.150.0
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==1.4.1
|
||||
zha-quirks==2.0.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==2.0.0
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.13
|
||||
|
||||
@@ -8,6 +8,7 @@ from unittest.mock import AsyncMock, MagicMock, create_autospec, patch
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from zha.quirks import DEVICE_REGISTRY
|
||||
import zhaquirks
|
||||
import zigpy
|
||||
from zigpy.application import ControllerApplication
|
||||
@@ -18,7 +19,6 @@ import zigpy.device
|
||||
import zigpy.group
|
||||
import zigpy.profiles
|
||||
from zigpy.profiles import zha
|
||||
import zigpy.quirks
|
||||
import zigpy.state
|
||||
import zigpy.types
|
||||
import zigpy.util
|
||||
@@ -397,7 +397,7 @@ def zigpy_device_mock(zigpy_app_controller) -> Callable[..., zigpy.device.Device
|
||||
device = quirk(zigpy_app_controller, device.ieee, device.nwk, device)
|
||||
else:
|
||||
# Allow zigpy to apply quirks if we don't pass one explicitly
|
||||
device = zigpy.quirks.get_device(device)
|
||||
device = DEVICE_REGISTRY.resolve(device)
|
||||
|
||||
if patch_cluster:
|
||||
for endpoint in (ep for epid, ep in device.endpoints.items() if epid):
|
||||
|
||||
@@ -6,7 +6,9 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
from zha.application import Platform as ZhaPlatform
|
||||
from zha.application.platforms import PlatformEntity
|
||||
from zha.quirks import DEVICE_REGISTRY
|
||||
from zha.zigbee.device import DeviceEntityAddedEvent, DeviceEntityRemovedEvent
|
||||
from zhaquirks.builder import QuirkBuilder
|
||||
from zigpy.device import Device
|
||||
from zigpy.profiles import zha
|
||||
from zigpy.zcl.clusters import general
|
||||
@@ -257,3 +259,71 @@ async def test_remove_entity_reference_when_ieee_already_cleared(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert ieee not in gateway_proxy._ha_entity_refs
|
||||
|
||||
|
||||
async def test_reinterview_quirk_class_swap_rebinds_proxy(
|
||||
hass: HomeAssistant,
|
||||
setup_zha: Callable[..., Coroutine[None]],
|
||||
zigpy_device_mock: Callable[..., Device],
|
||||
) -> None:
|
||||
"""A re-interview that changes the ZHA `Device` class re-binds the proxy."""
|
||||
await setup_zha()
|
||||
gateway = get_zha_gateway(hass)
|
||||
gateway_proxy = get_zha_gateway_proxy(hass)
|
||||
|
||||
endpoints = {
|
||||
1: {
|
||||
SIG_EP_INPUT: [general.Basic.cluster_id, general.OnOff.cluster_id],
|
||||
SIG_EP_OUTPUT: [],
|
||||
SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH,
|
||||
SIG_EP_PROFILE: zha.PROFILE_ID,
|
||||
}
|
||||
}
|
||||
|
||||
# Join with no matching quirk: resolves to the base `Device` class.
|
||||
zigpy_device = zigpy_device_mock(
|
||||
endpoints,
|
||||
ieee="01:2d:6f:00:0a:90:69:e8",
|
||||
manufacturer="Fake",
|
||||
model="Model_A",
|
||||
)
|
||||
gateway.get_or_create_device(zigpy_device)
|
||||
await gateway.async_device_initialized(zigpy_device)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
proxy = gateway_proxy.get_device_proxy(zigpy_device.ieee)
|
||||
assert proxy is not None
|
||||
original_device = proxy.device
|
||||
assert not original_device.quirk_applied
|
||||
|
||||
switch_id = find_entity_id(Platform.SWITCH, proxy, hass)
|
||||
assert switch_id is not None
|
||||
assert hass.states.get(switch_id).state == STATE_OFF
|
||||
|
||||
with DEVICE_REGISTRY.preserve_state():
|
||||
# Register a v2 quirk for the re-interviewed model -> `QuirkV2Device`.
|
||||
(
|
||||
QuirkBuilder("Fake", "Model_B")
|
||||
.friendly_name(model="Quirk B", manufacturer="Fake")
|
||||
.add_to_registry()
|
||||
)
|
||||
|
||||
reinterviewed = zigpy_device_mock(
|
||||
endpoints,
|
||||
ieee="01:2d:6f:00:0a:90:69:e8",
|
||||
manufacturer="Fake",
|
||||
model="Model_B",
|
||||
)
|
||||
gateway.device_reinterviewed(reinterviewed)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
# The `Device` object was replaced by a quirked one
|
||||
new_device = gateway.get_device(zigpy_device.ieee)
|
||||
assert new_device is not original_device
|
||||
assert new_device.quirk_applied
|
||||
|
||||
# The proxy followed the swap
|
||||
assert proxy.device is new_device
|
||||
|
||||
# And the switch entity was re-materialized on the new object.
|
||||
assert hass.states.get(switch_id).state == STATE_OFF
|
||||
|
||||
@@ -4,10 +4,9 @@ from collections.abc import Callable, Coroutine
|
||||
from unittest.mock import call, patch
|
||||
|
||||
import pytest
|
||||
from zhaquirks.builder import NumberDeviceClass, QuirkBuilder
|
||||
from zigpy.device import Device
|
||||
from zigpy.profiles import zha
|
||||
from zigpy.quirks.v2 import QuirkBuilder
|
||||
from zigpy.quirks.v2.homeassistant.number import NumberDeviceClass
|
||||
from zigpy.typing import UNDEFINED
|
||||
from zigpy.zcl.clusters import general
|
||||
import zigpy.zcl.foundation as zcl_f
|
||||
|
||||
@@ -4,9 +4,9 @@ from collections.abc import Callable, Coroutine
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from zhaquirks.builder import QuirkBuilder
|
||||
from zigpy.device import Device
|
||||
from zigpy.profiles import zha
|
||||
from zigpy.quirks.v2 import QuirkBuilder
|
||||
from zigpy.zcl import Cluster
|
||||
from zigpy.zcl.clusters import general, homeautomation, hvac, measurement, smartenergy
|
||||
from zigpy.zcl.clusters.hvac import Thermostat
|
||||
|
||||
Reference in New Issue
Block a user