Running

Like Homer, recently I have picked up running again :) Since I love gadgets and tech stuff any run only counts when it is published on Strava :) And since I want to automate everything that can be automated, I needed a way to automatically upload the activities from my Forerunner 305 to my Strava account when I put it in the USB cradle (connected to my Linux desktop) for charging after a run.

Looking with a bit of an analytics view at the past couple of years, a certain pattern in my running habit can be discovered... Every year I start again with a 0 to 5KM start-to-run podcast created by Evy Gruyaerts. And every year I am super happy hearing her say in a very Belgian accent "ik ben fier op u" (I am proud of you). Then at the end of summer, we go on holidays and after that, it is getting dark and cold in the evenings again and I tend to skip the running. This then results in me starting with Evy at the beginning of the next year again :) This year I want to do it differently: I want to keep on running and also go for the longer distances!

When I first got in contact with Docker containers last year, I decided to see if I could create a container that could automatically upload my runs to Strava after I connected my Forerunner 305 to my Linux computer. In the post Notification of new USB devices in docker container I explained the technical requirements from a container perspective. Plugging in the device should trigger an automated reaction in the container, which requires that the container somehow has access to the USB devices of the host system.

In this post, I want to provide some insights in the script that is doing the actual retrieval of the activities from the Forerunner 305 device and uploading this to my Strava account.

In order to achieve this, you will need the following:

  • Strava account, obviously ;)
  • API key for a Strava application. You can request one via the Developer Labs website of Strava. This key is needed to do the authentication with Strava to upload your activity
  • Garmin forerunner tools (sudo apt-get install garmin-forerunner-tools under ubuntu)
  • Garmin-dev to convert .gmn files to .tcx files
  • curl to perform the POST queries needed for the upload to strava

Now the steps in the script are quite simple:

  1. Use the command garmin_save_runs to retrieve all activities from the 305 device
  2. For each .gmn file, check if a corresponding .tcx file exists. If this is not the case, this means that the activity corresponding to this .gmn file is new
  3. For each new activity, check if there exists a Trackpoint node in the tcx file. If the device was full, it might take some time before the .gmn file contains the full details of your activity. If you leave the device connected for some time and run the garmin_save_runs again you should see the .tcx file contains Trackpoint nodes.
    If the .tcx file does contain Trackpoint nodes, it will be uploaded using a curl to do a POST query to Strava

The above steps are performed by the following function in the bash-script:

retrieveConvertAndUpload () {
    sleep 2
    garmin_save_runs 

    #Now we need to verify which runs are new and which ones we already knew
    find . -type f -name "*.gmn" | while read garminFileFullPath 
    do
        garminFilename=`basename $garminFileFullPath .gmn`

        tcxFilename="$garminFilename.tcx"
        tcxFilenameFullPath="/data/tcx/$tcxFilename"
        if [ ! -f "$tcxFilenameFullPath" ] 
        then
            echo "Converting the new file $garminFilename to a TCX file"

            /app/garmin-dev/gmn2tcx "$garminFileFullPath" > "$tcxFilenameFullPath" 2>/dev/null

            
            #Now check if the tcx file contains a track node in the XML file.
            #If not, it means that the device did not save the file internally 
            #completely yet and the gmn file only contains the total averages 
            #for the activity. In that case, we must delete not only the tcx 
            #file, but also the gmn file to ensure it will be downloaded 
            #automatically next time again from the device
            
            numberOfTrackpoints=`cat "$tcxFilenameFullPath" | grep "<Trackpoint>" | wc -l`
        

            #If the number of trackpoints > 0, then upload this to strava.
            #Otherwise, delete both the tcx and the garmin file
        
            if [ $numberOfTrackpoints -gt 0 ]
            then
                echo "Uploading the new TCX file to strava"
                
                if [ "${STRAVA_KEY}" != "" ] 
                then    
                    stravaUploadResult=`curl -X POST https://www.strava.com/api/v3/uploads \
                                    -H "Authorization: Bearer ${STRAVA_KEY}" \
                                    -F activity_type=run \
                                    -F file=@$tcxFilenameFullPath \
                                    -F data_type=tcx`

                    stravaUploadID=`echo $stravaUploadResult |  sed 's/.*"id"://' |  sed 's/,.*//'`

                    stravaAuthorizationError=`echo ${stravaUploadID} | grep "Authorization Error" | wc -l`

                    if [ $stravaAuthorizationError -gt 0 ] 
                    then
                        echo "There was an authorization error while uploading the data to strava."
                        echo "Please check your API key"
                        else    
                        echo "Waiting 8 seconds"
                        sleep 8
                        echo "Status of latest upload (${stravaUploadID}):"
                        curl -G https://www.strava.com/api/v3/uploads/$stravaUploadID \
                            -H "Authorization: Bearer ${STRAVA_KEY}"
                        echo ""
                    fi
                else
                    echo "Not uploading as no strava-key is present"
                fi

            else
                echo "The tcx/gmn file only contains average information. Deleting both files to ensure full activity"
                echo "will be downloaded next time we connect"

                rm "$tcxFilenameFullPath"
                rm "$garminFileFullPath"
            fi
        fi
    done
}

You can view the complete source for the bash file in the GitHub repository.

And of course, I have also published this image to dockerhub, meaning you can run this whole script easily in a container with the following command:

docker run --privileged \
    -v /dev/bus/usb:/dev/bus/usb  \
    -v ~/.garmin-strava-uploader:/data \
    -i \
    -e STRAVA_KEY='<FILL IN YOUR STRAVA API KEY>' \
    -e GARMIN_DATA_DIR='/data'  \
    -t gdiepen/garmin-strava-uploader

If you have any questions or remarks, or you are just happy that you now also can automate the uploading of your activities, let me know via a comment!