Skip to content

Commit

Permalink
Merge pull request #16 from garmin/production/akw/21.158.0_54
Browse files Browse the repository at this point in the history
Garmin FIT SDK 21.158.0
  • Loading branch information
gracepipho authored Dec 17, 2024
2 parents 16c3eff + 65e44ec commit 876c2cc
Show file tree
Hide file tree
Showing 15 changed files with 387 additions and 76 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@garmin/fitsdk",
"version": "21.141.0",
"version": "21.158.0",
"description": "FIT JavaScript SDK",
"main": "src/index.js",
"type": "module",
Expand Down
20 changes: 15 additions & 5 deletions src/accumulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Transfer (FIT) Protocol License.
/////////////////////////////////////////////////////////////////////////////////////////////
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
// Profile Version = 21.141.0Release
// Tag = production/release/21.141.0-0-g2aa27e1
// Profile Version = 21.158.0Release
// Tag = production/release/21.158.0-0-gc9428aa
/////////////////////////////////////////////////////////////////////////////////////////////


Expand All @@ -32,16 +32,26 @@ class AccumulatedField {
class Accumulator {
#messages = {};

add(mesgNum, fieldNum, value) {
createAccumulatedField(mesgNum, fieldNum, value) {
const accumualtedField = new AccumulatedField(value);

if (this.#messages[mesgNum] == null) {
this.#messages[mesgNum] = {};
}

this.#messages[mesgNum][fieldNum] = new AccumulatedField(value);
this.#messages[mesgNum][fieldNum] = accumualtedField;

return accumualtedField;
}

accumulate(mesgNum, fieldNum, value, bits) {
return this.#messages[mesgNum]?.[fieldNum]?.accumulate(value, bits) ?? value;
let accumualtedField = this.#messages[mesgNum]?.[fieldNum];

if(accumualtedField == null) {
accumualtedField = this.createAccumulatedField(mesgNum, fieldNum, value);
}

return accumualtedField.accumulate(value, bits);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/bit-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Transfer (FIT) Protocol License.
/////////////////////////////////////////////////////////////////////////////////////////////
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
// Profile Version = 21.141.0Release
// Tag = production/release/21.141.0-0-g2aa27e1
// Profile Version = 21.158.0Release
// Tag = production/release/21.158.0-0-gc9428aa
/////////////////////////////////////////////////////////////////////////////////////////////


Expand Down
4 changes: 2 additions & 2 deletions src/crc-calculator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Transfer (FIT) Protocol License.
/////////////////////////////////////////////////////////////////////////////////////////////
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
// Profile Version = 21.141.0Release
// Tag = production/release/21.141.0-0-g2aa27e1
// Profile Version = 21.158.0Release
// Tag = production/release/21.158.0-0-gc9428aa
/////////////////////////////////////////////////////////////////////////////////////////////


Expand Down
104 changes: 80 additions & 24 deletions src/decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Transfer (FIT) Protocol License.
/////////////////////////////////////////////////////////////////////////////////////////////
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
// Profile Version = 21.141.0Release
// Tag = production/release/21.141.0-0-g2aa27e1
// Profile Version = 21.158.0Release
// Tag = production/release/21.158.0-0-gc9428aa
/////////////////////////////////////////////////////////////////////////////////////////////


Expand All @@ -25,8 +25,17 @@ const MESG_DEFINITION_MASK = 0x40;
const DEV_DATA_MASK = 0x20;
const MESG_HEADER_MASK = 0x00;
const LOCAL_MESG_NUM_MASK = 0x0F;

const HEADER_WITH_CRC_SIZE = 14;
const HEADER_WITHOUT_CRC_SIZE = 12;
const CRC_SIZE = 2;

const DecodeMode = Object.freeze({
NORMAL: "normal",
SKIP_HEADER: "skipHeader",
DATA_ONLY: "dataOnly"
});

class Decoder {
#localMessageDefinitions = [];
#developerDataDefinitions = {};
Expand All @@ -36,6 +45,8 @@ class Decoder {
#fieldsWithSubFields = [];
#fieldsToExpand = [];

#decodeMode = DecodeMode.NORMAL;

#mesgListener = null;
#optExpandSubFields = true;
#optExpandComponents = true;
Expand Down Expand Up @@ -67,15 +78,15 @@ class Decoder {
static isFIT(stream) {
try {
const fileHeaderSize = stream.peekByte();
if ([14, 12].includes(fileHeaderSize) != true) {
if ([HEADER_WITH_CRC_SIZE, HEADER_WITHOUT_CRC_SIZE].includes(fileHeaderSize) != true) {
return false;
}

if (stream.length < fileHeaderSize + CRC_SIZE) {
return false;
}

const fileHeader = Decoder.#readFileHeader(stream, true);
const fileHeader = Decoder.#readFileHeader(stream, { resetPosition: true, });
if (fileHeader.dataType !== ".FIT") {
return false;
}
Expand Down Expand Up @@ -105,15 +116,15 @@ class Decoder {
return false;
}

const fileHeader = Decoder.#readFileHeader(this.#stream, true);
const fileHeader = Decoder.#readFileHeader(this.#stream, { resetPosition: true, });

if (this.#stream.length < fileHeader.headerSize + fileHeader.dataSize + CRC_SIZE) {
return false;
}

const buf = new Uint8Array(this.#stream.slice(0, this.#stream.length))

if (fileHeader.headerSize === 14 && fileHeader.headerCRC !== 0x0000
if (fileHeader.headerSize === HEADER_WITH_CRC_SIZE && fileHeader.headerCRC !== 0x0000
&& fileHeader.headerCRC != CrcCalculator.calculateCRC(buf, 0, 12)) {
return false;
}
Expand Down Expand Up @@ -150,6 +161,8 @@ class Decoder {
* @param {boolean} [options.convertDateTimesToDates=true] - (optional, default true)
* @param {Boolean} [options.includeUnknownData=false] - (optional, default false)
* @param {boolean} [options.mergeHeartRates=true] - (optional, default false)
* @param {boolean} [options.skipHeader=false] - (optional, default false)
* @param {boolean} [options.dataOnly=false] - (optional, default false)
* @return {Object} result - {messages:Array, errors:Array}
*/
read({
Expand All @@ -160,7 +173,9 @@ class Decoder {
convertTypesToStrings = true,
convertDateTimesToDates = true,
includeUnknownData = false,
mergeHeartRates = true } = {}) {
mergeHeartRates = true,
skipHeader = false,
dataOnly = false,} = {}) {

this.#mesgListener = mesgListener;
this.#optExpandSubFields = expandSubFields
Expand All @@ -182,7 +197,11 @@ class Decoder {
this.#throwError("mergeHeartRates requires applyScaleAndOffset and expandComponents to be enabled");
}

this.#stream.reset();
if (dataOnly && skipHeader) {
this.#throwError("dataOnly and skipHeader cannot both be enabled")
}

this.#decodeMode = skipHeader ? DecodeMode.SKIP_HEADER : dataOnly ? DecodeMode.DATA_ONLY : DecodeMode.NORMAL;

while (this.#stream.position < this.#stream.length) {
this.#decodeNextFile();
Expand All @@ -203,23 +222,23 @@ class Decoder {
#decodeNextFile() {
const position = this.#stream.position;

if (!this.isFIT()) {
if (this.#decodeMode === DecodeMode.NORMAL && !this.isFIT()) {
this.#throwError("input is not a FIT file");
}

this.#stream.crcCalculator = new CrcCalculator();

const fileHeader = Decoder.#readFileHeader(this.#stream);
const { headerSize, dataSize } = Decoder.#readFileHeader(this.#stream, { decodeMode: this.#decodeMode });

// Read data messages and definitions
while (this.#stream.position < (position + fileHeader.headerSize + fileHeader.dataSize)) {
while (this.#stream.position < (position + headerSize + dataSize)) {
this.#decodeNextRecord();
}

// Check the CRC
const calculatedCrc = this.#stream.crcCalculator.crc;
const crc = this.#stream.readUInt16();
if (crc !== calculatedCrc) {
if (this.#decodeMode === DecodeMode.NORMAL && crc !== calculatedCrc) {
this.#throwError("CRC error");
}
}
Expand Down Expand Up @@ -341,7 +360,7 @@ class Decoder {
}

if (field?.isAccumulated) {
this.#accumulator.add(mesgNum, fieldDefinition.fieldDefinitionNumber, rawFieldValue);
this.#setAccumulatedField(messageDefinition, message, field, rawFieldValue);
}
}
});
Expand Down Expand Up @@ -530,7 +549,7 @@ class Decoder {
while (this.#fieldsToExpand.length > 0) {
const name = this.#fieldsToExpand.shift();

const { rawFieldValue, fieldDefinitionNumber, isSubField } = message[name];
const { rawFieldValue, fieldDefinitionNumber, isSubField } = message[name] ?? mesg[name];
let field = Profile.messages[mesgNum].fields[fieldDefinitionNumber];
field = isSubField ? this.#lookupSubfield(field, name) : field;
const baseType = FIT.FieldTypeToBaseType[field.type];
Expand All @@ -546,6 +565,10 @@ class Decoder {
const bitStream = new BitStream(rawFieldValue, baseType);

for (let j = 0; j < field.components.length; j++) {
if (bitStream.bitsAvailable < field.bits[j]) {
break;
}

const targetField = fields[field.components[j]];
if (mesg[targetField.name] == null) {
const baseType = FIT.FieldTypeToBaseType[targetField.type];
Expand All @@ -560,22 +583,22 @@ class Decoder {
};
}

if (bitStream.bitsAvailable < field.bits[j]) {
break;
}

let value = bitStream.readBits(field.bits[j]);

value = this.#accumulator.accumulate(mesgNum, targetField.num, value, field.bits[j]) ?? value;
if (targetField.isAccumulated) {
value = this.#accumulator.accumulate(mesgNum, targetField.num, value, field.bits[j]);
}

// Undo component scale and offset before applying the destination field's scale and offset
value = (value / field.scale[j] - field.offset[j]);

mesg[targetField.name].rawFieldValue.push(value);
const rawValue = (value + targetField.offset) * targetField.scale;
mesg[targetField.name].rawFieldValue.push(rawValue);

if (value === mesg[targetField.name].invalidValue) {
if (rawValue === mesg[targetField.name].invalidValue) {
mesg[targetField.name].fieldValue.push(null);
}
else {
value = value / field.scale[j] - field.offset[j];

if (this.#optConvertTypesToStrings) {
value = this.#convertTypeToString(mesg, targetField, value);
}
Expand Down Expand Up @@ -681,6 +704,26 @@ class Decoder {
}
}

#setAccumulatedField(messageDefinition, message, field, rawFieldValue) {
const rawFieldValues = Array.isArray(rawFieldValue) ? rawFieldValue : [rawFieldValue];

rawFieldValues.forEach((value) => {
Object.values(message).forEach((containingField) => {
let components = messageDefinition.fields[containingField.fieldDefinitionNumber].components ?? []

components.forEach((componentFieldNum, i) => {
const targetField = messageDefinition.fields[componentFieldNum];

if(targetField?.num == field.num && targetField?.isAccumulated) {
value = (((value / field.scale) - field.offset) + containingField.offset[i]) * containingField.scale[i];
}
});
});

this.#accumulator.createAccumulatedField(messageDefinition.num, field.num, value);
});
}

#convertTypeToString(messageDefinition, field, rawFieldValue) {
if ([Profile.MesgNum.DEVELOPER_DATA_ID, Profile.MesgNum.FIELD_DESCRIPTION].includes(messageDefinition.globalMessageNumber)) {
return rawFieldValue;
Expand Down Expand Up @@ -711,9 +754,22 @@ class Decoder {
return subField != null ? subField : {};
}

static #readFileHeader(stream, resetPosition = false) {
static #readFileHeader(stream, { resetPosition = false, decodeMode = DecodeMode.NORMAL }) {
const position = stream.position;

if(decodeMode !== DecodeMode.NORMAL) {
if(decodeMode === DecodeMode.SKIP_HEADER) {
stream.seek(HEADER_WITH_CRC_SIZE);
}

const headerSize = decodeMode === DecodeMode.SKIP_HEADER ? HEADER_WITH_CRC_SIZE : 0;

return {
headerSize,
dataSize: stream.length - headerSize - CRC_SIZE,
};
}

const fileHeader = {
headerSize: stream.readByte(),
protocolVersion: stream.readByte(),
Expand Down
4 changes: 2 additions & 2 deletions src/fit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Transfer (FIT) Protocol License.
/////////////////////////////////////////////////////////////////////////////////////////////
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
// Profile Version = 21.141.0Release
// Tag = production/release/21.141.0-0-g2aa27e1
// Profile Version = 21.158.0Release
// Tag = production/release/21.158.0-0-gc9428aa
/////////////////////////////////////////////////////////////////////////////////////////////


Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Transfer (FIT) Protocol License.
/////////////////////////////////////////////////////////////////////////////////////////////
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
// Profile Version = 21.141.0Release
// Tag = production/release/21.141.0-0-g2aa27e1
// Profile Version = 21.158.0Release
// Tag = production/release/21.158.0-0-gc9428aa
/////////////////////////////////////////////////////////////////////////////////////////////


Expand Down
Loading

0 comments on commit 876c2cc

Please sign in to comment.