Skip to content

Commit

Permalink
Merge branch 'master' into dotenv-support
Browse files Browse the repository at this point in the history
  • Loading branch information
longstone authored Oct 2, 2023
2 parents d0a5bb4 + 23c9eeb commit 9f977b8
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 43 deletions.
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
lxml
requests
cloudscraper
garth
python-dotenv
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def read(fname):
"Topic :: Utilities",
"License :: OSI Approved :: MIT License",
],
install_requires=["lxml", "requests", "cloudscraper", "garth", "python-dotenv"],
install_requires=["lxml", "requests", "garth", "python-dotenv"],
entry_points={
"console_scripts": ["withings-sync=withings_sync.sync:main"],
},
Expand Down
34 changes: 18 additions & 16 deletions withings_sync/garmin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""This module handles the Garmin connectivity."""
import logging
import cloudscraper
import garth

import os

log = logging.getLogger("garmin")

Expand All @@ -22,31 +21,34 @@ class APIException(Exception):
class GarminConnect:
"""Main GarminConnect class"""

UPLOAD_URL = "https://connect.garmin.com/upload-service/upload/.fit"

# From https://github.com/cpfair/tapiriik
@staticmethod
def get_session(email=None, password=None):
"""tapiriik get_session code"""
session = cloudscraper.CloudScraper()

try:
garth.login(email, password)
except Exception as ex:
raise APIException("Authentication failure: {}. Did you enter correct credentials?".format(ex))
logged_in = False
if os.path.exists('./garmin_session'):
garth.resume('./garmin_session')
try:
garth.client.username
logged_in = True
except Exception:
pass

if not logged_in:
try:
garth.login(email, password)
garth.save('./garmin_session')
except Exception as ex:
raise APIException("Authentication failure: {}. Did you enter correct credentials?".format(ex))

session.headers.update({'NK': 'NT', 'authorization': garth.client.oauth2_token.__str__(), 'di-backend': 'connectapi.garmin.com'})
return session

@staticmethod
def login(username, password):
"""login to Garmin"""
return GarminConnect.get_session(email=username, password=password)

def upload_file(self, ffile, session):
def upload_file(self, ffile):
"""upload fit file to Garmin connect"""
files = {"data": ("withings.fit", ffile)}
res = session.post(self.UPLOAD_URL, files=files, headers={"nk": "NT"})
res = garth.client.post('connect', '/upload-service/upload/.fit', files=files, api=True, headers={'di-backend': 'connectapi.garmin.com'})
try:
resp = res.json()
if "detailedImportResult" not in resp:
Expand Down
61 changes: 36 additions & 25 deletions withings_sync/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ def date_parser(date_string):
default=GARMIN_USERNAME,
type=str,
metavar="GARMIN_USERNAME",
help="username to log in to Garmin Connect.",
help="Username to log in to Garmin Connect.",
)
parser.add_argument(
"--garmin-password",
"--gp",
default=GARMIN_PASSWORD,
type=str,
metavar="GARMIN_PASSWORD",
help="password to log in to Garmin Connect.",
help="Password to log in to Garmin Connect.",
)

parser.add_argument(
Expand All @@ -89,7 +89,7 @@ def date_parser(date_string):
default=TRAINERROAD_USERNAME,
type=str,
metavar="TRAINERROAD_USERNAME",
help="username to log in to TrainerRoad.",
help="Username to log in to TrainerRoad.",
)

parser.add_argument(
Expand All @@ -98,26 +98,28 @@ def date_parser(date_string):
default=TRAINERROAD_PASSWORD,
type=str,
metavar="TRAINERROAD_PASSWORD",
help="password to log in to TrainerRoad.",
help="Password to log in to TrainerRoad.",
)

parser.add_argument(
"--fromdate", "-f",
type=date_parser,
metavar="DATE"
metavar="DATE",
help="Date to start syncing from. Ex: 2023-12-20",
)

parser.add_argument(
"--todate", "-t",
type=date_parser,
default=date.today(),
metavar="DATE"
metavar="DATE",
help="Date for the last sync. Ex: 2023-12-30",
)

parser.add_argument(
"--to-fit", "-F",
action="store_true",
help="Write output file in FIT format."
help="Write output file in FIT format.",
)

parser.add_argument(
Expand All @@ -138,22 +140,22 @@ def date_parser(date_string):
parser.add_argument(
"--no-upload",
action="store_true",
help="Won't upload to Garmin Connect or " "TrainerRoad.",
help="Won't upload to Garmin Connect or TrainerRoad.",
)

parser.add_argument(
"--features",
nargs='+',
default=[],
metavar="BLOOD_PRESSURE",
help="Enable Features like BLOOD_PRESSURE"
help="Enable Features like BLOOD_PRESSURE."
)

parser.add_argument(
"--verbose",
"-v",
action="store_true",
help="Run verbosely"
help="Run verbosely."
)

return parser.parse_args()
Expand All @@ -162,8 +164,8 @@ def date_parser(date_string):
def sync_garmin(fit_file):
"""Sync generated fit file to Garmin Connect"""
garmin = GarminConnect()
session = garmin.login(ARGS.garmin_username, ARGS.garmin_password)
return garmin.upload_file(fit_file.getvalue(), session)
garmin.login(ARGS.garmin_username, ARGS.garmin_password)
return garmin.upload_file(fit_file.getvalue())


def sync_trainerroad(last_weight):
Expand Down Expand Up @@ -346,9 +348,9 @@ def prepare_syncdata(height, groups):
if "diastolic_blood_pressure" in group_data:
logging.debug(
"Record: %s, type=%s\n"
"diastolic_blood_pressure=%s kg, "
"systolic_blood_pressure=%s %%, "
"heart_pulse=%s kg, ",
"diastolic_blood_pressure=%s mmHg, "
"systolic_blood_pressure=%s mmHg, "
"heart_pulse=%s BPM, ",
group_data["date_time"],
group_data["type"],
group_data["diastolic_blood_pressure"],
Expand Down Expand Up @@ -462,21 +464,30 @@ def sync():
logging.info("No TrainerRoad username or a new measurement " "- skipping sync")

# Upload to Garmin Connect
if ARGS.garmin_username and (fit_data_weight is not None or fit_data_blood_pressure is not None):
if ARGS.garmin_username and (
fit_data_weight is not None or fit_data_blood_pressure is not None
):
logging.debug("attempting to upload fit file...")
if fit_data_weight is not None and sync_garmin(fit_data_weight):
logging.info("Fit file with weight information uploaded to Garmin Connect")
if fit_data_blood_pressure is not None and sync_garmin(fit_data_blood_pressure):
logging.info("Fit file with blood pressure information uploaded to Garmin Connect")
if fit_data_weight is not None:
gar_wg_state = sync_garmin(fit_data_weight)
if gar_wg_state:
logging.info(
"Fit file with weight information uploaded to Garmin Connect"
)
if fit_data_blood_pressure is not None:
gar_bp_state = sync_garmin(fit_data_blood_pressure)
if gar_bp_state:
logging.info(
"Fit file with blood pressure information uploaded to Garmin Connect"
)
if gar_wg_state or gar_bp_state:
# 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()
else:
logging.info("No Garmin username - skipping sync")
else:
logging.info("Skipping upload")

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

return 0


Expand Down

0 comments on commit 9f977b8

Please sign in to comment.