Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple users #84

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
44 changes: 7 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,19 @@ $ 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]
usage: withings-sync [-h] [--withings-userid WITHINGS_USERID] [--garmin-upload] [--trainerroad-upload] [--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.
--withings-userid WITHINGS_USERID, --wuid WITHINGS_USERID
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, from the describtion its not quite clear:
Is this a withings specific user id or is it just a valu i want for this user? Should I geht this from the api or can I just use 42?

API userid to use for Withings.
--garmin-upload, --gu
upload to Garmin Connect.
--trainerroad-upload, --tu
upload to TrainerRoad.
--fromdate DATE, -f DATE
--todate DATE, -t DATE
--to-fit, -F Write output file in FIT format.
Expand All @@ -50,34 +48,6 @@ optional arguments:
--verbose, -v Run verbosely
```

### Providing credentials via environment variables
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if you could describe, that username and password are requested during first run.


You can use the following environment variables for providing the Garmin and/or Trainerroad credentials:

- `GARMIN_USERNAME`
- `GARMIN_PASSWORD` 
- `TRAINERROAD_USERNAME`
- `TRAINERROAD_PASSWORD`

### 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)
3. Use command invocation arugment(s)

### Obtaining Withings Authorization Code

When running for a very first time, you need to obtain Withings authorization:
Expand Down
125 changes: 34 additions & 91 deletions withings_sync/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,7 @@
from withings_sync.trainerroad import TrainerRoad
from withings_sync.fit import FitEncoder_Weight


try:
with open("/run/secrets/garmin_username", encoding="utf-8") as secret:
GARMIN_USERNAME = secret.read()
except OSError:
GARMIN_USERNAME = ""

try:
with open("/run/secrets/garmin_password", encoding="utf-8") as secret:
GARMIN_PASSWORD = secret.read()
except OSError:
GARMIN_PASSWORD = ""

if "GARMIN_USERNAME" in os.environ:
GARMIN_USERNAME = os.getenv("GARMIN_USERNAME")

if "GARMIN_PASSWORD" in os.environ:
GARMIN_PASSWORD = os.getenv("GARMIN_PASSWORD")


try:
with open("/run/secrets/trainerroad_username", encoding="utf-8") as secret:
TRAINERROAD_USERNAME = secret.read()
except OSError:
TRAINERROAD_USERNAME = ""

try:
with open("/run/secrets/trainerroad_password", encoding="utf-8") as secret:
TRAINERROAD_PASSWORD = secret.read()
except OSError:
TRAINERROAD_PASSWORD = ""

if "TRAINERROAD_USERNAME" in os.environ:
TRAINERROAD_USERNAME = os.getenv("TRAINERROAD_USERNAME")

if "TRAINERROAD_PASSWORD" in os.environ:
TRAINERROAD_PASSWORD = os.getenv("TRAINERROAD_PASSWORD")
WITHINGS_USERID = 1


def get_args():
Expand All @@ -66,37 +30,26 @@ def date_parser(date_string):
return datetime.strptime(date_string, "%Y-%m-%d")

parser.add_argument(
"--garmin-username",
"--gu",
default=GARMIN_USERNAME,
"--withings-userid",
"--wuid",
default=WITHINGS_USERID,
type=str,
metavar="GARMIN_USERNAME",
help="username to log in to Garmin Connect.",
metavar="WITHINGS_USERID",
help="API userid to use for Withings.",
)

parser.add_argument(
"--garmin-password",
"--gp",
default=GARMIN_PASSWORD,
type=str,
metavar="GARMIN_PASSWORD",
help="password to log in to Garmin Connect.",
"--garmin-upload",
"--gu",
action="store_true",
help=("upload to Garmin Connect."),
)

parser.add_argument(
"--trainerroad-username",
"--trainerroad-upload",
"--tu",
default=TRAINERROAD_USERNAME,
type=str,
metavar="TRAINERROAD_USERNAME",
help="username to log in to TrainerRoad.",
)
parser.add_argument(
"--trainerroad-password",
"--tp",
default=TRAINERROAD_PASSWORD,
type=str,
metavar="TRAINERROAD_PASSWORD",
help="password to log in to TrainerRoad.",
action="store_true",
help=("upload to TrainerRoad."),
)

parser.add_argument("--fromdate", "-f", type=date_parser, metavar="DATE")
Expand All @@ -120,27 +73,21 @@ def date_parser(date_string):
metavar="BASENAME",
help=("Write downloaded measurements to file."),
)

parser.add_argument(
"--no-upload",
action="store_true",
help=("Won't upload to Garmin Connect or " "TrainerRoad."),
)
parser.add_argument("--verbose", "-v", action="store_true", help="Run verbosely")

return parser.parse_args()


def sync_garmin(fit_file):
def sync_garmin(withings, fit_file):
"""Sync generated fit file to Garmin Connect"""
garmin = GarminConnect()
session = garmin.login(ARGS.garmin_username, ARGS.garmin_password)
session = garmin.login(withings.get_garmin_username(), withings.get_garmin_password())
return garmin.upload_file(fit_file.getvalue(), session)


def sync_trainerroad(last_weight):
def sync_trainerroad(withings, last_weight):
"""Sync measured weight to TrainerRoad"""
t_road = TrainerRoad(ARGS.trainerroad_username, ARGS.trainerroad_password)
t_road = TrainerRoad(withings.get_trainerroad_username(), withings.get_trainerroad_password())
t_road.connect()
logging.info("Current TrainerRoad weight: %s kg ", t_road.weight)
logging.info("Updating TrainerRoad weight to %s kg", last_weight)
Expand Down Expand Up @@ -257,7 +204,7 @@ def prepare_syncdata(height, groups):
for dataentry in groupdata["raw_data"]:
logging.debug(dataentry)

logging.debug(
logging.info(
"Record: %s, height=%s m, "
"weight=%s kg, "
"fat_ratio=%s %%, "
Expand Down Expand Up @@ -320,7 +267,7 @@ def sync():
"""Sync measurements from Withings to Garmin a/o TrainerRoad"""

# Withings API
withings = WithingsAccount()
withings = WithingsAccount(ARGS.withings_userid)

if not ARGS.fromdate:
startdate = withings.get_lastsync()
Expand All @@ -342,38 +289,34 @@ def sync():
logging.error("No measurements to upload for date or period specified")
return -1

# Save this sync so we don't re-download the same data again (if no range has been specified)
if not ARGS.fromdate:
withings.set_lastsync()

last_weight, last_date_time, syncdata = prepare_syncdata(height, groups)

fit_data = generate_fitdata(syncdata)
json_data = generate_jsondata(syncdata)

write_to_file_when_needed(fit_data, json_data)

if ARGS.no_upload:
logging.info("Skipping upload")
return 0

# Upload to Trainer Road
if ARGS.trainerroad_username and last_weight is not None:
logging.info("Trainerroad username set -- attempting to sync")
if ARGS.trainerroad_upload and last_weight is not None:
logging.info("attempting to sync Trainerroad")
logging.info(" Last weight %s", last_weight)
logging.info(" Measured %s", last_date_time)
if sync_trainerroad(last_weight):
if sync_trainerroad(withings, last_weight):
logging.info("TrainerRoad update done!")
else:
logging.info("No Trainerroad username or a new measurement " "- skipping sync")
else:
return -1

# Upload to Garmin Connect
if ARGS.garmin_username and fit_data is not None:
logging.debug("attempting to upload fit file...")
if sync_garmin(fit_data):
if ARGS.garmin_upload and fit_data is not None:
logging.debug("attempting to upload fit file to Garmin...")
if sync_garmin(withings, fit_data):
logging.info("Fit file uploaded to Garmin Connect")
else:
logging.info("No Garmin username - skipping sync")
else:
return -1

# Save this sync so we don't re-download the same data again (if no range has been specified)
if not ARGS.fromdate:
withings.set_lastsync()
return 0


Expand Down
Loading