File Coverage

blib/lib/Parser/FIT.pm
Criterion Covered Total %
statement 266 275 96.7
branch 70 86 81.4
condition 9 11 81.8
subroutine 31 32 96.8
pod 4 11 36.3
total 380 415 91.5


line stmt bran cond sub pod time code
1             package Parser::FIT;
2              
3 12     12   1294025 use strict;
  12         28  
  12         494  
4 12     12   150 use warnings;
  12         36  
  12         786  
5 12     12   88 use Carp qw/croak confess/;
  12         40  
  12         972  
6 12     12   104 use feature 'state';
  12         34  
  12         2313  
7 12     12   20686 use Math::BigInt;
  12         672861  
  12         66  
8 12     12   463397 use Parser::FIT::Profile;
  12         127  
  12         72724  
9              
10             #require "Profile.pm";
11              
12             our $VERSION = 0.10;
13              
14             sub new {
15 18     18 1 2247057 my $class = shift;
16 18         62 my %options = @_;
17              
18 18         260 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 18 100       92 if(exists $options{on}) {
33 8         28 $ref->{messageHandlers} = $options{on};
34             }
35              
36 18 0 33     61 if(exists $options{debug} && $options{debug}) {
37 0         0 $ref->{_DEBUG} = 1;
38             }
39              
40 18         45 bless($ref, $class);
41              
42 18         58 return $ref;
43             }
44              
45             sub parse {
46 12     12 1 63 my $self = shift;
47 12         43 my $file = shift;
48              
49 12 50       51 croak "No file given to parse()!" unless($file);
50              
51 12         96 $self->_debug("Parsing '$file'");
52              
53 12 50       540 croak "File '$file' doesn't exist!" if(!-f $file);
54              
55 12         50 $self->_debug("Opening file");
56 12 50       662 open(my $input, "<", $file) or croak "Error opening '$file': $!";
57 12         40 binmode($input);
58              
59 12         70 $self->parse_fh($input);
60             }
61              
62             sub parse_fh {
63 14     14 0 29 my $self = shift;
64 14         41 my $input = shift;
65              
66 14 50       66 unless(ref $input eq "GLOB") {
67 0         0 die "parse_fh requires an opened filehandle as param!";
68             }
69              
70              
71 14         42 $self->{fh} = $input;
72 14         70 my $header = $self->_read_header();
73 14         64 $self->{header} = $self->_parse_header($header);
74             #my $dataBody = $self->_readBytes($self->{header}->{dataLength});
75 14         92 $self->_parse_data_records();
76             #$self->_parse_crc();
77              
78 14         530 close($input);
79             }
80              
81             sub parse_data {
82 2     2 1 269 my $self = shift;
83 2         8 my $data = shift;
84              
85 2 50       60 open(my $fh, "<", \$data) or die "Error opening scalar as file: $!";
86 2         10 binmode($fh);
87              
88 2         12 return $self->parse_fh($fh);
89             }
90              
91             sub _read_header {
92 14     14   23 my $self = shift;
93              
94 14         54 my $headerLengthByte = $self->_readBytes(1);
95 14         67 my $headerLength = unpack("c", $headerLengthByte);
96 14         34 $self->{headerLength} = $headerLength;
97              
98             # The 1-Byte headerLength field is included in the total header length
99 14         40 my $headerWithoutLengthByte = $headerLength - 1;
100              
101 14         50 my $header = $self->_readBytes($headerWithoutLengthByte);
102              
103 14         39 return $header;
104             }
105              
106             sub _parse_header {
107 23     23   27463 my $self = shift;
108 23         44 my $header = shift;
109              
110 23         53 my ($protocolVersion, $profile, $dataLength, $fileMagic, $crc);
111              
112 23         49 my $headerLength = length $header;
113              
114 23 100       111 if($headerLength == 13) {
    100          
115 16         99 ($protocolVersion, $profile, $dataLength, $fileMagic, $crc) = unpack("c s I! a4 s", $header);
116             }
117             elsif($headerLength == 11) {
118 1         8 ($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         82 croak "Invalid headerLength=${headerLength}! Don't know how to handle this.";
125             }
126              
127 17 100       109 croak "File either corrupted or not a real FIT file! (Missing magic '.FIT' string in header)" unless($fileMagic eq ".FIT");
128              
129 16         83 $self->_debug("ProtocolVersion: $protocolVersion");
130 16         76 $self->_debug("Profile: $profile");
131 16         60 $self->_debug("DataLength: $dataLength Bytes");
132 16         66 $self->_debug("FileMagic: $fileMagic");
133 16 100       146 $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 16         161 eof => $self->{headerLength} + $dataLength,
141             };
142              
143 16         78 return $headerInfo;
144             }
145              
146             sub _parse_record_header {
147 15091     15091   36705 my $self = shift;
148 15091         19788 my $recordHeader = shift;
149              
150             return {
151             # Bit 7 inidcates a normal header (=0) or "something else"
152 15091         69167 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 14     14   30 my $self = shift;
165              
166 14         46 $self->_debug("Parsing Data Records");
167 14         59 while($self->{totalBytesRead} < $self->{header}->{eof}) {
168            
169 15076         35824 my ($recordHeaderByte) = unpack("c", $self->_readBytes(1));
170 15076         84380 $self->_debug("HeaderBytes in Binary: " . sprintf("%08b", $recordHeaderByte));
171 15076         35986 my $header = $self->_parse_record_header($recordHeaderByte);
172              
173 15076 50       35197 if($header->{isNormalHeader}) {
174 15076 100       29405 if($header->{isDefinitionMessage}) {
175 364         1436 $self->_debug("Record definition header for LocalMessageType=" . $header->{localMessageType});
176 364         930 $self->_parse_definition_message($header);
177             }
178             else {
179 14712         31588 my $parseResult = $self->_parse_local_message_record($header);
180              
181 14712 100       34921 if(!defined $parseResult) {
182 101         283 $self->_debug("Skipping record for unknown LocalMessageType=" . $header->{localMessageType});
183 101         361 next;
184             }
185            
186 14611         67517 $self->_debug("Processed record for LocalMessageType=" . $header->{localMessageType});
187              
188 14611         57997 $self->emitRecord($parseResult->{messageType}, $parseResult->{fields});
189              
190 14611         165779 $self->{records}++;
191             }
192             }
193             }
194 14         72 $self->_debug("DataRecords finished! Found a total of " . $self->{records} . " Records");
195             }
196              
197             sub on {
198 15     15 1 5800 my $self = shift;
199 15         28 my $msgType = shift;
200 15         34 my $handler = shift;
201              
202 15         45 my $msgHandlers = $self->{messageHandlers};
203              
204 15 100       46 if($handler) {
205 9         34 $msgHandlers->{$msgType} = $handler;
206             }
207             else {
208 6         24 delete $msgHandlers->{$msgType};
209             }
210             }
211              
212             sub emitRecord {
213 14611     14611 0 20614 my $self = shift;
214 14611         30966 my ($msgType, $msgData) = @_;
215              
216 14611 100       27727 if($msgType eq 'field_description') {
217 2         4 $self->_debug("Encountered a field_description message");
218 2         5 $self->attachDeveloperDataToGlobalMessage($msgData);
219             }
220              
221 14611 100       32873 if(my $handler = $self->getHandler($msgType)) {
222 10147         33943 $handler->($msgData);
223             }
224              
225 14611 100       72710 if(my $allHandler = $self->getHandler("_any")) {
226 9303         22315 $allHandler->($msgType, $msgData);
227             }
228             }
229              
230             sub attachDeveloperDataToGlobalMessage {
231 2     2 0 2 my $self = shift;
232 2         3 my $msgData = shift;
233              
234 2         3 my $globalMessageid = $msgData->{native_mesg_num}->{value};
235 2         2 my $fieldId = $msgData->{field_definition_number}->{value};
236              
237 2         3 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 2         6 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 29222     29222 0 38353 my $self = shift;
252 29222         41117 my $msgType = shift;
253              
254 29222 50       51275 if(!$msgType) {
255 0         0 die "cannot get a handler for an unknown msgType!";
256             }
257              
258 29222 100       61802 if(exists $self->{messageHandlers}->{$msgType}) {
259 19450         68697 return $self->{messageHandlers}->{$msgType};
260             }
261              
262 9772         21085 return undef;
263             }
264              
265             sub _parse_definition_message {
266 364     364   595 my $self = shift;
267 364         532 my $header = shift;
268 364         791 my $localMessageType = $header->{localMessageType};
269              
270 364         746 my $data = $self->_readBytes(5);
271 364         1354 my ($reserved, $arch, $globalMessageId, $fields) = unpack("ccsc", $data);
272              
273 364         1029 my $globalMessageType = $self->_get_global_message_type($globalMessageId);
274              
275 364         1007 $self->_debug("DefinitionMessageHeader:");
276 364 100       2246 $self->_debug("Arch: $arch - GlobalMessage: " . (defined $globalMessageType ? $globalMessageType->{name} : "") . " ($globalMessageId) - #Fields: $fields");
277 364 50       824 confess "BigEndian isn't supported so far!" if($arch == 1);
278              
279 364         949 my ($messageFields, $devMsgFields, $recordLength) = ([], [], 0);
280 364         624 my @fields;
281              
282 364 100       860 my $fieldDefinitions = defined $globalMessageType ? $globalMessageType->{fields} : {};
283 364         1045 ($messageFields, $recordLength) = $self->_parse_defintion_message_fields($fieldDefinitions, $fields);
284              
285 364 100       1541 if($header->{isDeveloperData}) {
286 2         8 ($devMsgFields, my $devRecordLength) = $self->parseDeveloperDataDefinitionMessage($globalMessageType);
287 2         4 $recordLength += $devRecordLength;
288             }
289              
290 364         1350 my $combinedDataFields = [@$messageFields, @$devMsgFields];
291              
292             my $buildPackTemplate = sub {
293 5339     5339   6731 my $dataField = shift;
294              
295 5339         9882 my $templateChar = $dataField->{baseType}->{packTemplate};
296 5339         7614 my $arrayLength = $dataField->{arrayLength};
297 5339         9896 my $fieldName = $dataField->{fieldDescriptor}->{name};
298 5339   100     11115 my $fieldDatatype = $dataField->{fieldDescriptor}->{type} || "";
299              
300 5339         7878 my $templateString = "${templateChar}";
301              
302 5339 100       10419 if($arrayLength > 1) {
303 896         1366 $templateString .= "[${arrayLength}]";
304             }
305              
306 5339         17122 $templateString .= " # $fieldName baseType=$fieldDatatype baseTypeSize=$dataField->{baseType}->{size}, storageSize=$dataField->{storageSize}, byteOffsetInMessage=" . $dataField->{byteOffsetInMessage};
307              
308 5339         15304 return $templateString;
309 364         2956 };
310              
311             my $localMessage = {
312             size => $recordLength,
313             dataFields => $combinedDataFields,
314             globalMessage => $globalMessageType,,
315 5339         7707 unpackTemplate => join("\n", map { $buildPackTemplate->($_) } @$combinedDataFields),
316             isDeveloperMessage => $header->{isDeveloperData},
317 364         1411 isUnknownMessage => !defined $globalMessageType,
318             };
319              
320 364         11436 $self->{localMessages}->[$localMessageType] = $localMessage;
321              
322 364         1727 $self->_debug("Following Record length: " . $localMessage->{size} . " bytes");
323             }
324              
325             sub parseDeveloperDataDefinitionMessage {
326 2     2 0 3 my $self = shift;
327 2         3 my $globalMessageType = shift;
328              
329 2         7 my $developerFieldCount = unpack("C", $self->_readBytes(1));
330              
331 2         7 my ($devMsgFields, $devRecordLength) = $self->_parse_defintion_message_fields($globalMessageType->{developer_data}, $developerFieldCount);
332              
333 2         6 foreach my $field (@$devMsgFields) {
334             # 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
335             # Therefore we simply overwrite the wrongly extracted BaseType from the definition message with the correct one.
336 2         6 $field->{baseType} = $field->{fieldDescriptor}->{baseType};
337             }
338              
339 2         6 return ($devMsgFields, $devRecordLength);
340             }
341              
342             sub _parse_defintion_message_fields {
343 366     366   563 my $self = shift;
344 366         553 my $fieldDefinitions = shift;
345 366         567 my $numberOfFields = shift;
346              
347 366         558 my $recordLength = 0;
348              
349 366         514 my @dataFields;
350              
351 366         611 my $byteOffsetInMessage = 0;
352 366         1032 foreach my $i (1..$numberOfFields) {
353 5339         19203 my $fieldDefinitionData = $self->_readBytes(3); # Every Field has 3 Bytes
354 5339         19691 my ($fieldDefinitionId, $size, $baseTypeData) = unpack("CCc", $fieldDefinitionData);
355 5339         12164 my ($baseTypeEndian, $baseTypeNumber) = ($baseTypeData & 128, $baseTypeData & 15);
356 5339         10840 my $baseType = $self->_get_base_type($baseTypeNumber);
357 5339         14777 my $fieldDescriptor = $fieldDefinitions->{$fieldDefinitionId};
358              
359 5339 50       10707 die "Failed to parse file: Size=$size cannot be 0 or less" if($size <= 0);
360              
361 5339 100       15511 if(!defined $fieldDescriptor) {
362             $fieldDescriptor = {
363             isUnkownField => 1,
364             name => "",
365             type => $baseType->{name},
366 574         2255 };
367             }
368              
369 5339         12308 my $fieldName = $fieldDescriptor->{name};
370 5339         31660 $self->_debug("FieldDefinition[$i]: $fieldName ($fieldDefinitionId), Size: $size, BaseType: " . $baseType->{name} . " ($baseTypeNumber), BaseTypeEndian: $baseTypeEndian");
371 5339         12180 $recordLength += $size;
372              
373             my $fieldDefinition = {
374             baseType => $baseType,
375             storageSize => $size,
376             isArray => $size > $baseType->{size},
377             arrayLength => $size/$baseType->{size},
378 5339         45882 fieldDescriptor => $fieldDescriptor,
379             byteOffsetInMessage => $byteOffsetInMessage
380             };
381              
382 5339         11123 push(@dataFields, $fieldDefinition);
383 5339         12959 $byteOffsetInMessage += $size;
384             }
385              
386 366         1557 return (\@dataFields, $recordLength);
387             }
388              
389             sub _global_message_id_to_name {
390 366     366   588 my $self = shift;
391 366         577 my $globalMessageId = shift;
392              
393             # Manufacterer specific message types
394 366 50       984 if($globalMessageId >= 0xFF00) {
395 0         0 return "mfg_range_min";
396             }
397              
398 366         1423 state $globalMessageNames = {
399             0 => "file_id",
400             1 => "capabilities",
401             2 => "device_settings",
402             3 => "user_profile",
403             4 => "hrm_profile",
404             5 => "sdm_profile",
405             6 => "bike_profile",
406             7 => "zones_target",
407             8 => "hr_zone",
408             9 => "power_zone",
409             10 => "met_zone",
410             12 => "sport",
411             15 => "goal",
412             18 => "session",
413             19 => "lap",
414             20 => "record",
415             21 => "event",
416             23 => "device_info",
417             26 => "workout",
418             27 => "workout_step",
419             28 => "schedule",
420             30 => "weight_scale",
421             31 => "course",
422             32 => "course_point",
423             33 => "totals",
424             34 => "activity",
425             35 => "software",
426             37 => "file_capabilities",
427             38 => "mesg_capabilities",
428             39 => "field_capabilities",
429             49 => "file_creator",
430             51 => "blood_pressure",
431             53 => "speed_zone",
432             55 => "monitoring",
433             72 => "training_file",
434             78 => "hrv",
435             80 => "ant_rx",
436             81 => "ant_tx",
437             82 => "ant_channel_id",
438             101 => "length",
439             103 => "monitoring_info",
440             105 => "pad",
441             106 => "slave_device",
442             127 => "connectivity",
443             128 => "weather_conditions",
444             129 => "weather_alert",
445             131 => "cadence_zone",
446             132 => "hr",
447             142 => "segment_lap",
448             145 => "memo_glob",
449             148 => "segment_id",
450             149 => "segment_leaderboard_entry",
451             150 => "segment_point",
452             151 => "segment_file",
453             158 => "workout_session",
454             159 => "watchface_settings",
455             160 => "gps_metadata",
456             161 => "camera_event",
457             162 => "timestamp_correlation",
458             164 => "gyroscope_data",
459             165 => "accelerometer_data",
460             167 => "three_d_sensor_calibration",
461             169 => "video_frame",
462             174 => "obdii_data",
463             177 => "nmea_sentence",
464             178 => "aviation_attitude",
465             184 => "video",
466             185 => "video_title",
467             186 => "video_description",
468             187 => "video_clip",
469             188 => "ohr_settings",
470             200 => "exd_screen_configuration",
471             201 => "exd_data_field_configuration",
472             202 => "exd_data_concept_configuration",
473             206 => "field_description",
474             207 => "developer_data_id",
475             208 => "magnetometer_data",
476             209 => "barometer_data",
477             210 => "one_d_sensor_calibration",
478             225 => "set",
479             227 => "stress_level",
480             258 => "dive_settings",
481             259 => "dive_gas",
482             262 => "dive_alarm",
483             264 => "exercise_title",
484             268 => "dive_summary",
485             285 => "jump",
486             317 => "climb_pro",
487             };
488              
489 366 100       1174 if(exists $globalMessageNames->{$globalMessageId}) {
490 350         1076 return $globalMessageNames->{$globalMessageId};
491             }
492             else {
493 16         39 return undef;
494             }
495             }
496              
497             sub getLocalMessageById {
498 14712     14712 0 19409 my $self = shift;
499 14712         18848 my $localMessageId = shift;
500              
501 14712         25444 my $localMessage = $self->{localMessages}->[$localMessageId];
502              
503 14712 50       31017 if(!defined $localMessage) {
504 0         0 die "Encountered a record localMessageId=$localMessageId which was not introduced by a definition message!";
505             }
506              
507 14712         23432 return $localMessage;
508             }
509              
510             sub _get_global_message_type {
511 366     366   586 my $self = shift;
512              
513 366         1057 my $globalMessageName = $self->_global_message_id_to_name(shift);
514              
515 366 100       867 if(!defined $globalMessageName) {
516 16         27 return undef;
517             }
518            
519 350 50       1186 if(exists $Parser::FIT::Profile::PROFILE->{$globalMessageName}) {
520 350         865 return $Parser::FIT::Profile::PROFILE->{$globalMessageName};
521             }
522             else {
523 0         0 return undef;
524             }
525             }
526              
527             sub _parse_local_message_record {
528 14712     14712   20496 my $self = shift;
529 14712         19508 my $header = shift;
530              
531 14712         22588 my $localMessageId = $header->{localMessageType};
532 14712         29716 my $localMessage = $self->getLocalMessageById($localMessageId);
533              
534 14712         35008 my $recordLength = $localMessage->{size};
535 14712         26048 my $record = $self->_readBytes($recordLength);
536              
537             # skip unknown messages (the _readBytes above is correct, since we need to "remove" the bytes from the stream)
538 14712 100       36172 if($localMessage->{isUnknownMessage}) {
539 101         184 return undef;
540             }
541              
542 14611         23199 my $unpackTemplate = $localMessage->{unpackTemplate};
543 14611         99032 my @rawFields = unpack($unpackTemplate, $record);
544              
545 14611         22620 my %result;
546              
547 14611         18178 my $fieldCount = scalar @{$localMessage->{dataFields}};
  14611         25380  
548 14611         21282 my $rawValueIndex = 0;
549 14611         31186 for(my $i = 0; $i < $fieldCount; $i++) {
550 200572         329104 my $localMessageField = $localMessage->{dataFields}->[$i];
551 200572         294116 my $rawValue = $rawFields[$rawValueIndex];
552              
553             # Special case for strings:
554             # The pack/unpack template for strings looks like an array (e.G. Z[32]) but does not produce
555             # multiple entries after unpacking, but only a single one.
556             # Therefore after reading a string we should only proceed to the next entry.
557             # While reading a uint8 array (e.G. c[4]) we must skip 4 entries to arrive at the next field.
558 200572 100       386637 if($localMessageField->{baseType}->{name} eq "string") {
559 11         17 $rawValueIndex += 1;
560             }
561             else {
562 200561         308446 $rawValueIndex += $localMessageField->{arrayLength};
563             }
564              
565 200572         284987 my $fieldDescriptor = $localMessageField->{fieldDescriptor};
566 200572         317298 my $fieldName = $fieldDescriptor->{name};
567              
568 200572 100       378821 if($fieldDescriptor->{isUnkownField}) {
569 937         2057 next;
570             }
571              
572 199635         258114 my $postProcessedValue = undef;
573 199635         275810 my $baseType = $localMessageField->{baseType};
574              
575 199635         380466 my $isValid = $self->_check_for_valid_value($baseType, $rawValue);
576              
577 199635 100       370806 if($isValid) {
578 182127         311475 $postProcessedValue = $self->postProcessRawValue($rawValue, $fieldDescriptor);
579             }
580              
581 199635         1126060 $result{$fieldName} = {
582             value => $postProcessedValue,
583             rawValue => $rawValue,
584             fieldDescriptor => $fieldDescriptor,
585             isInvalid => !$isValid,
586             };
587             }
588              
589             return {
590             messageType => $localMessage->{globalMessage}->{name},
591 14611         99188 fields => \%result
592             };
593             }
594              
595             sub _check_for_valid_value {
596 199635     199635   276942 my $self = shift;
597 199635         262166 my $baseType = shift;
598 199635         255374 my $rawValue = shift;
599              
600             # All strings are good.
601 199635 100       387466 if($baseType->{name} eq "string") {
602 7         13 return 1;
603             }
604              
605 199628         305121 my $isValid = $baseType->{invalid} != $rawValue;
606 199628         370243 return $isValid;
607             }
608              
609             sub postProcessRawValue {
610 182133     182133 0 284400 my $self = shift;
611 182133         240661 my $rawValue = shift;
612 182133         233648 my $fieldDescriptor = shift;
613              
614 182133 100       354435 if(defined $fieldDescriptor->{scale}) {
615 77773         139887 $rawValue /= $fieldDescriptor->{scale};
616             }
617              
618 182133 100       348073 if(defined $fieldDescriptor->{offset}) {
619 13432         23558 $rawValue -= $fieldDescriptor->{offset};
620             }
621              
622 182133 100 100     553166 if(defined $fieldDescriptor->{unit} && $fieldDescriptor->{unit} eq "semicircles") {
623 1394         2541 state $semicirclesToDegreesConversionRate = 180 / 2**31;
624 1394         2013 $rawValue *= $semicirclesToDegreesConversionRate;
625             }
626              
627 182133 100 100     542477 if(defined $fieldDescriptor->{type} && $fieldDescriptor->{type} eq "date_time") {
628 14709         20668 state $fitEpocheOffset = 631065600;
629 14709         30980 $rawValue += $fitEpocheOffset;
630             }
631              
632 182133         320958 return $rawValue;
633             }
634              
635             sub _get_base_type {
636 5341     5341   6865 my $self = shift;
637 5341         7120 my $index = shift;
638              
639             # See "Table 7. FIT Base Types and Invalid Values" at https://developer.garmin.com/fit/protocol/
640 5341         81469 my $types = [
641             {
642             name => "enum",
643             size => 1,
644             invalid => 0xff,
645             packTemplate => "c",
646             },
647             {
648             name => "sint8",
649             size => 1,
650             invalid => 0x7f,
651             packTemplate => "c"
652             },
653             {
654             name => "uint8",
655             size => 1,
656             invalid => 0xff,
657             packTemplate => "C",
658              
659             },
660             {
661             name => "sint16",
662             size => 2,
663             invalid => 0x7fff,
664             packTemplate => "s",
665             },
666             {
667             name => "uint16",
668             size => 2,
669             invalid => 0xffff,
670             packTemplate => "S"
671             },
672             {
673             name => "sint32",
674             size => 4,
675             invalid => 0x7fffffff,
676             packTemplate => "l"
677             },
678             {
679             name => "uint32",
680             size => 4,
681             invalid => 0xffffffff,
682             packTemplate => "L",
683             },
684             {
685             name => "string",
686             size => 1,
687             invalid => 0x00,
688             packTemplate => "Z"
689             },
690             {
691             name => "float32",
692             size => 4,
693             invalid => 0xffffffff,
694             packTemplate => "f"
695             },
696             {
697             name => "float64",
698             size => 8,
699             invalid => Math::BigInt->new("0xffffffffffffffff"),
700             packTemplate => "d",
701             },
702             {
703             name => "uint8z",
704             size => 1,
705             invalid => 0x00,
706             # TODO this should be a capital C since it is unsigned!
707             packTemplate => "c"
708             },
709             {
710             name => "uint16z",
711             size => 2,
712             invalid => 0x0000,
713             packTemplate => "S",
714             },
715             {
716             name => "uint32z",
717             size => 4,
718             invalid => 0x00000000,
719             packTemplate => "L"
720             },
721             {
722             name => "byte",
723             size => 1,
724             invalid => 0xFF,
725             packTemplate => "C",
726             },
727             {
728             name => "sint64",
729             size => 8,
730             invalid => Math::BigInt->new("0x7fffffffffffffff"),
731             packTemplate => "q",
732             },
733             {
734             name => "uint64",
735             size => 8,
736             invalid => Math::BigInt->new("0xffffffffffffffff"),
737             packTemplate => "Q",
738             },
739             {
740             name => "uint64z",
741             size => 8,
742             invalid => 0x0000000000000000,
743             packTemplate => "Q",
744             }
745             ];
746              
747 5341 50       6512056 if($index >= @{$types}) {
  5341         16738  
748 0         0 die "Invalid index=$index for BaseTypeLookup!";
749             }
750              
751 5341         74829 return $types->[$index];
752             }
753              
754       0     sub _parse_crc {
755             # TODO implement this one...some time :D
756             }
757              
758             sub _debug {
759 36717     36717   54759 my $self = shift;
760 36717 50       91562 if($self->{_DEBUG}) {
761 0         0 print "[FIT.pm DEBUG] ", @_;
762 0         0 print "\n";
763             }
764             }
765              
766             sub _readBytes {
767 35521     35521   48905 my $self = shift;
768 35521         51319 my $num = shift;
769              
770 35521         55499 $self->{totalBytesRead} += $num;
771 35521         46132 my $buffer;
772 35521         167761 my $bytesRead = read($self->{fh}, $buffer, $num);
773             # TODO error handling based on bytesRead
774 35521         90573 return $buffer;
775             }
776              
777              
778              
779              
780              
781             1;
782              
783              
784             __END__