From 925dc9ec136fdbe31c1cb4dd5510ba346007dca2 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Thu, 27 Mar 2025 08:49:45 -0500 Subject: [PATCH 1/4] Garmin FIT SDK 21.168.0 Change-Id: Ic4beda33cd58699e6ce8bec94daa814c2be2ec7f --- package.json | 2 +- src/accumulator.js | 4 +- src/bit-stream.js | 4 +- src/crc-calculator.js | 4 +- src/decoder.js | 15 +- src/encoder.js | 8 +- src/fit.js | 4 +- src/index.js | 4 +- src/mesg-definition.js | 5 +- src/output-stream.js | 4 +- src/profile.js | 753 ++++++++++++++++++++++++++++++++++- src/stream.js | 4 +- src/utils-hr-mesg.js | 4 +- src/utils-internal.js | 22 +- src/utils-memo-glob.js | 65 +++ src/utils.js | 4 +- test/data/test-data.js | 161 ++++++++ test/encoder.test.js | 80 +++- test/utils-memo-glob.test.js | 121 ++++++ 19 files changed, 1233 insertions(+), 35 deletions(-) create mode 100644 src/utils-memo-glob.js create mode 100644 test/utils-memo-glob.test.js diff --git a/package.json b/package.json index dda2061..f710d0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@garmin/fitsdk", - "version": "21.161.0", + "version": "21.168.0", "description": "FIT JavaScript SDK", "main": "src/index.js", "type": "module", diff --git a/src/accumulator.js b/src/accumulator.js index 036f8fb..a70dd9d 100644 --- a/src/accumulator.js +++ b/src/accumulator.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/bit-stream.js b/src/bit-stream.js index f6e7ef3..8003163 100644 --- a/src/bit-stream.js +++ b/src/bit-stream.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/crc-calculator.js b/src/crc-calculator.js index 16aedf7..5ade937 100644 --- a/src/crc-calculator.js +++ b/src/crc-calculator.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/decoder.js b/src/decoder.js index 39035bd..eeddf25 100644 --- a/src/decoder.js +++ b/src/decoder.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// @@ -15,6 +15,7 @@ import BitStream from "../src/bit-stream.js"; import CrcCalculator from "./crc-calculator.js"; import FIT from "./fit.js"; import HrMesgUtils from "./utils-hr-mesg.js"; +import MemoGlobUtils from "./utils-memo-glob.js"; import Profile from "./profile.js"; import Stream from "./stream.js"; import Utils from "./utils.js"; @@ -57,6 +58,7 @@ class Decoder { #optConvertDateTimesToDates = true; #optIncludeUnknownData = false; #optMergeHeartRates = true; + #optDecodeMemoGlobs = false; /** * Creates a FIT File Decoder @@ -179,7 +181,8 @@ class Decoder { * @param {Boolean} [options.convertTypesToStrings=true] - (optional, default true) * @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.mergeHeartRates=true] - (optional, default true) + * @param {boolean} [options.decodeMemoGlobs=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} @@ -195,6 +198,7 @@ class Decoder { convertDateTimesToDates = true, includeUnknownData = false, mergeHeartRates = true, + decodeMemoGlobs = false, skipHeader = false, dataOnly = false,} = {}) { @@ -208,6 +212,7 @@ class Decoder { this.#optConvertDateTimesToDates = convertDateTimesToDates; this.#optIncludeUnknownData = includeUnknownData; this.#optMergeHeartRates = mergeHeartRates; + this.#optDecodeMemoGlobs = decodeMemoGlobs; this.#localMessageDefinitions = []; this.#developerDataDefinitions = {}; @@ -233,6 +238,10 @@ class Decoder { if (this.#optMergeHeartRates) { HrMesgUtils.mergeHeartRates(this.#messages.hrMesgs, this.#messages.recordMesgs); } + + if (this.#optDecodeMemoGlobs) { + MemoGlobUtils.decodeMemoGlobs(this.#messages); + } } catch (error) { errors.push(error); diff --git a/src/encoder.js b/src/encoder.js index 53ca7b1..f540d8d 100644 --- a/src/encoder.js +++ b/src/encoder.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// @@ -212,8 +212,8 @@ class Encoder { throw new Error(); } - const scale = Array.isArray(fieldDefinition.scale) ? fieldDefinition.scale[0] : fieldDefinition.scale; - const offset = Array.isArray(fieldDefinition.offset) ? fieldDefinition.offset[0] : fieldDefinition.offset; + const scale = fieldDefinition.components.length > 1 ? 1 : fieldDefinition.scale; + const offset = fieldDefinition.components.length > 1 ? 0 : fieldDefinition.offset; return (value + offset) * scale; } diff --git a/src/fit.js b/src/fit.js index 637efef..fe303de 100644 --- a/src/fit.js +++ b/src/fit.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/index.js b/src/index.js index 1b8e94b..1277b11 100644 --- a/src/index.js +++ b/src/index.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/mesg-definition.js b/src/mesg-definition.js index 2c78186..4652d81 100644 --- a/src/mesg-definition.js +++ b/src/mesg-definition.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// @@ -64,6 +64,7 @@ class MesgDefinition { type: fieldProfile[1].type, scale: fieldProfile[1].scale, offset: fieldProfile[1].offset, + components: fieldProfile[1].components, }); }); diff --git a/src/output-stream.js b/src/output-stream.js index 9c8767f..60a54ef 100644 --- a/src/output-stream.js +++ b/src/output-stream.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/profile.js b/src/profile.js index 275b82b..babaa72 100644 --- a/src/profile.js +++ b/src/profile.js @@ -5,15 +5,15 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// const Profile = { version: { major: 21, - minor: 161, + minor: 168, patch: 0, type: "Release" }, @@ -3308,6 +3308,73 @@ const Profile = { subFields: [] }, }, + }, + 13: { + num: 13, + name: "trainingSettings", + messagesKey: "trainingSettingsMesgs", + fields: { + 31: { + num: 31, + name: "targetDistance", + type: "uint32", + baseType: "uint32", + array: "false", + scale: 100, + offset: 0, + units: "m", + bits: [], + components: [], + isAccumulated: false, + hasComponents: false, + subFields: [] + }, + 32: { + num: 32, + name: "targetSpeed", + type: "uint16", + baseType: "uint16", + array: "false", + scale: 1000, + offset: 0, + units: "m/s", + bits: [], + components: [], + isAccumulated: false, + hasComponents: false, + subFields: [] + }, + 33: { + num: 33, + name: "targetTime", + type: "uint32", + baseType: "uint32", + array: "false", + scale: 1, + offset: 0, + units: "s", + bits: [], + components: [], + isAccumulated: false, + hasComponents: false, + subFields: [] + }, + 153: { + num: 153, // A more precise target speed field + name: "preciseTargetSpeed", + type: "uint32", + baseType: "uint32", + array: "false", + scale: 1000000, + offset: 0, + units: "m/s", + bits: [], + components: [], + isAccumulated: false, + hasComponents: false, + subFields: [] + }, + }, }, 258: { num: 258, @@ -23138,6 +23205,7 @@ types: { 9: "powerZone", 10: "metZone", 12: "sport", + 13: "trainingSettings", 15: "goal", 18: "session", 19: "lap", @@ -24220,6 +24288,7 @@ types: { 150: "myzone", 151: "abawo", 152: "bafang", + 153: "luhongTechnology", 255: "development", 257: "healthandlife", 258: "lezyne", @@ -24298,6 +24367,8 @@ types: { 331: "mywhoosh", 332: "ravemen", 333: "tektroRacingProducts", + 334: "daradInnovationCorporation", + 335: "cycloptim", 5759: "actigraphcorp", }, garminProduct: { @@ -24540,6 +24611,8 @@ types: { 3028: "gpsmap66", 3049: "approachS10", 3066: "vivoactive3mL", + 3076: "fr245", + 3077: "fr245Music", 3085: "approachG80", 3092: "edge130Asia", 3095: "edge1030Bontrager", @@ -24570,6 +24643,7 @@ types: { 3250: "marqExpedition", 3251: "marqAthlete", 3258: "descentMk2", + 3282: "fr45", 3284: "gpsmap66i", 3287: "fenix6SSport", 3288: "fenix6S", @@ -24619,6 +24693,8 @@ types: { 3570: "edge1030Plus", 3578: "rally200", // Rally 100/200 Power Meter Series 3589: "fr745", + 3596: "venusqMusic", + 3599: "venusqMusicV2", 3600: "venusq", 3615: "lily", 3624: "marqAdventurer", @@ -24643,9 +24719,10 @@ types: { 3843: "edge1040", 3850: "marqGolferAsia", 3851: "venu2Plus", - 3865: "gnss", + 3865: "gnss", // Airoha AG3335M Family 3869: "fr55", 3888: "instinct2", + 3889: "instinct2s", 3905: "fenix7s", 3906: "fenix7", 3907: "fenix7x", @@ -24736,8 +24813,13 @@ types: { 4585: "instinct3Solar45mm", 4586: "instinct3Amoled45mm", 4587: "instinct3Amoled50mm", + 4588: "descentG2", + 4647: "approachS44", + 4656: "approachS50", 4666: "fenixE", 4759: "instinct3Solar50mm", + 4775: "tactix8Amoled", + 4776: "tactix8Solar", 10007: "sdm4", // SDM4 footpod 10014: "edgeRemote", 20533: "tacxTrainingAppWin", @@ -25658,6 +25740,25 @@ types: { 30: "tricepsExtension", 31: "warmUp", 32: "run", + 33: "bike", + 34: "cardioSensors", // Exercises within workouts that use GPS/sensors rather than rep counting + 35: "move", + 36: "pose", + 37: "bandedExercises", + 38: "battleRope", + 39: "elliptical", + 40: "floorClimb", + 41: "indoorBike", + 42: "indoorRow", + 43: "ladder", + 44: "sandbag", + 45: "sled", + 46: "sledgeHammer", + 47: "stairStepper", + 49: "suspension", + 50: "tire", + 52: "runIndoor", + 53: "bikeOutdoor", 65534: "unknown", }, benchPressExerciseName: { @@ -25735,6 +25836,27 @@ types: { 19: "weightedSquatJacks", 20: "tripleUnder", 21: "weightedTripleUnder", + 22: "elliptical", + 23: "spinning", + 24: "polePaddleForwardWheelchair", + 25: "polePaddleBackwardWheelchair", + 26: "poleHandcycleForwardWheelchair", + 27: "poleHandcycleBackwardWheelchair", + 28: "poleRainbowWheelchair", + 29: "doublePunchForwardWheelchair", + 30: "doublePunchDownWheelchair", + 31: "doublePunchSidewaysWheelchair", + 32: "doublePunchUpWheelchair", + 33: "sitSkiWheelchair", + 34: "sittingJacksWheelchair", + 35: "punchForwardWheelchair", + 36: "punchDownWheelchair", + 37: "punchSidewaysWheelchair", + 38: "punchUpWheelchair", + 39: "punchBagWheelchair", + 40: "poleDdFfUuWheelchair", + 41: "butterflyArmsWheelchair", + 42: "punch", }, carryExerciseName: { 0: "barHolds", @@ -25742,6 +25864,10 @@ types: { 2: "farmersWalkOnToes", 3: "hexDumbbellHold", 4: "overheadCarry", + 5: "dumbbellWaiterCarry", + 6: "farmersCarryWalkLunge", + 7: "farmersCarry", + 8: "farmersCarryOnToes", }, chopExerciseName: { 0: "cablePullThrough", @@ -25842,6 +25968,27 @@ types: { 70: "swimming", 71: "teaser", 72: "theHundred", + 73: "bicepCurlWithLegExtensionWithWeights", + 75: "hangingLSit", + 77: "lowerLiftWithWeights", + 79: "ringLSit", + 80: "rowing1WithWeights", + 81: "rowing2WithWeights", + 82: "scissorsWithWeights", + 83: "singleLegStretchWithWeights", + 84: "toesToElbows", + 85: "weightedCrissCross", + 86: "weightedDoubleLegStretch", + 87: "weightedTheHundred", + 88: "lSit", + 89: "turkishGetUp", + 90: "weightedRingLSit", + 91: "weightedHangingLSit", + 92: "weightedLSit", + 93: "sideBendLowWheelchair", + 94: "sideBendMidWheelchair", + 95: "sideBendHighWheelchair", + 96: "seatedSideBend", }, crunchExerciseName: { 0: "bicycleCrunch", @@ -25929,6 +26076,7 @@ types: { 82: "weightedToesToBar", 83: "crunch", 84: "straightLegCrunchWithBall", + 86: "legClimbCrunch", }, curlExerciseName: { 0: "alternatingDumbbellBicepsCurl", @@ -25975,6 +26123,13 @@ types: { 41: "swissBallEzBarPreacherCurl", 42: "twistingStandingDumbbellBicepsCurl", 43: "wideGripEzBarBicepsCurl", + 44: "oneArmConcentrationCurl", + 45: "standingZottmanBicepsCurl", + 46: "dumbbellBicepsCurl", + 47: "dragCurlWheelchair", + 48: "dumbbellBicepsCurlWheelchair", + 49: "bottleCurl", + 50: "seatedBottleCurl", }, deadliftExerciseName: { 0: "barbellDeadlift", @@ -25996,6 +26151,11 @@ types: { 16: "sumoDeadliftHighPull", 17: "trapBarDeadlift", 18: "wideGripBarbellDeadlift", + 20: "kettlebellDeadlift", + 21: "kettlebellSumoDeadlift", + 23: "romanianDeadlift", + 24: "singleLegRomanianDeadliftCircuit", + 25: "straightLegDeadlift", }, flyeExerciseName: { 0: "cableCrossover", @@ -26008,6 +26168,9 @@ types: { 7: "swissBallDumbbellFlye", 8: "armRotations", 9: "hugATree", + 10: "faceDownInclineReverseFlye", + 11: "inclineReverseFlye", + 12: "rearDeltFlyWheelchair", }, hipRaiseExerciseName: { 0: "barbellHipThrustOnFloor", @@ -26096,11 +26259,13 @@ types: { 31: "weightedStandingRearLegRaise", 32: "supineHipInternalRotation", 33: "weightedSupineHipInternalRotation", + 34: "lyingAbductionStretch", }, hipSwingExerciseName: { 0: "singleArmKettlebellSwing", 1: "singleArmDumbbellSwing", 2: "stepOutSwing", + 3: "oneArmSwing", }, hyperextensionExerciseName: { 0: "backExtensionWithOppositeArmAndLegReach", @@ -26179,6 +26344,13 @@ types: { 31: "weightedWallSlide", 32: "armCircles", 33: "shavingTheHead", + 34: "dumbbellLateralRaise", + 36: "ringDipKipping", + 37: "wallWalk", + 38: "dumbbellFrontRaiseWheelchair", + 39: "dumbbellLateralRaiseWheelchair", + 40: "poleDoubleArmOverheadAndForwardWheelchair", + 41: "poleStraightArmOverheadWheelchair", }, legCurlExerciseName: { 0: "legCurl", @@ -26193,6 +26365,8 @@ types: { 9: "staggeredStanceGoodMorning", 10: "swissBallHipRaiseAndLegCurl", 11: "zercherGoodMorning", + 12: "bandGoodMorning", + 13: "barGoodMorning", }, legRaiseExerciseName: { 0: "hangingKneeRaise", @@ -26300,6 +26474,16 @@ types: { 78: "walkingLunge", 79: "weightedWalkingLunge", 80: "wideGripOverheadBarbellSplitSquat", + 81: "alternatingDumbbellLunge", + 82: "dumbbellReverseLunge", + 83: "overheadDumbbellLunge", + 84: "scissorPowerSwitch", + 85: "dumbbellOverheadWalkingLunge", + 86: "curtsyLunge", + 87: "weightedCurtsyLunge", + 88: "weightedShiftingSideLunge", + 89: "weightedSideLungeAndPress", + 90: "weightedSideLungeJumpOff", }, olympicLiftExerciseName: { 0: "barbellHangPowerClean", @@ -26323,6 +26507,14 @@ types: { 18: "singleArmKettlebellSnatch", 19: "splitJerk", 20: "squatCleanAndJerk", + 21: "dumbbellHangSnatch", + 22: "dumbbellPowerCleanAndJerk", + 23: "dumbbellPowerCleanAndPushPress", + 24: "dumbbellPowerCleanAndStrictPress", + 25: "dumbbellSnatch", + 26: "medicineBallClean", + 27: "cleanAndPress", + 28: "snatch", }, plankExerciseName: { 0: "45DegreePlank", @@ -26460,6 +26652,7 @@ types: { 132: "plankWithArmVariations", 133: "plankWithLegLift", 134: "reversePlankWithLegPull", + 135: "ringPlankSprawls", }, plyoExerciseName: { 0: "alternatingJumpLunge", @@ -26495,6 +26688,11 @@ types: { 30: "weightedSquatJumpOntoBox", 31: "squatJumpsInAndOut", 32: "weightedSquatJumpsInAndOut", + 33: "boxJump", + 34: "boxJumpOvers", + 35: "boxJumpOversOverTheBox", + 36: "starJumpSquats", + 37: "jumpSquat", }, pullUpExerciseName: { 0: "bandedPullUps", @@ -26536,6 +26734,13 @@ types: { 36: "suspendedChinUp", 37: "weightedSuspendedChinUp", 38: "pullUp", + 39: "chinUp", + 40: "neutralGripChinUp", + 41: "weightedChinUp", + 42: "bandAssistedPullUp", + 43: "neutralGripPullUp", + 44: "weightedNeutralGripChinUp", + 45: "weightedNeutralGripPullUp", }, pushUpExerciseName: { 0: "chestPressWithBand", @@ -26617,6 +26822,18 @@ types: { 76: "weightedRingPushUp", 77: "pushUp", 78: "pilatesPushup", + 79: "dynamicPushUp", + 80: "kippingHandstandPushUp", + 81: "shoulderTappingPushUp", + 82: "bicepsPushUp", + 83: "hinduPushUp", + 84: "pikePushUp", + 85: "wideGripPushUp", + 86: "weightedBicepsPushUp", + 87: "weightedHinduPushUp", + 88: "weightedPikePushUp", + 89: "kippingParalletteHandstandPushUp", + 90: "wallPushUp", }, rowExerciseName: { 0: "barbellStraightLegDeadliftToRow", @@ -26653,6 +26870,25 @@ types: { 31: "underhandGripCableRow", 32: "vGripCableRow", 33: "wideGripSeatedCableRow", + 34: "alternatingDumbbellRow", + 35: "invertedRow", + 36: "row", + 37: "weightedRow", + 38: "indoorRow", + 39: "bandedFacePulls", + 40: "chestSupportedDumbbellRow", + 41: "declineRingRow", + 42: "elevatedRingRow", + 43: "rdlBentOverRowWithBarbellDumbbell", + 44: "ringRow", + 45: "barbellRow", + 46: "bentOverRowWithBarbell", + 47: "bentOverRowWithDumbell", + 48: "seatedUnderhandGripCableRow", + 49: "trxInvertedRow", + 50: "weightedInvertedRow", + 51: "weightedTrxInvertedRow", + 52: "dumbbellRowWheelchair", }, shoulderPressExerciseName: { 0: "alternatingDumbbellShoulderPress", @@ -26679,6 +26915,13 @@ types: { 21: "splitStanceHammerCurlToPress", 22: "swissBallDumbbellShoulderPress", 23: "weightPlateFrontRaise", + 24: "dumbbellShoulderPress", + 25: "militaryPress", + 27: "strictPress", + 28: "dumbbellFrontRaise", + 29: "dumbbellCurlToOverheadPressWheelchair", + 30: "arnoldPressWheelchair", + 31: "overheadDumbbellPressWheelchair", }, shoulderStabilityExerciseName: { 0: "90DegreeCableExternalRotation", @@ -26714,6 +26957,9 @@ types: { 30: "weightedSwissBallWRaise", 31: "swissBallYRaise", 32: "weightedSwissBallYRaise", + 33: "cableInternalRotation", + 34: "lyingInternalRotation", + 35: "seatedDumbbellInternalRotation", }, shrugExerciseName: { 0: "barbellJumpShrug", @@ -26733,6 +26979,14 @@ types: { 14: "serratusShrug", 15: "weightedSerratusShrug", 16: "wideGripJumpShrug", + 17: "wideGripBarbellShrug", + 18: "behindTheBackShrug", + 19: "dumbbellShrugWheelchair", + 20: "shrugWheelchair", + 21: "shrugArmDownWheelchair", + 22: "shrugArmMidWheelchair", + 23: "shrugArmUpWheelchair", + 24: "uprightRow", }, sitUpExerciseName: { 0: "alternatingSitUp", @@ -26773,6 +27027,9 @@ types: { 35: "xAbs", 36: "weightedXAbs", 37: "sitUp", + 38: "ghdSitUps", + 39: "sitUpTurkishGetUp", + 40: "russianTwistOnSwissBall", }, squatExerciseName: { 0: "legPress", @@ -26867,6 +27124,15 @@ types: { 89: "squatJumpsInNOut", 90: "pilatesPlieSquatsParallelTurnedOutFlatAndHeels", 91: "releveStraightLegAndKneeBentWithOneLegVariation", + 92: "alternatingBoxDumbbellStepUps", + 93: "dumbbellOverheadSquatSingleArm", + 94: "dumbbellSquatSnatch", + 95: "medicineBallSquat", + 97: "wallBallSquatAndPress", + 98: "squatAmericanSwing", + 100: "airSquat", + 101: "dumbbellThrusters", + 102: "overheadBarbellSquat", }, totalBodyExerciseName: { 0: "burpee", @@ -26882,6 +27148,218 @@ types: { 10: "weightedSquatPlankPushUp", 11: "standingTRotationBalance", 12: "weightedStandingTRotationBalance", + 13: "barbellBurpee", + 15: "burpeeBoxJumpOverYesLiterallyJumpingOverTheBox", + 16: "burpeeBoxJumpStepUpOver", + 17: "lateralBarbellBurpee", + 18: "totalBodyBurpeeOverBar", + 19: "burpeeBoxJumpOver", + 20: "burpeeWheelchair", + }, + moveExerciseName: { + 0: "archAndCurl", + 1: "armCirclesWithBallBandAndWeight", + 2: "armStretch", + 3: "backMassage", + 4: "bellyBreathing", + 5: "bridgeWithBall", + 6: "diamondLegCrunch", + 7: "diamondLegLift", + 8: "eightPointShoulderOpener", + 9: "footRolling", + 10: "footwork", + 11: "footworkOnDisc", + 12: "forwardFold", + 13: "frogWithBand", + 14: "halfRollUp", + 15: "hamstringCurl", + 16: "hamstringStretch", + 17: "hipStretch", + 18: "hugATreeWithBallBandAndWeight", + 19: "kneeCircles", + 20: "kneeFoldsOnDisc", + 21: "lateralFlexion", + 22: "legStretchWithBand", + 23: "legStretchWithLegCircles", + 24: "lowerLiftOnDisc", + 25: "lungeSquat", + 26: "lungesWithKneeLift", + 27: "mermaidStretch", + 28: "neutralPelvicPosition", + 29: "pelvicClocksOnDisc", + 30: "pilatesPlieSquatsParallelTurnedOutFlatAndHeelsWithChair", + 31: "piriformisStretch", + 32: "plankKneeCrosses", + 33: "plankKneePulls", + 34: "plankUpDowns", + 35: "prayerMudra", + 36: "psoasLungeStretch", + 37: "ribcageBreathing", + 38: "rollDown", + 39: "rollUpWithWeightAndBand", + 40: "saw", + 41: "scapularStabilization", + 42: "scissorsOnDisc", + 43: "seatedHipStretchup", + 44: "seatedTwist", + 45: "shavingTheHeadWithBallBandAndWeight", + 46: "spinalTwist", + 47: "spinalTwistStretch", + 48: "spineStretchForward", + 49: "squatOpenArmTwistPose", + 50: "squatsWithBall", + 51: "standAndHang", + 52: "standingSideStretch", + 53: "standingSingleLegForwardBendWithItBandOpener", + 54: "straightLegCrunchWithLegLift", + 55: "straightLegCrunchWithLegLiftWithBall", + 56: "straightLegCrunchWithLegsCrossed", + 57: "straightLegCrunchWithLegsCrossedWithBall", + 58: "straightLegDiagonalCrunch", + 59: "straightLegDiagonalCrunchWithBall", + 60: "tailboneCurl", + 61: "throatLock", + 62: "tickTockSideRoll", + 63: "twist", + 64: "vLegCrunches", + 65: "vSit", + 66: "forwardFoldWheelchair", + 67: "forwardFoldPlusWheelchair", + 68: "armCirclesLowForwardWheelchair", + 69: "armCirclesMidForwardWheelchair", + 70: "armCirclesHighForwardWheelchair", + 71: "armCirclesLowBackwardWheelchair", + 72: "armCirclesMidBackwardWheelchair", + 73: "armCirclesHighBackwardWheelchair", + 74: "coreTwistsWheelchair", + 75: "armRaiseWheelchair", + 76: "chestExpandWheelchair", + 77: "armExtendWheelchair", + 78: "forwardBendWheelchair", + 79: "toeTouchWheelchair", + 80: "extendedToeTouchWheelchair", + 81: "seatedArmCircles", + 82: "trunkRotations", + 83: "seatedTrunkRotations", + 84: "toeTouch", + }, + poseExerciseName: { + 0: "allFours", + 1: "ankleToKnee", + 2: "babyCobra", + 3: "boat", + 4: "boundAngle", + 5: "boundSeatedSingleLegForwardBend", + 6: "bow", + 7: "bowedHalfMoon", + 8: "bridge", + 9: "cat", + 10: "chair", + 11: "childs", + 12: "corpse", + 13: "cowFace", + 14: "cow", + 15: "devotionalWarrior", + 16: "dolphinPlank", + 17: "dolphin", + 18: "downDogKneeToNose", + 19: "downDogSplit", + 20: "downDogSplitOpenHipBentKnee", + 21: "downwardFacingDog", + 22: "eagle", + 23: "easySeated", + 24: "extendedPuppy", + 25: "extendedSideAngle", + 26: "fish", + 27: "fourLimbedStaff", + 28: "fullSplit", + 29: "gate", + 30: "halfChairHalfAnkleToKnee", + 31: "halfMoon", + 32: "headToKnee", + 33: "heron", + 34: "heros", + 35: "highLunge", + 36: "kneesChestChin", + 37: "lizard", + 38: "locust", + 39: "lowLunge", + 40: "lowLungeTwist", + 41: "lowLungeWithKneeDown", + 42: "mermaid", + 43: "mountain", + 44: "oneLeggedDownwardFacingPoseOpenHipBentKnee", + 45: "oneLeggedPigeon", + 46: "peacefulWarrior", + 47: "plank", + 48: "plow", + 49: "reclinedHandToFoot", + 50: "revolvedHalfMoon", + 51: "revolvedHeadToKnee", + 52: "revolvedTriangle", + 53: "runnersLunge", + 54: "seatedEasySideBend", + 55: "seatedEasyTwist", + 56: "seatedLongLegForwardBend", + 57: "seatedWideLegForwardBend", + 58: "shoulderStand", + 59: "sideBoat", + 60: "sidePlank", + 61: "sphinx", + 62: "squatOpenArmTwist", + 63: "squatPalmPress", + 64: "staff", + 65: "standingArmsUp", + 66: "standingForwardBendHalfwayUp", + 67: "standingForwardBend", + 68: "standingSideOpener", + 69: "standingSingleLegForwardBend", + 70: "standingSplit", + 71: "standingWideLegForwardBend", + 72: "standingWideLegForwardBendWithTwist", + 73: "supineSpinalTwist", + 74: "tableTop", + 75: "threadTheNeedle", + 76: "thunderbolt", + 77: "thunderboltPoseBothSidesArmStretch", + 78: "tree", + 79: "triangle", + 80: "upDog", + 81: "upwardFacingPlank", + 82: "warriorOne", + 83: "warriorThree", + 84: "warriorTwo", + 85: "wheel", + 86: "wideSideLunge", + 87: "deepBreathingWheelchair", + 88: "deepBreathingLowWheelchair", + 89: "deepBreathingMidWheelchair", + 90: "deepBreathingHighWheelchair", + 91: "prayerWheelchair", + 92: "overheadPrayerWheelchair", + 93: "cactusWheelchair", + 94: "breathingPunchesWheelchair", + 95: "breathingPunchesExtendedWheelchair", + 96: "breathingPunchesOverheadWheelchair", + 97: "breathingPunchesOverheadAndDownWheelchair", + 98: "breathingPunchesSideWheelchair", + 99: "breathingPunchesExtendedSideWheelchair", + 100: "breathingPunchesOverheadSideWheelchair", + 101: "breathingPunchesOverheadAndDownSideWheelchair", + 102: "leftHandBackWheelchair", + 103: "triangleWheelchair", + 104: "threadTheNeedleWheelchair", + 105: "neckFlexionAndExtensionWheelchair", + 106: "neckLateralFlexionWheelchair", + 107: "spineFlexionAndExtensionWheelchair", + 108: "spineRotationWheelchair", + 109: "spineLateralFlexionWheelchair", + 110: "alternativeSkiingWheelchair", + 111: "reachForwardWheelchair", + 112: "warriorWheelchair", + 113: "reverseWarriorWheelchair", + 114: "downwardFacingDogToCobra", + 115: "seatedCatCow", }, tricepsExtensionExerciseName: { 0: "benchDip", @@ -26925,6 +27403,10 @@ types: { 38: "tricepsExtensionOnFloor", 39: "tricepsPressdown", 40: "weightedDip", + 41: "alternatingDumbbellLyingTricepsExtension", + 42: "tricepsPress", + 43: "dumbbellKickbackWheelchair", + 44: "overheadDumbbellTricepsExtensionWheelchair", }, warmUpExerciseName: { 0: "quadrupedRocking", @@ -26958,12 +27440,274 @@ types: { 28: "walkingLegCradles", 29: "walkout", 30: "walkoutFromPushUpPosition", + 31: "bicepsStretch", + 32: "glutesStretch", + 33: "standingHamstringStretch", + 34: "stretch90_90", + 35: "stretchAbs", + 36: "stretchButterfly", + 37: "stretchCalf", + 38: "stretchCatCow", + 39: "stretchChildsPose", + 40: "stretchCobra", + 41: "stretchForearms", + 42: "stretchForwardGlutes", + 43: "stretchFrontSplit", + 44: "stretchHamstring", + 45: "stretchHipFlexorAndQuad", + 46: "stretchLat", + 47: "stretchLevatorScapulae", + 48: "stretchLungeWithSpinalTwist", + 49: "stretchLungingHipFlexor", + 50: "stretchLyingAbduction", + 51: "stretchLyingItBand", + 52: "stretchLyingKneeToChest", + 53: "stretchLyingPiriformis", + 54: "stretchLyingSpinalTwist", + 55: "stretchNeck", + 56: "stretchObliques", + 57: "stretchOverUnderShoulder", + 58: "stretchPectoral", + 59: "stretchPigeonPose", + 60: "stretchPiriformis", + 61: "stretchQuad", + 62: "stretchScorpion", + 63: "stretchShoulder", + 64: "stretchSide", + 65: "stretchSideLunge", + 66: "stretchSideSplit", + 67: "stretchStandingItBand", + 68: "stretchStraddle", + 69: "stretchTriceps", + 70: "stretchWallChestAndShoulder", + 71: "neckRotationsWheelchair", + 72: "halfKneelingArmRotation", + 73: "threeWayAnkleMobilization", + 74: "ninetyNinetyHipSwitch", // 90_90_hip_switch + 75: "activeFrog", + 76: "shoulderSweeps", + 77: "ankleLunges", + 78: "backRollFoamRoller", + 79: "bearCrawl", + 80: "latissimusDorsiFoamRoll", + 81: "reverseTHipOpener", + 82: "shoulderRolls", + 83: "chestOpeners", + 84: "tricepsStretch", + 85: "upperBackStretch", + 86: "hipCircles", + 87: "ankleStretch", + 88: "marchingInPlace", + 89: "tricepsStretchWheelchair", + 90: "upperBackStretchWheelchair", }, runExerciseName: { 0: "run", 1: "walk", 2: "jog", 3: "sprint", + 4: "runOrWalk", + 5: "speedWalk", + 6: "warmUp", + }, + bikeExerciseName: { + 0: "bike", + 1: "ride", + 2: "sprint", + }, + bandedExercisesExerciseName: { + 1: "abTwist", + 2: "backExtension", + 3: "bicycleCrunch", + 4: "calfRaises", + 5: "chestPress", + 6: "clamShells", + 7: "curl", + 8: "deadbug", + 9: "deadlift", + 10: "donkeyKick", + 11: "externalRotation", + 12: "externalRotationAt90DegreeAbduction", + 13: "facePull", + 14: "fireHydrant", + 15: "fly", + 16: "frontRaise", + 17: "gluteBridge", + 18: "hamstringCurls", + 19: "highPlankLegLifts", + 20: "hipExtension", + 21: "internalRotation", + 22: "jumpingJack", + 23: "kneelingCrunch", + 24: "lateralBandWalks", + 25: "lateralRaise", + 26: "latpull", + 27: "legAbduction", + 28: "legAdduction", + 29: "legExtension", + 30: "lunge", + 31: "plank", + 32: "pullApart", + 33: "pushUps", + 34: "reverseCrunch", + 35: "row", + 36: "shoulderAbduction", + 37: "shoulderExtension", + 38: "shoulderExternalRotation", + 39: "shoulderFlexionTo90Degrees", + 40: "sidePlankLegLifts", + 41: "sideRaise", + 42: "squat", + 43: "squatToPress", + 44: "tricepExtension", + 45: "tricepKickback", + 46: "uprightRow", + 47: "wallCrawlWithExternalRotation", + 49: "lateralRaiseWheelchair", + 50: "tricepsExtensionWheelchair", + 51: "chestFlyInclineWheelchair", + 52: "chestFlyDeclineWheelchair", + 53: "pullDownWheelchair", + 54: "straightArmPullDownWheelchair", + 55: "curlWheelchair", + 56: "overheadCurlWheelchair", + 57: "facePullWheelchair", + 58: "aroundTheWorldWheelchair", + 59: "pullApartWheelchair", + 60: "sideCurlWheelchair", + 61: "overheadPressWheelchair", + }, + battleRopeExerciseName: { + 0: "alternatingFigureEight", + 1: "alternatingJumpWave", + 2: "alternatingKneelingToStandingWave", + 3: "alternatingLungeWave", + 4: "alternatingSquatWave", + 5: "alternatingWave", + 6: "alternatingWaveWithLateralShuffle", + 7: "clapWave", + 8: "doubleArmFigureEight", + 9: "doubleArmSideToSideSnake", + 10: "doubleArmSideWave", + 11: "doubleArmSlam", + 12: "doubleArmWave", + 13: "grapplerToss", + 14: "hipToss", + 15: "inAndOutWave", + 16: "insideCircle", + 17: "jumpingJacks", + 18: "outsideCircle", + 19: "rainbow", + 20: "sidePlankWave", + 21: "sidewinder", + 22: "sittingRussianTwist", + 23: "snakeWave", + 24: "splitJack", + 25: "stageCoach", + 26: "ultimateWarrior", + 27: "upperCuts", + }, + ellipticalExerciseName: { + 0: "elliptical", + }, + floorClimbExerciseName: { + 0: "floorClimb", + }, + indoorBikeExerciseName: { + 0: "airBike", + 1: "assaultBike", + 3: "stationaryBike", + }, + indoorRowExerciseName: { + 0: "rowingMachine", + }, + ladderExerciseName: { + 0: "agility", + 1: "speed", + }, + sandbagExerciseName: { + 0: "aroundTheWorld", + 1: "backSquat", + 2: "bearCrawlPullThrough", + 3: "bearHugSquat", + 4: "clean", + 5: "cleanAndPress", + 6: "curl", + 7: "frontCarry", + 8: "frontSquat", + 9: "lunge", + 10: "overheadPress", + 11: "plankPullThrough", + 12: "rotationalLunge", + 13: "row", + 14: "russianTwist", + 15: "shouldering", + 16: "shoveling", + 17: "sideLunge", + 18: "sprint", + 19: "zercherSquat", + }, + sledExerciseName: { + 0: "backwardDrag", + 1: "chestPress", + 2: "forwardDrag", + 3: "lowPush", + 4: "push", + 5: "row", + }, + sledgeHammerExerciseName: { + 0: "lateralSwing", + 1: "hammerSlam", + }, + stairStepperExerciseName: { + 0: "stairStepper", + }, + suspensionExerciseName: { + 0: "chestFly", + 1: "chestPress", + 2: "crunch", + 3: "curl", + 4: "dip", + 5: "facePull", + 6: "gluteBridge", + 7: "hamstringCurl", + 8: "hipDrop", + 9: "invertedRow", + 10: "kneeDriveJump", + 11: "kneeToChest", + 12: "latPullover", + 13: "lunge", + 14: "mountainClimber", + 15: "pendulum", + 16: "pike", + 17: "plank", + 18: "powerPull", + 19: "pullUp", + 20: "pushUp", + 21: "reverseMountainClimber", + 22: "reversePlank", + 23: "rollout", + 24: "row", + 25: "sideLunge", + 26: "sidePlank", + 27: "singleLegDeadlift", + 28: "singleLegSquat", + 29: "sitUp", + 30: "split", + 31: "squat", + 32: "squatJump", + 33: "tricepPress", + 34: "yFly", + }, + tireExerciseName: { + 0: "flip", + }, + bikeOutdoorExerciseName: { + 0: "bike", + }, + runIndoorExerciseName: { + 0: "indoorTrackRun", + 1: "treadmill", }, waterType: { 0: "fresh", @@ -27153,6 +27897,7 @@ MesgNum : { CADENCE_ZONE: 131, POWER_ZONE: 9, MET_ZONE: 10, + TRAINING_SETTINGS: 13, DIVE_SETTINGS: 258, DIVE_ALARM: 262, DIVE_APNEA_ALARM: 393, diff --git a/src/stream.js b/src/stream.js index 4ca324b..7d0f342 100644 --- a/src/stream.js +++ b/src/stream.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/utils-hr-mesg.js b/src/utils-hr-mesg.js index 8fa8694..75c358c 100644 --- a/src/utils-hr-mesg.js +++ b/src/utils-hr-mesg.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/utils-internal.js b/src/utils-internal.js index be47b41..bdae60f 100644 --- a/src/utils-internal.js +++ b/src/utils-internal.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// @@ -18,6 +18,23 @@ const sanitizeValues = (values) => { return values.length === 1 ? values[0] : values; } +const trimStringTrailingNulls = (string) => { + if (string == null) { + return; + } + + let strings = string.split("\u0000") + + while (strings[strings.length - 1] === "") { + strings.pop(); + if (strings.length === 0) { + return ""; + } + } + + return strings.length === 1 ? strings[0] : strings; +} + const onlyNullValues = (values) => values.reduce((state, value) => value != null ? false : state, true); const onlyInvalidValues = (rawFieldValue, invalidValue) => { @@ -30,6 +47,7 @@ const onlyInvalidValues = (rawFieldValue, invalidValue) => { export default { sanitizeValues, + trimStringTrailingNulls, onlyNullValues, onlyInvalidValues }; diff --git a/src/utils-memo-glob.js b/src/utils-memo-glob.js new file mode 100644 index 0000000..b0f77ef --- /dev/null +++ b/src/utils-memo-glob.js @@ -0,0 +1,65 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2025 Garmin International, Inc. +// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you +// may not use this file except in compliance with the Flexible and Interoperable Data +// Transfer (FIT) Protocol License. +///////////////////////////////////////////////////////////////////////////////////////////// +// ****WARNING**** This file is auto-generated! Do NOT edit this file. +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 +///////////////////////////////////////////////////////////////////////////////////////////// + +import Profile from "./profile.js"; + +const decodeMemoGlobs = (messages) => { + if ((messages?.memoGlobMesgs?.length ?? 0) == 0) { + return; + } + + const memoGlobMesgs = messages.memoGlobMesgs; + + // Group memoGlob mesgs by mesgNum, parentIndex, and fieldNum + const groupedMemoGlobs = Object.groupBy(memoGlobMesgs, ({ mesgNum, parentIndex, fieldNum }) => { + return JSON.stringify({ + mesgNum, + parentIndex, + fieldNum + }); + }); + + Object.entries(groupedMemoGlobs).forEach(([key, memoGlobMesgs]) => { + const { mesgNum, parentIndex, fieldNum } = JSON.parse(key); + + // Sort grouped memoGlob messages by part index + memoGlobMesgs.sort((a, b) => a.partIndex - b.partIndex); + + const mesgProfile = Object.values(Profile.messages).find((mesgDefinition) => { + return mesgDefinition.name == mesgNum || mesgDefinition.num == mesgNum + }); + + const targetMesg = messages[mesgProfile?.messagesKey ?? mesgNum]?.[parentIndex]; + if (targetMesg == null) { + return; + } + + const targetFieldKey = mesgProfile?.fields?.[fieldNum]?.name ?? fieldNum; + + const memoGlobBytes = memoGlobMesgs.reduce((accumluatedBytes, mesg) => { + return accumluatedBytes.concat(mesg.data); + }, []); + + targetMesg[targetFieldKey] = decodeMemoGlobBytesToUtf8(memoGlobBytes); + }); +} + +const decodeMemoGlobBytesToUtf8 = (memoGlobBytes) => { + let decoder = new TextDecoder('utf-8'); + let bytes = new Uint8Array(memoGlobBytes); + + return decoder.decode(bytes); +} + +export default { + decodeMemoGlobs, + decodeMemoGlobBytesToUtf8 +} \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index 0f616cc..9495d55 100644 --- a/src/utils.js +++ b/src/utils.js @@ -5,8 +5,8 @@ // Transfer (FIT) Protocol License. ///////////////////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. -// Profile Version = 21.161.0Release -// Tag = production/release/21.161.0-0-g58854c0 +// Profile Version = 21.168.0Release +// Tag = production/release/21.168.0-0-gb831b31 ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/data/test-data.js b/test/data/test-data.js index f620cd0..aee1031 100644 --- a/test/data/test-data.js +++ b/test/data/test-data.js @@ -129,6 +129,162 @@ const fitFileCompressedSpeedAndDistanceWithInitialDistance = [ 0x0D, 0x00, 0x8B, 0x00, 0x08, 0x00, 0xF9, 0x00, 0x14, 0x65, 0xB1 ]; +const fitFileMemoGlobSimple = [ + 0x0E, 0x20, 0xAB, 0x52, 0x45, 0x00, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54, + 0xCA, 0x8E, 0x40, 0x00, 0x01, 0x00, 0x1B, 0x02, 0xFE, 0x02, 0x84, 0x08, + 0x10, 0x07, 0x00, 0x00, 0x00, 0x77, 0x6F, 0x72, 0x6B, 0x6F, 0x75, 0x74, + 0x53, 0x74, 0x65, 0x70, 0x4D, 0x65, 0x73, 0x67, 0x00, 0x40, 0x00, 0x01, + 0x00, 0x91, 0x05, 0xFA, 0x04, 0x86, 0x01, 0x02, 0x84, 0x02, 0x02, 0x84, + 0x03, 0x01, 0x02, 0x04, 0x07, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1B, 0x00, 0x00, 0x08, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x00, 0xEC, + 0x85 +]; + +const fitFileMemoGlobMultibyte = [ + 0x0E, 0x20, 0xAB, 0x52, 0x4F, 0x03, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54, + 0x79, 0xF1, 0x40, 0x00, 0x01, 0x00, 0x1B, 0x02, 0xFE, 0x02, 0x84, 0x08, + 0x10, 0x07, 0x00, 0x00, 0x00, 0x77, 0x6F, 0x72, 0x6B, 0x6F, 0x75, 0x74, + 0x53, 0x74, 0x65, 0x70, 0x4D, 0x65, 0x73, 0x67, 0x00, 0x40, 0x00, 0x01, + 0x00, 0x91, 0x05, 0xFA, 0x04, 0x86, 0x01, 0x02, 0x84, 0x02, 0x02, 0x84, + 0x03, 0x01, 0x02, 0x04, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1B, 0x00, 0x00, 0x08, 0x22, 0xE7, 0xAC, 0xAC, 0xEF, 0xBC, 0x91, 0xE6, + 0x9D, 0xA1, 0x20, 0xE3, 0x81, 0x99, 0xE3, 0x81, 0xB9, 0xE3, 0x81, 0xA6, + 0xE3, 0x81, 0xAE, 0xE4, 0xBA, 0xBA, 0xE9, 0x96, 0x93, 0xE3, 0x81, 0xAF, + 0xE3, 0x80, 0x81, 0xE7, 0x94, 0x9F, 0xE3, 0x81, 0xBE, 0xE3, 0x82, 0x8C, + 0xE3, 0x81, 0xAA, 0xE3, 0x81, 0x8C, 0xE3, 0x82, 0x89, 0xE3, 0x81, 0xAB, + 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA6, 0xE8, 0x87, 0xAA, 0xE7, 0x94, 0xB1, + 0xE3, 0x81, 0xA7, 0xE3, 0x81, 0x82, 0xE3, 0x82, 0x8A, 0xE3, 0x80, 0x81, + 0xE3, 0x81, 0x8B, 0xE3, 0x81, 0xA4, 0xE3, 0x80, 0x81, 0xE5, 0xB0, 0x8A, + 0xE5, 0x8E, 0xB3, 0xE3, 0x81, 0xA8, 0xE6, 0xA8, 0xA9, 0xE5, 0x88, 0xA9, + 0xE3, 0x81, 0xA8, 0xE3, 0x81, 0xAB, 0xE3, 0x81, 0xA4, 0xE3, 0x81, 0x84, + 0xE3, 0x81, 0xA6, 0xE5, 0xB9, 0xB3, 0xE7, 0xAD, 0x89, 0xE3, 0x81, 0xA7, + 0xE3, 0x81, 0x82, 0xE3, 0x82, 0x8B, 0xE3, 0x80, 0x82, 0xE4, 0xBA, 0xBA, + 0xE9, 0x96, 0x93, 0xE3, 0x81, 0xAF, 0xE3, 0x80, 0x81, 0xE7, 0x90, 0x86, + 0xE6, 0x80, 0xA7, 0xE3, 0x81, 0xA8, 0xE8, 0x89, 0xAF, 0xE5, 0xBF, 0x83, + 0xE3, 0x81, 0xA8, 0xE3, 0x82, 0x92, 0xE6, 0x8E, 0x88, 0xE3, 0x81, 0x91, + 0xE3, 0x82, 0x89, 0xE3, 0x82, 0x8C, 0xE3, 0x81, 0xA6, 0xE3, 0x81, 0x8A, + 0xE3, 0x82, 0x8A, 0xE3, 0x80, 0x81, 0xE4, 0xBA, 0x92, 0xE3, 0x81, 0x84, + 0xE3, 0x81, 0xAB, 0xE5, 0x90, 0x8C, 0xE8, 0x83, 0x9E, 0xE3, 0x81, 0xAE, + 0xE7, 0xB2, 0xBE, 0xE7, 0xA5, 0x9E, 0xE3, 0x82, 0x92, 0xE3, 0x82, 0x82, + 0xE3, 0x81, 0xA3, 0xE3, 0x81, 0xA6, 0xE8, 0xA1, 0x8C, 0xE5, 0x8B, 0x95, + 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xAA, 0xE3, 0x81, 0x91, 0xE3, 0x82, 0x8C, + 0xE3, 0x81, 0xB0, 0xE3, 0x81, 0xAA, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x1B, 0x00, 0x00, 0x08, 0x82, 0x89, 0xE3, 0x81, 0xAA, 0xE3, 0x81, + 0x84, 0xE3, 0x80, 0x82, 0xE7, 0xAC, 0xAC, 0xEF, 0xBC, 0x92, 0xE6, 0x9D, + 0xA1, 0x20, 0xE3, 0x81, 0x99, 0xE3, 0x81, 0xB9, 0xE3, 0x81, 0xA6, 0xE4, + 0xBA, 0xBA, 0xE3, 0x81, 0xAF, 0xE3, 0x80, 0x81, 0xE4, 0xBA, 0xBA, 0xE7, + 0xA8, 0xAE, 0xE3, 0x80, 0x81, 0xE7, 0x9A, 0xAE, 0xE8, 0x86, 0x9A, 0xE3, + 0x81, 0xAE, 0xE8, 0x89, 0xB2, 0xE3, 0x80, 0x81, 0xE6, 0x80, 0xA7, 0xE3, + 0x80, 0x81, 0xE8, 0xA8, 0x80, 0xE8, 0xAA, 0x9E, 0xE3, 0x80, 0x81, 0xE5, + 0xAE, 0x97, 0xE6, 0x95, 0x99, 0xE3, 0x80, 0x81, 0xE6, 0x94, 0xBF, 0xE6, + 0xB2, 0xBB, 0xE4, 0xB8, 0x8A, 0xE3, 0x81, 0x9D, 0xE3, 0x81, 0xAE, 0xE4, + 0xBB, 0x96, 0xE3, 0x81, 0xAE, 0xE6, 0x84, 0x8F, 0xE8, 0xA6, 0x8B, 0xE3, + 0x80, 0x81, 0xE5, 0x9B, 0xBD, 0xE6, 0xB0, 0x91, 0xE7, 0x9A, 0x84, 0xE3, + 0x82, 0x82, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0x8F, 0xE3, 0x81, 0xAF, 0xE7, + 0xA4, 0xBE, 0xE4, 0xBC, 0x9A, 0xE7, 0x9A, 0x84, 0xE5, 0x87, 0xBA, 0xE8, + 0xBA, 0xAB, 0xE3, 0x80, 0x81, 0xE8, 0xB2, 0xA1, 0xE7, 0x94, 0xA3, 0xE3, + 0x80, 0x81, 0xE9, 0x96, 0x80, 0xE5, 0x9C, 0xB0, 0xE3, 0x81, 0x9D, 0xE3, + 0x81, 0xAE, 0xE4, 0xBB, 0x96, 0xE3, 0x81, 0xAE, 0xE5, 0x9C, 0xB0, 0xE4, + 0xBD, 0x8D, 0xE5, 0x8F, 0x88, 0xE3, 0x81, 0xAF, 0xE3, 0x81, 0x93, 0xE3, + 0x82, 0x8C, 0xE3, 0x81, 0xAB, 0xE9, 0xA1, 0x9E, 0xE3, 0x81, 0x99, 0xE3, + 0x82, 0x8B, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0x8B, 0xE3, 0x81, 0xAA, 0xE3, + 0x82, 0x8B, 0xE8, 0x87, 0xAA, 0xE7, 0x94, 0xB1, 0xE3, 0x81, 0xAB, 0xE3, + 0x82, 0x88, 0xE3, 0x82, 0x8B, 0xE5, 0xB7, 0xAE, 0xE5, 0x88, 0xA5, 0xE3, + 0x82, 0x92, 0xE3, 0x82, 0x82, 0xE5, 0x8F, 0x97, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x1B, 0x00, 0x00, 0x08, 0xE3, 0x81, 0x91, 0xE3, 0x82, 0x8B, + 0xE3, 0x81, 0x93, 0xE3, 0x81, 0xA8, 0xE3, 0x81, 0xAA, 0xE3, 0x81, 0x8F, + 0xE3, 0x80, 0x81, 0xE3, 0x81, 0x93, 0xE3, 0x81, 0xAE, 0xE5, 0xAE, 0xA3, + 0xE8, 0xA8, 0x80, 0xE3, 0x81, 0xAB, 0xE6, 0x8E, 0xB2, 0xE3, 0x81, 0x92, + 0xE3, 0x82, 0x8B, 0xE3, 0x81, 0x99, 0xE3, 0x81, 0xB9, 0xE3, 0x81, 0xA6, + 0xE3, 0x81, 0xAE, 0xE6, 0xA8, 0xA9, 0xE5, 0x88, 0xA9, 0xE3, 0x81, 0xA8, + 0xE8, 0x87, 0xAA, 0xE7, 0x94, 0xB1, 0xE3, 0x81, 0xA8, 0xE3, 0x82, 0x92, + 0xE4, 0xBA, 0xAB, 0xE6, 0x9C, 0x89, 0xE3, 0x81, 0x99, 0xE3, 0x82, 0x8B, + 0xE3, 0x81, 0x93, 0xE3, 0x81, 0xA8, 0xE3, 0x81, 0x8C, 0xE3, 0x81, 0xA7, + 0xE3, 0x81, 0x8D, 0xE3, 0x82, 0x8B, 0xE3, 0x80, 0x82, 0x22, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA9, 0x6A +]; + +const fitFileMemoGlobLoremIpsum = [ + 0x0E, 0x20, 0xAB, 0x52, 0x46, 0x02, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54, + 0xA9, 0x5B, 0x40, 0x00, 0x01, 0x00, 0x1B, 0x02, 0xFE, 0x02, 0x84, 0x08, + 0x10, 0x07, 0x00, 0x00, 0x00, 0x77, 0x6F, 0x72, 0x6B, 0x6F, 0x75, 0x74, + 0x53, 0x74, 0x65, 0x70, 0x4D, 0x65, 0x73, 0x67, 0x00, 0x40, 0x00, 0x01, + 0x00, 0x91, 0x05, 0xFA, 0x04, 0x86, 0x01, 0x02, 0x84, 0x02, 0x02, 0x84, + 0x03, 0x01, 0x02, 0x04, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1B, 0x00, 0x00, 0x08, 0x4C, 0x6F, 0x72, 0x65, 0x6D, 0x20, 0x69, 0x70, + 0x73, 0x75, 0x6D, 0x20, 0x64, 0x6F, 0x6C, 0x6F, 0x72, 0x20, 0x73, 0x69, + 0x74, 0x20, 0x61, 0x6D, 0x65, 0x74, 0x2C, 0x20, 0x63, 0x6F, 0x6E, 0x73, + 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, + 0x69, 0x73, 0x63, 0x69, 0x6E, 0x67, 0x20, 0x65, 0x6C, 0x69, 0x74, 0x2C, + 0x20, 0x73, 0x65, 0x64, 0x20, 0x64, 0x6F, 0x20, 0x65, 0x69, 0x75, 0x73, + 0x6D, 0x6F, 0x64, 0x20, 0x74, 0x65, 0x6D, 0x70, 0x6F, 0x72, 0x20, 0x69, + 0x6E, 0x63, 0x69, 0x64, 0x69, 0x64, 0x75, 0x6E, 0x74, 0x20, 0x75, 0x74, + 0x20, 0x6C, 0x61, 0x62, 0x6F, 0x72, 0x65, 0x20, 0x65, 0x74, 0x20, 0x64, + 0x6F, 0x6C, 0x6F, 0x72, 0x65, 0x20, 0x6D, 0x61, 0x67, 0x6E, 0x61, 0x20, + 0x61, 0x6C, 0x69, 0x71, 0x75, 0x61, 0x2E, 0x20, 0x55, 0x74, 0x20, 0x65, + 0x6E, 0x69, 0x6D, 0x20, 0x61, 0x64, 0x20, 0x6D, 0x69, 0x6E, 0x69, 0x6D, + 0x20, 0x76, 0x65, 0x6E, 0x69, 0x61, 0x6D, 0x2C, 0x20, 0x71, 0x75, 0x69, + 0x73, 0x20, 0x6E, 0x6F, 0x73, 0x74, 0x72, 0x75, 0x64, 0x20, 0x65, 0x78, + 0x65, 0x72, 0x63, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x75, + 0x6C, 0x6C, 0x61, 0x6D, 0x63, 0x6F, 0x20, 0x6C, 0x61, 0x62, 0x6F, 0x72, + 0x69, 0x73, 0x20, 0x6E, 0x69, 0x73, 0x69, 0x20, 0x75, 0x74, 0x20, 0x61, + 0x6C, 0x69, 0x71, 0x75, 0x69, 0x70, 0x20, 0x65, 0x78, 0x20, 0x65, 0x61, + 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x6F, 0x64, 0x6F, 0x20, 0x63, 0x6F, 0x6E, + 0x73, 0x65, 0x71, 0x75, 0x61, 0x74, 0x2E, 0x20, 0x44, 0x75, 0x69, 0x73, + 0x20, 0x61, 0x75, 0x74, 0x65, 0x20, 0x69, 0x72, 0x75, 0x72, 0x65, 0x20, + 0x64, 0x6F, 0x6C, 0x6F, 0x72, 0x20, 0x69, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x1B, 0x00, 0x00, 0x08, 0x6E, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, + 0x68, 0x65, 0x6E, 0x64, 0x65, 0x72, 0x69, 0x74, 0x20, 0x69, 0x6E, 0x20, + 0x76, 0x6F, 0x6C, 0x75, 0x70, 0x74, 0x61, 0x74, 0x65, 0x20, 0x76, 0x65, + 0x6C, 0x69, 0x74, 0x20, 0x65, 0x73, 0x73, 0x65, 0x20, 0x63, 0x69, 0x6C, + 0x6C, 0x75, 0x6D, 0x20, 0x64, 0x6F, 0x6C, 0x6F, 0x72, 0x65, 0x20, 0x65, + 0x75, 0x20, 0x66, 0x75, 0x67, 0x69, 0x61, 0x74, 0x20, 0x6E, 0x75, 0x6C, + 0x6C, 0x61, 0x20, 0x70, 0x61, 0x72, 0x69, 0x61, 0x74, 0x75, 0x72, 0x2E, + 0x20, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x65, 0x75, 0x72, 0x20, 0x73, + 0x69, 0x6E, 0x74, 0x20, 0x6F, 0x63, 0x63, 0x61, 0x65, 0x63, 0x61, 0x74, + 0x20, 0x63, 0x75, 0x70, 0x69, 0x64, 0x61, 0x74, 0x61, 0x74, 0x20, 0x6E, + 0x6F, 0x6E, 0x20, 0x70, 0x72, 0x6F, 0x69, 0x64, 0x65, 0x6E, 0x74, 0x2C, + 0x20, 0x73, 0x75, 0x6E, 0x74, 0x20, 0x69, 0x6E, 0x20, 0x63, 0x75, 0x6C, + 0x70, 0x61, 0x20, 0x71, 0x75, 0x69, 0x20, 0x6F, 0x66, 0x66, 0x69, 0x63, + 0x69, 0x61, 0x20, 0x64, 0x65, 0x73, 0x65, 0x72, 0x75, 0x6E, 0x74, 0x20, + 0x6D, 0x6F, 0x6C, 0x6C, 0x69, 0x74, 0x20, 0x61, 0x6E, 0x69, 0x6D, 0x20, + 0x69, 0x64, 0x20, 0x65, 0x73, 0x74, 0x20, 0x6C, 0x61, 0x62, 0x6F, 0x72, + 0x75, 0x6D, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC9, 0x3B +]; + +const fitFileMemoGlobUnknownTargetField = [ + 0x0E, 0x20, 0xAB, 0x52, 0x32, 0x00, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54, + 0x8C, 0x4C, 0x40, 0x00, 0x01, 0x00, 0x1B, 0x01, 0xFE, 0x02, 0x84, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x01, 0x00, 0x91, 0x05, 0xFA, 0x04, 0x86, 0x01, + 0x02, 0x84, 0x02, 0x02, 0x84, 0x03, 0x01, 0x02, 0x04, 0x07, 0x0A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0xFA, 0x73, 0x74, 0x72, + 0x69, 0x6E, 0x67, 0x00, 0x29, 0x1D +]; +const fitFileMemoGlobUnknownTargetMesg = [ + 0x0E, 0x20, 0xAB, 0x52, 0x31, 0x00, 0x00, 0x00, 0x2E, 0x46, 0x49, 0x54, + 0xCC, 0x59, 0x40, 0x00, 0x01, 0x03, 0xE8, 0x01, 0xFE, 0x01, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x01, 0x00, 0x91, 0x05, 0xFA, 0x04, 0x86, 0x01, 0x02, + 0x84, 0x02, 0x02, 0x84, 0x03, 0x01, 0x02, 0x04, 0x07, 0x0A, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xE8, 0x00, 0x00, 0xFA, 0x73, 0x74, 0x72, 0x69, + 0x6E, 0x67, 0x00, 0xD3, 0xAC +]; + const gearChangeData = [ { "timestamp": 1024873717, @@ -525,6 +681,11 @@ export default { fitFileAccumulatedComponents, fitFileCompressedSpeedAndDistanceWithInitialDistance, fitFileCompressedSpeedAndDistance, + fitFileMemoGlobSimple, + fitFileMemoGlobMultibyte, + fitFileMemoGlobLoremIpsum, + fitFileMemoGlobUnknownTargetField, + fitFileMemoGlobUnknownTargetMesg, gearChangeData, workout800mRepeatsLittleEndian, workout800mRepeatsBigEndian diff --git a/test/encoder.test.js b/test/encoder.test.js index 6fcdec8..2b4c6db 100644 --- a/test/encoder.test.js +++ b/test/encoder.test.js @@ -7,7 +7,7 @@ import { describe, expect, test } from "vitest"; -import { Encoder, Profile } from "../src/index.js"; +import { Profile, Encoder, Stream, Decoder } from "../src/index.js"; describe("Encoder Tests", () => { test("A file encoded with no messages should be 16 bytes long.", () => { @@ -172,3 +172,81 @@ describe("Encoder Tests", () => { }); }); }); + +describe("Encoder-Decoder Integration Tests", () => { + const DECODER_OPTIONS = { + convertDateTimesToDates: false, + }; + + test("Can decode encoded file", () => { + const fileIdMesg = { + type: "activity", + manufacturer: "development", + product: 0, + timeCreated: 1000000000, // Wed, 08 Sep 2021 01:46:40 GMT + serialNumber: 1234, + }; + + try { + const encoder = new Encoder(); + encoder.onMesg(Profile.MesgNum.FILE_ID, fileIdMesg); + const stream = Stream.fromByteArray(encoder.close()); + + const decoder = new Decoder(stream); + + const { messages, errors, } = decoder.read(DECODER_OPTIONS); + + expect(errors.length).toBe(0); + + expect(messages.fileIdMesgs.length).toBe(1); + expect(messages.fileIdMesgs[0]).toMatchObject(fileIdMesg); + } + catch (error) { + console.error(`${error.name}: ${error.message} \n${JSON.stringify(error.cause, null, 3)}`); + throw error; + } + }); + + test("Can decode encoded message with expanded component fields", () => { + const hrMesg = { + timestamp: 840026841, + filteredBpm: [71, 72, 75, 77, 79, 81, 83, 83], + eventTimestamp12: [78, 91, 230, 94, 209, 70, 64, 135, 161, 245, 28, 254], + }; + + const expectedExpandedEventTimestamps = [ + 2.826171875, + 3.5986328125, + 4.341796875, + 5.1064453125, + 5.8125, + 6.5234375, + 7.2392578125, + 7.9697265625, + ]; + + try { + const encoder = new Encoder(); + encoder.onMesg(Profile.MesgNum.HR, hrMesg); + const stream = Stream.fromByteArray(encoder.close()); + + const decoder = new Decoder(stream); + + const { messages, errors, } = decoder.read(DECODER_OPTIONS); + + expect(errors.length).toBe(0); + + expect(messages.hrMesgs.length).toBe(1); + + // Decoded HR message should have expanded event timestamps + expect(messages.hrMesgs[0].eventTimestamp).toEqual(expectedExpandedEventTimestamps); + + expect(messages.hrMesgs[0]).toMatchObject(hrMesg); + } + catch (error) { + console.error(`${error.name}: ${error.message} \n${JSON.stringify(error.cause, null, 3)}`); + throw error; + } + }); +}); + diff --git a/test/utils-memo-glob.test.js b/test/utils-memo-glob.test.js new file mode 100644 index 0000000..5017d5b --- /dev/null +++ b/test/utils-memo-glob.test.js @@ -0,0 +1,121 @@ +///////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2025 Garmin International, Inc. +// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you +// may not use this file except in compliance with the Flexible and Interoperable Data +// Transfer (FIT) Protocol License. +///////////////////////////////////////////////////////////////////////////////////////////// + +import { describe, expect, test } from "vitest"; + +import Data from "../test/data/test-data.js"; +import Decoder from "../src/decoder.js"; +import MemoGlobUtils from "../src/utils-memo-glob.js"; +import Profile from "../src/profile.js"; +import Stream from "../src/stream.js"; +import UtilsInternal from "../src/utils-internal.js"; + +const loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +const japaneseMultibyteString = "\"第1条 すべての人間は、生まれながらにして自由であり、かつ、尊厳と権利とについて平等である。人間は、理性と良心とを授けられており、互いに同胞の精神をもって行動しなければならない。第2条 すべて人は、人種、皮膚の色、性、言語、宗教、政治上その他の意見、国民的もしくは社会的出身、財産、門地その他の地位又はこれに類するいかなる自由による差別をも受けることなく、この宣言に掲げるすべての権利と自由とを享有することができる。\"" + +describe("Memo Glob Decoder Integration Tests", () => { + test.each([ + { name: "Simple string to existing message decodes properly", data: Data.fitFileMemoGlobSimple, expected: "string", targetMesg: "workoutStepMesgs", targetField: "notes", }, + { name: "Long string to existing message decodes properly", data: Data.fitFileMemoGlobLoremIpsum, expected: loremIpsum, targetMesg: "workoutStepMesgs", targetField: "notes", }, + { name: "Long Japanese multibyte string to existing message decodes properly", data: Data.fitFileMemoGlobMultibyte, expected: japaneseMultibyteString, targetMesg: "workoutStepMesgs", targetField: "notes", }, + { name: "Simple string to unknown target field in existing mesg decodes properly and creates new field", data: Data.fitFileMemoGlobUnknownTargetField, expected: "string", targetMesg: "workoutStepMesgs", targetField: 250, }, + { name: "Simple string to unknown message does not decode as the message is excluded as unknown data", data: Data.fitFileMemoGlobUnknownTargetMesg, expected: undefined, targetMesg: "1000", targetField: 250, }, + ])( + "Decoder default options - Test %# - $name", ({ data, expected, targetMesg, targetField, }) => { + const stream = Stream.fromByteArray(data); + const decode = new Decoder(stream); + const { messages, errors } = decode.read({ decodeMemoGlobs: true, }); + expect(errors.length).toBe(0); + + const decodedMemoGlobString = messages[targetMesg]?.[0]?.[targetField]; + + let cleanedString = UtilsInternal.trimStringTrailingNulls(decodedMemoGlobString); + + expect(cleanedString).toBe(expected); + } + ); + + test.each([ + { name: "Simple string to existing message decodes properly", data: Data.fitFileMemoGlobSimple, expected: "string", targetMesg: "workoutStepMesgs", targetField: "notes", }, + { name: "Long string to existing message decodes properly", data: Data.fitFileMemoGlobLoremIpsum, expected: loremIpsum, targetMesg: "workoutStepMesgs", targetField: "notes", }, + { name: "Long Japanese multibyte string to existing message decodes properly", data: Data.fitFileMemoGlobMultibyte, expected: japaneseMultibyteString, targetMesg: "workoutStepMesgs", targetField: "notes", }, + { name: "Simple string to unknown target field in existing mesg decodes properly and creates new field", data: Data.fitFileMemoGlobUnknownTargetField, expected: "string", targetMesg: "workoutStepMesgs", targetField: 250, }, + { name: "Simple string to unknown message decodes as the message is included as unknown data", data: Data.fitFileMemoGlobUnknownTargetMesg, expected: "string", targetMesg: "1000", targetField: 250, }, + ])( + "Decoder include unknown data - Test %# - $name", ({ data, expected, targetMesg, targetField, }) => { + const stream = Stream.fromByteArray(data); + const decode = new Decoder(stream); + const { messages, errors } = decode.read({ includeUnknownData: true, decodeMemoGlobs: true }); + expect(errors.length).toBe(0); + + const decodedMemoGlobString = messages[targetMesg]?.[0]?.[targetField]; + + let cleanedString = UtilsInternal.trimStringTrailingNulls(decodedMemoGlobString); + + expect(cleanedString).toBe(expected); + } + ); +}); + +describe("MemoGlob Utils Tests", () => { + test("decodeMemoGlobs when message is workoutStep adds decoded string to workoutStep", () => { + const messages = { + memoGlobMesgs: [{ mesgNum: "workoutStep", parentIndex: 0, fieldNum: 8, partIndex: 0, data: [104, 101, 108, 108, 111, 33] }], + workoutStepMesgs: [{ messageIndex: 0, notes: "to be overwritten" }] + } + + MemoGlobUtils.decodeMemoGlobs(messages); + + expect(messages.workoutStepMesgs[0].notes).toBe("hello!"); + }); + + test("decodeMemoGlobs when target field does not exist should create new field", () => { + const utf8Encode = new TextEncoder(); + + const messages = { + memoGlobMesgs: [{ mesgNum: "workoutStep", parentIndex: 0, fieldNum: 1, partIndex: 0, data: Array.from(utf8Encode.encode("hello!")) }], + workoutStepMesgs: [{ messageIndex: 0, notes: "Not the target field" }] + } + + MemoGlobUtils.decodeMemoGlobs(messages); + + expect(messages.workoutStepMesgs[0].notes).toBe("Not the target field"); + expect(messages.workoutStepMesgs[0].durationType).toBe("hello!"); + + }); + + test("decodeMemoGlobs when message is not found does not throw error", () => { + const messages = { + memoGlobMesgs: [{ mesgNum: Profile.MesgNum.RECORD, parentIndex: 0, fieldNum: 2, partIndex: 0, }], + } + + MemoGlobUtils.decodeMemoGlobs(messages); + }); + + test("decodeMemoGlobs when message is unknown adds decoded string to unknown message and field", () => { + const messages = { + memoGlobMesgs: [{ mesgNum: 1000, parentIndex: 0, fieldNum: 2, partIndex: 0, data: [104, 101, 108, 108, 111, 33] }], + 1000: [{ 2: "to be overwritten" }] + } + + MemoGlobUtils.decodeMemoGlobs(messages); + + expect(messages[1000][0][2]).toBe("hello!"); + }); + + test("decodeMemoGlobs when message is unknown adds decoded string to unknown message and field", () => { + const messages = { + memoGlobMesgs: [{ mesgNum: 1000, parentIndex: 0, fieldNum: 2, partIndex: 0, data: [104, 101, 108, 108, 111, 33] }], + 1000: [{ 2: "to be overwritten" }] + } + + MemoGlobUtils.decodeMemoGlobs(messages); + + expect(messages[1000][0][2]).toBe("hello!"); + }); + +}); \ No newline at end of file From 87ecf04748a88486915ddb1b91eb7e089b17861d Mon Sep 17 00:00:00 2001 From: Elijah Flinders <14484665+Lijah99@users.noreply.github.com> Date: Thu, 27 Mar 2025 08:01:18 -0600 Subject: [PATCH 2/4] Update run-tests.yml to use newer versions of Node --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2a18ced..90122d6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [16.x, 18.x, 20.x] + node-version: [21.x, 22.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: From 4a88671b8885829f34aceed33c85784489aa8742 Mon Sep 17 00:00:00 2001 From: Elijah Flinders <14484665+Lijah99@users.noreply.github.com> Date: Thu, 27 Mar 2025 08:07:26 -0600 Subject: [PATCH 3/4] Update README.md to specify minimum NodeJS requirements --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aaf847..befe0eb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The FIT SDK documentation is available at [https://developer.garmin.com/fit](htt ## FIT SDK Developer Forum Share your knowledge, ask questions, and get the latest FIT SDK news in the [FIT SDK Developer Forum](https://forums.garmin.com/developer/). ## FIT JavaScript SDK Requirements -The FIT JavaScript SDK uses ECMAScript module syntax and requires Node.js v14.0 or higher, or a browser with a compatible JavaScript runtime engine. +The FIT JavaScript SDK uses ECMAScript module syntax and requires Node.js v21.0 or higher, or a browser with a compatible JavaScript runtime engine. ## Install ```sh npm install @garmin/fitsdk From e8596b3bf96491e22bde2d8f1261117bb048182e Mon Sep 17 00:00:00 2001 From: Elijah Flinders <14484665+Lijah99@users.noreply.github.com> Date: Thu, 27 Mar 2025 08:09:15 -0600 Subject: [PATCH 4/4] Update publish-npm.yml to use NodeJS v20 This is the latest LTS version of Node, so that is what should be used to publish --- .github/workflows/publish-npm.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 262c191..1c51473 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 - run: npm install --package-lock=false - run: npm test @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm install --package-lock=false - run: npm publish