mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add Bluetooth support to La Marzocco integration (#108287)
* 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>
This commit is contained in:
parent
e23943debf
commit
e5fa6f0176
@ -29,6 +29,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -8,8 +8,22 @@ from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
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,
|
||||
@ -18,7 +32,7 @@ from homeassistant.helpers.selector import (
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_MACHINE, DOMAIN
|
||||
from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -32,6 +46,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
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
|
||||
@ -47,6 +62,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data = {
|
||||
**data,
|
||||
**user_input,
|
||||
**self._discovered,
|
||||
}
|
||||
|
||||
lm = LaMarzoccoClient()
|
||||
@ -71,6 +87,18 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
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
|
||||
@ -93,9 +121,12 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Let user select machine to connect to."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input:
|
||||
serial_number = user_input[CONF_MACHINE]
|
||||
await self.async_set_unique_id(serial_number)
|
||||
self._abort_if_unique_id_configured()
|
||||
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):
|
||||
@ -141,6 +172,30 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
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:
|
||||
@ -165,3 +220,36 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
@ -5,3 +5,5 @@ from typing import Final
|
||||
DOMAIN: Final = "lamarzocco"
|
||||
|
||||
CONF_MACHINE: Final = "machine"
|
||||
|
||||
CONF_USE_BLUETOOTH = "use_bluetooth"
|
||||
|
@ -5,23 +5,32 @@ from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from bleak.backends.device import BLEDevice
|
||||
from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.const import BT_MODEL_NAMES
|
||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||
|
||||
from homeassistant.components.bluetooth import (
|
||||
async_ble_device_from_address,
|
||||
async_discovered_service_info,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_MACHINE, DOMAIN
|
||||
from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
NAME_PREFIXES = tuple(BT_MODEL_NAMES)
|
||||
|
||||
|
||||
class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Class to handle fetching data from the La Marzocco API centrally."""
|
||||
|
||||
@ -36,6 +45,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
self.local_connection_configured = (
|
||||
self.config_entry.data.get(CONF_HOST) is not None
|
||||
)
|
||||
self._use_bluetooth = False
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch data from API endpoint."""
|
||||
@ -80,6 +90,46 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
name="lm_websocket_task",
|
||||
)
|
||||
|
||||
# initialize Bluetooth
|
||||
if self.config_entry.options.get(CONF_USE_BLUETOOTH, True):
|
||||
|
||||
def bluetooth_configured() -> bool:
|
||||
return self.config_entry.data.get(
|
||||
CONF_MAC, ""
|
||||
) and self.config_entry.data.get(CONF_NAME, "")
|
||||
|
||||
if not bluetooth_configured():
|
||||
machine = self.config_entry.data[CONF_MACHINE]
|
||||
for discovery_info in async_discovered_service_info(self.hass):
|
||||
if (
|
||||
(name := discovery_info.name)
|
||||
and name.startswith(NAME_PREFIXES)
|
||||
and name.split("_")[1] == machine
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"Found Bluetooth device, configuring with Bluetooth"
|
||||
)
|
||||
# found a device, add MAC address to config entry
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.config_entry,
|
||||
data={
|
||||
**self.config_entry.data,
|
||||
CONF_MAC: discovery_info.address,
|
||||
CONF_NAME: discovery_info.name,
|
||||
},
|
||||
)
|
||||
break
|
||||
|
||||
if bluetooth_configured():
|
||||
# config entry contains BT config
|
||||
_LOGGER.debug("Initializing with known Bluetooth device")
|
||||
await self.lm.init_bluetooth_with_known_device(
|
||||
self.config_entry.data[CONF_USERNAME],
|
||||
self.config_entry.data.get(CONF_MAC, ""),
|
||||
self.config_entry.data.get(CONF_NAME, ""),
|
||||
)
|
||||
self._use_bluetooth = True
|
||||
|
||||
self.lm.initialized = True
|
||||
|
||||
async def _async_handle_request(
|
||||
@ -98,3 +148,15 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
except RequestNotSuccessful as ex:
|
||||
_LOGGER.debug(ex, exc_info=True)
|
||||
raise UpdateFailed("Querying API failed. Error: %s" % ex) from ex
|
||||
|
||||
def async_get_ble_device(self) -> BLEDevice | None:
|
||||
"""Get a Bleak Client for the machine."""
|
||||
# according to HA best practices, we should not reuse the same client
|
||||
# get a new BLE device from hass and init a new Bleak Client with it
|
||||
if not self._use_bluetooth:
|
||||
return None
|
||||
|
||||
return async_ble_device_from_address(
|
||||
self.hass,
|
||||
self.lm.lm_bluetooth.address,
|
||||
)
|
||||
|
@ -1,8 +1,23 @@
|
||||
{
|
||||
"domain": "lamarzocco",
|
||||
"name": "La Marzocco",
|
||||
"bluetooth": [
|
||||
{
|
||||
"local_name": "MICRA_*"
|
||||
},
|
||||
{
|
||||
"local_name": "MINI_*"
|
||||
},
|
||||
{
|
||||
"local_name": "GS3_*"
|
||||
},
|
||||
{
|
||||
"local_name": "GS3AV_*"
|
||||
}
|
||||
],
|
||||
"codeowners": ["@zweckj"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/lamarzocco",
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
|
@ -63,7 +63,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
|
||||
native_step=PRECISION_TENTHS,
|
||||
native_min_value=85,
|
||||
native_max_value=104,
|
||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_coffee_temp(temp),
|
||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_coffee_temp(
|
||||
temp, coordinator.async_get_ble_device()
|
||||
),
|
||||
native_value_fn=lambda lm: lm.current_status["coffee_set_temp"],
|
||||
),
|
||||
LaMarzoccoNumberEntityDescription(
|
||||
@ -74,7 +76,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
|
||||
native_step=PRECISION_WHOLE,
|
||||
native_min_value=126,
|
||||
native_max_value=131,
|
||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_steam_temp(int(temp)),
|
||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_steam_temp(
|
||||
int(temp), coordinator.async_get_ble_device()
|
||||
),
|
||||
native_value_fn=lambda lm: lm.current_status["steam_set_temp"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
in (
|
||||
|
@ -36,7 +36,7 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
||||
translation_key="steam_temp_select",
|
||||
options=["1", "2", "3"],
|
||||
select_option_fn=lambda coordinator, option: coordinator.lm.set_steam_level(
|
||||
int(option)
|
||||
int(option), coordinator.async_get_ble_device()
|
||||
),
|
||||
current_option_fn=lambda lm: lm.current_status["steam_level_set"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
|
@ -42,6 +42,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"title": "Update Configuration",
|
||||
"use_bluetooth": "Use Bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"brew_active": {
|
||||
|
@ -31,7 +31,9 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = (
|
||||
key="main",
|
||||
translation_key="main",
|
||||
name=None,
|
||||
control_fn=lambda coordinator, state: coordinator.lm.set_power(state),
|
||||
control_fn=lambda coordinator, state: coordinator.lm.set_power(
|
||||
state, coordinator.async_get_ble_device()
|
||||
),
|
||||
is_on_fn=lambda coordinator: coordinator.lm.current_status["power"],
|
||||
),
|
||||
LaMarzoccoSwitchEntityDescription(
|
||||
@ -47,7 +49,9 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = (
|
||||
LaMarzoccoSwitchEntityDescription(
|
||||
key="steam_boiler_enable",
|
||||
translation_key="steam_boiler",
|
||||
control_fn=lambda coordinator, state: coordinator.lm.set_steam(state),
|
||||
control_fn=lambda coordinator, state: coordinator.lm.set_steam(
|
||||
state, coordinator.async_get_ble_device()
|
||||
),
|
||||
is_on_fn=lambda coordinator: coordinator.lm.current_status[
|
||||
"steam_boiler_enable"
|
||||
],
|
||||
|
@ -266,6 +266,22 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [
|
||||
"domain": "keymitt_ble",
|
||||
"local_name": "mib*",
|
||||
},
|
||||
{
|
||||
"domain": "lamarzocco",
|
||||
"local_name": "MICRA_*",
|
||||
},
|
||||
{
|
||||
"domain": "lamarzocco",
|
||||
"local_name": "MINI_*",
|
||||
},
|
||||
{
|
||||
"domain": "lamarzocco",
|
||||
"local_name": "GS3_*",
|
||||
},
|
||||
{
|
||||
"domain": "lamarzocco",
|
||||
"local_name": "GS3AV_*",
|
||||
},
|
||||
{
|
||||
"domain": "ld2410_ble",
|
||||
"local_name": "HLK-LD2410B_*",
|
||||
|
@ -1,7 +1,10 @@
|
||||
"""Mock inputs for tests."""
|
||||
|
||||
from lmcloud.const import LaMarzoccoModel
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -15,6 +18,13 @@ PASSWORD_SELECTION = {
|
||||
|
||||
USER_INPUT = PASSWORD_SELECTION | {CONF_USERNAME: "username"}
|
||||
|
||||
MODEL_DICT = {
|
||||
LaMarzoccoModel.GS3_AV: ("GS01234", "GS3 AV"),
|
||||
LaMarzoccoModel.GS3_MP: ("GS01234", "GS3 MP"),
|
||||
LaMarzoccoModel.LINEA_MICRA: ("MR01234", "Linea Micra"),
|
||||
LaMarzoccoModel.LINEA_MINI: ("LM01234", "Linea Mini"),
|
||||
}
|
||||
|
||||
|
||||
async def async_init_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
@ -22,3 +32,24 @@ async def async_init_integration(
|
||||
"""Set up the La Marzocco integration for testing."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
def get_bluetooth_service_info(
|
||||
model: LaMarzoccoModel, serial: str
|
||||
) -> BluetoothServiceInfo:
|
||||
"""Return a mocked BluetoothServiceInfo."""
|
||||
if model in (LaMarzoccoModel.GS3_AV, LaMarzoccoModel.GS3_MP):
|
||||
name = f"GS3_{serial}"
|
||||
elif model == LaMarzoccoModel.LINEA_MINI:
|
||||
name = f"MINI_{serial}"
|
||||
elif model == LaMarzoccoModel.LINEA_MICRA:
|
||||
name = f"MICRA_{serial}"
|
||||
return BluetoothServiceInfo(
|
||||
name=name,
|
||||
address="aa:bb:cc:dd:ee:ff",
|
||||
rssi=-63,
|
||||
manufacturer_data={},
|
||||
service_data={},
|
||||
service_uuids=[],
|
||||
source="local",
|
||||
)
|
||||
|
@ -7,10 +7,10 @@ from lmcloud.const import LaMarzoccoModel
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.lamarzocco.const import CONF_MACHINE, DOMAIN
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import USER_INPUT, async_init_integration
|
||||
from . import MODEL_DICT, USER_INPUT, async_init_integration
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
@ -28,7 +28,12 @@ def mock_config_entry(
|
||||
title="My LaMarzocco",
|
||||
domain=DOMAIN,
|
||||
data=USER_INPUT
|
||||
| {CONF_MACHINE: mock_lamarzocco.serial_number, CONF_HOST: "host"},
|
||||
| {
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
CONF_HOST: "host",
|
||||
CONF_NAME: "name",
|
||||
CONF_MAC: "mac",
|
||||
},
|
||||
unique_id=mock_lamarzocco.serial_number,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
@ -58,25 +63,17 @@ def mock_lamarzocco(
|
||||
"""Return a mocked LM client."""
|
||||
model_name = device_fixture
|
||||
|
||||
if model_name == LaMarzoccoModel.GS3_AV:
|
||||
serial_number = "GS01234"
|
||||
true_model_name = "GS3 AV"
|
||||
elif model_name == LaMarzoccoModel.GS3_MP:
|
||||
serial_number = "GS01234"
|
||||
true_model_name = "GS3 MP"
|
||||
elif model_name == LaMarzoccoModel.LINEA_MICRA:
|
||||
serial_number = "MR01234"
|
||||
true_model_name = "Linea Micra"
|
||||
elif model_name == LaMarzoccoModel.LINEA_MINI:
|
||||
serial_number = "LM01234"
|
||||
true_model_name = "Linea Mini"
|
||||
(serial_number, true_model_name) = MODEL_DICT[model_name]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoClient",
|
||||
autospec=True,
|
||||
) as lamarzocco_mock, patch(
|
||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoClient",
|
||||
new=lamarzocco_mock,
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoClient",
|
||||
autospec=True,
|
||||
) as lamarzocco_mock,
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoClient",
|
||||
new=lamarzocco_mock,
|
||||
),
|
||||
):
|
||||
lamarzocco = lamarzocco_mock.return_value
|
||||
|
||||
@ -118,6 +115,9 @@ def mock_lamarzocco(
|
||||
|
||||
lamarzocco.lm_local_api.websocket_connect = websocket_connect_mock
|
||||
|
||||
lamarzocco.lm_bluetooth = MagicMock()
|
||||
lamarzocco.lm_bluetooth.address = "AA:BB:CC:DD:EE:FF"
|
||||
|
||||
yield lamarzocco
|
||||
|
||||
|
||||
@ -130,3 +130,8 @@ def remove_local_connection(
|
||||
del data[CONF_HOST]
|
||||
hass.config_entries.async_update_entry(mock_config_entry, data=data)
|
||||
return mock_config_entry
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_bluetooth(enable_bluetooth):
|
||||
"""Auto mock bluetooth."""
|
||||
|
@ -29,7 +29,7 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[-set_power]
|
||||
# name: test_switches[-set_power-args_on0-args_off0]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234',
|
||||
@ -42,7 +42,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[-set_power].1
|
||||
# name: test_switches[-set_power-args_on0-args_off0].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -75,7 +75,51 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_auto_on_off-set_auto_on_off_global]
|
||||
# name: test_switches[-set_power-kwargs_on0-kwargs_off0]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234',
|
||||
'icon': 'mdi:power',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.gs01234',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[-set_power-kwargs_on0-kwargs_off0].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.gs01234',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:power',
|
||||
'original_name': None,
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'GS01234_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-args_on1-args_off1]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Auto on/off',
|
||||
@ -88,7 +132,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_auto_on_off-set_auto_on_off_global].1
|
||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-args_on1-args_off1].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -121,7 +165,51 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_steam_boiler-set_steam]
|
||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-kwargs_on1-kwargs_off1]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Auto on/off',
|
||||
'icon': 'mdi:alarm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.gs01234_auto_on_off',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-kwargs_on1-kwargs_off1].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.gs01234_auto_on_off',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:alarm',
|
||||
'original_name': 'Auto on/off',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'auto_on_off',
|
||||
'unique_id': 'GS01234_auto_on_off',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_steam_boiler-set_steam-args_on2-args_off2]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Steam boiler',
|
||||
@ -134,7 +222,53 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_steam_boiler-set_steam].1
|
||||
# name: test_switches[_steam_boiler-set_steam-args_on2-args_off2].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.gs01234_steam_boiler',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Steam boiler',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'steam_boiler',
|
||||
'unique_id': 'GS01234_steam_boiler_enable',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_steam_boiler-set_steam-kwargs_on2-kwargs_off2]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Steam boiler',
|
||||
'icon': 'mdi:water-boiler',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.gs01234_steam_boiler',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_steam_boiler-set_steam-kwargs_on2-kwargs_off2].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
|
@ -5,13 +5,17 @@ from unittest.mock import MagicMock
|
||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.lamarzocco.const import CONF_MACHINE, DOMAIN
|
||||
from homeassistant.components.lamarzocco.const import (
|
||||
CONF_MACHINE,
|
||||
CONF_USE_BLUETOOTH,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResult, FlowResultType
|
||||
|
||||
from . import USER_INPUT
|
||||
from . import USER_INPUT, async_init_integration, get_bluetooth_service_info
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -233,3 +237,131 @@ async def test_reauth_flow(
|
||||
assert result2["reason"] == "reauth_successful"
|
||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
||||
assert mock_config_entry.data[CONF_PASSWORD] == "new_password"
|
||||
|
||||
|
||||
async def test_bluetooth_discovery(
|
||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
||||
) -> None:
|
||||
"""Test bluetooth discovery."""
|
||||
service_info = get_bluetooth_service_info(
|
||||
mock_lamarzocco.model_name, mock_lamarzocco.serial_number
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "machine_selection"
|
||||
|
||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == FlowResultType.CREATE_ENTRY
|
||||
|
||||
assert result3["title"] == mock_lamarzocco.serial_number
|
||||
assert result3["data"] == {
|
||||
**USER_INPUT,
|
||||
CONF_HOST: "192.168.1.1",
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
CONF_NAME: service_info.name,
|
||||
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
||||
}
|
||||
|
||||
assert len(mock_lamarzocco.check_local_connection.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_bluetooth_discovery_errors(
|
||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
||||
) -> None:
|
||||
"""Test bluetooth discovery errors."""
|
||||
service_info = get_bluetooth_service_info(
|
||||
mock_lamarzocco.model_name, mock_lamarzocco.serial_number
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_BLUETOOTH},
|
||||
data=service_info,
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
mock_lamarzocco.get_all_machines.return_value = [("GS98765", "GS3 MP")]
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "machine_not_found"}
|
||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
||||
|
||||
mock_lamarzocco.get_all_machines.return_value = [
|
||||
(mock_lamarzocco.serial_number, mock_lamarzocco.model_name)
|
||||
]
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "machine_selection"
|
||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 2
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == FlowResultType.CREATE_ENTRY
|
||||
|
||||
assert result3["title"] == mock_lamarzocco.serial_number
|
||||
assert result3["data"] == {
|
||||
**USER_INPUT,
|
||||
CONF_HOST: "192.168.1.1",
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
CONF_NAME: service_info.name,
|
||||
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
||||
}
|
||||
|
||||
|
||||
async def test_options_flow(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test options flow."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
assert mock_config_entry.state is config_entries.ConfigEntryState.LOADED
|
||||
|
||||
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result2 = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_USE_BLUETOOTH: False,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result2["data"] == {
|
||||
CONF_USE_BLUETOOTH: False,
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
"""Test initialization of lamarzocco."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||
|
||||
from homeassistant.components.lamarzocco.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.const import CONF_MAC, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import async_init_integration, get_bluetooth_service_info
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@ -17,8 +20,7 @@ async def test_load_unload_config_entry(
|
||||
mock_lamarzocco: MagicMock,
|
||||
) -> None:
|
||||
"""Test loading and unloading the integration."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
@ -36,8 +38,7 @@ async def test_config_entry_not_ready(
|
||||
"""Test the La Marzocco configuration entry not ready."""
|
||||
mock_lamarzocco.update_local_machine_status.side_effect = RequestNotSuccessful("")
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
assert len(mock_lamarzocco.update_local_machine_status.mock_calls) == 1
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
@ -50,8 +51,7 @@ async def test_invalid_auth(
|
||||
) -> None:
|
||||
"""Test auth error during setup."""
|
||||
mock_lamarzocco.update_local_machine_status.side_effect = AuthFail("")
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
assert len(mock_lamarzocco.update_local_machine_status.mock_calls) == 1
|
||||
@ -66,3 +66,29 @@ async def test_invalid_auth(
|
||||
assert "context" in flow
|
||||
assert flow["context"].get("source") == SOURCE_REAUTH
|
||||
assert flow["context"].get("entry_id") == mock_config_entry.entry_id
|
||||
|
||||
|
||||
async def test_bluetooth_is_set_from_discovery(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_lamarzocco: MagicMock,
|
||||
) -> None:
|
||||
"""Assert we're not searching for a new BT device when we already found one previously."""
|
||||
|
||||
# remove the bluetooth configuration from entry
|
||||
data = mock_config_entry.data.copy()
|
||||
del data[CONF_NAME]
|
||||
del data[CONF_MAC]
|
||||
hass.config_entries.async_update_entry(mock_config_entry, data=data)
|
||||
|
||||
service_info = get_bluetooth_service_info(
|
||||
mock_lamarzocco.model_name, mock_lamarzocco.serial_number
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.lamarzocco.coordinator.async_discovered_service_info",
|
||||
return_value=[service_info],
|
||||
):
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
mock_lamarzocco.init_bluetooth_with_known_device.assert_called_once()
|
||||
assert mock_config_entry.data[CONF_NAME] == service_info.name
|
||||
assert mock_config_entry.data[CONF_MAC] == service_info.address
|
||||
|
@ -53,7 +53,9 @@ async def test_coffee_boiler(
|
||||
)
|
||||
|
||||
assert len(mock_lamarzocco.set_coffee_temp.mock_calls) == 1
|
||||
mock_lamarzocco.set_coffee_temp.assert_called_once_with(temperature=95)
|
||||
mock_lamarzocco.set_coffee_temp.assert_called_once_with(
|
||||
temperature=95, ble_device=None
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -62,7 +64,12 @@ async def test_coffee_boiler(
|
||||
@pytest.mark.parametrize(
|
||||
("entity_name", "value", "func_name", "kwargs"),
|
||||
[
|
||||
("steam_target_temperature", 131, "set_steam_temp", {"temperature": 131}),
|
||||
(
|
||||
"steam_target_temperature",
|
||||
131,
|
||||
"set_steam_temp",
|
||||
{"temperature": 131, "ble_device": None},
|
||||
),
|
||||
("tea_water_duration", 15, "set_dose_hot_water", {"value": 15}),
|
||||
],
|
||||
)
|
||||
|
@ -50,7 +50,7 @@ async def test_steam_boiler_level(
|
||||
)
|
||||
|
||||
assert len(mock_lamarzocco.set_steam_level.mock_calls) == 1
|
||||
mock_lamarzocco.set_steam_level.assert_called_once_with(level=1)
|
||||
mock_lamarzocco.set_steam_level.assert_called_once_with(1, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -5,6 +5,7 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.lamarzocco.const import DOMAIN
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@ -14,15 +15,22 @@ from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_name", "method_name"),
|
||||
("entity_name", "method_name", "args_on", "args_off"),
|
||||
[
|
||||
("", "set_power"),
|
||||
("_auto_on_off", "set_auto_on_off_global"),
|
||||
("_steam_boiler", "set_steam"),
|
||||
("", "set_power", (True, None), (False, None)),
|
||||
(
|
||||
"_auto_on_off",
|
||||
"set_auto_on_off_global",
|
||||
(True,),
|
||||
(False,),
|
||||
),
|
||||
("_steam_boiler", "set_steam", (True, None), (False, None)),
|
||||
],
|
||||
)
|
||||
async def test_switches(
|
||||
@ -32,6 +40,8 @@ async def test_switches(
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_name: str,
|
||||
method_name: str,
|
||||
args_on: tuple,
|
||||
args_off: tuple,
|
||||
) -> None:
|
||||
"""Test the La Marzocco switches."""
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
@ -56,7 +66,7 @@ async def test_switches(
|
||||
)
|
||||
|
||||
assert len(control_fn.mock_calls) == 1
|
||||
control_fn.assert_called_once_with(False)
|
||||
control_fn.assert_called_once_with(*args_off)
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
@ -68,7 +78,7 @@ async def test_switches(
|
||||
)
|
||||
|
||||
assert len(control_fn.mock_calls) == 2
|
||||
control_fn.assert_called_with(True)
|
||||
control_fn.assert_called_with(*args_on)
|
||||
|
||||
|
||||
async def test_device(
|
||||
@ -90,3 +100,26 @@ async def test_device(
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
assert device == snapshot
|
||||
|
||||
|
||||
async def test_call_without_bluetooth_works(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that if not using bluetooth, the switch still works."""
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
coordinator = hass.data[DOMAIN][mock_config_entry.entry_id]
|
||||
coordinator._use_bluetooth = False
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{
|
||||
ATTR_ENTITY_ID: f"switch.{serial_number}_steam_boiler",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_lamarzocco.set_steam.mock_calls) == 1
|
||||
mock_lamarzocco.set_steam.assert_called_once_with(False, None)
|
||||
|
Loading…
x
Reference in New Issue
Block a user