From 3b2497192c821d06c78bb798470b989224960a16 Mon Sep 17 00:00:00 2001 From: Jezza34000 Date: Mon, 14 Nov 2022 13:40:12 +0100 Subject: [PATCH 1/3] Support wss credentials renewal, add missing data error support --- custom_components/weback_vacuum/VacDevice.py | 35 ++++++---- custom_components/weback_vacuum/WebackApi.py | 67 +++++++++++++------- custom_components/weback_vacuum/__init__.py | 11 +++- custom_components/weback_vacuum/vacuum.py | 11 +--- 4 files changed, 79 insertions(+), 45 deletions(-) diff --git a/custom_components/weback_vacuum/VacDevice.py b/custom_components/weback_vacuum/VacDevice.py index 5018f61..5b2775b 100644 --- a/custom_components/weback_vacuum/VacDevice.py +++ b/custom_components/weback_vacuum/VacDevice.py @@ -6,9 +6,10 @@ class VacDevice(WebackWssCtrl): - def __init__(self, thing_name, thing_nickname, sub_type, thing_status, wss_url, region_name, jwt_token): + def __init__(self, thing_name, thing_nickname, sub_type, thing_status, + user, password, region, country, app, client_id, api_version): _LOGGER.debug("WebackApi RobotController __init__") - super().__init__(wss_url, region_name, jwt_token) + super().__init__(user, password, region, country, app, client_id, api_version) self.name = thing_name self.nickname = thing_nickname self.sub_type = sub_type @@ -33,9 +34,11 @@ async def watch_state(self): # -> Properties @property - def current_mode(self) -> str: + def current_mode(self): """ Raw working_status field string """ - return self.robot_status['working_status'] + if 'working_status' in self.robot_status: + return self.robot_status['working_status'] + return self.IDLE_MODE @property def raw_status(self) -> str: @@ -52,7 +55,9 @@ def is_cleaning(self) -> bool: @property def is_available(self): """ Boolean define if robot is connected to cloud """ - return self.robot_status['connected'] == 'true' + if 'connected' in self.robot_status: + return self.robot_status['connected'] == 'true' + return False @property def is_charging(self): @@ -62,22 +67,28 @@ def is_charging(self): @property def error_info(self): """ Raw error_info field string """ - return self.robot_status["error_info"] + if 'error_info' in self.robot_status: + return self.robot_status['error_info'] + return None @property def battery_level(self): """ Raw battery_level field integer """ - return int(self.robot_status["battery_level"]) + if 'battery_level' in self.robot_status: + return int(self.robot_status['battery_level']) + return 0 @property def fan_status(self): """ Raw fan_status field string """ - return self.robot_status["fan_status"] + if 'fan_status' in self.robot_status: + return self.robot_status['fan_status'] @property def mop_status(self): """ Raw fan_status field string """ - return self.robot_status["water_level"] + if 'water_level' in self.robot_status: + return self.robot_status['water_level'] @property def fan_speed_list(self): @@ -92,12 +103,14 @@ def mop_level_list(self): @property def clean_time(self): """Return clean time""" - return self.robot_status["clean_time"] + if 'clean_time' in self.robot_status: + return self.robot_status['clean_time'] @property def clean_area(self): """Return clean area in square meter""" - return self.robot_status["clean_area"] + if 'clean_area' in self.robot_status: + return self.robot_status['clean_area'] @property def vacuum_or_mop(self) -> int: diff --git a/custom_components/weback_vacuum/WebackApi.py b/custom_components/weback_vacuum/WebackApi.py index 0ada168..b671d60 100644 --- a/custom_components/weback_vacuum/WebackApi.py +++ b/custom_components/weback_vacuum/WebackApi.py @@ -156,26 +156,26 @@ def save_token_file(self): try: config = configparser.ConfigParser() config.add_section('weback_token') - config.set('weback_token', 'jwt_token', self.jwt_token) - config.set('weback_token', 'token_exp', self.token_exp) - config.set('weback_token', 'api_url', self.api_url) - config.set('weback_token', 'wss_url', self.wss_url) - config.set('weback_token', 'region_name', self.region_name) + config.set('weback_token', 'jwt_token', str(self.jwt_token)) + config.set('weback_token', 'token_exp', str(self.token_exp)) + config.set('weback_token', 'api_url', str(self.api_url)) + config.set('weback_token', 'wss_url', str(self.wss_url)) + config.set('weback_token', 'region_name', str(self.region_name)) with open('weback_creds', 'w') as configfile: config.write(configfile) _LOGGER.debug(f"WebackApi saved new creds") - except: - _LOGGER.debug(f"WebackApi failed to saved new creds") + except Exception as e: + _LOGGER.debug(f"WebackApi failed to saved new creds details={e}") @staticmethod - def check_token_is_valid(token: str) -> bool: + def check_token_is_valid(token) -> bool: """ Check if token validity is still OK or not """ _LOGGER.debug(f"WebackApi checking token validity : {token}") try: - now_date = datetime.today() - dt_token = datetime.strptime(token, "%Y-%d-%m %H:%M:%S.%f") + now_date = datetime.today() - timedelta(minutes=15) + dt_token = datetime.strptime(str(token), "%Y-%m-%d %H:%M:%S.%f") if now_date < dt_token: _LOGGER.debug(f"WebackApi token is valid") return True @@ -239,7 +239,7 @@ async def send_http(url, **params): # _LOGGER.debug(f"WebackVacuumApi (WSS) null_callback: {message}") -class WebackWssCtrl: +class WebackWssCtrl(WebackApi): # Clean mode CLEAN_MODE_AUTO = 'AutoClean' @@ -267,7 +267,7 @@ class WebackWssCtrl: # Idle state IDLE_MODE_HIBERNATING = 'Hibernating' - IDLE_MODE = "Idle" + IDLE_MODE = 'Idle' # Standby/Paused state CLEAN_MODE_STOP = 'Standby' @@ -302,7 +302,10 @@ class WebackWssCtrl: MOP_ON = 2 # Error state - ROBOT_ERROR = "Malfunction" + ROBOT_ERROR = 'Malfunction' + + # Unknow state + ROBOT_UNKNOWN = 'unknown' # Robot Error codes ROBOT_ERROR_NO = "NoError" @@ -379,14 +382,12 @@ class WebackWssCtrl: WebSocket Weback API controller Handle websocket to send/receive robot control """ - def __init__(self, wss_url, region_name, jwt_token): + def __init__(self, user, password, region, country, app, client_id, api_version): + super().__init__(user, password, region, country, app, client_id, api_version) _LOGGER.debug("WebackApi WSS Control __init__") self.ws = None self.authorization = "Basic KG51bGwpOihudWxsKQ==" self.socket_state = SOCK_CLOSE - self.jwt_token = jwt_token - self.region_name = region_name - self.wss_url = wss_url self.robot_status = None self.subscriber = [] self.wst = None @@ -394,10 +395,29 @@ def __init__(self, wss_url, region_name, jwt_token): self._refresh_time = 60 self.sent_counter = 0 + async def check_credentials(self): + """ + Check if credentials for WSS link are OK + """ + _LOGGER.debug(f"WebackApi (WSS) Checking credentials...") + if not self.region_name or not self.jwt_token or not self.check_token_is_valid(self.token_exp): + _LOGGER.debug(f"WebackApi (WSS) Credentials need renewal") + # Cred renewal necessary + if await self.login(): + return True + else: + return False + _LOGGER.debug(f"WebackApi (WSS) Credentials are OK") + return True + async def open_wss_thread(self): """ Connect WebSocket to Weback Server and create a thread to maintain connexion alive """ + if not await self.check_credentials(): + _LOGGER.error(f"WebackApi (WSS) Failed to obtain WSS credentials") + return False + _LOGGER.debug(f"WebackApi (WSS) Addr={self.wss_url} / Region={self.region_name} / Token={self.jwt_token}") try: @@ -569,12 +589,13 @@ async def update_status(self, thing_name, sub_type): def adapt_refresh_time(self, status): """Adapt refreshing time depending on robot status""" _LOGGER.debug(f"WebackApi (WSS) adapt for : {status}") - if status['working_status'] in self.DOCKED_STATES: - _LOGGER.debug("WebackApi (WSS) > Set refreshing to 120s") - self._refresh_time = 120 - else: - _LOGGER.debug("WebackApi (WSS) > Set refreshing to 5s") - self._refresh_time = 5 + if 'working_status' in status: + if status['working_status'] not in self.DOCKED_STATES: + _LOGGER.debug("WebackApi (WSS) > Set refreshing to 5s") + self._refresh_time = 5 + return + _LOGGER.debug("WebackApi (WSS) > Set refreshing to 120s") + self._refresh_time = 120 async def refresh_handler(self, thing_name, sub_type): _LOGGER.debug("WebackApi (WSS) Start refresh_handler") diff --git a/custom_components/weback_vacuum/__init__.py b/custom_components/weback_vacuum/__init__.py index ffdc137..e138157 100644 --- a/custom_components/weback_vacuum/__init__.py +++ b/custom_components/weback_vacuum/__init__.py @@ -79,9 +79,14 @@ async def async_setup(hass, config): robot["thing_nickname"], robot["sub_type"], robot["thing_status"], - weback_api.wss_url, - weback_api.region_name, - weback_api.jwt_token) + config[DOMAIN].get(CONF_USERNAME), + config[DOMAIN].get(CONF_PASSWORD), + config[DOMAIN].get(CONF_REGION), + config[DOMAIN].get(CONF_LANGUAGE), + config[DOMAIN].get(CONF_APP), + config[DOMAIN].get(CONF_CLIENT_ID), + config[DOMAIN].get(CONF_API_VERSION), + ) hass.data[DOMAIN].append(vacuum_device) if hass.data[DOMAIN]: diff --git a/custom_components/weback_vacuum/vacuum.py b/custom_components/weback_vacuum/vacuum.py index 79250da..cb6dae8 100644 --- a/custom_components/weback_vacuum/vacuum.py +++ b/custom_components/weback_vacuum/vacuum.py @@ -72,7 +72,7 @@ def __init__(self, device: VacDevice): self.device.subscribe(lambda vacdevice: self.schedule_update_ha_state(False)) self._error = None - BASE_FEATURES = ( + self._attr_supported_features = ( VacuumEntityFeature.TURN_ON | VacuumEntityFeature.TURN_OFF | VacuumEntityFeature.STATUS @@ -84,13 +84,8 @@ def __init__(self, device: VacDevice): | VacuumEntityFeature.LOCATE | VacuumEntityFeature.START | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.FAN_SPEED ) - - if self.device.vacuum_or_mop != 0: - _LOGGER.debug(f"Add fan_speed features for this robot") - supported_features = BASE_FEATURES | VacuumEntityFeature.FAN_SPEED - - self._attr_supported_features = supported_features _LOGGER.info(f"Vacuum initialized: {self.name}") @property @@ -109,7 +104,7 @@ def name(self): @property def available(self): - _LOGGER.debug("Vacuum: available", self.device.is_available) + _LOGGER.debug(f"Vacuum: available={self.device.is_available}") """Returns true if vacuum is online""" return self.device.is_available From b19bfb98e49c1a7e15709b2f0edb7edc6af4c86d Mon Sep 17 00:00:00 2001 From: jalcaras Date: Tue, 15 Nov 2022 10:34:06 +0100 Subject: [PATCH 2/3] fix token renewal --- custom_components/weback_vacuum/WebackApi.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/custom_components/weback_vacuum/WebackApi.py b/custom_components/weback_vacuum/WebackApi.py index b671d60..729b125 100644 --- a/custom_components/weback_vacuum/WebackApi.py +++ b/custom_components/weback_vacuum/WebackApi.py @@ -394,6 +394,9 @@ def __init__(self, user, password, region, country, app, client_id, api_version) self.ws = None self._refresh_time = 60 self.sent_counter = 0 + + # Reloading cached creds + self.verify_cached_creds() async def check_credentials(self): """ From d4b829fcfd94b19c4b3560d47670c6ee0b7ccbc0 Mon Sep 17 00:00:00 2001 From: Jezza34000 <57314417+Jezza34000@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:14:08 +0100 Subject: [PATCH 3/3] update README.md --- README.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b52ba51..ef9acd8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![](https://img.shields.io/github/release/Jezza34000/homeassistant_weback_component/all.svg?style=for-the-badge)](https://github.com/Jezza34000/homeassistant_weback_component) [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=for-the-badge)](https://github.com/hacs/integration) -Home Assistant component for controling robot from brand like : Neatsvor / Tesvor / Orfeld / Abir... +Home Assistant component for controlling robot from brand like : Neatsvor / Tesvor / Orfeld / Abir... Who using WeBack or Tesvor apps. ## Installation @@ -36,9 +36,20 @@ weback_vacuum: **application** : if you use "WeBack" do not try to change this field. \ **client_id**, **api_version**, **language**: seems to have no effect. Do not use it. -> **Warning** : Some user are expericing problem with the use of sharing option in WeBack's app. I recommand to not use this option and remove shared account to ensure a proper working +Config example : + +``` YAML +weback_vacuum: + username: mymail@contactme.com + password: mysupersecuredpassword + region: 33 +``` + +> Do not use any leading/ending characters like < > " ' + + +Once configuration set you can restart Home Assistant. +After restart, a new vacuum entity is created with the name defined into WeBack apps. -Once set you can restart Home Assistant. ## Important : API change since 2022 @@ -52,6 +63,17 @@ Go to WeBack app : * Add your robot to your new account +## Issues + +If you find any bug or you're experiencing any problem, please set your Home Assistant log level to debug before opening any issues. And provide full log. +To set your HA into debug level copy this into your `configuration.yaml` : + +``` YAML +logger: + default: error + logs: + custom_components.weback_vacuum: debug +```