From eb3838c9099c65668e2230c67de2893dd2f219c4 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Mon, 10 Oct 2022 23:25:28 +0300 Subject: [PATCH 01/12] Add files via upload Added support for multiple users --- withings_sync/sync.py | 125 ++++++++++--------------------------- withings_sync/withings2.py | 78 ++++++++++++++++------- 2 files changed, 90 insertions(+), 113 deletions(-) diff --git a/withings_sync/sync.py b/withings_sync/sync.py index cdd6805..2fb2b70 100644 --- a/withings_sync/sync.py +++ b/withings_sync/sync.py @@ -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(): @@ -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") @@ -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) @@ -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 %%, " @@ -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() @@ -342,10 +289,6 @@ 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) @@ -353,27 +296,27 @@ def sync(): 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 diff --git a/withings_sync/withings2.py b/withings_sync/withings2.py index d648e65..2ab12f4 100644 --- a/withings_sync/withings2.py +++ b/withings_sync/withings2.py @@ -54,20 +54,21 @@ class WithingsOAuth2: """This class takes care of the Withings OAuth2 authentication""" app_config = user_config = None + - def __init__(self): + def __init__(self, api_user_id): app_cfg = WithingsConfig(APP_CONFIG) self.app_config = app_cfg.config self.user_cfg = WithingsConfig(USER_CONFIG) self.user_config = self.user_cfg.config + self.api_user_id = api_user_id - if not self.user_config.get("access_token"): - if not self.user_config.get("authentification_code"): - self.user_config[ - "authentification_code" - ] = self.get_authenticationcode() - + if (not self.user_config.get(self.api_user_id)) or (not self.user_config[self.api_user_id].get("authentification_code")) : + self.user_config[self.api_user_id] ={} + self.user_config[self.api_user_id]["authentification_code"] = self.get_authenticationcode() + + if not self.user_config[self.api_user_id].get("access_token"): self.get_accesstoken() self.refresh_accesstoken() @@ -121,7 +122,7 @@ def get_accesstoken(self): "grant_type": "authorization_code", "client_id": self.app_config["client_id"], "client_secret": self.app_config["consumer_secret"], - "code": self.user_config["authentification_code"], + "code": self.user_config[self.api_user_id]["authentification_code"], "redirect_uri": self.app_config["callback_url"], } @@ -143,9 +144,9 @@ def get_accesstoken(self): " script again to obtain a new link." ) - self.user_config["access_token"] = body.get("access_token") - self.user_config["refresh_token"] = body.get("refresh_token") - self.user_config["userid"] = body.get("userid") + self.user_config[self.api_user_id]["access_token"] = body.get("access_token") + self.user_config[self.api_user_id]["refresh_token"] = body.get("refresh_token") + self.user_config[self.api_user_id]["userid"] = body.get("userid") def refresh_accesstoken(self): """refresh Withings access token""" @@ -156,7 +157,7 @@ def refresh_accesstoken(self): "grant_type": "refresh_token", "client_id": self.app_config["client_id"], "client_secret": self.app_config["consumer_secret"], - "refresh_token": self.user_config["refresh_token"], + "refresh_token": self.user_config[self.api_user_id]["refresh_token"], } req = requests.post(TOKEN_URL, params) @@ -177,26 +178,59 @@ def refresh_accesstoken(self): " script again to obtain a new link." ) - self.user_config["access_token"] = body.get("access_token") - self.user_config["refresh_token"] = body.get("refresh_token") - self.user_config["userid"] = body.get("userid") + self.user_config[self.api_user_id]["access_token"] = body.get("access_token") + self.user_config[self.api_user_id]["refresh_token"] = body.get("refresh_token") + self.user_config[self.api_user_id]["userid"] = body.get("userid") class WithingsAccount: """This class gets measurements from Withings""" - def __init__(self): - self.withings = WithingsOAuth2() + def __init__(self, api_user_id): + self.api_user_id = str(api_user_id) + self.withings = WithingsOAuth2(self.api_user_id) + + def get_garmin_username(self): + """get Garmin username""" + if not self.withings.user_config[self.api_user_id].get("garmin_username"): + garmin_username = input("Garmin username : ") + self.withings.user_config[self.api_user_id]["garmin_username"] = garmin_username + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["garmin_username"] + + def get_garmin_password(self): + """get Garmin password""" + if not self.withings.user_config[self.api_user_id].get("garmin_password"): + garmin_password = input("Garmin password : ") + self.withings.user_config[self.api_user_id]["garmin_password"] = garmin_password + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["garmin_password"] + + def get_trainerroad_username(self): + """get TrainerRoad username""" + if not self.withings.user_config[self.api_user_id].get("trainerroad_username"): + trainerroad_username = input("TrainerRoad username : ") + self.withings.user_config[self.api_user_id]["trainerroad_username"] = trainerroad_username + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["trainerroad_username"] + + def get_trainerroad_password(self): + """get TrainerRoad password""" + if not self.withings.user_config[self.api_user_id].get("trainerroad_password"): + trainerroad_password = input("TrainerRoad password : ") + self.withings.user_config[self.api_user_id]["trainerroad_password"] = trainerroad_password + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["trainerroad_password"] def get_lastsync(self): """get last sync timestamp""" - if not self.withings.user_config.get("last_sync"): + if not self.withings.user_config[self.api_user_id].get("last_sync"): return int(time.mktime(date.today().timetuple())) - return self.withings.user_config["last_sync"] + return self.withings.user_config[self.api_user_id]["last_sync"] def set_lastsync(self): """set last sync timestamp""" - self.withings.user_config["last_sync"] = int(time.time()) + self.withings.user_config[self.api_user_id]["last_sync"] = int(time.time()) log.info("Saving Last Sync") self.withings.update_config() @@ -205,7 +239,7 @@ def get_measurements(self, startdate, enddate): log.info("Get Measurements") params = { - "access_token": self.withings.user_config["access_token"], + "access_token": self.withings.user_config[self.api_user_id]["access_token"], # 'meastype': MEASTYPE_WEIGHT, "category": 1, "startdate": startdate, @@ -233,7 +267,7 @@ def get_height(self): log.debug("Get Height") params = { - "access_token": self.withings.user_config["access_token"], + "access_token": self.withings.user_config[self.api_user_id]["access_token"], "meastype": WithingsMeasure.TYPE_HEIGHT, "category": 1, } From bc32074069db963e177f2d36278cf77fb5af88c0 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Mon, 10 Oct 2022 23:34:56 +0300 Subject: [PATCH 02/12] Update README.md --- README.md | 44 +++++++------------------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 642d951..2d41228 100644 --- a/README.md +++ b/README.md @@ -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 + 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. @@ -50,34 +48,6 @@ optional arguments: --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` - -### 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: From ea25656a82c36849603c4cbed899915418ec8024 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:24:43 +0300 Subject: [PATCH 03/12] Add files via upload --- README.md | 87 +------- fit.py | 316 +++++++++++++++++++++++++++++ garmin.py | 215 ++++++++++++++++++++ sync.py | 362 +++++++++++++++++++++++++++++++++ trainerroad.py | 214 ++++++++++++++++++++ withings2.py | 528 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1636 insertions(+), 86 deletions(-) create mode 100644 fit.py create mode 100644 garmin.py create mode 100644 sync.py create mode 100644 trainerroad.py create mode 100644 withings2.py diff --git a/README.md b/README.md index 2d41228..8348045 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,12 @@ A tool for synchronisation of the Withings API to: * 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. -## Installation - -```bash -$ pip install withings-sync -``` ## Usage ``` 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] + [--todate DATE] [--to-fit] [--to-json] [--output BASENAME] [--verbose] A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road or to provide a json string. @@ -44,7 +39,6 @@ optional arguments: --to-json, -J Write output file in JSON format. --output BASENAME, -o BASENAME Write downloaded measurements to file. - --no-upload Won't upload to Garmin Connect or TrainerRoad. --verbose, -v Run verbosely ``` @@ -93,82 +87,3 @@ E.g. by running the script every 10 minutes. 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= --garmin-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 : -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 -``` - -### 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 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`. diff --git a/fit.py b/fit.py new file mode 100644 index 0000000..4c2e5f4 --- /dev/null +++ b/fit.py @@ -0,0 +1,316 @@ +from io import BytesIO +from struct import pack +from struct import unpack +from datetime import datetime +import time + + +def _calcCRC(crc, byte): + table = [0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, + 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400] + # compute checksum of lower four bits of byte + tmp = table[crc & 0xF] + crc = (crc >> 4) & 0x0FFF + crc = crc ^ tmp ^ table[byte & 0xF] + # now compute checksum of upper four bits of byte + tmp = table[crc & 0xF] + crc = (crc >> 4) & 0x0FFF + crc = crc ^ tmp ^ table[(byte >> 4) & 0xF] + return crc + + +class FitBaseType(object): + """BaseType Definition + + see FIT Protocol Document(Page.20)""" + enum = {'#': 0, 'endian': 0, 'field': 0x00, 'name': 'enum', 'invalid': 0xFF, 'size': 1} + sint8 = {'#': 1, 'endian': 0, 'field': 0x01, 'name': 'sint8', 'invalid': 0x7F, 'size': 1} + uint8 = {'#': 2, 'endian': 0, 'field': 0x02, 'name': 'uint8', 'invalid': 0xFF, 'size': 1} + sint16 = {'#': 3, 'endian': 1, 'field': 0x83, 'name': 'sint16', 'invalid': 0x7FFF, 'size': 2} + uint16 = {'#': 4, 'endian': 1, 'field': 0x84, 'name': 'uint16', 'invalid': 0xFFFF, 'size': 2} + sint32 = {'#': 5, 'endian': 1, 'field': 0x85, 'name': 'sint32', 'invalid': 0x7FFFFFFF, 'size': 4} + uint32 = {'#': 6, 'endian': 1, 'field': 0x86, 'name': 'uint32', 'invalid': 0xFFFFFFFF, 'size': 4} + string = {'#': 7, 'endian': 0, 'field': 0x07, 'name': 'string', 'invalid': 0x00, 'size': 1} + float32 = {'#': 8, 'endian': 1, 'field': 0x88, 'name': 'float32', 'invalid': 0xFFFFFFFF, 'size': 2} + float64 = {'#': 9, 'endian': 1, 'field': 0x89, 'name': 'float64', 'invalid': 0xFFFFFFFFFFFFFFFF, 'size': 4} + uint8z = {'#': 10, 'endian': 0, 'field': 0x0A, 'name': 'uint8z', 'invalid': 0x00, 'size': 1} + uint16z = {'#': 11, 'endian': 1, 'field': 0x8B, 'name': 'uint16z', 'invalid': 0x0000, 'size': 2} + uint32z = {'#': 12, 'endian': 1, 'field': 0x8C, 'name': 'uint32z', 'invalid': 0x00000000, 'size': 4} + byte = {'#': 13, 'endian': 0, 'field': 0x0D, 'name': 'byte', 'invalid': 0xFF, + 'size': 1} # array of byte, field is invalid if all bytes are invalid + + @staticmethod + def get_format(basetype): + formats = { + 0: 'B', 1: 'b', 2: 'B', 3: 'h', 4: 'H', 5: 'i', 6: 'I', 7: 's', 8: 'f', + 9: 'd', 10: 'B', 11: 'H', 12: 'I', 13: 'c', + } + return formats[basetype['#']] + + @staticmethod + def pack(basetype, value): + """function to avoid DeprecationWarning""" + if basetype['#'] in (1, 2, 3, 4, 5, 6, 10, 11, 12): + value = int(value) + fmt = FitBaseType.get_format(basetype) + return pack(fmt, value) + + +class Fit(object): + HEADER_SIZE = 12 + + # not sure if this is the mesg_num + GMSG_NUMS = { + 'file_id': 0, + 'device_info': 23, + 'weight_scale': 30, + 'file_creator': 49, + 'blood_pressure': 51, + } + + +class FitEncoder(Fit): + FILE_TYPE = 9 + LMSG_TYPE_FILE_INFO = 0 + LMSG_TYPE_FILE_CREATOR = 1 + LMSG_TYPE_DEVICE_INFO = 2 + + def __init__(self): + self.buf = BytesIO() + self.write_header() # create header first + self.device_info_defined = False + + def __str__(self): + orig_pos = self.buf.tell() + self.buf.seek(0) + lines = [] + while True: + b = self.buf.read(16) + if not b: + break + lines.append(' '.join(['%02x' % ord(c) for c in b])) + self.buf.seek(orig_pos) + return '\n'.join(lines) + + def write_header(self, header_size=Fit.HEADER_SIZE, + protocol_version=16, + profile_version=108, + data_size=0, + data_type=b'.FIT'): + self.buf.seek(0) + s = pack('BBHI4s', header_size, protocol_version, profile_version, data_size, data_type) + self.buf.write(s) + + def _build_content_block(self, content): + field_defs = [] + values = [] + for num, basetype, value, scale in content: + s = pack('BBB', num, basetype['size'], basetype['field']) + field_defs.append(s) + if value is None: + # invalid value + value = basetype['invalid'] + elif scale is not None: + value *= scale + values.append(FitBaseType.pack(basetype, value)) + return (b''.join(field_defs), b''.join(values)) + + def write_file_info(self, serial_number=None, time_created=None, manufacturer=None, product=None, number=None): + if time_created is None: + time_created = datetime.now() + + content = [ + (3, FitBaseType.uint32z, serial_number, None), + (4, FitBaseType.uint32, self.timestamp(time_created), None), + (1, FitBaseType.uint16, manufacturer, None), + (2, FitBaseType.uint16, product, None), + (5, FitBaseType.uint16, number, None), + (0, FitBaseType.enum, self.FILE_TYPE, None), # type + ] + fields, values = self._build_content_block(content) + + # create fixed content + msg_number = self.GMSG_NUMS['file_id'] + fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) + + self.buf.write(b''.join([ + # definition + self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_FILE_INFO), + fixed_content, + fields, + # record + self.record_header(lmsg_type=self.LMSG_TYPE_FILE_INFO), + values, + ])) + + def write_file_creator(self, software_version=None, hardware_version=None): + content = [ + (0, FitBaseType.uint16, software_version, None), + (1, FitBaseType.uint8, hardware_version, None), + ] + fields, values = self._build_content_block(content) + + msg_number = self.GMSG_NUMS['file_creator'] + fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) + self.buf.write(b''.join([ + # definition + self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_FILE_CREATOR), + fixed_content, + fields, + # record + self.record_header(lmsg_type=self.LMSG_TYPE_FILE_CREATOR), + values, + ])) + + def write_device_info(self, timestamp, serial_number=None, cum_operationg_time=None, manufacturer=None, + product=None, software_version=None, battery_voltage=None, device_index=None, + device_type=None, hardware_version=None, battery_status=None): + content = [ + (253, FitBaseType.uint32, self.timestamp(timestamp), 1), + (3, FitBaseType.uint32z, serial_number, 1), + (7, FitBaseType.uint32, cum_operationg_time, 1), + (8, FitBaseType.uint32, None, None), # unknown field(undocumented) + (2, FitBaseType.uint16, manufacturer, 1), + (4, FitBaseType.uint16, product, 1), + (5, FitBaseType.uint16, software_version, 100), + (10, FitBaseType.uint16, battery_voltage, 256), + (0, FitBaseType.uint8, device_index, 1), + (1, FitBaseType.uint8, device_type, 1), + (6, FitBaseType.uint8, hardware_version, 1), + (11, FitBaseType.uint8, battery_status, None), + ] + fields, values = self._build_content_block(content) + + if not self.device_info_defined: + header = self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_DEVICE_INFO) + msg_number = self.GMSG_NUMS['device_info'] + fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) + self.buf.write(header + fixed_content + fields) + self.device_info_defined = True + + header = self.record_header(lmsg_type=self.LMSG_TYPE_DEVICE_INFO) + self.buf.write(header + values) + + def record_header(self, definition=False, lmsg_type=0): + msg = 0 + if definition: + msg = 1 << 6 # 6th bit is a definition message + return pack('B', msg + lmsg_type) + + def crc(self): + orig_pos = self.buf.tell() + self.buf.seek(0) + + crc = 0 + while True: + b = self.buf.read(1) + if not b: + break + crc = _calcCRC(crc, unpack('b', b)[0]) + self.buf.seek(orig_pos) + return pack('H', crc) + + def finish(self): + """re-weite file-header, then append crc to end of file""" + data_size = self.get_size() - self.HEADER_SIZE + self.write_header(data_size=data_size) + crc = self.crc() + self.buf.seek(0, 2) + self.buf.write(crc) + + def get_size(self): + orig_pos = self.buf.tell() + self.buf.seek(0, 2) + size = self.buf.tell() + self.buf.seek(orig_pos) + return size + + def getvalue(self): + return self.buf.getvalue() + + def timestamp(self, t): + """the timestamp in fit protocol is seconds since + UTC 00:00 Dec 31 1989 (631065600)""" + if isinstance(t, datetime): + t = time.mktime(t.timetuple()) + return t - 631065600 + + +class FitEncoderBloodPressure(FitEncoder): + # Here might be dragons - no idea what lsmg stand for, found 14 somewhere in the deepest web + LMSG_TYPE_BLOOD_PRESSURE = 14 + + def __init__(self): + super().__init__() + self.blood_pressure_monitor_defined = False + + def write_blood_pressure(self, + timestamp, + diastolic_blood_pressure=None, + systolic_blood_pressure=None, + mean_arterial_pressure=None, + map_3_sample_mean=None, + map_morning_values=None, + map_evening_values=None, + heart_rate=None, ): + # BLOOD PRESSURE FILE MESSAGES + content = [ + (253, FitBaseType.uint32, self.timestamp(timestamp), 1), + (0, FitBaseType.uint16, systolic_blood_pressure, 1), + (1, FitBaseType.uint16, diastolic_blood_pressure, 1), + (2, FitBaseType.uint16, mean_arterial_pressure, 1), + (3, FitBaseType.uint16, map_3_sample_mean, 1), + (4, FitBaseType.uint16, map_morning_values, 1), + (5, FitBaseType.uint16, map_evening_values, 1), + (6, FitBaseType.uint8, heart_rate, 1), + ] + fields, values = self._build_content_block(content) + + if not self.blood_pressure_monitor_defined: + header = self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_BLOOD_PRESSURE) + msg_number = self.GMSG_NUMS['blood_pressure'] + fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) + self.buf.write(header + fixed_content + fields) + self.blood_pressure_monitor_defined = True + + header = self.record_header(lmsg_type=self.LMSG_TYPE_BLOOD_PRESSURE) + self.buf.write(header + values) + + +class FitEncoderWeight(FitEncoder): + LMSG_TYPE_WEIGHT_SCALE = 3 + + def __init__(self): + super().__init__() + self.weight_scale_defined = False + + def write_weight_scale(self, timestamp, weight, percent_fat=None, percent_hydration=None, + visceral_fat_mass=None, bone_mass=None, muscle_mass=None, basal_met=None, + active_met=None, physique_rating=None, metabolic_age=None, + visceral_fat_rating=None, bmi=None): + content = [ + (253, FitBaseType.uint32, self.timestamp(timestamp), 1), + (0, FitBaseType.uint16, weight, 100), + (1, FitBaseType.uint16, percent_fat, 100), + (2, FitBaseType.uint16, percent_hydration, 100), + (3, FitBaseType.uint16, visceral_fat_mass, 100), + (4, FitBaseType.uint16, bone_mass, 100), + (5, FitBaseType.uint16, muscle_mass, 100), + (7, FitBaseType.uint16, basal_met, 4), + (9, FitBaseType.uint16, active_met, 4), + (8, FitBaseType.uint8, physique_rating, 1), + (10, FitBaseType.uint8, metabolic_age, 1), + (11, FitBaseType.uint8, visceral_fat_rating, 1), + (13, FitBaseType.uint16, bmi, 10), + ] + fields, values = self._build_content_block(content) + + if not self.weight_scale_defined: + header = self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_WEIGHT_SCALE) + msg_number = self.GMSG_NUMS['weight_scale'] + fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) + self.buf.write(header + fixed_content + fields) + self.weight_scale_defined = True + + header = self.record_header(lmsg_type=self.LMSG_TYPE_WEIGHT_SCALE) + self.buf.write(header + values) diff --git a/garmin.py b/garmin.py new file mode 100644 index 0000000..d66df78 --- /dev/null +++ b/garmin.py @@ -0,0 +1,215 @@ +"""This module handles the Garmin connectivity.""" +import urllib.request +import urllib.error +import urllib.parse +import re +import json +import logging +import cloudscraper + + +log = logging.getLogger("garmin") + + +class LoginSucceeded(Exception): + """Used to raise on LoginSucceeded""" + + +class LoginFailed(Exception): + """Used to raise on LoginFailed""" + + +class APIException(Exception): + """Used to raise on APIException""" + + +class GarminConnect: + """Main GarminConnect class""" + + LOGIN_URL = "https://connect.garmin.com/signin" + UPLOAD_URL = "https://connect.garmin.com/modern/proxy/upload-service/upload/.fit" + + def create_opener(self, cookie): + """Garmin opener""" + this = self + + class _HTTPRedirectHandler(urllib.request.HTTPRedirectHandler): + def http_error_302( + self, req, fp, code, msg, headers + ): # pylint: disable=too-many-arguments + if req.get_full_url() == this.LOGIN_URL: + raise LoginSucceeded + + return urllib.request.HTTPRedirectHandler.http_error_302( + self, req, fp, code, msg, headers + ) + + return urllib.request.build_opener( + _HTTPRedirectHandler, urllib.request.HTTPCookieProcessor(cookie) + ) + + # From https://github.com/cpfair/tapiriik + @staticmethod + def get_session(email=None, password=None): + """tapiriik get_session code""" + session = cloudscraper.CloudScraper() + + data = { + "username": email, + "password": password, + "_eventId": "submit", + "embed": "true", + } + params = { + "service": "https://connect.garmin.com/modern", + "clientId": "GarminConnect", + "gauthHost": "https://sso.garmin.com/sso", + "consumeServiceTicket": "false", + } + + headers = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 " + + "(KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Referer": "https://jhartman.pl", + "origin": "https://sso.garmin.com", + } + + # I may never understand what motivates people to mangle a perfectly + # good protocol like HTTP in the ways they do... + preresp = session.get( + "https://sso.garmin.com/sso/signin", params=params, headers=headers + ) + if preresp.status_code != 200: + raise APIException( + f"SSO prestart error {preresp.status_code} {preresp.text}" + ) + + ssoresp = session.post( + "https://sso.garmin.com/sso/login", + params=params, + data=data, + allow_redirects=False, + headers=headers, + ) + + if ssoresp.status_code == 429: + raise APIException( + "SSO error 429: You are being rate limited: " + + "The owner of this website (sso.garmin.com) " + + "has banned you temporarily from accessing this website." + ) + + if ssoresp.status_code != 200 or "temporarily unavailable" in ssoresp.text: + raise APIException(f"SSO error {ssoresp.status_code} {ssoresp.text}") + + if ">sendEvent('FAIL')" in ssoresp.text: + raise APIException("Invalid login") + + if ">sendEvent('ACCOUNT_LOCKED')" in ssoresp.text: + raise APIException("Account Locked") + + if "renewPassword" in ssoresp.text: + raise APIException("Reset password") + + # self.print_cookies(cookies=session.cookies) + + # ...AND WE'RE NOT DONE YET! + + gcredeemresp = session.get( + "https://connect.garmin.com/modern", allow_redirects=False, headers=headers + ) + if gcredeemresp.status_code != 302: + raise APIException( + f"GC redeem-start error {gcredeemresp.status_code} {gcredeemresp.text}" + ) + + url_prefix = "https://connect.garmin.com" + + # There are 6 redirects that need to be followed to get the correct cookie + # ... :( + max_redirect_count = 7 + current_redirect_count = 1 + while True: + url = gcredeemresp.headers["location"] + + # Fix up relative redirects. + if url.startswith("/"): + url = url_prefix + url + url_prefix = "/".join(url.split("/")[:3]) + gcredeemresp = session.get(url, allow_redirects=False) + + if ( + current_redirect_count >= max_redirect_count + and gcredeemresp.status_code != 200 + ): + raise APIException( + f"GC redeem {current_redirect_count}/" + "{max_redirect_count} error " + "{gcredeemresp.status_code} " + "{gcredeemresp.text}" + ) + + if gcredeemresp.status_code in [200, 404]: + break + + current_redirect_count += 1 + if current_redirect_count > max_redirect_count: + break + + # GarminConnect.print_cookies(session.cookies) + session.headers.update(headers) + + return session + + @staticmethod + def get_json(page_html, key): + """Return json from text.""" + found = re.search(key + r" = (\{.*\});", page_html, re.M) + if found: + json_text = found.group(1).replace('\\"', '"') + return json.loads(json_text) + return None + + @staticmethod + def print_cookies(cookies): + """print cookies""" + log.debug("Cookies: ") + for key, value in list(cookies.items()): + log.debug(" %s = %s", key, value) + + @staticmethod + def login(username, password): + """login to Garmin""" + session = GarminConnect.get_session(email=username, password=password) + try: + dashboard = session.get("http://connect.garmin.com/modern") + userdata = GarminConnect.get_json(dashboard.text, "VIEWER_SOCIAL_PROFILE") + username = userdata["userName"] + + log.info("Garmin Connect User Name: %s", username) + + except Exception as exception: # pylint: disable=broad-except + log.error(exception) + log.error( + "Unable to retrieve Garmin username! Most likely: " + "incorrect Garmin login or password!" + ) + log.debug(dashboard.text) + + return session + + def upload_file(self, ffile, session): + """upload fit file to Garmin connect""" + files = {"data": ("withings.fit", ffile)} + res = session.post(self.UPLOAD_URL, files=files, headers={"nk": "NT"}) + try: + resp = res.json() + if "detailedImportResult" not in resp: + raise KeyError + except (ValueError, KeyError): + if res.status_code == 204: # HTTP result 204 - 'no content' + log.error("No data to upload, try to use --fromdate and --todate") + else: + log.error("Bad response during GC upload: %s", res.status_code) + + return res.status_code in [200, 201, 204] diff --git a/sync.py b/sync.py new file mode 100644 index 0000000..679ff9a --- /dev/null +++ b/sync.py @@ -0,0 +1,362 @@ +"""This module syncs measurement data from Withings to Garmin a/o TrainerRoad.""" +import argparse +import time +import sys +import os +import logging +import json + +from datetime import date, datetime + +from withings2 import WithingsAccount +from garmin import GarminConnect +from trainerroad import TrainerRoad +from fit import FitEncoderWeight, FitEncoderBloodPressure + +WITHINGS_USERID = 1 + +def date_parser(date_string): + return datetime.strptime(date_string, "%Y-%m-%d") + +def get_args(): + """get command-line arguments""" + parser = argparse.ArgumentParser( + description=( + "A tool for synchronisation of Withings " + "(ex. Nokia Health Body) to Garmin Connect" + " and Trainer Road or to provide a json string." + ) + ) + + parser.add_argument( + "--withings-userid", + "--wuid", + default=WITHINGS_USERID, + type=str, + metavar="WITHINGS_USERID", + help="API userid to use for Withings.", + ) + + parser.add_argument( + "--garmin-upload", + "--gu", + action="store_true", + help=("upload to Garmin Connect."), + ) + + parser.add_argument( + "--trainerroad-upload", + "--tu", + action="store_true", + help=("upload to TrainerRoad."), + ) + + parser.add_argument( + "--fromdate", + "-f", + type=date_parser, + metavar="DATE" + ) + + parser.add_argument( + "--todate", + "-t", + type=date_parser, + default=date.today(), + metavar="DATE" + ) + + parser.add_argument( + "--to-fit", + "-F", + action="store_true", + help=("Write output file in FIT format.") + ) + + parser.add_argument( + "--to-json", + "-J", + action="store_true", + help=("Write output file in JSON format."), + ) + + parser.add_argument( + "--output", + "-o", + type=str, + metavar="BASENAME", + help=("Write downloaded measurements to file."), + ) + + parser.add_argument( + "--verbose", + "-v", + action="store_true", + help="Run verbosely" + ) + + return parser.parse_args() + + +def sync_garmin(withings, fit_file): + """Sync generated fit file to Garmin Connect""" + garmin = GarminConnect() + session = garmin.login(withings.get_garmin_username(), withings.get_garmin_password()) + return garmin.upload_file(fit_file.getvalue(), session) + + +def sync_trainerroad(withings, last_weight): + """Sync measured weight to TrainerRoad""" + 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) + t_road.weight = round(last_weight, 1) + t_road.disconnect() + return t_road.weight + + +def generate_fitdata(syncdata): + """Generate fit data from measured data""" + logging.debug("Generating fit data...") + + have_weight = False + for record in syncdata: + if "weight" in record: + have_weight = True + break + next + + if not have_weight: + logging.info("No weight data to sync for FIT file") + return None + + fit = FitEncoderWeight() + fit.write_file_info() + fit.write_file_creator() + + for record in syncdata: + if "weight" not in record: + next + fit.write_device_info(timestamp=record["date_time"]) + fit.write_weight_scale( + timestamp=record["date_time"], + weight=record["weight"], + percent_fat=record["fat_ratio"], + percent_hydration=record["percent_hydration"], + bone_mass=record["bone_mass"], + muscle_mass=record["muscle_mass"], + bmi=record["bmi"], + ) + + fit.finish() + + logging.debug("Fit data generated...") + return fit + + +def generate_jsondata(syncdata): + """Generate fit data from measured data""" + logging.debug("Generating json data...") + + json_data = {} + for record in syncdata: + sdt = str(record["date_time"]) + json_data[sdt] = {} + for dataentry in record["raw_data"]: + for k,jd in dataentry.json_dict().items(): + json_data[sdt][k] = jd + if "bmi" in record: + json_data[sdt]["BMI"] = { "Value": record["bmi"], "Unit": "kg/m^2"} + if "percent_hydration" in record: + json_data[sdt]["Percent_Hydration"] = { "Value": record["percent_hydration"], "Unit": "%"} + logging.debug("Json data generated...") + return json_data + + +def prepare_syncdata(height, groups): + """Prepare measurement data to be sent""" + syncdata = [] + + last_date_time = None + last_weight = None + + syncDict = {} + + for group in groups: + # Get extra physical measurements + dt = group.get_datetime() + if dt not in syncDict: + syncDict[dt] = {} + groupdata = { + "date_time": group.get_datetime(), + "height": height, + "weight": group.get_weight(), + "fat_ratio": group.get_fat_ratio(), + "muscle_mass": group.get_muscle_mass(), + "hydration": group.get_hydration(), + "percent_hydration": None, + "bone_mass": group.get_bone_mass(), + "pulse_wave_velocity": group.get_pulse_wave_velocity(), + "heart_pulse": group.get_heart_pulse(), + "bmi": None, + "raw_data": group.get_raw_data() + } + + + # if groupdata["weight"] is None: + # logging.info( + # "This Withings metric contains no weight data. Not syncing..." + # ) + # logging.debug("Detected data: ") + # continue + if height and groupdata["weight"]: + groupdata["bmi"] = round( + groupdata["weight"] / pow(groupdata["height"], 2), 1 + ) + if groupdata["hydration"]: + groupdata["percent_hydration"] = round( + groupdata["hydration"] * 100.0 / groupdata["weight"], 2 + ) + + logging.debug("%s Detected data: ", dt) + #for dataentry in raw_data: + for dataentry in groupdata["raw_data"]: + logging.debug(dataentry) + + logging.info( + "Record: %s, height=%s m, " + "weight=%s kg, " + "fat_ratio=%s %%, " + "muscle_mass=%s kg, " + "percent_hydration=%s %%, " + "bone_mass=%s kg, " + "bmi=%s", + groupdata["date_time"], + groupdata["height"], + groupdata["weight"], + groupdata["fat_ratio"], + groupdata["muscle_mass"], + groupdata["percent_hydration"], + groupdata["bone_mass"], + groupdata["bmi"], + ) + + # join groups with same timestamp + for k,v in groupdata.items(): + syncDict[dt][k] = v + + for groupdata in syncDict.values(): + syncdata.append(groupdata) + logging.debug("Processed data: ") + for k,v in groupdata.items(): + logging.debug(k, v) + if last_date_time is None or groupdata["date_time"] > last_date_time: + last_date_time = groupdata["date_time"] + last_weight = groupdata["weight"] + logging.debug("last_dt: %s last_weight: %s", last_date_time, last_weight) + + if last_weight is None: + logging.error("Invalid or no weight data detected") + + return last_weight, last_date_time, syncdata + + +def write_to_file_when_needed(fit_data, json_data): + """Write measurements to file when requested""" + if ARGS.output is not None: + if ARGS.to_fit and fit_data is not None: + filename = ARGS.output + ".fit" + logging.info("Writing fitfile to %s.", filename) + try: + with open(filename, "wb") as fitfile: + fitfile.write(fit_data.getvalue()) + except OSError: + logging.error("Unable to open output fitfile!") + if ARGS.to_json: + filename = ARGS.output + ".json" + logging.info("Writing jsonfile to %s.", filename) + try: + with open(filename, "w", encoding="utf-8") as jsonfile: + json.dump(json_data, jsonfile, indent=4) + except OSError: + logging.error("Unable to open output jsonfile!") + + +def sync(): + """Sync measurements from Withings to Garmin a/o TrainerRoad""" + + # Withings API + withings = WithingsAccount(ARGS.withings_userid) + + if not ARGS.fromdate: + startdate = withings.get_lastsync() + else: + startdate = int(time.mktime(ARGS.fromdate.timetuple())) + + enddate = int(time.mktime(ARGS.todate.timetuple())) + 86399 + logging.info( + "Fetching measurements from %s to %s", + time.strftime("%Y-%m-%d %H:%M", time.gmtime(startdate)), + time.strftime("%Y-%m-%d %H:%M", time.gmtime(enddate)), + ) + + height = withings.get_height() + groups = withings.get_measurements(startdate=startdate, enddate=enddate) + + # Only upload if there are measurement returned + if groups is None or len(groups) == 0: + logging.error("No measurements to upload for date or period specified") + return -1 + + 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) + + # Upload to Trainer Road + 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(withings, last_weight): + logging.info("TrainerRoad update done!") + else: + return -1 + + # Upload to Garmin Connect + 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: + 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 + + +ARGS = get_args() + + +def main(): + """Main""" + logging.basicConfig( + level=logging.DEBUG if ARGS.verbose else logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + stream=sys.stdout, + ) + logging.debug("Script invoked with the following arguments: %s", ARGS) + + if sys.version_info < (3, 0): + print("Sorry, requires Python3, not Python2.") + sys.exit(1) + + sync() + +if __name__ == "__main__": + main() diff --git a/trainerroad.py b/trainerroad.py new file mode 100644 index 0000000..106567e --- /dev/null +++ b/trainerroad.py @@ -0,0 +1,214 @@ +import requests +import json +import logging + +from lxml import etree +from io import StringIO + +logger = logging.getLogger(__name__) + + +class TrainerRoad: + _ftp = 'Ftp' + _weight = 'Weight' + _units_metric = 'kmh' + _units_imperial = 'mph' + _input_data_names = (_ftp, _weight, 'Marketing', 'DateOfBirth') + _select_data_names = ('TimeZoneId', 'IsPrivate', + 'Units', 'IsVirtualPowerEnabled') + _numerical_verify = (_ftp, _weight) + _string_verify = _select_data_names + ('Marketing',) + _login_url = 'https://www.trainerroad.com/app/login' + _logout_url = 'https://www.trainerroad.com/app/logout' + _rider_url = 'https://www.trainerroad.com/app/profile/rider-information' + _download_tcx_url = 'http://www.trainerroad.com/cycling/rides/download' + _workouts_url = 'https://api.trainerroad.com/api/careerworkouts' + _rvt = '__RequestVerificationToken' + + def __init__(self, username, password): + self._username = username + self._password = password + self._session = None + + def connect(self): + self._session = requests.Session() + self._session.auth = (self._username, self._password) + + data = {'Username': self._username, + 'Password': self._password} + + r = self._session.post(self._login_url, data=data, + allow_redirects=False) + + if r.status_code not in [200, 302]: + # There was an error + raise RuntimeError("Error loging in to TrainerRoad (Code {})" + .format(r.status_code)) + + logger.info('Logged into TrainerRoad as "{}"'.format(self._username)) + + def disconnect(self): + r = self._session.get(self._logout_url, allow_redirects=False) + if r.status_code not in [200, 302]: + raise RuntimeError("Error loging out of TrainerRoad (Code {})" + .format(r.status_code)) + + self._session = None + logger.info('Logged out of TrainerRoad as "{}"'.format(self._username)) + + def __enter__(self): + self.connect() + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.disconnect() + + def _parse_value(self, tree, name): + rtn = tree.xpath('//form//input[@name="{}"]/@value'.format(name)) + if not rtn: + raise RuntimeError('Input {} not found in form'.format(name)) + return rtn[0] + + def _parse_name(self, tree, name): + rtn = tree.xpath('//form//select[@name="{}"]//option' + '[@selected="selected"]/@value'.format(name)) + if not rtn: + raise RuntimeError('Input {} not found in form'.format(name)) + + return rtn[0] + + def _get(self, url): + if self._session is None: + raise RuntimeError('Not Connected') + + r = self._session.get(url) + + if r.status_code != 200: + raise RuntimeError("Error getting info from TrainerRoad (Code {})" + .format(r.status_code)) + + return r + + def _post(self, url, data): + if self._session is None: + raise RuntimeError('Not Connected') + + r = self._session.post(url, data) + + if r.status_code != 200: + raise RuntimeError("Error posting info to TrainerRoad (Code {})" + .format(r.status_code)) + + return r + + def _read_profile(self): + r = self._get(self._rider_url) + + parser = etree.HTMLParser() + tree = etree.parse(StringIO(r.text), parser) + + token = self._parse_value(tree, self._rvt) + + input_data = {} + for key in self._input_data_names: + input_data[key] = self._parse_value(tree, key) + + select_data = {} + for key in self._select_data_names: + select_data[key] = self._parse_name(tree, key) + + return (dict(**input_data, **select_data), token) + + def _write_profile(self, new_values): + # Read values + data, token = self._read_profile() + + logger.info("Read profile values {}".format(data)) + logger.debug("Token = {}".format(token)) + + # Update values with new_values + for key, value in new_values.items(): + if key not in data: + raise ValueError("Key \"{}\" is not in profile form" + .format(key)) + if key == self._weight and data['Units'] == self._units_imperial: + value = round(value/0.45359237, 1) + logger.debug("Converting Weight to lbs {}".format(value)) + data[key] = str(value) + + logger.info("New profile values {}".format(data)) + + # Now post the form + token = {self._rvt: token} + self._post(self._rider_url, data=dict(**data, **token)) + + # Now re-read to check + _data, token = self._read_profile() + + logger.info("Read profile values (verification) {}".format(_data)) + + for key in self._numerical_verify: + logger.debug('Numerically verifying key "{}" "{}" with "{}"' + .format(key, data[key], _data[key])) + if float(data[key]) != float(_data[key]): + raise RuntimeError('Failed to verify numerical key {}' + .format(key)) + for key in self._string_verify: + logger.debug('String verifying key "{}" "{}" with "{}"' + .format(key, data[key], _data[key])) + if data[key] != _data[key]: + raise RuntimeError('Failed to verify string key {}'.format(key)) + + return + + @property + def ftp(self): + values, token = self._read_profile() + return values[self._ftp] + + @ftp.setter + def ftp(self, value): + self._write_profile({self._ftp: value}) + + @property + def weight(self): + values, token = self._read_profile() + return values[self._weight] + + @weight.setter + def weight(self, value): + self._write_profile({self._weight: value}) + + def download_tcx(self, id): + res = self._session.get('{}/{}'.format(self._download_tcx_url, str(id))) + if res.status_code != 200: + raise RuntimeError("Unable to download (code = {})" + .format(res.status_code)) + + return res.text + + def get_workouts(self): + res = self._session.get(self._workouts_url) + if res.status_code != 200: + raise RuntimeError("Unable to download (code = {})" + .format(res.status_code)) + + data = json.loads(res.text) + logger.debug(json.dumps(data, indent=4, sort_keys=True)) + + logger.info('Recieved info on {} workouts'.format(len(data))) + + return data + + def get_workout(self, guid): + res = self._session.get(self._workout_url + + '?guid={}'.format(str(guid))) + + if res.status_code != 200: + raise RuntimeError('Unable to get workout "{}" (Code = {})' + .format(guid, res.status_code)) + + data = json.loads(res.text) + logger.debug(json.dumps(data, indent=4, sort_keys=True)) + + return data diff --git a/withings2.py b/withings2.py new file mode 100644 index 0000000..7d5152b --- /dev/null +++ b/withings2.py @@ -0,0 +1,528 @@ +"""This module takes care of the communication with Withings.""" +from datetime import date, datetime +import logging +import json +import os +import time +import pkg_resources +import requests + +log = logging.getLogger("withings") + +HOME = os.environ.get("HOME", ".") +AUTHORIZE_URL = "https://account.withings.com/oauth2_user/authorize2" +TOKEN_URL = "https://wbsapi.withings.net/v2/oauth2" +GETMEAS_URL = "https://wbsapi.withings.net/measure?action=getmeas" + +APP_CONFIG = os.environ.get( + "WITHINGS_APP", + pkg_resources.resource_filename(__name__, "config/withings_app.json"), +) +USER_CONFIG = os.environ.get("WITHINGS_USER", HOME + "/.withings_user.json") + + +class WithingsException(Exception): + """Pass WithingsExceptions""" + + +class WithingsConfig: + """This class takes care of the Withings config file""" + + config = {} + config_file = "" + + def __init__(self, config_file): + self.config_file = config_file + self.read() + + def read(self): + """reads config file""" + try: + with open(self.config_file, encoding="utf8") as configfile: + self.config = json.load(configfile) + except (ValueError, FileNotFoundError): + log.error("Can't read config file %s", self.config_file) + self.config = {} + + def write(self): + """writes config file""" + with open(self.config_file, "w", encoding="utf8") as configfile: + json.dump(self.config, configfile, indent=4, sort_keys=True) + + +class WithingsOAuth2: + """This class takes care of the Withings OAuth2 authentication""" + + app_config = user_config = None + + + def __init__(self, api_user_id): + app_cfg = WithingsConfig(APP_CONFIG) + self.app_config = app_cfg.config + + self.user_cfg = WithingsConfig(USER_CONFIG) + self.user_config = self.user_cfg.config + self.api_user_id = api_user_id + + if (not self.user_config.get(self.api_user_id)) or (not self.user_config[self.api_user_id].get("authentification_code")) : + self.user_config[self.api_user_id] ={} + self.user_config[self.api_user_id]["authentification_code"] = self.get_authenticationcode() + + if not self.user_config[self.api_user_id].get("access_token"): + self.get_accesstoken() + + self.refresh_accesstoken() + + self.user_cfg.write() + + def update_config(self): + """updates config file""" + self.user_cfg.write() + + def get_authenticationcode(self): + """get Withings authentication code""" + params = { + "response_type": "code", + "client_id": self.app_config["client_id"], + "state": "OK", + "scope": "user.metrics", + "redirect_uri": self.app_config["callback_url"], + } + + log.warning( + "User interaction needed to get Authentification " "Code from Withings!" + ) + log.warning("") + log.warning( + "Open the following URL in your web browser and copy back " + "the token. You will have *30 seconds* before the " + "token expires. HURRY UP!" + ) + log.warning("(This is one-time activity)") + log.warning("") + + url = AUTHORIZE_URL + "?" + + for key, value in params.items(): + url = url + key + "=" + value + "&" + + log.info(url) + log.info("") + + authentification_code = input("Token : ") + + return authentification_code + + def get_accesstoken(self): + """get Withings access token""" + log.info("Get Access Token") + + params = { + "action": "requesttoken", + "grant_type": "authorization_code", + "client_id": self.app_config["client_id"], + "client_secret": self.app_config["consumer_secret"], + "code": self.user_config[self.api_user_id]["authentification_code"], + "redirect_uri": self.app_config["callback_url"], + } + + req = requests.post(TOKEN_URL, params) + resp = req.json() + + status = resp.get("status") + body = resp.get("body") + + if status != 0: + log.error("Received error code: %d", status) + log.error( + "Check here for an interpretation of this error: " + "http://developer.withings.com/api-reference#section/Response-status" + ) + log.error("") + log.error( + "If it's regarding an invalid code, try to start the" + " script again to obtain a new link." + ) + + self.user_config[self.api_user_id]["access_token"] = body.get("access_token") + self.user_config[self.api_user_id]["refresh_token"] = body.get("refresh_token") + self.user_config[self.api_user_id]["userid"] = body.get("userid") + + def refresh_accesstoken(self): + """refresh Withings access token""" + log.info("Refresh Access Token") + + params = { + "action": "requesttoken", + "grant_type": "refresh_token", + "client_id": self.app_config["client_id"], + "client_secret": self.app_config["consumer_secret"], + "refresh_token": self.user_config[self.api_user_id]["refresh_token"], + } + + req = requests.post(TOKEN_URL, params) + resp = req.json() + + status = resp.get("status") + body = resp.get("body") + + if status != 0: + log.error("Received error code: %d", status) + log.error( + "Check here for an interpretation of this error: " + "http://developer.withings.com/api-reference#section/Response-status" + ) + log.error("") + log.error( + "If it's regarding an invalid code, try to start the" + " script again to obtain a new link." + ) + + self.user_config[self.api_user_id]["access_token"] = body.get("access_token") + self.user_config[self.api_user_id]["refresh_token"] = body.get("refresh_token") + self.user_config[self.api_user_id]["userid"] = body.get("userid") + + +class WithingsAccount: + """This class gets measurements from Withings""" + + def __init__(self, api_user_id): + self.api_user_id = str(api_user_id) + self.withings = WithingsOAuth2(self.api_user_id) + + def get_garmin_username(self): + """get Garmin username""" + if not self.withings.user_config[self.api_user_id].get("garmin_username"): + garmin_username = input("Garmin username : ") + self.withings.user_config[self.api_user_id]["garmin_username"] = garmin_username + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["garmin_username"] + + def get_garmin_password(self): + """get Garmin password""" + if not self.withings.user_config[self.api_user_id].get("garmin_password"): + garmin_password = input("Garmin password : ") + self.withings.user_config[self.api_user_id]["garmin_password"] = garmin_password + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["garmin_password"] + + def get_trainerroad_username(self): + """get TrainerRoad username""" + if not self.withings.user_config[self.api_user_id].get("trainerroad_username"): + trainerroad_username = input("TrainerRoad username : ") + self.withings.user_config[self.api_user_id]["trainerroad_username"] = trainerroad_username + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["trainerroad_username"] + + def get_trainerroad_password(self): + """get TrainerRoad password""" + if not self.withings.user_config[self.api_user_id].get("trainerroad_password"): + trainerroad_password = input("TrainerRoad password : ") + self.withings.user_config[self.api_user_id]["trainerroad_password"] = trainerroad_password + self.withings.update_config() + return self.withings.user_config[self.api_user_id]["trainerroad_password"] + + def get_lastsync(self): + """get last sync timestamp""" + if not self.withings.user_config[self.api_user_id].get("last_sync"): + return int(time.mktime(date.today().timetuple())) + return self.withings.user_config[self.api_user_id]["last_sync"] + + def set_lastsync(self): + """set last sync timestamp""" + self.withings.user_config[self.api_user_id]["last_sync"] = int(time.time()) + log.info("Saving Last Sync") + self.withings.update_config() + + def get_measurements(self, startdate, enddate): + """get Withings measurements""" + log.info("Get Measurements") + + params = { + "access_token": self.withings.user_config[self.api_user_id]["access_token"], + # 'meastype': MEASTYPE_WEIGHT, + "category": 1, + "startdate": startdate, + "enddate": enddate, + } + + req = requests.post(GETMEAS_URL, params) + + measurements = req.json() + + if measurements.get("status") == 0: + log.debug("Measurements received") + return [ + WithingsMeasureGroup(g) + for g in measurements.get("body").get("measuregrps") + ] + return None + + def get_height(self): + """get height from Withings""" + height = None + height_timestamp = None + height_group = None + + log.debug("Get Height") + + params = { + "access_token": self.withings.user_config[self.api_user_id]["access_token"], + "meastype": WithingsMeasure.TYPE_HEIGHT, + "category": 1, + } + + req = requests.post(GETMEAS_URL, params) + + measurements = req.json() + + if measurements.get("status") == 0: + log.debug("Height received") + + # there could be multiple height records. use the latest one + for record in measurements.get("body").get("measuregrps"): + height_group = WithingsMeasureGroup(record) + if height is not None: + if height_timestamp is not None: + if height_group.get_datetime() > height_timestamp: + height = height_group.get_height() + else: + height = height_group.get_height() + height_timestamp = height_group.get_datetime() + + return height + + +class WithingsMeasureGroup: + """This class takes care of the group measurement functions""" + + def __init__(self, measuregrp): + self._raw_data = measuregrp + self.grpid = measuregrp.get("grpid") + self.attrib = measuregrp.get("attrib") + self.date = measuregrp.get("date") + self.category = measuregrp.get("category") + self.measures = [WithingsMeasure(m) for m in measuregrp["measures"]] + + def __iter__(self): + for measure in self.measures: + yield measure + + def __len__(self): + return len(self.measures) + + def get_datetime(self): + """convenient function to get date & time""" + return datetime.fromtimestamp(self.date) + + def get_raw_data(self): + """convenient function to get raw data""" + return self.measures + + def get_weight(self): + """convenient function to get weight""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_WEIGHT: + return round(measure.get_value(), 2) + return None + + def get_height(self): + """convenient function to get height""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_HEIGHT: + return round(measure.get_value(), 2) + return None + + def get_fat_free_mass(self): + """convenient function to get fat free mass""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_FAT_FREE_MASS: + return round(measure.get_value(), 2) + return None + + def get_fat_ratio(self): + """convenient function to get fat ratio""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_FAT_RATIO: + return round(measure.get_value(), 2) + return None + + def get_fat_mass_weight(self): + """convenient function to get fat mass weight""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_FAT_MASS_WEIGHT: + return round(measure.get_value(), 2) + return None + + def get_diastolic_blood_pressure(self): + """convenient function to get diastolic blood pressure""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_DIASTOLIC_BLOOD_PRESSURE: + return round(measure.get_value(), 2) + return None + + def get_systolic_blood_pressure(self): + """convenient function to get systolic blood pressure""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_SYSTOLIC_BLOOD_PRESSURE: + return round(measure.get_value(), 2) + return None + + def get_heart_pulse(self): + """convenient function to get heart pulse""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_HEART_PULSE: + return round(measure.get_value(), 2) + return None + + def get_temperature(self): + """convenient function to get temperature""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_TEMPERATURE: + return round(measure.get_value(), 2) + return None + + def get_sp02(self): + """convenient function to get sp02""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_SP02: + return round(measure.get_value(), 2) + return None + + def get_body_temperature(self): + """convenient function to get body temperature""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_BODY_TEMPERATURE: + return round(measure.get_value(), 2) + return None + + def get_skin_temperature(self): + """convenient function to get skin temperature""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_SKIN_TEMPERATURE: + return round(measure.get_value(), 2) + return None + + def get_muscle_mass(self): + """convenient function to get muscle mass""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_MUSCLE_MASS: + return round(measure.get_value(), 2) + return None + + def get_hydration(self): + """convenient function to get hydration""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_HYDRATION: + return round(measure.get_value(), 2) + return None + + def get_bone_mass(self): + """convenient function to get bone mass""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_BONE_MASS: + return round(measure.get_value(), 2) + return None + + def get_pulse_wave_velocity(self): + """convenient function to get pulse wave velocity""" + for measure in self.measures: + if measure.type == WithingsMeasure.TYPE_PULSE_WAVE_VELOCITY: + return round(measure.get_value(), 2) + return None + + +class WithingsMeasure: + """This class takes care of the individual measurements""" + + TYPE_WEIGHT = 1 + TYPE_HEIGHT = 4 + TYPE_FAT_FREE_MASS = 5 + TYPE_FAT_RATIO = 6 + TYPE_FAT_MASS_WEIGHT = 8 + TYPE_DIASTOLIC_BLOOD_PRESSURE = 9 + TYPE_SYSTOLIC_BLOOD_PRESSURE = 10 + TYPE_HEART_PULSE = 11 + TYPE_TEMPERATURE = 12 + TYPE_SP02 = 54 + TYPE_BODY_TEMPERATURE = 71 + TYPE_SKIN_TEMPERATURE = 73 + TYPE_MUSCLE_MASS = 76 + TYPE_HYDRATION = 77 + TYPE_BONE_MASS = 88 + TYPE_PULSE_WAVE_VELOCITY = 91 + TYPE_VO2MAX = 123 + TYPE_QRS_INTERVAL = 135 + TYPE_PR_INTERVAL = 136 + TYPE_QT_INTERVAL = 137 + TYPE_CORRECTED_QT_INTERVAL = 138 + TYPE_ATRIAL_FIBRILLATION_PPG = 139 + TYPE_FAT_MASS_SEGMENTS = 174 + TYPE_EXTRACELLULAR_WATER = 168 + TYPE_INTRACELLULAR_WATER = 169 + TYPE_VISCERAL_FAT = 170 + TYPE_MUSCLE_MASS_SEGMENTS = 175 + TYPE_VASCULAR_AGE = 155 + TYPE_ATRIAL_FIBRILLATION = 130 + TYPE_NERVE_HEALTH_LEFT_FOOT = 158 + TYPE_NERVE_HEALTH_RIGHT_FOOT = 159 + TYPE_NERVE_HEALTH_FEET = 167 + TYPE_ELECTRODERMAL_ACTIVITY_FEET = 196 + TYPE_ELECTRODERMAL_ACTIVITY_LEFT_FOOT = 197 + TYPE_ELECTRODERMAL_ACTIVITY_RIGHT_FOOT = 198 + + withings_table = { + TYPE_WEIGHT: ["Weight", "kg"], + TYPE_HEIGHT: ["Height", "meter"], + TYPE_FAT_FREE_MASS: ["Fat Free Mass", "kg"], + TYPE_FAT_RATIO: ["Fat Ratio", "%"], + TYPE_FAT_MASS_WEIGHT: ["Fat Mass Weight", "kg"], + TYPE_DIASTOLIC_BLOOD_PRESSURE: ["Diastolic Blood Pressure", "mmHg"], + TYPE_SYSTOLIC_BLOOD_PRESSURE: ["Systolic Blood Pressure", "mmHg"], + TYPE_HEART_PULSE: ["Heart Pulse", "bpm"], + TYPE_TEMPERATURE: ["Temperature", "celsius"], + TYPE_SP02: ["SP02", "%"], + TYPE_BODY_TEMPERATURE: ["Body Temperature", "celsius"], + TYPE_SKIN_TEMPERATURE: ["Skin Temperature", "celsius"], + TYPE_MUSCLE_MASS: ["Muscle Mass", "kg"], + TYPE_HYDRATION: ["Hydration", "kg"], + TYPE_BONE_MASS: ["Bone Mass", "kg"], + TYPE_PULSE_WAVE_VELOCITY: ["Pulse Wave Velocity", "m/s"], + TYPE_VO2MAX: ["VO2 max", "ml/min/kg"], + TYPE_QRS_INTERVAL: ["QRS interval duration based on ECG signal", "ms"], + TYPE_PR_INTERVAL: ["PR interval duration based on ECG signal", "ms"], + TYPE_QT_INTERVAL: ["QT interval duration based on ECG signal", "ms"], + TYPE_CORRECTED_QT_INTERVAL: ["Corrected QT interval duration based on ECG signal", "ms"], + TYPE_ATRIAL_FIBRILLATION_PPG: ["Atrial fibrillation result from PPG", "ms"], + TYPE_FAT_MASS_SEGMENTS: ["Fat Mass for segments in mass unit", "kg"], + TYPE_EXTRACELLULAR_WATER: ["Extracellular Water", "kg"], + TYPE_INTRACELLULAR_WATER: ["Intracellular Water", "kg"], + TYPE_VISCERAL_FAT: ["Extracellular Water", "kg"], + TYPE_MUSCLE_MASS_SEGMENTS: ["Muscle Mass for segments in mass unit", "kg"], + TYPE_VASCULAR_AGE: ["Vascular age", "years"], + TYPE_ATRIAL_FIBRILLATION: ["Atrial fibrillation result", "ms"], + TYPE_NERVE_HEALTH_LEFT_FOOT: ["Nerve Health Score left foot", ""], + TYPE_NERVE_HEALTH_RIGHT_FOOT: ["Nerve Health Score right foot", ""], + TYPE_NERVE_HEALTH_FEET: ["Nerve Health Score feet", ""], + TYPE_ELECTRODERMAL_ACTIVITY_FEET: ["Electrodermal activity feet", ""], + TYPE_ELECTRODERMAL_ACTIVITY_LEFT_FOOT: ["Electrodermal activity left foot", ""], + TYPE_ELECTRODERMAL_ACTIVITY_RIGHT_FOOT: ["Electrodermal activity right foot", ""], + } + + def __init__(self, measure): + self._raw_data = measure + self.value = measure.get("value") + self.type = measure.get("type") + self.unit = measure.get("unit") + self.type_s = self.withings_table.get(self.type, ["unknown", ""])[0] + self.unit_s = self.withings_table.get(self.type, ["unknown", ""])[1] + + def __str__(self): + return f"{self.type_s}: {self.get_value()} {self.unit_s}" + + def json_dict(self): + return { f"{self.type_s.replace(' ','_')}": { "Value": round(self.get_value(), 2), "Unit": f'{self.unit_s}'}} + + def get_value(self): + """get value""" + return self.value * pow(10, self.unit) From 71da33acfce81308589cd349fb1853f2140f45a7 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:27:08 +0300 Subject: [PATCH 04/12] Delete Dockerfile --- Dockerfile | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 7017e13..0000000 --- a/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM python:3.10-alpine - -# Install python-lxml -RUN apk add --no-cache --virtual .build-deps \ - gcc musl-dev \ - libxslt-dev libxml2-dev &&\ - pip install lxml && \ - apk del .build-deps && \ - apk add --no-cache libxslt libxml2 - -RUN mkdir -p /src -COPY . /src - -RUN cd /src && \ - python3 ./setup.py install - -ENTRYPOINT ["withings-sync"] From 3222bf58a2a00bdcbc71bf1921c085c9c58a1628 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:27:40 +0300 Subject: [PATCH 05/12] Delete .gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6e69b7c..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.pyc -*.egg-info/ -dist/ \ No newline at end of file From c1f3fccd30e79c70aba6eb309faf4481e57a5734 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:28:37 +0300 Subject: [PATCH 06/12] Delete setup.py --- setup.py | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index d6289c9..0000000 --- a/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -from setuptools import setup - - -# Utility function to read the README file. -# Used for the long_description. It's nice, because now 1) we have a top level -# README file and 2) it's easier to type in the README file than to put a raw -# string in below ... -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() - - -setup( - name='withings-sync', - version='3.4.2', - author='Masayuki Hamasaki, Steffen Vogel', - author_email='post@steffenvogel.de', - description='A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road.', - license='MIT', - keywords='garmin withings sync api scale smarthome', - url='http://packages.python.org/an_example_pypi_project', - packages=['withings_sync'], - long_description=read('README.md'), - long_description_content_type='text/markdown', - classifiers=[ - 'Topic :: Utilities', - 'License :: OSI Approved :: MIT License', - ], - install_requires=[ - 'lxml', - 'requests', - # request has a fix dependency on charset-normalizer charset_normalizer~=2.0.0 - # (see https://requests.readthedocs.io/en/latest/user/advanced/#encodings) - # We pin it here to avoid conflicts with httpx - 'charset-normalizer~=2.0.0', - 'httpx', - 'httpx[http2]' - ], - entry_points={ - 'console_scripts': [ - 'withings-sync=withings_sync.sync:main' - ], - }, - zip_safe=False, - include_package_data=True -) From 12a0d445fc48405c8768eb7ea5865c719e2a1987 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:30:15 +0300 Subject: [PATCH 07/12] Delete withings_sync directory --- withings_sync/__init__.py | 0 withings_sync/config/withings_app.json | 5 - withings_sync/fit.py | 270 -------------- withings_sync/garmin.py | 204 ---------- withings_sync/sync.py | 339 ----------------- withings_sync/trainerroad.py | 214 ----------- withings_sync/withings2.py | 490 ------------------------- 7 files changed, 1522 deletions(-) delete mode 100644 withings_sync/__init__.py delete mode 100644 withings_sync/config/withings_app.json delete mode 100644 withings_sync/fit.py delete mode 100644 withings_sync/garmin.py delete mode 100644 withings_sync/sync.py delete mode 100644 withings_sync/trainerroad.py delete mode 100644 withings_sync/withings2.py diff --git a/withings_sync/__init__.py b/withings_sync/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/withings_sync/config/withings_app.json b/withings_sync/config/withings_app.json deleted file mode 100644 index 95295d7..0000000 --- a/withings_sync/config/withings_app.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "callback_url": "https://jaroslawhartman.github.io/withings-sync/contrib/withings.html", - "client_id": "183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626", - "consumer_secret": "a75d655c985d9e6391df1514c16719ef7bd69fa7c5d3fd0eac2e2b0ed48f1765" -} diff --git a/withings_sync/fit.py b/withings_sync/fit.py deleted file mode 100644 index 7165ec6..0000000 --- a/withings_sync/fit.py +++ /dev/null @@ -1,270 +0,0 @@ -from io import BytesIO -from struct import pack -from struct import unpack -from datetime import datetime -import time - - -def _calcCRC(crc, byte): - table = [0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, - 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400] - # compute checksum of lower four bits of byte - tmp = table[crc & 0xF] - crc = (crc >> 4) & 0x0FFF - crc = crc ^ tmp ^ table[byte & 0xF] - # now compute checksum of upper four bits of byte - tmp = table[crc & 0xF] - crc = (crc >> 4) & 0x0FFF - crc = crc ^ tmp ^ table[(byte >> 4) & 0xF] - return crc - - -class FitBaseType(object): - """BaseType Definition - - see FIT Protocol Document(Page.20)""" - enum = {'#': 0, 'endian': 0, 'field': 0x00, 'name': 'enum', 'invalid': 0xFF, 'size': 1} - sint8 = {'#': 1, 'endian': 0, 'field': 0x01, 'name': 'sint8', 'invalid': 0x7F, 'size': 1} - uint8 = {'#': 2, 'endian': 0, 'field': 0x02, 'name': 'uint8', 'invalid': 0xFF, 'size': 1} - sint16 = {'#': 3, 'endian': 1, 'field': 0x83, 'name': 'sint16', 'invalid': 0x7FFF, 'size': 2} - uint16 = {'#': 4, 'endian': 1, 'field': 0x84, 'name': 'uint16', 'invalid': 0xFFFF, 'size': 2} - sint32 = {'#': 5, 'endian': 1, 'field': 0x85, 'name': 'sint32', 'invalid': 0x7FFFFFFF, 'size': 4} - uint32 = {'#': 6, 'endian': 1, 'field': 0x86, 'name': 'uint32', 'invalid': 0xFFFFFFFF, 'size': 4} - string = {'#': 7, 'endian': 0, 'field': 0x07, 'name': 'string', 'invalid': 0x00, 'size': 1} - float32 = {'#': 8, 'endian': 1, 'field': 0x88, 'name': 'float32', 'invalid': 0xFFFFFFFF, 'size': 2} - float64 = {'#': 9, 'endian': 1, 'field': 0x89, 'name': 'float64', 'invalid': 0xFFFFFFFFFFFFFFFF, 'size': 4} - uint8z = {'#': 10, 'endian': 0, 'field': 0x0A, 'name': 'uint8z', 'invalid': 0x00, 'size': 1} - uint16z = {'#': 11, 'endian': 1, 'field': 0x8B, 'name': 'uint16z', 'invalid': 0x0000, 'size': 2} - uint32z = {'#': 12, 'endian': 1, 'field': 0x8C, 'name': 'uint32z', 'invalid': 0x00000000, 'size': 4} - byte = {'#': 13, 'endian': 0, 'field': 0x0D, 'name': 'byte', 'invalid': 0xFF, 'size': 1} # array of byte, field is invalid if all bytes are invalid - - @staticmethod - def get_format(basetype): - formats = { - 0: 'B', 1: 'b', 2: 'B', 3: 'h', 4: 'H', 5: 'i', 6: 'I', 7: 's', 8: 'f', - 9: 'd', 10: 'B', 11: 'H', 12: 'I', 13: 'c', - } - return formats[basetype['#']] - - @staticmethod - def pack(basetype, value): - """function to avoid DeprecationWarning""" - if basetype['#'] in (1,2,3,4,5,6,10,11,12): - value = int(value) - fmt = FitBaseType.get_format(basetype) - return pack(fmt, value) - - -class Fit(object): - HEADER_SIZE = 12 - - GMSG_NUMS = { - 'file_id': 0, - 'device_info': 23, - 'weight_scale': 30, - 'file_creator': 49, - } - - -class FitEncoder(Fit): - def timestamp(self, t): - """the timestamp in fit protocol is seconds since - UTC 00:00 Dec 31 1989 (631065600)""" - if isinstance(t, datetime): - t = time.mktime(t.timetuple()) - return t - 631065600 - - -class FitEncoder_Weight(FitEncoder): - FILE_TYPE = 9 - LMSG_TYPE_FILE_INFO = 0 - LMSG_TYPE_FILE_CREATOR = 1 - LMSG_TYPE_DEVICE_INFO = 2 - LMSG_TYPE_WEIGHT_SCALE = 3 - - def __init__(self): - self.buf = BytesIO() - self.write_header() # create header first - self.device_info_defined = False - self.weight_scale_defined = False - - def __str__(self): - orig_pos = self.buf.tell() - self.buf.seek(0) - lines = [] - while True: - b = self.buf.read(16) - if not b: - break - lines.append(' '.join(['%02x' % ord(c) for c in b])) - self.buf.seek(orig_pos) - return '\n'.join(lines) - - def write_header(self, header_size=Fit.HEADER_SIZE, - protocol_version=16, - profile_version=108, - data_size=0, - data_type=b'.FIT'): - self.buf.seek(0) - s = pack('BBHI4s', header_size, protocol_version, profile_version, data_size, data_type) - self.buf.write(s) - - def _build_content_block(self, content): - field_defs = [] - values = [] - for num, basetype, value, scale in content: - s = pack('BBB', num, basetype['size'], basetype['field']) - field_defs.append(s) - if value is None: - # invalid value - value = basetype['invalid'] - elif scale is not None: - value *= scale - values.append(FitBaseType.pack(basetype, value)) - return (b''.join(field_defs), b''.join(values)) - - def write_file_info(self, serial_number=None, time_created=None, manufacturer=None, product=None, number=None): - if time_created is None: - time_created = datetime.now() - - content = [ - (3, FitBaseType.uint32z, serial_number, None), - (4, FitBaseType.uint32, self.timestamp(time_created), None), - (1, FitBaseType.uint16, manufacturer, None), - (2, FitBaseType.uint16, product, None), - (5, FitBaseType.uint16, number, None), - (0, FitBaseType.enum, self.FILE_TYPE, None), # type - ] - fields, values = self._build_content_block(content) - - # create fixed content - msg_number = self.GMSG_NUMS['file_id'] - fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) - - self.buf.write(b''.join([ - # definition - self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_FILE_INFO), - fixed_content, - fields, - #record - self.record_header(lmsg_type=self.LMSG_TYPE_FILE_INFO), - values, - ])) - - - def write_file_creator(self, software_version=None, hardware_version=None): - content = [ - (0, FitBaseType.uint16, software_version, None), - (1, FitBaseType.uint8, hardware_version, None), - ] - fields, values = self._build_content_block(content) - - msg_number = self.GMSG_NUMS['file_creator'] - fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) - self.buf.write(b''.join([ - # definition - self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_FILE_CREATOR), - fixed_content, - fields, - #record - self.record_header(lmsg_type=self.LMSG_TYPE_FILE_CREATOR), - values, - ])) - - def write_device_info(self, timestamp, serial_number=None, cum_operationg_time=None, manufacturer=None, - product=None, software_version=None, battery_voltage=None, device_index=None, - device_type=None, hardware_version=None, battery_status=None): - content = [ - (253, FitBaseType.uint32, self.timestamp(timestamp), 1), - (3, FitBaseType.uint32z, serial_number, 1), - (7, FitBaseType.uint32, cum_operationg_time, 1), - (8, FitBaseType.uint32, None, None), # unknown field(undocumented) - (2, FitBaseType.uint16, manufacturer, 1), - (4, FitBaseType.uint16, product, 1), - (5, FitBaseType.uint16, software_version, 100), - (10, FitBaseType.uint16, battery_voltage, 256), - (0, FitBaseType.uint8, device_index, 1), - (1, FitBaseType.uint8, device_type, 1), - (6, FitBaseType.uint8, hardware_version, 1), - (11, FitBaseType.uint8, battery_status, None), - ] - fields, values = self._build_content_block(content) - - if not self.device_info_defined: - header = self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_DEVICE_INFO) - msg_number = self.GMSG_NUMS['device_info'] - fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) - self.buf.write(header + fixed_content + fields) - self.device_info_defined = True - - header = self.record_header(lmsg_type=self.LMSG_TYPE_DEVICE_INFO) - self.buf.write(header + values) - - def write_weight_scale(self, timestamp, weight, percent_fat=None, percent_hydration=None, - visceral_fat_mass=None, bone_mass=None, muscle_mass=None, basal_met=None, - active_met=None, physique_rating=None, metabolic_age=None, - visceral_fat_rating=None, bmi=None): - content = [ - (253, FitBaseType.uint32, self.timestamp(timestamp), 1), - (0, FitBaseType.uint16, weight, 100), - (1, FitBaseType.uint16, percent_fat, 100), - (2, FitBaseType.uint16, percent_hydration, 100), - (3, FitBaseType.uint16, visceral_fat_mass, 100), - (4, FitBaseType.uint16, bone_mass, 100), - (5, FitBaseType.uint16, muscle_mass, 100), - (7, FitBaseType.uint16, basal_met, 4), - (9, FitBaseType.uint16, active_met, 4), - (8, FitBaseType.uint8, physique_rating, 1), - (10, FitBaseType.uint8, metabolic_age, 1), - (11, FitBaseType.uint8, visceral_fat_rating, 1), - (13, FitBaseType.uint16, bmi, 10), - ] - fields, values = self._build_content_block(content) - - if not self.weight_scale_defined: - header = self.record_header(definition=True, lmsg_type=self.LMSG_TYPE_WEIGHT_SCALE) - msg_number = self.GMSG_NUMS['weight_scale'] - fixed_content = pack('BBHB', 0, 0, msg_number, len(content)) # reserved, architecture(0: little endian) - self.buf.write(header + fixed_content + fields) - self.weight_scale_defined = True - - header = self.record_header(lmsg_type=self.LMSG_TYPE_WEIGHT_SCALE) - self.buf.write(header + values) - - def record_header(self, definition=False, lmsg_type=0): - msg = 0 - if definition: - msg = 1 << 6 # 6th bit is a definition message - return pack('B', msg + lmsg_type) - - def crc(self): - orig_pos = self.buf.tell() - self.buf.seek(0) - - crc = 0 - while True: - b = self.buf.read(1) - if not b: - break - crc = _calcCRC(crc, unpack('b', b)[0]) - self.buf.seek(orig_pos) - return pack('H', crc) - - def finish(self): - """re-weite file-header, then append crc to end of file""" - data_size = self.get_size() - self.HEADER_SIZE - self.write_header(data_size=data_size) - crc = self.crc() - self.buf.seek(0, 2) - self.buf.write(crc) - - def get_size(self): - orig_pos = self.buf.tell() - self.buf.seek(0, 2) - size = self.buf.tell() - self.buf.seek(orig_pos) - return size - - def getvalue(self): - return self.buf.getvalue() - diff --git a/withings_sync/garmin.py b/withings_sync/garmin.py deleted file mode 100644 index ffd4723..0000000 --- a/withings_sync/garmin.py +++ /dev/null @@ -1,204 +0,0 @@ -from datetime import timedelta -import urllib.request -import httpx -import urllib.error -import urllib.parse -import re -import sys -import json -import logging - -log = logging.getLogger('garmin') - - -class LoginSucceeded(Exception): - pass - - -class LoginFailed(Exception): - pass - - -class APIException(Exception): - pass - - -class GarminConnect(object): - LOGIN_URL = 'https://connect.garmin.com/signin' - UPLOAD_URL = 'https://connect.garmin.com/modern/proxy/upload-service/upload/.fit' - - def create_opener(self, cookie): - this = self - - class _HTTPRedirectHandler(urllib.request.HTTPRedirectHandler): - def http_error_302(self, req, fp, code, msg, headers): - if req.get_full_url() == this.LOGIN_URL: - raise LoginSucceeded - - return urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) - - return urllib.request.build_opener(_HTTPRedirectHandler, urllib.request.HTTPCookieProcessor(cookie)) - - # From https://github.com/cpfair/tapiriik - - def _get_session(self, record=None, email=None, password=None): - session = httpx.Client(http2=True) - - # JSIG CAS, cool I guess. - # Not quite OAuth though, so I'll continue to collect raw credentials. - # Commented stuff left in case this ever breaks because of missing parameters... - data = { - 'username': email, - 'password': password, - '_eventId': 'submit', - 'embed': 'true', - # 'displayNameRequired': 'false' - } - params = { - 'service': 'https://connect.garmin.com/modern', - # 'redirectAfterAccountLoginUrl': 'http://connect.garmin.com/modern', - # 'redirectAfterAccountCreationUrl': 'http://connect.garmin.com/modern', - # 'webhost': 'olaxpw-connect00.garmin.com', - 'clientId': 'GarminConnect', - 'gauthHost': 'https://sso.garmin.com/sso', - # 'rememberMeShown': 'true', - # 'rememberMeChecked': 'false', - 'consumeServiceTicket': 'false', - # 'id': 'gauth-widget', - # 'embedWidget': 'false', - # 'cssUrl': 'https://static.garmincdn.com/com.garmin.connect/ui/src-css/gauth-custom.css', - # 'source': 'http://connect.garmin.com/en-US/signin', - # 'createAccountShown': 'true', - # 'openCreateAccount': 'false', - # 'usernameShown': 'true', - # 'displayNameShown': 'false', - # 'initialFocus': 'true', - # 'locale': 'en' - } - - headers = { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36', - 'Referer': 'https://jhartman.pl', - 'origin': 'https://sso.garmin.com' - } - - # I may never understand what motivates people to mangle a perfectly good protocol like HTTP in the ways they do... - preResp = session.get('https://sso.garmin.com/sso/signin', params=params, headers=headers) - if preResp.status_code != 200: - raise APIException('SSO prestart error %s %s' % (preResp.status_code, preResp.text)) - - ssoResp = session.post('https://sso.garmin.com/sso/login', params=params, data=data, headers=headers) - - if ssoResp.status_code != 200 or 'temporarily unavailable' in ssoResp.text: - raise APIException('SSO error %s %s' % (ssoResp.status_code, ssoResp.text)) - - if '>sendEvent(\'FAIL\')' in ssoResp.text: - raise APIException('Invalid login') - - if '>sendEvent(\'ACCOUNT_LOCKED\')' in ssoResp.text: - raise APIException('Account Locked') - - if 'renewPassword' in ssoResp.text: - raise APIException('Reset password') - - # self.print_cookies(cookies=session.cookies) - - # ...AND WE'RE NOT DONE YET! - - gcRedeemResp = session.get('https://connect.garmin.com/modern', headers=headers) - if gcRedeemResp.status_code != 302: - raise APIException(f'GC redeem-start error {gcRedeemResp.status_code} {gcRedeemResp.text}') - - url_prefix = 'https://connect.garmin.com' - - # There are 6 redirects that need to be followed to get the correct cookie - # ... :( - max_redirect_count = 7 - current_redirect_count = 1 - while True: - url = gcRedeemResp.headers['location'] - - # Fix up relative redirects. - if url.startswith('/'): - url = url_prefix + url - url_prefix = '/'.join(url.split('/')[:3]) - gcRedeemResp = session.get(url) - - if (current_redirect_count >= max_redirect_count and - gcRedeemResp.status_code != 200): - raise APIException(f'GC redeem {current_redirect_count}/' - '{max_redirect_count} error ' - '{gcRedeemResp.status_code} ' - '{gcRedeemResp.text}') - - if gcRedeemResp.status_code in [200, 404]: - break - - current_redirect_count += 1 - if current_redirect_count > max_redirect_count: - break - - # self.print_cookies(session.cookies) - session.headers.update(headers) - - return session - - - @staticmethod - def get_json(page_html, key): - """Return json from text.""" - found = re.search(key + r" = (\{.*\});", page_html, re.M) - if found: - json_text = found.group(1).replace('\\"', '"') - return json.loads(json_text) - return None - - - def print_cookies(self, cookies): - log.debug('Cookies: ') - for key, value in list(cookies.items()): - log.debug(' %s = %s', key, value) - - - def login(self, username, password): - - session = self._get_session(email=username, password=password) - - try: - dashboard = session.get('http://connect.garmin.com/modern',follow_redirects=True) - userdata = GarminConnect.get_json(dashboard.text, "VIEWER_SOCIAL_PROFILE") - username = userdata['userName'] - - log.info('Garmin Connect User Name: %s', username) - - except Exception as e: - log.error(e) - log.error('Unable to retrieve Garmin username! Most likely: ' - 'incorrect Garmin login or password!') - log.debug(dashboard.text) - - return session - - def upload_file(self, f, session): - files = { - 'data': ( - 'withings.fit', f - ) - } - - res = session.post(self.UPLOAD_URL, - files=files, - headers={'nk': 'NT'}) - - try: - resp = res.json() - - if 'detailedImportResult' not in resp: - raise KeyError - except (ValueError, KeyError): - if res.status_code == 204: # HTTP result 204 - 'no content' - log.error('No data to upload, try to use --fromdate and --todate') - else: - log.error('Bad response during GC upload: %s', res.status_code) - - return res.status_code in [200, 201, 204] diff --git a/withings_sync/sync.py b/withings_sync/sync.py deleted file mode 100644 index 2fb2b70..0000000 --- a/withings_sync/sync.py +++ /dev/null @@ -1,339 +0,0 @@ -"""This module syncs measurement data from Withings to Garmin a/o TrainerRoad.""" -import argparse -import time -import sys -import os -import logging -import json - -from datetime import date, datetime - -from withings_sync.withings2 import WithingsAccount -from withings_sync.garmin import GarminConnect -from withings_sync.trainerroad import TrainerRoad -from withings_sync.fit import FitEncoder_Weight - -WITHINGS_USERID = 1 - - -def get_args(): - """get command-line arguments""" - parser = argparse.ArgumentParser( - description=( - "A tool for synchronisation of Withings " - "(ex. Nokia Health Body) to Garmin Connect" - " and Trainer Road or to provide a json string." - ) - ) - - def date_parser(date_string): - return datetime.strptime(date_string, "%Y-%m-%d") - - parser.add_argument( - "--withings-userid", - "--wuid", - default=WITHINGS_USERID, - type=str, - metavar="WITHINGS_USERID", - help="API userid to use for Withings.", - ) - - parser.add_argument( - "--garmin-upload", - "--gu", - action="store_true", - help=("upload to Garmin Connect."), - ) - - parser.add_argument( - "--trainerroad-upload", - "--tu", - action="store_true", - help=("upload to TrainerRoad."), - ) - - parser.add_argument("--fromdate", "-f", type=date_parser, metavar="DATE") - parser.add_argument( - "--todate", "-t", type=date_parser, default=date.today(), metavar="DATE" - ) - - parser.add_argument( - "--to-fit", "-F", action="store_true", help=("Write output file in FIT format.") - ) - parser.add_argument( - "--to-json", - "-J", - action="store_true", - help=("Write output file in JSON format."), - ) - parser.add_argument( - "--output", - "-o", - type=str, - metavar="BASENAME", - help=("Write downloaded measurements to file."), - ) - parser.add_argument("--verbose", "-v", action="store_true", help="Run verbosely") - - return parser.parse_args() - - -def sync_garmin(withings, fit_file): - """Sync generated fit file to Garmin Connect""" - garmin = GarminConnect() - session = garmin.login(withings.get_garmin_username(), withings.get_garmin_password()) - return garmin.upload_file(fit_file.getvalue(), session) - - -def sync_trainerroad(withings, last_weight): - """Sync measured weight to TrainerRoad""" - 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) - t_road.weight = round(last_weight, 1) - t_road.disconnect() - return t_road.weight - - -def generate_fitdata(syncdata): - """Generate fit data from measured data""" - logging.debug("Generating fit data...") - - have_weight = False - for record in syncdata: - if "weight" in record: - have_weight = True - break - next - - if not have_weight: - logging.info("No weight data to sync for FIT file") - return None - - fit = FitEncoder_Weight() - fit.write_file_info() - fit.write_file_creator() - - for record in syncdata: - if "weight" not in record: - next - fit.write_device_info(timestamp=record["date_time"]) - fit.write_weight_scale( - timestamp=record["date_time"], - weight=record["weight"], - percent_fat=record["fat_ratio"], - percent_hydration=record["percent_hydration"], - bone_mass=record["bone_mass"], - muscle_mass=record["muscle_mass"], - bmi=record["bmi"], - ) - - fit.finish() - - logging.debug("Fit data generated...") - return fit - - -def generate_jsondata(syncdata): - """Generate fit data from measured data""" - logging.debug("Generating json data...") - - json_data = {} - for record in syncdata: - sdt = str(record["date_time"]) - json_data[sdt] = {} - for dataentry in record["raw_data"]: - for k,jd in dataentry.json_dict().items(): - json_data[sdt][k] = jd - if "bmi" in record: - json_data[sdt]["BMI"] = { "Value": record["bmi"], "Unit": "kg/m^2"} - if "percent_hydration" in record: - json_data[sdt]["Percent_Hydration"] = { "Value": record["percent_hydration"], "Unit": "%"} - logging.debug("Json data generated...") - return json_data - - -def prepare_syncdata(height, groups): - """Prepare measurement data to be sent""" - syncdata = [] - - last_date_time = None - last_weight = None - - syncDict = {} - - for group in groups: - # Get extra physical measurements - dt = group.get_datetime() - if dt not in syncDict: - syncDict[dt] = {} - groupdata = { - "date_time": group.get_datetime(), - "height": height, - "weight": group.get_weight(), - "fat_ratio": group.get_fat_ratio(), - "muscle_mass": group.get_muscle_mass(), - "hydration": group.get_hydration(), - "percent_hydration": None, - "bone_mass": group.get_bone_mass(), - "pulse_wave_velocity": group.get_pulse_wave_velocity(), - "heart_pulse": group.get_heart_pulse(), - "bmi": None, - "raw_data": group.get_raw_data() - } - - - # if groupdata["weight"] is None: - # logging.info( - # "This Withings metric contains no weight data. Not syncing..." - # ) - # logging.debug("Detected data: ") - # continue - if height and groupdata["weight"]: - groupdata["bmi"] = round( - groupdata["weight"] / pow(groupdata["height"], 2), 1 - ) - if groupdata["hydration"]: - groupdata["percent_hydration"] = round( - groupdata["hydration"] * 100.0 / groupdata["weight"], 2 - ) - - logging.debug("%s Detected data: ", dt) - #for dataentry in raw_data: - for dataentry in groupdata["raw_data"]: - logging.debug(dataentry) - - logging.info( - "Record: %s, height=%s m, " - "weight=%s kg, " - "fat_ratio=%s %%, " - "muscle_mass=%s kg, " - "percent_hydration=%s %%, " - "bone_mass=%s kg, " - "bmi=%s", - groupdata["date_time"], - groupdata["height"], - groupdata["weight"], - groupdata["fat_ratio"], - groupdata["muscle_mass"], - groupdata["percent_hydration"], - groupdata["bone_mass"], - groupdata["bmi"], - ) - - # join groups with same timestamp - for k,v in groupdata.items(): - syncDict[dt][k] = v - - for groupdata in syncDict.values(): - syncdata.append(groupdata) - logging.debug("Processed data: ") - for k,v in groupdata.items(): - logging.debug(k, v) - if last_date_time is None or groupdata["date_time"] > last_date_time: - last_date_time = groupdata["date_time"] - last_weight = groupdata["weight"] - logging.debug("last_dt: %s last_weight: %s", last_date_time, last_weight) - - if last_weight is None: - logging.error("Invalid or no weight data detected") - - return last_weight, last_date_time, syncdata - - -def write_to_file_when_needed(fit_data, json_data): - """Write measurements to file when requested""" - if ARGS.output is not None: - if ARGS.to_fit and fit_data is not None: - filename = ARGS.output + ".fit" - logging.info("Writing fitfile to %s.", filename) - try: - with open(filename, "wb") as fitfile: - fitfile.write(fit_data.getvalue()) - except OSError: - logging.error("Unable to open output fitfile!") - if ARGS.to_json: - filename = ARGS.output + ".json" - logging.info("Writing jsonfile to %s.", filename) - try: - with open(filename, "w", encoding="utf-8") as jsonfile: - json.dump(json_data, jsonfile, indent=4) - except OSError: - logging.error("Unable to open output jsonfile!") - - -def sync(): - """Sync measurements from Withings to Garmin a/o TrainerRoad""" - - # Withings API - withings = WithingsAccount(ARGS.withings_userid) - - if not ARGS.fromdate: - startdate = withings.get_lastsync() - else: - startdate = int(time.mktime(ARGS.fromdate.timetuple())) - - enddate = int(time.mktime(ARGS.todate.timetuple())) + 86399 - logging.info( - "Fetching measurements from %s to %s", - time.strftime("%Y-%m-%d %H:%M", time.gmtime(startdate)), - time.strftime("%Y-%m-%d %H:%M", time.gmtime(enddate)), - ) - - height = withings.get_height() - groups = withings.get_measurements(startdate=startdate, enddate=enddate) - - # Only upload if there are measurement returned - if groups is None or len(groups) == 0: - logging.error("No measurements to upload for date or period specified") - return -1 - - 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) - - # Upload to Trainer Road - 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(withings, last_weight): - logging.info("TrainerRoad update done!") - else: - return -1 - - # Upload to Garmin Connect - 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: - 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 - - -ARGS = get_args() - - -def main(): - """Main""" - logging.basicConfig( - level=logging.DEBUG if ARGS.verbose else logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - stream=sys.stdout, - ) - logging.debug("Script invoked with the following arguments: %s", ARGS) - - if sys.version_info < (3, 0): - print("Sorry, requires Python3, not Python2.") - sys.exit(1) - - sync() diff --git a/withings_sync/trainerroad.py b/withings_sync/trainerroad.py deleted file mode 100644 index c566305..0000000 --- a/withings_sync/trainerroad.py +++ /dev/null @@ -1,214 +0,0 @@ -import requests -import json -import logging - -from lxml import etree -from io import StringIO - -logger = logging.getLogger(__name__) - - -class TrainerRoad: - _ftp = 'Ftp' - _weight = 'Weight' - _units_metric = 'kmh' - _units_imperial = 'mph' - _input_data_names = (_ftp, _weight, 'Marketing') - _select_data_names = ('TimeZoneId', 'IsPrivate', - 'Units', 'IsVirtualPowerEnabled') - _numerical_verify = (_ftp, _weight) - _string_verify = _select_data_names + ('Marketing',) - _login_url = 'https://www.trainerroad.com/app/login' - _logout_url = 'https://www.trainerroad.com/app/logout' - _rider_url = 'https://www.trainerroad.com/app/profile/rider-information' - _download_tcx_url = 'http://www.trainerroad.com/cycling/rides/download' - _workouts_url = 'https://api.trainerroad.com/api/careerworkouts' - _rvt = '__RequestVerificationToken' - - def __init__(self, username, password): - self._username = username - self._password = password - self._session = None - - def connect(self): - self._session = requests.Session() - self._session.auth = (self._username, self._password) - - data = {'Username': self._username, - 'Password': self._password} - - r = self._session.post(self._login_url, data=data, - allow_redirects=False) - - if r.status_code not in [200, 302]: - # There was an error - raise RuntimeError("Error loging in to TrainerRoad (Code {})" - .format(r.status_code)) - - logger.info('Logged into TrainerRoad as "{}"'.format(self._username)) - - def disconnect(self): - r = self._session.get(self._logout_url, allow_redirects=False) - if r.status_code not in [200, 302]: - raise RuntimeError("Error loging out of TrainerRoad (Code {})" - .format(r.status_code)) - - self._session = None - logger.info('Logged out of TrainerRoad as "{}"'.format(self._username)) - - def __enter__(self): - self.connect() - return self - - def __exit__(self, exc_type, exc_value, exc_traceback): - self.disconnect() - - def _parse_value(self, tree, name): - rtn = tree.xpath('//form//input[@name="{}"]/@value'.format(name)) - if not rtn: - raise RuntimeError('Input {} not found in form'.format(name)) - return rtn[0] - - def _parse_name(self, tree, name): - rtn = tree.xpath('//form//select[@name="{}"]//option' - '[@selected="selected"]/@value'.format(name)) - if not rtn: - raise RuntimeError('Input {} not found in form'.format(name)) - - return rtn[0] - - def _get(self, url): - if self._session is None: - raise RuntimeError('Not Connected') - - r = self._session.get(url) - - if r.status_code != 200: - raise RuntimeError("Error getting info from TrainerRoad (Code {})" - .format(r.status_code)) - - return r - - def _post(self, url, data): - if self._session is None: - raise RuntimeError('Not Connected') - - r = self._session.post(url, data) - - if r.status_code != 200: - raise RuntimeError("Error posting info to TrainerRoad (Code {})" - .format(r.status_code)) - - return r - - def _read_profile(self): - r = self._get(self._rider_url) - - parser = etree.HTMLParser() - tree = etree.parse(StringIO(r.text), parser) - - token = self._parse_value(tree, self._rvt) - - input_data = {} - for key in self._input_data_names: - input_data[key] = self._parse_value(tree, key) - - select_data = {} - for key in self._select_data_names: - select_data[key] = self._parse_name(tree, key) - - return (dict(**input_data, **select_data), token) - - def _write_profile(self, new_values): - # Read values - data, token = self._read_profile() - - logger.info("Read profile values {}".format(data)) - logger.debug("Token = {}".format(token)) - - # Update values with new_values - for key, value in new_values.items(): - if key not in data: - raise ValueError("Key \"{}\" is not in profile form" - .format(key)) - if key == self._weight and data['Units'] == self._units_imperial: - value = round(value/0.45359237, 1) - logger.debug("Converting Weight to lbs {}".format(value)) - data[key] = str(value) - - logger.info("New profile values {}".format(data)) - - # Now post the form - token = {self._rvt: token} - self._post(self._rider_url, data=dict(**data, **token)) - - # Now re-read to check - _data, token = self._read_profile() - - logger.info("Read profile values (verification) {}".format(_data)) - - for key in self._numerical_verify: - logger.debug('Numerically verifying key "{}" "{}" with "{}"' - .format(key, data[key], _data[key])) - if float(data[key]) != float(_data[key]): - raise RuntimeError('Failed to verify numerical key {}' - .format(key)) - for key in self._string_verify: - logger.debug('String verifying key "{}" "{}" with "{}"' - .format(key, data[key], _data[key])) - if data[key] != _data[key]: - raise RuntimeError('Failed to verify string key {}'.format(key)) - - return - - @property - def ftp(self): - values, token = self._read_profile() - return values[self._ftp] - - @ftp.setter - def ftp(self, value): - self._write_profile({self._ftp: value}) - - @property - def weight(self): - values, token = self._read_profile() - return values[self._weight] - - @weight.setter - def weight(self, value): - self._write_profile({self._weight: value}) - - def download_tcx(self, id): - res = self._session.get('{}/{}'.format(self._download_tcx_url, str(id))) - if res.status_code != 200: - raise RuntimeError("Unable to download (code = {})" - .format(res.status_code)) - - return res.text - - def get_workouts(self): - res = self._session.get(self._workouts_url) - if res.status_code != 200: - raise RuntimeError("Unable to download (code = {})" - .format(res.status_code)) - - data = json.loads(res.text) - logger.debug(json.dumps(data, indent=4, sort_keys=True)) - - logger.info('Recieved info on {} workouts'.format(len(data))) - - return data - - def get_workout(self, guid): - res = self._session.get(self._workout_url - + '?guid={}'.format(str(guid))) - - if res.status_code != 200: - raise RuntimeError('Unable to get workout "{}" (Code = {})' - .format(guid, res.status_code)) - - data = json.loads(res.text) - logger.debug(json.dumps(data, indent=4, sort_keys=True)) - - return data diff --git a/withings_sync/withings2.py b/withings_sync/withings2.py deleted file mode 100644 index 2ab12f4..0000000 --- a/withings_sync/withings2.py +++ /dev/null @@ -1,490 +0,0 @@ -"""This module takes care of the communication with Withings.""" -from datetime import date, datetime -import logging -import json -import os -import time -import pkg_resources -import requests - -log = logging.getLogger("withings") - -HOME = os.environ.get("HOME", ".") -AUTHORIZE_URL = "https://account.withings.com/oauth2_user/authorize2" -TOKEN_URL = "https://wbsapi.withings.net/v2/oauth2" -GETMEAS_URL = "https://wbsapi.withings.net/measure?action=getmeas" - -APP_CONFIG = os.environ.get( - "WITHINGS_APP", - pkg_resources.resource_filename(__name__, "config/withings_app.json"), -) -USER_CONFIG = os.environ.get("WITHINGS_USER", HOME + "/.withings_user.json") - - -class WithingsException(Exception): - """Pass WithingsExceptions""" - - -class WithingsConfig: - """This class takes care of the Withings config file""" - - config = {} - config_file = "" - - def __init__(self, config_file): - self.config_file = config_file - self.read() - - def read(self): - """reads config file""" - try: - with open(self.config_file, encoding="utf8") as configfile: - self.config = json.load(configfile) - except (ValueError, FileNotFoundError): - log.error("Can't read config file %s", self.config_file) - self.config = {} - - def write(self): - """writes config file""" - with open(self.config_file, "w", encoding="utf8") as configfile: - json.dump(self.config, configfile, indent=4, sort_keys=True) - - -class WithingsOAuth2: - """This class takes care of the Withings OAuth2 authentication""" - - app_config = user_config = None - - - def __init__(self, api_user_id): - app_cfg = WithingsConfig(APP_CONFIG) - self.app_config = app_cfg.config - - self.user_cfg = WithingsConfig(USER_CONFIG) - self.user_config = self.user_cfg.config - self.api_user_id = api_user_id - - if (not self.user_config.get(self.api_user_id)) or (not self.user_config[self.api_user_id].get("authentification_code")) : - self.user_config[self.api_user_id] ={} - self.user_config[self.api_user_id]["authentification_code"] = self.get_authenticationcode() - - if not self.user_config[self.api_user_id].get("access_token"): - self.get_accesstoken() - - self.refresh_accesstoken() - - self.user_cfg.write() - - def update_config(self): - """updates config file""" - self.user_cfg.write() - - def get_authenticationcode(self): - """get Withings authentication code""" - params = { - "response_type": "code", - "client_id": self.app_config["client_id"], - "state": "OK", - "scope": "user.metrics", - "redirect_uri": self.app_config["callback_url"], - } - - log.warning( - "User interaction needed to get Authentification " "Code from Withings!" - ) - log.warning("") - log.warning( - "Open the following URL in your web browser and copy back " - "the token. You will have *30 seconds* before the " - "token expires. HURRY UP!" - ) - log.warning("(This is one-time activity)") - log.warning("") - - url = AUTHORIZE_URL + "?" - - for key, value in params.items(): - url = url + key + "=" + value + "&" - - log.info(url) - log.info("") - - authentification_code = input("Token : ") - - return authentification_code - - def get_accesstoken(self): - """get Withings access token""" - log.info("Get Access Token") - - params = { - "action": "requesttoken", - "grant_type": "authorization_code", - "client_id": self.app_config["client_id"], - "client_secret": self.app_config["consumer_secret"], - "code": self.user_config[self.api_user_id]["authentification_code"], - "redirect_uri": self.app_config["callback_url"], - } - - req = requests.post(TOKEN_URL, params) - resp = req.json() - - status = resp.get("status") - body = resp.get("body") - - if status != 0: - log.error("Received error code: %d", status) - log.error( - "Check here for an interpretation of this error: " - "http://developer.withings.com/api-reference#section/Response-status" - ) - log.error("") - log.error( - "If it's regarding an invalid code, try to start the" - " script again to obtain a new link." - ) - - self.user_config[self.api_user_id]["access_token"] = body.get("access_token") - self.user_config[self.api_user_id]["refresh_token"] = body.get("refresh_token") - self.user_config[self.api_user_id]["userid"] = body.get("userid") - - def refresh_accesstoken(self): - """refresh Withings access token""" - log.info("Refresh Access Token") - - params = { - "action": "requesttoken", - "grant_type": "refresh_token", - "client_id": self.app_config["client_id"], - "client_secret": self.app_config["consumer_secret"], - "refresh_token": self.user_config[self.api_user_id]["refresh_token"], - } - - req = requests.post(TOKEN_URL, params) - resp = req.json() - - status = resp.get("status") - body = resp.get("body") - - if status != 0: - log.error("Received error code: %d", status) - log.error( - "Check here for an interpretation of this error: " - "http://developer.withings.com/api-reference#section/Response-status" - ) - log.error("") - log.error( - "If it's regarding an invalid code, try to start the" - " script again to obtain a new link." - ) - - self.user_config[self.api_user_id]["access_token"] = body.get("access_token") - self.user_config[self.api_user_id]["refresh_token"] = body.get("refresh_token") - self.user_config[self.api_user_id]["userid"] = body.get("userid") - - -class WithingsAccount: - """This class gets measurements from Withings""" - - def __init__(self, api_user_id): - self.api_user_id = str(api_user_id) - self.withings = WithingsOAuth2(self.api_user_id) - - def get_garmin_username(self): - """get Garmin username""" - if not self.withings.user_config[self.api_user_id].get("garmin_username"): - garmin_username = input("Garmin username : ") - self.withings.user_config[self.api_user_id]["garmin_username"] = garmin_username - self.withings.update_config() - return self.withings.user_config[self.api_user_id]["garmin_username"] - - def get_garmin_password(self): - """get Garmin password""" - if not self.withings.user_config[self.api_user_id].get("garmin_password"): - garmin_password = input("Garmin password : ") - self.withings.user_config[self.api_user_id]["garmin_password"] = garmin_password - self.withings.update_config() - return self.withings.user_config[self.api_user_id]["garmin_password"] - - def get_trainerroad_username(self): - """get TrainerRoad username""" - if not self.withings.user_config[self.api_user_id].get("trainerroad_username"): - trainerroad_username = input("TrainerRoad username : ") - self.withings.user_config[self.api_user_id]["trainerroad_username"] = trainerroad_username - self.withings.update_config() - return self.withings.user_config[self.api_user_id]["trainerroad_username"] - - def get_trainerroad_password(self): - """get TrainerRoad password""" - if not self.withings.user_config[self.api_user_id].get("trainerroad_password"): - trainerroad_password = input("TrainerRoad password : ") - self.withings.user_config[self.api_user_id]["trainerroad_password"] = trainerroad_password - self.withings.update_config() - return self.withings.user_config[self.api_user_id]["trainerroad_password"] - - def get_lastsync(self): - """get last sync timestamp""" - if not self.withings.user_config[self.api_user_id].get("last_sync"): - return int(time.mktime(date.today().timetuple())) - return self.withings.user_config[self.api_user_id]["last_sync"] - - def set_lastsync(self): - """set last sync timestamp""" - self.withings.user_config[self.api_user_id]["last_sync"] = int(time.time()) - log.info("Saving Last Sync") - self.withings.update_config() - - def get_measurements(self, startdate, enddate): - """get Withings measurements""" - log.info("Get Measurements") - - params = { - "access_token": self.withings.user_config[self.api_user_id]["access_token"], - # 'meastype': MEASTYPE_WEIGHT, - "category": 1, - "startdate": startdate, - "enddate": enddate, - } - - req = requests.post(GETMEAS_URL, params) - - measurements = req.json() - - if measurements.get("status") == 0: - log.debug("Measurements received") - return [ - WithingsMeasureGroup(g) - for g in measurements.get("body").get("measuregrps") - ] - return None - - def get_height(self): - """get height from Withings""" - height = None - height_timestamp = None - height_group = None - - log.debug("Get Height") - - params = { - "access_token": self.withings.user_config[self.api_user_id]["access_token"], - "meastype": WithingsMeasure.TYPE_HEIGHT, - "category": 1, - } - - req = requests.post(GETMEAS_URL, params) - - measurements = req.json() - - if measurements.get("status") == 0: - log.debug("Height received") - - # there could be multiple height records. use the latest one - for record in measurements.get("body").get("measuregrps"): - height_group = WithingsMeasureGroup(record) - if height is not None: - if height_timestamp is not None: - if height_group.get_datetime() > height_timestamp: - height = height_group.get_height() - else: - height = height_group.get_height() - height_timestamp = height_group.get_datetime() - - return height - - -class WithingsMeasureGroup: - """This class takes care of the group measurement functions""" - - def __init__(self, measuregrp): - self._raw_data = measuregrp - self.grpid = measuregrp.get("grpid") - self.attrib = measuregrp.get("attrib") - self.date = measuregrp.get("date") - self.category = measuregrp.get("category") - self.measures = [WithingsMeasure(m) for m in measuregrp["measures"]] - - def __iter__(self): - for measure in self.measures: - yield measure - - def __len__(self): - return len(self.measures) - - def get_datetime(self): - """convenient function to get date & time""" - return datetime.fromtimestamp(self.date) - - def get_raw_data(self): - """convenient function to get raw data""" - return self.measures - - def get_weight(self): - """convenient function to get weight""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_WEIGHT: - return round(measure.get_value(), 2) - return None - - def get_height(self): - """convenient function to get height""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_HEIGHT: - return round(measure.get_value(), 2) - return None - - def get_fat_free_mass(self): - """convenient function to get fat free mass""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_FAT_FREE_MASS: - return round(measure.get_value(), 2) - return None - - def get_fat_ratio(self): - """convenient function to get fat ratio""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_FAT_RATIO: - return round(measure.get_value(), 2) - return None - - def get_fat_mass_weight(self): - """convenient function to get fat mass weight""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_FAT_MASS_WEIGHT: - return round(measure.get_value(), 2) - return None - - def get_diastolic_blood_pressure(self): - """convenient function to get diastolic blood pressure""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_DIASTOLIC_BLOOD_PRESSURE: - return round(measure.get_value(), 2) - return None - - def get_systolic_blood_pressure(self): - """convenient function to get systolic blood pressure""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_SYSTOLIC_BLOOD_PRESSURE: - return round(measure.get_value(), 2) - return None - - def get_heart_pulse(self): - """convenient function to get heart pulse""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_HEART_PULSE: - return round(measure.get_value(), 2) - return None - - def get_temperature(self): - """convenient function to get temperature""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_TEMPERATURE: - return round(measure.get_value(), 2) - return None - - def get_sp02(self): - """convenient function to get sp02""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_SP02: - return round(measure.get_value(), 2) - return None - - def get_body_temperature(self): - """convenient function to get body temperature""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_BODY_TEMPERATURE: - return round(measure.get_value(), 2) - return None - - def get_skin_temperature(self): - """convenient function to get skin temperature""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_SKIN_TEMPERATURE: - return round(measure.get_value(), 2) - return None - - def get_muscle_mass(self): - """convenient function to get muscle mass""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_MUSCLE_MASS: - return round(measure.get_value(), 2) - return None - - def get_hydration(self): - """convenient function to get hydration""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_HYDRATION: - return round(measure.get_value(), 2) - return None - - def get_bone_mass(self): - """convenient function to get bone mass""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_BONE_MASS: - return round(measure.get_value(), 2) - return None - - def get_pulse_wave_velocity(self): - """convenient function to get pulse wave velocity""" - for measure in self.measures: - if measure.type == WithingsMeasure.TYPE_PULSE_WAVE_VELOCITY: - return round(measure.get_value(), 2) - return None - - -class WithingsMeasure: - """This class takes care of the individual measurements""" - - TYPE_WEIGHT = 1 - TYPE_HEIGHT = 4 - TYPE_FAT_FREE_MASS = 5 - TYPE_FAT_RATIO = 6 - TYPE_FAT_MASS_WEIGHT = 8 - TYPE_DIASTOLIC_BLOOD_PRESSURE = 9 - TYPE_SYSTOLIC_BLOOD_PRESSURE = 10 - TYPE_HEART_PULSE = 11 - TYPE_TEMPERATURE = 12 - TYPE_SP02 = 54 - TYPE_BODY_TEMPERATURE = 71 - TYPE_SKIN_TEMPERATURE = 73 - TYPE_MUSCLE_MASS = 76 - TYPE_HYDRATION = 77 - TYPE_BONE_MASS = 88 - TYPE_PULSE_WAVE_VELOCITY = 91 - - withings_table = { - TYPE_WEIGHT: ["Weight", "kg"], - TYPE_HEIGHT: ["Height", "meter"], - TYPE_FAT_FREE_MASS: ["Fat Free Mass", "kg"], - TYPE_FAT_RATIO: ["Fat Ratio", "%"], - TYPE_FAT_MASS_WEIGHT: ["Fat Mass Weight", "kg"], - TYPE_DIASTOLIC_BLOOD_PRESSURE: ["Diastolic Blood Pressure", "mmHg"], - TYPE_SYSTOLIC_BLOOD_PRESSURE: ["Systolic Blood Pressure", "mmHg"], - TYPE_HEART_PULSE: ["Heart Pulse", "bpm"], - TYPE_TEMPERATURE: ["Temperature", "celsius"], - TYPE_SP02: ["SP02", "%"], - TYPE_BODY_TEMPERATURE: ["Body Temperature", "celsius"], - TYPE_SKIN_TEMPERATURE: ["Skin Temperature", "celsius"], - TYPE_MUSCLE_MASS: ["Muscle Mass", "kg"], - TYPE_HYDRATION: ["Hydration", "kg"], - TYPE_BONE_MASS: ["Bone Mass", "kg"], - TYPE_PULSE_WAVE_VELOCITY: ["Pulse Wave Velocity", "m/s"], - } - - def __init__(self, measure): - self._raw_data = measure - self.value = measure.get("value") - self.type = measure.get("type") - self.unit = measure.get("unit") - self.type_s = self.withings_table.get(self.type, ["unknown", ""])[0] - self.unit_s = self.withings_table.get(self.type, ["unknown", ""])[1] - - def __str__(self): - return f"{self.type_s}: {self.get_value()} {self.unit_s}" - - def json_dict(self): - return { f"{self.type_s.replace(' ','_')}": { "Value": round(self.get_value(), 2), "Unit": f'{self.unit_s}'}} - - def get_value(self): - """get value""" - return self.value * pow(10, self.unit) From b29e682393c761891cd492e4ed00ff2dda8a1cbb Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:30:38 +0300 Subject: [PATCH 08/12] Delete .github/workflows directory --- .github/workflows/build-publish.yml | 38 --------------------- .github/workflows/docker-image.yml | 52 ----------------------------- 2 files changed, 90 deletions(-) delete mode 100644 .github/workflows/build-publish.yml delete mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml deleted file mode 100644 index 2033325..0000000 --- a/.github/workflows/build-publish.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- - -name: Build and Publish Release via PyPi - -on: push - -jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@master - - - name: Set up Python 3.10 - uses: actions/setup-python@v3 - with: - python-version: "3.10" - - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - - name: Build a source tarball - run: >- - python -m - build - --sdist - --outdir dist/ . - - - name: Publish distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index 585ae85..0000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,52 +0,0 @@ ---- - -name: Publish Docker Image - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - schedule: - - cron: '21 5 */5 * *' - push: - branches: [ master ] - # Publish semver tags as releases. - tags: [ 'v*.*.*' ] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build-and-push-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Log in to the Container registry - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} From 939917f33acd9d4d890b131bd2d915812f1f199f Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:30:59 +0300 Subject: [PATCH 09/12] Delete contrib directory --- contrib/do_release.sh | 31 ---------- contrib/icon.png | Bin 195554 -> 0 bytes contrib/k8s-deployment.yaml | 112 ------------------------------------ contrib/k8s-job.yaml | 89 ---------------------------- contrib/withings.html | 37 ------------ 5 files changed, 269 deletions(-) delete mode 100644 contrib/do_release.sh delete mode 100644 contrib/icon.png delete mode 100644 contrib/k8s-deployment.yaml delete mode 100644 contrib/k8s-job.yaml delete mode 100644 contrib/withings.html diff --git a/contrib/do_release.sh b/contrib/do_release.sh deleted file mode 100644 index 692922c..0000000 --- a/contrib/do_release.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -set -e - -DOCKER_REPO=stv0g -DOCKER_IMAGE=${DOCKER_REPO}/withings-sync -DOCKER_PLATFORMS=linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 - -VER=$(sed -n -e 's/.*version='\''\([0-9\.]*\)'\''.*/\1/p' < setup.py) - -git tag v${VER} || true -git push --tags - -# Build Docker image - -docker buildx create \ - --use \ - --platform ${DOCKER_PLATFORMS} \ - --name cross-platform-build - -docker buildx build \ - --platform ${DOCKER_PLATFORMS} \ - --tag ${DOCKER_IMAGE}:${VER} \ - --push . - -exit 0 - -# Publish to pypi.org -python3 setup.py sdist - -twine upload dist/withings-sync-${VER}.tar.gz diff --git a/contrib/icon.png b/contrib/icon.png deleted file mode 100644 index c9eb8b35b593c5dc0d60f63c335733332e98983a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195554 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

5*(EiMS694t<*MH-aTVTz>q#Jy03s=dHz0DgTsb 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 WvVw*NZo^23Kk#EitzwN=;r|Z_B(v`T diff --git a/contrib/k8s-deployment.yaml b/contrib/k8s-deployment.yaml deleted file mode 100644 index abca4f1..0000000 --- a/contrib/k8s-deployment.yaml +++ /dev/null @@ -1,112 +0,0 @@ -# Deploy withings-sync as a Kubernetes deployment -# Please also see contrib/k8s-job.yaml for a Kubernetes CronJob -# -# To install: -# - kubectl create namespace FOO -# - kubectl -n FOO create -f (this file) -# -# To check logs: -# - kubectl -n FOO logs -l app=withings-sync -# -# Because the app is running without STDIN, it will not pause to wait -# for you to enter the oauth token. Instead it will throw an error -# EOFError: EOF when reading a line -# and continue to sleep. To set up for the first time, run something -# like this manually, and enter the oauth token provided after you -# follow the link given: -# -# - kubectl -n FOO exec -ti $( kubectl -n FOO get pods | awk '$3=="Running" {print $1; exit}' ) -- withings-sync - - ---- -# Secret for credentials. By defining this as stringData: -# instead of data: we can enter details here in plain text and -# it gets base64 encoded when added to the system. -# -# Leave unused variables defined, but empty "" -# -apiVersion: v1 -kind: Secret -metadata: - name: credentials -type: Opaque -stringData: - # NOT base64 - GARMIN_PASSWORD: "someone@example.com" - GARMIN_USERNAME: "password-goes-here" - TRAINERROAD_PASSWORD: "" - TRAINERROAD_USERNAME: "" - ---- -# PVC for storing Withings OAuth tokens. Different -# storage methods have different minimum sizes. -# -# If you don't have a default storage class, you must -# specify it -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: withings-oauth-cache -spec: - accessModes: - - ReadWriteOnce - # uncomment, and define if needed - # storageClassName: class - resources: - requests: - storage: 1Mi - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: withings-sync -spec: - replicas: 1 - revisionHistoryLimit: 3 - selector: - matchLabels: - app: withings-sync - template: - metadata: - labels: - app: withings-sync - spec: - containers: - - name: withings-sync - image: docker.io/stv0g/withings-sync - imagePullPolicy: IfNotPresent - - # override $HOME to put our oauth file in a known place - env: - - name: HOME - value: /data - - # read usernames/passwords from the secret we defined - envFrom: - - secretRef: - name: credentials - - volumeMounts: - - mountPath: /data - name: oauth-cache - - # run every 15 minutes, synched to clock time - command: - - sh - - -c - - while true; do date; withings-sync; echo; sleep 10000; done - - # Never run this container as root - securityContext: - fsGroup: 1234 - runAsGroup: 1234 - runAsUser: 1234 - runAsNonRoot: True - - volumes: - - name: oauth-cache - persistentVolumeClaim: - claimName: withings-oauth-cache - - diff --git a/contrib/k8s-job.yaml b/contrib/k8s-job.yaml deleted file mode 100644 index 80ce27f..0000000 --- a/contrib/k8s-job.yaml +++ /dev/null @@ -1,89 +0,0 @@ ---- -# Secret for credentials. By defining this as stringData: -# instead of data: we can enter details here in plain text and -# it gets base64 encoded when added to the system. -# -# Leave unused variables defined, but empty "" -apiVersion: v1 -kind: Secret -metadata: - name: credentials -type: Opaque -stringData: - # NOT base64 - GARMIN_PASSWORD: "someone@example.com" - GARMIN_USERNAME: "password-goes-here" - TRAINERROAD_PASSWORD: "" - TRAINERROAD_USERNAME: "" - ---- -# PVC for storing Withings OAuth tokens. Different -# storage methods have different minimum sizes. -# -# If you don't have a default storage class, you must -# specify it -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - finalizers: - - kubernetes.io/pvc-protection - name: withings-oauth-cache -spec: - accessModes: - - ReadWriteOnce - # uncomment, and define if needed - # storageClassName: class - resources: - requests: - storage: 1Mi - volumeMode: Filesystem - ---- -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: withings-garmin-sync -spec: - concurrencyPolicy: Allow - failedJobsHistoryLimit: 1 - jobTemplate: - spec: - template: - spec: - containers: - - args: - - -v - - # override $HOME to put our oauth file in a known place - env: - - name: HOME - value: /root - - # read usernames/passwords from the secret we defined - envFrom: - - secretRef: - name: credentials - image: ghcr.io/jaroslawhartman/withings-sync:master - imagePullPolicy: Always - name: withings-garmin-sync - volumeMounts: - - mountPath: /root/ - name: oauth-cache - - restartPolicy: Never - - # Never run this container as root - securityContext: - fsGroup: 1234 - runAsGroup: 1234 - runAsUser: 1234 - runAsNonRoot: True - - volumes: - - name: oauth-cache - persistentVolumeClaim: - claimName: withings-oauth-cache - - schedule: '0 */3 * * *' - successfulJobsHistoryLimit: 3 - diff --git a/contrib/withings.html b/contrib/withings.html deleted file mode 100644 index c9cd5f3..0000000 --- a/contrib/withings.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - Withings Authorization Token - - -

Your Withings Authorization Token

- - -

You must copy it back to the script during next 30 seconds, otherwise it will get expired.

- - - - From 21864883cb5480ed04d7c311a4919ef33aea6ccc Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:31:32 +0300 Subject: [PATCH 10/12] Delete MANIFEST.in --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 695153d..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include withings_sync/config/*.json \ No newline at end of file From e3ec58880a87cf41ac478980ae8bce7f2769fa4d Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:39:39 +0300 Subject: [PATCH 11/12] Create withings_app.json --- config/withings_app.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 config/withings_app.json diff --git a/config/withings_app.json b/config/withings_app.json new file mode 100644 index 0000000..95295d7 --- /dev/null +++ b/config/withings_app.json @@ -0,0 +1,5 @@ +{ + "callback_url": "https://jaroslawhartman.github.io/withings-sync/contrib/withings.html", + "client_id": "183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626", + "consumer_secret": "a75d655c985d9e6391df1514c16719ef7bd69fa7c5d3fd0eac2e2b0ed48f1765" +} From a311625e64abaef68433bfb857dc658e0858cf81 Mon Sep 17 00:00:00 2001 From: skatsavos Date: Thu, 11 May 2023 00:41:58 +0300 Subject: [PATCH 12/12] Update requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index ab90481..3e0c1de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ lxml +requests +cloudscraper