459 lines
14 KiB
Python
459 lines
14 KiB
Python
from rowers.imports import *
|
|
import datetime
|
|
|
|
import requests
|
|
from requests_oauthlib import OAuth1,OAuth1Session
|
|
from requests_oauthlib.oauth1_session import TokenRequestDenied
|
|
from requests import Request, Session
|
|
import rowers.mytypes as mytypes
|
|
from rowers.mytypes import otwtypes
|
|
from rowers.rower_rules import is_workout_user,ispromember
|
|
from iso8601 import ParseError
|
|
|
|
import pandas as pd
|
|
|
|
import numpy
|
|
import json
|
|
from json.decoder import JSONDecodeError
|
|
from uuid import uuid4
|
|
|
|
from rowsandall_app.settings import (
|
|
GARMIN_CLIENT_KEY, GARMIN_REDIRECT_URI, GARMIN_CLIENT_SECRET
|
|
)
|
|
|
|
from pytz import timezone as tz, utc
|
|
|
|
from rowers.tasks import handle_get_garmin_file
|
|
import django_rq
|
|
queue = django_rq.get_queue('default')
|
|
queuelow = django_rq.get_queue('low')
|
|
queuehigh = django_rq.get_queue('low')
|
|
from rowers.utils import myqueue
|
|
from rowers.models import C2WorldClassAgePerformance,Rower,Workout,TombStone
|
|
|
|
from django.core.exceptions import PermissionDenied
|
|
|
|
from rowers.utils import custom_exception_handler,NoTokenError
|
|
from rowingdata import rowingdata
|
|
|
|
oauth_data = {
|
|
'client_id': GARMIN_CLIENT_KEY,
|
|
'client_secret': GARMIN_CLIENT_SECRET,
|
|
'redirect_uri': GARMIN_REDIRECT_URI,
|
|
'authorization_uri': "https://connectapi.garmin.com/oauth-service/oauth/request_token",
|
|
'content_type': 'application/x-www-form-urlencoded',
|
|
'tokenname': 'garmintoken',
|
|
'refreshtokenname': 'garminrefreshtoken',
|
|
'expirydatename': 'garmintokenexpirydate',
|
|
'bearer_auth': True,
|
|
'base_url': "https://connect.garmin.com/oauthConfirm",
|
|
'scope':'write',
|
|
'headers': 'Authorization: OAuth oauth_version="1.0"'
|
|
}
|
|
|
|
columns = {
|
|
'startTimeInSeconds':'TimeStamp (sec)',
|
|
'latitudeInDegree':' latitude',
|
|
'longitudeInDegree':' longitude',
|
|
'heartRate':' HRCur (bpm)',
|
|
'speedMetersPerSecond':' AverageBoatSpeed (m/s)',
|
|
'totalDistanceInMeters':' Horizontal (meters)',
|
|
'clockDurationInSeconds':' ElapsedTime (sec)',
|
|
'powerInWatts':' Power (watts)',
|
|
'bikeCadenceInRPM':' Cadence (stokes/min)',
|
|
}
|
|
|
|
def garmin_authorize():
|
|
redirect_uri = oauth_data['redirect_uri']
|
|
client_secret = oauth_data['client_secret']
|
|
client_id = oauth_data['client_id']
|
|
base_uri = oauth_data['base_url']
|
|
|
|
|
|
garmin = OAuth1Session(oauth_data['client_id'],
|
|
client_secret=oauth_data['client_secret'],
|
|
)
|
|
fetch_response = garmin.fetch_request_token(oauth_data['authorization_uri'])
|
|
resource_owner_key = fetch_response.get('oauth_token')
|
|
resource_owner_secret = fetch_response.get('oauth_token_secret')
|
|
|
|
authorization_url = garmin.authorization_url(base_uri)
|
|
return authorization_url,resource_owner_key,resource_owner_secret
|
|
|
|
def garmin_processcallback(redirect_response,resource_owner_key,resource_owner_secret):
|
|
garmin = OAuth1Session(oauth_data['client_id'],
|
|
client_secret=oauth_data['client_secret'],
|
|
)
|
|
oauth_response = garmin.parse_authorization_response(redirect_response)
|
|
|
|
verifier = oauth_response.get('oauth_verifier')
|
|
token = oauth_response.get('oauth_token')
|
|
access_token_url = 'https://connectapi.garmin.com/oauth-service/oauth/access_token'
|
|
|
|
# Using OAuth1Session
|
|
garmin = OAuth1Session(oauth_data['client_id'],
|
|
client_secret=oauth_data['client_secret'],
|
|
resource_owner_key=resource_owner_key,
|
|
resource_owner_secret=resource_owner_secret,
|
|
verifier=verifier,)
|
|
try:
|
|
oauth_tokens = garmin.fetch_access_token(access_token_url)
|
|
garmintoken = oauth_tokens.get('oauth_token')
|
|
garminrefreshtoken = oauth_tokens.get('oauth_token_secret')
|
|
except TokenRequestDenied:
|
|
garmintoken = ''
|
|
garminrefreshtoken = ''
|
|
|
|
return garmintoken,garminrefreshtoken
|
|
|
|
def garmin_open(user):
|
|
r = Rower.objects.get(user=user)
|
|
token = Rower.garmintoken
|
|
|
|
if (token == '') or (token is None):
|
|
raise NoTokenError("User has no garmin token")
|
|
|
|
return token
|
|
|
|
def get_garmin_file(r,callbackURL,starttime,fileType):
|
|
job = myqueue(
|
|
queue,
|
|
handle_get_garmin_file,
|
|
oauth_data['client_id'],
|
|
oauth_data['client_secret'],
|
|
r.garmintoken,
|
|
r.garminrefreshtoken,
|
|
r.user.id,
|
|
callbackURL,
|
|
fileType,
|
|
)
|
|
|
|
return job.id
|
|
|
|
def get_garmin_workout_list(user):
|
|
r = Rower.objects.get(user=user)
|
|
if (r.garmintoken == '') or (r.garmintoken is None):
|
|
s = "Token doesn't exist. Need to authorize"
|
|
return custom_exception_handler(401,s)
|
|
|
|
garmin = OAuth1Session(oauth_data['client_id'],
|
|
client_secret=oauth_data['client_secret'],
|
|
resource_owner_key=r.garmintoken,
|
|
resource_owner_secret=r.garminrefreshtoken,
|
|
)
|
|
|
|
url = 'https://healthapi.garmin.com/wellness-api/rest/activities?uploadStartTimeInSeconds=1593113760&uploadEndTimeInSeconds=1593279360'
|
|
|
|
result = garmin.get(url)
|
|
|
|
return result
|
|
|
|
def garmin_can_export_session(user):
|
|
result = get_garmin_permissions(user)
|
|
if 'WORKOUT_IMPORT' in result:
|
|
return True
|
|
|
|
return False
|
|
|
|
from rowers import utils
|
|
|
|
def step_to_garmin(step,order=0):
|
|
durationtype = step['dict']['durationType']
|
|
durationvalue = step['dict']['durationValue']
|
|
durationvaluetype = ''
|
|
if durationtype == 'Time':
|
|
durationtype = 'TIME'
|
|
durationvalue = int(durationvalue/1000.)
|
|
elif durationtype == 'Distance':
|
|
durationtype = 'DISTANCE'
|
|
durationvalue = int(durationvalue/100)
|
|
durationvaluetype = 'METER'
|
|
elif durationtype == 'HrLessThan':
|
|
durationtype = 'HR_LESS_THAN'
|
|
if durationvalue <= 100:
|
|
durationvaluetype = 'PERCENT'
|
|
else:
|
|
durationvaluetype = ''
|
|
durationvalue -= 100
|
|
elif durationtype == 'HrGreaterThan':
|
|
durationtype = 'HR_GREATER_THAN'
|
|
if durationvalue <= 100:
|
|
durationvaluetype = 'PERCENT'
|
|
else:
|
|
durationvaluetype = ''
|
|
durationvalue -= 100
|
|
elif durationtype == 'PowerLessThan':
|
|
durationtype = 'POWER_LESS_THAN'
|
|
if durationvalue <= 1000:
|
|
durationvaluetype = 'PERCENT'
|
|
else:
|
|
durationvaluetype = ''
|
|
durationvalue -= 1000
|
|
elif durationtype == 'PowerGreaterThan':
|
|
durationtype = 'POWER_GREATER_THAN'
|
|
if durationvalue <= 1000:
|
|
durationvaluetype = 'PERCENT'
|
|
else:
|
|
durationvaluetype = ''
|
|
durationvalue -= 1000
|
|
elif durationtype == 'Reps':
|
|
durationtype = 'REPS'
|
|
|
|
out = {
|
|
'type': step['type'],
|
|
'stepOrder':order,
|
|
'repeatType':step['type'],
|
|
'repeatValue':step['repeatValue'],
|
|
'intensity':step['dict']['intensity'],
|
|
'description':step['dict']['wkt_step_name'],
|
|
'durationType':durationtype,
|
|
'durationValue':durationvalue,
|
|
'durationValueType':durationvaluetype,
|
|
'targetType':step['dict']['targetType'],
|
|
'targetValue':step['dict']['targetValue'],
|
|
'targetValueLow':step['dict']['targetValueLow'],
|
|
'targetValueHigh':step['dict']['targetValueHigh'],
|
|
}
|
|
try:
|
|
steps = step['steps']
|
|
lijst = []
|
|
order += 1
|
|
for s in steps:
|
|
sout, order = step_to_garmin(s, order)
|
|
lijst.append(sout)
|
|
out['steps'] = lijst
|
|
order -= 1
|
|
except KeyError:
|
|
pass
|
|
|
|
order += 1
|
|
|
|
return out, order
|
|
|
|
def ps_to_garmin(ps,r):
|
|
payload = {
|
|
'workoutName': ps.name,
|
|
'sport': 'GENERIC',
|
|
'description':'Uploaded from Rowsandall.com',
|
|
'estimatedDurationInSecs':60*ps.approximate_duration,
|
|
'estimatedDistanceInMeters': ps.approximate_distance,
|
|
'workoutProvider': 'Rowsandall.com',
|
|
'workoutSourceId': 'Rowsandall.com',
|
|
}
|
|
|
|
steps = []
|
|
|
|
steplist = utils.ps_dict_order_dict(ps.steps)
|
|
|
|
|
|
while steplist:
|
|
step, steplist = utils.peel(steplist)
|
|
steps.append(step)
|
|
|
|
steps = list(reversed(steps))
|
|
|
|
lijst = []
|
|
i = 0
|
|
for step in steps:
|
|
gstep, i = step_to_garmin(step,i)
|
|
lijst.append(gstep)
|
|
|
|
payload['steps'] = lijst
|
|
|
|
garmin = OAuth1Session(oauth_data['client_id'],
|
|
client_secret=oauth_data['client_secret'],
|
|
resource_owner_key=r.garmintoken,
|
|
resource_owner_secret=r.garminrefreshtoken,
|
|
signature_method='HMAC-SHA1'
|
|
)
|
|
|
|
url = 'https://apis.garmin.com/training-api/workout/'
|
|
|
|
response = garmin.post(url,data=payload)
|
|
|
|
return response
|
|
|
|
|
|
def get_garmin_permissions(user):
|
|
r = Rower.objects.get(user=user)
|
|
if (r.garmintoken == '') or (r.garmintoken is None):
|
|
s = "Token doesn't exist. Need to authorize"
|
|
return custom_exception_handler(401,s)
|
|
|
|
garmin = OAuth1Session(oauth_data['client_id'],
|
|
client_secret=oauth_data['client_secret'],
|
|
resource_owner_key=r.garmintoken,
|
|
resource_owner_secret=r.garminrefreshtoken,
|
|
)
|
|
|
|
url = 'https://apis.garmin.com/userPermissions/'
|
|
|
|
result = garmin.get(url)
|
|
|
|
if result.status_code == 200:
|
|
return result.json()
|
|
|
|
return []
|
|
|
|
def garmin_session_create(ps,user):
|
|
if not ps.steps:
|
|
return 0
|
|
if not garmin_can_export_session(user):
|
|
return 0
|
|
|
|
garmindict = ps_to_garmin(ps)
|
|
|
|
r = Rower.objects.get(user=user)
|
|
if (r.garmintoken == '') or (r.garmintoken is None):
|
|
s = "Token doesn't exist. Need to authorize"
|
|
return custom_exception_handler(401,s)
|
|
|
|
garmin = OAuth1Session(oauth_data['client_id'],
|
|
client_secret=oauth_data['client_secret'],
|
|
resource_owner_key=r.garmintoken,
|
|
resource_owner_secret=r.garminrefreshtoken,
|
|
)
|
|
|
|
url = 'http://apis.garmin.com/training-api/schedule/'
|
|
|
|
response = garmin.post(url,data=garmindict)
|
|
|
|
if response.status_code != 200:
|
|
return 0
|
|
|
|
return response.json()['workoutId']
|
|
|
|
def garmin_getworkout(garminid,r,activity):
|
|
starttime = activity['startTimeInSeconds']
|
|
startdatetime = arrow.get(starttime)
|
|
try:
|
|
offset = activity['startTimeOffsetInSeconds']
|
|
except KeyError:
|
|
offset = 0
|
|
durationseconds = activity['durationInSeconds']
|
|
duration = dataprep.totaltime_sec_to_string(durationseconds)
|
|
activitytype = activity['activityType']
|
|
name = ''
|
|
date = startdatetime.date()
|
|
try:
|
|
distance = activity['distanceInMeters']
|
|
except KeyError:
|
|
distance = 0
|
|
try:
|
|
averagehr = activity['averageHeartRateInBeatsPerMinute']
|
|
maxhr = activity['maxHeartRateInBeatsPerMinute']
|
|
except KeyError:
|
|
averagehr = 0
|
|
maxhr = 0
|
|
try:
|
|
w = Workout.objects.get(uploadedtogarmin=garminid)
|
|
except Workout.DoesNotExist:
|
|
newcsvfile='media/garmin{code}_{importid}.csv'.format(
|
|
code=uuid4().hex[:16],
|
|
importid=garminid,
|
|
)
|
|
w = Workout(user=r,csvfilename=newcsvfile)
|
|
|
|
utc_offset = datetime.timedelta(seconds=offset)
|
|
now = datetime.datetime.now(pytz.utc)
|
|
zones = [tz.zone for tz in map(pytz.timezone, pytz.all_timezones_set)
|
|
if now.astimezone(tz).utcoffset() == utc_offset]
|
|
if r.defaulttimezone in zones:
|
|
thetimezone = r.defaulttimezone
|
|
elif len(zones):
|
|
thetimezone = zones[0]
|
|
else:
|
|
thetimezone = utc
|
|
|
|
startdatetime = datetime.datetime(
|
|
year=startdatetime.year,
|
|
month=startdatetime.month,
|
|
day=startdatetime.day,
|
|
hour=startdatetime.hour,
|
|
minute=startdatetime.minute,
|
|
second=startdatetime.second,
|
|
).astimezone(pytz.timezone(thetimezone))
|
|
|
|
w.startdatetime = startdatetime
|
|
w.starttime = w.startdatetime.time()
|
|
try:
|
|
w.duration = datetime.datetime.strptime(duration,"%H:%M:%S.%f").time()
|
|
except ValueError:
|
|
w.duration = datetime.datetime.strptime(duration,"%H:%M:%S")
|
|
try:
|
|
w.workouttype = mytypes.garminmappinginv[activitytype]
|
|
except KeyError:
|
|
w.workouttype = 'other'
|
|
w.name = name
|
|
w.date = date
|
|
w.distance = distance
|
|
w.uploadedtogarmin = garminid
|
|
|
|
w.save()
|
|
|
|
|
|
return w
|
|
|
|
def garmin_workouts_from_details(data):
|
|
activities = data['activityDetails']
|
|
for activity in activities:
|
|
try:
|
|
garmintoken = activity['userAccessToken']
|
|
except KeyError:
|
|
return 0
|
|
except TypeError:
|
|
return 0
|
|
try:
|
|
r = Rower.objects.get(garmintoken=garmintoken)
|
|
garminid = activity['summaryId'][:-7]
|
|
summary = activity['summary']
|
|
w = garmin_getworkout(garminid,r,summary)
|
|
samples = activity['samples']
|
|
df = pd.DataFrame(samples)
|
|
df.rename(columns=columns,inplace=True)
|
|
try:
|
|
pace = 500./df[' AverageBoatSpeed (m/s)']
|
|
except KeyError:
|
|
pace = 0
|
|
df[' AverageBoatSpeed (m/s)'] = 0
|
|
df[' Stroke500mPace (sec/500m)'] = pace
|
|
try:
|
|
spm = df[' Cadence (stokes/min)']
|
|
except KeyError:
|
|
df[' Cadence (stokes/min)'] = 0
|
|
df['cum_dist'] = df[' Horizontal (meters)']
|
|
try:
|
|
power = df[' Power (watts)']
|
|
except KeyError:
|
|
df[' Power (watts)'] = 0
|
|
df[' AverageDriveForce (lbs)'] = 0
|
|
df[' DriveLength (meters)'] = 0
|
|
df[' PeakDriveForce (lbs)'] = 1
|
|
df[' DriveTime (ms)'] = 0
|
|
|
|
rowdata = rowingdata(df=df)
|
|
rowdata.write_csv(w.csvfilename,gzip=True)
|
|
data = dataprep.dataprep(rowdata.df,id=w.id)
|
|
summary = rowdata.allstats()
|
|
w.summary=summary
|
|
w.uploadedtogarmin = garminid
|
|
w.save()
|
|
trimp,hrtss = dataprep.workout_trimp(w)
|
|
rscore,normp = dataprep.workout_rscore(w)
|
|
except Rower.DoesNotExist:
|
|
pass
|
|
|
|
return 1
|
|
|
|
def garmin_workouts_from_summaries(activities):
|
|
for activity in activities:
|
|
garmintoken = activity['userAccessToken']
|
|
try:
|
|
r = Rower.objects.get(garmintoken=garmintoken)
|
|
id = activity['summaryId']
|
|
w = garmin_getworkout(id,r,activity)
|
|
except Rower.DoesNotExist:
|
|
pass
|
|
|
|
return 1
|