Add update platform to La Marzocco (#108235)

* add update

* requested changes

* improve

* docstring

* docstring
This commit is contained in:
Josef Zweck 2024-01-17 14:49:08 +01:00 committed by GitHub
parent 3eb1283fa5
commit ee44e9d4d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 303 additions and 1 deletions

View File

@ -13,6 +13,7 @@ PLATFORMS = [
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.UPDATE,
]

View File

@ -104,6 +104,14 @@
"steam_boiler": {
"name": "Steam boiler"
}
},
"update": {
"machine_firmware": {
"name": "Machine firmware"
},
"gateway_firmware": {
"name": "Gateway firmware"
}
}
}
}

View File

@ -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()

View File

@ -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

View File

@ -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,
})
# ---

View File

@ -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,
)