mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add scale support to lamarzocco (#133335)
This commit is contained in:
parent
3df992790d
commit
bddd8624bb
@ -3,6 +3,7 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pylamarzocco.const import MachineModel
|
||||
from pylamarzocco.models import LaMarzoccoMachineConfig
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
@ -15,7 +16,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .coordinator import LaMarzoccoConfigEntry
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -28,7 +29,7 @@ class LaMarzoccoBinarySensorEntityDescription(
|
||||
):
|
||||
"""Description of a La Marzocco binary sensor."""
|
||||
|
||||
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool]
|
||||
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool | None]
|
||||
|
||||
|
||||
ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
||||
@ -57,6 +58,15 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
SCALE_ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
||||
LaMarzoccoBinarySensorEntityDescription(
|
||||
key="connected",
|
||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||
is_on_fn=lambda config: config.scale.connected if config.scale else None,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -66,11 +76,30 @@ async def async_setup_entry(
|
||||
"""Set up binary sensor entities."""
|
||||
coordinator = entry.runtime_data.config_coordinator
|
||||
|
||||
async_add_entities(
|
||||
entities = [
|
||||
LaMarzoccoBinarySensorEntity(coordinator, description)
|
||||
for description in ENTITIES
|
||||
if description.supported_fn(coordinator)
|
||||
)
|
||||
]
|
||||
|
||||
if (
|
||||
coordinator.device.model == MachineModel.LINEA_MINI
|
||||
and coordinator.device.config.scale
|
||||
):
|
||||
entities.extend(
|
||||
LaMarzoccoScaleBinarySensorEntity(coordinator, description)
|
||||
for description in SCALE_ENTITIES
|
||||
)
|
||||
|
||||
def _async_add_new_scale() -> None:
|
||||
async_add_entities(
|
||||
LaMarzoccoScaleBinarySensorEntity(coordinator, description)
|
||||
for description in SCALE_ENTITIES
|
||||
)
|
||||
|
||||
coordinator.new_device_callback.append(_async_add_new_scale)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity):
|
||||
@ -79,6 +108,14 @@ class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity):
|
||||
entity_description: LaMarzoccoBinarySensorEntityDescription
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self.entity_description.is_on_fn(self.coordinator.device.config)
|
||||
|
||||
|
||||
class LaMarzoccoScaleBinarySensorEntity(
|
||||
LaMarzoccoBinarySensorEntity, LaMarzoccScaleEntity
|
||||
):
|
||||
"""Binary sensor for La Marzocco scales."""
|
||||
|
||||
entity_description: LaMarzoccoBinarySensorEntityDescription
|
||||
|
@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
@ -14,8 +15,9 @@ from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -62,6 +64,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
self.device = device
|
||||
self.local_connection_configured = local_client is not None
|
||||
self._local_client = local_client
|
||||
self.new_device_callback: list[Callable] = []
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Do the data update."""
|
||||
@ -86,6 +89,8 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
|
||||
"""Class to handle fetching data from the La Marzocco API centrally."""
|
||||
|
||||
_scale_address: str | None = None
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
if self._local_client is not None:
|
||||
@ -118,6 +123,25 @@ class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
|
||||
"""Fetch data from API endpoint."""
|
||||
await self.device.get_config()
|
||||
_LOGGER.debug("Current status: %s", str(self.device.config))
|
||||
self._async_add_remove_scale()
|
||||
|
||||
@callback
|
||||
def _async_add_remove_scale(self) -> None:
|
||||
"""Add or remove a scale when added or removed."""
|
||||
if self.device.config.scale and not self._scale_address:
|
||||
self._scale_address = self.device.config.scale.address
|
||||
for scale_callback in self.new_device_callback:
|
||||
scale_callback()
|
||||
elif not self.device.config.scale and self._scale_address:
|
||||
device_registry = dr.async_get(self.hass)
|
||||
if device := device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, self._scale_address)}
|
||||
):
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
self._scale_address = None
|
||||
|
||||
|
||||
class LaMarzoccoFirmwareUpdateCoordinator(LaMarzoccoUpdateCoordinator):
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pylamarzocco.const import FirmwareType
|
||||
from pylamarzocco.devices.machine import LaMarzoccoMachine
|
||||
@ -85,3 +86,26 @@ class LaMarzoccoEntity(LaMarzoccoBaseEntity):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, entity_description.key)
|
||||
self.entity_description = entity_description
|
||||
|
||||
|
||||
class LaMarzoccScaleEntity(LaMarzoccoEntity):
|
||||
"""Common class for scale."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: LaMarzoccoUpdateCoordinator,
|
||||
entity_description: LaMarzoccoEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, entity_description)
|
||||
scale = coordinator.device.config.scale
|
||||
if TYPE_CHECKING:
|
||||
assert scale
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, scale.address)},
|
||||
name=scale.name,
|
||||
manufacturer="Acaia",
|
||||
model="Lunar",
|
||||
model_id="Y.301",
|
||||
via_device=(DOMAIN, coordinator.device.serial_number),
|
||||
)
|
||||
|
@ -43,6 +43,9 @@
|
||||
"preinfusion_off": {
|
||||
"default": "mdi:water"
|
||||
},
|
||||
"scale_target": {
|
||||
"default": "mdi:scale-balance"
|
||||
},
|
||||
"smart_standby_time": {
|
||||
"default": "mdi:timer"
|
||||
},
|
||||
@ -54,6 +57,13 @@
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"active_bbw": {
|
||||
"default": "mdi:alpha-u",
|
||||
"state": {
|
||||
"a": "mdi:alpha-a",
|
||||
"b": "mdi:alpha-b"
|
||||
}
|
||||
},
|
||||
"smart_standby_mode": {
|
||||
"default": "mdi:power",
|
||||
"state": {
|
||||
|
@ -33,7 +33,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMarzoccoConfigEntry, LaMarzoccoUpdateCoordinator
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -56,7 +56,9 @@ class LaMarzoccoKeyNumberEntityDescription(
|
||||
):
|
||||
"""Description of an La Marzocco number entity with keys."""
|
||||
|
||||
native_value_fn: Callable[[LaMarzoccoMachineConfig, PhysicalKey], float | int]
|
||||
native_value_fn: Callable[
|
||||
[LaMarzoccoMachineConfig, PhysicalKey], float | int | None
|
||||
]
|
||||
set_value_fn: Callable[
|
||||
[LaMarzoccoMachine, float | int, PhysicalKey], Coroutine[Any, Any, bool]
|
||||
]
|
||||
@ -203,6 +205,27 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
SCALE_KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
||||
LaMarzoccoKeyNumberEntityDescription(
|
||||
key="scale_target",
|
||||
translation_key="scale_target",
|
||||
native_step=PRECISION_WHOLE,
|
||||
native_min_value=1,
|
||||
native_max_value=100,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
set_value_fn=lambda machine, weight, key: machine.set_bbw_recipe_target(
|
||||
key, int(weight)
|
||||
),
|
||||
native_value_fn=lambda config, key: (
|
||||
config.bbw_settings.doses[key] if config.bbw_settings else None
|
||||
),
|
||||
supported_fn=(
|
||||
lambda coordinator: coordinator.device.model == MachineModel.LINEA_MINI
|
||||
and coordinator.device.config.scale is not None
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -224,6 +247,27 @@ async def async_setup_entry(
|
||||
LaMarzoccoKeyNumberEntity(coordinator, description, key)
|
||||
for key in range(min(num_keys, 1), num_keys + 1)
|
||||
)
|
||||
|
||||
for description in SCALE_KEY_ENTITIES:
|
||||
if description.supported_fn(coordinator):
|
||||
if bbw_settings := coordinator.device.config.bbw_settings:
|
||||
entities.extend(
|
||||
LaMarzoccoScaleTargetNumberEntity(
|
||||
coordinator, description, int(key)
|
||||
)
|
||||
for key in bbw_settings.doses
|
||||
)
|
||||
|
||||
def _async_add_new_scale() -> None:
|
||||
if bbw_settings := coordinator.device.config.bbw_settings:
|
||||
async_add_entities(
|
||||
LaMarzoccoScaleTargetNumberEntity(coordinator, description, int(key))
|
||||
for description in SCALE_KEY_ENTITIES
|
||||
for key in bbw_settings.doses
|
||||
)
|
||||
|
||||
coordinator.new_device_callback.append(_async_add_new_scale)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
@ -281,7 +325,7 @@ class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
||||
self.pyhsical_key = pyhsical_key
|
||||
|
||||
@property
|
||||
def native_value(self) -> float:
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the current value."""
|
||||
return self.entity_description.native_value_fn(
|
||||
self.coordinator.device.config, PhysicalKey(self.pyhsical_key)
|
||||
@ -305,3 +349,11 @@ class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
||||
},
|
||||
) from exc
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class LaMarzoccoScaleTargetNumberEntity(
|
||||
LaMarzoccoKeyNumberEntity, LaMarzoccScaleEntity
|
||||
):
|
||||
"""Entity representing a key number on the scale."""
|
||||
|
||||
entity_description: LaMarzoccoKeyNumberEntityDescription
|
||||
|
@ -62,9 +62,9 @@ rules:
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: done
|
||||
dynamic-devices:
|
||||
status: exempt
|
||||
status: done
|
||||
comment: |
|
||||
Device type integration.
|
||||
Device type integration, only possible for addon scale
|
||||
entity-category: done
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
@ -74,9 +74,9 @@ rules:
|
||||
reconfiguration-flow: done
|
||||
repair-issues: done
|
||||
stale-devices:
|
||||
status: exempt
|
||||
status: done
|
||||
comment: |
|
||||
Device type integration.
|
||||
Device type integration, only possible for addon scale
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
|
@ -4,7 +4,13 @@ from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pylamarzocco.const import MachineModel, PrebrewMode, SmartStandbyMode, SteamLevel
|
||||
from pylamarzocco.const import (
|
||||
MachineModel,
|
||||
PhysicalKey,
|
||||
PrebrewMode,
|
||||
SmartStandbyMode,
|
||||
SteamLevel,
|
||||
)
|
||||
from pylamarzocco.devices.machine import LaMarzoccoMachine
|
||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||
from pylamarzocco.models import LaMarzoccoMachineConfig
|
||||
@ -17,7 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMarzoccoConfigEntry
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -52,7 +58,7 @@ class LaMarzoccoSelectEntityDescription(
|
||||
):
|
||||
"""Description of a La Marzocco select entity."""
|
||||
|
||||
current_option_fn: Callable[[LaMarzoccoMachineConfig], str]
|
||||
current_option_fn: Callable[[LaMarzoccoMachineConfig], str | None]
|
||||
select_option_fn: Callable[[LaMarzoccoMachine, str], Coroutine[Any, Any, bool]]
|
||||
|
||||
|
||||
@ -100,6 +106,22 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
SCALE_ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
||||
LaMarzoccoSelectEntityDescription(
|
||||
key="active_bbw",
|
||||
translation_key="active_bbw",
|
||||
options=["a", "b"],
|
||||
select_option_fn=lambda machine, option: machine.set_active_bbw_recipe(
|
||||
PhysicalKey[option.upper()]
|
||||
),
|
||||
current_option_fn=lambda config: (
|
||||
config.bbw_settings.active_dose.name.lower()
|
||||
if config.bbw_settings
|
||||
else None
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -109,11 +131,30 @@ async def async_setup_entry(
|
||||
"""Set up select entities."""
|
||||
coordinator = entry.runtime_data.config_coordinator
|
||||
|
||||
async_add_entities(
|
||||
entities = [
|
||||
LaMarzoccoSelectEntity(coordinator, description)
|
||||
for description in ENTITIES
|
||||
if description.supported_fn(coordinator)
|
||||
)
|
||||
]
|
||||
|
||||
if (
|
||||
coordinator.device.model == MachineModel.LINEA_MINI
|
||||
and coordinator.device.config.scale
|
||||
):
|
||||
entities.extend(
|
||||
LaMarzoccoScaleSelectEntity(coordinator, description)
|
||||
for description in SCALE_ENTITIES
|
||||
)
|
||||
|
||||
def _async_add_new_scale() -> None:
|
||||
async_add_entities(
|
||||
LaMarzoccoScaleSelectEntity(coordinator, description)
|
||||
for description in SCALE_ENTITIES
|
||||
)
|
||||
|
||||
coordinator.new_device_callback.append(_async_add_new_scale)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
||||
@ -122,7 +163,7 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
||||
entity_description: LaMarzoccoSelectEntityDescription
|
||||
|
||||
@property
|
||||
def current_option(self) -> str:
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the current selected option."""
|
||||
return str(
|
||||
self.entity_description.current_option_fn(self.coordinator.device.config)
|
||||
@ -145,3 +186,9 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
||||
},
|
||||
) from exc
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class LaMarzoccoScaleSelectEntity(LaMarzoccoSelectEntity, LaMarzoccScaleEntity):
|
||||
"""Select entity for La Marzocco scales."""
|
||||
|
||||
entity_description: LaMarzoccoSelectEntityDescription
|
||||
|
@ -12,12 +12,17 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import EntityCategory, UnitOfTemperature, UnitOfTime
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .coordinator import LaMarzoccoConfigEntry
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -91,6 +96,21 @@ STATISTIC_ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
SCALE_ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
LaMarzoccoSensorEntityDescription(
|
||||
key="scale_battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
value_fn=lambda device: (
|
||||
device.config.scale.battery if device.config.scale else 0
|
||||
),
|
||||
supported_fn=(
|
||||
lambda coordinator: coordinator.device.model == MachineModel.LINEA_MINI
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -106,6 +126,15 @@ async def async_setup_entry(
|
||||
if description.supported_fn(config_coordinator)
|
||||
]
|
||||
|
||||
if (
|
||||
config_coordinator.device.model == MachineModel.LINEA_MINI
|
||||
and config_coordinator.device.config.scale
|
||||
):
|
||||
entities.extend(
|
||||
LaMarzoccoScaleSensorEntity(config_coordinator, description)
|
||||
for description in SCALE_ENTITIES
|
||||
)
|
||||
|
||||
statistics_coordinator = entry.runtime_data.statistics_coordinator
|
||||
entities.extend(
|
||||
LaMarzoccoSensorEntity(statistics_coordinator, description)
|
||||
@ -113,6 +142,14 @@ async def async_setup_entry(
|
||||
if description.supported_fn(statistics_coordinator)
|
||||
)
|
||||
|
||||
def _async_add_new_scale() -> None:
|
||||
async_add_entities(
|
||||
LaMarzoccoScaleSensorEntity(config_coordinator, description)
|
||||
for description in SCALE_ENTITIES
|
||||
)
|
||||
|
||||
config_coordinator.new_device_callback.append(_async_add_new_scale)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
@ -125,3 +162,9 @@ class LaMarzoccoSensorEntity(LaMarzoccoEntity, SensorEntity):
|
||||
def native_value(self) -> int | float:
|
||||
"""State of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.device)
|
||||
|
||||
|
||||
class LaMarzoccoScaleSensorEntity(LaMarzoccoSensorEntity, LaMarzoccScaleEntity):
|
||||
"""Sensor for a La Marzocco scale."""
|
||||
|
||||
entity_description: LaMarzoccoSensorEntityDescription
|
||||
|
@ -122,6 +122,9 @@
|
||||
"preinfusion_off_key": {
|
||||
"name": "Preinfusion time Key {key}"
|
||||
},
|
||||
"scale_target_key": {
|
||||
"name": "Brew by weight target {key}"
|
||||
},
|
||||
"smart_standby_time": {
|
||||
"name": "Smart standby time"
|
||||
},
|
||||
@ -133,6 +136,13 @@
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"active_bbw": {
|
||||
"name": "Active brew by weight recipe",
|
||||
"state": {
|
||||
"a": "Recipe A",
|
||||
"b": "Recipe B"
|
||||
}
|
||||
},
|
||||
"prebrew_infusion_select": {
|
||||
"name": "Prebrew/-infusion mode",
|
||||
"state": {
|
||||
|
@ -135,7 +135,10 @@ def mock_lamarzocco(device_fixture: MachineModel) -> Generator[MagicMock]:
|
||||
serial_number=serial_number,
|
||||
name=serial_number,
|
||||
)
|
||||
config = load_json_object_fixture("config.json", DOMAIN)
|
||||
if device_fixture == MachineModel.LINEA_MINI:
|
||||
config = load_json_object_fixture("config_mini.json", DOMAIN)
|
||||
else:
|
||||
config = load_json_object_fixture("config.json", DOMAIN)
|
||||
statistics = json.loads(load_fixture("statistics.json", DOMAIN))
|
||||
|
||||
dummy_machine.parse_config(config)
|
||||
|
116
tests/components/lamarzocco/fixtures/config_mini.json
Normal file
116
tests/components/lamarzocco/fixtures/config_mini.json
Normal file
@ -0,0 +1,116 @@
|
||||
{
|
||||
"version": "v1",
|
||||
"preinfusionModesAvailable": ["ByDoseType"],
|
||||
"machineCapabilities": [
|
||||
{
|
||||
"family": "LINEA",
|
||||
"groupsNumber": 1,
|
||||
"coffeeBoilersNumber": 1,
|
||||
"hasCupWarmer": false,
|
||||
"steamBoilersNumber": 1,
|
||||
"teaDosesNumber": 1,
|
||||
"machineModes": ["BrewingMode", "StandBy"],
|
||||
"schedulingType": "smartWakeUpSleep"
|
||||
}
|
||||
],
|
||||
"machine_sn": "Sn01239157",
|
||||
"machine_hw": "0",
|
||||
"isPlumbedIn": false,
|
||||
"isBackFlushEnabled": false,
|
||||
"standByTime": 0,
|
||||
"tankStatus": true,
|
||||
"settings": [],
|
||||
"recipes": [
|
||||
{
|
||||
"id": "Recipe1",
|
||||
"dose_mode": "Mass",
|
||||
"recipe_doses": [
|
||||
{ "id": "A", "target": 32 },
|
||||
{ "id": "B", "target": 45 }
|
||||
]
|
||||
}
|
||||
],
|
||||
"recipeAssignment": [
|
||||
{
|
||||
"dose_index": "DoseA",
|
||||
"recipe_id": "Recipe1",
|
||||
"recipe_dose": "A",
|
||||
"group": "Group1"
|
||||
}
|
||||
],
|
||||
"groupCapabilities": [
|
||||
{
|
||||
"capabilities": {
|
||||
"groupType": "AV_Group",
|
||||
"groupNumber": "Group1",
|
||||
"boilerId": "CoffeeBoiler1",
|
||||
"hasScale": false,
|
||||
"hasFlowmeter": false,
|
||||
"numberOfDoses": 1
|
||||
},
|
||||
"doses": [
|
||||
{
|
||||
"groupNumber": "Group1",
|
||||
"doseIndex": "DoseA",
|
||||
"doseType": "MassType",
|
||||
"stopTarget": 32
|
||||
}
|
||||
],
|
||||
"doseMode": { "groupNumber": "Group1", "brewingType": "ManualType" }
|
||||
}
|
||||
],
|
||||
"machineMode": "StandBy",
|
||||
"teaDoses": { "DoseA": { "doseIndex": "DoseA", "stopTarget": 0 } },
|
||||
"scale": {
|
||||
"connected": true,
|
||||
"address": "44:b7:d0:74:5f:90",
|
||||
"name": "LMZ-123A45",
|
||||
"battery": 64
|
||||
},
|
||||
"boilers": [
|
||||
{ "id": "SteamBoiler", "isEnabled": false, "target": 0, "current": 0 },
|
||||
{ "id": "CoffeeBoiler1", "isEnabled": true, "target": 89, "current": 42 }
|
||||
],
|
||||
"boilerTargetTemperature": { "SteamBoiler": 0, "CoffeeBoiler1": 89 },
|
||||
"preinfusionMode": {
|
||||
"Group1": {
|
||||
"groupNumber": "Group1",
|
||||
"preinfusionStyle": "PreinfusionByDoseType"
|
||||
}
|
||||
},
|
||||
"preinfusionSettings": {
|
||||
"mode": "TypeB",
|
||||
"Group1": [
|
||||
{
|
||||
"groupNumber": "Group1",
|
||||
"doseType": "DoseA",
|
||||
"preWetTime": 2,
|
||||
"preWetHoldTime": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"wakeUpSleepEntries": [
|
||||
{
|
||||
"id": "T6aLl42",
|
||||
"days": [
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
"sunday"
|
||||
],
|
||||
"steam": false,
|
||||
"enabled": false,
|
||||
"timeOn": "24:0",
|
||||
"timeOff": "24:0"
|
||||
}
|
||||
],
|
||||
"smartStandBy": { "mode": "LastBrewing", "minutes": 10, "enabled": true },
|
||||
"clock": "2024-08-31T14:47:45",
|
||||
"firmwareVersions": [
|
||||
{ "name": "machine_firmware", "fw_version": "2.12" },
|
||||
{ "name": "gateway_firmware", "fw_version": "v3.6-rc4" }
|
||||
]
|
||||
}
|
@ -140,3 +140,50 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_scale_connectivity[Linea Mini]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'connectivity',
|
||||
'friendly_name': 'LMZ-123A45 Connectivity',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.lmz_123a45_connectivity',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_scale_connectivity[Linea Mini].1
|
||||
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.lmz_123a45_connectivity',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.CONNECTIVITY: 'connectivity'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Connectivity',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'LM012345_connected',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
|
@ -39,3 +39,35 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_scale_device[Linea Mini]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'configuration_url': None,
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'lamarzocco',
|
||||
'44:b7:d0:74:5f:90',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Acaia',
|
||||
'model': 'Lunar',
|
||||
'model_id': 'Y.301',
|
||||
'name': 'LMZ-123A45',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': None,
|
||||
'via_device_id': <ANY>,
|
||||
})
|
||||
# ---
|
||||
|
@ -657,7 +657,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1',
|
||||
'state': '3',
|
||||
})
|
||||
# ---
|
||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Linea Mini].1
|
||||
@ -771,7 +771,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1',
|
||||
'state': '3',
|
||||
})
|
||||
# ---
|
||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Linea Mini].1
|
||||
@ -885,7 +885,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1',
|
||||
'state': '3',
|
||||
})
|
||||
# ---
|
||||
# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Linea Mini].1
|
||||
@ -983,3 +983,113 @@
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_set_target[Linea Mini-1]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'LMZ-123A45 Brew by weight target 1',
|
||||
'max': 100,
|
||||
'min': 1,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.lmz_123a45_brew_by_weight_target_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '32',
|
||||
})
|
||||
# ---
|
||||
# name: test_set_target[Linea Mini-1].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 100,
|
||||
'min': 1,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.lmz_123a45_brew_by_weight_target_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': 'Brew by weight target 1',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'scale_target_key',
|
||||
'unique_id': 'LM012345_scale_target_key1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_set_target[Linea Mini-2]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'LMZ-123A45 Brew by weight target 2',
|
||||
'max': 100,
|
||||
'min': 1,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.lmz_123a45_brew_by_weight_target_2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '45',
|
||||
})
|
||||
# ---
|
||||
# name: test_set_target[Linea Mini-2].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 100,
|
||||
'min': 1,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.lmz_123a45_brew_by_weight_target_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': 'Brew by weight target 2',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'scale_target_key',
|
||||
'unique_id': 'LM012345_scale_target_key2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
|
@ -1,4 +1,59 @@
|
||||
# serializer version: 1
|
||||
# name: test_active_bbw_recipe[Linea Mini]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'LMZ-123A45 Active brew by weight recipe',
|
||||
'options': list([
|
||||
'a',
|
||||
'b',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.lmz_123a45_active_brew_by_weight_recipe',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'a',
|
||||
})
|
||||
# ---
|
||||
# name: test_active_bbw_recipe[Linea Mini].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'a',
|
||||
'b',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_id': 'select.lmz_123a45_active_brew_by_weight_recipe',
|
||||
'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': 'Active brew by weight recipe',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'active_bbw',
|
||||
'unique_id': 'LM012345_active_bbw',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_pre_brew_infusion_select[GS3 AV]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
|
@ -1,4 +1,55 @@
|
||||
# serializer version: 1
|
||||
# name: test_scale_battery[Linea Mini]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'LMZ-123A45 Battery',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.lmz_123a45_battery',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '64',
|
||||
})
|
||||
# ---
|
||||
# name: test_scale_battery[Linea Mini].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.lmz_123a45_battery',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Battery',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'LM012345_scale_battery',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[GS012345_current_coffee_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -4,7 +4,10 @@ from datetime import timedelta
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pylamarzocco.const import MachineModel
|
||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||
from pylamarzocco.models import LaMarzoccoScale
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
@ -98,3 +101,68 @@ async def test_sensor_going_unavailable(
|
||||
state = hass.states.get(brewing_active_sensor)
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_scale_connectivity(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the scale binary sensors."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("binary_sensor.lmz_123a45_connectivity")
|
||||
assert state
|
||||
assert state == snapshot
|
||||
|
||||
entry = entity_registry.async_get(state.entity_id)
|
||||
assert entry
|
||||
assert entry.device_id
|
||||
assert entry == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA],
|
||||
)
|
||||
async def test_other_models_no_scale_connectivity(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Ensure the other models don't have a connectivity sensor."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("binary_sensor.lmz_123a45_connectivity")
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_connectivity_on_new_scale_added(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Ensure the connectivity binary sensor for a new scale is added automatically."""
|
||||
|
||||
mock_lamarzocco.config.scale = None
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("binary_sensor.scale_123a45_connectivity")
|
||||
assert state is None
|
||||
|
||||
mock_lamarzocco.config.scale = LaMarzoccoScale(
|
||||
connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50
|
||||
)
|
||||
|
||||
freezer.tick(timedelta(minutes=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.scale_123a45_connectivity")
|
||||
assert state
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""Test initialization of lamarzocco."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from pylamarzocco.const import FirmwareType
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pylamarzocco.const import FirmwareType, MachineModel
|
||||
from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
@ -27,7 +29,7 @@ from homeassistant.helpers import (
|
||||
|
||||
from . import USER_INPUT, async_init_integration, get_bluetooth_service_info
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_load_unload_config_entry(
|
||||
@ -251,3 +253,49 @@ async def test_device(
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
assert device == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_scale_device(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the device."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, mock_lamarzocco.config.scale.address)}
|
||||
)
|
||||
assert device
|
||||
assert device == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_remove_stale_scale(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Ensure stale scale is cleaned up."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
scale_address = mock_lamarzocco.config.scale.address
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, scale_address)})
|
||||
assert device
|
||||
|
||||
mock_lamarzocco.config.scale = None
|
||||
|
||||
freezer.tick(timedelta(minutes=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, scale_address)})
|
||||
assert device is None
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""Tests for the La Marzocco number entities."""
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pylamarzocco.const import (
|
||||
KEYS_PER_MODEL,
|
||||
BoilerType,
|
||||
@ -11,6 +13,7 @@ from pylamarzocco.const import (
|
||||
PrebrewMode,
|
||||
)
|
||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||
from pylamarzocco.models import LaMarzoccoScale
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
@ -26,7 +29,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import async_init_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -444,3 +447,91 @@ async def test_number_error(
|
||||
blocking=True,
|
||||
)
|
||||
assert exc_info.value.translation_key == "number_exception_key"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("physical_key", [PhysicalKey.A, PhysicalKey.B])
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_set_target(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
physical_key: PhysicalKey,
|
||||
) -> None:
|
||||
"""Test the La Marzocco set target sensors."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
entity_name = f"number.lmz_123a45_brew_by_weight_target_{int(physical_key)}"
|
||||
|
||||
state = hass.states.get(entity_name)
|
||||
|
||||
assert state
|
||||
assert state == snapshot
|
||||
|
||||
entry = entity_registry.async_get(state.entity_id)
|
||||
assert entry
|
||||
assert entry == snapshot
|
||||
|
||||
# service call
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_name,
|
||||
ATTR_VALUE: 42,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_lamarzocco.set_bbw_recipe_target.assert_called_once_with(physical_key, 42)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA],
|
||||
)
|
||||
async def test_other_models_no_scale_set_target(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Ensure the other models don't have a set target numbers."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
for i in range(1, 3):
|
||||
state = hass.states.get(f"number.lmz_123a45_brew_by_weight_target_{i}")
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_set_target_on_new_scale_added(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Ensure the set target numbers for a new scale are added automatically."""
|
||||
|
||||
mock_lamarzocco.config.scale = None
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
for i in range(1, 3):
|
||||
state = hass.states.get(f"number.scale_123a45_brew_by_weight_target_{i}")
|
||||
assert state is None
|
||||
|
||||
mock_lamarzocco.config.scale = LaMarzoccoScale(
|
||||
connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50
|
||||
)
|
||||
|
||||
freezer.tick(timedelta(minutes=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for i in range(1, 3):
|
||||
state = hass.states.get(f"number.scale_123a45_brew_by_weight_target_{i}")
|
||||
assert state
|
||||
|
@ -1,9 +1,18 @@
|
||||
"""Tests for the La Marzocco select entities."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from pylamarzocco.const import MachineModel, PrebrewMode, SmartStandbyMode, SteamLevel
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pylamarzocco.const import (
|
||||
MachineModel,
|
||||
PhysicalKey,
|
||||
PrebrewMode,
|
||||
SmartStandbyMode,
|
||||
SteamLevel,
|
||||
)
|
||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||
from pylamarzocco.models import LaMarzoccoScale
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
@ -17,9 +26,12 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||
from . import async_init_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MICRA])
|
||||
async def test_steam_boiler_level(
|
||||
hass: HomeAssistant,
|
||||
@ -54,6 +66,9 @@ async def test_steam_boiler_level(
|
||||
mock_lamarzocco.set_steam_level.assert_called_once_with(level=SteamLevel.LEVEL_2)
|
||||
|
||||
|
||||
pytest.mark.usefixtures("init_integration")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MINI],
|
||||
@ -69,6 +84,7 @@ async def test_steam_boiler_level_none(
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[MachineModel.LINEA_MICRA, MachineModel.GS3_AV, MachineModel.LINEA_MINI],
|
||||
@ -106,6 +122,7 @@ async def test_pre_brew_infusion_select(
|
||||
mock_lamarzocco.set_prebrew_mode.assert_called_once_with(mode=PrebrewMode.PREBREW)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[MachineModel.GS3_MP],
|
||||
@ -121,6 +138,7 @@ async def test_pre_brew_infusion_select_none(
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_smart_standby_mode(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
@ -155,6 +173,7 @@ async def test_smart_standby_mode(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_select_errors(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
@ -179,3 +198,77 @@ async def test_select_errors(
|
||||
blocking=True,
|
||||
)
|
||||
assert exc_info.value.translation_key == "select_option_error"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_active_bbw_recipe(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_lamarzocco: MagicMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the La Marzocco active bbw recipe select."""
|
||||
|
||||
state = hass.states.get("select.lmz_123a45_active_brew_by_weight_recipe")
|
||||
|
||||
assert state
|
||||
assert state == snapshot
|
||||
|
||||
entry = entity_registry.async_get(state.entity_id)
|
||||
assert entry
|
||||
assert entry == snapshot
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.lmz_123a45_active_brew_by_weight_recipe",
|
||||
ATTR_OPTION: "b",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_lamarzocco.set_active_bbw_recipe.assert_called_once_with(PhysicalKey.B)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA],
|
||||
)
|
||||
async def test_other_models_no_active_bbw_select(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
) -> None:
|
||||
"""Ensure the other models don't have a battery sensor."""
|
||||
|
||||
state = hass.states.get("select.lmz_123a45_active_brew_by_weight_recipe")
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_active_bbw_select_on_new_scale_added(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Ensure the active bbw select for a new scale is added automatically."""
|
||||
|
||||
mock_lamarzocco.config.scale = None
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("select.scale_123a45_active_brew_by_weight_recipe")
|
||||
assert state is None
|
||||
|
||||
mock_lamarzocco.config.scale = LaMarzoccoScale(
|
||||
connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50
|
||||
)
|
||||
|
||||
freezer.tick(timedelta(minutes=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("select.scale_123a45_active_brew_by_weight_recipe")
|
||||
assert state
|
||||
|
@ -1,8 +1,11 @@
|
||||
"""Tests for La Marzocco sensors."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pylamarzocco.const import MachineModel
|
||||
from pylamarzocco.models import LaMarzoccoScale
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
@ -12,7 +15,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import async_init_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
SENSORS = (
|
||||
"total_coffees_made",
|
||||
@ -85,3 +88,67 @@ async def test_no_steam_linea_mini(
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
state = hass.states.get(f"sensor.{serial_number}_current_temp_steam")
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_scale_battery(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the scale battery sensor."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("sensor.lmz_123a45_battery")
|
||||
assert state == snapshot
|
||||
|
||||
entry = entity_registry.async_get(state.entity_id)
|
||||
assert entry
|
||||
assert entry.device_id
|
||||
assert entry == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MICRA],
|
||||
)
|
||||
async def test_other_models_no_scale_battery(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Ensure the other models don't have a battery sensor."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("sensor.lmz_123a45_battery")
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MINI])
|
||||
async def test_battery_on_new_scale_added(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Ensure the battery sensor for a new scale is added automatically."""
|
||||
|
||||
mock_lamarzocco.config.scale = None
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("sensor.lmz_123a45_battery")
|
||||
assert state is None
|
||||
|
||||
mock_lamarzocco.config.scale = LaMarzoccoScale(
|
||||
connected=True, name="Scale-123A45", address="aa:bb:cc:dd:ee:ff", battery=50
|
||||
)
|
||||
|
||||
freezer.tick(timedelta(minutes=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.scale_123a45_battery")
|
||||
assert state
|
||||
|
Loading…
x
Reference in New Issue
Block a user