diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index 1ef76461..5edd13d6 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -9,6 +9,7 @@ import cgi import requests import requests.auth import json +import iso8601 from django.utils import timezone from datetime import datetime from datetime import timedelta @@ -21,8 +22,8 @@ from django.conf import settings from django.contrib.auth import authenticate, login, logout from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required - - +import dataprep +import pytz from rowingdata import rowingdata import pandas as pd import numpy as np @@ -34,6 +35,8 @@ from requests import Request, Session from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET +from rowers.tasks import handle_c2_import_stroke_data + # Custom error class - to raise a NoTokenError class C2NoTokenError(Exception): def __init__(self,value): @@ -82,7 +85,96 @@ def c2_open(user): return thetoken +def add_stroke_data(user,c2id,workoutid,startdatetime,csvfilename): + r = Rower.objects.get(user=user) + if (r.c2token == '') or (r.c2token is None): + return custom_exception_handler(401,s) + s = "Token doesn't exist. Need to authorize" + elif (timezone.now()>r.tokenexpirydate): + s = "Token expired. Needs to refresh." + return custom_exception_handler(401,s) + else: + # ready to fetch. Hurray + res = handle_c2_import_stroke_data(r.c2token, + c2id, + workoutid, + startdatetime, + csvfilename) + + return 1 + + +# get workout metrics, then relay stroke data to an asynchronous task +def create_async_workout(user,c2id): + + res = get_c2_workout(user,c2id) + if (res.status_code == 200): + data = res.json()['data'] + splitdata = None + if 'workout' in data: + if 'splits' in data['workout']: + splitdata = data['workout']['splits'] + if 'intervals' in data['workout']: + splitdata = data['workout']['intervals'] + + distance = data['distance'] + c2id = data['id'] + workouttype = data['type'] + verified = data['verified'] + startdatetime = iso8601.parse_date(data['date']) + weightclass = data['weight_class'] + weightcategory = 'hwt' + if weightclass == "L": + weightcategory = 'lwt' + + # Create CSV file name and save data to CSV file + csvfilename ='media/Import_'+str(c2id)+'.csv.gz' + + totaltime = data['time']/10. + duration = dataprep.totaltime_sec_to_string(totaltime) + + try: + timezone_str = tz(data['timezone']) + except: + timezone_str = 'UTC' + + workoutdate = startdatetime.astimezone( + pytz.timezone(timezone_str) + ).strftime('%Y-%m-%d') + starttime = startdatetime.astimezone( + pytz.timezone(timezone_str) + ).strftime('%H:%M:%S') + + r = Rower.objects.get(user=user) + + + w = Workout( + user=r, + workouttype = workouttype, + name = 'Imported workout', + date = workoutdate, + starttime = starttime, + startdatetime = startdatetime, + timezone = timezone_str, + duration = duration, + distance=distance, + weightcategory = weightcategory, + uploadedtoc2 = c2id, + csvfilename = csvfilename, + notes = 'imported from Concept2 log' + ) + + w.save() + + # Check if workout has stroke data, and get the stroke data + if data['stroke_data']: + result = add_stroke_data(user,c2id,w.id,startdatetime,csvfilename) + else: + # create synthetic stroke data + pass + + return w.id # convert datetime object to seconds def makeseconds(t): diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 3d177657..ae83c7c2 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -723,9 +723,35 @@ def create_row_df(r,distance,duration,startdatetime, return (id, message) +def totaltime_sec_to_string(totaltime): + hours = int(totaltime / 3600.) + if hours > 23: + message = 'Warning: The workout duration was longer than 23 hours. ' + hours = 23 + + minutes = int((totaltime - 3600. * hours) / 60.) + if minutes > 59: + minutes = 59 + if not message: + message = 'Warning: there is something wrong with the workout duration' + + seconds = int(totaltime - 3600. * hours - 60. * minutes) + if seconds > 59: + seconds = 59 + if not message: + message = 'Warning: there is something wrong with the workout duration' + + tenths = int(10 * (totaltime - 3600. * hours - 60. * minutes - seconds)) + if tenths > 9: + tenths = 9 + if not message: + message = 'Warning: there is something wrong with the workout duration' + + duration = "%s:%s:%s.%s" % (hours, minutes, seconds, tenths) + + return duration + # Processes painsled CSV file to database - - def save_workout_database(f2, r, dosmooth=True, workouttype='rower', dosummary=True, title='Workout', workoutsource='unknown', @@ -838,30 +864,6 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', if np.isnan(totaltime): totaltime = 0 - hours = int(totaltime / 3600.) - if hours > 23: - message = 'Warning: The workout duration was longer than 23 hours. ' - hours = 23 - - minutes = int((totaltime - 3600. * hours) / 60.) - if minutes > 59: - minutes = 59 - if not message: - message = 'Warning: there is something wrong with the workout duration' - - seconds = int(totaltime - 3600. * hours - 60. * minutes) - if seconds > 59: - seconds = 59 - if not message: - message = 'Warning: there is something wrong with the workout duration' - - tenths = int(10 * (totaltime - 3600. * hours - 60. * minutes - seconds)) - if tenths > 9: - tenths = 9 - if not message: - message = 'Warning: there is something wrong with the workout duration' - - duration = "%s:%s:%s.%s" % (hours, minutes, seconds, tenths) if dosummary: summary = row.allstats() @@ -897,6 +899,8 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', except KeyError: timezone_str = r.defaulttimezone + duration = totaltime_sec_to_string(totaltime) + workoutdate = workoutstartdatetime.astimezone( pytz.timezone(timezone_str) ).strftime('%Y-%m-%d') diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py index ef840b15..f085c945 100644 --- a/rowers/dataprepnodjango.py +++ b/rowers/dataprepnodjango.py @@ -1,10 +1,10 @@ # This is Data prep used for testing purposes (no Django environment) # Uses the debug SQLite database for stroke data from rowingdata import rowingdata as rrdata - +from rowingdata import make_cumvalues from rowingdata import rower as rrower from rowingdata import main as rmain - +from time import strftime from pandas import DataFrame,Series import pandas as pd @@ -131,6 +131,89 @@ def rdata(file,rower=rrower()): return res +# Saves C2 stroke data to CSV and database +def add_c2_stroke_data_db(strokedata,workoutid,starttimeunix,csvfilename, + debug=False): + + res = make_cumvalues(0.1*strokedata['t']) + cum_time = res[0] + lapidx = res[1] + + unixtime = cum_time+starttimeunix + # unixtime[0] = starttimeunix + seconds = 0.1*strokedata.ix[:,'t'] + + nr_rows = len(unixtime) + + try: + latcoord = strokedata.ix[:,'lat'] + loncoord = strokedata.ix[:,'lon'] + except: + latcoord = np.zeros(nr_rows) + loncoord = np.zeros(nr_rows) + + + try: + strokelength = strokedata.ix[:,'strokelength'] + except: + strokelength = np.zeros(nr_rows) + + dist2 = 0.1*strokedata.ix[:,'d'] + + try: + spm = strokedata.ix[:,'spm'] + except KeyError: + spm = 0*dist2 + + try: + hr = strokedata.ix[:,'hr'] + except KeyError: + hr = 0*spm + pace = strokedata.ix[:,'p']/10. + pace = np.clip(pace,0,1e4) + pace = pace.replace(0,300) + + velo = 500./pace + + power = 2.8*velo**3 + + # save csv + # Create data frame with all necessary data to write to csv + df = pd.DataFrame({'TimeStamp (sec)':unixtime, + ' Horizontal (meters)': dist2, + ' Cadence (stokes/min)':spm, + ' HRCur (bpm)':hr, + ' longitude':loncoord, + ' latitude':latcoord, + ' Stroke500mPace (sec/500m)':pace, + ' Power (watts)':power, + ' DragFactor':np.zeros(nr_rows), + ' DriveLength (meters)':np.zeros(nr_rows), + ' StrokeDistance (meters)':strokelength, + ' DriveTime (ms)':np.zeros(nr_rows), + ' StrokeRecoveryTime (ms)':np.zeros(nr_rows), + ' AverageDriveForce (lbs)':np.zeros(nr_rows), + ' PeakDriveForce (lbs)':np.zeros(nr_rows), + ' lapIdx':lapidx, + ' ElapsedTime (sec)':seconds, + 'cum_dist': dist2 + }) + + + df.sort_values(by='TimeStamp (sec)',ascending=True) + + timestr = strftime("%Y%m%d-%H%M%S") + + + # Create CSV file name and save data to CSV file + + res = df.to_csv(csvfilename,index_label='index', + compression='gzip') + + data = dataprep(df,id=workoutid,bands=False,debug=debug) + + return data + # Processes painsled CSV file to database def save_workout_database(f2,r,dosmooth=True,workouttype='rower', dosummary=True,title='Workout', diff --git a/rowers/tasks.py b/rowers/tasks.py index 05ba597e..258a34a9 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -32,7 +32,8 @@ from utils import deserialize_list from rowers.dataprepnodjango import ( update_strokedata, new_workout_from_file, getsmallrowdata_db, updatecpdata_sql, - update_agegroup_db,fitnessmetric_to_sql + update_agegroup_db,fitnessmetric_to_sql, + add_c2_stroke_data_db ) from django.core.mail import send_mail, EmailMessage @@ -40,8 +41,9 @@ from django.db.utils import OperationalError import datautils import utils - +import requests import longtask +import arrow # testing task @@ -50,6 +52,33 @@ import longtask def add(x, y): return x + y + +@app.task +def handle_c2_import_stroke_data(c2token, + c2id,workoutid, + startdatetime, + csvfilename,debug=True): + authorizationstring = str('Bearer ' + c2token) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} + url = "https://log.concept2.com/api/users/me/results/"+str(c2id)+"/strokes" + s = requests.get(url,headers=headers) + if s.status_code == 200: + strokedata = pd.DataFrame.from_dict(s.json()['data']) + starttimeunix = arrow.get(startdatetime).timestamp + result = add_c2_stroke_data_db( + strokedata,workoutid,starttimeunix, + csvfilename,debug=debug, + ) + + + return 1 + else: + return 0 + + return 0 + def getagegrouprecord(age,sex='male',weightcategory='hwt', distance=2000,duration=None,indf=pd.DataFrame()): diff --git a/rowers/views.py b/rowers/views.py index 2e3b967e..25f8475d 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -9115,34 +9115,7 @@ def workout_getc2workout_all(request,page=1,message=""): ]) newids = [c2id for c2id in c2ids if not c2id in knownc2ids] for c2id in newids: - res = c2stuff.get_c2_workout(request.user,c2id) - if (res.status_code == 200): - data = res.json()['data'] - splitdata = None - if 'workout' in data: - if 'splits' in data['workout']: - splitdata = data['workout']['splits'] - if 'intervals' in data['workout']: - splitdata = data['workout']['intervals'] - - # Check if workout has stroke data, and get the stroke data - if data['stroke_data']: - res2 = c2stuff.get_c2_workout_strokes(request.user,c2id) - # We have stroke data - if res2.status_code == 200: - strokedata = pd.DataFrame.from_dict(res2.json()['data']) - # create the workout - try: - id,message = add_workout_from_strokedata( - request.user,c2id,data,strokedata, - source='c2') - w = Workout.objects.get(id=id) - w.uploadedtoc2=c2id - w.save() - if message: - messages.error(request,message) - except KeyError: - pass + workoutid = c2stuff.create_async_workout(request.user,c2id) url = reverse(workouts_view) return HttpResponseRedirect(url)