From 4cd361682dd165f52a42dcd25c3267c1865ef47a Mon Sep 17 00:00:00 2001 From: jaydeethree <34945248+jaydeethree@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:42:24 -0800 Subject: [PATCH] Attempt to improve reliability: * Retry API calls that fail * Only mark entities unavailable if they come from an API call that failed - this way if some of the API calls succeed but others fail, we will still get data from the ones that succeeded * Update user agent to a current version of Chrome (I doubt this actually matters but it's worth a try) --- .../weatherdotcom/coordinator.py | 105 +++++++++++------- custom_components/weatherdotcom/sensor.py | 2 + 2 files changed, 69 insertions(+), 38 deletions(-) diff --git a/custom_components/weatherdotcom/coordinator.py b/custom_components/weatherdotcom/coordinator.py index 8cf55fd..9fee1d3 100644 --- a/custom_components/weatherdotcom/coordinator.py +++ b/custom_components/weatherdotcom/coordinator.py @@ -117,53 +117,80 @@ async def get_weather(self): """Get weather data.""" headers = { 'Accept-Encoding': 'gzip', - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36" + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" } - try: - async with async_timeout.timeout(10): - url = self._build_url(_RESOURCECURRENT) - response = await self._session.get(url, headers=headers) - result_current = await response.json(content_type=None) - if result_current is None: - raise ValueError('NO CURRENT RESULT') - self._check_errors(url, result_current) - - async with async_timeout.timeout(10): - url = self._build_url(_RESOURCEFORECASTDAILY) - response = await self._session.get(url, headers=headers) - result_forecast_daily = await response.json(content_type=None) - - if result_forecast_daily is None: - raise ValueError('NO DAILY FORECAST RESULT') - self._check_errors(url, result_forecast_daily) - - async with async_timeout.timeout(10): - url = self._build_url(_RESOURCEFORECASTHOURLY) - response = await self._session.get(url, headers=headers) - result_forecast_hourly = await response.json(content_type=None) - - if result_forecast_hourly is None: - raise ValueError('NO HOURLY FORECAST RESULT') - self._check_errors(url, result_forecast_hourly) - + current_error = False + daily_error = False + hourly_error = False + result_current = None + result_forecast_daily = None + result_forecast_hourly = None + + for attempt in range(2): + try: + async with async_timeout.timeout(10): + url = self._build_url(_RESOURCECURRENT) + response = await self._session.get(url, headers=headers) + result_current = await response.json(content_type=None) + if result_current is None: + raise ValueError('No weather data found') + self._check_errors(url, result_current) + break + except (ValueError, asyncio.TimeoutError, aiohttp.ClientError) as err: + if attempt == 1: + _LOGGER.error('Error getting current weather: %s', err) + current_error = True + else: + _LOGGER.error('Error getting current weather, will retry: %s', err) + await asyncio.sleep(5) + + for attempt in range(2): + try: + async with async_timeout.timeout(10): + url = self._build_url(_RESOURCEFORECASTDAILY) + response = await self._session.get(url, headers=headers) + result_forecast_daily = await response.json(content_type=None) + if result_forecast_daily is None: + raise ValueError('No weather data found') + self._check_errors(url, result_forecast_daily) + break + except (ValueError, asyncio.TimeoutError, aiohttp.ClientError) as err: + if attempt == 1: + _LOGGER.error('Error getting daily weather: %s', err) + daily_error = True + else: + _LOGGER.error('Error getting daily weather, will retry: %s', err) + await asyncio.sleep(5) + + for attempt in range(2): + try: + async with async_timeout.timeout(10): + url = self._build_url(_RESOURCEFORECASTHOURLY) + response = await self._session.get(url, headers=headers) + result_forecast_hourly = await response.json(content_type=None) + if result_forecast_hourly is None: + raise ValueError('No weather data found') + self._check_errors(url, result_forecast_hourly) + break + except (ValueError, asyncio.TimeoutError, aiohttp.ClientError) as err: + if attempt == 1: + _LOGGER.error('Error getting hourly weather: %s', err) + hourly_error = True + else: + _LOGGER.error('Error getting hourly weather, will retry: %s', err) + await asyncio.sleep(5) + + if current_error and daily_error and hourly_error: + raise UpdateFailed('Failed to get weather data') + else: result = { RESULTS_CURRENT: result_current, RESULTS_FORECAST_DAILY: result_forecast_daily, RESULTS_FORECAST_HOURLY: result_forecast_hourly, } - self.data = result - return result - except ValueError as err: - _LOGGER.error("Check Weather.com API %s", err.args) - raise UpdateFailed(err) - except (asyncio.TimeoutError, aiohttp.ClientError) as err: - _LOGGER.error("Error fetching Weather.com data: %s", repr(err)) - raise UpdateFailed(err) - # _LOGGER.debug(f'Weather.com data {self.data}') - def _build_url(self, baseurl): baseurl += '&language={language}' baseurl += _RESOURCESHARED @@ -190,6 +217,8 @@ def _check_errors(self, url: str, response: dict): ) def get_current(self, field): + if self.data[RESULTS_CURRENT] == None: + return None return self.data[RESULTS_CURRENT][field] def get_forecast_daily(self, field, period=0): diff --git a/custom_components/weatherdotcom/sensor.py b/custom_components/weatherdotcom/sensor.py index 08c266c..39caa87 100644 --- a/custom_components/weatherdotcom/sensor.py +++ b/custom_components/weatherdotcom/sensor.py @@ -125,6 +125,8 @@ def _get_sensor_data( ) -> Any: """Get sensor data.""" # windGust is often null. When it is, set it to windSpeed instead. + if sensors[RESULTS_CURRENT] == None: + return None if kind == FIELD_WINDGUST and sensors[RESULTS_CURRENT][kind] == None: return sensors[RESULTS_CURRENT][FIELD_WINDSPEED] else: