diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index b7c2eb8352e8..2ab5451e6d96 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -27,6 +27,7 @@ from .manager import ( DEVICE_CONFLICT_ISSUE_FORMAT, ESPHomeManager, async_create_api_client, + async_get_manufacturer_model, cleanup_instance, ) from .websocket_api import async_setup as async_setup_websocket_api @@ -52,14 +53,16 @@ def _async_scan_serial_ports( if device_info is None: continue + manufacturer, model = async_get_manufacturer_model(device_info) + ports.extend( SerialDevice( device=str(serial_proxy.build_url(entry.entry_id, proxy.name)), serial_number=( device_info.mac_address.replace(":", "") + "-" + slugify(proxy.name) ), - manufacturer=device_info.manufacturer, - description=f"{device_info.model} ({proxy.name})", + manufacturer=manufacturer, + description=f"{model} ({proxy.name})", ) for proxy in device_info.serial_proxies ) diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index ba51d7e693d3..2ea140594100 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -1190,6 +1190,15 @@ class ESPHomeManager: ) +@callback +def async_get_manufacturer_model(device_info: EsphomeDeviceInfo) -> tuple[str, str]: + """Return the manufacturer and model to use for a device.""" + if device_info.project_name: + project_name = device_info.project_name.split(".") + return project_name[0], project_name[1] + return device_info.manufacturer or "espressif", device_info.model + + @callback def _async_setup_device_registry( hass: HomeAssistant, entry: ESPHomeConfigEntry, entry_data: RuntimeEntryData @@ -1236,14 +1245,8 @@ def _async_setup_device_registry( ): configuration_url = f"homeassistant://app/{dashboard.addon_slug}" - manufacturer = "espressif" - if device_info.manufacturer: - manufacturer = device_info.manufacturer - model = device_info.model + manufacturer, model = async_get_manufacturer_model(device_info) if device_info.project_name: - project_name = device_info.project_name.split(".") - manufacturer = project_name[0] - model = project_name[1] sw_version = ( f"{device_info.project_version} (ESPHome {device_info.esphome_version})" ) diff --git a/tests/components/esphome/test_serial_proxy.py b/tests/components/esphome/test_serial_proxy.py index b774a49f877c..2bb28fdf9540 100644 --- a/tests/components/esphome/test_serial_proxy.py +++ b/tests/components/esphome/test_serial_proxy.py @@ -134,6 +134,65 @@ async def test_scan_serial_ports_happy_path( ] +@pytest.mark.usefixtures("mock_zeroconf") +async def test_scan_serial_ports_uses_project_info( + hass: HomeAssistant, + mock_client: APIClient, + mock_esphome_device: MockESPHomeDeviceType, +) -> None: + """Project information takes precedence over manufacturer and model.""" + device = await mock_esphome_device( + mock_client=mock_client, + device_info={ + "mac_address": "AA:BB:CC:DD:EE:FF", + "manufacturer": "Espressif", + "model": "ESP32", + "project_name": "vendor.gadget", + "serial_proxies": [ + SerialProxyInfo(name="uart0", port_type=SerialProxyPortType.TTL) + ], + }, + ) + + assert _async_scan_serial_ports(hass) == [ + SerialDevice( + device=str(serial_proxy.build_url(device.entry.entry_id, "uart0")), + serial_number="AABBCCDDEEFF-uart0", + manufacturer="vendor", + description="gadget (uart0)", + ) + ] + + +@pytest.mark.usefixtures("mock_zeroconf") +async def test_scan_serial_ports_defaults_manufacturer( + hass: HomeAssistant, + mock_client: APIClient, + mock_esphome_device: MockESPHomeDeviceType, +) -> None: + """A device without a manufacturer falls back to espressif.""" + device = await mock_esphome_device( + mock_client=mock_client, + device_info={ + "mac_address": "AA:BB:CC:DD:EE:FF", + "manufacturer": "", + "model": "ESP32", + "serial_proxies": [ + SerialProxyInfo(name="uart0", port_type=SerialProxyPortType.TTL) + ], + }, + ) + + assert _async_scan_serial_ports(hass) == [ + SerialDevice( + device=str(serial_proxy.build_url(device.entry.entry_id, "uart0")), + serial_number="AABBCCDDEEFF-uart0", + manufacturer="espressif", + description="ESP32 (uart0)", + ) + ] + + @pytest.mark.usefixtures("mock_zeroconf") async def test_scan_serial_ports_skips_unavailable( hass: HomeAssistant,