1313MODERN_URL = 'https://connect.garmin.com/modern'
1414SIGNIN_URL = 'https://sso.garmin.com/sso/signin'
1515
16+
1617class Garmin (object ):
1718 """
1819 Object using Garmin Connect 's API-method.
1920 See https://connect.garmin.com/
2021 """
2122 url_user_summary = MODERN_URL + '/proxy/usersummary-service/usersummary/daily/'
22- url_user_summary_chart = MODERN_URL + '/proxy/wellness-service/wellness/dailySummaryChart/'
23+ url_user_summary_chart = MODERN_URL + \
24+ '/proxy/wellness-service/wellness/dailySummaryChart/'
2325 url_heartrates = MODERN_URL + '/proxy/wellness-service/wellness/dailyHeartRate/'
2426 url_sleepdata = MODERN_URL + '/proxy/wellness-service/wellness/dailySleepData/'
25- url_body_composition = MODERN_URL + '/proxy/weight-service/weight/daterangesnapshot'
26- url_activities = MODERN_URL + '/proxy/activitylist-service/activities/search/activities'
27- url_exercise_sets = MODERN_URL + '/proxy/activity-service/activity/'
27+ url_body_composition = MODERN_URL + \
28+ '/proxy/weight-service/weight/daterangesnapshot'
29+ url_activities = MODERN_URL + \
30+ '/proxy/activitylist-service/activities/search/activities'
31+ url_activity = MODERN_URL + '/proxy/activity-service/activity/'
32+ url_personal_record = MODERN_URL + '/proxy/personalrecord-service/personalrecord/'
2833 url_tcx_download = MODERN_URL + "/proxy/download-service/export/tcx/activity/"
2934 url_gpx_download = MODERN_URL + "/proxy/download-service/export/gpx/activity/"
3035 url_kml_download = MODERN_URL + "/proxy/download-service/export/kml/activity/"
3136 url_fit_download = MODERN_URL + "/proxy/download-service/files/activity/"
3237 url_csv_download = MODERN_URL + "/proxy/download-service/export/csv/activity/"
3338 url_device_list = MODERN_URL + '/proxy/device-service/deviceregistration/devices'
34- url_device_settings = MODERN_URL + '/proxy/device-service/deviceservice/device-info/settings/'
39+ url_device_service = MODERN_URL + \
40+ '/proxy/device-service/deviceservice/'
3541
3642 headers = {
3743 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36' ,
@@ -50,7 +56,6 @@ def __init__(self, email, password):
5056 self .full_name = ""
5157 self .unit_system = ""
5258
53-
5459 def login (self ):
5560 """
5661 Login to portal
@@ -87,9 +92,11 @@ def login(self):
8792 'displayNameRequired' : 'false'
8893 }
8994
90- self .logger .debug ("Login to Garmin Connect using POST url %s" , SIGNIN_URL )
95+ self .logger .debug (
96+ "Login to Garmin Connect using POST url %s" , SIGNIN_URL )
9197 try :
92- response = self .req .post (SIGNIN_URL , headers = self .headers , params = params , data = data )
98+ response = self .req .post (
99+ SIGNIN_URL , headers = self .headers , params = params , data = data )
93100 if response .status_code == 429 :
94101 raise GarminConnectTooManyRequestsError ("Too many requests" )
95102
@@ -99,7 +106,8 @@ def login(self):
99106 raise GarminConnectConnectionError ("Error connecting" ) from err
100107
101108 self .logger .debug ("Response is %s" , response .text )
102- response_url = re .search (r'"(https:[^"]+?ticket=[^"]+)"' , response .text )
109+ response_url = re .search (
110+ r'"(https:[^"]+?ticket=[^"]+)"' , response .text )
103111
104112 if not response_url :
105113 raise GarminConnectAuthenticationError ("Authentication error" )
@@ -117,17 +125,18 @@ def login(self):
117125
118126 self .logger .debug ("Profile info is %s" , response .text )
119127
120- self .user_prefs = self .parse_json (response .text , 'VIEWER_USERPREFERENCES' )
128+ self .user_prefs = self .parse_json (
129+ response .text , 'VIEWER_USERPREFERENCES' )
121130 self .unit_system = self .user_prefs ['measurementSystem' ]
122131 self .logger .debug ("Unit system is %s" , self .unit_system )
123132
124- self .social_profile = self .parse_json (response .text , 'VIEWER_SOCIAL_PROFILE' )
133+ self .social_profile = self .parse_json (
134+ response .text , 'VIEWER_SOCIAL_PROFILE' )
125135 self .display_name = self .social_profile ['displayName' ]
126136 self .full_name = self .social_profile ['fullName' ]
127137 self .logger .debug ("Display name is %s" , self .display_name )
128138 self .logger .debug ("Fullname is %s" , self .full_name )
129139
130-
131140 def parse_json (self , html , key ):
132141 """
133142 Find and return json data
@@ -137,7 +146,6 @@ def parse_json(self, html, key):
137146 text = found .group (1 ).replace ('\\ "' , '"' )
138147 return json .loads (text )
139148
140-
141149 def fetch_data (self , url ):
142150 """
143151 Fetch and return data
@@ -150,57 +158,59 @@ def fetch_data(self, url):
150158 self .logger .debug ("Fetch response code %s" , response .status_code )
151159 response .raise_for_status ()
152160 except requests .exceptions .HTTPError as err :
153- self .logger .debug ("Exception occurred during data retrieval - perhaps session expired - trying relogin: %s" % err )
161+ self .logger .debug (
162+ "Exception occurred during data retrieval - perhaps session expired - trying relogin: %s" % err )
154163 self .login ()
155164 try :
156165 response = self .req .get (url , headers = self .headers )
157166 if response .status_code == 429 :
158- raise GarminConnectTooManyRequestsError ("Too many requests" )
167+ raise GarminConnectTooManyRequestsError (
168+ "Too many requests" )
159169
160- self .logger .debug ("Fetch response code %s" , response .status_code )
170+ self .logger .debug ("Fetch response code %s" ,
171+ response .status_code )
161172 response .raise_for_status ()
162173 except requests .exceptions .HTTPError as err :
163- self .logger .debug ("Exception occurred during data retrieval, relogin without effect: %s" % err )
174+ self .logger .debug (
175+ "Exception occurred during data retrieval, relogin without effect: %s" % err )
164176 raise GarminConnectConnectionError ("Error connecting" ) from err
165177
166178 resp_json = response .json ()
167179 self .logger .debug ("Fetch response json %s" , resp_json )
168180 return resp_json
169181
170-
171182 def get_full_name (self ):
172183 """
173184 Return full name
174185 """
175186 return self .full_name
176187
177-
178188 def get_unit_system (self ):
179189 """
180190 Return unit system
181191 """
182192 return self .unit_system
183193
184-
185194 def get_stats_and_body (self , cdate ):
186195 """
187196 Return activity data and body composition
188197 """
189198 return ({** self .get_stats (cdate ), ** self .get_body_composition (cdate )['totalAverage' ]})
190199
191-
192200 def get_stats (self , cdate ): # cDate = 'YYY-mm-dd'
193201 """
194202 Fetch available activity data
195203 """
196- summaryurl = self .url_user_summary + self .display_name + '?' + 'calendarDate=' + cdate
204+ summaryurl = self .url_user_summary + \
205+ self .display_name + '?' + 'calendarDate=' + cdate
197206 self .logger .debug ("Fetching statistics %s" , summaryurl )
198207 try :
199208 response = self .req .get (summaryurl , headers = self .headers )
200209 if response .status_code == 429 :
201210 raise GarminConnectTooManyRequestsError ("Too many requests" )
202211
203- self .logger .debug ("Statistics response code %s" , response .status_code )
212+ self .logger .debug ("Statistics response code %s" ,
213+ response .status_code )
204214 response .raise_for_status ()
205215 except requests .exceptions .HTTPError as err :
206216 raise GarminConnectConnectionError ("Error connecting" ) from err
@@ -212,20 +222,22 @@ def get_stats(self, cdate): # cDate = 'YYY-mm-dd'
212222 try :
213223 response = self .req .get (summaryurl , headers = self .headers )
214224 if response .status_code == 429 :
215- raise GarminConnectTooManyRequestsError ("Too many requests" )
225+ raise GarminConnectTooManyRequestsError (
226+ "Too many requests" )
216227
217- self .logger .debug ("Statistics response code %s" , response .status_code )
228+ self .logger .debug (
229+ "Statistics response code %s" , response .status_code )
218230 response .raise_for_status ()
219231 except requests .exceptions .HTTPError as err :
220- self .logger .debug ("Exception occurred during statistics retrieval, relogin without effect: %s" % err )
232+ self .logger .debug (
233+ "Exception occurred during statistics retrieval, relogin without effect: %s" % err )
221234 raise GarminConnectConnectionError ("Error connecting" ) from err
222235 else :
223236 resp_json = response .json ()
224237
225238 self .logger .debug ("Statistics response json %s" , resp_json )
226239 return resp_json
227240
228-
229241 def get_heart_rates (self , cdate ): # cDate = 'YYYY-mm-dd'
230242 """
231243 Fetch available heart rates data
@@ -235,7 +247,6 @@ def get_heart_rates(self, cdate): # cDate = 'YYYY-mm-dd'
235247
236248 return self .fetch_data (hearturl )
237249
238-
239250 def get_sleep_data (self , cdate ): # cDate = 'YYYY-mm-dd'
240251 """
241252 Fetch available sleep data
@@ -254,26 +265,28 @@ def get_steps_data(self, cdate): # cDate = 'YYYY-mm-dd'
254265
255266 return self .fetch_data (steps_url )
256267
257-
258268 def get_body_composition (self , cdate ): # cDate = 'YYYY-mm-dd'
259269 """
260270 Fetch available body composition data (only for cDate)
261271 """
262- bodycompositionurl = self .url_body_composition + '?startDate=' + cdate + '&endDate=' + cdate
263- self .logger .debug ("Fetching body composition with url %s" , bodycompositionurl )
272+ bodycompositionurl = self .url_body_composition + \
273+ '?startDate=' + cdate + '&endDate=' + cdate
274+ self .logger .debug (
275+ "Fetching body composition with url %s" , bodycompositionurl )
264276
265277 return self .fetch_data (bodycompositionurl )
266278
267279 def get_activities (self , start , limit ):
268280 """
269281 Fetch available activities
270282 """
271- activitiesurl = self .url_activities + '?start=' + str (start ) + '&limit=' + str (limit )
283+ activitiesurl = self .url_activities + '?start=' + \
284+ str (start ) + '&limit=' + str (limit )
272285 self .logger .debug ("Fetching activities with url %s" , activitiesurl )
273286
274287 return self .fetch_data (activitiesurl )
275288
276- def get_activities_by_date (self , startdate : str , enddate : str , activitytype = "" ) -> list [ json ] :
289+ def get_activities_by_date (self , startdate , enddate , activitytype ) :
277290 """
278291 Fetch available activities between specific dates
279292
@@ -297,7 +310,7 @@ def get_activities_by_date(self, startdate: str, enddate: str, activitytype = ""
297310 activityslug = ""
298311 while returndata :
299312 activitiesurl = self .url_activities + '?startDate=' + str (startdate ) + '&endDate=' + str (
300- enddate ) + '&start=' + str (start ) + '&limit=' + str (limit ) + activityslug
313+ enddate ) + '&start=' + str (start ) + '&limit=' + str (limit ) + activityslug
301314 self .logger .debug ("Fetching activities with url %s" , activitiesurl )
302315 act = self .fetch_data (activitiesurl )
303316 if act :
@@ -310,28 +323,86 @@ def get_activities_by_date(self, startdate: str, enddate: str, activitytype = ""
310323
311324 def get_excercise_sets (self , activity_id ):
312325 activity_id = str (activity_id )
313- exercisesetsurl = f"{ self .url_exercise_sets } { activity_id } /exerciseSets"
314- self .logger .debug (f"Fetching exercise sets for activity_id { activity_id } " )
326+ exercisesetsurl = f"{ self .url_activity } { activity_id } /exerciseSets"
327+ self .logger .debug (
328+ f"Fetching exercise sets for activity_id { activity_id } " )
315329
316330 return self .fetch_data (exercisesetsurl )
317331
332+ def get_activity_splits (self , activity_id ):
333+ activity_id = str (activity_id )
334+ splits_url = f"{ self .url_activity } { activity_id } /splits"
335+ self .logger .debug (
336+ f"Fetching splits for activity_id { activity_id } " )
337+
338+ return self .fetch_data (splits_url )
339+
340+ def get_activity_split_summaries (self , activity_id ):
341+ activity_id = str (activity_id )
342+ split_summaries_url = f"{ self .url_activity } { activity_id } /split_summaries"
343+ self .logger .debug (
344+ f"Fetching split summaries for activity_id { activity_id } " )
345+
346+ return self .fetch_data (split_summaries_url )
347+
348+ def get_activity_weather (self , activity_id ):
349+ activity_id = str (activity_id )
350+ activity_weather_url = f"{ self .url_activity } { activity_id } /weather"
351+ self .logger .debug (
352+ f"Fetching weather for activity_id { activity_id } " )
353+
354+ return self .fetch_data (activity_weather_url )
355+
356+ def get_activity_hr_in_timezones (self , activity_id ):
357+ activity_id = str (activity_id )
358+ activity_hr_timezone_url = f"{ self .url_activity } { activity_id } /hrTimeInZones"
359+ self .logger .debug (
360+ f"Fetching split summaries for activity_id { activity_id } " )
361+
362+ return self .fetch_data (activity_hr_timezone_url )
363+
364+ def get_activity_details (self , activity_id , maxChartSize = 2000 , maxPolylineSize = 4000 ):
365+ activity_id = str (activity_id )
366+ params = f"maxChartSize={ maxChartSize } &maxPolylineSize={ maxPolylineSize } "
367+ details_url = f"{ self .url_activity } { activity_id } /details?{ params } "
368+ self .logger .debug (
369+ f"Fetching details for activity_id { activity_id } " )
370+
371+ return self .fetch_data (details_url )
372+
373+ def get_personal_record (self , owner_display_name ):
374+ personal_records_url = f"{ self .url_personal_record } prs/{ owner_display_name } "
375+ self .logger .debug (
376+ f"Fetching prs for owner { owner_display_name } " )
377+
378+ return self .fetch_data (personal_records_url )
379+
318380 def get_devices (self ):
319381 """
320382 Fetch available devices for the current account
321383 """
322384 devicesurl = self .url_device_list
323- self .logger .debug ("Fetching available devices for the current account with url %s" , devicesurl )
385+ self .logger .debug (
386+ "Fetching available devices for the current account with url %s" , devicesurl )
324387
325388 return self .fetch_data (devicesurl )
326389
327390 def get_device_settings (self , device_id ):
328391 """
329392 Fetch device settings for current device
330393 """
331- devicesurl = f"{ self .url_device_settings } { device_id } "
394+ devicesurl = f"{ self .url_device_service } device-info/settings/ { device_id } "
332395 self .logger .debug ("Fetching device settings with url %s" , devicesurl )
333396 return self .fetch_data (devicesurl )
334397
398+ def get_device_last_used (self ):
399+ """
400+ Fetch device last used
401+ """
402+ device_last_used_url = f"{ self .url_device_service } mylastused"
403+ self .logger .debug (
404+ "Fetching device last used with url %s" , device_last_used_url )
405+ return self .fetch_data (device_last_used_url )
335406
336407 class ActivityDownloadFormat (Enum ):
337408 ORIGINAL = auto ()
0 commit comments