Add sensors to lamarzocco (#143156)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Josef Zweck 2025-04-19 12:30:22 +02:00 committed by GitHub
parent 7c7f18b501
commit 9b8a35dbb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 281 additions and 0 deletions

View File

@ -40,6 +40,7 @@ PLATFORMS = [
Platform.CALENDAR,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.UPDATE,
]

View File

@ -72,6 +72,14 @@
}
}
},
"sensor": {
"coffee_boiler_ready_time": {
"default": "mdi:av-timer"
},
"steam_boiler_ready_time": {
"default": "mdi:av-timer"
}
},
"switch": {
"main": {
"default": "mdi:power",

View File

@ -0,0 +1,115 @@
"""Sensor platform for La Marzocco espresso machines."""
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from typing import cast
from pylamarzocco.const import ModelName, WidgetType
from pylamarzocco.models import (
BaseWidgetOutput,
CoffeeBoiler,
SteamBoilerLevel,
SteamBoilerTemperature,
)
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .coordinator import LaMarzoccoConfigEntry
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
# 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[
[dict[WidgetType, BaseWidgetOutput]], StateType | datetime | None
]
ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
LaMarzoccoSensorEntityDescription(
key="coffee_boiler_ready_time",
translation_key="coffee_boiler_ready_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=(
lambda config: cast(
CoffeeBoiler, config[WidgetType.CM_COFFEE_BOILER]
).ready_start_time
),
entity_category=EntityCategory.DIAGNOSTIC,
),
LaMarzoccoSensorEntityDescription(
key="steam_boiler_ready_time",
translation_key="steam_boiler_ready_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=(
lambda config: cast(
SteamBoilerLevel, config[WidgetType.CM_STEAM_BOILER_LEVEL]
).ready_start_time
),
entity_category=EntityCategory.DIAGNOSTIC,
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (ModelName.LINEA_MICRA, ModelName.LINEA_MINI_R)
),
),
LaMarzoccoSensorEntityDescription(
key="steam_boiler_ready_time",
translation_key="steam_boiler_ready_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=(
lambda config: cast(
SteamBoilerTemperature, config[WidgetType.CM_STEAM_BOILER_TEMPERATURE]
).ready_start_time
),
entity_category=EntityCategory.DIAGNOSTIC,
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (ModelName.GS3_AV, ModelName.GS3_MP, ModelName.LINEA_MINI)
),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: LaMarzoccoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up sensor entities."""
coordinator = entry.runtime_data.config_coordinator
async_add_entities(
LaMarzoccoSensorEntity(coordinator, description)
for description in ENTITIES
if description.supported_fn(coordinator)
)
class LaMarzoccoSensorEntity(LaMarzoccoEntity, SensorEntity):
"""Sensor representing espresso machine water reservoir status."""
entity_description: LaMarzoccoSensorEntityDescription
@property
def native_value(self) -> StateType | datetime | None:
"""Return value of the sensor."""
return self.entity_description.value_fn(
self.coordinator.device.dashboard.config
)

View File

@ -140,6 +140,14 @@
}
}
},
"sensor": {
"coffee_boiler_ready_time": {
"name": "Coffee boiler ready time"
},
"steam_boiler_ready_time": {
"name": "Steam boiler ready time"
}
},
"switch": {
"auto_on_off": {
"name": "Auto on/off ({id})"

View File

@ -0,0 +1,97 @@
# serializer version: 1
# name: test_sensors[sensor.gs012345_coffee_boiler_ready_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.gs012345_coffee_boiler_ready_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Coffee boiler ready time',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'coffee_boiler_ready_time',
'unique_id': 'GS012345_coffee_boiler_ready_time',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.gs012345_coffee_boiler_ready_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'GS012345 Coffee boiler ready time',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_coffee_boiler_ready_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_sensors[sensor.gs012345_steam_boiler_ready_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.gs012345_steam_boiler_ready_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Steam boiler ready time',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'steam_boiler_ready_time',
'unique_id': 'GS012345_steam_boiler_ready_time',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.gs012345_steam_boiler_ready_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'GS012345 Steam boiler ready time',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_steam_boiler_ready_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@ -0,0 +1,52 @@
"""Tests for La Marzocco sensors."""
from unittest.mock import MagicMock, patch
from pylamarzocco.const import ModelName
import pytest
from syrupy import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import async_init_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_sensors(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
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)
@pytest.mark.parametrize(
"device_fixture",
[ModelName.GS3_AV, ModelName.GS3_MP, ModelName.LINEA_MINI, ModelName.LINEA_MICRA],
)
async def test_steam_ready_entity_for_all_machines(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test the La Marzocco steam ready sensor for all machines."""
serial_number = mock_lamarzocco.serial_number
await async_init_integration(hass, mock_config_entry)
state = hass.states.get(f"sensor.{serial_number}_steam_boiler_ready_time")
assert state
entry = entity_registry.async_get(state.entity_id)
assert entry