diff --git a/homeassistant/components/lamarzocco/__init__.py b/homeassistant/components/lamarzocco/__init__.py index ba37a7f90d7..0adfc4bebfe 100644 --- a/homeassistant/components/lamarzocco/__init__.py +++ b/homeassistant/components/lamarzocco/__init__.py @@ -9,6 +9,7 @@ from .coordinator import LaMarzoccoUpdateCoordinator PLATFORMS = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.NUMBER, Platform.SELECT, Platform.SENSOR, diff --git a/homeassistant/components/lamarzocco/button.py b/homeassistant/components/lamarzocco/button.py new file mode 100644 index 00000000000..689250fa37b --- /dev/null +++ b/homeassistant/components/lamarzocco/button.py @@ -0,0 +1,60 @@ +"""Button platform for La Marzocco espresso machines.""" + +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +from typing import Any + +from lmcloud import LMCloud as LaMarzoccoClient + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription + + +@dataclass(frozen=True, kw_only=True) +class LaMarzoccoButtonEntityDescription( + LaMarzoccoEntityDescription, + ButtonEntityDescription, +): + """Description of a La Marzocco button.""" + + press_fn: Callable[[LaMarzoccoClient], Coroutine[Any, Any, None]] + + +ENTITIES: tuple[LaMarzoccoButtonEntityDescription, ...] = ( + LaMarzoccoButtonEntityDescription( + key="start_backflush", + translation_key="start_backflush", + icon="mdi:water-sync", + press_fn=lambda lm: lm.start_backflush(), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up button entities.""" + + coordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + LaMarzoccoButtonEntity(coordinator, description) + for description in ENTITIES + if description.supported_fn(coordinator) + ) + + +class LaMarzoccoButtonEntity(LaMarzoccoEntity, ButtonEntity): + """La Marzocco Button Entity.""" + + entity_description: LaMarzoccoButtonEntityDescription + + async def async_press(self) -> None: + """Press button.""" + await self.entity_description.press_fn(self.coordinator.lm) diff --git a/homeassistant/components/lamarzocco/strings.json b/homeassistant/components/lamarzocco/strings.json index fc326b41666..7537405c6cd 100644 --- a/homeassistant/components/lamarzocco/strings.json +++ b/homeassistant/components/lamarzocco/strings.json @@ -51,6 +51,11 @@ "name": "Water tank empty" } }, + "button": { + "start_backflush": { + "name": "Start backflush" + } + }, "number": { "coffee_temp": { "name": "Coffee target temperature" diff --git a/tests/components/lamarzocco/snapshots/test_button.ambr b/tests/components/lamarzocco/snapshots/test_button.ambr new file mode 100644 index 00000000000..e092032e8f5 --- /dev/null +++ b/tests/components/lamarzocco/snapshots/test_button.ambr @@ -0,0 +1,45 @@ +# serializer version: 1 +# name: test_start_backflush + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234 Start backflush', + 'icon': 'mdi:water-sync', + }), + 'context': , + 'entity_id': 'button.gs01234_start_backflush', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_start_backflush.1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.gs01234_start_backflush', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:water-sync', + 'original_name': 'Start backflush', + 'platform': 'lamarzocco', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'start_backflush', + 'unique_id': 'GS01234_start_backflush', + 'unit_of_measurement': None, + }) +# --- diff --git a/tests/components/lamarzocco/test_button.py b/tests/components/lamarzocco/test_button.py new file mode 100644 index 00000000000..7d910a57561 --- /dev/null +++ b/tests/components/lamarzocco/test_button.py @@ -0,0 +1,45 @@ +"""Tests for the La Marzocco Buttons.""" + + +from unittest.mock import MagicMock + +import pytest +from syrupy import SnapshotAssertion + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +pytestmark = pytest.mark.usefixtures("init_integration") + + +async def test_start_backflush( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test the La Marzocco backflush button.""" + + serial_number = mock_lamarzocco.serial_number + + state = hass.states.get(f"button.{serial_number}_start_backflush") + assert state + assert state == snapshot + + entry = entity_registry.async_get(state.entity_id) + assert entry + assert entry == snapshot + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: f"button.{serial_number}_start_backflush", + }, + blocking=True, + ) + + assert len(mock_lamarzocco.start_backflush.mock_calls) == 1 + mock_lamarzocco.start_backflush.assert_called_once()