Add reconfigure for lamarzocco (#122160)

* add reconfigure

* fix strings, add to label

* Update homeassistant/components/lamarzocco/config_flow.py

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Update test_config_flow.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* ruff

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/125006/head
Josef Zweck 2024-09-08 12:04:35 +02:00 committed by GitHub
parent f5b754a382
commit c0492d4af4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 177 additions and 10 deletions

View File

@ -10,7 +10,10 @@ from lmcloud.exceptions import AuthFail, RequestNotSuccessful
from lmcloud.models import LaMarzoccoDeviceInfo
import voluptuous as vol
from homeassistant.components.bluetooth import BluetoothServiceInfo
from homeassistant.components.bluetooth import (
BluetoothServiceInfo,
async_discovered_service_info,
)
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
@ -53,6 +56,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
"""Initialize the config flow."""
self.reauth_entry: ConfigEntry | None = None
self.reconfigure_entry: ConfigEntry | None = None
self._config: dict[str, Any] = {}
self._fleet: dict[str, LaMarzoccoDeviceInfo] = {}
self._discovered: dict[str, str] = {}
@ -92,13 +96,9 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
if not errors:
if self.reauth_entry:
self.hass.config_entries.async_update_entry(
self.reauth_entry, data=data
return self.async_update_reload_and_abort(
self.reauth_entry, data=data, reason="reauth_successful"
)
await self.hass.config_entries.async_reload(
self.reauth_entry.entry_id
)
return self.async_abort(reason="reauth_successful")
if self._discovered:
if self._discovered[CONF_MACHINE] not in self._fleet:
errors["base"] = "machine_not_found"
@ -134,6 +134,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input:
if not self._discovered:
serial_number = user_input[CONF_MACHINE]
if self.reconfigure_entry is None:
await self.async_set_unique_id(serial_number)
self._abort_if_unique_id_configured()
else:
@ -153,6 +154,13 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
self._config[CONF_HOST] = user_input[CONF_HOST]
if not errors:
if self.reconfigure_entry:
for service_info in async_discovered_service_info(self.hass):
self._discovered[service_info.name] = service_info.address
if self._discovered:
return await self.async_step_bluetooth_selection()
return self.async_create_entry(
title=selected_device.name,
data={
@ -191,6 +199,45 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_bluetooth_selection(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle Bluetooth device selection."""
assert self.reconfigure_entry
if user_input is not None:
return self.async_update_reload_and_abort(
self.reconfigure_entry,
data={
**self._config,
CONF_MAC: user_input[CONF_MAC],
},
reason="reconfigure_successful",
)
bt_options = [
SelectOptionDict(
value=device_mac,
label=f"{device_name} ({device_mac})",
)
for device_name, device_mac in self._discovered.items()
]
return self.async_show_form(
step_id="bluetooth_selection",
data_schema=vol.Schema(
{
vol.Required(CONF_MAC): SelectSelector(
SelectSelectorConfig(
options=bt_options,
mode=SelectSelectorMode.DROPDOWN,
)
),
},
),
)
async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo
) -> ConfigFlowResult:
@ -240,6 +287,40 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
return await self.async_step_user(user_input)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reconfiguration of the config entry."""
self.reconfigure_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm reconfiguration of the device."""
assert self.reconfigure_entry
if not user_input:
return self.async_show_form(
step_id="reconfigure_confirm",
data_schema=vol.Schema(
{
vol.Required(
CONF_USERNAME,
default=self.reconfigure_entry.data[CONF_USERNAME],
): str,
vol.Required(
CONF_PASSWORD,
default=self.reconfigure_entry.data[CONF_PASSWORD],
): str,
}
),
)
return await self.async_step_user(user_input)
@staticmethod
@callback
def async_get_options_flow(

View File

@ -3,6 +3,7 @@
"flow_title": "La Marzocco Espresso {host}",
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
@ -21,6 +22,12 @@
"password": "Your password from the La Marzocco app"
}
},
"bluetooth_selection": {
"description": "Select your device from available Bluetooth devices.",
"data": {
"mac": "Bluetooth device"
}
},
"machine_selection": {
"description": "Select the machine you want to integrate. Set the \"IP\" to get access to shot time related sensors.",
"data": {
@ -39,6 +46,16 @@
"data_description": {
"password": "[%key:component::lamarzocco::config::step::user::data_description::password%]"
}
},
"reconfigure_confirm": {
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"username": "[%key:component::lamarzocco::config::step::user::data_description::username%]",
"password": "[%key:component::lamarzocco::config::step::user::data_description::password%]"
}
}
}
},

View File

@ -7,7 +7,12 @@ from lmcloud.models import LaMarzoccoDeviceInfo
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
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_RECONFIGURE,
SOURCE_USER,
ConfigEntryState,
)
from homeassistant.const import (
CONF_HOST,
CONF_MAC,
@ -259,6 +264,70 @@ async def test_reauth_flow(
assert mock_config_entry.data[CONF_PASSWORD] == "new_password"
async def test_reconfigure_flow(
hass: HomeAssistant,
mock_cloud_client: MagicMock,
mock_config_entry: MockConfigEntry,
mock_device_info: LaMarzoccoDeviceInfo,
) -> None:
"""Testing reconfgure flow."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_RECONFIGURE,
"unique_id": mock_config_entry.unique_id,
"entry_id": mock_config_entry.entry_id,
},
data=mock_config_entry.data,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure_confirm"
result2 = await __do_successful_user_step(hass, result, mock_cloud_client)
service_info = get_bluetooth_service_info(
mock_device_info.model, mock_device_info.serial_number
)
with (
patch(
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
return_value=True,
),
patch(
"homeassistant.components.lamarzocco.config_flow.async_discovered_service_info",
return_value=[service_info],
),
):
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
CONF_HOST: "192.168.1.1",
CONF_MACHINE: mock_device_info.serial_number,
},
)
await hass.async_block_till_done()
assert result3["type"] is FlowResultType.FORM
assert result3["step_id"] == "bluetooth_selection"
result4 = await hass.config_entries.flow.async_configure(
result3["flow_id"],
{CONF_MAC: service_info.address},
)
assert result4["type"] is FlowResultType.ABORT
assert result4["reason"] == "reconfigure_successful"
assert mock_config_entry.title == "My LaMarzocco"
assert mock_config_entry.data == {
**mock_config_entry.data,
CONF_MAC: service_info.address,
}
async def test_bluetooth_discovery(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,