|
13 | 13 | from typing import Any, Protocol, TypeVar, overload |
14 | 14 |
|
15 | 15 | from roborock.data import RoborockBase, RRiot |
16 | | -from roborock.exceptions import RoborockException, RoborockUnsupportedFeature |
| 16 | +from roborock.exceptions import RoborockException, RoborockInvalidStatus, RoborockUnsupportedFeature |
17 | 17 | from roborock.protocol import Utils |
18 | 18 | from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol |
19 | 19 | from roborock.roborock_typing import RoborockCommand |
@@ -106,6 +106,24 @@ def _as_payload(self, security_data: SecurityData | None) -> bytes: |
106 | 106 |
|
107 | 107 | ResponseData = dict[str, Any] | list | int |
108 | 108 |
|
| 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 | + |
109 | 127 |
|
110 | 128 | @dataclass(kw_only=True, frozen=True) |
111 | 129 | class ResponseMessage: |
@@ -154,26 +172,38 @@ def decode_rpc_response(message: RoborockMessage) -> ResponseMessage: |
154 | 172 | ) from e |
155 | 173 |
|
156 | 174 | request_id: int | None = data_point_response.get("id") |
157 | | - exc: RoborockException | None = None |
| 175 | + api_error: RoborockException | None = None |
158 | 176 | if error := data_point_response.get("error"): |
159 | | - exc = RoborockException(error) |
| 177 | + api_error = _create_api_error(error) |
| 178 | + |
160 | 179 | if (result := data_point_response.get("result")) is None: |
161 | | - exc = RoborockException(f"Invalid V1 message format: missing 'result' in data point for {message.payload!r}") |
| 180 | + # Some firmware versions return an error-only response (no "result" key). |
| 181 | + # Preserve that error instead of overwriting it with a parsing exception. |
| 182 | + if api_error is None: |
| 183 | + api_error = RoborockException( |
| 184 | + f"Invalid V1 message format: missing 'result' in data point for {message.payload!r}" |
| 185 | + ) |
| 186 | + result = {} |
162 | 187 | else: |
163 | 188 | _LOGGER.debug("Decoded V1 message result: %s", result) |
164 | 189 | if isinstance(result, str): |
165 | 190 | if result == "unknown_method": |
166 | | - exc = RoborockUnsupportedFeature("The method called is not recognized by the device.") |
| 191 | + api_error = RoborockUnsupportedFeature("The method called is not recognized by the device.") |
167 | 192 | elif result != "ok": |
168 | | - exc = RoborockException(f"Unexpected API Result: {result}") |
| 193 | + api_error = RoborockException(f"Unexpected API Result: {result}") |
169 | 194 | result = {} |
170 | 195 | if not isinstance(result, dict | list | int): |
171 | | - raise RoborockException( |
172 | | - f"Invalid V1 message format: 'result' was unexpected type {type(result)}. {message.payload!r}" |
173 | | - ) |
174 | | - if not request_id and exc: |
175 | | - raise exc |
176 | | - return ResponseMessage(request_id=request_id, data=result, api_error=exc) |
| 196 | + # If we already have an API error, prefer returning a response object |
| 197 | + # rather than failing to decode the message entirely. |
| 198 | + if api_error is None: |
| 199 | + raise RoborockException( |
| 200 | + f"Invalid V1 message format: 'result' was unexpected type {type(result)}. {message.payload!r}" |
| 201 | + ) |
| 202 | + result = {} |
| 203 | + |
| 204 | + if not request_id and api_error: |
| 205 | + raise api_error |
| 206 | + return ResponseMessage(request_id=request_id, data=result, api_error=api_error) |
177 | 207 |
|
178 | 208 |
|
179 | 209 | @dataclass |
|
0 commit comments