From d4f311c61211f6ac5e9539cd0d1eeefa8b759cbe Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 31 Oct 2019 11:45:44 +0100 Subject: [PATCH] adding splits to C2 workout export --- rowers/c2stuff.py | 112 +++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index 5c71d85a..82f199f8 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -120,7 +120,7 @@ def c2_open(user): if (timezone.now()>r.tokenexpirydate): res = rower_c2_token_refresh(user) if res == None: - raise NoTokenError("User has no token") + raise NoTokenError("User has no token") if res[0] != None: thetoken = res[0] else: @@ -156,7 +156,7 @@ def get_c2_workouts(rower): if not isprorower(rower): return 0 - + try: thetoken = c2_open(rower.user) except NoTokenError: @@ -181,7 +181,7 @@ def get_c2_workouts(rower): ] knownc2ids = uniqify(knownc2ids+tombstones) - + newids = [c2id for c2id in c2ids if not c2id in knownc2ids] for c2id in newids: @@ -267,7 +267,7 @@ def c2wc(weightclass): # Concept2 logbook sends over split data for each interval # We use it here to generate a custom summary -# Some users complained about small differences +# Some users complained about small differences def summaryfromsplitdata(splitdata,data,filename,sep='|'): totaldist = data['distance'] @@ -310,17 +310,17 @@ def summaryfromsplitdata(splitdata,data,filename,sep='|'): restvelo = restdistance/resttime except (ZeroDivisionError,OverflowError): restvelo = 0 - + restpower = 2.8*restvelo**(3.0) try: avgdps = totaldist/data['stroke_count'] except (ZeroDivisionError,OverflowError,KeyError): avgdps = 0 - + from rowingdata import summarystring,workstring,interval_string - + sums = summarystring(totaldist,totaltime,avgpace,spm,avghr,maxhr, avgdps,avgpower,readFile=filename, separator=sep) @@ -336,16 +336,16 @@ def summaryfromsplitdata(splitdata,data,filename,sep='|'): sums += '#-{sep}SDist{sep}-Split-{sep}-SPace-{sep}-Pwr-{sep}SPM-{sep}AvgHR{sep}MaxHR{sep}DPS-\n'.format( sep=sep ) - + intervalnr=0 sa = [] results = [] - + try: timebased = data['workout_type'] in ['FixedTimeSplits','FixedTimeInterval'] except KeyError: timebased = False - + for interval in splitdata: idist = interval['distance'] itime = interval['time']/10. @@ -373,7 +373,7 @@ def summaryfromsplitdata(splitdata,data,filename,sep='|'): if timebased: iarr = [itime,'seconds','work'] resarr = [idist] - + if irest_time > 0: iarr += [irest_time,'seconds','rest'] try: @@ -390,7 +390,7 @@ def summaryfromsplitdata(splitdata,data,filename,sep='|'): else: ivelo = 0 ipower = 0 - + sums += interval_string(intervalnr,idist,itime,ipace,ispm, iavghr,imaxhr,0,ipower,separator=sep) intervalnr+=1 @@ -398,14 +398,14 @@ def summaryfromsplitdata(splitdata,data,filename,sep='|'): return sums,sa,results # Not used now. Could be used to add workout split data to Concept2 -# logbook but needs to be reviewed. +# logbook but needs to be reviewed. def createc2workoutdata_as_splits(w): filename = w.csvfilename row = rowingdata(csvfile=filename) # resize per minute df = row.df.groupby(lambda x:x/60).mean() - + averagehr = int(df[' HRCur (bpm)'].mean()) maxhr = int(df[' HRCur (bpm)'].max()) @@ -443,7 +443,7 @@ def createc2workoutdata_as_splits(w): wtype = w.workouttype if wtype in otwtypes: wtype = 'water' - + data = { "type": wtype, "date": w.startdatetime.isoformat(), @@ -467,7 +467,7 @@ def createc2workoutdata_as_splits(w): def createc2workoutdata(w): filename = w.csvfilename try: - row = rowingdata(filename) + row = rowingdata(csvfile=filename) except IOError: return 0 @@ -478,19 +478,45 @@ def createc2workoutdata(w): averagehr = 0 maxhr = 0 + # Calculate intervalstats + itime, idist, itype = row.intervalstats_values() + lapnames = row.df[' lapIdx'].unique() + nrintervals = len(itime) + 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]) + spmav = int(row.df[' Cadence (stokes/min)'][mask].mean().astype(int)) + hrav = int(row.df[' HRCur (bpm)'][mask].mean().astype(int)) + 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: pass - + d = 10*row.df.loc[:,' Horizontal (meters)'].values try: d[0] = d[1] except IndexError: pass - + p = abs(10*row.df.loc[:,' Stroke500mPace (sec/500m)'].values) p = np.clip(p,0,3600) if w.workouttype == 'bike': @@ -516,7 +542,7 @@ def createc2workoutdata(w): p = p.tolist() spm = spm.tolist() hr = hr.tolist() - + for i in range(len(t)): thisrecord = {"t":t[i], "d":d[i], @@ -547,14 +573,18 @@ def createc2workoutdata(w): "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 # Refresh Concept2 authorization token @@ -570,7 +600,7 @@ def do_refresh_token(refreshtoken): 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 @@ -672,7 +702,7 @@ def get_workout(user,c2id): url = "https://log.concept2.com/api/users/me/results/"+str(c2id) s = requests.get(url,headers=headers) - + data = s.json()['data'] splitdata = None @@ -694,7 +724,7 @@ def get_workout(user,c2id): strokedata = pd.DataFrame() else: strokedata = pd.DataFrame() - + return data,strokedata # Get stroke data belonging to C2 ID @@ -740,7 +770,7 @@ def get_c2_workout_list(user,page=1): return s - + # Get username, having access token. # Handy for checking if the API access is working def get_username(access_token): @@ -751,7 +781,7 @@ def get_username(access_token): import urllib url = "https://log.concept2.com/api/users/me" response = requests.get(url,headers=headers) - + me_json = response.json() @@ -776,14 +806,14 @@ def get_userid(access_token): response = requests.get(url,headers=headers) except: return 0 - + me_json = response.json() try: res = me_json['data']['id'] except KeyError: res = 0 - + return res # For debugging purposes @@ -799,7 +829,7 @@ def process_callback(request): return HttpResponse("got a user name: %s" % username) def default(o): - if isinstance(o, numpy.int64): return int(o) + if isinstance(o, numpy.int64): return int(o) raise TypeError # Uploading workout @@ -810,7 +840,7 @@ def workout_c2_upload(user,w): return "This workout type cannot be uploaded to Concept2",0 except KeyError: return "This workout type cannot be uploaded to Concept2",0 - + thetoken = c2_open(user) r = Rower.objects.get(user=user) @@ -825,7 +855,7 @@ def workout_c2_upload(user,w): if data == 0: return "Error: No data file. Contact info@rowsandall.com if the problem persists",0 - + authorizationstring = str('Bearer ' + r.c2token) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', @@ -849,7 +879,7 @@ def workout_c2_upload(user,w): else: message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text c2id = 0 - + return message,c2id @@ -882,7 +912,7 @@ def add_workout_from_data(user,importid,data,strokedata, workouttype = mytypes.c2mappinginv[data['type']] except KeyError: workouttype = 'rower' - + if workouttype not in [x[0] for x in Workout.workouttypes]: workouttype = 'other' try: @@ -902,14 +932,14 @@ def add_workout_from_data(user,importid,data,strokedata, rowdatetime = iso8601.parse_date(data['start_date']) except ParseError: rowdatetime = iso8601.parse_date(data['date']) - - + + try: c2intervaltype = data['workout_type'] - + except KeyError: c2intervaltype = '' - + try: title = data['name'] except KeyError: @@ -951,7 +981,7 @@ def add_workout_from_data(user,importid,data,strokedata, spm = strokedata.loc[:,'spm'] except KeyError: spm = 0*dist2 - + try: hr = strokedata.loc[:,'hr'] except KeyError: @@ -966,7 +996,7 @@ def add_workout_from_data(user,importid,data,strokedata, velo = 1000./pace pace = 500./velo - + # save csv # Create data frame with all necessary data to write to csv df = pd.DataFrame({'TimeStamp (sec)':unixtime, @@ -988,9 +1018,9 @@ def add_workout_from_data(user,importid,data,strokedata, ' ElapsedTime (sec)':seconds }) - + df.sort_values(by='TimeStamp (sec)',ascending=True) - + timestr = strftime("%Y%m%d-%H%M%S") @@ -1024,6 +1054,6 @@ def add_workout_from_data(user,importid,data,strokedata, dosummary=True ) - + return id,message