33# Coded for Circuit Python 8.2
44
55import os
6- import board
76import time
87import microcontroller
98import ssl
@@ -91,6 +90,7 @@ def time_calc(input_time):
9190 print (f"Top NVM Again (just to make sure): { top_nvm } " )
9291 print (f"Settings.toml Initial Refresh Token: { Fitbit_First_Refresh_Token } " )
9392
93+ latest_15_avg = "Latest 15 Minute Averages"
9494while True :
9595 # Use Settings.toml refresh token on first run
9696 if top_nvm != Fitbit_First_Refresh_Token :
@@ -118,7 +118,8 @@ def time_calc(input_time):
118118 # ----------------------------- POST FOR REFRESH TOKEN -----------------------
119119 if debug :
120120 print (
121- f"FULL REFRESH TOKEN POST:{ fitbit_oauth_token } { fitbit_oauth_refresh_token } "
121+ f"FULL REFRESH TOKEN POST:{ fitbit_oauth_token } "
122+ + f"{ fitbit_oauth_refresh_token } "
122123 )
123124 print (f"Current Refresh Token: { Refresh_Token } " )
124125 # TOKEN REFRESH POST
@@ -151,7 +152,7 @@ def time_calc(input_time):
151152 microcontroller .nvm [0 :64 ] = nvmtoken
152153 if debug :
153154 print (f"Next Token for NVM: { nvmtoken .decode ()} " )
154- print (f "Next token written to NVM Successfully!" )
155+ print ("Next token written to NVM Successfully!" )
155156 except OSError as e :
156157 print ("OS Error:" , e )
157158 continue
@@ -195,6 +196,7 @@ def time_calc(input_time):
195196 fitbit_get_response = requests .get (url = FITBIT_SOURCE , headers = fitbit_header )
196197 try :
197198 fitbit_json = fitbit_get_response .json ()
199+ intraday_response = fitbit_json ["activities-heart-intraday" ]["dataset" ]
198200 except ConnectionError as e :
199201 print ("Connection Error:" , e )
200202 print ("Retrying in 10 seconds" )
@@ -203,7 +205,7 @@ def time_calc(input_time):
203205 print (f"Full API GET URL: { FITBIT_SOURCE } " )
204206 print (f"Header: { fitbit_header } " )
205207 # print(f"JSON Full Response: {fitbit_json}")
206- # print(f"Intraday Full Response: {fitbit_json["activities-heart-intraday"]["dataset"] }")
208+ # print(f"Intraday Full Response: {intraday_response }")
207209
208210 try :
209211 # Fitbit's sync to your mobile device & server every 15 minutes in chunks.
@@ -265,18 +267,317 @@ def time_calc(input_time):
265267 activities_latest_heart_value14 = fitbit_json [
266268 "activities-heart-intraday"
267269 ]["dataset" ][response_length - 15 ]["value" ]
270+ latest_15_avg = "Latest 15 Minute Averages"
268271 print (
269- f"Latest 15 Minute Averages: { activities_latest_heart_value14 } ,{ activities_latest_heart_value13 } ,{ activities_latest_heart_value12 } ,{ activities_latest_heart_value11 } ,{ activities_latest_heart_value10 } ,{ activities_latest_heart_value9 } ,{ activities_latest_heart_value8 } ,{ activities_latest_heart_value7 } ,{ activities_latest_heart_value6 } ,{ activities_latest_heart_value5 } ,{ activities_latest_heart_value4 } ,{ activities_latest_heart_value3 } ,{ activities_latest_heart_value2 } ,{ activities_latest_heart_value1 } ,{ activities_latest_heart_value0 } "
272+ f"{ latest_15_avg } "
273+ + f"{ activities_latest_heart_value14 } ,"
274+ + f"{ activities_latest_heart_value13 } ,"
275+ + f"{ activities_latest_heart_value12 } ,"
276+ + f"{ activities_latest_heart_value11 } ,"
277+ + f"{ activities_latest_heart_value10 } ,"
278+ + f"{ activities_latest_heart_value9 } ,"
279+ + f"{ activities_latest_heart_value8 } ,"
280+ + f"{ activities_latest_heart_value7 } ,"
281+ + f"{ activities_latest_heart_value6 } ,"
282+ + f"{ activities_latest_heart_value5 } ,"
283+ + f"{ activities_latest_heart_value4 } ,"
284+ + f"{ activities_latest_heart_value3 } ,"
285+ + f"{ activities_latest_heart_value2 } ,"
286+ + f"{ activities_latest_heart_value1 } ,"
287+ + f"{ activities_latest_heart_value0 } "
270288 )
271289 else :
272- print (f "Waiting for latest 15 values sync..." )
273- print (f "Not enough values for today to display yet." )
274- print (f "No display from midnight to 00:15" )
290+ print ("Waiting for latest 15 values sync..." )
291+ print ("Not enough values for today to display yet." )
292+ print ("No display from midnight to 00:15" )
275293
276294 except KeyError as keyerror :
277295 print (f"Key Error: { keyerror } " )
278296 print (
279- f"Too Many Requests, Expired token, invalid permission, or (key:value) pair error."
297+ "Too Many Requests, Expired token,"
298+ + "invalid permission,"
299+ + "or (key:value) pair error."
300+ )
301+ continue
302+
303+ print ("Board Uptime: " , time_calc (time .monotonic ())) # Board Up-Time seconds
304+ print ("\n Finished!" )
305+ print ("Next Update in: " , time_calc (sleep_time ))
306+ print ("===============================" )
307+
308+ except (ValueError , RuntimeError ) as e :
309+ print ("Failed to get data, retrying\n " , e )
310+ time .sleep (60 )
311+ continue
312+ time .sleep (sleep_time )
313+
314+ # Fitbit_ClientID = "YourAppClientID"
315+ # Fitbit_Token = "Long 256 character string (SHA-256)"
316+ # Fitbit_First_Refresh_Token = "64 character string"
317+ # Fitbit_UserID = "UserID authorizing the ClientID"
318+
319+ Fitbit_ClientID = os .getenv ("Fitbit_ClientID" )
320+ Fitbit_Token = os .getenv ("Fitbit_Token" )
321+ # overides nvm first run only
322+ Fitbit_First_Refresh_Token = os .getenv ("Fitbit_First_Refresh_Token" )
323+ Fitbit_UserID = os .getenv ("Fitbit_UserID" )
324+
325+ wifi_ssid = os .getenv ("CIRCUITPY_WIFI_SSID" )
326+ wifi_pw = os .getenv ("CIRCUITPY_WIFI_PASSWORD" )
327+
328+ # Time between API refreshes
329+ # 300 = 5 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
330+ sleep_time = 900
331+
332+
333+ # Converts seconds in minutes/hours/days
334+ def time_calc (input_time ):
335+ if input_time < 60 :
336+ sleep_int = input_time
337+ time_output = f"{ sleep_int :.0f} seconds"
338+ elif 60 <= input_time < 3600 :
339+ sleep_int = input_time / 60
340+ time_output = f"{ sleep_int :.0f} minutes"
341+ elif 3600 <= input_time < 86400 :
342+ sleep_int = input_time / 60 / 60
343+ time_output = f"{ sleep_int :.1f} hours"
344+ else :
345+ sleep_int = input_time / 60 / 60 / 24
346+ time_output = f"{ sleep_int :.1f} days"
347+ return time_output
348+
349+
350+ # Authenticates Client ID & SHA-256 Token to POST
351+ fitbit_oauth_header = {"Content-Type" : "application/x-www-form-urlencoded" }
352+ fitbit_oauth_token = "https://api.fitbit.com/oauth2/token"
353+
354+ # Connect to Wi-Fi
355+ print ("\n ===============================" )
356+ print ("Connecting to WiFi..." )
357+ requests = adafruit_requests .Session (pool , ssl .create_default_context ())
358+ while not wifi .radio .ipv4_address :
359+ try :
360+ wifi .radio .connect (wifi_ssid , wifi_pw )
361+ except ConnectionError as e :
362+ print ("Connection Error:" , e )
363+ print ("Retrying in 10 seconds" )
364+ time .sleep (10 )
365+ print ("Connected!\n " )
366+
367+ # First run uses settings.toml token
368+ Refresh_Token = Fitbit_First_Refresh_Token
369+
370+ if debug :
371+ print (f"Top NVM Again (just to make sure): { top_nvm } " )
372+ print (f"Settings.toml Initial Refresh Token: { Fitbit_First_Refresh_Token } " )
373+
374+ while True :
375+ # Use Settings.toml refresh token on first run
376+ if top_nvm != Fitbit_First_Refresh_Token :
377+ Refresh_Token = microcontroller .nvm [0 :64 ].decode ()
378+ if debug :
379+ # NVM 64 should match Current Refresh Token
380+ print (f"NVM 64: { microcontroller .nvm [0 :64 ].decode ()} " )
381+ print (f"Current Refresh_Token: { Refresh_Token } " )
382+ else :
383+ if debug :
384+ # First run use settings.toml refresh token instead
385+ print (f"Initial_Refresh_Token: { Refresh_Token } " )
386+
387+ try :
388+ if debug :
389+ print ("\n -----Token Refresh POST Attempt -------" )
390+ fitbit_oauth_refresh_token = (
391+ "&grant_type=refresh_token"
392+ + "&client_id="
393+ + str (Fitbit_ClientID )
394+ + "&refresh_token="
395+ + str (Refresh_Token )
396+ )
397+
398+ # ----------------------------- POST FOR REFRESH TOKEN -----------------------
399+ if debug :
400+ print (
401+ f"FULL REFRESH TOKEN POST:{ fitbit_oauth_token } "
402+ + f"{ fitbit_oauth_refresh_token } "
403+ )
404+ print (f"Current Refresh Token: { Refresh_Token } " )
405+ # TOKEN REFRESH POST
406+ fitbit_oauth_refresh_POST = requests .post (
407+ url = fitbit_oauth_token ,
408+ data = fitbit_oauth_refresh_token ,
409+ headers = fitbit_oauth_header ,
410+ )
411+ try :
412+ fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST .json ()
413+
414+ fitbit_new_token = fitbit_refresh_oauth_json ["access_token" ]
415+ if debug :
416+ print ("Your Private SHA-256 Token: " , fitbit_new_token )
417+ fitbit_access_token = fitbit_new_token # NEW FULL TOKEN
418+
419+ # If current token valid will respond
420+ fitbit_new_refesh_token = fitbit_refresh_oauth_json ["refresh_token" ]
421+ Refresh_Token = fitbit_new_refesh_token
422+ fitbit_token_expiration = fitbit_refresh_oauth_json ["expires_in" ]
423+ fitbit_scope = fitbit_refresh_oauth_json ["scope" ]
424+ fitbit_token_type = fitbit_refresh_oauth_json ["token_type" ]
425+ fitbit_user_id = fitbit_refresh_oauth_json ["user_id" ]
426+ if debug :
427+ print ("Next Refresh Token: " , Refresh_Token )
428+
429+ # Store Next Token into NVM
430+ try :
431+ nvmtoken = b"" + fitbit_new_refesh_token
432+ microcontroller .nvm [0 :64 ] = nvmtoken
433+ if debug :
434+ print (f"Next Token for NVM: { nvmtoken .decode ()} " )
435+ print ("Next token written to NVM Successfully!" )
436+ except OSError as e :
437+ print ("OS Error:" , e )
438+ continue
439+
440+ if debug :
441+ # Extraneous token data for debugging
442+ print ("Token Expires in: " , time_calc (fitbit_token_expiration ))
443+ print ("Scope: " , fitbit_scope )
444+ print ("Token Type: " , fitbit_token_type )
445+ print ("UserID: " , fitbit_user_id )
446+
447+ except KeyError as e :
448+ print ("Key Error:" , e )
449+ print ("Expired token, invalid permission, or (key:value) pair error." )
450+ time .sleep (300 )
451+ continue
452+
453+ # ----------------------------- GET DATA -------------------------------------
454+ # POST should respond with current & next refresh token we can GET for data
455+ # 64-bit Refresh tokens will "keep alive" SHA-256 token indefinitely
456+ # Fitbit main SHA-256 token expires in 8 hours unless refreshed!
457+ # ----------------------------------------------------------------------------
458+ detail_level = "1min" # Supported: 1sec | 1min | 5min | 15min
459+ requested_date = "today" # Date format yyyy-MM-dd or today
460+ fitbit_header = {
461+ "Authorization" : "Bearer " + fitbit_access_token + "" ,
462+ "Client-Id" : "" + Fitbit_ClientID + "" ,
463+ }
464+ # Heart Intraday Scope
465+ FITBIT_SOURCE = (
466+ "https://api.fitbit.com/1/user/"
467+ + Fitbit_UserID
468+ + "/activities/heart/date/today"
469+ + "/1d/"
470+ + detail_level
471+ + ".json"
472+ )
473+
474+ print ("\n Attempting to GET FITBIT Stats!" )
475+ print ("===============================" )
476+ fitbit_get_response = requests .get (url = FITBIT_SOURCE , headers = fitbit_header )
477+ try :
478+ fitbit_json = fitbit_get_response .json ()
479+ intraday_response = fitbit_json ["activities-heart-intraday" ]["dataset" ]
480+ except ConnectionError as e :
481+ print ("Connection Error:" , e )
482+ print ("Retrying in 10 seconds" )
483+
484+ if debug :
485+ print (f"Full API GET URL: { FITBIT_SOURCE } " )
486+ print (f"Header: { fitbit_header } " )
487+ # print(f"JSON Full Response: {fitbit_json}")
488+ print (f"Intraday Full Response: { intraday_response } " )
489+
490+ try :
491+ # Fitbit's sync to your mobile device & server every 15 minutes in chunks.
492+ # Pointless to poll their API faster than 15 minute intervals.
493+ activities_heart_value = fitbit_json ["activities-heart-intraday" ]["dataset" ]
494+ response_length = len (activities_heart_value )
495+ if response_length >= 15 :
496+ activities_timestamp = fitbit_json ["activities-heart" ][0 ]["dateTime" ]
497+ print (f"Fitbit Date: { activities_timestamp } " )
498+ activities_latest_heart_time = fitbit_json ["activities-heart-intraday" ][
499+ "dataset"
500+ ][response_length - 1 ]["time" ]
501+ print (f"Fitbit Time: { activities_latest_heart_time [0 :- 3 ]} " )
502+ print (f"Today's Logged Pulses : { response_length } " )
503+
504+ # Each 1min heart rate is a 60 second average
505+ activities_latest_heart_value0 = fitbit_json [
506+ "activities-heart-intraday"
507+ ]["dataset" ][response_length - 1 ]["value" ]
508+ activities_latest_heart_value1 = fitbit_json [
509+ "activities-heart-intraday"
510+ ]["dataset" ][response_length - 2 ]["value" ]
511+ activities_latest_heart_value2 = fitbit_json [
512+ "activities-heart-intraday"
513+ ]["dataset" ][response_length - 3 ]["value" ]
514+ activities_latest_heart_value3 = fitbit_json [
515+ "activities-heart-intraday"
516+ ]["dataset" ][response_length - 4 ]["value" ]
517+ activities_latest_heart_value4 = fitbit_json [
518+ "activities-heart-intraday"
519+ ]["dataset" ][response_length - 5 ]["value" ]
520+ activities_latest_heart_value5 = fitbit_json [
521+ "activities-heart-intraday"
522+ ]["dataset" ][response_length - 6 ]["value" ]
523+ activities_latest_heart_value6 = fitbit_json [
524+ "activities-heart-intraday"
525+ ]["dataset" ][response_length - 7 ]["value" ]
526+ activities_latest_heart_value7 = fitbit_json [
527+ "activities-heart-intraday"
528+ ]["dataset" ][response_length - 8 ]["value" ]
529+ activities_latest_heart_value8 = fitbit_json [
530+ "activities-heart-intraday"
531+ ]["dataset" ][response_length - 9 ]["value" ]
532+ activities_latest_heart_value9 = fitbit_json [
533+ "activities-heart-intraday"
534+ ]["dataset" ][response_length - 10 ]["value" ]
535+ activities_latest_heart_value10 = fitbit_json [
536+ "activities-heart-intraday"
537+ ]["dataset" ][response_length - 11 ]["value" ]
538+ activities_latest_heart_value11 = fitbit_json [
539+ "activities-heart-intraday"
540+ ]["dataset" ][response_length - 12 ]["value" ]
541+ activities_latest_heart_value12 = fitbit_json [
542+ "activities-heart-intraday"
543+ ]["dataset" ][response_length - 13 ]["value" ]
544+ activities_latest_heart_value13 = fitbit_json [
545+ "activities-heart-intraday"
546+ ]["dataset" ][response_length - 14 ]["value" ]
547+ activities_latest_heart_value14 = fitbit_json [
548+ "activities-heart-intraday"
549+ ]["dataset" ][response_length - 15 ]["value" ]
550+
551+ print (
552+ f"{ latest_15_avg } "
553+ + f"{ activities_latest_heart_value14 } ,"
554+ + f"{ activities_latest_heart_value13 } ,"
555+ + f"{ activities_latest_heart_value12 } ,"
556+ + f"{ activities_latest_heart_value11 } ,"
557+ + f"{ activities_latest_heart_value10 } ,"
558+ + f"{ activities_latest_heart_value9 } ,"
559+ + f"{ activities_latest_heart_value8 } ,"
560+ + f"{ activities_latest_heart_value7 } ,"
561+ + f"{ activities_latest_heart_value6 } ,"
562+ + f"{ activities_latest_heart_value5 } ,"
563+ + f"{ activities_latest_heart_value4 } ,"
564+ + f"{ activities_latest_heart_value3 } ,"
565+ + f"{ activities_latest_heart_value2 } ,"
566+ + f"{ activities_latest_heart_value1 } ,"
567+ + f"{ activities_latest_heart_value0 } "
568+ )
569+ else :
570+ print ("Waiting for latest 15 values sync..." )
571+ print ("Not enough values for today to display yet." )
572+ print ("No display from midnight to 00:15" )
573+
574+ except KeyError as keyerror :
575+ print (f"Key Error: { keyerror } " )
576+ print (
577+ "Too Many Requests, "
578+ + "Expired token, "
579+ + "invalid permission, or "
580+ + "(key:value) pair error."
280581 )
281582 continue
282583
0 commit comments