Compare commits

..

18 Commits
0.2.35 ... main

Author SHA1 Message Date
Ron
c9df43a1f3 Merge pull request #365 from cyberjunky/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2026-01-01 17:34:32 +01:00
Ron
d6053f2977 Merge pull request #369 from cyberjunky/dependabot/pip/pyupgrade-3.21.2
Bump pyupgrade from 3.21.0 to 3.21.2
2026-01-01 17:33:22 +01:00
Ron
40f4bb9b44 Merge branch 'main' into dependabot/pip/pyupgrade-3.21.2 2026-01-01 17:33:06 +01:00
Ron
6fe3fb3881 Merge pull request #376 from cyberjunky/dependabot/pip/pre-commit-4.5.1
Bump pre-commit from 4.3.0 to 4.5.1
2026-01-01 17:31:47 +01:00
Ron
2b17d44056 Merge pull request #377 from cyberjunky/dependabot/pip/homeassistant-2025.12.4
Bump homeassistant from 2025.10.2 to 2025.12.4
2026-01-01 17:31:36 +01:00
Ron
b2fa46400e Merge pull request #378 from cyberjunky/dependabot/pip/ruff-0.14.10
Bump ruff from 0.14.3 to 0.14.10
2026-01-01 17:31:23 +01:00
dependabot[bot]
51764dbdee Bump ruff from 0.14.3 to 0.14.10
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.3 to 0.14.10.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.14.3...0.14.10)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.14.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 06:06:23 +00:00
dependabot[bot]
a5b6a77480 Bump homeassistant from 2025.10.2 to 2025.12.4
Bumps [homeassistant](https://github.com/home-assistant/core) from 2025.10.2 to 2025.12.4.
- [Release notes](https://github.com/home-assistant/core/releases)
- [Commits](https://github.com/home-assistant/core/compare/2025.10.2...2025.12.4)

---
updated-dependencies:
- dependency-name: homeassistant
  dependency-version: 2025.12.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 06:06:14 +00:00
dependabot[bot]
e61f38f02d Bump pre-commit from 4.3.0 to 4.5.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.3.0 to 4.5.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.3.0...v4.5.1)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-version: 4.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 06:06:04 +00:00
dependabot[bot]
1946869604 Bump pyupgrade from 3.21.0 to 3.21.2
Bumps [pyupgrade](https://github.com/asottile/pyupgrade) from 3.21.0 to 3.21.2.
- [Commits](https://github.com/asottile/pyupgrade/compare/v3.21.0...v3.21.2)

---
updated-dependencies:
- dependency-name: pyupgrade
  dependency-version: 3.21.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 06:13:22 +00:00
dependabot[bot]
d366b64013 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 06:08:05 +00:00
Ron Klinkien
e1371ee532 Fix home-assistant vulnerability 2025-11-06 16:11:20 +01:00
Ron Klinkien
2f2f38839a Handle 403/404 errors gracefully for users without gear
- Change 403 Forbidden errors from ERROR to DEBUG level
- Change 404 Not Found errors from ERROR to DEBUG level
- Add helpful message indicating user may not have gear configured
- Prevents error spam in logs for users without Garmin gear
- Integration continues to work normally without gear sensors
2025-11-06 16:06:02 +01:00
Ron
d6e669494d Enhance README with project badges
Added badges for GitHub release, activity, license, and maintenance to the README.
2025-11-06 15:25:01 +01:00
Ron Klinkien
53b21e92fb Fix userprofile keyerror, remove duplicate gear fetch code 2025-11-06 14:55:29 +01:00
Ron Klinkien
441e4a0018 Reauth fix 2025-11-05 09:58:25 +00:00
Ron Klinkien
8dd4995fb2 Merged several fixes 2025-11-05 09:58:19 +00:00
Ron Klinkien
160a1b96e9 Config migration improvements 2025-11-04 20:59:04 +00:00
6 changed files with 150 additions and 90 deletions

View File

@@ -10,5 +10,5 @@ jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v5"
- uses: "actions/checkout@v6"
- uses: home-assistant/actions/hassfest@master

View File

@@ -1,3 +1,8 @@
[![GitHub Release][releases-shield]][releases]
[![GitHub Activity][commits-shield]][commits]
[![License][license-shield]](LICENSE)
![Project Maintenance][maintenance-shield]
[![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=for-the-badge&logo=paypal)](https://www.paypal.me/cyberjunkynl/)
[![Sponsor on GitHub](https://img.shields.io/badge/Sponsor-GitHub-red.svg?style=for-the-badge&logo=github)](https://github.com/sponsors/cyberjunky)
@@ -281,3 +286,10 @@ If you find this library useful for your projects, please consider supporting it
- Shows appreciation for hundreds of hours of development
Every contribution, no matter the size, makes a difference and is greatly appreciated! 🙏
[releases-shield]: https://img.shields.io/github/release/cyberjunky/home-assistant-garmin_connect.svg?style=for-the-badge
[releases]: https://github.com/cyberjunky/home-assistant-garmin_connect/releases
[commits-shield]: https://img.shields.io/github/commit-activity/y/cyberjunky/home-assistant-garmin_connect.svg?style=for-the-badge
[commits]: https://github.com/cyberjunky/home-assistant-garmin_connect/commits/main
[license-shield]: https://img.shields.io/github/license/cyberjunky/home-assistant-garmin_connect.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/badge/maintainer-cyberjunky-blue.svg?style=for-the-badge

View File

@@ -39,57 +39,96 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"Migrating Garmin Connect config entry from version %s", entry.version)
if entry.version == 1:
# Check if we need to migrate (old entries have username/password, new ones have token)
if CONF_TOKEN not in entry.data:
# Missing token - check if we have username/password to migrate
if CONF_USERNAME in entry.data and CONF_PASSWORD in entry.data:
# Scenario 1: Has USERNAME + PASSWORD but no TOKEN (old auth method)
# Migrate to: ID + TOKEN
if (
CONF_TOKEN not in entry.data
and CONF_USERNAME in entry.data
and CONF_PASSWORD in entry.data
):
_LOGGER.info(
"Migrating Garmin Connect config entry from username/password to token-based authentication")
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
# Determine if user is in China
in_china = hass.config.country == "CN"
# Create temporary API client to get token
api = Garmin(email=username, password=password, is_cn=in_china)
try:
# Login to get the token
await hass.async_add_executor_job(api.login)
# Get the OAuth tokens
tokens = api.garth.dumps()
# Create new data with token, keeping the ID
new_data = {
CONF_ID: entry.data.get(CONF_ID, username),
CONF_TOKEN: tokens,
}
# Update the config entry
hass.config_entries.async_update_entry(entry, data=new_data)
_LOGGER.info(
"Migrating Garmin Connect config entry to token-based authentication")
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
# Determine if user is in China
in_china = hass.config.country == "CN"
# Create temporary API client to get token
api = Garmin(email=username, password=password, is_cn=in_china)
try:
# Login to get the token
await hass.async_add_executor_job(api.login)
# Get the OAuth tokens
tokens = api.garth.dumps()
# Create new data with token, keeping the ID
new_data = {
CONF_ID: entry.data.get(CONF_ID, username),
CONF_TOKEN: tokens,
}
# Update the config entry
hass.config_entries.async_update_entry(entry, data=new_data)
_LOGGER.info(
"Successfully migrated Garmin Connect config entry")
return True
except Exception as err: # pylint: disable=broad-except
_LOGGER.error(
"Failed to migrate Garmin Connect config entry. "
"Please re-authenticate. Error: %s", err
)
return False
else:
# No token and no username/password - config entry is incomplete
_LOGGER.warning(
"Garmin Connect config entry is missing required data (no token and no credentials). "
"This entry cannot be migrated automatically. A reauth flow will be triggered."
)
# Return True to allow setup to proceed, which will trigger reauth via ConfigEntryAuthFailed
"Successfully migrated Garmin Connect config entry")
return True
except Exception as err: # pylint: disable=broad-except
_LOGGER.error(
"Failed to migrate Garmin Connect config entry. "
"Please re-add the integration. Error: %s",
err,
)
return False
# Scenario 2: Has USERNAME + TOKEN but no ID (partially migrated)
# Migrate to: ID + TOKEN (remove USERNAME)
elif (
CONF_ID not in entry.data
and CONF_USERNAME in entry.data
and CONF_TOKEN in entry.data
):
_LOGGER.info(
"Migrating Garmin Connect config entry: converting USERNAME to ID")
username = entry.data[CONF_USERNAME]
# Create new data with ID instead of USERNAME
new_data = {
CONF_ID: username,
CONF_TOKEN: entry.data[CONF_TOKEN],
}
# Update the config entry
hass.config_entries.async_update_entry(entry, data=new_data)
_LOGGER.info(
"Successfully migrated Garmin Connect config entry from USERNAME to ID")
return True
# Scenario 3: Missing both TOKEN and credentials (incomplete/corrupted)
# Add placeholder ID to allow reauth flow
elif CONF_TOKEN not in entry.data:
if CONF_ID not in entry.data:
_LOGGER.info(
"Config entry missing CONF_ID, adding placeholder for reauth flow")
new_data = {
**entry.data,
CONF_ID: entry.entry_id, # Use entry_id as fallback
}
hass.config_entries.async_update_entry(entry, data=new_data)
_LOGGER.info(
"Garmin Connect config entry is incomplete (missing token). "
"Reauthentication will be required to complete setup."
)
return True
return True
@@ -162,14 +201,18 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
try:
# Check if the token exists in the entry data
if CONF_TOKEN not in self.entry.data:
_LOGGER.warning(
_LOGGER.info(
"Token not found in config entry. Reauthentication required."
)
raise ConfigEntryAuthFailed(
"Token not found in config entry. This may be an old or incomplete configuration. "
"A reauthentication flow will be initiated. Please check your notifications."
)
raise ConfigEntryAuthFailed(
"Token not found. Please reauthenticate via the notification prompt.")
await self.hass.async_add_executor_job(self.api.login, self.entry.data[CONF_TOKEN])
except ConfigEntryAuthFailed:
# Re-raise ConfigEntryAuthFailed without logging as "unknown error"
raise
except GarminConnectAuthenticationError as err:
_LOGGER.error(
"Authentication error occurred during login: %s", err.response.text)
@@ -342,10 +385,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(
@@ -395,36 +442,34 @@ 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)
else:
_LOGGER.debug("No gear defaults data found")
_LOGGER.debug("No gear data available, skipping gear stats and defaults fetch")
except GarminConnectAuthenticationError as err:
_LOGGER.error(
"Authentication error occurred while fetching Gear data: %s", err.response.text)
@@ -441,9 +486,12 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
if err.response.status_code == 401:
_LOGGER.error(
"Authentication error while fetching Gear data: %s", err.response.text)
elif err.response.status_code == 403:
_LOGGER.debug(
"Access forbidden while fetching Gear data (user may not have gear configured): %s", err.response.text)
elif err.response.status_code == 404:
_LOGGER.error(
"URL not found error while fetching Gear data: %s", err.response.text)
_LOGGER.debug(
"Gear data not found (user may not have gear configured): %s", err.response.text)
elif err.response.status_code == 429:
_LOGGER.error(
"Too many requests error while fetching Gear data: %s", err.response.text)

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/cyberjunky/home-assistant-garmin_connect/issues",
"requirements": ["garminconnect>=0.2.31"],
"version": "0.2.35"
"version": "0.2.38"
}

View File

@@ -1 +1 @@
homeassistant==2025.1.4
homeassistant==2025.12.4

View File

@@ -1,8 +1,8 @@
--requirement requirements_base.txt
codespell==2.4.1
isort==7.0.0
pre-commit==4.3.0
pre-commit==4.5.1
pre-commit-hooks==6.0.0
pyupgrade==3.21.0
ruff==0.14.3
pyupgrade==3.21.2
ruff==0.14.10
vulture==2.14