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,