Skip to content

This provide a swift package to parse Fit File and have access to the information in swift.

License

Notifications You must be signed in to change notification settings

roznet/FitFileParser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

93 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fit File Parsing for swift Actions Status

This code provide a Swift Package for parsing the Fit Files format typically produced by Garmin Devices and other fitness devices.

It uses the official Fit SDK

You can see a few example of how to use it on iOS and macOS in fit-sdk-swift

This package is also fully integrated into the open source and released MacOS and iOS apps FitFileExplorer and ConnectStats.

The open source app FitFileExplorer that uses this library can be especially helpful in exploring the structure of a fit files while working with the package.

How to Install

It's fairly straight forward to use from an xcode project, following the standard approach to add a Swift Package:

  • you can use the File/Swift Package/Add package dependency function, choose your project and enter the url of the package https://github.com/roznet/FitFileParser
  • Add the package as a dependency to your target
  • import FitFileParser in the target code where you want to parse a fit file

How to use

A fit file is loaded using let fitfile = FitFile(file: URL) or let fitfile = FitFile(data : Data)

You can then use access function like fitfile.messages(forMessageType: FitMessageType.session) to get objects of type FitMessage

The type FitMessageType represent the messages type (mesg_num in the sdk). You can access all the known messages with syntax like FitMessageType.record, FitMessageType.session, FitMessageType.sport, ...

The function message.interpretedFields() provide access to the fields for the message as a dictionary of keys to FitFieldValue which can be either a date, a string, a double value, a double value with unit or a gps coordinate.

Here is a full example

if let fit = FitFile(file: path ) {
    var gps : [CLLocationCoordinate2D] = []
    var hr  : [Double] = []
    var ts  : [Date]   = []
    for message in fit.messages(forMessageType: .record) {
        if let one_gps = message.interpretedValue(key: "position"),
           let one_hr  = message.interpretedValue(key: "heart_rate"),
           let one_ts  = message.interpretedValue(key: "timestamp") {
            if case let FitValue.coordinate(coord) = one_gps {
                gps.append( coord )
            }
            if case let FitValue.time(date) = one_ts {
                ts.append( date )
            }
            if case let FitValue.valueUnit(d , _) = one_hr {
                hr.append( d )
            }
        }
    }
}

Alternatively you can use the convenience optional computed property to get the value if more convenient

//...
 if let one_gps = message.interpretedField(key: "position")?.coordinate,
    let one_hr  = message.interpretedField(key: "heart_rate")?.valueUnit?.value,
    let one_ts  = message.interpretedField(key: "timestamp")?.time {
     gps.append( one_gps )
     ts.append( one_ts)
     hr.append( one_hr )
}
//...

Approach

This code builds upon the example c code from the official SDK and integrate it into swift to generate a native object with an array of messages made of native swift dictionaries. It adds support for developer fields and a .generic parsing mode to process any message and field not predefined in the profile.

When using the .fast parsing mode (from the sdk c example), all the keys and fields are generated from the types defined in Profile.xlsx from the example SDK.

There are two available approaches to parsing, determined by the parsingType argument in the FitFile constructor:

  • .fast this method will only parse the fields defined as an example in the Profile.xlsx and therefore matching those in fit_example.h from the sdk. This approach is the fastest as it relies on pre-defined static parsing of the fields.
  • .generic this method will blindly convert all the messages found in the files, interpreting as much as possible from the information in Profile.xlsx as possible, but also building information from unkonwn messages and types. This approach is a bit slower as tries to interpret each fields dynamically.

All the required code is auto generated by running a python script fitsdkparser.py that takes as input the Profile.xlsx from the sdk.

Update for a new SDK

When a new SDK is available, after download, you can copy the new Profile.xlsx into the python directory and edit the file fitsdkversion.txt with the version used.

You need to then run the fitsdkparser.py script that will automatically update the swift code for the latest version of the sdk

Why implement this Library?

The main motivation to write this library was speed of parsing the fit file for the ConnectStats use case.

This library was built to replace the original cpp code from the SDK used in ConnectStats and FitFileExplorer. As ConnectStats now receives the FIT files from Garmin, the files are parsed live on the phone as they are received and performance was therefore important for the user experience.

The cpp parsing ended up very slow, and it made fit file parsing on ConnectStats or FitFileExplorer quite slow. This approach in c/swift is much faster.

You can check the benchmarking of the library versus a few others.