From 60e85c287631fc0195ac9e6ddef58907d62dc561 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 9 Jul 2020 14:39:44 +0200 Subject: [PATCH 1/4] solve #564 adding fit and tcx tests --- rowers/dataprep.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ rowers/mytypes.py | 38 ++++++++++++++++++++++++++++++++++++++ rowers/tasks.py | 1 - 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index f6eb2dbf..27036a41 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -70,6 +70,7 @@ import zipfile import pandas as pd import numpy as np import itertools +from fitparse import FitFile import math from rowers.tasks import ( handle_sendemail_unrecognized, handle_sendemail_breakthrough, @@ -94,6 +95,7 @@ queuehigh = django_rq.get_queue('default') from rowsandall_app.settings import SITE_URL from rowers.mytypes import otwtypes,otetypes +from rowers import mytypes from rowers.database import * from rowers.opaque import encoder @@ -1533,6 +1535,40 @@ def handle_nonpainsled(f2, fileformat, summary=''): # This routine should be used everywhere in views.py and mailprocessing.py # Currently there is code duplication +def get_workouttype_from_fit(filename): + fitfile = FitFile(filename,check_crc=False) + records = fitfile.messages + fittype = 'rowing' + workouttype = 'water' + for record in records: + if record.name == 'sport': + fittype = record.get_values()['sport'].lower() + try: + workouttype = mytypes.fitmappinginv[fittype] + except KeyError: + workouttype = 'other' + + return workouttype + +import rowingdata.tcxtools as tcxtools + +def get_workouttype_from_tcx(filename): + d = tcxtools.tcx_getdict(filename) + tcxtype = 'rowing' + workouttype = 'water' + try: + tcxtype = d['Activities']['Activity']['@Sport'].lower() + if tcxtype == 'other': + tcxtype = 'rowing' + except KeyError: + tcxtype = 'rowing' + + try: + workouttype = mytypes.garminmappinginv[tcxtype.upper()] + except KeyError: + workouttype = 'water' + + return workouttype def new_workout_from_file(r, f2, workouttype='rower', @@ -1554,6 +1590,8 @@ def new_workout_from_file(r, f2, summary = '' oarlength = 2.89 inboard = 0.88 + + # Save zip files to email box for further processing if len(fileformat) == 3 and fileformat[0] == 'zip': uploadoptions['fromuploadform'] = True bodyyaml = yaml.safe_dump(uploadoptions,default_flow_style=False) @@ -1625,6 +1663,12 @@ def new_workout_from_file(r, f2, # email attachment which can safely be ignored return (0, '', f2) + # Get workout type from fit & tcx + if (fileformat == 'fit'): + workouttype = get_workouttype_from_fit(f2) + if (fileformat == 'tcx'): + workouttype = get_workouttype_from_tcx(f2) + # handle non-Painsled by converting it to painsled compatible CSV if (fileformat != 'csv'): f2, summary, oarlength, inboard, fileformat, impeller = handle_nonpainsled( @@ -1636,6 +1680,8 @@ def new_workout_from_file(r, f2, message = 'Something went wrong' return (0, message, '') + + dosummary = (fileformat != 'fit' and 'speedcoach2' not in fileformat) dosummary = dosummary or summary == '' diff --git a/rowers/mytypes.py b/rowers/mytypes.py index 992f536f..36699446 100644 --- a/rowers/mytypes.py +++ b/rowers/mytypes.py @@ -112,6 +112,42 @@ garmincollection = ( garminmapping = {key:value for key,value in Reverse(garmincollection)} +fitcollection = ( +('water','rowing'), +('rower','rowing'), +('skierg','cross_country_skiing'), +('Bike','cycling'), +('bikeerg','cycling'), +('dynamic','rowing'), +('slides','rowing'), +('paddle','paddling'), +('snow','cross_country_skiing'), +('coastal','rowing'), +('c-boat','rowing'), +('churchboat','rowing'), +('Ride','cycling'), +('Run','running'), +('NordicSki','cross_country_skiing'), +('Swim','swimming'), +('Hike','hiking'), +('RollerSki','cross_country_skiing'), +('Walk','walking'), +('Canoeing','boating'), +('Crossfit','fitness_equipment'), +('StandUpPaddling','stand_up_paddle_boarding'), +('IceSkate','ice_skating'), +('WeightTraining','training'), +('InlineSkate','inline_skating'), +('Kayaking','kayaking'), +('Workout','generic'), +('Yoga','generic'), +('other','generic'), +) + + + +fitmapping = {key:value for key,value in Reverse(fitcollection)} + stcollection = ( ('water','Rowing'), ('rower','Rowing'), @@ -289,6 +325,8 @@ polarmappinginv = {value:key for key,value in Reverse(polarcollection) if value garminmappinginv = {value:key for key, value in Reverse(garmincollection) if value is not None} +fitmappinginv = {value:key for key,value in Reverse(fitcollection) if value is not None} + otwtypes = ( 'water', 'coastal', diff --git a/rowers/tasks.py b/rowers/tasks.py index 1d978ed0..7be5e3b9 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -707,7 +707,6 @@ def handle_get_garmin_file(client_id, )+filetype response = garmin.get(url, stream=True) - print(response.status_code,filename) if response.status_code == 200: with open(filename, 'wb') as out_file: shutil.copyfileobj(response.raw, out_file) From ecb49a580eecadca6513acb73fec0f0b34939ceb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 9 Jul 2020 16:43:46 +0200 Subject: [PATCH 2/4] small improvement tcx and fit detection --- rowers/dataprep.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 27036a41..eca85b45 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1535,38 +1535,36 @@ def handle_nonpainsled(f2, fileformat, summary=''): # This routine should be used everywhere in views.py and mailprocessing.py # Currently there is code duplication -def get_workouttype_from_fit(filename): +def get_workouttype_from_fit(filename,workouttype='water'): fitfile = FitFile(filename,check_crc=False) records = fitfile.messages fittype = 'rowing' - workouttype = 'water' for record in records: if record.name == 'sport': fittype = record.get_values()['sport'].lower() try: workouttype = mytypes.fitmappinginv[fittype] except KeyError: - workouttype = 'other' + return workouttype return workouttype import rowingdata.tcxtools as tcxtools -def get_workouttype_from_tcx(filename): +def get_workouttype_from_tcx(filename,workouttype='water'): d = tcxtools.tcx_getdict(filename) tcxtype = 'rowing' - workouttype = 'water' try: tcxtype = d['Activities']['Activity']['@Sport'].lower() if tcxtype == 'other': tcxtype = 'rowing' except KeyError: - tcxtype = 'rowing' + return workouttype try: workouttype = mytypes.garminmappinginv[tcxtype.upper()] except KeyError: - workouttype = 'water' + return workouttype return workouttype @@ -1665,9 +1663,9 @@ def new_workout_from_file(r, f2, # Get workout type from fit & tcx if (fileformat == 'fit'): - workouttype = get_workouttype_from_fit(f2) + workouttype = get_workouttype_from_fit(f2,workouttype=workouttype) if (fileformat == 'tcx'): - workouttype = get_workouttype_from_tcx(f2) + workouttype = get_workouttype_from_tcx(f2,workouttype=workouttype) # handle non-Painsled by converting it to painsled compatible CSV if (fileformat != 'csv'): From a1a54be22dda4d1848c72e1a3b6af6533cf1d783 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 10 Jul 2020 10:27:35 +0200 Subject: [PATCH 3/4] fixes --- rowers/templates/help.html | 16 ++++++++-------- rowers/views/importviews.py | 11 +---------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/rowers/templates/help.html b/rowers/templates/help.html index 5ce3b582..cf49d2f7 100644 --- a/rowers/templates/help.html +++ b/rowers/templates/help.html @@ -47,7 +47,7 @@

The tools also provide the ability to review a row, stroke by stroke in plots versus time or distance. Basic plots in include HR, Pace, Stroke rate, and power for the erg.

- +

The tools also provide a text summary of the row. @@ -119,16 +119,16 @@

Some of our functionality is not related to a single workout, but instead looks at comparisons, trends, statistics, and other. You can find all - that under the Analysis Tab. + that under the Analysis Tab.

-

On-line Racing

+

On-line Challenges

- On-line racing is a - fun way to race other Rowsandall.com users - rowing on the same stretch of water. + On-line challenges are a + fun way to test your boat speed and course line against other Rowsandall.com users + rowing on the same stretch of water or on the Concept2 ergometer.

Training Plan

@@ -146,8 +146,8 @@ functionality related to interaction with your team, if you are part of one.

- - + +
  • Need more help?

    diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py index 0963cbf3..d1981dc9 100644 --- a/rowers/views/importviews.py +++ b/rowers/views/importviews.py @@ -1035,11 +1035,7 @@ def garmin_summaries_view(request): def garmin_newfiles_ping(request): t = time.localtime() timestamp = time.strftime('%b-%d-%Y_%H%M', t) - with open('garminlog.log','a') as f: - f.write('\n') - f.write(timestamp) - f.write(' ') - f.write(str(request.body)) + if request.method != 'POST': return HttpResponse(status=200) @@ -1090,11 +1086,6 @@ def garmin_details_view(request): t = time.localtime() timestamp = time.strftime('%b-%d-%Y_%H%M', t) - with open('garminlog.log','a') as f: - f.write('\n') - f.write(timestamp) - f.write(' ') - f.write(str(request.body)) # POST request data = json.loads(request.body) From 40c852092ddfee7cde9fd7abd85c51d81f7c8624 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 10 Jul 2020 10:43:40 +0200 Subject: [PATCH 4/4] improved workout names --- rowers/dataprep.py | 27 +++++++++++++++++++++++++-- rowers/tasks.py | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index eca85b45..ca1844bc 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -749,6 +749,19 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, return datadf +def getpartofday(dt): + h = dt.hour + if h < 12: + return "Morning" + elif h < 18: + return "Afternoon" + elif h < 22: + return "Evening" + else: + return "Night" + + return None + def getstatsfields(): fielddict = {name:d['verbose_name'] for name,d in rowingmetrics} @@ -1140,8 +1153,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', impeller=False): message = None - if title is None: - title = 'Workout' + powerperc = 100 * np.array([r.pw_ut2, r.pw_ut1, @@ -1155,6 +1167,16 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', powerperc=powerperc, powerzones=r.powerzones) row = rdata(f2, rower=rr) + if title is None or title == '': + title = 'Workout' + partofday = getpartofday(row.rowdatetime) + + if partofday is not None: + title = '{partofday} workout {workouttype}'.format( + partofday=partofday, + workouttype=workouttype, + ) + if row.df.empty: return (0, 'Error: CSV data file was empty') @@ -1657,6 +1679,7 @@ def new_workout_from_file(r, f2, r.user.email) return (0, message, f2) + if fileformat == 'att': # email attachment which can safely be ignored return (0, '', f2) diff --git a/rowers/tasks.py b/rowers/tasks.py index 7be5e3b9..8d12d008 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -717,7 +717,7 @@ def handle_get_garmin_file(client_id, 'secret':UPLOAD_SERVICE_SECRET, 'user':userid, 'file': filename, - 'title': filename[6:], + 'title': '', 'workouttype':'water', 'boattype':'1x', }