mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Add live firmware update detection to Plugwise (#165936)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -65,6 +65,7 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[dict[str, GwEntityData
|
||||
)
|
||||
self._connected: bool = False
|
||||
self._current_devices: set[str] = set()
|
||||
self._firmware_list: dict[str, str | None] = {}
|
||||
self._stored_devices: set[str] = set()
|
||||
self.new_devices: set[str] = set()
|
||||
|
||||
@@ -129,6 +130,7 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[dict[str, GwEntityData
|
||||
) from err
|
||||
|
||||
self._add_remove_devices(data)
|
||||
self._update_device_firmware(data)
|
||||
return data
|
||||
|
||||
def _add_remove_devices(self, data: dict[str, GwEntityData]) -> None:
|
||||
@@ -138,6 +140,9 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[dict[str, GwEntityData
|
||||
# 'new_devices' contains all devices present in 'data' at init ('self._current_devices' is empty)
|
||||
# this is required for the proper initialization of all the present platform entities.
|
||||
self.new_devices = set_of_data - self._current_devices
|
||||
for device_id in self.new_devices:
|
||||
self._firmware_list.setdefault(device_id, data[device_id].get("firmware"))
|
||||
|
||||
current_devices = (
|
||||
self._stored_devices if not self._current_devices else self._current_devices
|
||||
)
|
||||
@@ -149,21 +154,52 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[dict[str, GwEntityData
|
||||
"""Clean registries when removed devices found."""
|
||||
device_reg = dr.async_get(self.hass)
|
||||
for device_id in removed_devices:
|
||||
device_entry = device_reg.async_get_device({(DOMAIN, device_id)})
|
||||
if device_entry is None:
|
||||
LOGGER.warning(
|
||||
"Failed to remove %s device/zone %s, not present in device_registry",
|
||||
if (
|
||||
device_entry := device_reg.async_get_device({(DOMAIN, device_id)})
|
||||
) is not None:
|
||||
device_reg.async_update_device(
|
||||
device_entry.id, remove_config_entry_id=self.config_entry.entry_id
|
||||
)
|
||||
LOGGER.debug(
|
||||
"%s %s %s removed from device_registry",
|
||||
DOMAIN,
|
||||
device_entry.model,
|
||||
device_id,
|
||||
)
|
||||
continue # pragma: no cover
|
||||
|
||||
device_reg.async_update_device(
|
||||
device_entry.id, remove_config_entry_id=self.config_entry.entry_id
|
||||
)
|
||||
self._firmware_list.pop(device_id, None)
|
||||
|
||||
def _update_device_firmware(self, data: dict[str, GwEntityData]) -> None:
|
||||
"""Detect firmware changes and update the device registry."""
|
||||
for device_id, device in data.items():
|
||||
# Only update firmware when the key is present and not None, to avoid
|
||||
# wiping stored firmware on partial or transient updates.
|
||||
if "firmware" not in device:
|
||||
continue
|
||||
new_firmware = device.get("firmware")
|
||||
if new_firmware is None:
|
||||
continue
|
||||
if (
|
||||
device_id in self._firmware_list
|
||||
and new_firmware != self._firmware_list[device_id]
|
||||
):
|
||||
updated = self._update_firmware_in_dr(device_id, new_firmware)
|
||||
if updated:
|
||||
self._firmware_list[device_id] = new_firmware
|
||||
|
||||
def _update_firmware_in_dr(self, device_id: str, firmware: str | None) -> bool:
|
||||
"""Update device sw_version in device_registry."""
|
||||
device_reg = dr.async_get(self.hass)
|
||||
if (
|
||||
device_entry := device_reg.async_get_device({(DOMAIN, device_id)})
|
||||
) is not None:
|
||||
device_reg.async_update_device(device_entry.id, sw_version=firmware)
|
||||
LOGGER.debug(
|
||||
"%s %s %s removed from device_registry",
|
||||
"Firmware in device_registry updated for %s %s %s",
|
||||
DOMAIN,
|
||||
device_entry.model,
|
||||
device_id,
|
||||
)
|
||||
return True
|
||||
|
||||
return False # pragma: no cover
|
||||
|
||||
@@ -372,6 +372,38 @@ async def test_delete_removed_device(
|
||||
assert device_entry is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("chosen_env", ["m_adam_heating"], indirect=True)
|
||||
@pytest.mark.parametrize("cooling_present", [False], indirect=True)
|
||||
async def test_update_device_firmware(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_smile_adam_heat_cool: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
init_integration: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test device firmware update via coordinator."""
|
||||
data = mock_smile_adam_heat_cool.async_update.return_value
|
||||
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, "da224107914542988a88561b4452b0f6")}
|
||||
)
|
||||
assert device_entry is not None
|
||||
assert str(device_entry.sw_version) == "3.9.0"
|
||||
|
||||
data["da224107914542988a88561b4452b0f6"]["firmware"] = "3.10.13"
|
||||
with patch(HA_PLUGWISE_SMILE_ASYNC_UPDATE, return_value=data):
|
||||
freezer.tick(timedelta(minutes=1))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, "da224107914542988a88561b4452b0f6")}
|
||||
)
|
||||
assert device_entry is not None
|
||||
assert str(device_entry.sw_version) == "3.10.13"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("chosen_env", ["m_adam_heating"], indirect=True)
|
||||
@pytest.mark.parametrize("cooling_present", [False], indirect=True)
|
||||
async def test_update_interval_adam(
|
||||
|
||||
Reference in New Issue
Block a user