From dbe193aaa43b23c0fbd74b7ef715dc795c01902c Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 15 Nov 2023 13:36:20 +0100 Subject: [PATCH] Add `release_url` property of Shelly update entities (#103739) --- homeassistant/components/shelly/const.py | 9 ++++++++ homeassistant/components/shelly/update.py | 26 ++++++++++++++++++++--- homeassistant/components/shelly/utils.py | 11 ++++++++++ tests/components/shelly/conftest.py | 1 + tests/components/shelly/test_update.py | 12 ++++++++++- tests/components/shelly/test_utils.py | 22 +++++++++++++++++++ 6 files changed, 77 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 0275b805208..02dc347e495 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -186,3 +186,12 @@ OTA_BEGIN = "ota_begin" OTA_ERROR = "ota_error" OTA_PROGRESS = "ota_progress" OTA_SUCCESS = "ota_success" + +GEN1_RELEASE_URL = "https://shelly-api-docs.shelly.cloud/gen1/#changelog" +GEN2_RELEASE_URL = "https://shelly-api-docs.shelly.cloud/gen2/changelog/" +DEVICES_WITHOUT_FIRMWARE_CHANGELOG = ( + "SAWD-0A1XX10EU1", + "SHMOS-01", + "SHMOS-02", + "SHTRV-01", +) diff --git a/homeassistant/components/shelly/update.py b/homeassistant/components/shelly/update.py index d4528f55288..9e52a292108 100644 --- a/homeassistant/components/shelly/update.py +++ b/homeassistant/components/shelly/update.py @@ -34,7 +34,7 @@ from .entity import ( async_setup_entry_rest, async_setup_entry_rpc, ) -from .utils import get_device_entry_gen +from .utils import get_device_entry_gen, get_release_url LOGGER = logging.getLogger(__name__) @@ -156,10 +156,15 @@ class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity): self, block_coordinator: ShellyBlockCoordinator, attribute: str, - description: RestEntityDescription, + description: RestUpdateDescription, ) -> None: """Initialize update entity.""" super().__init__(block_coordinator, attribute, description) + self._attr_release_url = get_release_url( + block_coordinator.device.gen, + block_coordinator.model, + description.beta, + ) self._in_progress_old_version: str | None = None @property @@ -225,11 +230,14 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): coordinator: ShellyRpcCoordinator, key: str, attribute: str, - description: RpcEntityDescription, + description: RpcUpdateDescription, ) -> None: """Initialize update entity.""" super().__init__(coordinator, key, attribute, description) self._ota_in_progress: bool = False + self._attr_release_url = get_release_url( + coordinator.device.gen, coordinator.model, description.beta + ) async def async_added_to_hass(self) -> None: """When entity is added to hass.""" @@ -336,3 +344,15 @@ class RpcSleepingUpdateEntity( return None return self.last_state.attributes.get(ATTR_LATEST_VERSION) + + @property + def release_url(self) -> str | None: + """URL to the full release notes.""" + if not self.coordinator.device.initialized: + return None + + return get_release_url( + self.coordinator.device.gen, + self.coordinator.model, + self.entity_description.beta, + ) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 4d25812361c..eff21e71413 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -26,7 +26,10 @@ from .const import ( BASIC_INPUTS_EVENTS_TYPES, CONF_COAP_PORT, DEFAULT_COAP_PORT, + DEVICES_WITHOUT_FIRMWARE_CHANGELOG, DOMAIN, + GEN1_RELEASE_URL, + GEN2_RELEASE_URL, LOGGER, RPC_INPUTS_EVENTS_TYPES, SHBTN_INPUTS_EVENTS_TYPES, @@ -408,3 +411,11 @@ def mac_address_from_name(name: str) -> str | None: """Convert a name to a mac address.""" mac = name.partition(".")[0].partition("-")[-1] return mac.upper() if len(mac) == 12 else None + + +def get_release_url(gen: int, model: str, beta: bool) -> str | None: + """Return release URL or None.""" + if beta or model in DEVICES_WITHOUT_FIRMWARE_CHANGELOG: + return None + + return GEN1_RELEASE_URL if gen == 1 else GEN2_RELEASE_URL diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 438ca9b5ace..8b4ca0824c4 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -281,6 +281,7 @@ async def mock_block_device(): firmware_version="some fw string", initialized=True, model="SHSW-1", + gen=1, ) type(device).name = PropertyMock(return_value="Test name") block_device_mock.return_value = device diff --git a/tests/components/shelly/test_update.py b/tests/components/shelly/test_update.py index 454afb73ce1..06eac49e293 100644 --- a/tests/components/shelly/test_update.py +++ b/tests/components/shelly/test_update.py @@ -5,11 +5,16 @@ from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCal from freezegun.api import FrozenDateTimeFactory import pytest -from homeassistant.components.shelly.const import DOMAIN +from homeassistant.components.shelly.const import ( + DOMAIN, + GEN1_RELEASE_URL, + GEN2_RELEASE_URL, +) from homeassistant.components.update import ( ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, + ATTR_RELEASE_URL, DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL, UpdateEntityFeature, @@ -75,6 +80,7 @@ async def test_block_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "2" assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_RELEASE_URL] == GEN1_RELEASE_URL monkeypatch.setitem(mock_block_device.status["update"], "old_version", "2") await mock_rest_update(hass, freezer) @@ -117,6 +123,7 @@ async def test_block_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "2b" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_RELEASE_URL] is None await hass.services.async_call( UPDATE_DOMAIN, @@ -270,6 +277,7 @@ async def test_rpc_update(hass: HomeAssistant, mock_rpc_device, monkeypatch) -> assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "2" assert state.attributes[ATTR_IN_PROGRESS] == 0 + assert state.attributes[ATTR_RELEASE_URL] == GEN2_RELEASE_URL inject_rpc_device_event( monkeypatch, @@ -341,6 +349,7 @@ async def test_rpc_sleeping_update( assert state.attributes[ATTR_LATEST_VERSION] == "2" assert state.attributes[ATTR_IN_PROGRESS] is False assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0) + assert state.attributes[ATTR_RELEASE_URL] == GEN2_RELEASE_URL monkeypatch.setitem(mock_rpc_device.shelly, "ver", "2") mock_rpc_device.mock_update() @@ -467,6 +476,7 @@ async def test_rpc_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "1" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_RELEASE_URL] is None monkeypatch.setitem( mock_rpc_device.status["sys"], diff --git a/tests/components/shelly/test_utils.py b/tests/components/shelly/test_utils.py index 3d273ff3059..cacef1fad71 100644 --- a/tests/components/shelly/test_utils.py +++ b/tests/components/shelly/test_utils.py @@ -1,12 +1,14 @@ """Tests for Shelly utils.""" import pytest +from homeassistant.components.shelly.const import GEN1_RELEASE_URL, GEN2_RELEASE_URL from homeassistant.components.shelly.utils import ( get_block_channel_name, get_block_device_sleep_period, get_block_input_triggers, get_device_uptime, get_number_of_channels, + get_release_url, get_rpc_channel_name, get_rpc_input_triggers, is_block_momentary_input, @@ -224,3 +226,23 @@ async def test_get_rpc_input_triggers(mock_rpc_device, monkeypatch) -> None: monkeypatch.setattr(mock_rpc_device, "config", {"input:0": {"type": "switch"}}) assert not get_rpc_input_triggers(mock_rpc_device) + + +@pytest.mark.parametrize( + ("gen", "model", "beta", "expected"), + [ + (1, "SHMOS-01", False, None), + (1, "SHSW-1", False, GEN1_RELEASE_URL), + (1, "SHSW-1", True, None), + (2, "SAWD-0A1XX10EU1", False, None), + (2, "SNSW-102P16EU", False, GEN2_RELEASE_URL), + (2, "SNSW-102P16EU", True, None), + ], +) +def test_get_release_url( + gen: int, model: str, beta: bool, expected: str | None +) -> None: + """Test get_release_url() with a device without a release note URL.""" + result = get_release_url(gen, model, beta) + + assert result is expected