Allow integrations to contribute serial port scanning helpers#168660
Allow integrations to contribute serial port scanning helpers#168660puddly wants to merge 7 commits intohome-assistant:devfrom
Conversation
|
Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
|
Hey there @bdraco, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
|
Hey there @jesserockz, @kbx81, @bdraco, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
There was a problem hiding this comment.
Pull request overview
This PR extends the usb system integration with a registry mechanism so other integrations can contribute/override results returned by serial port scans (e.g., virtual serial proxies and enhanced metadata for known ports).
Changes:
- Add
usb.async_register_serial_port_scannerand routeusb.async_scan_serial_portsthrough the USB integration runtime to merge real scan results with contributed ports. - Register scanner contributors in ESPHome (serial proxies as
esphome://URIs) and Home Assistant Yellow (enrich/dev/ttyAMA1with metadata). - Add/adjust test coverage for contributed scanners and the new scan behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| homeassistant/components/usb/init.py | Adds scanner registration API and merges contributed serial ports into scans. |
| homeassistant/components/usb/utils.py | Removes the old standalone async_scan_serial_ports helper. |
| homeassistant/components/esphome/init.py | Registers an ESPHome serial proxy scanner that emits esphome:// ports. |
| homeassistant/components/esphome/manifest.json | Ensures ESPHome setup ordering includes usb. |
| homeassistant/components/homeassistant_yellow/init.py | Registers a Yellow scanner to add metadata to the built-in radio port. |
| homeassistant/components/homeassistant_yellow/manifest.json | Declares usb as a dependency for Yellow. |
| tests/components/usb/init.py | Updates patch target for serial scanning to the new module location. |
| tests/components/usb/test_init.py | Adds a test verifying contributed ports override/append to scan results. |
| tests/components/esphome/test_init.py | Adds tests for ESPHome scanner output and availability handling. |
| tests/components/homeassistant_yellow/test_init.py | Adds test asserting Yellow contributes/enriches its radio serial port. |
| self.initial_scan_done = False | ||
| self._initial_scan_callbacks: list[CALLBACK_TYPE] = [] | ||
| self._port_event_callbacks: set[PORT_EVENT_CALLBACK_TYPE] = set() | ||
| self._serial_port_scanners: set[SERIAL_PORT_SCANNER_TYPE] = set() |
There was a problem hiding this comment.
Make serial port scanner overrides deterministic by preserving registration order instead of using a set, since set iteration order makes the 'last writer wins' behavior undefined when multiple scanners contribute the same device path.
| for port in scanner(self.hass): | ||
| ports[port.device] = port |
There was a problem hiding this comment.
Isolate failures in contributed scanners by catching and logging exceptions per scanner so one misbehaving integration doesn't break serial port listing and discovery flows.
| for port in scanner(self.hass): | |
| ports[port.device] = port | |
| try: | |
| for port in scanner(self.hass): | |
| ports[port.device] = port | |
| except Exception: | |
| _LOGGER.exception( | |
| "Error while scanning serial ports with %s", scanner | |
| ) |
| noise_psk = entry.data.get(CONF_NOISE_PSK) or None | ||
|
|
||
| for index, proxy in enumerate(device_info.serial_proxies): | ||
| query = {"port_name": proxy.name} | ||
|
|
||
| if noise_psk is not None: | ||
| query["key"] = noise_psk | ||
|
|
||
| if password is not None: | ||
| query["password"] = password | ||
|
|
||
| ports.append( | ||
| SerialDevice( | ||
| device=str( | ||
| URL.build( | ||
| scheme="esphome", | ||
| host=client.connected_address, | ||
| port=client.port, | ||
| query=query, |
There was a problem hiding this comment.
Avoid embedding secrets (Noise PSK/password) and potentially sensitive connection info in the contributed esphome:// port URI, since it can be surfaced in the UI/logs and leak credentials; prefer an opaque identifier (e.g., entry_id + proxy name) that ESPHome can resolve to connection details internally.
| ), | ||
| SerialDevice( | ||
| device=f"esphome://10.0.0.2:6053/?port_name=uart0&key={quote(noise_psk, safe='')}", | ||
| serial_number="11:22:33:44:55:AA-0", | ||
| manufacturer="Espressif", | ||
| description="Noise ESP (uart0)", | ||
| ), | ||
| SerialDevice( | ||
| device="esphome://10.0.0.3:6053/?port_name=uart0&password=secret", | ||
| serial_number="11:22:33:44:55:AA-0", | ||
| manufacturer="Espressif", | ||
| description="Password ESP (uart0)", | ||
| ), | ||
| SerialDevice( | ||
| device="esphome://10.0.0.3:6053/?port_name=uart1&password=secret", | ||
| serial_number="11:22:33:44:55:AA-1", | ||
| manufacturer="Espressif", | ||
| description="Password ESP (uart1)", | ||
| ), | ||
| ] | ||
|
|
||
|
|
There was a problem hiding this comment.
Update the test expectations to avoid asserting that secrets are present in the serial port URI (e.g., password=secret / key=...), so the tests don't lock in credential leakage as the intended behavior.
| query=query, | ||
| ) | ||
| ), | ||
| serial_number=f"{device_info.mac_address}-{index}", |
There was a problem hiding this comment.
This is okay as long as we all agree that these are ephemeral. Otherwise they should include the port name instead
| device=RADIO_DEVICE, | ||
| serial_number=None, | ||
| manufacturer=MANUFACTURER, | ||
| description="Yellow Radio", |
There was a problem hiding this comment.
Should we say it's connected to zigbee chip?
Proposed change
This PR adds a new feature to the USB integration:
usb.async_register_serial_port_scanner. This function allows an integration to register itself to contribute serial devices to serial scans. I've added two integrations to the default implementation as the motivating use cases:esphome://URIs. Note: this simplified implementation will be replaced in a future PR with one that avoids sharing the device IP or noise PSK in URIs./dev/ttyAMA1serial port with one that has metadata.Screenshot:

Type of change
Additional information
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: