From 70fc6df5998e897120cfa943605dfbbf0e2ca09f Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 16 Oct 2025 12:50:43 +0200 Subject: [PATCH] Make Shelly deprecated firmware issue more general (#154539) --- homeassistant/components/shelly/__init__.py | 4 +-- homeassistant/components/shelly/const.py | 28 ++++++++++++++++---- homeassistant/components/shelly/repairs.py | 25 ++++++++++------- homeassistant/components/shelly/strings.json | 4 +-- tests/components/shelly/test_repairs.py | 21 ++++++++++----- 5 files changed, 57 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index fa920e786b00..79ade7708060 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -59,8 +59,8 @@ from .coordinator import ( ) from .repairs import ( async_manage_ble_scanner_firmware_unsupported_issue, + async_manage_deprecated_firmware_issue, async_manage_outbound_websocket_incorrectly_enabled_issue, - async_manage_wall_display_firmware_unsupported_issue, ) from .utils import ( async_create_issue_unsupported_firmware, @@ -337,7 +337,7 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ShellyConfigEntry) await hass.config_entries.async_forward_entry_setups( entry, runtime_data.platforms ) - async_manage_wall_display_firmware_unsupported_issue(hass, entry) + async_manage_deprecated_firmware_issue(hass, entry) async_manage_ble_scanner_firmware_unsupported_issue( hass, entry, diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 9576f8da8092..ff38a24a15b2 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -5,7 +5,7 @@ from __future__ import annotations from enum import StrEnum from logging import Logger, getLogger import re -from typing import Final +from typing import Final, TypedDict from aioshelly.const import ( MODEL_BULB, @@ -232,7 +232,6 @@ class BLEScannerMode(StrEnum): BLE_SCANNER_MIN_FIRMWARE = "1.5.1" -WALL_DISPLAY_MIN_FIRMWARE = "2.3.0" MAX_PUSH_UPDATE_FAILURES = 5 PUSH_UPDATE_ISSUE_ID = "push_update_{unique}" @@ -245,9 +244,28 @@ BLE_SCANNER_FIRMWARE_UNSUPPORTED_ISSUE_ID = "ble_scanner_firmware_unsupported_{u OUTBOUND_WEBSOCKET_INCORRECTLY_ENABLED_ISSUE_ID = ( "outbound_websocket_incorrectly_enabled_{unique}" ) -WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID = ( - "wall_display_firmware_unsupported_{unique}" -) +DEPRECATED_FIRMWARE_ISSUE_ID = "deprecated_firmware_{unique}" + + +class DeprecatedFirmwareInfo(TypedDict): + """TypedDict for Deprecated Firmware Info.""" + + min_firmware: str + ha_version: str + + +# Provide firmware deprecation data: +# key: device model +# value: dict with: +# min_firmware: minimum supported firmware version +# ha_version: Home Assistant version when older firmware will be deprecated +# Example: +# DEPRECATED_FIRMWARES: dict[str, DeprecatedFirmwareInfo] = { +# MODEL_WALL_DISPLAY: DeprecatedFirmwareInfo( +# {"min_firmware": "2.3.0", "ha_version": "2025.10.0"} +# ), +# } +DEPRECATED_FIRMWARES: dict[str, DeprecatedFirmwareInfo] = {} GAS_VALVE_OPEN_STATES = ("opening", "opened") diff --git a/homeassistant/components/shelly/repairs.py b/homeassistant/components/shelly/repairs.py index 742037599894..dba440419def 100644 --- a/homeassistant/components/shelly/repairs.py +++ b/homeassistant/components/shelly/repairs.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from aioshelly.const import MODEL_OUT_PLUG_S_G3, MODEL_PLUG_S_G3, MODEL_WALL_DISPLAY +from aioshelly.const import MODEL_OUT_PLUG_S_G3, MODEL_PLUG_S_G3 from aioshelly.exceptions import DeviceConnectionError, RpcCallError from aioshelly.rpc_device import RpcDevice from awesomeversion import AwesomeVersion @@ -19,10 +19,10 @@ from .const import ( BLE_SCANNER_FIRMWARE_UNSUPPORTED_ISSUE_ID, BLE_SCANNER_MIN_FIRMWARE, CONF_BLE_SCANNER_MODE, + DEPRECATED_FIRMWARE_ISSUE_ID, + DEPRECATED_FIRMWARES, DOMAIN, OUTBOUND_WEBSOCKET_INCORRECTLY_ENABLED_ISSUE_ID, - WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID, - WALL_DISPLAY_MIN_FIRMWARE, BLEScannerMode, ) from .coordinator import ShellyConfigEntry @@ -70,21 +70,25 @@ def async_manage_ble_scanner_firmware_unsupported_issue( @callback -def async_manage_wall_display_firmware_unsupported_issue( +def async_manage_deprecated_firmware_issue( hass: HomeAssistant, entry: ShellyConfigEntry, ) -> None: - """Manage the Wall Display firmware unsupported issue.""" - issue_id = WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id) + """Manage deprecated firmware issue.""" + issue_id = DEPRECATED_FIRMWARE_ISSUE_ID.format(unique=entry.unique_id) if TYPE_CHECKING: assert entry.runtime_data.rpc is not None device = entry.runtime_data.rpc.device + model = entry.data["model"] + + if model in DEPRECATED_FIRMWARES: + min_firmware = DEPRECATED_FIRMWARES[model]["min_firmware"] + ha_version = DEPRECATED_FIRMWARES[model]["ha_version"] - if entry.data["model"] == MODEL_WALL_DISPLAY: firmware = AwesomeVersion(device.shelly["ver"]) - if firmware < WALL_DISPLAY_MIN_FIRMWARE: + if firmware < min_firmware: ir.async_create_issue( hass, DOMAIN, @@ -92,11 +96,12 @@ def async_manage_wall_display_firmware_unsupported_issue( is_fixable=True, is_persistent=True, severity=ir.IssueSeverity.WARNING, - translation_key="wall_display_firmware_unsupported", + translation_key="deprecated_firmware", translation_placeholders={ "device_name": device.name, "ip_address": device.ip_address, "firmware": firmware, + "ha_version": ha_version, }, data={"entry_id": entry.entry_id}, ) @@ -241,7 +246,7 @@ async def async_create_fix_flow( if ( "ble_scanner_firmware_unsupported" in issue_id - or "wall_display_firmware_unsupported" in issue_id + or "deprecated_firmware" in issue_id ): return FirmwareUpdateFlow(device) diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 443abc119e56..90e41a1b63af 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -312,13 +312,13 @@ } } }, - "wall_display_firmware_unsupported": { + "deprecated_firmware": { "title": "{device_name} is running outdated firmware", "fix_flow": { "step": { "confirm": { "title": "{device_name} is running outdated firmware", - "description": "Your Shelly device {device_name} with IP address {ip_address} is running firmware {firmware}. This firmware version will not be supported by Shelly integration starting from Home Assistant 2025.11.0.\n\nSelect **Submit** button to start the OTA update to the latest stable firmware version." + "description": "Your Shelly device {device_name} with IP address {ip_address} is running firmware {firmware}. This firmware version will not be supported by Shelly integration starting from Home Assistant {ha_version}.\n\nSelect **Submit** button to start the OTA update to the latest stable firmware version." } }, "abort": { diff --git a/tests/components/shelly/test_repairs.py b/tests/components/shelly/test_repairs.py index d5d014028770..7597e2076494 100644 --- a/tests/components/shelly/test_repairs.py +++ b/tests/components/shelly/test_repairs.py @@ -1,6 +1,6 @@ """Test repairs handling for Shelly.""" -from unittest.mock import Mock +from unittest.mock import Mock, patch from aioshelly.const import MODEL_WALL_DISPLAY from aioshelly.exceptions import DeviceConnectionError, RpcCallError @@ -9,10 +9,11 @@ import pytest from homeassistant.components.shelly.const import ( BLE_SCANNER_FIRMWARE_UNSUPPORTED_ISSUE_ID, CONF_BLE_SCANNER_MODE, + DEPRECATED_FIRMWARE_ISSUE_ID, DOMAIN, OUTBOUND_WEBSOCKET_INCORRECTLY_ENABLED_ISSUE_ID, - WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID, BLEScannerMode, + DeprecatedFirmwareInfo, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir @@ -215,17 +216,25 @@ async def test_outbound_websocket_incorrectly_enabled_issue_exc( assert len(issue_registry.issues) == 1 -async def test_wall_display_unsupported_firmware_issue( +async def test_deprecated_firmware_issue( hass: HomeAssistant, hass_client: ClientSessionGenerator, mock_rpc_device: Mock, issue_registry: ir.IssueRegistry, ) -> None: - """Test repair issues handling for Wall Display with unsupported firmware.""" - issue_id = WALL_DISPLAY_FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=MOCK_MAC) + """Test repair issues handling deprecated firmware.""" + issue_id = DEPRECATED_FIRMWARE_ISSUE_ID.format(unique=MOCK_MAC) assert await async_setup_component(hass, "repairs", {}) await hass.async_block_till_done() - await init_integration(hass, 2, model=MODEL_WALL_DISPLAY) + with patch( + "homeassistant.components.shelly.repairs.DEPRECATED_FIRMWARES", + { + MODEL_WALL_DISPLAY: DeprecatedFirmwareInfo( + {"min_firmware": "2.3.0", "ha_version": "2025.10.0"} + ) + }, + ): + await init_integration(hass, 2, model=MODEL_WALL_DISPLAY) # The default fw version in tests is 1.0.0, the repair issue should be created. assert issue_registry.async_get_issue(DOMAIN, issue_id)