Add preinfusion settings to lamarzocco (#143159)

This commit is contained in:
Josef Zweck 2025-04-19 12:29:08 +02:00 committed by GitHub
parent 6e8c971038
commit 7c7f18b501
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 451 additions and 3 deletions

View File

@ -36,6 +36,15 @@
}, },
"smart_standby_time": { "smart_standby_time": {
"default": "mdi:timer" "default": "mdi:timer"
},
"preinfusion_time": {
"default": "mdi:water"
},
"prebrew_time_on": {
"default": "mdi:water"
},
"prebrew_time_off": {
"default": "mdi:water-off"
} }
}, },
"select": { "select": {

View File

@ -5,9 +5,9 @@ from dataclasses import dataclass
from typing import Any, cast from typing import Any, cast
from pylamarzocco import LaMarzoccoMachine from pylamarzocco import LaMarzoccoMachine
from pylamarzocco.const import WidgetType from pylamarzocco.const import ModelName, PreExtractionMode, WidgetType
from pylamarzocco.exceptions import RequestNotSuccessful from pylamarzocco.exceptions import RequestNotSuccessful
from pylamarzocco.models import CoffeeBoiler from pylamarzocco.models import CoffeeBoiler, PreBrewing
from homeassistant.components.number import ( from homeassistant.components.number import (
NumberDeviceClass, NumberDeviceClass,
@ -77,6 +77,123 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
), ),
native_value_fn=lambda machine: machine.schedule.smart_wake_up_sleep.smart_stand_by_minutes, native_value_fn=lambda machine: machine.schedule.smart_wake_up_sleep.smart_stand_by_minutes,
), ),
LaMarzoccoNumberEntityDescription(
key="preinfusion_off",
translation_key="preinfusion_time",
device_class=NumberDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
native_step=PRECISION_TENTHS,
native_min_value=0,
native_max_value=10,
entity_category=EntityCategory.CONFIG,
set_value_fn=(
lambda machine, value: machine.set_pre_extraction_times(
seconds_on=0,
seconds_off=float(value),
)
),
native_value_fn=(
lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
)
.times.pre_infusion[0]
.seconds.seconds_out
),
available_fn=(
lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
).mode
is PreExtractionMode.PREINFUSION
),
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (
ModelName.LINEA_MICRA,
ModelName.LINEA_MINI,
ModelName.LINEA_MINI_R,
)
),
),
LaMarzoccoNumberEntityDescription(
key="prebrew_on",
translation_key="prebrew_time_on",
device_class=NumberDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
native_step=PRECISION_TENTHS,
native_min_value=0,
native_max_value=10,
entity_category=EntityCategory.CONFIG,
set_value_fn=(
lambda machine, value: machine.set_pre_extraction_times(
seconds_on=float(value),
seconds_off=cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
)
.times.pre_brewing[0]
.seconds.seconds_out,
)
),
native_value_fn=(
lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
)
.times.pre_brewing[0]
.seconds.seconds_in
),
available_fn=lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
).mode
is PreExtractionMode.PREBREWING,
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (
ModelName.LINEA_MICRA,
ModelName.LINEA_MINI,
ModelName.LINEA_MINI_R,
)
),
),
LaMarzoccoNumberEntityDescription(
key="prebrew_off",
translation_key="prebrew_time_off",
device_class=NumberDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
native_step=PRECISION_TENTHS,
native_min_value=0,
native_max_value=10,
entity_category=EntityCategory.CONFIG,
set_value_fn=(
lambda machine, value: machine.set_pre_extraction_times(
seconds_on=cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
)
.times.pre_brewing[0]
.seconds.seconds_in,
seconds_off=float(value),
)
),
native_value_fn=(
lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
)
.times.pre_brewing[0]
.seconds.seconds_out
),
available_fn=(
lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
).mode
is PreExtractionMode.PREBREWING
),
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (
ModelName.LINEA_MICRA,
ModelName.LINEA_MINI,
ModelName.LINEA_MINI_R,
)
),
),
) )

View File

@ -104,6 +104,15 @@
}, },
"smart_standby_time": { "smart_standby_time": {
"name": "Smart standby time" "name": "Smart standby time"
},
"preinfusion_time": {
"name": "Preinfusion time"
},
"prebrew_time_on": {
"name": "Prebrew on time"
},
"prebrew_time_off": {
"name": "Prebrew off time"
} }
}, },
"select": { "select": {

View File

@ -115,3 +115,177 @@
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>, 'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}) })
# --- # ---
# name: test_prebrew_off[Linea Micra]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'MR012345 Prebrew off time',
'max': 10,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'context': <ANY>,
'entity_id': 'number.mr012345_prebrew_off_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '5.0',
})
# ---
# name: test_prebrew_off[Linea Micra].1
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mr012345_prebrew_off_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Prebrew off time',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'prebrew_time_off',
'unique_id': 'MR012345_prebrew_off',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
})
# ---
# name: test_prebrew_on[Linea Micra]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'MR012345 Prebrew on time',
'max': 10,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'context': <ANY>,
'entity_id': 'number.mr012345_prebrew_on_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '5.0',
})
# ---
# name: test_prebrew_on[Linea Micra].1
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mr012345_prebrew_on_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Prebrew on time',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'prebrew_time_on',
'unique_id': 'MR012345_prebrew_on',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
})
# ---
# name: test_preinfusion[Linea Micra]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'MR012345 Preinfusion time',
'max': 10,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mr012345_preinfusion_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '4.0',
})
# ---
# name: test_preinfusion[Linea Micra].1
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mr012345_preinfusion_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Preinfusion time',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'preinfusion_time',
'unique_id': 'MR012345_preinfusion_off',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---

View File

@ -3,7 +3,12 @@
from typing import Any from typing import Any
from unittest.mock import MagicMock from unittest.mock import MagicMock
from pylamarzocco.const import SmartStandByType from pylamarzocco.const import (
ModelName,
PreExtractionMode,
SmartStandByType,
WidgetType,
)
from pylamarzocco.exceptions import RequestNotSuccessful from pylamarzocco.exceptions import RequestNotSuccessful
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
@ -85,6 +90,140 @@ async def test_general_numbers(
mock_func.assert_called_once_with(**kwargs) mock_func.assert_called_once_with(**kwargs)
@pytest.mark.parametrize("device_fixture", [ModelName.LINEA_MICRA])
async def test_preinfusion(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test preinfusion number."""
await async_init_integration(hass, mock_config_entry)
serial_number = mock_lamarzocco.serial_number
entity_id = f"number.{serial_number}_preinfusion_time"
state = hass.states.get(entity_id)
assert state
assert state == snapshot
entry = entity_registry.async_get(state.entity_id)
assert entry
assert entry.device_id
assert entry == snapshot
# service call
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: 5.3,
},
blocking=True,
)
mock_lamarzocco.set_pre_extraction_times.assert_called_once_with(
seconds_off=5.3,
seconds_on=0,
)
@pytest.mark.parametrize("device_fixture", [ModelName.LINEA_MICRA])
async def test_prebrew_on(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test prebrew on number."""
mock_lamarzocco.dashboard.config[
WidgetType.CM_PRE_BREWING
].mode = PreExtractionMode.PREBREWING
await async_init_integration(hass, mock_config_entry)
serial_number = mock_lamarzocco.serial_number
entity_id = f"number.{serial_number}_prebrew_on_time"
state = hass.states.get(entity_id)
assert state
assert state == snapshot
entry = entity_registry.async_get(state.entity_id)
assert entry
assert entry.device_id
assert entry == snapshot
# service call
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: 5.3,
},
blocking=True,
)
mock_lamarzocco.set_pre_extraction_times.assert_called_once_with(
seconds_on=5.3,
seconds_off=mock_lamarzocco.dashboard.config[WidgetType.CM_PRE_BREWING]
.times.pre_brewing[0]
.seconds.seconds_out,
)
@pytest.mark.parametrize("device_fixture", [ModelName.LINEA_MICRA])
async def test_prebrew_off(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test prebrew off number."""
mock_lamarzocco.dashboard.config[
WidgetType.CM_PRE_BREWING
].mode = PreExtractionMode.PREBREWING
await async_init_integration(hass, mock_config_entry)
serial_number = mock_lamarzocco.serial_number
entity_id = f"number.{serial_number}_prebrew_off_time"
state = hass.states.get(entity_id)
assert state
assert state == snapshot
entry = entity_registry.async_get(state.entity_id)
assert entry
assert entry.device_id
assert entry == snapshot
# service call
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: 7,
},
blocking=True,
)
mock_lamarzocco.set_pre_extraction_times.assert_called_once_with(
seconds_off=7,
seconds_on=mock_lamarzocco.dashboard.config[WidgetType.CM_PRE_BREWING]
.times.pre_brewing[0]
.seconds.seconds_in,
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_number_error( async def test_number_error(
hass: HomeAssistant, hass: HomeAssistant,