line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Parser::FIT; |
2
|
|
|
|
|
|
|
|
3
|
6
|
|
|
6
|
|
326567
|
use strict; |
|
6
|
|
|
|
|
50
|
|
|
6
|
|
|
|
|
148
|
|
4
|
6
|
|
|
6
|
|
24
|
use warnings; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
133
|
|
5
|
6
|
|
|
6
|
|
23
|
use Carp qw/croak carp/; |
|
6
|
|
|
|
|
9
|
|
|
6
|
|
|
|
|
297
|
|
6
|
6
|
|
|
6
|
|
33
|
use feature 'state'; |
|
6
|
|
|
|
|
8
|
|
|
6
|
|
|
|
|
672
|
|
7
|
6
|
|
|
6
|
|
10313
|
use Math::BigInt; |
|
6
|
|
|
|
|
143542
|
|
|
6
|
|
|
|
|
27
|
|
8
|
6
|
|
|
6
|
|
228953
|
use Parser::FIT::Profile; |
|
6
|
|
|
|
|
239
|
|
|
6
|
|
|
|
|
14200
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
#require "Profile.pm"; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = 0.04; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
sub new { |
15
|
12
|
|
|
12
|
1
|
19544
|
my $class = shift; |
16
|
12
|
|
|
|
|
43
|
my %options = @_; |
17
|
|
|
|
|
|
|
|
18
|
12
|
|
|
|
|
170
|
my $ref = { |
19
|
|
|
|
|
|
|
_DEBUG => 0, |
20
|
|
|
|
|
|
|
header => {}, |
21
|
|
|
|
|
|
|
body => {}, |
22
|
|
|
|
|
|
|
globalMessages => [], |
23
|
|
|
|
|
|
|
localMessages => [], |
24
|
|
|
|
|
|
|
records => 0, |
25
|
|
|
|
|
|
|
fh => undef, |
26
|
|
|
|
|
|
|
buffer => "", |
27
|
|
|
|
|
|
|
headerLength => 0, |
28
|
|
|
|
|
|
|
totalBytesRead => 0, |
29
|
|
|
|
|
|
|
messageHandlers => {}, |
30
|
|
|
|
|
|
|
}; |
31
|
|
|
|
|
|
|
|
32
|
12
|
100
|
|
|
|
52
|
if(exists $options{on}) { |
33
|
5
|
|
|
|
|
18
|
$ref->{messageHandlers} = $options{on}; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
|
36
|
12
|
0
|
33
|
|
|
46
|
if(exists $options{debug} && $options{debug}) { |
37
|
0
|
|
|
|
|
0
|
$ref->{_DEBUG} = 1; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
12
|
|
|
|
|
26
|
bless($ref, $class); |
41
|
|
|
|
|
|
|
|
42
|
12
|
|
|
|
|
32
|
return $ref; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub parse { |
46
|
9
|
|
|
9
|
1
|
43
|
my $self = shift; |
47
|
9
|
|
|
|
|
19
|
my $file = shift; |
48
|
|
|
|
|
|
|
|
49
|
9
|
50
|
|
|
|
26
|
croak "No file given to parse()!" unless($file); |
50
|
|
|
|
|
|
|
|
51
|
9
|
|
|
|
|
51
|
$self->_debug("Parsing '$file'"); |
52
|
|
|
|
|
|
|
|
53
|
9
|
50
|
|
|
|
391
|
croak "File '$file' doesn't exist!" if(!-f $file); |
54
|
|
|
|
|
|
|
|
55
|
9
|
|
|
|
|
51
|
$self->_debug("Opening file"); |
56
|
9
|
50
|
|
|
|
516
|
open(my $input, "<", $file) or croak "Error opening '$file': $!"; |
57
|
9
|
|
|
|
|
64
|
binmode($input); |
58
|
|
|
|
|
|
|
|
59
|
9
|
|
|
|
|
42
|
$self->parse_fh($input); |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub parse_fh { |
63
|
9
|
|
|
9
|
0
|
23
|
my $self = shift; |
64
|
9
|
|
|
|
|
17
|
my $input = shift; |
65
|
|
|
|
|
|
|
|
66
|
9
|
50
|
|
|
|
47
|
unless(ref $input eq "GLOB") { |
67
|
0
|
|
|
|
|
0
|
die "parse_fh requires an opened filehandle as param!"; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
9
|
|
|
|
|
22
|
$self->{fh} = $input; |
72
|
9
|
|
|
|
|
34
|
my $header = $self->_read_header(); |
73
|
9
|
|
|
|
|
24
|
$self->{header} = $self->_parse_header($header); |
74
|
|
|
|
|
|
|
#my $dataBody = $self->_readBytes($self->{header}->{dataLength}); |
75
|
9
|
|
|
|
|
30
|
$self->_parse_data_records(); |
76
|
|
|
|
|
|
|
#$self->_parse_crc(); |
77
|
|
|
|
|
|
|
|
78
|
9
|
|
|
|
|
457
|
close($input); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub parse_data { |
82
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
83
|
0
|
|
|
|
|
0
|
my $data = shift; |
84
|
|
|
|
|
|
|
|
85
|
0
|
0
|
|
|
|
0
|
open(my $fh, "<", \$data) or die "Error opening scalar as file: $!"; |
86
|
0
|
|
|
|
|
0
|
binmode($fh); |
87
|
|
|
|
|
|
|
|
88
|
0
|
|
|
|
|
0
|
return $self->parse_fh($fh); |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub _read_header { |
92
|
9
|
|
|
9
|
|
18
|
my $self = shift; |
93
|
|
|
|
|
|
|
|
94
|
9
|
|
|
|
|
27
|
my $headerLengthByte = $self->_readBytes(1); |
95
|
9
|
|
|
|
|
45
|
my $headerLength = unpack("c", $headerLengthByte); |
96
|
9
|
|
|
|
|
25
|
$self->{headerLength} = $headerLength; |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# The 1-Byte headerLength field is included in the total header length |
99
|
9
|
|
|
|
|
20
|
my $headerWithoutLengthByte = $headerLength - 1; |
100
|
|
|
|
|
|
|
|
101
|
9
|
|
|
|
|
22
|
my $header = $self->_readBytes($headerWithoutLengthByte); |
102
|
|
|
|
|
|
|
|
103
|
9
|
|
|
|
|
20
|
return $header; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
sub _parse_header { |
107
|
18
|
|
|
18
|
|
13264
|
my $self = shift; |
108
|
18
|
|
|
|
|
27
|
my $header = shift; |
109
|
|
|
|
|
|
|
|
110
|
18
|
|
|
|
|
30
|
my ($protocolVersion, $profile, $dataLength, $fileMagic, $crc); |
111
|
|
|
|
|
|
|
|
112
|
18
|
|
|
|
|
32
|
my $headerLength = length $header; |
113
|
|
|
|
|
|
|
|
114
|
18
|
100
|
|
|
|
54
|
if($headerLength == 13) { |
|
|
100
|
|
|
|
|
|
115
|
11
|
|
|
|
|
75
|
($protocolVersion, $profile, $dataLength, $fileMagic, $crc) = unpack("c s I! a4 s", $header); |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
elsif($headerLength == 11) { |
118
|
1
|
|
|
|
|
5
|
($protocolVersion, $profile, $dataLength, $fileMagic) = unpack("c s I! a4", $header); |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# Short header has no CRC value |
121
|
1
|
|
|
|
|
2
|
$crc = undef; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
else { |
124
|
6
|
|
|
|
|
49
|
croak "Invalid headerLength=${headerLength}! Don't know how to handle this."; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
12
|
100
|
|
|
|
52
|
croak "File either corrupted or not a real FIT file! (Missing magic '.FIT' string in header)" unless($fileMagic eq ".FIT"); |
128
|
|
|
|
|
|
|
|
129
|
11
|
|
|
|
|
47
|
$self->_debug("ProtocolVersion: $protocolVersion"); |
130
|
11
|
|
|
|
|
47
|
$self->_debug("Profile: $profile"); |
131
|
11
|
|
|
|
|
42
|
$self->_debug("DataLength: $dataLength Bytes"); |
132
|
11
|
|
|
|
|
36
|
$self->_debug("FileMagic: $fileMagic"); |
133
|
11
|
100
|
|
|
|
50
|
$self->_debug("CRC: " . (defined($crc) ? $crc : "N/A")); |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
my $headerInfo = { |
136
|
|
|
|
|
|
|
protocolVersion => $protocolVersion, |
137
|
|
|
|
|
|
|
profile => $profile, |
138
|
|
|
|
|
|
|
dataLength => $dataLength, |
139
|
|
|
|
|
|
|
crc => $crc, |
140
|
11
|
|
|
|
|
78
|
eof => $self->{headerLength} + $dataLength, |
141
|
|
|
|
|
|
|
}; |
142
|
|
|
|
|
|
|
|
143
|
11
|
|
|
|
|
30
|
return $headerInfo; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub _parse_record_header { |
147
|
10343
|
|
|
10343
|
|
18945
|
my $self = shift; |
148
|
10343
|
|
|
|
|
10629
|
my $recordHeader = shift; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
return { |
151
|
|
|
|
|
|
|
# Bit 7 inidcates a normal header (=0) or "something else" |
152
|
10343
|
|
|
|
|
33182
|
isNormalHeader => (($recordHeader & (1<<7)) == 0), |
153
|
|
|
|
|
|
|
# Bit 6 indicates a definition msg |
154
|
|
|
|
|
|
|
isDefinitionMessage => (($recordHeader & (1<<6)) > 0), |
155
|
|
|
|
|
|
|
# Bit 5 indicates "developer data flag" |
156
|
|
|
|
|
|
|
isDeveloperData => (($recordHeader & (1<<5)) > 0), |
157
|
|
|
|
|
|
|
# Bit 4 is reserved |
158
|
|
|
|
|
|
|
# Bits 3-0 define the localMessageType |
159
|
|
|
|
|
|
|
localMessageType => $recordHeader & 0xF, |
160
|
|
|
|
|
|
|
}; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
sub _parse_data_records { |
164
|
9
|
|
|
9
|
|
16
|
my $self = shift; |
165
|
|
|
|
|
|
|
|
166
|
9
|
|
|
|
|
28
|
$self->_debug("Parsing Data Records"); |
167
|
9
|
|
|
|
|
30
|
while($self->{totalBytesRead} < $self->{header}->{eof}) { |
168
|
|
|
|
|
|
|
|
169
|
10328
|
|
|
|
|
16152
|
my ($recordHeaderByte) = unpack("c", $self->_readBytes(1)); |
170
|
10328
|
|
|
|
|
40599
|
$self->_debug("HeaderBytes in Binary: " . sprintf("%08b", $recordHeaderByte)); |
171
|
10328
|
|
|
|
|
17927
|
my $header = $self->_parse_record_header($recordHeaderByte); |
172
|
|
|
|
|
|
|
|
173
|
10328
|
50
|
|
|
|
17408
|
if($header->{isNormalHeader}) { |
174
|
10328
|
100
|
|
|
|
15415
|
if($header->{isDefinitionMessage}) { |
175
|
262
|
|
|
|
|
895
|
$self->_debug("Record definition header for LocalMessageType=" . $header->{localMessageType}); |
176
|
262
|
|
|
|
|
678
|
$self->_parse_definition_message($header); |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
else { |
179
|
10066
|
|
|
|
|
14359
|
my $parseResult = $self->_parse_local_message_record($header); |
180
|
|
|
|
|
|
|
|
181
|
10066
|
50
|
|
|
|
16431
|
if(!defined $parseResult) { |
182
|
0
|
|
|
|
|
0
|
$self->_debug("Skipping record for unknown LocalMessageType=" . $header->{localMessageType}); |
183
|
0
|
|
|
|
|
0
|
next; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
10066
|
|
|
|
|
34203
|
$self->_debug("Processed record for LocalMessageType=" . $header->{localMessageType}); |
187
|
|
|
|
|
|
|
|
188
|
10066
|
|
|
|
|
22781
|
$self->emitRecord($parseResult->{messageType}, $parseResult->{fields}); |
189
|
|
|
|
|
|
|
|
190
|
10066
|
|
|
|
|
73087
|
$self->{records}++; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
} |
194
|
9
|
|
|
|
|
46
|
$self->_debug("DataRecords finished! Found a total of " . $self->{records} . " Records"); |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub on { |
198
|
9
|
|
|
9
|
1
|
58
|
my $self = shift; |
199
|
9
|
|
|
|
|
18
|
my $msgType = shift; |
200
|
9
|
|
|
|
|
12
|
my $handler = shift; |
201
|
|
|
|
|
|
|
|
202
|
9
|
|
|
|
|
20
|
my $msgHandlers = $self->{messageHandlers}; |
203
|
|
|
|
|
|
|
|
204
|
9
|
100
|
|
|
|
22
|
if($handler) { |
205
|
6
|
|
|
|
|
21
|
$msgHandlers->{$msgType} = $handler; |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
else { |
208
|
3
|
|
|
|
|
13
|
delete $msgHandlers->{$msgType}; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
sub emitRecord { |
213
|
10066
|
|
|
10066
|
0
|
11044
|
my $self = shift; |
214
|
10066
|
|
|
|
|
15874
|
my ($msgType, $msgData) = @_; |
215
|
|
|
|
|
|
|
|
216
|
10066
|
50
|
|
|
|
14773
|
if($msgType eq 'field_description') { |
217
|
0
|
|
|
|
|
0
|
$self->_debug("Encountered a field_description message"); |
218
|
0
|
|
|
|
|
0
|
$self->attachDeveloperDataToGlobalMessage($msgData); |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
10066
|
100
|
|
|
|
14878
|
if(my $handler = $self->getHandler($msgType)) { |
222
|
9456
|
|
|
|
|
18748
|
$handler->($msgData); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
10066
|
100
|
|
|
|
30034
|
if(my $allHandler = $self->getHandler("_any")) { |
226
|
9303
|
|
|
|
|
13989
|
$allHandler->($msgType, $msgData); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
sub attachDeveloperDataToGlobalMessage { |
231
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
232
|
0
|
|
|
|
|
0
|
my $msgData = shift; |
233
|
|
|
|
|
|
|
|
234
|
0
|
|
|
|
|
0
|
my $globalMessageid = $msgData->{native_mesg_num}->{value}; |
235
|
0
|
|
|
|
|
0
|
my $fieldId = $msgData->{field_definition_number}->{value}; |
236
|
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
0
|
my $globalMessage = $self->_get_global_message_type($globalMessageid); |
238
|
|
|
|
|
|
|
$globalMessage->{developer_data}->{$fieldId} = { |
239
|
|
|
|
|
|
|
id => $fieldId, |
240
|
|
|
|
|
|
|
name => $msgData->{field_name}->{value}, |
241
|
|
|
|
|
|
|
units => $msgData->{units}->{value}, |
242
|
0
|
|
|
|
|
0
|
baseType => $self->_get_base_type($msgData->{fit_base_type_id}->{value} & 15), |
243
|
|
|
|
|
|
|
type => undef, |
244
|
|
|
|
|
|
|
# Developer data ist never scaled/offsetted! |
245
|
|
|
|
|
|
|
offset => undef, |
246
|
|
|
|
|
|
|
scale => undef, |
247
|
|
|
|
|
|
|
}; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
sub getHandler { |
251
|
20132
|
|
|
20132
|
0
|
20831
|
my $self = shift; |
252
|
20132
|
|
|
|
|
20048
|
my $msgType = shift; |
253
|
|
|
|
|
|
|
|
254
|
20132
|
50
|
|
|
|
25297
|
if(!$msgType) { |
255
|
0
|
|
|
|
|
0
|
die "cannot get a handler for an unknown msgType!"; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
20132
|
100
|
|
|
|
30730
|
if(exists $self->{messageHandlers}->{$msgType}) { |
259
|
18759
|
|
|
|
|
37364
|
return $self->{messageHandlers}->{$msgType}; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
1373
|
|
|
|
|
2433
|
return undef; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub _parse_definition_message { |
266
|
262
|
|
|
262
|
|
393
|
my $self = shift; |
267
|
262
|
|
|
|
|
288
|
my $header = shift; |
268
|
262
|
|
|
|
|
437
|
my $localMessageType = $header->{localMessageType}; |
269
|
|
|
|
|
|
|
|
270
|
262
|
|
|
|
|
428
|
my $data = $self->_readBytes(5); |
271
|
262
|
|
|
|
|
747
|
my ($reserved, $arch, $globalMessageId, $fields) = unpack("ccsc", $data); |
272
|
|
|
|
|
|
|
|
273
|
262
|
|
|
|
|
694
|
my $globalMessageType = $self->_get_global_message_type($globalMessageId); |
274
|
|
|
|
|
|
|
|
275
|
262
|
|
|
|
|
549
|
$self->_debug("DefinitionMessageHeader:"); |
276
|
262
|
50
|
|
|
|
1451
|
$self->_debug("Arch: $arch - GlobalMessage: " . (defined $globalMessageType ? $globalMessageType->{name} : "") . " ($globalMessageId) - #Fields: $fields"); |
277
|
262
|
50
|
|
|
|
531
|
carp "BigEndian isn't supported so far!" if($arch == 1); |
278
|
|
|
|
|
|
|
|
279
|
262
|
|
|
|
|
491
|
my ($messageFields, $devMsgFields, $recordLength) = ([], [], 0); |
280
|
262
|
|
|
|
|
357
|
my @fields; |
281
|
|
|
|
|
|
|
|
282
|
262
|
50
|
|
|
|
500
|
if(defined $globalMessageType) { |
283
|
262
|
|
|
|
|
639
|
($messageFields, $recordLength) = $self->_parse_defintion_message_fields($globalMessageType->{fields}, $fields); |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
262
|
50
|
|
|
|
906
|
if($header->{isDeveloperData}) { |
287
|
0
|
|
|
|
|
0
|
($devMsgFields, my $devRecordLength) = $self->parseDeveloperDataDefinitionMessage($globalMessageType); |
288
|
0
|
|
|
|
|
0
|
$recordLength += $devRecordLength; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
262
|
|
|
|
|
898
|
my $combinedDataFields = [@$messageFields, @$devMsgFields]; |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
my $localMessage = { |
294
|
|
|
|
|
|
|
size => $recordLength, |
295
|
|
|
|
|
|
|
dataFields => $combinedDataFields, |
296
|
|
|
|
|
|
|
globalMessage => $globalMessageType,, |
297
|
3702
|
|
|
|
|
14253
|
unpackTemplate => join("", map { $_->{baseType}->{packTemplate} . '[' . $_->{arrayLength} . ']' } @$combinedDataFields), |
298
|
|
|
|
|
|
|
isDeveloperMessage => $header->{isDeveloperData}, |
299
|
262
|
|
|
|
|
798
|
isUnknownMessage => !defined $globalMessageType, |
300
|
|
|
|
|
|
|
}; |
301
|
|
|
|
|
|
|
|
302
|
262
|
|
|
|
|
6425
|
$self->{localMessages}->[$localMessageType] = $localMessage; |
303
|
|
|
|
|
|
|
|
304
|
262
|
|
|
|
|
944
|
$self->_debug("Following Record length: " . $localMessage->{size} . " bytes"); |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
sub parseDeveloperDataDefinitionMessage { |
308
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
309
|
0
|
|
|
|
|
0
|
my $globalMessageType = shift; |
310
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
0
|
my $developerFieldCount = unpack("C", $self->_readBytes(1)); |
312
|
|
|
|
|
|
|
|
313
|
0
|
|
|
|
|
0
|
my ($devMsgFields, $devRecordLength) = $self->_parse_defintion_message_fields($globalMessageType->{developer_data}, $developerFieldCount); |
314
|
|
|
|
|
|
|
|
315
|
0
|
|
|
|
|
0
|
foreach my $field (@$devMsgFields) { |
316
|
|
|
|
|
|
|
# BaseTypes of Dev Data is not included in the 3bytes of the definition message but in the field_description message which introduces the dev data |
317
|
|
|
|
|
|
|
# Therefore we simply overwrite the wrongly extracted BaseType from the definition message with the correct one. |
318
|
0
|
|
|
|
|
0
|
$field->{baseType} = $field->{fieldDescriptor}->{baseType}; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
0
|
|
|
|
|
0
|
return ($devMsgFields, $devRecordLength); |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
sub _parse_defintion_message_fields { |
325
|
262
|
|
|
262
|
|
407
|
my $self = shift; |
326
|
262
|
|
|
|
|
357
|
my $fieldDefinitions = shift; |
327
|
262
|
|
|
|
|
335
|
my $numberOfFields = shift; |
328
|
|
|
|
|
|
|
|
329
|
262
|
|
|
|
|
292
|
my $recordLength = 0; |
330
|
|
|
|
|
|
|
|
331
|
262
|
|
|
|
|
311
|
my @dataFields; |
332
|
|
|
|
|
|
|
|
333
|
262
|
|
|
|
|
588
|
foreach(1..$numberOfFields) { |
334
|
3702
|
|
|
|
|
6010
|
my $fieldDefinitionData = $self->_readBytes(3); # Every Field has 3 Bytes |
335
|
3702
|
|
|
|
|
9032
|
my ($fieldDefinition, $size, $baseTypeData) = unpack("Ccc", $fieldDefinitionData); |
336
|
3702
|
|
|
|
|
6160
|
my ($baseTypeEndian, $baseTypeNumber) = ($baseTypeData & 128, $baseTypeData & 15); |
337
|
3702
|
|
|
|
|
5336
|
my $baseType = $self->_get_base_type($baseTypeNumber); |
338
|
3702
|
|
|
|
|
8328
|
my $fieldDescriptor = $fieldDefinitions->{$fieldDefinition}; |
339
|
|
|
|
|
|
|
|
340
|
3702
|
50
|
|
|
|
6158
|
if(!defined $fieldDescriptor) { |
341
|
0
|
|
|
|
|
0
|
$fieldDescriptor = { |
342
|
|
|
|
|
|
|
isUnkownField => 1, |
343
|
|
|
|
|
|
|
name => "" |
344
|
|
|
|
|
|
|
}; |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
3702
|
|
|
|
|
7506
|
my $fieldName = $fieldDescriptor->{name}; |
348
|
3702
|
|
|
|
|
14519
|
$self->_debug("FieldDefinition: Nr: $fieldDefinition (" . $fieldName . "), Size: $size, BaseType: " . $baseType->{name} . " ($baseTypeNumber), BaseTypeEndian: $baseTypeEndian"); |
349
|
3702
|
|
|
|
|
4344
|
$recordLength += $size; |
350
|
|
|
|
|
|
|
|
351
|
3702
|
|
|
|
|
16741
|
push(@dataFields, { baseType => $baseType, storageSize => $size, isArray => $size > $baseType->{size}, arrayLength => $size/$baseType->{size}, fieldDescriptor => $fieldDescriptor }); |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
262
|
|
|
|
|
902
|
return (\@dataFields, $recordLength); |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
sub _global_message_id_to_name { |
358
|
262
|
|
|
262
|
|
434
|
my $self = shift; |
359
|
262
|
|
|
|
|
364
|
my $globalMessageId = shift; |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# Manufacterer specific message types |
362
|
262
|
50
|
|
|
|
625
|
if($globalMessageId >= 0xFF00) { |
363
|
0
|
|
|
|
|
0
|
return "mfg_range_min"; |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
262
|
|
|
|
|
568
|
state $globalMessageNames = { |
367
|
|
|
|
|
|
|
0 => "file_id", |
368
|
|
|
|
|
|
|
1 => "capabilities", |
369
|
|
|
|
|
|
|
2 => "device_settings", |
370
|
|
|
|
|
|
|
3 => "user_profile", |
371
|
|
|
|
|
|
|
4 => "hrm_profile", |
372
|
|
|
|
|
|
|
5 => "sdm_profile", |
373
|
|
|
|
|
|
|
6 => "bike_profile", |
374
|
|
|
|
|
|
|
7 => "zones_target", |
375
|
|
|
|
|
|
|
8 => "hr_zone", |
376
|
|
|
|
|
|
|
9 => "power_zone", |
377
|
|
|
|
|
|
|
10 => "met_zone", |
378
|
|
|
|
|
|
|
12 => "sport", |
379
|
|
|
|
|
|
|
15 => "goal", |
380
|
|
|
|
|
|
|
18 => "session", |
381
|
|
|
|
|
|
|
19 => "lap", |
382
|
|
|
|
|
|
|
20 => "record", |
383
|
|
|
|
|
|
|
21 => "event", |
384
|
|
|
|
|
|
|
23 => "device_info", |
385
|
|
|
|
|
|
|
26 => "workout", |
386
|
|
|
|
|
|
|
27 => "workout_step", |
387
|
|
|
|
|
|
|
28 => "schedule", |
388
|
|
|
|
|
|
|
30 => "weight_scale", |
389
|
|
|
|
|
|
|
31 => "course", |
390
|
|
|
|
|
|
|
32 => "course_point", |
391
|
|
|
|
|
|
|
33 => "totals", |
392
|
|
|
|
|
|
|
34 => "activity", |
393
|
|
|
|
|
|
|
35 => "software", |
394
|
|
|
|
|
|
|
37 => "file_capabilities", |
395
|
|
|
|
|
|
|
38 => "mesg_capabilities", |
396
|
|
|
|
|
|
|
39 => "field_capabilities", |
397
|
|
|
|
|
|
|
49 => "file_creator", |
398
|
|
|
|
|
|
|
51 => "blood_pressure", |
399
|
|
|
|
|
|
|
53 => "speed_zone", |
400
|
|
|
|
|
|
|
55 => "monitoring", |
401
|
|
|
|
|
|
|
72 => "training_file", |
402
|
|
|
|
|
|
|
78 => "hrv", |
403
|
|
|
|
|
|
|
80 => "ant_rx", |
404
|
|
|
|
|
|
|
81 => "ant_tx", |
405
|
|
|
|
|
|
|
82 => "ant_channel_id", |
406
|
|
|
|
|
|
|
101 => "length", |
407
|
|
|
|
|
|
|
103 => "monitoring_info", |
408
|
|
|
|
|
|
|
105 => "pad", |
409
|
|
|
|
|
|
|
106 => "slave_device", |
410
|
|
|
|
|
|
|
127 => "connectivity", |
411
|
|
|
|
|
|
|
128 => "weather_conditions", |
412
|
|
|
|
|
|
|
129 => "weather_alert", |
413
|
|
|
|
|
|
|
131 => "cadence_zone", |
414
|
|
|
|
|
|
|
132 => "hr", |
415
|
|
|
|
|
|
|
142 => "segment_lap", |
416
|
|
|
|
|
|
|
145 => "memo_glob", |
417
|
|
|
|
|
|
|
148 => "segment_id", |
418
|
|
|
|
|
|
|
149 => "segment_leaderboard_entry", |
419
|
|
|
|
|
|
|
150 => "segment_point", |
420
|
|
|
|
|
|
|
151 => "segment_file", |
421
|
|
|
|
|
|
|
158 => "workout_session", |
422
|
|
|
|
|
|
|
159 => "watchface_settings", |
423
|
|
|
|
|
|
|
160 => "gps_metadata", |
424
|
|
|
|
|
|
|
161 => "camera_event", |
425
|
|
|
|
|
|
|
162 => "timestamp_correlation", |
426
|
|
|
|
|
|
|
164 => "gyroscope_data", |
427
|
|
|
|
|
|
|
165 => "accelerometer_data", |
428
|
|
|
|
|
|
|
167 => "three_d_sensor_calibration", |
429
|
|
|
|
|
|
|
169 => "video_frame", |
430
|
|
|
|
|
|
|
174 => "obdii_data", |
431
|
|
|
|
|
|
|
177 => "nmea_sentence", |
432
|
|
|
|
|
|
|
178 => "aviation_attitude", |
433
|
|
|
|
|
|
|
184 => "video", |
434
|
|
|
|
|
|
|
185 => "video_title", |
435
|
|
|
|
|
|
|
186 => "video_description", |
436
|
|
|
|
|
|
|
187 => "video_clip", |
437
|
|
|
|
|
|
|
188 => "ohr_settings", |
438
|
|
|
|
|
|
|
200 => "exd_screen_configuration", |
439
|
|
|
|
|
|
|
201 => "exd_data_field_configuration", |
440
|
|
|
|
|
|
|
202 => "exd_data_concept_configuration", |
441
|
|
|
|
|
|
|
206 => "field_description", |
442
|
|
|
|
|
|
|
207 => "developer_data_id", |
443
|
|
|
|
|
|
|
208 => "magnetometer_data", |
444
|
|
|
|
|
|
|
209 => "barometer_data", |
445
|
|
|
|
|
|
|
210 => "one_d_sensor_calibration", |
446
|
|
|
|
|
|
|
225 => "set", |
447
|
|
|
|
|
|
|
227 => "stress_level", |
448
|
|
|
|
|
|
|
258 => "dive_settings", |
449
|
|
|
|
|
|
|
259 => "dive_gas", |
450
|
|
|
|
|
|
|
262 => "dive_alarm", |
451
|
|
|
|
|
|
|
264 => "exercise_title", |
452
|
|
|
|
|
|
|
268 => "dive_summary", |
453
|
|
|
|
|
|
|
285 => "jump", |
454
|
|
|
|
|
|
|
317 => "climb_pro", |
455
|
|
|
|
|
|
|
}; |
456
|
|
|
|
|
|
|
|
457
|
262
|
50
|
|
|
|
668
|
if(exists $globalMessageNames->{$globalMessageId}) { |
458
|
262
|
|
|
|
|
678
|
return $globalMessageNames->{$globalMessageId}; |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
else { |
461
|
0
|
|
|
|
|
0
|
return undef; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
sub getLocalMessageById { |
466
|
10066
|
|
|
10066
|
0
|
10764
|
my $self = shift; |
467
|
10066
|
|
|
|
|
9652
|
my $localMessageId = shift; |
468
|
|
|
|
|
|
|
|
469
|
10066
|
|
|
|
|
12608
|
my $localMessage = $self->{localMessages}->[$localMessageId]; |
470
|
|
|
|
|
|
|
|
471
|
10066
|
50
|
|
|
|
14080
|
if(!defined $localMessage) { |
472
|
0
|
|
|
|
|
0
|
die "Encountered a record localMessageId=$localMessageId which was not introduced by a definition message!"; |
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
|
475
|
10066
|
|
|
|
|
11393
|
return $localMessage; |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
sub _get_global_message_type { |
479
|
262
|
|
|
262
|
|
376
|
my $self = shift; |
480
|
|
|
|
|
|
|
|
481
|
262
|
|
|
|
|
581
|
my $globalMessageName = $self->_global_message_id_to_name(shift); |
482
|
|
|
|
|
|
|
|
483
|
262
|
50
|
|
|
|
564
|
if(!defined $globalMessageName) { |
484
|
0
|
|
|
|
|
0
|
return undef; |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
262
|
50
|
|
|
|
715
|
if(exists $Parser::FIT::Profile::PROFILE->{$globalMessageName}) { |
488
|
262
|
|
|
|
|
595
|
return $Parser::FIT::Profile::PROFILE->{$globalMessageName}; |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
else { |
491
|
0
|
|
|
|
|
0
|
return undef; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
sub _parse_local_message_record { |
496
|
10066
|
|
|
10066
|
|
10935
|
my $self = shift; |
497
|
10066
|
|
|
|
|
10278
|
my $header = shift; |
498
|
|
|
|
|
|
|
|
499
|
10066
|
|
|
|
|
11294
|
my $localMessageId = $header->{localMessageType}; |
500
|
10066
|
|
|
|
|
14147
|
my $localMessage = $self->getLocalMessageById($localMessageId); |
501
|
|
|
|
|
|
|
|
502
|
10066
|
|
|
|
|
13605
|
my $recordLength = $localMessage->{size}; |
503
|
10066
|
|
|
|
|
12630
|
my $record = $self->_readBytes($recordLength); |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# skip unknown messages (the _readBytes above is correct, since we need to "remove" the bytes from the stream) |
506
|
10066
|
50
|
|
|
|
17261
|
if($localMessage->{isUnknownMessage}) { |
507
|
0
|
|
|
|
|
0
|
return undef; |
508
|
|
|
|
|
|
|
} |
509
|
|
|
|
|
|
|
|
510
|
10066
|
|
|
|
|
11675
|
my $unpackTemplate = $localMessage->{unpackTemplate}; |
511
|
10066
|
|
|
|
|
34154
|
my @rawFields = unpack($unpackTemplate, $record); |
512
|
|
|
|
|
|
|
|
513
|
10066
|
|
|
|
|
11488
|
my %result; |
514
|
|
|
|
|
|
|
|
515
|
10066
|
|
|
|
|
9490
|
my $fieldCount = scalar @{$localMessage->{dataFields}}; |
|
10066
|
|
|
|
|
13594
|
|
516
|
10066
|
|
|
|
|
16801
|
for(my $i = 0; $i < $fieldCount; $i++) { |
517
|
162163
|
|
|
|
|
198662
|
my $localMessageField = $localMessage->{dataFields}->[$i]; |
518
|
162163
|
|
|
|
|
165937
|
my $rawValue = $rawFields[$i]; |
519
|
|
|
|
|
|
|
|
520
|
162163
|
|
|
|
|
168350
|
my $fieldDescriptor = $localMessageField->{fieldDescriptor}; |
521
|
162163
|
|
|
|
|
174953
|
my $fieldName = $fieldDescriptor->{name}; |
522
|
|
|
|
|
|
|
|
523
|
162163
|
50
|
|
|
|
200454
|
if($fieldDescriptor->{isUnkownField}) { |
524
|
0
|
|
|
|
|
0
|
next; |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
162163
|
|
|
|
|
203071
|
my $postProcessedValue = $self->postProcessRawValue($rawValue, $fieldDescriptor); |
528
|
|
|
|
|
|
|
|
529
|
162163
|
|
|
|
|
539379
|
$result{$fieldName} = { |
530
|
|
|
|
|
|
|
value => $postProcessedValue, |
531
|
|
|
|
|
|
|
rawValue => $rawValue, |
532
|
|
|
|
|
|
|
fieldDescriptor => $fieldDescriptor, |
533
|
|
|
|
|
|
|
}; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
return { |
537
|
|
|
|
|
|
|
messageType => $localMessage->{globalMessage}->{name}, |
538
|
10066
|
|
|
|
|
39539
|
fields => \%result |
539
|
|
|
|
|
|
|
}; |
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
sub postProcessRawValue { |
543
|
162163
|
|
|
162163
|
0
|
159838
|
my $self = shift; |
544
|
162163
|
|
|
|
|
161870
|
my $rawValue = shift; |
545
|
162163
|
|
|
|
|
157741
|
my $fieldDescriptor = shift; |
546
|
|
|
|
|
|
|
|
547
|
162163
|
100
|
|
|
|
214560
|
if(defined $fieldDescriptor->{scale}) { |
548
|
74821
|
|
|
|
|
89962
|
$rawValue /= $fieldDescriptor->{scale}; |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
|
551
|
162163
|
100
|
|
|
|
203736
|
if(defined $fieldDescriptor->{offset}) { |
552
|
9143
|
|
|
|
|
12181
|
$rawValue -= $fieldDescriptor->{offset}; |
553
|
|
|
|
|
|
|
} |
554
|
|
|
|
|
|
|
|
555
|
162163
|
50
|
66
|
|
|
338088
|
if(defined $fieldDescriptor->{unit} && $fieldDescriptor->{unit} eq "semicircles") { |
556
|
0
|
|
|
|
|
0
|
state $semicirclesToDegreesConversionRate = 180 / 2**31; |
557
|
0
|
|
|
|
|
0
|
$rawValue *= $semicirclesToDegreesConversionRate; |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
|
560
|
162163
|
100
|
66
|
|
|
334895
|
if(defined $fieldDescriptor->{type} && $fieldDescriptor->{type} eq "date_time") { |
561
|
10146
|
|
|
|
|
11696
|
state $fitEpocheOffset = 631065600; |
562
|
10146
|
|
|
|
|
10816
|
$rawValue += $fitEpocheOffset; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
162163
|
|
|
|
|
224901
|
return $rawValue; |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
sub _get_base_type { |
569
|
3702
|
|
|
3702
|
|
4402
|
my $self = shift; |
570
|
3702
|
|
|
|
|
4086
|
my $index = shift; |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
# See "Table 7. FIT Base Types and Invalid Values" at https://developer.garmin.com/fit/protocol/ |
573
|
3702
|
|
|
|
|
33729
|
my $types = [ |
574
|
|
|
|
|
|
|
{ |
575
|
|
|
|
|
|
|
name => "enum", |
576
|
|
|
|
|
|
|
size => 1, |
577
|
|
|
|
|
|
|
invalid => 0xff, |
578
|
|
|
|
|
|
|
packTemplate => "c", |
579
|
|
|
|
|
|
|
}, |
580
|
|
|
|
|
|
|
{ |
581
|
|
|
|
|
|
|
name => "sint8", |
582
|
|
|
|
|
|
|
size => 1, |
583
|
|
|
|
|
|
|
invalid => 0x7f, |
584
|
|
|
|
|
|
|
packTemplate => "c" |
585
|
|
|
|
|
|
|
}, |
586
|
|
|
|
|
|
|
{ |
587
|
|
|
|
|
|
|
name => "uint8", |
588
|
|
|
|
|
|
|
size => 1, |
589
|
|
|
|
|
|
|
invalid => 0xff, |
590
|
|
|
|
|
|
|
packTemplate => "C", |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
}, |
593
|
|
|
|
|
|
|
{ |
594
|
|
|
|
|
|
|
name => "sint16", |
595
|
|
|
|
|
|
|
size => 2, |
596
|
|
|
|
|
|
|
invalid => 0x7fff, |
597
|
|
|
|
|
|
|
packTemplate => "s", |
598
|
|
|
|
|
|
|
}, |
599
|
|
|
|
|
|
|
{ |
600
|
|
|
|
|
|
|
name => "uint16", |
601
|
|
|
|
|
|
|
size => 2, |
602
|
|
|
|
|
|
|
invalid => 0xffff, |
603
|
|
|
|
|
|
|
packTemplate => "S" |
604
|
|
|
|
|
|
|
}, |
605
|
|
|
|
|
|
|
{ |
606
|
|
|
|
|
|
|
name => "sint32", |
607
|
|
|
|
|
|
|
size => 4, |
608
|
|
|
|
|
|
|
invalid => 0x7fffffff, |
609
|
|
|
|
|
|
|
packTemplate => "l" |
610
|
|
|
|
|
|
|
}, |
611
|
|
|
|
|
|
|
{ |
612
|
|
|
|
|
|
|
name => "uint32", |
613
|
|
|
|
|
|
|
size => 4, |
614
|
|
|
|
|
|
|
invalid => 0xffffffff, |
615
|
|
|
|
|
|
|
packTemplate => "L", |
616
|
|
|
|
|
|
|
}, |
617
|
|
|
|
|
|
|
{ |
618
|
|
|
|
|
|
|
name => "string", |
619
|
|
|
|
|
|
|
size => 1, |
620
|
|
|
|
|
|
|
invalid => 0x00, |
621
|
|
|
|
|
|
|
packTemplate => "Z" |
622
|
|
|
|
|
|
|
}, |
623
|
|
|
|
|
|
|
{ |
624
|
|
|
|
|
|
|
name => "float32", |
625
|
|
|
|
|
|
|
size => 4, |
626
|
|
|
|
|
|
|
invalid => 0xffffffff, |
627
|
|
|
|
|
|
|
packTemplate => "f" |
628
|
|
|
|
|
|
|
}, |
629
|
|
|
|
|
|
|
{ |
630
|
|
|
|
|
|
|
name => "float64", |
631
|
|
|
|
|
|
|
size => 8, |
632
|
|
|
|
|
|
|
invalid => Math::BigInt->new("0xffffffffffffffff"), |
633
|
|
|
|
|
|
|
packTemplate => "d", |
634
|
|
|
|
|
|
|
}, |
635
|
|
|
|
|
|
|
{ |
636
|
|
|
|
|
|
|
name => "uint8z", |
637
|
|
|
|
|
|
|
size => 1, |
638
|
|
|
|
|
|
|
invalid => 0x00, |
639
|
|
|
|
|
|
|
packTemplate => "c" |
640
|
|
|
|
|
|
|
}, |
641
|
|
|
|
|
|
|
{ |
642
|
|
|
|
|
|
|
name => "uint16z", |
643
|
|
|
|
|
|
|
size => 2, |
644
|
|
|
|
|
|
|
invalid => 0x0000, |
645
|
|
|
|
|
|
|
packTemplate => "S", |
646
|
|
|
|
|
|
|
}, |
647
|
|
|
|
|
|
|
{ |
648
|
|
|
|
|
|
|
name => "uint32z", |
649
|
|
|
|
|
|
|
size => 4, |
650
|
|
|
|
|
|
|
invalid => 0x00000000, |
651
|
|
|
|
|
|
|
packTemplate => "L" |
652
|
|
|
|
|
|
|
}, |
653
|
|
|
|
|
|
|
{ |
654
|
|
|
|
|
|
|
name => "byte", |
655
|
|
|
|
|
|
|
size => 1, |
656
|
|
|
|
|
|
|
invalid => 0xFF, |
657
|
|
|
|
|
|
|
packTemplate => "C", |
658
|
|
|
|
|
|
|
}, |
659
|
|
|
|
|
|
|
{ |
660
|
|
|
|
|
|
|
name => "sint64", |
661
|
|
|
|
|
|
|
size => 8, |
662
|
|
|
|
|
|
|
invalid => Math::BigInt->new("0x7fffffffffffffff"), |
663
|
|
|
|
|
|
|
packTemplate => "q", |
664
|
|
|
|
|
|
|
}, |
665
|
|
|
|
|
|
|
{ |
666
|
|
|
|
|
|
|
name => "uint64", |
667
|
|
|
|
|
|
|
size => 8, |
668
|
|
|
|
|
|
|
invalid => Math::BigInt->new("0xffffffffffffffff"), |
669
|
|
|
|
|
|
|
packTemplate => "Q", |
670
|
|
|
|
|
|
|
}, |
671
|
|
|
|
|
|
|
{ |
672
|
|
|
|
|
|
|
name => "uint64z", |
673
|
|
|
|
|
|
|
size => 8, |
674
|
|
|
|
|
|
|
invalid => 0x0000000000000000, |
675
|
|
|
|
|
|
|
packTemplate => "Q", |
676
|
|
|
|
|
|
|
} |
677
|
|
|
|
|
|
|
]; |
678
|
|
|
|
|
|
|
|
679
|
3702
|
50
|
|
|
|
2404035
|
if($index >= @{$types}) { |
|
3702
|
|
|
|
|
7001
|
|
680
|
0
|
|
|
|
|
0
|
die "Invalid index=$index for BaseTypeLookup!"; |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
3702
|
|
|
|
|
28566
|
return $types->[$index]; |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
0
|
|
|
sub _parse_crc { |
687
|
|
|
|
|
|
|
# TODO implement this one...some time :D |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
sub _debug { |
691
|
25235
|
|
|
25235
|
|
29074
|
my $self = shift; |
692
|
25235
|
50
|
|
|
|
45264
|
if($self->{_DEBUG}) { |
693
|
0
|
|
|
|
|
0
|
print "[FIT.pm DEBUG] ", @_; |
694
|
0
|
|
|
|
|
0
|
print "\n"; |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
sub _readBytes { |
699
|
24376
|
|
|
24376
|
|
25823
|
my $self = shift; |
700
|
24376
|
|
|
|
|
25762
|
my $num = shift; |
701
|
|
|
|
|
|
|
|
702
|
24376
|
|
|
|
|
27482
|
$self->{totalBytesRead} += $num; |
703
|
24376
|
|
|
|
|
24198
|
my $buffer; |
704
|
24376
|
|
|
|
|
54941
|
my $bytesRead = read($self->{fh}, $buffer, $num); |
705
|
|
|
|
|
|
|
# TODO error handling based on bytesRead |
706
|
24376
|
|
|
|
|
43938
|
return $buffer; |
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
1; |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
__END__ |