fit-file-parser 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +190 -119
- package/dist/binary.d.ts +13 -1
- package/dist/binary.js +285 -164
- package/dist/cjs/binary.d.ts +13 -1
- package/dist/cjs/binary.js +285 -164
- package/dist/cjs/fit-parser.js +37 -19
- package/dist/cjs/fit.d.ts +6 -0
- package/dist/cjs/fit.js +167 -44
- package/dist/cjs/fit_types.d.ts +1297 -207
- package/dist/cjs/garmin_profile.generated.d.ts +9 -0
- package/dist/cjs/garmin_profile.generated.js +17838 -0
- package/dist/cjs/type_generator.d.ts +3 -0
- package/dist/cjs/type_generator.js +37 -9
- package/dist/fit-parser.js +37 -19
- package/dist/fit.d.ts +6 -0
- package/dist/fit.js +167 -44
- package/dist/fit_types.d.ts +1297 -207
- package/dist/garmin_profile.generated.d.ts +9 -0
- package/dist/garmin_profile.generated.js +17835 -0
- package/dist/type_generator.d.ts +3 -0
- package/dist/type_generator.js +36 -9
- package/package.json +9 -6
|
@@ -19,6 +19,9 @@ export declare function generateOptions(options: {
|
|
|
19
19
|
}): ts.Node[];
|
|
20
20
|
export declare function generateUtilities(): Statement[];
|
|
21
21
|
export declare function generateFitType(): Statement;
|
|
22
|
+
export declare function generateParsedMessages(messages: {
|
|
23
|
+
[messageId: number]: Message;
|
|
24
|
+
}): Statement;
|
|
22
25
|
export declare function generateMessages(messages: {
|
|
23
26
|
[messageId: number]: Message;
|
|
24
27
|
}): Statement[];
|
|
@@ -16,6 +16,7 @@ exports.generateTypes = generateTypes;
|
|
|
16
16
|
exports.generateOptions = generateOptions;
|
|
17
17
|
exports.generateUtilities = generateUtilities;
|
|
18
18
|
exports.generateFitType = generateFitType;
|
|
19
|
+
exports.generateParsedMessages = generateParsedMessages;
|
|
19
20
|
exports.generateMessages = generateMessages;
|
|
20
21
|
exports.main = main;
|
|
21
22
|
const typescript_1 = __importDefault(require("typescript"));
|
|
@@ -59,6 +60,9 @@ function generateArrayProperty(name, type, optional = true) {
|
|
|
59
60
|
return generateProperty(name, typescript_1.default.factory.createArrayTypeNode(type), optional);
|
|
60
61
|
}
|
|
61
62
|
function generateTypeFromField(def) {
|
|
63
|
+
if (def.array === true) {
|
|
64
|
+
return typescript_1.default.factory.createArrayTypeNode(generateTypeFromField(Object.assign(Object.assign({}, def), { array: false })));
|
|
65
|
+
}
|
|
62
66
|
switch (def.type) {
|
|
63
67
|
case 'uint32_array':
|
|
64
68
|
case 'uint16_array':
|
|
@@ -68,6 +72,8 @@ function generateTypeFromField(def) {
|
|
|
68
72
|
case 'sint8_array':
|
|
69
73
|
case 'byte_array':
|
|
70
74
|
return typescript_1.default.factory.createArrayTypeNode(typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword));
|
|
75
|
+
case 'exercise_category_array':
|
|
76
|
+
return typescript_1.default.factory.createArrayTypeNode(typescript_1.default.factory.createTypeReferenceNode('ExerciseCategory'));
|
|
71
77
|
case 'bool':
|
|
72
78
|
return typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.BooleanKeyword);
|
|
73
79
|
case 'date_time':
|
|
@@ -109,11 +115,13 @@ function generateTypes(types) {
|
|
|
109
115
|
const names = Object.values(type);
|
|
110
116
|
const typeAlias = typescript_1.default.factory.createTypeAliasDeclaration([
|
|
111
117
|
typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
|
|
112
|
-
], snakeToCamel(name), undefined,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
], snakeToCamel(name), undefined, names.length === 0
|
|
119
|
+
? typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)
|
|
120
|
+
: typescript_1.default.factory.createUnionTypeNode([...names.map(n => typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral(String(n)))), ...(name === 'mesg_num'
|
|
121
|
+
? [
|
|
122
|
+
typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral('definition')),
|
|
123
|
+
]
|
|
124
|
+
: [])]));
|
|
117
125
|
nodes.push(typeAlias);
|
|
118
126
|
});
|
|
119
127
|
return nodes;
|
|
@@ -180,6 +188,7 @@ function generateAdditionalFields(msg) {
|
|
|
180
188
|
developer_data_ids: 'parsed_developer_data_id',
|
|
181
189
|
field_descriptions: 'parsed_field_description',
|
|
182
190
|
sports: 'parsed_sport',
|
|
191
|
+
sets: 'parsed_set',
|
|
183
192
|
splits: 'parsed_split',
|
|
184
193
|
split_summaries: 'parsed_split_summary',
|
|
185
194
|
};
|
|
@@ -211,6 +220,7 @@ function generateFitType() {
|
|
|
211
220
|
tank_updates: 'parsed_tank_update',
|
|
212
221
|
tank_summaries: 'parsed_tank_summary',
|
|
213
222
|
jumps: 'parsed_jump',
|
|
223
|
+
sets: 'parsed_set',
|
|
214
224
|
splits: 'parsed_split',
|
|
215
225
|
split_summaries: 'parsed_split_summary',
|
|
216
226
|
time_in_zone: 'parsed_time_in_zone',
|
|
@@ -227,15 +237,27 @@ function generateFitType() {
|
|
|
227
237
|
activity: 'parsed_activity',
|
|
228
238
|
zones_target: '?parsed_zones_target',
|
|
229
239
|
};
|
|
240
|
+
const existingMessageNames = new Set([
|
|
241
|
+
...Object.values(collectionProperties),
|
|
242
|
+
...Object.values(referenceProperties),
|
|
243
|
+
].map(name => name.replace('?', '').replace(/^parsed_/, '')));
|
|
244
|
+
const additionalReferenceProperties = Object.values(fit_js_1.FIT.messages)
|
|
245
|
+
.filter(message => !existingMessageNames.has(message.name))
|
|
246
|
+
.map(message => generateProperty(message.name, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(`parsed_${message.name}`))));
|
|
230
247
|
return typescript_1.default.factory.createInterfaceDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'ParsedFit', undefined, undefined, [
|
|
231
248
|
generateProperty('protocolVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
|
|
232
249
|
generateProperty('profileVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
|
|
250
|
+
generateProperty('messages', typescript_1.default.factory.createTypeReferenceNode('ParsedMessages')),
|
|
233
251
|
...Object.keys(referenceProperties).map(prop => generateProperty(prop, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(referenceProperties[prop].replace('?', ''))), referenceProperties[prop].startsWith('?'))),
|
|
234
252
|
...Object.keys(collectionProperties).map(prop => generateArrayProperty(prop, collectionProperties[prop] === 'unknown'
|
|
235
253
|
? typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.UnknownKeyword)
|
|
236
254
|
: typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(collectionProperties[prop])))),
|
|
255
|
+
...additionalReferenceProperties,
|
|
237
256
|
]);
|
|
238
257
|
}
|
|
258
|
+
function generateParsedMessages(messages) {
|
|
259
|
+
return typescript_1.default.factory.createInterfaceDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'ParsedMessages', undefined, undefined, Object.values(messages).map(message => generateArrayProperty(message.name, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(`parsed_${message.name}`)))));
|
|
260
|
+
}
|
|
239
261
|
function generateMessages(messages) {
|
|
240
262
|
const nodes = [];
|
|
241
263
|
Object.keys(messages).forEach((name) => {
|
|
@@ -245,11 +267,15 @@ function generateMessages(messages) {
|
|
|
245
267
|
typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
|
|
246
268
|
], snakeToCamel(`parsed_${msg.name}`), undefined, undefined, [
|
|
247
269
|
...Object.keys(msg).filter(n => n !== 'name').reduce((acc, id) => {
|
|
270
|
+
var _a;
|
|
248
271
|
const def = msg[Number(id)];
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
272
|
+
const definitions = [def, ...((_a = def.aliases) !== null && _a !== void 0 ? _a : [])];
|
|
273
|
+
definitions.forEach((definition) => {
|
|
274
|
+
if (!usedFields.has(definition.field)) {
|
|
275
|
+
usedFields.add(definition.field);
|
|
276
|
+
acc.push(generateProperty(definition.field, generateTypeFromField(definition), !['start_time', 'timestamp'].includes(definition.field)));
|
|
277
|
+
}
|
|
278
|
+
});
|
|
253
279
|
return acc;
|
|
254
280
|
}, []),
|
|
255
281
|
...generateAdditionalFields(msg),
|
|
@@ -271,6 +297,8 @@ function main() {
|
|
|
271
297
|
nodes.push(...generateOptions(fit_js_1.FIT.options));
|
|
272
298
|
nodes.push(...comment('parsed from Fit.messages'));
|
|
273
299
|
nodes.push(...generateMessages(fit_js_1.FIT.messages));
|
|
300
|
+
nodes.push(...comment('all recognized messages, retained in file order'));
|
|
301
|
+
nodes.push(generateParsedMessages(fit_js_1.FIT.messages));
|
|
274
302
|
nodes.push(...comment('the returned type after parsing a .fit file'));
|
|
275
303
|
nodes.push(generateFitType());
|
|
276
304
|
const nodesArray = typescript_1.default.factory.createNodeArray(nodes);
|
package/dist/fit-parser.js
CHANGED
|
@@ -26,23 +26,23 @@ export default class FitParser {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
parse(content, callback) {
|
|
29
|
-
var _a;
|
|
29
|
+
var _a, _b;
|
|
30
30
|
const blob = content instanceof ArrayBuffer
|
|
31
31
|
? new Uint8Array(content)
|
|
32
32
|
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
33
33
|
const dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
|
|
34
34
|
if (blob.length < 12) {
|
|
35
35
|
callback('File to small to be a FIT file', undefined);
|
|
36
|
-
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
36
|
+
return;
|
|
39
37
|
}
|
|
40
38
|
const headerLength = blob[0];
|
|
41
39
|
if (headerLength !== 14 && headerLength !== 12) {
|
|
42
40
|
callback('Incorrect header size', undefined);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (blob.length < headerLength) {
|
|
44
|
+
callback('File to small to be a FIT file', undefined);
|
|
45
|
+
return;
|
|
46
46
|
}
|
|
47
47
|
let fileTypeString = '';
|
|
48
48
|
for (let i = 8; i < 12; i++) {
|
|
@@ -50,29 +50,33 @@ export default class FitParser {
|
|
|
50
50
|
}
|
|
51
51
|
if (fileTypeString !== '.FIT') {
|
|
52
52
|
callback('Missing \'.FIT\' in header', undefined);
|
|
53
|
-
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
53
|
+
return;
|
|
56
54
|
}
|
|
57
55
|
if (headerLength === 14 && !this.options.force) {
|
|
58
56
|
const crcHeader = blob[12] + (blob[13] << 8);
|
|
59
57
|
const crcHeaderCalc = calculateCRC(blob, 0, 12);
|
|
60
|
-
if (crcHeader !== crcHeaderCalc) {
|
|
61
|
-
|
|
62
|
-
// TODO: fix Header CRC check
|
|
58
|
+
if (crcHeader !== 0 && crcHeader !== crcHeaderCalc) {
|
|
59
|
+
callback('Header CRC mismatch', undefined);
|
|
63
60
|
return;
|
|
64
61
|
}
|
|
65
62
|
}
|
|
66
63
|
const protocolVersion = blob[1];
|
|
67
64
|
const profileVersion = blob[2] + (blob[3] << 8);
|
|
68
|
-
const dataLength =
|
|
65
|
+
const dataLength = dataView.getUint32(4, true);
|
|
69
66
|
const crcStart = dataLength + headerLength;
|
|
67
|
+
if (crcStart > blob.length) {
|
|
68
|
+
callback('File data exceeds input length', undefined);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!this.options.force && crcStart + 2 > blob.length) {
|
|
72
|
+
callback('File CRC missing', undefined);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
70
75
|
if (!this.options.force) {
|
|
71
76
|
const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
|
|
72
|
-
const crcFileCalc = calculateCRC(blob,
|
|
77
|
+
const crcFileCalc = calculateCRC(blob, 0, crcStart);
|
|
73
78
|
if (crcFile !== crcFileCalc) {
|
|
74
|
-
|
|
75
|
-
// TODO: fix File CRC check
|
|
79
|
+
callback('File CRC mismatch', undefined);
|
|
76
80
|
return;
|
|
77
81
|
}
|
|
78
82
|
}
|
|
@@ -80,6 +84,7 @@ export default class FitParser {
|
|
|
80
84
|
profileVersion,
|
|
81
85
|
protocolVersion,
|
|
82
86
|
};
|
|
87
|
+
const messages = {};
|
|
83
88
|
let sessions = [];
|
|
84
89
|
let laps = [];
|
|
85
90
|
const records = [];
|
|
@@ -102,6 +107,7 @@ export default class FitParser {
|
|
|
102
107
|
const tank_updates = [];
|
|
103
108
|
const tank_summaries = [];
|
|
104
109
|
const jumps = [];
|
|
110
|
+
const sets = [];
|
|
105
111
|
const splits = [];
|
|
106
112
|
const split_summaries = [];
|
|
107
113
|
const time_in_zone = [];
|
|
@@ -110,14 +116,20 @@ export default class FitParser {
|
|
|
110
116
|
let loopIndex = headerLength;
|
|
111
117
|
const messageTypes = [];
|
|
112
118
|
const developerFields = [];
|
|
119
|
+
const decoderState = {};
|
|
113
120
|
const isModeCascade = this.options.mode === 'cascade';
|
|
114
121
|
const isCascadeNeeded = isModeCascade || this.options.mode === 'both';
|
|
115
122
|
let startDate;
|
|
116
123
|
let lastStopTimestamp;
|
|
117
124
|
let pausedTime = 0;
|
|
118
125
|
while (loopIndex < crcStart) {
|
|
119
|
-
const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView);
|
|
126
|
+
const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState);
|
|
120
127
|
loopIndex = nextIndex;
|
|
128
|
+
if (messageType !== ''
|
|
129
|
+
&& messageType !== 'definition'
|
|
130
|
+
&& message !== undefined) {
|
|
131
|
+
((_a = messages[messageType]) !== null && _a !== void 0 ? _a : (messages[messageType] = [])).push(message);
|
|
132
|
+
}
|
|
121
133
|
switch (messageType) {
|
|
122
134
|
case 'lap':
|
|
123
135
|
laps.push(message);
|
|
@@ -205,6 +217,9 @@ export default class FitParser {
|
|
|
205
217
|
case 'jump':
|
|
206
218
|
jumps.push(message);
|
|
207
219
|
break;
|
|
220
|
+
case 'set':
|
|
221
|
+
sets.push(message);
|
|
222
|
+
break;
|
|
208
223
|
case 'split':
|
|
209
224
|
splits.push(message);
|
|
210
225
|
break;
|
|
@@ -245,15 +260,17 @@ export default class FitParser {
|
|
|
245
260
|
fitObj.time_in_zone = time_in_zone;
|
|
246
261
|
fitObj.activity_metrics = activity_metrics;
|
|
247
262
|
fitObj.user_metrics = user_metrics;
|
|
263
|
+
fitObj.messages = messages;
|
|
248
264
|
if (isCascadeNeeded) {
|
|
249
265
|
laps = mapDataIntoLap(laps, 'records', records);
|
|
250
266
|
laps = mapDataIntoLap(laps, 'lengths', lengths);
|
|
251
267
|
sessions = mapDataIntoSession(sessions, laps);
|
|
252
|
-
fitObj.activity = Object.assign(Object.assign({}, ((
|
|
268
|
+
fitObj.activity = Object.assign(Object.assign({}, ((_b = fitObj.activity) !== null && _b !== void 0 ? _b : {})), { // ugly but we assume the activity was parsed correctly with all other members correctly
|
|
253
269
|
sessions,
|
|
254
270
|
events,
|
|
255
271
|
hrv,
|
|
256
272
|
device_infos, developer_data_ids: applications, field_descriptions: fieldDescriptions, sports,
|
|
273
|
+
sets,
|
|
257
274
|
splits,
|
|
258
275
|
split_summaries });
|
|
259
276
|
}
|
|
@@ -267,6 +284,7 @@ export default class FitParser {
|
|
|
267
284
|
fitObj.developer_data_ids = applications;
|
|
268
285
|
fitObj.field_descriptions = fieldDescriptions;
|
|
269
286
|
fitObj.hrv = hrv;
|
|
287
|
+
fitObj.sets = sets;
|
|
270
288
|
}
|
|
271
289
|
callback(undefined, fitObj);
|
|
272
290
|
}
|
package/dist/fit.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { FitOptions, MesgNum } from './fit_types.js';
|
|
2
2
|
export interface FieldDefinition {
|
|
3
3
|
type: string | number;
|
|
4
|
+
rawType?: string | number;
|
|
4
5
|
fDefNo: number;
|
|
5
6
|
size: number;
|
|
7
|
+
array?: boolean;
|
|
6
8
|
endianAbility: boolean;
|
|
7
9
|
littleEndian: boolean;
|
|
8
10
|
baseTypeNo: number;
|
|
@@ -13,13 +15,17 @@ export interface FieldDefinition {
|
|
|
13
15
|
requiresBoundedDataView?: boolean;
|
|
14
16
|
developerDataIndex?: number;
|
|
15
17
|
isDeveloperField?: boolean;
|
|
18
|
+
aliases?: MessageObject[];
|
|
16
19
|
}
|
|
17
20
|
export interface MessageObject {
|
|
18
21
|
field: string;
|
|
19
22
|
type: string;
|
|
23
|
+
baseType?: string;
|
|
24
|
+
array?: boolean;
|
|
20
25
|
scale: number | null;
|
|
21
26
|
offset: number;
|
|
22
27
|
units: string;
|
|
28
|
+
aliases?: MessageObject[];
|
|
23
29
|
}
|
|
24
30
|
export interface Message {
|
|
25
31
|
name: MesgNum;
|
package/dist/fit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GARMIN_MESSAGES, GARMIN_TYPES } from './garmin_profile.generated.js';
|
|
1
2
|
const metersInOneKilometer = 1000;
|
|
2
3
|
const secondsInOneHour = 3600;
|
|
3
4
|
// according to https://en.wikipedia.org/wiki/Mile
|
|
@@ -8,7 +9,7 @@ const psiInOneBar = 14.5037738;
|
|
|
8
9
|
// which contains all the types and values
|
|
9
10
|
// i would wish for some kind of inference mechanism here but for now we leave it as any
|
|
10
11
|
// maybe zod could help here
|
|
11
|
-
|
|
12
|
+
const FIT_OVERRIDES = {
|
|
12
13
|
scConst: 180 / Math.pow(2, 31),
|
|
13
14
|
options: {
|
|
14
15
|
speedUnits: {
|
|
@@ -46,6 +47,11 @@ export const FIT = {
|
|
|
46
47
|
},
|
|
47
48
|
},
|
|
48
49
|
temperatureUnits: {
|
|
50
|
+
// canonical native temperature unit: degree Celsius [°C]
|
|
51
|
+
'celsius': {
|
|
52
|
+
multiplier: 1,
|
|
53
|
+
offset: 0,
|
|
54
|
+
},
|
|
49
55
|
// native temperature unit: degree Celsius [°C]
|
|
50
56
|
'°C': {
|
|
51
57
|
multiplier: 1,
|
|
@@ -54,7 +60,7 @@ export const FIT = {
|
|
|
54
60
|
// kelvin [K]
|
|
55
61
|
'kelvin': {
|
|
56
62
|
multiplier: 1,
|
|
57
|
-
offset:
|
|
63
|
+
offset: 273.15,
|
|
58
64
|
},
|
|
59
65
|
// degree fahrenheit [°F]
|
|
60
66
|
'fahrenheit': {
|
|
@@ -1794,20 +1800,6 @@ export const FIT = {
|
|
|
1794
1800
|
offset: 0,
|
|
1795
1801
|
units: 'percent',
|
|
1796
1802
|
},
|
|
1797
|
-
214: {
|
|
1798
|
-
field: 'avg_grit',
|
|
1799
|
-
type: 'float32',
|
|
1800
|
-
scale: null,
|
|
1801
|
-
offset: 0,
|
|
1802
|
-
units: '',
|
|
1803
|
-
},
|
|
1804
|
-
215: {
|
|
1805
|
-
field: 'avg_flow',
|
|
1806
|
-
type: 'float32',
|
|
1807
|
-
scale: null,
|
|
1808
|
-
offset: 0,
|
|
1809
|
-
units: '',
|
|
1810
|
-
},
|
|
1811
1803
|
140: {
|
|
1812
1804
|
field: 'recovery_advisor',
|
|
1813
1805
|
type: 'uint16', // Minutes?
|
|
@@ -4132,22 +4124,25 @@ export const FIT = {
|
|
|
4132
4124
|
},
|
|
4133
4125
|
2: {
|
|
4134
4126
|
field: 'distance',
|
|
4135
|
-
type: '
|
|
4136
|
-
|
|
4127
|
+
type: 'uint32',
|
|
4128
|
+
baseType: 'uint32',
|
|
4129
|
+
scale: 100,
|
|
4137
4130
|
offset: 0,
|
|
4138
4131
|
units: 'm',
|
|
4139
4132
|
},
|
|
4140
4133
|
3: {
|
|
4141
4134
|
field: 'cycles',
|
|
4142
|
-
type: '
|
|
4143
|
-
|
|
4135
|
+
type: 'uint32',
|
|
4136
|
+
baseType: 'uint32',
|
|
4137
|
+
scale: 2,
|
|
4144
4138
|
offset: 0,
|
|
4145
4139
|
units: 'cycles',
|
|
4146
4140
|
},
|
|
4147
4141
|
4: {
|
|
4148
4142
|
field: 'active_time',
|
|
4149
|
-
type: '
|
|
4150
|
-
|
|
4143
|
+
type: 'uint32',
|
|
4144
|
+
baseType: 'uint32',
|
|
4145
|
+
scale: 1000,
|
|
4151
4146
|
offset: 0,
|
|
4152
4147
|
units: 's',
|
|
4153
4148
|
},
|
|
@@ -4202,28 +4197,33 @@ export const FIT = {
|
|
|
4202
4197
|
},
|
|
4203
4198
|
12: {
|
|
4204
4199
|
field: 'temperature',
|
|
4205
|
-
type: '
|
|
4206
|
-
|
|
4200
|
+
type: 'sint16',
|
|
4201
|
+
baseType: 'sint16',
|
|
4202
|
+
scale: 100,
|
|
4207
4203
|
offset: 0,
|
|
4208
4204
|
units: 'C',
|
|
4209
4205
|
},
|
|
4210
4206
|
14: {
|
|
4211
4207
|
field: 'temperature_min',
|
|
4212
|
-
type: '
|
|
4213
|
-
|
|
4208
|
+
type: 'sint16',
|
|
4209
|
+
baseType: 'sint16',
|
|
4210
|
+
scale: 100,
|
|
4214
4211
|
offset: 0,
|
|
4215
4212
|
units: 'C',
|
|
4216
4213
|
},
|
|
4217
4214
|
15: {
|
|
4218
4215
|
field: 'temperature_max',
|
|
4219
|
-
type: '
|
|
4220
|
-
|
|
4216
|
+
type: 'sint16',
|
|
4217
|
+
baseType: 'sint16',
|
|
4218
|
+
scale: 100,
|
|
4221
4219
|
offset: 0,
|
|
4222
4220
|
units: 'C',
|
|
4223
4221
|
},
|
|
4224
4222
|
16: {
|
|
4225
4223
|
field: 'activity_time',
|
|
4226
|
-
type: '
|
|
4224
|
+
type: 'uint16',
|
|
4225
|
+
baseType: 'uint16',
|
|
4226
|
+
array: true,
|
|
4227
4227
|
scale: null,
|
|
4228
4228
|
offset: 0,
|
|
4229
4229
|
units: '',
|
|
@@ -4594,17 +4594,6 @@ export const FIT = {
|
|
|
4594
4594
|
units: '',
|
|
4595
4595
|
},
|
|
4596
4596
|
},
|
|
4597
|
-
108: {
|
|
4598
|
-
name: 'o_hr_settings',
|
|
4599
|
-
253: {
|
|
4600
|
-
field: 'timestamp',
|
|
4601
|
-
type: 'date_time',
|
|
4602
|
-
scale: null,
|
|
4603
|
-
offset: 0,
|
|
4604
|
-
units: '',
|
|
4605
|
-
},
|
|
4606
|
-
0: { field: 'enabled', type: 'byte', scale: null, offset: 0, units: '' },
|
|
4607
|
-
},
|
|
4608
4597
|
140: {
|
|
4609
4598
|
name: 'activity_metrics',
|
|
4610
4599
|
1: {
|
|
@@ -4739,6 +4728,86 @@ export const FIT = {
|
|
|
4739
4728
|
units: '',
|
|
4740
4729
|
},
|
|
4741
4730
|
},
|
|
4731
|
+
225: {
|
|
4732
|
+
name: 'set',
|
|
4733
|
+
254: {
|
|
4734
|
+
field: 'timestamp',
|
|
4735
|
+
type: 'date_time',
|
|
4736
|
+
scale: null,
|
|
4737
|
+
offset: 0,
|
|
4738
|
+
units: '',
|
|
4739
|
+
},
|
|
4740
|
+
0: {
|
|
4741
|
+
field: 'duration',
|
|
4742
|
+
type: 'uint32',
|
|
4743
|
+
scale: 1000,
|
|
4744
|
+
offset: 0,
|
|
4745
|
+
units: 's',
|
|
4746
|
+
},
|
|
4747
|
+
3: {
|
|
4748
|
+
field: 'repetitions',
|
|
4749
|
+
type: 'uint16',
|
|
4750
|
+
scale: null,
|
|
4751
|
+
offset: 0,
|
|
4752
|
+
units: '',
|
|
4753
|
+
},
|
|
4754
|
+
4: {
|
|
4755
|
+
field: 'weight',
|
|
4756
|
+
type: 'uint16',
|
|
4757
|
+
scale: 16,
|
|
4758
|
+
offset: 0,
|
|
4759
|
+
units: 'kg',
|
|
4760
|
+
},
|
|
4761
|
+
5: {
|
|
4762
|
+
field: 'set_type',
|
|
4763
|
+
type: 'set_type',
|
|
4764
|
+
scale: null,
|
|
4765
|
+
offset: 0,
|
|
4766
|
+
units: '',
|
|
4767
|
+
},
|
|
4768
|
+
6: {
|
|
4769
|
+
field: 'start_time',
|
|
4770
|
+
type: 'date_time',
|
|
4771
|
+
scale: null,
|
|
4772
|
+
offset: 0,
|
|
4773
|
+
units: '',
|
|
4774
|
+
},
|
|
4775
|
+
7: {
|
|
4776
|
+
field: 'category',
|
|
4777
|
+
type: 'exercise_category_array',
|
|
4778
|
+
scale: null,
|
|
4779
|
+
offset: 0,
|
|
4780
|
+
units: '',
|
|
4781
|
+
},
|
|
4782
|
+
8: {
|
|
4783
|
+
field: 'category_subtype',
|
|
4784
|
+
type: 'uint16_array',
|
|
4785
|
+
scale: null,
|
|
4786
|
+
offset: 0,
|
|
4787
|
+
units: '',
|
|
4788
|
+
},
|
|
4789
|
+
9: {
|
|
4790
|
+
field: 'weight_display_unit',
|
|
4791
|
+
type: 'fit_base_unit',
|
|
4792
|
+
scale: null,
|
|
4793
|
+
offset: 0,
|
|
4794
|
+
units: '',
|
|
4795
|
+
},
|
|
4796
|
+
10: {
|
|
4797
|
+
field: 'message_index',
|
|
4798
|
+
type: 'message_index',
|
|
4799
|
+
scale: null,
|
|
4800
|
+
offset: 0,
|
|
4801
|
+
units: '',
|
|
4802
|
+
},
|
|
4803
|
+
11: {
|
|
4804
|
+
field: 'wkt_step_index',
|
|
4805
|
+
type: 'message_index',
|
|
4806
|
+
scale: null,
|
|
4807
|
+
offset: 0,
|
|
4808
|
+
units: '',
|
|
4809
|
+
},
|
|
4810
|
+
},
|
|
4742
4811
|
227: {
|
|
4743
4812
|
name: 'stress_level',
|
|
4744
4813
|
0: {
|
|
@@ -5200,7 +5269,7 @@ export const FIT = {
|
|
|
5200
5269
|
units: 's',
|
|
5201
5270
|
},
|
|
5202
5271
|
4: {
|
|
5203
|
-
field: '
|
|
5272
|
+
field: 'time_in_cadence_zone',
|
|
5204
5273
|
type: 'uint32_array',
|
|
5205
5274
|
scale: 1000,
|
|
5206
5275
|
offset: 0,
|
|
@@ -5228,11 +5297,11 @@ export const FIT = {
|
|
|
5228
5297
|
units: 'm/s',
|
|
5229
5298
|
},
|
|
5230
5299
|
8: {
|
|
5231
|
-
field: '
|
|
5232
|
-
type: '
|
|
5300
|
+
field: 'cadence_zone_high_boundary',
|
|
5301
|
+
type: 'uint8_array',
|
|
5233
5302
|
scale: null,
|
|
5234
5303
|
offset: 0,
|
|
5235
|
-
units: '
|
|
5304
|
+
units: 'rpm',
|
|
5236
5305
|
},
|
|
5237
5306
|
9: {
|
|
5238
5307
|
field: 'power_zone_high_boundary',
|
|
@@ -9085,3 +9154,57 @@ export const FIT = {
|
|
|
9085
9154
|
},
|
|
9086
9155
|
},
|
|
9087
9156
|
};
|
|
9157
|
+
function equivalentProfileName(left, right) {
|
|
9158
|
+
return String(left).replace(/_/g, '').toLowerCase()
|
|
9159
|
+
=== String(right).replace(/_/g, '').toLowerCase();
|
|
9160
|
+
}
|
|
9161
|
+
function mergeMessages() {
|
|
9162
|
+
const messages = Object.assign({}, FIT_OVERRIDES.messages);
|
|
9163
|
+
Object.entries(GARMIN_MESSAGES).forEach(([messageId, generatedMessage]) => {
|
|
9164
|
+
const overrideMessage = FIT_OVERRIDES.messages[Number(messageId)];
|
|
9165
|
+
if (!overrideMessage) {
|
|
9166
|
+
messages[Number(messageId)] = generatedMessage;
|
|
9167
|
+
return;
|
|
9168
|
+
}
|
|
9169
|
+
const mergedMessage = Object.assign(Object.assign({}, overrideMessage), generatedMessage);
|
|
9170
|
+
Object.entries(generatedMessage).forEach(([fieldId, generatedField]) => {
|
|
9171
|
+
var _a;
|
|
9172
|
+
if (fieldId === 'name') {
|
|
9173
|
+
return;
|
|
9174
|
+
}
|
|
9175
|
+
const overrideField = overrideMessage[Number(fieldId)];
|
|
9176
|
+
const hasCompatibleName = overrideField
|
|
9177
|
+
&& equivalentProfileName(overrideField.field, generatedField.field);
|
|
9178
|
+
mergedMessage[Number(fieldId)] = hasCompatibleName
|
|
9179
|
+
? Object.assign(Object.assign(Object.assign({}, generatedField), overrideField), { baseType: generatedField.baseType, array: (_a = overrideField.array) !== null && _a !== void 0 ? _a : generatedField.array }) : Object.assign(Object.assign(Object.assign({}, overrideField), generatedField), { aliases: overrideField ? [overrideField] : undefined });
|
|
9180
|
+
});
|
|
9181
|
+
messages[Number(messageId)] = mergedMessage;
|
|
9182
|
+
});
|
|
9183
|
+
return messages;
|
|
9184
|
+
}
|
|
9185
|
+
function mergeTypes() {
|
|
9186
|
+
const typeNames = new Set([
|
|
9187
|
+
...Object.keys(GARMIN_TYPES),
|
|
9188
|
+
...Object.keys(FIT_OVERRIDES.types),
|
|
9189
|
+
]);
|
|
9190
|
+
return Object.fromEntries([...typeNames].map((typeName) => {
|
|
9191
|
+
var _a, _b;
|
|
9192
|
+
const generatedValues = (_a = GARMIN_TYPES[typeName]) !== null && _a !== void 0 ? _a : {};
|
|
9193
|
+
const overrideValues = (_b = FIT_OVERRIDES.types[typeName]) !== null && _b !== void 0 ? _b : {};
|
|
9194
|
+
const values = Object.assign({}, generatedValues);
|
|
9195
|
+
Object.entries(overrideValues).forEach(([valueId, overrideValue]) => {
|
|
9196
|
+
const generatedValue = generatedValues[Number(valueId)];
|
|
9197
|
+
if (generatedValue === undefined
|
|
9198
|
+
|| equivalentProfileName(overrideValue, generatedValue)) {
|
|
9199
|
+
values[Number(valueId)] = overrideValue;
|
|
9200
|
+
}
|
|
9201
|
+
});
|
|
9202
|
+
return [typeName, values];
|
|
9203
|
+
}));
|
|
9204
|
+
}
|
|
9205
|
+
export const FIT = {
|
|
9206
|
+
scConst: FIT_OVERRIDES.scConst,
|
|
9207
|
+
options: FIT_OVERRIDES.options,
|
|
9208
|
+
messages: mergeMessages(),
|
|
9209
|
+
types: mergeTypes(),
|
|
9210
|
+
};
|