From 84cefb31ab23e9802ee2333aa567ad223e0d3465 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 5 Apr 2021 14:54:18 +0200 Subject: [PATCH] adding all new NK workouts --- rowers/nkstuff.py | 185 ++++++++++++------------------------ rowers/tasks.py | 146 ++++++++++++++++++++++++++++ rowers/utils.py | 139 +++++++++++++++++++++++++++ rowers/views/importviews.py | 19 +++- 4 files changed, 364 insertions(+), 125 deletions(-) diff --git a/rowers/nkstuff.py b/rowers/nkstuff.py index f55aeef5..709e9db7 100644 --- a/rowers/nkstuff.py +++ b/rowers/nkstuff.py @@ -22,7 +22,7 @@ queuehigh = django_rq.get_queue('low') from rowers.rower_rules import is_workout_user, ispromember from iso8601 import ParseError -from rowers.utils import myqueue +from rowers.utils import myqueue,get_nk_summary, get_nk_allstats, get_nk_intervalstats,getdict import rowers.mytypes as mytypes import gzip @@ -33,6 +33,9 @@ from rowsandall_app.settings import ( UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET, ) +from rowers.tasks import handle_nk_async_workout + + try: from json.decoder import JSONDecodeError except ImportError: @@ -108,6 +111,64 @@ def nk_open(user): return thetoken +def get_nk_workouts(rower, do_async=True): + try: + thetoken = nk_open(rower.user) + except NoTokenError: + return 0 + + res = get_nk_workout_list(rower.user) + + if res.status_code != 200: + return 0 + + nkids = [item['id'] for item in res.json()] + alldata = {} + for item in res.json(): + alldata[item['id']] = item + + knownnkids = [ + w.uploadedtonk for w in Workout.objects.filter(user=rower) + ] + + tombstones = [ + t.uploadedtonk for t in TombStone.objects.filter(user=rower) + ] + + parkedids = [] + try: + with open('nkblocked.json','r') as nkblocked: + jsondata = json.load(nkblocked) + parkedids = jsondata['ids'] + except FileNotFoundError: + pass + + knownnkids = uniqify(knownnkids+tombstones+parkedids) + newids = [nkid for nkid in nkids if not nkid in knownnkids] + + newparkedids = uniqify(newids+parkedids) + + with open('nkblocked.json','wt') as nkblocked: + data = {'ids':newparkedids} + json.dump(data,nkblocked) + + counter = 0 + for nkid in newids: + res = myqueue(queuehigh, + handle_nk_async_workout, + alldata, + rower.user.id, + rower.nktoken, + nkid, + counter, + rower.defaulttimezone + ) + counter += 1 + + return 1 + + + def do_refresh_token(refreshtoken): post_data = {"grant_type": "refresh_token", #"client_id":NK_CLIENT_ID, @@ -161,7 +222,7 @@ def get_nk_workout_list(user,fake=False): # ready to fetch. Hurray endTime = int(arrow.now().timestamp())*1000 endTime = str(endTime) - startTime = arrow.now()-timedelta(days=90)*1000 + startTime = arrow.now()-timedelta(days=30)*1000 startTime = str(int(startTime.timestamp())) authorizationstring = str('Bearer ' + r.nktoken) headers = {'Authorization': authorizationstring, @@ -182,16 +243,6 @@ def get_nk_workout_list(user,fake=False): return s # -#def get_workout(user,nkid): -def getdict(x, seatIndex=1): - seatStrokes = pd.DataFrame(x) - try: - seatStrokes = seatStrokes.set_index('seatIndex') - return dict(seatStrokes.loc[seatIndex]) - except KeyError: - pass - - return {} def add_workout_from_data(user,nkid,data,strokedata,source='nk',splitdata=None, workoutsource='nklinklogbook'): @@ -266,116 +317,6 @@ def add_workout_from_data(user,nkid,data,strokedata,source='nk',splitdata=None, return workoutid,"" -def get_nk_intervalstats(workoutdata,strokedata): - intervals = workoutdata['intervals'] - separator = "|" - stri = "Workout Details\n" - stri += "#-{sep}SDist{sep}-Split-{sep}-SPace-{sep}-Pwr-{sep}-SPM--{sep}-AvgHR-{sep}DPS-\n".format( - sep=separator) - - i = 0 - - for interval in intervals: - id = interval['id'] - sdist = interval['totalDistanceGps'] - avgpace = interval['avgPaceGps']/1000. - avgpacetd = timedelta(seconds=avgpace) - newpacestring = dataprep.strfdelta(avgpacetd) - - elapsedSeconds = interval['elapsedTime']/1000. # secs - elapsedTime = datetime.utcfromtimestamp(elapsedSeconds) - - newsplitstring = "%s.%i" % (elapsedTime.strftime("%M:%S"), elapsedTime.microsecond/100000) - - pwr = interval['avgPower'] - avghr = interval['avgHeartRate'] - spm = interval['avgStrokeRate'] - dps = interval['distStrokeGps'] - - stri += "{i:0>2}{sep}{sdist:0>5}{sep}{split}{sep}{space}{sep} {pwr:0>3} {sep}".format( - i=i + 1, - sdist=int(float(sdist)), - split=newsplitstring, - space=newpacestring, - pwr=int(pwr), - sep=separator, - ) - stri += " {spm} {sep} {avghr:0>3} {sep}{dps:0>4.1f}\n".format( - sep=separator, - avghr=avghr, - spm=spm, - dps=dps, - ) - - i += 1 - - return stri - -def get_nk_summary(workoutdata,strokedata): - - name = workoutdata['name'] - - avgpace = workoutdata['avgPaceGps']/1000. # secs - avgpacetd = timedelta(seconds=avgpace) - avgpacestring = dataprep.strfdelta(avgpacetd) - - elapsedSeconds = workoutdata['elapsedTime']/1000. # secs - elapsedTime = datetime.utcfromtimestamp(elapsedSeconds) - - timestring = "%s.%i" % (elapsedTime.strftime("%H:%M:%S"), elapsedTime.microsecond/100000) - - dist = workoutdata['totalDistanceGps'] - spm = workoutdata['avgStrokeRate'] - avghr = workoutdata['avgHeartRate'] - avgdps = workoutdata['distStrokeGps'] - maxhr = strokedata['heartRate'].max() - pwr = workoutdata['avgPower'] - - sep = "|" - - - stri1 = "Workout Summary - " + name + "\n" - stri1 += "--{sep}Total{sep}--Total---{sep}--Avg--{sep}-Avg-{sep}-Avg--{sep}-Avg-{sep}-Max-{sep}-Avg\n".format( - sep=sep) - stri1 += "--{sep}Dist-{sep}--Time----{sep}-Pace--{sep}-Pwr-{sep}-SPM--{sep}-HR--{sep}-HR--{sep}-DPS\n".format( - sep=sep) - - - - stri1 += "--{sep}{dist:0>5.0f}{sep}".format( - sep=sep, - dist=dist, - ) - - stri1 += timestring + sep + avgpacestring - - stri1 += "{sep}{avgpower:0>5.1f}".format( - sep=sep, - avgpower=pwr, - ) - - stri1 += "{sep} {avgsr:2.1f} {sep}{avghr:0>5.1f}{sep}".format( - avgsr=spm, - sep=sep, - avghr=avghr - ) - - stri1 += "{maxhr:0>5.1f}{sep}{avgdps:0>4.1f}\n".format( - sep=sep, - maxhr=maxhr, - avgdps=avgdps - ) - - return stri1 - - - - return stri1 - -def get_nk_allstats(data,workoutdata): - stri = get_nk_summary(data, workoutdata) + \ - get_nk_intervalstats(data, workoutdata) - return stri def get_workout(user,nkid): diff --git a/rowers/tasks.py b/rowers/tasks.py index c841185f..84957b55 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -53,6 +53,7 @@ from rowsandall_app.settings import PROGRESS_CACHE_SECRET from rowsandall_app.settings import SETTINGS_NAME from rowsandall_app.settings import workoutemailbox from rowsandall_app.settings import UPLOAD_SERVICE_SECRET, UPLOAD_SERVICE_URL +from rowsandall_app.settings import NK_API_LOCATION from requests_oauthlib import OAuth1, OAuth1Session @@ -110,6 +111,8 @@ def safetimedelta(x): siteurl = SITE_URL +from rowers.utils import get_nk_summary, get_nk_allstats, get_nk_intervalstats,getdict + # testing task from rowers.emails import send_template_email @@ -2952,6 +2955,149 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,max_attempts,de return workoutid +@app.task +def handle_nk_async_workout(alldata,userid,nktoken,nkid,delaysec,defaulttimezone,debug=False,**kwargs): + time.sleep(delaysec) + data = alldata[nkid] + + params = { + 'sessionIds': nkid, + } + + authorizationstring = str('Bearer ' + nktoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json', + } + + # get strokes + url = NK_API_LOCATION+"api/v1/sessions/strokes" + response = requests.get(url,headers=headers,params=params) + + + if response.status_code != 200: + # error handling and logging + return 0 + + jsonData = response.json() + strokeData = jsonData[str(nkid)] + df = pd.DataFrame.from_dict(strokeData) + oarlockData = df['oarlockStrokes'] + + oarlockData = oarlockData.apply(lambda x:getdict(x, seatIndex=1)) + df2 = pd.DataFrame.from_records(oarlockData.values) + + df.set_index('timestamp') + + if not df2.empty: + df2.set_index('timestamp') + df = df.merge(df2,left_index=True,right_index=True) + df = df.rename(columns={"timestamp_x":"timestamp"}) + + df = df.drop('oarlockStrokes',axis=1) + df.sort_values(by='timestamp',ascending=True,inplace=True) + df.fillna(inplace=True,method='ffill') + + # get workout data + timestampbegin = df['timestamp'].min() + timestampend = df['timestamp'].max() + + csvfilename = 'media/{code}_{nkid}.csv.gz'.format( + nkid=nkid, + code = uuid4().hex[:16] + ) + + df.to_csv(csvfilename, index_label='index', compression='gzip') + + + title = data["name"] + speedInput = data["speedInput"] + elapsedTime = data["elapsedTime"] + totalDistanceGps = data["totalDistanceGps"] + totalDistanceImp = data["totalDistanceImp"] + intervals = data["intervals"] # add intervals + oarlockSessions = data["oarlockSessions"] + deviceId = data["deviceId"] # you could get the firmware version + + summary = get_nk_allstats(data,df) + + # oarlock inboard, length, boat name + if oarlockSessions: + oarlocksession = oarlockSessions[0] # should take seatIndex + boatName = oarlocksession["boatName"] + oarLength = oarlocksession["oarLength"] # cm + oarInboardLength = oarlocksession["oarInboardLength"] # cm + seatNumber = oarlocksession["seatNumber"] + else: + boatName = '' + oarLength = 289 + oarInboardLength = 88 + seatNumber = 1 + + workouttype = "water" + boattype = "1x" + + uploadoptions = { + 'secret': UPLOAD_SERVICE_SECRET, + 'user':userid, + 'file': csvfilename, + 'title': title, + 'workouttype': workouttype, + 'boattype': boattype, + 'nkid':nkid, + 'inboard': oarInboardLength/100., + 'oarlength': oarLength/100., + 'summary':summary, + } + + session = requests.session() + newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'} + session.headers.update(newHeaders) + + response = session.post(UPLOAD_SERVICE_URL,json=uploadoptions) + + if response.status_code != 200: + return 0,response.text + + try: + workoutid = response.json()['id'] + except KeyError: + workoutid = 1 + + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + query = 'SELECT uploadedtonk from rowers_workout WHERE id ={workoutid}'.format(workoutid=workoutid) + + newnkid = 0 + with engine.connect() as conn, conn.begin(): + result = conn.execute(query) + tdata = result.fetchall() + if tdata: + newnkid = tdata[0][0] + + conn.close() + + parkedids = [] + with open('nkblocked.json','r') as nkblocked: + jsondata = json.load(nkblocked) + parkedids = jsondata['ids'] + + newparkedids = [id for id in parkedids if id != newnkid] + with open('nkblocked.json','wt') as nkblocked: + tdata = {'ids':newparkedids} + nkblocked.seek(0) + json.dump(tdata,nkblocked) + + # evt update workout summary + + # return + return workoutid + + + @app.task def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone,debug=False,**kwargs): time.sleep(delaysec) diff --git a/rowers/utils.py b/rowers/utils.py index c1e69aab..062f10c2 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -1092,3 +1092,142 @@ def step_to_string(step,short=False): s = 'Repeat {duration}'.format(duration=duration) return s,type, nr, repeatID, repeatValue + +def strfdelta(tdelta): + try: + minutes, seconds = divmod(tdelta.seconds, 60) + tenths = int(tdelta.microseconds / 1e5) + except AttributeError: + minutes, seconds = divmod(tdelta.view(np.int64), 60e9) + seconds, rest = divmod(seconds, 1e9) + tenths = int(rest / 1e8) + res = "{minutes:0>2}:{seconds:0>2}.{tenths:0>1}".format( + minutes=minutes, + seconds=seconds, + tenths=tenths, + ) + + return res + + +def get_nk_intervalstats(workoutdata,strokedata): + intervals = workoutdata['intervals'] + separator = "|" + stri = "Workout Details\n" + stri += "#-{sep}SDist{sep}-Split-{sep}-SPace-{sep}-Pwr-{sep}-SPM--{sep}-AvgHR-{sep}DPS-\n".format( + sep=separator) + + i = 0 + + for interval in intervals: + id = interval['id'] + sdist = interval['totalDistanceGps'] + avgpace = interval['avgPaceGps']/1000. + avgpacetd = timedelta(seconds=avgpace) + newpacestring = strfdelta(avgpacetd) + + elapsedSeconds = interval['elapsedTime']/1000. # secs + elapsedTime = datetime.datetime.utcfromtimestamp(elapsedSeconds) + + newsplitstring = "%s.%i" % (elapsedTime.strftime("%M:%S"), elapsedTime.microsecond/100000) + + pwr = interval['avgPower'] + avghr = interval['avgHeartRate'] + spm = interval['avgStrokeRate'] + dps = interval['distStrokeGps'] + + stri += "{i:0>2}{sep}{sdist:0>5}{sep}{split}{sep}{space}{sep} {pwr:0>3} {sep}".format( + i=i + 1, + sdist=int(float(sdist)), + split=newsplitstring, + space=newpacestring, + pwr=int(pwr), + sep=separator, + ) + stri += " {spm} {sep} {avghr:0>3} {sep}{dps:0>4.1f}\n".format( + sep=separator, + avghr=avghr, + spm=spm, + dps=dps, + ) + + i += 1 + + return stri + +def get_nk_summary(workoutdata,strokedata): + + name = workoutdata['name'] + + avgpace = workoutdata['avgPaceGps']/1000. # secs + avgpacetd = timedelta(seconds=avgpace) + avgpacestring = strfdelta(avgpacetd) + + elapsedSeconds = workoutdata['elapsedTime']/1000. # secs + elapsedTime = datetime.datetime.utcfromtimestamp(elapsedSeconds) + + timestring = "%s.%i" % (elapsedTime.strftime("%H:%M:%S"), elapsedTime.microsecond/100000) + + dist = workoutdata['totalDistanceGps'] + spm = workoutdata['avgStrokeRate'] + avghr = workoutdata['avgHeartRate'] + avgdps = workoutdata['distStrokeGps'] + maxhr = strokedata['heartRate'].max() + pwr = workoutdata['avgPower'] + + sep = "|" + + + stri1 = "Workout Summary - " + name + "\n" + stri1 += "--{sep}Total{sep}--Total---{sep}--Avg--{sep}-Avg-{sep}-Avg--{sep}-Avg-{sep}-Max-{sep}-Avg\n".format( + sep=sep) + stri1 += "--{sep}Dist-{sep}--Time----{sep}-Pace--{sep}-Pwr-{sep}-SPM--{sep}-HR--{sep}-HR--{sep}-DPS\n".format( + sep=sep) + + + + stri1 += "--{sep}{dist:0>5.0f}{sep}".format( + sep=sep, + dist=dist, + ) + + stri1 += timestring + sep + avgpacestring + + stri1 += "{sep}{avgpower:0>5.1f}".format( + sep=sep, + avgpower=pwr, + ) + + stri1 += "{sep} {avgsr:2.1f} {sep}{avghr:0>5.1f}{sep}".format( + avgsr=spm, + sep=sep, + avghr=avghr + ) + + stri1 += "{maxhr:0>5.1f}{sep}{avgdps:0>4.1f}\n".format( + sep=sep, + maxhr=maxhr, + avgdps=avgdps + ) + + return stri1 + + + + return stri1 + +def get_nk_allstats(data,workoutdata): + stri = get_nk_summary(data, workoutdata) + \ + get_nk_intervalstats(data, workoutdata) + return stri + +#def get_workout(user,nkid): +def getdict(x, seatIndex=1): + seatStrokes = pd.DataFrame(x) + try: + seatStrokes = seatStrokes.set_index('seatIndex') + return dict(seatStrokes.loc[seatIndex]) + except KeyError: + pass + + return {} diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py index f0dcc5b4..e3120a29 100644 --- a/rowers/views/importviews.py +++ b/rowers/views/importviews.py @@ -817,8 +817,21 @@ def rower_process_nkcallback(request): @login_required() def workout_getnkworkout_all(request): - messages.error(request,"Future Functionality, coming soon!") - url = reverse('workout_nkimport_view') + try: + thetoken = nk_open(request.user) + except NoTokenError: + return HttpResponseRedirect("rower_nk_authorize") + + r = getrequestrower(request) + + result = nkstuff.get_nk_workouts(r,do_async=True) + + if result: + messages.info(request,"Your NK workouts will be imported in the coming few minutes") + else: + messages.error(request,"Your NK workouts import failed") + + url = reverse('workouts_view') return HttpResponseRedirect(url) @login_required() @@ -854,7 +867,7 @@ def workout_nkimport_view(request,userid=0): parkedids = [] try: with open('nkblocked.json','r') as nkblocked: - jsondata = json.load(nkplocked) + jsondata = json.load(nkblocked) parkedids = jsondata['ids'] except FileNotFoundError: pass