# withings-sync
A tool for synchronisation of the Withings API to:
- Garmin Connect
- Trainer Road
- raw JSON output
## Installation
```bash
$ pip install withings-sync
```
## Usage
```
usage: withings-sync [-h] [--garmin-username GARMIN_USERNAME] [--garmin-password GARMIN_PASSWORD] [--trainerroad-username TRAINERROAD_USERNAME] [--trainerroad-password TRAINERROAD_PASSWORD] [--fromdate DATE]
[--todate DATE] [--to-fit] [--to-json] [--output BASENAME] [--no-upload] [--verbose]
A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road or to provide a json string.
optional arguments:
-h, --help show this help message and exit
--garmin-username GARMIN_USERNAME, --gu GARMIN_USERNAME
username to log in to Garmin Connect.
--garmin-password GARMIN_PASSWORD, --gp GARMIN_PASSWORD
password to log in to Garmin Connect.
--trainerroad-username TRAINERROAD_USERNAME, --tu TRAINERROAD_USERNAME
username to log in to TrainerRoad.
--trainerroad-password TRAINERROAD_PASSWORD, --tp TRAINERROAD_PASSWORD
password to log in to TrainerRoad.
--fromdate DATE, -f DATE
--todate DATE, -t DATE
--to-fit, -F Write output file in FIT format.
--to-json, -J Write output file in JSON format.
--output BASENAME, -o BASENAME
Write downloaded measurements to file.
--features Enable Features
BLOOD_PRESSURE = sync blood pressure
--no-upload Won't upload to Garmin Connect or TrainerRoad.
--verbose, -v Run verbosely
```
### Providing credentials via environment variables
You can use the following environment variables for providing the Garmin and/or Trainerroad credentials:
- `GARMIN_USERNAME`
- `GARMIN_PASSWORD`
- `TRAINERROAD_USERNAME`
- `TRAINERROAD_PASSWORD`
The CLI also uses python-dotenv to populate the variables above. Therefore setting the environment variables
has the same effect as placing the variables in a `.env` file in the working directory.
### Providing credentials via secrets files
You can also populate the following 'secrets' files to provide the Garmin and/or Trainerroad credentials:
- `/run/secrets/garmin_username`
- `/run/secrets/garmin_password`
- `/run/secrets/trainerroad_username`
- `/run/secrets/trainerroad_password`
Secrets are useful in an orchestrated container context — see the [Docker Swarm](https://docs.docker.com/engine/swarm/secrets/) or [Rancher](https://rancher.com/docs/rancher/v1.6/en/cattle/secrets/) docs for more information on how to securely inject secrets into a container.
### Order of priority for credentials
In the case of credentials being available via multiple means (e.g. [environment variables](#providing-credentials-via-environment-variables) and [secrets files](#providing-credentials-via-secrets-files)), the order of resolution for determining which credentials to use is as follows, with later methods overriding credentials supplied by an earlier method:
1. Read secrets file(s)
2. Read environment variable(s), variables set explicitly take precedence over values from a `.env` file.
3. Use command invocation argument(s)
### Obtaining Withings Authorization Code
When running for a very first time, you need to obtain Withings authorization:
```bash
$ withings-sync -f 2019-01-25 -v
Can't read config file config/withings_user.json
User interaction needed to get Authentification Code from Withings!
Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!
(This is one-time activity)
https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html&
Token :
```
You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt.
This is one-time activity and it will not be needed to repeat.
## Tips
### Garmin SSO errors
Some users reported errors raised by the Garmin SSO login:
```
withings_sync.garmin.APIException: SSO error 401
```
or
```
withings_sync.garmin.APIException: SSO error 403
```
These errors are raised if a user tries to login too frequently.
E.g. by running the script every 10 minutes.
**We recommend to run the script around 8-10 times per day (every 2-3 hours).**
See also: https://github.com/jaroslawhartman/withings-sync/issues/31
### Docker
```
$ docker pull ghcr.io/jaroslawhartman/withings-sync:master
```
First start to ensure the script can start successfully:
Obtaining Withings authorisation:
```
$ docker run -v $HOME:/root --interactive --tty --name withings ghcr.io/jaroslawhartman/withings-sync:master --garmin-username=<username> --garmin-password=<password>
Can't read config file config/withings_user.json
User interaction needed to get Authentification Code from Withings!
Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!
(This is one-time activity)
https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html&
Token : <token>
Withings: Get Access Token
Withings: Refresh Access Token
Withings: Get Measurements
Measurements received
JaHa.WAW.PL
Garmin Connect User Name: JaHa.WAW.PL
Fit file uploaded to Garmin Connect
```
And for subsequent runs:
```
$ docker start -i withings
Withings: Refresh Access Token
Withings: Get Measurements
Measurements received
JaHa.WAW.PL
Garmin Connect User Name: JaHa.WAW.PL
Fit file uploaded to Garmin Connect
```
### Garmin auth
You can configure the location of the garmin session file with the variabe `GARMIN_SESSION`.
### Run a periodic Kubernetes job
Edit the credentials in `contrib/k8s-job.yaml` and run:
```bash
$ kubectl apply -f contrib/k8s-job.yaml
```
### For advanced users - registering own Withings application
The script has been registered as a Withings application and got assigned `Client ID` and `Consumer Secret`. If you wish to create your own application - feel free!
* First you need a Withings account. [Sign up here](https://account.withings.com/connectionuser/account_create).
* Then you need a Withings developer app registered. [Create your app here](https://account.withings.com/partner/add_oauth2).
Note, registering it is quite cumbersome, as you need to have a callback URL and an Icon. Anyway, when done, you should have the following identifiers:
| Identfier | Example |
|-----------------|--------------------------------------------------------------------|
| Client ID | `183e03.................765c98c10e8f1aa647a37067a1......baf491626` |
| Consumer Secret | `a75d65.................4c16719ef7bd69fa7c5d3fd0ea......ed48f1765` |
| Callback URI | `https://jhartman.pl/withings/notify` |
Configure them in `config/withings_app.json`, for example:
```
{
"callback_url": "https://wieloryb.uk.to/withings/withings.html",
"client_id": "183e0******0b3551f96765c98c1******b64bbbaf491626",
"consumer_secret": "a75d65******1df1514c16719ef7bd69fa7*****2e2b0ed48f1765"
}
```
For the callback URL you will need to setup a webserver hosting `contrib/withings.html`.
To do this in a Docker installation, you can use the environment variable `WITHINGS_APP` to point to a mounted `withings_app.json`
Example docker-compose:
```
withings-sync:
container_name: withings-sync
image: ghcr.io/jaroslawhartman/withings-sync:latest
volumes:
- "withings-sync:/root"
- "/etc/localtime:/etc/localtime:ro"
environment:
WITHINGS_APP: /root/withings_app.json
(...)
```
You can then add the app-config in `withings-sync/withings_app.json`
### Run a periodic docker-compose cronjob
We take the official docker image and override the entrypoint to crond.
If you have completed the initial setup (withings_user.json created and working), you can create the following config
```
version: "3.8"
services:
withings-sync:
container_name: withings-sync
image: ghcr.io/jaroslawhartman/withings-sync:master
volumes:
- "${VOLUME_PATH}/withings-sync:/root"
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=${TIME_ZONE}
entrypoint: "/root/entrypoint.sh"
```
The `entrypoint.sh` will then register the cronjob. For example:
```
#!/bin/sh
echo "$(( $RANDOM % 59 +0 )) */3 * * * withings-sync --gu garmin-username --gp 'mypassword' -v | tee -a /root/withings-sync.log" > /etc/crontabs/root
crond -f -l 6 -L /dev/stdout
```
This will run the job every 3 hours (at a random minute) and writing the output to console and the `/root/withings-sync.log`.
## Release
Release works via the GitHub [Draft a new Release](https://github.com/jaroslawhartman/withings-sync/releases/new)
function.
Keep in mind to bump the `version` key in `setup.py`.
### Docker Image
An image is created magically by GitHub Action and published
to [ghcr](https://github.com/jaroslawhartman/withings-sync/pkgs/container/withings-sync).
### pypi
You'll find a script to create and upload a release to pypi here `contrib/do_release.sh`.
It requires [twine](https://pypi.org/project/twine/).
This needs the permission on the [pypi-project](https://pypi.org/project/withings-sync/).
## References
* SSO authorization derived from https://github.com/cpfair/tapiriik
* TrainerRoad API from https://github.com/stuwilkins/python-trainerroad
## Credits / Authors
* Based on [withings-garmin](https://github.com/ikasamah/withings-garmin) by Masayuki Hamasaki, improved to support SSO authorization in Garmin Connect 2.
* Based on [withings-garmin-v2](https://github.com/jaroslawhartman/withings-garmin-v2) by Jarek Hartman, improved Python 3 compatability, code-style and setuptools packaging, Kubernetes and Docker support.
Raw data
{
"_id": null,
"home_page": "https://github.com/jaroslawhartman/withings-sync",
"name": "withings-sync",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "garmin withings sync api scale smarthome",
"author": "Masayuki Hamasaki, Steffen Vogel",
"author_email": "post@steffenvogel.de",
"download_url": "https://files.pythonhosted.org/packages/d7/2c/553d1723e9e1ca90bcc30d3bc0424d6fc9ead6e0e2aff56ad2d19e05f542/withings_sync-4.2.5.tar.gz",
"platform": null,
"description": "# withings-sync\n\nA tool for synchronisation of the Withings API to:\n\n- Garmin Connect\n- Trainer Road\n- raw JSON output\n\n## Installation\n\n```bash\n$ pip install withings-sync\n```\n\n## Usage\n\n```\nusage: withings-sync [-h] [--garmin-username GARMIN_USERNAME] [--garmin-password GARMIN_PASSWORD] [--trainerroad-username TRAINERROAD_USERNAME] [--trainerroad-password TRAINERROAD_PASSWORD] [--fromdate DATE]\n [--todate DATE] [--to-fit] [--to-json] [--output BASENAME] [--no-upload] [--verbose]\n\nA tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road or to provide a json string.\n\noptional arguments:\n -h, --help show this help message and exit\n --garmin-username GARMIN_USERNAME, --gu GARMIN_USERNAME\n username to log in to Garmin Connect.\n --garmin-password GARMIN_PASSWORD, --gp GARMIN_PASSWORD\n password to log in to Garmin Connect.\n --trainerroad-username TRAINERROAD_USERNAME, --tu TRAINERROAD_USERNAME\n username to log in to TrainerRoad.\n --trainerroad-password TRAINERROAD_PASSWORD, --tp TRAINERROAD_PASSWORD\n password to log in to TrainerRoad.\n --fromdate DATE, -f DATE\n --todate DATE, -t DATE\n --to-fit, -F Write output file in FIT format.\n --to-json, -J Write output file in JSON format.\n --output BASENAME, -o BASENAME\n Write downloaded measurements to file.\n --features Enable Features\n BLOOD_PRESSURE = sync blood pressure\n --no-upload Won't upload to Garmin Connect or TrainerRoad.\n --verbose, -v Run verbosely\n```\n\n### Providing credentials via environment variables\n\nYou can use the following environment variables for providing the Garmin and/or Trainerroad credentials:\n\n- `GARMIN_USERNAME`\n- `GARMIN_PASSWORD`\n- `TRAINERROAD_USERNAME`\n- `TRAINERROAD_PASSWORD`\n\nThe CLI also uses python-dotenv to populate the variables above. Therefore setting the environment variables\nhas the same effect as placing the variables in a `.env` file in the working directory.\n\n### Providing credentials via secrets files\n\nYou can also populate the following 'secrets' files to provide the Garmin and/or Trainerroad credentials:\n\n- `/run/secrets/garmin_username`\n- `/run/secrets/garmin_password`\n- `/run/secrets/trainerroad_username`\n- `/run/secrets/trainerroad_password`\n\nSecrets are useful in an orchestrated container context \u2014 see the [Docker Swarm](https://docs.docker.com/engine/swarm/secrets/) or [Rancher](https://rancher.com/docs/rancher/v1.6/en/cattle/secrets/) docs for more information on how to securely inject secrets into a container.\n\n### Order of priority for credentials\n\nIn the case of credentials being available via multiple means (e.g. [environment variables](#providing-credentials-via-environment-variables) and [secrets files](#providing-credentials-via-secrets-files)), the order of resolution for determining which credentials to use is as follows, with later methods overriding credentials supplied by an earlier method:\n\n1. Read secrets file(s)\n2. Read environment variable(s), variables set explicitly take precedence over values from a `.env` file.\n3. Use command invocation argument(s)\n\n### Obtaining Withings Authorization Code\n\nWhen running for a very first time, you need to obtain Withings authorization:\n\n```bash\n$ withings-sync -f 2019-01-25 -v\nCan't read config file config/withings_user.json\nUser interaction needed to get Authentification Code from Withings!\n\nOpen the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!\n(This is one-time activity)\n\nhttps://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html&\n\nToken :\n```\n\nYou need to visit the URL listed by the script and then - copy Authentification Code back to the prompt.\n\nThis is one-time activity and it will not be needed to repeat.\n\n\n## Tips\n\n### Garmin SSO errors\n\nSome users reported errors raised by the Garmin SSO login:\n\n```\nwithings_sync.garmin.APIException: SSO error 401\n```\n\nor \n\n```\nwithings_sync.garmin.APIException: SSO error 403\n```\n\nThese errors are raised if a user tries to login too frequently.\nE.g. by running the script every 10 minutes.\n\n**We recommend to run the script around 8-10 times per day (every 2-3 hours).**\n\nSee also: https://github.com/jaroslawhartman/withings-sync/issues/31\n\n### Docker\n\n```\n$ docker pull ghcr.io/jaroslawhartman/withings-sync:master\n```\n\nFirst start to ensure the script can start successfully:\n\n\nObtaining Withings authorisation:\n\n```\n$ docker run -v $HOME:/root --interactive --tty --name withings ghcr.io/jaroslawhartman/withings-sync:master --garmin-username=<username> --garmin-password=<password>\n\nCan't read config file config/withings_user.json\nUser interaction needed to get Authentification Code from Withings!\n\nOpen the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!\n(This is one-time activity)\n\nhttps://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html&\n\nToken : <token>\nWithings: Get Access Token\nWithings: Refresh Access Token\nWithings: Get Measurements\n Measurements received\nJaHa.WAW.PL\nGarmin Connect User Name: JaHa.WAW.PL\nFit file uploaded to Garmin Connect\n```\n\nAnd for subsequent runs:\n\n```\n$ docker start -i withings\nWithings: Refresh Access Token\nWithings: Get Measurements\n Measurements received\nJaHa.WAW.PL\nGarmin Connect User Name: JaHa.WAW.PL\nFit file uploaded to Garmin Connect\n```\n### Garmin auth\n\nYou can configure the location of the garmin session file with the variabe `GARMIN_SESSION`.\n\n### Run a periodic Kubernetes job\n\nEdit the credentials in `contrib/k8s-job.yaml` and run:\n\n```bash\n$ kubectl apply -f contrib/k8s-job.yaml\n```\n\n### For advanced users - registering own Withings application\n\nThe script has been registered as a Withings application and got assigned `Client ID` and `Consumer Secret`. If you wish to create your own application - feel free! \n\n\n* First you need a Withings account. [Sign up here](https://account.withings.com/connectionuser/account_create).\n* Then you need a Withings developer app registered. [Create your app here](https://account.withings.com/partner/add_oauth2).\n\nNote, registering it is quite cumbersome, as you need to have a callback URL and an Icon. Anyway, when done, you should have the following identifiers:\n\n| Identfier | Example |\n|-----------------|--------------------------------------------------------------------|\n| Client ID | `183e03.................765c98c10e8f1aa647a37067a1......baf491626` |\n| Consumer Secret | `a75d65.................4c16719ef7bd69fa7c5d3fd0ea......ed48f1765` |\n| Callback URI | `https://jhartman.pl/withings/notify` |\n\nConfigure them in `config/withings_app.json`, for example:\n\n```\n{\n \"callback_url\": \"https://wieloryb.uk.to/withings/withings.html\",\n \"client_id\": \"183e0******0b3551f96765c98c1******b64bbbaf491626\",\n \"consumer_secret\": \"a75d65******1df1514c16719ef7bd69fa7*****2e2b0ed48f1765\"\n}\n```\n\nFor the callback URL you will need to setup a webserver hosting `contrib/withings.html`.\n\nTo do this in a Docker installation, you can use the environment variable `WITHINGS_APP` to point to a mounted `withings_app.json`\n\nExample docker-compose:\n```\n withings-sync:\n container_name: withings-sync\n image: ghcr.io/jaroslawhartman/withings-sync:latest\n volumes:\n - \"withings-sync:/root\"\n - \"/etc/localtime:/etc/localtime:ro\"\n environment:\n WITHINGS_APP: /root/withings_app.json\n(...)\n```\nYou can then add the app-config in `withings-sync/withings_app.json`\n\n\n### Run a periodic docker-compose cronjob\n\nWe take the official docker image and override the entrypoint to crond.\n\nIf you have completed the initial setup (withings_user.json created and working), you can create the following config\n\n```\nversion: \"3.8\"\nservices:\n withings-sync:\n container_name: withings-sync\n image: ghcr.io/jaroslawhartman/withings-sync:master\n volumes:\n - \"${VOLUME_PATH}/withings-sync:/root\" \n - /etc/localtime:/etc/localtime:ro\n environment:\n - TZ=${TIME_ZONE}\n entrypoint: \"/root/entrypoint.sh\"\n```\n\nThe `entrypoint.sh` will then register the cronjob. For example:\n\n```\n#!/bin/sh\necho \"$(( $RANDOM % 59 +0 )) */3 * * * withings-sync --gu garmin-username --gp 'mypassword' -v | tee -a /root/withings-sync.log\" > /etc/crontabs/root\ncrond -f -l 6 -L /dev/stdout\n```\n\nThis will run the job every 3 hours (at a random minute) and writing the output to console and the `/root/withings-sync.log`.\n\n## Release\n\nRelease works via the GitHub [Draft a new Release](https://github.com/jaroslawhartman/withings-sync/releases/new) \nfunction.\nKeep in mind to bump the `version` key in `setup.py`.\n\n### Docker Image\n\nAn image is created magically by GitHub Action and published \nto [ghcr](https://github.com/jaroslawhartman/withings-sync/pkgs/container/withings-sync).\n\n### pypi\n\nYou'll find a script to create and upload a release to pypi here `contrib/do_release.sh`.\nIt requires [twine](https://pypi.org/project/twine/).\nThis needs the permission on the [pypi-project](https://pypi.org/project/withings-sync/).\n\n## References\n\n* SSO authorization derived from https://github.com/cpfair/tapiriik\n* TrainerRoad API from https://github.com/stuwilkins/python-trainerroad\n\n## Credits / Authors\n\n* Based on [withings-garmin](https://github.com/ikasamah/withings-garmin) by Masayuki Hamasaki, improved to support SSO authorization in Garmin Connect 2.\n* Based on [withings-garmin-v2](https://github.com/jaroslawhartman/withings-garmin-v2) by Jarek Hartman, improved Python 3 compatability, code-style and setuptools packaging, Kubernetes and Docker support. \n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road.",
"version": "4.2.5",
"project_urls": {
"Homepage": "https://github.com/jaroslawhartman/withings-sync"
},
"split_keywords": [
"garmin",
"withings",
"sync",
"api",
"scale",
"smarthome"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d72c553d1723e9e1ca90bcc30d3bc0424d6fc9ead6e0e2aff56ad2d19e05f542",
"md5": "602f0b108802164e975df1c74ca9aab4",
"sha256": "ed98da96e3779d166a9bdb7c0d67bd9a250d57146a684ffd21c62fc190f2b849"
},
"downloads": -1,
"filename": "withings_sync-4.2.5.tar.gz",
"has_sig": false,
"md5_digest": "602f0b108802164e975df1c74ca9aab4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 22755,
"upload_time": "2024-08-20T09:14:47",
"upload_time_iso_8601": "2024-08-20T09:14:47.602994Z",
"url": "https://files.pythonhosted.org/packages/d7/2c/553d1723e9e1ca90bcc30d3bc0424d6fc9ead6e0e2aff56ad2d19e05f542/withings_sync-4.2.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-20 09:14:47",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jaroslawhartman",
"github_project": "withings-sync",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "lxml",
"specs": []
},
{
"name": "requests",
"specs": []
},
{
"name": "garth",
"specs": []
},
{
"name": "setuptools",
"specs": []
}
],
"lcname": "withings-sync"
}