3
3
# Coded for Circuit Python 8.2
4
4
5
5
import os
6
- import board
7
6
import time
8
7
import microcontroller
9
8
import ssl
@@ -91,6 +90,7 @@ def time_calc(input_time):
91
90
print (f"Top NVM Again (just to make sure): { top_nvm } " )
92
91
print (f"Settings.toml Initial Refresh Token: { Fitbit_First_Refresh_Token } " )
93
92
93
+ latest_15_avg = "Latest 15 Minute Averages"
94
94
while True :
95
95
# Use Settings.toml refresh token on first run
96
96
if top_nvm != Fitbit_First_Refresh_Token :
@@ -118,7 +118,8 @@ def time_calc(input_time):
118
118
# ----------------------------- POST FOR REFRESH TOKEN -----------------------
119
119
if debug :
120
120
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 } "
122
123
)
123
124
print (f"Current Refresh Token: { Refresh_Token } " )
124
125
# TOKEN REFRESH POST
@@ -151,7 +152,7 @@ def time_calc(input_time):
151
152
microcontroller .nvm [0 :64 ] = nvmtoken
152
153
if debug :
153
154
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!" )
155
156
except OSError as e :
156
157
print ("OS Error:" , e )
157
158
continue
@@ -195,6 +196,7 @@ def time_calc(input_time):
195
196
fitbit_get_response = requests .get (url = FITBIT_SOURCE , headers = fitbit_header )
196
197
try :
197
198
fitbit_json = fitbit_get_response .json ()
199
+ intraday_response = fitbit_json ["activities-heart-intraday" ]["dataset" ]
198
200
except ConnectionError as e :
199
201
print ("Connection Error:" , e )
200
202
print ("Retrying in 10 seconds" )
@@ -203,7 +205,7 @@ def time_calc(input_time):
203
205
print (f"Full API GET URL: { FITBIT_SOURCE } " )
204
206
print (f"Header: { fitbit_header } " )
205
207
# 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 }")
207
209
208
210
try :
209
211
# Fitbit's sync to your mobile device & server every 15 minutes in chunks.
@@ -265,18 +267,317 @@ def time_calc(input_time):
265
267
activities_latest_heart_value14 = fitbit_json [
266
268
"activities-heart-intraday"
267
269
]["dataset" ][response_length - 15 ]["value" ]
270
+ latest_15_avg = "Latest 15 Minute Averages"
268
271
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 } "
270
288
)
271
289
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" )
275
293
276
294
except KeyError as keyerror :
277
295
print (f"Key Error: { keyerror } " )
278
296
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."
280
581
)
281
582
continue
282
583
0 commit comments