Add binary sensor platforms to La Marzocco (#108212)

* add sensor

* remove switch

* requested changes

* property instead of function

* add missing snapshot

* rename var, fixture

* add binary sensors

* reorder strings

* rename sensor

* switch to supported_fn
This commit is contained in:
Josef Zweck 2024-01-17 11:42:22 +01:00 committed by GitHub
parent a25653e167
commit e811cf1ae8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 261 additions and 0 deletions

View file

@ -8,6 +8,7 @@ from .const import DOMAIN
from .coordinator import LaMarzoccoUpdateCoordinator
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.SENSOR,
Platform.SWITCH,
]

View file

@ -0,0 +1,90 @@
"""Binary Sensor platform for La Marzocco espresso machines."""
from collections.abc import Callable
from dataclasses import dataclass
from lmcloud import LMCloud as LaMarzoccoClient
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
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 LaMarzoccoBinarySensorEntityDescription(
LaMarzoccoEntityDescription,
BinarySensorEntityDescription,
):
"""Description of a La Marzocco binary sensor."""
is_on_fn: Callable[[LaMarzoccoClient], bool]
icon_on: str
icon_off: str
ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
LaMarzoccoBinarySensorEntityDescription(
key="water_tank",
translation_key="water_tank",
device_class=BinarySensorDeviceClass.PROBLEM,
icon_on="mdi:water-remove",
icon_off="mdi:water-check",
is_on_fn=lambda lm: not lm.current_status.get("water_reservoir_contact"),
entity_category=EntityCategory.DIAGNOSTIC,
supported_fn=lambda coordinator: coordinator.local_connection_configured,
),
LaMarzoccoBinarySensorEntityDescription(
key="brew_active",
translation_key="brew_active",
device_class=BinarySensorDeviceClass.RUNNING,
icon_off="mdi:cup-off",
icon_on="mdi:cup-water",
is_on_fn=lambda lm: bool(lm.current_status.get("brew_active")),
available_fn=lambda lm: lm.websocket_connected,
entity_category=EntityCategory.DIAGNOSTIC,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up binary sensor entities."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
LaMarzoccoBinarySensorEntity(coordinator, description)
for description in ENTITIES
if description.supported_fn(coordinator)
)
class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity):
"""Binary Sensor representing espresso machine water reservoir status."""
entity_description: LaMarzoccoBinarySensorEntityDescription
@property
def icon(self) -> str | None:
"""Return the icon."""
return (
self.entity_description.icon_on
if self.is_on
else self.entity_description.icon_off
)
@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
return self.entity_description.is_on_fn(self.coordinator.lm)

View file

@ -43,6 +43,14 @@
}
},
"entity": {
"binary_sensor": {
"brew_active": {
"name": "Brewing active"
},
"water_tank": {
"name": "Water tank empty"
}
},
"sensor": {
"current_temp_coffee": {
"name": "Current coffee temperature"

View file

@ -0,0 +1,91 @@
# serializer version: 1
# name: test_binary_sensors[GS01234_brewing_active-binary_sensor]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'running',
'friendly_name': 'GS01234 Brewing active',
'icon': 'mdi:cup-off',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.gs01234_brewing_active',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[GS01234_brewing_active-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.gs01234_brewing_active',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.RUNNING: 'running'>,
'original_icon': 'mdi:cup-off',
'original_name': 'Brewing active',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'brew_active',
'unique_id': 'GS01234_brew_active',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[GS01234_water_tank_empty-binary_sensor]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'GS01234 Water tank empty',
'icon': 'mdi:water-check',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.gs01234_water_tank_empty',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[GS01234_water_tank_empty-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.gs01234_water_tank_empty',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': 'mdi:water-check',
'original_name': 'Water tank empty',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'water_tank',
'unique_id': 'GS01234_water_tank',
'unit_of_measurement': None,
})
# ---

View file

@ -0,0 +1,71 @@
"""Tests for La Marzocco binary sensors."""
from unittest.mock import MagicMock
import pytest
from syrupy import SnapshotAssertion
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import async_init_integration
from tests.common import MockConfigEntry
BINARY_SENSORS = (
"brewing_active",
"water_tank_empty",
)
async def test_binary_sensors(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the La Marzocco binary sensors."""
await async_init_integration(hass, mock_config_entry)
serial_number = mock_lamarzocco.serial_number
for binary_sensor in BINARY_SENSORS:
state = hass.states.get(f"binary_sensor.{serial_number}_{binary_sensor}")
assert state
assert state == snapshot(name=f"{serial_number}_{binary_sensor}-binary_sensor")
entry = entity_registry.async_get(state.entity_id)
assert entry
assert entry.device_id
assert entry == snapshot(name=f"{serial_number}_{binary_sensor}-entry")
@pytest.mark.usefixtures("remove_local_connection")
async def test_brew_active_does_not_exists(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the La Marzocco currently_making_coffee doesn't exist if host not set."""
await async_init_integration(hass, mock_config_entry)
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."""
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"
)
assert state
assert state.state == STATE_UNAVAILABLE