Converting HRM Files to TCX

Polar WebSync logo
I recently posted an AWK script for combining GPX and HRM files into TCX format. This script is really handy when you have GPX files with or without matching HRM files, but what if you have HRM files without GPX files?

It did not immediately occur to me that there'd be any value in converting lone HRM files to TCX, since I think of TCX as being geographic location focused (which is not strictly true). However, prompted by Conrad, it became apparent that I could indeed convert HRM files (without GPX files) to TCX, and indeed, I already had 30 or so such HRM files that I'd been entering into Strava manually (stationary trainer sessions, and weights workouts).

So, here's the resulting AWK script:

# hrm2tcx.awk by Paul Colby (http://colby.id.au), no rights reserved ;)

$Id: hrm2tcx.awk 296 2012-02-24 11:22:18Z paul $

BEGIN { # Skip to the HR data in the HRM file. DISTANCE=0 # Distance is required by the TCX format. FS="=" while ((!FOUNDHRDATA) && (getline > 0)) { if ($1 == "Version") { HRMVERSION=$2 } else if ((HRMVERSION <= 105) && ($1 == "Mode")) { FLAG=int(substr($2,1,1)) # First integer flag (0, 1 or 3). HAVEALTITUDE=(FLAG == 1) ? 1 : 0 HAVECADENCE=(FLAG == 0) ? 1 : 0 IMPERIALUNITS=int(substr($2,3,1)); # Third bit flag (0 or 1). } else if ((HRMVERSION >= 106) && ($1 == "SMode")) { HAVEALTITUDE=int(substr($2,3,1)) # Third bit flag (0 or 1). HAVECADENCE=int(substr($2,2,1)) # Second bit flag (0 or 1). HAVESPEED=int(substr($2,1,1)) # First bit flag (0 or 1). IMPERIALUNITS=int(substr($2,8,1)); # Eighth bit flag (0 or 1). } else if ($1 == "Date") { STARTTIME=substr($2,1,4)" "substr($2,5,2)" "substr($2,7,2) } else if ($1 == "StartTime") { STARTTIME=STARTTIME" "substr($2,1,2)" "substr($2,4,2)" "substr($2,7,2) STARTTIME=mktime(STARTTIME) } else if ($1 == "Length") { DURATION=$2 } else if ($1 == "Interval") { HRMINTERVAL=int($2) } else if ($1 == "[Trip]") { getline DISTANCE # We'll use this one :) if (IMPERIALUNITS > 0) DISTANCE=(DISTANCE160.9344); # 1/10 miles to meters. else DISTANCE=(DISTANCE100); # 1/10 km to meters. getline ASCENT # Not used. getline TOTALTIME # Not used. getline AVGALTITUDE # Not used. getline MAXALTITUDE # Not used. getline AVGSPEED # Not used. getline MAXSPEED # We'll use this one :) if (IMPERIALUNITS > 0) MAXSPEED=(MAXSPEED160.9344/60.0/60.0); # 1/10 mph to m/s. else MAXSPEED=(MAXSPEED100.0 /60.0/60.0); # 1/10 km/h to m/s. getline ODOMETER # Not used. } else if (($1 == "[HRData]") || ($1 == "[HRData]\r")) { FOUND_HRDATA=NR } } FS=" "

printf "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n\ <TrainingCenterDatabase xmlns=\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2\"\ xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\ xsi:schemaLocation=\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2\ http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd\">\n"

printf "\n <Activities>\n" if (!SPORT) SPORT=(HAVECADENCE) ? "Biking" : "Other"; printf " <Activity Sport=\"%s\">\n", SPORT printf " <Id>%s%s</Id>\n <Lap StartTime=\"%s%s\">\n", strftime("%Y-%m-%dT%H:%M:%S", STARTTIME), TIMEZONE, strftime("%Y-%m-%dT%H:%M:%S", STARTTIME), TIMEZONE split(DURATION, DURATIONARRAY, ":"); DURATIONNUMBER=DURATIONARRAY[1]6060 + DURATIONARRAY[2]*60 + DURATIONARRAY[3]; printf " <TotalTimeSeconds>%s</TotalTimeSeconds>\n", DURATIONNUMBER printf " <DistanceMeters>%f</DistanceMeters>\n", DISTANCE if (MAXSPEED) { printf " <MaximumSpeed>%f</MaximumSpeed>\n", MAX_SPEED } printf " <Calories>0</Calories>\n" printf " <Intensity>Active</Intensity>\n <TriggerMethod>Manual</TriggerMethod>\n" printf " <Track>\n" DISTANCE=0 }

{ if (NF) { printf " <Trackpoint>\n" TIMESTAMP=STARTTIME + ((NR-FOUNDHRDATA-1) * HRMINTERVAL); printf " <Time>%s%s</Time>\n", strftime("%Y-%m-%dT%H:%M:%S", TIMESTAMP), TIMEZONE if ((HAVEALTITUDE == 0) && (ALTITUDE > 0)) { printf " <AltitudeMeters>%f</AltitudeMeters>\n", ALTITUDE ALTITUDE=0 } if (HAVEALTITUDE > 0) { ALTITUDE=(HRMVERSION <= 105) ? ALTITUDE=$3 : ALTITUDE=$(2+HAVESPEED+HAVECADENCE); if (HRMVERSION <= 102) ALTITUDE=(ALTITUDE*10); if (IMPERIALUNITS > 0) ALTITUDE=(ALTITUDE/0.3048); # feet to meters. printf " <AltitudeMeters>%f</AltitudeMeters>\n", ALTITUDE } if (HAVESPEED) { if (IMPERIALUNITS > 0) SPEED=($2160.9344/60.0/60.0); # 1/10 mph to m/s. else SPEED=($2100.0 /60.0/60.0); # 1/10 km/h to m/s. DISTANCE=DISTANCE + (SPEED * HRMINTERVAL) printf " <DistanceMeters>%f</DistanceMeters>\n", DISTANCE } if ($1) { printf " <HeartRateBpm xsi:type=\"HeartRateInBeatsPerMinutet\">\n" printf " <Value>%s</Value>\n", $1 printf " </HeartRateBpm>\n" } if (HAVECADENCE) printf " <Cadence>%s</Cadence>\n", $(2+HAVESPEED) printf " </Trackpoint>\n" } }

END { printf " </Track>\n </Lap>\n" printf " </Activity>\n </Activities>\n"

split("$Revision: 296 $", REVISION, " ") split("$Date: 2012-02-24 22:22:18 +1100 (Fri, 24 Feb 2012) $", DATE, " ") printf "\n <Author xsi:type=\"Application_t\"> \n\ <Name>Paul Colby's HRM to TCX Converter</Name> \n\ <Build> \n\ <Version> \n\ <VersionMajor>1</VersionMajor> \n\ <VersionMinor>1</VersionMinor> \n\ <BuildMajor>0</BuildMajor> \n\ <BuildMinor>%d</BuildMinor> \n\ </Version> \n\ <Type>Internal</Type> \n\ <Time>%sT%s%s</Time> \n\ <Builder>PaulColby</Builder> \n\ </Build> \n\ <LangID>EN</LangID> \n\ <PartNumber>636-F6C62-80</PartNumber> \n\ </Author>\n", REVISION[2], DATE[2], DATE[3], DATE[4]

printf "\n</TrainingCenterDatabase>\n" }

(You can download it from this direct link, or from the files list at the end of this article).

Usage

Usage is as follows:

gawk -f hrm2tcx.awk [-v ALTITUDE=n.n] [-v TIMEZONE=Z|+nnnn] file.hrm > file.tcx

How it Works

Its pretty straight-forward. In many ways, its really just a sub-set of my gpx2tcx.awk script, so rather than repeat myself, check out my previous Combining GPX and HRM Files into TCX Format post instead.

Windows Command Shell

I've also written a simple Windows command script to automatically call the above AWK script on all HRM files in the current directory which do not already have matching TCX files, nor matching GPX files (if you have matching GPX files, I suggest you use my Windows gpx2tcx batch file for those instead).

:: hrm2tcx.cmd by Paul Colby (http://colby.id.au), no rights reserved ;)
:: $Id: hrm2tcx.cmd 298 2012-02-24 22:09:33Z paul $
@echo off

:: Optional: uncomment the following line to change UTC timezones. set TIMEZONE=+11:00

:: Optional: uncomment the following line to set the altitude of the first point. :: This will do nothing if your HRM files actually include altitude data, but helps :: devices like the RCX5 with no altitude data, and sites like Strava that don't like that. ::set ALTITUDE=1.0

:: Update this path to include the location(s) UnxUtils is insstalled. set PATH=%PATH%;C:\Program Files\UnxUtils\usr\local\wbin;C:\Program Files (x86)\UnxUtils\usr\local\wbin

:: Jump the to "main" block. goto main

:convert if not exist "%~1.hrm" goto :EOF echo Processing %~1... gawk.exe -f "hrm2tcx.awk" -v ALTITUDE=%ALTITUDE% -v TIMEZONE=%TIMEZONE% "%~1.hrm" > %~1.tcx goto :EOF

:main FOR /f %%A IN ( 'ls.exe -1 .gpx *.hrm *.tcx 2^>^&1 ^| sed.exe -e "s/.[^.]$//" ^| uniq.exe -c ^| sed -ne "s/^ *1.//p"' ) DO call::convert %%A

pause

Enjoy!! :)

Attachments

comments powered by Disqus