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