From bbb8a1bacc96f426ee1706ff84303fe03d6ee477 Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Thu, 17 Apr 2025 13:34:06 +0200 Subject: [PATCH] Migrate lamarzocco to pylamarzocco 2.0.0 (#142098) * Migrate lamarzocco to pylamarzocco 2.0.0 * bump manifest * Remove CONF_TOKEN * remove icons * Rename coordiantor * use none for token * Bump version * Move first get settings * remove sensor snapshots * Change iot_class from cloud_polling to cloud_push * Update integrations.json * Re-add release url * Remove extra icon, fix native step * fomat * Rename const * review comments * Update tests/components/lamarzocco/test_config_flow.py Co-authored-by: Joost Lekkerkerker * add unique id check --------- Co-authored-by: J. Nick Koston Co-authored-by: Joost Lekkerkerker --- .../components/lamarzocco/__init__.py | 150 ++- .../components/lamarzocco/binary_sensor.py | 70 +- .../components/lamarzocco/calendar.py | 59 +- .../components/lamarzocco/config_flow.py | 72 +- .../components/lamarzocco/coordinator.py | 80 +- .../components/lamarzocco/diagnostics.py | 24 +- homeassistant/components/lamarzocco/entity.py | 34 +- .../components/lamarzocco/icons.json | 45 - .../components/lamarzocco/manifest.json | 4 +- homeassistant/components/lamarzocco/number.py | 283 +---- homeassistant/components/lamarzocco/select.py | 127 +-- homeassistant/components/lamarzocco/sensor.py | 226 ---- .../components/lamarzocco/strings.json | 68 +- homeassistant/components/lamarzocco/switch.py | 83 +- homeassistant/components/lamarzocco/update.py | 26 +- homeassistant/generated/integrations.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/lamarzocco/__init__.py | 20 +- tests/components/lamarzocco/conftest.py | 126 +-- .../lamarzocco/fixtures/config.json | 198 ---- .../lamarzocco/fixtures/config_gs3.json | 377 ++++++ .../lamarzocco/fixtures/config_micra.json | 237 ++++ .../lamarzocco/fixtures/config_mini.json | 390 +++++-- .../lamarzocco/fixtures/schedule.json | 61 + .../lamarzocco/fixtures/settings.json | 50 + .../lamarzocco/fixtures/statistics.json | 26 - .../components/lamarzocco/fixtures/thing.json | 16 + .../snapshots/test_binary_sensor.ambr | 48 - .../snapshots/test_diagnostics.ambr | 861 ++++++++++++-- .../lamarzocco/snapshots/test_init.ambr | 39 +- .../lamarzocco/snapshots/test_number.ambr | 1006 +---------------- .../lamarzocco/snapshots/test_select.ambr | 182 ++- .../lamarzocco/snapshots/test_sensor.ambr | 521 --------- .../lamarzocco/snapshots/test_update.ambr | 10 +- .../lamarzocco/test_binary_sensor.py | 86 +- tests/components/lamarzocco/test_calendar.py | 7 +- .../components/lamarzocco/test_config_flow.py | 270 ++--- tests/components/lamarzocco/test_init.py | 225 ++-- tests/components/lamarzocco/test_number.py | 441 +------- tests/components/lamarzocco/test_select.py | 114 +- tests/components/lamarzocco/test_sensor.py | 138 --- tests/components/lamarzocco/test_switch.py | 18 +- tests/components/lamarzocco/test_update.py | 29 +- 44 files changed, 2442 insertions(+), 4411 deletions(-) delete mode 100644 homeassistant/components/lamarzocco/sensor.py delete mode 100644 tests/components/lamarzocco/fixtures/config.json create mode 100644 tests/components/lamarzocco/fixtures/config_gs3.json create mode 100644 tests/components/lamarzocco/fixtures/config_micra.json create mode 100644 tests/components/lamarzocco/fixtures/schedule.json create mode 100644 tests/components/lamarzocco/fixtures/settings.json delete mode 100644 tests/components/lamarzocco/fixtures/statistics.json create mode 100644 tests/components/lamarzocco/fixtures/thing.json delete mode 100644 tests/components/lamarzocco/snapshots/test_sensor.ambr delete mode 100644 tests/components/lamarzocco/test_sensor.py diff --git a/homeassistant/components/lamarzocco/__init__.py b/homeassistant/components/lamarzocco/__init__.py index 25c8fd1091e..b871f2eb23a 100644 --- a/homeassistant/components/lamarzocco/__init__.py +++ b/homeassistant/components/lamarzocco/__init__.py @@ -1,27 +1,27 @@ """The La Marzocco integration.""" +import asyncio import logging from packaging import version -from pylamarzocco.clients.bluetooth import LaMarzoccoBluetoothClient -from pylamarzocco.clients.cloud import LaMarzoccoCloudClient -from pylamarzocco.clients.local import LaMarzoccoLocalClient -from pylamarzocco.const import BT_MODEL_PREFIXES, FirmwareType -from pylamarzocco.devices.machine import LaMarzoccoMachine +from pylamarzocco import ( + LaMarzoccoBluetoothClient, + LaMarzoccoCloudClient, + LaMarzoccoMachine, +) +from pylamarzocco.const import FirmwareType from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful from homeassistant.components.bluetooth import async_discovered_service_info from homeassistant.const import ( - CONF_HOST, CONF_MAC, - CONF_MODEL, - CONF_NAME, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME, Platform, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.aiohttp_client import async_create_clientsession @@ -29,9 +29,9 @@ from .const import CONF_USE_BLUETOOTH, DOMAIN from .coordinator import ( LaMarzoccoConfigEntry, LaMarzoccoConfigUpdateCoordinator, - LaMarzoccoFirmwareUpdateCoordinator, LaMarzoccoRuntimeData, - LaMarzoccoStatisticsUpdateCoordinator, + LaMarzoccoScheduleUpdateCoordinator, + LaMarzoccoSettingsUpdateCoordinator, ) PLATFORMS = [ @@ -40,11 +40,12 @@ PLATFORMS = [ Platform.CALENDAR, Platform.NUMBER, Platform.SELECT, - Platform.SENSOR, Platform.SWITCH, Platform.UPDATE, ] +BT_MODEL_PREFIXES = ("MICRA", "MINI", "GS3") + _LOGGER = logging.getLogger(__name__) @@ -61,31 +62,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) - client=client, ) - # initialize the firmware update coordinator early to check the firmware version - firmware_device = LaMarzoccoMachine( - model=entry.data[CONF_MODEL], - serial_number=entry.unique_id, - name=entry.data[CONF_NAME], - cloud_client=cloud_client, - ) + try: + settings = await cloud_client.get_thing_settings(serial) + except AuthFail as ex: + raise ConfigEntryAuthFailed( + translation_domain=DOMAIN, translation_key="authentication_failed" + ) from ex + except RequestNotSuccessful as ex: + _LOGGER.debug(ex, exc_info=True) + raise ConfigEntryNotReady( + translation_domain=DOMAIN, translation_key="api_error" + ) from ex - firmware_coordinator = LaMarzoccoFirmwareUpdateCoordinator( - hass, entry, firmware_device - ) - await firmware_coordinator.async_config_entry_first_refresh() gateway_version = version.parse( - firmware_device.firmware[FirmwareType.GATEWAY].current_version + settings.firmwares[FirmwareType.GATEWAY].build_version ) - if gateway_version >= version.parse("v5.0.9"): - # remove host from config entry, it is not supported anymore - data = {k: v for k, v in entry.data.items() if k != CONF_HOST} - hass.config_entries.async_update_entry( - entry, - data=data, - ) - - elif gateway_version < version.parse("v3.4-rc5"): + if gateway_version < version.parse("v5.0.9"): # incompatible gateway firmware, create an issue ir.async_create_issue( hass, @@ -97,24 +90,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) - translation_placeholders={"gateway_version": str(gateway_version)}, ) - # initialize local API - local_client: LaMarzoccoLocalClient | None = None - if (host := entry.data.get(CONF_HOST)) is not None: - _LOGGER.debug("Initializing local API") - local_client = LaMarzoccoLocalClient( - host=host, - local_bearer=entry.data[CONF_TOKEN], - client=client, - ) - # initialize Bluetooth bluetooth_client: LaMarzoccoBluetoothClient | None = None - if entry.options.get(CONF_USE_BLUETOOTH, True): - - def bluetooth_configured() -> bool: - return entry.data.get(CONF_MAC, "") and entry.data.get(CONF_NAME, "") - - if not bluetooth_configured(): + if entry.options.get(CONF_USE_BLUETOOTH, True) and ( + token := settings.ble_auth_token + ): + if CONF_MAC not in entry.data: for discovery_info in async_discovered_service_info(hass): if ( (name := discovery_info.name) @@ -128,38 +109,43 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) - data={ **entry.data, CONF_MAC: discovery_info.address, - CONF_NAME: discovery_info.name, }, ) - break - if bluetooth_configured(): + if not entry.data[CONF_TOKEN]: + # update the token in the config entry + hass.config_entries.async_update_entry( + entry, + data={ + **entry.data, + CONF_TOKEN: token, + }, + ) + + if CONF_MAC in entry.data: _LOGGER.debug("Initializing Bluetooth device") bluetooth_client = LaMarzoccoBluetoothClient( - username=entry.data[CONF_USERNAME], - serial_number=serial, - token=entry.data[CONF_TOKEN], address_or_ble_device=entry.data[CONF_MAC], + ble_token=token, ) device = LaMarzoccoMachine( - model=entry.data[CONF_MODEL], serial_number=entry.unique_id, - name=entry.data[CONF_NAME], cloud_client=cloud_client, - local_client=local_client, bluetooth_client=bluetooth_client, ) coordinators = LaMarzoccoRuntimeData( - LaMarzoccoConfigUpdateCoordinator(hass, entry, device, local_client), - firmware_coordinator, - LaMarzoccoStatisticsUpdateCoordinator(hass, entry, device), + LaMarzoccoConfigUpdateCoordinator(hass, entry, device), + LaMarzoccoSettingsUpdateCoordinator(hass, entry, device), + LaMarzoccoScheduleUpdateCoordinator(hass, entry, device), ) - # API does not like concurrent requests, so no asyncio.gather here - await coordinators.config_coordinator.async_config_entry_first_refresh() - await coordinators.statistics_coordinator.async_config_entry_first_refresh() + await asyncio.gather( + coordinators.config_coordinator.async_config_entry_first_refresh(), + coordinators.settings_coordinator.async_config_entry_first_refresh(), + coordinators.schedule_coordinator.async_config_entry_first_refresh(), + ) entry.runtime_data = coordinators @@ -184,41 +170,45 @@ async def async_migrate_entry( hass: HomeAssistant, entry: LaMarzoccoConfigEntry ) -> bool: """Migrate config entry.""" - if entry.version > 2: + if entry.version > 3: # guard against downgrade from a future version return False if entry.version == 1: + _LOGGER.error( + "Migration from version 1 is no longer supported, please remove and re-add the integration" + ) + return False + + if entry.version == 2: cloud_client = LaMarzoccoCloudClient( username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], ) try: - fleet = await cloud_client.get_customer_fleet() + things = await cloud_client.list_things() except (AuthFail, RequestNotSuccessful) as exc: _LOGGER.error("Migration failed with error %s", exc) return False - - assert entry.unique_id is not None - device = fleet[entry.unique_id] - v2_data = { + v3_data = { CONF_USERNAME: entry.data[CONF_USERNAME], CONF_PASSWORD: entry.data[CONF_PASSWORD], - CONF_MODEL: device.model, - CONF_NAME: device.name, - CONF_TOKEN: device.communication_key, + CONF_TOKEN: next( + ( + thing.ble_auth_token + for thing in things + if thing.serial_number == entry.unique_id + ), + None, + ), } - - if CONF_HOST in entry.data: - v2_data[CONF_HOST] = entry.data[CONF_HOST] - if CONF_MAC in entry.data: - v2_data[CONF_MAC] = entry.data[CONF_MAC] - + v3_data[CONF_MAC] = entry.data[CONF_MAC] hass.config_entries.async_update_entry( entry, - data=v2_data, - version=2, + data=v3_data, + version=3, ) _LOGGER.debug("Migrated La Marzocco config entry to version 2") + return True diff --git a/homeassistant/components/lamarzocco/binary_sensor.py b/homeassistant/components/lamarzocco/binary_sensor.py index a98cddcda9c..2c45104859a 100644 --- a/homeassistant/components/lamarzocco/binary_sensor.py +++ b/homeassistant/components/lamarzocco/binary_sensor.py @@ -2,9 +2,10 @@ from collections.abc import Callable from dataclasses import dataclass +from typing import cast -from pylamarzocco.const import MachineModel -from pylamarzocco.models import LaMarzoccoMachineConfig +from pylamarzocco.const import BackFlushStatus, MachineState, WidgetType +from pylamarzocco.models import BackFlush, BaseWidgetOutput, MachineStatus from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -16,7 +17,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .coordinator import LaMarzoccoConfigEntry -from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity +from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription # Coordinator is used to centralize the data updates PARALLEL_UPDATES = 0 @@ -29,7 +30,7 @@ class LaMarzoccoBinarySensorEntityDescription( ): """Description of a La Marzocco binary sensor.""" - is_on_fn: Callable[[LaMarzoccoMachineConfig], bool | None] + is_on_fn: Callable[[dict[WidgetType, BaseWidgetOutput]], bool | None] ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = ( @@ -37,32 +38,30 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = ( key="water_tank", translation_key="water_tank", device_class=BinarySensorDeviceClass.PROBLEM, - is_on_fn=lambda config: not config.water_contact, + is_on_fn=lambda config: WidgetType.CM_NO_WATER in config, entity_category=EntityCategory.DIAGNOSTIC, - supported_fn=lambda coordinator: coordinator.local_connection_configured, ), LaMarzoccoBinarySensorEntityDescription( key="brew_active", translation_key="brew_active", device_class=BinarySensorDeviceClass.RUNNING, - is_on_fn=lambda config: config.brew_active, - available_fn=lambda device: device.websocket_connected, + is_on_fn=( + lambda config: cast( + MachineStatus, config[WidgetType.CM_MACHINE_STATUS] + ).status + is MachineState.BREWING + ), + available_fn=lambda device: device.websocket.connected, entity_category=EntityCategory.DIAGNOSTIC, ), LaMarzoccoBinarySensorEntityDescription( key="backflush_enabled", translation_key="backflush_enabled", device_class=BinarySensorDeviceClass.RUNNING, - is_on_fn=lambda config: config.backflush_enabled, - entity_category=EntityCategory.DIAGNOSTIC, - ), -) - -SCALE_ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = ( - LaMarzoccoBinarySensorEntityDescription( - key="connected", - device_class=BinarySensorDeviceClass.CONNECTIVITY, - is_on_fn=lambda config: config.scale.connected if config.scale else None, + is_on_fn=( + lambda config: cast(BackFlush, config[WidgetType.CM_BACK_FLUSH]).status + is BackFlushStatus.REQUESTED + ), entity_category=EntityCategory.DIAGNOSTIC, ), ) @@ -76,30 +75,11 @@ async def async_setup_entry( """Set up binary sensor entities.""" coordinator = entry.runtime_data.config_coordinator - entities = [ + async_add_entities( LaMarzoccoBinarySensorEntity(coordinator, description) for description in ENTITIES if description.supported_fn(coordinator) - ] - - if ( - coordinator.device.model in (MachineModel.LINEA_MINI, MachineModel.LINEA_MINI_R) - and coordinator.device.config.scale - ): - entities.extend( - LaMarzoccoScaleBinarySensorEntity(coordinator, description) - for description in SCALE_ENTITIES - ) - - def _async_add_new_scale() -> None: - async_add_entities( - LaMarzoccoScaleBinarySensorEntity(coordinator, description) - for description in SCALE_ENTITIES - ) - - coordinator.new_device_callback.append(_async_add_new_scale) - - async_add_entities(entities) + ) class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity): @@ -110,12 +90,6 @@ class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity): @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" - return self.entity_description.is_on_fn(self.coordinator.device.config) - - -class LaMarzoccoScaleBinarySensorEntity( - LaMarzoccoBinarySensorEntity, LaMarzoccScaleEntity -): - """Binary sensor for La Marzocco scales.""" - - entity_description: LaMarzoccoBinarySensorEntityDescription + return self.entity_description.is_on_fn( + self.coordinator.device.dashboard.config + ) diff --git a/homeassistant/components/lamarzocco/calendar.py b/homeassistant/components/lamarzocco/calendar.py index 4365bf56b2d..e4673372d0a 100644 --- a/homeassistant/components/lamarzocco/calendar.py +++ b/homeassistant/components/lamarzocco/calendar.py @@ -3,7 +3,7 @@ from collections.abc import Iterator from datetime import datetime, timedelta -from pylamarzocco.models import LaMarzoccoWakeUpSleepEntry +from pylamarzocco.const import WeekDay from homeassistant.components.calendar import CalendarEntity, CalendarEvent from homeassistant.core import HomeAssistant @@ -18,15 +18,15 @@ PARALLEL_UPDATES = 0 CALENDAR_KEY = "auto_on_off_schedule" -DAY_OF_WEEK = [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday", -] +WEEKDAY_TO_ENUM = { + 0: WeekDay.MONDAY, + 1: WeekDay.TUESDAY, + 2: WeekDay.WEDNESDAY, + 3: WeekDay.THURSDAY, + 4: WeekDay.FRIDAY, + 5: WeekDay.SATURDAY, + 6: WeekDay.SUNDAY, +} async def async_setup_entry( @@ -36,10 +36,12 @@ async def async_setup_entry( ) -> None: """Set up switch entities and services.""" - coordinator = entry.runtime_data.config_coordinator + coordinator = entry.runtime_data.schedule_coordinator + async_add_entities( - LaMarzoccoCalendarEntity(coordinator, CALENDAR_KEY, wake_up_sleep_entry) - for wake_up_sleep_entry in coordinator.device.config.wake_up_sleep_entries.values() + LaMarzoccoCalendarEntity(coordinator, CALENDAR_KEY, schedule.identifier) + for schedule in coordinator.device.schedule.smart_wake_up_sleep.schedules + if schedule.identifier ) @@ -52,12 +54,12 @@ class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity): self, coordinator: LaMarzoccoUpdateCoordinator, key: str, - wake_up_sleep_entry: LaMarzoccoWakeUpSleepEntry, + identifier: str, ) -> None: """Set up calendar.""" - super().__init__(coordinator, f"{key}_{wake_up_sleep_entry.entry_id}") - self.wake_up_sleep_entry = wake_up_sleep_entry - self._attr_translation_placeholders = {"id": wake_up_sleep_entry.entry_id} + super().__init__(coordinator, f"{key}_{identifier}") + self._identifier = identifier + self._attr_translation_placeholders = {"id": identifier} @property def event(self) -> CalendarEvent | None: @@ -112,24 +114,31 @@ class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity): def _async_get_calendar_event(self, date: datetime) -> CalendarEvent | None: """Return calendar event for a given weekday.""" + schedule_entry = ( + self.coordinator.device.schedule.smart_wake_up_sleep.schedules_dict[ + self._identifier + ] + ) # check first if auto/on off is turned on in general - if not self.wake_up_sleep_entry.enabled: + if not schedule_entry.enabled: return None # parse the schedule for the day - if DAY_OF_WEEK[date.weekday()] not in self.wake_up_sleep_entry.days: + if WEEKDAY_TO_ENUM[date.weekday()] not in schedule_entry.days: return None - hour_on, minute_on = self.wake_up_sleep_entry.time_on.split(":") - hour_off, minute_off = self.wake_up_sleep_entry.time_off.split(":") + hour_on = schedule_entry.on_time_minutes // 60 + minute_on = schedule_entry.on_time_minutes % 60 + hour_off = schedule_entry.off_time_minutes // 60 + minute_off = schedule_entry.off_time_minutes % 60 - # if off time is 24:00, then it means the off time is the next day - # only for legacy schedules day_offset = 0 - if hour_off == "24": + if hour_off == 24: + # if the machine is scheduled to turn off at midnight, we need to + # set the end date to the next day day_offset = 1 - hour_off = "0" + hour_off = 0 end_date = date.replace( hour=int(hour_off), diff --git a/homeassistant/components/lamarzocco/config_flow.py b/homeassistant/components/lamarzocco/config_flow.py index 87a9824423a..6808fc3e419 100644 --- a/homeassistant/components/lamarzocco/config_flow.py +++ b/homeassistant/components/lamarzocco/config_flow.py @@ -7,10 +7,9 @@ import logging from typing import Any from aiohttp import ClientSession -from pylamarzocco.clients.cloud import LaMarzoccoCloudClient -from pylamarzocco.clients.local import LaMarzoccoLocalClient +from pylamarzocco import LaMarzoccoCloudClient from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoDeviceInfo +from pylamarzocco.models import Thing import voluptuous as vol from homeassistant.components.bluetooth import ( @@ -26,9 +25,7 @@ from homeassistant.config_entries import ( ) from homeassistant.const import ( CONF_ADDRESS, - CONF_HOST, CONF_MAC, - CONF_MODEL, CONF_NAME, CONF_PASSWORD, CONF_TOKEN, @@ -59,14 +56,14 @@ _LOGGER = logging.getLogger(__name__) class LmConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for La Marzocco.""" - VERSION = 2 + VERSION = 3 _client: ClientSession def __init__(self) -> None: """Initialize the config flow.""" self._config: dict[str, Any] = {} - self._fleet: dict[str, LaMarzoccoDeviceInfo] = {} + self._things: dict[str, Thing] = {} self._discovered: dict[str, str] = {} async def async_step_user( @@ -83,7 +80,6 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): data = { **data, **user_input, - **self._discovered, } self._client = async_create_clientsession(self.hass) @@ -93,7 +89,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): client=self._client, ) try: - self._fleet = await cloud_client.get_customer_fleet() + things = await cloud_client.list_things() except AuthFail: _LOGGER.debug("Server rejected login credentials") errors["base"] = "invalid_auth" @@ -101,37 +97,30 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): _LOGGER.error("Error connecting to server: %s", exc) errors["base"] = "cannot_connect" else: - if not self._fleet: + self._things = {thing.serial_number: thing for thing in things} + if not self._things: errors["base"] = "no_machines" if not errors: + self._config = data if self.source == SOURCE_REAUTH: return self.async_update_reload_and_abort( self._get_reauth_entry(), data=data ) if self._discovered: - if self._discovered[CONF_MACHINE] not in self._fleet: + if self._discovered[CONF_MACHINE] not in self._things: errors["base"] = "machine_not_found" else: - 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( - step_id="machine_selection", - data_schema=vol.Schema( - {vol.Optional(CONF_HOST): cv.string} - ), - ) + # store discovered connection address + if CONF_MAC in self._discovered: + self._config[CONF_MAC] = self._discovered[CONF_MAC] + if CONF_ADDRESS in self._discovered: + self._config[CONF_ADDRESS] = self._discovered[CONF_ADDRESS] + return await self.async_step_machine_selection( + user_input={CONF_MACHINE: self._discovered[CONF_MACHINE]} + ) if not errors: - self._config = data return await self.async_step_machine_selection() placeholders: dict[str, str] | None = None @@ -175,18 +164,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): else: serial_number = self._discovered[CONF_MACHINE] - selected_device = self._fleet[serial_number] - - # validate local connection if host is provided - if user_input.get(CONF_HOST): - if not await LaMarzoccoLocalClient.validate_connection( - client=self._client, - host=user_input[CONF_HOST], - token=selected_device.communication_key, - ): - errors[CONF_HOST] = "cannot_connect" - else: - self._config[CONF_HOST] = user_input[CONF_HOST] + selected_device = self._things[serial_number] if not errors: if self.source == SOURCE_RECONFIGURE: @@ -200,18 +178,16 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): title=selected_device.name, data={ **self._config, - CONF_NAME: selected_device.name, - CONF_MODEL: selected_device.model, - CONF_TOKEN: selected_device.communication_key, + CONF_TOKEN: self._things[serial_number].ble_auth_token, }, ) machine_options = [ SelectOptionDict( - value=device.serial_number, - label=f"{device.model} ({device.serial_number})", + value=thing.serial_number, + label=f"{thing.name} ({thing.serial_number})", ) - for device in self._fleet.values() + for thing in self._things.values() ] machine_selection_schema = vol.Schema( @@ -224,7 +200,6 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): mode=SelectSelectorMode.DROPDOWN, ) ), - vol.Optional(CONF_HOST): cv.string, } ) @@ -304,7 +279,6 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(serial) self._abort_if_unique_id_configured( updates={ - CONF_HOST: discovery_info.ip, CONF_ADDRESS: discovery_info.macaddress, } ) @@ -316,8 +290,8 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): discovery_info.ip, ) + self._discovered[CONF_NAME] = discovery_info.hostname self._discovered[CONF_MACHINE] = serial - self._discovered[CONF_HOST] = discovery_info.ip self._discovered[CONF_ADDRESS] = discovery_info.macaddress return await self.async_step_user() diff --git a/homeassistant/components/lamarzocco/coordinator.py b/homeassistant/components/lamarzocco/coordinator.py index dddca6565e4..a8b3d9d0ee7 100644 --- a/homeassistant/components/lamarzocco/coordinator.py +++ b/homeassistant/components/lamarzocco/coordinator.py @@ -3,28 +3,25 @@ from __future__ import annotations from abc import abstractmethod -from collections.abc import Callable from dataclasses import dataclass from datetime import timedelta import logging from typing import Any -from pylamarzocco.clients.local import LaMarzoccoLocalClient -from pylamarzocco.devices.machine import LaMarzoccoMachine +from pylamarzocco import LaMarzoccoMachine from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed -from homeassistant.helpers import device_registry as dr from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN SCAN_INTERVAL = timedelta(seconds=30) -FIRMWARE_UPDATE_INTERVAL = timedelta(hours=1) -STATISTICS_UPDATE_INTERVAL = timedelta(minutes=5) +SETTINGS_UPDATE_INTERVAL = timedelta(hours=1) +SCHEDULE_UPDATE_INTERVAL = timedelta(minutes=5) _LOGGER = logging.getLogger(__name__) @@ -33,8 +30,8 @@ class LaMarzoccoRuntimeData: """Runtime data for La Marzocco.""" config_coordinator: LaMarzoccoConfigUpdateCoordinator - firmware_coordinator: LaMarzoccoFirmwareUpdateCoordinator - statistics_coordinator: LaMarzoccoStatisticsUpdateCoordinator + settings_coordinator: LaMarzoccoSettingsUpdateCoordinator + schedule_coordinator: LaMarzoccoScheduleUpdateCoordinator type LaMarzoccoConfigEntry = ConfigEntry[LaMarzoccoRuntimeData] @@ -51,7 +48,6 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): hass: HomeAssistant, entry: LaMarzoccoConfigEntry, device: LaMarzoccoMachine, - local_client: LaMarzoccoLocalClient | None = None, ) -> None: """Initialize coordinator.""" super().__init__( @@ -62,9 +58,6 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): update_interval=self._default_update_interval, ) self.device = device - self.local_connection_configured = local_client is not None - self._local_client = local_client - self.new_device_callback: list[Callable] = [] async def _async_update_data(self) -> None: """Do the data update.""" @@ -89,30 +82,22 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator): """Class to handle fetching data from the La Marzocco API centrally.""" - _scale_address: str | None = None - async def _async_connect_websocket(self) -> None: """Set up the coordinator.""" - if self._local_client is not None and ( - self._local_client.websocket is None or self._local_client.websocket.closed - ): + if not self.device.websocket.connected: _LOGGER.debug("Init WebSocket in background task") self.config_entry.async_create_background_task( hass=self.hass, - target=self.device.websocket_connect( - notify_callback=lambda: self.async_set_updated_data(None) + target=self.device.connect_dashboard_websocket( + update_callback=lambda _: self.async_set_updated_data(None) ), name="lm_websocket_task", ) async def websocket_close(_: Any | None = None) -> None: - if ( - self._local_client is not None - and self._local_client.websocket is not None - and not self._local_client.websocket.closed - ): - await self._local_client.websocket.close() + if self.device.websocket.connected: + await self.device.websocket.disconnect() self.config_entry.async_on_unload( self.hass.bus.async_listen_once( @@ -123,47 +108,28 @@ class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator): async def _internal_async_update_data(self) -> None: """Fetch data from API endpoint.""" - await self.device.get_config() - _LOGGER.debug("Current status: %s", str(self.device.config)) + await self.device.get_dashboard() + _LOGGER.debug("Current status: %s", self.device.dashboard.to_dict()) await self._async_connect_websocket() - self._async_add_remove_scale() - - @callback - def _async_add_remove_scale(self) -> None: - """Add or remove a scale when added or removed.""" - if self.device.config.scale and not self._scale_address: - self._scale_address = self.device.config.scale.address - for scale_callback in self.new_device_callback: - scale_callback() - elif not self.device.config.scale and self._scale_address: - device_registry = dr.async_get(self.hass) - if device := device_registry.async_get_device( - identifiers={(DOMAIN, self._scale_address)} - ): - device_registry.async_update_device( - device_id=device.id, - remove_config_entry_id=self.config_entry.entry_id, - ) - self._scale_address = None -class LaMarzoccoFirmwareUpdateCoordinator(LaMarzoccoUpdateCoordinator): - """Coordinator for La Marzocco firmware.""" +class LaMarzoccoSettingsUpdateCoordinator(LaMarzoccoUpdateCoordinator): + """Coordinator for La Marzocco settings.""" - _default_update_interval = FIRMWARE_UPDATE_INTERVAL + _default_update_interval = SETTINGS_UPDATE_INTERVAL async def _internal_async_update_data(self) -> None: """Fetch data from API endpoint.""" - await self.device.get_firmware() - _LOGGER.debug("Current firmware: %s", str(self.device.firmware)) + await self.device.get_settings() + _LOGGER.debug("Current settings: %s", self.device.settings.to_dict()) -class LaMarzoccoStatisticsUpdateCoordinator(LaMarzoccoUpdateCoordinator): - """Coordinator for La Marzocco statistics.""" +class LaMarzoccoScheduleUpdateCoordinator(LaMarzoccoUpdateCoordinator): + """Coordinator for La Marzocco schedule.""" - _default_update_interval = STATISTICS_UPDATE_INTERVAL + _default_update_interval = SCHEDULE_UPDATE_INTERVAL async def _internal_async_update_data(self) -> None: """Fetch data from API endpoint.""" - await self.device.get_statistics() - _LOGGER.debug("Current statistics: %s", str(self.device.statistics)) + await self.device.get_schedule() + _LOGGER.debug("Current schedule: %s", self.device.schedule.to_dict()) diff --git a/homeassistant/components/lamarzocco/diagnostics.py b/homeassistant/components/lamarzocco/diagnostics.py index 204a8b7142a..6837dd6a9ee 100644 --- a/homeassistant/components/lamarzocco/diagnostics.py +++ b/homeassistant/components/lamarzocco/diagnostics.py @@ -2,10 +2,7 @@ from __future__ import annotations -from dataclasses import asdict -from typing import Any, TypedDict - -from pylamarzocco.const import FirmwareType +from typing import Any from homeassistant.components.diagnostics import async_redact_data from homeassistant.core import HomeAssistant @@ -17,15 +14,6 @@ TO_REDACT = { } -class DiagnosticsData(TypedDict): - """Diagnostic data for La Marzocco.""" - - model: str - config: dict[str, Any] - firmware: list[dict[FirmwareType, dict[str, Any]]] - statistics: dict[str, Any] - - async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: LaMarzoccoConfigEntry, @@ -33,12 +21,4 @@ async def async_get_config_entry_diagnostics( """Return diagnostics for a config entry.""" coordinator = entry.runtime_data.config_coordinator device = coordinator.device - # collect all data sources - diagnostics_data = DiagnosticsData( - model=device.model, - config=asdict(device.config), - firmware=[{key: asdict(firmware)} for key, firmware in device.firmware.items()], - statistics=asdict(device.statistics), - ) - - return async_redact_data(diagnostics_data, TO_REDACT) + return async_redact_data(device.to_dict(), TO_REDACT) diff --git a/homeassistant/components/lamarzocco/entity.py b/homeassistant/components/lamarzocco/entity.py index 3e70ff1acdf..2e3a7f2ce83 100644 --- a/homeassistant/components/lamarzocco/entity.py +++ b/homeassistant/components/lamarzocco/entity.py @@ -2,10 +2,9 @@ from collections.abc import Callable from dataclasses import dataclass -from typing import TYPE_CHECKING +from pylamarzocco import LaMarzoccoMachine from pylamarzocco.const import FirmwareType -from pylamarzocco.devices.machine import LaMarzoccoMachine from homeassistant.const import CONF_ADDRESS, CONF_MAC from homeassistant.helpers.device_registry import ( @@ -46,12 +45,12 @@ class LaMarzoccoBaseEntity( self._attr_unique_id = f"{device.serial_number}_{key}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, device.serial_number)}, - name=device.name, + name=device.dashboard.name, manufacturer="La Marzocco", - model=device.full_model_name, - model_id=device.model, + model=device.dashboard.model_name.value, + model_id=device.dashboard.model_code.value, serial_number=device.serial_number, - sw_version=device.firmware[FirmwareType.MACHINE].current_version, + sw_version=device.settings.firmwares[FirmwareType.MACHINE].build_version, ) connections: set[tuple[str, str]] = set() if coordinator.config_entry.data.get(CONF_ADDRESS): @@ -86,26 +85,3 @@ class LaMarzoccoEntity(LaMarzoccoBaseEntity): """Initialize the entity.""" super().__init__(coordinator, entity_description.key) self.entity_description = entity_description - - -class LaMarzoccScaleEntity(LaMarzoccoEntity): - """Common class for scale.""" - - def __init__( - self, - coordinator: LaMarzoccoUpdateCoordinator, - entity_description: LaMarzoccoEntityDescription, - ) -> None: - """Initialize the entity.""" - super().__init__(coordinator, entity_description) - scale = coordinator.device.config.scale - if TYPE_CHECKING: - assert scale - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, scale.address)}, - name=scale.name, - manufacturer="Acaia", - model="Lunar", - model_id="Y.301", - via_device=(DOMAIN, coordinator.device.serial_number), - ) diff --git a/homeassistant/components/lamarzocco/icons.json b/homeassistant/components/lamarzocco/icons.json index 2be882fafea..7a42bcd6028 100644 --- a/homeassistant/components/lamarzocco/icons.json +++ b/homeassistant/components/lamarzocco/icons.json @@ -34,36 +34,11 @@ "dose": { "default": "mdi:cup-water" }, - "prebrew_off": { - "default": "mdi:water-off" - }, - "prebrew_on": { - "default": "mdi:water" - }, - "preinfusion_off": { - "default": "mdi:water" - }, - "scale_target": { - "default": "mdi:scale-balance" - }, "smart_standby_time": { "default": "mdi:timer" - }, - "steam_temp": { - "default": "mdi:thermometer-water" - }, - "tea_water_duration": { - "default": "mdi:timer-sand" } }, "select": { - "active_bbw": { - "default": "mdi:alpha-u", - "state": { - "a": "mdi:alpha-a", - "b": "mdi:alpha-b" - } - }, "smart_standby_mode": { "default": "mdi:power", "state": { @@ -88,26 +63,6 @@ } } }, - "sensor": { - "drink_stats_coffee": { - "default": "mdi:chart-line" - }, - "drink_stats_flushing": { - "default": "mdi:chart-line" - }, - "drink_stats_coffee_key": { - "default": "mdi:chart-scatter-plot" - }, - "shot_timer": { - "default": "mdi:timer" - }, - "current_temp_coffee": { - "default": "mdi:thermometer" - }, - "current_temp_steam": { - "default": "mdi:thermometer" - } - }, "switch": { "main": { "default": "mdi:power", diff --git a/homeassistant/components/lamarzocco/manifest.json b/homeassistant/components/lamarzocco/manifest.json index 73f00b2bdd0..3053056a2d0 100644 --- a/homeassistant/components/lamarzocco/manifest.json +++ b/homeassistant/components/lamarzocco/manifest.json @@ -34,8 +34,8 @@ ], "documentation": "https://www.home-assistant.io/integrations/lamarzocco", "integration_type": "device", - "iot_class": "cloud_polling", + "iot_class": "cloud_push", "loggers": ["pylamarzocco"], "quality_scale": "platinum", - "requirements": ["pylamarzocco==1.4.9"] + "requirements": ["pylamarzocco==2.0.0b1"] } diff --git a/homeassistant/components/lamarzocco/number.py b/homeassistant/components/lamarzocco/number.py index 08e9ad7e590..6b849f1783d 100644 --- a/homeassistant/components/lamarzocco/number.py +++ b/homeassistant/components/lamarzocco/number.py @@ -2,18 +2,12 @@ from collections.abc import Callable, Coroutine from dataclasses import dataclass -from typing import Any +from typing import Any, cast -from pylamarzocco.const import ( - KEYS_PER_MODEL, - BoilerType, - MachineModel, - PhysicalKey, - PrebrewMode, -) -from pylamarzocco.devices.machine import LaMarzoccoMachine +from pylamarzocco import LaMarzoccoMachine +from pylamarzocco.const import WidgetType from pylamarzocco.exceptions import RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoMachineConfig +from pylamarzocco.models import CoffeeBoiler from homeassistant.components.number import ( NumberDeviceClass, @@ -32,8 +26,8 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import DOMAIN -from .coordinator import LaMarzoccoConfigEntry, LaMarzoccoUpdateCoordinator -from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity +from .coordinator import LaMarzoccoConfigEntry +from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription PARALLEL_UPDATES = 1 @@ -45,25 +39,10 @@ class LaMarzoccoNumberEntityDescription( ): """Description of a La Marzocco number entity.""" - native_value_fn: Callable[[LaMarzoccoMachineConfig], float | int] + native_value_fn: Callable[[LaMarzoccoMachine], float | int] set_value_fn: Callable[[LaMarzoccoMachine, float | int], Coroutine[Any, Any, bool]] -@dataclass(frozen=True, kw_only=True) -class LaMarzoccoKeyNumberEntityDescription( - LaMarzoccoEntityDescription, - NumberEntityDescription, -): - """Description of an La Marzocco number entity with keys.""" - - native_value_fn: Callable[ - [LaMarzoccoMachineConfig, PhysicalKey], float | int | None - ] - set_value_fn: Callable[ - [LaMarzoccoMachine, float | int, PhysicalKey], Coroutine[Any, Any, bool] - ] - - ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = ( LaMarzoccoNumberEntityDescription( key="coffee_temp", @@ -73,43 +52,11 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = ( native_step=PRECISION_TENTHS, native_min_value=85, native_max_value=104, - set_value_fn=lambda machine, temp: machine.set_temp(BoilerType.COFFEE, temp), - native_value_fn=lambda config: config.boilers[ - BoilerType.COFFEE - ].target_temperature, - ), - LaMarzoccoNumberEntityDescription( - key="steam_temp", - translation_key="steam_temp", - device_class=NumberDeviceClass.TEMPERATURE, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - native_step=PRECISION_WHOLE, - native_min_value=126, - native_max_value=131, - set_value_fn=lambda machine, temp: machine.set_temp(BoilerType.STEAM, temp), - native_value_fn=lambda config: config.boilers[ - BoilerType.STEAM - ].target_temperature, - supported_fn=lambda coordinator: coordinator.device.model - in ( - MachineModel.GS3_AV, - MachineModel.GS3_MP, - ), - ), - LaMarzoccoNumberEntityDescription( - key="tea_water_duration", - translation_key="tea_water_duration", - device_class=NumberDeviceClass.DURATION, - native_unit_of_measurement=UnitOfTime.SECONDS, - native_step=PRECISION_WHOLE, - native_min_value=0, - native_max_value=30, - set_value_fn=lambda machine, value: machine.set_dose_tea_water(int(value)), - native_value_fn=lambda config: config.dose_hot_water, - supported_fn=lambda coordinator: coordinator.device.model - in ( - MachineModel.GS3_AV, - MachineModel.GS3_MP, + set_value_fn=lambda machine, temp: machine.set_coffee_target_temperature(temp), + native_value_fn=( + lambda machine: cast( + CoffeeBoiler, machine.dashboard.config[WidgetType.CM_COFFEE_BOILER] + ).target_temperature ), ), LaMarzoccoNumberEntityDescription( @@ -117,119 +64,18 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = ( translation_key="smart_standby_time", device_class=NumberDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.MINUTES, - native_step=10, - native_min_value=10, - native_max_value=240, - entity_category=EntityCategory.CONFIG, - set_value_fn=lambda machine, value: machine.set_smart_standby( - enabled=machine.config.smart_standby.enabled, - mode=machine.config.smart_standby.mode, - minutes=int(value), - ), - native_value_fn=lambda config: config.smart_standby.minutes, - ), -) - - -KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = ( - LaMarzoccoKeyNumberEntityDescription( - key="prebrew_off", - translation_key="prebrew_off", - device_class=NumberDeviceClass.DURATION, - native_unit_of_measurement=UnitOfTime.SECONDS, - native_step=PRECISION_TENTHS, - native_min_value=1, - native_max_value=10, - entity_category=EntityCategory.CONFIG, - set_value_fn=lambda machine, value, key: machine.set_prebrew_time( - prebrew_off_time=value, key=key - ), - native_value_fn=lambda config, key: config.prebrew_configuration[key][ - 0 - ].off_time, - available_fn=lambda device: len(device.config.prebrew_configuration) > 0 - and device.config.prebrew_mode - in (PrebrewMode.PREBREW, PrebrewMode.PREBREW_ENABLED), - supported_fn=lambda coordinator: coordinator.device.model - != MachineModel.GS3_MP, - ), - LaMarzoccoKeyNumberEntityDescription( - key="prebrew_on", - translation_key="prebrew_on", - device_class=NumberDeviceClass.DURATION, - native_unit_of_measurement=UnitOfTime.SECONDS, - native_step=PRECISION_TENTHS, - native_min_value=2, - native_max_value=10, - entity_category=EntityCategory.CONFIG, - set_value_fn=lambda machine, value, key: machine.set_prebrew_time( - prebrew_on_time=value, key=key - ), - native_value_fn=lambda config, key: config.prebrew_configuration[key][ - 0 - ].off_time, - available_fn=lambda device: len(device.config.prebrew_configuration) > 0 - and device.config.prebrew_mode - in (PrebrewMode.PREBREW, PrebrewMode.PREBREW_ENABLED), - supported_fn=lambda coordinator: coordinator.device.model - != MachineModel.GS3_MP, - ), - LaMarzoccoKeyNumberEntityDescription( - key="preinfusion_off", - translation_key="preinfusion_off", - device_class=NumberDeviceClass.DURATION, - native_unit_of_measurement=UnitOfTime.SECONDS, - native_step=PRECISION_TENTHS, - native_min_value=2, - native_max_value=29, - entity_category=EntityCategory.CONFIG, - set_value_fn=lambda machine, value, key: machine.set_preinfusion_time( - preinfusion_time=value, key=key - ), - native_value_fn=lambda config, key: config.prebrew_configuration[key][ - 1 - ].preinfusion_time, - available_fn=lambda device: len(device.config.prebrew_configuration) > 0 - and device.config.prebrew_mode == PrebrewMode.PREINFUSION, - supported_fn=lambda coordinator: coordinator.device.model - != MachineModel.GS3_MP, - ), - LaMarzoccoKeyNumberEntityDescription( - key="dose", - translation_key="dose", - native_unit_of_measurement="ticks", native_step=PRECISION_WHOLE, native_min_value=0, - native_max_value=999, + native_max_value=240, entity_category=EntityCategory.CONFIG, - set_value_fn=lambda machine, ticks, key: machine.set_dose( - dose=int(ticks), key=key - ), - native_value_fn=lambda config, key: config.doses[key], - supported_fn=lambda coordinator: coordinator.device.model - == MachineModel.GS3_AV, - ), -) - -SCALE_KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = ( - LaMarzoccoKeyNumberEntityDescription( - key="scale_target", - translation_key="scale_target", - native_step=PRECISION_WHOLE, - native_min_value=1, - native_max_value=100, - entity_category=EntityCategory.CONFIG, - set_value_fn=lambda machine, weight, key: machine.set_bbw_recipe_target( - key, int(weight) - ), - native_value_fn=lambda config, key: ( - config.bbw_settings.doses[key] if config.bbw_settings else None - ), - supported_fn=( - lambda coordinator: coordinator.device.model - in (MachineModel.LINEA_MINI, MachineModel.LINEA_MINI_R) - and coordinator.device.config.scale is not None + set_value_fn=( + lambda machine, value: machine.set_smart_standby( + enabled=machine.schedule.smart_wake_up_sleep.smart_stand_by_enabled, + mode=machine.schedule.smart_wake_up_sleep.smart_stand_by_after, + minutes=int(value), + ) ), + native_value_fn=lambda machine: machine.schedule.smart_wake_up_sleep.smart_stand_by_minutes, ), ) @@ -247,34 +93,6 @@ async def async_setup_entry( if description.supported_fn(coordinator) ] - for description in KEY_ENTITIES: - if description.supported_fn(coordinator): - num_keys = KEYS_PER_MODEL[MachineModel(coordinator.device.model)] - entities.extend( - LaMarzoccoKeyNumberEntity(coordinator, description, key) - for key in range(min(num_keys, 1), num_keys + 1) - ) - - for description in SCALE_KEY_ENTITIES: - if description.supported_fn(coordinator): - if bbw_settings := coordinator.device.config.bbw_settings: - entities.extend( - LaMarzoccoScaleTargetNumberEntity( - coordinator, description, int(key) - ) - for key in bbw_settings.doses - ) - - def _async_add_new_scale() -> None: - if bbw_settings := coordinator.device.config.bbw_settings: - async_add_entities( - LaMarzoccoScaleTargetNumberEntity(coordinator, description, int(key)) - for description in SCALE_KEY_ENTITIES - for key in bbw_settings.doses - ) - - coordinator.new_device_callback.append(_async_add_new_scale) - async_add_entities(entities) @@ -286,7 +104,7 @@ class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity): @property def native_value(self) -> float: """Return the current value.""" - return self.entity_description.native_value_fn(self.coordinator.device.config) + return self.entity_description.native_value_fn(self.coordinator.device) async def async_set_native_value(self, value: float) -> None: """Set the value.""" @@ -305,62 +123,3 @@ class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity): }, ) from exc self.async_write_ha_state() - - -class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity): - """Number representing espresso machine with key support.""" - - entity_description: LaMarzoccoKeyNumberEntityDescription - - def __init__( - self, - coordinator: LaMarzoccoUpdateCoordinator, - description: LaMarzoccoKeyNumberEntityDescription, - pyhsical_key: int, - ) -> None: - """Initialize the entity.""" - super().__init__(coordinator, description) - - # Physical Key on the machine the entity represents. - if pyhsical_key == 0: - pyhsical_key = 1 - else: - self._attr_translation_key = f"{description.translation_key}_key" - self._attr_translation_placeholders = {"key": str(pyhsical_key)} - self._attr_unique_id = f"{super()._attr_unique_id}_key{pyhsical_key}" - self._attr_entity_registry_enabled_default = False - self.pyhsical_key = pyhsical_key - - @property - def native_value(self) -> float | None: - """Return the current value.""" - return self.entity_description.native_value_fn( - self.coordinator.device.config, PhysicalKey(self.pyhsical_key) - ) - - async def async_set_native_value(self, value: float) -> None: - """Set the value.""" - if value != self.native_value: - try: - await self.entity_description.set_value_fn( - self.coordinator.device, value, PhysicalKey(self.pyhsical_key) - ) - except RequestNotSuccessful as exc: - raise HomeAssistantError( - translation_domain=DOMAIN, - translation_key="number_exception_key", - translation_placeholders={ - "key": self.entity_description.key, - "value": str(value), - "physical_key": str(self.pyhsical_key), - }, - ) from exc - self.async_write_ha_state() - - -class LaMarzoccoScaleTargetNumberEntity( - LaMarzoccoKeyNumberEntity, LaMarzoccScaleEntity -): - """Entity representing a key number on the scale.""" - - entity_description: LaMarzoccoKeyNumberEntityDescription diff --git a/homeassistant/components/lamarzocco/select.py b/homeassistant/components/lamarzocco/select.py index 5ebe2d7b9da..44dad6bfb2a 100644 --- a/homeassistant/components/lamarzocco/select.py +++ b/homeassistant/components/lamarzocco/select.py @@ -2,18 +2,18 @@ from collections.abc import Callable, Coroutine from dataclasses import dataclass -from typing import Any +from typing import Any, cast from pylamarzocco.const import ( - MachineModel, - PhysicalKey, - PrebrewMode, - SmartStandbyMode, - SteamLevel, + ModelName, + PreExtractionMode, + SmartStandByType, + SteamTargetLevel, + WidgetType, ) -from pylamarzocco.devices.machine import LaMarzoccoMachine +from pylamarzocco.devices import LaMarzoccoMachine from pylamarzocco.exceptions import RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoMachineConfig +from pylamarzocco.models import PreBrewing, SteamBoilerLevel from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.const import EntityCategory @@ -23,30 +23,29 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import DOMAIN from .coordinator import LaMarzoccoConfigEntry -from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity +from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription PARALLEL_UPDATES = 1 STEAM_LEVEL_HA_TO_LM = { - "1": SteamLevel.LEVEL_1, - "2": SteamLevel.LEVEL_2, - "3": SteamLevel.LEVEL_3, + "1": SteamTargetLevel.LEVEL_1, + "2": SteamTargetLevel.LEVEL_2, + "3": SteamTargetLevel.LEVEL_3, } STEAM_LEVEL_LM_TO_HA = {value: key for key, value in STEAM_LEVEL_HA_TO_LM.items()} PREBREW_MODE_HA_TO_LM = { - "disabled": PrebrewMode.DISABLED, - "prebrew": PrebrewMode.PREBREW, - "prebrew_enabled": PrebrewMode.PREBREW_ENABLED, - "preinfusion": PrebrewMode.PREINFUSION, + "disabled": PreExtractionMode.DISABLED, + "prebrew": PreExtractionMode.PREBREWING, + "preinfusion": PreExtractionMode.PREINFUSION, } PREBREW_MODE_LM_TO_HA = {value: key for key, value in PREBREW_MODE_HA_TO_LM.items()} STANDBY_MODE_HA_TO_LM = { - "power_on": SmartStandbyMode.POWER_ON, - "last_brewing": SmartStandbyMode.LAST_BREWING, + "power_on": SmartStandByType.POWER_ON, + "last_brewing": SmartStandByType.LAST_BREW, } STANDBY_MODE_LM_TO_HA = {value: key for key, value in STANDBY_MODE_HA_TO_LM.items()} @@ -59,7 +58,7 @@ class LaMarzoccoSelectEntityDescription( ): """Description of a La Marzocco select entity.""" - current_option_fn: Callable[[LaMarzoccoMachineConfig], str | None] + current_option_fn: Callable[[LaMarzoccoMachine], str | None] select_option_fn: Callable[[LaMarzoccoMachine, str], Coroutine[Any, Any, bool]] @@ -71,25 +70,36 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = ( select_option_fn=lambda machine, option: machine.set_steam_level( STEAM_LEVEL_HA_TO_LM[option] ), - current_option_fn=lambda config: STEAM_LEVEL_LM_TO_HA[config.steam_level], - supported_fn=lambda coordinator: coordinator.device.model - == MachineModel.LINEA_MICRA, + current_option_fn=lambda machine: STEAM_LEVEL_LM_TO_HA[ + cast( + SteamBoilerLevel, + machine.dashboard.config[WidgetType.CM_STEAM_BOILER_LEVEL], + ).target_level + ], + supported_fn=( + lambda coordinator: coordinator.device.dashboard.model_name + in (ModelName.LINEA_MINI_R, ModelName.LINEA_MICRA) + ), ), LaMarzoccoSelectEntityDescription( key="prebrew_infusion_select", translation_key="prebrew_infusion_select", entity_category=EntityCategory.CONFIG, options=["disabled", "prebrew", "preinfusion"], - select_option_fn=lambda machine, option: machine.set_prebrew_mode( + select_option_fn=lambda machine, option: machine.set_pre_extraction_mode( PREBREW_MODE_HA_TO_LM[option] ), - current_option_fn=lambda config: PREBREW_MODE_LM_TO_HA[config.prebrew_mode], - supported_fn=lambda coordinator: coordinator.device.model - in ( - MachineModel.GS3_AV, - MachineModel.LINEA_MICRA, - MachineModel.LINEA_MINI, - MachineModel.LINEA_MINI_R, + current_option_fn=lambda machine: PREBREW_MODE_LM_TO_HA[ + cast(PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]).mode + ], + supported_fn=( + lambda coordinator: coordinator.device.dashboard.model_name + in ( + ModelName.LINEA_MICRA, + ModelName.LINEA_MINI, + ModelName.LINEA_MINI_R, + ModelName.GS3_AV, + ) ), ), LaMarzoccoSelectEntityDescription( @@ -98,32 +108,16 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = ( entity_category=EntityCategory.CONFIG, options=["power_on", "last_brewing"], select_option_fn=lambda machine, option: machine.set_smart_standby( - enabled=machine.config.smart_standby.enabled, + enabled=machine.schedule.smart_wake_up_sleep.smart_stand_by_enabled, mode=STANDBY_MODE_HA_TO_LM[option], - minutes=machine.config.smart_standby.minutes, + minutes=machine.schedule.smart_wake_up_sleep.smart_stand_by_minutes, ), - current_option_fn=lambda config: STANDBY_MODE_LM_TO_HA[ - config.smart_standby.mode + current_option_fn=lambda machine: STANDBY_MODE_LM_TO_HA[ + machine.schedule.smart_wake_up_sleep.smart_stand_by_after ], ), ) -SCALE_ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = ( - LaMarzoccoSelectEntityDescription( - key="active_bbw", - translation_key="active_bbw", - options=["a", "b"], - select_option_fn=lambda machine, option: machine.set_active_bbw_recipe( - PhysicalKey[option.upper()] - ), - current_option_fn=lambda config: ( - config.bbw_settings.active_dose.name.lower() - if config.bbw_settings - else None - ), - ), -) - async def async_setup_entry( hass: HomeAssistant, @@ -133,30 +127,11 @@ async def async_setup_entry( """Set up select entities.""" coordinator = entry.runtime_data.config_coordinator - entities = [ + async_add_entities( LaMarzoccoSelectEntity(coordinator, description) for description in ENTITIES if description.supported_fn(coordinator) - ] - - if ( - coordinator.device.model in (MachineModel.LINEA_MINI, MachineModel.LINEA_MINI_R) - and coordinator.device.config.scale - ): - entities.extend( - LaMarzoccoScaleSelectEntity(coordinator, description) - for description in SCALE_ENTITIES - ) - - def _async_add_new_scale() -> None: - async_add_entities( - LaMarzoccoScaleSelectEntity(coordinator, description) - for description in SCALE_ENTITIES - ) - - coordinator.new_device_callback.append(_async_add_new_scale) - - async_add_entities(entities) + ) class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity): @@ -167,9 +142,7 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity): @property def current_option(self) -> str | None: """Return the current selected option.""" - return str( - self.entity_description.current_option_fn(self.coordinator.device.config) - ) + return self.entity_description.current_option_fn(self.coordinator.device) async def async_select_option(self, option: str) -> None: """Change the selected option.""" @@ -188,9 +161,3 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity): }, ) from exc self.async_write_ha_state() - - -class LaMarzoccoScaleSelectEntity(LaMarzoccoSelectEntity, LaMarzoccScaleEntity): - """Select entity for La Marzocco scales.""" - - entity_description: LaMarzoccoSelectEntityDescription diff --git a/homeassistant/components/lamarzocco/sensor.py b/homeassistant/components/lamarzocco/sensor.py deleted file mode 100644 index 0d4a5e53ebe..00000000000 --- a/homeassistant/components/lamarzocco/sensor.py +++ /dev/null @@ -1,226 +0,0 @@ -"""Sensor platform for La Marzocco espresso machines.""" - -from collections.abc import Callable -from dataclasses import dataclass - -from pylamarzocco.const import KEYS_PER_MODEL, BoilerType, MachineModel, PhysicalKey -from pylamarzocco.devices.machine import LaMarzoccoMachine - -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.const import ( - PERCENTAGE, - EntityCategory, - UnitOfTemperature, - UnitOfTime, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback - -from .coordinator import LaMarzoccoConfigEntry, LaMarzoccoUpdateCoordinator -from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity - -# Coordinator is used to centralize the data updates -PARALLEL_UPDATES = 0 - - -@dataclass(frozen=True, kw_only=True) -class LaMarzoccoSensorEntityDescription( - LaMarzoccoEntityDescription, SensorEntityDescription -): - """Description of a La Marzocco sensor.""" - - value_fn: Callable[[LaMarzoccoMachine], float | int] - - -@dataclass(frozen=True, kw_only=True) -class LaMarzoccoKeySensorEntityDescription( - LaMarzoccoEntityDescription, SensorEntityDescription -): - """Description of a keyed La Marzocco sensor.""" - - value_fn: Callable[[LaMarzoccoMachine, PhysicalKey], int | None] - - -ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = ( - LaMarzoccoSensorEntityDescription( - key="shot_timer", - translation_key="shot_timer", - native_unit_of_measurement=UnitOfTime.SECONDS, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.DURATION, - value_fn=lambda device: device.config.brew_active_duration, - available_fn=lambda device: device.websocket_connected, - entity_category=EntityCategory.DIAGNOSTIC, - supported_fn=lambda coordinator: coordinator.local_connection_configured, - ), - LaMarzoccoSensorEntityDescription( - key="current_temp_coffee", - translation_key="current_temp_coffee", - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - suggested_display_precision=1, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.TEMPERATURE, - value_fn=lambda device: device.config.boilers[ - BoilerType.COFFEE - ].current_temperature, - ), - LaMarzoccoSensorEntityDescription( - key="current_temp_steam", - translation_key="current_temp_steam", - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - suggested_display_precision=1, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.TEMPERATURE, - value_fn=lambda device: device.config.boilers[ - BoilerType.STEAM - ].current_temperature, - supported_fn=lambda coordinator: coordinator.device.model - not in (MachineModel.LINEA_MINI, MachineModel.LINEA_MINI_R), - ), -) - -STATISTIC_ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = ( - LaMarzoccoSensorEntityDescription( - key="drink_stats_coffee", - translation_key="drink_stats_coffee", - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device: device.statistics.total_coffee, - available_fn=lambda device: len(device.statistics.drink_stats) > 0, - entity_category=EntityCategory.DIAGNOSTIC, - ), - LaMarzoccoSensorEntityDescription( - key="drink_stats_flushing", - translation_key="drink_stats_flushing", - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device: device.statistics.total_flushes, - available_fn=lambda device: len(device.statistics.drink_stats) > 0, - entity_category=EntityCategory.DIAGNOSTIC, - ), -) - -KEY_STATISTIC_ENTITIES: tuple[LaMarzoccoKeySensorEntityDescription, ...] = ( - LaMarzoccoKeySensorEntityDescription( - key="drink_stats_coffee_key", - translation_key="drink_stats_coffee_key", - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device, key: device.statistics.drink_stats.get(key), - available_fn=lambda device: len(device.statistics.drink_stats) > 0, - entity_category=EntityCategory.DIAGNOSTIC, - entity_registry_enabled_default=False, - ), -) - -SCALE_ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = ( - LaMarzoccoSensorEntityDescription( - key="scale_battery", - native_unit_of_measurement=PERCENTAGE, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.BATTERY, - value_fn=lambda device: ( - device.config.scale.battery if device.config.scale else 0 - ), - supported_fn=( - lambda coordinator: coordinator.device.model - in (MachineModel.LINEA_MINI, MachineModel.LINEA_MINI_R) - ), - ), -) - - -async def async_setup_entry( - hass: HomeAssistant, - entry: LaMarzoccoConfigEntry, - async_add_entities: AddConfigEntryEntitiesCallback, -) -> None: - """Set up sensor entities.""" - config_coordinator = entry.runtime_data.config_coordinator - - entities: list[LaMarzoccoSensorEntity | LaMarzoccoKeySensorEntity] = [] - - entities = [ - LaMarzoccoSensorEntity(config_coordinator, description) - for description in ENTITIES - if description.supported_fn(config_coordinator) - ] - - if ( - config_coordinator.device.model - in (MachineModel.LINEA_MINI, MachineModel.LINEA_MINI_R) - and config_coordinator.device.config.scale - ): - entities.extend( - LaMarzoccoScaleSensorEntity(config_coordinator, description) - for description in SCALE_ENTITIES - ) - - statistics_coordinator = entry.runtime_data.statistics_coordinator - entities.extend( - LaMarzoccoSensorEntity(statistics_coordinator, description) - for description in STATISTIC_ENTITIES - if description.supported_fn(statistics_coordinator) - ) - - num_keys = KEYS_PER_MODEL[MachineModel(config_coordinator.device.model)] - if num_keys > 0: - entities.extend( - LaMarzoccoKeySensorEntity(statistics_coordinator, description, key) - for description in KEY_STATISTIC_ENTITIES - for key in range(1, num_keys + 1) - ) - - def _async_add_new_scale() -> None: - async_add_entities( - LaMarzoccoScaleSensorEntity(config_coordinator, description) - for description in SCALE_ENTITIES - ) - - config_coordinator.new_device_callback.append(_async_add_new_scale) - - async_add_entities(entities) - - -class LaMarzoccoSensorEntity(LaMarzoccoEntity, SensorEntity): - """Sensor representing espresso machine temperature data.""" - - entity_description: LaMarzoccoSensorEntityDescription - - @property - def native_value(self) -> int | float | None: - """State of the sensor.""" - return self.entity_description.value_fn(self.coordinator.device) - - -class LaMarzoccoKeySensorEntity(LaMarzoccoEntity, SensorEntity): - """Sensor for a La Marzocco key.""" - - entity_description: LaMarzoccoKeySensorEntityDescription - - def __init__( - self, - coordinator: LaMarzoccoUpdateCoordinator, - description: LaMarzoccoKeySensorEntityDescription, - key: int, - ) -> None: - """Initialize the sensor.""" - super().__init__(coordinator, description) - self.key = key - self._attr_translation_placeholders = {"key": str(key)} - self._attr_unique_id = f"{super()._attr_unique_id}_key{key}" - - @property - def native_value(self) -> int | None: - """State of the sensor.""" - return self.entity_description.value_fn( - self.coordinator.device, PhysicalKey(self.key) - ) - - -class LaMarzoccoScaleSensorEntity(LaMarzoccoSensorEntity, LaMarzoccScaleEntity): - """Sensor for a La Marzocco scale.""" - - entity_description: LaMarzoccoSensorEntityDescription diff --git a/homeassistant/components/lamarzocco/strings.json b/homeassistant/components/lamarzocco/strings.json index f087856dbed..fe7475a23c9 100644 --- a/homeassistant/components/lamarzocco/strings.json +++ b/homeassistant/components/lamarzocco/strings.json @@ -32,13 +32,11 @@ } }, "machine_selection": { - "description": "Select the machine you want to integrate. Set the \"IP\" to get access to shot time related sensors.", + "description": "Select the machine you want to integrate.", "data": { - "host": "[%key:common::config_flow::data::ip%]", "machine": "Machine" }, "data_description": { - "host": "Local IP address of the machine", "machine": "Select the machine you want to integrate" } }, @@ -101,54 +99,16 @@ "coffee_temp": { "name": "Coffee target temperature" }, - "dose_key": { - "name": "Dose Key {key}" - }, - "prebrew_on": { - "name": "Prebrew on time" - }, - "prebrew_on_key": { - "name": "Prebrew on time Key {key}" - }, - "prebrew_off": { - "name": "Prebrew off time" - }, - "prebrew_off_key": { - "name": "Prebrew off time Key {key}" - }, - "preinfusion_off": { - "name": "Preinfusion time" - }, - "preinfusion_off_key": { - "name": "Preinfusion time Key {key}" - }, - "scale_target_key": { - "name": "Brew by weight target {key}" - }, "smart_standby_time": { "name": "Smart standby time" - }, - "steam_temp": { - "name": "Steam target temperature" - }, - "tea_water_duration": { - "name": "Tea water duration" } }, "select": { - "active_bbw": { - "name": "Active brew by weight recipe", - "state": { - "a": "Recipe A", - "b": "Recipe B" - } - }, "prebrew_infusion_select": { "name": "Prebrew/-infusion mode", "state": { "disabled": "[%key:common::state::disabled%]", "prebrew": "Prebrew", - "prebrew_enabled": "Prebrew", "preinfusion": "Preinfusion" } }, @@ -168,29 +128,6 @@ } } }, - "sensor": { - "current_temp_coffee": { - "name": "Current coffee temperature" - }, - "current_temp_steam": { - "name": "Current steam temperature" - }, - "drink_stats_coffee": { - "name": "Total coffees made", - "unit_of_measurement": "coffees" - }, - "drink_stats_coffee_key": { - "name": "Coffees made Key {key}", - "unit_of_measurement": "coffees" - }, - "drink_stats_flushing": { - "name": "Total flushes made", - "unit_of_measurement": "flushes" - }, - "shot_timer": { - "name": "Shot timer" - } - }, "switch": { "auto_on_off": { "name": "Auto on/off ({id})" @@ -233,9 +170,6 @@ "number_exception": { "message": "Error while setting value {value} for number {key}" }, - "number_exception_key": { - "message": "Error while setting value {value} for number {key}, key {physical_key}" - }, "select_option_error": { "message": "Error while setting select option {option} for {key}" }, diff --git a/homeassistant/components/lamarzocco/switch.py b/homeassistant/components/lamarzocco/switch.py index ee03ba421d4..ca5fb820150 100644 --- a/homeassistant/components/lamarzocco/switch.py +++ b/homeassistant/components/lamarzocco/switch.py @@ -2,12 +2,17 @@ from collections.abc import Callable, Coroutine from dataclasses import dataclass -from typing import Any +from typing import Any, cast -from pylamarzocco.const import BoilerType -from pylamarzocco.devices.machine import LaMarzoccoMachine +from pylamarzocco import LaMarzoccoMachine +from pylamarzocco.const import MachineMode, ModelName, WidgetType from pylamarzocco.exceptions import RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoMachineConfig +from pylamarzocco.models import ( + MachineStatus, + SteamBoilerLevel, + SteamBoilerTemperature, + WakeUpScheduleSettings, +) from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.const import EntityCategory @@ -30,7 +35,7 @@ class LaMarzoccoSwitchEntityDescription( """Description of a La Marzocco Switch.""" control_fn: Callable[[LaMarzoccoMachine, bool], Coroutine[Any, Any, bool]] - is_on_fn: Callable[[LaMarzoccoMachineConfig], bool] + is_on_fn: Callable[[LaMarzoccoMachine], bool] ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = ( @@ -39,13 +44,42 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = ( translation_key="main", name=None, control_fn=lambda machine, state: machine.set_power(state), - is_on_fn=lambda config: config.turned_on, + is_on_fn=( + lambda machine: cast( + MachineStatus, machine.dashboard.config[WidgetType.CM_MACHINE_STATUS] + ).mode + is MachineMode.BREWING_MODE + ), ), LaMarzoccoSwitchEntityDescription( key="steam_boiler_enable", translation_key="steam_boiler", control_fn=lambda machine, state: machine.set_steam(state), - is_on_fn=lambda config: config.boilers[BoilerType.STEAM].enabled, + is_on_fn=( + lambda machine: cast( + SteamBoilerLevel, + machine.dashboard.config[WidgetType.CM_STEAM_BOILER_LEVEL], + ).enabled + ), + supported_fn=( + lambda coordinator: coordinator.device.dashboard.model_name + in (ModelName.LINEA_MINI_R, ModelName.LINEA_MICRA) + ), + ), + LaMarzoccoSwitchEntityDescription( + key="steam_boiler_enable", + translation_key="steam_boiler", + control_fn=lambda machine, state: machine.set_steam(state), + is_on_fn=( + lambda machine: cast( + SteamBoilerTemperature, + machine.dashboard.config[WidgetType.CM_STEAM_BOILER_TEMPERATURE], + ).enabled + ), + supported_fn=( + lambda coordinator: coordinator.device.dashboard.model_name + not in (ModelName.LINEA_MINI_R, ModelName.LINEA_MICRA) + ), ), LaMarzoccoSwitchEntityDescription( key="smart_standby_enabled", @@ -53,10 +87,10 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = ( entity_category=EntityCategory.CONFIG, control_fn=lambda machine, state: machine.set_smart_standby( enabled=state, - mode=machine.config.smart_standby.mode, - minutes=machine.config.smart_standby.minutes, + mode=machine.schedule.smart_wake_up_sleep.smart_stand_by_after, + minutes=machine.schedule.smart_wake_up_sleep.smart_stand_by_minutes, ), - is_on_fn=lambda config: config.smart_standby.enabled, + is_on_fn=lambda machine: machine.schedule.smart_wake_up_sleep.smart_stand_by_enabled, ), ) @@ -78,8 +112,8 @@ async def async_setup_entry( ) entities.extend( - LaMarzoccoAutoOnOffSwitchEntity(coordinator, wake_up_sleep_entry_id) - for wake_up_sleep_entry_id in coordinator.device.config.wake_up_sleep_entries + LaMarzoccoAutoOnOffSwitchEntity(coordinator, wake_up_sleep_entry) + for wake_up_sleep_entry in coordinator.device.schedule.smart_wake_up_sleep.schedules ) async_add_entities(entities) @@ -117,7 +151,7 @@ class LaMarzoccoSwitchEntity(LaMarzoccoEntity, SwitchEntity): @property def is_on(self) -> bool: """Return true if device is on.""" - return self.entity_description.is_on_fn(self.coordinator.device.config) + return self.entity_description.is_on_fn(self.coordinator.device) class LaMarzoccoAutoOnOffSwitchEntity(LaMarzoccoBaseEntity, SwitchEntity): @@ -129,22 +163,21 @@ class LaMarzoccoAutoOnOffSwitchEntity(LaMarzoccoBaseEntity, SwitchEntity): def __init__( self, coordinator: LaMarzoccoUpdateCoordinator, - identifier: str, + schedule_entry: WakeUpScheduleSettings, ) -> None: """Initialize the switch.""" - super().__init__(coordinator, f"auto_on_off_{identifier}") - self._identifier = identifier - self._attr_translation_placeholders = {"id": identifier} - self.entity_category = EntityCategory.CONFIG + super().__init__(coordinator, f"auto_on_off_{schedule_entry.identifier}") + assert schedule_entry.identifier + self._schedule_entry = schedule_entry + self._identifier = schedule_entry.identifier + self._attr_translation_placeholders = {"id": schedule_entry.identifier} + self._attr_entity_category = EntityCategory.CONFIG async def _async_enable(self, state: bool) -> None: """Enable or disable the auto on/off schedule.""" - wake_up_sleep_entry = self.coordinator.device.config.wake_up_sleep_entries[ - self._identifier - ] - wake_up_sleep_entry.enabled = state + self._schedule_entry.enabled = state try: - await self.coordinator.device.set_wake_up_sleep(wake_up_sleep_entry) + await self.coordinator.device.set_wakeup_schedule(self._schedule_entry) except RequestNotSuccessful as exc: raise HomeAssistantError( translation_domain=DOMAIN, @@ -164,6 +197,4 @@ class LaMarzoccoAutoOnOffSwitchEntity(LaMarzoccoBaseEntity, SwitchEntity): @property def is_on(self) -> bool: """Return true if switch is on.""" - return self.coordinator.device.config.wake_up_sleep_entries[ - self._identifier - ].enabled + return self._schedule_entry.enabled diff --git a/homeassistant/components/lamarzocco/update.py b/homeassistant/components/lamarzocco/update.py index 37960d26e95..487cef042c9 100644 --- a/homeassistant/components/lamarzocco/update.py +++ b/homeassistant/components/lamarzocco/update.py @@ -59,7 +59,7 @@ async def async_setup_entry( ) -> None: """Create update entities.""" - coordinator = entry.runtime_data.firmware_coordinator + coordinator = entry.runtime_data.settings_coordinator async_add_entities( LaMarzoccoUpdateEntity(coordinator, description) for description in ENTITIES @@ -74,18 +74,20 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity): _attr_supported_features = UpdateEntityFeature.INSTALL @property - def installed_version(self) -> str | None: + def installed_version(self) -> str: """Return the current firmware version.""" - return self.coordinator.device.firmware[ + return self.coordinator.device.settings.firmwares[ self.entity_description.component - ].current_version + ].build_version @property def latest_version(self) -> str: """Return the latest firmware version.""" - return self.coordinator.device.firmware[ + if available_update := self.coordinator.device.settings.firmwares[ self.entity_description.component - ].latest_version + ].available_update: + return available_update.build_version + return self.installed_version @property def release_url(self) -> str | None: @@ -99,9 +101,7 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity): self._attr_in_progress = True self.async_write_ha_state() try: - success = await self.coordinator.device.update_firmware( - self.entity_description.component - ) + await self.coordinator.device.update_firmware() except RequestNotSuccessful as exc: raise HomeAssistantError( translation_domain=DOMAIN, @@ -110,13 +110,5 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity): "key": self.entity_description.key, }, ) from exc - if not success: - raise HomeAssistantError( - translation_domain=DOMAIN, - translation_key="update_failed", - translation_placeholders={ - "key": self.entity_description.key, - }, - ) self._attr_in_progress = False await self.coordinator.async_request_refresh() diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index e3dd9a4635f..8dda9de3705 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3329,7 +3329,7 @@ "name": "La Marzocco", "integration_type": "device", "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_push" }, "lametric": { "name": "LaMetric", diff --git a/requirements_all.txt b/requirements_all.txt index 9e7329d4b78..6c1b3fc6a42 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2089,7 +2089,7 @@ pykwb==0.0.8 pylacrosse==0.4 # homeassistant.components.lamarzocco -pylamarzocco==1.4.9 +pylamarzocco==2.0.0b1 # homeassistant.components.lastfm pylast==5.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 42def0664fd..47403cf14d6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1704,7 +1704,7 @@ pykrakenapi==0.1.8 pykulersky==0.5.8 # homeassistant.components.lamarzocco -pylamarzocco==1.4.9 +pylamarzocco==2.0.0b1 # homeassistant.components.lastfm pylast==5.1.0 diff --git a/tests/components/lamarzocco/__init__.py b/tests/components/lamarzocco/__init__.py index f6ca0fe40df..80493aa83c9 100644 --- a/tests/components/lamarzocco/__init__.py +++ b/tests/components/lamarzocco/__init__.py @@ -1,6 +1,6 @@ """Mock inputs for tests.""" -from pylamarzocco.const import MachineModel +from pylamarzocco.const import ModelName from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant @@ -19,10 +19,10 @@ PASSWORD_SELECTION = { USER_INPUT = PASSWORD_SELECTION | {CONF_USERNAME: "username"} SERIAL_DICT = { - MachineModel.GS3_AV: "GS012345", - MachineModel.GS3_MP: "GS012345", - MachineModel.LINEA_MICRA: "MR012345", - MachineModel.LINEA_MINI: "LM012345", + ModelName.GS3_AV: "GS012345", + ModelName.GS3_MP: "GS012345", + ModelName.LINEA_MICRA: "MR012345", + ModelName.LINEA_MINI: "LM012345", } WAKE_UP_SLEEP_ENTRY_IDS = ["Os2OswX", "aXFz5bJ"] @@ -37,15 +37,13 @@ async def async_init_integration( await hass.async_block_till_done() -def get_bluetooth_service_info( - model: MachineModel, serial: str -) -> BluetoothServiceInfo: +def get_bluetooth_service_info(model: ModelName, serial: str) -> BluetoothServiceInfo: """Return a mocked BluetoothServiceInfo.""" - if model in (MachineModel.GS3_AV, MachineModel.GS3_MP): + if model in (ModelName.GS3_AV, ModelName.GS3_MP): name = f"GS3_{serial}" - elif model == MachineModel.LINEA_MINI: + elif model == ModelName.LINEA_MINI: name = f"MINI_{serial}" - elif model == MachineModel.LINEA_MICRA: + elif model == ModelName.LINEA_MICRA: name = f"MICRA_{serial}" return BluetoothServiceInfo( name=name, diff --git a/tests/components/lamarzocco/conftest.py b/tests/components/lamarzocco/conftest.py index 658e0dd96bc..40ab976ebdb 100644 --- a/tests/components/lamarzocco/conftest.py +++ b/tests/components/lamarzocco/conftest.py @@ -1,28 +1,25 @@ """Lamarzocco session fixtures.""" from collections.abc import Generator -import json from unittest.mock import AsyncMock, MagicMock, patch from bleak.backends.device import BLEDevice -from pylamarzocco.const import FirmwareType, MachineModel, SteamLevel -from pylamarzocco.devices.machine import LaMarzoccoMachine -from pylamarzocco.models import LaMarzoccoDeviceInfo +from pylamarzocco.const import ModelName +from pylamarzocco.models import ( + Thing, + ThingDashboardConfig, + ThingSchedulingSettings, + ThingSettings, +) import pytest from homeassistant.components.lamarzocco.const import DOMAIN -from homeassistant.const import ( - CONF_ADDRESS, - CONF_HOST, - CONF_MODEL, - CONF_NAME, - CONF_TOKEN, -) +from homeassistant.const import CONF_ADDRESS, CONF_TOKEN from homeassistant.core import HomeAssistant from . import SERIAL_DICT, USER_INPUT, async_init_integration -from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture +from tests.common import MockConfigEntry, load_json_object_fixture @pytest.fixture @@ -42,33 +39,11 @@ def mock_config_entry( return MockConfigEntry( title="My LaMarzocco", domain=DOMAIN, - version=2, + version=3, data=USER_INPUT | { - CONF_MODEL: mock_lamarzocco.model, CONF_ADDRESS: "00:00:00:00:00:00", - CONF_HOST: "host", CONF_TOKEN: "token", - CONF_NAME: "GS3", - }, - unique_id=mock_lamarzocco.serial_number, - ) - - -@pytest.fixture -def mock_config_entry_no_local_connection( - hass: HomeAssistant, mock_lamarzocco: MagicMock -) -> MockConfigEntry: - """Return the default mocked config entry.""" - return MockConfigEntry( - title="My LaMarzocco", - domain=DOMAIN, - version=2, - data=USER_INPUT - | { - CONF_MODEL: mock_lamarzocco.model, - CONF_TOKEN: "token", - CONF_NAME: "GS3", }, unique_id=mock_lamarzocco.serial_number, ) @@ -85,26 +60,13 @@ async def init_integration( @pytest.fixture -def device_fixture() -> MachineModel: +def device_fixture() -> ModelName: """Return the device fixture for a specific device.""" - return MachineModel.GS3_AV + return ModelName.GS3_AV -@pytest.fixture -def mock_device_info(device_fixture: MachineModel) -> LaMarzoccoDeviceInfo: - """Return a mocked La Marzocco device info.""" - return LaMarzoccoDeviceInfo( - model=device_fixture, - serial_number=SERIAL_DICT[device_fixture], - name="GS3", - communication_key="token", - ) - - -@pytest.fixture -def mock_cloud_client( - mock_device_info: LaMarzoccoDeviceInfo, -) -> Generator[MagicMock]: +@pytest.fixture(autouse=True) +def mock_cloud_client() -> Generator[MagicMock]: """Return a mocked LM cloud client.""" with ( patch( @@ -117,54 +79,48 @@ def mock_cloud_client( ), ): client = cloud_client.return_value - client.get_customer_fleet.return_value = { - mock_device_info.serial_number: mock_device_info - } + client.list_things.return_value = [ + Thing.from_dict(load_json_object_fixture("thing.json", DOMAIN)) + ] + client.get_thing_settings.return_value = ThingSettings.from_dict( + load_json_object_fixture("settings.json", DOMAIN) + ) yield client @pytest.fixture -def mock_lamarzocco(device_fixture: MachineModel) -> Generator[MagicMock]: +def mock_lamarzocco(device_fixture: ModelName) -> Generator[MagicMock]: """Return a mocked LM client.""" - model = device_fixture - serial_number = SERIAL_DICT[model] - - dummy_machine = LaMarzoccoMachine( - model=model, - serial_number=serial_number, - name=serial_number, - ) - if device_fixture == MachineModel.LINEA_MINI: + if device_fixture == ModelName.LINEA_MINI: config = load_json_object_fixture("config_mini.json", DOMAIN) + elif device_fixture == ModelName.LINEA_MICRA: + config = load_json_object_fixture("config_micra.json", DOMAIN) else: - config = load_json_object_fixture("config.json", DOMAIN) - statistics = json.loads(load_fixture("statistics.json", DOMAIN)) - - dummy_machine.parse_config(config) - dummy_machine.parse_statistics(statistics) + config = load_json_object_fixture("config_gs3.json", DOMAIN) + schedule = load_json_object_fixture("schedule.json", DOMAIN) + settings = load_json_object_fixture("settings.json", DOMAIN) with ( patch( "homeassistant.components.lamarzocco.LaMarzoccoMachine", autospec=True, - ) as lamarzocco_mock, + ) as machine_mock_init, ): - lamarzocco = lamarzocco_mock.return_value + machine_mock = machine_mock_init.return_value - lamarzocco.name = dummy_machine.name - lamarzocco.model = dummy_machine.model - lamarzocco.serial_number = dummy_machine.serial_number - lamarzocco.full_model_name = dummy_machine.full_model_name - lamarzocco.config = dummy_machine.config - lamarzocco.statistics = dummy_machine.statistics - lamarzocco.firmware = dummy_machine.firmware - lamarzocco.steam_level = SteamLevel.LEVEL_1 - - lamarzocco.firmware[FirmwareType.GATEWAY].latest_version = "v3.5-rc3" - lamarzocco.firmware[FirmwareType.MACHINE].latest_version = "1.55" - - yield lamarzocco + machine_mock.serial_number = SERIAL_DICT[device_fixture] + machine_mock.dashboard = ThingDashboardConfig.from_dict(config) + machine_mock.schedule = ThingSchedulingSettings.from_dict(schedule) + machine_mock.settings = ThingSettings.from_dict(settings) + machine_mock.dashboard.model_name = device_fixture + machine_mock.to_dict.return_value = { + "serial_number": machine_mock.serial_number, + "dashboard": machine_mock.dashboard.to_dict(), + "schedule": machine_mock.schedule.to_dict(), + "settings": machine_mock.settings.to_dict(), + } + yield machine_mock @pytest.fixture(autouse=True) diff --git a/tests/components/lamarzocco/fixtures/config.json b/tests/components/lamarzocco/fixtures/config.json deleted file mode 100644 index 5aac86dde97..00000000000 --- a/tests/components/lamarzocco/fixtures/config.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "version": "v1", - "preinfusionModesAvailable": ["ByDoseType"], - "machineCapabilities": [ - { - "family": "GS3AV", - "groupsNumber": 1, - "coffeeBoilersNumber": 1, - "hasCupWarmer": false, - "steamBoilersNumber": 1, - "teaDosesNumber": 1, - "machineModes": ["BrewingMode", "StandBy"], - "schedulingType": "weeklyScheduling" - } - ], - "machine_sn": "Sn01239157", - "machine_hw": "2", - "isPlumbedIn": true, - "isBackFlushEnabled": false, - "standByTime": 0, - "smartStandBy": { - "enabled": true, - "minutes": 10, - "mode": "LastBrewing" - }, - "tankStatus": true, - "groupCapabilities": [ - { - "capabilities": { - "groupType": "AV_Group", - "groupNumber": "Group1", - "boilerId": "CoffeeBoiler1", - "hasScale": false, - "hasFlowmeter": true, - "numberOfDoses": 4 - }, - "doses": [ - { - "groupNumber": "Group1", - "doseIndex": "DoseA", - "doseType": "PulsesType", - "stopTarget": 135 - }, - { - "groupNumber": "Group1", - "doseIndex": "DoseB", - "doseType": "PulsesType", - "stopTarget": 97 - }, - { - "groupNumber": "Group1", - "doseIndex": "DoseC", - "doseType": "PulsesType", - "stopTarget": 108 - }, - { - "groupNumber": "Group1", - "doseIndex": "DoseD", - "doseType": "PulsesType", - "stopTarget": 121 - } - ], - "doseMode": { - "groupNumber": "Group1", - "brewingType": "PulsesType" - } - } - ], - "machineMode": "BrewingMode", - "teaDoses": { - "DoseA": { - "doseIndex": "DoseA", - "stopTarget": 8 - } - }, - "boilers": [ - { - "id": "SteamBoiler", - "isEnabled": true, - "target": 123.90000152587891, - "current": 123.80000305175781 - }, - { - "id": "CoffeeBoiler1", - "isEnabled": true, - "target": 95, - "current": 96.5 - } - ], - "boilerTargetTemperature": { - "SteamBoiler": 123.90000152587891, - "CoffeeBoiler1": 95 - }, - "preinfusionMode": { - "Group1": { - "groupNumber": "Group1", - "preinfusionStyle": "PreinfusionByDoseType" - } - }, - "preinfusionSettings": { - "mode": "TypeB", - "Group1": [ - { - "mode": "TypeA", - "groupNumber": "Group1", - "doseType": "DoseA", - "preWetTime": 0.5, - "preWetHoldTime": 1 - }, - { - "mode": "TypeB", - "groupNumber": "Group1", - "doseType": "DoseA", - "preWetTime": 0, - "preWetHoldTime": 4 - }, - { - "mode": "TypeA", - "groupNumber": "Group1", - "doseType": "DoseB", - "preWetTime": 0.5, - "preWetHoldTime": 1 - }, - { - "mode": "TypeB", - "groupNumber": "Group1", - "doseType": "DoseB", - "preWetTime": 0, - "preWetHoldTime": 4 - }, - { - "mode": "TypeA", - "groupNumber": "Group1", - "doseType": "DoseC", - "preWetTime": 3.3, - "preWetHoldTime": 3.3 - }, - { - "mode": "TypeB", - "groupNumber": "Group1", - "doseType": "DoseC", - "preWetTime": 0, - "preWetHoldTime": 4 - }, - { - "mode": "TypeA", - "groupNumber": "Group1", - "doseType": "DoseD", - "preWetTime": 2, - "preWetHoldTime": 2 - }, - { - "mode": "TypeB", - "groupNumber": "Group1", - "doseType": "DoseD", - "preWetTime": 0, - "preWetHoldTime": 4 - } - ] - }, - "wakeUpSleepEntries": [ - { - "days": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ], - "enabled": true, - "id": "Os2OswX", - "steam": true, - "timeOff": "24:0", - "timeOn": "22:0" - }, - { - "days": ["sunday"], - "enabled": true, - "id": "aXFz5bJ", - "steam": true, - "timeOff": "7:30", - "timeOn": "7:0" - } - ], - "clock": "1901-07-08T10:29:00", - "firmwareVersions": [ - { - "name": "machine_firmware", - "fw_version": "1.40" - }, - { - "name": "gateway_firmware", - "fw_version": "v3.1-rc4" - } - ] -} diff --git a/tests/components/lamarzocco/fixtures/config_gs3.json b/tests/components/lamarzocco/fixtures/config_gs3.json new file mode 100644 index 00000000000..0c6c6c70b0a --- /dev/null +++ b/tests/components/lamarzocco/fixtures/config_gs3.json @@ -0,0 +1,377 @@ +{ + "serialNumber": "GS012345", + "type": "CoffeeMachine", + "name": "GS012345", + "location": "HOME", + "modelCode": "GS3AV", + "modelName": "GS3AV", + "connected": true, + "connectionDate": 1742489087479, + "offlineMode": false, + "requireFirmwareUpdate": false, + "availableFirmwareUpdate": false, + "coffeeStation": null, + "imageUrl": "https://lion.lamarzocco.io/img/thing-model/detail/gs3av/gs3av-1.png", + "bleAuthToken": null, + "widgets": [ + { + "code": "CMMachineStatus", + "index": 1, + "output": { + "status": "PoweredOn", + "availableModes": ["BrewingMode", "StandBy"], + "mode": "BrewingMode", + "nextStatus": { + "status": "StandBy", + "startTime": 1742857195332 + }, + "brewingStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMCoffeeBoiler", + "index": 1, + "output": { + "status": "Ready", + "enabled": true, + "enabledSupported": false, + "targetTemperature": 95.0, + "targetTemperatureMin": 80, + "targetTemperatureMax": 110, + "targetTemperatureStep": 0.1, + "readyStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMSteamBoilerTemperature", + "index": 1, + "output": { + "status": "Off", + "enabled": true, + "enabledSupported": true, + "targetTemperature": 123.9, + "targetTemperatureSupported": true, + "targetTemperatureMin": 95, + "targetTemperatureMax": 140, + "targetTemperatureStep": 0.1, + "readyStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMGroupDoses", + "index": 1, + "output": { + "mirrorWithGroup1Supported": false, + "mirrorWithGroup1": null, + "mirrorWithGroup1NotEffective": false, + "availableModes": ["PulsesType"], + "mode": "PulsesType", + "profile": null, + "doses": { + "PulsesType": [ + { + "doseIndex": "DoseA", + "dose": 126.0, + "doseMin": 0, + "doseMax": 9999, + "doseStep": 1 + }, + { + "doseIndex": "DoseB", + "dose": 126.0, + "doseMin": 0, + "doseMax": 9999, + "doseStep": 1 + }, + { + "doseIndex": "DoseC", + "dose": 160.0, + "doseMin": 0, + "doseMax": 9999, + "doseStep": 1 + }, + { + "doseIndex": "DoseD", + "dose": 77.0, + "doseMin": 0, + "doseMax": 9999, + "doseStep": 1 + } + ] + }, + "continuousDoseSupported": false, + "continuousDose": null, + "brewingPressureSupported": false, + "brewingPressure": null + }, + "tutorialUrl": null + }, + { + "code": "CMPreBrewing", + "index": 1, + "output": { + "availableModes": ["PreBrewing", "PreInfusion", "Disabled"], + "mode": "PreInfusion", + "times": { + "PreBrewing": [ + { + "doseIndex": "DoseA", + "seconds": { + "In": 0.5, + "Out": 1.0 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 10, + "Out": 10 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + }, + { + "doseIndex": "DoseB", + "seconds": { + "In": 0.5, + "Out": 1.0 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 10, + "Out": 10 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + }, + { + "doseIndex": "DoseC", + "seconds": { + "In": 3.3, + "Out": 3.3 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 10, + "Out": 10 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + }, + { + "doseIndex": "DoseD", + "seconds": { + "In": 2.0, + "Out": 2.0 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 10, + "Out": 10 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + } + ], + "PreInfusion": [ + { + "doseIndex": "DoseA", + "seconds": { + "In": 0.0, + "Out": 4.0 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 25, + "Out": 25 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + }, + { + "doseIndex": "DoseB", + "seconds": { + "In": 0.0, + "Out": 4.0 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 25, + "Out": 25 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + }, + { + "doseIndex": "DoseC", + "seconds": { + "In": 0.0, + "Out": 4.0 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 25, + "Out": 25 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + }, + { + "doseIndex": "DoseD", + "seconds": { + "In": 0.0, + "Out": 4.0 + }, + "secondsMin": { + "In": 0, + "Out": 0 + }, + "secondsMax": { + "In": 25, + "Out": 25 + }, + "secondsStep": { + "In": 0.1, + "Out": 0.1 + } + } + ] + }, + "doseIndexSupported": true + }, + "tutorialUrl": "https://www.lamarzocco.com/it/en/app/support/brewing-features/#gs3-av-linea-micra-linea-mini-home" + }, + { + "code": "CMHotWaterDose", + "index": 1, + "output": { + "enabledSupported": false, + "enabled": true, + "doses": [ + { + "doseIndex": "DoseA", + "dose": 8.0, + "doseMin": 0, + "doseMax": 90, + "doseStep": 1 + } + ] + }, + "tutorialUrl": null + }, + { + "code": "CMBackFlush", + "index": 1, + "output": { + "lastCleaningStartTime": null, + "status": "Off" + }, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/cleaning-and-backflush/#gs3-av" + } + ], + "invalidWidgets": [ + { + "code": "CMMachineGroupStatus", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMSteamBoilerLevel", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMPreExtraction", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMPreInfusionEnable", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMPreInfusion", + "index": 1, + "output": null, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/brewing-features/#commercial" + }, + { + "code": "CMBrewByWeightDoses", + "index": 1, + "output": null, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/brew-by-weight" + }, + { + "code": "CMCupWarmer", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMAutoFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMRinseFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMSteamFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMNoWater", + "index": 1, + "output": null, + "tutorialUrl": null + } + ], + "runningCommands": [] +} diff --git a/tests/components/lamarzocco/fixtures/config_micra.json b/tests/components/lamarzocco/fixtures/config_micra.json new file mode 100644 index 00000000000..64345c93682 --- /dev/null +++ b/tests/components/lamarzocco/fixtures/config_micra.json @@ -0,0 +1,237 @@ +{ + "serialNumber": "MR012345", + "type": "CoffeeMachine", + "name": "MR012345", + "location": null, + "modelCode": "LINEAMICRA", + "modelName": "LINEA MICRA", + "connected": true, + "connectionDate": 1742526019892, + "offlineMode": false, + "requireFirmwareUpdate": false, + "availableFirmwareUpdate": false, + "coffeeStation": null, + "imageUrl": "https://lion.lamarzocco.io/img/thing-model/detail/lineamicra/lineamicra-1-c-bianco.png", + "bleAuthToken": null, + "widgets": [ + { + "code": "CMMachineStatus", + "index": 1, + "output": { + "status": "StandBy", + "availableModes": ["BrewingMode", "StandBy"], + "mode": "StandBy", + "nextStatus": null, + "brewingStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMCoffeeBoiler", + "index": 1, + "output": { + "status": "StandBy", + "enabled": true, + "enabledSupported": false, + "targetTemperature": 94.0, + "targetTemperatureMin": 80, + "targetTemperatureMax": 100, + "targetTemperatureStep": 0.1, + "readyStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMSteamBoilerLevel", + "index": 1, + "output": { + "status": "StandBy", + "enabled": true, + "enabledSupported": true, + "targetLevel": "Level3", + "targetLevelSupported": true, + "readyStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMPreExtraction", + "index": 1, + "output": { + "availableModes": ["PreBrewing", "PreInfusion", "Disabled"], + "mode": "PreInfusion", + "times": { + "In": { + "seconds": 0.0, + "secondsMin": { + "PreBrewing": 2, + "PreInfusion": 2 + }, + "secondsMax": { + "PreBrewing": 9, + "PreInfusion": 9 + }, + "secondsStep": { + "PreBrewing": 0.1, + "PreInfusion": 0.1 + } + }, + "Out": { + "seconds": 4.0, + "secondsMin": { + "PreBrewing": 1, + "PreInfusion": 1 + }, + "secondsMax": { + "PreBrewing": 9, + "PreInfusion": 25 + }, + "secondsStep": { + "PreBrewing": 0.1, + "PreInfusion": 0.1 + } + } + } + }, + "tutorialUrl": null + }, + { + "code": "CMPreBrewing", + "index": 1, + "output": { + "availableModes": ["PreBrewing", "PreInfusion", "Disabled"], + "mode": "PreInfusion", + "times": { + "PreInfusion": [ + { + "doseIndex": "ByGroup", + "seconds": { + "Out": 4.0, + "In": 0.0 + }, + "secondsMin": { + "Out": 1, + "In": 1 + }, + "secondsMax": { + "Out": 25, + "In": 25 + }, + "secondsStep": { + "Out": 0.1, + "In": 0.1 + } + } + ], + "PreBrewing": [ + { + "doseIndex": "ByGroup", + "seconds": { + "Out": 5.0, + "In": 5.0 + }, + "secondsMin": { + "Out": 1, + "In": 1 + }, + "secondsMax": { + "Out": 9, + "In": 9 + }, + "secondsStep": { + "Out": 0.1, + "In": 0.1 + } + } + ] + }, + "doseIndexSupported": false + }, + "tutorialUrl": "https://www.lamarzocco.com/it/en/app/support/brewing-features/#gs3-av-linea-micra-linea-mini-home" + }, + { + "code": "CMBackFlush", + "index": 1, + "output": { + "lastCleaningStartTime": null, + "status": "Off" + }, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/cleaning-and-backflush/#linea-micra" + } + ], + "invalidWidgets": [ + { + "code": "CMMachineGroupStatus", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMSteamBoilerTemperature", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMGroupDoses", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMPreInfusionEnable", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMPreInfusion", + "index": 1, + "output": null, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/brewing-features/#commercial" + }, + { + "code": "CMBrewByWeightDoses", + "index": 1, + "output": null, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/brew-by-weight" + }, + { + "code": "CMCupWarmer", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMHotWaterDose", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMAutoFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMRinseFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMSteamFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMNoWater", + "index": 1, + "output": null, + "tutorialUrl": null + } + ], + "runningCommands": [] +} diff --git a/tests/components/lamarzocco/fixtures/config_mini.json b/tests/components/lamarzocco/fixtures/config_mini.json index a726d715a6f..a5a285800e9 100644 --- a/tests/components/lamarzocco/fixtures/config_mini.json +++ b/tests/components/lamarzocco/fixtures/config_mini.json @@ -1,124 +1,284 @@ { - "version": "v1", - "preinfusionModesAvailable": ["ByDoseType"], - "machineCapabilities": [ - { - "family": "LINEA", - "groupsNumber": 1, - "coffeeBoilersNumber": 1, - "hasCupWarmer": false, - "steamBoilersNumber": 1, - "teaDosesNumber": 1, - "machineModes": ["BrewingMode", "StandBy"], - "schedulingType": "smartWakeUpSleep" - } - ], - "machine_sn": "Sn01239157", - "machine_hw": "0", - "isPlumbedIn": false, - "isBackFlushEnabled": false, - "standByTime": 0, - "tankStatus": true, - "settings": [], - "recipes": [ - { - "id": "Recipe1", - "dose_mode": "Mass", - "recipe_doses": [ - { "id": "A", "target": 32 }, - { "id": "B", "target": 45 } - ] - } - ], - "recipeAssignment": [ - { - "dose_index": "DoseA", - "recipe_id": "Recipe1", - "recipe_dose": "A", - "group": "Group1" - } - ], - "groupCapabilities": [ - { - "capabilities": { - "groupType": "AV_Group", - "groupNumber": "Group1", - "boilerId": "CoffeeBoiler1", - "hasScale": false, - "hasFlowmeter": false, - "numberOfDoses": 1 - }, - "doses": [ - { - "groupNumber": "Group1", - "doseIndex": "DoseA", - "doseType": "MassType", - "stopTarget": 32 - } - ], - "doseMode": { "groupNumber": "Group1", "brewingType": "ManualType" } - } - ], - "machineMode": "StandBy", - "teaDoses": { "DoseA": { "doseIndex": "DoseA", "stopTarget": 0 } }, - "scale": { - "connected": true, - "address": "44:b7:d0:74:5f:90", - "name": "LMZ-123A45", - "battery": 64 - }, - "boilers": [ - { "id": "SteamBoiler", "isEnabled": false, "target": 0, "current": 0 }, - { "id": "CoffeeBoiler1", "isEnabled": true, "target": 89, "current": 42 } - ], - "boilerTargetTemperature": { "SteamBoiler": 0, "CoffeeBoiler1": 89 }, - "preinfusionMode": { - "Group1": { - "groupNumber": "Group1", - "preinfusionStyle": "PreinfusionByDoseType" - } - }, - "preinfusionSettings": { - "mode": "TypeB", - "Group1": [ + "serialNumber": "LM012345", + "type": "CoffeeMachine", + "name": "LM012345", + "location": null, + "modelCode": "LINEAMINI", + "modelName": "LINEA MINI", + "connected": true, + "connectionDate": 1742683649814, + "offlineMode": false, + "requireFirmwareUpdate": false, + "availableFirmwareUpdate": true, + "coffeeStation": { + "id": "a59cd870-dc75-428f-b73e-e5a247c6db73", + "name": "My coffee station", + "coffeeMachine": { + "serialNumber": "LM012345", + "type": "CoffeeMachine", + "name": null, + "location": null, + "modelCode": "LINEAMINI", + "modelName": "LINEA MINI", + "connected": true, + "connectionDate": 1742683649814, + "offlineMode": false, + "requireFirmwareUpdate": false, + "availableFirmwareUpdate": true, + "imageUrl": "https://lion.lamarzocco.io/img/thing-model/list/lineamini/lineamini-1-c-nero_op.png", + "bleAuthToken": null + }, + "grinders": [], + "accessories": [ { - "mode": "TypeA", - "groupNumber": "Group1", - "doseType": "Continuous", - "preWetTime": 2, - "preWetHoldTime": 3 - }, - { - "mode": "TypeB", - "groupNumber": "Group1", - "doseType": "Continuous", - "preWetTime": 0, - "preWetHoldTime": 3 + "type": "ScaleAcaiaLunar", + "name": "LMZ-123A12", + "connected": false, + "batteryLevel": null, + "imageUrl": null } ] }, - "wakeUpSleepEntries": [ + "imageUrl": "https://lion.lamarzocco.io/img/thing-model/detail/lineamini/lineamini-1-c-nero_op.png", + "bleAuthToken": null, + "widgets": [ { - "id": "T6aLl42", - "days": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ], - "steam": false, - "enabled": false, - "timeOn": "24:0", - "timeOff": "24:0" + "code": "CMMachineStatus", + "index": 1, + "output": { + "status": "StandBy", + "availableModes": ["BrewingMode", "StandBy"], + "mode": "StandBy", + "nextStatus": null, + "brewingStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMCoffeeBoiler", + "index": 1, + "output": { + "status": "StandBy", + "enabled": true, + "enabledSupported": false, + "targetTemperature": 90.0, + "targetTemperatureMin": 80, + "targetTemperatureMax": 100, + "targetTemperatureStep": 0.1, + "readyStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMSteamBoilerTemperature", + "index": 1, + "output": { + "status": "Off", + "enabled": false, + "enabledSupported": true, + "targetTemperature": 0.0, + "targetTemperatureSupported": false, + "targetTemperatureMin": 95, + "targetTemperatureMax": 140, + "targetTemperatureStep": 0.1, + "readyStartTime": null + }, + "tutorialUrl": null + }, + { + "code": "CMPreExtraction", + "index": 1, + "output": { + "availableModes": ["PreBrewing", "Disabled"], + "mode": "Disabled", + "times": { + "In": { + "seconds": 2.0, + "secondsMin": { + "PreBrewing": 2, + "PreInfusion": 2 + }, + "secondsMax": { + "PreBrewing": 9, + "PreInfusion": 9 + }, + "secondsStep": { + "PreBrewing": 0.1, + "PreInfusion": 0.1 + } + }, + "Out": { + "seconds": 3.0, + "secondsMin": { + "PreBrewing": 1, + "PreInfusion": 1 + }, + "secondsMax": { + "PreBrewing": 9, + "PreInfusion": 25 + }, + "secondsStep": { + "PreBrewing": 0.1, + "PreInfusion": 0.1 + } + } + } + }, + "tutorialUrl": null + }, + { + "code": "CMPreBrewing", + "index": 1, + "output": { + "availableModes": ["PreBrewing", "Disabled"], + "mode": "Disabled", + "times": { + "PreBrewing": [ + { + "doseIndex": "ByGroup", + "seconds": { + "Out": 3.0, + "In": 2.0 + }, + "secondsMin": { + "Out": 1, + "In": 1 + }, + "secondsMax": { + "Out": 9, + "In": 9 + }, + "secondsStep": { + "Out": 0.1, + "In": 0.1 + } + } + ] + }, + "doseIndexSupported": false + }, + "tutorialUrl": "https://www.lamarzocco.com/it/en/app/support/brewing-features/#gs3-av-linea-micra-linea-mini-home" + }, + { + "code": "CMBrewByWeightDoses", + "index": 1, + "output": { + "scaleConnected": false, + "availableModes": ["Continuous"], + "mode": "Continuous", + "doses": { + "Dose1": { + "dose": 34.5, + "doseMin": 5, + "doseMax": 100, + "doseStep": 0.1 + }, + "Dose2": { + "dose": 17.5, + "doseMin": 5, + "doseMax": 100, + "doseStep": 0.1 + } + } + }, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/brew-by-weight" + }, + { + "code": "CMBackFlush", + "index": 1, + "output": { + "lastCleaningStartTime": 1742731776135, + "status": "Off" + }, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/cleaning-and-backflush/#linea-mini" + }, + { + "code": "ThingScale", + "index": 2, + "output": { + "name": "LMZ-123A12", + "connected": false, + "batteryLevel": 0.0, + "calibrationRequired": false + }, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/training-scale-location/#linea-mini" } ], - "smartStandBy": { "mode": "LastBrewing", "minutes": 10, "enabled": true }, - "clock": "2024-08-31T14:47:45", - "firmwareVersions": [ - { "name": "machine_firmware", "fw_version": "2.12" }, - { "name": "gateway_firmware", "fw_version": "v3.6-rc4" } - ] + "invalidWidgets": [ + { + "code": "CMMachineGroupStatus", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMSteamBoilerLevel", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMGroupDoses", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMPreInfusionEnable", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMPreInfusion", + "index": 1, + "output": null, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/brewing-features/#commercial" + }, + { + "code": "CMCupWarmer", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMHotWaterDose", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMAutoFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMRinseFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMSteamFlush", + "index": 1, + "output": null, + "tutorialUrl": null + }, + { + "code": "CMNoWater", + "index": 1, + "output": { + "allarm": false + }, + "tutorialUrl": null + }, + { + "code": "ThingScale", + "index": 1, + "output": null, + "tutorialUrl": "http://lamarzocco.com/it/en/app/support/training-scale-location/#linea-mini" + } + ], + "runningCommands": [] } diff --git a/tests/components/lamarzocco/fixtures/schedule.json b/tests/components/lamarzocco/fixtures/schedule.json new file mode 100644 index 00000000000..1767503f5b9 --- /dev/null +++ b/tests/components/lamarzocco/fixtures/schedule.json @@ -0,0 +1,61 @@ +{ + "serialNumber": "MR123456", + "type": "CoffeeMachine", + "name": "MR123456", + "location": null, + "modelCode": "LINEAMICRA", + "modelName": "LINEA MICRA", + "connected": true, + "connectionDate": 1742526019892, + "offlineMode": false, + "requireFirmwareUpdate": false, + "availableFirmwareUpdate": false, + "coffeeStation": null, + "imageUrl": "https://lion.lamarzocco.io/img/thing-model/detail/lineamicra/lineamicra-1-c-bianco.png", + "bleAuthToken": null, + "smartWakeUpSleepSupported": true, + "smartWakeUpSleep": { + "smartStandByEnabled": true, + "smartStandByMinutes": 10, + "smartStandByMinutesMin": 1, + "smartStandByMinutesMax": 30, + "smartStandByMinutesStep": 1, + "smartStandByAfter": "PowerOn", + "schedules": [ + { + "id": "Os2OswX", + "enabled": true, + "onTimeMinutes": 1320, + "offTimeMinutes": 1440, + "days": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday" + ], + "steamBoiler": true + }, + { + "id": "aXFz5bJ", + "enabled": true, + "onTimeMinutes": 420, + "offTimeMinutes": 450, + "days": ["Sunday"], + "steamBoiler": false + } + ] + }, + "smartWakeUpSleepTutorialUrl": "https://www.lamarzocco.com/it/en/app/support/scheduling/#gs3-linea-micra-linea-mini-home", + "weeklySupported": false, + "weekly": null, + "weeklyTutorialUrl": "https://www.lamarzocco.com/it/en/app/support/scheduling/#linea-classic-s", + "autoOnOffSupported": false, + "autoOnOff": null, + "autoOnOffTutorialUrl": "https://www.lamarzocco.com/it/en/app/support/scheduling/#gb5-s-x-kb90-linea-pb-pbx-strada-s-x-commercial", + "autoStandBySupported": false, + "autoStandBy": null, + "autoStandByTutorialUrl": null +} diff --git a/tests/components/lamarzocco/fixtures/settings.json b/tests/components/lamarzocco/fixtures/settings.json new file mode 100644 index 00000000000..a2bd27febb2 --- /dev/null +++ b/tests/components/lamarzocco/fixtures/settings.json @@ -0,0 +1,50 @@ +{ + "serialNumber": "MR123456", + "type": "CoffeeMachine", + "name": "MR123456", + "location": null, + "modelCode": "LINEAMICRA", + "modelName": "LINEA MICRA", + "connected": true, + "connectionDate": 1742526019892, + "offlineMode": false, + "requireFirmwareUpdate": false, + "availableFirmwareUpdate": false, + "coffeeStation": null, + "imageUrl": "https://lion.lamarzocco.io/img/thing-model/detail/lineamicra/lineamicra-1-c-bianco.png", + "bleAuthToken": null, + "actualFirmwares": [ + { + "type": "Gateway", + "buildVersion": "v5.0.9", + "changeLog": "What’s new in this version:\n\n* New La Marzocco compatibility\n* Improved connectivity\n* Improved pairing process\n* Improved statistics\n* Boilers heating time\n* Last backflush date (GS3 MP excluded)\n* Automatic gateway updates option", + "thingModelCode": "LineaMicra", + "status": "ToUpdate", + "availableUpdate": { + "type": "Gateway", + "buildVersion": "v5.0.10", + "changeLog": "What’s new in this version:\n\n* fixed an issue that could cause the machine powers up outside scheduled time\n* minor improvements", + "thingModelCode": "LineaMicra" + } + }, + { + "type": "Machine", + "buildVersion": "v1.17", + "changeLog": null, + "thingModelCode": "LineaMicra", + "status": "Updated", + "availableUpdate": null + } + ], + "wifiSsid": "MyWifi", + "wifiRssi": -51, + "plumbInSupported": true, + "isPlumbedIn": true, + "cropsterSupported": false, + "cropsterActive": null, + "hemroSupported": false, + "hemroActive": null, + "factoryResetSupported": true, + "autoUpdateSupported": true, + "autoUpdate": false +} diff --git a/tests/components/lamarzocco/fixtures/statistics.json b/tests/components/lamarzocco/fixtures/statistics.json deleted file mode 100644 index c82d02cc7c1..00000000000 --- a/tests/components/lamarzocco/fixtures/statistics.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "count": 1047, - "coffeeType": 0 - }, - { - "count": 560, - "coffeeType": 1 - }, - { - "count": 468, - "coffeeType": 2 - }, - { - "count": 312, - "coffeeType": 3 - }, - { - "count": 2252, - "coffeeType": 4 - }, - { - "coffeeType": -1, - "count": 1740 - } -] diff --git a/tests/components/lamarzocco/fixtures/thing.json b/tests/components/lamarzocco/fixtures/thing.json new file mode 100644 index 00000000000..4265ad9ed8d --- /dev/null +++ b/tests/components/lamarzocco/fixtures/thing.json @@ -0,0 +1,16 @@ +{ + "serialNumber": "GS012345", + "type": "CoffeeMachine", + "name": "GS012345", + "location": "HOME", + "modelCode": "GS3AV", + "modelName": "GS3AV", + "connected": true, + "connectionDate": 1742489087479, + "offlineMode": false, + "requireFirmwareUpdate": false, + "availableFirmwareUpdate": false, + "coffeeStation": null, + "imageUrl": "https://lion.lamarzocco.io/img/thing-model/detail/gs3av/gs3av-1.png", + "bleAuthToken": null +} diff --git a/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr b/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr index 6cd4e8cd5ae..2abf182095e 100644 --- a/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr +++ b/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr @@ -143,51 +143,3 @@ 'state': 'off', }) # --- -# name: test_scale_connectivity[Linea Mini] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'connectivity', - 'friendly_name': 'LMZ-123A45 Connectivity', - }), - 'context': , - 'entity_id': 'binary_sensor.lmz_123a45_connectivity', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'on', - }) -# --- -# name: test_scale_connectivity[Linea Mini].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'binary_sensor', - 'entity_category': , - 'entity_id': 'binary_sensor.lmz_123a45_connectivity', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Connectivity', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'LM012345_connected', - 'unit_of_measurement': None, - }) -# --- diff --git a/tests/components/lamarzocco/snapshots/test_diagnostics.ambr b/tests/components/lamarzocco/snapshots/test_diagnostics.ambr index 018449f7c9a..6026ea0d7f4 100644 --- a/tests/components/lamarzocco/snapshots/test_diagnostics.ambr +++ b/tests/components/lamarzocco/snapshots/test_diagnostics.ambr @@ -1,135 +1,766 @@ # serializer version: 1 # name: test_diagnostics dict({ - 'config': dict({ - 'backflush_enabled': False, - 'bbw_settings': None, - 'boilers': dict({ - 'CoffeeBoiler1': dict({ - 'current_temperature': 96.5, - 'enabled': True, - 'target_temperature': 95, + 'dashboard': dict({ + 'available_firmware_update': False, + 'ble_auth_token': None, + 'coffee_station': None, + 'config': dict({ + 'CMBackFlush': dict({ + 'last_cleaning_start_time': None, + 'status': 'Off', }), - 'SteamBoiler': dict({ - 'current_temperature': 123.80000305175781, + 'CMCoffeeBoiler': dict({ 'enabled': True, - 'target_temperature': 123.9000015258789, + 'enabled_supported': False, + 'ready_start_time': None, + 'status': 'Ready', + 'target_temperature': 95.0, + 'target_temperature_max': 110, + 'target_temperature_min': 80, + 'target_temperature_step': 0.1, }), - }), - 'brew_active': False, - 'brew_active_duration': 0, - 'dose_hot_water': 8, - 'doses': dict({ - '1': 135, - '2': 97, - '3': 108, - '4': 121, - }), - 'plumbed_in': True, - 'prebrew_configuration': dict({ - '1': list([ - dict({ - 'off_time': 1, - 'on_time': 0.5, + 'CMGroupDoses': dict({ + 'available_modes': list([ + 'PulsesType', + ]), + 'brewing_pressure': None, + 'brewing_pressure_supported': False, + 'continuous_dose': None, + 'continuous_dose_supported': False, + 'doses': dict({ + 'pulses_type': list([ + dict({ + 'dose': 126.0, + 'dose_index': 'DoseA', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + dict({ + 'dose': 126.0, + 'dose_index': 'DoseB', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + dict({ + 'dose': 160.0, + 'dose_index': 'DoseC', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + dict({ + 'dose': 77.0, + 'dose_index': 'DoseD', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + ]), }), - dict({ - 'off_time': 4, - 'on_time': 0, - }), - ]), - '2': list([ - dict({ - 'off_time': 1, - 'on_time': 0.5, - }), - dict({ - 'off_time': 4, - 'on_time': 0, - }), - ]), - '3': list([ - dict({ - 'off_time': 3.3, - 'on_time': 3.3, - }), - dict({ - 'off_time': 4, - 'on_time': 0, - }), - ]), - '4': list([ - dict({ - 'off_time': 2, - 'on_time': 2, - }), - dict({ - 'off_time': 4, - 'on_time': 0, - }), - ]), - }), - 'prebrew_mode': 'TypeB', - 'scale': None, - 'smart_standby': dict({ - 'enabled': True, - 'minutes': 10, - 'mode': 'LastBrewing', - }), - 'turned_on': True, - 'wake_up_sleep_entries': dict({ - 'Os2OswX': dict({ - 'days': list([ - 'monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - 'saturday', - 'sunday', + 'mirror_with_group_1': None, + 'mirror_with_group_1_not_effective': False, + 'mirror_with_group_1_supported': False, + 'mode': 'PulsesType', + 'profile': None, + }), + 'CMHotWaterDose': dict({ + 'doses': list([ + dict({ + 'dose': 8.0, + 'dose_index': 'DoseA', + 'dose_max': 90.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), ]), 'enabled': True, - 'entry_id': 'Os2OswX', - 'steam': True, - 'time_off': '24:0', - 'time_on': '22:0', + 'enabled_supported': False, }), - 'aXFz5bJ': dict({ - 'days': list([ - 'sunday', + 'CMMachineStatus': dict({ + 'available_modes': list([ + 'BrewingMode', + 'StandBy', ]), + 'brewing_start_time': None, + 'mode': 'BrewingMode', + 'next_status': dict({ + 'start_time': '2025-03-24T22:59:55.332000+00:00', + 'status': 'StandBy', + }), + 'status': 'PoweredOn', + }), + 'CMPreBrewing': dict({ + 'available_modes': list([ + 'PreBrewing', + 'PreInfusion', + 'Disabled', + ]), + 'dose_index_supported': True, + 'mode': 'PreInfusion', + 'times': dict({ + 'pre_brewing': list([ + dict({ + 'dose_index': 'DoseA', + 'seconds': dict({ + 'In': 0.5, + 'Out': 1.0, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseB', + 'seconds': dict({ + 'In': 0.5, + 'Out': 1.0, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseC', + 'seconds': dict({ + 'In': 3.3, + 'Out': 3.3, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseD', + 'seconds': dict({ + 'In': 2.0, + 'Out': 2.0, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + ]), + 'pre_infusion': list([ + dict({ + 'dose_index': 'DoseA', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseB', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseC', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseD', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + ]), + }), + }), + 'CMSteamBoilerTemperature': dict({ 'enabled': True, - 'entry_id': 'aXFz5bJ', - 'steam': True, - 'time_off': '7:30', - 'time_on': '7:0', + 'enabled_supported': True, + 'ready_start_time': None, + 'status': 'Off', + 'target_temperature': 123.9, + 'target_temperature_max': 140, + 'target_temperature_min': 95, + 'target_temperature_step': 0.1, + 'target_temperature_supported': True, }), }), - 'water_contact': True, + 'connected': True, + 'connection_date': '2025-03-20T16:44:47.479000+00:00', + 'image_url': 'https://lion.lamarzocco.io/img/thing-model/detail/gs3av/gs3av-1.png', + 'location': 'HOME', + 'model_code': 'GS3AV', + 'model_name': 'GS3 AV', + 'name': 'GS012345', + 'offline_mode': False, + 'require_firmware_update': False, + 'serial_number': '**REDACTED**', + 'type': 'CoffeeMachine', + 'widgets': list([ + dict({ + 'code': 'CMMachineStatus', + 'index': 1, + 'output': dict({ + 'available_modes': list([ + 'BrewingMode', + 'StandBy', + ]), + 'brewing_start_time': None, + 'mode': 'BrewingMode', + 'next_status': dict({ + 'start_time': '2025-03-24T22:59:55.332000+00:00', + 'status': 'StandBy', + }), + 'status': 'PoweredOn', + }), + }), + dict({ + 'code': 'CMCoffeeBoiler', + 'index': 1, + 'output': dict({ + 'enabled': True, + 'enabled_supported': False, + 'ready_start_time': None, + 'status': 'Ready', + 'target_temperature': 95.0, + 'target_temperature_max': 110, + 'target_temperature_min': 80, + 'target_temperature_step': 0.1, + }), + }), + dict({ + 'code': 'CMSteamBoilerTemperature', + 'index': 1, + 'output': dict({ + 'enabled': True, + 'enabled_supported': True, + 'ready_start_time': None, + 'status': 'Off', + 'target_temperature': 123.9, + 'target_temperature_max': 140, + 'target_temperature_min': 95, + 'target_temperature_step': 0.1, + 'target_temperature_supported': True, + }), + }), + dict({ + 'code': 'CMGroupDoses', + 'index': 1, + 'output': dict({ + 'available_modes': list([ + 'PulsesType', + ]), + 'brewing_pressure': None, + 'brewing_pressure_supported': False, + 'continuous_dose': None, + 'continuous_dose_supported': False, + 'doses': dict({ + 'pulses_type': list([ + dict({ + 'dose': 126.0, + 'dose_index': 'DoseA', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + dict({ + 'dose': 126.0, + 'dose_index': 'DoseB', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + dict({ + 'dose': 160.0, + 'dose_index': 'DoseC', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + dict({ + 'dose': 77.0, + 'dose_index': 'DoseD', + 'dose_max': 9999.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + ]), + }), + 'mirror_with_group_1': None, + 'mirror_with_group_1_not_effective': False, + 'mirror_with_group_1_supported': False, + 'mode': 'PulsesType', + 'profile': None, + }), + }), + dict({ + 'code': 'CMPreBrewing', + 'index': 1, + 'output': dict({ + 'available_modes': list([ + 'PreBrewing', + 'PreInfusion', + 'Disabled', + ]), + 'dose_index_supported': True, + 'mode': 'PreInfusion', + 'times': dict({ + 'pre_brewing': list([ + dict({ + 'dose_index': 'DoseA', + 'seconds': dict({ + 'In': 0.5, + 'Out': 1.0, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseB', + 'seconds': dict({ + 'In': 0.5, + 'Out': 1.0, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseC', + 'seconds': dict({ + 'In': 3.3, + 'Out': 3.3, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseD', + 'seconds': dict({ + 'In': 2.0, + 'Out': 2.0, + }), + 'seconds_max': dict({ + 'In': 10.0, + 'Out': 10.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + ]), + 'pre_infusion': list([ + dict({ + 'dose_index': 'DoseA', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseB', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseC', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + dict({ + 'dose_index': 'DoseD', + 'seconds': dict({ + 'In': 0.0, + 'Out': 4.0, + }), + 'seconds_max': dict({ + 'In': 25.0, + 'Out': 25.0, + }), + 'seconds_min': dict({ + 'In': 0.0, + 'Out': 0.0, + }), + 'seconds_step': dict({ + 'In': 0.1, + 'Out': 0.1, + }), + }), + ]), + }), + }), + }), + dict({ + 'code': 'CMHotWaterDose', + 'index': 1, + 'output': dict({ + 'doses': list([ + dict({ + 'dose': 8.0, + 'dose_index': 'DoseA', + 'dose_max': 90.0, + 'dose_min': 0.0, + 'dose_step': 1, + }), + ]), + 'enabled': True, + 'enabled_supported': False, + }), + }), + dict({ + 'code': 'CMBackFlush', + 'index': 1, + 'output': dict({ + 'last_cleaning_start_time': None, + 'status': 'Off', + }), + }), + ]), }), - 'firmware': list([ - dict({ - 'machine': dict({ - 'current_version': '1.40', - 'latest_version': '1.55', + 'schedule': dict({ + 'available_firmware_update': False, + 'ble_auth_token': None, + 'coffee_station': None, + 'connected': True, + 'connection_date': '2025-03-21T03:00:19.892000+00:00', + 'image_url': 'https://lion.lamarzocco.io/img/thing-model/detail/lineamicra/lineamicra-1-c-bianco.png', + 'location': None, + 'model_code': 'LINEAMICRA', + 'model_name': 'Linea Micra', + 'name': 'MR123456', + 'offline_mode': False, + 'require_firmware_update': False, + 'serial_number': '**REDACTED**', + 'smart_wake_up_sleep': dict({ + 'schedules': list([ + dict({ + 'days': list([ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ]), + 'enabled': True, + 'id': 'Os2OswX', + 'offTimeMinutes': 1440, + 'onTimeMinutes': 1320, + 'steamBoiler': True, + }), + dict({ + 'days': list([ + 'Sunday', + ]), + 'enabled': True, + 'id': 'aXFz5bJ', + 'offTimeMinutes': 450, + 'onTimeMinutes': 420, + 'steamBoiler': False, + }), + ]), + 'schedules_dict': dict({ + 'Os2OswX': dict({ + 'days': list([ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ]), + 'enabled': True, + 'id': 'Os2OswX', + 'offTimeMinutes': 1440, + 'onTimeMinutes': 1320, + 'steamBoiler': True, + }), + 'aXFz5bJ': dict({ + 'days': list([ + 'Sunday', + ]), + 'enabled': True, + 'id': 'aXFz5bJ', + 'offTimeMinutes': 450, + 'onTimeMinutes': 420, + 'steamBoiler': False, + }), + }), + 'smart_stand_by_after': 'PowerOn', + 'smart_stand_by_enabled': True, + 'smart_stand_by_minutes': 10, + 'smart_stand_by_minutes_max': 30, + 'smart_stand_by_minutes_min': 1, + 'smart_stand_by_minutes_step': 1, + }), + 'smart_wake_up_sleep_supported': True, + 'type': 'CoffeeMachine', + }), + 'serial_number': '**REDACTED**', + 'settings': dict({ + 'actual_firmwares': list([ + dict({ + 'available_update': dict({ + 'build_version': 'v5.0.10', + 'change_log': ''' + What’s new in this version: + + * fixed an issue that could cause the machine powers up outside scheduled time + * minor improvements + ''', + 'thing_model_code': 'LineaMicra', + 'type': 'Gateway', + }), + 'build_version': 'v5.0.9', + 'change_log': ''' + What’s new in this version: + + * New La Marzocco compatibility + * Improved connectivity + * Improved pairing process + * Improved statistics + * Boilers heating time + * Last backflush date (GS3 MP excluded) + * Automatic gateway updates option + ''', + 'status': 'ToUpdate', + 'thing_model_code': 'LineaMicra', + 'type': 'Gateway', + }), + dict({ + 'available_update': None, + 'build_version': 'v1.17', + 'change_log': 'None', + 'status': 'Updated', + 'thing_model_code': 'LineaMicra', + 'type': 'Machine', + }), + ]), + 'auto_update': False, + 'auto_update_supported': True, + 'available_firmware_update': False, + 'ble_auth_token': None, + 'coffee_station': None, + 'connected': True, + 'connection_date': '2025-03-21T03:00:19.892000+00:00', + 'cropster_active': False, + 'cropster_supported': False, + 'factory_reset_supported': True, + 'firmwares': dict({ + 'Gateway': dict({ + 'available_update': dict({ + 'build_version': 'v5.0.10', + 'change_log': ''' + What’s new in this version: + + * fixed an issue that could cause the machine powers up outside scheduled time + * minor improvements + ''', + 'thing_model_code': 'LineaMicra', + 'type': 'Gateway', + }), + 'build_version': 'v5.0.9', + 'change_log': ''' + What’s new in this version: + + * New La Marzocco compatibility + * Improved connectivity + * Improved pairing process + * Improved statistics + * Boilers heating time + * Last backflush date (GS3 MP excluded) + * Automatic gateway updates option + ''', + 'status': 'ToUpdate', + 'thing_model_code': 'LineaMicra', + 'type': 'Gateway', + }), + 'Machine': dict({ + 'available_update': None, + 'build_version': 'v1.17', + 'change_log': 'None', + 'status': 'Updated', + 'thing_model_code': 'LineaMicra', + 'type': 'Machine', }), }), - dict({ - 'gateway': dict({ - 'current_version': 'v3.1-rc4', - 'latest_version': 'v3.5-rc3', - }), - }), - ]), - 'model': 'GS3 AV', - 'statistics': dict({ - 'continous': 2252, - 'drink_stats': dict({ - '1': 1047, - '2': 560, - '3': 468, - '4': 312, - }), - 'total_flushes': 1740, + 'hemro_active': False, + 'hemro_supported': False, + 'image_url': 'https://lion.lamarzocco.io/img/thing-model/detail/lineamicra/lineamicra-1-c-bianco.png', + 'is_plumbed_in': True, + 'location': None, + 'model_code': 'LINEAMICRA', + 'model_name': 'Linea Micra', + 'name': 'MR123456', + 'offline_mode': False, + 'plumb_in_supported': True, + 'require_firmware_update': False, + 'serial_number': '**REDACTED**', + 'type': 'CoffeeMachine', + 'wifi_rssi': -51, + 'wifi_ssid': 'MyWifi', }), }) # --- diff --git a/tests/components/lamarzocco/snapshots/test_init.ambr b/tests/components/lamarzocco/snapshots/test_init.ambr index 4c210136bd2..18b2fd0fbc3 100644 --- a/tests/components/lamarzocco/snapshots/test_init.ambr +++ b/tests/components/lamarzocco/snapshots/test_init.ambr @@ -29,47 +29,14 @@ 'labels': set({ }), 'manufacturer': 'La Marzocco', - 'model': , - 'model_id': , + 'model': 'GS3 AV', + 'model_id': 'GS3AV', 'name': 'GS012345', 'name_by_user': None, 'primary_config_entry': , 'serial_number': 'GS012345', 'suggested_area': None, - 'sw_version': '1.40', + 'sw_version': 'v1.17', 'via_device_id': None, }) # --- -# name: test_scale_device[Linea Mini] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'config_entries_subentries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'lamarzocco', - '44:b7:d0:74:5f:90', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'Acaia', - 'model': 'Lunar', - 'model_id': 'Y.301', - 'name': 'LMZ-123A45', - 'name_by_user': None, - 'primary_config_entry': , - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- diff --git a/tests/components/lamarzocco/snapshots/test_number.ambr b/tests/components/lamarzocco/snapshots/test_number.ambr index de1f11b14eb..d9a644567d5 100644 --- a/tests/components/lamarzocco/snapshots/test_number.ambr +++ b/tests/components/lamarzocco/snapshots/test_number.ambr @@ -1,5 +1,5 @@ # serializer version: 1 -# name: test_general_numbers[coffee_target_temperature-94-set_temp-kwargs0] +# name: test_general_numbers[coffee_target_temperature-94-set_coffee_target_temperature-kwargs0] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'temperature', @@ -15,10 +15,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '95', + 'state': '95.0', }) # --- -# name: test_general_numbers[coffee_target_temperature-94-set_temp-kwargs0].1 +# name: test_general_numbers[coffee_target_temperature-94-set_coffee_target_temperature-kwargs0].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -63,9 +63,9 @@ 'device_class': 'duration', 'friendly_name': 'GS012345 Smart standby time', 'max': 240, - 'min': 10, + 'min': 0, 'mode': , - 'step': 10, + 'step': 1, 'unit_of_measurement': , }), 'context': , @@ -83,9 +83,9 @@ 'area_id': None, 'capabilities': dict({ 'max': 240, - 'min': 10, + 'min': 0, 'mode': , - 'step': 10, + 'step': 1, }), 'config_entry_id': , 'config_subentry_id': , @@ -115,995 +115,3 @@ 'unit_of_measurement': , }) # --- -# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 AV] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'GS012345 Steam target temperature', - 'max': 131, - 'min': 126, - 'mode': , - 'step': 1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_steam_target_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '123.900001525879', - }) -# --- -# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 AV].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 131, - 'min': 126, - 'mode': , - 'step': 1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': None, - 'entity_id': 'number.gs012345_steam_target_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Steam target temperature', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'steam_temp', - 'unique_id': 'GS012345_steam_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 MP] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'GS012345 Steam target temperature', - 'max': 131, - 'min': 126, - 'mode': , - 'step': 1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_steam_target_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '123.900001525879', - }) -# --- -# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 MP].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 131, - 'min': 126, - 'mode': , - 'step': 1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': None, - 'entity_id': 'number.gs012345_steam_target_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Steam target temperature', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'steam_temp', - 'unique_id': 'GS012345_steam_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 AV] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Tea water duration', - 'max': 30, - 'min': 0, - 'mode': , - 'step': 1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_tea_water_duration', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '8', - }) -# --- -# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 AV].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 30, - 'min': 0, - 'mode': , - 'step': 1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': None, - 'entity_id': 'number.gs012345_tea_water_duration', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Tea water duration', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'tea_water_duration', - 'unique_id': 'GS012345_tea_water_duration', - 'unit_of_measurement': , - }) -# --- -# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 MP] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Tea water duration', - 'max': 30, - 'min': 0, - 'mode': , - 'step': 1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_tea_water_duration', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '8', - }) -# --- -# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 MP].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 30, - 'min': 0, - 'mode': , - 'step': 1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': None, - 'entity_id': 'number.gs012345_tea_water_duration', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Tea water duration', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'tea_water_duration', - 'unique_id': 'GS012345_tea_water_duration', - 'unit_of_measurement': , - }) -# --- -# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS012345_dose_key_1-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Dose Key 1', - 'max': 999, - 'min': 0, - 'mode': , - 'step': 1, - 'unit_of_measurement': 'ticks', - }), - 'context': , - 'entity_id': 'number.gs012345_dose_key_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '135', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS012345_dose_key_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Dose Key 2', - 'max': 999, - 'min': 0, - 'mode': , - 'step': 1, - 'unit_of_measurement': 'ticks', - }), - 'context': , - 'entity_id': 'number.gs012345_dose_key_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '97', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS012345_dose_key_3-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Dose Key 3', - 'max': 999, - 'min': 0, - 'mode': , - 'step': 1, - 'unit_of_measurement': 'ticks', - }), - 'context': , - 'entity_id': 'number.gs012345_dose_key_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '108', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS012345_dose_key_4-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Dose Key 4', - 'max': 999, - 'min': 0, - 'mode': , - 'step': 1, - 'unit_of_measurement': 'ticks', - }), - 'context': , - 'entity_id': 'number.gs012345_dose_key_4', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '121', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_1-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew off time Key 1', - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_off_time_key_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew off time Key 2', - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_off_time_key_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_3-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew off time Key 3', - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_off_time_key_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '3.3', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_4-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew off time Key 4', - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_off_time_key_4', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_1-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew on time Key 1', - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_on_time_key_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew on time Key 2', - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_on_time_key_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_3-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew on time Key 3', - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_on_time_key_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '3.3', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_4-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Prebrew on time Key 4', - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_prebrew_on_time_key_4', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_1-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Preinfusion time Key 1', - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_preinfusion_time_key_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '4', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Preinfusion time Key 2', - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_preinfusion_time_key_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '4', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_3-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Preinfusion time Key 3', - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_preinfusion_time_key_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '4', - }) -# --- -# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_4-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Preinfusion time Key 4', - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.gs012345_preinfusion_time_key_4', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '4', - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Linea Mini] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'LM012345 Prebrew off time', - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.lm012345_prebrew_off_time', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '3', - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Linea Mini].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.lm012345_prebrew_off_time', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Prebrew off time', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'prebrew_off', - 'unique_id': 'LM012345_prebrew_off', - 'unit_of_measurement': , - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Micra] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'MR012345 Prebrew off time', - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.mr012345_prebrew_off_time', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Micra].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 10, - 'min': 1, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.mr012345_prebrew_off_time', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Prebrew off time', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'prebrew_off', - 'unique_id': 'MR012345_prebrew_off', - 'unit_of_measurement': , - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Linea Mini] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'LM012345 Prebrew on time', - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.lm012345_prebrew_on_time', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '3', - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Linea Mini].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.lm012345_prebrew_on_time', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Prebrew on time', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'prebrew_on', - 'unique_id': 'LM012345_prebrew_on', - 'unit_of_measurement': , - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Micra] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'MR012345 Prebrew on time', - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.mr012345_prebrew_on_time', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Micra].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 10, - 'min': 2, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.mr012345_prebrew_on_time', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Prebrew on time', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'prebrew_on', - 'unique_id': 'MR012345_prebrew_on', - 'unit_of_measurement': , - }) -# --- -# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Linea Mini] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'LM012345 Preinfusion time', - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.lm012345_preinfusion_time', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '3', - }) -# --- -# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Linea Mini].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.lm012345_preinfusion_time', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Preinfusion time', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'preinfusion_off', - 'unique_id': 'LM012345_preinfusion_off', - 'unit_of_measurement': , - }) -# --- -# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Micra] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'MR012345 Preinfusion time', - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.mr012345_preinfusion_time', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '4', - }) -# --- -# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Micra].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 29, - 'min': 2, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.mr012345_preinfusion_time', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Preinfusion time', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'preinfusion_off', - 'unique_id': 'MR012345_preinfusion_off', - 'unit_of_measurement': , - }) -# --- -# name: test_set_target[Linea Mini-1] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'LMZ-123A45 Brew by weight target 1', - 'max': 100, - 'min': 1, - 'mode': , - 'step': 1, - }), - 'context': , - 'entity_id': 'number.lmz_123a45_brew_by_weight_target_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '32', - }) -# --- -# name: test_set_target[Linea Mini-1].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 100, - 'min': 1, - 'mode': , - 'step': 1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.lmz_123a45_brew_by_weight_target_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Brew by weight target 1', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'scale_target_key', - 'unique_id': 'LM012345_scale_target_key1', - 'unit_of_measurement': None, - }) -# --- -# name: test_set_target[Linea Mini-2] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'LMZ-123A45 Brew by weight target 2', - 'max': 100, - 'min': 1, - 'mode': , - 'step': 1, - }), - 'context': , - 'entity_id': 'number.lmz_123a45_brew_by_weight_target_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '45', - }) -# --- -# name: test_set_target[Linea Mini-2].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 100, - 'min': 1, - 'mode': , - 'step': 1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.lmz_123a45_brew_by_weight_target_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Brew by weight target 2', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'scale_target_key', - 'unique_id': 'LM012345_scale_target_key2', - 'unit_of_measurement': None, - }) -# --- diff --git a/tests/components/lamarzocco/snapshots/test_select.ambr b/tests/components/lamarzocco/snapshots/test_select.ambr index 2e88688652a..218b0092a49 100644 --- a/tests/components/lamarzocco/snapshots/test_select.ambr +++ b/tests/components/lamarzocco/snapshots/test_select.ambr @@ -1,60 +1,4 @@ # serializer version: 1 -# name: test_active_bbw_recipe[Linea Mini] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'LMZ-123A45 Active brew by weight recipe', - 'options': list([ - 'a', - 'b', - ]), - }), - 'context': , - 'entity_id': 'select.lmz_123a45_active_brew_by_weight_recipe', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'a', - }) -# --- -# name: test_active_bbw_recipe[Linea Mini].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'options': list([ - 'a', - 'b', - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'select', - 'entity_category': None, - 'entity_id': 'select.lmz_123a45_active_brew_by_weight_recipe', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Active brew by weight recipe', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_bbw', - 'unique_id': 'LM012345_active_bbw', - 'unit_of_measurement': None, - }) -# --- # name: test_pre_brew_infusion_select[GS3 AV] StateSnapshot({ 'attributes': ReadOnlyDict({ @@ -113,6 +57,64 @@ 'unit_of_measurement': None, }) # --- +# name: test_pre_brew_infusion_select[Linea Micra] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'MR012345 Prebrew/-infusion mode', + 'options': list([ + 'disabled', + 'prebrew', + 'preinfusion', + ]), + }), + 'context': , + 'entity_id': 'select.mr012345_prebrew_infusion_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'preinfusion', + }) +# --- +# name: test_pre_brew_infusion_select[Linea Micra].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'disabled', + 'prebrew', + 'preinfusion', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.mr012345_prebrew_infusion_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Prebrew/-infusion mode', + 'platform': 'lamarzocco', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'prebrew_infusion_select', + 'unique_id': 'MR012345_prebrew_infusion_select', + 'unit_of_measurement': None, + }) +# --- # name: test_pre_brew_infusion_select[Linea Mini] StateSnapshot({ 'attributes': ReadOnlyDict({ @@ -128,7 +130,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'preinfusion', + 'state': 'disabled', }) # --- # name: test_pre_brew_infusion_select[Linea Mini].1 @@ -171,64 +173,6 @@ 'unit_of_measurement': None, }) # --- -# name: test_pre_brew_infusion_select[Micra] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'MR012345 Prebrew/-infusion mode', - 'options': list([ - 'disabled', - 'prebrew', - 'preinfusion', - ]), - }), - 'context': , - 'entity_id': 'select.mr012345_prebrew_infusion_mode', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'preinfusion', - }) -# --- -# name: test_pre_brew_infusion_select[Micra].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'options': list([ - 'disabled', - 'prebrew', - 'preinfusion', - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'select', - 'entity_category': , - 'entity_id': 'select.mr012345_prebrew_infusion_mode', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Prebrew/-infusion mode', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'prebrew_infusion_select', - 'unique_id': 'MR012345_prebrew_infusion_select', - 'unit_of_measurement': None, - }) -# --- # name: test_smart_standby_mode StateSnapshot({ 'attributes': ReadOnlyDict({ @@ -243,7 +187,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'last_brewing', + 'state': 'power_on', }) # --- # name: test_smart_standby_mode.1 @@ -285,7 +229,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_steam_boiler_level[Micra] +# name: test_steam_boiler_level[Linea Micra] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'MR012345 Steam level', @@ -300,10 +244,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1', + 'state': '3', }) # --- -# name: test_steam_boiler_level[Micra].1 +# name: test_steam_boiler_level[Linea Micra].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), diff --git a/tests/components/lamarzocco/snapshots/test_sensor.ambr b/tests/components/lamarzocco/snapshots/test_sensor.ambr deleted file mode 100644 index 996dff93433..00000000000 --- a/tests/components/lamarzocco/snapshots/test_sensor.ambr +++ /dev/null @@ -1,521 +0,0 @@ -# serializer version: 1 -# name: test_scale_battery[Linea Mini] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'battery', - 'friendly_name': 'LMZ-123A45 Battery', - 'state_class': , - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.lmz_123a45_battery', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '64', - }) -# --- -# name: test_scale_battery[Linea Mini].1 - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.lmz_123a45_battery', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Battery', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'LM012345_scale_battery', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_1-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gs012345_coffees_made_key_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Coffees made Key 1', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'drink_stats_coffee_key', - 'unique_id': 'GS012345_drink_stats_coffee_key_key1', - 'unit_of_measurement': 'coffees', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_1-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Coffees made Key 1', - 'state_class': , - 'unit_of_measurement': 'coffees', - }), - 'context': , - 'entity_id': 'sensor.gs012345_coffees_made_key_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1047', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_2-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gs012345_coffees_made_key_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Coffees made Key 2', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'drink_stats_coffee_key', - 'unique_id': 'GS012345_drink_stats_coffee_key_key2', - 'unit_of_measurement': 'coffees', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Coffees made Key 2', - 'state_class': , - 'unit_of_measurement': 'coffees', - }), - 'context': , - 'entity_id': 'sensor.gs012345_coffees_made_key_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '560', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_3-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gs012345_coffees_made_key_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Coffees made Key 3', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'drink_stats_coffee_key', - 'unique_id': 'GS012345_drink_stats_coffee_key_key3', - 'unit_of_measurement': 'coffees', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_3-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Coffees made Key 3', - 'state_class': , - 'unit_of_measurement': 'coffees', - }), - 'context': , - 'entity_id': 'sensor.gs012345_coffees_made_key_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '468', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_4-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gs012345_coffees_made_key_4', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Coffees made Key 4', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'drink_stats_coffee_key', - 'unique_id': 'GS012345_drink_stats_coffee_key_key4', - 'unit_of_measurement': 'coffees', - }) -# --- -# name: test_sensors[sensor.gs012345_coffees_made_key_4-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Coffees made Key 4', - 'state_class': , - 'unit_of_measurement': 'coffees', - }), - 'context': , - 'entity_id': 'sensor.gs012345_coffees_made_key_4', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '312', - }) -# --- -# name: test_sensors[sensor.gs012345_current_coffee_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.gs012345_current_coffee_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Current coffee temperature', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'current_temp_coffee', - 'unique_id': 'GS012345_current_temp_coffee', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.gs012345_current_coffee_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'GS012345 Current coffee temperature', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.gs012345_current_coffee_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '96.5', - }) -# --- -# name: test_sensors[sensor.gs012345_current_steam_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.gs012345_current_steam_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Current steam temperature', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'current_temp_steam', - 'unique_id': 'GS012345_current_temp_steam', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.gs012345_current_steam_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'GS012345 Current steam temperature', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.gs012345_current_steam_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '123.800003051758', - }) -# --- -# name: test_sensors[sensor.gs012345_shot_timer-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gs012345_shot_timer', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Shot timer', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'shot_timer', - 'unique_id': 'GS012345_shot_timer', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.gs012345_shot_timer-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'duration', - 'friendly_name': 'GS012345 Shot timer', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.gs012345_shot_timer', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[sensor.gs012345_total_coffees_made-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gs012345_total_coffees_made', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Total coffees made', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'drink_stats_coffee', - 'unique_id': 'GS012345_drink_stats_coffee', - 'unit_of_measurement': 'coffees', - }) -# --- -# name: test_sensors[sensor.gs012345_total_coffees_made-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Total coffees made', - 'state_class': , - 'unit_of_measurement': 'coffees', - }), - 'context': , - 'entity_id': 'sensor.gs012345_total_coffees_made', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2387', - }) -# --- -# name: test_sensors[sensor.gs012345_total_flushes_made-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gs012345_total_flushes_made', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Total flushes made', - 'platform': 'lamarzocco', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'drink_stats_flushing', - 'unique_id': 'GS012345_drink_stats_flushing', - 'unit_of_measurement': 'flushes', - }) -# --- -# name: test_sensors[sensor.gs012345_total_flushes_made-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'GS012345 Total flushes made', - 'state_class': , - 'unit_of_measurement': 'flushes', - }), - 'context': , - 'entity_id': 'sensor.gs012345_total_flushes_made', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1740', - }) -# --- diff --git a/tests/components/lamarzocco/snapshots/test_update.ambr b/tests/components/lamarzocco/snapshots/test_update.ambr index 17d0528c3d8..d1ca030ab8c 100644 --- a/tests/components/lamarzocco/snapshots/test_update.ambr +++ b/tests/components/lamarzocco/snapshots/test_update.ambr @@ -42,8 +42,8 @@ 'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png', 'friendly_name': 'GS012345 Gateway firmware', 'in_progress': False, - 'installed_version': 'v3.1-rc4', - 'latest_version': 'v3.5-rc3', + 'installed_version': 'v5.0.9', + 'latest_version': 'v5.0.10', 'release_summary': None, 'release_url': 'https://support-iot.lamarzocco.com/firmware-updates/', 'skipped_version': None, @@ -102,8 +102,8 @@ 'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png', 'friendly_name': 'GS012345 Machine firmware', 'in_progress': False, - 'installed_version': '1.40', - 'latest_version': '1.55', + 'installed_version': 'v1.17', + 'latest_version': 'v1.17', 'release_summary': None, 'release_url': 'https://support-iot.lamarzocco.com/firmware-updates/', 'skipped_version': None, @@ -116,6 +116,6 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'on', + 'state': 'off', }) # --- diff --git a/tests/components/lamarzocco/test_binary_sensor.py b/tests/components/lamarzocco/test_binary_sensor.py index d50d0ad9f84..d9e32d8dd41 100644 --- a/tests/components/lamarzocco/test_binary_sensor.py +++ b/tests/components/lamarzocco/test_binary_sensor.py @@ -4,10 +4,7 @@ from datetime import timedelta from unittest.mock import MagicMock, patch from freezegun.api import FrozenDateTimeFactory -from pylamarzocco.const import MachineModel from pylamarzocco.exceptions import RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoScale -import pytest from syrupy import SnapshotAssertion from homeassistant.const import STATE_UNAVAILABLE, Platform @@ -35,26 +32,14 @@ async def test_binary_sensors( await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) -async def test_brew_active_does_not_exists( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry_no_local_connection: MockConfigEntry, -) -> None: - """Test the La Marzocco currently_making_coffee doesn't exist if host not set.""" - - await async_init_integration(hass, mock_config_entry_no_local_connection) - state = hass.states.get(f"sensor.{mock_lamarzocco.serial_number}_brewing_active") - assert state is None - - async def test_brew_active_unavailable( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry, ) -> None: - """Test the La Marzocco currently_making_coffee becomes unavailable.""" + """Test the La Marzocco brew active becomes unavailable.""" - mock_lamarzocco.websocket_connected = False + mock_lamarzocco.websocket.connected = False await async_init_integration(hass, mock_config_entry) state = hass.states.get( f"binary_sensor.{mock_lamarzocco.serial_number}_brewing_active" @@ -79,7 +64,7 @@ async def test_sensor_going_unavailable( assert state assert state.state != STATE_UNAVAILABLE - mock_lamarzocco.get_config.side_effect = RequestNotSuccessful("") + mock_lamarzocco.get_dashboard.side_effect = RequestNotSuccessful("") freezer.tick(timedelta(minutes=10)) async_fire_time_changed(hass) await hass.async_block_till_done() @@ -87,68 +72,3 @@ async def test_sensor_going_unavailable( state = hass.states.get(brewing_active_sensor) assert state assert state.state == STATE_UNAVAILABLE - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_scale_connectivity( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - entity_registry: er.EntityRegistry, - snapshot: SnapshotAssertion, -) -> None: - """Test the scale binary sensors.""" - await async_init_integration(hass, mock_config_entry) - - state = hass.states.get("binary_sensor.lmz_123a45_connectivity") - assert state - assert state == snapshot - - entry = entity_registry.async_get(state.entity_id) - assert entry - assert entry.device_id - assert entry == snapshot - - -@pytest.mark.parametrize( - "device_fixture", - [MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA], -) -async def test_other_models_no_scale_connectivity( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - snapshot: SnapshotAssertion, -) -> None: - """Ensure the other models don't have a connectivity sensor.""" - await async_init_integration(hass, mock_config_entry) - - state = hass.states.get("binary_sensor.lmz_123a45_connectivity") - assert state is None - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_connectivity_on_new_scale_added( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, -) -> None: - """Ensure the connectivity binary sensor for a new scale is added automatically.""" - - mock_lamarzocco.config.scale = None - await async_init_integration(hass, mock_config_entry) - - state = hass.states.get("binary_sensor.scale_123a45_connectivity") - assert state is None - - mock_lamarzocco.config.scale = LaMarzoccoScale( - connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50 - ) - - freezer.tick(timedelta(minutes=10)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - state = hass.states.get("binary_sensor.scale_123a45_connectivity") - assert state diff --git a/tests/components/lamarzocco/test_calendar.py b/tests/components/lamarzocco/test_calendar.py index dd590a20db1..0d8db9bec89 100644 --- a/tests/components/lamarzocco/test_calendar.py +++ b/tests/components/lamarzocco/test_calendar.py @@ -127,7 +127,12 @@ async def test_no_calendar_events_global_disable( wake_up_sleep_entry_id = WAKE_UP_SLEEP_ENTRY_IDS[0] - mock_lamarzocco.config.wake_up_sleep_entries[wake_up_sleep_entry_id].enabled = False + wake_up_sleep_entry = mock_lamarzocco.schedule.smart_wake_up_sleep.schedules_dict[ + wake_up_sleep_entry_id + ] + + assert wake_up_sleep_entry + wake_up_sleep_entry.enabled = False test_time = datetime(2024, 1, 12, 11, tzinfo=dt_util.get_default_time_zone()) freezer.move_to(test_time) diff --git a/tests/components/lamarzocco/test_config_flow.py b/tests/components/lamarzocco/test_config_flow.py index 02ade8f2b9c..2bdbd007034 100644 --- a/tests/components/lamarzocco/test_config_flow.py +++ b/tests/components/lamarzocco/test_config_flow.py @@ -1,11 +1,11 @@ """Test the La Marzocco config flow.""" from collections.abc import Generator +from copy import deepcopy from unittest.mock import AsyncMock, MagicMock, patch -from pylamarzocco.const import MachineModel +from pylamarzocco.const import ModelName from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoDeviceInfo import pytest from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE @@ -15,18 +15,11 @@ from homeassistant.config_entries import ( SOURCE_DHCP, SOURCE_USER, ConfigEntryState, + ConfigFlowResult, ) -from homeassistant.const import ( - CONF_ADDRESS, - CONF_HOST, - CONF_MAC, - CONF_MODEL, - CONF_NAME, - CONF_PASSWORD, - CONF_TOKEN, -) +from homeassistant.const import CONF_ADDRESS, CONF_MAC, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult, FlowResultType +from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from . import USER_INPUT, async_init_integration, get_bluetooth_service_info @@ -35,8 +28,8 @@ from tests.common import MockConfigEntry async def __do_successful_user_step( - hass: HomeAssistant, result: FlowResult, mock_cloud_client: MagicMock -) -> FlowResult: + hass: HomeAssistant, result: ConfigFlowResult, mock_cloud_client: MagicMock +) -> ConfigFlowResult: """Successfully configure the user step.""" result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -50,39 +43,28 @@ async def __do_successful_user_step( async def __do_sucessful_machine_selection_step( - hass: HomeAssistant, result2: FlowResult, mock_device_info: LaMarzoccoDeviceInfo + hass: HomeAssistant, result2: ConfigFlowResult ) -> None: """Successfully configure the machine selection step.""" - with patch( - "homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection", - return_value=True, - ): - 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() + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {CONF_MACHINE: "GS012345"}, + ) assert result3["type"] is FlowResultType.CREATE_ENTRY - assert result3["title"] == "GS3" + assert result3["title"] == "GS012345" assert result3["data"] == { **USER_INPUT, - CONF_HOST: "192.168.1.1", - CONF_MODEL: mock_device_info.model, - CONF_NAME: mock_device_info.name, - CONF_TOKEN: mock_device_info.communication_key, + CONF_TOKEN: None, } + assert result3["result"].unique_id == "GS012345" async def test_form( hass: HomeAssistant, mock_cloud_client: MagicMock, - mock_device_info: LaMarzoccoDeviceInfo, mock_setup_entry: Generator[AsyncMock], ) -> None: """Test we get the form.""" @@ -94,13 +76,12 @@ async def test_form( assert result["step_id"] == "user" result2 = await __do_successful_user_step(hass, result, mock_cloud_client) - await __do_sucessful_machine_selection_step(hass, result2, mock_device_info) + await __do_sucessful_machine_selection_step(hass, result2) async def test_form_abort_already_configured( hass: HomeAssistant, mock_cloud_client: MagicMock, - mock_device_info: LaMarzoccoDeviceInfo, mock_config_entry: MockConfigEntry, ) -> None: """Test we abort if already configured.""" @@ -124,8 +105,7 @@ async def test_form_abort_already_configured( result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { - CONF_HOST: "192.168.1.1", - CONF_MACHINE: mock_device_info.serial_number, + CONF_MACHINE: "GS012345", }, ) await hass.async_block_till_done() @@ -134,15 +114,23 @@ async def test_form_abort_already_configured( assert result3["reason"] == "already_configured" +@pytest.mark.parametrize( + ("side_effect", "error"), + [ + (AuthFail(""), "invalid_auth"), + (RequestNotSuccessful(""), "cannot_connect"), + ], +) async def test_form_invalid_auth( hass: HomeAssistant, - mock_device_info: LaMarzoccoDeviceInfo, mock_cloud_client: MagicMock, mock_setup_entry: Generator[AsyncMock], + side_effect: Exception, + error: str, ) -> None: """Test invalid auth error.""" - mock_cloud_client.get_customer_fleet.side_effect = AuthFail("") + mock_cloud_client.list_things.side_effect = side_effect result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) @@ -153,67 +141,24 @@ async def test_form_invalid_auth( ) assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "invalid_auth"} - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1 + assert result2["errors"] == {"base": error} + assert len(mock_cloud_client.list_things.mock_calls) == 1 # test recovery from failure - mock_cloud_client.get_customer_fleet.side_effect = None + mock_cloud_client.list_things.side_effect = None result2 = await __do_successful_user_step(hass, result, mock_cloud_client) - await __do_sucessful_machine_selection_step(hass, result2, mock_device_info) + await __do_sucessful_machine_selection_step(hass, result2) -async def test_form_invalid_host( +async def test_form_no_machines( hass: HomeAssistant, mock_cloud_client: MagicMock, - mock_device_info: LaMarzoccoDeviceInfo, mock_setup_entry: Generator[AsyncMock], ) -> None: - """Test invalid auth error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - assert result["type"] is FlowResultType.FORM - assert result["errors"] == {} + """Test we don't have any devices.""" - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - USER_INPUT, - ) - await hass.async_block_till_done() - - assert result2["type"] is FlowResultType.FORM - assert result2["step_id"] == "machine_selection" - - with patch( - "homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection", - return_value=False, - ): - 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["errors"] == {"host": "cannot_connect"} - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1 - - # test recovery from failure - await __do_sucessful_machine_selection_step(hass, result2, mock_device_info) - - -async def test_form_cannot_connect( - hass: HomeAssistant, - mock_cloud_client: MagicMock, - mock_device_info: LaMarzoccoDeviceInfo, - mock_setup_entry: Generator[AsyncMock], -) -> None: - """Test cannot connect error.""" - - mock_cloud_client.get_customer_fleet.return_value = {} + original_return = mock_cloud_client.list_things.return_value + mock_cloud_client.list_things.return_value = [] result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -226,25 +171,13 @@ async def test_form_cannot_connect( assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "no_machines"} - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1 - - mock_cloud_client.get_customer_fleet.side_effect = RequestNotSuccessful("") - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - USER_INPUT, - ) - - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "cannot_connect"} - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 2 + assert len(mock_cloud_client.list_things.mock_calls) == 1 # test recovery from failure - mock_cloud_client.get_customer_fleet.side_effect = None - mock_cloud_client.get_customer_fleet.return_value = { - mock_device_info.serial_number: mock_device_info - } + mock_cloud_client.list_things.return_value = original_return + result2 = await __do_successful_user_step(hass, result, mock_cloud_client) - await __do_sucessful_machine_selection_step(hass, result2, mock_device_info) + await __do_sucessful_machine_selection_step(hass, result2) async def test_reauth_flow( @@ -269,7 +202,7 @@ async def test_reauth_flow( assert result2["type"] is FlowResultType.ABORT await hass.async_block_till_done() assert result2["reason"] == "reauth_successful" - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1 + assert len(mock_cloud_client.list_things.mock_calls) == 1 assert mock_config_entry.data[CONF_PASSWORD] == "new_password" @@ -277,7 +210,6 @@ async def test_reconfigure_flow( hass: HomeAssistant, mock_cloud_client: MagicMock, mock_config_entry: MockConfigEntry, - mock_device_info: LaMarzoccoDeviceInfo, mock_setup_entry: Generator[AsyncMock], ) -> None: """Testing reconfgure flow.""" @@ -289,15 +221,9 @@ async def test_reconfigure_flow( assert result["step_id"] == "reconfigure" 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 - ) + service_info = get_bluetooth_service_info(ModelName.GS3_MP, "GS012345") 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], @@ -306,8 +232,7 @@ async def test_reconfigure_flow( result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { - CONF_HOST: "192.168.1.1", - CONF_MACHINE: mock_device_info.serial_number, + CONF_MACHINE: "GS012345", }, ) await hass.async_block_till_done() @@ -338,8 +263,10 @@ async def test_bluetooth_discovery( ) -> None: """Test bluetooth discovery.""" service_info = get_bluetooth_service_info( - mock_lamarzocco.model, mock_lamarzocco.serial_number + ModelName.GS3_MP, mock_lamarzocco.serial_number ) + mock_cloud_client.list_things.return_value[0].ble_auth_token = "dummyToken" + result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_BLUETOOTH}, data=service_info ) @@ -351,33 +278,13 @@ async def test_bluetooth_discovery( result["flow_id"], USER_INPUT, ) - assert result2["type"] is FlowResultType.FORM - assert result2["step_id"] == "machine_selection" + assert result2["type"] is FlowResultType.CREATE_ENTRY - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1 - with patch( - "homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection", - return_value=True, - ): - 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"] is FlowResultType.CREATE_ENTRY - - assert result3["title"] == "GS3" - assert result3["data"] == { + assert result2["title"] == "GS012345" + assert result2["data"] == { **USER_INPUT, - CONF_HOST: "192.168.1.1", - CONF_MACHINE: mock_lamarzocco.serial_number, - CONF_NAME: "GS3", CONF_MAC: "aa:bb:cc:dd:ee:ff", - CONF_MODEL: mock_lamarzocco.model, - CONF_TOKEN: "token", + CONF_TOKEN: "dummyToken", } @@ -392,7 +299,7 @@ async def test_bluetooth_discovery_already_configured( mock_config_entry.add_to_hass(hass) service_info = get_bluetooth_service_info( - mock_lamarzocco.model, mock_lamarzocco.serial_number + ModelName.GS3_MP, mock_lamarzocco.serial_number ) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_BLUETOOTH}, data=service_info @@ -405,12 +312,11 @@ async def test_bluetooth_discovery_errors( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_cloud_client: MagicMock, - mock_device_info: LaMarzoccoDeviceInfo, mock_setup_entry: Generator[AsyncMock], ) -> None: """Test bluetooth discovery errors.""" service_info = get_bluetooth_service_info( - mock_lamarzocco.model, mock_lamarzocco.serial_number + ModelName.GS3_MP, mock_lamarzocco.serial_number ) result = await hass.config_entries.flow.async_init( DOMAIN, @@ -421,61 +327,37 @@ async def test_bluetooth_discovery_errors( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" - mock_cloud_client.get_customer_fleet.return_value = {"GS98765", ""} + original_return = deepcopy(mock_cloud_client.list_things.return_value) + mock_cloud_client.list_things.return_value[0].serial_number = "GS98765" + result2 = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT, ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "machine_not_found"} - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1 + assert len(mock_cloud_client.list_things.mock_calls) == 1 - mock_cloud_client.get_customer_fleet.return_value = { - mock_device_info.serial_number: mock_device_info - } + mock_cloud_client.list_things.return_value = original_return result2 = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT, ) - assert result2["type"] is FlowResultType.FORM - assert result2["step_id"] == "machine_selection" - assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 2 - with patch( - "homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection", - return_value=True, - ): - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], - { - CONF_HOST: "192.168.1.1", - }, - ) - await hass.async_block_till_done() + assert result2["type"] is FlowResultType.CREATE_ENTRY - assert result3["type"] is FlowResultType.CREATE_ENTRY - - assert result3["title"] == "GS3" - assert result3["data"] == { + assert result2["title"] == "GS012345" + assert result2["data"] == { **USER_INPUT, - CONF_HOST: "192.168.1.1", - CONF_MACHINE: mock_lamarzocco.serial_number, - CONF_NAME: "GS3", CONF_MAC: "aa:bb:cc:dd:ee:ff", - CONF_MODEL: mock_lamarzocco.model, - CONF_TOKEN: "token", + CONF_TOKEN: None, } -@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, mock_setup_entry: Generator[AsyncMock], ) -> None: """Test dhcp discovery.""" @@ -493,24 +375,16 @@ async def test_dhcp_discovery( 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_ADDRESS: "aa:bb:cc:dd:ee:ff", - 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, - } + 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_ADDRESS: "aa:bb:cc:dd:ee:ff", + CONF_TOKEN: None, + } async def test_dhcp_discovery_abort_on_hostname_changed( @@ -541,7 +415,6 @@ async def test_dhcp_already_configured_and_update( mock_config_entry: MockConfigEntry, ) -> None: """Test discovered IP address change.""" - old_ip = mock_config_entry.data[CONF_HOST] old_address = mock_config_entry.data[CONF_ADDRESS] mock_config_entry.add_to_hass(hass) @@ -557,9 +430,6 @@ async def test_dhcp_already_configured_and_update( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "already_configured" - assert mock_config_entry.data[CONF_HOST] != old_ip - assert mock_config_entry.data[CONF_HOST] == "192.168.1.42" - assert mock_config_entry.data[CONF_ADDRESS] != old_address assert mock_config_entry.data[CONF_ADDRESS] == "aa:bb:cc:dd:ee:ff" diff --git a/tests/components/lamarzocco/test_init.py b/tests/components/lamarzocco/test_init.py index a9a3b9f23e1..62314085b2e 100644 --- a/tests/components/lamarzocco/test_init.py +++ b/tests/components/lamarzocco/test_init.py @@ -1,11 +1,10 @@ """Test initialization of lamarzocco.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch -from freezegun.api import FrozenDateTimeFactory -from pylamarzocco.const import FirmwareType, MachineModel +from pylamarzocco.const import FirmwareType, ModelName from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful +from pylamarzocco.models import WebSocketDetails import pytest from syrupy import SnapshotAssertion @@ -13,6 +12,7 @@ from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE from homeassistant.components.lamarzocco.const import DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.const import ( + CONF_ADDRESS, CONF_HOST, CONF_MAC, CONF_MODEL, @@ -29,7 +29,7 @@ from homeassistant.helpers import ( from . import USER_INPUT, async_init_integration, get_bluetooth_service_info -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import MockConfigEntry async def test_load_unload_config_entry( @@ -54,25 +54,48 @@ async def test_config_entry_not_ready( mock_lamarzocco: MagicMock, ) -> None: """Test the La Marzocco configuration entry not ready.""" - mock_lamarzocco.get_config.side_effect = RequestNotSuccessful("") + mock_lamarzocco.get_dashboard.side_effect = RequestNotSuccessful("") await async_init_integration(hass, mock_config_entry) - assert len(mock_lamarzocco.get_config.mock_calls) == 1 + assert len(mock_lamarzocco.get_dashboard.mock_calls) == 1 assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY +@pytest.mark.parametrize( + ("side_effect", "expected_state"), + [ + (AuthFail(""), ConfigEntryState.SETUP_ERROR), + (RequestNotSuccessful(""), ConfigEntryState.SETUP_RETRY), + ], +) +async def test_get_settings_errors( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_cloud_client: MagicMock, + side_effect: Exception, + expected_state: ConfigEntryState, +) -> None: + """Test error during initial settings get.""" + mock_cloud_client.get_thing_settings.side_effect = side_effect + + await async_init_integration(hass, mock_config_entry) + + assert len(mock_cloud_client.get_thing_settings.mock_calls) == 1 + assert mock_config_entry.state is expected_state + + async def test_invalid_auth( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_lamarzocco: MagicMock, ) -> None: """Test auth error during setup.""" - mock_lamarzocco.get_config.side_effect = AuthFail("") + mock_lamarzocco.get_dashboard.side_effect = AuthFail("") await async_init_integration(hass, mock_config_entry) assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR - assert len(mock_lamarzocco.get_config.mock_calls) == 1 + assert len(mock_lamarzocco.get_dashboard.mock_calls) == 1 flows = hass.config_entries.flow.async_progress() assert len(flows) == 1 @@ -86,37 +109,54 @@ async def test_invalid_auth( assert flow["context"].get("entry_id") == mock_config_entry.entry_id -async def test_v1_migration( +async def test_v1_migration_fails( hass: HomeAssistant, mock_cloud_client: MagicMock, mock_lamarzocco: MagicMock, ) -> None: """Test v1 -> v2 Migration.""" - common_data = { - **USER_INPUT, - CONF_HOST: "host", - CONF_MAC: "aa:bb:cc:dd:ee:ff", - } entry_v1 = MockConfigEntry( domain=DOMAIN, version=1, unique_id=mock_lamarzocco.serial_number, - data={ - **common_data, - CONF_MACHINE: mock_lamarzocco.serial_number, - }, + data={}, ) entry_v1.add_to_hass(hass) await hass.config_entries.async_setup(entry_v1.entry_id) await hass.async_block_till_done() - assert entry_v1.version == 2 - assert dict(entry_v1.data) == { - **common_data, - CONF_NAME: "GS3", - CONF_MODEL: mock_lamarzocco.model, - CONF_TOKEN: "token", + assert entry_v1.state is ConfigEntryState.MIGRATION_ERROR + + +async def test_v2_migration( + hass: HomeAssistant, + mock_cloud_client: MagicMock, + mock_lamarzocco: MagicMock, +) -> None: + """Test v2 -> v3 Migration.""" + + entry_v2 = MockConfigEntry( + domain=DOMAIN, + version=2, + unique_id=mock_lamarzocco.serial_number, + data={ + **USER_INPUT, + CONF_HOST: "192.168.1.24", + CONF_NAME: "La Marzocco", + CONF_MODEL: ModelName.GS3_MP.value, + CONF_MAC: "aa:bb:cc:dd:ee:ff", + }, + ) + entry_v2.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry_v2.entry_id) + assert entry_v2.state is ConfigEntryState.LOADED + assert entry_v2.version == 3 + assert dict(entry_v2.data) == { + **USER_INPUT, + CONF_MAC: "aa:bb:cc:dd:ee:ff", + CONF_TOKEN: None, } @@ -128,28 +168,28 @@ async def test_migration_errors( ) -> None: """Test errors during migration.""" - mock_cloud_client.get_customer_fleet.side_effect = RequestNotSuccessful("Error") + mock_cloud_client.list_things.side_effect = RequestNotSuccessful("Error") - entry_v1 = MockConfigEntry( + entry_v2 = MockConfigEntry( domain=DOMAIN, - version=1, + version=2, unique_id=mock_lamarzocco.serial_number, data={ **USER_INPUT, CONF_MACHINE: mock_lamarzocco.serial_number, }, ) - entry_v1.add_to_hass(hass) + entry_v2.add_to_hass(hass) - assert not await hass.config_entries.async_setup(entry_v1.entry_id) - assert entry_v1.state is ConfigEntryState.MIGRATION_ERROR + assert not await hass.config_entries.async_setup(entry_v2.entry_id) + assert entry_v2.state is ConfigEntryState.MIGRATION_ERROR async def test_config_flow_entry_migration_downgrade( hass: HomeAssistant, ) -> None: """Test that config entry fails setup if the version is from the future.""" - entry = MockConfigEntry(domain=DOMAIN, version=3) + entry = MockConfigEntry(domain=DOMAIN, version=4) entry.add_to_hass(hass) assert not await hass.config_entries.async_setup(entry.entry_id) @@ -159,12 +199,14 @@ async def test_bluetooth_is_set_from_discovery( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_lamarzocco: MagicMock, + mock_cloud_client: MagicMock, ) -> None: """Check we can fill a device from discovery info.""" service_info = get_bluetooth_service_info( - mock_lamarzocco.model, mock_lamarzocco.serial_number + ModelName.GS3_MP, mock_lamarzocco.serial_number ) + mock_cloud_client.get_thing_settings.return_value.ble_auth_token = "token" with ( patch( "homeassistant.components.lamarzocco.async_discovered_service_info", @@ -174,17 +216,15 @@ async def test_bluetooth_is_set_from_discovery( "homeassistant.components.lamarzocco.LaMarzoccoMachine" ) as mock_machine_class, ): - mock_machine = MagicMock() - mock_machine.get_firmware = AsyncMock() - mock_machine.firmware = mock_lamarzocco.firmware - mock_machine_class.return_value = mock_machine + mock_machine_class.return_value = mock_lamarzocco await async_init_integration(hass, mock_config_entry) discovery.assert_called_once() - assert mock_machine_class.call_count == 2 + assert mock_machine_class.call_count == 1 _, kwargs = mock_machine_class.call_args assert kwargs["bluetooth_client"] is not None - assert mock_config_entry.data[CONF_NAME] == service_info.name + assert mock_config_entry.data[CONF_MAC] == service_info.address + assert mock_config_entry.data[CONF_TOKEN] == "token" async def test_websocket_closed_on_unload( @@ -193,34 +233,38 @@ async def test_websocket_closed_on_unload( mock_lamarzocco: MagicMock, ) -> None: """Test the websocket is closed on unload.""" - with patch( - "homeassistant.components.lamarzocco.LaMarzoccoLocalClient", - autospec=True, - ) as local_client: - client = local_client.return_value - client.websocket = AsyncMock() + mock_disconnect_callback = AsyncMock() + mock_websocket = MagicMock() + mock_websocket.closed = True - await async_init_integration(hass, mock_config_entry) - mock_lamarzocco.websocket_connect.assert_called_once() + mock_lamarzocco.websocket = WebSocketDetails( + mock_websocket, mock_disconnect_callback + ) - client.websocket.closed = False - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - client.websocket.close.assert_called_once() + await async_init_integration(hass, mock_config_entry) + mock_lamarzocco.connect_dashboard_websocket.assert_called_once() + mock_websocket.closed = False + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + mock_disconnect_callback.assert_called_once() @pytest.mark.parametrize( - ("version", "issue_exists"), [("v3.5-rc6", False), ("v3.3-rc4", True)] + ("version", "issue_exists"), [("v3.5-rc6", True), ("v5.0.9", False)] ) async def test_gateway_version_issue( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_lamarzocco: MagicMock, + mock_cloud_client: MagicMock, version: str, issue_exists: bool, ) -> None: """Make sure we get the issue for certain gateway firmware versions.""" - mock_lamarzocco.firmware[FirmwareType.GATEWAY].current_version = version + mock_cloud_client.get_thing_settings.return_value.firmwares[ + FirmwareType.GATEWAY + ].build_version = version await async_init_integration(hass, mock_config_entry) @@ -229,34 +273,33 @@ async def test_gateway_version_issue( assert (issue is not None) == issue_exists -async def test_conf_host_removed_for_new_gateway( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_lamarzocco: MagicMock, -) -> None: - """Make sure we get the issue for certain gateway firmware versions.""" - mock_lamarzocco.firmware[FirmwareType.GATEWAY].current_version = "v5.0.9" - - await async_init_integration(hass, mock_config_entry) - - assert CONF_HOST not in mock_config_entry.data - - async def test_device( hass: HomeAssistant, mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion, ) -> None: """Test the device.""" - + mock_config_entry = MockConfigEntry( + title="My LaMarzocco", + domain=DOMAIN, + version=3, + data=USER_INPUT + | { + CONF_ADDRESS: "00:00:00:00:00:00", + CONF_TOKEN: "token", + CONF_MAC: "aa:bb:cc:dd:ee:ff", + }, + unique_id=mock_lamarzocco.serial_number, + ) await async_init_integration(hass, mock_config_entry) hass.config_entries.async_update_entry( mock_config_entry, - data={**mock_config_entry.data, CONF_MAC: "aa:bb:cc:dd:ee:ff"}, + data={ + **mock_config_entry.data, + }, ) state = hass.states.get(f"switch.{mock_lamarzocco.serial_number}") @@ -269,49 +312,3 @@ async def test_device( device = device_registry.async_get(entry.device_id) assert device assert device == snapshot - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_scale_device( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - device_registry: dr.DeviceRegistry, - snapshot: SnapshotAssertion, -) -> None: - """Test the device.""" - - await async_init_integration(hass, mock_config_entry) - - device = device_registry.async_get_device( - identifiers={(DOMAIN, mock_lamarzocco.config.scale.address)} - ) - assert device - assert device == snapshot - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_remove_stale_scale( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - device_registry: dr.DeviceRegistry, - freezer: FrozenDateTimeFactory, -) -> None: - """Ensure stale scale is cleaned up.""" - - await async_init_integration(hass, mock_config_entry) - - scale_address = mock_lamarzocco.config.scale.address - - device = device_registry.async_get_device(identifiers={(DOMAIN, scale_address)}) - assert device - - mock_lamarzocco.config.scale = None - - freezer.tick(timedelta(minutes=10)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - device = device_registry.async_get_device(identifiers={(DOMAIN, scale_address)}) - assert device is None diff --git a/tests/components/lamarzocco/test_number.py b/tests/components/lamarzocco/test_number.py index 65c5e264f22..d70b99c7f57 100644 --- a/tests/components/lamarzocco/test_number.py +++ b/tests/components/lamarzocco/test_number.py @@ -1,19 +1,10 @@ """Tests for the La Marzocco number entities.""" -from datetime import timedelta from typing import Any from unittest.mock import MagicMock -from freezegun.api import FrozenDateTimeFactory -from pylamarzocco.const import ( - KEYS_PER_MODEL, - BoilerType, - MachineModel, - PhysicalKey, - PrebrewMode, -) +from pylamarzocco.const import SmartStandByType from pylamarzocco.exceptions import RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoScale import pytest from syrupy import SnapshotAssertion @@ -22,14 +13,14 @@ from homeassistant.components.number import ( DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er from . import async_init_integration -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import MockConfigEntry @pytest.mark.parametrize( @@ -38,14 +29,14 @@ from tests.common import MockConfigEntry, async_fire_time_changed ( "coffee_target_temperature", 94, - "set_temp", - {"boiler": BoilerType.COFFEE, "temperature": 94}, + "set_coffee_target_temperature", + {"temperature": 94}, ), ( "smart_standby_time", 23, "set_smart_standby", - {"enabled": True, "mode": "LastBrewing", "minutes": 23}, + {"enabled": True, "mode": SmartStandByType.POWER_ON, "minutes": 23}, ), ], ) @@ -94,318 +85,6 @@ async def test_general_numbers( mock_func.assert_called_once_with(**kwargs) -@pytest.mark.parametrize("device_fixture", [MachineModel.GS3_AV, MachineModel.GS3_MP]) -@pytest.mark.parametrize( - ("entity_name", "value", "func_name", "kwargs"), - [ - ( - "steam_target_temperature", - 131, - "set_temp", - {"boiler": BoilerType.STEAM, "temperature": 131}, - ), - ("tea_water_duration", 15, "set_dose_tea_water", {"dose": 15}), - ], -) -async def test_gs3_exclusive( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - entity_registry: er.EntityRegistry, - device_registry: dr.DeviceRegistry, - snapshot: SnapshotAssertion, - entity_name: str, - value: float, - func_name: str, - kwargs: dict[str, float], -) -> None: - """Test exclusive entities for GS3 AV/MP.""" - await async_init_integration(hass, mock_config_entry) - serial_number = mock_lamarzocco.serial_number - - func = getattr(mock_lamarzocco, func_name) - - state = hass.states.get(f"number.{serial_number}_{entity_name}") - assert state - assert state == snapshot - - entry = entity_registry.async_get(state.entity_id) - assert entry - assert entry.device_id - assert entry == snapshot - - device = device_registry.async_get(entry.device_id) - assert device - - # service call - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: f"number.{serial_number}_{entity_name}", - ATTR_VALUE: value, - }, - blocking=True, - ) - - assert len(func.mock_calls) == 1 - func.assert_called_once_with(**kwargs) - - -@pytest.mark.parametrize( - "device_fixture", [MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI] -) -async def test_gs3_exclusive_none( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Ensure GS3 exclusive is None for unsupported models.""" - await async_init_integration(hass, mock_config_entry) - ENTITIES = ("steam_target_temperature", "tea_water_duration") - - serial_number = mock_lamarzocco.serial_number - for entity in ENTITIES: - state = hass.states.get(f"number.{serial_number}_{entity}") - assert state is None - - -@pytest.mark.parametrize( - "device_fixture", [MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI] -) -@pytest.mark.parametrize( - ("entity_name", "function_name", "prebrew_mode", "value", "kwargs"), - [ - ( - "prebrew_off_time", - "set_prebrew_time", - PrebrewMode.PREBREW, - 6, - {"prebrew_off_time": 6.0, "key": PhysicalKey.A}, - ), - ( - "prebrew_on_time", - "set_prebrew_time", - PrebrewMode.PREBREW, - 6, - {"prebrew_on_time": 6.0, "key": PhysicalKey.A}, - ), - ( - "preinfusion_time", - "set_preinfusion_time", - PrebrewMode.PREINFUSION, - 7, - {"preinfusion_time": 7.0, "key": PhysicalKey.A}, - ), - ], -) -async def test_pre_brew_infusion_numbers( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - entity_registry: er.EntityRegistry, - snapshot: SnapshotAssertion, - entity_name: str, - function_name: str, - prebrew_mode: PrebrewMode, - value: float, - kwargs: dict[str, float], -) -> None: - """Test the La Marzocco prebrew/-infusion sensors.""" - - mock_lamarzocco.config.prebrew_mode = prebrew_mode - await async_init_integration(hass, mock_config_entry) - - serial_number = mock_lamarzocco.serial_number - - state = hass.states.get(f"number.{serial_number}_{entity_name}") - - assert state - assert state == snapshot - - entry = entity_registry.async_get(state.entity_id) - assert entry - assert entry == snapshot - - # service call - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: f"number.{serial_number}_{entity_name}", - ATTR_VALUE: value, - }, - blocking=True, - ) - - function = getattr(mock_lamarzocco, function_name) - function.assert_called_once_with(**kwargs) - - -@pytest.mark.parametrize( - "device_fixture", [MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI] -) -@pytest.mark.parametrize( - ("prebrew_mode", "entity", "unavailable"), - [ - ( - PrebrewMode.PREBREW, - ("prebrew_off_time", "prebrew_on_time"), - ("preinfusion_time",), - ), - ( - PrebrewMode.PREINFUSION, - ("preinfusion_time",), - ("prebrew_off_time", "prebrew_on_time"), - ), - ], -) -async def test_pre_brew_infusion_numbers_unavailable( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - prebrew_mode: PrebrewMode, - entity: tuple[str, ...], - unavailable: tuple[str, ...], -) -> None: - """Test entities are unavailable depending on selected state.""" - - mock_lamarzocco.config.prebrew_mode = prebrew_mode - await async_init_integration(hass, mock_config_entry) - - serial_number = mock_lamarzocco.serial_number - for entity_name in entity: - state = hass.states.get(f"number.{serial_number}_{entity_name}") - assert state - assert state.state != STATE_UNAVAILABLE - - for entity_name in unavailable: - state = hass.states.get(f"number.{serial_number}_{entity_name}") - assert state - assert state.state == STATE_UNAVAILABLE - - -@pytest.mark.parametrize("device_fixture", [MachineModel.GS3_AV]) -@pytest.mark.usefixtures("entity_registry_enabled_by_default") -@pytest.mark.parametrize( - ("entity_name", "value", "prebrew_mode", "function_name", "kwargs"), - [ - ( - "prebrew_off_time", - 6, - PrebrewMode.PREBREW, - "set_prebrew_time", - {"prebrew_off_time": 6.0}, - ), - ( - "prebrew_on_time", - 6, - PrebrewMode.PREBREW, - "set_prebrew_time", - {"prebrew_on_time": 6.0}, - ), - ( - "preinfusion_time", - 7, - PrebrewMode.PREINFUSION, - "set_preinfusion_time", - {"preinfusion_time": 7.0}, - ), - ("dose", 6, PrebrewMode.DISABLED, "set_dose", {"dose": 6}), - ], -) -async def test_pre_brew_infusion_key_numbers( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - snapshot: SnapshotAssertion, - entity_name: str, - value: float, - prebrew_mode: PrebrewMode, - function_name: str, - kwargs: dict[str, float], -) -> None: - """Test the La Marzocco number sensors for GS3AV model.""" - - mock_lamarzocco.config.prebrew_mode = prebrew_mode - await async_init_integration(hass, mock_config_entry) - - serial_number = mock_lamarzocco.serial_number - - func = getattr(mock_lamarzocco, function_name) - - state = hass.states.get(f"number.{serial_number}_{entity_name}") - assert state is None - - for key in PhysicalKey: - state = hass.states.get(f"number.{serial_number}_{entity_name}_key_{key}") - assert state - assert state == snapshot(name=f"{serial_number}_{entity_name}_key_{key}-state") - - # service call - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: f"number.{serial_number}_{entity_name}_key_{key}", - ATTR_VALUE: value, - }, - blocking=True, - ) - - kwargs["key"] = key - - assert len(func.mock_calls) == key.value - func.assert_called_with(**kwargs) - - -@pytest.mark.parametrize("device_fixture", [MachineModel.GS3_AV]) -async def test_disabled_entites( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test the La Marzocco prebrew/-infusion sensors for GS3AV model.""" - await async_init_integration(hass, mock_config_entry) - ENTITIES = ( - "prebrew_off_time", - "prebrew_on_time", - "preinfusion_time", - "set_dose", - ) - - serial_number = mock_lamarzocco.serial_number - - for entity_name in ENTITIES: - for key in PhysicalKey: - state = hass.states.get(f"number.{serial_number}_{entity_name}_key_{key}") - assert state is None - - -@pytest.mark.parametrize( - "device_fixture", - [MachineModel.GS3_MP, MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI], -) -async def test_not_existing_key_entities( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Assert not existing key entities.""" - await async_init_integration(hass, mock_config_entry) - serial_number = mock_lamarzocco.serial_number - - for entity in ( - "prebrew_off_time", - "prebrew_on_time", - "preinfusion_time", - "set_dose", - ): - for key in range(1, KEYS_PER_MODEL[MachineModel.GS3_AV] + 1): - state = hass.states.get(f"number.{serial_number}_{entity}_key_{key}") - assert state is None - - @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_number_error( hass: HomeAssistant, @@ -419,7 +98,9 @@ async def test_number_error( state = hass.states.get(f"number.{serial_number}_coffee_target_temperature") assert state - mock_lamarzocco.set_temp.side_effect = RequestNotSuccessful("Boom") + mock_lamarzocco.set_coffee_target_temperature.side_effect = RequestNotSuccessful( + "Boom" + ) with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( NUMBER_DOMAIN, @@ -431,107 +112,3 @@ async def test_number_error( blocking=True, ) assert exc_info.value.translation_key == "number_exception" - - state = hass.states.get(f"number.{serial_number}_dose_key_1") - assert state - - mock_lamarzocco.set_dose.side_effect = RequestNotSuccessful("Boom") - with pytest.raises(HomeAssistantError) as exc_info: - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: f"number.{serial_number}_dose_key_1", - ATTR_VALUE: 99, - }, - blocking=True, - ) - assert exc_info.value.translation_key == "number_exception_key" - - -@pytest.mark.parametrize("physical_key", [PhysicalKey.A, PhysicalKey.B]) -@pytest.mark.usefixtures("entity_registry_enabled_by_default") -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_set_target( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - entity_registry: er.EntityRegistry, - snapshot: SnapshotAssertion, - physical_key: PhysicalKey, -) -> None: - """Test the La Marzocco set target sensors.""" - - await async_init_integration(hass, mock_config_entry) - - entity_name = f"number.lmz_123a45_brew_by_weight_target_{int(physical_key)}" - - state = hass.states.get(entity_name) - - assert state - assert state == snapshot - - entry = entity_registry.async_get(state.entity_id) - assert entry - assert entry == snapshot - - # service call - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: entity_name, - ATTR_VALUE: 42, - }, - blocking=True, - ) - - mock_lamarzocco.set_bbw_recipe_target.assert_called_once_with(physical_key, 42) - - -@pytest.mark.parametrize( - "device_fixture", - [MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA], -) -async def test_other_models_no_scale_set_target( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - snapshot: SnapshotAssertion, -) -> None: - """Ensure the other models don't have a set target numbers.""" - await async_init_integration(hass, mock_config_entry) - - for i in range(1, 3): - state = hass.states.get(f"number.lmz_123a45_brew_by_weight_target_{i}") - assert state is None - - -@pytest.mark.usefixtures("entity_registry_enabled_by_default") -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_set_target_on_new_scale_added( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, -) -> None: - """Ensure the set target numbers for a new scale are added automatically.""" - - mock_lamarzocco.config.scale = None - await async_init_integration(hass, mock_config_entry) - - for i in range(1, 3): - state = hass.states.get(f"number.scale_123a45_brew_by_weight_target_{i}") - assert state is None - - mock_lamarzocco.config.scale = LaMarzoccoScale( - connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50 - ) - - freezer.tick(timedelta(minutes=10)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - for i in range(1, 3): - state = hass.states.get(f"number.scale_123a45_brew_by_weight_target_{i}") - assert state diff --git a/tests/components/lamarzocco/test_select.py b/tests/components/lamarzocco/test_select.py index 3bfb579e6d4..78cb9e313dd 100644 --- a/tests/components/lamarzocco/test_select.py +++ b/tests/components/lamarzocco/test_select.py @@ -1,18 +1,14 @@ """Tests for the La Marzocco select entities.""" -from datetime import timedelta from unittest.mock import MagicMock -from freezegun.api import FrozenDateTimeFactory from pylamarzocco.const import ( - MachineModel, - PhysicalKey, - PrebrewMode, - SmartStandbyMode, - SteamLevel, + ModelName, + PreExtractionMode, + SmartStandByType, + SteamTargetLevel, ) from pylamarzocco.exceptions import RequestNotSuccessful -from pylamarzocco.models import LaMarzoccoScale import pytest from syrupy import SnapshotAssertion @@ -26,15 +22,11 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from . import async_init_integration - -from tests.common import MockConfigEntry, async_fire_time_changed - pytest.mark.usefixtures("init_integration") @pytest.mark.usefixtures("init_integration") -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MICRA]) +@pytest.mark.parametrize("device_fixture", [ModelName.LINEA_MICRA]) async def test_steam_boiler_level( hass: HomeAssistant, entity_registry: er.EntityRegistry, @@ -65,12 +57,14 @@ async def test_steam_boiler_level( blocking=True, ) - mock_lamarzocco.set_steam_level.assert_called_once_with(level=SteamLevel.LEVEL_2) + mock_lamarzocco.set_steam_level.assert_called_once_with( + level=SteamTargetLevel.LEVEL_2 + ) @pytest.mark.parametrize( "device_fixture", - [MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MINI], + [ModelName.GS3_AV, ModelName.GS3_MP, ModelName.LINEA_MINI], ) async def test_steam_boiler_level_none( hass: HomeAssistant, @@ -86,7 +80,7 @@ async def test_steam_boiler_level_none( @pytest.mark.usefixtures("init_integration") @pytest.mark.parametrize( "device_fixture", - [MachineModel.LINEA_MICRA, MachineModel.GS3_AV, MachineModel.LINEA_MINI], + [ModelName.LINEA_MICRA, ModelName.GS3_AV, ModelName.LINEA_MINI], ) async def test_pre_brew_infusion_select( hass: HomeAssistant, @@ -118,19 +112,21 @@ async def test_pre_brew_infusion_select( blocking=True, ) - mock_lamarzocco.set_prebrew_mode.assert_called_once_with(mode=PrebrewMode.PREBREW) + mock_lamarzocco.set_pre_extraction_mode.assert_called_once_with( + mode=PreExtractionMode.PREBREWING + ) @pytest.mark.usefixtures("init_integration") @pytest.mark.parametrize( "device_fixture", - [MachineModel.GS3_MP], + [ModelName.GS3_MP], ) async def test_pre_brew_infusion_select_none( hass: HomeAssistant, mock_lamarzocco: MagicMock, ) -> None: - """Ensure the La Marzocco Steam Level Select is not created for non-Micra models.""" + """Ensure GS3 MP has no prebrew models.""" serial_number = mock_lamarzocco.serial_number state = hass.states.get(f"select.{serial_number}_prebrew_infusion_mode") @@ -162,13 +158,13 @@ async def test_smart_standby_mode( SERVICE_SELECT_OPTION, { ATTR_ENTITY_ID: f"select.{serial_number}_smart_standby_mode", - ATTR_OPTION: "power_on", + ATTR_OPTION: "last_brewing", }, blocking=True, ) mock_lamarzocco.set_smart_standby.assert_called_once_with( - enabled=True, mode=SmartStandbyMode.POWER_ON, minutes=10 + enabled=True, mode=SmartStandByType.LAST_BREW, minutes=10 ) @@ -183,7 +179,7 @@ async def test_select_errors( state = hass.states.get(f"select.{serial_number}_prebrew_infusion_mode") assert state - mock_lamarzocco.set_prebrew_mode.side_effect = RequestNotSuccessful("Boom") + mock_lamarzocco.set_pre_extraction_mode.side_effect = RequestNotSuccessful("Boom") # Test setting invalid option with pytest.raises(HomeAssistantError) as exc_info: @@ -197,77 +193,3 @@ async def test_select_errors( blocking=True, ) assert exc_info.value.translation_key == "select_option_error" - - -@pytest.mark.usefixtures("init_integration") -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_active_bbw_recipe( - hass: HomeAssistant, - entity_registry: er.EntityRegistry, - mock_lamarzocco: MagicMock, - snapshot: SnapshotAssertion, -) -> None: - """Test the La Marzocco active bbw recipe select.""" - - state = hass.states.get("select.lmz_123a45_active_brew_by_weight_recipe") - - assert state - assert state == snapshot - - entry = entity_registry.async_get(state.entity_id) - assert entry - assert entry == snapshot - - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.lmz_123a45_active_brew_by_weight_recipe", - ATTR_OPTION: "b", - }, - blocking=True, - ) - - mock_lamarzocco.set_active_bbw_recipe.assert_called_once_with(PhysicalKey.B) - - -@pytest.mark.usefixtures("init_integration") -@pytest.mark.parametrize( - "device_fixture", - [MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA], -) -async def test_other_models_no_active_bbw_select( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, -) -> None: - """Ensure the other models don't have a battery sensor.""" - - state = hass.states.get("select.lmz_123a45_active_brew_by_weight_recipe") - assert state is None - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_active_bbw_select_on_new_scale_added( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, -) -> None: - """Ensure the active bbw select for a new scale is added automatically.""" - - mock_lamarzocco.config.scale = None - await async_init_integration(hass, mock_config_entry) - - state = hass.states.get("select.scale_123a45_active_brew_by_weight_recipe") - assert state is None - - mock_lamarzocco.config.scale = LaMarzoccoScale( - connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50 - ) - - freezer.tick(timedelta(minutes=10)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - state = hass.states.get("select.scale_123a45_active_brew_by_weight_recipe") - assert state diff --git a/tests/components/lamarzocco/test_sensor.py b/tests/components/lamarzocco/test_sensor.py deleted file mode 100644 index 43a0826d551..00000000000 --- a/tests/components/lamarzocco/test_sensor.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Tests for La Marzocco sensors.""" - -from datetime import timedelta -from unittest.mock import MagicMock, patch - -from freezegun.api import FrozenDateTimeFactory -from pylamarzocco.const import MachineModel -from pylamarzocco.models import LaMarzoccoScale -import pytest -from syrupy import SnapshotAssertion - -from homeassistant.const import STATE_UNAVAILABLE, Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er - -from . import async_init_integration - -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform - - -@pytest.mark.usefixtures("entity_registry_enabled_by_default") -async def test_sensors( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - entity_registry: er.EntityRegistry, - mock_config_entry: MockConfigEntry, - snapshot: SnapshotAssertion, -) -> None: - """Test the La Marzocco sensors.""" - - with patch("homeassistant.components.lamarzocco.PLATFORMS", [Platform.SENSOR]): - await async_init_integration(hass, mock_config_entry) - await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) - - -async def test_shot_timer_not_exists( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry_no_local_connection: MockConfigEntry, -) -> None: - """Test the La Marzocco shot timer doesn't exist if host not set.""" - - await async_init_integration(hass, mock_config_entry_no_local_connection) - state = hass.states.get(f"sensor.{mock_lamarzocco.serial_number}_shot_timer") - assert state is None - - -async def test_shot_timer_unavailable( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test the La Marzocco brew_active becomes unavailable.""" - - mock_lamarzocco.websocket_connected = False - await async_init_integration(hass, mock_config_entry) - state = hass.states.get(f"sensor.{mock_lamarzocco.serial_number}_shot_timer") - assert state - assert state.state == STATE_UNAVAILABLE - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_no_steam_linea_mini( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Ensure Linea Mini has no steam temp.""" - await async_init_integration(hass, mock_config_entry) - - serial_number = mock_lamarzocco.serial_number - state = hass.states.get(f"sensor.{serial_number}_current_temp_steam") - assert state is None - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_scale_battery( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - entity_registry: er.EntityRegistry, - snapshot: SnapshotAssertion, -) -> None: - """Test the scale battery sensor.""" - await async_init_integration(hass, mock_config_entry) - - state = hass.states.get("sensor.lmz_123a45_battery") - assert state - assert state == snapshot - - entry = entity_registry.async_get(state.entity_id) - assert entry - assert entry.device_id - assert entry == snapshot - - -@pytest.mark.parametrize( - "device_fixture", - [MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA], -) -async def test_other_models_no_scale_battery( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - snapshot: SnapshotAssertion, -) -> None: - """Ensure the other models don't have a battery sensor.""" - await async_init_integration(hass, mock_config_entry) - - state = hass.states.get("sensor.lmz_123a45_battery") - assert state is None - - -@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI]) -async def test_battery_on_new_scale_added( - hass: HomeAssistant, - mock_lamarzocco: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, -) -> None: - """Ensure the battery sensor for a new scale is added automatically.""" - - mock_lamarzocco.config.scale = None - await async_init_integration(hass, mock_config_entry) - - state = hass.states.get("sensor.lmz_123a45_battery") - assert state is None - - mock_lamarzocco.config.scale = LaMarzoccoScale( - connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50 - ) - - freezer.tick(timedelta(minutes=10)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - state = hass.states.get("sensor.scale_123a45_battery") - assert state diff --git a/tests/components/lamarzocco/test_switch.py b/tests/components/lamarzocco/test_switch.py index d8370ad8575..586abfb630f 100644 --- a/tests/components/lamarzocco/test_switch.py +++ b/tests/components/lamarzocco/test_switch.py @@ -3,6 +3,7 @@ from typing import Any from unittest.mock import MagicMock, patch +from pylamarzocco.const import SmartStandByType from pylamarzocco.exceptions import RequestNotSuccessful import pytest from syrupy import SnapshotAssertion @@ -47,7 +48,7 @@ async def test_switches( ( "_smart_standby_enabled", "set_smart_standby", - {"mode": "LastBrewing", "minutes": 10}, + {"mode": SmartStandByType.POWER_ON, "minutes": 10}, ), ], ) @@ -124,12 +125,15 @@ async def test_auto_on_off_switches( blocking=True, ) - wake_up_sleep_entry = mock_lamarzocco.config.wake_up_sleep_entries[ - wake_up_sleep_entry_id - ] + wake_up_sleep_entry = ( + mock_lamarzocco.schedule.smart_wake_up_sleep.schedules_dict[ + wake_up_sleep_entry_id + ] + ) + assert wake_up_sleep_entry wake_up_sleep_entry.enabled = False - mock_lamarzocco.set_wake_up_sleep.assert_called_with(wake_up_sleep_entry) + mock_lamarzocco.set_wakeup_schedule.assert_called_with(wake_up_sleep_entry) await hass.services.async_call( SWITCH_DOMAIN, @@ -140,7 +144,7 @@ async def test_auto_on_off_switches( blocking=True, ) wake_up_sleep_entry.enabled = True - mock_lamarzocco.set_wake_up_sleep.assert_called_with(wake_up_sleep_entry) + mock_lamarzocco.set_wakeup_schedule.assert_called_with(wake_up_sleep_entry) async def test_switch_exceptions( @@ -183,7 +187,7 @@ async def test_switch_exceptions( state = hass.states.get(f"switch.{serial_number}_auto_on_off_os2oswx") assert state - mock_lamarzocco.set_wake_up_sleep.side_effect = RequestNotSuccessful("Boom") + mock_lamarzocco.set_wakeup_schedule.side_effect = RequestNotSuccessful("Boom") with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( SWITCH_DOMAIN, diff --git a/tests/components/lamarzocco/test_update.py b/tests/components/lamarzocco/test_update.py index 4089ffa297a..964c3d82172 100644 --- a/tests/components/lamarzocco/test_update.py +++ b/tests/components/lamarzocco/test_update.py @@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch -from pylamarzocco.const import FirmwareType from pylamarzocco.exceptions import RequestNotSuccessful import pytest from syrupy import SnapshotAssertion @@ -31,19 +30,10 @@ async def test_update( await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) -@pytest.mark.parametrize( - ("entity_name", "component"), - [ - ("machine_firmware", FirmwareType.MACHINE), - ("gateway_firmware", FirmwareType.GATEWAY), - ], -) async def test_update_entites( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry, - entity_name: str, - component: FirmwareType, ) -> None: """Test the La Marzocco update entities.""" @@ -55,43 +45,34 @@ async def test_update_entites( UPDATE_DOMAIN, SERVICE_INSTALL, { - ATTR_ENTITY_ID: f"update.{serial_number}_{entity_name}", + ATTR_ENTITY_ID: f"update.{serial_number}_gateway_firmware", }, blocking=True, ) - mock_lamarzocco.update_firmware.assert_called_once_with(component) + mock_lamarzocco.update_firmware.assert_called_once_with() -@pytest.mark.parametrize( - ("attr", "value"), - [ - ("side_effect", RequestNotSuccessful("Boom")), - ("return_value", False), - ], -) async def test_update_error( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry, - attr: str, - value: bool | Exception, ) -> None: """Test error during update.""" await async_init_integration(hass, mock_config_entry) - state = hass.states.get(f"update.{mock_lamarzocco.serial_number}_machine_firmware") + state = hass.states.get(f"update.{mock_lamarzocco.serial_number}_gateway_firmware") assert state - setattr(mock_lamarzocco.update_firmware, attr, value) + mock_lamarzocco.update_firmware.side_effect = RequestNotSuccessful("Boom") with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( UPDATE_DOMAIN, SERVICE_INSTALL, { - ATTR_ENTITY_ID: f"update.{mock_lamarzocco.serial_number}_machine_firmware", + ATTR_ENTITY_ID: f"update.{mock_lamarzocco.serial_number}_gateway_firmware", }, blocking=True, )