Adventures connecting a Garmin Forerunner 305 to Linux, and on to Strava.
To talk to my Garmin unit I used gpsbabel.
gpsbabel -t -i garmin -f usb: -x track,pack,split,title="ACTIVE LOG # %Y%m%d" -o gpx,garminextensions -F outputfile.gpx
Option | Meaning |
---|---|
-t | Download track information. GPS data is generally classed either as a track which is a collection of position points each with its own date and time, a course which is a collection of position points usually spread out more sparsely than track points, or a waypoint which is a single position point. |
-i garmin | Input file format: Garmin serial/USB protocol |
-f usb: | Input file: USB port |
-x track,pack,split,title="ACTIVE LOG # %Y%m%d" | Invoke filter to split tracks apart and name them by the date they were recorded. This option still generates one output file contaiing the split data. |
-o gpx,garminextensions | Output in standard gpx format. Adding garminextensions downloads
nonstandard gps data as well, for example heartrate data. |
-F outputfile.gpx | Output file: save it as outputfile.gpx |
For this I used the split_logbooks perl script from gpsvisualizer.com.
split_logbooks outputfile.gpx /where/to/save/split/files
This should create a list of gpx files which are split by date, e.g.:
ACTIVE LOG # 20140608.gpx ACTIVE LOG # 20140610.gpx ACTIVE LOG # 20140622.gpx ACTIVE LOG # 20140624.gpx ACTIVE LOG # 20140626.gpx ACTIVE LOG # 20140703.gpx
I don't get around to deleting tracks on the garmin very frequently, so most of the files generated will be ones I've already downloaded. This shell script performs steps 1 and 2 and only saves new files into an archive directory. The script also allows you to execute commands on only the new files.
#!/bin/bash DOWNLOAD_DIR="/home/nick/gps/download/" ARCHIVE_DIR="/home/nick/gps/archive/" GPS_SPLIT="/home/nick/gps/split_logbooks" gpsbabel -t -i garmin -f usb: -x track,pack,split,title="ACTIVE LOG # %Y%m%d" -o gpx,garminextensions -F outputfile.gpx $GPS_SPLIT outputfile.gpx $DOWNLOAD_DIR for FILE_PATH in $DOWNLOAD_DIR*.gpx; do FILENAME=${FILE_PATH#$DOWNLOAD_DIR} if [[ ! -f $ARCHIVE_DIR$FILENAME ]]; then echo "Found new activity: $FILENAME" # # If you want to do something to the new files, # you can do it here. The new filename is stored # in the variable "$FILE_PATH" # mv "$FILE_PATH" $ARCHIVE_DIR else echo "Ignoring repeat activity: $FILENAME" rm "$FILE_PATH" fi done
This requires a little setup work, but once you have done this everything is pretty straight forward. You need to have python installed along with the stravalib python library.
To upload gpx files outside of the usual web interface you first have to let Strava get to know you better. This is done by applying for an API key here: www.strava.com/developers
As part of the request process you will need to describe your application, e.g. "GPX upload tool", and provide an authorization callback domain. Applications which use the Strava API have to seek user authorisation from Strava the first time they are used and the mandatory "authorization callback domain" field is the website it points your browser to once this process is complete. For our purposes here it just needs to be the name of a real website, e.g. www.dajda.net.
Once you have submitted an application Strava will send you two (very long) API keys: a public access token and a client secret token, and also a client ID. Make a note of all these.
Insert your client ID (the short number) and the authorization callback domain you used
into the following script and run it (python strava_script_1.py
)
from stravalib import Client MY_STRAVA_CLIENT_ID = Your Strava client ID AUTHORIZATION_CALLBACK_DOMAIN = "Your authorization callback domain here, in quotes, in the form http://my.callback.domin" client = Client() url = client.authorization_url(client_id=MY_STRAVA_CLIENT_ID, redirect_uri=AUTHORIZATION_CALLBACK_DOMAIN, scope='view_private,write') print url print url
This will return a url. Enter this in your browser and it will ask you to confirm you want to grant your application access to your
account data. Once you have OKed this, it will redirect you to the authorization callback domain page you entered.
In the new URL in the address bar will be something line ?state=&code=[a long alphanumeric string]
. This is a temporary
authorization string. Make a note of this and enter it into the following python script:
from stravalib import Client BROWSER_CODE= "The string you just noted, in quotes" MY_STRAVA_CLIENT_ID = Your Strava client ID MY_STRAVA_CLIENT_SECRET = "The secret access token Strava gave you, in quotes" client = Client() access_token = client.exchange_code_for_token(client_id=MY_STRAVA_CLIENT_ID, client_secret=MY_STRAVA_CLIENT_SECRET, code=BROWSER_CODE) print access_token
This exchanges your temporary authorization string for a permanent one which your application can use until you go into Strava and revoke access to the application. Make a note of this string.
The python script below uploads a gpx file to your Strava account using the permanent authorization string from step 4.2.
Specify the gpx filename on the command line and don't forget to insert the permanent authorization string into the code.
It will set the activity as private by default. This is useful if you want to filter out commuting and regular training activities.
Alternatively just set PRIVATE_BY_DEFAULT
to False
to make everything public.
from stravalib import Client import sys import smtplib # Strava access token STORED_ACCESS_TOKEN = "Put the permanent access token here, in quotes" # Whether to make private by default PRIVATE_BY_DEFAULT = True # Interpret command line try: ACTIVITY_FILE_NAME = sys.argv[1] except: print "Usage: %s FILE" % sys.argv[0] sys.exit(1) # Conect to Strava client = Client(access_token=STORED_ACCESS_TOKEN) upload_worked = False print "Uploading '%s'" % ACTIVITY_FILE_NAME activity_file = open(ACTIVITY_FILE_NAME, 'r') try: upload = client.upload_activity(activity_file, "gpx") except: summary = "client.upload_activity - upload failed" else: try: activity = upload.wait(timeout=60) except: summary = "Activity upload failed" else: summary = "Successfully uploaded activity: \n" summary += " %s: %s\n\n" % (ACTIVITY_FILE_NAME, activity.name) # Mark it as private? if PRIVATE_BY_DEFAULT is True: try: client.update_activity(activity.id, private=True) except: summary += "Note: Could not set activity to private\n\n" # Report some summary info activity_detailed = client.get_activity(activity.id) summary += "Moving time: %s\n" % activity_detailed.moving_time summary += "Distance: %.1f miles\n" % (activity_detailed.distance / 1609.34) summary += "Elevation gain: %d m\n" % int(activity_detailed.total_elevation_gain) summary += "Average speed: %.1f miles/hour\n" % (activity_detailed.average_speed * 2.2369) summary += "Max speed: %.1f miles/hour\n" % (activity_detailed.max_speed * 2.2369) print summary # Do we need to signal bad news? if upload_worked == False: sys.exit(1)
Coming soon: How to combine all of the above so activities are automatically read from the Forerunner and uploaded to Strava when the Forerunner is plugged into its USB dock.