NAME
Parser::FIT - A parser for garmin FIT (Flexible and Interoperable Data Transfer) files
SYNOPSIS
use Parser::FIT;
my $recordCount = 0;
my $parser = Parser::FIT->new(on => {
record => sub { $recordMsg = shift; $recordCount++; }
});
$parser->parse("some/file.fit");
print "The file contained $recordCount records.";
ALPHA STATUS
The module is in an early alpha status. APIs may change. Parse results may be wrong.
Additionally i will probably not implement the full set of FIT messages. I started the module for my personal needs to be able to parse FIT files from my garmin bike computer. So results for e.g. a triathlon multisport watch may varry greatly!
But this module is free and open source: Feel free to contribute code, example data, etc!
METHODS
new
Create a new Parser::FIT object.
Parser::FIT->new(
debug => 1|0 # enable/disable debug output. Disabled by default
on => { # Provide a hashref of message handlers
sessiont => sub { },
lap => sub { },
}
)
on
Register and deregister handlers for a parser.
$parser->on(record => sub { my $message = shift; });
Concrete message handlers receive on paramter which represents the parsed message. See "MESSAGES" for more details.
Registering an already existing handler overwrites the old one.
$parser->on(session => sub { say "foo" });
$parser->on(session => sub { say "bar" }); # Overwrites the previous handler
Registering a falsy value for a message type will deregister the handler:
$parser->on(session => undef);
There is currently no check, if the provided message name actually represents an existing one from the FIT specs.
Additionally there is a special message name: _any
. Which can be used to receive just every message encountered by the parser. The _any
handler receives two parameters. The first one is the messageType
which is just a string with the name of the message. The second one is a message hash-ref.
$parser->on(_any => sub {
my $messageType = shift;
my $message = shift;
print "Saw a message of type $msgType";
});
The on
method can also be called from inside a handler callback in order to de-/register handlers based on the stream of events
# Count the number of records per lap
my $lapCount = 0;
my $lapResults = [];
$parser->on("lap" => sub {
my $lapMsg = shift;
my $lapCount++;
$parser->on("record" => {
$lapResults[$lapCount]++;
});
});
parse
Parse a file and call registered message handlers.
$parser->parse('/some/file.fit');
parse_data
Parse FIT data contained in a scalar and call registered message handlers.
$parser->parse_data($inMemoryFitData);
DATA STRUCTURES
This section explains the used data structures you may or may not encounter when using this module.
MESSAGES
A message is a hash-ref where the keys map to fieldnames defined by the FIT Profile (aka Profile.xls
) for the given message.
The FIT protocol defines so called local messages
which allow to only store a subset of the so called global message
. For example the session
global message defines 134 fields, but an actually recorded session message in a FIT file may only contain 20 of these.
This way it is possible to create FIT files which only contain the data the device is currently "seeing". But this also means, that this data may change "in-flight". For example if a session is started without an heartrate sensor, the include FIT data will not have heartrate related data. When later in the session the user straps on a heartrate sensor and pairs it with his device, all upcoming data inside the FIT file will have heartrate data. The same is true for sensors/data that goes away while recording.
Therefore you always have to check if the desired data is actually in the message.
For a list of field names you may expect to see, you can check the Garmin FIT SDK. It includes a Profile.xls
file which defines all the valid fields for every global message.
The fields of a message are represented as message fields.
An example record
message:
{
'speed' => {
'fieldDescriptor' => {
'name' => 'speed',
'id' => '6',
'scale' => 1000,
'unit' => 'm/s',
'type' => 'uint16',
'offset' => undef
},
'rawValue' => 2100
'value' => '2.1',
},
'position_lat' => {
'fieldDescriptor' => { }, # skipped for readability
'rawValue' => 574866379,
'value' => '48.184743'
},
'distance' => {
'fieldDescriptor' => { }, # skipped for readability
'rawValue' => 238,
'value' => '2.38',
},
'heart_rate' => {
'fieldDescriptor' => { }, # skipped for readability
'value' => 70,
'rawValue' => 70
},
'timestamp' => {
'rawValue' => 983200317,
'fieldDescriptor' => { }, # skipped for readability
'value' => 1614265917
},
'altitude' => {
'value' => '790.8',
'fieldDescriptor' => { }, # skipped for readability
'rawValue' => 6454
},
'position_long' => {
'fieldDescriptor' => { }, # skipped for readability
'value' => '9.102652',
'rawValue' => 108598869
}
}
=head2 MESSAGE FIELDS
A message field represents actuall data inside a message. It consists of a hash-ref containg:
- value
-
The value after post processing.
- rawValue
-
The original value as it is stored in the FIT file.
- fieldDescriptor
-
A hash-ref containing a field descriptor which describes this field.
FIELD DESCRIPTOR
A field descriptor
is just a hash-ref with some key-value pairs describing the underlying field.
The keys are:
- id
-
The id of the field in relation to the message type.
- name
-
The name of the field this descriptor represents.
- unit
-
The unit of measurement (e.G.
kcal
,m
,bpm
). - scale
-
The scale by which the rawValue needs to be scaled.
- type
-
The original FIT data type (e.G.
uint8
,date_time
).
The values for these keys are directly taken from the FIT Profile.xls
.
POST PROCESSING
SCALE
The FIT protocol defines for various data fields a scale (e.G. distances define a scale of 100) in order to optimize the low-level storage type.
Parser::FIT divides the rawValue
by the scale and stores the result in value
. The rawValue
stays untouched.
OFFSET
The FIT protocol defines for various data fields an offset (e.G. altitude values are offset by 500m) in order to optimize the low-level storage type.
Parser::FIT subtracts the offsets from the rawValue
and stores the result in value
. The rawValue
stays untouched.
CONVERSIONS
The FIT protocol defines various special data types. Parser::FIT converts the following types to "more usefull" ones:
SEMICRICLES
Fields with the data type semicricles
get converted to degrees via this formula: degrees = semicircles * (180/2^31)
.
So the value
of a field with data type semicricles
is in degrees. The rawValue
stays in semicircles.
DATE_TIME
Fields with the data type date_time
get converted to unix epoche timestamps via this formula: unixTimestamp = fitTimestamp + 631065600
.
Internally FIT is using it's own epoche starting at December 31, 1989 UTC.
AUTHOR
This module was created by Sven Eppler <ghandi@cpan.org>
COPYRIGHT AND LICENSE
Copyright (C) 2018-2022 by Sven Eppler
This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.