mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Add more features to lamarzocco updates (#143157)
This commit is contained in:
parent
930fa18224
commit
09131d8647
@ -1,9 +1,10 @@
|
|||||||
"""Support for La Marzocco update entities."""
|
"""Support for La Marzocco update entities."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pylamarzocco.const import FirmwareType
|
from pylamarzocco.const import FirmwareType, UpdateCommandStatus
|
||||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||||
|
|
||||||
from homeassistant.components.update import (
|
from homeassistant.components.update import (
|
||||||
@ -22,6 +23,7 @@ from .coordinator import LaMarzoccoConfigEntry
|
|||||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
MAX_UPDATE_WAIT = 150
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -71,7 +73,11 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
|
|||||||
"""Entity representing the update state."""
|
"""Entity representing the update state."""
|
||||||
|
|
||||||
entity_description: LaMarzoccoUpdateEntityDescription
|
entity_description: LaMarzoccoUpdateEntityDescription
|
||||||
_attr_supported_features = UpdateEntityFeature.INSTALL
|
_attr_supported_features = (
|
||||||
|
UpdateEntityFeature.INSTALL
|
||||||
|
| UpdateEntityFeature.PROGRESS
|
||||||
|
| UpdateEntityFeature.RELEASE_NOTES
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def installed_version(self) -> str:
|
def installed_version(self) -> str:
|
||||||
@ -94,15 +100,40 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
|
|||||||
"""Return the release notes URL."""
|
"""Return the release notes URL."""
|
||||||
return "https://support-iot.lamarzocco.com/firmware-updates/"
|
return "https://support-iot.lamarzocco.com/firmware-updates/"
|
||||||
|
|
||||||
|
def release_notes(self) -> str | None:
|
||||||
|
"""Return the release notes for the latest firmware version."""
|
||||||
|
if available_update := self.coordinator.device.settings.firmwares[
|
||||||
|
self.entity_description.component
|
||||||
|
].available_update:
|
||||||
|
return available_update.change_log
|
||||||
|
return None
|
||||||
|
|
||||||
async def async_install(
|
async def async_install(
|
||||||
self, version: str | None, backup: bool, **kwargs: Any
|
self, version: str | None, backup: bool, **kwargs: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Install an update."""
|
"""Install an update."""
|
||||||
|
|
||||||
self._attr_in_progress = True
|
self._attr_in_progress = True
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
def _raise_timeout_error() -> None: # to avoid TRY301
|
||||||
|
raise TimeoutError("Update timed out")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.coordinator.device.update_firmware()
|
await self.coordinator.device.update_firmware()
|
||||||
except RequestNotSuccessful as exc:
|
while (
|
||||||
|
update_progress := await self.coordinator.device.get_firmware()
|
||||||
|
).command_status is UpdateCommandStatus.IN_PROGRESS:
|
||||||
|
if counter >= MAX_UPDATE_WAIT:
|
||||||
|
_raise_timeout_error()
|
||||||
|
self._attr_update_percentage = update_progress.progress_percentage
|
||||||
|
self.async_write_ha_state()
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
except (TimeoutError, RequestNotSuccessful) as exc:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key="update_failed",
|
translation_key="update_failed",
|
||||||
@ -110,5 +141,6 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
|
|||||||
"key": self.entity_description.key,
|
"key": self.entity_description.key,
|
||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
self._attr_in_progress = False
|
finally:
|
||||||
await self.coordinator.async_request_refresh()
|
self._attr_in_progress = False
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
'original_name': 'Gateway firmware',
|
'original_name': 'Gateway firmware',
|
||||||
'platform': 'lamarzocco',
|
'platform': 'lamarzocco',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <UpdateEntityFeature: 1>,
|
'supported_features': <UpdateEntityFeature: 21>,
|
||||||
'translation_key': 'gateway_firmware',
|
'translation_key': 'gateway_firmware',
|
||||||
'unique_id': 'GS012345_gateway_firmware',
|
'unique_id': 'GS012345_gateway_firmware',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
@ -47,7 +47,7 @@
|
|||||||
'release_summary': None,
|
'release_summary': None,
|
||||||
'release_url': 'https://support-iot.lamarzocco.com/firmware-updates/',
|
'release_url': 'https://support-iot.lamarzocco.com/firmware-updates/',
|
||||||
'skipped_version': None,
|
'skipped_version': None,
|
||||||
'supported_features': <UpdateEntityFeature: 1>,
|
'supported_features': <UpdateEntityFeature: 21>,
|
||||||
'title': None,
|
'title': None,
|
||||||
'update_percentage': None,
|
'update_percentage': None,
|
||||||
}),
|
}),
|
||||||
@ -87,7 +87,7 @@
|
|||||||
'original_name': 'Machine firmware',
|
'original_name': 'Machine firmware',
|
||||||
'platform': 'lamarzocco',
|
'platform': 'lamarzocco',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <UpdateEntityFeature: 1>,
|
'supported_features': <UpdateEntityFeature: 21>,
|
||||||
'translation_key': 'machine_firmware',
|
'translation_key': 'machine_firmware',
|
||||||
'unique_id': 'GS012345_machine_firmware',
|
'unique_id': 'GS012345_machine_firmware',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
@ -107,7 +107,7 @@
|
|||||||
'release_summary': None,
|
'release_summary': None,
|
||||||
'release_url': 'https://support-iot.lamarzocco.com/firmware-updates/',
|
'release_url': 'https://support-iot.lamarzocco.com/firmware-updates/',
|
||||||
'skipped_version': None,
|
'skipped_version': None,
|
||||||
'supported_features': <UpdateEntityFeature: 1>,
|
'supported_features': <UpdateEntityFeature: 21>,
|
||||||
'title': None,
|
'title': None,
|
||||||
'update_percentage': None,
|
'update_percentage': None,
|
||||||
}),
|
}),
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
"""Tests for the La Marzocco Update Entities."""
|
"""Tests for the La Marzocco Update Entities."""
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
from collections.abc import Generator
|
||||||
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
from pylamarzocco.const import (
|
||||||
|
FirmwareType,
|
||||||
|
UpdateCommandStatus,
|
||||||
|
UpdateProgressInfo,
|
||||||
|
UpdateStatus,
|
||||||
|
)
|
||||||
from pylamarzocco.exceptions import RequestNotSuccessful
|
from pylamarzocco.exceptions import RequestNotSuccessful
|
||||||
|
from pylamarzocco.models import UpdateDetails
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
@ -15,6 +23,17 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
from . import async_init_integration
|
from . import async_init_integration
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, snapshot_platform
|
from tests.common import MockConfigEntry, snapshot_platform
|
||||||
|
from tests.typing import WebSocketGenerator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_sleep() -> Generator[AsyncMock]:
|
||||||
|
"""Mock asyncio.sleep."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.lamarzocco.update.asyncio.sleep",
|
||||||
|
return_value=AsyncMock(),
|
||||||
|
) as mock_sleep:
|
||||||
|
yield mock_sleep
|
||||||
|
|
||||||
|
|
||||||
async def test_update(
|
async def test_update(
|
||||||
@ -29,17 +48,51 @@ async def test_update(
|
|||||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
async def test_update_entites(
|
async def test_update_process(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco update entities."""
|
"""Test the La Marzocco update entities."""
|
||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
|
mock_lamarzocco.get_firmware.side_effect = [
|
||||||
|
UpdateDetails(
|
||||||
|
status=UpdateStatus.TO_UPDATE,
|
||||||
|
command_status=UpdateCommandStatus.IN_PROGRESS,
|
||||||
|
progress_info=UpdateProgressInfo.STARTING_PROCESS,
|
||||||
|
progress_percentage=0,
|
||||||
|
),
|
||||||
|
UpdateDetails(
|
||||||
|
status=UpdateStatus.UPDATED,
|
||||||
|
command_status=None,
|
||||||
|
progress_info=None,
|
||||||
|
progress_percentage=None,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
await async_init_integration(hass, mock_config_entry)
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "update/release_notes",
|
||||||
|
"entity_id": f"update.{serial_number}_gateway_firmware",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = await client.receive_json()
|
||||||
|
assert (
|
||||||
|
mock_lamarzocco.settings.firmwares[
|
||||||
|
FirmwareType.GATEWAY
|
||||||
|
].available_update.change_log
|
||||||
|
in result["result"]
|
||||||
|
)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
UPDATE_DOMAIN,
|
UPDATE_DOMAIN,
|
||||||
SERVICE_INSTALL,
|
SERVICE_INSTALL,
|
||||||
@ -76,3 +129,35 @@ async def test_update_error(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
assert exc_info.value.translation_key == "update_failed"
|
assert exc_info.value.translation_key == "update_failed"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_times_out(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test error during update."""
|
||||||
|
mock_lamarzocco.get_firmware.return_value = UpdateDetails(
|
||||||
|
status=UpdateStatus.TO_UPDATE,
|
||||||
|
command_status=UpdateCommandStatus.IN_PROGRESS,
|
||||||
|
progress_info=UpdateProgressInfo.STARTING_PROCESS,
|
||||||
|
progress_percentage=0,
|
||||||
|
)
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
state = hass.states.get(f"update.{mock_lamarzocco.serial_number}_gateway_firmware")
|
||||||
|
assert state
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("homeassistant.components.lamarzocco.update.MAX_UPDATE_WAIT", 0),
|
||||||
|
pytest.raises(HomeAssistantError) as exc_info,
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
UPDATE_DOMAIN,
|
||||||
|
SERVICE_INSTALL,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: f"update.{mock_lamarzocco.serial_number}_gateway_firmware",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert exc_info.value.translation_key == "update_failed"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user