Add DHCP discovery for known Elgato devices (#168002)

This commit is contained in:
Franck Nijhof
2026-04-11 22:26:22 +02:00
committed by GitHub
parent 2e648aca8b
commit 322dc2adeb
6 changed files with 118 additions and 8 deletions
+28 -1
View File
@@ -12,6 +12,8 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_MAC
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .const import DOMAIN
@@ -23,7 +25,6 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN):
VERSION = 1
host: str
port: int
serial_number: str
mac: str | None = None
@@ -70,6 +71,32 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a flow initiated by zeroconf."""
return self._async_create_entry()
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle DHCP discovery of a known Elgato device.
Only devices already configured (matched via ``registered_devices``)
reach this step. It is used to keep the stored host in sync with the
current IP address of the device.
"""
mac = format_mac(discovery_info.macaddress)
for entry in self._async_current_entries():
if (entry_mac := entry.data.get(CONF_MAC)) is None or format_mac(
entry_mac
) != mac:
continue
if entry.data[CONF_HOST] != discovery_info.ip:
self.hass.config_entries.async_update_entry(
entry,
data=entry.data | {CONF_HOST: discovery_info.ip},
)
self.hass.config_entries.async_schedule_reload(entry.entry_id)
return self.async_abort(reason="already_configured")
return self.async_abort(reason="no_devices_found")
@callback
def _async_show_setup_form(
self, errors: dict[str, str] | None = None
@@ -3,6 +3,11 @@
"name": "Elgato Light",
"codeowners": ["@frenck"],
"config_flow": true,
"dhcp": [
{
"registered_devices": true
}
],
"documentation": "https://www.home-assistant.io/integrations/elgato",
"integration_type": "device",
"iot_class": "local_polling",
@@ -39,11 +39,7 @@ rules:
# Gold
devices: done
diagnostics: done
discovery-update-info:
status: todo
comment: |
The integration doesn't update the device info based on DHCP discovery
of known existing devices.
discovery-update-info: done
discovery: done
docs-data-update: todo
docs-examples: todo
+2 -1
View File
@@ -2,7 +2,8 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
+4
View File
@@ -173,6 +173,10 @@ DHCP: Final[list[dict[str, str | bool]]] = [
"domain": "dlink",
"hostname": "dsp-w215",
},
{
"domain": "elgato",
"registered_devices": True,
},
{
"domain": "elkm1",
"registered_devices": True,
+78 -1
View File
@@ -7,10 +7,11 @@ from elgato import ElgatoConnectionError
import pytest
from homeassistant.components.elgato.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER, SOURCE_ZEROCONF
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_SOURCE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from tests.common import MockConfigEntry
@@ -254,3 +255,79 @@ async def test_zeroconf_during_onboarding(
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_elgato.info.mock_calls) == 1
assert len(mock_onboarding.mock_calls) == 1
@pytest.mark.usefixtures("mock_elgato")
async def test_dhcp_discovery_updates_host(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test DHCP discovery of a known device updates its stored host."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_DHCP},
data=DhcpServiceInfo(
hostname="elgato",
ip="127.0.0.42",
macaddress="aabbccddeeff",
),
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_HOST] == "127.0.0.42"
@pytest.mark.usefixtures("mock_elgato")
async def test_dhcp_discovery_same_host(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test DHCP discovery does nothing when the host is already up to date."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_DHCP},
data=DhcpServiceInfo(
hostname="elgato",
ip="127.0.0.1",
macaddress="aabbccddeeff",
),
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_HOST] == "127.0.0.1"
@pytest.mark.usefixtures("mock_elgato")
async def test_dhcp_discovery_no_match(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test DHCP discovery aborts when no matching entry is configured."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_DHCP},
data=DhcpServiceInfo(
hostname="elgato",
ip="127.0.0.42",
macaddress="001122334455",
),
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "no_devices_found"
assert mock_config_entry.data[CONF_HOST] == "127.0.0.1"