[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-72327":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":18,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":22,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":38,"readmeContent":39,"aiSummary":40,"trendingCount":16,"starSnapshotCount":16,"syncStatus":41,"lastSyncTime":42,"discoverSource":43},72327,"garmin-grafana","arpanghosh8453\u002Fgarmin-grafana","arpanghosh8453","A Dockerized python Script to fetch Garmin health data and populate that in a InfluxDB Database, for visualization long term health trends with Grafana","",null,"Python",3284,207,26,29,0,11,33,93,104.75,"BSD 3-Clause \"New\" or \"Revised\" License",false,"main",true,[26,27,28,29,30,31,32,33,34,35,36,37],"database","garmin","garmin-api","garmin-connect","garmin-data","garmin-smartwatches","grafana","grafana-dashboard","grafana-influxdb","influxdb","python","python3","2026-06-12 04:01:04","\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fi.imgur.com\u002FPYsbwqj.png\" width=\"450\" height=\"164\" align=\"center\">\n\u003C\u002Fp>\n\n# Grafana for Garmin Dashboard\n\nA docker container to fetch data from Garmin servers and store the data in a local influxdb database for appealing visualization with Grafana.\n\n> [!IMPORTANT]\n> Garmin is a registered trademark of Garmin Ltd. or its subsidiaries. Grafana is a registered trademark of Grafana Labs. This project is an independent, open-source tool and is not affiliated with, endorsed by, sponsored by, or approved by Garmin Ltd. or Grafana Labs.\n\n> [!NOTE]\n> This project repository is dynamically mirrored in [Codeberg](https:\u002F\u002Fcodeberg.org\u002Farpanghosh8453\u002Fgarmin-grafana) as a backup. An alternative docker image is available at [codeberg.org\u002Farpanghosh8453\u002Fgarmin-grafana](https:\u002F\u002Fcodeberg.org\u002Farpanghosh8453\u002F-\u002Fpackages\u002Fcontainer\u002Fgarmin-grafana).\n\n> [!TIP]\n> If you are a **Fitbit user**, please check out the [sister project](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Ffitbit-grafana) made for Fitbit\n\n## Table of contents\n\n- [Dashboard Example](#dashboard-example)\n- [Features](#features)\n- [Why use this project?](#why-use-this-project)\n- **Installation**\n  - EASY : [Automated installation](#automatic-install-with-helper-script-recommended-for-less-techy-people) with helper script\n  - ADVANCED : [Manual step by step installation](#manual-install-with-docker-recommended-if-you-understand-linux-concepts) guide\n  - SYNOLOGY : [Installation Guide](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fdiscussions\u002F107#discussion-8326104)\n  - KUBERNETES : [Helm](.\u002Fk8s\u002FREADME.md) chart for Kubernetes. Try with minikube - [Makefile](.\u002Fk8s\u002FMakefile) for easy deployment.\n- **How to**\n  - How to [pull historic (old) data](#historical-data-fetching-bulk-update) (bulk update)?\n  - How to [import from garmin connect local export files](#importing-from-garmin-connect-export)?\n  - How to [update to newer versions](#update-to-new-versions) of this project?\n  - How to [export data as CSV files](#export-data-to-csv-files) for AI insights?\n  - How to [backup the InfluxDB Database?](#backup-influxdb-database)\n  - How to use [multiple accounts](#multi-user-instance-setup)? - if you want to set up a dashboard for your spouse\n  - [Troubleshooting](#troubleshooting) Guide\n  - [Need Help?](#need-help)\n  - [Want a Desktop App?](#want-a-desktop-app)\n- Project supplement\n  - [Credits](#credits)\n  - [Dependencies](#dependencies)\n  - [Contribution Guideline](#contribution-guideline)\n  - [Limitations](#limitations)\n- [Support this project](#love-this-project)\n- [Star History](#star-history)\n\n## Dashboard Example\n\n![Dashboard](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fblob\u002Fmain\u002FGrafana_Dashboard\u002FGarmin-Grafana-Dashboard-Preview.png?raw=true)\n\n## Features\n\n- Automatic data collection from Garmin\n- Collects comprehensive health metrics including:\n  - Heart Rate Data\n  - Hourly steps Heatmap\n  - Daily Step Count\n  - Sleep Data and patterns (SpO2, Breathing rate, Sleep movements, HRV)\n  - Sleep regularity heatmap (Visualize sleep routine)\n  - Stress Data\n  - Body Battery data\n  - Calories\n  - Sleep Score\n  - Activity Minutes and HR zones\n  - Activity Timeline (workouts)\n  - GPS data from workouts (track, pace, altitude, HR)\n  - And more...\n- Automated data fetching in regular interval (set and forget)\n- Historical data backfilling\n\n## Why use this project?\n\n- **Free and Fully Open Source**: 100% transparent and open project — modify, distribute extend, and self-host as you wish, with no hidden costs. Just credit the author and support this project as you please!\n- **Local Ownership**: Keep a complete, private backup of your Garmin data. The script automatically syncs new data after each Garmin Connect upload — no manual action needed (\"set and forget\").\n- **Full Visualization Freedom**: You're not limited by Garmin’s app. Combine multiple metrics on a single panel, zoom into specific time windows, view raw (non-averaged) data over days or weeks, and build fully custom dashboards.\n- **Deeper Insights - All day metrics**: Explore your data to discover patterns, optimize performance, and track trends over longer periods of time. Export for advanced analysis (Python, Excel, etc.) from Grafana, set custom alerts, or create new personalized metrics. This project fetches _almost_ everything from your Garmin watch - not just limited to Activities analytics like most other online platforms\n- **No 3rd party data sharing**: You avoid sharing your sensitive health related data with any 3rd party service provider while having a great data visualization platform for free!\n\n## Automatic Install with helper script (Recommended For less techy people)\n\n> [!IMPORTANT]\n> This script is for initial setup only. if you already have used it or followed the manual setup to deploy this project, you should not run this again once the garminconnect OAuth tokens are saved (first successful data fetch). Please check the `update to new versions` section for upgrading the container(s).\n\n> [!TIP]\n> If you are getting some errors you can't figure out, give the almighty [ChatGPT](https:\u002F\u002Fchat.openai.com\u002F) a try, it's often known to be helpful troubleshooting issues with this project and script.\n\nThis script requires a linux environment. If Docker is not installed on your Linux\u002FMacOS system, follow the instructions to [install docker manually](https:\u002F\u002Fdocs.docker.com\u002Fengine\u002Finstall\u002F) on Linux. There is also an [automated docker installation script](https:\u002F\u002Fgithub.com\u002Fdocker\u002Fdocker-install) available using the one-liner command `curl -fsSL https:\u002F\u002Fget.docker.com -o get-docker.sh && sh get-docker.sh`.\n\nIf you are on `Windows` you should consider using [WSL](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows\u002Fwsl\u002Finstall) to get a linux sub-system up and running.\n\n#### Detailed steps for Windows users are as follows:\n\n- [Install docker desktop](https:\u002F\u002Fdocs.docker.com\u002Fget-started\u002Fintroduction\u002Fget-docker-desktop\u002F)\n- Install `WSL` and `Ubuntu` from the Microsoft Store\n- Start -> Run -> type `WSL.exe`, Follow the prompts to create your Linux sudo (admin) user and password. This password will be required for later steps.\n- Open docker desktop and agree to the EULA\n- Reboot your machine (important)\n- Once back up, WSL and Docker should be installed and linked together.\n- Start -> Run -> type `WSL.exe`, then run the below bash command in the terminal window.\n\nFor Linux or MacOS, simply run the following bash command from your linux command line (terminal).\n\n> [!NOTE]\n> If you get the error that `git : command not found` then you need to install `git` with the command `sudo apt install git` for Ubuntu\u002FDebian\u002FWSL(windows) based systems. For Mac, you need to use `brew install git`. If you are on a non-debian linux distribution, please use your OS specific package manager replacing `apt`.\n\nUse the following command to clone this repository to your local machine\n\n```bash\ncd ~ && git clone https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana.git garmin-grafana\n```\n\nUse the command next to install it automatically using the easy-install script. If it fails because docker was not installed, retry the command again after installing docker.\n\n```bash\ncd garmin-grafana && sudo bash .\u002Feasy-install.sh\n```\n\nEnter the Garmin Connect credentials when prompted and you should be all up and running (you will be prompted for 2FA code as well if you have that set up). Once the data keeps coming, you can check out the `http:\u002F\u002Flocalhost:3000` to reach Grafana (by default), do the initial setup with the default username `admin` and password `admin`. Check out the dashboards link on the left sidebar. you should have a dashboard auto-configured as `Garmin-Stats` under the dashboards section. There you should see the data added. It will **keep updating automatically** as soon as new data syncs with your Garmin Connect account.\n\n> [!NOTE]\n> When you run this for the first time, it will only automatically fetch the data for **last 7 days** only and keep pulling new data that syncs with Garmin Connect moving forward. if you want to sync your older data, that is super easy to do. You just need to run the following command in the terminal (`WSL` for windows) replacing the YYYY-MM-DD with appropriate start and end dates (MANUAL_START_DATE value must be older than MANUAL_END_DATE value)\n>\n> ```bash\n> cd ~\u002Fgarmin-grafana && docker compose run --rm -e MANUAL_START_DATE=YYYY-MM-DD -e MANUAL_END_DATE=YYYY-MM-DD garmin-fetch-data\n> ```\n\nThat should be everything you need for now! The script will be running in the background as long as your machine is up. After a restart, make sure to open docker desktop if you are on windows (if it does not start automatically) so that the containers get booted up as well. For Linux, everything should restart reliably after a reboot. If you are having issues, make sure the docker daemon is running and check the container status with `docker ps` command\n\n## Manual Install with Docker (Recommended if you understand linux concepts)\n\n> [!IMPORTANT]\n> Install docker if you don't have it already. Docker is supported in all major platforms\u002FOS. Please check the [docker installation guide](https:\u002F\u002Fdocs.docker.com\u002Fengine\u002Finstall\u002F). You can install it on Windows via WSL, on Unraid via Docker Compose plugin, on Proxmox via Docker-LXC, and natively on Linux and Mac.\n\n1. Clone this repository with the command `git clone https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana.git`. Change your working directory with `cd garmin-grafana`. Then create a folder named `garminconnect-tokens` inside the current folder (`garmin-grafana`) with the command `mkdir garminconnect-tokens`. Run `chown -R 1000:1000 garminconnect-tokens` to change the ownership of the garminconnect-tokens folder (so the `garmin-fetch-data` container's internal user can use it to store the Authentication tokens). You can also run `chmod -R 777 garminconnect-tokens` to make the folder generally available for every user on the system if you keep getting `PermissionError` during script execution. Cloning this repository allows you to maintain the folder and file structure, and allows you to use Grafana self-provisioning database.\n2. Create an empty `compose.yml` file inside the current `garmin-grafana` folder with the content of the given [compose-example.yml](.\u002Fcompose-example.yml) or simply rename the present `compose-example.yml` file to `compose.yml` with `mv compose-example.yml compose.yml` ( Change the environment variables inside according to instructions )\n\n> [!TIP]\n> The Docker image is also available as `ghcr.io\u002Farpanghosh8453\u002Fgarmin-fetch-data:latest` alongside `thisisarpanghosh\u002Fgarmin-fetch-data:latest`.\n\n3. You can use two additional environment variables `GARMINCONNECT_EMAIL` and `GARMINCONNECT_BASE64_PASSWORD` to add the login information directly. otherwise you will need to enter them in the initial setup phase when prompted. If you are not using these environment variables to pass your garmin Connect login informations, you must remove them altogether (remove the full lines including the variable names or comment out with a `#` in front of the variable names - as done in the example be default) from the compose file - leaving them to placeholder values or empty values might lead to invalid login attempt and possibily `401 Client Error`. Please note that here the password must be encoded with [Base64](http:\u002F\u002Fbase64encode.org\u002F) when using the `GARMINCONNECT_BASE64_PASSWORD` ENV variable. This is to ensure your Garmin Connect password is not in plaintext in the compose file. The script will decode it and use it when required. If you set these two ENV variables and do not have two factor authentication (via SMS or email), you can directly jump to `step 5`. If you are in mainland China and use Garmin-cn account you need to set `GARMINCONNECT_IS_CN=True`. You can also select what data you want to fetch with the `FETCH_SELECTION` variable in the compose file.\n\n> [!NOTE]\n> If you are planning to use Influxdb V3, you need to enter the admin access token in `INFLUXDB_V3_ACCESS_TOKEN`. To generate the admin token you should run `docker exec influxdb influxdb3 create token --admin` command. This will give you the admin token which you must update to `INFLUXDB_V3_ACCESS_TOKEN` ENV variable. You can do this only once and the token can't be viewed or retrieved ever again (influxdb only stores a hash of it in the database for comparison). So please store this token carefully.\n\n4. If you did not set up the email and password ENV variables or have 2FA enabled, you must run the following command first to get the Email, password and 2FA code prompt interactively: `docker pull thisisarpanghosh\u002Fgarmin-fetch-data:latest && docker compose run --rm garmin-fetch-data`. Enter the Email, Password (the characters will be visible when you type to avoid confusion, so find some privacy. If you paste the password, make sure there is no trailing space or unwanted characters), and 2FA code (if you have that enabled). Once you see the successful authentication message, you are good to go. The script will exit on it's own prompting you to restart the script (follow next step). This will automatically remove this orphan container as this was started with the `--rm` flag. You need to login like this **only once**. The script will [save the session Authentication tokens](https:\u002F\u002Fgithub.com\u002Fcyberjunky\u002Fpython-garminconnect\u002Fissues\u002F213#issuecomment-2213292471) in the container's internal `\u002Fhome\u002Fappuser\u002F.garminconnect` folder for future use. That token can be used for all the future requests as long as it's valid (expected session token lifetime is about [one year](https:\u002F\u002Fgithub.com\u002Fcyberjunky\u002Fpython-garminconnect\u002Fissues\u002F213), as Garmin seems to use long term valid access tokens instead of short term valid {access token + refresh token} pairs). This helps in reusing the authentication without logging in every time when the container starts, as that leads to `429 Client Error`, when login is attempted repeatedly from the same IP address. If you run into `429 Client Error` during your first login attempt with this script, please refer to the troubleshooting section below.\n\n> [!TIP]\n> You can un-comment the line `# user: root` in the `compose.yml` file to run the container as root (superuser) - this will resolve any permission error or read\u002Fwrite issue you are encountering. Use this if the above `chown` or `chmod` did not work for you and you keep getting the `Permission Error` during running this initial setup. If you do this, you must change the compose volume mount from `.\u002Fgarminconnect-tokens:\u002Fhome\u002Fappuser\u002F.garminconnect` to `.\u002Fgarminconnect-tokens:\u002Froot\u002F.garminconnect` so that the token files are preserved when you take down the containers with `docker compose down` for restarting or rebuilding.\n\n5. If you are using self provisioning provided with this project for Grafana setup, then you should update the placeholder variable name `${DS_GARMIN_STATS}` (for supporting external import) in the dashboard JSON to static `garmin_influxdb` as it is the uid set during the self provisioning of the dashboard. To do this, run the following command in the root garmin-grafana directory:\n\n**Linux**\n```\nsed -i 's\u002F\\${DS_GARMIN_STATS}\u002Fgarmin_influxdb\u002Fg' Grafana_Dashboard\u002FGarmin-Grafana-Dashboard.json\n```\n\n**MacOS**\n```\nsed -i '' 's\u002F\\${DS_GARMIN_STATS}\u002Fgarmin_influxdb\u002Fg' Grafana_Dashboard\u002FGarmin-Grafana-Dashboard.json\n```\n\n6. Finally run : `docker compose up -d` ( to launch the full stack in detached mode ). Thereafter you should check the logs with `docker compose logs --follow` to see any potential error from the containers. This will help you debug the issue, if there is any (especially read\u002Fwrite permission issues). if you are using docker volumes, there is little chance of this happening as file permissions will be managed by docker. For bind mounts, if you are having permission issues, please check the troubleshooting section.\n7. Now you can check out the `http:\u002F\u002Flocalhost:3000` to reach Grafana (by default), do the initial setup with the default username `admin` and password `admin`. If you have cloned the repository as instructed in step 1, and using self-provisioning for the grafana dashboards + databases, then you should have an automatic dashboard setup under the Dashboards section named as `Garmin-Grafana` filled with data! - and you are done!\n8. if you are not using self-provisioning, then you need to manually add influxdb as the data source and continue follow the rest of these instructions. Please note the influxdb hostname is set as `influxdb` with port `8086` so you should use `http:\u002F\u002Finfluxdb:8086` for the address during data source setup and not `http:\u002F\u002Flocalhost:8086` because influxdb is a running as a separate container but part of the same docker network and stack. Here the database name should be `GarminStats` matching the influxdb DB name from the docker compose. The query language used for the dashboard is `influxql` which is supported by both InfluxDB 1.x and 3.x, so please select that from the language dropdown during setup. Use the same username and password you used for your influxdb container (check your docker compose config for influxdb container, here we used `influxdb_user` and `influxdb_secret_password` in default configuration) Test the connection to make sure the influxdb is up and reachable (you are good to go if it finds the measurements when you test the connection)\n9. To use the Grafana dashboard with manual import, please use the [JSON file](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fblob\u002Fmain\u002FGrafana_Dashboard\u002FGarmin-Grafana-Dashboard.json) downloaded directly from GitHub or use the import code **23245** to pull them directly from the Grafana dashboard cloud. In the Grafana dashboard, the heatmap panels require an additional plugin that you must install. This can be done by using the `GF_PLUGINS_PREINSTALL=marcusolsson-hourly-heatmap-panel` environment variable like in the [compose-example.yml](.\u002Fcompose-example.yml) file, or after the creation of the container very easily with docker commands. Just run `docker exec -it grafana grafana cli plugins install marcusolsson-hourly-heatmap-panel` and then run `docker restart grafana` to apply that plugin update. Now, you should be able to see the Heatmap panels on the dashboard loading successfully.\n\n> [!NOTE]\n> When you run this for the first time, it will only automatically fetch the data for **last 7 days** only and keep pulling new data that syncs with Garmin Connect moving forward. In order to sync back older data, use the following command replacing the YYYY-MM-DD with appropriate start and end dates (MANUAL_START_DATE value must be older than MANUAL_END_DATE value)\n>\n> ```bash\n> docker compose run --rm -e MANUAL_START_DATE=YYYY-MM-DD -e MANUAL_END_DATE=YYYY-MM-DD garmin-fetch-data\n> ```\n\nIf you have come this far, everything should be working. If not, please check the **troubleshooting section** for known issues. If it is already working, **CONGRATULATIONS!**. Enjoy your dashboard and keep exercising! If you like the dashboard and my sincere effort behind it, please **star this repository**. If you enjoy it a lot and want to show your appreciation and share the joy with me, feel free to [buy me a coffee](https:\u002F\u002Fko-fi.com\u002FA0A84F3DP). Maintaining this project takes a lot of my free time and your support keeps me motivated to develop more features for the community and spend more time on similar projects. if you are having any trouble, feel free to open an issue here, I will try my best to help you!\n\n---\n\nThis project is made for InfluxDB 1.11, as Flux queries on influxDB 2.x can be problematic to use with Grafana at times. In fact, InfluxQL is being reintroduced in InfluxDB 3.0, reflecting user feedback. Grafana also has better compatibility\u002Fstability with InfluxQL from InfluxDB 1.11. Moreover, there are statistical evidence that Influxdb 1.11 queries run faster compared to influxdb 2.x. Since InfluxDB 2.x offers no clear benefits for this project, there are no plans for a migration.\n\n> [!IMPORTANT]\n> If you have an existing **InfluxDB v2.x** database and want to integrate that with this project, you can follow [this guide](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fdiscussions\u002F63#discussioncomment-13025100), although we officially do not support InfluxDB v2.x with this project. We have direct support for **InfluxDB v3.x**, but we do not actively encourage people to use that as it comes with paywalled features (InfluxDB v3.x OSS limits the data query time to 72 hours - long term queries are only available in enterprise and InfluxData cloud hosted instances) that are essential for long term data visualization. As we are interested in visualization of long term data trends, this limit defeats the purpose. Hence, we strongly recommend using InfluxDB 1.11.x (default settings) to our users as long as it's not discontinued from production.\n\n### Additional configuration and environment variables\n\n✅ The Above compose file creates an open read\u002Fwrite access influxdb database with no authentication. Unless you expose this database to the open internet directly, this poses no threat. If you share your local network, you may enable authentication and grant appropriate read\u002Fwrite access to the influxdb_user on the GarminStats database manually if you want with `INFLUXDB_ADMIN_ENABLED`, `INFLUXDB_ADMIN_USER`, and `INFLUXDB_ADMIN_PASSWORD` ENV variables during the setup by following the [influxdb guide](https:\u002F\u002Fgithub.com\u002Fdocker-library\u002Fdocs\u002Fblob\u002Fmaster\u002Finfluxdb\u002FREADME.md) but this won't be covered here for the sake of simplicity.\n\n✅ You can also enable additional advanced training data fetching (such as Hill Score, Training Readiness, Endurance Score Blood Pressure, Hydration etc.) with `FETCH_SELECTION` ENV variable in the compose file. Check [Discussion #119](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fdiscussions\u002F119#discussion-8338271) to know what additional options are available. There is no panel showing these additional data on the default grafana dashboard. You must create your own to visualize these on Grafana or [use this one](https:\u002F\u002Fgithub.com\u002Fbrunothesatellite\u002Fgrafana-dashboard) from @brunothesatellite which contains more panels.\n\n✅ By default, the pulled FIT files are not stored as files to save storage space during import (an in-memory IO buffer is used instead). If you want to keep the FIT files downloaded during the import for future use in `Strava` or any other application where FIT files are supported for import, you can turn on `KEEP_FIT_FILES=True` under `garmin-fetch-data` environment variables in the compose file. To access the files from the host machine, you should create a folder named `fit_filestore` with `mkdir fit_filestore` inside the `garmin-fetch-data` folder (where your compose file is currently located) and change the ownership with `chown 1000:1000 fit_filestore`, and then must setup a volume bind mount like this `.\u002Ffit_filestore:\u002Fhome\u002Fappuser\u002Ffit_filestore` under the volumes section of `garmin-fetch-data`. This would map the container's internal `\u002Fhome\u002Fappuser\u002Ffit_filestore` folder to the `fit_filestore` folder you created. You will see the FIT files for your activities appear inside this `fit_filestore` folder once the script starts running.\n\n✅ By default indoor activities FIT files lacking GPS data are not processed (Activity summaries are processed for all activities, just not the detailed intra-activity HR, Pace etc. which are included only inside the FIT files and require additional processing power) to save resources and processing time per fetched activity. If you want to process all activities regardless of GPS data availability associated with the activity, you can set `ALWAYS_PROCESS_FIT_FILES=True` in the environment variables section of the `garmin-fetch-data` container as that will ensure all FIT files are processed irrespective of GPS data availability with the activities.\n\n✅ If you are having missing data on previous days till midnight (which are available on Garmin Connect but missing on dashboard) or sync issues when using the automatic periodic fetching, consider updating the container to recent version and use `USER_TIMEZONE` environment variable under the `garmin-fetch-data` service. The value must be a valid tz identifier like `Europe\u002FBudapest`. This variable is optional and the script tries to determine the timezone and fetch the UTC offset automatically if this variable is set as empty. If you see the automatic identification is not working for you, this variable can be used to override that behaviour and ensures the script is using the hardcoded timezone for all data fetching related activities. The previous gaps won't be filled (you need to fetch them using historic bulk update method), but moving forward, the script will keep everything in sync.\n\n✅ Want this dashboard in **Imperial units** instead of **metric units**? I can't maintain two separate dashboards at the same time but here is an [excellent step-by-step guide](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fissues\u002F27#issuecomment-2817081738) on how you can do it yourself on your dashboard! Also, If you prefer 24 hours time format instead of 12 hour with AM\u002FPM, you can remove `GF_DATE_FORMATS_*` ENV variables from the `compose.yml` file.\n\n### Collecting periodic watch battery levels\n\nUnfortunately, Garmin Connect does not sync the device battery level (possibly due to infrequent passive syncing intervals). Hence, it's not possible to get the watch's battery data directly using this setup. However, I have found an alternative, which requires a lot of additional setup (out of the scope for this project - but I will give a brief walkthrough).\n\nYou will need a self-hosted\u002Fcloud instance of [homeassistant](https:\u002F\u002Fwww.home-assistant.io\u002F) and [GarminHomeAssistant (Watch Application) from Connect IQ](https:\u002F\u002Fapps.garmin.com\u002Fen-US\u002Fapps\u002F61c91d28-ec5e-438d-9f83-39e9f45b199d). Detailed installation instructions are [available here](https:\u002F\u002Fgithub.com\u002Fhouse-of-abbey\u002FGarminHomeAssistant). This application is Free and open source as well just like this project, and the maintainer is very supportive!\n\nAfter you install it, you need to enable the battery level and other stats collection (background running) in the application settings on Connect IQ. You will see the battery level history on HomeAssistant entities panel (appearing as `sensor.garmin_device_battery_level`) thereafter. If you want to integrate this data to the InfluxDB database and Grafana dashboard you have with this project, you need to add an additional InfluxDB addon configuration in the `configuration.yaml` file of HomeAssistant installation like following.\n\n```yaml\ninfluxdb:\n  host: influxdb\n  port: 8086\n  database: GarminStats\n  username: influxdb_user\n  password: influxdb_secret_password\n  ssl: false\n  verify_ssl: false\n  max_retries: 3\n  include:\n    entities:\n      - sensor.garmin_device_battery_level\n  tags:\n    source: hass\n```\n\nThere is a Grafana panel in the dashboard (given with this project) which displays this data when available. If you do not have this setup, you should remove that panel from the dashboard, as battery data collection is not possible from the watch otherwise.\n\n## Multi user instance setup\n\nIf this is working well for you, maybe you want to set this up for your family\u002Fspouse. For that, you should not duplicate the full compose stack (you can, but then you will have two instances or Grafana and Influxdb containers running on the same host machine, which is not a smart idea). You should be able to do this by following [this guide](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fissues\u002F96#issuecomment-2868627808). There is no automatic setup script for this - you need to have a little understanding of docker and follow the given instructions.\n\n## Update to new versions\n\nUpdating with docker is super simple. Just go to the folder where the `compose.yml` is and run `docker compose pull` and then `docker compose down && docker compose up -d`. Please verify if everything is running correctly by checking the logs with `docker compose logs --follow`\n\n> [!CAUTION]\n> If you run `docker compose down -v`, that (using the `-v` flag) will purge the persistent docker volumes for the influxdb (if you are using docker volumes - default setup) which will wipe out all the data and databases stored in the influxdb container. Please be careful about this action but it can be useful if you want to start fresh wiping out the old database and container. This action cannot be undone.\n\n## Historical data fetching (bulk update)\n\n> [!TIP]\n> Please note that this process is intentionally rate limited with a 5 second wait period between each day update to ensure the Garmin servers are not overloaded with requests when using bulk update. You can update the value with `RATE_LIMIT_CALLS_SECONDS` ENV variable in the `garmin-fetch-data` container, but lowering it is not recommended,\n\n> [!NOTE]\n> Please note that this process, if repeated multiple times, **DOES NOT create any duplicate data** in the database if used with InfluxDB. InfluxDB being a time series database, uses timestamp and tags combined to create a hash that is used as primary key. So writing the same values with same timestamp and tags effectively overwrites the previous field values.\n\n#### Procedure\n\n1. Please run the above docker based installation steps `1` to `4` first (to set up the Garmin Connect login session tokens if not done already).\n2. Stop the running container and remove it with `docker compose down` if running already\n3. Run command `docker compose run --rm -e MANUAL_START_DATE=YYYY-MM-DD -e MANUAL_END_DATE=YYYY-MM-DD garmin-fetch-data` to update the data between the two dates. You need to replace the `YYYY-MM-DD` with the actual dates in that format, for example `docker compose run --rm -e MANUAL_START_DATE=2025-04-12 -e MANUAL_END_DATE=2025-04-14 garmin-fetch-data`. The `MANUAL_END_DATE` variable is optional, if not provided, the script assumes it to be the current date. `MANUAL_END_DATE` must be in future to the `MANUAL_START_DATE` variable passed, and in case they are same, data is still pulled for that specific date.\n\n> [!TIP]\n> If you are running this more than once to update the old data after container update, and want to only fetch specific data points instead of everything for the bulk fetch (to save time and resources), you can set `FETCH_SELECTION` to the measurements you want to fetch again. You can override the value of compose like this `docker compose run --rm -e MANUAL_START_DATE=YYYY-MM-DD -e MANUAL_END_DATE=YYYY-MM-DD -e FETCH_SELECTION=activity,sleep garmin-fetch-data` if you just want to update\u002Fre-fetch the past activities and sleep data and nothing else. Look at the compose file comments to know what values are available for this variable.\n\n1. Please note that the bulk data fetching is done in **reverse chronological order**. So you will have recent data first and it will keep going back until it hits `MANUAL_START_DATE`. You can have this running in background. If this terminates after some time unexpectedly, you can check back the last successful update date from the container stdout logs and use that as the `MANUAL_END_DATE` when running bulk update again as it's done in reverse chronological order.\n2. After successful bulk fetching, you will see a `Bulk update success` message and the container will exit and remove itself automatically.\n3. Now you can run the regular periodic update with `docker compose up -d`\n\n> [!IMPORTANT]\n> Garmin puts **Intraday historic data** older than **six months** in **cold storage (archived database)** and they are not available to the regular API endpoints directly anymore. You can do a manual refresh request for that day from the app, and only then the data becomes available for 7 days before it goes back to cold storage again. There is a daily server-side limit on the refresh requests (estimated around 20-40 per day) - So it's not possible to refresh the data in bulk while importing. if you have used this script to bulk fetch your past data older than 6 months, the intraday data points (indtaday HR rates, intraday sleep stages, etc.) will be missing for the older dates - although the daily average data points remain available the API endpoints for any past dates (regardless of how old they are) and hence remains unaffected. Please check out [Issue #77](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fissues\u002F77) if you want to know more about this. This is not a limitation of this project as it is imposed by Garmin's API design.\n\n## Importing from Garmin connect export\n\nSee [here](docs\u002Fmanual-import-instructions.md) for instructions on how to manually import local files from (e.g, Garmin Bulk Export or local .FIT file). Please make note that it will not import intraday level data. Just the basic average daily stats and the activity or workout stats from the FIT file. For intraday level detailed data use the bulk import option given [above](#historical-data-fetching-bulk-update). \n\n## Export Data to CSV files\n\nThis project provides additional utilities to export the data as CSV for external analysis or AI integration. After the export, you can use the CSV files to feed into ChatGPT (If you are not in EU, your data will be used for training) or any locally hosted LLM chat interface with [Openweb-UI](https:\u002F\u002Fgithub.com\u002Fopen-webui\u002Fopen-webui) or [anythingllm](https:\u002F\u002Fanythingllm.com\u002F) (Which natively supports RAG based document ingestion and available as Windows application) to get insights from your long term health data. If you turn on chat history, you may be able to get more insightful recommendations over time.\n\nThere are two ways to export the data into CSV files.\n\n1. Use the native CSV export functionality of Grafana, where you can export the data shown on any Grafana panel using [this guide](https:\u002F\u002Fgrafana.com\u002Fblog\u002F2024\u002F05\u002F30\u002Fhow-to-export-any-grafana-visualization-to-a-csv-file-microsoft-excel-or-google-sheets\u002F) as CSV.\n2. If the above method is tedious and you want to grab all measurements in detail as CSV files with one command directly from the local InfluxDB database, a convenient exporter script is provided with this project (included inside the docker container).\n\n   2.1 Simply run the following docker command from your terminal `docker exec garmin-fetch-data python \u002Fapp\u002Fgarmin_grafana\u002Finfluxdb_exporter.py` to export the last 30 days data. The script takes additional arguments such as `last-n-days` or `start-date` and `end-date` if you want to export data for last n days or for a specific date range. You should run the command like\n\n   ```\n   docker exec garmin-fetch-data python \u002Fapp\u002Fgarmin_grafana\u002Finfluxdb_exporter.py --last-n-days=7\n   ```\n\n   or\n\n   ```\n   docker exec garmin-fetch-data python \u002Fapp\u002Fgarmin_grafana\u002Finfluxdb_exporter.py --start-date=2025-01-01 --end-date=2025-03-01\n   ```\n\n   2.2 When the export is finished, you will see an output file path in the format ` Exported N measurement CSVs into \u002Ftmp\u002FGarminStats_Export_XYZ.zip`. The zip filename will vary based on when you run the command and how many days you selected. Take note of the full export path name.\n\n   2.3 Now the exported zip is saved inside the container, we need to copy it to our host machine. To do this, run `docker cp garmin-fetch-data:\u002Ftmp\u002FGarminStats_Export_XYZ.zip .\u002F` and replace the `\u002Ftmp\u002FGarminStats_Export_XYZ.zip` part with your zip filename from the output of the previous command. This command will place the zip file in your current working directory - you can replace the `.\u002F` ending of the command with a local path like `~\u002Fgarmin-grafana\u002F` if you want to place it somewhere specific. Once the copy is complete, you can remove the export zip from the container by running `docker exec garmin-fetch-data rm \u002Ftmp\u002FGarminStats_Export_XYZ.zip` to free up some space (optional).\n\n   2.4 Now unzip the zip file you have and you will see all the measurements are available as separate CSV files. You can run your custom analysis with these or ask LLM for insights by directly feeding the CSV file(s)!\n\n## Backup InfluxDB Database\n\nWhether you are using a bind mount or a docker volume, creating a restorable archival backup of your valuable health data is always advised. Assuming you named your database as `GarminStats` and influxdb container name is `influxdb`, you can use the following script to create a static archival backup of your data present in the influxdb database at that time point. These restore points can be used to re-create the influxdb database with the archived data without requesting them from Garmin's servers again, which is not only time consuming but also resource intensive.\n\n```bash\n#!\u002Fbin\u002Fbash\nTIMESTAMP=$(date +%F_%H-%M)\nBACKUP_DIR=\".\u002Finfluxdb_backups\u002F$TIMESTAMP\"\nmkdir -p \"$BACKUP_DIR\"\ndocker exec influxdb influxd backup -portable -db GarminStats \u002Ftmp\u002Finfluxdb_backup\ndocker cp influxdb:\u002Ftmp\u002Finfluxdb_backup \"$BACKUP_DIR\"\ndocker exec influxdb rm -r \"\u002Ftmp\u002Finfluxdb_backup\"\n```\n\nThe above bash script would create a folder named `influxdb_backups` inside your current working directory and create a subfolder under it with current date-time. Then it will create the backup for `GarminStats` database and copy the backup files to that location.\n\nFor restoring the data from a backup, you first need to make the files available inside the new influxdb docker container. You can use `docker cp` or volume bind mount for this. Once the backup data is available to the container internally, you can simply run `docker exec influxdb influxd restore -portable -db GarminStats \u002Fpath\u002Fto\u002Finternal-backup-directory` to restore the backup.\n\nPlease read detailed guide on this from the [influxDB documentation for backup and restore](https:\u002F\u002Fdocs.influxdata.com\u002Finfluxdb\u002Fv1\u002Fadministration\u002Fbackup_and_restore\u002F)\n\n## Troubleshooting\n\n- The issued session token is apparently [valid only for 1 year](https:\u002F\u002Fgithub.com\u002Fcyberjunky\u002Fpython-garminconnect\u002Fissues\u002F213) or less. Therefore, the automatic fetch will fail after the token expires. If you are using it more than one year, you may need to stop, remove and redeploy the container (follow the same instructions for initial setup, you will be asked for the username and password + 2FA code again). if you are not using MFA\u002F2FA (SMS or email one time code), you can use the `GARMINCONNECT_EMAIL` and `GARMINCONNECT_BASE64_PASSWORD` (remember, this is [base64 encoded](http:\u002F\u002Fbase64encode.org\u002F) password, not plaintext) ENV variables in the compose file to give this info directly, so the script will be able to re-generate the tokens once they expire. Unfortunately, if you are using MFA\u002F2FA, you need to enter the one time code manually after rebuilding the container every year when the tokens expire to keep the script running (Once the session token is valid again, the script will automatically back-fill the data you missed)\n- If you are getting `429 Client Error` after a few login tries during the initial setup, this is an indication that you are being rate limited based on your public IP. Garmin has a set limit for repeated login attempts from the same IP address to protect your account. You can wait for a few hours or a day, or switch to a different wifi network outside your home (will give you a new public IP) or just simply use mobile hotspot (will give you a new public IP as well) for the initial login attempt. Similar behavior is discussed in the [python-garminconnect issue tracker](https:\u002F\u002Fgithub.com\u002Fcyberjunky\u002Fpython-garminconnect\u002Fissues).\n- Running into `401 Client Error` when trying to login for the first time? make sure you are using the correct username and password for your account. If you enter it at runtime, it should be in plaintext but if you add it with environment variables in the docker compose stack, it must be [Base64 encoded](https:\u002F\u002Fwww.base64encode.org\u002F). if you are 100% sure you are using the right credentials, and still get this error, it's probably due to the fact that you are connected to a VPN network which is preventing the log in request (see issue [#20](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fissues\u002F20)). If you are not using a VPN, then please try running the container with mobile hotspot network or with a VPN exit tunnel (both gives you a different public IP) - you need to try this from a different network somehow.\n- If you want to bind mount the docker volumes for the `garmin-fetch-data` container, please keep in mind that the script runs with the internal user `appuser` with uid and gid set as 1000. So please chown the bind mount folder accordingly as stated in the above instructions. Also, `grafana` container requires the bind mount folders to be owned by `472:472` and `influxdb:1.11` container requires the bind mount folders to be owned by `1500:1500`. If none of this solves the `Permission Denied` issue for you, you can change the bind mount folder permission as `777` with `chmod -R 777 garminconnect-tokens`. Another solution could be to add `user: root` in the container configuration to run it as root instead of default `appuser` (this option has security considerations)\n- If the Activities details (GPS, Pace, HR, Altitude) are not appearing on the dashboard, make sure to select an Activity listed on the top left corner of the Dashboard (In the `Activity with GPS` variable dropdown). If you see no values are available there, but in the log you see the activities are being pulled successfully, then it's due to a Grafana Bug. Go to the dashboard variable settings, and please ensure the correct datasource is selected for the variable and the query is set to `SHOW TAG VALUES FROM \"ActivityGPS\" WITH KEY = \"ActivitySelector\" WHERE $timeFilter`. Once you set this properly after the dashboard import, the values should show up correctly in the dropdown and you will be able to select specific Activity and view it's stats on the dashboard.\n- Missing the battery levels data on the dashboard? Check out the section titled `Collecting periodic watch battery levels` to know how to set it up.\n\n## Credits\n\nThis project is made possible by **generous community contribution** towards the [gofundme](https:\u002F\u002Fgofund.me\u002F0d53b8d1) advertised in [this post](https:\u002F\u002Fwww.reddit.com\u002Fr\u002FGarmin\u002Fcomments\u002F1jucwhu\u002Fupdate_free_and_open_source_garmin_grafana\u002F) on Reddit's [r\u002Fgarmin](https:\u002F\u002Fwww.reddit.com\u002Fr\u002FGarmin) community. I wanted to build this tool for a long time, but funds were never sufficient for me to get a Garmin, because they are pretty expensive. With the community donations, I was able to buy a `Garmin Vivoactive 6` and built this tool open to everyone. if you are using this tool and enjoy it, please remember what made this possible! Huge shoutout to the [r\u002Fgarmin](https:\u002F\u002Fwww.reddit.com\u002Fr\u002FGarmin) community for being generous, trusting me and actively supporting my idea!\n\n## Dependencies\n\n- [python-garminconnect](https:\u002F\u002Fgithub.com\u002Fcyberjunky\u002Fpython-garminconnect) by [cyberjunky](https:\u002F\u002Fgithub.com\u002Fcyberjunky) : Garmin Web API wrapper\n\n## Contribution Guideline\n\nPlease find the contribution guidelines [here](.github\u002FCONTRIBUTING.md)\n\n## Limitations\n\nThis project depends on Garmin cloud. This does not directly sync data from your watch. Your data syncs to Garmin cloud first, and then within the set interval, the script periodically fetches the data from the Garmin servers using the locally stored Oauth tokens. Implementing direct sync is quite tricky and it will unpair your current device and overtake the syncing activities. If there is any script or user error, this might cause permanent data loss. As this project do not come with any kind of liability or warranty, it falls on the user using this. If you are looking for direct sync from your watch, this project is not for you. You might look into the [Gadgetbridge project](https:\u002F\u002Fgadgetbridge.org\u002Fgadgets\u002Fwearables\u002Fgarmin\u002F), which might be able to accomplish this for you if you are ready to take full responsibility of your data. This direct sync feature is currently not in our roadmap.\n\n## Love this project?\n\nI'm thrilled that you're using this dashboard. Your interest and engagement mean a lot to me! You can view and analyze more detailed health statistics with this setup than paying a connect+ subscription fee to Garmin.\n\nMaintaining and improving this project takes a significant amount of my free time. Your support helps keep me motivated to add new features and work on similar projects that benefit the community.\n\nIf you find this project helpful, please consider:\n\n⭐ Starring this repository to show your support and spread the news!\n\n☕ [Buying me a coffee](https:\u002F\u002Fko-fi.com\u002FA0A84F3DP) if you'd like to contribute to its maintenance and future development.\n\n[![ko-fi](https:\u002F\u002Fko-fi.com\u002Fimg\u002Fgithubbutton_sm.svg)](https:\u002F\u002Fko-fi.com\u002FA0A84F3DP)\n\n## Need Help?\n\nIf you're experiencing any issues with running this project or have questions, feel free to [open an issue](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Fgarmin-grafana\u002Fissues\u002Fnew\u002Fchoose) on this repository. I'll do my best to assist you.\n\n## Need a Desktop App?\n\nToo complicated to self host just to see your FIT file activity data? What about a completely offline Desktop solution, which can be installed from standard binaries? Here is a cross platform solution, check out [fit-dashboard](https:\u002F\u002Fgithub.com\u002Farpanghosh8453\u002Ffit-dashboard)\n\n\u003Cimg width=\"1897\" height=\"1018\" alt=\"individual_page\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fd64eb39a-5e84-46e5-aa42-44aa6e4e9274\" \u002F>\n\u003Cimg width=\"1905\" height=\"1019\" alt=\"overview_page\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fb9aa8762-66c1-453a-9bf6-f974e9600280\" \u002F>\n\n\n## Star History\n\n[![Star History Chart](https:\u002F\u002Fapi.star-history.com\u002Fsvg?repos=arpanghosh8453\u002Fgarmin-grafana&type=Date)](https:\u002F\u002Fwww.star-history.com\u002F#arpanghosh8453\u002Fgarmin-grafana&Date)\n","该项目是一个Docker化的Python脚本，用于从Garmin服务器获取健康数据并存储到InfluxDB数据库中，以便通过Grafana进行长期健康趋势的可视化。其核心功能包括自动收集来自Garmin的综合健康指标，如心率、步数热图、睡眠模式等，并利用Grafana的强大图表展示能力呈现这些数据。技术上采用了Python语言编写，结合了Docker容器化部署和InfluxDB时间序列数据库的特点，确保了系统的易用性和高效性。适合于需要长期跟踪个人或家庭成员健康状况的场景使用，尤其是对于那些希望以更直观方式理解自身健康数据变化趋势的用户而言，此项目提供了一个理想的解决方案。",2,"2026-06-11 03:41:22","high_star"]