Prioritize the correct CP2102N serial port on macOS (#116461)

This commit is contained in:
puddly 2024-06-22 12:37:55 -04:00 committed by GitHub
parent 4d982a9227
commit bd65afa207
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 133 additions and 4 deletions

View File

@ -362,10 +362,33 @@ class USBDiscovery:
async def _async_process_ports(self, ports: list[ListPortInfo]) -> None:
"""Process each discovered port."""
for port in ports:
if port.vid is None and port.pid is None:
continue
await self._async_process_discovered_usb_device(usb_device_from_port(port))
usb_devices = [
usb_device_from_port(port)
for port in ports
if port.vid is not None or port.pid is not None
]
# CP2102N chips create *two* serial ports on macOS: `/dev/cu.usbserial-` and
# `/dev/cu.SLAB_USBtoUART*`. The former does not work and we should ignore them.
if sys.platform == "darwin":
silabs_serials = {
dev.serial_number
for dev in usb_devices
if dev.device.startswith("/dev/cu.SLAB_USBtoUART")
}
usb_devices = [
dev
for dev in usb_devices
if dev.serial_number not in silabs_serials
or (
dev.serial_number in silabs_serials
and dev.device.startswith("/dev/cu.SLAB_USBtoUART")
)
]
for usb_device in usb_devices:
await self._async_process_discovered_usb_device(usb_device)
async def _async_scan_serial(self) -> None:
"""Scan serial ports."""

View File

@ -1054,3 +1054,109 @@ async def test_resolve_serial_by_id(
assert len(mock_config_flow.mock_calls) == 1
assert mock_config_flow.mock_calls[0][1][0] == "test1"
assert mock_config_flow.mock_calls[0][2]["data"].device == "/dev/serial/by-id/bla"
@pytest.mark.parametrize(
"ports",
[
[
MagicMock(
device="/dev/cu.usbserial-2120",
vid=0x3039,
pid=0x3039,
serial_number=conbee_device.serial_number,
manufacturer=conbee_device.manufacturer,
description=conbee_device.description,
),
MagicMock(
device="/dev/cu.usbserial-1120",
vid=0x3039,
pid=0x3039,
serial_number=slae_sh_device.serial_number,
manufacturer=slae_sh_device.manufacturer,
description=slae_sh_device.description,
),
MagicMock(
device="/dev/cu.SLAB_USBtoUART",
vid=0x3039,
pid=0x3039,
serial_number=conbee_device.serial_number,
manufacturer=conbee_device.manufacturer,
description=conbee_device.description,
),
MagicMock(
device="/dev/cu.SLAB_USBtoUART2",
vid=0x3039,
pid=0x3039,
serial_number=slae_sh_device.serial_number,
manufacturer=slae_sh_device.manufacturer,
description=slae_sh_device.description,
),
],
[
MagicMock(
device="/dev/cu.SLAB_USBtoUART2",
vid=0x3039,
pid=0x3039,
serial_number=slae_sh_device.serial_number,
manufacturer=slae_sh_device.manufacturer,
description=slae_sh_device.description,
),
MagicMock(
device="/dev/cu.SLAB_USBtoUART",
vid=0x3039,
pid=0x3039,
serial_number=conbee_device.serial_number,
manufacturer=conbee_device.manufacturer,
description=conbee_device.description,
),
MagicMock(
device="/dev/cu.usbserial-1120",
vid=0x3039,
pid=0x3039,
serial_number=slae_sh_device.serial_number,
manufacturer=slae_sh_device.manufacturer,
description=slae_sh_device.description,
),
MagicMock(
device="/dev/cu.usbserial-2120",
vid=0x3039,
pid=0x3039,
serial_number=conbee_device.serial_number,
manufacturer=conbee_device.manufacturer,
description=conbee_device.description,
),
],
],
)
async def test_cp2102n_ordering_on_macos(
ports: list[MagicMock], hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test CP2102N ordering on macOS."""
new_usb = [
{"domain": "test1", "vid": "3039", "pid": "3039", "description": "*2652*"}
]
with (
patch("sys.platform", "darwin"),
patch("pyudev.Context", side_effect=ImportError),
patch("homeassistant.components.usb.async_get_usb", return_value=new_usb),
patch("homeassistant.components.usb.comports", return_value=ports),
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
):
assert await async_setup_component(hass, "usb", {"usb": {}})
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
ws_client = await hass_ws_client(hass)
await ws_client.send_json({"id": 1, "type": "usb/scan"})
response = await ws_client.receive_json()
assert response["success"]
await hass.async_block_till_done()
assert len(mock_config_flow.mock_calls) == 1
assert mock_config_flow.mock_calls[0][1][0] == "test1"
# We always use `cu.SLAB_USBtoUART`
assert mock_config_flow.mock_calls[0][2]["data"].device == "/dev/cu.SLAB_USBtoUART2"