diff --git a/custom_components/smartthinq_sensors/__init__.py b/custom_components/smartthinq_sensors/__init__.py index d0061ada..47157d6a 100644 --- a/custom_components/smartthinq_sensors/__init__.py +++ b/custom_components/smartthinq_sensors/__init__.py @@ -54,6 +54,7 @@ MAX_RETRIES = 3 MAX_CONN_RETRIES = 2 MAX_LOOP_WARN = 3 +MAX_UPDATE_FAIL_ALLOWED = 10 # not stress to match cloud if multiple call MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) @@ -244,6 +245,9 @@ def __init__(self, device, name): self._retry_count = 0 self._disconnected = True self._not_logged = False + self._update_fail_count = 0 + self._not_logged_count = 0 + self._refresh_gateway = False @property def available(self) -> bool: @@ -297,9 +301,14 @@ def init_device(self): def _restart_monitor(self): """Restart the device monitor""" + refresh_gateway = False + if self._refresh_gateway: + refresh_gateway = True + self._refresh_gateway = False + try: if self._not_logged: - self._device.client.refresh() + self._device.client.refresh(refresh_gateway) self._not_logged = False self._disconnected = True @@ -311,11 +320,11 @@ def _restart_monitor(self): self._disconnected = True except NotLoggedInError: - _LOGGER.info("ThinQ Session expired. Refreshing.") + _LOGGER.debug("ThinQ Session expired. Refreshing.") self._not_logged = True except (reqExc.ConnectionError, reqExc.ConnectTimeout, reqExc.ReadTimeout): - _LOGGER.error("Connection to ThinQ failed. Network connection error") + _LOGGER.debug("Connection to ThinQ failed. Network connection error") self._disconnected = True self._not_logged = True @@ -328,6 +337,14 @@ def device_update(self): """Update device state""" _LOGGER.debug("Updating smartthinq device %s.", self.name) + if self._disconnected or self._not_logged: + if self._update_fail_count < MAX_UPDATE_FAIL_ALLOWED: + self._update_fail_count += 1 + if self._not_logged: + self._not_logged_count += 1 + else: + self._not_logged_count = 0 + for iteration in range(MAX_RETRIES): _LOGGER.debug("Polling...") @@ -339,10 +356,35 @@ def device_update(self): self._retry_count = 0 self._restart_monitor() + if self._disconnected or self._not_logged: + if self._update_fail_count >= MAX_UPDATE_FAIL_ALLOWED: + if self._state.is_on: + _LOGGER.warning( + "Connection to ThinQ for device %s failed. Reset device status.", + self.name, + ) + self._not_logged_count = 0 + self._state = self._device.reset_status() + return + elif ( + self._not_logged_count == MAX_UPDATE_FAIL_ALLOWED or ( + self._not_logged_count > 0 and + self._not_logged_count % 60 == 0 + ) + ): + _LOGGER.error( + "Connection to ThinQ for device %s is not available. Connection will be retried...", + self.name, + ) + if self._not_logged_count >= 60: + self._refresh_gateway = True + self._not_logged_count += 1 + if self._disconnected: return if not (self._disconnected or self._not_logged): + try: state = self._device.poll() @@ -355,7 +397,7 @@ def device_update(self): # time.sleep(1) except (reqExc.ConnectionError, reqExc.ConnectTimeout, reqExc.ReadTimeout): - _LOGGER.error("Connection to ThinQ failed. Network connection error") + _LOGGER.debug("Connection to ThinQ failed. Network connection error") self._not_logged = True return @@ -369,6 +411,7 @@ def device_update(self): # l = dir(state) # _LOGGER.debug('Status attributes: %s', l) + self._update_fail_count = 0 self._retry_count = 0 self._state = state diff --git a/custom_components/smartthinq_sensors/const.py b/custom_components/smartthinq_sensors/const.py index 2ae391ec..e87fa12c 100644 --- a/custom_components/smartthinq_sensors/const.py +++ b/custom_components/smartthinq_sensors/const.py @@ -2,7 +2,7 @@ Support to interface with LGE ThinQ Devices. """ -__version__ = "0.5.0" +__version__ = "0.5.1" PROJECT_URL = "https://github.com/ollo69/ha-smartthinq-sensors/" ISSUE_URL = "{}issues".format(PROJECT_URL) diff --git a/custom_components/smartthinq_sensors/wideq/core.py b/custom_components/smartthinq_sensors/wideq/core.py index d418880b..630dea53 100644 --- a/custom_components/smartthinq_sensors/wideq/core.py +++ b/custom_components/smartthinq_sensors/wideq/core.py @@ -293,6 +293,11 @@ def refresh(self): new_access_token = refresh_auth(self.gateway.oauth_root, self.refresh_token) return Auth(self.gateway, new_access_token, self.refresh_token) + def refresh_gateway(self, gateway): + """Refresh the gateway. + """ + self.gateway = gateway + def dump(self): return {"access_token": self.access_token, "refresh_token": self.refresh_token} @@ -573,7 +578,11 @@ def dump(self) -> Dict[str, Any]: return out - def refresh(self) -> None: + def refresh(self, refresh_gateway=False) -> None: + if refresh_gateway: + self._gateway = None + if not self._gateway: + self._auth.refresh_gateway(self.gateway) self._auth = self.auth.refresh() self._session, self._devices = self.auth.start_session() diff --git a/custom_components/smartthinq_sensors/wideq/core_v2.py b/custom_components/smartthinq_sensors/wideq/core_v2.py index 894385ea..a5fae4cf 100644 --- a/custom_components/smartthinq_sensors/wideq/core_v2.py +++ b/custom_components/smartthinq_sensors/wideq/core_v2.py @@ -348,6 +348,11 @@ def refresh(self): self.user_number, ) + def refresh_gateway(self, gateway): + """Refresh the gateway. + """ + self.gateway = gateway + def dump(self): return { "access_token": self.access_token, @@ -692,7 +697,11 @@ def dump(self) -> Dict[str, Any]: return out - def refresh(self) -> None: + def refresh(self, refresh_gateway=False) -> None: + if refresh_gateway: + self._gateway = None + if not self._gateway: + self._auth.refresh_gateway(self.gateway) self._auth = self.auth.refresh() self._session = self.auth.start_session() # self._device = None diff --git a/custom_components/smartthinq_sensors/wideq/device.py b/custom_components/smartthinq_sensors/wideq/device.py index 00ff7c12..95eaf8bc 100644 --- a/custom_components/smartthinq_sensors/wideq/device.py +++ b/custom_components/smartthinq_sensors/wideq/device.py @@ -684,6 +684,10 @@ def status(self): return None return self._status + def reset_status(self): + self._status = None + return self._status + def _set_control(self, key, value): """Set a device's control for `key` to `value`. """ @@ -841,7 +845,11 @@ def int_or_none(value): @property def has_data(self): - return self._data is not None + return True if self._data else False + + @property + def is_on(self) -> bool: + return False @property def is_info_v2(self): diff --git a/custom_components/smartthinq_sensors/wideq/dishwasher.py b/custom_components/smartthinq_sensors/wideq/dishwasher.py index 017100e0..15b2d664 100644 --- a/custom_components/smartthinq_sensors/wideq/dishwasher.py +++ b/custom_components/smartthinq_sensors/wideq/dishwasher.py @@ -9,9 +9,17 @@ ) STATE_DISHWASHER_POWER_OFF = "@DW_STATE_POWER_OFF_W" -STATE_DISHWASHER_COMPLETE = "@DW_STATE_COMPLETE_W" -STATE_DISHWASHER_ERROR_NO_ERROR = "No_Error" +STATE_DISHWASHER_END = [ + "@DW_STATE_END_W", + "@DW_STATE_COMPLETE_W", +] STATE_DISHWASHER_ERROR_OFF = "OFF" +STATE_DISHWASHER_ERROR_NO_ERROR = [ + "ERROR_NOERROR", + "ERROR_NOERROR_TITLE", + "No Error", + "No_Error", +] _LOGGER = logging.getLogger(__name__) @@ -21,6 +29,10 @@ class DishWasherDevice(Device): def __init__(self, client, device): super().__init__(client, device, DishWasherStatus(self, None)) + def reset_status(self): + self._status = DishWasherStatus(self, None) + return self._status + def poll(self) -> Optional["DishWasherStatus"]: """Poll the device's current state.""" @@ -48,24 +60,27 @@ def _get_run_state(self): if not self._run_state: state = self.lookup_enum(["State", "state"]) if not state: - return STATE_DISHWASHER_POWER_OFF - self._run_state = state + self._run_state = STATE_DISHWASHER_POWER_OFF + else: + self._run_state = state return self._run_state def _get_process(self): if not self._process: process = self.lookup_enum(["Process", "process"]) if not process: - return STATE_OPTIONITEM_NONE - self._process = process + self._process = STATE_OPTIONITEM_NONE + else: + self._process = process return self._process def _get_error(self): if not self._error: error = self.lookup_reference(["Error", "error"], ref_key="title") if not error: - return STATE_DISHWASHER_ERROR_OFF - self._error = error + self._error = STATE_DISHWASHER_ERROR_OFF + else: + self._error = error return self._error @property @@ -77,18 +92,20 @@ def is_on(self): def is_run_completed(self): run_state = self._get_run_state() process = self._get_process() - if run_state == STATE_DISHWASHER_COMPLETE or ( - run_state == STATE_DISHWASHER_POWER_OFF and process == STATE_DISHWASHER_COMPLETE + if run_state in STATE_DISHWASHER_END or ( + run_state == STATE_DISHWASHER_POWER_OFF and process in STATE_DISHWASHER_END ): return True return False @property def is_error(self): + if not self.is_on: + return False error = self._get_error() - if error != STATE_DISHWASHER_ERROR_NO_ERROR and error != STATE_DISHWASHER_ERROR_OFF: - return True - return False + if error in STATE_DISHWASHER_ERROR_NO_ERROR or error == STATE_DISHWASHER_ERROR_OFF: + return False + return True @property def run_state(self): @@ -106,11 +123,9 @@ def process_state(self): @property def error_state(self): - if not self.is_on: + if not self.is_error: return STATE_OPTIONITEM_NONE error = self._get_error() - if error == STATE_DISHWASHER_ERROR_NO_ERROR: - return STATE_OPTIONITEM_NONE return self._device.get_enum_text(error) @property diff --git a/custom_components/smartthinq_sensors/wideq/dryer.py b/custom_components/smartthinq_sensors/wideq/dryer.py index 702aa342..204aa2c8 100644 --- a/custom_components/smartthinq_sensors/wideq/dryer.py +++ b/custom_components/smartthinq_sensors/wideq/dryer.py @@ -9,9 +9,17 @@ ) STATE_DRYER_POWER_OFF = "@WM_STATE_POWER_OFF_W" -STATE_DRYER_END = "@WM_STATE_END_W" -STATE_DRYER_ERROR_NO_ERROR = "ERROR_NOERROR_TITLE" +STATE_DRYER_END = [ + "@WM_STATE_END_W", + "@WM_STATE_COMPLETE_W", +] STATE_DRYER_ERROR_OFF = "OFF" +STATE_DRYER_ERROR_NO_ERROR = [ + "ERROR_NOERROR", + "ERROR_NOERROR_TITLE", + "No Error", + "No_Error", +] _LOGGER = logging.getLogger(__name__) @@ -21,6 +29,10 @@ class DryerDevice(Device): def __init__(self, client, device): super().__init__(client, device, DryerStatus(self, None)) + def reset_status(self): + self._status = DryerStatus(self, None) + return self._status + def poll(self) -> Optional["DryerStatus"]: """Poll the device's current state.""" @@ -48,24 +60,27 @@ def _get_run_state(self): if not self._run_state: state = self.lookup_enum(["State", "state"]) if not state: - return STATE_DRYER_POWER_OFF - self._run_state = state + self._run_state = STATE_DRYER_POWER_OFF + else: + self._run_state = state return self._run_state def _get_pre_state(self): if not self._pre_state: state = self.lookup_enum(["PreState", "preState"]) if not state: - return STATE_DRYER_POWER_OFF - self._pre_state = state + self._pre_state = STATE_DRYER_POWER_OFF + else: + self._pre_state = state return self._pre_state def _get_error(self): if not self._error: error = self.lookup_reference(["Error", "error"], ref_key="title") if not error: - return STATE_DRYER_ERROR_OFF - self._error = error + self._error = STATE_DRYER_ERROR_OFF + else: + self._error = error return self._error @property @@ -77,18 +92,20 @@ def is_on(self): def is_run_completed(self): run_state = self._get_run_state() pre_state = self._get_pre_state() - if run_state == STATE_DRYER_END or ( - run_state == STATE_DRYER_POWER_OFF and pre_state == STATE_DRYER_END + if run_state in STATE_DRYER_END or ( + run_state == STATE_DRYER_POWER_OFF and pre_state in STATE_DRYER_END ): return True return False @property def is_error(self): + if not self.is_on: + return False error = self._get_error() - if error != STATE_DRYER_ERROR_NO_ERROR and error != STATE_DRYER_ERROR_OFF: - return True - return False + if error in STATE_DRYER_ERROR_NO_ERROR or error == STATE_DRYER_ERROR_OFF: + return False + return True @property def run_state(self): @@ -106,11 +123,9 @@ def pre_state(self): @property def error_state(self): - if not self.is_on: + if not self.is_error: return STATE_OPTIONITEM_NONE error = self._get_error() - if error == STATE_DRYER_ERROR_NO_ERROR: - return STATE_OPTIONITEM_NONE return self._device.get_enum_text(error) @property diff --git a/custom_components/smartthinq_sensors/wideq/refrigerator.py b/custom_components/smartthinq_sensors/wideq/refrigerator.py index edd5d8a6..045dc6b3 100644 --- a/custom_components/smartthinq_sensors/wideq/refrigerator.py +++ b/custom_components/smartthinq_sensors/wideq/refrigerator.py @@ -26,6 +26,10 @@ class RefrigeratorDevice(Device): def __init__(self, client, device): super().__init__(client, device, RefrigeratorStatus(self, None)) + def reset_status(self): + self._status = RefrigeratorStatus(self, None) + return self._status + def poll(self) -> Optional["RefrigeratorStatus"]: """Poll the device's current state.""" @@ -92,7 +96,7 @@ def _get_temp_val_v2(self, key): @property def is_on(self): - return True + return self.has_data @property def temp_refrigerator(self): diff --git a/custom_components/smartthinq_sensors/wideq/washer.py b/custom_components/smartthinq_sensors/wideq/washer.py index 02a3d435..459f9541 100644 --- a/custom_components/smartthinq_sensors/wideq/washer.py +++ b/custom_components/smartthinq_sensors/wideq/washer.py @@ -9,9 +9,17 @@ ) STATE_WASHER_POWER_OFF = "@WM_STATE_POWER_OFF_W" -STATE_WASHER_END = "@WM_STATE_END_W" -STATE_WASHER_ERROR_NO_ERROR = "ERROR_NOERROR_TITLE" +STATE_WASHER_END = [ + "@WM_STATE_END_W", + "@WM_STATE_COMPLETE_W", +] STATE_WASHER_ERROR_OFF = "OFF" +STATE_WASHER_ERROR_NO_ERROR = [ + "ERROR_NOERROR", + "ERROR_NOERROR_TITLE", + "No Error", + "No_Error", +] _LOGGER = logging.getLogger(__name__) @@ -21,6 +29,10 @@ class WasherDevice(Device): def __init__(self, client, device): super().__init__(client, device, WasherStatus(self, None)) + def reset_status(self): + self._status = WasherStatus(self, None) + return self._status + def poll(self) -> Optional["WasherStatus"]: """Poll the device's current state.""" @@ -48,24 +60,27 @@ def _get_run_state(self): if not self._run_state: state = self.lookup_enum(["State", "state"]) if not state: - return STATE_WASHER_POWER_OFF - self._run_state = state + self._run_state = STATE_WASHER_POWER_OFF + else: + self._run_state = state return self._run_state def _get_pre_state(self): if not self._pre_state: state = self.lookup_enum(["PreState", "preState"]) if not state: - return STATE_WASHER_POWER_OFF - self._pre_state = state + self._pre_state = STATE_WASHER_POWER_OFF + else: + self._pre_state = state return self._pre_state def _get_error(self): if not self._error: error = self.lookup_reference(["Error", "error"], ref_key="title") if not error: - return STATE_WASHER_ERROR_OFF - self._error = error + self._error = STATE_WASHER_ERROR_OFF + else: + self._error = error return self._error @property @@ -77,18 +92,20 @@ def is_on(self): def is_run_completed(self): run_state = self._get_run_state() pre_state = self._get_pre_state() - if run_state == STATE_WASHER_END or ( - run_state == STATE_WASHER_POWER_OFF and pre_state == STATE_WASHER_END + if run_state in STATE_WASHER_END or ( + run_state == STATE_WASHER_POWER_OFF and pre_state in STATE_WASHER_END ): return True return False @property def is_error(self): + if not self.is_on: + return False error = self._get_error() - if error != STATE_WASHER_ERROR_NO_ERROR and error != STATE_WASHER_ERROR_OFF: - return True - return False + if error in STATE_WASHER_ERROR_NO_ERROR or error == STATE_WASHER_ERROR_OFF: + return False + return True @property def run_state(self): @@ -106,11 +123,9 @@ def pre_state(self): @property def error_state(self): - if not self.is_on: + if not self.is_error: return STATE_OPTIONITEM_NONE error = self._get_error() - if error == STATE_WASHER_ERROR_NO_ERROR: - return STATE_OPTIONITEM_NONE return self._device.get_enum_text(error) @property