line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Parser::FIT; |
2
|
|
|
|
|
|
|
|
3
|
6
|
|
|
6
|
|
290346
|
use strict; |
|
6
|
|
|
|
|
46
|
|
|
6
|
|
|
|
|
151
|
|
4
|
6
|
|
|
6
|
|
25
|
use warnings; |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
145
|
|
5
|
6
|
|
|
6
|
|
24
|
use Carp qw/croak carp/; |
|
6
|
|
|
|
|
8
|
|
|
6
|
|
|
|
|
277
|
|
6
|
6
|
|
|
6
|
|
33
|
use feature 'state'; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
701
|
|
7
|
6
|
|
|
6
|
|
6181
|
use Math::BigInt; |
|
6
|
|
|
|
|
141020
|
|
|
6
|
|
|
|
|
26
|
|
8
|
6
|
|
|
6
|
|
216838
|
use Parser::FIT::Profile; |
|
6
|
|
|
|
|
336
|
|
|
6
|
|
|
|
|
14236
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
#require "Profile.pm"; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = 0.06; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
sub new { |
15
|
12
|
|
|
12
|
1
|
17619
|
my $class = shift; |
16
|
12
|
|
|
|
|
35
|
my %options = @_; |
17
|
|
|
|
|
|
|
|
18
|
12
|
|
|
|
|
134
|
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
|
|
|
|
50
|
if(exists $options{on}) { |
33
|
5
|
|
|
|
|
14
|
$ref->{messageHandlers} = $options{on}; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
|
36
|
12
|
0
|
33
|
|
|
41
|
if(exists $options{debug} && $options{debug}) { |
37
|
0
|
|
|
|
|
0
|
$ref->{_DEBUG} = 1; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
12
|
|
|
|
|
25
|
bless($ref, $class); |
41
|
|
|
|
|
|
|
|
42
|
12
|
|
|
|
|
32
|
return $ref; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub parse { |
46
|
9
|
|
|
9
|
1
|
42
|
my $self = shift; |
47
|
9
|
|
|
|
|
15
|
my $file = shift; |
48
|
|
|
|
|
|
|
|
49
|
9
|
50
|
|
|
|
26
|
croak "No file given to parse()!" unless($file); |
50
|
|
|
|
|
|
|
|
51
|
9
|
|
|
|
|
37
|
$self->_debug("Parsing '$file'"); |
52
|
|
|
|
|
|
|
|
53
|
9
|
50
|
|
|
|
380
|
croak "File '$file' doesn't exist!" if(!-f $file); |
54
|
|
|
|
|
|
|
|
55
|
9
|
|
|
|
|
45
|
$self->_debug("Opening file"); |
56
|
9
|
50
|
|
|
|
451
|
open(my $input, "<", $file) or croak "Error opening '$file': $!"; |
57
|
9
|
|
|
|
|
41
|
binmode($input); |
58
|
|
|
|
|
|
|
|
59
|
9
|
|
|
|
|
36
|
$self->parse_fh($input); |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub parse_fh { |
63
|
9
|
|
|
9
|
0
|
23
|
my $self = shift; |
64
|
9
|
|
|
|
|
14
|
my $input = shift; |
65
|
|
|
|
|
|
|
|
66
|
9
|
50
|
|
|
|
38
|
unless(ref $input eq "GLOB") { |
67
|
0
|
|
|
|
|
0
|
die "parse_fh requires an opened filehandle as param!"; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
9
|
|
|
|
|
25
|
$self->{fh} = $input; |
72
|
9
|
|
|
|
|
34
|
my $header = $self->_read_header(); |
73
|
9
|
|
|
|
|
27
|
$self->{header} = $self->_parse_header($header); |
74
|
|
|
|
|
|
|
#my $dataBody = $self->_readBytes($self->{header}->{dataLength}); |
75
|
9
|
|
|
|
|
26
|
$self->_parse_data_records(); |
76
|
|
|
|
|
|
|
#$self->_parse_crc(); |
77
|
|
|
|
|
|
|
|
78
|
9
|
|
|
|
|
368
|
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
|
|
15
|
my $self = shift; |
93
|
|
|
|
|
|
|
|
94
|
9
|
|
|
|
|
26
|
my $headerLengthByte = $self->_readBytes(1); |
95
|
9
|
|
|
|
|
36
|
my $headerLength = unpack("c", $headerLengthByte); |
96
|
9
|
|
|
|
|
19
|
$self->{headerLength} = $headerLength; |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# The 1-Byte headerLength field is included in the total header length |
99
|
9
|
|
|
|
|
19
|
my $headerWithoutLengthByte = $headerLength - 1; |
100
|
|
|
|
|
|
|
|
101
|
9
|
|
|
|
|
22
|
my $header = $self->_readBytes($headerWithoutLengthByte); |
102
|
|
|
|
|
|
|
|
103
|
9
|
|
|
|
|
23
|
return $header; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
sub _parse_header { |
107
|
18
|
|
|
18
|
|
13208
|
my $self = shift; |
108
|
18
|
|
|
|
|
29
|
my $header = shift; |
109
|
|
|
|
|
|
|
|
110
|
18
|
|
|
|
|
28
|
my ($protocolVersion, $profile, $dataLength, $fileMagic, $crc); |
111
|
|
|
|
|
|
|
|
112
|
18
|
|
|
|
|
31
|
my $headerLength = length $header; |
113
|
|
|
|
|
|
|
|
114
|
18
|
100
|
|
|
|
54
|
if($headerLength == 13) { |
|
|
100
|
|
|
|
|
|
115
|
11
|
|
|
|
|
74
|
($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
|
|
|
|
|
3
|
$crc = undef; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
else { |
124
|
6
|
|
|
|
|
53
|
croak "Invalid headerLength=${headerLength}! Don't know how to handle this."; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
12
|
100
|
|
|
|
53
|
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
|
|
|
|
|
44
|
$self->_debug("Profile: $profile"); |
131
|
11
|
|
|
|
|
44
|
$self->_debug("DataLength: $dataLength Bytes"); |
132
|
11
|
|
|
|
|
41
|
$self->_debug("FileMagic: $fileMagic"); |
133
|
11
|
100
|
|
|
|
58
|
$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
|
|
|
|
|
69
|
eof => $self->{headerLength} + $dataLength, |
141
|
|
|
|
|
|
|
}; |
142
|
|
|
|
|
|
|
|
143
|
11
|
|
|
|
|
31
|
return $headerInfo; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub _parse_record_header { |
147
|
10343
|
|
|
10343
|
|
19630
|
my $self = shift; |
148
|
10343
|
|
|
|
|
10952
|
my $recordHeader = shift; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
return { |
151
|
|
|
|
|
|
|
# Bit 7 inidcates a normal header (=0) or "something else" |
152
|
10343
|
|
|
|
|
33179
|
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
|
|
14
|
my $self = shift; |
165
|
|
|
|
|
|
|
|
166
|
9
|
|
|
|
|
27
|
$self->_debug("Parsing Data Records"); |
167
|
9
|
|
|
|
|
25
|
while($self->{totalBytesRead} < $self->{header}->{eof}) { |
168
|
|
|
|
|
|
|
|
169
|
10328
|
|
|
|
|
16301
|
my ($recordHeaderByte) = unpack("c", $self->_readBytes(1)); |
170
|
10328
|
|
|
|
|
42184
|
$self->_debug("HeaderBytes in Binary: " . sprintf("%08b", $recordHeaderByte)); |
171
|
10328
|
|
|
|
|
17857
|
my $header = $self->_parse_record_header($recordHeaderByte); |
172
|
|
|
|
|
|
|
|
173
|
10328
|
50
|
|
|
|
17380
|
if($header->{isNormalHeader}) { |
174
|
10328
|
100
|
|
|
|
14913
|
if($header->{isDefinitionMessage}) { |
175
|
262
|
|
|
|
|
787
|
$self->_debug("Record definition header for LocalMessageType=" . $header->{localMessageType}); |
176
|
262
|
|
|
|
|
458
|
$self->_parse_definition_message($header); |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
else { |
179
|
10066
|
|
|
|
|
15503
|
my $parseResult = $self->_parse_local_message_record($header); |
180
|
|
|
|
|
|
|
|
181
|
10066
|
50
|
|
|
|
16797
|
if(!defined $parseResult) { |
182
|
0
|
|
|
|
|
0
|
$self->_debug("Skipping record for unknown LocalMessageType=" . $header->{localMessageType}); |
183
|
0
|
|
|
|
|
0
|
next; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
10066
|
|
|
|
|
33394
|
$self->_debug("Processed record for LocalMessageType=" . $header->{localMessageType}); |
187
|
|
|
|
|
|
|
|
188
|
10066
|
|
|
|
|
22519
|
$self->emitRecord($parseResult->{messageType}, $parseResult->{fields}); |
189
|
|
|
|
|
|
|
|
190
|
10066
|
|
|
|
|
72137
|
$self->{records}++; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
} |
194
|
9
|
|
|
|
|
37
|
$self->_debug("DataRecords finished! Found a total of " . $self->{records} . " Records"); |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub on { |
198
|
9
|
|
|
9
|
1
|
52
|
my $self = shift; |
199
|
9
|
|
|
|
|
13
|
my $msgType = shift; |
200
|
9
|
|
|
|
|
11
|
my $handler = shift; |
201
|
|
|
|
|
|
|
|
202
|
9
|
|
|
|
|
16
|
my $msgHandlers = $self->{messageHandlers}; |
203
|
|
|
|
|
|
|
|
204
|
9
|
100
|
|
|
|
23
|
if($handler) { |
205
|
6
|
|
|
|
|
19
|
$msgHandlers->{$msgType} = $handler; |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
else { |
208
|
3
|
|
|
|
|
11
|
delete $msgHandlers->{$msgType}; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
sub emitRecord { |
213
|
10066
|
|
|
10066
|
0
|
10341
|
my $self = shift; |
214
|
10066
|
|
|
|
|
14573
|
my ($msgType, $msgData) = @_; |
215
|
|
|
|
|
|
|
|
216
|
10066
|
50
|
|
|
|
15354
|
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
|
|
|
|
14316
|
if(my $handler = $self->getHandler($msgType)) { |
222
|
9456
|
|
|
|
|
19412
|
$handler->($msgData); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
10066
|
100
|
|
|
|
30966
|
if(my $allHandler = $self->getHandler("_any")) { |
226
|
9303
|
|
|
|
|
15620
|
$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
|
20079
|
my $self = shift; |
252
|
20132
|
|
|
|
|
19554
|
my $msgType = shift; |
253
|
|
|
|
|
|
|
|
254
|
20132
|
50
|
|
|
|
26024
|
if(!$msgType) { |
255
|
0
|
|
|
|
|
0
|
die "cannot get a handler for an unknown msgType!"; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
20132
|
100
|
|
|
|
30384
|
if(exists $self->{messageHandlers}->{$msgType}) { |
259
|
18759
|
|
|
|
|
37159
|
return $self->{messageHandlers}->{$msgType}; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
1373
|
|
|
|
|
2334
|
return undef; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub _parse_definition_message { |
266
|
262
|
|
|
262
|
|
347
|
my $self = shift; |
267
|
262
|
|
|
|
|
377
|
my $header = shift; |
268
|
262
|
|
|
|
|
427
|
my $localMessageType = $header->{localMessageType}; |
269
|
|
|
|
|
|
|
|
270
|
262
|
|
|
|
|
432
|
my $data = $self->_readBytes(5); |
271
|
262
|
|
|
|
|
755
|
my ($reserved, $arch, $globalMessageId, $fields) = unpack("ccsc", $data); |
272
|
|
|
|
|
|
|
|
273
|
262
|
|
|
|
|
589
|
my $globalMessageType = $self->_get_global_message_type($globalMessageId); |
274
|
|
|
|
|
|
|
|
275
|
262
|
|
|
|
|
515
|
$self->_debug("DefinitionMessageHeader:"); |
276
|
262
|
50
|
|
|
|
1260
|
$self->_debug("Arch: $arch - GlobalMessage: " . (defined $globalMessageType ? $globalMessageType->{name} : "") . " ($globalMessageId) - #Fields: $fields"); |
277
|
262
|
50
|
|
|
|
463
|
carp "BigEndian isn't supported so far!" if($arch == 1); |
278
|
|
|
|
|
|
|
|
279
|
262
|
|
|
|
|
488
|
my ($messageFields, $devMsgFields, $recordLength) = ([], [], 0); |
280
|
262
|
|
|
|
|
331
|
my @fields; |
281
|
|
|
|
|
|
|
|
282
|
262
|
50
|
|
|
|
507
|
my $fieldDefinitions = defined $globalMessageType ? $globalMessageType->{fields} : {}; |
283
|
262
|
|
|
|
|
487
|
($messageFields, $recordLength) = $self->_parse_defintion_message_fields($fieldDefinitions, $fields); |
284
|
|
|
|
|
|
|
|
285
|
262
|
50
|
|
|
|
708
|
if($header->{isDeveloperData}) { |
286
|
0
|
|
|
|
|
0
|
($devMsgFields, my $devRecordLength) = $self->parseDeveloperDataDefinitionMessage($globalMessageType); |
287
|
0
|
|
|
|
|
0
|
$recordLength += $devRecordLength; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
262
|
|
|
|
|
748
|
my $combinedDataFields = [@$messageFields, @$devMsgFields]; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
my $localMessage = { |
293
|
|
|
|
|
|
|
size => $recordLength, |
294
|
|
|
|
|
|
|
dataFields => $combinedDataFields, |
295
|
|
|
|
|
|
|
globalMessage => $globalMessageType,, |
296
|
3702
|
|
|
|
|
12689
|
unpackTemplate => join("", map { $_->{baseType}->{packTemplate} . '[' . $_->{arrayLength} . ']' } @$combinedDataFields), |
297
|
|
|
|
|
|
|
isDeveloperMessage => $header->{isDeveloperData}, |
298
|
262
|
|
|
|
|
683
|
isUnknownMessage => !defined $globalMessageType, |
299
|
|
|
|
|
|
|
}; |
300
|
|
|
|
|
|
|
|
301
|
262
|
|
|
|
|
5013
|
$self->{localMessages}->[$localMessageType] = $localMessage; |
302
|
|
|
|
|
|
|
|
303
|
262
|
|
|
|
|
842
|
$self->_debug("Following Record length: " . $localMessage->{size} . " bytes"); |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
sub parseDeveloperDataDefinitionMessage { |
307
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
308
|
0
|
|
|
|
|
0
|
my $globalMessageType = shift; |
309
|
|
|
|
|
|
|
|
310
|
0
|
|
|
|
|
0
|
my $developerFieldCount = unpack("C", $self->_readBytes(1)); |
311
|
|
|
|
|
|
|
|
312
|
0
|
|
|
|
|
0
|
my ($devMsgFields, $devRecordLength) = $self->_parse_defintion_message_fields($globalMessageType->{developer_data}, $developerFieldCount); |
313
|
|
|
|
|
|
|
|
314
|
0
|
|
|
|
|
0
|
foreach my $field (@$devMsgFields) { |
315
|
|
|
|
|
|
|
# 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 |
316
|
|
|
|
|
|
|
# Therefore we simply overwrite the wrongly extracted BaseType from the definition message with the correct one. |
317
|
0
|
|
|
|
|
0
|
$field->{baseType} = $field->{fieldDescriptor}->{baseType}; |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
0
|
|
|
|
|
0
|
return ($devMsgFields, $devRecordLength); |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
sub _parse_defintion_message_fields { |
324
|
262
|
|
|
262
|
|
314
|
my $self = shift; |
325
|
262
|
|
|
|
|
344
|
my $fieldDefinitions = shift; |
326
|
262
|
|
|
|
|
313
|
my $numberOfFields = shift; |
327
|
|
|
|
|
|
|
|
328
|
262
|
|
|
|
|
285
|
my $recordLength = 0; |
329
|
|
|
|
|
|
|
|
330
|
262
|
|
|
|
|
292
|
my @dataFields; |
331
|
|
|
|
|
|
|
|
332
|
262
|
|
|
|
|
583
|
foreach(1..$numberOfFields) { |
333
|
3702
|
|
|
|
|
6087
|
my $fieldDefinitionData = $self->_readBytes(3); # Every Field has 3 Bytes |
334
|
3702
|
|
|
|
|
9026
|
my ($fieldDefinition, $size, $baseTypeData) = unpack("Ccc", $fieldDefinitionData); |
335
|
3702
|
|
|
|
|
6486
|
my ($baseTypeEndian, $baseTypeNumber) = ($baseTypeData & 128, $baseTypeData & 15); |
336
|
3702
|
|
|
|
|
5889
|
my $baseType = $self->_get_base_type($baseTypeNumber); |
337
|
3702
|
|
|
|
|
7743
|
my $fieldDescriptor = $fieldDefinitions->{$fieldDefinition}; |
338
|
|
|
|
|
|
|
|
339
|
3702
|
50
|
|
|
|
5863
|
if(!defined $fieldDescriptor) { |
340
|
0
|
|
|
|
|
0
|
$fieldDescriptor = { |
341
|
|
|
|
|
|
|
isUnkownField => 1, |
342
|
|
|
|
|
|
|
name => "" |
343
|
|
|
|
|
|
|
}; |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
3702
|
|
|
|
|
6715
|
my $fieldName = $fieldDescriptor->{name}; |
347
|
3702
|
|
|
|
|
14138
|
$self->_debug("FieldDefinition: Nr: $fieldDefinition (" . $fieldName . "), Size: $size, BaseType: " . $baseType->{name} . " ($baseTypeNumber), BaseTypeEndian: $baseTypeEndian"); |
348
|
3702
|
|
|
|
|
4627
|
$recordLength += $size; |
349
|
|
|
|
|
|
|
|
350
|
3702
|
|
|
|
|
16976
|
push(@dataFields, { baseType => $baseType, storageSize => $size, isArray => $size > $baseType->{size}, arrayLength => $size/$baseType->{size}, fieldDescriptor => $fieldDescriptor }); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
262
|
|
|
|
|
770
|
return (\@dataFields, $recordLength); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub _global_message_id_to_name { |
357
|
262
|
|
|
262
|
|
309
|
my $self = shift; |
358
|
262
|
|
|
|
|
317
|
my $globalMessageId = shift; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# Manufacterer specific message types |
361
|
262
|
50
|
|
|
|
471
|
if($globalMessageId >= 0xFF00) { |
362
|
0
|
|
|
|
|
0
|
return "mfg_range_min"; |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
262
|
|
|
|
|
525
|
state $globalMessageNames = { |
366
|
|
|
|
|
|
|
0 => "file_id", |
367
|
|
|
|
|
|
|
1 => "capabilities", |
368
|
|
|
|
|
|
|
2 => "device_settings", |
369
|
|
|
|
|
|
|
3 => "user_profile", |
370
|
|
|
|
|
|
|
4 => "hrm_profile", |
371
|
|
|
|
|
|
|
5 => "sdm_profile", |
372
|
|
|
|
|
|
|
6 => "bike_profile", |
373
|
|
|
|
|
|
|
7 => "zones_target", |
374
|
|
|
|
|
|
|
8 => "hr_zone", |
375
|
|
|
|
|
|
|
9 => "power_zone", |
376
|
|
|
|
|
|
|
10 => "met_zone", |
377
|
|
|
|
|
|
|
12 => "sport", |
378
|
|
|
|
|
|
|
15 => "goal", |
379
|
|
|
|
|
|
|
18 => "session", |
380
|
|
|
|
|
|
|
19 => "lap", |
381
|
|
|
|
|
|
|
20 => "record", |
382
|
|
|
|
|
|
|
21 => "event", |
383
|
|
|
|
|
|
|
23 => "device_info", |
384
|
|
|
|
|
|
|
26 => "workout", |
385
|
|
|
|
|
|
|
27 => "workout_step", |
386
|
|
|
|
|
|
|
28 => "schedule", |
387
|
|
|
|
|
|
|
30 => "weight_scale", |
388
|
|
|
|
|
|
|
31 => "course", |
389
|
|
|
|
|
|
|
32 => "course_point", |
390
|
|
|
|
|
|
|
33 => "totals", |
391
|
|
|
|
|
|
|
34 => "activity", |
392
|
|
|
|
|
|
|
35 => "software", |
393
|
|
|
|
|
|
|
37 => "file_capabilities", |
394
|
|
|
|
|
|
|
38 => "mesg_capabilities", |
395
|
|
|
|
|
|
|
39 => "field_capabilities", |
396
|
|
|
|
|
|
|
49 => "file_creator", |
397
|
|
|
|
|
|
|
51 => "blood_pressure", |
398
|
|
|
|
|
|
|
53 => "speed_zone", |
399
|
|
|
|
|
|
|
55 => "monitoring", |
400
|
|
|
|
|
|
|
72 => "training_file", |
401
|
|
|
|
|
|
|
78 => "hrv", |
402
|
|
|
|
|
|
|
80 => "ant_rx", |
403
|
|
|
|
|
|
|
81 => "ant_tx", |
404
|
|
|
|
|
|
|
82 => "ant_channel_id", |
405
|
|
|
|
|
|
|
101 => "length", |
406
|
|
|
|
|
|
|
103 => "monitoring_info", |
407
|
|
|
|
|
|
|
105 => "pad", |
408
|
|
|
|
|
|
|
106 => "slave_device", |
409
|
|
|
|
|
|
|
127 => "connectivity", |
410
|
|
|
|
|
|
|
128 => "weather_conditions", |
411
|
|
|
|
|
|
|
129 => "weather_alert", |
412
|
|
|
|
|
|
|
131 => "cadence_zone", |
413
|
|
|
|
|
|
|
132 => "hr", |
414
|
|
|
|
|
|
|
142 => "segment_lap", |
415
|
|
|
|
|
|
|
145 => "memo_glob", |
416
|
|
|
|
|
|
|
148 => "segment_id", |
417
|
|
|
|
|
|
|
149 => "segment_leaderboard_entry", |
418
|
|
|
|
|
|
|
150 => "segment_point", |
419
|
|
|
|
|
|
|
151 => "segment_file", |
420
|
|
|
|
|
|
|
158 => "workout_session", |
421
|
|
|
|
|
|
|
159 => "watchface_settings", |
422
|
|
|
|
|
|
|
160 => "gps_metadata", |
423
|
|
|
|
|
|
|
161 => "camera_event", |
424
|
|
|
|
|
|
|
162 => "timestamp_correlation", |
425
|
|
|
|
|
|
|
164 => "gyroscope_data", |
426
|
|
|
|
|
|
|
165 => "accelerometer_data", |
427
|
|
|
|
|
|
|
167 => "three_d_sensor_calibration", |
428
|
|
|
|
|
|
|
169 => "video_frame", |
429
|
|
|
|
|
|
|
174 => "obdii_data", |
430
|
|
|
|
|
|
|
177 => "nmea_sentence", |
431
|
|
|
|
|
|
|
178 => "aviation_attitude", |
432
|
|
|
|
|
|
|
184 => "video", |
433
|
|
|
|
|
|
|
185 => "video_title", |
434
|
|
|
|
|
|
|
186 => "video_description", |
435
|
|
|
|
|
|
|
187 => "video_clip", |
436
|
|
|
|
|
|
|
188 => "ohr_settings", |
437
|
|
|
|
|
|
|
200 => "exd_screen_configuration", |
438
|
|
|
|
|
|
|
201 => "exd_data_field_configuration", |
439
|
|
|
|
|
|
|
202 => "exd_data_concept_configuration", |
440
|
|
|
|
|
|
|
206 => "field_description", |
441
|
|
|
|
|
|
|
207 => "developer_data_id", |
442
|
|
|
|
|
|
|
208 => "magnetometer_data", |
443
|
|
|
|
|
|
|
209 => "barometer_data", |
444
|
|
|
|
|
|
|
210 => "one_d_sensor_calibration", |
445
|
|
|
|
|
|
|
225 => "set", |
446
|
|
|
|
|
|
|
227 => "stress_level", |
447
|
|
|
|
|
|
|
258 => "dive_settings", |
448
|
|
|
|
|
|
|
259 => "dive_gas", |
449
|
|
|
|
|
|
|
262 => "dive_alarm", |
450
|
|
|
|
|
|
|
264 => "exercise_title", |
451
|
|
|
|
|
|
|
268 => "dive_summary", |
452
|
|
|
|
|
|
|
285 => "jump", |
453
|
|
|
|
|
|
|
317 => "climb_pro", |
454
|
|
|
|
|
|
|
}; |
455
|
|
|
|
|
|
|
|
456
|
262
|
50
|
|
|
|
627
|
if(exists $globalMessageNames->{$globalMessageId}) { |
457
|
262
|
|
|
|
|
614
|
return $globalMessageNames->{$globalMessageId}; |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
else { |
460
|
0
|
|
|
|
|
0
|
return undef; |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
sub getLocalMessageById { |
465
|
10066
|
|
|
10066
|
0
|
10055
|
my $self = shift; |
466
|
10066
|
|
|
|
|
9572
|
my $localMessageId = shift; |
467
|
|
|
|
|
|
|
|
468
|
10066
|
|
|
|
|
12178
|
my $localMessage = $self->{localMessages}->[$localMessageId]; |
469
|
|
|
|
|
|
|
|
470
|
10066
|
50
|
|
|
|
15059
|
if(!defined $localMessage) { |
471
|
0
|
|
|
|
|
0
|
die "Encountered a record localMessageId=$localMessageId which was not introduced by a definition message!"; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
|
474
|
10066
|
|
|
|
|
11792
|
return $localMessage; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub _get_global_message_type { |
478
|
262
|
|
|
262
|
|
319
|
my $self = shift; |
479
|
|
|
|
|
|
|
|
480
|
262
|
|
|
|
|
509
|
my $globalMessageName = $self->_global_message_id_to_name(shift); |
481
|
|
|
|
|
|
|
|
482
|
262
|
50
|
|
|
|
515
|
if(!defined $globalMessageName) { |
483
|
0
|
|
|
|
|
0
|
return undef; |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
|
486
|
262
|
50
|
|
|
|
662
|
if(exists $Parser::FIT::Profile::PROFILE->{$globalMessageName}) { |
487
|
262
|
|
|
|
|
545
|
return $Parser::FIT::Profile::PROFILE->{$globalMessageName}; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
else { |
490
|
0
|
|
|
|
|
0
|
return undef; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
sub _parse_local_message_record { |
495
|
10066
|
|
|
10066
|
|
10798
|
my $self = shift; |
496
|
10066
|
|
|
|
|
10538
|
my $header = shift; |
497
|
|
|
|
|
|
|
|
498
|
10066
|
|
|
|
|
10532
|
my $localMessageId = $header->{localMessageType}; |
499
|
10066
|
|
|
|
|
12880
|
my $localMessage = $self->getLocalMessageById($localMessageId); |
500
|
|
|
|
|
|
|
|
501
|
10066
|
|
|
|
|
14450
|
my $recordLength = $localMessage->{size}; |
502
|
10066
|
|
|
|
|
12541
|
my $record = $self->_readBytes($recordLength); |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
# skip unknown messages (the _readBytes above is correct, since we need to "remove" the bytes from the stream) |
505
|
10066
|
50
|
|
|
|
17807
|
if($localMessage->{isUnknownMessage}) { |
506
|
0
|
|
|
|
|
0
|
return undef; |
507
|
|
|
|
|
|
|
} |
508
|
|
|
|
|
|
|
|
509
|
10066
|
|
|
|
|
11928
|
my $unpackTemplate = $localMessage->{unpackTemplate}; |
510
|
10066
|
|
|
|
|
33984
|
my @rawFields = unpack($unpackTemplate, $record); |
511
|
|
|
|
|
|
|
|
512
|
10066
|
|
|
|
|
11769
|
my %result; |
513
|
|
|
|
|
|
|
|
514
|
10066
|
|
|
|
|
10436
|
my $fieldCount = scalar @{$localMessage->{dataFields}}; |
|
10066
|
|
|
|
|
13003
|
|
515
|
10066
|
|
|
|
|
18309
|
for(my $i = 0; $i < $fieldCount; $i++) { |
516
|
162163
|
|
|
|
|
201850
|
my $localMessageField = $localMessage->{dataFields}->[$i]; |
517
|
162163
|
|
|
|
|
164053
|
my $rawValue = $rawFields[$i]; |
518
|
|
|
|
|
|
|
|
519
|
162163
|
|
|
|
|
175298
|
my $fieldDescriptor = $localMessageField->{fieldDescriptor}; |
520
|
162163
|
|
|
|
|
180054
|
my $fieldName = $fieldDescriptor->{name}; |
521
|
|
|
|
|
|
|
|
522
|
162163
|
50
|
|
|
|
213074
|
if($fieldDescriptor->{isUnkownField}) { |
523
|
0
|
|
|
|
|
0
|
next; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
162163
|
|
|
|
|
209633
|
my $postProcessedValue = $self->postProcessRawValue($rawValue, $fieldDescriptor); |
527
|
|
|
|
|
|
|
|
528
|
162163
|
|
|
|
|
536796
|
$result{$fieldName} = { |
529
|
|
|
|
|
|
|
value => $postProcessedValue, |
530
|
|
|
|
|
|
|
rawValue => $rawValue, |
531
|
|
|
|
|
|
|
fieldDescriptor => $fieldDescriptor, |
532
|
|
|
|
|
|
|
}; |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
return { |
536
|
|
|
|
|
|
|
messageType => $localMessage->{globalMessage}->{name}, |
537
|
10066
|
|
|
|
|
37729
|
fields => \%result |
538
|
|
|
|
|
|
|
}; |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
sub postProcessRawValue { |
542
|
162163
|
|
|
162163
|
0
|
159903
|
my $self = shift; |
543
|
162163
|
|
|
|
|
154909
|
my $rawValue = shift; |
544
|
162163
|
|
|
|
|
149252
|
my $fieldDescriptor = shift; |
545
|
|
|
|
|
|
|
|
546
|
162163
|
100
|
|
|
|
212695
|
if(defined $fieldDescriptor->{scale}) { |
547
|
74821
|
|
|
|
|
87565
|
$rawValue /= $fieldDescriptor->{scale}; |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
|
550
|
162163
|
100
|
|
|
|
209269
|
if(defined $fieldDescriptor->{offset}) { |
551
|
9143
|
|
|
|
|
11566
|
$rawValue -= $fieldDescriptor->{offset}; |
552
|
|
|
|
|
|
|
} |
553
|
|
|
|
|
|
|
|
554
|
162163
|
50
|
66
|
|
|
366503
|
if(defined $fieldDescriptor->{unit} && $fieldDescriptor->{unit} eq "semicircles") { |
555
|
0
|
|
|
|
|
0
|
state $semicirclesToDegreesConversionRate = 180 / 2**31; |
556
|
0
|
|
|
|
|
0
|
$rawValue *= $semicirclesToDegreesConversionRate; |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
162163
|
100
|
66
|
|
|
340517
|
if(defined $fieldDescriptor->{type} && $fieldDescriptor->{type} eq "date_time") { |
560
|
10146
|
|
|
|
|
10494
|
state $fitEpocheOffset = 631065600; |
561
|
10146
|
|
|
|
|
10972
|
$rawValue += $fitEpocheOffset; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
162163
|
|
|
|
|
219276
|
return $rawValue; |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
sub _get_base_type { |
568
|
3702
|
|
|
3702
|
|
3893
|
my $self = shift; |
569
|
3702
|
|
|
|
|
4080
|
my $index = shift; |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
# See "Table 7. FIT Base Types and Invalid Values" at https://developer.garmin.com/fit/protocol/ |
572
|
3702
|
|
|
|
|
32180
|
my $types = [ |
573
|
|
|
|
|
|
|
{ |
574
|
|
|
|
|
|
|
name => "enum", |
575
|
|
|
|
|
|
|
size => 1, |
576
|
|
|
|
|
|
|
invalid => 0xff, |
577
|
|
|
|
|
|
|
packTemplate => "c", |
578
|
|
|
|
|
|
|
}, |
579
|
|
|
|
|
|
|
{ |
580
|
|
|
|
|
|
|
name => "sint8", |
581
|
|
|
|
|
|
|
size => 1, |
582
|
|
|
|
|
|
|
invalid => 0x7f, |
583
|
|
|
|
|
|
|
packTemplate => "c" |
584
|
|
|
|
|
|
|
}, |
585
|
|
|
|
|
|
|
{ |
586
|
|
|
|
|
|
|
name => "uint8", |
587
|
|
|
|
|
|
|
size => 1, |
588
|
|
|
|
|
|
|
invalid => 0xff, |
589
|
|
|
|
|
|
|
packTemplate => "C", |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
}, |
592
|
|
|
|
|
|
|
{ |
593
|
|
|
|
|
|
|
name => "sint16", |
594
|
|
|
|
|
|
|
size => 2, |
595
|
|
|
|
|
|
|
invalid => 0x7fff, |
596
|
|
|
|
|
|
|
packTemplate => "s", |
597
|
|
|
|
|
|
|
}, |
598
|
|
|
|
|
|
|
{ |
599
|
|
|
|
|
|
|
name => "uint16", |
600
|
|
|
|
|
|
|
size => 2, |
601
|
|
|
|
|
|
|
invalid => 0xffff, |
602
|
|
|
|
|
|
|
packTemplate => "S" |
603
|
|
|
|
|
|
|
}, |
604
|
|
|
|
|
|
|
{ |
605
|
|
|
|
|
|
|
name => "sint32", |
606
|
|
|
|
|
|
|
size => 4, |
607
|
|
|
|
|
|
|
invalid => 0x7fffffff, |
608
|
|
|
|
|
|
|
packTemplate => "l" |
609
|
|
|
|
|
|
|
}, |
610
|
|
|
|
|
|
|
{ |
611
|
|
|
|
|
|
|
name => "uint32", |
612
|
|
|
|
|
|
|
size => 4, |
613
|
|
|
|
|
|
|
invalid => 0xffffffff, |
614
|
|
|
|
|
|
|
packTemplate => "L", |
615
|
|
|
|
|
|
|
}, |
616
|
|
|
|
|
|
|
{ |
617
|
|
|
|
|
|
|
name => "string", |
618
|
|
|
|
|
|
|
size => 1, |
619
|
|
|
|
|
|
|
invalid => 0x00, |
620
|
|
|
|
|
|
|
packTemplate => "Z" |
621
|
|
|
|
|
|
|
}, |
622
|
|
|
|
|
|
|
{ |
623
|
|
|
|
|
|
|
name => "float32", |
624
|
|
|
|
|
|
|
size => 4, |
625
|
|
|
|
|
|
|
invalid => 0xffffffff, |
626
|
|
|
|
|
|
|
packTemplate => "f" |
627
|
|
|
|
|
|
|
}, |
628
|
|
|
|
|
|
|
{ |
629
|
|
|
|
|
|
|
name => "float64", |
630
|
|
|
|
|
|
|
size => 8, |
631
|
|
|
|
|
|
|
invalid => Math::BigInt->new("0xffffffffffffffff"), |
632
|
|
|
|
|
|
|
packTemplate => "d", |
633
|
|
|
|
|
|
|
}, |
634
|
|
|
|
|
|
|
{ |
635
|
|
|
|
|
|
|
name => "uint8z", |
636
|
|
|
|
|
|
|
size => 1, |
637
|
|
|
|
|
|
|
invalid => 0x00, |
638
|
|
|
|
|
|
|
packTemplate => "c" |
639
|
|
|
|
|
|
|
}, |
640
|
|
|
|
|
|
|
{ |
641
|
|
|
|
|
|
|
name => "uint16z", |
642
|
|
|
|
|
|
|
size => 2, |
643
|
|
|
|
|
|
|
invalid => 0x0000, |
644
|
|
|
|
|
|
|
packTemplate => "S", |
645
|
|
|
|
|
|
|
}, |
646
|
|
|
|
|
|
|
{ |
647
|
|
|
|
|
|
|
name => "uint32z", |
648
|
|
|
|
|
|
|
size => 4, |
649
|
|
|
|
|
|
|
invalid => 0x00000000, |
650
|
|
|
|
|
|
|
packTemplate => "L" |
651
|
|
|
|
|
|
|
}, |
652
|
|
|
|
|
|
|
{ |
653
|
|
|
|
|
|
|
name => "byte", |
654
|
|
|
|
|
|
|
size => 1, |
655
|
|
|
|
|
|
|
invalid => 0xFF, |
656
|
|
|
|
|
|
|
packTemplate => "C", |
657
|
|
|
|
|
|
|
}, |
658
|
|
|
|
|
|
|
{ |
659
|
|
|
|
|
|
|
name => "sint64", |
660
|
|
|
|
|
|
|
size => 8, |
661
|
|
|
|
|
|
|
invalid => Math::BigInt->new("0x7fffffffffffffff"), |
662
|
|
|
|
|
|
|
packTemplate => "q", |
663
|
|
|
|
|
|
|
}, |
664
|
|
|
|
|
|
|
{ |
665
|
|
|
|
|
|
|
name => "uint64", |
666
|
|
|
|
|
|
|
size => 8, |
667
|
|
|
|
|
|
|
invalid => Math::BigInt->new("0xffffffffffffffff"), |
668
|
|
|
|
|
|
|
packTemplate => "Q", |
669
|
|
|
|
|
|
|
}, |
670
|
|
|
|
|
|
|
{ |
671
|
|
|
|
|
|
|
name => "uint64z", |
672
|
|
|
|
|
|
|
size => 8, |
673
|
|
|
|
|
|
|
invalid => 0x0000000000000000, |
674
|
|
|
|
|
|
|
packTemplate => "Q", |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
]; |
677
|
|
|
|
|
|
|
|
678
|
3702
|
50
|
|
|
|
2422876
|
if($index >= @{$types}) { |
|
3702
|
|
|
|
|
7034
|
|
679
|
0
|
|
|
|
|
0
|
die "Invalid index=$index for BaseTypeLookup!"; |
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
|
682
|
3702
|
|
|
|
|
28204
|
return $types->[$index]; |
683
|
|
|
|
|
|
|
} |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
0
|
|
|
sub _parse_crc { |
686
|
|
|
|
|
|
|
# TODO implement this one...some time :D |
687
|
|
|
|
|
|
|
} |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
sub _debug { |
690
|
25235
|
|
|
25235
|
|
27612
|
my $self = shift; |
691
|
25235
|
50
|
|
|
|
45798
|
if($self->{_DEBUG}) { |
692
|
0
|
|
|
|
|
0
|
print "[FIT.pm DEBUG] ", @_; |
693
|
0
|
|
|
|
|
0
|
print "\n"; |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
sub _readBytes { |
698
|
24376
|
|
|
24376
|
|
25662
|
my $self = shift; |
699
|
24376
|
|
|
|
|
24658
|
my $num = shift; |
700
|
|
|
|
|
|
|
|
701
|
24376
|
|
|
|
|
26397
|
$self->{totalBytesRead} += $num; |
702
|
24376
|
|
|
|
|
23191
|
my $buffer; |
703
|
24376
|
|
|
|
|
57449
|
my $bytesRead = read($self->{fh}, $buffer, $num); |
704
|
|
|
|
|
|
|
# TODO error handling based on bytesRead |
705
|
24376
|
|
|
|
|
43248
|
return $buffer; |
706
|
|
|
|
|
|
|
} |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
1; |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
__END__ |