summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--python-garminconnect.spec1788
-rw-r--r--sources1
3 files changed, 1790 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..c37621f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/garminconnect-0.1.55.tar.gz
diff --git a/python-garminconnect.spec b/python-garminconnect.spec
new file mode 100644
index 0000000..8823e3e
--- /dev/null
+++ b/python-garminconnect.spec
@@ -0,0 +1,1788 @@
+%global _empty_manifest_terminate_build 0
+Name: python-garminconnect
+Version: 0.1.55
+Release: 1
+Summary: Python 3 API wrapper for Garmin Connect
+License: MIT license
+URL: https://github.com/cyberjunky/python-garminconnect
+Source0: https://mirrors.aliyun.com/pypi/web/packages/87/7f/ff45ac4ed757230bc7945e06c6dfc0f3598ce08d1c431914d58da712d33e/garminconnect-0.1.55.tar.gz
+BuildArch: noarch
+
+
+%description
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
+
+# Python: Garmin Connect
+
+Python 3 API wrapper for Garmin Connect to get your statistics.
+
+## About
+
+This package allows you to request garmin device, activity and health data from your Garmin Connect account.
+See <https://connect.garmin.com/>
+
+## Installation
+
+```bash
+pip3 install garminconnect
+```
+
+## API Demo Program
+
+I wrote this for testing and playing with all available/known API calls.
+If you run it from the python-garmin connect directory it will use the library code beneath it, so you can develop without reinstalling the package.
+
+The code also demonstrates how to implement session saving and re-using of the cookies.
+
+You can set environment variables with your credentials like so, this is optional:
+
+```bash
+export EMAIL=<your garmin email>
+export PASSWORD=<your garmin password>
+```
+
+Install the pre-requisites for the example program (not all are needed for using the library package):
+
+```bash
+pip3 install cloudscraper readchar requests pwinput
+```
+
+Or you can just run the program and enter your credentials when asked, it will create and save a session file and use that until it's outdated/invalid.
+
+```
+python3 ./example.py
+*** Garmin Connect API Demo by cyberjunky ***
+
+1 -- Get full name
+2 -- Get unit system
+3 -- Get activity data for '2023-03-10'
+4 -- Get activity data for '2023-03-10' (compatible with garminconnect-ha)
+5 -- Get body composition data for '2023-03-10' (compatible with garminconnect-ha)
+6 -- Get body composition data for from '2023-03-03' to '2023-03-10' (to be compatible with garminconnect-ha)
+7 -- Get stats and body composition data for '2023-03-10'
+8 -- Get steps data for '2023-03-10'
+9 -- Get heart rate data for '2023-03-10'
+0 -- Get training readiness data for '2023-03-10'
+- -- Get daily step data for '2023-03-03' to '2023-03-10'
+/ -- Get body battery data for '2023-03-03' to '2023-03-10'
+! -- Get floors data for '2023-03-03'
+? -- Get blood pressure data for '2023-03-03' to '2023-03-10'
+. -- Get training status data for '2023-03-10'
+a -- Get resting heart rate data for 2023-03-10'
+b -- Get hydration data for '2023-03-10'
+c -- Get sleep data for '2023-03-10'
+d -- Get stress data for '2023-03-10'
+e -- Get respiration data for '2023-03-10'
+f -- Get SpO2 data for '2023-03-10'
+g -- Get max metric data (like vo2MaxValue and fitnessAge) for '2023-03-10'
+h -- Get personal record for user
+i -- Get earned badges for user
+j -- Get adhoc challenges data from start '0' and limit '100'
+k -- Get available badge challenges data from '1' and limit '100'
+l -- Get badge challenges data from '1' and limit '100'
+m -- Get non completed badge challenges data from '1' and limit '100'
+n -- Get activities data from start '0' and limit '100'
+o -- Get last activity
+p -- Download activities data by date from '2023-03-03' to '2023-03-10'
+r -- Get all kinds of activities data from '0'
+s -- Upload activity data from file 'MY_ACTIVITY.fit'
+t -- Get all kinds of Garmin device info
+u -- Get active goals
+v -- Get future goals
+w -- Get past goals
+y -- Get all Garmin device alarms
+x -- Get Heart Rate Variability data (HRV) for '2023-03-10'
+z -- Get progress summary from '2023-03-03' to '2023-03-10' for all metrics
+A -- Get gear, the defaults, activity types and statistics
+Z -- Logout Garmin Connect portal
+q -- Exit
+
+Make your selection:
+
+```
+
+This is some example code, and probably older than the latest code which can be found in 'example.py'.
+
+```python
+#!/usr/bin/env python3
+"""
+pip3 install cloudscraper requests readchar pwinput
+
+export EMAIL=<your garmin email>
+export PASSWORD=<your garmin password>
+
+"""
+import datetime
+import json
+import logging
+import os
+import sys
+
+import requests
+import pwinput
+import readchar
+
+from garminconnect import (
+ Garmin,
+ GarminConnectAuthenticationError,
+ GarminConnectConnectionError,
+ GarminConnectTooManyRequestsError,
+)
+
+# Configure debug logging
+# logging.basicConfig(level=logging.DEBUG)
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+# Load environment variables if defined
+email = os.getenv("EMAIL")
+password = os.getenv("PASSWORD")
+api = None
+
+# Example selections and settings
+today = datetime.date.today()
+startdate = today - datetime.timedelta(days=7) # Select past week
+start = 0
+limit = 100
+start_badge = 1 # Badge related calls calls start counting at 1
+activitytype = "" # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
+activityfile = "MY_ACTIVITY.fit" # Supported file types are: .fit .gpx .tcx
+
+menu_options = {
+ "1": "Get full name",
+ "2": "Get unit system",
+ "3": f"Get activity data for '{today.isoformat()}'",
+ "4": f"Get activity data for '{today.isoformat()}' (compatible with garminconnect-ha)",
+ "5": f"Get body composition data for '{today.isoformat()}' (compatible with garminconnect-ha)",
+ "6": f"Get body composition data for from '{startdate.isoformat()}' to '{today.isoformat()}' (to be compatible with garminconnect-ha)",
+ "7": f"Get stats and body composition data for '{today.isoformat()}'",
+ "8": f"Get steps data for '{today.isoformat()}'",
+ "9": f"Get heart rate data for '{today.isoformat()}'",
+ "0": f"Get training readiness data for '{today.isoformat()}'",
+ "-": f"Get daily step data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "/": f"Get body battery data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "!": f"Get floors data for '{startdate.isoformat()}'",
+ "?": f"Get blood pressure data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ ".": f"Get training status data for '{today.isoformat()}'",
+ "a": f"Get resting heart rate data for {today.isoformat()}'",
+ "b": f"Get hydration data for '{today.isoformat()}'",
+ "c": f"Get sleep data for '{today.isoformat()}'",
+ "d": f"Get stress data for '{today.isoformat()}'",
+ "e": f"Get respiration data for '{today.isoformat()}'",
+ "f": f"Get SpO2 data for '{today.isoformat()}'",
+ "g": f"Get max metric data (like vo2MaxValue and fitnessAge) for '{today.isoformat()}'",
+ "h": "Get personal record for user",
+ "i": "Get earned badges for user",
+ "j": f"Get adhoc challenges data from start '{start}' and limit '{limit}'",
+ "k": f"Get available badge challenges data from '{start_badge}' and limit '{limit}'",
+ "l": f"Get badge challenges data from '{start_badge}' and limit '{limit}'",
+ "m": f"Get non completed badge challenges data from '{start_badge}' and limit '{limit}'",
+ "n": f"Get activities data from start '{start}' and limit '{limit}'",
+ "o": "Get last activity",
+ "p": f"Download activities data by date from '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "r": f"Get all kinds of activities data from '{start}'",
+ "s": f"Upload activity data from file '{activityfile}'",
+ "t": "Get all kinds of Garmin device info",
+ "u": "Get active goals",
+ "v": "Get future goals",
+ "w": "Get past goals",
+ "y": "Get all Garmin device alarms",
+ "x": f"Get Heart Rate Variability data (HRV) for '{today.isoformat()}'",
+ "z": f"Get progress summary from '{startdate.isoformat()}' to '{today.isoformat()}' for all metrics",
+ "A": "Get gear, the defaults, activity types and statistics",
+ "Z": "Logout Garmin Connect portal",
+ "q": "Exit",
+}
+
+def display_json(api_call, output):
+ """Format API output for better readability."""
+
+ dashed = "-"*20
+ header = f"{dashed} {api_call} {dashed}"
+ footer = "-"*len(header)
+
+ print(header)
+ print(json.dumps(output, indent=4))
+ print(footer)
+
+def display_text(output):
+ """Format API output for better readability."""
+
+ dashed = "-"*60
+ header = f"{dashed}"
+ footer = "-"*len(header)
+
+ print(header)
+ print(json.dumps(output, indent=4))
+ print(footer)
+
+def get_credentials():
+ """Get user credentials."""
+ email = input("Login e-mail: ")
+ password = pwinput.pwinput(prompt='Password: ')
+
+ return email, password
+
+
+def init_api(email, password):
+ """Initialize Garmin API with your credentials."""
+
+ try:
+ ## Try to load the previous session
+ with open("session.json") as f:
+ saved_session = json.load(f)
+
+ print(
+ "Login to Garmin Connect using session loaded from 'session.json'...\n"
+ )
+
+ # Use the loaded session for initializing the API (without need for credentials)
+ api = Garmin(session_data=saved_session)
+
+ # Login using the
+ api.login()
+
+ except (FileNotFoundError, GarminConnectAuthenticationError):
+ # Login to Garmin Connect portal with credentials since session is invalid or not present.
+ print(
+ "Session file not present or turned invalid, login with your Garmin Connect credentials.\n"
+ "NOTE: Credentials will not be stored, the session cookies will be stored in 'session.json' for future use.\n"
+ )
+ try:
+ # Ask for credentials if not set as environment variables
+ if not email or not password:
+ email, password = get_credentials()
+
+ api = Garmin(email, password)
+ api.login()
+
+ # Save session dictionary to json file for future use
+ with open("session.json", "w", encoding="utf-8") as f:
+ json.dump(api.session_data, f, ensure_ascii=False, indent=4)
+ except (
+ GarminConnectConnectionError,
+ GarminConnectAuthenticationError,
+ GarminConnectTooManyRequestsError,
+ requests.exceptions.HTTPError,
+ ) as err:
+ logger.error("Error occurred during Garmin Connect communication: %s", err)
+ return None
+
+ return api
+
+
+def print_menu():
+ """Print examples menu."""
+ for key in menu_options.keys():
+ print(f"{key} -- {menu_options[key]}")
+ print("Make your selection: ", end="", flush=True)
+
+
+def switch(api, i):
+ """Run selected API call."""
+
+ # Exit example program
+ if i == "q":
+ print("Bye!")
+ sys.exit()
+
+ # Skip requests if login failed
+ if api:
+ try:
+ print(f"\n\nExecuting: {menu_options[i]}\n")
+
+ # USER BASICS
+ if i == "1":
+ # Get full name from profile
+ display_json("api.get_full_name()", api.get_full_name())
+ elif i == "2":
+ # Get unit system from profile
+ display_json("api.get_unit_system()", api.get_unit_system())
+
+ # USER STATISTIC SUMMARIES
+ elif i == "3":
+ # Get activity data for 'YYYY-MM-DD'
+ display_json(f"api.get_stats('{today.isoformat()}')", api.get_stats(today.isoformat()))
+ elif i == "4":
+ # Get activity data (to be compatible with garminconnect-ha)
+ display_json(f"api.get_user_summary('{today.isoformat()}')", api.get_user_summary(today.isoformat()))
+ elif i == "5":
+ # Get body composition data for 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
+ display_json(f"api.get_body_composition('{today.isoformat()}')", api.get_body_composition(today.isoformat()))
+ elif i == "6":
+ # Get body composition data for multiple days 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
+ display_json(f"api.get_body_composition('{startdate.isoformat()}', '{today.isoformat()}')",
+ api.get_body_composition(startdate.isoformat(), today.isoformat())
+ )
+ elif i == "7":
+ # Get stats and body composition data for 'YYYY-MM-DD'
+ display_json(f"api.get_stats_and_body('{today.isoformat()}')", api.get_stats_and_body(today.isoformat()))
+
+ # USER STATISTICS LOGGED
+ elif i == "8":
+ # Get steps data for 'YYYY-MM-DD'
+ display_json(f"api.get_steps_data('{today.isoformat()}')", api.get_steps_data(today.isoformat()))
+ elif i == "9":
+ # Get heart rate data for 'YYYY-MM-DD'
+ display_json(f"api.get_heart_rates('{today.isoformat()}')", api.get_heart_rates(today.isoformat()))
+ elif i == "0":
+ # Get training readiness data for 'YYYY-MM-DD'
+ display_json(f"api.get_training_readiness('{today.isoformat()}')", api.get_training_readiness(today.isoformat()))
+ elif i == "/":
+ # Get daily body battery data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
+ display_json(f"api.get_body_battery('{startdate.isoformat()}, {today.isoformat()}')", api.get_body_battery(startdate.isoformat(), today.isoformat()))
+ elif i == "?":
+ # Get daily blood pressure data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
+ display_json(f"api.get_blood_pressure('{startdate.isoformat()}, {today.isoformat()}')", api.get_blood_pressure(startdate.isoformat(), today.isoformat()))
+ elif i == "-":
+ # Get daily step data for 'YYYY-MM-DD'
+ display_json(f"api.get_daily_steps('{startdate.isoformat()}, {today.isoformat()}')", api.get_daily_steps(startdate.isoformat(), today.isoformat()))
+ elif i == "!":
+ # Get daily floors data for 'YYYY-MM-DD'
+ display_json(f"api.get_floors('{today.isoformat()}')", api.get_floors(today.isoformat()))
+ elif i == ".":
+ # Get training status data for 'YYYY-MM-DD'
+ display_json(f"api.get_training_status('{today.isoformat()}')", api.get_training_status(today.isoformat()))
+ elif i == "a":
+ # Get resting heart rate data for 'YYYY-MM-DD'
+ display_json(f"api.get_rhr_day('{today.isoformat()}')", api.get_rhr_day(today.isoformat()))
+ elif i == "b":
+ # Get hydration data 'YYYY-MM-DD'
+ display_json(f"api.get_hydration_data('{today.isoformat()}')", api.get_hydration_data(today.isoformat()))
+ elif i == "c":
+ # Get sleep data for 'YYYY-MM-DD'
+ display_json(f"api.get_sleep_data('{today.isoformat()}')", api.get_sleep_data(today.isoformat()))
+ elif i == "d":
+ # Get stress data for 'YYYY-MM-DD'
+ display_json(f"api.get_stress_data('{today.isoformat()}')", api.get_stress_data(today.isoformat()))
+ elif i == "e":
+ # Get respiration data for 'YYYY-MM-DD'
+ display_json(f"api.get_respiration_data('{today.isoformat()}')", api.get_respiration_data(today.isoformat()))
+ elif i == "f":
+ # Get SpO2 data for 'YYYY-MM-DD'
+ display_json(f"api.get_spo2_data('{today.isoformat()}')", api.get_spo2_data(today.isoformat()))
+ elif i == "g":
+ # Get max metric data (like vo2MaxValue and fitnessAge) for 'YYYY-MM-DD'
+ display_json(f"api.get_max_metrics('{today.isoformat()}')", api.get_max_metrics(today.isoformat()))
+ elif i == "h":
+ # Get personal record for user
+ display_json("api.get_personal_record()", api.get_personal_record())
+ elif i == "i":
+ # Get earned badges for user
+ display_json("api.get_earned_badges()", api.get_earned_badges())
+ elif i == "j":
+ # Get adhoc challenges data from start and limit
+ display_json(
+ f"api.get_adhoc_challenges({start},{limit})", api.get_adhoc_challenges(start, limit)
+ ) # 1=start, 100=limit
+ elif i == "k":
+ # Get available badge challenges data from start and limit
+ display_json(
+ f"api.get_available_badge_challenges({start_badge}, {limit})", api.get_available_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+ elif i == "l":
+ # Get badge challenges data from start and limit
+ display_json(
+ f"api.get_badge_challenges({start_badge}, {limit})", api.get_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+ elif i == "m":
+ # Get non completed badge challenges data from start and limit
+ display_json(
+ f"api.get_non_completed_badge_challenges({start_badge}, {limit})", api.get_non_completed_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+
+ # ACTIVITIES
+ elif i == "n":
+ # Get activities data from start and limit
+ display_json(f"api.get_activities({start}, {limit})", api.get_activities(start, limit)) # 0=start, 1=limit
+ elif i == "o":
+ # Get last activity
+ display_json("api.get_last_activity()", api.get_last_activity())
+ elif i == "p":
+ # Get activities data from startdate 'YYYY-MM-DD' to enddate 'YYYY-MM-DD', with (optional) activitytype
+ # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
+ activities = api.get_activities_by_date(
+ startdate.isoformat(), today.isoformat(), activitytype
+ )
+
+ # Download activities
+ for activity in activities:
+
+ activity_id = activity["activityId"]
+ display_text(activity)
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.GPX)")
+ gpx_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.GPX
+ )
+ output_file = f"./{str(activity_id)}.gpx"
+ with open(output_file, "wb") as fb:
+ fb.write(gpx_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.TCX)")
+ tcx_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.TCX
+ )
+ output_file = f"./{str(activity_id)}.tcx"
+ with open(output_file, "wb") as fb:
+ fb.write(tcx_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.ORIGINAL)")
+ zip_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.ORIGINAL
+ )
+ output_file = f"./{str(activity_id)}.zip"
+ with open(output_file, "wb") as fb:
+ fb.write(zip_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.CSV)")
+ csv_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.CSV
+ )
+ output_file = f"./{str(activity_id)}.csv"
+ with open(output_file, "wb") as fb:
+ fb.write(csv_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ elif i == "r":
+ # Get activities data from start and limit
+ activities = api.get_activities(start, limit) # 0=start, 1=limit
+
+ # Get activity splits
+ first_activity_id = activities[0].get("activityId")
+
+ display_json(f"api.get_activity_splits({first_activity_id})", api.get_activity_splits(first_activity_id))
+
+ # Get activity split summaries for activity id
+ display_json(f"api.get_activity_split_summaries({first_activity_id})", api.get_activity_split_summaries(first_activity_id))
+
+ # Get activity weather data for activity
+ display_json(f"api.get_activity_weather({first_activity_id})", api.get_activity_weather(first_activity_id))
+
+ # Get activity hr timezones id
+ display_json(f"api.get_activity_hr_in_timezones({first_activity_id})", api.get_activity_hr_in_timezones(first_activity_id))
+
+ # Get activity details for activity id
+ display_json(f"api.get_activity_details({first_activity_id})", api.get_activity_details(first_activity_id))
+
+ # Get gear data for activity id
+ display_json(f"api.get_activity_gear({first_activity_id})", api.get_activity_gear(first_activity_id))
+
+ # Activity self evaluation data for activity id
+ display_json(f"api.get_activity_evaluation({first_activity_id})", api.get_activity_evaluation(first_activity_id))
+
+ # Get exercise sets in case the activity is a strength_training
+ if activities[0]["activityType"]["typeKey"] == "strength_training":
+ display_json(f"api.get_activity_exercise_sets({first_activity_id})", api.get_activity_exercise_sets(first_activity_id))
+
+ elif i == "s":
+ # Upload activity from file
+ display_json(f"api.upload_activity({activityfile})", api.upload_activity(activityfile))
+
+ # DEVICES
+ elif i == "t":
+ # Get Garmin devices
+ devices = api.get_devices()
+ display_json("api.get_devices()", devices)
+
+ # Get device last used
+ device_last_used = api.get_device_last_used()
+ display_json("api.get_device_last_used()", device_last_used)
+
+ # Get settings per device
+ for device in devices:
+ device_id = device["deviceId"]
+ display_json(f"api.get_device_settings({device_id})", api.get_device_settings(device_id))
+
+ # GOALS
+ elif i == "u":
+ # Get active goals
+ goals = api.get_goals("active")
+ display_json("api.get_goals(\"active\")", goals)
+
+ elif i == "v":
+ # Get future goals
+ goals = api.get_goals("future")
+ display_json("api.get_goals(\"future\")", goals)
+
+ elif i == "w":
+ # Get past goals
+ goals = api.get_goals("past")
+ display_json("api.get_goals(\"past\")", goals)
+
+ # ALARMS
+ elif i == "y":
+ # Get Garmin device alarms
+ alarms = api.get_device_alarms()
+ for alarm in alarms:
+ alarm_id = alarm["alarmId"]
+ display_json(f"api.get_device_alarms({alarm_id})", alarm)
+
+ elif i == "x":
+ # Get Heart Rate Variability (hrv) data
+ display_json(f"api.get_hrv_data({today.isoformat()})", api.get_hrv_data(today.isoformat()))
+
+ elif i == "z":
+ # Get progress summary
+ for metric in ["elevationGain", "duration", "distance", "movingDuration"]:
+ display_json(
+ f"api.get_progress_summary_between_dates({today.isoformat()})", api.get_progress_summary_between_dates(
+ startdate.isoformat(), today.isoformat(), metric
+ ))
+
+ # Gear
+ elif i == "A":
+ last_used_device = api.get_device_last_used()
+ display_json(f"api.get_device_last_used()", last_used_device)
+ userProfileNumber = last_used_device["userProfileNumber"]
+ gear = api.get_gear(userProfileNumber)
+ display_json(f"api.get_gear()", gear)
+ display_json(f"api.get_gear_defaults()", api.get_gear_defaults(userProfileNumber))
+ display_json(f"api.get()", api.get_activity_types())
+ for gear in gear:
+ uuid=gear["uuid"]
+ name=gear["displayName"]
+ display_json(f"api.get_gear_stats({uuid}) / {name}", api.get_gear_stats(uuid))
+
+ elif i == "Z":
+ # Logout Garmin Connect portal
+ display_json("api.logout()", api.logout())
+ api = None
+
+ except (
+ GarminConnectConnectionError,
+ GarminConnectAuthenticationError,
+ GarminConnectTooManyRequestsError,
+ requests.exceptions.HTTPError,
+ ) as err:
+ logger.error("Error occurred: %s", err)
+ except KeyError:
+ # Invalid menu option chosen
+ pass
+ else:
+ print("Could not login to Garmin Connect, try again later.")
+
+# Main program loop
+while True:
+ # Display header and login
+ print("\n*** Garmin Connect API Demo by cyberjunky ***\n")
+
+ # Init API
+ if not api:
+ api = init_api(email, password)
+
+ # Display menu
+ print_menu()
+ option = readchar.readkey()
+ switch(api, option)
+
+```
+
+## Donations
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
+
+%package -n python3-garminconnect
+Summary: Python 3 API wrapper for Garmin Connect
+Provides: python-garminconnect
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-garminconnect
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
+
+# Python: Garmin Connect
+
+Python 3 API wrapper for Garmin Connect to get your statistics.
+
+## About
+
+This package allows you to request garmin device, activity and health data from your Garmin Connect account.
+See <https://connect.garmin.com/>
+
+## Installation
+
+```bash
+pip3 install garminconnect
+```
+
+## API Demo Program
+
+I wrote this for testing and playing with all available/known API calls.
+If you run it from the python-garmin connect directory it will use the library code beneath it, so you can develop without reinstalling the package.
+
+The code also demonstrates how to implement session saving and re-using of the cookies.
+
+You can set environment variables with your credentials like so, this is optional:
+
+```bash
+export EMAIL=<your garmin email>
+export PASSWORD=<your garmin password>
+```
+
+Install the pre-requisites for the example program (not all are needed for using the library package):
+
+```bash
+pip3 install cloudscraper readchar requests pwinput
+```
+
+Or you can just run the program and enter your credentials when asked, it will create and save a session file and use that until it's outdated/invalid.
+
+```
+python3 ./example.py
+*** Garmin Connect API Demo by cyberjunky ***
+
+1 -- Get full name
+2 -- Get unit system
+3 -- Get activity data for '2023-03-10'
+4 -- Get activity data for '2023-03-10' (compatible with garminconnect-ha)
+5 -- Get body composition data for '2023-03-10' (compatible with garminconnect-ha)
+6 -- Get body composition data for from '2023-03-03' to '2023-03-10' (to be compatible with garminconnect-ha)
+7 -- Get stats and body composition data for '2023-03-10'
+8 -- Get steps data for '2023-03-10'
+9 -- Get heart rate data for '2023-03-10'
+0 -- Get training readiness data for '2023-03-10'
+- -- Get daily step data for '2023-03-03' to '2023-03-10'
+/ -- Get body battery data for '2023-03-03' to '2023-03-10'
+! -- Get floors data for '2023-03-03'
+? -- Get blood pressure data for '2023-03-03' to '2023-03-10'
+. -- Get training status data for '2023-03-10'
+a -- Get resting heart rate data for 2023-03-10'
+b -- Get hydration data for '2023-03-10'
+c -- Get sleep data for '2023-03-10'
+d -- Get stress data for '2023-03-10'
+e -- Get respiration data for '2023-03-10'
+f -- Get SpO2 data for '2023-03-10'
+g -- Get max metric data (like vo2MaxValue and fitnessAge) for '2023-03-10'
+h -- Get personal record for user
+i -- Get earned badges for user
+j -- Get adhoc challenges data from start '0' and limit '100'
+k -- Get available badge challenges data from '1' and limit '100'
+l -- Get badge challenges data from '1' and limit '100'
+m -- Get non completed badge challenges data from '1' and limit '100'
+n -- Get activities data from start '0' and limit '100'
+o -- Get last activity
+p -- Download activities data by date from '2023-03-03' to '2023-03-10'
+r -- Get all kinds of activities data from '0'
+s -- Upload activity data from file 'MY_ACTIVITY.fit'
+t -- Get all kinds of Garmin device info
+u -- Get active goals
+v -- Get future goals
+w -- Get past goals
+y -- Get all Garmin device alarms
+x -- Get Heart Rate Variability data (HRV) for '2023-03-10'
+z -- Get progress summary from '2023-03-03' to '2023-03-10' for all metrics
+A -- Get gear, the defaults, activity types and statistics
+Z -- Logout Garmin Connect portal
+q -- Exit
+
+Make your selection:
+
+```
+
+This is some example code, and probably older than the latest code which can be found in 'example.py'.
+
+```python
+#!/usr/bin/env python3
+"""
+pip3 install cloudscraper requests readchar pwinput
+
+export EMAIL=<your garmin email>
+export PASSWORD=<your garmin password>
+
+"""
+import datetime
+import json
+import logging
+import os
+import sys
+
+import requests
+import pwinput
+import readchar
+
+from garminconnect import (
+ Garmin,
+ GarminConnectAuthenticationError,
+ GarminConnectConnectionError,
+ GarminConnectTooManyRequestsError,
+)
+
+# Configure debug logging
+# logging.basicConfig(level=logging.DEBUG)
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+# Load environment variables if defined
+email = os.getenv("EMAIL")
+password = os.getenv("PASSWORD")
+api = None
+
+# Example selections and settings
+today = datetime.date.today()
+startdate = today - datetime.timedelta(days=7) # Select past week
+start = 0
+limit = 100
+start_badge = 1 # Badge related calls calls start counting at 1
+activitytype = "" # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
+activityfile = "MY_ACTIVITY.fit" # Supported file types are: .fit .gpx .tcx
+
+menu_options = {
+ "1": "Get full name",
+ "2": "Get unit system",
+ "3": f"Get activity data for '{today.isoformat()}'",
+ "4": f"Get activity data for '{today.isoformat()}' (compatible with garminconnect-ha)",
+ "5": f"Get body composition data for '{today.isoformat()}' (compatible with garminconnect-ha)",
+ "6": f"Get body composition data for from '{startdate.isoformat()}' to '{today.isoformat()}' (to be compatible with garminconnect-ha)",
+ "7": f"Get stats and body composition data for '{today.isoformat()}'",
+ "8": f"Get steps data for '{today.isoformat()}'",
+ "9": f"Get heart rate data for '{today.isoformat()}'",
+ "0": f"Get training readiness data for '{today.isoformat()}'",
+ "-": f"Get daily step data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "/": f"Get body battery data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "!": f"Get floors data for '{startdate.isoformat()}'",
+ "?": f"Get blood pressure data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ ".": f"Get training status data for '{today.isoformat()}'",
+ "a": f"Get resting heart rate data for {today.isoformat()}'",
+ "b": f"Get hydration data for '{today.isoformat()}'",
+ "c": f"Get sleep data for '{today.isoformat()}'",
+ "d": f"Get stress data for '{today.isoformat()}'",
+ "e": f"Get respiration data for '{today.isoformat()}'",
+ "f": f"Get SpO2 data for '{today.isoformat()}'",
+ "g": f"Get max metric data (like vo2MaxValue and fitnessAge) for '{today.isoformat()}'",
+ "h": "Get personal record for user",
+ "i": "Get earned badges for user",
+ "j": f"Get adhoc challenges data from start '{start}' and limit '{limit}'",
+ "k": f"Get available badge challenges data from '{start_badge}' and limit '{limit}'",
+ "l": f"Get badge challenges data from '{start_badge}' and limit '{limit}'",
+ "m": f"Get non completed badge challenges data from '{start_badge}' and limit '{limit}'",
+ "n": f"Get activities data from start '{start}' and limit '{limit}'",
+ "o": "Get last activity",
+ "p": f"Download activities data by date from '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "r": f"Get all kinds of activities data from '{start}'",
+ "s": f"Upload activity data from file '{activityfile}'",
+ "t": "Get all kinds of Garmin device info",
+ "u": "Get active goals",
+ "v": "Get future goals",
+ "w": "Get past goals",
+ "y": "Get all Garmin device alarms",
+ "x": f"Get Heart Rate Variability data (HRV) for '{today.isoformat()}'",
+ "z": f"Get progress summary from '{startdate.isoformat()}' to '{today.isoformat()}' for all metrics",
+ "A": "Get gear, the defaults, activity types and statistics",
+ "Z": "Logout Garmin Connect portal",
+ "q": "Exit",
+}
+
+def display_json(api_call, output):
+ """Format API output for better readability."""
+
+ dashed = "-"*20
+ header = f"{dashed} {api_call} {dashed}"
+ footer = "-"*len(header)
+
+ print(header)
+ print(json.dumps(output, indent=4))
+ print(footer)
+
+def display_text(output):
+ """Format API output for better readability."""
+
+ dashed = "-"*60
+ header = f"{dashed}"
+ footer = "-"*len(header)
+
+ print(header)
+ print(json.dumps(output, indent=4))
+ print(footer)
+
+def get_credentials():
+ """Get user credentials."""
+ email = input("Login e-mail: ")
+ password = pwinput.pwinput(prompt='Password: ')
+
+ return email, password
+
+
+def init_api(email, password):
+ """Initialize Garmin API with your credentials."""
+
+ try:
+ ## Try to load the previous session
+ with open("session.json") as f:
+ saved_session = json.load(f)
+
+ print(
+ "Login to Garmin Connect using session loaded from 'session.json'...\n"
+ )
+
+ # Use the loaded session for initializing the API (without need for credentials)
+ api = Garmin(session_data=saved_session)
+
+ # Login using the
+ api.login()
+
+ except (FileNotFoundError, GarminConnectAuthenticationError):
+ # Login to Garmin Connect portal with credentials since session is invalid or not present.
+ print(
+ "Session file not present or turned invalid, login with your Garmin Connect credentials.\n"
+ "NOTE: Credentials will not be stored, the session cookies will be stored in 'session.json' for future use.\n"
+ )
+ try:
+ # Ask for credentials if not set as environment variables
+ if not email or not password:
+ email, password = get_credentials()
+
+ api = Garmin(email, password)
+ api.login()
+
+ # Save session dictionary to json file for future use
+ with open("session.json", "w", encoding="utf-8") as f:
+ json.dump(api.session_data, f, ensure_ascii=False, indent=4)
+ except (
+ GarminConnectConnectionError,
+ GarminConnectAuthenticationError,
+ GarminConnectTooManyRequestsError,
+ requests.exceptions.HTTPError,
+ ) as err:
+ logger.error("Error occurred during Garmin Connect communication: %s", err)
+ return None
+
+ return api
+
+
+def print_menu():
+ """Print examples menu."""
+ for key in menu_options.keys():
+ print(f"{key} -- {menu_options[key]}")
+ print("Make your selection: ", end="", flush=True)
+
+
+def switch(api, i):
+ """Run selected API call."""
+
+ # Exit example program
+ if i == "q":
+ print("Bye!")
+ sys.exit()
+
+ # Skip requests if login failed
+ if api:
+ try:
+ print(f"\n\nExecuting: {menu_options[i]}\n")
+
+ # USER BASICS
+ if i == "1":
+ # Get full name from profile
+ display_json("api.get_full_name()", api.get_full_name())
+ elif i == "2":
+ # Get unit system from profile
+ display_json("api.get_unit_system()", api.get_unit_system())
+
+ # USER STATISTIC SUMMARIES
+ elif i == "3":
+ # Get activity data for 'YYYY-MM-DD'
+ display_json(f"api.get_stats('{today.isoformat()}')", api.get_stats(today.isoformat()))
+ elif i == "4":
+ # Get activity data (to be compatible with garminconnect-ha)
+ display_json(f"api.get_user_summary('{today.isoformat()}')", api.get_user_summary(today.isoformat()))
+ elif i == "5":
+ # Get body composition data for 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
+ display_json(f"api.get_body_composition('{today.isoformat()}')", api.get_body_composition(today.isoformat()))
+ elif i == "6":
+ # Get body composition data for multiple days 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
+ display_json(f"api.get_body_composition('{startdate.isoformat()}', '{today.isoformat()}')",
+ api.get_body_composition(startdate.isoformat(), today.isoformat())
+ )
+ elif i == "7":
+ # Get stats and body composition data for 'YYYY-MM-DD'
+ display_json(f"api.get_stats_and_body('{today.isoformat()}')", api.get_stats_and_body(today.isoformat()))
+
+ # USER STATISTICS LOGGED
+ elif i == "8":
+ # Get steps data for 'YYYY-MM-DD'
+ display_json(f"api.get_steps_data('{today.isoformat()}')", api.get_steps_data(today.isoformat()))
+ elif i == "9":
+ # Get heart rate data for 'YYYY-MM-DD'
+ display_json(f"api.get_heart_rates('{today.isoformat()}')", api.get_heart_rates(today.isoformat()))
+ elif i == "0":
+ # Get training readiness data for 'YYYY-MM-DD'
+ display_json(f"api.get_training_readiness('{today.isoformat()}')", api.get_training_readiness(today.isoformat()))
+ elif i == "/":
+ # Get daily body battery data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
+ display_json(f"api.get_body_battery('{startdate.isoformat()}, {today.isoformat()}')", api.get_body_battery(startdate.isoformat(), today.isoformat()))
+ elif i == "?":
+ # Get daily blood pressure data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
+ display_json(f"api.get_blood_pressure('{startdate.isoformat()}, {today.isoformat()}')", api.get_blood_pressure(startdate.isoformat(), today.isoformat()))
+ elif i == "-":
+ # Get daily step data for 'YYYY-MM-DD'
+ display_json(f"api.get_daily_steps('{startdate.isoformat()}, {today.isoformat()}')", api.get_daily_steps(startdate.isoformat(), today.isoformat()))
+ elif i == "!":
+ # Get daily floors data for 'YYYY-MM-DD'
+ display_json(f"api.get_floors('{today.isoformat()}')", api.get_floors(today.isoformat()))
+ elif i == ".":
+ # Get training status data for 'YYYY-MM-DD'
+ display_json(f"api.get_training_status('{today.isoformat()}')", api.get_training_status(today.isoformat()))
+ elif i == "a":
+ # Get resting heart rate data for 'YYYY-MM-DD'
+ display_json(f"api.get_rhr_day('{today.isoformat()}')", api.get_rhr_day(today.isoformat()))
+ elif i == "b":
+ # Get hydration data 'YYYY-MM-DD'
+ display_json(f"api.get_hydration_data('{today.isoformat()}')", api.get_hydration_data(today.isoformat()))
+ elif i == "c":
+ # Get sleep data for 'YYYY-MM-DD'
+ display_json(f"api.get_sleep_data('{today.isoformat()}')", api.get_sleep_data(today.isoformat()))
+ elif i == "d":
+ # Get stress data for 'YYYY-MM-DD'
+ display_json(f"api.get_stress_data('{today.isoformat()}')", api.get_stress_data(today.isoformat()))
+ elif i == "e":
+ # Get respiration data for 'YYYY-MM-DD'
+ display_json(f"api.get_respiration_data('{today.isoformat()}')", api.get_respiration_data(today.isoformat()))
+ elif i == "f":
+ # Get SpO2 data for 'YYYY-MM-DD'
+ display_json(f"api.get_spo2_data('{today.isoformat()}')", api.get_spo2_data(today.isoformat()))
+ elif i == "g":
+ # Get max metric data (like vo2MaxValue and fitnessAge) for 'YYYY-MM-DD'
+ display_json(f"api.get_max_metrics('{today.isoformat()}')", api.get_max_metrics(today.isoformat()))
+ elif i == "h":
+ # Get personal record for user
+ display_json("api.get_personal_record()", api.get_personal_record())
+ elif i == "i":
+ # Get earned badges for user
+ display_json("api.get_earned_badges()", api.get_earned_badges())
+ elif i == "j":
+ # Get adhoc challenges data from start and limit
+ display_json(
+ f"api.get_adhoc_challenges({start},{limit})", api.get_adhoc_challenges(start, limit)
+ ) # 1=start, 100=limit
+ elif i == "k":
+ # Get available badge challenges data from start and limit
+ display_json(
+ f"api.get_available_badge_challenges({start_badge}, {limit})", api.get_available_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+ elif i == "l":
+ # Get badge challenges data from start and limit
+ display_json(
+ f"api.get_badge_challenges({start_badge}, {limit})", api.get_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+ elif i == "m":
+ # Get non completed badge challenges data from start and limit
+ display_json(
+ f"api.get_non_completed_badge_challenges({start_badge}, {limit})", api.get_non_completed_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+
+ # ACTIVITIES
+ elif i == "n":
+ # Get activities data from start and limit
+ display_json(f"api.get_activities({start}, {limit})", api.get_activities(start, limit)) # 0=start, 1=limit
+ elif i == "o":
+ # Get last activity
+ display_json("api.get_last_activity()", api.get_last_activity())
+ elif i == "p":
+ # Get activities data from startdate 'YYYY-MM-DD' to enddate 'YYYY-MM-DD', with (optional) activitytype
+ # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
+ activities = api.get_activities_by_date(
+ startdate.isoformat(), today.isoformat(), activitytype
+ )
+
+ # Download activities
+ for activity in activities:
+
+ activity_id = activity["activityId"]
+ display_text(activity)
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.GPX)")
+ gpx_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.GPX
+ )
+ output_file = f"./{str(activity_id)}.gpx"
+ with open(output_file, "wb") as fb:
+ fb.write(gpx_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.TCX)")
+ tcx_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.TCX
+ )
+ output_file = f"./{str(activity_id)}.tcx"
+ with open(output_file, "wb") as fb:
+ fb.write(tcx_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.ORIGINAL)")
+ zip_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.ORIGINAL
+ )
+ output_file = f"./{str(activity_id)}.zip"
+ with open(output_file, "wb") as fb:
+ fb.write(zip_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.CSV)")
+ csv_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.CSV
+ )
+ output_file = f"./{str(activity_id)}.csv"
+ with open(output_file, "wb") as fb:
+ fb.write(csv_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ elif i == "r":
+ # Get activities data from start and limit
+ activities = api.get_activities(start, limit) # 0=start, 1=limit
+
+ # Get activity splits
+ first_activity_id = activities[0].get("activityId")
+
+ display_json(f"api.get_activity_splits({first_activity_id})", api.get_activity_splits(first_activity_id))
+
+ # Get activity split summaries for activity id
+ display_json(f"api.get_activity_split_summaries({first_activity_id})", api.get_activity_split_summaries(first_activity_id))
+
+ # Get activity weather data for activity
+ display_json(f"api.get_activity_weather({first_activity_id})", api.get_activity_weather(first_activity_id))
+
+ # Get activity hr timezones id
+ display_json(f"api.get_activity_hr_in_timezones({first_activity_id})", api.get_activity_hr_in_timezones(first_activity_id))
+
+ # Get activity details for activity id
+ display_json(f"api.get_activity_details({first_activity_id})", api.get_activity_details(first_activity_id))
+
+ # Get gear data for activity id
+ display_json(f"api.get_activity_gear({first_activity_id})", api.get_activity_gear(first_activity_id))
+
+ # Activity self evaluation data for activity id
+ display_json(f"api.get_activity_evaluation({first_activity_id})", api.get_activity_evaluation(first_activity_id))
+
+ # Get exercise sets in case the activity is a strength_training
+ if activities[0]["activityType"]["typeKey"] == "strength_training":
+ display_json(f"api.get_activity_exercise_sets({first_activity_id})", api.get_activity_exercise_sets(first_activity_id))
+
+ elif i == "s":
+ # Upload activity from file
+ display_json(f"api.upload_activity({activityfile})", api.upload_activity(activityfile))
+
+ # DEVICES
+ elif i == "t":
+ # Get Garmin devices
+ devices = api.get_devices()
+ display_json("api.get_devices()", devices)
+
+ # Get device last used
+ device_last_used = api.get_device_last_used()
+ display_json("api.get_device_last_used()", device_last_used)
+
+ # Get settings per device
+ for device in devices:
+ device_id = device["deviceId"]
+ display_json(f"api.get_device_settings({device_id})", api.get_device_settings(device_id))
+
+ # GOALS
+ elif i == "u":
+ # Get active goals
+ goals = api.get_goals("active")
+ display_json("api.get_goals(\"active\")", goals)
+
+ elif i == "v":
+ # Get future goals
+ goals = api.get_goals("future")
+ display_json("api.get_goals(\"future\")", goals)
+
+ elif i == "w":
+ # Get past goals
+ goals = api.get_goals("past")
+ display_json("api.get_goals(\"past\")", goals)
+
+ # ALARMS
+ elif i == "y":
+ # Get Garmin device alarms
+ alarms = api.get_device_alarms()
+ for alarm in alarms:
+ alarm_id = alarm["alarmId"]
+ display_json(f"api.get_device_alarms({alarm_id})", alarm)
+
+ elif i == "x":
+ # Get Heart Rate Variability (hrv) data
+ display_json(f"api.get_hrv_data({today.isoformat()})", api.get_hrv_data(today.isoformat()))
+
+ elif i == "z":
+ # Get progress summary
+ for metric in ["elevationGain", "duration", "distance", "movingDuration"]:
+ display_json(
+ f"api.get_progress_summary_between_dates({today.isoformat()})", api.get_progress_summary_between_dates(
+ startdate.isoformat(), today.isoformat(), metric
+ ))
+
+ # Gear
+ elif i == "A":
+ last_used_device = api.get_device_last_used()
+ display_json(f"api.get_device_last_used()", last_used_device)
+ userProfileNumber = last_used_device["userProfileNumber"]
+ gear = api.get_gear(userProfileNumber)
+ display_json(f"api.get_gear()", gear)
+ display_json(f"api.get_gear_defaults()", api.get_gear_defaults(userProfileNumber))
+ display_json(f"api.get()", api.get_activity_types())
+ for gear in gear:
+ uuid=gear["uuid"]
+ name=gear["displayName"]
+ display_json(f"api.get_gear_stats({uuid}) / {name}", api.get_gear_stats(uuid))
+
+ elif i == "Z":
+ # Logout Garmin Connect portal
+ display_json("api.logout()", api.logout())
+ api = None
+
+ except (
+ GarminConnectConnectionError,
+ GarminConnectAuthenticationError,
+ GarminConnectTooManyRequestsError,
+ requests.exceptions.HTTPError,
+ ) as err:
+ logger.error("Error occurred: %s", err)
+ except KeyError:
+ # Invalid menu option chosen
+ pass
+ else:
+ print("Could not login to Garmin Connect, try again later.")
+
+# Main program loop
+while True:
+ # Display header and login
+ print("\n*** Garmin Connect API Demo by cyberjunky ***\n")
+
+ # Init API
+ if not api:
+ api = init_api(email, password)
+
+ # Display menu
+ print_menu()
+ option = readchar.readkey()
+ switch(api, option)
+
+```
+
+## Donations
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
+
+%package help
+Summary: Development documents and examples for garminconnect
+Provides: python3-garminconnect-doc
+%description help
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
+
+# Python: Garmin Connect
+
+Python 3 API wrapper for Garmin Connect to get your statistics.
+
+## About
+
+This package allows you to request garmin device, activity and health data from your Garmin Connect account.
+See <https://connect.garmin.com/>
+
+## Installation
+
+```bash
+pip3 install garminconnect
+```
+
+## API Demo Program
+
+I wrote this for testing and playing with all available/known API calls.
+If you run it from the python-garmin connect directory it will use the library code beneath it, so you can develop without reinstalling the package.
+
+The code also demonstrates how to implement session saving and re-using of the cookies.
+
+You can set environment variables with your credentials like so, this is optional:
+
+```bash
+export EMAIL=<your garmin email>
+export PASSWORD=<your garmin password>
+```
+
+Install the pre-requisites for the example program (not all are needed for using the library package):
+
+```bash
+pip3 install cloudscraper readchar requests pwinput
+```
+
+Or you can just run the program and enter your credentials when asked, it will create and save a session file and use that until it's outdated/invalid.
+
+```
+python3 ./example.py
+*** Garmin Connect API Demo by cyberjunky ***
+
+1 -- Get full name
+2 -- Get unit system
+3 -- Get activity data for '2023-03-10'
+4 -- Get activity data for '2023-03-10' (compatible with garminconnect-ha)
+5 -- Get body composition data for '2023-03-10' (compatible with garminconnect-ha)
+6 -- Get body composition data for from '2023-03-03' to '2023-03-10' (to be compatible with garminconnect-ha)
+7 -- Get stats and body composition data for '2023-03-10'
+8 -- Get steps data for '2023-03-10'
+9 -- Get heart rate data for '2023-03-10'
+0 -- Get training readiness data for '2023-03-10'
+- -- Get daily step data for '2023-03-03' to '2023-03-10'
+/ -- Get body battery data for '2023-03-03' to '2023-03-10'
+! -- Get floors data for '2023-03-03'
+? -- Get blood pressure data for '2023-03-03' to '2023-03-10'
+. -- Get training status data for '2023-03-10'
+a -- Get resting heart rate data for 2023-03-10'
+b -- Get hydration data for '2023-03-10'
+c -- Get sleep data for '2023-03-10'
+d -- Get stress data for '2023-03-10'
+e -- Get respiration data for '2023-03-10'
+f -- Get SpO2 data for '2023-03-10'
+g -- Get max metric data (like vo2MaxValue and fitnessAge) for '2023-03-10'
+h -- Get personal record for user
+i -- Get earned badges for user
+j -- Get adhoc challenges data from start '0' and limit '100'
+k -- Get available badge challenges data from '1' and limit '100'
+l -- Get badge challenges data from '1' and limit '100'
+m -- Get non completed badge challenges data from '1' and limit '100'
+n -- Get activities data from start '0' and limit '100'
+o -- Get last activity
+p -- Download activities data by date from '2023-03-03' to '2023-03-10'
+r -- Get all kinds of activities data from '0'
+s -- Upload activity data from file 'MY_ACTIVITY.fit'
+t -- Get all kinds of Garmin device info
+u -- Get active goals
+v -- Get future goals
+w -- Get past goals
+y -- Get all Garmin device alarms
+x -- Get Heart Rate Variability data (HRV) for '2023-03-10'
+z -- Get progress summary from '2023-03-03' to '2023-03-10' for all metrics
+A -- Get gear, the defaults, activity types and statistics
+Z -- Logout Garmin Connect portal
+q -- Exit
+
+Make your selection:
+
+```
+
+This is some example code, and probably older than the latest code which can be found in 'example.py'.
+
+```python
+#!/usr/bin/env python3
+"""
+pip3 install cloudscraper requests readchar pwinput
+
+export EMAIL=<your garmin email>
+export PASSWORD=<your garmin password>
+
+"""
+import datetime
+import json
+import logging
+import os
+import sys
+
+import requests
+import pwinput
+import readchar
+
+from garminconnect import (
+ Garmin,
+ GarminConnectAuthenticationError,
+ GarminConnectConnectionError,
+ GarminConnectTooManyRequestsError,
+)
+
+# Configure debug logging
+# logging.basicConfig(level=logging.DEBUG)
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+# Load environment variables if defined
+email = os.getenv("EMAIL")
+password = os.getenv("PASSWORD")
+api = None
+
+# Example selections and settings
+today = datetime.date.today()
+startdate = today - datetime.timedelta(days=7) # Select past week
+start = 0
+limit = 100
+start_badge = 1 # Badge related calls calls start counting at 1
+activitytype = "" # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
+activityfile = "MY_ACTIVITY.fit" # Supported file types are: .fit .gpx .tcx
+
+menu_options = {
+ "1": "Get full name",
+ "2": "Get unit system",
+ "3": f"Get activity data for '{today.isoformat()}'",
+ "4": f"Get activity data for '{today.isoformat()}' (compatible with garminconnect-ha)",
+ "5": f"Get body composition data for '{today.isoformat()}' (compatible with garminconnect-ha)",
+ "6": f"Get body composition data for from '{startdate.isoformat()}' to '{today.isoformat()}' (to be compatible with garminconnect-ha)",
+ "7": f"Get stats and body composition data for '{today.isoformat()}'",
+ "8": f"Get steps data for '{today.isoformat()}'",
+ "9": f"Get heart rate data for '{today.isoformat()}'",
+ "0": f"Get training readiness data for '{today.isoformat()}'",
+ "-": f"Get daily step data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "/": f"Get body battery data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "!": f"Get floors data for '{startdate.isoformat()}'",
+ "?": f"Get blood pressure data for '{startdate.isoformat()}' to '{today.isoformat()}'",
+ ".": f"Get training status data for '{today.isoformat()}'",
+ "a": f"Get resting heart rate data for {today.isoformat()}'",
+ "b": f"Get hydration data for '{today.isoformat()}'",
+ "c": f"Get sleep data for '{today.isoformat()}'",
+ "d": f"Get stress data for '{today.isoformat()}'",
+ "e": f"Get respiration data for '{today.isoformat()}'",
+ "f": f"Get SpO2 data for '{today.isoformat()}'",
+ "g": f"Get max metric data (like vo2MaxValue and fitnessAge) for '{today.isoformat()}'",
+ "h": "Get personal record for user",
+ "i": "Get earned badges for user",
+ "j": f"Get adhoc challenges data from start '{start}' and limit '{limit}'",
+ "k": f"Get available badge challenges data from '{start_badge}' and limit '{limit}'",
+ "l": f"Get badge challenges data from '{start_badge}' and limit '{limit}'",
+ "m": f"Get non completed badge challenges data from '{start_badge}' and limit '{limit}'",
+ "n": f"Get activities data from start '{start}' and limit '{limit}'",
+ "o": "Get last activity",
+ "p": f"Download activities data by date from '{startdate.isoformat()}' to '{today.isoformat()}'",
+ "r": f"Get all kinds of activities data from '{start}'",
+ "s": f"Upload activity data from file '{activityfile}'",
+ "t": "Get all kinds of Garmin device info",
+ "u": "Get active goals",
+ "v": "Get future goals",
+ "w": "Get past goals",
+ "y": "Get all Garmin device alarms",
+ "x": f"Get Heart Rate Variability data (HRV) for '{today.isoformat()}'",
+ "z": f"Get progress summary from '{startdate.isoformat()}' to '{today.isoformat()}' for all metrics",
+ "A": "Get gear, the defaults, activity types and statistics",
+ "Z": "Logout Garmin Connect portal",
+ "q": "Exit",
+}
+
+def display_json(api_call, output):
+ """Format API output for better readability."""
+
+ dashed = "-"*20
+ header = f"{dashed} {api_call} {dashed}"
+ footer = "-"*len(header)
+
+ print(header)
+ print(json.dumps(output, indent=4))
+ print(footer)
+
+def display_text(output):
+ """Format API output for better readability."""
+
+ dashed = "-"*60
+ header = f"{dashed}"
+ footer = "-"*len(header)
+
+ print(header)
+ print(json.dumps(output, indent=4))
+ print(footer)
+
+def get_credentials():
+ """Get user credentials."""
+ email = input("Login e-mail: ")
+ password = pwinput.pwinput(prompt='Password: ')
+
+ return email, password
+
+
+def init_api(email, password):
+ """Initialize Garmin API with your credentials."""
+
+ try:
+ ## Try to load the previous session
+ with open("session.json") as f:
+ saved_session = json.load(f)
+
+ print(
+ "Login to Garmin Connect using session loaded from 'session.json'...\n"
+ )
+
+ # Use the loaded session for initializing the API (without need for credentials)
+ api = Garmin(session_data=saved_session)
+
+ # Login using the
+ api.login()
+
+ except (FileNotFoundError, GarminConnectAuthenticationError):
+ # Login to Garmin Connect portal with credentials since session is invalid or not present.
+ print(
+ "Session file not present or turned invalid, login with your Garmin Connect credentials.\n"
+ "NOTE: Credentials will not be stored, the session cookies will be stored in 'session.json' for future use.\n"
+ )
+ try:
+ # Ask for credentials if not set as environment variables
+ if not email or not password:
+ email, password = get_credentials()
+
+ api = Garmin(email, password)
+ api.login()
+
+ # Save session dictionary to json file for future use
+ with open("session.json", "w", encoding="utf-8") as f:
+ json.dump(api.session_data, f, ensure_ascii=False, indent=4)
+ except (
+ GarminConnectConnectionError,
+ GarminConnectAuthenticationError,
+ GarminConnectTooManyRequestsError,
+ requests.exceptions.HTTPError,
+ ) as err:
+ logger.error("Error occurred during Garmin Connect communication: %s", err)
+ return None
+
+ return api
+
+
+def print_menu():
+ """Print examples menu."""
+ for key in menu_options.keys():
+ print(f"{key} -- {menu_options[key]}")
+ print("Make your selection: ", end="", flush=True)
+
+
+def switch(api, i):
+ """Run selected API call."""
+
+ # Exit example program
+ if i == "q":
+ print("Bye!")
+ sys.exit()
+
+ # Skip requests if login failed
+ if api:
+ try:
+ print(f"\n\nExecuting: {menu_options[i]}\n")
+
+ # USER BASICS
+ if i == "1":
+ # Get full name from profile
+ display_json("api.get_full_name()", api.get_full_name())
+ elif i == "2":
+ # Get unit system from profile
+ display_json("api.get_unit_system()", api.get_unit_system())
+
+ # USER STATISTIC SUMMARIES
+ elif i == "3":
+ # Get activity data for 'YYYY-MM-DD'
+ display_json(f"api.get_stats('{today.isoformat()}')", api.get_stats(today.isoformat()))
+ elif i == "4":
+ # Get activity data (to be compatible with garminconnect-ha)
+ display_json(f"api.get_user_summary('{today.isoformat()}')", api.get_user_summary(today.isoformat()))
+ elif i == "5":
+ # Get body composition data for 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
+ display_json(f"api.get_body_composition('{today.isoformat()}')", api.get_body_composition(today.isoformat()))
+ elif i == "6":
+ # Get body composition data for multiple days 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
+ display_json(f"api.get_body_composition('{startdate.isoformat()}', '{today.isoformat()}')",
+ api.get_body_composition(startdate.isoformat(), today.isoformat())
+ )
+ elif i == "7":
+ # Get stats and body composition data for 'YYYY-MM-DD'
+ display_json(f"api.get_stats_and_body('{today.isoformat()}')", api.get_stats_and_body(today.isoformat()))
+
+ # USER STATISTICS LOGGED
+ elif i == "8":
+ # Get steps data for 'YYYY-MM-DD'
+ display_json(f"api.get_steps_data('{today.isoformat()}')", api.get_steps_data(today.isoformat()))
+ elif i == "9":
+ # Get heart rate data for 'YYYY-MM-DD'
+ display_json(f"api.get_heart_rates('{today.isoformat()}')", api.get_heart_rates(today.isoformat()))
+ elif i == "0":
+ # Get training readiness data for 'YYYY-MM-DD'
+ display_json(f"api.get_training_readiness('{today.isoformat()}')", api.get_training_readiness(today.isoformat()))
+ elif i == "/":
+ # Get daily body battery data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
+ display_json(f"api.get_body_battery('{startdate.isoformat()}, {today.isoformat()}')", api.get_body_battery(startdate.isoformat(), today.isoformat()))
+ elif i == "?":
+ # Get daily blood pressure data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
+ display_json(f"api.get_blood_pressure('{startdate.isoformat()}, {today.isoformat()}')", api.get_blood_pressure(startdate.isoformat(), today.isoformat()))
+ elif i == "-":
+ # Get daily step data for 'YYYY-MM-DD'
+ display_json(f"api.get_daily_steps('{startdate.isoformat()}, {today.isoformat()}')", api.get_daily_steps(startdate.isoformat(), today.isoformat()))
+ elif i == "!":
+ # Get daily floors data for 'YYYY-MM-DD'
+ display_json(f"api.get_floors('{today.isoformat()}')", api.get_floors(today.isoformat()))
+ elif i == ".":
+ # Get training status data for 'YYYY-MM-DD'
+ display_json(f"api.get_training_status('{today.isoformat()}')", api.get_training_status(today.isoformat()))
+ elif i == "a":
+ # Get resting heart rate data for 'YYYY-MM-DD'
+ display_json(f"api.get_rhr_day('{today.isoformat()}')", api.get_rhr_day(today.isoformat()))
+ elif i == "b":
+ # Get hydration data 'YYYY-MM-DD'
+ display_json(f"api.get_hydration_data('{today.isoformat()}')", api.get_hydration_data(today.isoformat()))
+ elif i == "c":
+ # Get sleep data for 'YYYY-MM-DD'
+ display_json(f"api.get_sleep_data('{today.isoformat()}')", api.get_sleep_data(today.isoformat()))
+ elif i == "d":
+ # Get stress data for 'YYYY-MM-DD'
+ display_json(f"api.get_stress_data('{today.isoformat()}')", api.get_stress_data(today.isoformat()))
+ elif i == "e":
+ # Get respiration data for 'YYYY-MM-DD'
+ display_json(f"api.get_respiration_data('{today.isoformat()}')", api.get_respiration_data(today.isoformat()))
+ elif i == "f":
+ # Get SpO2 data for 'YYYY-MM-DD'
+ display_json(f"api.get_spo2_data('{today.isoformat()}')", api.get_spo2_data(today.isoformat()))
+ elif i == "g":
+ # Get max metric data (like vo2MaxValue and fitnessAge) for 'YYYY-MM-DD'
+ display_json(f"api.get_max_metrics('{today.isoformat()}')", api.get_max_metrics(today.isoformat()))
+ elif i == "h":
+ # Get personal record for user
+ display_json("api.get_personal_record()", api.get_personal_record())
+ elif i == "i":
+ # Get earned badges for user
+ display_json("api.get_earned_badges()", api.get_earned_badges())
+ elif i == "j":
+ # Get adhoc challenges data from start and limit
+ display_json(
+ f"api.get_adhoc_challenges({start},{limit})", api.get_adhoc_challenges(start, limit)
+ ) # 1=start, 100=limit
+ elif i == "k":
+ # Get available badge challenges data from start and limit
+ display_json(
+ f"api.get_available_badge_challenges({start_badge}, {limit})", api.get_available_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+ elif i == "l":
+ # Get badge challenges data from start and limit
+ display_json(
+ f"api.get_badge_challenges({start_badge}, {limit})", api.get_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+ elif i == "m":
+ # Get non completed badge challenges data from start and limit
+ display_json(
+ f"api.get_non_completed_badge_challenges({start_badge}, {limit})", api.get_non_completed_badge_challenges(start_badge, limit)
+ ) # 1=start, 100=limit
+
+ # ACTIVITIES
+ elif i == "n":
+ # Get activities data from start and limit
+ display_json(f"api.get_activities({start}, {limit})", api.get_activities(start, limit)) # 0=start, 1=limit
+ elif i == "o":
+ # Get last activity
+ display_json("api.get_last_activity()", api.get_last_activity())
+ elif i == "p":
+ # Get activities data from startdate 'YYYY-MM-DD' to enddate 'YYYY-MM-DD', with (optional) activitytype
+ # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
+ activities = api.get_activities_by_date(
+ startdate.isoformat(), today.isoformat(), activitytype
+ )
+
+ # Download activities
+ for activity in activities:
+
+ activity_id = activity["activityId"]
+ display_text(activity)
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.GPX)")
+ gpx_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.GPX
+ )
+ output_file = f"./{str(activity_id)}.gpx"
+ with open(output_file, "wb") as fb:
+ fb.write(gpx_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.TCX)")
+ tcx_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.TCX
+ )
+ output_file = f"./{str(activity_id)}.tcx"
+ with open(output_file, "wb") as fb:
+ fb.write(tcx_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.ORIGINAL)")
+ zip_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.ORIGINAL
+ )
+ output_file = f"./{str(activity_id)}.zip"
+ with open(output_file, "wb") as fb:
+ fb.write(zip_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.CSV)")
+ csv_data = api.download_activity(
+ activity_id, dl_fmt=api.ActivityDownloadFormat.CSV
+ )
+ output_file = f"./{str(activity_id)}.csv"
+ with open(output_file, "wb") as fb:
+ fb.write(csv_data)
+ print(f"Activity data downloaded to file {output_file}")
+
+ elif i == "r":
+ # Get activities data from start and limit
+ activities = api.get_activities(start, limit) # 0=start, 1=limit
+
+ # Get activity splits
+ first_activity_id = activities[0].get("activityId")
+
+ display_json(f"api.get_activity_splits({first_activity_id})", api.get_activity_splits(first_activity_id))
+
+ # Get activity split summaries for activity id
+ display_json(f"api.get_activity_split_summaries({first_activity_id})", api.get_activity_split_summaries(first_activity_id))
+
+ # Get activity weather data for activity
+ display_json(f"api.get_activity_weather({first_activity_id})", api.get_activity_weather(first_activity_id))
+
+ # Get activity hr timezones id
+ display_json(f"api.get_activity_hr_in_timezones({first_activity_id})", api.get_activity_hr_in_timezones(first_activity_id))
+
+ # Get activity details for activity id
+ display_json(f"api.get_activity_details({first_activity_id})", api.get_activity_details(first_activity_id))
+
+ # Get gear data for activity id
+ display_json(f"api.get_activity_gear({first_activity_id})", api.get_activity_gear(first_activity_id))
+
+ # Activity self evaluation data for activity id
+ display_json(f"api.get_activity_evaluation({first_activity_id})", api.get_activity_evaluation(first_activity_id))
+
+ # Get exercise sets in case the activity is a strength_training
+ if activities[0]["activityType"]["typeKey"] == "strength_training":
+ display_json(f"api.get_activity_exercise_sets({first_activity_id})", api.get_activity_exercise_sets(first_activity_id))
+
+ elif i == "s":
+ # Upload activity from file
+ display_json(f"api.upload_activity({activityfile})", api.upload_activity(activityfile))
+
+ # DEVICES
+ elif i == "t":
+ # Get Garmin devices
+ devices = api.get_devices()
+ display_json("api.get_devices()", devices)
+
+ # Get device last used
+ device_last_used = api.get_device_last_used()
+ display_json("api.get_device_last_used()", device_last_used)
+
+ # Get settings per device
+ for device in devices:
+ device_id = device["deviceId"]
+ display_json(f"api.get_device_settings({device_id})", api.get_device_settings(device_id))
+
+ # GOALS
+ elif i == "u":
+ # Get active goals
+ goals = api.get_goals("active")
+ display_json("api.get_goals(\"active\")", goals)
+
+ elif i == "v":
+ # Get future goals
+ goals = api.get_goals("future")
+ display_json("api.get_goals(\"future\")", goals)
+
+ elif i == "w":
+ # Get past goals
+ goals = api.get_goals("past")
+ display_json("api.get_goals(\"past\")", goals)
+
+ # ALARMS
+ elif i == "y":
+ # Get Garmin device alarms
+ alarms = api.get_device_alarms()
+ for alarm in alarms:
+ alarm_id = alarm["alarmId"]
+ display_json(f"api.get_device_alarms({alarm_id})", alarm)
+
+ elif i == "x":
+ # Get Heart Rate Variability (hrv) data
+ display_json(f"api.get_hrv_data({today.isoformat()})", api.get_hrv_data(today.isoformat()))
+
+ elif i == "z":
+ # Get progress summary
+ for metric in ["elevationGain", "duration", "distance", "movingDuration"]:
+ display_json(
+ f"api.get_progress_summary_between_dates({today.isoformat()})", api.get_progress_summary_between_dates(
+ startdate.isoformat(), today.isoformat(), metric
+ ))
+
+ # Gear
+ elif i == "A":
+ last_used_device = api.get_device_last_used()
+ display_json(f"api.get_device_last_used()", last_used_device)
+ userProfileNumber = last_used_device["userProfileNumber"]
+ gear = api.get_gear(userProfileNumber)
+ display_json(f"api.get_gear()", gear)
+ display_json(f"api.get_gear_defaults()", api.get_gear_defaults(userProfileNumber))
+ display_json(f"api.get()", api.get_activity_types())
+ for gear in gear:
+ uuid=gear["uuid"]
+ name=gear["displayName"]
+ display_json(f"api.get_gear_stats({uuid}) / {name}", api.get_gear_stats(uuid))
+
+ elif i == "Z":
+ # Logout Garmin Connect portal
+ display_json("api.logout()", api.logout())
+ api = None
+
+ except (
+ GarminConnectConnectionError,
+ GarminConnectAuthenticationError,
+ GarminConnectTooManyRequestsError,
+ requests.exceptions.HTTPError,
+ ) as err:
+ logger.error("Error occurred: %s", err)
+ except KeyError:
+ # Invalid menu option chosen
+ pass
+ else:
+ print("Could not login to Garmin Connect, try again later.")
+
+# Main program loop
+while True:
+ # Display header and login
+ print("\n*** Garmin Connect API Demo by cyberjunky ***\n")
+
+ # Init API
+ if not api:
+ api = init_api(email, password)
+
+ # Display menu
+ print_menu()
+ option = readchar.readkey()
+ switch(api, option)
+
+```
+
+## Donations
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
+
+%prep
+%autosetup -n garminconnect-0.1.55
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "\"/%h/%f\"\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "\"/%h/%f\"\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "\"/%h/%f\"\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "\"/%h/%f\"\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "\"/%h/%f.gz\"\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-garminconnect -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Tue Jun 20 2023 Python_Bot <Python_Bot@openeuler.org> - 0.1.55-1
+- Package Spec generated
diff --git a/sources b/sources
new file mode 100644
index 0000000..fde4c4e
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+975f73227bdcb17d1613743448c7eada garminconnect-0.1.55.tar.gz