From 54190ed383488be83ad0b163d658daed823fdde7 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 16 May 2021 07:30:56 +0200
Subject: [PATCH 1/9] works except for target cadence
---
garminscript.py | 115 ++++++++++++++++++++++++---
rowers/garmin_stuff.py | 174 ++++++++++++++++++++++++++++-------------
rowers/models.py | 2 +
3 files changed, 225 insertions(+), 66 deletions(-)
diff --git a/garminscript.py b/garminscript.py
index b288cd1e..9cb803af 100644
--- a/garminscript.py
+++ b/garminscript.py
@@ -28,23 +28,24 @@ payload = {
'workoutSourceId': 'Rowsandall.com',
'steps': [
{
- 'type': 'WorkoutStep', 'stepOrder': 0,
+ 'type': 'WorkoutStep',
+ 'stepOrder': 0,
#'repeatType': 'Step',
- 'repeatValue': 1,
+ #'repeatValue': 1,
'intensity': 'ACTIVE',
- 'description': '0',
+ 'description': 'At 220W',
'durationType': 'TIME',
'durationValue': 1800,
- 'durationValueType': 'METER',
+ 'durationValueType': None,
'targetType': 'POWER',
- 'targetValue': 1226,
+ 'targetValue': 226,
'targetValueLow': None,
'targetValueHigh': None,
}
]
}
-payload = {
+payload2 = {
"workoutName": "Test",
"description": "Test",
"sport": "CYCLING",
@@ -55,16 +56,93 @@ payload = {
"intensity": "INTERVAL",
"description": "Free Ride",
"durationType": "TIME",
- "durationValue": 300,
+ "durationValue": 1800,
"durationValueType": None,
"targetType": "POWER",
- "targetValue": None,
- "targetValueLow": 0,
- "targetValueHigh": 0.7,
- "targetValueType": "PERCENT",
+ "targetValue": 226,
+ "targetValueLow": None,
+ "targetValueHigh": None,
+ "targetValueType": None,
"exerciseCategory": None
}]}
+payload = {'workoutName': '4x1000m',
+ 'sport': 'GENERIC',
+ 'description': 'Uploaded from Rowsandall.com',
+ 'estimatedDurationInSecs': 2700,
+ 'estimatedDistanceInMeters': 8768,
+ 'workoutProvider': 'Rowsandall.com',
+ 'workoutSourceId': 'Rowsandall.com',
+ 'steps': [{'type': 'WorkoutStep',
+ 'stepOrder': 0,
+ 'repeatType': None,
+ 'repeatValue': 1,
+ 'intensity': 'INTERVAL',
+ 'description': '0',
+ 'durationType': 'DISTANCE',
+ 'durationValue': 2000,
+ 'durationValueType': 'METER',
+ 'targetType': None,
+ 'targetValue': None,
+ 'targetValueLow': None,
+ 'targetValueHigh': None},
+ {'type': 'WorkoutRepeatStep',
+ 'stepOrder': 1,
+ 'repeatType': 'REPEAT_UNTIL_STEPS_CMPLT',
+ 'repeatValue': 4,
+ 'intensity': 'INTERVAL',
+ 'description': '3',
+ 'durationType': 'REPS',
+ 'durationValue': None,
+ 'durationValueType': None,
+ 'targetType': None,
+ 'targetValue': None,
+ 'targetValueLow': None,
+ 'targetValueHigh': None,
+ 'steps': [{'type': 'WorkoutStep',
+ 'stepOrder': 2,
+ 'repeatType': None,
+ 'repeatValue': 1,
+ 'intensity': 'INTERVAL',
+ 'description': '1',
+ 'durationType': 'DISTANCE',
+ 'durationValue': 1000,
+ 'durationValueType': 'METER',
+ #'targetType': 'CADENCE',
+ #'targetValue': 25,
+ #'targetValueLow': None,
+ #'targetValueHigh': None
+ },
+ {'type': 'WorkoutStep',
+ 'stepOrder': 3,
+ 'repeatType': None,
+ 'repeatValue': 1,
+ 'intensity': 'REST',
+ 'description': '2',
+ 'durationType': 'TIME',
+ 'durationValue': 60,
+ 'durationValueType': None,
+ 'targetType': None,
+ 'targetValue': None,
+ 'targetValueLow': None,
+ 'targetValueHigh': None}]},
+ {'type': 'WorkoutStep',
+ 'stepOrder': 4,
+ 'repeatType': None,
+ 'repeatValue': 1,
+ 'intensity': 'INTERVAL',
+ 'description': '4',
+ 'durationType': 'DISTANCE',
+ 'durationValue': 2000,
+ 'durationValueType': 'METER',
+ 'targetType': None,
+ 'targetValue': None,
+ 'targetValueLow': None,
+ 'targetValueHigh': None}]}
+
+
+
+
print(json.dumps(payload))
@@ -95,4 +173,17 @@ response = requests.post(url,auth=authheaders,json=payload)
# build base_string
print(response.status_code)
-print(response.text)
+
+print(response.json())
+garmin_workout_id = response.json()['workoutId']
+url = 'http://apis.garmin.com/training-api/schedule'
+
+payload = {
+ 'workoutId': garmin_workout_id,
+ 'date': '2021-05-16'
+}
+
+
+response = requests.post(url,auth=authheaders,json=payload)
+print(response.status_code)
+print(response.json())
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index 6d255b64..1636e48e 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -27,13 +27,6 @@ from rowsandall_app.settings import (
from pytz import timezone as tz, utc
-#try:
-# import http.client as http_client
-#except ImportError:
- # Python 2
-# import httplib as http_client
-#http_client.HTTPConnection.debuglevel = 1
-
# You must initialize logging, otherwise you'll not see debug output.
#logging.basicConfig()
#logging.getLogger().setLevel(logging.DEBUG)
@@ -82,6 +75,36 @@ columns = {
'bikeCadenceInRPM':' Cadence (stokes/min)',
}
+targettypes = {
+ "Speed": "SPEED",
+ "HeartRate": "HEART_RATE",
+ "Open": "OPEN",
+ "Cadence": "CADENCE",
+ "Power": "POWER",
+ "Grade": "GRADE",
+ "Resistance": "RESISTANCE",
+ "Power3s": "POWER_3S",
+ "Power10s": "POWER_10S",
+ "Power30s": "POWER_30S",
+ "PowerLap": "POWER_LAP",
+ "SwimStroke": "SWIM_STROKE",
+ "SpeedLap": "SPEED_LAP",
+ "HeartRateLap": "HEART_RATE_LAP"
+}
+
+repeattypes = {
+ "RepeatUntilStepsCmplt": "REPEAT_UNTIL_STEPS_CMPLT",
+ "RepeatUntilTime": "REPEAT_UNTIL_TIME",
+ "RepeatUntilDistance": "REPEAT_UNTIL_TIME",
+ "RepeatUntilCalories": "REPEAT_UNTIL_CALORIES" ,
+ "RepeatUntilHrLessThan": "REPEAT_UNTIL_HR_LESS_THAN" ,
+ "RepeatUntilHrGreaterThan": "REPEAT_UNTIL_HR_GREATER_THAN",
+ "RepeatUntilPowerLessThan": "REPEAT_UNTIL_POWER_LESS_THAN",
+ "RepeatUntilPowerGreaterThan": "REPEAT_UNTIL_POWER_GREATER_THAN",
+ "RepeatUntilPowerLapLessThan": "REPEAT_UNTIL_POWER_LAP_LESS_THAN",
+ "RepeatUntilPowerLapGreaterThan": "REPEAT_UNTIL_POWER_LAP_GREATER_THAN",
+}
+
def garmin_authorize(): # pragma: no cover
redirect_uri = oauth_data['redirect_uri']
client_secret = oauth_data['client_secret']
@@ -179,9 +202,11 @@ from rowers import utils
def step_to_garmin(step,order=0):
durationtype = step['dict']['durationType']
durationvalue = step['dict']['durationValue']
- durationvaluetype = ''
+ durationvaluetype = None
try:
- intensity = step['dict']['intensity']
+ intensity = step['dict']['intensity'].upper()
+ if intensity.lower() == 'active':
+ intensity = 'INTERVAL'
except KeyError:
intensity = None
#durationvaluetype = ''
@@ -197,56 +222,87 @@ def step_to_garmin(step,order=0):
if durationvalue <= 100:
durationvaluetype = 'PERCENT'
else:
- durationvaluetype = ''
+ durationvaluetype = None
durationvalue -= 100
elif durationtype == 'HrGreaterThan': # pragma: no cover
durationtype = 'HR_GREATER_THAN'
if durationvalue <= 100:
durationvaluetype = 'PERCENT'
else:
- durationvaluetype = ''
+ durationvaluetype = None
durationvalue -= 100
elif durationtype == 'PowerLessThan': # pragma: no cover
durationtype = 'POWER_LESS_THAN'
if durationvalue <= 1000:
durationvaluetype = 'PERCENT'
else:
- durationvaluetype = ''
+ durationvaluetype = None
durationvalue -= 1000
elif durationtype == 'PowerGreaterThan': # pragma: no cover
durationtype = 'POWER_GREATER_THAN'
if durationvalue <= 1000:
durationvaluetype = 'PERCENT'
else:
- durationvaluetype = ''
+ durationvaluetype = None
durationvalue -= 1000
elif durationtype == 'Reps': # pragma: no cover
durationtype = 'REPS'
try:
targetType = step['dict']['targetType']
+ targetType = targettypes[targetType]
except KeyError:
targetType = None
try:
targetValue = step['dict']['targetValue']
+ if targetValue == 0:
+ targetValue = None
except KeyError:
targetValue = None
try:
targetValueLow = step['dict']['targetValueLow']
+ if targetValueLow == 0:
+ targetValueLow = None
except KeyError:
targetValueLow = None
try:
- targetValueHigh = step['dict']['targetValueHigh'],
+ targetValueHigh = step['dict']['targetValueHigh']
+ if targetValueHigh == 0:
+ targetValueHigh = None
except KeyError:
targetValueHigh = None
+ if targetType.lower() == "power":
+ targetType = 'POWER'
+ if targetValue is not None and targetValue > 1000:
+ targetValue -= 1000
+ if targetValueHigh is not None and targetValueHigh > 1000:
+ targetValueHigh -= 1000
+ if targetValueLow is not None and targetValueLow > 1000:
+ targetValueLow -= 1000
+
+ if targetValue is None and targetValueLow is None and targetValueHigh is None:
+ targetType = None
+
+ steptype = 'WorkoutRepeatStep'
+ if step['type'].lower() == 'step':
+ steptype = 'WorkoutStep'
+
+ repeattype = None
+ if steptype == 'WorkoutRepeatStep':
+ repeattype = repeattypes[step['dict']['durationType']]
+ durationtype = 'REPS'
+ durationvalue = None
+ durationvaluetype = None
+ targetType = None
+ targetValue = None
out = {
- 'type': step['type'],
+ 'type': steptype,
'stepOrder':order,
- 'repeatType':step['type'],
+ 'repeatType':repeattype,
'repeatValue':step['repeatValue'],
'intensity':intensity,
'description':step['dict']['wkt_step_name'],
@@ -303,30 +359,10 @@ def ps_to_garmin(ps,r):
lijst.append(gstep)
payload['steps'] = lijst
- url = 'https://apis.garmin.com/training-api/workout/'
- garmin = OAuth1Session(oauth_data['client_id'],
- client_secret=oauth_data['client_secret'],
- resource_owner_key=r.garmintoken,
- resource_owner_secret=r.garminrefreshtoken,
- signature_method='HMAC-SHA1',
- encoding='base64'
- )
+ return payload
- response = garmin.post(url,data=payload)
-
- #POST /training-api/workout?undefined HTTP/1.1
- #Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0"
- #Host: apis.garmin.com
- #Accept: */*
-
- #curl -v --header 'Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0"' 'https://apis.garmin.com/training-api/workout'
-
- #Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0"
-
- return response
-
def get_garmin_permissions(user): # pragma: no cover
r = Rower.objects.get(user=user)
@@ -334,15 +370,18 @@ def get_garmin_permissions(user): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
- garmin = OAuth1Session(oauth_data['client_id'],
- client_secret=oauth_data['client_secret'],
- resource_owner_key=r.garmintoken,
- resource_owner_secret=r.garminrefreshtoken,
- )
+ garminheaders = OAuth1(
+ client_key = oauth_data['client_id'],
+ client_secret=oauth_data['client_secret'],
+ resource_owner_key=r.garmintoken,
+ resource_owner_secret=r.garminrefreshtoken,
+ signature_method='HMAC-SHA1',
+ )
- url = 'https://apis.garmin.com/userPermissions/'
- result = garmin.get(url)
+ url = 'https://apis.garmin.com/userPermissions'
+
+ result = requests.get(url,auth=garminheaders)
if result.status_code == 200:
return result.json()
@@ -351,31 +390,58 @@ def get_garmin_permissions(user): # pragma: no cover
def garmin_session_create(ps,user): # pragma: no cover
if not ps.steps:
+ print('aap')
return 0
if not garmin_can_export_session(user):
+ print('noot')
return 0
- garmindict = ps_to_garmin(ps)
-
r = Rower.objects.get(user=user)
if (r.garmintoken == '') or (r.garmintoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
- garmin = OAuth1Session(oauth_data['client_id'],
- client_secret=oauth_data['client_secret'],
- resource_owner_key=r.garmintoken,
- resource_owner_secret=r.garminrefreshtoken,
- )
+ payload = ps_to_garmin(ps,r)
- url = 'http://apis.garmin.com/training-api/schedule/'
+ url = 'https://apis.garmin.com/training-api/workout'
+
+ garminheaders = OAuth1(
+ client_key = oauth_data['client_id'],
+ client_secret=oauth_data['client_secret'],
+ resource_owner_key=r.garmintoken,
+ resource_owner_secret=r.garminrefreshtoken,
+ signature_method='HMAC-SHA1',
+ )
+
+ response = requests.post(url,auth=garminheaders,json=payload)
+ print(response.status_code,response.text)
- response = garmin.post(url,data=garmindict)
if response.status_code != 200:
return 0
- return response.json()['workoutId']
+ garmin_workout_id = response.json()['workoutId']
+
+
+ url = 'http://apis.garmin.com/training-api/schedule'
+
+ payload = {
+ 'workoutId': garmin_workout_id,
+ 'date': ps.preferreddate.strftime('%Y-%m-%d')
+ }
+
+
+ response = requests.post(url,auth=garminheaders,json=payload)
+ print(response.status_code,response.text)
+
+ if response.status_code != 200:
+ return 0
+
+ ps.garmin_schedule_id = response.json()
+ ps.garmin_workout_id = garmin_workout_id
+ ps.save()
+
+ return garmin_workout_id
def garmin_getworkout(garminid,r,activity):
starttime = activity['startTimeInSeconds']
diff --git a/rowers/models.py b/rowers/models.py
index efac1c2d..7d612fef 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -2399,6 +2399,8 @@ class PlannedSession(models.Model):
steps = PlannedSessionStepField(default={},null=True)
interval_string = models.TextField(max_length=1000,default=None,blank=True,null=True,
verbose_name='Interval String (optional)')
+ garmin_workout_id = models.BigIntegerField(default=0)
+ garmin_schedule_id = models.BigIntegerField(default=0)
tags = TaggableManager(blank=True)
From cbf7c4ab0bc05324bfcdd2317992144d16e133d8 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 16 May 2021 15:33:03 +0200
Subject: [PATCH 2/9] adding simple export to Garmin
---
garminscript.py | 4 +--
rowers/garmin_stuff.py | 25 +++++++++++-----
rowers/models.py | 14 +++++++++
rowers/plannedsessions.py | 2 +-
rowers/templates/plannedsessionview.html | 5 ++++
rowers/templates/rower_exportsettings.html | 5 +++-
rowers/urls.py | 2 ++
rowers/utils.py | 17 ++++++++---
rowers/views/planviews.py | 33 ++++++++++++++++++++++
9 files changed, 92 insertions(+), 15 deletions(-)
diff --git a/garminscript.py b/garminscript.py
index 9cb803af..f29698ec 100644
--- a/garminscript.py
+++ b/garminscript.py
@@ -66,8 +66,8 @@ payload2 = {
"exerciseCategory": None
}]}
-payload = {'workoutName': '4x1000m',
- 'sport': 'GENERIC',
+payload = {'workoutName': '4x1000m row',
+ 'sport': 'CARDIO_TRAINING',
'description': 'Uploaded from Rowsandall.com',
'estimatedDurationInSecs': 2700,
'estimatedDistanceInMeters': 8768,
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index 1636e48e..9b5bf773 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -11,6 +11,8 @@ from rowers.mytypes import otwtypes
from rowers.rower_rules import is_workout_user,ispromember
from iso8601 import ParseError
+from rowers.plannedsessions import ps_dict_get_description
+
import pandas as pd
@@ -131,6 +133,7 @@ def garmin_processcallback(redirect_response,resource_owner_key,resource_owner_s
verifier = oauth_response.get('oauth_verifier')
token = oauth_response.get('oauth_token')
access_token_url = 'https://connectapi.garmin.com/oauth-service/oauth/access_token'
+ print(token,access_token_url)
# Using OAuth1Session
garmin = OAuth1Session(oauth_data['client_id'],
@@ -191,6 +194,8 @@ def get_garmin_workout_list(user): # pragma: no cover
return result
def garmin_can_export_session(user): # pragma: no cover
+ if user.rower.rowerplan not in ['coach','plan']:
+ return False
result = get_garmin_permissions(user)
if 'WORKOUT_IMPORT' in result:
return True
@@ -254,7 +259,6 @@ def step_to_garmin(step,order=0):
except KeyError:
targetType = None
-
try:
targetValue = step['dict']['targetValue']
if targetValue == 0:
@@ -283,6 +287,13 @@ def step_to_garmin(step,order=0):
if targetValueLow is not None and targetValueLow > 1000:
targetValueLow -= 1000
+ # temp - throw away target other than power
+ if targetType.lower() != 'power':
+ targetType = None
+ targetValue = None
+ targetValueLow = None
+ targetValueHigh = None
+
if targetValue is None and targetValueLow is None and targetValueHigh is None:
targetType = None
@@ -333,8 +344,8 @@ def step_to_garmin(step,order=0):
def ps_to_garmin(ps,r):
payload = {
'workoutName': ps.name,
- 'sport': 'GENERIC',
- 'description':'Uploaded from Rowsandall.com',
+ 'sport':r.garminactivity,
+ 'description':ps_dict_get_description(ps.steps),
'estimatedDurationInSecs':60*ps.approximate_duration,
'estimatedDistanceInMeters': ps.approximate_distance,
'workoutProvider': 'Rowsandall.com',
@@ -390,12 +401,13 @@ def get_garmin_permissions(user): # pragma: no cover
def garmin_session_create(ps,user): # pragma: no cover
if not ps.steps:
- print('aap')
return 0
if not garmin_can_export_session(user):
- print('noot')
return 0
+ if ps.garmin_schedule_id != 0:
+ return ps.garmin_schedule_id
+
r = Rower.objects.get(user=user)
if (r.garmintoken == '') or (r.garmintoken is None):
s = "Token doesn't exist. Need to authorize"
@@ -414,7 +426,7 @@ def garmin_session_create(ps,user): # pragma: no cover
)
response = requests.post(url,auth=garminheaders,json=payload)
- print(response.status_code,response.text)
+
if response.status_code != 200:
@@ -432,7 +444,6 @@ def garmin_session_create(ps,user): # pragma: no cover
response = requests.post(url,auth=garminheaders,json=payload)
- print(response.status_code,response.text)
if response.status_code != 200:
return 0
diff --git a/rowers/models.py b/rowers/models.py
index 7d612fef..d5449391 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -837,6 +837,15 @@ class Rower(models.Model):
('None','None'),
)
+ garminsports = (
+ ('GENERIC','Custom'),
+ ('RUNNING','Running'),
+ ('CYCLING','Cycling'),
+ ('LAP_SWIMMING','Lap Swimming'),
+ ('STRENGTH_TRAINING','Strength Training'),
+ ('CARDIO_TRAINING','Cardio Training'),
+ )
+
user = models.OneToOneField(User,on_delete=models.CASCADE)
#billing details
@@ -1015,6 +1024,10 @@ class Rower(models.Model):
garminrefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True)
+ garminactivity = models.CharField(default='RUNNING',max_length=200,
+ verbose_name='Garmin Activity for Structured Workouts',
+ choices=garminsports)
+
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
stravatokenexpirydate = models.DateTimeField(blank=True,null=True)
stravarefreshtoken = models.CharField(default='',max_length=1000,
@@ -3853,6 +3866,7 @@ class RowerExportForm(ModelForm):
model = Rower
fields = [
'stravaexportas',
+ 'garminactivity',
'polar_auto_import',
'c2_auto_export',
'c2_auto_import',
diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py
index 687ac4a4..60e82e56 100644
--- a/rowers/plannedsessions.py
+++ b/rowers/plannedsessions.py
@@ -86,7 +86,7 @@ from rowers.tasks import (
from rowers.utils import totaltime_sec_to_string
def ps_dict_get_description(d,short=False): # pragma: no cover
- sdict,totalmeters,totalseconds,totalrscore = ps_dict_order(d,short=short)
+ sdict,totalmeters,totalseconds,totalrscore = ps_dict_order(d,short=short,html=False)
s = ''
for item in sdict:
s += item['string']+'\n'
diff --git a/rowers/templates/plannedsessionview.html b/rowers/templates/plannedsessionview.html
index 21d57028..2389ffcc 100644
--- a/rowers/templates/plannedsessionview.html
+++ b/rowers/templates/plannedsessionview.html
@@ -18,6 +18,11 @@
Edit Session
/
Save to Library
+ {% if psdict.garmin_schedule_id.1 %}
+ Exported to Garmin
+ {% else %}
+ Export to Garmin
+ {% endif %}
{% endif %}
Session {{ psdict.name.1 }}
diff --git a/rowers/templates/rower_exportsettings.html b/rowers/templates/rower_exportsettings.html
index 79332c87..e631af88 100644
--- a/rowers/templates/rower_exportsettings.html
+++ b/rowers/templates/rower_exportsettings.html
@@ -63,7 +63,10 @@
{% endif %}
Garmin Connnect has no manual sync, so connecting your account to your Garmin account will
- automatically auto-sync workouts from Garmin to Rowsandall (but not in the other direction).
+ automatically auto-sync workouts from Garmin to Rowsandall (but not in the other direction). If you
+ want to export our structured workout sessions to your Garmin device, you have to set the "Garmin Activity"
+ to a activity type that is supported by your watch. Not all watches support "Custom" activities, so
+ you may have to set your activity to Run or Ride while rowing.
Strava Auto Import also imports activity changes on Strava to Rowsandall, except when you delete
diff --git a/rowers/urls.py b/rowers/urls.py
index 0377375f..7f002f21 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -769,6 +769,8 @@ urlpatterns = [
name='plannedsession_templateedit_view'),
re_path(r'^sessions/(?P\d+)/maketemplate/$',views.plannedsession_totemplate_view,
name='plannedsession_totemplate_view'),
+ re_path(r'^sessions/(?P\d+)/togarmin/$',views.plannedsession_togarmin_view,
+ name='plannedsession_togarmin_view'),
re_path(r'^sessions/(?P\d+)/compare/$',
views.plannedsession_compare_view,
name='plannedsession_compare_view'),
diff --git a/rowers/utils.py b/rowers/utils.py
index defdd0af..0c1a35d4 100644
--- a/rowers/utils.py
+++ b/rowers/utils.py
@@ -775,7 +775,7 @@ def ps_dict_order_dict(d,short=False):
return sdict2
-def ps_dict_order(d,short=False,rower=None):
+def ps_dict_order(d,short=False,rower=None,html=True):
sdict = collections.OrderedDict({})
steps = d['steps']
@@ -836,7 +836,10 @@ def ps_dict_order(d,short=False,rower=None):
holduntil.append(item['repeatID'])
multiplier.append(item['repeatValue'])
factor *= item['repeatValue']
- spaces += ' '
+ if html:
+ spaces += ' '
+ else:
+ spaces += ' '
if item['type'] == 'Step':
item['string'] = spaces+item['string']
sdict3.append(item)
@@ -847,7 +850,10 @@ def ps_dict_order(d,short=False,rower=None):
if item['stepID'] == holduntil[-1]:
sdict3.append(hold.pop())
factor /= multiplier.pop()
- spaces = spaces[:-18]
+ if html:
+ spaces = spaces[:-18]
+ else:
+ spaces = spaces[:-3]
holduntil.pop()
else: # pragma: no cover
prevstep = sdict3.pop()
@@ -861,7 +867,10 @@ def ps_dict_order(d,short=False,rower=None):
factor /= multiplier.pop()
sdict3.append(prevstep)
holduntil.pop()
- spaces = spaces[:-18]
+ if html:
+ spaces = spaces[:-18]
+ else:
+ spaces = spaces[:-3]
sdict = list(reversed(sdict3))
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index dc117e6b..e9a75884 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -8,6 +8,7 @@ from rowingdata import trainingparser
import json
from taggit.models import Tag
+import rowers.garmin_stuff as gs
@login_required
@permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True)
@@ -1979,6 +1980,37 @@ def plannedsession_templateedit_view(request,id=0):
})
+@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
+@user_passes_test(can_plan, login_url="/rowers/paidplans/",
+ message="This functionality requires a Coach or Self-Coach plan",
+ redirect_field_name=None)
+def plannedsession_togarmin_view(request,id=0):
+
+ r = getrequestplanrower(request)
+
+ startdate, enddate = get_dates_timeperiod(request)
+
+ ps = get_object_or_404(PlannedSession,pk=id)
+
+ result = gs.garmin_session_create(ps,r.user)
+
+ if not result:
+ messages.error(request,'You failed to export your session to Garmin Connect')
+ else:
+ messages.info(request,'Session is now on Garmin Connect. Sync your Garmin watch')
+
+ url = reverse(plannedsession_view,kwargs={'userid':r.user.id,
+ 'id':ps.id,})
+
+ startdatestring = startdate.strftime('%Y-%m-%d')
+ enddatestring = enddate.strftime('%Y-%m-%d')
+ url += '?when='+startdatestring+'/'+enddatestring
+
+ next = request.GET.get('next', url)
+
+ return HttpResponseRedirect(next)
+
+
@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
@user_passes_test(can_plan, login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
@@ -2385,6 +2417,7 @@ def plannedsession_view(request,id=0,userid=0):
steps = ps_dict_get_description_html(d,short=False)
+
return render(request,'plannedsessionview.html',
{
'psdict': psdict,
From a9da920ffa389050608130f772bf4467544251a1 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 16 May 2021 16:07:24 +0200
Subject: [PATCH 3/9] fixing tests
---
rowers/garmin_stuff.py | 4 ++--
rowers/tests/test_plans.py | 4 +++-
rowers/tests/test_user.py | 1 +
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index 9b5bf773..2943a26a 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -278,7 +278,7 @@ def step_to_garmin(step,order=0):
except KeyError:
targetValueHigh = None
- if targetType.lower() == "power":
+ if targetType is not None and targetType.lower() == "power":
targetType = 'POWER'
if targetValue is not None and targetValue > 1000:
targetValue -= 1000
@@ -288,7 +288,7 @@ def step_to_garmin(step,order=0):
targetValueLow -= 1000
# temp - throw away target other than power
- if targetType.lower() != 'power':
+ if targetType is not None and targetType.lower() != 'power':
targetType = None
targetValue = None
targetValueLow = None
diff --git a/rowers/tests/test_plans.py b/rowers/tests/test_plans.py
index ecb90802..c9085517 100644
--- a/rowers/tests/test_plans.py
+++ b/rowers/tests/test_plans.py
@@ -13,6 +13,8 @@ from rowers import garmin_stuff
import rowers.plannedsessions as plannedsessions
from django.db import transaction
+import json
+
from rowers.views.workoutviews import plannedsession_compare_view
from rowers.views.otherviews import download_fit
from rowers.opaque import encoder
@@ -1899,7 +1901,7 @@ description: ""
self.assertEqual(len(stepsdict),2)
response = garmin_stuff.ps_to_garmin(self.ps_trimp,self.r)
- self.assertEqual(response.status_code,200)
+ self.assertEqual(len(json.dumps(response)),861)
url = '0'
request = self.factory.get(url)
diff --git a/rowers/tests/test_user.py b/rowers/tests/test_user.py
index 47ed565b..cf604395 100644
--- a/rowers/tests/test_user.py
+++ b/rowers/tests/test_user.py
@@ -143,6 +143,7 @@ class UserPreferencesTest(TestCase):
form_data = {
'stravaexportas':'Rowing',
+ 'garminactivity': 'RUNNING',
'polar_auto_import':True,
'c2_auto_export':False,
'c2_auto_import':False,
From e88553a04c931726418bdc0c1b4c89f81e20818d Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 16 May 2021 18:03:45 +0200
Subject: [PATCH 4/9] adding test plans
---
rowers/tests/test_plans.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rowers/tests/test_plans.py b/rowers/tests/test_plans.py
index c9085517..c30433af 100644
--- a/rowers/tests/test_plans.py
+++ b/rowers/tests/test_plans.py
@@ -1901,7 +1901,7 @@ description: ""
self.assertEqual(len(stepsdict),2)
response = garmin_stuff.ps_to_garmin(self.ps_trimp,self.r)
- self.assertEqual(len(json.dumps(response)),861)
+ self.assertTrue(len(json.dumps(response))>800)
url = '0'
request = self.factory.get(url)
From d195f03a55af5af538f6ed065067254db25e7e2b Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 17 May 2021 09:07:04 +0200
Subject: [PATCH 5/9] adding some tests for garmin training api
---
rowers/garmin_stuff.py | 19 ++++++++--------
rowers/tests/mocks.py | 30 +++++++++++++++++++++++++
rowers/tests/test_imports.py | 43 ++++++++++++++++++++++++++++++++++++
3 files changed, 82 insertions(+), 10 deletions(-)
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index 2943a26a..3fbe874f 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -133,7 +133,7 @@ def garmin_processcallback(redirect_response,resource_owner_key,resource_owner_s
verifier = oauth_response.get('oauth_verifier')
token = oauth_response.get('oauth_token')
access_token_url = 'https://connectapi.garmin.com/oauth-service/oauth/access_token'
- print(token,access_token_url)
+
# Using OAuth1Session
garmin = OAuth1Session(oauth_data['client_id'],
@@ -193,9 +193,9 @@ def get_garmin_workout_list(user): # pragma: no cover
return result
-def garmin_can_export_session(user): # pragma: no cover
+def garmin_can_export_session(user):
if user.rower.rowerplan not in ['coach','plan']:
- return False
+ return False # pragma: no cover
result = get_garmin_permissions(user)
if 'WORKOUT_IMPORT' in result:
return True
@@ -215,10 +215,10 @@ def step_to_garmin(step,order=0):
except KeyError:
intensity = None
#durationvaluetype = ''
- if durationtype == 'Time': # pragma: no cover
+ if durationtype == 'Time':
durationtype = 'TIME'
durationvalue = int(durationvalue/1000.)
- elif durationtype == 'Distance': # pragma: no cover
+ elif durationtype == 'Distance':
durationtype = 'DISTANCE'
durationvalue = int(durationvalue/100)
durationvaluetype = 'METER'
@@ -375,7 +375,7 @@ def ps_to_garmin(ps,r):
-def get_garmin_permissions(user): # pragma: no cover
+def get_garmin_permissions(user):
r = Rower.objects.get(user=user)
if (r.garmintoken == '') or (r.garmintoken is None):
s = "Token doesn't exist. Need to authorize"
@@ -399,7 +399,7 @@ def get_garmin_permissions(user): # pragma: no cover
return []
-def garmin_session_create(ps,user): # pragma: no cover
+def garmin_session_create(ps,user):
if not ps.steps:
return 0
if not garmin_can_export_session(user):
@@ -427,15 +427,13 @@ def garmin_session_create(ps,user): # pragma: no cover
response = requests.post(url,auth=garminheaders,json=payload)
-
-
if response.status_code != 200:
return 0
garmin_workout_id = response.json()['workoutId']
- url = 'http://apis.garmin.com/training-api/schedule'
+ url = 'https://apis.garmin.com/training-api/schedule'
payload = {
'workoutId': garmin_workout_id,
@@ -444,6 +442,7 @@ def garmin_session_create(ps,user): # pragma: no cover
response = requests.post(url,auth=garminheaders,json=payload)
+
if response.status_code != 200:
return 0
diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py
index 66ba56bf..80ad25d5 100644
--- a/rowers/tests/mocks.py
+++ b/rowers/tests/mocks.py
@@ -874,6 +874,10 @@ def mocked_requests(*args, **kwargs):
return MockResponse(json_data,200)
+ if len(args)==1 and 'userPermissions' in args[0]:
+ json_data = ['WORKOUT_IMPORT','ACTIVITY_EXPORT']
+ return MockResponse(json_data,200)
+
if 'garmin' in args:
return MockOAuth1Session()
@@ -882,6 +886,8 @@ def mocked_requests(*args, **kwargs):
args = [kwargs['url']]
if "tofit" in kwargs['url']:
args = [kwargs['url']]
+ if "tojson" in kwargs['url']:
+ args = [kwargs['url']]
if not args:
return MockSession()
@@ -997,10 +1003,23 @@ def mocked_requests(*args, **kwargs):
garmindownloadregex = '.*?garmin\.com\/mockfile?id=1'
garmindownloadtester = re.compile(garmindownloadregex)
+ garmintrainingregex = '.*?garmin\.com\/training-api\/workout'
+ garmintrainingtester = re.compile(garmintrainingregex)
+
+ garmintrainingscheduleregex = '.*?garmin\.com\/training-api\/schedule'
+ garmintrainingscheduletester = re.compile(garmintrainingscheduleregex)
if garmintester.match(args[0]):
if garmindownloadtester.match(args[0]):
return MockStreamResponse('rowers/tests/testdata/3x250m.fit',200)
+ if garmintrainingtester.match(args[0]):
+ json_data = {
+ 'workoutId':1212,
+ }
+ return MockResponse(json_data,200)
+ if garmintrainingscheduletester.match(args[0]):
+ json_data = 1234
+ return MockResponse(json_data,200)
if stravaathletetester.match(args[0]):
json_data = stravaathletejson
@@ -1254,6 +1273,8 @@ class MockResponse:
def json(self):
return self.json_data
+
+
class MockOAuth1Session:
def __init__(self,*args, **kwargs):
pass
@@ -1264,5 +1285,14 @@ class MockOAuth1Session:
def post(*args, **kwargs):
return MockResponse({},200)
+ def fetch_request_token(*args, **kwargs):
+ return {
+ 'oauth_token':'aap',
+ 'oauth_token_secret':'noot',
+ }
+
+ def authorization_url(*args, **kwargs):
+ return 'url'
+
def mocked_invoiceid(*args,**kwargs):
return 1
diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py
index bfef0eee..556f3b93 100644
--- a/rowers/tests/test_imports.py
+++ b/rowers/tests/test_imports.py
@@ -18,6 +18,9 @@ from rowers import stravastuff
import urllib
import json
+from django.db import transaction
+import rowers.garmin_stuff as gs
+
@pytest.mark.django_db
@override_settings(TESTING=True)
class GarminObjects(DjangoTestCase):
@@ -35,11 +38,31 @@ class GarminObjects(DjangoTestCase):
)
self.r.garmintoken = 'dfdzf'
self.r.garminrefreshtoken = 'fsls'
+ self.r.rowerplan = 'plan'
self.r.save()
self.c.login(username='john',password='koeinsloot')
self.nu = datetime.datetime.now()
+ startdate = nu.date()
+ enddate = (nu+datetime.timedelta(days=3)).date()
+ preferreddate = startdate
+
+ self.ps_trimp = SessionFactory(
+ startdate=startdate,enddate=enddate,
+ sessiontype='test',
+ sessionmode = 'TRIMP',
+ criterium = 'none',
+ sessionvalue = 77,
+ sessionunit='none',
+ preferreddate=preferreddate,
+ manager=self.u,
+ )
+
+ self.ps_trimp.interval_string = '10min+4x1000m@200W/20sec+2000m@24spm+10min'
+ self.ps_trimp.save()
+
+
def tearDown(self):
ws = Workout.objects.filter(user=self.r)
for w in ws:
@@ -137,6 +160,26 @@ class GarminObjects(DjangoTestCase):
self.assertEqual(res,1)
+ @patch('rowers.garmin_stuff.OAuth1Session')
+ def notest_garmin_callback(self,MockOAuth1Session):
+ with transaction.atomic():
+ response = self.c.get('/garmin_callback/?oauth_token=528ea5d9-1163-434d-b172-f428c5d9f522&oauth_verifier=LW33ZMBP8H')
+ self.assertEqual(response.status_code, 200)
+
+ @patch('rowers.garmin_stuff.requests.get',side_effect=mocked_requests)
+ def test_garmin_can_export_session(self,mock_get):
+ result = gs.garmin_can_export_session(self.u)
+ self.assertTrue(result)
+
+ def test_ps_to_garmin(self):
+ res = gs.ps_to_garmin(self.ps_trimp,self.r)
+ self.assertTrue(len(json.dumps(res))>500)
+
+ @patch('rowers.garmin_stuff.requests.get',side_effect=mocked_requests)
+ @patch('rowers.garmin_stuff.requests.post',side_effect=mocked_requests)
+ def test_garmin_session_create(self,mock_get,mock_post):
+ res = gs.garmin_session_create(self.ps_trimp,self.u)
+ self.assertEqual(res,1212)
@pytest.mark.django_db
From ce9621890ab7da4ed45a9f08e21ff2e9ed292582 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 17 May 2021 09:53:00 +0200
Subject: [PATCH 6/9] more tests and coverage
---
rowers/garmin_stuff.py | 30 +++++++++++++++---------------
rowers/stravastuff.py | 2 +-
rowers/tasks.py | 2 +-
rowers/tests/test_imports.py | 8 ++++++++
rowers/views/planviews.py | 6 +++---
5 files changed, 28 insertions(+), 20 deletions(-)
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index 3fbe874f..ee4d1e9f 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -200,7 +200,7 @@ def garmin_can_export_session(user):
if 'WORKOUT_IMPORT' in result:
return True
- return False
+ return False # pragma: no cover
from rowers import utils
@@ -261,19 +261,19 @@ def step_to_garmin(step,order=0):
try:
targetValue = step['dict']['targetValue']
- if targetValue == 0:
+ if targetValue == 0: # pragma: no cover
targetValue = None
except KeyError:
targetValue = None
try:
targetValueLow = step['dict']['targetValueLow']
- if targetValueLow == 0:
+ if targetValueLow == 0: # pragma: no cover
targetValueLow = None
except KeyError:
targetValueLow = None
try:
targetValueHigh = step['dict']['targetValueHigh']
- if targetValueHigh == 0:
+ if targetValueHigh == 0: # pragma: no cover
targetValueHigh = None
except KeyError:
targetValueHigh = None
@@ -282,9 +282,9 @@ def step_to_garmin(step,order=0):
targetType = 'POWER'
if targetValue is not None and targetValue > 1000:
targetValue -= 1000
- if targetValueHigh is not None and targetValueHigh > 1000:
+ if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover
targetValueHigh -= 1000
- if targetValueLow is not None and targetValueLow > 1000:
+ if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover
targetValueLow -= 1000
# temp - throw away target other than power
@@ -377,7 +377,7 @@ def ps_to_garmin(ps,r):
def get_garmin_permissions(user):
r = Rower.objects.get(user=user)
- if (r.garmintoken == '') or (r.garmintoken is None):
+ if (r.garmintoken == '') or (r.garmintoken is None): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
@@ -397,19 +397,19 @@ def get_garmin_permissions(user):
if result.status_code == 200:
return result.json()
- return []
+ return [] # pragma: no cover
def garmin_session_create(ps,user):
if not ps.steps:
- return 0
+ return 0 # pragma: no cover
if not garmin_can_export_session(user):
- return 0
+ return 0 # pragma: no cover
if ps.garmin_schedule_id != 0:
- return ps.garmin_schedule_id
+ return ps.garmin_schedule_id # pragma: no cover
r = Rower.objects.get(user=user)
- if (r.garmintoken == '') or (r.garmintoken is None):
+ if (r.garmintoken == '') or (r.garmintoken is None): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
@@ -427,7 +427,7 @@ def garmin_session_create(ps,user):
response = requests.post(url,auth=garminheaders,json=payload)
- if response.status_code != 200:
+ if response.status_code != 200: # pragma: no cover
return 0
garmin_workout_id = response.json()['workoutId']
@@ -442,9 +442,9 @@ def garmin_session_create(ps,user):
response = requests.post(url,auth=garminheaders,json=payload)
-
- if response.status_code != 200:
+
+ if response.status_code != 200: # pragma: no cover
return 0
ps.garmin_schedule_id = response.json()
diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py
index 5ff2fda8..7e13afbd 100644
--- a/rowers/stravastuff.py
+++ b/rowers/stravastuff.py
@@ -383,7 +383,7 @@ def async_get_workout(user,stravaid):
# Get a Strava workout summary data and stroke data by ID
def get_workout(user,stravaid,do_async=False):
- if do_async:
+ if do_async: # pragma: no cover
res = async_get_workout(user,stravaid)
return {},pd.DataFrame()
try:
diff --git a/rowers/tasks.py b/rowers/tasks.py
index b4d9b3b2..32c050d6 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -3364,7 +3364,7 @@ def fetch_strava_workout(stravatoken,oauth_data,stravaid,csvfilename,userid,debu
try:
thetimezone = workoutsummary['timezone']
- except:
+ except: # pragma: no cover
thetimezone = 'UTC'
try:
diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py
index 556f3b93..05706e37 100644
--- a/rowers/tests/test_imports.py
+++ b/rowers/tests/test_imports.py
@@ -181,6 +181,14 @@ class GarminObjects(DjangoTestCase):
res = gs.garmin_session_create(self.ps_trimp,self.u)
self.assertEqual(res,1212)
+ @patch('rowers.garmin_stuff.requests.get',side_effect=mocked_requests)
+ @patch('rowers.garmin_stuff.requests.post',side_effect=mocked_requests)
+ def test_togarmin_view(self,mock_get,mock_post):
+ url = reverse('plannedsession_togarmin_view',kwargs={'id':self.ps_trimp.id})
+ response = self.c.get(url,follow=True)
+
+ self.assertEqual(response.status_code,200)
+
@pytest.mark.django_db
@override_settings(TESTING=True)
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index e9a75884..9d02627a 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1994,9 +1994,9 @@ def plannedsession_togarmin_view(request,id=0):
result = gs.garmin_session_create(ps,r.user)
- if not result:
- messages.error(request,'You failed to export your session to Garmin Connect')
- else:
+ if not result: # pragma: no cover
+ messages.error(request,'You failed to export your session to Garmin Connect') # pragma: no cover
+ else: # pragma: no cover
messages.info(request,'Session is now on Garmin Connect. Sync your Garmin watch')
url = reverse(plannedsession_view,kwargs={'userid':r.user.id,
From f803878691e58c83005a37a07e9e6b8f85bf0467 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 17 May 2021 17:54:56 +0200
Subject: [PATCH 7/9] using targetValueHigh and targetValueLow as recommended
---
rowers/garmin_stuff.py | 32 +++++++++++++++++++-------------
1 file changed, 19 insertions(+), 13 deletions(-)
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index ee4d1e9f..5237f927 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -267,32 +267,38 @@ def step_to_garmin(step,order=0):
targetValue = None
try:
targetValueLow = step['dict']['targetValueLow']
- if targetValueLow == 0: # pragma: no cover
+ if targetValueLow == 0 and targetValue is not None and targetValue > 0:
+ targetValueLow = targetValue
+ targetValue = None
+ elif targetValueLow == 0: # pragma: no cover
targetValueLow = None
except KeyError:
targetValueLow = None
try:
targetValueHigh = step['dict']['targetValueHigh']
- if targetValueHigh == 0: # pragma: no cover
+ if targetValue is not None and targetValue > 0 and targetValueHigh == 0:
+ targetValueHigh = targetValue
+ targetValue = 0
+ elif targetValueHigh == 0: # pragma: no cover
targetValueHigh = None
except KeyError:
targetValueHigh = None
if targetType is not None and targetType.lower() == "power":
targetType = 'POWER'
- if targetValue is not None and targetValue > 1000:
- targetValue -= 1000
- if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover
- targetValueHigh -= 1000
- if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover
- targetValueLow -= 1000
+ #if targetValue is not None and targetValue > 1000:
+ # targetValue -= 1000
+ #if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover
+ # targetValueHigh -= 1000
+ #if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover
+ # targetValueLow -= 1000
# temp - throw away target other than power
- if targetType is not None and targetType.lower() != 'power':
- targetType = None
- targetValue = None
- targetValueLow = None
- targetValueHigh = None
+ #if targetType is not None and targetType.lower() != 'power':
+ # targetType = None
+ # targetValue = None
+ # targetValueLow = None
+ # targetValueHigh = None
if targetValue is None and targetValueLow is None and targetValueHigh is None:
targetType = None
From 44c13ebe2496ce5ffc390b591fd69d626df614d6 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 18 May 2021 07:53:06 +0200
Subject: [PATCH 8/9] ugly code to convert to Garmin API payload
---
rowers/garmin_stuff.py | 33 ++++++++++++++++++---------------
1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index 5237f927..43335cc8 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -265,6 +265,16 @@ def step_to_garmin(step,order=0):
targetValue = None
except KeyError:
targetValue = None
+
+ if targetType is not None and targetType.lower() == "power":
+ targetType = 'POWER'
+ if targetValue is not None and targetValue <= 1000:
+ targetValueType = 'PERCENT'
+ else:
+ targetValueType = None
+ targetValue -= 1000
+
+
try:
targetValueLow = step['dict']['targetValueLow']
if targetValueLow == 0 and targetValue is not None and targetValue > 0:
@@ -272,6 +282,10 @@ def step_to_garmin(step,order=0):
targetValue = None
elif targetValueLow == 0: # pragma: no cover
targetValueLow = None
+ elif targetValueLow <= 1000 and targetType == 'POWER':
+ targetValueType = 'PERCENT'
+ elif targetValueLow > 1000 and targetType == 'POWER':
+ targetValueLow -= 1000
except KeyError:
targetValueLow = None
try:
@@ -279,26 +293,15 @@ def step_to_garmin(step,order=0):
if targetValue is not None and targetValue > 0 and targetValueHigh == 0:
targetValueHigh = targetValue
targetValue = 0
+ elif targetValueHigh <= 1000 and targetType == 'POWER':
+ targetValueType = 'PERCENT'
+ elif targetValueHigh > 1000 and targetType == 'POWER':
+ targetValueHigh -= 1000
elif targetValueHigh == 0: # pragma: no cover
targetValueHigh = None
except KeyError:
targetValueHigh = None
- if targetType is not None and targetType.lower() == "power":
- targetType = 'POWER'
- #if targetValue is not None and targetValue > 1000:
- # targetValue -= 1000
- #if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover
- # targetValueHigh -= 1000
- #if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover
- # targetValueLow -= 1000
-
- # temp - throw away target other than power
- #if targetType is not None and targetType.lower() != 'power':
- # targetType = None
- # targetValue = None
- # targetValueLow = None
- # targetValueHigh = None
if targetValue is None and targetValueLow is None and targetValueHigh is None:
targetType = None
From 867825b607037e603f7446b7f8fb90adaf03a371 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 18 May 2021 08:34:06 +0200
Subject: [PATCH 9/9] coverage and test
---
rowers/garmin_stuff.py | 14 +++++++-------
rowers/tasks.py | 2 +-
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py
index 43335cc8..f82334a7 100644
--- a/rowers/garmin_stuff.py
+++ b/rowers/garmin_stuff.py
@@ -269,7 +269,7 @@ def step_to_garmin(step,order=0):
if targetType is not None and targetType.lower() == "power":
targetType = 'POWER'
if targetValue is not None and targetValue <= 1000:
- targetValueType = 'PERCENT'
+ targetValueType = 'PERCENT' # pragma: no cover
else:
targetValueType = None
targetValue -= 1000
@@ -277,25 +277,25 @@ def step_to_garmin(step,order=0):
try:
targetValueLow = step['dict']['targetValueLow']
- if targetValueLow == 0 and targetValue is not None and targetValue > 0:
+ if targetValueLow == 0 and targetValue is not None and targetValue > 0: # pragma: no cover
targetValueLow = targetValue
targetValue = None
elif targetValueLow == 0: # pragma: no cover
targetValueLow = None
- elif targetValueLow <= 1000 and targetType == 'POWER':
+ elif targetValueLow <= 1000 and targetType == 'POWER': # pragma: no cover
targetValueType = 'PERCENT'
- elif targetValueLow > 1000 and targetType == 'POWER':
+ elif targetValueLow > 1000 and targetType == 'POWER': # pragma: no cover
targetValueLow -= 1000
except KeyError:
targetValueLow = None
try:
targetValueHigh = step['dict']['targetValueHigh']
- if targetValue is not None and targetValue > 0 and targetValueHigh == 0:
+ if targetValue is not None and targetValue > 0 and targetValueHigh == 0: # pragma: no cover
targetValueHigh = targetValue
targetValue = 0
- elif targetValueHigh <= 1000 and targetType == 'POWER':
+ elif targetValueHigh <= 1000 and targetType == 'POWER': # pragma: no cover
targetValueType = 'PERCENT'
- elif targetValueHigh > 1000 and targetType == 'POWER':
+ elif targetValueHigh > 1000 and targetType == 'POWER': # pragma: no cover
targetValueHigh -= 1000
elif targetValueHigh == 0: # pragma: no cover
targetValueHigh = None
diff --git a/rowers/tasks.py b/rowers/tasks.py
index ea06d589..5ce2807d 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -2993,7 +2993,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
verified = data['verified']
try:
startdatetime = iso8601.parse_date(data['date_utc'])
- except:
+ except: # pragma: no cover
startdatetime = iso8601.parse_date(data['date'])
weightclass = data['weight_class']