Add sensors for drink stats per key to lamarzocco (#136582)

* Add sensors for drink stats per key to lamarzocco

* Add icon

* Use UOM translations

* fix tests

* remove translation key

* Update sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/136998/head
Josef Zweck 2025-01-31 12:55:51 +01:00 committed by GitHub
parent cde59613a5
commit f21ab24b8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 275 additions and 11 deletions

View File

@ -95,6 +95,9 @@
"drink_stats_flushing": {
"default": "mdi:chart-line"
},
"drink_stats_coffee_key": {
"default": "mdi:chart-scatter-plot"
},
"shot_timer": {
"default": "mdi:timer"
},

View File

@ -3,7 +3,7 @@
from collections.abc import Callable
from dataclasses import dataclass
from pylamarzocco.const import BoilerType, MachineModel
from pylamarzocco.const import KEYS_PER_MODEL, BoilerType, MachineModel, PhysicalKey
from pylamarzocco.devices.machine import LaMarzoccoMachine
from homeassistant.components.sensor import (
@ -21,7 +21,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import LaMarzoccoConfigEntry
from .coordinator import LaMarzoccoConfigEntry, LaMarzoccoUpdateCoordinator
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
# Coordinator is used to centralize the data updates
@ -37,6 +37,15 @@ class LaMarzoccoSensorEntityDescription(
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",
@ -79,7 +88,6 @@ STATISTIC_ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
LaMarzoccoSensorEntityDescription(
key="drink_stats_coffee",
translation_key="drink_stats_coffee",
native_unit_of_measurement="drinks",
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda device: device.statistics.total_coffee,
available_fn=lambda device: len(device.statistics.drink_stats) > 0,
@ -88,7 +96,6 @@ STATISTIC_ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
LaMarzoccoSensorEntityDescription(
key="drink_stats_flushing",
translation_key="drink_stats_flushing",
native_unit_of_measurement="drinks",
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda device: device.statistics.total_flushes,
available_fn=lambda device: len(device.statistics.drink_stats) > 0,
@ -96,6 +103,18 @@ STATISTIC_ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
),
)
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",
@ -120,6 +139,8 @@ async def async_setup_entry(
"""Set up sensor entities."""
config_coordinator = entry.runtime_data.config_coordinator
entities: list[LaMarzoccoSensorEntity | LaMarzoccoKeySensorEntity] = []
entities = [
LaMarzoccoSensorEntity(config_coordinator, description)
for description in ENTITIES
@ -142,6 +163,14 @@ async def async_setup_entry(
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)
@ -159,11 +188,36 @@ class LaMarzoccoSensorEntity(LaMarzoccoEntity, SensorEntity):
entity_description: LaMarzoccoSensorEntityDescription
@property
def native_value(self) -> int | float:
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."""

View File

@ -175,10 +175,16 @@
"name": "Current steam temperature"
},
"drink_stats_coffee": {
"name": "Total coffees made"
"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"
"name": "Total flushes made",
"unit_of_measurement": "flushes"
},
"shot_timer": {
"name": "Shot timer"

View File

@ -50,6 +50,206 @@
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[sensor.gs012345_coffees_made_key_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.gs012345_coffees_made_key_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': 'coffees',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_coffees_made_key_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1047',
})
# ---
# name: test_sensors[sensor.gs012345_coffees_made_key_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.gs012345_coffees_made_key_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': 'coffees',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_coffees_made_key_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '560',
})
# ---
# name: test_sensors[sensor.gs012345_coffees_made_key_3-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.gs012345_coffees_made_key_3',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': 'coffees',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_coffees_made_key_3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '468',
})
# ---
# name: test_sensors[sensor.gs012345_coffees_made_key_4-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.gs012345_coffees_made_key_4',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': 'coffees',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_coffees_made_key_4',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '312',
})
# ---
# name: test_sensors[sensor.gs012345_current_coffee_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -241,7 +441,7 @@
'supported_features': 0,
'translation_key': 'drink_stats_coffee',
'unique_id': 'GS012345_drink_stats_coffee',
'unit_of_measurement': 'drinks',
'unit_of_measurement': 'coffees',
})
# ---
# name: test_sensors[sensor.gs012345_total_coffees_made-state]
@ -249,7 +449,7 @@
'attributes': ReadOnlyDict({
'friendly_name': 'GS012345 Total coffees made',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': 'drinks',
'unit_of_measurement': 'coffees',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_total_coffees_made',
@ -291,7 +491,7 @@
'supported_features': 0,
'translation_key': 'drink_stats_flushing',
'unique_id': 'GS012345_drink_stats_flushing',
'unit_of_measurement': 'drinks',
'unit_of_measurement': 'flushes',
})
# ---
# name: test_sensors[sensor.gs012345_total_flushes_made-state]
@ -299,7 +499,7 @@
'attributes': ReadOnlyDict({
'friendly_name': 'GS012345 Total flushes made',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': 'drinks',
'unit_of_measurement': 'flushes',
}),
'context': <ANY>,
'entity_id': 'sensor.gs012345_total_flushes_made',

View File

@ -18,6 +18,7 @@ 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,