From b07b2a3ffc307715d5e7f59a8632b514ead8751e Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 10 Nov 2025 19:17:33 -0800 Subject: [PATCH 1/5] fix: Update trait `refresh` method to return `None` --- roborock/devices/traits/v1/clean_summary.py | 31 +++++++------------ roborock/devices/traits/v1/common.py | 3 +- roborock/devices/traits/v1/device_features.py | 12 +++---- roborock/devices/traits/v1/home.py | 5 ++- roborock/devices/traits/v1/network_info.py | 9 ++---- tests/devices/traits/v1/test_rooms.py | 6 ++-- 6 files changed, 25 insertions(+), 41 deletions(-) diff --git a/roborock/devices/traits/v1/clean_summary.py b/roborock/devices/traits/v1/clean_summary.py index 0062a116..8fea3a54 100644 --- a/roborock/devices/traits/v1/clean_summary.py +++ b/roborock/devices/traits/v1/clean_summary.py @@ -14,7 +14,7 @@ class CleanSummaryTrait(CleanSummaryWithDetail, common.V1TraitMixin): command = RoborockCommand.GET_CLEAN_SUMMARY - async def refresh(self) -> Self: + async def refresh(self) -> None: """Refresh the clean summary data and last clean record. Assumes that the clean summary has already been fetched. @@ -23,10 +23,9 @@ async def refresh(self) -> Self: if not self.records: _LOGGER.debug("No clean records available in clean summary.") self.last_clean_record = None - return self + return last_record_id = self.records[-1] self.last_clean_record = await self.get_clean_record(last_record_id) - return self @classmethod def _parse_type_response(cls, response: common.V1ResponseData) -> Self: @@ -61,23 +60,15 @@ def _parse_clean_record_response(cls, response: common.V1ResponseData) -> CleanR if isinstance(response[-1], dict): records = [CleanRecord.from_dict(rec) for rec in response] final_record = records[-1] - try: - # This code is semi-presumptuous - so it is put in a try finally to be safe. - final_record.begin = records[0].begin - final_record.begin_datetime = records[0].begin_datetime - final_record.start_type = records[0].start_type - for rec in records[0:-1]: - final_record.duration = (final_record.duration or 0) + (rec.duration or 0) - final_record.area = (final_record.area or 0) + (rec.area or 0) - final_record.avoid_count = (final_record.avoid_count or 0) + (rec.avoid_count or 0) - final_record.wash_count = (final_record.wash_count or 0) + (rec.wash_count or 0) - final_record.square_meter_area = (final_record.square_meter_area or 0) + ( - rec.square_meter_area or 0 - ) - return final_record - except Exception: - # Return final record when an exception occurred - return final_record + # Aggregate basic stats from prior partial records (exclude last which is full) + final_record.begin = records[0].begin + final_record.start_type = records[0].start_type + for rec in records[0:-1]: + final_record.duration = (final_record.duration or 0) + (rec.duration or 0) + final_record.area = (final_record.area or 0) + (rec.area or 0) + final_record.avoid_count = (final_record.avoid_count or 0) + (rec.avoid_count or 0) + final_record.wash_count = (final_record.wash_count or 0) + (rec.wash_count or 0) + return final_record # There are still a few unknown variables in this. begin, end, duration, area = unpack_list(response, 4) return CleanRecord(begin=begin, end=end, duration=duration, area=area) diff --git a/roborock/devices/traits/v1/common.py b/roborock/devices/traits/v1/common.py index 3d3fd23c..b948d915 100644 --- a/roborock/devices/traits/v1/common.py +++ b/roborock/devices/traits/v1/common.py @@ -76,7 +76,7 @@ def rpc_channel(self) -> V1RpcChannel: raise ValueError("Device trait in invalid state") return self._rpc_channel - async def refresh(self) -> Self: + async def refresh(self) -> None: """Refresh the contents of this trait.""" response = await self.rpc_channel.send_command(self.command) new_data = self._parse_response(response) @@ -84,7 +84,6 @@ async def refresh(self) -> Self: raise ValueError(f"Internal error, unexpected response type: {new_data!r}") _LOGGER.debug("Refreshed %s: %s", self.__class__.__name__, new_data) self._update_trait_values(new_data) - return self def _update_trait_values(self, new_data: RoborockBase) -> None: """Update the values of this trait from another instance.""" diff --git a/roborock/devices/traits/v1/device_features.py b/roborock/devices/traits/v1/device_features.py index f4b7e163..718d4548 100644 --- a/roborock/devices/traits/v1/device_features.py +++ b/roborock/devices/traits/v1/device_features.py @@ -1,5 +1,4 @@ from dataclasses import fields -from typing import Self from roborock.data import AppInitStatus, RoborockProductNickname from roborock.device_features import DeviceFeatures @@ -13,7 +12,7 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin): command = RoborockCommand.APP_GET_INIT_STATUS - def __init__(self, product_nickname: RoborockProductNickname, cache: Cache) -> None: + def __init__(self, product_nickname: RoborockProductNickname, cache: Cache) -> None: # pylint: disable=super-init-not-called """Initialize MapContentTrait.""" self._nickname = product_nickname self._cache = cache @@ -22,7 +21,7 @@ def __init__(self, product_nickname: RoborockProductNickname, cache: Cache) -> N for field in fields(self): setattr(self, field.name, False) - async def refresh(self) -> Self: + async def refresh(self) -> None: """Refresh the contents of this trait. This will use cached device features if available since they do not @@ -32,12 +31,11 @@ async def refresh(self) -> Self: cache_data = await self._cache.get() if cache_data.device_features is not None: self._update_trait_values(cache_data.device_features) - return self + return # Save cached device features - device_features = await super().refresh() - cache_data.device_features = device_features + await super().refresh() + cache_data.device_features = self await self._cache.set(cache_data) - return device_features def _parse_response(self, response: common.V1ResponseData) -> DeviceFeatures: """Parse the response from the device into a MapContentTrait instance.""" diff --git a/roborock/devices/traits/v1/home.py b/roborock/devices/traits/v1/home.py index a2d2b444..87d1604d 100644 --- a/roborock/devices/traits/v1/home.py +++ b/roborock/devices/traits/v1/home.py @@ -158,7 +158,7 @@ async def _build_home_map_info(self) -> tuple[dict[int, CombinedMapInfo], dict[i home_map_info[map_info.map_flag] = combined_map_info return home_map_info, home_map_content - async def refresh(self) -> Self: + async def refresh(self) -> None: """Refresh current map's underlying map and room data, updating cache as needed. This will only refresh the current map's data and will not populate non @@ -171,7 +171,7 @@ async def refresh(self) -> Self: # then we'll fall through below to refresh the current map only. try: await self.discover_home() - return self + return except RoborockDeviceBusy: _LOGGER.debug("Cannot refresh home data while device is busy cleaning") @@ -189,7 +189,6 @@ async def refresh(self) -> Self: await self._update_current_map( map_flag, combined_map_info, new_map_content, update_cache=self._discovery_completed ) - return self @property def home_map_info(self) -> dict[int, CombinedMapInfo] | None: diff --git a/roborock/devices/traits/v1/network_info.py b/roborock/devices/traits/v1/network_info.py index 8c5f0bb9..53ff17d5 100644 --- a/roborock/devices/traits/v1/network_info.py +++ b/roborock/devices/traits/v1/network_info.py @@ -3,7 +3,6 @@ from __future__ import annotations import logging -from typing import Self from roborock.data import NetworkInfo from roborock.devices.cache import Cache @@ -25,20 +24,20 @@ class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin): command = RoborockCommand.GET_NETWORK_INFO - def __init__(self, device_uid: str, cache: Cache) -> None: + def __init__(self, device_uid: str, cache: Cache) -> None: # pylint: disable=super-init-not-called """Initialize the trait.""" self._device_uid = device_uid self._cache = cache self.ip = "" - async def refresh(self) -> Self: + async def refresh(self) -> None: """Refresh the network info from the cache.""" cache_data = await self._cache.get() if cache_data.network_info and (network_info := cache_data.network_info.get(self._device_uid)): _LOGGER.debug("Using cached network info for device %s", self._device_uid) self._update_trait_values(network_info) - return self + return # Load from device if not in cache _LOGGER.debug("No cached network info for device %s, fetching from device", self._device_uid) @@ -48,8 +47,6 @@ async def refresh(self) -> Self: cache_data.network_info[self._device_uid] = self await self._cache.set(cache_data) - return self - def _parse_response(self, response: common.V1ResponseData) -> NetworkInfo: """Parse the response from the device into a NetworkInfo.""" if not isinstance(response, dict): diff --git a/tests/devices/traits/v1/test_rooms.py b/tests/devices/traits/v1/test_rooms.py index 271ecd61..e5fd7e66 100644 --- a/tests/devices/traits/v1/test_rooms.py +++ b/tests/devices/traits/v1/test_rooms.py @@ -48,11 +48,11 @@ async def test_refresh_rooms_trait( assert not rooms_trait.rooms # Load the room mapping information - refreshed_trait = await rooms_trait.refresh() + await rooms_trait.refresh() # Verify the room mappings are now populated - assert refreshed_trait.rooms - rooms = refreshed_trait.rooms + assert rooms_trait.rooms + rooms = rooms_trait.rooms assert len(rooms) == 3 assert rooms[0].segment_id == 16 From f362064a86387b40672c46f536fc2cd708e1262f Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 10 Nov 2025 19:30:26 -0800 Subject: [PATCH 2/5] chore: Revert unnecessary change to Clean Summary Trait --- roborock/devices/traits/v1/clean_summary.py | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/roborock/devices/traits/v1/clean_summary.py b/roborock/devices/traits/v1/clean_summary.py index 8fea3a54..85bfa756 100644 --- a/roborock/devices/traits/v1/clean_summary.py +++ b/roborock/devices/traits/v1/clean_summary.py @@ -60,16 +60,18 @@ def _parse_clean_record_response(cls, response: common.V1ResponseData) -> CleanR if isinstance(response[-1], dict): records = [CleanRecord.from_dict(rec) for rec in response] final_record = records[-1] - # Aggregate basic stats from prior partial records (exclude last which is full) - final_record.begin = records[0].begin - final_record.start_type = records[0].start_type - for rec in records[0:-1]: - final_record.duration = (final_record.duration or 0) + (rec.duration or 0) - final_record.area = (final_record.area or 0) + (rec.area or 0) - final_record.avoid_count = (final_record.avoid_count or 0) + (rec.avoid_count or 0) - final_record.wash_count = (final_record.wash_count or 0) + (rec.wash_count or 0) - return final_record - # There are still a few unknown variables in this. + try: + # This code is semi-presumptuous - so it is put in a try finally to be safe. + final_record.begin = records[0].begin + final_record.start_type = records[0].start_type + for rec in records[0:-1]: + final_record.duration = (final_record.duration or 0) + (rec.duration or 0) + final_record.area = (final_record.area or 0) + (rec.area or 0) + final_record.avoid_count = (final_record.avoid_count or 0) + (rec.avoid_count or 0) + final_record.wash_count = (final_record.wash_count or 0) + (rec.wash_count or 0) + except Exception: + # Return final record when an exception occurred + return final_record # There are still a few unknown variables in this. begin, end, duration, area = unpack_list(response, 4) return CleanRecord(begin=begin, end=end, duration=duration, area=area) raise ValueError(f"Unexpected clean record format: {response!r}") From 2a4d57334b1ecbf8a86815213d08732bedd3d323 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 10 Nov 2025 19:32:14 -0800 Subject: [PATCH 3/5] chore: Fix lint errors --- roborock/devices/traits/v1/clean_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roborock/devices/traits/v1/clean_summary.py b/roborock/devices/traits/v1/clean_summary.py index 85bfa756..26740095 100644 --- a/roborock/devices/traits/v1/clean_summary.py +++ b/roborock/devices/traits/v1/clean_summary.py @@ -71,7 +71,7 @@ def _parse_clean_record_response(cls, response: common.V1ResponseData) -> CleanR final_record.wash_count = (final_record.wash_count or 0) + (rec.wash_count or 0) except Exception: # Return final record when an exception occurred - return final_record # There are still a few unknown variables in this. + return final_record # There are still a few unknown variables in this. begin, end, duration, area = unpack_list(response, 4) return CleanRecord(begin=begin, end=end, duration=duration, area=area) raise ValueError(f"Unexpected clean record format: {response!r}") From ca5dd0430932308134ce7298655348a62d3b653c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 10 Nov 2025 21:03:04 -0800 Subject: [PATCH 4/5] chore: Revert accidental CleanSummary changes --- roborock/devices/traits/v1/clean_summary.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/roborock/devices/traits/v1/clean_summary.py b/roborock/devices/traits/v1/clean_summary.py index 26740095..182ab73a 100644 --- a/roborock/devices/traits/v1/clean_summary.py +++ b/roborock/devices/traits/v1/clean_summary.py @@ -63,15 +63,22 @@ def _parse_clean_record_response(cls, response: common.V1ResponseData) -> CleanR try: # This code is semi-presumptuous - so it is put in a try finally to be safe. final_record.begin = records[0].begin + final_record.begin_datetime = records[0].begin_datetime final_record.start_type = records[0].start_type for rec in records[0:-1]: final_record.duration = (final_record.duration or 0) + (rec.duration or 0) final_record.area = (final_record.area or 0) + (rec.area or 0) final_record.avoid_count = (final_record.avoid_count or 0) + (rec.avoid_count or 0) final_record.wash_count = (final_record.wash_count or 0) + (rec.wash_count or 0) + final_record.square_meter_area = (final_record.square_meter_area or 0) + ( + rec.square_meter_area or 0 + ) + return final_record except Exception: # Return final record when an exception occurred - return final_record # There are still a few unknown variables in this. + return final_record + + # There are still a few unknown variables in this. begin, end, duration, area = unpack_list(response, 4) return CleanRecord(begin=begin, end=end, duration=duration, area=area) raise ValueError(f"Unexpected clean record format: {response!r}") From 364e07a3dd6dba0cc1d160082a81acc928ffdd50 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 10 Nov 2025 21:03:26 -0800 Subject: [PATCH 5/5] chore: remove unnecessary whitespace --- roborock/devices/traits/v1/clean_summary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/roborock/devices/traits/v1/clean_summary.py b/roborock/devices/traits/v1/clean_summary.py index 182ab73a..10b3e737 100644 --- a/roborock/devices/traits/v1/clean_summary.py +++ b/roborock/devices/traits/v1/clean_summary.py @@ -77,7 +77,6 @@ def _parse_clean_record_response(cls, response: common.V1ResponseData) -> CleanR except Exception: # Return final record when an exception occurred return final_record - # There are still a few unknown variables in this. begin, end, duration, area = unpack_list(response, 4) return CleanRecord(begin=begin, end=end, duration=duration, area=area)