mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Name the Yellow-internal radio and multi-PAN addon as ZHA serial ports (#88208)
* Expose the Yellow-internal radio and multi-PAN addon as named serial ports * Remove the serial number if it isn't available * Use consistent names for the addon and Zigbee radio * Add `homeassistant_hardware` and `_yellow` as `after_dependencies` * Handle `hassio` not existing when listing serial ports * Add unit tests
This commit is contained in:
parent
70e1d14da0
commit
74696a3fac
@ -7,6 +7,7 @@ import json
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
|
from serial.tools.list_ports_common import ListPortInfo
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import zigpy.backups
|
import zigpy.backups
|
||||||
from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH
|
from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH
|
||||||
@ -14,9 +15,13 @@ from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH
|
|||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import onboarding, usb, zeroconf
|
from homeassistant.components import onboarding, usb, zeroconf
|
||||||
from homeassistant.components.file_upload import process_uploaded_file
|
from homeassistant.components.file_upload import process_uploaded_file
|
||||||
|
from homeassistant.components.hassio import AddonError, AddonState
|
||||||
|
from homeassistant.components.homeassistant_hardware import silabs_multiprotocol_addon
|
||||||
|
from homeassistant.components.homeassistant_yellow import hardware as yellow_hardware
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowHandler, FlowResult
|
from homeassistant.data_entry_flow import FlowHandler, FlowResult
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.selector import FileSelector, FileSelectorConfig
|
from homeassistant.helpers.selector import FileSelector, FileSelectorConfig
|
||||||
from homeassistant.util import dt
|
from homeassistant.util import dt
|
||||||
|
|
||||||
@ -72,6 +77,41 @@ def _format_backup_choice(
|
|||||||
return f"{dt.as_local(backup.backup_time).strftime('%c')} ({identifier})"
|
return f"{dt.as_local(backup.backup_time).strftime('%c')} ({identifier})"
|
||||||
|
|
||||||
|
|
||||||
|
async def list_serial_ports(hass: HomeAssistant) -> list[ListPortInfo]:
|
||||||
|
"""List all serial ports, including the Yellow radio and the multi-PAN addon."""
|
||||||
|
ports = await hass.async_add_executor_job(serial.tools.list_ports.comports)
|
||||||
|
|
||||||
|
# Add useful info to the Yellow's serial port selection screen
|
||||||
|
try:
|
||||||
|
yellow_hardware.async_info(hass)
|
||||||
|
except HomeAssistantError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
yellow_radio = next(p for p in ports if p.device == "/dev/ttyAMA1")
|
||||||
|
yellow_radio.description = "Yellow Zigbee module"
|
||||||
|
yellow_radio.manufacturer = "Nabu Casa"
|
||||||
|
|
||||||
|
# Present the multi-PAN addon as a setup option, if it's available
|
||||||
|
addon_manager = silabs_multiprotocol_addon.get_addon_manager(hass)
|
||||||
|
|
||||||
|
try:
|
||||||
|
addon_info = await addon_manager.async_get_addon_info()
|
||||||
|
except (AddonError, KeyError):
|
||||||
|
addon_info = None
|
||||||
|
|
||||||
|
if addon_info is not None and addon_info.state != AddonState.NOT_INSTALLED:
|
||||||
|
addon_port = ListPortInfo(
|
||||||
|
device=silabs_multiprotocol_addon.get_zigbee_socket(hass, addon_info),
|
||||||
|
skip_link_detection=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
addon_port.description = "Multiprotocol add-on"
|
||||||
|
addon_port.manufacturer = "Nabu Casa"
|
||||||
|
ports.append(addon_port)
|
||||||
|
|
||||||
|
return ports
|
||||||
|
|
||||||
|
|
||||||
class BaseZhaFlow(FlowHandler):
|
class BaseZhaFlow(FlowHandler):
|
||||||
"""Mixin for common ZHA flow steps and forms."""
|
"""Mixin for common ZHA flow steps and forms."""
|
||||||
|
|
||||||
@ -120,9 +160,9 @@ class BaseZhaFlow(FlowHandler):
|
|||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Choose a serial port."""
|
"""Choose a serial port."""
|
||||||
ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
|
ports = await list_serial_ports(self.hass)
|
||||||
list_of_ports = [
|
list_of_ports = [
|
||||||
f"{p}, s/n: {p.serial_number or 'n/a'}"
|
f"{p}{', s/n: ' + p.serial_number if p.serial_number else ''}"
|
||||||
+ (f" - {p.manufacturer}" if p.manufacturer else "")
|
+ (f" - {p.manufacturer}" if p.manufacturer else "")
|
||||||
for p in ports
|
for p in ports
|
||||||
]
|
]
|
||||||
@ -146,7 +186,7 @@ class BaseZhaFlow(FlowHandler):
|
|||||||
return await self.async_step_manual_pick_radio_type()
|
return await self.async_step_manual_pick_radio_type()
|
||||||
|
|
||||||
self._title = (
|
self._title = (
|
||||||
f"{port.description}, s/n: {port.serial_number or 'n/a'}"
|
f"{port.description}{', s/n: ' + port.serial_number if port.serial_number else ''}"
|
||||||
f" - {port.manufacturer}"
|
f" - {port.manufacturer}"
|
||||||
if port.manufacturer
|
if port.manufacturer
|
||||||
else ""
|
else ""
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
{
|
{
|
||||||
"domain": "zha",
|
"domain": "zha",
|
||||||
"name": "Zigbee Home Automation",
|
"name": "Zigbee Home Automation",
|
||||||
"after_dependencies": ["onboarding", "usb", "zeroconf"],
|
"after_dependencies": [
|
||||||
|
"onboarding",
|
||||||
|
"usb",
|
||||||
|
"zeroconf",
|
||||||
|
"homeassistant_hardware",
|
||||||
|
"homeassistant_yellow"
|
||||||
|
],
|
||||||
"codeowners": ["@dmulcahey", "@adminiuga", "@puddly"],
|
"codeowners": ["@dmulcahey", "@adminiuga", "@puddly"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["file_upload"],
|
"dependencies": ["file_upload"],
|
||||||
|
@ -15,6 +15,7 @@ import zigpy.types
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import ssdp, usb, zeroconf
|
from homeassistant.components import ssdp, usb, zeroconf
|
||||||
|
from homeassistant.components.hassio import AddonState
|
||||||
from homeassistant.components.ssdp import ATTR_UPNP_MANUFACTURER_URL, ATTR_UPNP_SERIAL
|
from homeassistant.components.ssdp import ATTR_UPNP_MANUFACTURER_URL, ATTR_UPNP_SERIAL
|
||||||
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 (
|
||||||
@ -1840,3 +1841,46 @@ async def test_options_flow_migration_reset_old_adapter(
|
|||||||
user_input={},
|
user_input={},
|
||||||
)
|
)
|
||||||
assert result4["step_id"] == "choose_serial_port"
|
assert result4["step_id"] == "choose_serial_port"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_port_yellow_port_name(hass: HomeAssistant) -> None:
|
||||||
|
"""Test config flow serial port name for Yellow Zigbee radio."""
|
||||||
|
port = com_port(device="/dev/ttyAMA1")
|
||||||
|
port.serial_number = None
|
||||||
|
port.manufacturer = None
|
||||||
|
port.description = None
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zha.config_flow.yellow_hardware.async_info"
|
||||||
|
), patch("serial.tools.list_ports.comports", MagicMock(return_value=[port])):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={CONF_SOURCE: SOURCE_USER},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
result["data_schema"].schema["path"].container[0]
|
||||||
|
== "/dev/ttyAMA1 - Yellow Zigbee module - Nabu Casa"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_port_multiprotocol_port_name(hass: HomeAssistant) -> None:
|
||||||
|
"""Test config flow serial port name for multiprotocol add-on."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hassio.addon_manager.AddonManager.async_get_addon_info"
|
||||||
|
) as async_get_addon_info, patch(
|
||||||
|
"serial.tools.list_ports.comports", MagicMock(return_value=[])
|
||||||
|
):
|
||||||
|
async_get_addon_info.return_value.state = AddonState.RUNNING
|
||||||
|
async_get_addon_info.return_value.hostname = "core-silabs-multiprotocol"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={CONF_SOURCE: SOURCE_USER},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
result["data_schema"].schema["path"].container[0]
|
||||||
|
== "socket://core-silabs-multiprotocol:9999 - Multiprotocol add-on - Nabu Casa"
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user