Skip to content

Commit e6b4e2b

Browse files
committed
chore: make v1_protocol responsible for parsing api error code
1 parent 3dc64de commit e6b4e2b

File tree

5 files changed

+31
-27
lines changed

5 files changed

+31
-27
lines changed

roborock/devices/traits/v1/common.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from typing import ClassVar, Self
1010

1111
from roborock.data import RoborockBase
12-
from roborock.exceptions import RoborockException
1312
from roborock.protocols.v1_protocol import V1RpcChannel
1413
from roborock.roborock_typing import RoborockCommand
1514

@@ -18,20 +17,6 @@
1817
V1ResponseData = dict | list | int | str
1918

2019

21-
def extract_v1_api_error_code(err: RoborockException) -> int | None:
22-
"""Extract a V1 RPC API error code from a RoborockException, if present.
23-
24-
V1 RPC error responses typically look like: {"code": -10007, "message": "..."}.
25-
"""
26-
if not err.args:
27-
return None
28-
payload = err.args[0]
29-
if isinstance(payload, dict):
30-
code = payload.get("code")
31-
return code if isinstance(code, int) else None
32-
return None
33-
34-
3520
@dataclass
3621
class V1TraitMixin(ABC):
3722
"""Base model that supports v1 traits.

roborock/devices/traits/v1/home.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from roborock.data.v1.v1_code_mappings import RoborockStateCode
2525
from roborock.devices.cache import DeviceCache
2626
from roborock.devices.traits.v1 import common
27-
from roborock.exceptions import RoborockDeviceBusy, RoborockException
27+
from roborock.exceptions import RoborockDeviceBusy, RoborockException, RoborockInvalidStatus
2828
from roborock.roborock_typing import RoborockCommand
2929

3030
from .map_content import MapContent, MapContentTrait
@@ -152,13 +152,10 @@ async def _build_home_map_info(self) -> tuple[dict[int, CombinedMapInfo], dict[i
152152
_LOGGER.debug("Loading map %s", map_info.map_flag)
153153
try:
154154
await self._maps_trait.set_current_map(map_info.map_flag)
155-
except RoborockException as ex:
156-
# Some firmware revisions return -10007 ("invalid status"/action locked) when attempting
157-
# to switch maps while the device is in a state that forbids it. Treat this as a
158-
# "busy" condition so callers can fall back to refreshing the current map only.
159-
if common.extract_v1_api_error_code(ex) == -10007:
160-
raise RoborockDeviceBusy("Cannot switch maps right now (device action locked)") from ex
161-
raise
155+
except RoborockInvalidStatus as ex:
156+
# Device is in a state that forbids map switching. Translate to
157+
# "busy" so callers can fall back to refreshing the current map only.
158+
raise RoborockDeviceBusy("Cannot switch maps right now (device action locked)") from ex
162159
await asyncio.sleep(MAP_SLEEP)
163160

164161
map_content = await self._refresh_map_content()

roborock/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,9 @@ class RoborockDeviceBusy(RoborockException):
8787
"""Class for Roborock device busy exceptions."""
8888

8989

90+
class RoborockInvalidStatus(RoborockException):
91+
"""Class for Roborock invalid status exceptions (device action locked)."""
92+
93+
9094
class RoborockUnsupportedFeature(RoborockException):
9195
"""Class for Roborock unsupported feature exceptions."""

roborock/protocols/v1_protocol.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from typing import Any, Protocol, TypeVar, overload
1414

1515
from roborock.data import RoborockBase, RRiot
16-
from roborock.exceptions import RoborockException, RoborockUnsupportedFeature
16+
from roborock.exceptions import RoborockException, RoborockInvalidStatus, RoborockUnsupportedFeature
1717
from roborock.protocol import Utils
1818
from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
1919
from roborock.roborock_typing import RoborockCommand
@@ -106,6 +106,24 @@ def _as_payload(self, security_data: SecurityData | None) -> bytes:
106106

107107
ResponseData = dict[str, Any] | list | int
108108

109+
# V1 RPC error code mappings to specific exception types
110+
_V1_ERROR_CODE_EXCEPTIONS: dict[int, type[RoborockException]] = {
111+
-10007: RoborockInvalidStatus, # "invalid status" - device action locked
112+
}
113+
114+
115+
def _create_api_error(error: Any) -> RoborockException:
116+
"""Create an appropriate exception for a V1 RPC error response.
117+
118+
Maps known error codes to specific exception types for easier handling
119+
at higher levels.
120+
"""
121+
if isinstance(error, dict):
122+
code = error.get("code")
123+
if isinstance(code, int) and (exc_type := _V1_ERROR_CODE_EXCEPTIONS.get(code)):
124+
return exc_type(error)
125+
return RoborockException(error)
126+
109127

110128
@dataclass(kw_only=True, frozen=True)
111129
class ResponseMessage:
@@ -156,7 +174,7 @@ def decode_rpc_response(message: RoborockMessage) -> ResponseMessage:
156174
request_id: int | None = data_point_response.get("id")
157175
api_error: RoborockException | None = None
158176
if error := data_point_response.get("error"):
159-
api_error = RoborockException(error)
177+
api_error = _create_api_error(error)
160178

161179
if (result := data_point_response.get("result")) is None:
162180
# Some firmware versions return an error-only response (no "result" key).

tests/devices/traits/v1/test_home.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from roborock.devices.traits.v1.maps import MapsTrait
1616
from roborock.devices.traits.v1.rooms import RoomsTrait
1717
from roborock.devices.traits.v1.status import StatusTrait
18-
from roborock.exceptions import RoborockDeviceBusy, RoborockException
18+
from roborock.exceptions import RoborockDeviceBusy, RoborockException, RoborockInvalidStatus
1919
from roborock.map.map_parser import ParsedMapData
2020
from roborock.roborock_typing import RoborockCommand
2121
from tests import mock_data
@@ -550,7 +550,7 @@ async def test_refresh_falls_back_when_map_switch_action_locked(
550550
# Discovery attempt: we can list maps, but switching maps fails with -10007.
551551
mock_mqtt_rpc_channel.send_command.side_effect = [
552552
MULTI_MAP_LIST_DATA, # Maps refresh during discover_home()
553-
RoborockException({"code": -10007, "message": "invalid status"}), # LOAD_MULTI_MAP action locked
553+
RoborockInvalidStatus({"code": -10007, "message": "invalid status"}), # LOAD_MULTI_MAP action locked
554554
MULTI_MAP_LIST_DATA, # Maps refresh during refresh() fallback
555555
]
556556

0 commit comments

Comments
 (0)