* init * init tests * linting * checks * tests, linting * pylint * add tests * switch tests * add water heater tests * change icons * extra args cleanup * moar tests * services tests * remove extra platforms * test for unique id * back to single instance * add diagnostics * remove extra platforms * test for unique id * back to single instance * Add better connection management for Idasen Desk (#102135) * Return 'None' for light attributes when off instead of removing them (#101946) * Bump home-assistant-bluetooth to 1.10.4 (#102268) * Bump orjson to 3.9.9 (#102267) * Bump opower to 0.0.37 (#102265) * Bump Python-Roborock to 0.35.0 (#102275) * Add CodeQL CI Job (#102273) * Remove unused dsmr sensors (#102223) * rebase messed up conftest * more tests for init * add client to coveragerc * add client to coveragerc * next lmcloud version * strict typing * more typing * allow multiple machines * remove unneeded var * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/diagnostics.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/__init__.py Co-authored-by: Robert Resch <robert@resch.dev> * PR suggestions * remove base exception * Update manifest.json * update lmcloud * update lmcloud * remove ignore * selection bugfix for machines with space in name * bugfix temps * add options flow * send out full user input * remove options flow * split the tests to avoid timeouts * use selectoptionsdict for selection * removing rccoleman * improve test coverage to 100% * Update config_flow.py Co-authored-by: Robert Resch <robert@resch.dev> * Update config_flow.py Co-authored-by: Robert Resch <robert@resch.dev> * Update config_flow.py Co-authored-by: Robert Resch <robert@resch.dev> * autoselect cloud machine for discovered machine * move default values to 3rd party lib * bring property changes from lmcloud * moving things to lmcloud * move validation to method * move more things to lmcloud * remove unused const * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/__init__.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/__init__.py Co-authored-by: Robert Resch <robert@resch.dev> * remove callback from coordinator * remove waterheater, add switch * improvement to background task * next lmcloud * adapt to lib changes * Update homeassistant/components/lamarzocco/strings.json Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/strings.json Co-authored-by: Robert Resch <robert@resch.dev> * requested changes * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch <robert@resch.dev> * Update tests/components/lamarzocco/test_config_flow.py Co-authored-by: Robert Resch <robert@resch.dev> * Update tests/components/lamarzocco/test_config_flow.py Co-authored-by: Robert Resch <robert@resch.dev> * some requested changes * changes * requested changes * move steam boiler to controls * fix: remove entities from GS3MP model + tests * remove dataclass decorator * next lmcloud version * improvements * move reauth to user step * improve config flow * remove asserts in favor of runtimeerrors * undo conftest comment * make duc return none * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/lamarzocco/config_flow.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * remove diagnostics, changes * refine config flow * remove runtimeerrors in favor of asserts * move initialization of lm_client to coordinator * remove things from lmclient * remove lm_client * remove lm_client * bump lm version * correctly set initialized for tests * move exception handling inside init + tests * add test for switch without bluetooth on * bump lmcloud * pass httpx client to LMLocalAPI * add call function to reduce code * switch to snapshot testing * remove bluetooth * bump version * cleanup import * remove unused const * set correct integration_type * correct default selection in CF * reduce unnecessary tests by fixture change * use other json loads helpers * move prebrew/infusion to select entity * bump lmcloud * Update coordinator.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update coordinator.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update coordinator.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update entity.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update entity.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * requested feedback * step description, bump lmcloud * create init integration functino * revert * ruff * remove leftover BT test * make main switch main entity * bump lmcloud * re-add bluetooth * improve * bump firmware (again) * correct test * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/lamarzocco/strings.json Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * separate device test * add BT to entites * fix import * docstring * minor * fix rebase * get device from discovered devices * tweak * change tests * switch to dict * switch to options * fix * fix --------- Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com> Co-authored-by: Paul Bottein <paul.bottein@gmail.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: tronikos <tronikos@users.noreply.github.com> Co-authored-by: Luke Lashley <conway220@gmail.com> Co-authored-by: Franck Nijhof <git@frenck.dev> Co-authored-by: dupondje <jean-louis@dupond.be> Co-authored-by: Robert Resch <robert@resch.dev> Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
255 lines
8.1 KiB
Python
255 lines
8.1 KiB
Python
"""Config flow for La Marzocco integration."""
|
|
|
|
from collections.abc import Mapping
|
|
import logging
|
|
from typing import Any
|
|
|
|
from lmcloud import LMCloud as LaMarzoccoClient
|
|
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.bluetooth import BluetoothServiceInfo
|
|
from homeassistant.config_entries import (
|
|
ConfigEntry,
|
|
ConfigFlow,
|
|
ConfigFlowResult,
|
|
OptionsFlow,
|
|
OptionsFlowWithConfigEntry,
|
|
)
|
|
from homeassistant.const import (
|
|
CONF_HOST,
|
|
CONF_MAC,
|
|
CONF_NAME,
|
|
CONF_PASSWORD,
|
|
CONF_USERNAME,
|
|
)
|
|
from homeassistant.core import callback
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.selector import (
|
|
SelectOptionDict,
|
|
SelectSelector,
|
|
SelectSelectorConfig,
|
|
SelectSelectorMode,
|
|
)
|
|
|
|
from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
"""Handle a config flow for La Marzocco."""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize the config flow."""
|
|
|
|
self.reauth_entry: ConfigEntry | None = None
|
|
self._config: dict[str, Any] = {}
|
|
self._machines: list[tuple[str, str]] = []
|
|
self._discovered: dict[str, str] = {}
|
|
|
|
async def async_step_user(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Handle the initial step."""
|
|
|
|
errors = {}
|
|
|
|
if user_input:
|
|
data: dict[str, Any] = {}
|
|
if self.reauth_entry:
|
|
data = dict(self.reauth_entry.data)
|
|
data = {
|
|
**data,
|
|
**user_input,
|
|
**self._discovered,
|
|
}
|
|
|
|
lm = LaMarzoccoClient()
|
|
try:
|
|
self._machines = await lm.get_all_machines(data)
|
|
except AuthFail:
|
|
_LOGGER.debug("Server rejected login credentials")
|
|
errors["base"] = "invalid_auth"
|
|
except RequestNotSuccessful as exc:
|
|
_LOGGER.error("Error connecting to server: %s", exc)
|
|
errors["base"] = "cannot_connect"
|
|
else:
|
|
if not self._machines:
|
|
errors["base"] = "no_machines"
|
|
|
|
if not errors:
|
|
if self.reauth_entry:
|
|
self.hass.config_entries.async_update_entry(
|
|
self.reauth_entry, data=data
|
|
)
|
|
await self.hass.config_entries.async_reload(
|
|
self.reauth_entry.entry_id
|
|
)
|
|
return self.async_abort(reason="reauth_successful")
|
|
if self._discovered:
|
|
serials = [machine[0] for machine in self._machines]
|
|
if self._discovered[CONF_MACHINE] not in serials:
|
|
errors["base"] = "machine_not_found"
|
|
else:
|
|
self._config = data
|
|
return self.async_show_form(
|
|
step_id="machine_selection",
|
|
data_schema=vol.Schema(
|
|
{vol.Optional(CONF_HOST): cv.string}
|
|
),
|
|
)
|
|
|
|
if not errors:
|
|
self._config = data
|
|
return await self.async_step_machine_selection()
|
|
|
|
return self.async_show_form(
|
|
step_id="user",
|
|
data_schema=vol.Schema(
|
|
{
|
|
vol.Required(CONF_USERNAME): str,
|
|
vol.Required(CONF_PASSWORD): str,
|
|
}
|
|
),
|
|
errors=errors,
|
|
)
|
|
|
|
async def async_step_machine_selection(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Let user select machine to connect to."""
|
|
errors: dict[str, str] = {}
|
|
if user_input:
|
|
if not self._discovered:
|
|
serial_number = user_input[CONF_MACHINE]
|
|
await self.async_set_unique_id(serial_number)
|
|
self._abort_if_unique_id_configured()
|
|
else:
|
|
serial_number = self._discovered[CONF_MACHINE]
|
|
|
|
# validate local connection if host is provided
|
|
if user_input.get(CONF_HOST):
|
|
lm = LaMarzoccoClient()
|
|
if not await lm.check_local_connection(
|
|
credentials=self._config,
|
|
host=user_input[CONF_HOST],
|
|
serial=serial_number,
|
|
):
|
|
errors[CONF_HOST] = "cannot_connect"
|
|
|
|
if not errors:
|
|
return self.async_create_entry(
|
|
title=serial_number,
|
|
data=self._config | user_input,
|
|
)
|
|
|
|
machine_options = [
|
|
SelectOptionDict(
|
|
value=serial_number,
|
|
label=f"{model_name} ({serial_number})",
|
|
)
|
|
for serial_number, model_name in self._machines
|
|
]
|
|
|
|
machine_selection_schema = vol.Schema(
|
|
{
|
|
vol.Required(
|
|
CONF_MACHINE, default=machine_options[0]["value"]
|
|
): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=machine_options,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
)
|
|
),
|
|
vol.Optional(CONF_HOST): cv.string,
|
|
}
|
|
)
|
|
|
|
return self.async_show_form(
|
|
step_id="machine_selection",
|
|
data_schema=machine_selection_schema,
|
|
errors=errors,
|
|
)
|
|
|
|
async def async_step_bluetooth(
|
|
self, discovery_info: BluetoothServiceInfo
|
|
) -> ConfigFlowResult:
|
|
"""Handle a flow initialized by discovery over Bluetooth."""
|
|
address = discovery_info.address
|
|
name = discovery_info.name
|
|
|
|
_LOGGER.debug(
|
|
"Discovered La Marzocco machine %s through Bluetooth at address %s",
|
|
name,
|
|
address,
|
|
)
|
|
|
|
self._discovered[CONF_NAME] = name
|
|
self._discovered[CONF_MAC] = address
|
|
|
|
serial = name.split("_")[1]
|
|
self._discovered[CONF_MACHINE] = serial
|
|
|
|
await self.async_set_unique_id(serial)
|
|
self._abort_if_unique_id_configured()
|
|
|
|
return await self.async_step_user()
|
|
|
|
async def async_step_reauth(
|
|
self, entry_data: Mapping[str, Any]
|
|
) -> ConfigFlowResult:
|
|
"""Perform reauth upon an API authentication error."""
|
|
self.reauth_entry = self.hass.config_entries.async_get_entry(
|
|
self.context["entry_id"]
|
|
)
|
|
return await self.async_step_reauth_confirm()
|
|
|
|
async def async_step_reauth_confirm(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Dialog that informs the user that reauth is required."""
|
|
if not user_input:
|
|
return self.async_show_form(
|
|
step_id="reauth_confirm",
|
|
data_schema=vol.Schema(
|
|
{
|
|
vol.Required(CONF_PASSWORD): str,
|
|
}
|
|
),
|
|
)
|
|
|
|
return await self.async_step_user(user_input)
|
|
|
|
@staticmethod
|
|
@callback
|
|
def async_get_options_flow(
|
|
config_entry: ConfigEntry,
|
|
) -> OptionsFlow:
|
|
"""Create the options flow."""
|
|
return LmOptionsFlowHandler(config_entry)
|
|
|
|
|
|
class LmOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
|
"""Handles options flow for the component."""
|
|
|
|
async def async_step_init(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Manage the options for the custom component."""
|
|
if user_input:
|
|
return self.async_create_entry(title="", data=user_input)
|
|
|
|
options_schema = vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_USE_BLUETOOTH,
|
|
default=self.options.get(CONF_USE_BLUETOOTH, True),
|
|
): cv.boolean,
|
|
}
|
|
)
|
|
|
|
return self.async_show_form(
|
|
step_id="init",
|
|
data_schema=options_schema,
|
|
)
|