Skip to content

Commit

Permalink
Add scale support to lamarzocco (#133335)
Browse files Browse the repository at this point in the history
  • Loading branch information
zweckj authored Dec 20, 2024
1 parent 3df9927 commit bddd862
Show file tree
Hide file tree
Showing 21 changed files with 1,059 additions and 31 deletions.
47 changes: 42 additions & 5 deletions homeassistant/components/lamarzocco/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -15,7 +16,7 @@
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
Expand All @@ -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, ...] = (
Expand Down Expand Up @@ -57,6 +58,15 @@ class 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,
Expand All @@ -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):
Expand All @@ -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
26 changes: 25 additions & 1 deletion homeassistant/components/lamarzocco/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,8 +15,9 @@

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
Expand Down Expand Up @@ -62,6 +64,7 @@ def __init__(
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."""
Expand All @@ -86,6 +89,8 @@ async def _internal_async_update_data(self) -> 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:
Expand Down Expand Up @@ -118,6 +123,25 @@ async def _internal_async_update_data(self) -> None:
"""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):
Expand Down
24 changes: 24 additions & 0 deletions homeassistant/components/lamarzocco/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -85,3 +86,26 @@ def __init__(
"""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),
)
10 changes: 10 additions & 0 deletions homeassistant/components/lamarzocco/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
"preinfusion_off": {
"default": "mdi:water"
},
"scale_target": {
"default": "mdi:scale-balance"
},
"smart_standby_time": {
"default": "mdi:timer"
},
Expand All @@ -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": {
Expand Down
58 changes: 55 additions & 3 deletions homeassistant/components/lamarzocco/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

from .const import DOMAIN
from .coordinator import LaMarzoccoConfigEntry, LaMarzoccoUpdateCoordinator
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription, LaMarzoccScaleEntity

PARALLEL_UPDATES = 1

Expand All @@ -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]
]
Expand Down Expand Up @@ -203,6 +205,27 @@ class 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,
Expand All @@ -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)


Expand Down Expand Up @@ -281,7 +325,7 @@ def __init__(
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)
Expand All @@ -305,3 +349,11 @@ async def async_set_native_value(self, value: float) -> None:
},
) from exc
self.async_write_ha_state()


class LaMarzoccoScaleTargetNumberEntity(
LaMarzoccoKeyNumberEntity, LaMarzoccScaleEntity
):
"""Entity representing a key number on the scale."""

entity_description: LaMarzoccoKeyNumberEntityDescription
8 changes: 4 additions & 4 deletions homeassistant/components/lamarzocco/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit bddd862

Please sign in to comment.