Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic ZHA Zeroconf discovery #126294

Merged
merged 14 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 62 additions & 22 deletions homeassistant/components/zha/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,17 @@

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(
Expand Down Expand Up @@ -615,34 +624,65 @@ async def async_step_zeroconf(
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""

# Hostname is format: livingroom.local.
local_name = discovery_info.hostname[:-1]
port = discovery_info.port or DEFAULT_ZHA_ZEROCONF_PORT

# Fix incorrect port for older TubesZB devices
if "tube" in local_name and port == ESPHOME_API_PORT:
port = DEFAULT_ZHA_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"]
# 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 name and port == LEGACY_ZEROCONF_ESPHOME_API_PORT:
port = LEGACY_ZEROCONF_PORT

# 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")
puddly marked this conversation as resolved.
Show resolved Hide resolved

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

Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/zha/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
{
"type": "_czc._tcp.local.",
"name": "czc*"
},
{
"type": "_zigbee-coordinator._tcp.local.",
"name": "*"
}
]
}
3 changes: 2 additions & 1 deletion homeassistant/components/zha/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/generated/zeroconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,12 @@
"name": "*zigate*",
},
],
"_zigbee-coordinator._tcp.local.": [
{
"domain": "zha",
"name": "*",
},
],
"_zigstar_gw._tcp.local.": [
{
"domain": "zha",
Expand Down
Loading
Loading