mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 17:27:10 +00:00
Generic ZHA Zeroconf discovery (#126294)
This commit is contained in:
parent
e9286971aa
commit
50fdbe9b3b
@ -70,8 +70,17 @@ UPLOADED_BACKUP_FILE = "uploaded_backup_file"
|
||||
|
||||
REPAIR_MY_URL = "https://my.home-assistant.io/redirect/repairs/"
|
||||
|
||||
DEFAULT_ZHA_ZEROCONF_PORT = 6638
|
||||
ESPHOME_API_PORT = 6053
|
||||
LEGACY_ZEROCONF_PORT = 6638
|
||||
LEGACY_ZEROCONF_ESPHOME_API_PORT = 6053
|
||||
|
||||
ZEROCONF_SERVICE_TYPE = "_zigbee-coordinator._tcp.local."
|
||||
ZEROCONF_PROPERTIES_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("radio_type"): vol.All(str, vol.In([t.name for t in RadioType])),
|
||||
vol.Required("serial_number"): str,
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
def _format_backup_choice(
|
||||
@ -617,34 +626,65 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle zeroconf discovery."""
|
||||
|
||||
# Hostname is format: livingroom.local.
|
||||
local_name = discovery_info.hostname[:-1]
|
||||
port = discovery_info.port or DEFAULT_ZHA_ZEROCONF_PORT
|
||||
# Transform legacy zeroconf discovery into the new format
|
||||
if discovery_info.type != ZEROCONF_SERVICE_TYPE:
|
||||
port = discovery_info.port or LEGACY_ZEROCONF_PORT
|
||||
name = discovery_info.name
|
||||
|
||||
# Fix incorrect port for older TubesZB devices
|
||||
if "tube" in local_name and port == ESPHOME_API_PORT:
|
||||
port = DEFAULT_ZHA_ZEROCONF_PORT
|
||||
# Fix incorrect port for older TubesZB devices
|
||||
if "tube" in name and port == LEGACY_ZEROCONF_ESPHOME_API_PORT:
|
||||
port = LEGACY_ZEROCONF_PORT
|
||||
|
||||
if "radio_type" in discovery_info.properties:
|
||||
self._radio_mgr.radio_type = self._radio_mgr.parse_radio_type(
|
||||
discovery_info.properties["radio_type"]
|
||||
# Determine the radio type
|
||||
if "radio_type" in discovery_info.properties:
|
||||
radio_type = discovery_info.properties["radio_type"]
|
||||
elif "efr32" in name:
|
||||
radio_type = RadioType.ezsp.name
|
||||
elif "zigate" in name:
|
||||
radio_type = RadioType.zigate.name
|
||||
else:
|
||||
radio_type = RadioType.znp.name
|
||||
|
||||
fallback_title = name.split("._", 1)[0]
|
||||
title = discovery_info.properties.get("name", fallback_title)
|
||||
|
||||
discovery_info = zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=discovery_info.ip_address,
|
||||
ip_addresses=discovery_info.ip_addresses,
|
||||
port=port,
|
||||
hostname=discovery_info.hostname,
|
||||
type=ZEROCONF_SERVICE_TYPE,
|
||||
name=f"{title}.{ZEROCONF_SERVICE_TYPE}",
|
||||
properties={
|
||||
"radio_type": radio_type,
|
||||
# To maintain backwards compatibility
|
||||
"serial_number": discovery_info.hostname.removesuffix(".local."),
|
||||
},
|
||||
)
|
||||
elif "efr32" in local_name:
|
||||
self._radio_mgr.radio_type = RadioType.ezsp
|
||||
else:
|
||||
self._radio_mgr.radio_type = RadioType.znp
|
||||
|
||||
node_name = local_name.removesuffix(".local")
|
||||
device_path = f"socket://{discovery_info.host}:{port}"
|
||||
try:
|
||||
discovery_props = ZEROCONF_PROPERTIES_SCHEMA(discovery_info.properties)
|
||||
except vol.Invalid:
|
||||
return self.async_abort(reason="invalid_zeroconf_data")
|
||||
|
||||
radio_type = self._radio_mgr.parse_radio_type(discovery_props["radio_type"])
|
||||
device_path = f"socket://{discovery_info.host}:{discovery_info.port}"
|
||||
title = discovery_info.name.removesuffix(f".{ZEROCONF_SERVICE_TYPE}")
|
||||
|
||||
await self._set_unique_id_and_update_ignored_flow(
|
||||
unique_id=node_name,
|
||||
unique_id=discovery_props["serial_number"],
|
||||
device_path=device_path,
|
||||
)
|
||||
|
||||
self.context["title_placeholders"] = {CONF_NAME: node_name}
|
||||
self._title = device_path
|
||||
self.context["title_placeholders"] = {CONF_NAME: title}
|
||||
self._title = title
|
||||
self._radio_mgr.device_path = device_path
|
||||
self._radio_mgr.radio_type = radio_type
|
||||
self._radio_mgr.device_settings = {
|
||||
CONF_DEVICE_PATH: device_path,
|
||||
CONF_BAUDRATE: 115200,
|
||||
CONF_FLOW_CONTROL: None,
|
||||
}
|
||||
|
||||
return await self.async_step_confirm()
|
||||
|
||||
|
@ -130,6 +130,10 @@
|
||||
{
|
||||
"type": "_czc._tcp.local.",
|
||||
"name": "czc*"
|
||||
},
|
||||
{
|
||||
"type": "_zigbee-coordinator._tcp.local.",
|
||||
"name": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -76,7 +76,8 @@
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||
"not_zha_device": "This device is not a zha device",
|
||||
"usb_probe_failed": "Failed to probe the usb device",
|
||||
"wrong_firmware_installed": "Your device is running the wrong firmware and cannot be used with ZHA until the correct firmware is installed. [A repair has been created]({repair_url}) with more information and instructions for how to fix this."
|
||||
"wrong_firmware_installed": "Your device is running the wrong firmware and cannot be used with ZHA until the correct firmware is installed. [A repair has been created]({repair_url}) with more information and instructions for how to fix this.",
|
||||
"invalid_zeroconf_data": "The coordinator has invalid zeroconf service info and cannot be identified by ZHA"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
@ -872,6 +872,12 @@ ZEROCONF = {
|
||||
"name": "*zigate*",
|
||||
},
|
||||
],
|
||||
"_zigbee-coordinator._tcp.local.": [
|
||||
{
|
||||
"domain": "zha",
|
||||
"name": "*",
|
||||
},
|
||||
],
|
||||
"_zigstar_gw._tcp.local.": [
|
||||
{
|
||||
"domain": "zha",
|
||||
|
@ -154,104 +154,180 @@ def com_port(device="/dev/ttyUSB1234") -> ListPortInfo:
|
||||
return port
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entry_name", "unique_id", "radio_type", "service_info"),
|
||||
[
|
||||
(
|
||||
# TubesZB, old ESPHome devices (ZNP)
|
||||
"tubeszb-cc2652-poe",
|
||||
"tubeszb-cc2652-poe",
|
||||
RadioType.znp,
|
||||
zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="tubeszb-cc2652-poe.local.",
|
||||
name="tubeszb-cc2652-poe._esphomelib._tcp.local.",
|
||||
port=6053, # the ESPHome API port is remapped to 6638
|
||||
type="_esphomelib._tcp.local.",
|
||||
properties={
|
||||
"project_version": "3.0",
|
||||
"project_name": "tubezb.cc2652-poe",
|
||||
"network": "ethernet",
|
||||
"board": "esp32-poe",
|
||||
"platform": "ESP32",
|
||||
"maс": "8c4b14c33c24",
|
||||
"version": "2023.12.8",
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
# TubesZB, old ESPHome device (EFR32)
|
||||
"tubeszb-efr32-poe",
|
||||
"tubeszb-efr32-poe",
|
||||
RadioType.ezsp,
|
||||
zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="tubeszb-efr32-poe.local.",
|
||||
name="tubeszb-efr32-poe._esphomelib._tcp.local.",
|
||||
port=6053, # the ESPHome API port is remapped to 6638
|
||||
type="_esphomelib._tcp.local.",
|
||||
properties={
|
||||
"project_version": "3.0",
|
||||
"project_name": "tubezb.efr32-poe",
|
||||
"network": "ethernet",
|
||||
"board": "esp32-poe",
|
||||
"platform": "ESP32",
|
||||
"maс": "8c4b14c33c24",
|
||||
"version": "2023.12.8",
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
# TubesZB, newer devices
|
||||
"TubeZB",
|
||||
"tubeszb-cc2652-poe",
|
||||
RadioType.znp,
|
||||
zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="tubeszb-cc2652-poe.local.",
|
||||
name="tubeszb-cc2652-poe._tubeszb._tcp.local.",
|
||||
port=6638,
|
||||
properties={
|
||||
"name": "TubeZB",
|
||||
"radio_type": "znp",
|
||||
"version": "1.0",
|
||||
"baud_rate": "115200",
|
||||
"data_flow_control": "software",
|
||||
},
|
||||
type="_tubeszb._tcp.local.",
|
||||
),
|
||||
),
|
||||
(
|
||||
# Expected format for all new devices
|
||||
"Some Zigbee Gateway (12345)",
|
||||
"aabbccddeeff",
|
||||
RadioType.znp,
|
||||
zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="some-zigbee-gateway-12345.local.",
|
||||
name="Some Zigbee Gateway (12345)._zigbee-coordinator._tcp.local.",
|
||||
port=6638,
|
||||
properties={"radio_type": "znp", "serial_number": "aabbccddeeff"},
|
||||
type="_zigbee-coordinator._tcp.local.",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
||||
async def test_zeroconf_discovery_znp(hass: HomeAssistant) -> None:
|
||||
@patch(f"bellows.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
||||
async def test_zeroconf_discovery(
|
||||
entry_name: str,
|
||||
unique_id: str,
|
||||
radio_type: RadioType,
|
||||
service_info: zeroconf.ZeroconfServiceInfo,
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test zeroconf flow -- radio detected."""
|
||||
service_info = zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="tube._tube_zb_gw._tcp.local.",
|
||||
name="tube",
|
||||
port=6053,
|
||||
properties={"name": "tube_123456"},
|
||||
type="mock_type",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
result_init = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
assert flow["step_id"] == "confirm"
|
||||
|
||||
# Confirm discovery
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
flow["flow_id"], user_input={}
|
||||
)
|
||||
assert result1["step_id"] == "manual_port_config"
|
||||
assert result_init["step_id"] == "confirm"
|
||||
|
||||
# Confirm port settings
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"], user_input={}
|
||||
result_confirm = await hass.config_entries.flow.async_configure(
|
||||
result_init["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result2["type"] is FlowResultType.MENU
|
||||
assert result2["step_id"] == "choose_formation_strategy"
|
||||
assert result_confirm["type"] is FlowResultType.MENU
|
||||
assert result_confirm["step_id"] == "choose_formation_strategy"
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
result_form = await hass.config_entries.flow.async_configure(
|
||||
result_confirm["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result3["title"] == "socket://192.168.1.200:6638"
|
||||
assert result3["data"] == {
|
||||
assert result_form["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result_form["title"] == entry_name
|
||||
assert result_form["context"]["unique_id"] == unique_id
|
||||
assert result_form["data"] == {
|
||||
CONF_DEVICE: {
|
||||
CONF_BAUDRATE: 115200,
|
||||
CONF_FLOW_CONTROL: None,
|
||||
CONF_DEVICE_PATH: "socket://192.168.1.200:6638",
|
||||
},
|
||||
CONF_RADIO_TYPE: "znp",
|
||||
CONF_RADIO_TYPE: radio_type.name,
|
||||
}
|
||||
|
||||
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
@patch(f"zigpy_zigate.{PROBE_FUNCTION_PATH}")
|
||||
async def test_zigate_via_zeroconf(setup_entry_mock, hass: HomeAssistant) -> None:
|
||||
async def test_legacy_zeroconf_discovery_zigate(
|
||||
setup_entry_mock, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Test zeroconf flow -- zigate radio detected."""
|
||||
service_info = zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="_zigate-zigbee-gateway._tcp.local.",
|
||||
name="any",
|
||||
hostname="_zigate-zigbee-gateway.local.",
|
||||
name="some name._zigate-zigbee-gateway._tcp.local.",
|
||||
port=1234,
|
||||
properties={"radio_type": "zigate"},
|
||||
properties={},
|
||||
type="mock_type",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
result_init = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
assert flow["step_id"] == "confirm"
|
||||
|
||||
# Confirm discovery
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
flow["flow_id"], user_input={}
|
||||
)
|
||||
assert result1["step_id"] == "manual_port_config"
|
||||
assert result_init["step_id"] == "confirm"
|
||||
|
||||
# Confirm the radio is deprecated
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
flow["flow_id"], user_input={}
|
||||
result_confirm_deprecated = await hass.config_entries.flow.async_configure(
|
||||
result_init["flow_id"], user_input={}
|
||||
)
|
||||
assert result2["step_id"] == "verify_radio"
|
||||
assert "ZiGate" in result2["description_placeholders"]["name"]
|
||||
assert result_confirm_deprecated["step_id"] == "verify_radio"
|
||||
assert "ZiGate" in result_confirm_deprecated["description_placeholders"]["name"]
|
||||
|
||||
# Confirm port settings
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"], user_input={}
|
||||
result_confirm = await hass.config_entries.flow.async_configure(
|
||||
result_confirm_deprecated["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result3["type"] is FlowResultType.MENU
|
||||
assert result3["step_id"] == "choose_formation_strategy"
|
||||
assert result_confirm["type"] is FlowResultType.MENU
|
||||
assert result_confirm["step_id"] == "choose_formation_strategy"
|
||||
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
result_form = await hass.config_entries.flow.async_configure(
|
||||
result_confirm["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result4["title"] == "socket://192.168.1.200:1234"
|
||||
assert result4["data"] == {
|
||||
assert result_form["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result_form["title"] == "some name"
|
||||
assert result_form["data"] == {
|
||||
CONF_DEVICE: {
|
||||
CONF_DEVICE_PATH: "socket://192.168.1.200:1234",
|
||||
CONF_BAUDRATE: 115200,
|
||||
@ -261,75 +337,50 @@ async def test_zigate_via_zeroconf(setup_entry_mock, hass: HomeAssistant) -> Non
|
||||
}
|
||||
|
||||
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
@patch(f"bellows.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
||||
async def test_efr32_via_zeroconf(hass: HomeAssistant) -> None:
|
||||
"""Test zeroconf flow -- efr32 radio detected."""
|
||||
async def test_zeroconf_discovery_bad_payload(hass: HomeAssistant) -> None:
|
||||
"""Test zeroconf flow with a bad payload."""
|
||||
service_info = zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="efr32._esphomelib._tcp.local.",
|
||||
name="efr32",
|
||||
hostname="some.hostname",
|
||||
name="any",
|
||||
port=1234,
|
||||
properties={},
|
||||
type="mock_type",
|
||||
properties={"radio_type": "some bogus radio"},
|
||||
type="_zigbee-coordinator._tcp.local.",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
assert flow["step_id"] == "confirm"
|
||||
|
||||
# Confirm discovery
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
flow["flow_id"], user_input={}
|
||||
)
|
||||
assert result1["step_id"] == "manual_port_config"
|
||||
|
||||
# Confirm port settings
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result2["type"] is FlowResultType.MENU
|
||||
assert result2["step_id"] == "choose_formation_strategy"
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result3["title"] == "socket://192.168.1.200:1234"
|
||||
assert result3["data"] == {
|
||||
CONF_DEVICE: {
|
||||
CONF_DEVICE_PATH: "socket://192.168.1.200:1234",
|
||||
CONF_BAUDRATE: 115200,
|
||||
CONF_FLOW_CONTROL: None,
|
||||
},
|
||||
CONF_RADIO_TYPE: "ezsp",
|
||||
}
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "invalid_zeroconf_data"
|
||||
|
||||
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
||||
async def test_discovery_via_zeroconf_ip_change_ignored(hass: HomeAssistant) -> None:
|
||||
async def test_legacy_zeroconf_discovery_ip_change_ignored(hass: HomeAssistant) -> None:
|
||||
"""Test zeroconf flow that was ignored gets updated."""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="tube_zb_gw_cc2652p2_poe",
|
||||
unique_id="tubeszb-cc2652-poe",
|
||||
source=config_entries.SOURCE_IGNORE,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
service_info = zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.22"),
|
||||
ip_addresses=[ip_address("192.168.1.22")],
|
||||
hostname="tube_zb_gw_cc2652p2_poe.local.",
|
||||
name="mock_name",
|
||||
port=6053,
|
||||
properties={"address": "tube_zb_gw_cc2652p2_poe.local"},
|
||||
type="mock_type",
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
hostname="tubeszb-cc2652-poe.local.",
|
||||
name="tubeszb-cc2652-poe._tubeszb._tcp.local.",
|
||||
port=6638,
|
||||
properties={
|
||||
"name": "TubeZB",
|
||||
"radio_type": "znp",
|
||||
"version": "1.0",
|
||||
"baud_rate": "115200",
|
||||
"data_flow_control": "software",
|
||||
},
|
||||
type="_tubeszb._tcp.local.",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||
@ -338,11 +389,13 @@ async def test_discovery_via_zeroconf_ip_change_ignored(hass: HomeAssistant) ->
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
assert entry.data[CONF_DEVICE] == {
|
||||
CONF_DEVICE_PATH: "socket://192.168.1.22:6638",
|
||||
CONF_DEVICE_PATH: "socket://192.168.1.200:6638",
|
||||
}
|
||||
|
||||
|
||||
async def test_discovery_confirm_final_abort_if_entries(hass: HomeAssistant) -> None:
|
||||
async def test_legacy_zeroconf_discovery_confirm_final_abort_if_entries(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test discovery aborts if ZHA was set up after the confirmation dialog is shown."""
|
||||
service_info = zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
@ -677,7 +730,7 @@ async def test_discovery_via_usb_zha_ignored_updates(hass: HomeAssistant) -> Non
|
||||
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
||||
async def test_discovery_already_setup(hass: HomeAssistant) -> None:
|
||||
async def test_legacy_zeroconf_discovery_already_setup(hass: HomeAssistant) -> None:
|
||||
"""Test zeroconf flow -- radio detected."""
|
||||
service_info = zeroconf.ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user