Skip to content

Commit

Permalink
Generic ZHA Zeroconf discovery (home-assistant#126294)
Browse files Browse the repository at this point in the history
  • Loading branch information
puddly authored Nov 21, 2024
1 parent e928697 commit 50fdbe9
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 129 deletions.
84 changes: 62 additions & 22 deletions homeassistant/components/zha/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,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 @@ -617,34 +626,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")

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

0 comments on commit 50fdbe9

Please sign in to comment.