Skip to content

Instantly share code, notes, and snippets.

@jonblack
Created February 7, 2024 08:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jonblack/9447b06a55843336022b725acf32b4fa to your computer and use it in GitHub Desktop.
Save jonblack/9447b06a55843336022b725acf32b4fa to your computer and use it in GitHub Desktop.
Garmin HRV/sleep Plot

Garmin Connect only supports showing a maximum 4-week plot of HRV data. This script scrapes HRV and sleep data and plots it on a single graph, making it much easier to see the trend over time.

In order for the script to work, you need to provide the correct values for the Authorization and Cookie headers. To get these:

  • Log in to Garmin Connect and navigate to the HRV status report
  • Open the browser developer tools and select the network tab
  • In the HRV status report, click to a previous period. This will cause a network request to update the report
  • In the developer tools, click on the network request and look for the request header.
  • Copy the values for the above headers and paste them into the script

Run the script with python main.py.

A new browser window should open with your graph.

import math
import requests
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from datetime import date, timedelta
import pandas as pd
import numpy as np
headers = {
'DI-Backend': 'connectapi.garmin.com',
'Authorization': '',
'Cookie': '',
}
def request(url: str):
r = requests.get(url, headers=headers)
if r.status_code != 200:
print(url)
print(r.status_code)
print(r.text)
exit()
return r
def get_dates(beg: date, end: date, period: int):
dates = []
cbeg = beg
while cbeg < end:
cend = cbeg + timedelta(days=period)
if cend > end:
cend = end
dates.append([cbeg, cend])
cbeg = cend + timedelta(days=1)
return dates
def get_hrv_summaries(beg: date, end: date):
print(f"Get HRV Summaries between {beg} and {end}")
x = []
y_hrv_last_night_avg = []
y_hrv_weekly_avg = []
y_hrv_blower = []
y_hrv_bupper = []
dates = get_dates(beg, end, 366)
for beg, end in dates:
url = f'https://connect.garmin.com/hrv-service/hrv/daily/{beg}/{end}'
r = request(url)
hrvs = r.json().get("hrvSummaries")
for hrv in hrvs:
x.append(hrv.get("calendarDate"))
y_hrv_last_night_avg.append(hrv.get("lastNightAvg"))
y_hrv_weekly_avg.append(hrv.get("weeklyAvg"))
baseline = hrv.get("baseline")
if baseline:
y_hrv_blower.append(baseline.get("balancedLow"))
y_hrv_bupper.append(baseline.get("balancedUpper"))
else:
y_hrv_blower.append(None)
y_hrv_bupper.append(None)
return x, y_hrv_last_night_avg, y_hrv_weekly_avg, y_hrv_blower, y_hrv_bupper
def get_sleep_stats(beg: date, end: date):
print(f"Get Sleep Stats between {beg} and {end}")
x = []
y = []
dates = get_dates(beg, end, 27)
for bdate, edate in dates:
url = f'https://connect.garmin.com/sleep-service/stats/sleep/daily/{bdate}/{edate}'
r = request(url)
inds = r.json().get("individualStats")
for ind in inds:
x.append(ind.get("calendarDate"))
sleep = ind.get("values").get("totalSleepTimeInSeconds") / 3600
y.append(sleep)
return x, y
# main
beg = date.fromisoformat('2022-11-04')
end = date.today()
x1, y_hrv_last_night_avg, y_hrv, y_bl, y_bu = get_hrv_summaries(beg, end)
x2, y = get_sleep_stats(beg, end)
# Merge datasets
hrv_df = pd.DataFrame({
'x': x1,
'y_ln_hrv': y_hrv_last_night_avg,
'y_hrv': y_hrv,
'y_bl': y_bl,
'y_bu': y_bu
})
sleep_df = pd.DataFrame({
'x': x2,
'y_sleep': y
})
df = pd.merge(hrv_df, sleep_df, on='x', how='outer')
df['x'] = pd.to_datetime(df['x'])
df['y_sleep_7da'] = df['y_sleep'].rolling(min_periods=3, window=7).mean()
df['color'] = np.where(df['y_sleep'] < 7, 'red', 'green')
fig = make_subplots(
rows=3,
cols=1,
shared_xaxes=True,
subplot_titles=['Last Night Average HRV', '7-day Average HRV', 'Sleep Amount (green is >= 7 hours, black=7 day average)']
)
fig.append_trace(go.Scatter(x=df['x'], y=df['y_ln_hrv'], line_color='green', name="last night average hrv", showlegend=False), 1, 1)
fig.append_trace(go.Scatter(x=df['x'], y=df['y_hrv'], line_color='blue', name="7 day average hrv", showlegend=False), 2, 1)
fig.append_trace(go.Scatter(x=df['x'], y=df['y_bl'], line_color='darkgrey', name="", showlegend=False), 2, 1)
fig.append_trace(go.Scatter(x=df['x'], y=df['y_bu'], line_color='darkgrey', name="", showlegend=False), 2, 1)
fig.append_trace(go.Bar(x=df['x'], y=df['y_sleep'], marker_color=df['color'], name="sleep in hours", showlegend=False), 3, 1)
fig.append_trace(go.Scatter(x=df['x'], y=df['y_sleep_7da'], marker_color='black', name="7 day average", showlegend=False), 3, 1)
fig.show()
certifi==2023.11.17
charset-normalizer==3.3.2
idna==3.6
numpy==1.26.3
packaging==23.2
pandas==2.2.0
plotly==5.18.0
python-dateutil==2.8.2
pytz==2023.3.post1
requests==2.31.0
six==1.16.0
tenacity==8.2.3
tzdata==2023.4
urllib3==2.1.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment