mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 02:37:08 +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/"
|
REPAIR_MY_URL = "https://my.home-assistant.io/redirect/repairs/"
|
||||||
|
|
||||||
DEFAULT_ZHA_ZEROCONF_PORT = 6638
|
LEGACY_ZEROCONF_PORT = 6638
|
||||||
ESPHOME_API_PORT = 6053
|
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(
|
def _format_backup_choice(
|
||||||
@ -617,34 +626,65 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
|
|||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle zeroconf discovery."""
|
"""Handle zeroconf discovery."""
|
||||||
|
|
||||||
# Hostname is format: livingroom.local.
|
# Transform legacy zeroconf discovery into the new format
|
||||||
local_name = discovery_info.hostname[:-1]
|
if discovery_info.type != ZEROCONF_SERVICE_TYPE:
|
||||||
port = discovery_info.port or DEFAULT_ZHA_ZEROCONF_PORT
|
port = discovery_info.port or LEGACY_ZEROCONF_PORT
|
||||||
|
name = discovery_info.name
|
||||||
|
|
||||||
# Fix incorrect port for older TubesZB devices
|
# Fix incorrect port for older TubesZB devices
|
||||||
if "tube" in local_name and port == ESPHOME_API_PORT:
|
if "tube" in name and port == LEGACY_ZEROCONF_ESPHOME_API_PORT:
|
||||||
port = DEFAULT_ZHA_ZEROCONF_PORT
|
port = LEGACY_ZEROCONF_PORT
|
||||||
|
|
||||||
if "radio_type" in discovery_info.properties:
|
# Determine the radio type
|
||||||
self._radio_mgr.radio_type = self._radio_mgr.parse_radio_type(
|
if "radio_type" in discovery_info.properties:
|
||||||
discovery_info.properties["radio_type"]
|
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")
|
try:
|
||||||
device_path = f"socket://{discovery_info.host}:{port}"
|
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(
|
await self._set_unique_id_and_update_ignored_flow(
|
||||||
unique_id=node_name,
|
unique_id=discovery_props["serial_number"],
|
||||||
device_path=device_path,
|
device_path=device_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.context["title_placeholders"] = {CONF_NAME: node_name}
|
self.context["title_placeholders"] = {CONF_NAME: title}
|
||||||
self._title = device_path
|
self._title = title
|
||||||
self._radio_mgr.device_path = device_path
|
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()
|
return await self.async_step_confirm()
|
||||||
|
|
||||||
|
@ -130,6 +130,10 @@
|
|||||||
{
|
{
|
||||||
"type": "_czc._tcp.local.",
|
"type": "_czc._tcp.local.",
|
||||||
"name": "czc*"
|
"name": "czc*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "_zigbee-coordinator._tcp.local.",
|
||||||
|
"name": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,8 @@
|
|||||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||||
"not_zha_device": "This device is not a zha device",
|
"not_zha_device": "This device is not a zha device",
|
||||||
"usb_probe_failed": "Failed to probe the usb 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": {
|
"options": {
|
||||||
|
@ -872,6 +872,12 @@ ZEROCONF = {
|
|||||||
"name": "*zigate*",
|
"name": "*zigate*",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"_zigbee-coordinator._tcp.local.": [
|
||||||
|
{
|
||||||
|
"domain": "zha",
|
||||||
|
"name": "*",
|
||||||
|
},
|
||||||
|
],
|
||||||
"_zigstar_gw._tcp.local.": [
|
"_zigstar_gw._tcp.local.": [
|
||||||
{
|
{
|
||||||
"domain": "zha",
|
"domain": "zha",
|
||||||
|
@ -154,104 +154,180 @@ def com_port(device="/dev/ttyUSB1234") -> ListPortInfo:
|
|||||||
return port
|
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("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", 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."""
|
"""Test zeroconf flow -- radio detected."""
|
||||||
service_info = zeroconf.ZeroconfServiceInfo(
|
result_init = await hass.config_entries.flow.async_init(
|
||||||
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(
|
|
||||||
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||||
)
|
)
|
||||||
assert flow["step_id"] == "confirm"
|
assert result_init["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
|
# Confirm port settings
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result_confirm = await hass.config_entries.flow.async_configure(
|
||||||
result1["flow_id"], user_input={}
|
result_init["flow_id"], user_input={}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result2["type"] is FlowResultType.MENU
|
assert result_confirm["type"] is FlowResultType.MENU
|
||||||
assert result2["step_id"] == "choose_formation_strategy"
|
assert result_confirm["step_id"] == "choose_formation_strategy"
|
||||||
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result_form = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result_confirm["flow_id"],
|
||||||
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result_form["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result3["title"] == "socket://192.168.1.200:6638"
|
assert result_form["title"] == entry_name
|
||||||
assert result3["data"] == {
|
assert result_form["context"]["unique_id"] == unique_id
|
||||||
|
assert result_form["data"] == {
|
||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
CONF_BAUDRATE: 115200,
|
CONF_BAUDRATE: 115200,
|
||||||
CONF_FLOW_CONTROL: 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: radio_type.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
@patch(f"zigpy_zigate.{PROBE_FUNCTION_PATH}")
|
@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."""
|
"""Test zeroconf flow -- zigate radio detected."""
|
||||||
service_info = zeroconf.ZeroconfServiceInfo(
|
service_info = zeroconf.ZeroconfServiceInfo(
|
||||||
ip_address=ip_address("192.168.1.200"),
|
ip_address=ip_address("192.168.1.200"),
|
||||||
ip_addresses=[ip_address("192.168.1.200")],
|
ip_addresses=[ip_address("192.168.1.200")],
|
||||||
hostname="_zigate-zigbee-gateway._tcp.local.",
|
hostname="_zigate-zigbee-gateway.local.",
|
||||||
name="any",
|
name="some name._zigate-zigbee-gateway._tcp.local.",
|
||||||
port=1234,
|
port=1234,
|
||||||
properties={"radio_type": "zigate"},
|
properties={},
|
||||||
type="mock_type",
|
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
|
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||||
)
|
)
|
||||||
assert flow["step_id"] == "confirm"
|
assert result_init["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 the radio is deprecated
|
# Confirm the radio is deprecated
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result_confirm_deprecated = await hass.config_entries.flow.async_configure(
|
||||||
flow["flow_id"], user_input={}
|
result_init["flow_id"], user_input={}
|
||||||
)
|
)
|
||||||
assert result2["step_id"] == "verify_radio"
|
assert result_confirm_deprecated["step_id"] == "verify_radio"
|
||||||
assert "ZiGate" in result2["description_placeholders"]["name"]
|
assert "ZiGate" in result_confirm_deprecated["description_placeholders"]["name"]
|
||||||
|
|
||||||
# Confirm port settings
|
# Confirm port settings
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result_confirm = await hass.config_entries.flow.async_configure(
|
||||||
result1["flow_id"], user_input={}
|
result_confirm_deprecated["flow_id"], user_input={}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result3["type"] is FlowResultType.MENU
|
assert result_confirm["type"] is FlowResultType.MENU
|
||||||
assert result3["step_id"] == "choose_formation_strategy"
|
assert result_confirm["step_id"] == "choose_formation_strategy"
|
||||||
|
|
||||||
result4 = await hass.config_entries.flow.async_configure(
|
result_form = await hass.config_entries.flow.async_configure(
|
||||||
result3["flow_id"],
|
result_confirm["flow_id"],
|
||||||
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
assert result_form["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result4["title"] == "socket://192.168.1.200:1234"
|
assert result_form["title"] == "some name"
|
||||||
assert result4["data"] == {
|
assert result_form["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_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))
|
async def test_zeroconf_discovery_bad_payload(hass: HomeAssistant) -> None:
|
||||||
@patch(f"bellows.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
"""Test zeroconf flow with a bad payload."""
|
||||||
async def test_efr32_via_zeroconf(hass: HomeAssistant) -> None:
|
|
||||||
"""Test zeroconf flow -- efr32 radio detected."""
|
|
||||||
service_info = zeroconf.ZeroconfServiceInfo(
|
service_info = zeroconf.ZeroconfServiceInfo(
|
||||||
ip_address=ip_address("192.168.1.200"),
|
ip_address=ip_address("192.168.1.200"),
|
||||||
ip_addresses=[ip_address("192.168.1.200")],
|
ip_addresses=[ip_address("192.168.1.200")],
|
||||||
hostname="efr32._esphomelib._tcp.local.",
|
hostname="some.hostname",
|
||||||
name="efr32",
|
name="any",
|
||||||
port=1234,
|
port=1234,
|
||||||
properties={},
|
properties={"radio_type": "some bogus radio"},
|
||||||
type="mock_type",
|
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
|
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||||
)
|
)
|
||||||
assert flow["step_id"] == "confirm"
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "invalid_zeroconf_data"
|
||||||
# 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",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", 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."""
|
"""Test zeroconf flow that was ignored gets updated."""
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
unique_id="tube_zb_gw_cc2652p2_poe",
|
unique_id="tubeszb-cc2652-poe",
|
||||||
source=config_entries.SOURCE_IGNORE,
|
source=config_entries.SOURCE_IGNORE,
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
service_info = zeroconf.ZeroconfServiceInfo(
|
service_info = zeroconf.ZeroconfServiceInfo(
|
||||||
ip_address=ip_address("192.168.1.22"),
|
ip_address=ip_address("192.168.1.200"),
|
||||||
ip_addresses=[ip_address("192.168.1.22")],
|
ip_addresses=[ip_address("192.168.1.200")],
|
||||||
hostname="tube_zb_gw_cc2652p2_poe.local.",
|
hostname="tubeszb-cc2652-poe.local.",
|
||||||
name="mock_name",
|
name="tubeszb-cc2652-poe._tubeszb._tcp.local.",
|
||||||
port=6053,
|
port=6638,
|
||||||
properties={"address": "tube_zb_gw_cc2652p2_poe.local"},
|
properties={
|
||||||
type="mock_type",
|
"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(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info
|
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["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
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.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."""
|
"""Test discovery aborts if ZHA was set up after the confirmation dialog is shown."""
|
||||||
service_info = zeroconf.ZeroconfServiceInfo(
|
service_info = zeroconf.ZeroconfServiceInfo(
|
||||||
ip_address=ip_address("192.168.1.200"),
|
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("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", 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."""
|
"""Test zeroconf flow -- radio detected."""
|
||||||
service_info = zeroconf.ZeroconfServiceInfo(
|
service_info = zeroconf.ZeroconfServiceInfo(
|
||||||
ip_address=ip_address("192.168.1.200"),
|
ip_address=ip_address("192.168.1.200"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user