File Coverage

blib/lib/Parser/FIT.pm
Criterion Covered Total %
statement 205 245 83.6
branch 49 76 64.4
condition 5 9 55.5
subroutine 26 30 86.6
pod 4 11 36.3
total 289 371 77.9


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__