first attempt on c2
This commit is contained in:
450
rowers/integrations/c2.py
Normal file
450
rowers/integrations/c2.py
Normal file
@@ -0,0 +1,450 @@
|
||||
from .integrations import SyncIntegration, NoTokenError
|
||||
from rowers.models import User, Rower, Workout, TombStone
|
||||
from rowingdata import rowingdata
|
||||
import numpy as np
|
||||
import datetime
|
||||
import json
|
||||
import urllib
|
||||
from rowers.utils import dologging, uniqify
|
||||
from django.utils import timezone
|
||||
import requests
|
||||
from pytz.exceptions import UnknownTimeZoneError
|
||||
import pytz
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET
|
||||
)
|
||||
|
||||
from rowers.tasks import (
|
||||
handle_c2_import_stroke_data, handle_c2_sync, handle_c2_async_workout,
|
||||
handle_c2_getworkout
|
||||
)
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('high')
|
||||
|
||||
from rowers.utils import myqueue, dologging
|
||||
|
||||
from requests import Request, Session
|
||||
|
||||
import rowers.mytypes as mytypes
|
||||
from rowers.rower_rules import is_workout_user, ispromember
|
||||
|
||||
def default(o): # pragma: no cover
|
||||
if isinstance(o, numpy.int64):
|
||||
return int(o)
|
||||
raise TypeError
|
||||
|
||||
# convert datetime object to seconds
|
||||
def makeseconds(t):
|
||||
seconds = t.hour*3600.+t.minute*60.+t.second+0.1*int(t.microsecond/1.e5)
|
||||
return seconds
|
||||
|
||||
# convert our weight class code to Concept2 weight class code
|
||||
|
||||
|
||||
def c2wc(weightclass):
|
||||
if (weightclass == "lwt"): # pragma: no cover
|
||||
res = "L"
|
||||
else:
|
||||
res = "H"
|
||||
|
||||
return res
|
||||
|
||||
|
||||
class C2Integration(SyncIntegration):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(C2Integration, self).__init__(self, *args, **kwargs)
|
||||
self.oauth_data = {
|
||||
'client_id': C2_CLIENT_ID,
|
||||
'client_secret': C2_CLIENT_SECRET,
|
||||
'redirect_uri': C2_REDIRECT_URI,
|
||||
'autorization_uri': "https://log.concept2.com/oauth/authorize",
|
||||
'content_type': 'application/x-www-form-urlencoded',
|
||||
'tokenname': 'c2token',
|
||||
'refreshtokenname': 'c2refreshtoken',
|
||||
'expirydatename': 'tokenexpirydate',
|
||||
'bearer_auth': True,
|
||||
'base_url': "https://log.concept2.com/oauth/access_token",
|
||||
'scope': 'write',
|
||||
}
|
||||
|
||||
def get_token(self, code, *args, **kwargs):
|
||||
messg = ''
|
||||
scope = "user:read,results:write"
|
||||
|
||||
post_data = {"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"redirect_uri": C2_REDIRECT_URI,
|
||||
"client_secret": C2_CLIENT_SECRET,
|
||||
"client_id": C2_CLIENT_ID,
|
||||
}
|
||||
headers = {'user-agent': 'sanderroosendaal'}
|
||||
url = "https://log.concept2.com/oauth/access_token"
|
||||
s = Session()
|
||||
req = Request('POST', url, data=post_data, headers=headers)
|
||||
|
||||
prepped = req.prepare()
|
||||
prepped.body += "&scope="
|
||||
prepped.body += scope
|
||||
|
||||
response = s.send(prepped)
|
||||
|
||||
token_json = response.json()
|
||||
|
||||
try:
|
||||
status_code = response.status_code
|
||||
except AttributeError: # pragma: no cover
|
||||
return (0, response.text)
|
||||
|
||||
if status_code == 200:
|
||||
thetoken = token_json['access_token']
|
||||
expires_in = token_json['expires_in']
|
||||
refresh_token = token_json['refresh_token']
|
||||
else: # pragma: no cover
|
||||
raise NoTokenError("No Token")
|
||||
|
||||
|
||||
return (thetoken, expires_in, refresh_token, messg)
|
||||
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
return super(C2Integration, self).open(*args, **kwargs)
|
||||
|
||||
def token_refresh(self, *args, **kwargs):
|
||||
return super(C2Integration, self).open(*args, **kwargs)
|
||||
|
||||
def make_authorization_url(self, *args, **kwargs):
|
||||
scope = "user:read,results:write"
|
||||
|
||||
params = {"client_id": C2_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"redirect_uri": C2_REDIRECT_URI}
|
||||
url = "https://log.concept2.com/oauth/authorize?" + \
|
||||
urllib.parse.urlencode(params)
|
||||
url += "&scope="+scope
|
||||
return url
|
||||
|
||||
def get_userid(self, *args, **kwargs):
|
||||
_ = self.open()
|
||||
access_token = self.rower.c2token
|
||||
authorizationstring = str('Bearer ' + access_token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://log.concept2.com/api/users/me"
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
except: # pragma: no cover
|
||||
return 0
|
||||
|
||||
try:
|
||||
me_json = response.json()
|
||||
except: # pragma: no cover
|
||||
return 0
|
||||
try:
|
||||
res = me_json['data']['id']
|
||||
except KeyError: # pragma: no cover
|
||||
return 0
|
||||
|
||||
return res
|
||||
|
||||
def createworkoutdata(self, w, *args, **kwargs) -> dict:
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except IOError: # pragma: no cover
|
||||
return 0
|
||||
|
||||
try:
|
||||
averagehr = int(row.df[' HRCur (bpm)'].mean())
|
||||
maxhr = int(row.df[' HRCur (bpm)'].max())
|
||||
except (ValueError, KeyError): # pragma: no cover
|
||||
averagehr = 0
|
||||
maxhr = 0
|
||||
|
||||
# Calculate intervalstats
|
||||
itime, idist, itype = row.intervalstats_values()
|
||||
lapnames = range(len(itime))
|
||||
nrintervals = len(itime)
|
||||
|
||||
try:
|
||||
lapnames = row.df[' lapIdx'].unique()
|
||||
except KeyError: # pragma: no cover
|
||||
pass
|
||||
|
||||
if len(lapnames) != nrintervals:
|
||||
newlapnames = []
|
||||
for name in lapnames:
|
||||
newlapnames += [name, name]
|
||||
lapnames = newlapnames
|
||||
intervaldata = []
|
||||
for i in range(nrintervals):
|
||||
if itime[i] > 0:
|
||||
mask = (row.df[' lapIdx'] == lapnames[i]) & (
|
||||
row.df[' WorkoutState'] == itype[i])
|
||||
try:
|
||||
spmav = int(row.df[' Cadence (stokes/min)']
|
||||
[mask].mean().astype(int))
|
||||
hrav = int(row.df[' HRCur (bpm)'][mask].mean().astype(int))
|
||||
except AttributeError: # pragma: no cover
|
||||
try:
|
||||
spmav = int(row.df[' Cadence (stokes/min)'][mask].mean())
|
||||
hrav = int(row.df[' HRCur (bpm)'][mask].mean())
|
||||
except ValueError:
|
||||
spmav = 0
|
||||
try:
|
||||
hrav = int(row.df[' HRCur (bpm)'][mask].mean())
|
||||
except ValuError:
|
||||
hrav = 0
|
||||
intervaldict = {
|
||||
'type': 'distance',
|
||||
'time': int(10*itime[i]),
|
||||
'distance': int(idist[i]),
|
||||
'heart_rate': {
|
||||
'average': hrav,
|
||||
},
|
||||
'stroke_rate': spmav,
|
||||
}
|
||||
intervaldata.append(intervaldict)
|
||||
|
||||
# adding diff, trying to see if this is valid
|
||||
t = 10*row.df.loc[:, 'TimeStamp (sec)'].values - \
|
||||
10*row.df.loc[:, 'TimeStamp (sec)'].iloc[0]
|
||||
try:
|
||||
t[0] = t[1]
|
||||
except IndexError: # pragma: no cover
|
||||
pass
|
||||
|
||||
d = 10*row.df.loc[:, ' Horizontal (meters)'].values
|
||||
try:
|
||||
d[0] = d[1]
|
||||
except IndexError: # pragma: no cover
|
||||
pass
|
||||
|
||||
p = abs(10*row.df.loc[:, ' Stroke500mPace (sec/500m)'].values)
|
||||
p = np.clip(p, 0, 3600)
|
||||
if w.workouttype == 'bike': # pragma: no cover
|
||||
p = 2.0*p
|
||||
|
||||
t = t.astype(int)
|
||||
d = d.astype(int)
|
||||
p = p.astype(int)
|
||||
spm = row.df[' Cadence (stokes/min)'].astype(int)
|
||||
|
||||
try:
|
||||
spm[0] = spm[1]
|
||||
except (KeyError, IndexError): # pragma: no cover
|
||||
spm = 0*t
|
||||
try:
|
||||
hr = row.df[' HRCur (bpm)'].astype(int)
|
||||
except ValueError: # pragma: no cover
|
||||
hr = 0*d
|
||||
stroke_data = []
|
||||
|
||||
t = t.tolist()
|
||||
d = d.tolist()
|
||||
p = p.tolist()
|
||||
spm = spm.tolist()
|
||||
hr = hr.tolist()
|
||||
|
||||
for i in range(len(t)):
|
||||
thisrecord = {"t": t[i],
|
||||
"d": d[i],
|
||||
"p": p[i],
|
||||
"spm": spm[i],
|
||||
"hr": hr[i]}
|
||||
stroke_data.append(thisrecord)
|
||||
|
||||
try:
|
||||
durationstr = datetime.datetime.strptime(
|
||||
str(w.duration), "%H:%M:%S.%f")
|
||||
except ValueError:
|
||||
durationstr = datetime.datetime.strptime(str(w.duration), "%H:%M:%S")
|
||||
|
||||
workouttype = w.workouttype
|
||||
if workouttype in mytypes.otwtypes:
|
||||
workouttype = 'water'
|
||||
|
||||
if w.timezone == 'tzutc()': # pragma: no cover
|
||||
w.timezone = 'UTC'
|
||||
w.save()
|
||||
|
||||
try:
|
||||
wendtime = w.startdatetime.astimezone(pytz.timezone(
|
||||
w.timezone))+datetime.timedelta(seconds=makeseconds(durationstr))
|
||||
except UnknownTimeZoneError:
|
||||
wendtime = w.startdatetime.astimezone(pytz.utc)+datetime.timedelta(seconds=makeseconds(durationstr))
|
||||
|
||||
data = {
|
||||
"type": mytypes.c2mapping[workouttype],
|
||||
# w.startdatetime.isoformat(),
|
||||
"date": wendtime.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
"stroke_count": int(row.stroke_count),
|
||||
"timezone": w.timezone,
|
||||
"distance": int(w.distance),
|
||||
"time": int(10*makeseconds(durationstr)),
|
||||
"weight_class": c2wc(w.weightcategory),
|
||||
"comments": w.notes,
|
||||
'stroke_rate': int(row.df[' Cadence (stokes/min)'].mean()),
|
||||
'drag_factor': int(row.dragfactor),
|
||||
"heart_rate": {
|
||||
"average": averagehr,
|
||||
"max": maxhr,
|
||||
},
|
||||
"stroke_data": stroke_data,
|
||||
'workout': {
|
||||
'splits': intervaldata,
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def workout_export(self, workout, *args, **kwargs) -> str:
|
||||
try:
|
||||
if mytypes.c2mapping[workout.workouttype] is None: # pragma: no cover
|
||||
return "0"
|
||||
except KeyError: # pragma: no cover
|
||||
return "0"
|
||||
|
||||
_ = self.open()
|
||||
r = self.rower
|
||||
user = self.user
|
||||
|
||||
if (is_workout_user(user, workout)):
|
||||
c2userid = self.get_userid()
|
||||
if not c2userid: # pragma: no cover
|
||||
raise NoTokenError("User has no token")
|
||||
|
||||
dologging('debuglog.log',
|
||||
'Upload to C2 user {userid}'.format(userid=user.id))
|
||||
data = self.createworkoutdata(workout)
|
||||
dologging('c2_log.log', json.dumps(data))
|
||||
|
||||
if data == 0: # pragma: no cover
|
||||
return 0
|
||||
|
||||
authorizationstring = str('Bearer ' + r.c2token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
url = "https://log.concept2.com/api/users/%s/results" % (c2userid)
|
||||
|
||||
_ = myqueue(queue,
|
||||
handle_c2_sync,
|
||||
workout.id,
|
||||
url,
|
||||
headers,
|
||||
json.dumps(data, default=default))
|
||||
c2id = 0
|
||||
|
||||
return c2id
|
||||
|
||||
|
||||
|
||||
def get_workout(self, id):
|
||||
_ = self.open()
|
||||
_ = myqueue(queuehigh,
|
||||
handle_c2_getworkout,
|
||||
self.user.id,
|
||||
self.rower.c2token,
|
||||
id,
|
||||
self.rower.defaulttimezone)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
def get_workouts(self, *args, **kwargs):
|
||||
page = kwargs.get('page',1)
|
||||
|
||||
try:
|
||||
_ = self.open()
|
||||
except NoTokenError:
|
||||
return 0
|
||||
|
||||
# this part to get_workout_list
|
||||
workouts = self.get_workout_list(page=page)
|
||||
|
||||
counter = 0
|
||||
for workout in workouts:
|
||||
c2id = workout['id']
|
||||
if workout['new'] == 'NEW':
|
||||
self.get_workout(c2id)
|
||||
|
||||
return 1
|
||||
|
||||
# should be unified to workout list
|
||||
def get_workout_list(self, *args, **kwargs):
|
||||
page = kwargs.get('page',1)
|
||||
r = self.rower
|
||||
if (r.c2token == '') or (r.c2token is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401, s)
|
||||
elif (timezone.now() > r.tokenexpirydate): # pragma: no cover
|
||||
s = "Token expired. Needs to refresh."
|
||||
|
||||
return custom_exception_handler(401, s)
|
||||
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.c2token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://log.concept2.com/api/users/me/results"
|
||||
url += "?page={page}".format(page=page)
|
||||
|
||||
res = requests.get(url, headers=headers)
|
||||
|
||||
if (res.status_code != 200): # pragma: no cover
|
||||
return []
|
||||
|
||||
workouts = []
|
||||
c2ids = [item['id'] for item in res.json()['data']]
|
||||
knownc2ids = uniqify([
|
||||
w.uploadedtoc2 for w in Workout.objects.filter(user=self.rower)
|
||||
])
|
||||
tombstones = [
|
||||
t.uploadedtoc2 for t in TombStone.objects.filter(user=self.rower)
|
||||
]
|
||||
parkedids = []
|
||||
try:
|
||||
with open('c2blocked.json', 'r') as c2blocked:
|
||||
jsondata = json.load(c2blocked)
|
||||
parkedids = jsondata['ids']
|
||||
except: # pragma: no cover
|
||||
pass
|
||||
|
||||
knownc2ids = uniqify(knownc2ids+tombstones+parkedids)
|
||||
for item in res.json()['data']:
|
||||
d = item['distance']
|
||||
i = item['id']
|
||||
ttot = item['time_formatted']
|
||||
s = item['date']
|
||||
r = item['type']
|
||||
s2 = item['source']
|
||||
c = item['comments']
|
||||
if i in knownc2ids:
|
||||
nnn = ''
|
||||
else: # pragma: no cover
|
||||
nnn = 'NEW'
|
||||
keys = ['id', 'distance', 'duration', 'starttime',
|
||||
'rowtype', 'source', 'comment', 'new']
|
||||
values = [i, d, ttot, s, r, s2, c, nnn]
|
||||
ress = dict(zip(keys, values))
|
||||
workouts.append(ress)
|
||||
|
||||
return workouts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# just as a quick test during development
|
||||
u = User.objects.get(id=1)
|
||||
|
||||
c2_integration_1 = C2Integration(u)
|
||||
Reference in New Issue
Block a user