mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Bump ZHA dependencies (#104335)
This commit is contained in:
parent
999875d0e4
commit
bd8f01bd35
@ -9,12 +9,12 @@ import re
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from zhaquirks import setup as setup_quirks
|
from zhaquirks import setup as setup_quirks
|
||||||
from zigpy.config import CONF_DATABASE, CONF_DEVICE, CONF_DEVICE_PATH
|
from zigpy.config import CONF_DATABASE, CONF_DEVICE, CONF_DEVICE_PATH
|
||||||
from zigpy.exceptions import NetworkSettingsInconsistent
|
from zigpy.exceptions import NetworkSettingsInconsistent, TransientConnectionError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_TYPE, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import CONF_TYPE, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import Event, HomeAssistant
|
from homeassistant.core import Event, HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
@ -29,6 +29,7 @@ from .core.const import (
|
|||||||
CONF_CUSTOM_QUIRKS_PATH,
|
CONF_CUSTOM_QUIRKS_PATH,
|
||||||
CONF_DEVICE_CONFIG,
|
CONF_DEVICE_CONFIG,
|
||||||
CONF_ENABLE_QUIRKS,
|
CONF_ENABLE_QUIRKS,
|
||||||
|
CONF_FLOW_CONTROL,
|
||||||
CONF_RADIO_TYPE,
|
CONF_RADIO_TYPE,
|
||||||
CONF_USB_PATH,
|
CONF_USB_PATH,
|
||||||
CONF_ZIGPY,
|
CONF_ZIGPY,
|
||||||
@ -36,6 +37,8 @@ from .core.const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
SIGNAL_ADD_ENTITIES,
|
SIGNAL_ADD_ENTITIES,
|
||||||
|
STARTUP_FAILURE_DELAY_S,
|
||||||
|
STARTUP_RETRIES,
|
||||||
RadioType,
|
RadioType,
|
||||||
)
|
)
|
||||||
from .core.device import get_device_automation_triggers
|
from .core.device import get_device_automation_triggers
|
||||||
@ -158,42 +161,67 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
|
|
||||||
_LOGGER.debug("Trigger cache: %s", zha_data.device_trigger_cache)
|
_LOGGER.debug("Trigger cache: %s", zha_data.device_trigger_cache)
|
||||||
|
|
||||||
zha_gateway = ZHAGateway(hass, zha_data.yaml_config, config_entry)
|
# Retry setup a few times before giving up to deal with missing serial ports in VMs
|
||||||
|
for attempt in range(STARTUP_RETRIES):
|
||||||
|
try:
|
||||||
|
zha_gateway = await ZHAGateway.async_from_config(
|
||||||
|
hass=hass,
|
||||||
|
config=zha_data.yaml_config,
|
||||||
|
config_entry=config_entry,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
except NetworkSettingsInconsistent as exc:
|
||||||
|
await warn_on_inconsistent_network_settings(
|
||||||
|
hass,
|
||||||
|
config_entry=config_entry,
|
||||||
|
old_state=exc.old_state,
|
||||||
|
new_state=exc.new_state,
|
||||||
|
)
|
||||||
|
raise ConfigEntryError(
|
||||||
|
"Network settings do not match most recent backup"
|
||||||
|
) from exc
|
||||||
|
except TransientConnectionError as exc:
|
||||||
|
raise ConfigEntryNotReady from exc
|
||||||
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Couldn't start coordinator (attempt %s of %s)",
|
||||||
|
attempt + 1,
|
||||||
|
STARTUP_RETRIES,
|
||||||
|
exc_info=exc,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
if attempt < STARTUP_RETRIES - 1:
|
||||||
await zha_gateway.async_initialize()
|
await asyncio.sleep(STARTUP_FAILURE_DELAY_S)
|
||||||
except NetworkSettingsInconsistent as exc:
|
continue
|
||||||
await warn_on_inconsistent_network_settings(
|
|
||||||
hass,
|
|
||||||
config_entry=config_entry,
|
|
||||||
old_state=exc.old_state,
|
|
||||||
new_state=exc.new_state,
|
|
||||||
)
|
|
||||||
raise HomeAssistantError(
|
|
||||||
"Network settings do not match most recent backup"
|
|
||||||
) from exc
|
|
||||||
except Exception:
|
|
||||||
if RadioType[config_entry.data[CONF_RADIO_TYPE]] == RadioType.ezsp:
|
|
||||||
try:
|
|
||||||
await warn_on_wrong_silabs_firmware(
|
|
||||||
hass, config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
|
|
||||||
)
|
|
||||||
except AlreadyRunningEZSP as exc:
|
|
||||||
# If connecting fails but we somehow probe EZSP (e.g. stuck in the
|
|
||||||
# bootloader), reconnect, it should work
|
|
||||||
raise ConfigEntryNotReady from exc
|
|
||||||
|
|
||||||
raise
|
if RadioType[config_entry.data[CONF_RADIO_TYPE]] == RadioType.ezsp:
|
||||||
|
try:
|
||||||
|
# Ignore all exceptions during probing, they shouldn't halt setup
|
||||||
|
await warn_on_wrong_silabs_firmware(
|
||||||
|
hass, config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
|
||||||
|
)
|
||||||
|
except AlreadyRunningEZSP as ezsp_exc:
|
||||||
|
raise ConfigEntryNotReady from ezsp_exc
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
repairs.async_delete_blocking_issues(hass)
|
repairs.async_delete_blocking_issues(hass)
|
||||||
|
|
||||||
|
manufacturer = zha_gateway.state.node_info.manufacturer
|
||||||
|
model = zha_gateway.state.node_info.model
|
||||||
|
|
||||||
|
if manufacturer is None and model is None:
|
||||||
|
manufacturer = "Unknown"
|
||||||
|
model = "Unknown"
|
||||||
|
|
||||||
device_registry.async_get_or_create(
|
device_registry.async_get_or_create(
|
||||||
config_entry_id=config_entry.entry_id,
|
config_entry_id=config_entry.entry_id,
|
||||||
connections={(dr.CONNECTION_ZIGBEE, str(zha_gateway.coordinator_ieee))},
|
connections={(dr.CONNECTION_ZIGBEE, str(zha_gateway.state.node_info.ieee))},
|
||||||
identifiers={(DOMAIN, str(zha_gateway.coordinator_ieee))},
|
identifiers={(DOMAIN, str(zha_gateway.state.node_info.ieee))},
|
||||||
name="Zigbee Coordinator",
|
name="Zigbee Coordinator",
|
||||||
manufacturer="ZHA",
|
manufacturer=manufacturer,
|
||||||
model=zha_gateway.radio_description,
|
model=model,
|
||||||
|
sw_version=zha_gateway.state.node_info.version,
|
||||||
)
|
)
|
||||||
|
|
||||||
websocket_api.async_load_api(hass)
|
websocket_api.async_load_api(hass)
|
||||||
@ -267,5 +295,23 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||||||
config_entry.version = 3
|
config_entry.version = 3
|
||||||
hass.config_entries.async_update_entry(config_entry, data=data)
|
hass.config_entries.async_update_entry(config_entry, data=data)
|
||||||
|
|
||||||
|
if config_entry.version == 3:
|
||||||
|
data = {**config_entry.data}
|
||||||
|
|
||||||
|
if not data[CONF_DEVICE].get(CONF_BAUDRATE):
|
||||||
|
data[CONF_DEVICE][CONF_BAUDRATE] = {
|
||||||
|
"deconz": 38400,
|
||||||
|
"xbee": 57600,
|
||||||
|
"ezsp": 57600,
|
||||||
|
"znp": 115200,
|
||||||
|
"zigate": 115200,
|
||||||
|
}[data[CONF_RADIO_TYPE]]
|
||||||
|
|
||||||
|
if not data[CONF_DEVICE].get(CONF_FLOW_CONTROL):
|
||||||
|
data[CONF_DEVICE][CONF_FLOW_CONTROL] = None
|
||||||
|
|
||||||
|
config_entry.version = 4
|
||||||
|
hass.config_entries.async_update_entry(config_entry, data=data)
|
||||||
|
|
||||||
_LOGGER.info("Migration to version %s successful", config_entry.version)
|
_LOGGER.info("Migration to version %s successful", config_entry.version)
|
||||||
return True
|
return True
|
||||||
|
@ -27,12 +27,13 @@ from homeassistant.util import dt as dt_util
|
|||||||
|
|
||||||
from .core.const import (
|
from .core.const import (
|
||||||
CONF_BAUDRATE,
|
CONF_BAUDRATE,
|
||||||
CONF_FLOWCONTROL,
|
CONF_FLOW_CONTROL,
|
||||||
CONF_RADIO_TYPE,
|
CONF_RADIO_TYPE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
RadioType,
|
RadioType,
|
||||||
)
|
)
|
||||||
from .radio_manager import (
|
from .radio_manager import (
|
||||||
|
DEVICE_SCHEMA,
|
||||||
HARDWARE_DISCOVERY_SCHEMA,
|
HARDWARE_DISCOVERY_SCHEMA,
|
||||||
RECOMMENDED_RADIOS,
|
RECOMMENDED_RADIOS,
|
||||||
ProbeResult,
|
ProbeResult,
|
||||||
@ -42,7 +43,7 @@ from .radio_manager import (
|
|||||||
CONF_MANUAL_PATH = "Enter Manually"
|
CONF_MANUAL_PATH = "Enter Manually"
|
||||||
SUPPORTED_PORT_SETTINGS = (
|
SUPPORTED_PORT_SETTINGS = (
|
||||||
CONF_BAUDRATE,
|
CONF_BAUDRATE,
|
||||||
CONF_FLOWCONTROL,
|
CONF_FLOW_CONTROL,
|
||||||
)
|
)
|
||||||
DECONZ_DOMAIN = "deconz"
|
DECONZ_DOMAIN = "deconz"
|
||||||
|
|
||||||
@ -160,7 +161,7 @@ class BaseZhaFlow(FlowHandler):
|
|||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=self._title,
|
title=self._title,
|
||||||
data={
|
data={
|
||||||
CONF_DEVICE: device_settings,
|
CONF_DEVICE: DEVICE_SCHEMA(device_settings),
|
||||||
CONF_RADIO_TYPE: self._radio_mgr.radio_type.name,
|
CONF_RADIO_TYPE: self._radio_mgr.radio_type.name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -281,7 +282,7 @@ class BaseZhaFlow(FlowHandler):
|
|||||||
for (
|
for (
|
||||||
param,
|
param,
|
||||||
value,
|
value,
|
||||||
) in self._radio_mgr.radio_type.controller.SCHEMA_DEVICE.schema.items():
|
) in DEVICE_SCHEMA.schema.items():
|
||||||
if param not in SUPPORTED_PORT_SETTINGS:
|
if param not in SUPPORTED_PORT_SETTINGS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -488,7 +489,7 @@ class BaseZhaFlow(FlowHandler):
|
|||||||
class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN):
|
class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow."""
|
"""Handle a config flow."""
|
||||||
|
|
||||||
VERSION = 3
|
VERSION = 4
|
||||||
|
|
||||||
async def _set_unique_id_or_update_path(
|
async def _set_unique_id_or_update_path(
|
||||||
self, unique_id: str, device_path: str
|
self, unique_id: str, device_path: str
|
||||||
@ -646,22 +647,17 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN
|
|||||||
|
|
||||||
name = discovery_data["name"]
|
name = discovery_data["name"]
|
||||||
radio_type = self._radio_mgr.parse_radio_type(discovery_data["radio_type"])
|
radio_type = self._radio_mgr.parse_radio_type(discovery_data["radio_type"])
|
||||||
|
device_settings = discovery_data["port"]
|
||||||
try:
|
device_path = device_settings[CONF_DEVICE_PATH]
|
||||||
device_settings = radio_type.controller.SCHEMA_DEVICE(
|
|
||||||
discovery_data["port"]
|
|
||||||
)
|
|
||||||
except vol.Invalid:
|
|
||||||
return self.async_abort(reason="invalid_hardware_data")
|
|
||||||
|
|
||||||
await self._set_unique_id_or_update_path(
|
await self._set_unique_id_or_update_path(
|
||||||
unique_id=f"{name}_{radio_type.name}_{device_settings[CONF_DEVICE_PATH]}",
|
unique_id=f"{name}_{radio_type.name}_{device_path}",
|
||||||
device_path=device_settings[CONF_DEVICE_PATH],
|
device_path=device_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._title = name
|
self._title = name
|
||||||
self._radio_mgr.radio_type = radio_type
|
self._radio_mgr.radio_type = radio_type
|
||||||
self._radio_mgr.device_path = device_settings[CONF_DEVICE_PATH]
|
self._radio_mgr.device_path = device_path
|
||||||
self._radio_mgr.device_settings = device_settings
|
self._radio_mgr.device_settings = device_settings
|
||||||
self.context["title_placeholders"] = {CONF_NAME: name}
|
self.context["title_placeholders"] = {CONF_NAME: name}
|
||||||
|
|
||||||
|
@ -127,6 +127,7 @@ CONF_ALARM_FAILED_TRIES = "alarm_failed_tries"
|
|||||||
CONF_ALARM_ARM_REQUIRES_CODE = "alarm_arm_requires_code"
|
CONF_ALARM_ARM_REQUIRES_CODE = "alarm_arm_requires_code"
|
||||||
|
|
||||||
CONF_BAUDRATE = "baudrate"
|
CONF_BAUDRATE = "baudrate"
|
||||||
|
CONF_FLOW_CONTROL = "flow_control"
|
||||||
CONF_CUSTOM_QUIRKS_PATH = "custom_quirks_path"
|
CONF_CUSTOM_QUIRKS_PATH = "custom_quirks_path"
|
||||||
CONF_DEFAULT_LIGHT_TRANSITION = "default_light_transition"
|
CONF_DEFAULT_LIGHT_TRANSITION = "default_light_transition"
|
||||||
CONF_DEVICE_CONFIG = "device_config"
|
CONF_DEVICE_CONFIG = "device_config"
|
||||||
@ -136,7 +137,6 @@ CONF_ALWAYS_PREFER_XY_COLOR_MODE = "always_prefer_xy_color_mode"
|
|||||||
CONF_GROUP_MEMBERS_ASSUME_STATE = "group_members_assume_state"
|
CONF_GROUP_MEMBERS_ASSUME_STATE = "group_members_assume_state"
|
||||||
CONF_ENABLE_IDENTIFY_ON_JOIN = "enable_identify_on_join"
|
CONF_ENABLE_IDENTIFY_ON_JOIN = "enable_identify_on_join"
|
||||||
CONF_ENABLE_QUIRKS = "enable_quirks"
|
CONF_ENABLE_QUIRKS = "enable_quirks"
|
||||||
CONF_FLOWCONTROL = "flow_control"
|
|
||||||
CONF_RADIO_TYPE = "radio_type"
|
CONF_RADIO_TYPE = "radio_type"
|
||||||
CONF_USB_PATH = "usb_path"
|
CONF_USB_PATH = "usb_path"
|
||||||
CONF_USE_THREAD = "use_thread"
|
CONF_USE_THREAD = "use_thread"
|
||||||
|
@ -285,7 +285,7 @@ class ZHADevice(LogMixin):
|
|||||||
if not self.is_coordinator:
|
if not self.is_coordinator:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.ieee == self.gateway.coordinator_ieee
|
return self.ieee == self.gateway.state.node_info.ieee
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_end_device(self) -> bool | None:
|
def is_end_device(self) -> bool | None:
|
||||||
|
@ -11,7 +11,7 @@ import itertools
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Any, NamedTuple
|
from typing import TYPE_CHECKING, Any, NamedTuple, Self
|
||||||
|
|
||||||
from zigpy.application import ControllerApplication
|
from zigpy.application import ControllerApplication
|
||||||
from zigpy.config import (
|
from zigpy.config import (
|
||||||
@ -24,15 +24,14 @@ from zigpy.config import (
|
|||||||
)
|
)
|
||||||
import zigpy.device
|
import zigpy.device
|
||||||
import zigpy.endpoint
|
import zigpy.endpoint
|
||||||
from zigpy.exceptions import NetworkSettingsInconsistent, TransientConnectionError
|
|
||||||
import zigpy.group
|
import zigpy.group
|
||||||
|
from zigpy.state import State
|
||||||
from zigpy.types.named import EUI64
|
from zigpy.types.named import EUI64
|
||||||
|
|
||||||
from homeassistant import __path__ as HOMEASSISTANT_PATH
|
from homeassistant import __path__ as HOMEASSISTANT_PATH
|
||||||
from homeassistant.components.system_log import LogEntry, _figure_out_source
|
from homeassistant.components.system_log import LogEntry, _figure_out_source
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
@ -66,8 +65,6 @@ from .const import (
|
|||||||
SIGNAL_ADD_ENTITIES,
|
SIGNAL_ADD_ENTITIES,
|
||||||
SIGNAL_GROUP_MEMBERSHIP_CHANGE,
|
SIGNAL_GROUP_MEMBERSHIP_CHANGE,
|
||||||
SIGNAL_REMOVE,
|
SIGNAL_REMOVE,
|
||||||
STARTUP_FAILURE_DELAY_S,
|
|
||||||
STARTUP_RETRIES,
|
|
||||||
UNKNOWN_MANUFACTURER,
|
UNKNOWN_MANUFACTURER,
|
||||||
UNKNOWN_MODEL,
|
UNKNOWN_MODEL,
|
||||||
ZHA_GW_MSG,
|
ZHA_GW_MSG,
|
||||||
@ -123,10 +120,6 @@ class DevicePairingStatus(Enum):
|
|||||||
class ZHAGateway:
|
class ZHAGateway:
|
||||||
"""Gateway that handles events that happen on the ZHA Zigbee network."""
|
"""Gateway that handles events that happen on the ZHA Zigbee network."""
|
||||||
|
|
||||||
# -- Set in async_initialize --
|
|
||||||
application_controller: ControllerApplication
|
|
||||||
radio_description: str
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, hass: HomeAssistant, config: ConfigType, config_entry: ConfigEntry
|
self, hass: HomeAssistant, config: ConfigType, config_entry: ConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -135,7 +128,8 @@ class ZHAGateway:
|
|||||||
self._config = config
|
self._config = config
|
||||||
self._devices: dict[EUI64, ZHADevice] = {}
|
self._devices: dict[EUI64, ZHADevice] = {}
|
||||||
self._groups: dict[int, ZHAGroup] = {}
|
self._groups: dict[int, ZHAGroup] = {}
|
||||||
self.coordinator_zha_device: ZHADevice | None = None
|
self.application_controller: ControllerApplication = None
|
||||||
|
self.coordinator_zha_device: ZHADevice = None # type: ignore[assignment]
|
||||||
self._device_registry: collections.defaultdict[
|
self._device_registry: collections.defaultdict[
|
||||||
EUI64, list[EntityReference]
|
EUI64, list[EntityReference]
|
||||||
] = collections.defaultdict(list)
|
] = collections.defaultdict(list)
|
||||||
@ -147,13 +141,11 @@ class ZHAGateway:
|
|||||||
self._log_relay_handler = LogRelayHandler(hass, self)
|
self._log_relay_handler = LogRelayHandler(hass, self)
|
||||||
self.config_entry = config_entry
|
self.config_entry = config_entry
|
||||||
self._unsubs: list[Callable[[], None]] = []
|
self._unsubs: list[Callable[[], None]] = []
|
||||||
|
self.shutting_down = False
|
||||||
|
|
||||||
def get_application_controller_data(self) -> tuple[ControllerApplication, dict]:
|
def get_application_controller_data(self) -> tuple[ControllerApplication, dict]:
|
||||||
"""Get an uninitialized instance of a zigpy `ControllerApplication`."""
|
"""Get an uninitialized instance of a zigpy `ControllerApplication`."""
|
||||||
radio_type = self.config_entry.data[CONF_RADIO_TYPE]
|
radio_type = RadioType[self.config_entry.data[CONF_RADIO_TYPE]]
|
||||||
|
|
||||||
app_controller_cls = RadioType[radio_type].controller
|
|
||||||
self.radio_description = RadioType[radio_type].description
|
|
||||||
|
|
||||||
app_config = self._config.get(CONF_ZIGPY, {})
|
app_config = self._config.get(CONF_ZIGPY, {})
|
||||||
database = self._config.get(
|
database = self._config.get(
|
||||||
@ -170,7 +162,7 @@ class ZHAGateway:
|
|||||||
# event loop, when a connection to a TCP coordinator fails in a specific way
|
# event loop, when a connection to a TCP coordinator fails in a specific way
|
||||||
if (
|
if (
|
||||||
CONF_USE_THREAD not in app_config
|
CONF_USE_THREAD not in app_config
|
||||||
and RadioType[radio_type] is RadioType.ezsp
|
and radio_type is RadioType.ezsp
|
||||||
and app_config[CONF_DEVICE][CONF_DEVICE_PATH].startswith("socket://")
|
and app_config[CONF_DEVICE][CONF_DEVICE_PATH].startswith("socket://")
|
||||||
):
|
):
|
||||||
app_config[CONF_USE_THREAD] = False
|
app_config[CONF_USE_THREAD] = False
|
||||||
@ -189,48 +181,40 @@ class ZHAGateway:
|
|||||||
):
|
):
|
||||||
app_config.setdefault(CONF_NWK, {})[CONF_NWK_CHANNEL] = 15
|
app_config.setdefault(CONF_NWK, {})[CONF_NWK_CHANNEL] = 15
|
||||||
|
|
||||||
return app_controller_cls, app_controller_cls.SCHEMA(app_config)
|
return radio_type.controller, radio_type.controller.SCHEMA(app_config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def async_from_config(
|
||||||
|
cls, hass: HomeAssistant, config: ConfigType, config_entry: ConfigEntry
|
||||||
|
) -> Self:
|
||||||
|
"""Create an instance of a gateway from config objects."""
|
||||||
|
instance = cls(hass, config, config_entry)
|
||||||
|
await instance.async_initialize()
|
||||||
|
return instance
|
||||||
|
|
||||||
async def async_initialize(self) -> None:
|
async def async_initialize(self) -> None:
|
||||||
"""Initialize controller and connect radio."""
|
"""Initialize controller and connect radio."""
|
||||||
discovery.PROBE.initialize(self.hass)
|
discovery.PROBE.initialize(self.hass)
|
||||||
discovery.GROUP_PROBE.initialize(self.hass)
|
discovery.GROUP_PROBE.initialize(self.hass)
|
||||||
|
|
||||||
|
self.shutting_down = False
|
||||||
|
|
||||||
app_controller_cls, app_config = self.get_application_controller_data()
|
app_controller_cls, app_config = self.get_application_controller_data()
|
||||||
self.application_controller = await app_controller_cls.new(
|
app = await app_controller_cls.new(
|
||||||
config=app_config,
|
config=app_config,
|
||||||
auto_form=False,
|
auto_form=False,
|
||||||
start_radio=False,
|
start_radio=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for attempt in range(STARTUP_RETRIES):
|
await app.startup(auto_form=True)
|
||||||
try:
|
|
||||||
await self.application_controller.startup(auto_form=True)
|
|
||||||
except TransientConnectionError as exc:
|
|
||||||
raise ConfigEntryNotReady from exc
|
|
||||||
except NetworkSettingsInconsistent:
|
|
||||||
raise
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Couldn't start %s coordinator (attempt %s of %s)",
|
|
||||||
self.radio_description,
|
|
||||||
attempt + 1,
|
|
||||||
STARTUP_RETRIES,
|
|
||||||
exc_info=exc,
|
|
||||||
)
|
|
||||||
|
|
||||||
if attempt == STARTUP_RETRIES - 1:
|
|
||||||
raise exc
|
|
||||||
|
|
||||||
await asyncio.sleep(STARTUP_FAILURE_DELAY_S)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# Explicitly shut down the controller application on failure
|
# Explicitly shut down the controller application on failure
|
||||||
await self.application_controller.shutdown()
|
await app.shutdown()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
self.application_controller = app
|
||||||
|
|
||||||
zha_data = get_zha_data(self.hass)
|
zha_data = get_zha_data(self.hass)
|
||||||
zha_data.gateway = self
|
zha_data.gateway = self
|
||||||
|
|
||||||
@ -244,6 +228,17 @@ class ZHAGateway:
|
|||||||
self.application_controller.add_listener(self)
|
self.application_controller.add_listener(self)
|
||||||
self.application_controller.groups.add_listener(self)
|
self.application_controller.groups.add_listener(self)
|
||||||
|
|
||||||
|
def connection_lost(self, exc: Exception) -> None:
|
||||||
|
"""Handle connection lost event."""
|
||||||
|
if self.shutting_down:
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.debug("Connection to the radio was lost: %r", exc)
|
||||||
|
|
||||||
|
self.hass.async_create_task(
|
||||||
|
self.hass.config_entries.async_reload(self.config_entry.entry_id)
|
||||||
|
)
|
||||||
|
|
||||||
def _find_coordinator_device(self) -> zigpy.device.Device:
|
def _find_coordinator_device(self) -> zigpy.device.Device:
|
||||||
zigpy_coordinator = self.application_controller.get_device(nwk=0x0000)
|
zigpy_coordinator = self.application_controller.get_device(nwk=0x0000)
|
||||||
|
|
||||||
@ -258,6 +253,7 @@ class ZHAGateway:
|
|||||||
@callback
|
@callback
|
||||||
def async_load_devices(self) -> None:
|
def async_load_devices(self) -> None:
|
||||||
"""Restore ZHA devices from zigpy application state."""
|
"""Restore ZHA devices from zigpy application state."""
|
||||||
|
|
||||||
for zigpy_device in self.application_controller.devices.values():
|
for zigpy_device in self.application_controller.devices.values():
|
||||||
zha_device = self._async_get_or_create_device(zigpy_device, restored=True)
|
zha_device = self._async_get_or_create_device(zigpy_device, restored=True)
|
||||||
delta_msg = "not known"
|
delta_msg = "not known"
|
||||||
@ -280,6 +276,7 @@ class ZHAGateway:
|
|||||||
@callback
|
@callback
|
||||||
def async_load_groups(self) -> None:
|
def async_load_groups(self) -> None:
|
||||||
"""Initialize ZHA groups."""
|
"""Initialize ZHA groups."""
|
||||||
|
|
||||||
for group_id in self.application_controller.groups:
|
for group_id in self.application_controller.groups:
|
||||||
group = self.application_controller.groups[group_id]
|
group = self.application_controller.groups[group_id]
|
||||||
zha_group = self._async_get_or_create_group(group)
|
zha_group = self._async_get_or_create_group(group)
|
||||||
@ -521,9 +518,9 @@ class ZHAGateway:
|
|||||||
entity_registry.async_remove(entry.entity_id)
|
entity_registry.async_remove(entry.entity_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coordinator_ieee(self) -> EUI64:
|
def state(self) -> State:
|
||||||
"""Return the active coordinator's IEEE address."""
|
"""Return the active coordinator's network state."""
|
||||||
return self.application_controller.state.node_info.ieee
|
return self.application_controller.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self) -> dict[EUI64, ZHADevice]:
|
def devices(self) -> dict[EUI64, ZHADevice]:
|
||||||
@ -711,6 +708,7 @@ class ZHAGateway:
|
|||||||
group_id: int | None = None,
|
group_id: int | None = None,
|
||||||
) -> ZHAGroup | None:
|
) -> ZHAGroup | None:
|
||||||
"""Create a new Zigpy Zigbee group."""
|
"""Create a new Zigpy Zigbee group."""
|
||||||
|
|
||||||
# we start with two to fill any gaps from a user removing existing groups
|
# we start with two to fill any gaps from a user removing existing groups
|
||||||
|
|
||||||
if group_id is None:
|
if group_id is None:
|
||||||
@ -758,19 +756,13 @@ class ZHAGateway:
|
|||||||
async def shutdown(self) -> None:
|
async def shutdown(self) -> None:
|
||||||
"""Stop ZHA Controller Application."""
|
"""Stop ZHA Controller Application."""
|
||||||
_LOGGER.debug("Shutting down ZHA ControllerApplication")
|
_LOGGER.debug("Shutting down ZHA ControllerApplication")
|
||||||
|
self.shutting_down = True
|
||||||
|
|
||||||
for unsubscribe in self._unsubs:
|
for unsubscribe in self._unsubs:
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
for device in self.devices.values():
|
for device in self.devices.values():
|
||||||
device.async_cleanup_handles()
|
device.async_cleanup_handles()
|
||||||
# shutdown is called when the config entry unloads are processed
|
await self.application_controller.shutdown()
|
||||||
# there are cases where unloads are processed because of a failure of
|
|
||||||
# some sort and the application controller may not have been
|
|
||||||
# created yet
|
|
||||||
if (
|
|
||||||
hasattr(self, "application_controller")
|
|
||||||
and self.application_controller is not None
|
|
||||||
):
|
|
||||||
await self.application_controller.shutdown()
|
|
||||||
|
|
||||||
def handle_message(
|
def handle_message(
|
||||||
self,
|
self,
|
||||||
|
@ -92,7 +92,7 @@ class BaseZhaEntity(LogMixin, entity.Entity):
|
|||||||
manufacturer=zha_device_info[ATTR_MANUFACTURER],
|
manufacturer=zha_device_info[ATTR_MANUFACTURER],
|
||||||
model=zha_device_info[ATTR_MODEL],
|
model=zha_device_info[ATTR_MODEL],
|
||||||
name=zha_device_info[ATTR_NAME],
|
name=zha_device_info[ATTR_NAME],
|
||||||
via_device=(DOMAIN, zha_gateway.coordinator_ieee),
|
via_device=(DOMAIN, zha_gateway.state.node_info.ieee),
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -21,16 +21,16 @@
|
|||||||
"universal_silabs_flasher"
|
"universal_silabs_flasher"
|
||||||
],
|
],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"bellows==0.36.8",
|
"bellows==0.37.1",
|
||||||
"pyserial==3.5",
|
"pyserial==3.5",
|
||||||
"pyserial-asyncio==0.6",
|
"pyserial-asyncio==0.6",
|
||||||
"zha-quirks==0.0.107",
|
"zha-quirks==0.0.107",
|
||||||
"zigpy-deconz==0.21.1",
|
"zigpy-deconz==0.22.0",
|
||||||
"zigpy==0.59.0",
|
"zigpy==0.60.0",
|
||||||
"zigpy-xbee==0.19.0",
|
"zigpy-xbee==0.20.0",
|
||||||
"zigpy-zigate==0.11.0",
|
"zigpy-zigate==0.12.0",
|
||||||
"zigpy-znp==0.11.6",
|
"zigpy-znp==0.12.0",
|
||||||
"universal-silabs-flasher==0.0.14",
|
"universal-silabs-flasher==0.0.15",
|
||||||
"pyserial-asyncio-fast==0.11"
|
"pyserial-asyncio-fast==0.11"
|
||||||
],
|
],
|
||||||
"usb": [
|
"usb": [
|
||||||
|
@ -19,6 +19,7 @@ from zigpy.config import (
|
|||||||
CONF_DEVICE,
|
CONF_DEVICE,
|
||||||
CONF_DEVICE_PATH,
|
CONF_DEVICE_PATH,
|
||||||
CONF_NWK_BACKUP_ENABLED,
|
CONF_NWK_BACKUP_ENABLED,
|
||||||
|
SCHEMA_DEVICE,
|
||||||
)
|
)
|
||||||
from zigpy.exceptions import NetworkNotFormed
|
from zigpy.exceptions import NetworkNotFormed
|
||||||
|
|
||||||
@ -58,10 +59,21 @@ RETRY_DELAY_S = 1.0
|
|||||||
BACKUP_RETRIES = 5
|
BACKUP_RETRIES = 5
|
||||||
MIGRATION_RETRIES = 100
|
MIGRATION_RETRIES = 100
|
||||||
|
|
||||||
|
|
||||||
|
DEVICE_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("path"): str,
|
||||||
|
vol.Optional("baudrate", default=115200): int,
|
||||||
|
vol.Optional("flow_control", default=None): vol.In(
|
||||||
|
["hardware", "software", None]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
HARDWARE_DISCOVERY_SCHEMA = vol.Schema(
|
HARDWARE_DISCOVERY_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("name"): str,
|
vol.Required("name"): str,
|
||||||
vol.Required("port"): dict,
|
vol.Required("port"): DEVICE_SCHEMA,
|
||||||
vol.Required("radio_type"): str,
|
vol.Required("radio_type"): str,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -204,9 +216,7 @@ class ZhaRadioManager:
|
|||||||
for radio in AUTOPROBE_RADIOS:
|
for radio in AUTOPROBE_RADIOS:
|
||||||
_LOGGER.debug("Attempting to probe radio type %s", radio)
|
_LOGGER.debug("Attempting to probe radio type %s", radio)
|
||||||
|
|
||||||
dev_config = radio.controller.SCHEMA_DEVICE(
|
dev_config = SCHEMA_DEVICE({CONF_DEVICE_PATH: self.device_path})
|
||||||
{CONF_DEVICE_PATH: self.device_path}
|
|
||||||
)
|
|
||||||
probe_result = await radio.controller.probe(dev_config)
|
probe_result = await radio.controller.probe(dev_config)
|
||||||
|
|
||||||
if not probe_result:
|
if not probe_result:
|
||||||
@ -357,7 +367,7 @@ class ZhaMultiPANMigrationHelper:
|
|||||||
migration_data["new_discovery_info"]["radio_type"]
|
migration_data["new_discovery_info"]["radio_type"]
|
||||||
)
|
)
|
||||||
|
|
||||||
new_device_settings = new_radio_type.controller.SCHEMA_DEVICE(
|
new_device_settings = SCHEMA_DEVICE(
|
||||||
migration_data["new_discovery_info"]["port"]
|
migration_data["new_discovery_info"]["port"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -523,7 +523,7 @@ beautifulsoup4==4.12.2
|
|||||||
# beewi-smartclim==0.0.10
|
# beewi-smartclim==0.0.10
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
bellows==0.36.8
|
bellows==0.37.1
|
||||||
|
|
||||||
# homeassistant.components.bmw_connected_drive
|
# homeassistant.components.bmw_connected_drive
|
||||||
bimmer-connected==0.14.3
|
bimmer-connected==0.14.3
|
||||||
@ -2660,7 +2660,7 @@ unifi-discovery==1.1.7
|
|||||||
unifiled==0.11
|
unifiled==0.11
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
universal-silabs-flasher==0.0.14
|
universal-silabs-flasher==0.0.15
|
||||||
|
|
||||||
# homeassistant.components.upb
|
# homeassistant.components.upb
|
||||||
upb-lib==0.5.4
|
upb-lib==0.5.4
|
||||||
@ -2828,19 +2828,19 @@ zhong-hong-hvac==1.0.9
|
|||||||
ziggo-mediabox-xl==1.1.0
|
ziggo-mediabox-xl==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-deconz==0.21.1
|
zigpy-deconz==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-xbee==0.19.0
|
zigpy-xbee==0.20.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-zigate==0.11.0
|
zigpy-zigate==0.12.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-znp==0.11.6
|
zigpy-znp==0.12.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.59.0
|
zigpy==0.60.0
|
||||||
|
|
||||||
# homeassistant.components.zoneminder
|
# homeassistant.components.zoneminder
|
||||||
zm-py==0.5.2
|
zm-py==0.5.2
|
||||||
|
@ -445,7 +445,7 @@ base36==0.1.1
|
|||||||
beautifulsoup4==4.12.2
|
beautifulsoup4==4.12.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
bellows==0.36.8
|
bellows==0.37.1
|
||||||
|
|
||||||
# homeassistant.components.bmw_connected_drive
|
# homeassistant.components.bmw_connected_drive
|
||||||
bimmer-connected==0.14.3
|
bimmer-connected==0.14.3
|
||||||
@ -1979,7 +1979,7 @@ ultraheat-api==0.5.7
|
|||||||
unifi-discovery==1.1.7
|
unifi-discovery==1.1.7
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
universal-silabs-flasher==0.0.14
|
universal-silabs-flasher==0.0.15
|
||||||
|
|
||||||
# homeassistant.components.upb
|
# homeassistant.components.upb
|
||||||
upb-lib==0.5.4
|
upb-lib==0.5.4
|
||||||
@ -2117,19 +2117,19 @@ zeversolar==0.3.1
|
|||||||
zha-quirks==0.0.107
|
zha-quirks==0.0.107
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-deconz==0.21.1
|
zigpy-deconz==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-xbee==0.19.0
|
zigpy-xbee==0.20.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-zigate==0.11.0
|
zigpy-zigate==0.12.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-znp==0.11.6
|
zigpy-znp==0.12.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.59.0
|
zigpy==0.60.0
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.54.0
|
zwave-js-server-python==0.54.0
|
||||||
|
@ -293,7 +293,14 @@ async def test_option_flow_install_multi_pan_addon_zha(
|
|||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
zha_config_entry = MockConfigEntry(
|
zha_config_entry = MockConfigEntry(
|
||||||
data={"device": {"path": "/dev/ttyTEST123"}, "radio_type": "ezsp"},
|
data={
|
||||||
|
"device": {
|
||||||
|
"path": "/dev/ttyTEST123",
|
||||||
|
"baudrate": 115200,
|
||||||
|
"flow_control": None,
|
||||||
|
},
|
||||||
|
"radio_type": "ezsp",
|
||||||
|
},
|
||||||
domain=ZHA_DOMAIN,
|
domain=ZHA_DOMAIN,
|
||||||
options={},
|
options={},
|
||||||
title="Test",
|
title="Test",
|
||||||
@ -348,8 +355,8 @@ async def test_option_flow_install_multi_pan_addon_zha(
|
|||||||
assert zha_config_entry.data == {
|
assert zha_config_entry.data == {
|
||||||
"device": {
|
"device": {
|
||||||
"path": "socket://core-silabs-multiprotocol:9999",
|
"path": "socket://core-silabs-multiprotocol:9999",
|
||||||
"baudrate": 57600, # ZHA default
|
"baudrate": 115200,
|
||||||
"flow_control": "software", # ZHA default
|
"flow_control": None,
|
||||||
},
|
},
|
||||||
"radio_type": "ezsp",
|
"radio_type": "ezsp",
|
||||||
}
|
}
|
||||||
|
@ -337,8 +337,8 @@ async def test_option_flow_install_multi_pan_addon_zha(
|
|||||||
assert zha_config_entry.data == {
|
assert zha_config_entry.data == {
|
||||||
"device": {
|
"device": {
|
||||||
"path": "socket://core-silabs-multiprotocol:9999",
|
"path": "socket://core-silabs-multiprotocol:9999",
|
||||||
"baudrate": 57600, # ZHA default
|
"baudrate": 115200,
|
||||||
"flow_control": "software", # ZHA default
|
"flow_control": None,
|
||||||
},
|
},
|
||||||
"radio_type": "ezsp",
|
"radio_type": "ezsp",
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ async def test_setup_zha(
|
|||||||
assert config_entry.data == {
|
assert config_entry.data == {
|
||||||
"device": {
|
"device": {
|
||||||
"baudrate": 115200,
|
"baudrate": 115200,
|
||||||
"flow_control": "software",
|
"flow_control": None,
|
||||||
"path": CONFIG_ENTRY_DATA["device"],
|
"path": CONFIG_ENTRY_DATA["device"],
|
||||||
},
|
},
|
||||||
"radio_type": "ezsp",
|
"radio_type": "ezsp",
|
||||||
@ -200,8 +200,8 @@ async def test_setup_zha_multipan(
|
|||||||
config_entry = hass.config_entries.async_entries("zha")[0]
|
config_entry = hass.config_entries.async_entries("zha")[0]
|
||||||
assert config_entry.data == {
|
assert config_entry.data == {
|
||||||
"device": {
|
"device": {
|
||||||
"baudrate": 57600, # ZHA default
|
"baudrate": 115200,
|
||||||
"flow_control": "software", # ZHA default
|
"flow_control": None,
|
||||||
"path": "socket://core-silabs-multiprotocol:9999",
|
"path": "socket://core-silabs-multiprotocol:9999",
|
||||||
},
|
},
|
||||||
"radio_type": "ezsp",
|
"radio_type": "ezsp",
|
||||||
@ -255,7 +255,7 @@ async def test_setup_zha_multipan_other_device(
|
|||||||
assert config_entry.data == {
|
assert config_entry.data == {
|
||||||
"device": {
|
"device": {
|
||||||
"baudrate": 115200,
|
"baudrate": 115200,
|
||||||
"flow_control": "software",
|
"flow_control": None,
|
||||||
"path": CONFIG_ENTRY_DATA["device"],
|
"path": CONFIG_ENTRY_DATA["device"],
|
||||||
},
|
},
|
||||||
"radio_type": "ezsp",
|
"radio_type": "ezsp",
|
||||||
|
@ -249,8 +249,8 @@ async def test_option_flow_install_multi_pan_addon_zha(
|
|||||||
assert zha_config_entry.data == {
|
assert zha_config_entry.data == {
|
||||||
"device": {
|
"device": {
|
||||||
"path": "socket://core-silabs-multiprotocol:9999",
|
"path": "socket://core-silabs-multiprotocol:9999",
|
||||||
"baudrate": 57600, # ZHA default
|
"baudrate": 115200,
|
||||||
"flow_control": "software", # ZHA default
|
"flow_control": None,
|
||||||
},
|
},
|
||||||
"radio_type": "ezsp",
|
"radio_type": "ezsp",
|
||||||
}
|
}
|
||||||
|
@ -145,8 +145,8 @@ async def test_setup_zha_multipan(
|
|||||||
config_entry = hass.config_entries.async_entries("zha")[0]
|
config_entry = hass.config_entries.async_entries("zha")[0]
|
||||||
assert config_entry.data == {
|
assert config_entry.data == {
|
||||||
"device": {
|
"device": {
|
||||||
"baudrate": 57600, # ZHA default
|
"baudrate": 115200,
|
||||||
"flow_control": "software", # ZHA default
|
"flow_control": None,
|
||||||
"path": "socket://core-silabs-multiprotocol:9999",
|
"path": "socket://core-silabs-multiprotocol:9999",
|
||||||
},
|
},
|
||||||
"radio_type": "ezsp",
|
"radio_type": "ezsp",
|
||||||
|
@ -46,7 +46,7 @@ def disable_request_retry_delay():
|
|||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.zha.core.cluster_handlers.RETRYABLE_REQUEST_DECORATOR",
|
"homeassistant.components.zha.core.cluster_handlers.RETRYABLE_REQUEST_DECORATOR",
|
||||||
zigpy.util.retryable_request(tries=3, delay=0),
|
zigpy.util.retryable_request(tries=3, delay=0),
|
||||||
):
|
), patch("homeassistant.components.zha.STARTUP_FAILURE_DELAY_S", 0.01):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
@ -83,8 +83,8 @@ class _FakeApp(ControllerApplication):
|
|||||||
async def permit_ncp(self, time_s: int = 60):
|
async def permit_ncp(self, time_s: int = 60):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def permit_with_key(
|
async def permit_with_link_key(
|
||||||
self, node: zigpy.types.EUI64, code: bytes, time_s: int = 60
|
self, node: zigpy.types.EUI64, link_key: zigpy.types.KeyData, time_s: int = 60
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import pytest
|
|||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
from zigpy.backups import BackupManager
|
from zigpy.backups import BackupManager
|
||||||
import zigpy.config
|
import zigpy.config
|
||||||
from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH
|
from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH, SCHEMA_DEVICE
|
||||||
import zigpy.device
|
import zigpy.device
|
||||||
from zigpy.exceptions import NetworkNotFormed
|
from zigpy.exceptions import NetworkNotFormed
|
||||||
import zigpy.types
|
import zigpy.types
|
||||||
@ -22,7 +22,7 @@ from homeassistant.components.ssdp import ATTR_UPNP_MANUFACTURER_URL, ATTR_UPNP_
|
|||||||
from homeassistant.components.zha import config_flow, radio_manager
|
from homeassistant.components.zha import config_flow, radio_manager
|
||||||
from homeassistant.components.zha.core.const import (
|
from homeassistant.components.zha.core.const import (
|
||||||
CONF_BAUDRATE,
|
CONF_BAUDRATE,
|
||||||
CONF_FLOWCONTROL,
|
CONF_FLOW_CONTROL,
|
||||||
CONF_RADIO_TYPE,
|
CONF_RADIO_TYPE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EZSP_OVERWRITE_EUI64,
|
EZSP_OVERWRITE_EUI64,
|
||||||
@ -118,9 +118,7 @@ def mock_detect_radio_type(
|
|||||||
|
|
||||||
async def detect(self):
|
async def detect(self):
|
||||||
self.radio_type = radio_type
|
self.radio_type = radio_type
|
||||||
self.device_settings = radio_type.controller.SCHEMA_DEVICE(
|
self.device_settings = SCHEMA_DEVICE({CONF_DEVICE_PATH: self.device_path})
|
||||||
{CONF_DEVICE_PATH: self.device_path}
|
|
||||||
)
|
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -181,7 +179,7 @@ async def test_zeroconf_discovery_znp(hass: HomeAssistant) -> None:
|
|||||||
assert result3["data"] == {
|
assert result3["data"] == {
|
||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
CONF_DEVICE_PATH: "socket://192.168.1.200:6638",
|
CONF_DEVICE_PATH: "socket://192.168.1.200:6638",
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "znp",
|
CONF_RADIO_TYPE: "znp",
|
||||||
@ -238,6 +236,8 @@ async def test_zigate_via_zeroconf(setup_entry_mock, hass: HomeAssistant) -> Non
|
|||||||
assert result4["data"] == {
|
assert result4["data"] == {
|
||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "socket://192.168.1.200:1234",
|
CONF_DEVICE_PATH: "socket://192.168.1.200:1234",
|
||||||
|
CONF_BAUDRATE: 115200,
|
||||||
|
CONF_FLOW_CONTROL: None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "zigate",
|
CONF_RADIO_TYPE: "zigate",
|
||||||
}
|
}
|
||||||
@ -287,7 +287,7 @@ async def test_efr32_via_zeroconf(hass: HomeAssistant) -> None:
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "socket://192.168.1.200:1234",
|
CONF_DEVICE_PATH: "socket://192.168.1.200:1234",
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOWCONTROL: "software",
|
CONF_FLOW_CONTROL: None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "ezsp",
|
CONF_RADIO_TYPE: "ezsp",
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ async def test_discovery_via_zeroconf_ip_change(hass: HomeAssistant) -> None:
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "socket://192.168.1.5:6638",
|
CONF_DEVICE_PATH: "socket://192.168.1.5:6638",
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -328,7 +328,7 @@ async def test_discovery_via_zeroconf_ip_change(hass: HomeAssistant) -> None:
|
|||||||
assert entry.data[CONF_DEVICE] == {
|
assert entry.data[CONF_DEVICE] == {
|
||||||
CONF_DEVICE_PATH: "socket://192.168.1.22:6638",
|
CONF_DEVICE_PATH: "socket://192.168.1.22:6638",
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -483,6 +483,8 @@ async def test_zigate_discovery_via_usb(probe_mock, hass: HomeAssistant) -> None
|
|||||||
assert result4["data"] == {
|
assert result4["data"] == {
|
||||||
"device": {
|
"device": {
|
||||||
"path": "/dev/ttyZIGBEE",
|
"path": "/dev/ttyZIGBEE",
|
||||||
|
"baudrate": 115200,
|
||||||
|
"flow_control": None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "zigate",
|
CONF_RADIO_TYPE: "zigate",
|
||||||
}
|
}
|
||||||
@ -555,7 +557,7 @@ async def test_discovery_via_usb_path_changes(hass: HomeAssistant) -> None:
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "/dev/ttyUSB1",
|
CONF_DEVICE_PATH: "/dev/ttyUSB1",
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -579,7 +581,7 @@ async def test_discovery_via_usb_path_changes(hass: HomeAssistant) -> None:
|
|||||||
assert entry.data[CONF_DEVICE] == {
|
assert entry.data[CONF_DEVICE] == {
|
||||||
CONF_DEVICE_PATH: "/dev/ttyZIGBEE",
|
CONF_DEVICE_PATH: "/dev/ttyZIGBEE",
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -754,6 +756,8 @@ async def test_user_flow(hass: HomeAssistant) -> None:
|
|||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
"device": {
|
"device": {
|
||||||
"path": port.device,
|
"path": port.device,
|
||||||
|
CONF_BAUDRATE: 115200,
|
||||||
|
CONF_FLOW_CONTROL: None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "deconz",
|
CONF_RADIO_TYPE: "deconz",
|
||||||
}
|
}
|
||||||
@ -773,7 +777,11 @@ async def test_user_flow_not_detected(hass: HomeAssistant) -> None:
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={CONF_SOURCE: SOURCE_USER},
|
context={CONF_SOURCE: SOURCE_USER},
|
||||||
data={zigpy.config.CONF_DEVICE_PATH: port_select},
|
data={
|
||||||
|
zigpy.config.CONF_DEVICE_PATH: port_select,
|
||||||
|
CONF_BAUDRATE: 115200,
|
||||||
|
CONF_FLOW_CONTROL: None,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
@ -951,31 +959,6 @@ async def test_user_port_config(probe_mock, hass: HomeAssistant) -> None:
|
|||||||
assert probe_mock.await_count == 1
|
assert probe_mock.await_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("old_type", "new_type"),
|
|
||||||
[
|
|
||||||
("ezsp", "ezsp"),
|
|
||||||
("ti_cc", "znp"), # only one that should change
|
|
||||||
("znp", "znp"),
|
|
||||||
("deconz", "deconz"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_migration_ti_cc_to_znp(
|
|
||||||
old_type, new_type, hass: HomeAssistant, config_entry: MockConfigEntry
|
|
||||||
) -> None:
|
|
||||||
"""Test zigpy-cc to zigpy-znp config migration."""
|
|
||||||
config_entry.data = {**config_entry.data, CONF_RADIO_TYPE: old_type}
|
|
||||||
config_entry.version = 2
|
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
with patch("homeassistant.components.zha.async_setup_entry", return_value=True):
|
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert config_entry.version > 2
|
|
||||||
assert config_entry.data[CONF_RADIO_TYPE] == new_type
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("onboarded", [True, False])
|
@pytest.mark.parametrize("onboarded", [True, False])
|
||||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
async def test_hardware(onboarded, hass: HomeAssistant) -> None:
|
async def test_hardware(onboarded, hass: HomeAssistant) -> None:
|
||||||
@ -1022,7 +1005,7 @@ async def test_hardware(onboarded, hass: HomeAssistant) -> None:
|
|||||||
assert result3["data"] == {
|
assert result3["data"] == {
|
||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOWCONTROL: "hardware",
|
CONF_FLOW_CONTROL: "hardware",
|
||||||
CONF_DEVICE_PATH: "/dev/ttyAMA1",
|
CONF_DEVICE_PATH: "/dev/ttyAMA1",
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "ezsp",
|
CONF_RADIO_TYPE: "ezsp",
|
||||||
@ -1171,6 +1154,7 @@ async def test_formation_strategy_form_initial_network(
|
|||||||
|
|
||||||
|
|
||||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
||||||
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
async def test_onboarding_auto_formation_new_hardware(
|
async def test_onboarding_auto_formation_new_hardware(
|
||||||
mock_app, hass: HomeAssistant
|
mock_app, hass: HomeAssistant
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -1577,7 +1561,7 @@ async def test_options_flow_defaults(
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "/dev/ttyUSB0",
|
CONF_DEVICE_PATH: "/dev/ttyUSB0",
|
||||||
CONF_BAUDRATE: 12345,
|
CONF_BAUDRATE: 12345,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "znp",
|
CONF_RADIO_TYPE: "znp",
|
||||||
},
|
},
|
||||||
@ -1645,7 +1629,7 @@ async def test_options_flow_defaults(
|
|||||||
# Change everything
|
# Change everything
|
||||||
CONF_DEVICE_PATH: "/dev/new_serial_port",
|
CONF_DEVICE_PATH: "/dev/new_serial_port",
|
||||||
CONF_BAUDRATE: 54321,
|
CONF_BAUDRATE: 54321,
|
||||||
CONF_FLOWCONTROL: "software",
|
CONF_FLOW_CONTROL: "software",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1668,7 +1652,7 @@ async def test_options_flow_defaults(
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "/dev/new_serial_port",
|
CONF_DEVICE_PATH: "/dev/new_serial_port",
|
||||||
CONF_BAUDRATE: 54321,
|
CONF_BAUDRATE: 54321,
|
||||||
CONF_FLOWCONTROL: "software",
|
CONF_FLOW_CONTROL: "software",
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "znp",
|
CONF_RADIO_TYPE: "znp",
|
||||||
}
|
}
|
||||||
@ -1697,7 +1681,7 @@ async def test_options_flow_defaults_socket(hass: HomeAssistant) -> None:
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "socket://localhost:5678",
|
CONF_DEVICE_PATH: "socket://localhost:5678",
|
||||||
CONF_BAUDRATE: 12345,
|
CONF_BAUDRATE: 12345,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "znp",
|
CONF_RADIO_TYPE: "znp",
|
||||||
},
|
},
|
||||||
@ -1766,7 +1750,7 @@ async def test_options_flow_restarts_running_zha_if_cancelled(
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "socket://localhost:5678",
|
CONF_DEVICE_PATH: "socket://localhost:5678",
|
||||||
CONF_BAUDRATE: 12345,
|
CONF_BAUDRATE: 12345,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "znp",
|
CONF_RADIO_TYPE: "znp",
|
||||||
},
|
},
|
||||||
@ -1821,7 +1805,7 @@ async def test_options_flow_migration_reset_old_adapter(
|
|||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_DEVICE_PATH: "/dev/serial/by-id/old_radio",
|
CONF_DEVICE_PATH: "/dev/serial/by-id/old_radio",
|
||||||
CONF_BAUDRATE: 12345,
|
CONF_BAUDRATE: 12345,
|
||||||
CONF_FLOWCONTROL: None,
|
CONF_FLOW_CONTROL: None,
|
||||||
},
|
},
|
||||||
CONF_RADIO_TYPE: "znp",
|
CONF_RADIO_TYPE: "znp",
|
||||||
},
|
},
|
||||||
@ -1954,3 +1938,28 @@ async def test_discovery_wrong_firmware_installed(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
assert result["type"] == FlowResultType.ABORT
|
assert result["type"] == FlowResultType.ABORT
|
||||||
assert result["reason"] == "wrong_firmware_installed"
|
assert result["reason"] == "wrong_firmware_installed"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("old_type", "new_type"),
|
||||||
|
[
|
||||||
|
("ezsp", "ezsp"),
|
||||||
|
("ti_cc", "znp"), # only one that should change
|
||||||
|
("znp", "znp"),
|
||||||
|
("deconz", "deconz"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_migration_ti_cc_to_znp(
|
||||||
|
old_type: str, new_type: str, hass: HomeAssistant, config_entry: MockConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Test zigpy-cc to zigpy-znp config migration."""
|
||||||
|
config_entry.data = {**config_entry.data, CONF_RADIO_TYPE: old_type}
|
||||||
|
config_entry.version = 2
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.zha.async_setup_entry", return_value=True):
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.version > 2
|
||||||
|
assert config_entry.data[CONF_RADIO_TYPE] == new_type
|
||||||
|
@ -4,22 +4,21 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from zigpy.application import ControllerApplication
|
from zigpy.application import ControllerApplication
|
||||||
import zigpy.exceptions
|
|
||||||
import zigpy.profiles.zha as zha
|
import zigpy.profiles.zha as zha
|
||||||
import zigpy.zcl.clusters.general as general
|
import zigpy.zcl.clusters.general as general
|
||||||
import zigpy.zcl.clusters.lighting as lighting
|
import zigpy.zcl.clusters.lighting as lighting
|
||||||
|
|
||||||
from homeassistant.components.zha.core.const import RadioType
|
from homeassistant.components.zha.core.gateway import ZHAGateway
|
||||||
from homeassistant.components.zha.core.device import ZHADevice
|
|
||||||
from homeassistant.components.zha.core.group import GroupMember
|
from homeassistant.components.zha.core.group import GroupMember
|
||||||
from homeassistant.components.zha.core.helpers import get_zha_gateway
|
from homeassistant.components.zha.core.helpers import get_zha_gateway
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
|
||||||
|
|
||||||
from .common import async_find_group_entity_id
|
from .common import async_find_group_entity_id
|
||||||
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
|
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8"
|
IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8"
|
||||||
IEEE_GROUPABLE_DEVICE2 = "02:2d:6f:00:0a:90:69:e8"
|
IEEE_GROUPABLE_DEVICE2 = "02:2d:6f:00:0a:90:69:e8"
|
||||||
|
|
||||||
@ -224,101 +223,6 @@ async def test_gateway_create_group_with_id(
|
|||||||
assert zha_group.group_id == 0x1234
|
assert zha_group.group_id == 0x1234
|
||||||
|
|
||||||
|
|
||||||
@patch(
|
|
||||||
"homeassistant.components.zha.core.gateway.ZHAGateway.async_load_devices",
|
|
||||||
MagicMock(),
|
|
||||||
)
|
|
||||||
@patch(
|
|
||||||
"homeassistant.components.zha.core.gateway.ZHAGateway.async_load_groups",
|
|
||||||
MagicMock(),
|
|
||||||
)
|
|
||||||
@patch("homeassistant.components.zha.core.gateway.STARTUP_FAILURE_DELAY_S", 0.01)
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"startup_effect",
|
|
||||||
[
|
|
||||||
[asyncio.TimeoutError(), FileNotFoundError(), None],
|
|
||||||
[asyncio.TimeoutError(), None],
|
|
||||||
[None],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_gateway_initialize_success(
|
|
||||||
startup_effect: list[Exception | None],
|
|
||||||
hass: HomeAssistant,
|
|
||||||
device_light_1: ZHADevice,
|
|
||||||
coordinator: ZHADevice,
|
|
||||||
zigpy_app_controller: ControllerApplication,
|
|
||||||
) -> None:
|
|
||||||
"""Test ZHA initializing the gateway successfully."""
|
|
||||||
zha_gateway = get_zha_gateway(hass)
|
|
||||||
assert zha_gateway is not None
|
|
||||||
|
|
||||||
zigpy_app_controller.startup.side_effect = startup_effect
|
|
||||||
zigpy_app_controller.startup.reset_mock()
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"bellows.zigbee.application.ControllerApplication.new",
|
|
||||||
return_value=zigpy_app_controller,
|
|
||||||
):
|
|
||||||
await zha_gateway.async_initialize()
|
|
||||||
|
|
||||||
assert zigpy_app_controller.startup.call_count == len(startup_effect)
|
|
||||||
device_light_1.async_cleanup_handles()
|
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.zha.core.gateway.STARTUP_FAILURE_DELAY_S", 0.01)
|
|
||||||
async def test_gateway_initialize_failure(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
device_light_1: ZHADevice,
|
|
||||||
coordinator: ZHADevice,
|
|
||||||
zigpy_app_controller: ControllerApplication,
|
|
||||||
) -> None:
|
|
||||||
"""Test ZHA failing to initialize the gateway."""
|
|
||||||
zha_gateway = get_zha_gateway(hass)
|
|
||||||
assert zha_gateway is not None
|
|
||||||
|
|
||||||
zigpy_app_controller.startup.side_effect = [
|
|
||||||
asyncio.TimeoutError(),
|
|
||||||
RuntimeError(),
|
|
||||||
FileNotFoundError(),
|
|
||||||
]
|
|
||||||
zigpy_app_controller.startup.reset_mock()
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"bellows.zigbee.application.ControllerApplication.new",
|
|
||||||
return_value=zigpy_app_controller,
|
|
||||||
), pytest.raises(FileNotFoundError):
|
|
||||||
await zha_gateway.async_initialize()
|
|
||||||
|
|
||||||
assert zigpy_app_controller.startup.call_count == 3
|
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.zha.core.gateway.STARTUP_FAILURE_DELAY_S", 0.01)
|
|
||||||
async def test_gateway_initialize_failure_transient(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
device_light_1: ZHADevice,
|
|
||||||
coordinator: ZHADevice,
|
|
||||||
zigpy_app_controller: ControllerApplication,
|
|
||||||
) -> None:
|
|
||||||
"""Test ZHA failing to initialize the gateway but with a transient error."""
|
|
||||||
zha_gateway = get_zha_gateway(hass)
|
|
||||||
assert zha_gateway is not None
|
|
||||||
|
|
||||||
zigpy_app_controller.startup.side_effect = [
|
|
||||||
RuntimeError(),
|
|
||||||
zigpy.exceptions.TransientConnectionError(),
|
|
||||||
]
|
|
||||||
zigpy_app_controller.startup.reset_mock()
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"bellows.zigbee.application.ControllerApplication.new",
|
|
||||||
return_value=zigpy_app_controller,
|
|
||||||
), pytest.raises(ConfigEntryNotReady):
|
|
||||||
await zha_gateway.async_initialize()
|
|
||||||
|
|
||||||
# Initialization immediately stops and is retried after TransientConnectionError
|
|
||||||
assert zigpy_app_controller.startup.call_count == 2
|
|
||||||
|
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"homeassistant.components.zha.core.gateway.ZHAGateway.async_load_devices",
|
"homeassistant.components.zha.core.gateway.ZHAGateway.async_load_devices",
|
||||||
MagicMock(),
|
MagicMock(),
|
||||||
@ -340,22 +244,25 @@ async def test_gateway_initialize_bellows_thread(
|
|||||||
thread_state: bool,
|
thread_state: bool,
|
||||||
config_override: dict,
|
config_override: dict,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
coordinator: ZHADevice,
|
|
||||||
zigpy_app_controller: ControllerApplication,
|
zigpy_app_controller: ControllerApplication,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test ZHA disabling the UART thread when connecting to a TCP coordinator."""
|
"""Test ZHA disabling the UART thread when connecting to a TCP coordinator."""
|
||||||
zha_gateway = get_zha_gateway(hass)
|
config_entry.data = dict(config_entry.data)
|
||||||
assert zha_gateway is not None
|
config_entry.data["device"]["path"] = device_path
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
zha_gateway.config_entry.data = dict(zha_gateway.config_entry.data)
|
zha_gateway = ZHAGateway(hass, {"zigpy_config": config_override}, config_entry)
|
||||||
zha_gateway.config_entry.data["device"]["path"] = device_path
|
|
||||||
zha_gateway._config.setdefault("zigpy_config", {}).update(config_override)
|
|
||||||
|
|
||||||
await zha_gateway.async_initialize()
|
with patch(
|
||||||
|
"bellows.zigbee.application.ControllerApplication.new",
|
||||||
|
return_value=zigpy_app_controller,
|
||||||
|
) as mock_new:
|
||||||
|
await zha_gateway.async_initialize()
|
||||||
|
|
||||||
RadioType.ezsp.controller.new.mock_calls[-1].kwargs["config"][
|
mock_new.mock_calls[-1].kwargs["config"]["use_thread"] is thread_state
|
||||||
"use_thread"
|
|
||||||
] is thread_state
|
await zha_gateway.shutdown()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -373,15 +280,14 @@ async def test_gateway_force_multi_pan_channel(
|
|||||||
config_override: dict,
|
config_override: dict,
|
||||||
expected_channel: int | None,
|
expected_channel: int | None,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
coordinator,
|
config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test ZHA disabling the UART thread when connecting to a TCP coordinator."""
|
"""Test ZHA disabling the UART thread when connecting to a TCP coordinator."""
|
||||||
zha_gateway = get_zha_gateway(hass)
|
config_entry.data = dict(config_entry.data)
|
||||||
assert zha_gateway is not None
|
config_entry.data["device"]["path"] = device_path
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
zha_gateway.config_entry.data = dict(zha_gateway.config_entry.data)
|
zha_gateway = ZHAGateway(hass, {"zigpy_config": config_override}, config_entry)
|
||||||
zha_gateway.config_entry.data["device"]["path"] = device_path
|
|
||||||
zha_gateway._config.setdefault("zigpy_config", {}).update(config_override)
|
|
||||||
|
|
||||||
_, config = zha_gateway.get_application_controller_data()
|
_, config = zha_gateway.get_application_controller_data()
|
||||||
assert config["network"]["channel"] == expected_channel
|
assert config["network"]["channel"] == expected_channel
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Tests for ZHA integration init."""
|
"""Tests for ZHA integration init."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import typing
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -9,6 +10,7 @@ from zigpy.exceptions import TransientConnectionError
|
|||||||
|
|
||||||
from homeassistant.components.zha.core.const import (
|
from homeassistant.components.zha.core.const import (
|
||||||
CONF_BAUDRATE,
|
CONF_BAUDRATE,
|
||||||
|
CONF_FLOW_CONTROL,
|
||||||
CONF_RADIO_TYPE,
|
CONF_RADIO_TYPE,
|
||||||
CONF_USB_PATH,
|
CONF_USB_PATH,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -61,9 +63,8 @@ async def test_migration_from_v1_no_baudrate(
|
|||||||
assert config_entry_v1.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
assert config_entry_v1.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
||||||
assert CONF_DEVICE in config_entry_v1.data
|
assert CONF_DEVICE in config_entry_v1.data
|
||||||
assert config_entry_v1.data[CONF_DEVICE][CONF_DEVICE_PATH] == DATA_PORT_PATH
|
assert config_entry_v1.data[CONF_DEVICE][CONF_DEVICE_PATH] == DATA_PORT_PATH
|
||||||
assert CONF_BAUDRATE not in config_entry_v1.data[CONF_DEVICE]
|
|
||||||
assert CONF_USB_PATH not in config_entry_v1.data
|
assert CONF_USB_PATH not in config_entry_v1.data
|
||||||
assert config_entry_v1.version == 3
|
assert config_entry_v1.version == 4
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
@ -80,7 +81,7 @@ async def test_migration_from_v1_with_baudrate(
|
|||||||
assert CONF_USB_PATH not in config_entry_v1.data
|
assert CONF_USB_PATH not in config_entry_v1.data
|
||||||
assert CONF_BAUDRATE in config_entry_v1.data[CONF_DEVICE]
|
assert CONF_BAUDRATE in config_entry_v1.data[CONF_DEVICE]
|
||||||
assert config_entry_v1.data[CONF_DEVICE][CONF_BAUDRATE] == 115200
|
assert config_entry_v1.data[CONF_DEVICE][CONF_BAUDRATE] == 115200
|
||||||
assert config_entry_v1.version == 3
|
assert config_entry_v1.version == 4
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
@ -95,8 +96,7 @@ async def test_migration_from_v1_wrong_baudrate(
|
|||||||
assert CONF_DEVICE in config_entry_v1.data
|
assert CONF_DEVICE in config_entry_v1.data
|
||||||
assert config_entry_v1.data[CONF_DEVICE][CONF_DEVICE_PATH] == DATA_PORT_PATH
|
assert config_entry_v1.data[CONF_DEVICE][CONF_DEVICE_PATH] == DATA_PORT_PATH
|
||||||
assert CONF_USB_PATH not in config_entry_v1.data
|
assert CONF_USB_PATH not in config_entry_v1.data
|
||||||
assert CONF_BAUDRATE not in config_entry_v1.data[CONF_DEVICE]
|
assert config_entry_v1.version == 4
|
||||||
assert config_entry_v1.version == 3
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
@ -149,23 +149,74 @@ async def test_setup_with_v3_cleaning_uri(
|
|||||||
mock_zigpy_connect: ControllerApplication,
|
mock_zigpy_connect: ControllerApplication,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test migration of config entry from v3, applying corrections to the port path."""
|
"""Test migration of config entry from v3, applying corrections to the port path."""
|
||||||
config_entry_v3 = MockConfigEntry(
|
config_entry_v4 = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data={
|
data={
|
||||||
CONF_RADIO_TYPE: DATA_RADIO_TYPE,
|
CONF_RADIO_TYPE: DATA_RADIO_TYPE,
|
||||||
CONF_DEVICE: {CONF_DEVICE_PATH: path, CONF_BAUDRATE: 115200},
|
CONF_DEVICE: {
|
||||||
|
CONF_DEVICE_PATH: path,
|
||||||
|
CONF_BAUDRATE: 115200,
|
||||||
|
CONF_FLOW_CONTROL: None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
version=3,
|
version=4,
|
||||||
)
|
)
|
||||||
config_entry_v3.add_to_hass(hass)
|
config_entry_v4.add_to_hass(hass)
|
||||||
|
|
||||||
await hass.config_entries.async_setup(config_entry_v3.entry_id)
|
await hass.config_entries.async_setup(config_entry_v4.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await hass.config_entries.async_unload(config_entry_v3.entry_id)
|
await hass.config_entries.async_unload(config_entry_v4.entry_id)
|
||||||
|
|
||||||
assert config_entry_v3.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
assert config_entry_v4.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
||||||
assert config_entry_v3.data[CONF_DEVICE][CONF_DEVICE_PATH] == cleaned_path
|
assert config_entry_v4.data[CONF_DEVICE][CONF_DEVICE_PATH] == cleaned_path
|
||||||
assert config_entry_v3.version == 3
|
assert config_entry_v4.version == 4
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"radio_type",
|
||||||
|
"old_baudrate",
|
||||||
|
"old_flow_control",
|
||||||
|
"new_baudrate",
|
||||||
|
"new_flow_control",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
("znp", None, None, 115200, None),
|
||||||
|
("znp", None, "software", 115200, "software"),
|
||||||
|
("znp", 57600, "software", 57600, "software"),
|
||||||
|
("deconz", None, None, 38400, None),
|
||||||
|
("deconz", 115200, None, 115200, None),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
|
async def test_migration_baudrate_and_flow_control(
|
||||||
|
radio_type: str,
|
||||||
|
old_baudrate: int,
|
||||||
|
old_flow_control: typing.Literal["hardware", "software", None],
|
||||||
|
new_baudrate: int,
|
||||||
|
new_flow_control: typing.Literal["hardware", "software", None],
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test baudrate and flow control migration."""
|
||||||
|
config_entry.data = {
|
||||||
|
**config_entry.data,
|
||||||
|
CONF_RADIO_TYPE: radio_type,
|
||||||
|
CONF_DEVICE: {
|
||||||
|
CONF_BAUDRATE: old_baudrate,
|
||||||
|
CONF_FLOW_CONTROL: old_flow_control,
|
||||||
|
CONF_DEVICE_PATH: "/dev/null",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config_entry.version = 3
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.version > 3
|
||||||
|
assert config_entry.data[CONF_DEVICE][CONF_BAUDRATE] == new_baudrate
|
||||||
|
assert config_entry.data[CONF_DEVICE][CONF_FLOW_CONTROL] == new_flow_control
|
||||||
|
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
|
@ -95,6 +95,7 @@ def test_detect_radio_hardware_failure(hass: HomeAssistant) -> None:
|
|||||||
assert _detect_radio_hardware(hass, SKYCONNECT_DEVICE) == HardwareType.OTHER
|
assert _detect_radio_hardware(hass, SKYCONNECT_DEVICE) == HardwareType.OTHER
|
||||||
|
|
||||||
|
|
||||||
|
@patch("homeassistant.components.zha.STARTUP_RETRIES", new=1)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("detected_hardware", "expected_learn_more_url"),
|
("detected_hardware", "expected_learn_more_url"),
|
||||||
[
|
[
|
||||||
@ -188,6 +189,7 @@ async def test_multipan_firmware_no_repair_on_probe_failure(
|
|||||||
assert issue is None
|
assert issue is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch("homeassistant.components.zha.STARTUP_RETRIES", new=1)
|
||||||
async def test_multipan_firmware_retry_on_probe_ezsp(
|
async def test_multipan_firmware_retry_on_probe_ezsp(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
@ -312,6 +314,8 @@ async def test_inconsistent_settings_keep_new(
|
|||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
assert data["type"] == "create_entry"
|
assert data["type"] == "create_entry"
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
issue_registry.async_get_issue(
|
issue_registry.async_get_issue(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
@ -388,6 +392,8 @@ async def test_inconsistent_settings_restore_old(
|
|||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
assert data["type"] == "create_entry"
|
assert data["type"] == "create_entry"
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
issue_registry.async_get_issue(
|
issue_registry.async_get_issue(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
|
@ -62,7 +62,7 @@ from .conftest import (
|
|||||||
)
|
)
|
||||||
from .data import BASE_CUSTOM_CONFIGURATION, CONFIG_WITH_ALARM_OPTIONS
|
from .data import BASE_CUSTOM_CONFIGURATION, CONFIG_WITH_ALARM_OPTIONS
|
||||||
|
|
||||||
from tests.common import MockUser
|
from tests.common import MockConfigEntry, MockUser
|
||||||
|
|
||||||
IEEE_SWITCH_DEVICE = "01:2d:6f:00:0a:90:69:e7"
|
IEEE_SWITCH_DEVICE = "01:2d:6f:00:0a:90:69:e7"
|
||||||
IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8"
|
IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8"
|
||||||
@ -295,10 +295,12 @@ async def test_get_zha_config_with_alarm(
|
|||||||
|
|
||||||
|
|
||||||
async def test_update_zha_config(
|
async def test_update_zha_config(
|
||||||
zha_client, app_controller: ControllerApplication
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
zha_client,
|
||||||
|
app_controller: ControllerApplication,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test updating ZHA custom configuration."""
|
"""Test updating ZHA custom configuration."""
|
||||||
|
|
||||||
configuration: dict = deepcopy(CONFIG_WITH_ALARM_OPTIONS)
|
configuration: dict = deepcopy(CONFIG_WITH_ALARM_OPTIONS)
|
||||||
configuration["data"]["zha_options"]["default_light_transition"] = 10
|
configuration["data"]["zha_options"]["default_light_transition"] = 10
|
||||||
|
|
||||||
@ -312,10 +314,12 @@ async def test_update_zha_config(
|
|||||||
msg = await zha_client.receive_json()
|
msg = await zha_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
|
||||||
await zha_client.send_json({ID: 6, TYPE: "zha/configuration"})
|
await zha_client.send_json({ID: 6, TYPE: "zha/configuration"})
|
||||||
msg = await zha_client.receive_json()
|
msg = await zha_client.receive_json()
|
||||||
configuration = msg["result"]
|
configuration = msg["result"]
|
||||||
assert configuration == configuration
|
assert configuration == configuration
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
async def test_device_not_found(zha_client) -> None:
|
async def test_device_not_found(zha_client) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user