mirror of
https://github.com/home-assistant/core
synced 2024-10-14 16:36:33 +00:00
Add Update Entity for Linn devices (#95217)
* added update entity for Linn devices * Update homeassistant/components/openhome/update.py Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * use parent methods for version attributes * fixed issue with mocking openhome device * Update homeassistant/components/openhome/update.py Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * update entity name in tests --------- Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
25dc9f5942
commit
aaf2846a53
|
@ -17,7 +17,7 @@ from .const import DOMAIN
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.UPDATE]
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/openhome",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["async_upnp_client", "openhomedevice"],
|
||||
"requirements": ["openhomedevice==2.0.2"],
|
||||
"requirements": ["openhomedevice==2.2.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "urn:av-openhome-org:service:Product:1"
|
||||
|
|
|
@ -126,9 +126,9 @@ class OpenhomeDevice(MediaPlayerEntity):
|
|||
identifiers={
|
||||
(DOMAIN, self._device.uuid()),
|
||||
},
|
||||
manufacturer=self._device.device.manufacturer,
|
||||
model=self._device.device.model_name,
|
||||
name=self._device.device.friendly_name,
|
||||
manufacturer=self._device.manufacturer(),
|
||||
model=self._device.model_name(),
|
||||
name=self._device.friendly_name(),
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
102
homeassistant/components/openhome/update.py
Normal file
102
homeassistant/components/openhome/update.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
"""Update entities for Linn devices."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
from async_upnp_client.client import UpnpError
|
||||
|
||||
from homeassistant.components.update import (
|
||||
UpdateDeviceClass,
|
||||
UpdateEntity,
|
||||
UpdateEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up update entities for Reolink component."""
|
||||
|
||||
_LOGGER.debug("Setting up config entry: %s", config_entry.unique_id)
|
||||
|
||||
device = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
entity = OpenhomeUpdateEntity(device)
|
||||
|
||||
await entity.async_update()
|
||||
|
||||
async_add_entities([entity])
|
||||
|
||||
|
||||
class OpenhomeUpdateEntity(UpdateEntity):
|
||||
"""Update entity for a Linn DS device."""
|
||||
|
||||
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||
_attr_supported_features = UpdateEntityFeature.INSTALL
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize a Linn DS update entity."""
|
||||
self._device = device
|
||||
self._attr_unique_id = f"{device.uuid()}-update"
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return DeviceInfo(
|
||||
identifiers={
|
||||
(DOMAIN, self._device.uuid()),
|
||||
},
|
||||
manufacturer=self._device.manufacturer(),
|
||||
model=self._device.model_name(),
|
||||
name=self._device.friendly_name(),
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update state of entity."""
|
||||
|
||||
software_status = await self._device.software_status()
|
||||
|
||||
if not software_status:
|
||||
self._attr_installed_version = None
|
||||
self._attr_latest_version = None
|
||||
self._attr_release_summary = None
|
||||
self._attr_release_url = None
|
||||
return
|
||||
|
||||
self._attr_installed_version = software_status["current_software"]["version"]
|
||||
|
||||
if software_status["status"] == "update_available":
|
||||
self._attr_latest_version = software_status["update_info"]["updates"][0][
|
||||
"version"
|
||||
]
|
||||
self._attr_release_summary = software_status["update_info"]["updates"][0][
|
||||
"description"
|
||||
]
|
||||
self._attr_release_url = software_status["update_info"]["releasenotesuri"]
|
||||
|
||||
async def async_install(
|
||||
self, version: str | None, backup: bool, **kwargs: Any
|
||||
) -> None:
|
||||
"""Install the latest firmware version."""
|
||||
try:
|
||||
if self.latest_version:
|
||||
await self._device.update_firmware()
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError) as err:
|
||||
raise HomeAssistantError(
|
||||
f"Error updating {self._device.device.friendly_name}: {err}"
|
||||
) from err
|
|
@ -1348,7 +1348,7 @@ openerz-api==0.2.0
|
|||
openevsewifi==1.1.2
|
||||
|
||||
# homeassistant.components.openhome
|
||||
openhomedevice==2.0.2
|
||||
openhomedevice==2.2.0
|
||||
|
||||
# homeassistant.components.opensensemap
|
||||
opensensemap-api==0.2.0
|
||||
|
|
|
@ -1026,7 +1026,7 @@ openai==0.27.2
|
|||
openerz-api==0.2.0
|
||||
|
||||
# homeassistant.components.openhome
|
||||
openhomedevice==2.0.2
|
||||
openhomedevice==2.2.0
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.6
|
||||
|
|
172
tests/components/openhome/test_update.py
Normal file
172
tests/components/openhome/test_update.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
"""Tests for the Openhome update platform."""
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.openhome.const import DOMAIN
|
||||
from homeassistant.components.update import (
|
||||
ATTR_INSTALLED_VERSION,
|
||||
ATTR_LATEST_VERSION,
|
||||
ATTR_RELEASE_SUMMARY,
|
||||
ATTR_RELEASE_URL,
|
||||
DOMAIN as PLATFORM_DOMAIN,
|
||||
SERVICE_INSTALL,
|
||||
UpdateDeviceClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_HOST,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
LATEST_FIRMWARE_INSTALLED = {
|
||||
"status": "on_latest",
|
||||
"current_software": {"version": "4.100.502", "topic": "main", "channel": "release"},
|
||||
}
|
||||
|
||||
FIRMWARE_UPDATE_AVAILABLE = {
|
||||
"status": "update_available",
|
||||
"current_software": {"version": "4.99.491", "topic": "main", "channel": "release"},
|
||||
"update_info": {
|
||||
"legal": {
|
||||
"licenseurl": "http://products.linn.co.uk/VersionInfo/licenseV2.txt",
|
||||
"privacyurl": "https://www.linn.co.uk/privacy",
|
||||
"privacyuri": "https://products.linn.co.uk/VersionInfo/PrivacyV1.json",
|
||||
"privacyversion": 1,
|
||||
},
|
||||
"releasenotesuri": "http://docs.linn.co.uk/wiki/index.php/ReleaseNotes",
|
||||
"updates": [
|
||||
{
|
||||
"channel": "release",
|
||||
"date": "07 Jun 2023 12:29:48",
|
||||
"description": "Release build version 4.100.502 (07 Jun 2023 12:29:48)",
|
||||
"exaktlink": "3",
|
||||
"manifest": "https://cloud.linn.co.uk/update/components/836/4.100.502/manifest.json",
|
||||
"topic": "main",
|
||||
"variant": "836",
|
||||
"version": "4.100.502",
|
||||
}
|
||||
],
|
||||
"exaktUpdates": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def setup_integration(
|
||||
hass: HomeAssistant,
|
||||
software_status: dict,
|
||||
update_firmware: AsyncMock,
|
||||
) -> None:
|
||||
"""Load an openhome platform with mocked device."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: "http://localhost"},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.openhome.PLATFORMS", [Platform.UPDATE]), patch(
|
||||
"homeassistant.components.openhome.Device", MagicMock()
|
||||
) as mock_device:
|
||||
mock_device.return_value.init = AsyncMock()
|
||||
mock_device.return_value.uuid = MagicMock(return_value="uuid")
|
||||
mock_device.return_value.manufacturer = MagicMock(return_value="manufacturer")
|
||||
mock_device.return_value.model_name = MagicMock(return_value="model_name")
|
||||
mock_device.return_value.friendly_name = MagicMock(return_value="friendly_name")
|
||||
mock_device.return_value.software_status = AsyncMock(
|
||||
return_value=software_status
|
||||
)
|
||||
mock_device.return_value.update_firmware = update_firmware
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_not_supported(hass: HomeAssistant):
|
||||
"""Ensure update entity works if service not supported."""
|
||||
|
||||
update_firmware = AsyncMock()
|
||||
await setup_integration(hass, None, update_firmware)
|
||||
|
||||
state = hass.states.get("update.friendly_name")
|
||||
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == UpdateDeviceClass.FIRMWARE
|
||||
assert state.attributes[ATTR_INSTALLED_VERSION] is None
|
||||
assert state.attributes[ATTR_LATEST_VERSION] is None
|
||||
assert state.attributes[ATTR_RELEASE_URL] is None
|
||||
assert state.attributes[ATTR_RELEASE_SUMMARY] is None
|
||||
update_firmware.assert_not_called()
|
||||
|
||||
|
||||
async def test_on_latest_firmware(hass: HomeAssistant):
|
||||
"""Test device on latest firmware."""
|
||||
|
||||
update_firmware = AsyncMock()
|
||||
await setup_integration(hass, LATEST_FIRMWARE_INSTALLED, update_firmware)
|
||||
|
||||
state = hass.states.get("update.friendly_name")
|
||||
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == UpdateDeviceClass.FIRMWARE
|
||||
assert state.attributes[ATTR_INSTALLED_VERSION] == "4.100.502"
|
||||
assert state.attributes[ATTR_LATEST_VERSION] is None
|
||||
assert state.attributes[ATTR_RELEASE_URL] is None
|
||||
assert state.attributes[ATTR_RELEASE_SUMMARY] is None
|
||||
update_firmware.assert_not_called()
|
||||
|
||||
|
||||
async def test_update_available(hass: HomeAssistant):
|
||||
"""Test device has firmware update available."""
|
||||
|
||||
update_firmware = AsyncMock()
|
||||
await setup_integration(hass, FIRMWARE_UPDATE_AVAILABLE, update_firmware)
|
||||
|
||||
state = hass.states.get("update.friendly_name")
|
||||
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == UpdateDeviceClass.FIRMWARE
|
||||
assert state.attributes[ATTR_INSTALLED_VERSION] == "4.99.491"
|
||||
assert state.attributes[ATTR_LATEST_VERSION] == "4.100.502"
|
||||
assert (
|
||||
state.attributes[ATTR_RELEASE_URL]
|
||||
== "http://docs.linn.co.uk/wiki/index.php/ReleaseNotes"
|
||||
)
|
||||
assert (
|
||||
state.attributes[ATTR_RELEASE_SUMMARY]
|
||||
== "Release build version 4.100.502 (07 Jun 2023 12:29:48)"
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
PLATFORM_DOMAIN,
|
||||
SERVICE_INSTALL,
|
||||
{ATTR_ENTITY_ID: "update.friendly_name"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
update_firmware.assert_called_once()
|
||||
|
||||
|
||||
async def test_firmware_update_not_required(hass: HomeAssistant):
|
||||
"""Ensure firmware install does nothing if up to date."""
|
||||
|
||||
update_firmware = AsyncMock()
|
||||
await setup_integration(hass, LATEST_FIRMWARE_INSTALLED, update_firmware)
|
||||
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
PLATFORM_DOMAIN,
|
||||
SERVICE_INSTALL,
|
||||
{ATTR_ENTITY_ID: "update.friendly_name"},
|
||||
blocking=True,
|
||||
)
|
||||
update_firmware.assert_not_called()
|
Loading…
Reference in a new issue