From 26bfbf2525bec9b06e40ce0b7fc2ee58b297e943 Mon Sep 17 00:00:00 2001 From: galletn Date: Thu, 8 Aug 2024 12:05:45 +0200 Subject: [PATCH 1/9] Update vacuum.py add support for i2d_robot device type --- custom_components/iaqualink_robots/vacuum.py | 272 ++++++++++++------- 1 file changed, 175 insertions(+), 97 deletions(-) diff --git a/custom_components/iaqualink_robots/vacuum.py b/custom_components/iaqualink_robots/vacuum.py index dca2745..9c6b3a7 100644 --- a/custom_components/iaqualink_robots/vacuum.py +++ b/custom_components/iaqualink_robots/vacuum.py @@ -8,6 +8,7 @@ STATE_CLEANING, STATE_DOCKED, STATE_IDLE, + STATE_RETURNING, SUPPORT_BATTERY, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, @@ -45,6 +46,28 @@ | VacuumEntityFeature.STATUS ) +SUPPORT_IAQUALINK_ROBOTS_vr = ( + VacuumEntityFeature.START + | VacuumEntityFeature.STOP + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.RETURN_HOME +) + +SUPPORT_IAQUALINK_ROBOTS_cyclonext = ( + VacuumEntityFeature.START + | VacuumEntityFeature.STOP + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.STATUS +) + +SUPPORT_IAQUALINK_ROBOTS_i2d = ( + VacuumEntityFeature.START + | VacuumEntityFeature.STOP + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.STATUS +) + # Define the domain and platform for our vacuum entity DOMAIN = "iaqualink_robots" @@ -89,7 +112,7 @@ def __init__(self, config): self._fan_speed_list = ["Floor only", "Floor and walls"] self._fan_speed = self._fan_speed_list[0] self._debug = None - self._debug_mode=False + self._debug_mode=True @property def fan_speed(self): @@ -258,137 +281,170 @@ async def async_update(self): self._device_type = data[index]["device_type"] self._attributes['device_type'] = self._device_type + #Set modes based on model + if self._device_type == "vr": - #request device status over websocket - request = { "action": "subscribe", "namespace": "authorization", "payload": { "userId": self.id }, "service": "Authorization", "target": self._serial_number, "version": 1 } + self._fan_speed_list = ["Floor only", "SMART Floor and walls", "Floor and walls"] + self._supported_features = SUPPORT_IAQUALINK_ROBOTS_vr - data= None - data = await asyncio.wait_for(self.get_device_status(request), timeout=30) + if self._device_type == "cyclonext": - try: - self._status = data['payload']['robot']['state']['reported']['aws']['status'] - self._attributes['status'] = self._status - except: - #returns empty message somethimes, try second call - self._debug = data - if self._debug_mode == True: - self._attributes['debug'] = self._debug - - data = await asyncio.wait_for(self.get_device_status(request), timeout=30) + self._fan_speed_list = ["Floor only", "SMART Floor and walls"] + self._supported_features = SUPPORT_IAQUALINK_ROBOTS_cyclonext - self._status = data['payload']['robot']['state']['reported']['aws']['status'] - self._attributes['status'] = self._status - + #request status for i2d type + if self._device_type == "i2d_robot": + request = { "command": "/command","params": "request=0A0D","user_id": self._id } - self._last_online = datetime_obj = datetime.datetime.fromtimestamp((data['payload']['robot']['state']['reported']['aws']['timestamp']/1000)) #Convert Epoch To Unix - self._attributes['last_online'] = self._last_online + url = "https://r-api.iaqualink.net/v2/devices/" + self._serial_number + "/control.json" + data= None + data = await asyncio.wait_for(self.get_device_status_i2d(url,request), timeout=30) - #For VR device type device mapping - if self._device_type == "vr": try: - self._temperature = data['payload']['robot']['state']['reported']['equipment']['robot']['sensors']['sns_1']['val'] + if data["command"]["request"] == "0A0D": + self._status = "online" + self._attributes['status'] = self._status except: - try: - self._temperature = data['payload']['robot']['state']['reported']['equipment']['robot']['sensors']['sns_1']['state'] + try: + if data["status"] == "500": + self._status = "offline" + self._attributes['status'] = self._status except: - self._temperature = '0' #Zodiac XA 5095 iQ does not support temp for example see https://github.com/galletn/iaqualink/issues/9 - - self._attributes['temperature'] = self._temperature + self._debug = data + if self._debug_mode == True: + self._attributes['debug'] = self._debug - if data['payload']['robot']['state']['reported']['equipment']['robot']['state'] == 1: - self._state = STATE_CLEANING - else: - self._state = STATE_IDLE + else: + #request device status & attributes over websocket for cyclonext and vr device types + request = { "action": "subscribe", "namespace": "authorization", "payload": { "userId": self.id }, "service": "Authorization", "target": self._serial_number, "version": 1 } - self._canister = data['payload']['robot']['state']['reported']['equipment']['robot']['canister'] - self._attributes['canister'] = self._canister + data= None + data = await asyncio.wait_for(self.get_device_status(request), timeout=30) - self._error_state = data['payload']['robot']['state']['reported']['equipment']['robot']['errorState'] - self._attributes['error_state'] = self._error_state + try: + self._status = data['payload']['robot']['state']['reported']['aws']['status'] + self._attributes['status'] = self._status + except: + #returns empty message somethimes, try second call + self._debug = data + if self._debug_mode == True: + self._attributes['debug'] = self._debug + + data = await asyncio.wait_for(self.get_device_status(request), timeout=30) - self._total_hours = data['payload']['robot']['state']['reported']['equipment']['robot']['totalHours'] - self._attributes['total_hours'] = self._total_hours + self._status = data['payload']['robot']['state']['reported']['aws']['status'] + self._attributes['status'] = self._status + - self._cycle_start_time = datetime_obj = datetime.datetime.fromtimestamp((data['payload']['robot']['state']['reported']['equipment']['robot']['cycleStartTime'])) #Convert Epoch To Unix - self._attributes['cycle_start_time'] = self._cycle_start_time + self._last_online = datetime_obj = datetime.datetime.fromtimestamp((data['payload']['robot']['state']['reported']['aws']['timestamp']/1000)) #Convert Epoch To Unix + self._attributes['last_online'] = self._last_online - self._cycle = data['payload']['robot']['state']['reported']['equipment']['robot']['prCyc'] - self._attributes['cycle'] = self._cycle + #For VR device type device mapping + if self._device_type == "vr": - cycle_duration_values = data['payload']['robot']['state']['reported']['equipment']['robot']['durations'] + try: + self._temperature = data['payload']['robot']['state']['reported']['equipment']['robot']['sensors']['sns_1']['val'] + except: + try: + self._temperature = data['payload']['robot']['state']['reported']['equipment']['robot']['sensors']['sns_1']['state'] + except: + self._temperature = '0' #Zodiac XA 5095 iQ does not support temp for example see https://github.com/galletn/iaqualink/issues/9 + + self._attributes['temperature'] = self._temperature - self._cycle_duration = list(cycle_duration_values.values())[self._cycle] - self._attributes['cycle_duration'] = self._cycle_duration + if data['payload']['robot']['state']['reported']['equipment']['robot']['state'] == 1: + self._state = STATE_CLEANING + else: + if data['payload']['robot']['state']['reported']['equipment']['robot']['state'] == 3: + self._state = STATE_RETURNING + else: + self._state = STATE_IDLE - minutes = self._cycle_duration + self._canister = data['payload']['robot']['state']['reported']['equipment']['robot']['canister'] + self._attributes['canister'] = self._canister - try: - self._cycle_end_time = datetime_obj = self.add_minutes_to_datetime(self._cycle_start_time, minutes) - self._attributes['cycle_end_time'] = self._cycle_end_time - except: - self._attributes['cycle_end_time'] = None + self._error_state = data['payload']['robot']['state']['reported']['equipment']['robot']['errorState'] + self._attributes['error_state'] = self._error_state - try: - self._time_remaining = self.subtract_dates(datetime.datetime.now(), self._cycle_end_time) - self._attributes['time_remaining'] = self._time_remaining - except: - self._attributes['time_remaining'] = None + self._total_hours = data['payload']['robot']['state']['reported']['equipment']['robot']['totalHours'] + self._attributes['total_hours'] = self._total_hours + self._cycle_start_time = datetime_obj = datetime.datetime.fromtimestamp((data['payload']['robot']['state']['reported']['equipment']['robot']['cycleStartTime'])) #Convert Epoch To Unix + self._attributes['cycle_start_time'] = self._cycle_start_time - #For cyclonext device type device mapping - if self._device_type == "cyclonext": - - if data['payload']['robot']['state']['reported']['equipment']['robot.1']['mode']== 1: - self._state = STATE_CLEANING - else: - self._state = STATE_IDLE + self._cycle = data['payload']['robot']['state']['reported']['equipment']['robot']['prCyc'] + self._attributes['cycle'] = self._cycle - self._canister = data['payload']['robot']['state']['reported']['equipment']['robot.1']['canister'] - self._attributes['canister'] = self._canister + cycle_duration_values = data['payload']['robot']['state']['reported']['equipment']['robot']['durations'] - self._error_state = data['payload']['robot']['state']['reported']['equipment']['robot.1']['errors']['code'] - self._attributes['error_state'] = self._error_state + self._cycle_duration = list(cycle_duration_values.values())[self._cycle] + self._attributes['cycle_duration'] = self._cycle_duration - self._total_hours = data['payload']['robot']['state']['reported']['equipment']['robot.1']['totRunTime'] - self._attributes['total_hours'] = self._total_hours + minutes = self._cycle_duration - self._cycle_start_time = datetime_obj = datetime.datetime.fromtimestamp((data['payload']['robot']['state']['reported']['equipment']['robot.1']['cycleStartTime'])) #Convert Epoch To Unix - self._attributes['cycle_start_time'] = self._cycle_start_time + try: + self._cycle_end_time = datetime_obj = self.add_minutes_to_datetime(self._cycle_start_time, minutes) + self._attributes['cycle_end_time'] = self._cycle_end_time + except: + self._attributes['cycle_end_time'] = None - self._cycle = data['payload']['robot']['state']['reported']['equipment']['robot.1']['cycle'] - self._attributes['cycle'] = self._cycle + try: + self._time_remaining = self.subtract_dates(datetime.datetime.now(), self._cycle_end_time) + self._attributes['time_remaining'] = self._time_remaining + except: + self._attributes['time_remaining'] = None - cycle_duration_values = data['payload']['robot']['state']['reported']['equipment']['robot.1']['durations'] - self._cycle_duration = list(cycle_duration_values.values())[self._cycle] - self._attributes['cycle_duration'] = self._cycle_duration + #For cyclonext device type device mapping + if self._device_type == "cyclonext": + + if data['payload']['robot']['state']['reported']['equipment']['robot.1']['mode']== 1: + self._state = STATE_CLEANING + else: + self._state = STATE_IDLE - minutes = self._cycle_duration + self._canister = data['payload']['robot']['state']['reported']['equipment']['robot.1']['canister'] + self._attributes['canister'] = self._canister - try: - self._cycle_end_time = datetime_obj = self.add_minutes_to_datetime(self._cycle_start_time, minutes) - self._attributes['cycle_end_time'] = self._cycle_end_time - except: - self._attributes['cycle_end_time'] = None + self._error_state = data['payload']['robot']['state']['reported']['equipment']['robot.1']['errors']['code'] + self._attributes['error_state'] = self._error_state - try: - self._time_remaining = self.subtract_dates(datetime.datetime.now(), self._cycle_end_time) - self._attributes['time_remaining'] = self._time_remaining - except: - self._attributes['time_remaining'] = None + self._total_hours = data['payload']['robot']['state']['reported']['equipment']['robot.1']['totRunTime'] + self._attributes['total_hours'] = self._total_hours + + self._cycle_start_time = datetime_obj = datetime.datetime.fromtimestamp((data['payload']['robot']['state']['reported']['equipment']['robot.1']['cycleStartTime'])) #Convert Epoch To Unix + self._attributes['cycle_start_time'] = self._cycle_start_time - #If other device types add here + self._cycle = data['payload']['robot']['state']['reported']['equipment']['robot.1']['cycle'] + self._attributes['cycle'] = self._cycle + cycle_duration_values = data['payload']['robot']['state']['reported']['equipment']['robot.1']['durations'] + + self._cycle_duration = list(cycle_duration_values.values())[self._cycle] + self._attributes['cycle_duration'] = self._cycle_duration + + minutes = self._cycle_duration + + try: + self._cycle_end_time = datetime_obj = self.add_minutes_to_datetime(self._cycle_start_time, minutes) + self._attributes['cycle_end_time'] = self._cycle_end_time + except: + self._attributes['cycle_end_time'] = None + try: + self._time_remaining = self.subtract_dates(datetime.datetime.now(), self._cycle_end_time) + self._attributes['time_remaining'] = self._time_remaining + except: + self._attributes['time_remaining'] = None #Get model only first time to avoid load. - if self._model == None: - data = None - url = URL_GET_DEVICE_FEATURES + self._serial_number + "/features" - data = await asyncio.wait_for(self.get_device_features(url), timeout=30) + if self._model == None: + data = None + url = URL_GET_DEVICE_FEATURES + self._serial_number + "/features" + data = await asyncio.wait_for(self.get_device_features(url), timeout=30) - self._model = data['model'] - self._attributes['model'] = self._model + self._model = data['model'] + self._attributes['model'] = self._model async def send_login(self, data, headers): @@ -429,6 +485,15 @@ async def get_device_status(self, request): return message.json() finally: await asyncio.wait_for(session.close(), timeout=30) + + async def get_device_status_i2d(self, url, request): + """Get device status of the iaqualink_robots API.""" + async with aiohttp.ClientSession(headers={"Authorization": self._id_token, "api_key": self._api_key}) as session: + try: + async with session.post(url,json = request) as response: + return await response.json() + finally: + await asyncio.wait_for(session.close(), timeout=30) async def setCleanerState(self, request): """Get device status of the iaqualink_robots API.""" @@ -442,7 +507,6 @@ async def setCleanerState(self, request): await asyncio.sleep(2) await self.async_update() - def add_minutes_to_datetime(self, dt, minutes): return dt + datetime.timedelta(minutes=minutes) @@ -480,11 +544,13 @@ async def async_set_fan_speed(self, fan_speed): if fan_speed == "Floor only": _cycle_speed = "1" - if fan_speed == "Floor and walls": + if fan_speed == "SMART Floor and walls": _cycle_speed = "2" - request = {"action":"setCleaningMode","version":1,"namespace":"vr","payload":{"state":{"desired":{"equipment":{"robot":{"prCyc":_cycle_speed}}}},"clientToken": clientToken},"service":"StateController","target": self._serial_number} + if fan_speed == "Floor and walls": + _cycle_speed = "3" + request = {"action":"setCleaningMode","version":1,"namespace":"vr","payload":{"state":{"desired":{"equipment":{"robot":{"prCyc":_cycle_speed}}}},"clientToken": clientToken},"service":"StateController","target": self._serial_number} if self._device_type == "cyclonext": if fan_speed == "Floor only": @@ -498,3 +564,15 @@ async def async_set_fan_speed(self, fan_speed): data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) self._debug = data + + async def async_return_to_base(self, **kwargs): + """Set the vacuum cleaner to return to the dock.""" + if self._status == "connected": + self._state = STATE_RETURNING + clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id + + #Only supported by VR type + if self._device_type == "vr": + request = { "action": "setCleanerState", "namespace": "vr", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot": { "state": 3 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } + + data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) From fe4addcbd559951c3b7f429debd37430aba3d66e Mon Sep 17 00:00:00 2001 From: galletn Date: Thu, 8 Aug 2024 21:21:22 +0200 Subject: [PATCH 2/9] i2d_robot update added stop start for i2d_robot --- custom_components/iaqualink_robots/vacuum.py | 55 +++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/custom_components/iaqualink_robots/vacuum.py b/custom_components/iaqualink_robots/vacuum.py index 9c6b3a7..6d5e7e1 100644 --- a/custom_components/iaqualink_robots/vacuum.py +++ b/custom_components/iaqualink_robots/vacuum.py @@ -214,33 +214,47 @@ def supported_features(self): async def async_start(self): """Start the vacuum.""" # Your start code here - if self._status == "connected": + if self._status == "connected": self._state = STATE_CLEANING - clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id + if self._device_type == "i2d_robot": + request = { "command": "/command","params": "request=0A1240&timeout=800","user_id": self._id } + url = "https://r-api.iaqualink.net/v2/devices/" + self._serial_number + "/control.json" + data= None - if self._device_type == "vr": - request = { "action": "setCleanerState", "namespace": "vr", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot": { "state": 1 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } + data = await asyncio.wait_for(self.post_command_i2d(url,request), timeout=800) + else: + if self._status == "connected": + clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id + + if self._device_type == "vr": + request = { "action": "setCleanerState", "namespace": "vr", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot": { "state": 1 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } - if self._device_type == "cyclonext": - request = { "action": "setCleanerState", "namespace": "cyclonext", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot.1": { "mode":1 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } + if self._device_type == "cyclonext": + request = { "action": "setCleanerState", "namespace": "cyclonext", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot.1": { "mode":1 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } - data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) + data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) async def async_stop(self, **kwargs): """Stop the vacuum.""" # Your stop code here if self._status == "connected": self._state = STATE_IDLE - clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id + if self._device_type == "i2d_robot": + request = { "command": "/command","params": "request=0A1210&timeout=800","user_id": self._id } + url = "https://r-api.iaqualink.net/v2/devices/" + self._serial_number + "/control.json" + data= None - if self._device_type == "vr": - request = { "action": "setCleanerState", "namespace": "vr", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot": { "state": 0 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } - - if self._device_type == "cyclonext": - request = { "action": "setCleanerState", "namespace": "cyclonext", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot.1": { "mode":0 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } + data = await asyncio.wait_for(self.post_command_i2d(url,request), timeout=800) + else: + clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id + + if self._device_type == "vr": + request = { "action": "setCleanerState", "namespace": "vr", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot": { "state": 0 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } + if self._device_type == "cyclonext": + request = { "action": "setCleanerState", "namespace": "cyclonext", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot.1": { "mode":0 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } - data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) + data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) async def async_update(self): """Get the latest state of the vacuum.""" @@ -294,16 +308,19 @@ async def async_update(self): #request status for i2d type if self._device_type == "i2d_robot": - request = { "command": "/command","params": "request=0A0D","user_id": self._id } + request = { "command": "/command","params": "request=OA11","user_id": self._id } url = "https://r-api.iaqualink.net/v2/devices/" + self._serial_number + "/control.json" data= None - data = await asyncio.wait_for(self.get_device_status_i2d(url,request), timeout=30) + data = await asyncio.wait_for(self.post_command_i2d(url,request), timeout=30) try: - if data["command"]["request"] == "0A0D": - self._status = "online" + if data["command"]["request"] == "OA11": + self._status = "connected" self._attributes['status'] = self._status + self._debug = data["command"]["response"] + if self._debug_mode == True: + self._attributes['debug'] = self._debug except: try: if data["status"] == "500": @@ -486,7 +503,7 @@ async def get_device_status(self, request): finally: await asyncio.wait_for(session.close(), timeout=30) - async def get_device_status_i2d(self, url, request): + async def post_command_i2d(self, url, request): """Get device status of the iaqualink_robots API.""" async with aiohttp.ClientSession(headers={"Authorization": self._id_token, "api_key": self._api_key}) as session: try: From 66f533041d0710d2d5217b2d23747ab497b90335 Mon Sep 17 00:00:00 2001 From: galletn Date: Fri, 9 Aug 2024 13:09:15 +0200 Subject: [PATCH 3/9] update for i2d robot added return to base and switch fan mode for i2d robots --- custom_components/iaqualink_robots/vacuum.py | 79 ++++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/custom_components/iaqualink_robots/vacuum.py b/custom_components/iaqualink_robots/vacuum.py index 6d5e7e1..39da224 100644 --- a/custom_components/iaqualink_robots/vacuum.py +++ b/custom_components/iaqualink_robots/vacuum.py @@ -66,6 +66,7 @@ | VacuumEntityFeature.STOP | VacuumEntityFeature.FAN_SPEED | VacuumEntityFeature.STATUS + | VacuumEntityFeature.RETURN_HOME ) @@ -124,12 +125,10 @@ def fan_speed_list(self): @property def temperature(self): - """Return the state of the sensor.""" return self._temperature @property def name(self): - """Return the name of the vacuum.""" return self._name @property @@ -154,7 +153,6 @@ def last_name(self): @property def model(self): - """Return device model.""" return self._model @property @@ -203,7 +201,7 @@ def state(self): @property def status(self): - """Return the state of the vacuum.""" + """Return the status of the vacuum.""" return self._status @property @@ -306,6 +304,11 @@ async def async_update(self): self._fan_speed_list = ["Floor only", "SMART Floor and walls"] self._supported_features = SUPPORT_IAQUALINK_ROBOTS_cyclonext + if self._device_type == "i2d_robot": + + self._fan_speed_list = ["Floor only", "Walls only", "Floor and walls"] + self._supported_features = SUPPORT_IAQUALINK_ROBOTS_i2d + #request status for i2d type if self._device_type == "i2d_robot": request = { "command": "/command","params": "request=OA11","user_id": self._id } @@ -331,6 +334,14 @@ async def async_update(self): if self._debug_mode == True: self._attributes['debug'] = self._debug + if self._state == STATE_CLEANING: + minutes_remaining = data[11:13] + try: + self.add_minutes_to_datetime(datetime.datetime.now(), minutes_remaining) + self._attributes['time_remaining'] = self._time_remaining + except: + self._attributes['time_remaining'] = None + else: #request device status & attributes over websocket for cyclonext and vr device types request = { "action": "subscribe", "namespace": "authorization", "payload": { "userId": self.id }, "service": "Authorization", "target": self._serial_number, "version": 1 } @@ -554,42 +565,64 @@ async def async_set_fan_speed(self, fan_speed): if fan_speed not in self._fan_speed_list: raise ValueError('Invalid fan speed') self._fan_speed = fan_speed + if self._device_type == "i2d_robot": + if fan_speed == "Walls only": + _cycle_speed = "0A1284" - clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id - - if self._device_type == "vr": if fan_speed == "Floor only": - _cycle_speed = "1" - - if fan_speed == "SMART Floor and walls": - _cycle_speed = "2" + _cycle_speed = "0A1280" if fan_speed == "Floor and walls": - _cycle_speed = "3" + _cycle_speed = "0A1283" + + request = { "command": "/command","params": "request=" + _cycle_speed + "&timeout=800","user_id": self._id } + url = "https://r-api.iaqualink.net/v2/devices/" + self._serial_number + "/control.json" + data= None + data = await asyncio.wait_for(self.post_command_i2d(url,request), timeout=800) + + else: + clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id - request = {"action":"setCleaningMode","version":1,"namespace":"vr","payload":{"state":{"desired":{"equipment":{"robot":{"prCyc":_cycle_speed}}}},"clientToken": clientToken},"service":"StateController","target": self._serial_number} + if self._device_type == "vr": + if fan_speed == "Floor only": + _cycle_speed = "1" - if self._device_type == "cyclonext": - if fan_speed == "Floor only": - _cycle_speed = "1" + if fan_speed == "SMART Floor and walls": + _cycle_speed = "2" - if fan_speed == "Floor and walls": - _cycle_speed = "3" + if fan_speed == "Floor and walls": + _cycle_speed = "3" + + request = {"action":"setCleaningMode","version":1,"namespace":"vr","payload":{"state":{"desired":{"equipment":{"robot":{"prCyc":_cycle_speed}}}},"clientToken": clientToken},"service":"StateController","target": self._serial_number} + + if self._device_type == "cyclonext": + if fan_speed == "Floor only": + _cycle_speed = "1" + + if fan_speed == "Floor and walls": + _cycle_speed = "3" - request = {"action":"setCleaningMode","namespace":"cyclonext","payload":{"clientToken":clientToken,"state":{"desired":{ "equipment":{"robot.1":{"cycle":_cycle_speed}}}}},"service":"StateController","target":self._serial_number,"version":1} + request = {"action":"setCleaningMode","namespace":"cyclonext","payload":{"clientToken":clientToken,"state":{"desired":{ "equipment":{"robot.1":{"cycle":_cycle_speed}}}}},"service":"StateController","target":self._serial_number,"version":1} - data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) + data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) - self._debug = data + if self._debug_mode == True: + self._attributes['debug'] = self._debug async def async_return_to_base(self, **kwargs): """Set the vacuum cleaner to return to the dock.""" if self._status == "connected": self._state = STATE_RETURNING - clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id - #Only supported by VR type if self._device_type == "vr": + clientToken = str ( self._id ) + "|" + self._authentication_token + "|" + self._app_client_id request = { "action": "setCleanerState", "namespace": "vr", "payload": { "clientToken": clientToken, "state": { "desired": { "equipment": { "robot": { "state": 3 } } } } }, "service": "StateController", "target": self._serial_number, "version": 1 } data = await asyncio.wait_for(self.setCleanerState(request), timeout=30) + + if self._device_type == "i2d_robot": + request = { "command": "/command","params": "request=0A1701&timeout=800","user_id": self._id } + url = "https://r-api.iaqualink.net/v2/devices/" + self._serial_number + "/control.json" + data= None + + data = await asyncio.wait_for(self.post_command_i2d(url,request), timeout=800) From 88f8b21fba18926bd7b2799ab5d682102fac7537 Mon Sep 17 00:00:00 2001 From: galletn Date: Tue, 20 Aug 2024 20:49:46 +0200 Subject: [PATCH 4/9] Update vacuum.py fix for cyclonext cleaning modes --- custom_components/iaqualink_robots/vacuum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/iaqualink_robots/vacuum.py b/custom_components/iaqualink_robots/vacuum.py index 39da224..8f487e1 100644 --- a/custom_components/iaqualink_robots/vacuum.py +++ b/custom_components/iaqualink_robots/vacuum.py @@ -301,7 +301,7 @@ async def async_update(self): if self._device_type == "cyclonext": - self._fan_speed_list = ["Floor only", "SMART Floor and walls"] + self._fan_speed_list = ["Floor only", "Floor and walls"] self._supported_features = SUPPORT_IAQUALINK_ROBOTS_cyclonext if self._device_type == "i2d_robot": From 4bbf3ee2555af09f90cee20aa40ad8bd32f777b6 Mon Sep 17 00:00:00 2001 From: galletn Date: Sat, 24 Aug 2024 09:31:54 +0200 Subject: [PATCH 5/9] fix for total runtime for cyclonext not always existing #34 is fixed with latest update --- custom_components/iaqualink_robots/vacuum.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/custom_components/iaqualink_robots/vacuum.py b/custom_components/iaqualink_robots/vacuum.py index 1daed2b..50acae7 100644 --- a/custom_components/iaqualink_robots/vacuum.py +++ b/custom_components/iaqualink_robots/vacuum.py @@ -437,8 +437,11 @@ async def async_update(self): self._error_state = data['payload']['robot']['state']['reported']['equipment']['robot.1']['errors']['code'] self._attributes['error_state'] = self._error_state - self._total_hours = data['payload']['robot']['state']['reported']['equipment']['robot.1']['totRunTime'] - self._attributes['total_hours'] = self._total_hours + try: + self._total_hours = data['payload']['robot']['state']['reported']['equipment']['robot.1']['totRunTime'] + self._attributes['total_hours'] = self._total_hours + except: + self._attributes['total_hours'] = 0 #not supported by some cyclonex models self._cycle_start_time = datetime_obj = datetime.datetime.fromtimestamp((data['payload']['robot']['state']['reported']['equipment']['robot.1']['cycleStartTime'])) #Convert Epoch To Unix self._attributes['cycle_start_time'] = self._cycle_start_time @@ -625,4 +628,4 @@ async def async_return_to_base(self, **kwargs): url = "https://r-api.iaqualink.net/v2/devices/" + self._serial_number + "/control.json" data= None - data = await asyncio.wait_for(self.post_command_i2d(url,request), timeout=800) \ No newline at end of file + data = await asyncio.wait_for(self.post_command_i2d(url,request), timeout=800) From d2ff219944a0f1679887652057e7ed89661c94a7 Mon Sep 17 00:00:00 2001 From: galletn Date: Sat, 24 Aug 2024 09:35:49 +0200 Subject: [PATCH 6/9] Update manifest.json --- custom_components/iaqualink_robots/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/iaqualink_robots/manifest.json b/custom_components/iaqualink_robots/manifest.json index 7bb3d29..20a34a5 100644 --- a/custom_components/iaqualink_robots/manifest.json +++ b/custom_components/iaqualink_robots/manifest.json @@ -9,6 +9,6 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/galletn/iaqualink/issues/", "requirements": [], - "version": "v0.3.1" + "version": "1.4.0" } From d3bc94ee1ba2be1f1dada4287757fb7f8593831f Mon Sep 17 00:00:00 2001 From: galletn Date: Sat, 24 Aug 2024 09:39:00 +0200 Subject: [PATCH 7/9] Create validate.yaml --- .github/workflows/validate.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/validate.yaml diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml new file mode 100644 index 0000000..4b927da --- /dev/null +++ b/.github/workflows/validate.yaml @@ -0,0 +1,18 @@ +name: Validate + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + validate-hacs: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v3" + - name: HACS validation + uses: "hacs/action@main" + with: + category: "integration" From 3a296348da63385a8188d7d51c606f9d20625b43 Mon Sep 17 00:00:00 2001 From: galletn Date: Sat, 24 Aug 2024 09:51:59 +0200 Subject: [PATCH 8/9] Create hacs.json --- hacs.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 hacs.json diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/hacs.json @@ -0,0 +1 @@ + From deee6c21dff2d70e8f9f5ce8219317eb91d15e1b Mon Sep 17 00:00:00 2001 From: galletn Date: Sat, 24 Aug 2024 09:52:56 +0200 Subject: [PATCH 9/9] Update hacs.json --- hacs.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hacs.json b/hacs.json index 8b13789..04e8e03 100644 --- a/hacs.json +++ b/hacs.json @@ -1 +1,3 @@ - +{ + "name": "iaqualink_robots" +}