FITfileR icon indicating copy to clipboard operation
FITfileR copied to clipboard

Read if FITfile is activity

Open ec-nebi opened this issue 10 months ago • 2 comments

Hello,

Thank you for this package. It is awesome to have an R-only way to read FIT files. I have no previous experience in reading binary files, so I couldn't implement what I would like to do. Any hints would be greatly appreciated. I would like to create a function isFitActivity("file.fit") to check if a FIT file is an Activity before reading/loading the file with readFitFile.

Background
I have downloaded all my data from Garmin via the Export Data request. Since Garmin saves everything as FIT files (workouts, summaries, configurations, devices, etc.), I have plenty of FIT files. However, I would like to read only the Activities in R for further analysis.
Before reading (i.e., loading) a FIT file via readFitFile, I would like to check if it is an activity. This would significantly reduce time and errors, and it is indicated as a "best practice" in the Cookbook (see below).

Details
As far as I understand the protocol, this can be done by checking the first record that is not a definition (also referred to as record 2 according to figure 14 of the protocol: https://developer.garmin.com/fit/protocol/).

In the description of the Activity file (https://developer.garmin.com/fit/file-types/activity/), Garmin states that "The File Id message is required by all FIT file types and is expected to be the first message in the file. For Activity files, the Type property should be set to 4."

In the SDK cookbook, "Decoding FIT Activity Files" (https://developer.garmin.com/fit/cookbook/decoding-activity-files/), in the section "# Tips & Tricks for Decoding Activity Files," there is an indication that this could be done before loading the file:

What Type of File is This Anyways?
Knowing the type of FIT file being decoded can prevent the need to fully decode file types that are not supported, or determine how to process the decoded messages. Activity files and Workout files can both contain Workout and Workout Step messages, Activity files and Course files can contain Record messages, and a truncated Activity file might be missing its Activity and Session messages. This means that inspecting the message types found in a file is not a deterministic way to determine the type of file being decoded. A more reliable way to determine the file type is to check the Type field found in the File Id message.
Checking the file type can help prevent decoding and processing messages for an unsupported file type. The example project checks the file type in the OnFileIdMesg() delegate method. If the Type field is not File.Activity, then a custom FileTypeException is thrown. The exception is caught in the Main() method of the application. This prevents the file from being read if it is not the expected type.

And you have this snippet:

if((e.mesg as FileIdMesg).GetType() != File.Activity)
{
    throw new FileTypeException($"Expected File Type: Activity, received File Type: {(e.mesg as FileIdMesg).GetType().ToString()}");
}

Any hints would be welcome. Happy RRRiding!

ec-nebi avatar Jan 25 '25 21:01 ec-nebi

Thanks for the interest in the package. I've now added a function called readFitFileType() which should return a value from the fixed vocabulary for the possible file types. This only looks until it's found the apporopriate message, and should be much faster than trying to decode the whole file. Here's an example:

library(FITfileR)

garmin_file <- system.file("extdata", "Activities", "garmin-edge530-ride.fit", 
                           package = "FITfileR")

FITfileR::readFitFileType(garmin_file)
#> [1] "activity"

Possible values to be returned are:

device
settings
sport
activity
workout
course
schedules
weight
totals
goals
blood_pressure
monitoring_a
activity_summary
monitoring_daily
monitoring_b
segment
segment_list
exd_configuration

I don't actually have many files of type other than 'activity', so I'd be greatful to know if this works for your diverse set of files.

grimbough avatar Jan 29 '25 23:01 grimbough

It works like a charm. I did not expect that this would make such a difference. It went from
elapsed 424.637 to elapsed 1.582 for 3.3k files. I have not tested it in full detail; I get "activity", "segment_list", "monitoring_b", and 2/3 NA fileTypeText.

I will play a bit more with the package and come back to you to discuss your vision for it. I think I would like to have a fit_session() function next to record(), as Session is always a required message for the Activity file (https://developer.garmin.com/fit/file-types/activity/). Maybe messages() too, but perhaps you don’t want to overcomplicate the package purpose. If you’d like, I can see if I can submit a PR, but this will require quite some time—S4 is still quite unfamiliar terrain for me. No idea if it is too complex for my skills.

I saw FITfileR:::messages; it looks like an interesting function for exploring FIT files more extensively, but the output could be more user-friendly, and the function would need to be exported. It's in the help file, though. Do you have an idea of how it could return an object more easy to grasp? Maybe it would help to check the different kinds of definitions/messages within a record?

Anyway, a big thank you! I already have a lot to explore and to better understand what I’m doing with my bike. I’ll also check if I can find other file types, such as totals, goals, or settings. And it's fast!

ec-nebi avatar Jan 31 '25 01:01 ec-nebi