#
tokens: 12895/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .env_template
├── .gitignore
├── example.py
├── garmin_mcp_server.py
├── pyproject.toml
├── README.md
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.env_template:
--------------------------------------------------------------------------------

```
1 | GARMIN_EMAIL='[email protected]'
2 | GARMIN_PASSWORD='password'
3 | GARMIN_TOKEN_STORE='~/.garminconnect'
4 | GARMIN_TOKEN_STORE_BASE64='~/.garminconnect_base64'
5 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | .env
12 | .specstory/
13 | 
14 | # Version management
15 | .python-version
16 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Garmin Connect MCP Server
 2 | 
 3 | This project provides a server for interacting with the Garmin Connect API. It allows users to manage their Garmin data, including workouts, health metrics, and more.
 4 | 
 5 | ## Getting Started
 6 | 
 7 | ### Prerequisites
 8 | 
 9 | - Python 3.x
10 | - Required Python packages (install via `uv sync`)
11 | - A Garmin Connect account
12 | - Uses Python Garmin Connect Package to interact with Garmin Connect API: https://github.com/cyberjunky/python-garminconnect
13 | 
14 | ### Environment Variables
15 | 
16 | Create a `.env` file in the root directory from the `.env_template` file with the following variables:
17 | 
18 | - `GARMIN_EMAIL`
19 | - `GARMIN_PASSWORD`
20 | 
21 | ## Generate token for Garmin Connect
22 | 
23 | ```bash
24 | python example.py
25 | ```
26 | 
27 | ## Use MCP Inspector
28 | 
29 | ```bash
30 | mcp dev garmin_mcp_server.py
31 | ```
32 | 
33 | ## Register MCP Server in Claude Desktop
34 | 
35 | ```bash
36 | mcp install garmin_mcp_server.py
37 | ```
38 | 
39 | ### Running the Server
40 | 
41 | 
42 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "garmin-mcp"
 3 | version = "0.1.0"
 4 | description = "Garmin Connect MCP Server"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | dependencies = [
 8 |     "garminconnect>=0.2.25",
 9 |     "mcp[cli]>=1.3.0",
10 |     "readchar>=4.2.1",
11 | ]
12 | 
```

--------------------------------------------------------------------------------
/garmin_mcp_server.py:
--------------------------------------------------------------------------------

```python
  1 | # server.py
  2 | import datetime
  3 | from mcp.server.fastmcp import FastMCP
  4 | import logging
  5 | 
  6 | import requests
  7 | logging.basicConfig(level=logging.INFO)
  8 | logger = logging.getLogger(__name__)
  9 | 
 10 | from dotenv import load_dotenv
 11 | import os
 12 | from garth.exc import GarthHTTPError
 13 | 
 14 | from garminconnect import (
 15 |     Garmin,
 16 |     GarminConnectAuthenticationError,
 17 |     GarminConnectConnectionError,
 18 |     GarminConnectTooManyRequestsError,
 19 | )
 20 | 
 21 | # Load environment variables from .env file
 22 | load_dotenv()
 23 | 
 24 | print(f"Starting Garmin MCP server for {os.getenv('GARMIN_EMAIL')}")
 25 | 
 26 | # Load environment variables if defined
 27 | email = os.getenv("GARMIN_EMAIL")
 28 | password = os.getenv("GARMIN_PASSWORD")
 29 | tokenstore = os.getenv("GARMIN_TOKEN_STORE") or "~/.garminconnect"
 30 | tokenstore_base64 = os.getenv("GARMINTOKENS_BASE64") or "~/.garminconnect_base64"
 31 | api = None
 32 | 
 33 | def init_api(email, password):
 34 |     """Initialize Garmin API with your credentials."""
 35 | 
 36 |     try:
 37 |         # Using Oauth1 and OAuth2 token files from directory
 38 |         print(
 39 |             f"Trying to login to Garmin Connect using token data from directory '{tokenstore}'...\n"
 40 |         )
 41 | 
 42 |         # Using Oauth1 and Oauth2 tokens from base64 encoded string
 43 |         # print(
 44 |         #     f"Trying to login to Garmin Connect using token data from file '{tokenstore_base64}'...\n"
 45 |         # )
 46 |         # dir_path = os.path.expanduser(tokenstore_base64)
 47 |         # with open(dir_path, "r") as token_file:
 48 |         #     tokenstore = token_file.read()
 49 | 
 50 |         garmin = Garmin()
 51 |         garmin.login(tokenstore)
 52 | 
 53 |     except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError):
 54 |         # Session is expired. You'll need to log in again
 55 |         print(
 56 |             "Login tokens not present, login with your Garmin Connect credentials to generate them.\n"
 57 |             f"They will be stored in '{tokenstore}' for future use.\n"
 58 |         )
 59 |         try:
 60 |             # # Ask for credentials if not set as environment variables
 61 |             # if not email or not password:
 62 |             #     email, password = get_credentials()
 63 | 
 64 |             garmin = Garmin(
 65 |                 email=email, password=password, is_cn=False, prompt_mfa=get_mfa
 66 |             )
 67 |             garmin.login()
 68 |             # Save Oauth1 and Oauth2 token files to directory for next login
 69 |             garmin.garth.dump(tokenstore)
 70 |             print(
 71 |                 f"Oauth tokens stored in '{tokenstore}' directory for future use. (first method)\n"
 72 |             )
 73 |             # Encode Oauth1 and Oauth2 tokens to base64 string and safe to file for next login (alternative way)
 74 |             token_base64 = garmin.garth.dumps()
 75 |             dir_path = os.path.expanduser(tokenstore_base64)
 76 |             with open(dir_path, "w") as token_file:
 77 |                 token_file.write(token_base64)
 78 |             print(
 79 |                 f"Oauth tokens encoded as base64 string and saved to '{dir_path}' file for future use. (second method)\n"
 80 |             )
 81 |         except (
 82 |             FileNotFoundError,
 83 |             GarthHTTPError,
 84 |             GarminConnectAuthenticationError,
 85 |             requests.exceptions.HTTPError,
 86 |         ) as err:
 87 |             logger.error(err)
 88 |             return None
 89 | 
 90 |     return garmin
 91 | 
 92 | 
 93 | 
 94 | api = init_api(email, password)
 95 | 
 96 | # Create an MCP server
 97 | mcp = FastMCP("Garmin Connect MCP Server")
 98 | 
 99 | # Add an addition tool
100 | @mcp.tool()
101 | def fetch_sleep_data(date: str) -> dict:
102 |     """Returns sleep data for a given date
103 |     Args:
104 |         date: str - date in format YYYY-MM-DD
105 |     Returns:
106 |         dict - sleep data
107 |     """
108 |     return api.get_sleep_data(date)
109 | 
110 | @mcp.tool()
111 | def fetch_steps_data(date_from: str, date_to: str) -> dict:
112 |     """Returns steps data for the for the given date range
113 |     Args:
114 |         date_from: str - start date in format YYYY-MM-DD
115 |         date_to: str - end date in format YYYY-MM-DD
116 |     Returns:
117 |         dict - steps data
118 |     """
119 |     return api.get_daily_steps(date_from, date_to)
120 | 
121 | @mcp.tool()
122 | def fetch_activities_data(num_activities: int) -> dict:
123 |     """Returns activitie data for the for the given date range
124 |     Args:
125 |         num_activitie: int - number of activitie to fetch
126 |     Returns:
127 |         dict - workouts data
128 |     """
129 |     activities = api.get_activities(limit=num_activities)
130 |     
131 |     # # Get last fetched workout
132 |     # workout_id = workouts[-1]["workoutId"]
133 |     # workout_name = workouts[-1]["workoutName"]
134 |     
135 |     # downloaded_workouts = []
136 |     # for i in range(num_workouts):
137 |     #     workout_id = workouts[-(i + 1)]["workoutId"]
138 |     #     workout_name = workouts[-(i + 1)]["workoutName"]
139 |         
140 |     #     workout_data = api.download_workout(workout_id)
141 | 
142 |     #     downloaded_workouts.append(workout_data)
143 | 
144 |     # return downloaded_workouts
145 |     return activities
146 | 
147 | @mcp.tool()
148 | def fetch_heart_rate_data(date: str) -> dict:
149 |     """Returns heart rate data for a given date
150 |     Args:
151 |         date: str - date in format YYYY-MM-DD
152 |     Returns:
153 |         dict - heart rate data
154 |     """
155 |     return api.get_rhr_day(date)
156 | 
157 | @mcp.tool()
158 | def fetch_stress_data(date: str) -> dict:
159 |     """Returns stress data for a given date
160 |     Args:
161 |         date: str - date in format YYYY-MM-DD
162 |     Returns:
163 |         dict - stress data
164 |     """
165 |     return api.get_stress_data(date)
166 | 
167 | @mcp.tool()
168 | def fetch_body_battery_data(start_date: str, end_date: str) -> dict:
169 |     """Returns body battery data for a given date
170 |     Args:
171 |         start_date: str - start date in format YYYY-MM-DD
172 |         end_date: str - end date in format YYYY-MM-DD
173 |     Returns:
174 |         dict - body battery data
175 |     """
176 |     api.get_body_battery(start_date, end_date)
177 | 
178 |     
179 | 
180 | # Add a dynamic greeting resource
181 | @mcp.resource("greeting://{name}")
182 | def get_greeting(name: str) -> str:
183 |     """Get a personalized greeting"""
184 |     return f"Hello, {name}!"
185 | 
186 |  
187 | 
```

--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | pip3 install garth requests readchar
  4 | 
  5 | export EMAIL=<your garmin email>
  6 | export PASSWORD=<your garmin password>
  7 | 
  8 | """
  9 | import datetime
 10 | from datetime import timezone
 11 | import json
 12 | import logging
 13 | import os
 14 | import sys
 15 | from getpass import getpass
 16 | 
 17 | 
 18 | import readchar
 19 | import requests
 20 | from garth.exc import GarthHTTPError
 21 | 
 22 | from garminconnect import (
 23 |     Garmin,
 24 |     GarminConnectAuthenticationError,
 25 |     GarminConnectConnectionError,
 26 |     GarminConnectTooManyRequestsError,
 27 | )
 28 | 
 29 | # Configure debug logging
 30 | # logging.basicConfig(level=logging.DEBUG)
 31 | logging.basicConfig(level=logging.INFO)
 32 | logger = logging.getLogger(__name__)
 33 | 
 34 | from dotenv import load_dotenv
 35 | 
 36 | # Load environment variables from .env file
 37 | load_dotenv()
 38 | 
 39 | 
 40 | # Load environment variables if defined
 41 | email = os.getenv("GARMIN_EMAIL")
 42 | password = os.getenv("GARMIN_PASSWORD")
 43 | tokenstore = os.getenv("GARMIN_TOKEN_STORE") or "~/.garminconnect"
 44 | tokenstore_base64 = os.getenv("GARMINTOKENS_BASE64") or "~/.garminconnect_base64"
 45 | api = None
 46 | 
 47 | # Example selections and settings
 48 | 
 49 | # Let's say we want to scrape all activities using switch menu_option "p". We change the values of the below variables, IE startdate days, limit,...
 50 | today = datetime.date.today()
 51 | startdate = today - datetime.timedelta(days=7)  # Select past week
 52 | start = 0
 53 | limit = 100
 54 | start_badge = 1  # Badge related calls calls start counting at 1
 55 | activitytype = ""  # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
 56 | activityfile = "MY_ACTIVITY.fit"  # Supported file types are: .fit .gpx .tcx
 57 | weight = 89.6
 58 | weightunit = "kg"
 59 | # workout_example = """
 60 | # {
 61 | #     'workoutId': "random_id",
 62 | #     'ownerId': "random",
 63 | #     'workoutName': 'Any workout name',
 64 | #     'description': 'FTP 200, TSS 1, NP 114, IF 0.57',
 65 | #     'sportType': {'sportTypeId': 2, 'sportTypeKey': 'cycling'},
 66 | #     'workoutSegments': [
 67 | #         {
 68 | #             'segmentOrder': 1,
 69 | #             'sportType': {'sportTypeId': 2, 'sportTypeKey': 'cycling'},
 70 | #             'workoutSteps': [
 71 | #                 {'type': 'ExecutableStepDTO', 'stepOrder': 1,
 72 | #                     'stepType': {'stepTypeId': 3, 'stepTypeKey': 'interval'}, 'childStepId': None,
 73 | #                     'endCondition': {'conditionTypeId': 2, 'conditionTypeKey': 'time'}, 'endConditionValue': 60,
 74 | #                     'targetType': {'workoutTargetTypeId': 2, 'workoutTargetTypeKey': 'power.zone'},
 75 | #                     'targetValueOne': 95, 'targetValueTwo': 105},
 76 | #                 {'type': 'ExecutableStepDTO', 'stepOrder': 2,
 77 | #                     'stepType': {'stepTypeId': 3, 'stepTypeKey': 'interval'}, 'childStepId': None,
 78 | #                     'endCondition': {'conditionTypeId': 2, 'conditionTypeKey': 'time'}, 'endConditionValue': 120,
 79 | #                     'targetType': {'workoutTargetTypeId': 2, 'workoutTargetTypeKey': 'power.zone'},
 80 | #                     'targetValueOne': 114, 'targetValueTwo': 126}
 81 | #             ]
 82 | #         }
 83 | #     ]
 84 | # }
 85 | # """
 86 | 
 87 | menu_options = {
 88 |     "1": "Get full name",
 89 |     "2": "Get unit system",
 90 |     "3": f"Get activity data for '{today.isoformat()}'",
 91 |     "4": f"Get activity data for '{today.isoformat()}' (compatible with garminconnect-ha)",
 92 |     "5": f"Get body composition data for '{today.isoformat()}' (compatible with garminconnect-ha)",
 93 |     "6": f"Get body composition data for from '{startdate.isoformat()}' to '{today.isoformat()}' (to be compatible with garminconnect-ha)",
 94 |     "7": f"Get stats and body composition data for '{today.isoformat()}'",
 95 |     "8": f"Get steps data for '{today.isoformat()}'",
 96 |     "9": f"Get heart rate data for '{today.isoformat()}'",
 97 |     "0": f"Get training readiness data for '{today.isoformat()}'",
 98 |     "-": f"Get daily step data for '{startdate.isoformat()}' to '{today.isoformat()}'",
 99 |     "/": f"Get body battery data for '{startdate.isoformat()}' to '{today.isoformat()}'",
100 |     "!": f"Get floors data for '{startdate.isoformat()}'",
101 |     "?": f"Get blood pressure data for '{startdate.isoformat()}' to '{today.isoformat()}'",
102 |     ".": f"Get training status data for '{today.isoformat()}'",
103 |     "a": f"Get resting heart rate data for {today.isoformat()}'",
104 |     "b": f"Get hydration data for '{today.isoformat()}'",
105 |     "c": f"Get sleep data for '{today.isoformat()}'",
106 |     "d": f"Get stress data for '{today.isoformat()}'",
107 |     "e": f"Get respiration data for '{today.isoformat()}'",
108 |     "f": f"Get SpO2 data for '{today.isoformat()}'",
109 |     "g": f"Get max metric data (like vo2MaxValue and fitnessAge) for '{today.isoformat()}'",
110 |     "h": "Get personal record for user",
111 |     "i": "Get earned badges for user",
112 |     "j": f"Get adhoc challenges data from start '{start}' and limit '{limit}'",
113 |     "k": f"Get available badge challenges data from '{start_badge}' and limit '{limit}'",
114 |     "l": f"Get badge challenges data from '{start_badge}' and limit '{limit}'",
115 |     "m": f"Get non completed badge challenges data from '{start_badge}' and limit '{limit}'",
116 |     "n": f"Get activities data from start '{start}' and limit '{limit}'",
117 |     "o": "Get last activity",
118 |     "p": f"Download activities data by date from '{startdate.isoformat()}' to '{today.isoformat()}'",
119 |     "r": f"Get all kinds of activities data from '{start}'",
120 |     "s": f"Upload activity data from file '{activityfile}'",
121 |     "t": "Get all kinds of Garmin device info",
122 |     "u": "Get active goals",
123 |     "v": "Get future goals",
124 |     "w": "Get past goals",
125 |     "y": "Get all Garmin device alarms",
126 |     "x": f"Get Heart Rate Variability data (HRV) for '{today.isoformat()}'",
127 |     "z": f"Get progress summary from '{startdate.isoformat()}' to '{today.isoformat()}' for all metrics",
128 |     "A": "Get gear, the defaults, activity types and statistics",
129 |     "B": f"Get weight-ins from '{startdate.isoformat()}' to '{today.isoformat()}'",
130 |     "C": f"Get daily weigh-ins for '{today.isoformat()}'",
131 |     "D": f"Delete all weigh-ins for '{today.isoformat()}'",
132 |     "E": f"Add a weigh-in of {weight}{weightunit} on '{today.isoformat()}'",
133 |     "F": f"Get virtual challenges/expeditions from '{startdate.isoformat()}' to '{today.isoformat()}'",
134 |     "G": f"Get hill score data from '{startdate.isoformat()}' to '{today.isoformat()}'",
135 |     "H": f"Get endurance score data from '{startdate.isoformat()}' to '{today.isoformat()}'",
136 |     "I": f"Get activities for date '{today.isoformat()}'",
137 |     "J": "Get race predictions",
138 |     "K": f"Get all day stress data for '{today.isoformat()}'",
139 |     "L": f"Add body composition for '{today.isoformat()}'",
140 |     "M": "Set blood pressure '120,80,80,notes='Testing with example.py'",
141 |     "N": "Get user profile/settings",
142 |     "O": f"Reload epoch data for {today.isoformat()}",
143 |     "P": "Get workouts 0-100, get and download last one to .FIT file",
144 |     # "Q": "Upload workout from json data",
145 |     "R": "Get solar data from your devices",
146 |     "S": "Get pregnancy summary data",
147 |     "T": "Add hydration data",
148 |     "U": f"Get Fitness Age data for {today.isoformat()}",
149 |     "V": f"Get daily wellness events data for {startdate.isoformat()}",
150 |     "W": "Get userprofile settings",
151 |     "Z": "Remove stored login tokens (logout)",
152 |     "q": "Exit",
153 | }
154 | 
155 | 
156 | def display_json(api_call, output):
157 |     """Format API output for better readability."""
158 | 
159 |     dashed = "-" * 20
160 |     header = f"{dashed} {api_call} {dashed}"
161 |     footer = "-" * len(header)
162 | 
163 |     print(header)
164 | 
165 |     if isinstance(output, (int, str, dict, list)):
166 |         print(json.dumps(output, indent=4))
167 |     else:
168 |         print(output)
169 | 
170 |     print(footer)
171 | 
172 | 
173 | def display_text(output):
174 |     """Format API output for better readability."""
175 | 
176 |     dashed = "-" * 60
177 |     header = f"{dashed}"
178 |     footer = "-" * len(header)
179 | 
180 |     print(header)
181 |     print(json.dumps(output, indent=4))
182 |     print(footer)
183 | 
184 | 
185 | def get_credentials():
186 |     """Get user credentials."""
187 | 
188 |     email = input("Login e-mail: ")
189 |     password = getpass("Enter password: ")
190 | 
191 |     return email, password
192 | 
193 | 
194 | def init_api(email, password):
195 |     """Initialize Garmin API with your credentials."""
196 | 
197 |     try:
198 |         # Using Oauth1 and OAuth2 token files from directory
199 |         print(
200 |             f"Trying to login to Garmin Connect using token data from directory '{tokenstore}'...\n"
201 |         )
202 | 
203 |         # Using Oauth1 and Oauth2 tokens from base64 encoded string
204 |         # print(
205 |         #     f"Trying to login to Garmin Connect using token data from file '{tokenstore_base64}'...\n"
206 |         # )
207 |         # dir_path = os.path.expanduser(tokenstore_base64)
208 |         # with open(dir_path, "r") as token_file:
209 |         #     tokenstore = token_file.read()
210 | 
211 |         garmin = Garmin()
212 |         garmin.login(tokenstore)
213 | 
214 |     except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError):
215 |         # Session is expired. You'll need to log in again
216 |         print(
217 |             "Login tokens not present, login with your Garmin Connect credentials to generate them.\n"
218 |             f"They will be stored in '{tokenstore}' for future use.\n"
219 |         )
220 |         try:
221 |             # Ask for credentials if not set as environment variables
222 |             if not email or not password:
223 |                 email, password = get_credentials()
224 | 
225 |             garmin = Garmin(
226 |                 email=email, password=password, is_cn=False, prompt_mfa=get_mfa
227 |             )
228 |             garmin.login()
229 |             # Save Oauth1 and Oauth2 token files to directory for next login
230 |             garmin.garth.dump(tokenstore)
231 |             print(
232 |                 f"Oauth tokens stored in '{tokenstore}' directory for future use. (first method)\n"
233 |             )
234 |             # Encode Oauth1 and Oauth2 tokens to base64 string and safe to file for next login (alternative way)
235 |             token_base64 = garmin.garth.dumps()
236 |             dir_path = os.path.expanduser(tokenstore_base64)
237 |             with open(dir_path, "w") as token_file:
238 |                 token_file.write(token_base64)
239 |             print(
240 |                 f"Oauth tokens encoded as base64 string and saved to '{dir_path}' file for future use. (second method)\n"
241 |             )
242 |         except (
243 |             FileNotFoundError,
244 |             GarthHTTPError,
245 |             GarminConnectAuthenticationError,
246 |             requests.exceptions.HTTPError,
247 |         ) as err:
248 |             logger.error(err)
249 |             return None
250 | 
251 |     return garmin
252 | 
253 | 
254 | def get_mfa():
255 |     """Get MFA."""
256 | 
257 |     return input("MFA one-time code: ")
258 | 
259 | 
260 | def print_menu():
261 |     """Print examples menu."""
262 |     for key in menu_options.keys():
263 |         print(f"{key} -- {menu_options[key]}")
264 |     print("Make your selection: ", end="", flush=True)
265 | 
266 | 
267 | def switch(api, i):
268 |     """Run selected API call."""
269 | 
270 |     # Exit example program
271 |     if i == "q":
272 |         print("Be active, generate some data to fetch next time ;-) Bye!")
273 |         sys.exit()
274 | 
275 |     # Skip requests if login failed
276 |     if api:
277 |         try:
278 |             print(f"\n\nExecuting: {menu_options[i]}\n")
279 | 
280 |             # USER BASICS
281 |             if i == "1":
282 |                 # Get full name from profile
283 |                 display_json("api.get_full_name()", api.get_full_name())
284 |             elif i == "2":
285 |                 # Get unit system from profile
286 |                 display_json("api.get_unit_system()", api.get_unit_system())
287 | 
288 |             # USER STATISTIC SUMMARIES
289 |             elif i == "3":
290 |                 # Get activity data for 'YYYY-MM-DD'
291 |                 display_json(
292 |                     f"api.get_stats('{today.isoformat()}')",
293 |                     api.get_stats(today.isoformat()),
294 |                 )
295 |             elif i == "4":
296 |                 # Get activity data (to be compatible with garminconnect-ha)
297 |                 display_json(
298 |                     f"api.get_user_summary('{today.isoformat()}')",
299 |                     api.get_user_summary(today.isoformat()),
300 |                 )
301 |             elif i == "5":
302 |                 # Get body composition data for 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
303 |                 display_json(
304 |                     f"api.get_body_composition('{today.isoformat()}')",
305 |                     api.get_body_composition(today.isoformat()),
306 |                 )
307 |             elif i == "6":
308 |                 # Get body composition data for multiple days 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
309 |                 display_json(
310 |                     f"api.get_body_composition('{startdate.isoformat()}', '{today.isoformat()}')",
311 |                     api.get_body_composition(startdate.isoformat(), today.isoformat()),
312 |                 )
313 |             elif i == "7":
314 |                 # Get stats and body composition data for 'YYYY-MM-DD'
315 |                 display_json(
316 |                     f"api.get_stats_and_body('{today.isoformat()}')",
317 |                     api.get_stats_and_body(today.isoformat()),
318 |                 )
319 | 
320 |             # USER STATISTICS LOGGED
321 |             elif i == "8":
322 |                 # Get steps data for 'YYYY-MM-DD'
323 |                 display_json(
324 |                     f"api.get_steps_data('{today.isoformat()}')",
325 |                     api.get_steps_data(today.isoformat()),
326 |                 )
327 |             elif i == "9":
328 |                 # Get heart rate data for 'YYYY-MM-DD'
329 |                 display_json(
330 |                     f"api.get_heart_rates('{today.isoformat()}')",
331 |                     api.get_heart_rates(today.isoformat()),
332 |                 )
333 |             elif i == "0":
334 |                 # Get training readiness data for 'YYYY-MM-DD'
335 |                 display_json(
336 |                     f"api.get_training_readiness('{today.isoformat()}')",
337 |                     api.get_training_readiness(today.isoformat()),
338 |                 )
339 |             elif i == "/":
340 |                 # Get daily body battery data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
341 |                 display_json(
342 |                     f"api.get_body_battery('{startdate.isoformat()}, {today.isoformat()}')",
343 |                     api.get_body_battery(startdate.isoformat(), today.isoformat()),
344 |                 )
345 |                 # Get daily body battery event data for 'YYYY-MM-DD'
346 |                 display_json(
347 |                     f"api.get_body_battery_events('{startdate.isoformat()}, {today.isoformat()}')",
348 |                     api.get_body_battery_events(startdate.isoformat()),
349 |                 )
350 |             elif i == "?":
351 |                 # Get daily blood pressure data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
352 |                 display_json(
353 |                     f"api.get_blood_pressure('{startdate.isoformat()}, {today.isoformat()}')",
354 |                     api.get_blood_pressure(startdate.isoformat(), today.isoformat()),
355 |                 )
356 |             elif i == "-":
357 |                 # Get daily step data for 'YYYY-MM-DD'
358 |                 display_json(
359 |                     f"api.get_daily_steps('{startdate.isoformat()}, {today.isoformat()}')",
360 |                     api.get_daily_steps(startdate.isoformat(), today.isoformat()),
361 |                 )
362 |             elif i == "!":
363 |                 # Get daily floors data for 'YYYY-MM-DD'
364 |                 display_json(
365 |                     f"api.get_floors('{today.isoformat()}')",
366 |                     api.get_floors(today.isoformat()),
367 |                 )
368 |             elif i == ".":
369 |                 # Get training status data for 'YYYY-MM-DD'
370 |                 display_json(
371 |                     f"api.get_training_status('{today.isoformat()}')",
372 |                     api.get_training_status(today.isoformat()),
373 |                 )
374 |             elif i == "a":
375 |                 # Get resting heart rate data for 'YYYY-MM-DD'
376 |                 display_json(
377 |                     f"api.get_rhr_day('{today.isoformat()}')",
378 |                     api.get_rhr_day(today.isoformat()),
379 |                 )
380 |             elif i == "b":
381 |                 # Get hydration data 'YYYY-MM-DD'
382 |                 display_json(
383 |                     f"api.get_hydration_data('{today.isoformat()}')",
384 |                     api.get_hydration_data(today.isoformat()),
385 |                 )
386 |             elif i == "c":
387 |                 # Get sleep data for 'YYYY-MM-DD'
388 |                 display_json(
389 |                     f"api.get_sleep_data('{today.isoformat()}')",
390 |                     api.get_sleep_data(today.isoformat()),
391 |                 )
392 |             elif i == "d":
393 |                 # Get stress data for 'YYYY-MM-DD'
394 |                 display_json(
395 |                     f"api.get_stress_data('{today.isoformat()}')",
396 |                     api.get_stress_data(today.isoformat()),
397 |                 )
398 |             elif i == "e":
399 |                 # Get respiration data for 'YYYY-MM-DD'
400 |                 display_json(
401 |                     f"api.get_respiration_data('{today.isoformat()}')",
402 |                     api.get_respiration_data(today.isoformat()),
403 |                 )
404 |             elif i == "f":
405 |                 # Get SpO2 data for 'YYYY-MM-DD'
406 |                 display_json(
407 |                     f"api.get_spo2_data('{today.isoformat()}')",
408 |                     api.get_spo2_data(today.isoformat()),
409 |                 )
410 |             elif i == "g":
411 |                 # Get max metric data (like vo2MaxValue and fitnessAge) for 'YYYY-MM-DD'
412 |                 display_json(
413 |                     f"api.get_max_metrics('{today.isoformat()}')",
414 |                     api.get_max_metrics(today.isoformat()),
415 |                 )
416 |             elif i == "h":
417 |                 # Get personal record for user
418 |                 display_json("api.get_personal_record()", api.get_personal_record())
419 |             elif i == "i":
420 |                 # Get earned badges for user
421 |                 display_json("api.get_earned_badges()", api.get_earned_badges())
422 |             elif i == "j":
423 |                 # Get adhoc challenges data from start and limit
424 |                 display_json(
425 |                     f"api.get_adhoc_challenges({start},{limit})",
426 |                     api.get_adhoc_challenges(start, limit),
427 |                 )  # 1=start, 100=limit
428 |             elif i == "k":
429 |                 # Get available badge challenges data from start and limit
430 |                 display_json(
431 |                     f"api.get_available_badge_challenges({start_badge}, {limit})",
432 |                     api.get_available_badge_challenges(start_badge, limit),
433 |                 )  # 1=start, 100=limit
434 |             elif i == "l":
435 |                 # Get badge challenges data from start and limit
436 |                 display_json(
437 |                     f"api.get_badge_challenges({start_badge}, {limit})",
438 |                     api.get_badge_challenges(start_badge, limit),
439 |                 )  # 1=start, 100=limit
440 |             elif i == "m":
441 |                 # Get non completed badge challenges data from start and limit
442 |                 display_json(
443 |                     f"api.get_non_completed_badge_challenges({start_badge}, {limit})",
444 |                     api.get_non_completed_badge_challenges(start_badge, limit),
445 |                 )  # 1=start, 100=limit
446 | 
447 |             # ACTIVITIES
448 |             elif i == "n":
449 |                 # Get activities data from start and limit
450 |                 display_json(
451 |                     f"api.get_activities({start}, {limit})",
452 |                     api.get_activities(start, limit),
453 |                 )  # 0=start, 1=limit
454 |             elif i == "o":
455 |                 # Get last activity
456 |                 display_json("api.get_last_activity()", api.get_last_activity())
457 |             elif i == "p":
458 |                 # Get activities data from startdate 'YYYY-MM-DD' to enddate 'YYYY-MM-DD', with (optional) activitytype
459 |                 # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
460 |                 activities = api.get_activities_by_date(
461 |                     startdate.isoformat(), today.isoformat(), activitytype
462 |                 )
463 | 
464 |                 # Download activities
465 |                 for activity in activities:
466 |                     activity_start_time = datetime.datetime.strptime(
467 |                         activity["startTimeLocal"], "%Y-%m-%d %H:%M:%S"
468 |                     ).strftime(
469 |                         "%d-%m-%Y"
470 |                     )  # Format as DD-MM-YYYY, for creating unique activity names for scraping
471 |                     activity_id = activity["activityId"]
472 |                     activity_name = activity["activityName"]
473 |                     display_text(activity)
474 | 
475 |                     print(
476 |                         f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.GPX)"
477 |                     )
478 |                     gpx_data = api.download_activity(
479 |                         activity_id, dl_fmt=api.ActivityDownloadFormat.GPX
480 |                     )
481 |                     output_file = f"./{str(activity_name)}_{str(activity_start_time)}_{str(activity_id)}.gpx"
482 |                     with open(output_file, "wb") as fb:
483 |                         fb.write(gpx_data)
484 |                     print(f"Activity data downloaded to file {output_file}")
485 | 
486 |                     print(
487 |                         f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.TCX)"
488 |                     )
489 |                     tcx_data = api.download_activity(
490 |                         activity_id, dl_fmt=api.ActivityDownloadFormat.TCX
491 |                     )
492 |                     output_file = f"./{str(activity_name)}_{str(activity_start_time)}_{str(activity_id)}.tcx"
493 |                     with open(output_file, "wb") as fb:
494 |                         fb.write(tcx_data)
495 |                     print(f"Activity data downloaded to file {output_file}")
496 | 
497 |                     print(
498 |                         f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.ORIGINAL)"
499 |                     )
500 |                     zip_data = api.download_activity(
501 |                         activity_id, dl_fmt=api.ActivityDownloadFormat.ORIGINAL
502 |                     )
503 |                     output_file = f"./{str(activity_name)}_{str(activity_start_time)}_{str(activity_id)}.zip"
504 |                     with open(output_file, "wb") as fb:
505 |                         fb.write(zip_data)
506 |                     print(f"Activity data downloaded to file {output_file}")
507 | 
508 |                     print(
509 |                         f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.CSV)"
510 |                     )
511 |                     csv_data = api.download_activity(
512 |                         activity_id, dl_fmt=api.ActivityDownloadFormat.CSV
513 |                     )
514 |                     output_file = f"./{str(activity_name)}_{str(activity_start_time)}_{str(activity_id)}.csv"
515 |                     with open(output_file, "wb") as fb:
516 |                         fb.write(csv_data)
517 |                     print(f"Activity data downloaded to file {output_file}")
518 | 
519 |             elif i == "r":
520 |                 # Get activities data from start and limit
521 |                 activities = api.get_activities(start, limit)  # 0=start, 1=limit
522 | 
523 |                 # Get activity splits
524 |                 first_activity_id = activities[0].get("activityId")
525 | 
526 |                 display_json(
527 |                     f"api.get_activity_splits({first_activity_id})",
528 |                     api.get_activity_splits(first_activity_id),
529 |                 )
530 | 
531 |                 # Get activity typed splits
532 | 
533 |                 display_json(
534 |                     f"api.get_activity_typed_splits({first_activity_id})",
535 |                     api.get_activity_typed_splits(first_activity_id),
536 |                 )
537 |                 # Get activity split summaries for activity id
538 |                 display_json(
539 |                     f"api.get_activity_split_summaries({first_activity_id})",
540 |                     api.get_activity_split_summaries(first_activity_id),
541 |                 )
542 | 
543 |                 # Get activity weather data for activity
544 |                 display_json(
545 |                     f"api.get_activity_weather({first_activity_id})",
546 |                     api.get_activity_weather(first_activity_id),
547 |                 )
548 | 
549 |                 # Get activity hr timezones id
550 |                 display_json(
551 |                     f"api.get_activity_hr_in_timezones({first_activity_id})",
552 |                     api.get_activity_hr_in_timezones(first_activity_id),
553 |                 )
554 | 
555 |                 # Get activity details for activity id
556 |                 display_json(
557 |                     f"api.get_activity_details({first_activity_id})",
558 |                     api.get_activity_details(first_activity_id),
559 |                 )
560 | 
561 |                 # Get gear data for activity id
562 |                 display_json(
563 |                     f"api.get_activity_gear({first_activity_id})",
564 |                     api.get_activity_gear(first_activity_id),
565 |                 )
566 | 
567 |                 # Activity data for activity id
568 |                 display_json(
569 |                     f"api.get_activity({first_activity_id})",
570 |                     api.get_activity(first_activity_id),
571 |                 )
572 | 
573 |                 # Get exercise sets in case the activity is a strength_training
574 |                 if activities[0]["activityType"]["typeKey"] == "strength_training":
575 |                     display_json(
576 |                         f"api.get_activity_exercise_sets({first_activity_id})",
577 |                         api.get_activity_exercise_sets(first_activity_id),
578 |                     )
579 | 
580 |             elif i == "s":
581 |                 try:
582 |                     # Upload activity from file
583 |                     display_json(
584 |                         f"api.upload_activity({activityfile})",
585 |                         api.upload_activity(activityfile),
586 |                     )
587 |                 except FileNotFoundError:
588 |                     print(f"File to upload not found: {activityfile}")
589 | 
590 |             # DEVICES
591 |             elif i == "t":
592 |                 # Get Garmin devices
593 |                 devices = api.get_devices()
594 |                 display_json("api.get_devices()", devices)
595 | 
596 |                 # Get device last used
597 |                 device_last_used = api.get_device_last_used()
598 |                 display_json("api.get_device_last_used()", device_last_used)
599 | 
600 |                 # Get settings per device
601 |                 for device in devices:
602 |                     device_id = device["deviceId"]
603 |                     display_json(
604 |                         f"api.get_device_settings({device_id})",
605 |                         api.get_device_settings(device_id),
606 |                     )
607 | 
608 |                 # Get primary training device information
609 |                 primary_training_device = api.get_primary_training_device()
610 |                 display_json(
611 |                     "api.get_primary_training_device()", primary_training_device
612 |                 )
613 | 
614 |             elif i == "R":
615 |                 # Get solar data from Garmin devices
616 |                 devices = api.get_devices()
617 |                 display_json("api.get_devices()", devices)
618 | 
619 |                 # Get device last used
620 |                 device_last_used = api.get_device_last_used()
621 |                 display_json("api.get_device_last_used()", device_last_used)
622 | 
623 |                 # Get settings per device
624 |                 for device in devices:
625 |                     device_id = device["deviceId"]
626 |                     display_json(
627 |                         f"api.get_device_solar_data({device_id}, {today.isoformat()})",
628 |                         api.get_device_solar_data(device_id, today.isoformat()),
629 |                     )
630 |             # GOALS
631 |             elif i == "u":
632 |                 # Get active goals
633 |                 goals = api.get_goals("active")
634 |                 display_json('api.get_goals("active")', goals)
635 | 
636 |             elif i == "v":
637 |                 # Get future goals
638 |                 goals = api.get_goals("future")
639 |                 display_json('api.get_goals("future")', goals)
640 | 
641 |             elif i == "w":
642 |                 # Get past goals
643 |                 goals = api.get_goals("past")
644 |                 display_json('api.get_goals("past")', goals)
645 | 
646 |             # ALARMS
647 |             elif i == "y":
648 |                 # Get Garmin device alarms
649 |                 alarms = api.get_device_alarms()
650 |                 for alarm in alarms:
651 |                     alarm_id = alarm["alarmId"]
652 |                     display_json(f"api.get_device_alarms({alarm_id})", alarm)
653 | 
654 |             elif i == "x":
655 |                 # Get Heart Rate Variability (hrv) data
656 |                 display_json(
657 |                     f"api.get_hrv_data({today.isoformat()})",
658 |                     api.get_hrv_data(today.isoformat()),
659 |                 )
660 | 
661 |             elif i == "z":
662 |                 # Get progress summary
663 |                 for metric in [
664 |                     "elevationGain",
665 |                     "duration",
666 |                     "distance",
667 |                     "movingDuration",
668 |                 ]:
669 |                     display_json(
670 |                         f"api.get_progress_summary_between_dates({today.isoformat()})",
671 |                         api.get_progress_summary_between_dates(
672 |                             startdate.isoformat(), today.isoformat(), metric
673 |                         ),
674 |                     )
675 |             # GEAR
676 |             elif i == "A":
677 |                 last_used_device = api.get_device_last_used()
678 |                 display_json("api.get_device_last_used()", last_used_device)
679 |                 userProfileNumber = last_used_device["userProfileNumber"]
680 |                 gear = api.get_gear(userProfileNumber)
681 |                 display_json("api.get_gear()", gear)
682 |                 display_json(
683 |                     "api.get_gear_defaults()", api.get_gear_defaults(userProfileNumber)
684 |                 )
685 |                 display_json("api.get()", api.get_activity_types())
686 |                 for gear in gear:
687 |                     uuid = gear["uuid"]
688 |                     name = gear["displayName"]
689 |                     display_json(
690 |                         f"api.get_gear_stats({uuid}) / {name}", api.get_gear_stats(uuid)
691 |                     )
692 | 
693 |             # WEIGHT-INS
694 |             elif i == "B":
695 |                 # Get weigh-ins data
696 |                 display_json(
697 |                     f"api.get_weigh_ins({startdate.isoformat()}, {today.isoformat()})",
698 |                     api.get_weigh_ins(startdate.isoformat(), today.isoformat()),
699 |                 )
700 |             elif i == "C":
701 |                 # Get daily weigh-ins data
702 |                 display_json(
703 |                     f"api.get_daily_weigh_ins({today.isoformat()})",
704 |                     api.get_daily_weigh_ins(today.isoformat()),
705 |                 )
706 |             elif i == "D":
707 |                 # Delete weigh-ins data for today
708 |                 display_json(
709 |                     f"api.delete_weigh_ins({today.isoformat()}, delete_all=True)",
710 |                     api.delete_weigh_ins(today.isoformat(), delete_all=True),
711 |                 )
712 |             elif i == "E":
713 |                 # Add a weigh-in
714 |                 display_json(
715 |                     f"api.add_weigh_in(weight={weight}, unitKey={weightunit})",
716 |                     api.add_weigh_in(weight=weight, unitKey=weightunit),
717 |                 )
718 | 
719 |                 # Add a weigh-in with timestamps
720 |                 yesterday = today - datetime.timedelta(days=1) # Get yesterday's date
721 |                 weigh_in_date = datetime.datetime.strptime(yesterday.isoformat(), "%Y-%m-%d")
722 |                 local_timestamp = weigh_in_date.strftime('%Y-%m-%dT%H:%M:%S')
723 |                 gmt_timestamp = weigh_in_date.astimezone(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S')
724 | 
725 |                 display_json(
726 |                     f"api.add_weigh_in_with_timestamps(weight={weight}, unitKey={weightunit}, dateTimestamp={local_timestamp}, gmtTimestamp={gmt_timestamp})",
727 |                     api.add_weigh_in_with_timestamps(
728 |                         weight=weight,
729 |                         unitKey=weightunit,
730 |                         dateTimestamp=local_timestamp,
731 |                         gmtTimestamp=gmt_timestamp
732 |                     )
733 |                 )
734 | 
735 |             # CHALLENGES/EXPEDITIONS
736 |             elif i == "F":
737 |                 # Get virtual challenges/expeditions
738 |                 display_json(
739 |                     f"api.get_inprogress_virtual_challenges({startdate.isoformat()}, {today.isoformat()})",
740 |                     api.get_inprogress_virtual_challenges(
741 |                         startdate.isoformat(), today.isoformat()
742 |                     ),
743 |                 )
744 |             elif i == "G":
745 |                 # Get hill score data
746 |                 display_json(
747 |                     f"api.get_hill_score({startdate.isoformat()}, {today.isoformat()})",
748 |                     api.get_hill_score(startdate.isoformat(), today.isoformat()),
749 |                 )
750 |             elif i == "H":
751 |                 # Get endurance score data
752 |                 display_json(
753 |                     f"api.get_endurance_score({startdate.isoformat()}, {today.isoformat()})",
754 |                     api.get_endurance_score(startdate.isoformat(), today.isoformat()),
755 |                 )
756 |             elif i == "I":
757 |                 # Get activities for date
758 |                 display_json(
759 |                     f"api.get_activities_fordate({today.isoformat()})",
760 |                     api.get_activities_fordate(today.isoformat()),
761 |                 )
762 |             elif i == "J":
763 |                 # Get race predictions
764 |                 display_json("api.get_race_predictions()", api.get_race_predictions())
765 |             elif i == "K":
766 |                 # Get all day stress data for date
767 |                 display_json(
768 |                     f"api.get_all_day_stress({today.isoformat()})",
769 |                     api.get_all_day_stress(today.isoformat()),
770 |                 )
771 |             elif i == "L":
772 |                 # Add body composition
773 |                 weight = 70.0
774 |                 percent_fat = 15.4
775 |                 percent_hydration = 54.8
776 |                 visceral_fat_mass = 10.8
777 |                 bone_mass = 2.9
778 |                 muscle_mass = 55.2
779 |                 basal_met = 1454.1
780 |                 active_met = None
781 |                 physique_rating = None
782 |                 metabolic_age = 33.0
783 |                 visceral_fat_rating = None
784 |                 bmi = 22.2
785 |                 display_json(
786 |                     f"api.add_body_composition({today.isoformat()}, {weight}, {percent_fat}, {percent_hydration}, {visceral_fat_mass}, {bone_mass}, {muscle_mass}, {basal_met}, {active_met}, {physique_rating}, {metabolic_age}, {visceral_fat_rating}, {bmi})",
787 |                     api.add_body_composition(
788 |                         today.isoformat(),
789 |                         weight=weight,
790 |                         percent_fat=percent_fat,
791 |                         percent_hydration=percent_hydration,
792 |                         visceral_fat_mass=visceral_fat_mass,
793 |                         bone_mass=bone_mass,
794 |                         muscle_mass=muscle_mass,
795 |                         basal_met=basal_met,
796 |                         active_met=active_met,
797 |                         physique_rating=physique_rating,
798 |                         metabolic_age=metabolic_age,
799 |                         visceral_fat_rating=visceral_fat_rating,
800 |                         bmi=bmi,
801 |                     ),
802 |                 )
803 |             elif i == "M":
804 |                 # Set blood pressure values
805 |                 display_json(
806 |                     "api.set_blood_pressure(120, 80, 80, notes=`Testing with example.py`)",
807 |                     api.set_blood_pressure(
808 |                         120, 80, 80, notes="Testing with example.py"
809 |                     ),
810 |                 )
811 |             elif i == "N":
812 |                 # Get user profile
813 |                 display_json("api.get_user_profile()", api.get_user_profile())
814 |             elif i == "O":
815 |                 # Reload epoch data for date
816 |                 display_json(
817 |                     f"api.request_reload({today.isoformat()})",
818 |                     api.request_reload(today.isoformat()),
819 |                 )
820 | 
821 |             # WORKOUTS
822 |             elif i == "P":
823 |                 workouts = api.get_workouts()
824 |                 # Get workout 0-100
825 |                 display_json("api.get_workouts()", api.get_workouts())
826 | 
827 |                 # Get last fetched workout
828 |                 workout_id = workouts[-1]["workoutId"]
829 |                 workout_name = workouts[-1]["workoutName"]
830 |                 display_json(
831 |                     f"api.get_workout_by_id({workout_id})",
832 |                     api.get_workout_by_id(workout_id),
833 |                 )
834 | 
835 |                 # Download last fetched workout
836 |                 print(f"api.download_workout({workout_id})")
837 |                 workout_data = api.download_workout(workout_id)
838 | 
839 |                 output_file = f"./{str(workout_name)}.fit"
840 |                 with open(output_file, "wb") as fb:
841 |                     fb.write(workout_data)
842 |                 print(f"Workout data downloaded to file {output_file}")
843 | 
844 |             # elif i == "Q":
845 |             #     display_json(
846 |             #         f"api.upload_workout({workout_example})",
847 |             #         api.upload_workout(workout_example))
848 | 
849 |             # DAILY EVENTS
850 |             elif i == "V":
851 |                 # Get all day wellness events for 7 days ago
852 |                 display_json(
853 |                     f"api.get_all_day_events({today.isoformat()})",
854 |                     api.get_all_day_events(startdate.isoformat()),
855 |                 )
856 |             # WOMEN'S HEALTH
857 |             elif i == "S":
858 |                 # Get pregnancy summary data
859 |                 display_json("api.get_pregnancy_summary()", api.get_pregnancy_summary())
860 | 
861 |             # Additional related calls:
862 |             # get_menstrual_data_for_date(self, fordate: str): takes a single date and returns the Garmin Menstrual Summary data for that date
863 |             # get_menstrual_calendar_data(self, startdate: str, enddate: str) takes two dates and returns summaries of cycles that have days between the two days
864 | 
865 |             elif i == "T":
866 |                 # Add hydration data for today
867 |                 value_in_ml = 240
868 |                 raw_date = datetime.date.today()
869 |                 cdate = str(raw_date)
870 |                 raw_ts = datetime.datetime.now()
871 |                 timestamp = datetime.datetime.strftime(raw_ts, "%Y-%m-%dT%H:%M:%S.%f")
872 | 
873 |                 display_json(
874 |                     f"api.add_hydration_data(value_in_ml={value_in_ml},cdate='{cdate}',timestamp='{timestamp}')",
875 |                     api.add_hydration_data(
876 |                         value_in_ml=value_in_ml, cdate=cdate, timestamp=timestamp
877 |                     ),
878 |                 )
879 | 
880 |             elif i == "U":
881 |                 # Get fitness age data
882 |                 display_json(
883 |                     f"api.get_fitnessage_data({today.isoformat()})",
884 |                     api.get_fitnessage_data(today.isoformat()),
885 |                 )
886 | 
887 |             elif i == "W":
888 |                 # Get userprofile settings
889 |                 display_json(
890 |                     "api.get_userprofile_settings()", api.get_userprofile_settings()
891 |                 )
892 | 
893 |             elif i == "Z":
894 |                 # Remove stored login tokens for Garmin Connect portal
895 |                 tokendir = os.path.expanduser(tokenstore)
896 |                 print(f"Removing stored login tokens from: {tokendir}")
897 |                 try:
898 |                     for root, dirs, files in os.walk(tokendir, topdown=False):
899 |                         for name in files:
900 |                             os.remove(os.path.join(root, name))
901 |                         for name in dirs:
902 |                             os.rmdir(os.path.join(root, name))
903 |                     print(f"Directory {tokendir} removed")
904 |                 except FileNotFoundError:
905 |                     print(f"Directory not found: {tokendir}")
906 |                 api = None
907 | 
908 |         except (
909 |             GarminConnectConnectionError,
910 |             GarminConnectAuthenticationError,
911 |             GarminConnectTooManyRequestsError,
912 |             requests.exceptions.HTTPError,
913 |             GarthHTTPError,
914 |         ) as err:
915 |             logger.error(err)
916 |         except KeyError:
917 |             # Invalid menu option chosen
918 |             pass
919 |     else:
920 |         print("Could not login to Garmin Connect, try again later.")
921 | 
922 | 
923 | # Main program loop
924 | while True:
925 |     # Display header and login
926 |     print("\n*** Garmin Connect API Demo by cyberjunky ***\n")
927 | 
928 |     # Init API
929 |     if not api:
930 |         api = init_api(email, password)
931 | 
932 |     if api:
933 |         # Display menu
934 |         print_menu()
935 |         option = readchar.readkey()
936 |         switch(api, option)
937 |     else:
938 |         api = init_api(email, password)
```