Private
Public Access
1
0
Files
rowsandall/rowers/integrations/c2.py
2024-12-10 08:00:41 +01:00

452 lines
14 KiB
Python

from .integrations import SyncIntegration, NoTokenError, create_or_update_syncrecord, get_known_ids
from rowers.models import User, Rower, Workout, TombStone
from django.db.utils import IntegrityError
from rowingdata import rowingdata, rowingdata_pl
import numpy as np
import datetime
import json
import urllib
from rowers.utils import dologging, uniqify, custom_exception_handler
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__(*args, **kwargs)
self.oauth_data = {
'client_id': C2_CLIENT_ID,
'client_secret': C2_CLIENT_SECRET,
'redirect_uri': C2_REDIRECT_URI,
'authorization_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_name(self):
return "Concept2 Logbook"
def get_shortname(self):
return "c2"
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).token_refresh(*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
except:
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 ValueError:
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('c2_log.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, *args, **kwargs):
_ = self.open()
r = self.rower
record = create_or_update_syncrecord(r, None, c2id=id)
_ = 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)
count = 0
for workout in workouts:
c2id = workout['id']
if workout['new'] == 'NEW':
self.get_workout(c2id)
count+= 1
return count
# 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 = get_known_ids(r, 'c2id')
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', 'name', 'new']
values = [i, d, ttot, s, r, s2, c, nnn]
ress = dict(zip(keys, values))
workouts.append(ress)
return workouts