Skip to content

Commit 2f8fcc1

Browse files
committed
add basic Fitbit API example
requires google account, fitbit device, fitbit developer app, api tokens. Non-graphing display example just serial ouptut.
1 parent b62fc36 commit 2f8fcc1

File tree

1 file changed

+292
-0
lines changed

1 file changed

+292
-0
lines changed

examples/requests_api_fitbit.py

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# SPDX-FileCopyrightText: 2023 DJDevon3
2+
# SPDX-License-Identifier: MIT
3+
# Coded for Circuit Python 8.2
4+
5+
import os
6+
import board
7+
import time
8+
import microcontroller
9+
import ssl
10+
import wifi
11+
import socketpool
12+
import adafruit_requests
13+
14+
# Initialize WiFi Pool (There can be only 1 pool & top of script)
15+
pool = socketpool.SocketPool(wifi.radio)
16+
17+
# STREAMER WARNING: private data will be viewable while debug True
18+
debug = False # Set True for full debug view
19+
20+
# Can use to confirm first instance of NVM is correct refresh token
21+
top_nvm = microcontroller.nvm[0:64].decode()
22+
if debug:
23+
print(f"Top NVM: {top_nvm}") # NVM before settings.toml loaded
24+
25+
# --- Fitbit Developer Account & oAuth App Required: ---
26+
# Required: Google Login (Fitbit owned by Google) & Fitbit Device
27+
# Step 1: Create a personal app here: https://dev.fitbit.com
28+
# Step 2: Use their Tutorial to get the Token and first Refresh Token
29+
# Fitbit's Tutorial Step 4 is as far as you need to go.
30+
# https://dev.fitbit.com/build/reference/web-api/troubleshooting-guide/oauth2-tutorial/
31+
32+
# Ensure these are in settings.toml
33+
# Fitbit_ClientID = "YourAppClientID"
34+
# Fitbit_Token = "Long 256 character string (SHA-256)"
35+
# Fitbit_First_Refresh_Token = "64 character string"
36+
# Fitbit_UserID = "UserID authorizing the ClientID"
37+
38+
Fitbit_ClientID = os.getenv("Fitbit_ClientID")
39+
Fitbit_Token = os.getenv("Fitbit_Token")
40+
Fitbit_First_Refresh_Token = os.getenv(
41+
"Fitbit_First_Refresh_Token"
42+
) # overides nvm first run only
43+
Fitbit_UserID = os.getenv("Fitbit_UserID")
44+
45+
wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID")
46+
wifi_pw = os.getenv("CIRCUITPY_WIFI_PASSWORD")
47+
48+
# Time between API refreshes
49+
# 300 = 5 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
50+
sleep_time = 900
51+
52+
53+
# Converts seconds in minutes/hours/days
54+
def time_calc(input_time):
55+
if input_time < 60:
56+
sleep_int = input_time
57+
time_output = f"{sleep_int:.0f} seconds"
58+
elif 60 <= input_time < 3600:
59+
sleep_int = input_time / 60
60+
time_output = f"{sleep_int:.0f} minutes"
61+
elif 3600 <= input_time < 86400:
62+
sleep_int = input_time / 60 / 60
63+
time_output = f"{sleep_int:.1f} hours"
64+
else:
65+
sleep_int = input_time / 60 / 60 / 24
66+
time_output = f"{sleep_int:.1f} days"
67+
return time_output
68+
69+
70+
# Authenticates Client ID & SHA-256 Token to POST
71+
fitbit_oauth_header = {"Content-Type": "application/x-www-form-urlencoded"}
72+
fitbit_oauth_token = "https://api.fitbit.com/oauth2/token"
73+
74+
# Connect to Wi-Fi
75+
print("\n===============================")
76+
print("Connecting to WiFi...")
77+
requests = adafruit_requests.Session(pool, ssl.create_default_context())
78+
while not wifi.radio.ipv4_address:
79+
try:
80+
wifi.radio.connect(wifi_ssid, wifi_pw)
81+
except ConnectionError as e:
82+
print("Connection Error:", e)
83+
print("Retrying in 10 seconds")
84+
time.sleep(10)
85+
print("Connected!\n")
86+
87+
# First run uses settings.toml token
88+
Refresh_Token = Fitbit_First_Refresh_Token
89+
90+
if debug:
91+
print(f"Top NVM Again (just to make sure): {top_nvm}")
92+
print(f"Settings.toml Initial Refresh Token: {Fitbit_First_Refresh_Token}")
93+
94+
while True:
95+
# Use Settings.toml refresh token on first run
96+
if top_nvm != Fitbit_First_Refresh_Token:
97+
Refresh_Token = microcontroller.nvm[0:64].decode()
98+
if debug:
99+
# NVM 64 should match Current Refresh Token
100+
print(f"NVM 64: {microcontroller.nvm[0:64].decode()}")
101+
print(f"Current Refresh_Token: {Refresh_Token}")
102+
else:
103+
if debug:
104+
# First run use settings.toml refresh token instead
105+
print(f"Initial_Refresh_Token: {Refresh_Token}")
106+
107+
try:
108+
if debug:
109+
print("\n-----Token Refresh POST Attempt -------")
110+
fitbit_oauth_refresh_token = (
111+
"&grant_type=refresh_token"
112+
+ "&client_id="
113+
+ str(Fitbit_ClientID)
114+
+ "&refresh_token="
115+
+ str(Refresh_Token)
116+
)
117+
118+
# ----------------------------- POST FOR REFRESH TOKEN -----------------------
119+
if debug:
120+
print(
121+
f"FULL REFRESH TOKEN POST:{fitbit_oauth_token}{fitbit_oauth_refresh_token}"
122+
)
123+
print(f"Current Refresh Token: {Refresh_Token}")
124+
# TOKEN REFRESH POST
125+
fitbit_oauth_refresh_POST = requests.post(
126+
url=fitbit_oauth_token,
127+
data=fitbit_oauth_refresh_token,
128+
headers=fitbit_oauth_header,
129+
)
130+
try:
131+
fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json()
132+
133+
fitbit_new_token = fitbit_refresh_oauth_json["access_token"]
134+
if debug:
135+
print("Your Private SHA-256 Token: ", fitbit_new_token)
136+
fitbit_access_token = fitbit_new_token # NEW FULL TOKEN
137+
138+
# If current token valid will respond
139+
fitbit_new_refesh_token = fitbit_refresh_oauth_json["refresh_token"]
140+
Refresh_Token = fitbit_new_refesh_token
141+
fitbit_token_expiration = fitbit_refresh_oauth_json["expires_in"]
142+
fitbit_scope = fitbit_refresh_oauth_json["scope"]
143+
fitbit_token_type = fitbit_refresh_oauth_json["token_type"]
144+
fitbit_user_id = fitbit_refresh_oauth_json["user_id"]
145+
if debug:
146+
print("Next Refresh Token: ", Refresh_Token)
147+
148+
# Store Next Token into NVM
149+
try:
150+
nvmtoken = b"" + fitbit_new_refesh_token
151+
microcontroller.nvm[0:64] = nvmtoken
152+
if debug:
153+
print(f"Next Token for NVM: {nvmtoken.decode()}")
154+
print(f"Next token written to NVM Successfully!")
155+
except OSError as e:
156+
print("OS Error:", e)
157+
continue
158+
159+
if debug:
160+
# Extraneous token data for debugging
161+
print("Token Expires in: ", time_calc(fitbit_token_expiration))
162+
print("Scope: ", fitbit_scope)
163+
print("Token Type: ", fitbit_token_type)
164+
print("UserID: ", fitbit_user_id)
165+
166+
except KeyError as e:
167+
print("Key Error:", e)
168+
print("Expired token, invalid permission, or (key:value) pair error.")
169+
time.sleep(300)
170+
continue
171+
172+
# ----------------------------- GET DATA -------------------------------------
173+
# POST should respond with current & next refresh token we can GET for data
174+
# 64-bit Refresh tokens will "keep alive" SHA-256 token indefinitely
175+
# Fitbit main SHA-256 token expires in 8 hours unless refreshed!
176+
# ----------------------------------------------------------------------------
177+
detail_level = "1min" # Supported: 1sec | 1min | 5min | 15min
178+
requested_date = "today" # Date format yyyy-MM-dd or today
179+
fitbit_header = {
180+
"Authorization": "Bearer " + fitbit_access_token + "",
181+
"Client-Id": "" + Fitbit_ClientID + "",
182+
}
183+
# Heart Intraday Scope
184+
FITBIT_SOURCE = (
185+
"https://api.fitbit.com/1/user/"
186+
+ Fitbit_UserID
187+
+ "/activities/heart/date/today"
188+
+ "/1d/"
189+
+ detail_level
190+
+ ".json"
191+
)
192+
193+
print("\nAttempting to GET FITBIT Stats!")
194+
print("===============================")
195+
fitbit_get_response = requests.get(url=FITBIT_SOURCE, headers=fitbit_header)
196+
try:
197+
fitbit_json = fitbit_get_response.json()
198+
except ConnectionError as e:
199+
print("Connection Error:", e)
200+
print("Retrying in 10 seconds")
201+
202+
if debug:
203+
print(f"Full API GET URL: {FITBIT_SOURCE}")
204+
print(f"Header: {fitbit_header}")
205+
# print(f"JSON Full Response: {fitbit_json}")
206+
# print(f"Intraday Full Response: {fitbit_json["activities-heart-intraday"]["dataset"]}")
207+
208+
try:
209+
# Fitbit's sync to your mobile device & server every 15 minutes in chunks.
210+
# Pointless to poll their API faster than 15 minute intervals.
211+
activities_heart_value = fitbit_json["activities-heart-intraday"]["dataset"]
212+
response_length = len(activities_heart_value)
213+
if response_length >= 15:
214+
activities_timestamp = fitbit_json["activities-heart"][0]["dateTime"]
215+
print(f"Fitbit Date: {activities_timestamp}")
216+
activities_latest_heart_time = fitbit_json["activities-heart-intraday"][
217+
"dataset"
218+
][response_length - 1]["time"]
219+
print(f"Fitbit Time: {activities_latest_heart_time[0:-3]}")
220+
print(f"Today's Logged Pulses : {response_length}")
221+
222+
# Each 1min heart rate is a 60 second average
223+
activities_latest_heart_value0 = fitbit_json[
224+
"activities-heart-intraday"
225+
]["dataset"][response_length - 1]["value"]
226+
activities_latest_heart_value1 = fitbit_json[
227+
"activities-heart-intraday"
228+
]["dataset"][response_length - 2]["value"]
229+
activities_latest_heart_value2 = fitbit_json[
230+
"activities-heart-intraday"
231+
]["dataset"][response_length - 3]["value"]
232+
activities_latest_heart_value3 = fitbit_json[
233+
"activities-heart-intraday"
234+
]["dataset"][response_length - 4]["value"]
235+
activities_latest_heart_value4 = fitbit_json[
236+
"activities-heart-intraday"
237+
]["dataset"][response_length - 5]["value"]
238+
activities_latest_heart_value5 = fitbit_json[
239+
"activities-heart-intraday"
240+
]["dataset"][response_length - 6]["value"]
241+
activities_latest_heart_value6 = fitbit_json[
242+
"activities-heart-intraday"
243+
]["dataset"][response_length - 7]["value"]
244+
activities_latest_heart_value7 = fitbit_json[
245+
"activities-heart-intraday"
246+
]["dataset"][response_length - 8]["value"]
247+
activities_latest_heart_value8 = fitbit_json[
248+
"activities-heart-intraday"
249+
]["dataset"][response_length - 9]["value"]
250+
activities_latest_heart_value9 = fitbit_json[
251+
"activities-heart-intraday"
252+
]["dataset"][response_length - 10]["value"]
253+
activities_latest_heart_value10 = fitbit_json[
254+
"activities-heart-intraday"
255+
]["dataset"][response_length - 11]["value"]
256+
activities_latest_heart_value11 = fitbit_json[
257+
"activities-heart-intraday"
258+
]["dataset"][response_length - 12]["value"]
259+
activities_latest_heart_value12 = fitbit_json[
260+
"activities-heart-intraday"
261+
]["dataset"][response_length - 13]["value"]
262+
activities_latest_heart_value13 = fitbit_json[
263+
"activities-heart-intraday"
264+
]["dataset"][response_length - 14]["value"]
265+
activities_latest_heart_value14 = fitbit_json[
266+
"activities-heart-intraday"
267+
]["dataset"][response_length - 15]["value"]
268+
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}"
270+
)
271+
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")
275+
276+
except KeyError as keyerror:
277+
print(f"Key Error: {keyerror}")
278+
print(
279+
f"Too Many Requests, Expired token, invalid permission, or (key:value) pair error."
280+
)
281+
continue
282+
283+
print("Board Uptime: ", time_calc(time.monotonic())) # Board Up-Time seconds
284+
print("\nFinished!")
285+
print("Next Update in: ", time_calc(sleep_time))
286+
print("===============================")
287+
288+
except (ValueError, RuntimeError) as e:
289+
print("Failed to get data, retrying\n", e)
290+
time.sleep(60)
291+
continue
292+
time.sleep(sleep_time)

0 commit comments

Comments
 (0)