diff --git a/roborock/api.py b/roborock/api.py index 957f9232..8b976003 100644 --- a/roborock/api.py +++ b/roborock/api.py @@ -37,7 +37,15 @@ UserData, WashTowelMode, ) -from .exceptions import RoborockException, RoborockTimeout, VacuumError +from .exceptions import ( + RoborockAccountDoesNotExist, + RoborockException, + RoborockInvalidCode, + RoborockInvalidEmail, + RoborockTimeout, + RoborockUrlException, + VacuumError, +) from .roborock_future import RoborockFuture from .roborock_message import RoborockMessage from .typing import DeviceProp, DockSummary, RoborockCommand @@ -345,12 +353,15 @@ async def _get_base_url(self) -> str: params={"email": self._username, "needtwostepauth": "false"}, ) if response is None: - raise RoborockException("get url by email returned None") - if response.get("code") != 200: - raise RoborockException(response.get("error")) + raise RoborockUrlException("get url by email returned None") + response_code = response.get("code") + if response_code != 200: + if response_code == 2003: + raise RoborockInvalidEmail("Your email was incorrectly formatted.") + raise RoborockUrlException(response.get("error")) response_data = response.get("data") if response_data is None: - raise RoborockException("response does not have 'data'") + raise RoborockUrlException("response does not have 'data'") self.base_url = response_data.get("url") return self.base_url @@ -375,8 +386,12 @@ async def request_code(self) -> None: ) if code_response is None: raise RoborockException("Failed to get a response from send email code") - if code_response.get("code") != 200: - raise RoborockException(code_response.get("msg")) + response_code = code_response.get("code") + if response_code != 200: + if response_code == 2008: + raise RoborockAccountDoesNotExist("Account does not exist - check your login and try again.") + else: + raise RoborockException(f"{code_response.get('msg')} - response code: {code_response.get('code')}") async def pass_login(self, password: str) -> UserData: base_url = await self._get_base_url() @@ -395,7 +410,7 @@ async def pass_login(self, password: str) -> UserData: if login_response is None: raise RoborockException("Login response is none") if login_response.get("code") != 200: - raise RoborockException(login_response.get("msg")) + raise RoborockException(f"{login_response.get('msg')} - response code: {login_response.get('code')}") user_data = login_response.get("data") if not isinstance(user_data, dict): raise RoborockException("Got unexpected data type for user_data") @@ -417,8 +432,11 @@ async def code_login(self, code) -> UserData: ) if login_response is None: raise RoborockException("Login request response is None") - if login_response.get("code") != 200: - raise RoborockException(login_response.get("msg")) + response_code = login_response.get("code") + if response_code != 200: + if response_code == 2018: + raise RoborockInvalidCode("Invalid code - check your code and try again.") + raise RoborockException(f"{login_response.get('msg')} - response code: {response_code}") user_data = login_response.get("data") if not isinstance(user_data, dict): raise RoborockException("Got unexpected data type for user_data") @@ -439,7 +457,7 @@ async def get_home_data(self, user_data: UserData) -> HomeData: if home_id_response is None: raise RoborockException("home_id_response is None") if home_id_response.get("code") != 200: - raise RoborockException(home_id_response.get("msg")) + raise RoborockException(f"{home_id_response.get('msg')} - response code: {home_id_response.get('code')}") home_id = home_id_response["data"].get("rrHomeId") timestamp = math.floor(time.time()) diff --git a/roborock/const.py b/roborock/const.py new file mode 100644 index 00000000..a2f84f2f --- /dev/null +++ b/roborock/const.py @@ -0,0 +1,5 @@ +# Total time in seconds consumables have before Roborock recommends replacing +MAIN_BRUSH_REPLACE_TIME = 1080000 +SIDE_BRUSH_REPLACE_TIME = 720000 +FILTER_REPLACE_TIME = 540000 +SENSOR_DIRTY_REPLACE_TIME = 108000 diff --git a/roborock/containers.py b/roborock/containers.py index 54fac71a..0ccf7846 100644 --- a/roborock/containers.py +++ b/roborock/containers.py @@ -21,6 +21,7 @@ RoborockFanPowerCode, RoborockMopModeCode, ) +from .const import FILTER_REPLACE_TIME, MAIN_BRUSH_REPLACE_TIME, SENSOR_DIRTY_REPLACE_TIME, SIDE_BRUSH_REPLACE_TIME def camelize(s: str): @@ -279,6 +280,20 @@ class Consumable(RoborockBase): strainer_work_times: Optional[int] = None dust_collection_work_times: Optional[int] = None cleaning_brush_work_times: Optional[int] = None + main_brush_time_left: Optional[int] = None + side_brush_time_left: Optional[int] = None + filter_time_left: Optional[int] = None + sensor_time_left: Optional[int] = None + + def __post_init__(self): + self.main_brush_time_left = ( + MAIN_BRUSH_REPLACE_TIME - self.main_brush_work_time if self.main_brush_work_time else None + ) + self.side_brush_time_left = ( + SIDE_BRUSH_REPLACE_TIME - self.side_brush_work_time if self.side_brush_work_time else None + ) + self.filter_time_left = FILTER_REPLACE_TIME - self.filter_work_time if self.filter_work_time else None + self.sensor_time_left = SENSOR_DIRTY_REPLACE_TIME - self.sensor_dirty_time if self.sensor_dirty_time else None @dataclass diff --git a/roborock/exceptions.py b/roborock/exceptions.py index bcfeaef3..02824315 100644 --- a/roborock/exceptions.py +++ b/roborock/exceptions.py @@ -36,3 +36,19 @@ class CommandVacuumError(RoborockException): def __init__(self, command: str, vacuum_error: VacuumError): self.message = f"{command}: {str(vacuum_error)}" super().__init__(self.message) + + +class RoborockAccountDoesNotExist(RoborockException): + """Class for Roborock account does not exist exceptions.""" + + +class RoborockUrlException(RoborockException): + """Class for being unable to get the URL for the Roborock account.""" + + +class RoborockInvalidCode(RoborockException): + """Class for Roborock invalid code exceptions.""" + + +class RoborockInvalidEmail(RoborockException): + """Class for Roborock invalid formatted email exceptions.""" diff --git a/roborock/typing.py b/roborock/typing.py index ac392504..6a23d202 100644 --- a/roborock/typing.py +++ b/roborock/typing.py @@ -221,3 +221,17 @@ class DeviceProp: consumable: Optional[Consumable] = None last_clean_record: Optional[CleanRecord] = None dock_summary: Optional[DockSummary] = None + + def update(self, device_prop: DeviceProp) -> None: + if device_prop.status: + self.status = device_prop.status + if device_prop.dnd_timer: + self.dnd_timer = device_prop.dnd_timer + if device_prop.clean_summary: + self.clean_summary = device_prop.clean_summary + if device_prop.consumable: + self.consumable = device_prop.consumable + if device_prop.last_clean_record: + self.last_clean_record = device_prop.last_clean_record + if device_prop.dock_summary: + self.dock_summary = device_prop.dock_summary