Java Garmin/ANT FIT file reader

The ANT ‘Flexibile & Interoperatble Data Transfer’ (FIT) protocol

ANT devices and Garmin GPS units, like the Edge 810 and 830 I use, store all device information in .FIT files. The FIT protocol is open and is defined in the FIT SDK. It is a binary protocol, not human readable, designed for embedded devices (having limited resources).

Since I wanted to decode the activity files, containing the GPS track, I decided to write a FIT file reader.

The .FIT file

The FIT file is extensively described on the FIT Developer Site. A short summary is presented here for overview and for definition of the terms used in the software.

A FIT file consists of

  • Header
    The header defines some overall stuff like versions, data size, “.FIT” and a header CRC. 12 bytes
  • Data records
    Records containing the data, including a definition of the data
  • CRC
    Used to validate the file, 2 bytes
The .FIT file format

Data Records come in two flavours:

  • Definition message
    A Definition Message defines a local message type (0-15), the message that it represents and the subset of message fields that are used in this local message type.
    After the Definition Message occurred, subsequent Data Messages may be added for this local message type, containing the data for this message.
    Note that a local message type may be redefined and reused for another message by a subsequent Definition Message for that local message type. This is necessary, since the only 16 local message types are available and more messages may be used in one file.
  • Data message
    A Data Message contains the data for the message (fields) defined by the local message type.
    A Data Message refers to the latest Definition Message defining its local message type as is shown in the image below.
Example of the payload, numbers indicate local message type

Note that the same message may occur in multiple Definition Messages. In the FitReader software each Definition Message results in a FitMessage instance. This means that there may be multiple FitMessages that refer to the same message. The software however contains functionality to request a List of all FitDataRecords based on message name

Available messages with all their possible message fields are defined in the Global Fit Profile. This is the master list and is an Excel file Profile.xslx that is part of the FIT SDK. The Excel contains two tabs: one with field type definitions (‘Types’), one with message including message field definition (‘Messages’).

Note that not all manufacturers limit the messages to the messages defined in the Global Fit Profile. Devices like the Garmin devices use custom messages of which the definition is not public. Therefore, in this FitReader software the excel /src/main/resources/Profile.xslx is extended with two additional tabs ‘Additional Types’ and ‘Additional Messages’, to make it extendable for example by reverse engineering. Both tabs have the same format as the original tabs

Note that the Definition Message allows for additional fields to be defined, the Developer Defined Message Fields. These fields do not occur in the Global Fit Profile but are defined (non-descriptive) in the Definition Message itself.

If you compare the .FIT file to a database, the Definition Message defines the table. The message is the table name. The message fields define the column names and format. The Data Records are the rows in the table. There may be multiple tables with the same name (message), the protocol even allows for a different subset of columns (message fields) per tables.

Refer to the FIT Developer Site for a detailed explanation of the file format.

The Reader

Usage

The .FIT reader is delivered as Github repository. The project results in a JAR file with all dependencies, together with Javadocs. The FitReader class is the entry point. The readFile() method does the job. It returns a FitMessageRepository instance, containing the FitMessages read. Each FitMessage contains a number of fields. Fit Messages Fields come in two types:

  • FitMessageFields predefined in Global Profile (see FIT SDK; FitMessageFields).
  • FitDeveloperFields. These are defined in the FIT file itself.

Field values of each message are provided in FitDataRecords, whose values can be obtained by the FitMessage class.

Refer to the javadocs, Github repository the FitReader source code and tests, for some example code.

FitReader design

Next diagram shows the usage:

Reading FIT files
  1. Obtain the instance of the FitReader
  2. Read a fit file
  3. This is delegated to readInputStream()
  4. A FitMessageRepository is created and populated based on the file content
  5. The FitMessageRepository is returned
  6. Get a list of Message names (as explained a message may occur multiple times)
  7. All instances of the Message can be requested based on name. For each instance:
  8. Request the field names of the fields within the Message
  9. For a particular field you can request the details
  10. Check the number of data records associated with the field
  11. Request a value of the record depending on the field type: getFloatValue(), getIntValue(), getTimeValue(), …

If you want to know what your .FIT files look like, a handy function of is dumpMessageDefinitions() of FitMessageRepository. It dumps the message definitions in the file to the log4J debug output (Info level).

dumpMessageDefinitions()
MESSAGE 0-file_id: 1 records, local message type 0
file_id (00000)        serial_number (003) type:         uint32z/        uint32z, units:             , scale 1.000000, offset 0.000000
file_id (00000)         time_created (004) type:       date_time/         uint32, units:             , scale 1.000000, offset 0.000000
file_id (00000)         manufacturer (001) type:    manufacturer/         uint16, units:             , scale 1.000000, offset 0.000000
file_id (00000)              product (002) type:          uint16/         uint16, units:             , scale 1.000000, offset 0.000000
file_id (00000)               number (005) type:          uint16/         uint16, units:             , scale 1.000000, offset 0.000000
file_id (00000)                 type (000) type:            file/           enum, units:             , scale 1.000000, offset 0.000000
MESSAGE 49-file_creator: 1 records, local message type 1
file_creator (00049)     software_version (000) type:          uint16/         uint16, units:             , scale 1.000000, offset 0.000000
file_creator (00049)     hardware_version (001) type:           uint8/          uint8, units:             , scale 1.000000, offset 0.000000
MESSAGE 29-location: 2 records, local message type 2
location (00029)            timestamp (253) type:       date_time/         uint32, units:             , scale 1.000000, offset 0.000000
location (00029)                 name (000) type:          string/         string, units:             , scale 1.000000, offset 0.000000
location (00029)         position_lat (001) type:          sint32/         sint32, units:  semicircles, scale 1.000000, offset 0.000000
location (00029)        position_long (002) type:          sint32/         sint32, units:  semicircles, scale 1.000000, offset 0.000000
location (00029)        message_index (254) type:   message_index/         uint16, units:             , scale 1.000000, offset 0.000000
location (00029)               symbol (003) type:          uint16/         uint16, units:             , scale 1.000000, offset 0.000000
location (00029)             altitude (004) type:          uint16/         uint16, units:            m, scale 5.000000, offset 500.000000
location (00029)              unknown (005) type:          uint16/         uint16, units:             , scale 1.000000, offset 0.000000
location (00029)          description (006) type:          string/         string, units:             , scale 1.000000, offset 0.000000

Known issues

The datatype unsigned long is not supported

Garmin Track Converter

The ‘proof of the pudding’ for the FitReader is the Garmin Track Converter which I wrote to convert tracks logged with my Garmin Edge 880 bike computer. The converter takes in the .FIT files and converts the tracks to .GPX format, to be used in other programs.

Waypoints that can be indicated by the user are not stored in the track .FIT. Up to 100 waypoints can be stored in a separate file, Locations.fit. The converter takes this file into account and selects the waypoints that were logged during the track. These selected waypoints <wpt> are combined with the track data <trk> in the GPX file.

The name and serial number of the device is stored in the description of the track.

Download

  • TBD

Source code

Available on GitHub:

FitReader

Garmin Track Converter