mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 06:37:52 +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 collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from pylamarzocco.const import MachineModel
|
||||||
from pylamarzocco.models import LaMarzoccoMachineConfig
|
from pylamarzocco.models import LaMarzoccoMachineConfig
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
@ -15,7 +16,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import LaMarzoccoConfigEntry
|
from .coordinator import LaMarzoccoConfigEntry
|
||||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||||
|
|
||||||
# Coordinator is used to centralize the data updates
|
# Coordinator is used to centralize the data updates
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
@ -28,7 +29,7 @@ class LaMarzoccoBinarySensorEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco binary sensor."""
|
"""Description of a La Marzocco binary sensor."""
|
||||||
|
|
||||||
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool]
|
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool | None]
|
||||||
|
|
||||||
|
|
||||||
ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -66,12 +76,31 @@ async def async_setup_entry(
|
|||||||
"""Set up binary sensor entities."""
|
"""Set up binary sensor entities."""
|
||||||
coordinator = entry.runtime_data.config_coordinator
|
coordinator = entry.runtime_data.config_coordinator
|
||||||
|
|
||||||
async_add_entities(
|
entities = [
|
||||||
LaMarzoccoBinarySensorEntity(coordinator, description)
|
LaMarzoccoBinarySensorEntity(coordinator, description)
|
||||||
for description in ENTITIES
|
for description in ENTITIES
|
||||||
if description.supported_fn(coordinator)
|
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):
|
class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity):
|
||||||
"""Binary Sensor representing espresso machine water reservoir status."""
|
"""Binary Sensor representing espresso machine water reservoir status."""
|
||||||
@ -79,6 +108,14 @@ class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity):
|
|||||||
entity_description: LaMarzoccoBinarySensorEntityDescription
|
entity_description: LaMarzoccoBinarySensorEntityDescription
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
return self.entity_description.is_on_fn(self.coordinator.device.config)
|
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 __future__ import annotations
|
||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
@ -14,8 +15,9 @@ from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
|
|||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
import homeassistant.helpers.device_registry as dr
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -62,6 +64,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
|||||||
self.device = device
|
self.device = device
|
||||||
self.local_connection_configured = local_client is not None
|
self.local_connection_configured = local_client is not None
|
||||||
self._local_client = local_client
|
self._local_client = local_client
|
||||||
|
self.new_device_callback: list[Callable] = []
|
||||||
|
|
||||||
async def _async_update_data(self) -> None:
|
async def _async_update_data(self) -> None:
|
||||||
"""Do the data update."""
|
"""Do the data update."""
|
||||||
@ -86,6 +89,8 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
|||||||
class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
|
class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
|
||||||
"""Class to handle fetching data from the La Marzocco API centrally."""
|
"""Class to handle fetching data from the La Marzocco API centrally."""
|
||||||
|
|
||||||
|
_scale_address: str | None = None
|
||||||
|
|
||||||
async def _async_setup(self) -> None:
|
async def _async_setup(self) -> None:
|
||||||
"""Set up the coordinator."""
|
"""Set up the coordinator."""
|
||||||
if self._local_client is not None:
|
if self._local_client is not None:
|
||||||
@ -118,6 +123,25 @@ class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
|
|||||||
"""Fetch data from API endpoint."""
|
"""Fetch data from API endpoint."""
|
||||||
await self.device.get_config()
|
await self.device.get_config()
|
||||||
_LOGGER.debug("Current status: %s", str(self.device.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):
|
class LaMarzoccoFirmwareUpdateCoordinator(LaMarzoccoUpdateCoordinator):
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from pylamarzocco.const import FirmwareType
|
from pylamarzocco.const import FirmwareType
|
||||||
from pylamarzocco.devices.machine import LaMarzoccoMachine
|
from pylamarzocco.devices.machine import LaMarzoccoMachine
|
||||||
@ -85,3 +86,26 @@ class LaMarzoccoEntity(LaMarzoccoBaseEntity):
|
|||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(coordinator, entity_description.key)
|
super().__init__(coordinator, entity_description.key)
|
||||||
self.entity_description = entity_description
|
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": {
|
"preinfusion_off": {
|
||||||
"default": "mdi:water"
|
"default": "mdi:water"
|
||||||
},
|
},
|
||||||
|
"scale_target": {
|
||||||
|
"default": "mdi:scale-balance"
|
||||||
|
},
|
||||||
"smart_standby_time": {
|
"smart_standby_time": {
|
||||||
"default": "mdi:timer"
|
"default": "mdi:timer"
|
||||||
},
|
},
|
||||||
@ -54,6 +57,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
|
"active_bbw": {
|
||||||
|
"default": "mdi:alpha-u",
|
||||||
|
"state": {
|
||||||
|
"a": "mdi:alpha-a",
|
||||||
|
"b": "mdi:alpha-b"
|
||||||
|
}
|
||||||
|
},
|
||||||
"smart_standby_mode": {
|
"smart_standby_mode": {
|
||||||
"default": "mdi:power",
|
"default": "mdi:power",
|
||||||
"state": {
|
"state": {
|
||||||
|
@ -33,7 +33,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import LaMarzoccoConfigEntry, LaMarzoccoUpdateCoordinator
|
from .coordinator import LaMarzoccoConfigEntry, LaMarzoccoUpdateCoordinator
|
||||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
@ -56,7 +56,9 @@ class LaMarzoccoKeyNumberEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of an La Marzocco number entity with keys."""
|
"""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[
|
set_value_fn: Callable[
|
||||||
[LaMarzoccoMachine, float | int, PhysicalKey], Coroutine[Any, Any, bool]
|
[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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -224,6 +247,27 @@ async def async_setup_entry(
|
|||||||
LaMarzoccoKeyNumberEntity(coordinator, description, key)
|
LaMarzoccoKeyNumberEntity(coordinator, description, key)
|
||||||
for key in range(min(num_keys, 1), num_keys + 1)
|
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)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@ -281,7 +325,7 @@ class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
|||||||
self.pyhsical_key = pyhsical_key
|
self.pyhsical_key = pyhsical_key
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float:
|
def native_value(self) -> float | None:
|
||||||
"""Return the current value."""
|
"""Return the current value."""
|
||||||
return self.entity_description.native_value_fn(
|
return self.entity_description.native_value_fn(
|
||||||
self.coordinator.device.config, PhysicalKey(self.pyhsical_key)
|
self.coordinator.device.config, PhysicalKey(self.pyhsical_key)
|
||||||
@ -305,3 +349,11 @@ class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
|||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
self.async_write_ha_state()
|
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-troubleshooting: done
|
||||||
docs-use-cases: done
|
docs-use-cases: done
|
||||||
dynamic-devices:
|
dynamic-devices:
|
||||||
status: exempt
|
status: done
|
||||||
comment: |
|
comment: |
|
||||||
Device type integration.
|
Device type integration, only possible for addon scale
|
||||||
entity-category: done
|
entity-category: done
|
||||||
entity-device-class: done
|
entity-device-class: done
|
||||||
entity-disabled-by-default: done
|
entity-disabled-by-default: done
|
||||||
@ -74,9 +74,9 @@ rules:
|
|||||||
reconfiguration-flow: done
|
reconfiguration-flow: done
|
||||||
repair-issues: done
|
repair-issues: done
|
||||||
stale-devices:
|
stale-devices:
|
||||||
status: exempt
|
status: done
|
||||||
comment: |
|
comment: |
|
||||||
Device type integration.
|
Device type integration, only possible for addon scale
|
||||||
|
|
||||||
# Platinum
|
# Platinum
|
||||||
async-dependency: done
|
async-dependency: done
|
||||||
|
@ -4,7 +4,13 @@ from collections.abc import Callable, Coroutine
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
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.devices.machine import LaMarzoccoMachine
|
||||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||||
from pylamarzocco.models import LaMarzoccoMachineConfig
|
from pylamarzocco.models import LaMarzoccoMachineConfig
|
||||||
@ -17,7 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import LaMarzoccoConfigEntry
|
from .coordinator import LaMarzoccoConfigEntry
|
||||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
@ -52,7 +58,7 @@ class LaMarzoccoSelectEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco select entity."""
|
"""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]]
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -109,12 +131,31 @@ async def async_setup_entry(
|
|||||||
"""Set up select entities."""
|
"""Set up select entities."""
|
||||||
coordinator = entry.runtime_data.config_coordinator
|
coordinator = entry.runtime_data.config_coordinator
|
||||||
|
|
||||||
async_add_entities(
|
entities = [
|
||||||
LaMarzoccoSelectEntity(coordinator, description)
|
LaMarzoccoSelectEntity(coordinator, description)
|
||||||
for description in ENTITIES
|
for description in ENTITIES
|
||||||
if description.supported_fn(coordinator)
|
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):
|
class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
||||||
"""La Marzocco select entity."""
|
"""La Marzocco select entity."""
|
||||||
@ -122,7 +163,7 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
|||||||
entity_description: LaMarzoccoSelectEntityDescription
|
entity_description: LaMarzoccoSelectEntityDescription
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_option(self) -> str:
|
def current_option(self) -> str | None:
|
||||||
"""Return the current selected option."""
|
"""Return the current selected option."""
|
||||||
return str(
|
return str(
|
||||||
self.entity_description.current_option_fn(self.coordinator.device.config)
|
self.entity_description.current_option_fn(self.coordinator.device.config)
|
||||||
@ -145,3 +186,9 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
|||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
self.async_write_ha_state()
|
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,
|
SensorEntityDescription,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.const import EntityCategory, UnitOfTemperature, UnitOfTime
|
from homeassistant.const import (
|
||||||
|
PERCENTAGE,
|
||||||
|
EntityCategory,
|
||||||
|
UnitOfTemperature,
|
||||||
|
UnitOfTime,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import LaMarzoccoConfigEntry
|
from .coordinator import LaMarzoccoConfigEntry
|
||||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity
|
||||||
|
|
||||||
# Coordinator is used to centralize the data updates
|
# Coordinator is used to centralize the data updates
|
||||||
PARALLEL_UPDATES = 0
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -106,6 +126,15 @@ async def async_setup_entry(
|
|||||||
if description.supported_fn(config_coordinator)
|
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
|
statistics_coordinator = entry.runtime_data.statistics_coordinator
|
||||||
entities.extend(
|
entities.extend(
|
||||||
LaMarzoccoSensorEntity(statistics_coordinator, description)
|
LaMarzoccoSensorEntity(statistics_coordinator, description)
|
||||||
@ -113,6 +142,14 @@ async def async_setup_entry(
|
|||||||
if description.supported_fn(statistics_coordinator)
|
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)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@ -125,3 +162,9 @@ class LaMarzoccoSensorEntity(LaMarzoccoEntity, SensorEntity):
|
|||||||
def native_value(self) -> int | float:
|
def native_value(self) -> int | float:
|
||||||
"""State of the sensor."""
|
"""State of the sensor."""
|
||||||
return self.entity_description.value_fn(self.coordinator.device)
|
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": {
|
"preinfusion_off_key": {
|
||||||
"name": "Preinfusion time Key {key}"
|
"name": "Preinfusion time Key {key}"
|
||||||
},
|
},
|
||||||
|
"scale_target_key": {
|
||||||
|
"name": "Brew by weight target {key}"
|
||||||
|
},
|
||||||
"smart_standby_time": {
|
"smart_standby_time": {
|
||||||
"name": "Smart standby time"
|
"name": "Smart standby time"
|
||||||
},
|
},
|
||||||
@ -133,6 +136,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
|
"active_bbw": {
|
||||||
|
"name": "Active brew by weight recipe",
|
||||||
|
"state": {
|
||||||
|
"a": "Recipe A",
|
||||||
|
"b": "Recipe B"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prebrew_infusion_select": {
|
"prebrew_infusion_select": {
|
||||||
"name": "Prebrew/-infusion mode",
|
"name": "Prebrew/-infusion mode",
|
||||||
"state": {
|
"state": {
|
||||||
|
@ -135,6 +135,9 @@ def mock_lamarzocco(device_fixture: MachineModel) -> Generator[MagicMock]:
|
|||||||
serial_number=serial_number,
|
serial_number=serial_number,
|
||||||
name=serial_number,
|
name=serial_number,
|
||||||
)
|
)
|
||||||
|
if device_fixture == MachineModel.LINEA_MINI:
|
||||||
|
config = load_json_object_fixture("config_mini.json", DOMAIN)
|
||||||
|
else:
|
||||||
config = load_json_object_fixture("config.json", DOMAIN)
|
config = load_json_object_fixture("config.json", DOMAIN)
|
||||||
statistics = json.loads(load_fixture("statistics.json", DOMAIN))
|
statistics = json.loads(load_fixture("statistics.json", DOMAIN))
|
||||||
|
|
||||||
|
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,
|
'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,
|
'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_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <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
|
# 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_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <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
|
# 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_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <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
|
# 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'>,
|
'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
|
# 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]
|
# name: test_pre_brew_infusion_select[GS3 AV]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
|
@ -1,4 +1,55 @@
|
|||||||
# serializer version: 1
|
# 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]
|
# name: test_sensors[GS012345_current_coffee_temperature-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -4,7 +4,10 @@ from datetime import timedelta
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from pylamarzocco.const import MachineModel
|
||||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||||
|
from pylamarzocco.models import LaMarzoccoScale
|
||||||
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.const import STATE_UNAVAILABLE
|
from homeassistant.const import STATE_UNAVAILABLE
|
||||||
@ -98,3 +101,68 @@ async def test_sensor_going_unavailable(
|
|||||||
state = hass.states.get(brewing_active_sensor)
|
state = hass.states.get(brewing_active_sensor)
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_UNAVAILABLE
|
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."""
|
"""Test initialization of lamarzocco."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
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
|
from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
@ -27,7 +29,7 @@ from homeassistant.helpers import (
|
|||||||
|
|
||||||
from . import USER_INPUT, async_init_integration, get_bluetooth_service_info
|
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(
|
async def test_load_unload_config_entry(
|
||||||
@ -251,3 +253,49 @@ async def test_device(
|
|||||||
device = device_registry.async_get(entry.device_id)
|
device = device_registry.async_get(entry.device_id)
|
||||||
assert device
|
assert device
|
||||||
assert device == snapshot
|
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."""
|
"""Tests for the La Marzocco number entities."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
from pylamarzocco.const import (
|
from pylamarzocco.const import (
|
||||||
KEYS_PER_MODEL,
|
KEYS_PER_MODEL,
|
||||||
BoilerType,
|
BoilerType,
|
||||||
@ -11,6 +13,7 @@ from pylamarzocco.const import (
|
|||||||
PrebrewMode,
|
PrebrewMode,
|
||||||
)
|
)
|
||||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||||
|
from pylamarzocco.models import LaMarzoccoScale
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
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 . import async_init_integration
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -444,3 +447,91 @@ async def test_number_error(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
assert exc_info.value.translation_key == "number_exception_key"
|
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."""
|
"""Tests for the La Marzocco select entities."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import MagicMock
|
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.exceptions import RequestNotSuccessful
|
||||||
|
from pylamarzocco.models import LaMarzoccoScale
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
@ -17,9 +26,12 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er
|
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])
|
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MICRA])
|
||||||
async def test_steam_boiler_level(
|
async def test_steam_boiler_level(
|
||||||
hass: HomeAssistant,
|
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)
|
mock_lamarzocco.set_steam_level.assert_called_once_with(level=SteamLevel.LEVEL_2)
|
||||||
|
|
||||||
|
|
||||||
|
pytest.mark.usefixtures("init_integration")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture",
|
"device_fixture",
|
||||||
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MINI],
|
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MINI],
|
||||||
@ -69,6 +84,7 @@ async def test_steam_boiler_level_none(
|
|||||||
assert state is None
|
assert state is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_integration")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture",
|
"device_fixture",
|
||||||
[MachineModel.LINEA_MICRA, MachineModel.GS3_AV, MachineModel.LINEA_MINI],
|
[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)
|
mock_lamarzocco.set_prebrew_mode.assert_called_once_with(mode=PrebrewMode.PREBREW)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_integration")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture",
|
"device_fixture",
|
||||||
[MachineModel.GS3_MP],
|
[MachineModel.GS3_MP],
|
||||||
@ -121,6 +138,7 @@ async def test_pre_brew_infusion_select_none(
|
|||||||
assert state is None
|
assert state is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_integration")
|
||||||
async def test_smart_standby_mode(
|
async def test_smart_standby_mode(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
@ -155,6 +173,7 @@ async def test_smart_standby_mode(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_integration")
|
||||||
async def test_select_errors(
|
async def test_select_errors(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
@ -179,3 +198,77 @@ async def test_select_errors(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
assert exc_info.value.translation_key == "select_option_error"
|
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."""
|
"""Tests for La Marzocco sensors."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
from pylamarzocco.const import MachineModel
|
from pylamarzocco.const import MachineModel
|
||||||
|
from pylamarzocco.models import LaMarzoccoScale
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
@ -12,7 +15,7 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
|
|
||||||
from . import async_init_integration
|
from . import async_init_integration
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
SENSORS = (
|
SENSORS = (
|
||||||
"total_coffees_made",
|
"total_coffees_made",
|
||||||
@ -85,3 +88,67 @@ async def test_no_steam_linea_mini(
|
|||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
state = hass.states.get(f"sensor.{serial_number}_current_temp_steam")
|
state = hass.states.get(f"sensor.{serial_number}_current_temp_steam")
|
||||||
assert state is None
|
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