From a34dd7abfc445fb55cdab49208aba1ce7d4396b7 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Thu, 6 Nov 2025 15:49:58 +0100 Subject: [PATCH] Fix KeyError for userProfileId and add reload support - Fix KeyError when userProfileId is missing from summary after HA updates - Check if userProfileId exists before accessing it in 3 locations - Remove duplicate get_gear() call, reuse data from first fetch - Add async_reload_entry() function for reload without restart - Update README with reload instructions - Improves integration reliability and user experience --- README.md | 13 ++++ custom_components/garmin_connect/__init__.py | 66 +++++++++++--------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index e195071..7bdd5fb 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,19 @@ After successful set up a standard set of sensors are enabled. You can enable mo The integration will fetch new data every 5 minutes, make sure your devices sync to the Garmin Connect website. +### Reload Without Restart + +This integration supports **reloading without restarting Home Assistant**: +- Go to **Settings** → **Devices & Services** → **Garmin Connect** +- Click the **three dots (⋮)** → **Reload** + +This is useful after: +- Updating the integration via HACS +- Changing configuration options +- Troubleshooting issues + +No need to restart your entire Home Assistant instance! + ## Available Sensors Not every sensor holds meaningful values, it depends on the tracking and health devices you use, or the apps you have connected. diff --git a/custom_components/garmin_connect/__init__.py b/custom_components/garmin_connect/__init__.py index aec54f8..63415a6 100644 --- a/custom_components/garmin_connect/__init__.py +++ b/custom_components/garmin_connect/__init__.py @@ -166,6 +166,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return unload_ok +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload config entry.""" + await async_unload_entry(hass, entry) + await async_setup_entry(hass, entry) + + class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator): """Garmin Connect Data Update Coordinator.""" @@ -390,10 +396,14 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator): # Gear data try: - gear = await self.hass.async_add_executor_job( - self.api.get_gear, summary[Gear.USERPROFILE_ID] - ) - _LOGGER.debug("Gear data fetched: %s", gear) + # Check if userProfileId exists in summary before fetching gear data + if Gear.USERPROFILE_ID in summary: + gear = await self.hass.async_add_executor_job( + self.api.get_gear, summary[Gear.USERPROFILE_ID] + ) + _LOGGER.debug("Gear data fetched: %s", gear) + else: + _LOGGER.debug("No userProfileId found in summary, skipping gear data fetch") # Fitness age data fitnessage_data = await self.hass.async_add_executor_job( @@ -441,33 +451,33 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator): return {} try: - # Gear data like shoes, bike, etc. - gear = await self.hass.async_add_executor_job( - self.api.get_gear, summary[Gear.USERPROFILE_ID] - ) + # Use gear data from the first fetch if available if gear: - _LOGGER.debug("Gear data fetched: %s", gear) - else: - _LOGGER.debug("No gear data found") + # Gear stats data like distance, time, etc. + tasks: list[Awaitable] = [ + self.hass.async_add_executor_job( + self.api.get_gear_stats, gear_item[Gear.UUID]) + for gear_item in gear + ] + gear_stats = await asyncio.gather(*tasks) + if gear_stats: + _LOGGER.debug("Gear statistics data fetched: %s", gear_stats) + else: + _LOGGER.debug("No gear statistics data found") - # Gear stats data like distance, time, etc. - tasks: list[Awaitable] = [ - self.hass.async_add_executor_job( - self.api.get_gear_stats, gear_item[Gear.UUID]) - for gear_item in gear - ] - gear_stats = await asyncio.gather(*tasks) - if gear_stats: - _LOGGER.debug("Gear statistics data fetched: %s", gear_stats) + # Gear defaults data like shoe, bike, etc. + if Gear.USERPROFILE_ID in summary: + gear_defaults = await self.hass.async_add_executor_job( + self.api.get_gear_defaults, summary[Gear.USERPROFILE_ID] + ) + if gear_defaults: + _LOGGER.debug("Gear defaults data fetched: %s", gear_defaults) + else: + _LOGGER.debug("No gear defaults data found") + else: + _LOGGER.debug("No userProfileId found in summary, skipping gear defaults fetch") else: - _LOGGER.debug("No gear statistics data found") - - # Gear defaults data like shoe, bike, etc. - gear_defaults = await self.hass.async_add_executor_job( - self.api.get_gear_defaults, summary[Gear.USERPROFILE_ID] - ) - if gear_defaults: - _LOGGER.debug("Gear defaults data fetched: %s", gear_defaults) + _LOGGER.debug("No gear data available, skipping gear stats and defaults fetch") else: _LOGGER.debug("No gear defaults data found") except GarminConnectAuthenticationError as err: