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',
}