Sync Garmin watch data to InfluxDB. Pulls health and activity data from Garmin Connect and writes it as time-series points to an InfluxDB 2.x instance.
| Measurement | Fields |
|---|---|
daily_stats |
steps, distance, calories, resting/avg/min/max HR, stress, body battery, intensity minutes, floors |
sleep |
total/deep/light/REM/awake duration, SpO2, respiration, sleep scores |
heart_rate |
per-reading BPM throughout the day |
activity |
distance, duration, HR, calories, speed, elevation, cadence, power, VO2max (tagged by type & name) |
body_composition |
weight, BMI, body fat %, water %, muscle mass, bone mass, metabolic age, visceral fat |
respiration |
avg waking/sleep respiration, highest, lowest |
spo2 |
average, lowest, latest SpO2 |
Plus stress, HRV, hydration, training readiness and status, VO2 max
(max_metrics), heat/altitude acclimation, endurance and hill score, fitness age
and floors when your watch records them; python catgar.py --catalog lists every
field.
Times are stored as local clock digits marked UTC: a 07:05 weigh-in is stored
at 07:05Z whatever the zone, and a daily value at the day's 00:00Z, so a query
can group by local day without time-zone math.
- Python 3.9+
- A Garmin Connect account (same credentials you use in the Garmin Connect mobile app)
- An InfluxDB 2.x server with a bucket created for Garmin data
# Clone and install dependencies
git clone https://github.com/rotblauer/catGar.git
cd catGar
pip install -r requirements.txt
# Configure credentials
cp .env.example .env
# Edit .env with your Garmin and InfluxDB details| Variable | Required | Default | Description |
|---|---|---|---|
GARMIN_EMAIL |
Yes | — | Your Garmin Connect email |
GARMIN_PASSWORD |
Yes | — | Your Garmin Connect password |
INFLUXDB_URL |
No | http://localhost:8086 |
InfluxDB server URL |
INFLUXDB_TOKEN |
Yes | — | InfluxDB API token |
INFLUXDB_ORG |
Yes | — | InfluxDB organization |
INFLUXDB_BUCKET |
No | garmin |
InfluxDB bucket name |
# Sync since last sync (or today if first run)
python catgar.py
# Sync a specific date
python catgar.py 2024-06-15
# Sync the last 7 days
python catgar.py --days 7
# Initial backfill — all available data (up to 5 years)
python catgar.py --backfill
# Re-pull some categories only (names as in the sync summary)
python catgar.py --days 30 --only "daily stats,sleep"
# Which version is this?
python catgar.py --versionWith no date or flag, catGar syncs from the day before the last synced day
through today: a day's evening and that night's sleep reach Garmin Connect after
midnight, so yesterday is re-pulled until today ends. Re-writing a day overwrites
it in place. Only a whole run without errors moves the last synced day forward; a
run with --only or a single date leaves it alone.
--only names: daily stats, sleep, heart rate, body composition, respiration,
SpO2, stress, HRV, hydration, training readiness, training status, max metrics
(also writes heat/altitude acclimation), endurance score, hill score, fitness age,
floors, activities.
If you have an existing InfluxDB instance running on a Raspberry Pi, a single command handles everything — system packages, Python venv, credentials, systemd timer (every 4 hours), and an initial full backfill:
git clone https://github.com/rotblauer/catGar.git
cd catGar
sudo bash install.shThe installer will:
- Install
python3,pip,venv, andgitviaapt. - Copy the project to
/opt/catgarand create a virtual environment. - Prompt you for Garmin + InfluxDB credentials (writes
/opt/catgar/.env). - Install a systemd timer that runs the sync every 4 hours.
- Kick off an initial backfill of all available historical data.
After install you can check status with:
# Timer status
systemctl status catgar.timer
# Last run output
journalctl -u catgar.service -e
# Trigger a manual sync
sudo systemctl start catgar.serviceThe timer runs the copy in /opt/catgar, not your checkout, so a fix reaches it
only when you copy it over. From an up-to-date checkout:
git pull
sudo bash install.sh updateupdate installs dependencies first if requirements.txt changed, then copies
catgar.py, requirements.txt, .env.example and the unit files; changed units
are reinstalled (the old ones kept as /opt/catgar/*.bak). It prints the old and
new version and skips the credential prompts and the backfill. If /opt/catgar
is itself a clone, sudo git pull there and run sudo bash install.sh update
from it.
Every run logs its version (journalctl -u catgar.service | grep "catGar 20").
To ask the installed copy (sudo: it reads the root-only .env):
sudo /opt/catgar/venv/bin/python /opt/catgar/catgar.py --version.
Re-running the full sudo bash install.sh on a machine that has synced before
skips the backfill.
Run daily at 8 AM:
crontab -eAdd:
0 8 * * * cd /path/to/catGar && /path/to/python catgar.py >> /tmp/catgar.log 2>&1
Create ~/Library/LaunchAgents/com.catgar.sync.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.catgar.sync</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/path/to/catGar/catgar.py</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/catGar</string>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>8</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/catgar.log</string>
<key>StandardErrorPath</key>
<string>/tmp/catgar.log</string>
</dict>
</plist>Load the agent:
launchctl load ~/Library/LaunchAgents/com.catgar.sync.plistpip install pytest
python -m pytest tests/ -v