# All the functionality needed to connect to Runkeeper # Python import oauth2 as oauth import cgi import requests import requests.auth import json from django.utils import timezone from datetime import datetime import numpy as np from dateutil import parser import time import math from math import sin,cos,atan2,sqrt import os,sys # Django from django.shortcuts import render_to_response from django.http import HttpResponseRedirect, HttpResponse,JsonResponse 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 # Project # from .models import Profile from rowingdata import rowingdata import pandas as pd from rowers.models import Rower,Workout,checkworkoutuser from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET,RUNKEEPER_REDIRECT_URI, ) # Custom error class - to raise a NoTokenError class RunKeeperNoTokenError(Exception): def __init__(self,value): self.value=value def __str__(self): return repr(self.value) # Exponentially weighted moving average # Used for data smoothing of the jagged data obtained by Strava # See bitbucket issue 72 def ewmovingaverage(interval,window_size): # Experimental code using Exponential Weighted moving average try: intervaldf = pd.DataFrame({'v':interval}) idf_ewma1 = intervaldf.ewm(span=window_size) idf_ewma2 = intervaldf[::-1].ewm(span=window_size) i_ewma1 = idf_ewma1.mean().ix[:,'v'] i_ewma2 = idf_ewma2.mean().ix[:,'v'] interval2 = np.vstack((i_ewma1,i_ewma2[::-1])) interval2 = np.mean( interval2, axis=0) # average except ValueError: interval2 = interval return interval2 from utils import geo_distance # Custom exception handler, returns a 401 HTTP message # with exception details in the json data def custom_exception_handler(exc,message): response = { "errors": [ { "code": str(exc), "detail": message, } ] } res = HttpResponse(message) res.status_code = 401 res.json = json.dumps(response) return res # Checks if user has SportTracks token, renews them if they are expired def runkeeper_open(user): r = Rower.objects.get(user=user) if (r.runkeepertoken == '') or (r.runkeepertoken is None): s = "Token doesn't exist. Need to authorize" raise RunKeeperNoTokenError("User has no token") else: thetoken = r.runkeepertoken return thetoken # Exchange access code for long-lived access token def get_token(code): client_auth = requests.auth.HTTPBasicAuth(RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET) post_data = {"grant_type": "authorization_code", "code": code, "redirect_uri": RUNKEEPER_REDIRECT_URI, "client_secret": RUNKEEPER_CLIENT_SECRET, "client_id":RUNKEEPER_CLIENT_ID, } headers = {'user-agent': 'sanderroosendaal'} response = requests.post("https://runkeeper.com/apps/token", data=post_data, headers=headers) try: token_json = response.json() thetoken = token_json['access_token'] except KeyError: thetoken = 0 return thetoken # Make authorization URL including random string def make_authorization_url(request): # Generate a random string for the state parameter # Save it for use later to prevent xsrf attacks from uuid import uuid4 state = str(uuid4()) params = {"client_id": RUNKEEPER_CLIENT_ID, "response_type": "code", "redirect_uri": RUNKEEPER_REDIRECT_URI, } import urllib url = "https://www.runkeeper.com/opps/authorize" +urllib.urlencode(params) return HttpResponseRedirect(url) # Get list of workouts available on Runkeeper def get_runkeeper_workout_list(user): r = Rower.objects.get(user=user) if (r.runkeepertoken == '') or (r.runkeepertoken is None): s = "Token doesn't exist. Need to authorize" return custom_exception_handler(401,s) else: # ready to fetch. Hurray authorizationstring = str('Bearer ' + r.runkeepertoken) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} url = "https://api.runkeeper.com/fitnessActivities" s = requests.get(url,headers=headers) return s # Get workout summary data by Runkeeper ID def get_runkeeper_workout(user,runkeeperid): r = Rower.objects.get(user=user) if (r.runkeepertoken == '') or (r.runkeepertoken is None): return custom_exception_handler(401,s) s = "Token doesn't exist. Need to authorize" else: # ready to fetch. Hurray authorizationstring = str('Bearer ' + r.runkeepertoken) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} url = "https://api.runkeeper.com/fitnessActivities/"+str(runkeeperid) s = requests.get(url,headers=headers) return s # Create Workout Data for upload to SportTracks def createrunkeeperworkoutdata(w): filename = w.csvfilename try: row = rowingdata(filename) except: return 0 averagehr = int(row.df[' HRCur (bpm)'].mean()) maxhr = int(row.df[' HRCur (bpm)'].max()) duration = w.duration.hour*3600 duration += w.duration.minute*60 duration += w.duration.second duration += +1.0e-6*w.duration.microsecond # adding diff, trying to see if this is valid #t = row.df.ix[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)'] t = row.df.ix[:,'TimeStamp (sec)'].values-row.df.ix[0,'TimeStamp (sec)'] t[0] = t[1] d = row.df.ix[:,'cum_dist'].values d[0] = d[1] t = t.astype(int) d = d.astype(int) spm = row.df[' Cadence (stokes/min)'].astype(int) spm[0] = spm[1] hr = row.df[' HRCur (bpm)'].astype(int) haslatlon=1 try: lat = row.df[' latitude'].values lon = row.df[' longitude'].values if not lat.std() and not lon.std(): haslatlon = 0 except KeyError: haslatlon = 0 # path data if haslatlon: locdata = [] for e in zip(t,lat,lon): point = {'timestamp':e[0], 'latitude':e[1], 'longitude':e[2], 'altitude':0, "type":"gps"} locdata.append(point) hrdata = [] for e in zip(t,hr): point = {'timestamp':e[0], 'heart_rate':e[1] } hrdata.append(point) distancedata = [] for e in zip(t,d): point = {'timestamp':e[0], 'distance':e[1] } distancedata.append(point) start_time = w.startdatetime.strftime("%a, %d %b %Y %H:%M:%S") try: newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' except TypeError: newnotes = 'from '+w.workoutsource+' via rowsandall.com' if haslatlon: data = { "type": "Rowing", "start_time": start_time, "total_distance": int(w.distance), "duration": duration, "notes": newnotes, "average_heart_rate": averagehr, "path": locdata, "distance": distancedata, "heart_rate": hrdata, "post_to_twitter":"false", "post_to_facebook":"false", } else: data = { "type": "Rowing", "start_time": start_time, "total_distance": int(w.distance), "duration": duration, "notes": newnotes, "avg_heartrate": averagehr, "distance": distancedata, "heart_rate": hrdata, "post_to_twitter":"false", "post_to_facebook":"false", } return data # Obtain Runkeeper Workout ID from the response returned on successful # upload def getidfromresponse(response): uri = response.headers["Location"] id = uri[len(uri)-9:] return int(id) def geturifromid(access_token,id): authorizationstring = str('Bearer ' + access_token) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} import urllib url = "https://api.runkeeper.com/fitnessActivities/"+str(id) response = requests.get(url,headers=headers) try: me_json = response.json() except: return '' try: res = me_json['uri'] except KeyError: res = '' return res # Get user id, having access token # Handy for checking if the API access is working def get_userid(access_token): authorizationstring = str('Bearer ' + access_token) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} import urllib url = "https://api.runkeeper.com/user" response = requests.get(url,headers=headers) try: me_json = response.json() except: return '' try: res = me_json['userID'] except KeyError: res = '' return str(res) def workout_runkeeper_upload(user,w): message = "Uploading to Runkeeper" rkid = 0 r = w.user thetoken = runkeeper_open(r.user) # ready to upload. Hurray if (checkworkoutuser(user,w)): data = createrunkeeperworkoutdata(w) if not data: message = "Data error in Runkeeper Upload" rkid = 0 return message, rkid authorizationstring = str('Bearer ' + thetoken) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json', 'Content-Length':'nnn'} url = "https://api.runkeeper.com/fitnessActivities" response = requests.post(url,headers=headers,data=json.dumps(data)) # check for duplicate error first if (response.status_code == 409 ): message = "Duplicate error" w.uploadedtorunkeeper = -1 rkid = -1 w.save() return message, rkid elif (response.status_code == 201 or response.status_code==200): rkid = getidfromresponse(response) rkuri = geturifromid(thetoken,rkid) w.uploadedtorunkeeper = rkid w.save() return 'Successfully synchronized to Runkeeper',rkid else: s = response message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text) rkid = 0 return message, rkid else: message = "You are not authorized to upload this workout" rkid = 0 return message, rkid return message,rkid