Garmin Forerunner and Linux

Adventures connecting a Garmin Forerunner 305 to Linux, and on to Strava.

Step 1: Download data from the Garmin

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
-tDownload 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 garminInput 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,garminextensionsOutput in standard gpx format. Adding garminextensions downloads nonstandard gps data as well, for example heartrate data.
-F outputfile.gpxOutput file: save it as outputfile.gpx

Step 2: Split gpx file into separate track files

For this I used the split_logbooks perl script from

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

Step 3: Filter out the new stuff

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.



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

    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
        echo "Ignoring repeat activity: $FILENAME"
        rm "$FILE_PATH"

Step 4: Upload new gpx files to Strava

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.

Step 4.1: Become a Strava developer

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:

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.

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.

Step 4.2: First time authorization

Insert your client ID (the short number) and the authorization callback domain you used into the following script and run it (python

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.

Step 4.3: Upload it to Strava

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

# Interpret command line
    ACTIVITY_FILE_NAME = sys.argv[1]
    print "Usage: %s FILE" % sys.argv[0]

# 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')

    upload = client.upload_activity(activity_file, "gpx")
    summary = "client.upload_activity - upload failed"
        activity = upload.wait(timeout=60)
        summary = "Activity upload failed"
        summary = "Successfully uploaded activity: \n"
        summary += "   %s: %s\n\n" % (ACTIVITY_FILE_NAME,

        # Mark it as private?
        if PRIVATE_BY_DEFAULT is True:
                client.update_activity(, private=True)
                summary += "Note: Could not set activity to private\n\n"

        # Report some summary info        
        activity_detailed = client.get_activity(

        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:

Step 5: Putting it all together

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.