diff --git a/homeassistant/components/lamarzocco/icons.json b/homeassistant/components/lamarzocco/icons.json index 70adfe95134..727d3c66009 100644 --- a/homeassistant/components/lamarzocco/icons.json +++ b/homeassistant/components/lamarzocco/icons.json @@ -25,9 +25,21 @@ "coffee_temp": { "default": "mdi:thermometer-water" }, + "dose": { + "default": "mdi:weight-kilogram" + }, "steam_temp": { "default": "mdi:thermometer-water" }, + "prebrew_off": { + "default": "mdi:water-off" + }, + "prebrew_on": { + "default": "mdi:water" + }, + "preinfusion_off": { + "default": "mdi:water" + }, "tea_water_duration": { "default": "mdi:timer-sand" } diff --git a/homeassistant/components/lamarzocco/number.py b/homeassistant/components/lamarzocco/number.py index bf866872f5b..05f937f48f6 100644 --- a/homeassistant/components/lamarzocco/number.py +++ b/homeassistant/components/lamarzocco/number.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Any from lmcloud import LMCloud as LaMarzoccoClient -from lmcloud.const import LaMarzoccoModel +from lmcloud.const import KEYS_PER_MODEL, LaMarzoccoModel from homeassistant.components.number import ( NumberDeviceClass, @@ -16,6 +16,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( PRECISION_TENTHS, PRECISION_WHOLE, + EntityCategory, UnitOfTemperature, UnitOfTime, ) @@ -40,6 +41,19 @@ class LaMarzoccoNumberEntityDescription( ] +@dataclass(frozen=True, kw_only=True) +class LaMarzoccoKeyNumberEntityDescription( + LaMarzoccoEntityDescription, + NumberEntityDescription, +): + """Description of an La Marzocco number entity with keys.""" + + native_value_fn: Callable[[LaMarzoccoClient, int], float | int] + set_value_fn: Callable[ + [LaMarzoccoClient, float | int, int], Coroutine[Any, Any, bool] + ] + + ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = ( LaMarzoccoNumberEntityDescription( key="coffee_temp", @@ -89,6 +103,103 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = ( ) +async def _set_prebrew_on( + lm: LaMarzoccoClient, + value: float, + key: int, +) -> bool: + return await lm.configure_prebrew( + on_time=int(value * 1000), + off_time=int(lm.current_status[f"prebrewing_toff_k{key}"] * 1000), + key=key, + ) + + +async def _set_prebrew_off( + lm: LaMarzoccoClient, + value: float, + key: int, +) -> bool: + return await lm.configure_prebrew( + on_time=int(lm.current_status[f"prebrewing_ton_k{key}"] * 1000), + off_time=int(value * 1000), + key=key, + ) + + +async def _set_preinfusion( + lm: LaMarzoccoClient, + value: float, + key: int, +) -> bool: + return await lm.configure_prebrew( + off_time=int(value * 1000), + key=key, + ) + + +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=_set_prebrew_off, + native_value_fn=lambda lm, key: lm.current_status[f"prebrewing_ton_k{key}"], + available_fn=lambda lm: lm.current_status["enable_prebrewing"], + supported_fn=lambda coordinator: coordinator.lm.model_name + != LaMarzoccoModel.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=_set_prebrew_on, + native_value_fn=lambda lm, key: lm.current_status[f"prebrewing_toff_k{key}"], + available_fn=lambda lm: lm.current_status["enable_prebrewing"], + supported_fn=lambda coordinator: coordinator.lm.model_name + != LaMarzoccoModel.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=_set_preinfusion, + native_value_fn=lambda lm, key: lm.current_status[f"preinfusion_k{key}"], + available_fn=lambda lm: lm.current_status["enable_preinfusion"], + supported_fn=lambda coordinator: coordinator.lm.model_name + != LaMarzoccoModel.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, + entity_category=EntityCategory.CONFIG, + set_value_fn=lambda lm, ticks, key: lm.set_dose(key=key, value=int(ticks)), + native_value_fn=lambda lm, key: lm.current_status[f"dose_k{key}"], + supported_fn=lambda coordinator: coordinator.lm.model_name + == LaMarzoccoModel.GS3_AV, + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -103,6 +214,17 @@ async def async_setup_entry( if description.supported_fn(coordinator) ) + entities: list[LaMarzoccoKeyNumberEntity] = [] + for description in KEY_ENTITIES: + if description.supported_fn(coordinator): + num_keys = KEYS_PER_MODEL[coordinator.lm.model_name] + for key in range(min(num_keys, 1), num_keys + 1): + entities.append( + LaMarzoccoKeyNumberEntity(coordinator, description, key) + ) + + async_add_entities(entities) + class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity): """La Marzocco number entity.""" @@ -118,3 +240,42 @@ class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity): """Set the value.""" await self.entity_description.set_value_fn(self.coordinator, value) 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: + """Return the current value.""" + return self.entity_description.native_value_fn( + self.coordinator.lm, self.pyhsical_key + ) + + async def async_set_native_value(self, value: float) -> None: + """Set the value.""" + await self.entity_description.set_value_fn( + self.coordinator.lm, value, self.pyhsical_key + ) + self.async_write_ha_state() diff --git a/homeassistant/components/lamarzocco/strings.json b/homeassistant/components/lamarzocco/strings.json index 7537405c6cd..4a375e0a17b 100644 --- a/homeassistant/components/lamarzocco/strings.json +++ b/homeassistant/components/lamarzocco/strings.json @@ -60,6 +60,27 @@ "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}" + }, "steam_temp": { "name": "Steam target temperature" }, diff --git a/tests/components/lamarzocco/snapshots/test_number.ambr b/tests/components/lamarzocco/snapshots/test_number.ambr index 3c9fdce101f..f7f5b2191cf 100644 --- a/tests/components/lamarzocco/snapshots/test_number.ambr +++ b/tests/components/lamarzocco/snapshots/test_number.ambr @@ -269,3 +269,611 @@ 'unit_of_measurement': , }) # --- +# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234 Dose Key 1', + 'max': 999, + 'min': 0, + 'mode': , + 'step': 1, + 'unit_of_measurement': 'ticks', + }), + 'context': , + 'entity_id': 'number.gs01234_dose_key_1', + 'last_changed': , + 'last_updated': , + 'state': '1023', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234 Dose Key 2', + 'max': 999, + 'min': 0, + 'mode': , + 'step': 1, + 'unit_of_measurement': 'ticks', + }), + 'context': , + 'entity_id': 'number.gs01234_dose_key_2', + 'last_changed': , + 'last_updated': , + 'state': '1023', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234 Dose Key 3', + 'max': 999, + 'min': 0, + 'mode': , + 'step': 1, + 'unit_of_measurement': 'ticks', + }), + 'context': , + 'entity_id': 'number.gs01234_dose_key_3', + 'last_changed': , + 'last_updated': , + 'state': '1023', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234 Dose Key 4', + 'max': 999, + 'min': 0, + 'mode': , + 'step': 1, + 'unit_of_measurement': 'ticks', + }), + 'context': , + 'entity_id': 'number.gs01234_dose_key_4', + 'last_changed': , + 'last_updated': , + 'state': '1023', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew off time Key 1', + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_off_time_key_1', + 'last_changed': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew off time Key 2', + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_off_time_key_2', + 'last_changed': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew off time Key 3', + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_off_time_key_3', + 'last_changed': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew off time Key 4', + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_off_time_key_4', + 'last_changed': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew on time Key 1', + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_on_time_key_1', + 'last_changed': , + 'last_updated': , + 'state': '5', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew on time Key 2', + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_on_time_key_2', + 'last_changed': , + 'last_updated': , + 'state': '5', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew on time Key 3', + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_on_time_key_3', + 'last_changed': , + 'last_updated': , + 'state': '5', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Prebrew on time Key 4', + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_prebrew_on_time_key_4', + 'last_changed': , + 'last_updated': , + 'state': '5', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Preinfusion time Key 1', + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_preinfusion_time_key_1', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Preinfusion time Key 2', + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_preinfusion_time_key_2', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Preinfusion time Key 3', + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_preinfusion_time_key_3', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'GS01234 Preinfusion time Key 4', + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.gs01234_preinfusion_time_key_4', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Linea Mini] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'LM01234 Prebrew off time', + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.lm01234_prebrew_off_time', + 'last_changed': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Linea Mini].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.lm01234_prebrew_off_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': 'LM01234_prebrew_off', + 'unit_of_measurement': , + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Micra] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'MR01234 Prebrew off time', + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.mr01234_prebrew_off_time', + 'last_changed': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Micra].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 10, + 'min': 1, + 'mode': , + 'step': 0.1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mr01234_prebrew_off_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': 'MR01234_prebrew_off', + 'unit_of_measurement': , + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Linea Mini] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'LM01234 Prebrew on time', + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.lm01234_prebrew_on_time', + 'last_changed': , + 'last_updated': , + 'state': '5', + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Linea Mini].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.lm01234_prebrew_on_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': 'LM01234_prebrew_on', + 'unit_of_measurement': , + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Micra] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'MR01234 Prebrew on time', + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.mr01234_prebrew_on_time', + 'last_changed': , + 'last_updated': , + 'state': '5', + }) +# --- +# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Micra].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 10, + 'min': 2, + 'mode': , + 'step': 0.1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mr01234_prebrew_on_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': 'MR01234_prebrew_on', + 'unit_of_measurement': , + }) +# --- +# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Linea Mini] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'LM01234 Preinfusion time', + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.lm01234_preinfusion_time', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Linea Mini].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.lm01234_preinfusion_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': 'LM01234_preinfusion_off', + 'unit_of_measurement': , + }) +# --- +# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Micra] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'MR01234 Preinfusion time', + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.mr01234_preinfusion_time', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Micra].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 29, + 'min': 2, + 'mode': , + 'step': 0.1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mr01234_preinfusion_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': 'MR01234_preinfusion_off', + 'unit_of_measurement': , + }) +# --- diff --git a/tests/components/lamarzocco/test_number.py b/tests/components/lamarzocco/test_number.py index 7a9eb334637..86ae1b90126 100644 --- a/tests/components/lamarzocco/test_number.py +++ b/tests/components/lamarzocco/test_number.py @@ -1,9 +1,8 @@ """Tests for the La Marzocco number entities.""" - from unittest.mock import MagicMock -from lmcloud.const import LaMarzoccoModel +from lmcloud.const import KEYS_PER_MODEL, LaMarzoccoModel import pytest from syrupy import SnapshotAssertion @@ -12,7 +11,7 @@ from homeassistant.components.number import ( DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE, ) -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -126,3 +125,202 @@ async def test_gs3_exclusive_none( for entity in ENTITIES: state = hass.states.get(f"number.{serial_number}_{entity}") assert state is None + + +@pytest.mark.parametrize( + "device_fixture", [LaMarzoccoModel.LINEA_MICRA, LaMarzoccoModel.LINEA_MINI] +) +@pytest.mark.parametrize( + ("entity_name", "value", "kwargs"), + [ + ("prebrew_off_time", 6, {"on_time": 3000, "off_time": 6000, "key": 1}), + ("prebrew_on_time", 6, {"on_time": 6000, "off_time": 5000, "key": 1}), + ("preinfusion_time", 7, {"off_time": 7000, "key": 1}), + ], +) +async def test_pre_brew_infusion_numbers( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, + entity_registry: er.EntityRegistry, + device_registry: dr.DeviceRegistry, + snapshot: SnapshotAssertion, + entity_name: str, + value: float, + kwargs: dict[str, float], +) -> None: + """Test the La Marzocco prebrew/-infusion sensors.""" + + mock_lamarzocco.current_status["enable_preinfusion"] = True + + 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.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(mock_lamarzocco.configure_prebrew.mock_calls) == 1 + mock_lamarzocco.configure_prebrew.assert_called_once_with(**kwargs) + + +@pytest.mark.parametrize("device_fixture", [LaMarzoccoModel.GS3_AV]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +@pytest.mark.parametrize( + ("entity_name", "value", "function_name", "kwargs"), + [ + ( + "prebrew_off_time", + 6, + "configure_prebrew", + {"on_time": 3000, "off_time": 6000}, + ), + ( + "prebrew_on_time", + 6, + "configure_prebrew", + {"on_time": 6000, "off_time": 5000}, + ), + ("preinfusion_time", 7, "configure_prebrew", {"off_time": 7000}), + ("dose", 6, "set_dose", {"value": 6}), + ], +) +async def test_pre_brew_infusion_key_numbers( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, + snapshot: SnapshotAssertion, + entity_name: str, + value: float, + function_name: str, + kwargs: dict[str, float], +) -> None: + """Test the La Marzocco number sensors for GS3AV model.""" + + mock_lamarzocco.current_status["enable_preinfusion"] = True + + 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 range(1, KEYS_PER_MODEL[mock_lamarzocco.model_name] + 1): + 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 + func.assert_called_with(**kwargs) + + +@pytest.mark.parametrize("device_fixture", [LaMarzoccoModel.GS3_AV]) +async def test_disabled_entites( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, +) -> None: + """Test the La Marzocco prebrew/-infusion sensors for GS3AV model.""" + + 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 range(1, KEYS_PER_MODEL[mock_lamarzocco.model_name] + 1): + state = hass.states.get(f"number.{serial_number}_{entity_name}_key_{key}") + assert state is None + + +@pytest.mark.parametrize( + "device_fixture", + [LaMarzoccoModel.GS3_MP, LaMarzoccoModel.LINEA_MICRA, LaMarzoccoModel.LINEA_MINI], +) +async def test_not_existing_key_entites( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, +) -> None: + """Assert not existing key entities.""" + + 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[LaMarzoccoModel.GS3_AV] + 1): + state = hass.states.get(f"number.{serial_number}_{entity}_key_{key}") + assert state is None + + +@pytest.mark.parametrize( + "device_fixture", + [LaMarzoccoModel.GS3_MP], +) +async def test_not_existing_entites( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, +) -> None: + """Assert not existing entities.""" + + serial_number = mock_lamarzocco.serial_number + + for entity in ( + "prebrew_off_time", + "prebrew_on_time", + "preinfusion_time", + "set_dose", + ): + state = hass.states.get(f"number.{serial_number}_{entity}") + assert state is None + + +@pytest.mark.parametrize("device_fixture", [LaMarzoccoModel.LINEA_MICRA]) +async def test_not_settable_entites( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, +) -> None: + """Assert not settable causes error.""" + + serial_number = mock_lamarzocco.serial_number + + state = hass.states.get(f"number.{serial_number}_preinfusion_time") + assert state + assert state.state == STATE_UNAVAILABLE