Skip to content

Commit

Permalink
Add update platform to La Marzocco (#108235)
Browse files Browse the repository at this point in the history
* add update

* requested changes

* improve

* docstring

* docstring
  • Loading branch information
zweckj authored Jan 17, 2024
1 parent 3eb1283 commit ee44e9d
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 1 deletion.
1 change: 1 addition & 0 deletions homeassistant/components/lamarzocco/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.UPDATE,
]


Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/lamarzocco/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@
"steam_boiler": {
"name": "Steam boiler"
}
},
"update": {
"machine_firmware": {
"name": "Machine firmware"
},
"gateway_firmware": {
"name": "Gateway firmware"
}
}
}
}
105 changes: 105 additions & 0 deletions homeassistant/components/lamarzocco/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""Support for La Marzocco update entities."""

from collections.abc import Callable
from dataclasses import dataclass
from typing import Any

from lmcloud import LMCloud as LaMarzoccoClient
from lmcloud.const import LaMarzoccoUpdateableComponent

from homeassistant.components.update import (
UpdateDeviceClass,
UpdateEntity,
UpdateEntityDescription,
UpdateEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription


@dataclass(frozen=True, kw_only=True)
class LaMarzoccoUpdateEntityDescription(
LaMarzoccoEntityDescription,
UpdateEntityDescription,
):
"""Description of a La Marzocco update entities."""

current_fw_fn: Callable[[LaMarzoccoClient], str]
latest_fw_fn: Callable[[LaMarzoccoClient], str]
component: LaMarzoccoUpdateableComponent


ENTITIES: tuple[LaMarzoccoUpdateEntityDescription, ...] = (
LaMarzoccoUpdateEntityDescription(
key="machine_firmware",
translation_key="machine_firmware",
device_class=UpdateDeviceClass.FIRMWARE,
icon="mdi:cloud-download",
current_fw_fn=lambda lm: lm.firmware_version,
latest_fw_fn=lambda lm: lm.latest_firmware_version,
component=LaMarzoccoUpdateableComponent.MACHINE,
entity_category=EntityCategory.DIAGNOSTIC,
),
LaMarzoccoUpdateEntityDescription(
key="gateway_firmware",
translation_key="gateway_firmware",
device_class=UpdateDeviceClass.FIRMWARE,
icon="mdi:cloud-download",
current_fw_fn=lambda lm: lm.gateway_version,
latest_fw_fn=lambda lm: lm.latest_gateway_version,
component=LaMarzoccoUpdateableComponent.GATEWAY,
entity_category=EntityCategory.DIAGNOSTIC,
),
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Create update entities."""

coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
LaMarzoccoUpdateEntity(coordinator, description)
for description in ENTITIES
if description.supported_fn(coordinator)
)


class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
"""Entity representing the update state."""

entity_description: LaMarzoccoUpdateEntityDescription
_attr_supported_features = UpdateEntityFeature.INSTALL

@property
def installed_version(self) -> str | None:
"""Return the current firmware version."""
return self.entity_description.current_fw_fn(self.coordinator.lm)

@property
def latest_version(self) -> str:
"""Return the latest firmware version."""
return self.entity_description.latest_fw_fn(self.coordinator.lm)

async def async_install(
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update."""
self._attr_in_progress = True
self.async_write_ha_state()
success = await self.coordinator.lm.update_firmware(
self.entity_description.component
)
if not success:
raise HomeAssistantError("Update failed")
self._attr_in_progress = False
self.async_write_ha_state()
3 changes: 2 additions & 1 deletion tests/components/lamarzocco/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ def mock_lamarzocco(
lamarzocco.serial_number = serial_number

lamarzocco.firmware_version = "1.1"
lamarzocco.latest_firmware_version = "1.1"
lamarzocco.latest_firmware_version = "1.2"
lamarzocco.gateway_version = "v2.2-rc0"
lamarzocco.latest_gateway_version = "v3.1-rc4"
lamarzocco.update_firmware.return_value = True

lamarzocco.current_status = load_json_object_fixture(
"current_status.json", DOMAIN
Expand Down
111 changes: 111 additions & 0 deletions tests/components/lamarzocco/snapshots/test_update.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# serializer version: 1
# name: test_update_entites[gateway_firmware-gateway]
StateSnapshot({
'attributes': ReadOnlyDict({
'auto_update': False,
'device_class': 'firmware',
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
'friendly_name': 'GS01234 Gateway firmware',
'icon': 'mdi:cloud-download',
'in_progress': False,
'installed_version': 'v2.2-rc0',
'latest_version': 'v3.1-rc4',
'release_summary': None,
'release_url': None,
'skipped_version': None,
'supported_features': <UpdateEntityFeature: 1>,
'title': None,
}),
'context': <ANY>,
'entity_id': 'update.gs01234_gateway_firmware',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_update_entites[gateway_firmware-gateway].1
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'update',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'update.gs01234_gateway_firmware',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <UpdateDeviceClass.FIRMWARE: 'firmware'>,
'original_icon': 'mdi:cloud-download',
'original_name': 'Gateway firmware',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': <UpdateEntityFeature: 1>,
'translation_key': 'gateway_firmware',
'unique_id': 'GS01234_gateway_firmware',
'unit_of_measurement': None,
})
# ---
# name: test_update_entites[machine_firmware-machine]
StateSnapshot({
'attributes': ReadOnlyDict({
'auto_update': False,
'device_class': 'firmware',
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
'friendly_name': 'GS01234 Machine firmware',
'icon': 'mdi:cloud-download',
'in_progress': False,
'installed_version': '1.1',
'latest_version': '1.2',
'release_summary': None,
'release_url': None,
'skipped_version': None,
'supported_features': <UpdateEntityFeature: 1>,
'title': None,
}),
'context': <ANY>,
'entity_id': 'update.gs01234_machine_firmware',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_update_entites[machine_firmware-machine].1
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'update',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'update.gs01234_machine_firmware',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <UpdateDeviceClass.FIRMWARE: 'firmware'>,
'original_icon': 'mdi:cloud-download',
'original_name': 'Machine firmware',
'platform': 'lamarzocco',
'previous_unique_id': None,
'supported_features': <UpdateEntityFeature: 1>,
'translation_key': 'machine_firmware',
'unique_id': 'GS01234_machine_firmware',
'unit_of_measurement': None,
})
# ---
76 changes: 76 additions & 0 deletions tests/components/lamarzocco/test_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Tests for the La Marzocco Update Entities."""


from unittest.mock import MagicMock

from lmcloud.const import LaMarzoccoUpdateableComponent
import pytest
from syrupy import SnapshotAssertion

from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er

pytestmark = pytest.mark.usefixtures("init_integration")


@pytest.mark.parametrize(
("entity_name", "component"),
[
("machine_firmware", LaMarzoccoUpdateableComponent.MACHINE),
("gateway_firmware", LaMarzoccoUpdateableComponent.GATEWAY),
],
)
async def test_update_entites(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
entity_name: str,
component: LaMarzoccoUpdateableComponent,
) -> None:
"""Test the La Marzocco update entities."""

serial_number = mock_lamarzocco.serial_number

state = hass.states.get(f"update.{serial_number}_{entity_name}")
assert state
assert state == snapshot

entry = entity_registry.async_get(state.entity_id)
assert entry
assert entry == snapshot

await hass.services.async_call(
UPDATE_DOMAIN,
SERVICE_INSTALL,
{
ATTR_ENTITY_ID: f"update.{serial_number}_{entity_name}",
},
blocking=True,
)

mock_lamarzocco.update_firmware.assert_called_once_with(component)


async def test_update_error(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
) -> None:
"""Test error during update."""
state = hass.states.get(f"update.{mock_lamarzocco.serial_number}_machine_firmware")
assert state

mock_lamarzocco.update_firmware.return_value = False

with pytest.raises(HomeAssistantError, match="Update failed"):
await hass.services.async_call(
UPDATE_DOMAIN,
SERVICE_INSTALL,
{
ATTR_ENTITY_ID: f"update.{mock_lamarzocco.serial_number}_machine_firmware",
},
blocking=True,
)

0 comments on commit ee44e9d

Please sign in to comment.