55// Transfer (FIT) Protocol License.
66/////////////////////////////////////////////////////////////////////////////////////////////
77// ****WARNING**** This file is auto-generated! Do NOT edit this file.
8- // Profile Version = 21.141 .0Release
9- // Tag = production/release/21.141 .0-0-g2aa27e1
8+ // Profile Version = 21.158 .0Release
9+ // Tag = production/release/21.158 .0-0-gc9428aa
1010/////////////////////////////////////////////////////////////////////////////////////////////
1111
1212
@@ -25,8 +25,17 @@ const MESG_DEFINITION_MASK = 0x40;
2525const DEV_DATA_MASK = 0x20 ;
2626const MESG_HEADER_MASK = 0x00 ;
2727const LOCAL_MESG_NUM_MASK = 0x0F ;
28+
29+ const HEADER_WITH_CRC_SIZE = 14 ;
30+ const HEADER_WITHOUT_CRC_SIZE = 12 ;
2831const CRC_SIZE = 2 ;
2932
33+ const DecodeMode = Object . freeze ( {
34+ NORMAL : "normal" ,
35+ SKIP_HEADER : "skipHeader" ,
36+ DATA_ONLY : "dataOnly"
37+ } ) ;
38+
3039class Decoder {
3140 #localMessageDefinitions = [ ] ;
3241 #developerDataDefinitions = { } ;
@@ -36,6 +45,8 @@ class Decoder {
3645 #fieldsWithSubFields = [ ] ;
3746 #fieldsToExpand = [ ] ;
3847
48+ #decodeMode = DecodeMode . NORMAL ;
49+
3950 #mesgListener = null ;
4051 #optExpandSubFields = true ;
4152 #optExpandComponents = true ;
@@ -67,15 +78,15 @@ class Decoder {
6778 static isFIT ( stream ) {
6879 try {
6980 const fileHeaderSize = stream . peekByte ( ) ;
70- if ( [ 14 , 12 ] . includes ( fileHeaderSize ) != true ) {
81+ if ( [ HEADER_WITH_CRC_SIZE , HEADER_WITHOUT_CRC_SIZE ] . includes ( fileHeaderSize ) != true ) {
7182 return false ;
7283 }
7384
7485 if ( stream . length < fileHeaderSize + CRC_SIZE ) {
7586 return false ;
7687 }
7788
78- const fileHeader = Decoder . #readFileHeader( stream , true ) ;
89+ const fileHeader = Decoder . #readFileHeader( stream , { resetPosition : true , } ) ;
7990 if ( fileHeader . dataType !== ".FIT" ) {
8091 return false ;
8192 }
@@ -105,15 +116,15 @@ class Decoder {
105116 return false ;
106117 }
107118
108- const fileHeader = Decoder . #readFileHeader( this . #stream, true ) ;
119+ const fileHeader = Decoder . #readFileHeader( this . #stream, { resetPosition : true , } ) ;
109120
110121 if ( this . #stream. length < fileHeader . headerSize + fileHeader . dataSize + CRC_SIZE ) {
111122 return false ;
112123 }
113124
114125 const buf = new Uint8Array ( this . #stream. slice ( 0 , this . #stream. length ) )
115126
116- if ( fileHeader . headerSize === 14 && fileHeader . headerCRC !== 0x0000
127+ if ( fileHeader . headerSize === HEADER_WITH_CRC_SIZE && fileHeader . headerCRC !== 0x0000
117128 && fileHeader . headerCRC != CrcCalculator . calculateCRC ( buf , 0 , 12 ) ) {
118129 return false ;
119130 }
@@ -150,6 +161,8 @@ class Decoder {
150161 * @param {boolean } [options.convertDateTimesToDates=true] - (optional, default true)
151162 * @param {Boolean } [options.includeUnknownData=false] - (optional, default false)
152163 * @param {boolean } [options.mergeHeartRates=true] - (optional, default false)
164+ * @param {boolean } [options.skipHeader=false] - (optional, default false)
165+ * @param {boolean } [options.dataOnly=false] - (optional, default false)
153166 * @return {Object } result - {messages:Array, errors:Array}
154167 */
155168 read ( {
@@ -160,7 +173,9 @@ class Decoder {
160173 convertTypesToStrings = true ,
161174 convertDateTimesToDates = true ,
162175 includeUnknownData = false ,
163- mergeHeartRates = true } = { } ) {
176+ mergeHeartRates = true ,
177+ skipHeader = false ,
178+ dataOnly = false , } = { } ) {
164179
165180 this . #mesgListener = mesgListener ;
166181 this . #optExpandSubFields = expandSubFields
@@ -182,7 +197,11 @@ class Decoder {
182197 this . #throwError( "mergeHeartRates requires applyScaleAndOffset and expandComponents to be enabled" ) ;
183198 }
184199
185- this . #stream. reset ( ) ;
200+ if ( dataOnly && skipHeader ) {
201+ this . #throwError( "dataOnly and skipHeader cannot both be enabled" )
202+ }
203+
204+ this . #decodeMode = skipHeader ? DecodeMode . SKIP_HEADER : dataOnly ? DecodeMode . DATA_ONLY : DecodeMode . NORMAL ;
186205
187206 while ( this . #stream. position < this . #stream. length ) {
188207 this . #decodeNextFile( ) ;
@@ -203,23 +222,23 @@ class Decoder {
203222 #decodeNextFile( ) {
204223 const position = this . #stream. position ;
205224
206- if ( ! this . isFIT ( ) ) {
225+ if ( this . #decodeMode === DecodeMode . NORMAL && ! this . isFIT ( ) ) {
207226 this . #throwError( "input is not a FIT file" ) ;
208227 }
209228
210229 this . #stream. crcCalculator = new CrcCalculator ( ) ;
211230
212- const fileHeader = Decoder . #readFileHeader( this . #stream) ;
231+ const { headerSize , dataSize } = Decoder . #readFileHeader( this . #stream, { decodeMode : this . #decodeMode } ) ;
213232
214233 // Read data messages and definitions
215- while ( this . #stream. position < ( position + fileHeader . headerSize + fileHeader . dataSize ) ) {
234+ while ( this . #stream. position < ( position + headerSize + dataSize ) ) {
216235 this . #decodeNextRecord( ) ;
217236 }
218237
219238 // Check the CRC
220239 const calculatedCrc = this . #stream. crcCalculator . crc ;
221240 const crc = this . #stream. readUInt16 ( ) ;
222- if ( crc !== calculatedCrc ) {
241+ if ( this . #decodeMode === DecodeMode . NORMAL && crc !== calculatedCrc ) {
223242 this . #throwError( "CRC error" ) ;
224243 }
225244 }
@@ -341,7 +360,7 @@ class Decoder {
341360 }
342361
343362 if ( field ?. isAccumulated ) {
344- this . #accumulator . add ( mesgNum , fieldDefinition . fieldDefinitionNumber , rawFieldValue ) ;
363+ this . #setAccumulatedField ( messageDefinition , message , field , rawFieldValue ) ;
345364 }
346365 }
347366 } ) ;
@@ -530,7 +549,7 @@ class Decoder {
530549 while ( this . #fieldsToExpand. length > 0 ) {
531550 const name = this . #fieldsToExpand. shift ( ) ;
532551
533- const { rawFieldValue, fieldDefinitionNumber, isSubField } = message [ name ] ;
552+ const { rawFieldValue, fieldDefinitionNumber, isSubField } = message [ name ] ?? mesg [ name ] ;
534553 let field = Profile . messages [ mesgNum ] . fields [ fieldDefinitionNumber ] ;
535554 field = isSubField ? this . #lookupSubfield( field , name ) : field ;
536555 const baseType = FIT . FieldTypeToBaseType [ field . type ] ;
@@ -546,6 +565,10 @@ class Decoder {
546565 const bitStream = new BitStream ( rawFieldValue , baseType ) ;
547566
548567 for ( let j = 0 ; j < field . components . length ; j ++ ) {
568+ if ( bitStream . bitsAvailable < field . bits [ j ] ) {
569+ break ;
570+ }
571+
549572 const targetField = fields [ field . components [ j ] ] ;
550573 if ( mesg [ targetField . name ] == null ) {
551574 const baseType = FIT . FieldTypeToBaseType [ targetField . type ] ;
@@ -560,22 +583,22 @@ class Decoder {
560583 } ;
561584 }
562585
563- if ( bitStream . bitsAvailable < field . bits [ j ] ) {
564- break ;
565- }
566-
567586 let value = bitStream . readBits ( field . bits [ j ] ) ;
568587
569- value = this . #accumulator. accumulate ( mesgNum , targetField . num , value , field . bits [ j ] ) ?? value ;
588+ if ( targetField . isAccumulated ) {
589+ value = this . #accumulator. accumulate ( mesgNum , targetField . num , value , field . bits [ j ] ) ;
590+ }
591+
592+ // Undo component scale and offset before applying the destination field's scale and offset
593+ value = ( value / field . scale [ j ] - field . offset [ j ] ) ;
570594
571- mesg [ targetField . name ] . rawFieldValue . push ( value ) ;
595+ const rawValue = ( value + targetField . offset ) * targetField . scale ;
596+ mesg [ targetField . name ] . rawFieldValue . push ( rawValue ) ;
572597
573- if ( value === mesg [ targetField . name ] . invalidValue ) {
598+ if ( rawValue === mesg [ targetField . name ] . invalidValue ) {
574599 mesg [ targetField . name ] . fieldValue . push ( null ) ;
575600 }
576601 else {
577- value = value / field . scale [ j ] - field . offset [ j ] ;
578-
579602 if ( this . #optConvertTypesToStrings) {
580603 value = this . #convertTypeToString( mesg , targetField , value ) ;
581604 }
@@ -681,6 +704,26 @@ class Decoder {
681704 }
682705 }
683706
707+ #setAccumulatedField( messageDefinition , message , field , rawFieldValue ) {
708+ const rawFieldValues = Array . isArray ( rawFieldValue ) ? rawFieldValue : [ rawFieldValue ] ;
709+
710+ rawFieldValues . forEach ( ( value ) => {
711+ Object . values ( message ) . forEach ( ( containingField ) => {
712+ let components = messageDefinition . fields [ containingField . fieldDefinitionNumber ] . components ?? [ ]
713+
714+ components . forEach ( ( componentFieldNum , i ) => {
715+ const targetField = messageDefinition . fields [ componentFieldNum ] ;
716+
717+ if ( targetField ?. num == field . num && targetField ?. isAccumulated ) {
718+ value = ( ( ( value / field . scale ) - field . offset ) + containingField . offset [ i ] ) * containingField . scale [ i ] ;
719+ }
720+ } ) ;
721+ } ) ;
722+
723+ this . #accumulator. createAccumulatedField ( messageDefinition . num , field . num , value ) ;
724+ } ) ;
725+ }
726+
684727 #convertTypeToString( messageDefinition , field , rawFieldValue ) {
685728 if ( [ Profile . MesgNum . DEVELOPER_DATA_ID , Profile . MesgNum . FIELD_DESCRIPTION ] . includes ( messageDefinition . globalMessageNumber ) ) {
686729 return rawFieldValue ;
@@ -711,9 +754,22 @@ class Decoder {
711754 return subField != null ? subField : { } ;
712755 }
713756
714- static #readFileHeader( stream , resetPosition = false ) {
757+ static #readFileHeader( stream , { resetPosition = false , decodeMode = DecodeMode . NORMAL } ) {
715758 const position = stream . position ;
716759
760+ if ( decodeMode !== DecodeMode . NORMAL ) {
761+ if ( decodeMode === DecodeMode . SKIP_HEADER ) {
762+ stream . seek ( HEADER_WITH_CRC_SIZE ) ;
763+ }
764+
765+ const headerSize = decodeMode === DecodeMode . SKIP_HEADER ? HEADER_WITH_CRC_SIZE : 0 ;
766+
767+ return {
768+ headerSize,
769+ dataSize : stream . length - headerSize - CRC_SIZE ,
770+ } ;
771+ }
772+
717773 const fileHeader = {
718774 headerSize : stream . readByte ( ) ,
719775 protocolVersion : stream . readByte ( ) ,
0 commit comments