Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scale support to lamarzocco #133335

Merged
merged 8 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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