Add DHCP discovery to lamarzocco (#129675)

* Add DHCP discovery to lamarzocco

* ensure serial is upper

* shorten pattern

* parametrize across models
This commit is contained in:
Josef Zweck 2024-11-03 09:44:35 +01:00 committed by GitHub
parent ed3376352d
commit eddab96a69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 109 additions and 4 deletions

View File

@ -14,6 +14,7 @@ from homeassistant.components.bluetooth import (
BluetoothServiceInfo, BluetoothServiceInfo,
async_discovered_service_info, async_discovered_service_info,
) )
from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.config_entries import ( from homeassistant.config_entries import (
SOURCE_REAUTH, SOURCE_REAUTH,
SOURCE_RECONFIGURE, SOURCE_RECONFIGURE,
@ -103,6 +104,15 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
errors["base"] = "machine_not_found" errors["base"] = "machine_not_found"
else: else:
self._config = data self._config = data
# if DHCP discovery was used, auto fill machine selection
if CONF_HOST in self._discovered:
return await self.async_step_machine_selection(
user_input={
CONF_HOST: self._discovered[CONF_HOST],
CONF_MACHINE: self._discovered[CONF_MACHINE],
}
)
# if Bluetooth discovery was used, only select host
return self.async_show_form( return self.async_show_form(
step_id="machine_selection", step_id="machine_selection",
data_schema=vol.Schema( data_schema=vol.Schema(
@ -258,6 +268,27 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
return await self.async_step_user() return await self.async_step_user()
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle discovery via dhcp."""
serial = discovery_info.hostname.upper()
await self.async_set_unique_id(serial)
self._abort_if_unique_id_configured()
_LOGGER.debug(
"Discovered La Marzocco machine %s through DHCP at address %s",
discovery_info.hostname,
discovery_info.ip,
)
self._discovered[CONF_MACHINE] = serial
self._discovered[CONF_HOST] = discovery_info.ip
return await self.async_step_user()
async def async_step_reauth( async def async_step_reauth(
self, entry_data: Mapping[str, Any] self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult: ) -> ConfigFlowResult:

View File

@ -18,6 +18,17 @@
"codeowners": ["@zweckj"], "codeowners": ["@zweckj"],
"config_flow": true, "config_flow": true,
"dependencies": ["bluetooth_adapters"], "dependencies": ["bluetooth_adapters"],
"dhcp": [
{
"hostname": "gs[0-9][0-9][0-9][0-9][0-9][0-9]"
},
{
"hostname": "lm[0-9][0-9][0-9][0-9][0-9][0-9]"
},
{
"hostname": "mr[0-9][0-9][0-9][0-9][0-9][0-9]"
}
],
"documentation": "https://www.home-assistant.io/integrations/lamarzocco", "documentation": "https://www.home-assistant.io/integrations/lamarzocco",
"integration_type": "device", "integration_type": "device",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",

View File

@ -276,6 +276,18 @@ DHCP: Final[list[dict[str, str | bool]]] = [
"hostname": "polisy*", "hostname": "polisy*",
"macaddress": "000DB9*", "macaddress": "000DB9*",
}, },
{
"domain": "lamarzocco",
"hostname": "gs[0-9][0-9][0-9][0-9][0-9][0-9]",
},
{
"domain": "lamarzocco",
"hostname": "lm[0-9][0-9][0-9][0-9][0-9][0-9]",
},
{
"domain": "lamarzocco",
"hostname": "mr[0-9][0-9][0-9][0-9][0-9][0-9]",
},
{ {
"domain": "lametric", "domain": "lametric",
"registered_devices": True, "registered_devices": True,

View File

@ -75,11 +75,11 @@ def device_fixture() -> MachineModel:
@pytest.fixture @pytest.fixture
def mock_device_info() -> LaMarzoccoDeviceInfo: def mock_device_info(device_fixture: MachineModel) -> LaMarzoccoDeviceInfo:
"""Return a mocked La Marzocco device info.""" """Return a mocked La Marzocco device info."""
return LaMarzoccoDeviceInfo( return LaMarzoccoDeviceInfo(
model=MachineModel.GS3_AV, model=device_fixture,
serial_number="GS01234", serial_number=SERIAL_DICT[device_fixture],
name="GS3", name="GS3",
communication_key="token", communication_key="token",
) )

View File

@ -2,13 +2,20 @@
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from lmcloud.const import MachineModel
from lmcloud.exceptions import AuthFail, RequestNotSuccessful from lmcloud.exceptions import AuthFail, RequestNotSuccessful
from lmcloud.models import LaMarzoccoDeviceInfo from lmcloud.models import LaMarzoccoDeviceInfo
import pytest import pytest
from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
from homeassistant.components.lamarzocco.const import CONF_USE_BLUETOOTH, DOMAIN from homeassistant.components.lamarzocco.const import CONF_USE_BLUETOOTH, DOMAIN
from homeassistant.config_entries import SOURCE_BLUETOOTH, SOURCE_USER, ConfigEntryState from homeassistant.config_entries import (
SOURCE_BLUETOOTH,
SOURCE_DHCP,
SOURCE_USER,
ConfigEntryState,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_MAC, CONF_MAC,
@ -435,6 +442,50 @@ async def test_bluetooth_discovery_errors(
} }
@pytest.mark.parametrize(
"device_fixture",
[MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI, MachineModel.GS3_AV],
)
async def test_dhcp_discovery(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock,
mock_device_info: LaMarzoccoDeviceInfo,
) -> None:
"""Test dhcp discovery."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_DHCP},
data=DhcpServiceInfo(
ip="192.168.1.42",
hostname=mock_lamarzocco.serial_number,
macaddress="aa:bb:cc:dd:ee:ff",
),
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
with patch(
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT,
)
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["data"] == {
**USER_INPUT,
CONF_HOST: "192.168.1.42",
CONF_MACHINE: mock_lamarzocco.serial_number,
CONF_MODEL: mock_device_info.model,
CONF_NAME: mock_device_info.name,
CONF_TOKEN: mock_device_info.communication_key,
}
async def test_options_flow( async def test_options_flow(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,