fit-file-parser 1.9.1
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/.idea/dictionaries/dimtrioskanellopoulos.xml +7 -0
- package/.idea/easy-fit.iml +14 -0
- package/.idea/encodings.xml +4 -0
- package/.idea/inspectionProfiles/Project_Default.xml +62 -0
- package/.idea/misc.xml +7 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +589 -0
- package/CHANGELOG.md +38 -0
- package/CONTRIBUTORS.md +7 -0
- package/LICENSE +23 -0
- package/README.md +91 -0
- package/dist/binary.js +440 -0
- package/dist/fit-parser.js +266 -0
- package/dist/fit.js +4265 -0
- package/dist/messages.js +23 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Pierre Jacquier
|
|
4
|
+
http://pierrejacquier.com | @pierremtb
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# fit-file-parser
|
|
2
|
+
|
|
3
|
+
> Parse your .FIT files easily, directly from JS.
|
|
4
|
+
> Written in ES6. (Hope to change)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
$ npm install fit-file-parser --save
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## How to use
|
|
14
|
+
|
|
15
|
+
See in [examples](./examples) folder:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// Require the module
|
|
19
|
+
var FitParser = require('./../dist/fit-file-parser.js').default;
|
|
20
|
+
|
|
21
|
+
// Read a .FIT file
|
|
22
|
+
var fs = require('fs');
|
|
23
|
+
fs.readFile('./example.fit', function (err, content) {
|
|
24
|
+
|
|
25
|
+
// Create a FitParser instance (options argument is optional)
|
|
26
|
+
var fitParser = new FitParser({
|
|
27
|
+
force: true,
|
|
28
|
+
speedUnit: 'km/h',
|
|
29
|
+
lengthUnit: 'km',
|
|
30
|
+
temperatureUnit: 'kelvin',
|
|
31
|
+
elapsedRecordField: true,
|
|
32
|
+
mode: 'cascade',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Parse your file
|
|
36
|
+
fitParser.parse(content, function (error, data) {
|
|
37
|
+
|
|
38
|
+
// Handle result of parse method
|
|
39
|
+
if (error) {
|
|
40
|
+
console.log(error);
|
|
41
|
+
} else {
|
|
42
|
+
console.log(JSON.stringify(data));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API Documentation
|
|
51
|
+
### new FitParser(Object _options_)
|
|
52
|
+
Needed to create a new instance. _options_ is optional, and is used to customize the returned object.
|
|
53
|
+
|
|
54
|
+
Allowed properties :
|
|
55
|
+
- `mode`: String
|
|
56
|
+
- `cascade`: Returned object is organized as a tree, eg. each lap contains a `records` fields, that is an array of its records (**default**)
|
|
57
|
+
- `list`: Returned object is organized as lists of sessions, laps, records, etc..., without parent-child relation
|
|
58
|
+
- `both`: A mix of the two other modes, eg. `records` are available inside the root field as well as inside each laps
|
|
59
|
+
- `lengthUnit`: String
|
|
60
|
+
- `m`: Lengths are in meters (**default**)
|
|
61
|
+
- `km`: Lengths are in kilometers
|
|
62
|
+
- `mi`: Lengths are in miles
|
|
63
|
+
- `temperatureUnit`: String
|
|
64
|
+
- `celsius`:Temperatures are in °C (**default**)
|
|
65
|
+
- `kelvin`: Temperatures are in °K
|
|
66
|
+
- `fahrenheit`: Temperatures are in °F
|
|
67
|
+
- `speedUnit`: String
|
|
68
|
+
- `m/s`: Speeds are in meters per seconds (**default**)
|
|
69
|
+
- `km/h`: Speeds are in kilometers per hour
|
|
70
|
+
- `mph`: Speeds are in miles per hour
|
|
71
|
+
- `force`: Boolean
|
|
72
|
+
- `true`: Continues even if they are errors (**default for now**)
|
|
73
|
+
- `false`: Stops if an error occurs
|
|
74
|
+
- `elapsedRecordField`: Boolean
|
|
75
|
+
- `true`: Includes `elapsed_time`, containing the elapsed time in seconds since the first record, and `timer_time`, containing the time shown on the device, inside each `record` field
|
|
76
|
+
- `false` (**default**)
|
|
77
|
+
|
|
78
|
+
### fitParser.parse(Buffer _file_, Function _callback_)
|
|
79
|
+
_callback_ receives two arguments, the first as a error String, and the second as Object, result of parsing.
|
|
80
|
+
|
|
81
|
+
## Contributors
|
|
82
|
+
All started thanks to [Pierre Jacquier](https://github.com/pierremtb)
|
|
83
|
+
|
|
84
|
+
Big thanks to [Mikael Lofjärd](https://github.com/mlofjard) for [his early prototype](https://github.com/mlofjard/jsonfit).
|
|
85
|
+
See [CONTRIBUTORS](./CONTRIBUTORS.md).
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT license; see [LICENSE](./LICENSE).
|
|
90
|
+
|
|
91
|
+
(c) 2019 Dimitrios Kanellopoulos
|
package/dist/binary.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.addEndian = addEndian;
|
|
7
|
+
exports.readRecord = readRecord;
|
|
8
|
+
exports.getArrayBuffer = getArrayBuffer;
|
|
9
|
+
exports.calculateCRC = calculateCRC;
|
|
10
|
+
|
|
11
|
+
var _fit = require('./fit');
|
|
12
|
+
|
|
13
|
+
var _messages = require('./messages');
|
|
14
|
+
|
|
15
|
+
var _buffer = require('buffer');
|
|
16
|
+
|
|
17
|
+
function addEndian(littleEndian, bytes) {
|
|
18
|
+
var result = 0;
|
|
19
|
+
if (!littleEndian) bytes.reverse();
|
|
20
|
+
for (var i = 0; i < bytes.length; i++) {
|
|
21
|
+
result += bytes[i] << (i << 3) >>> 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var timestamp = 0;
|
|
28
|
+
var lastTimeOffset = 0;
|
|
29
|
+
var CompressedTimeMask = 31;
|
|
30
|
+
var CompressedLocalMesgNumMask = 0x60;
|
|
31
|
+
var CompressedHeaderMask = 0x80;
|
|
32
|
+
var GarminTimeOffset = 631065600000;
|
|
33
|
+
var monitoring_timestamp = 0;
|
|
34
|
+
|
|
35
|
+
function readData(blob, fDef, startIndex, options) {
|
|
36
|
+
if (fDef.endianAbility === true) {
|
|
37
|
+
var temp = [];
|
|
38
|
+
for (var i = 0; i < fDef.size; i++) {
|
|
39
|
+
temp.push(blob[startIndex + i]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var buffer = new Uint8Array(temp).buffer;
|
|
43
|
+
var dataView = new DataView(buffer);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
switch (fDef.type) {
|
|
47
|
+
case 'sint16':
|
|
48
|
+
return dataView.getInt16(0, fDef.littleEndian);
|
|
49
|
+
case 'uint16':
|
|
50
|
+
case 'uint16z':
|
|
51
|
+
return dataView.getUint16(0, fDef.littleEndian);
|
|
52
|
+
case 'sint32':
|
|
53
|
+
return dataView.getInt32(0, fDef.littleEndian);
|
|
54
|
+
case 'uint32':
|
|
55
|
+
case 'uint32z':
|
|
56
|
+
return dataView.getUint32(0, fDef.littleEndian);
|
|
57
|
+
case 'float32':
|
|
58
|
+
return dataView.getFloat32(0, fDef.littleEndian);
|
|
59
|
+
case 'float64':
|
|
60
|
+
return dataView.getFloat64(0, fDef.littleEndian);
|
|
61
|
+
case 'uint32_array':
|
|
62
|
+
var array32 = [];
|
|
63
|
+
for (var _i = 0; _i < fDef.size; _i += 4) {
|
|
64
|
+
array32.push(dataView.getUint32(_i, fDef.littleEndian));
|
|
65
|
+
}
|
|
66
|
+
return array32;
|
|
67
|
+
case 'uint16_array':
|
|
68
|
+
var array = [];
|
|
69
|
+
for (var _i2 = 0; _i2 < fDef.size; _i2 += 2) {
|
|
70
|
+
array.push(dataView.getUint16(_i2, fDef.littleEndian));
|
|
71
|
+
}
|
|
72
|
+
return array;
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (!options.force) {
|
|
76
|
+
throw e;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return addEndian(fDef.littleEndian, temp);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (fDef.type === 'string') {
|
|
84
|
+
var _temp = [];
|
|
85
|
+
for (var _i3 = 0; _i3 < fDef.size; _i3++) {
|
|
86
|
+
if (blob[startIndex + _i3]) {
|
|
87
|
+
_temp.push(blob[startIndex + _i3]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return new _buffer.Buffer.from(_temp).toString('utf-8');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (fDef.type === 'byte_array') {
|
|
94
|
+
var _temp2 = [];
|
|
95
|
+
for (var _i4 = 0; _i4 < fDef.size; _i4++) {
|
|
96
|
+
_temp2.push(blob[startIndex + _i4]);
|
|
97
|
+
}
|
|
98
|
+
return _temp2;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return blob[startIndex];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatByType(data, type, scale, offset) {
|
|
105
|
+
switch (type) {
|
|
106
|
+
case 'date_time':
|
|
107
|
+
case 'local_date_time':
|
|
108
|
+
return new Date(data * 1000 + GarminTimeOffset);
|
|
109
|
+
case 'sint32':
|
|
110
|
+
return data * _fit.FIT.scConst;
|
|
111
|
+
case 'uint8':
|
|
112
|
+
case 'sint16':
|
|
113
|
+
case 'uint32':
|
|
114
|
+
case 'uint16':
|
|
115
|
+
return scale ? data / scale + offset : data;
|
|
116
|
+
case 'uint32_array':
|
|
117
|
+
case 'uint16_array':
|
|
118
|
+
return data.map(function (dataItem) {
|
|
119
|
+
return scale ? dataItem / scale + offset : dataItem;
|
|
120
|
+
});
|
|
121
|
+
default:
|
|
122
|
+
if (!_fit.FIT.types[type]) {
|
|
123
|
+
return data;
|
|
124
|
+
}
|
|
125
|
+
// Quick check for a mask
|
|
126
|
+
var values = [];
|
|
127
|
+
for (var key in _fit.FIT.types[type]) {
|
|
128
|
+
if (_fit.FIT.types[type].hasOwnProperty(key)) {
|
|
129
|
+
values.push(_fit.FIT.types[type][key]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (values.indexOf('mask') === -1) {
|
|
133
|
+
return _fit.FIT.types[type][data];
|
|
134
|
+
}
|
|
135
|
+
var dataItem = {};
|
|
136
|
+
for (var key in _fit.FIT.types[type]) {
|
|
137
|
+
if (_fit.FIT.types[type].hasOwnProperty(key)) {
|
|
138
|
+
if (_fit.FIT.types[type][key] === 'mask') {
|
|
139
|
+
dataItem.value = data & key;
|
|
140
|
+
} else {
|
|
141
|
+
dataItem[_fit.FIT.types[type][key]] = !!((data & key) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return dataItem;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isInvalidValue(data, type) {
|
|
150
|
+
switch (type) {
|
|
151
|
+
case 'enum':
|
|
152
|
+
return data === 0xFF;
|
|
153
|
+
case 'sint8':
|
|
154
|
+
return data === 0x7F;
|
|
155
|
+
case 'uint8':
|
|
156
|
+
return data === 0xFF;
|
|
157
|
+
case 'sint16':
|
|
158
|
+
return data === 0x7FFF;
|
|
159
|
+
case 'uint16':
|
|
160
|
+
return data === 0xFFFF;
|
|
161
|
+
case 'sint32':
|
|
162
|
+
return data === 0x7FFFFFFF;
|
|
163
|
+
case 'uint32':
|
|
164
|
+
return data === 0xFFFFFFFF;
|
|
165
|
+
case 'string':
|
|
166
|
+
return data === 0x00;
|
|
167
|
+
case 'float32':
|
|
168
|
+
return data === 0xFFFFFFFF;
|
|
169
|
+
case 'float64':
|
|
170
|
+
return data === 0xFFFFFFFFFFFFFFFF;
|
|
171
|
+
case 'uint8z':
|
|
172
|
+
return data === 0x00;
|
|
173
|
+
case 'uint16z':
|
|
174
|
+
return data === 0x0000;
|
|
175
|
+
case 'uint32z':
|
|
176
|
+
return data === 0x000000;
|
|
177
|
+
case 'byte':
|
|
178
|
+
return data === 0xFF;
|
|
179
|
+
case 'sint64':
|
|
180
|
+
return data === 0x7FFFFFFFFFFFFFFF;
|
|
181
|
+
case 'uint64':
|
|
182
|
+
return data === 0xFFFFFFFFFFFFFFFF;
|
|
183
|
+
case 'uint64z':
|
|
184
|
+
return data === 0x0000000000000000;
|
|
185
|
+
default:
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function convertTo(data, unitsList, speedUnit) {
|
|
191
|
+
var unitObj = _fit.FIT.options[unitsList][speedUnit];
|
|
192
|
+
return unitObj ? data * unitObj.multiplier + unitObj.offset : data;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function applyOptions(data, field, options) {
|
|
196
|
+
switch (field) {
|
|
197
|
+
case 'speed':
|
|
198
|
+
case 'enhanced_speed':
|
|
199
|
+
case 'vertical_speed':
|
|
200
|
+
case 'avg_speed':
|
|
201
|
+
case 'max_speed':
|
|
202
|
+
case 'speed_1s':
|
|
203
|
+
case 'ball_speed':
|
|
204
|
+
case 'enhanced_avg_speed':
|
|
205
|
+
case 'enhanced_max_speed':
|
|
206
|
+
case 'avg_pos_vertical_speed':
|
|
207
|
+
case 'max_pos_vertical_speed':
|
|
208
|
+
case 'avg_neg_vertical_speed':
|
|
209
|
+
case 'max_neg_vertical_speed':
|
|
210
|
+
return convertTo(data, 'speedUnits', options.speedUnit);
|
|
211
|
+
case 'distance':
|
|
212
|
+
case 'total_distance':
|
|
213
|
+
case 'enhanced_avg_altitude':
|
|
214
|
+
case 'enhanced_min_altitude':
|
|
215
|
+
case 'enhanced_max_altitude':
|
|
216
|
+
case 'enhanced_altitude':
|
|
217
|
+
case 'height':
|
|
218
|
+
case 'odometer':
|
|
219
|
+
case 'avg_stroke_distance':
|
|
220
|
+
case 'min_altitude':
|
|
221
|
+
case 'avg_altitude':
|
|
222
|
+
case 'max_altitude':
|
|
223
|
+
case 'total_ascent':
|
|
224
|
+
case 'total_descent':
|
|
225
|
+
case 'altitude':
|
|
226
|
+
case 'cycle_length':
|
|
227
|
+
case 'auto_wheelsize':
|
|
228
|
+
case 'custom_wheelsize':
|
|
229
|
+
case 'gps_accuracy':
|
|
230
|
+
return convertTo(data, 'lengthUnits', options.lengthUnit);
|
|
231
|
+
case 'temperature':
|
|
232
|
+
case 'avg_temperature':
|
|
233
|
+
case 'max_temperature':
|
|
234
|
+
return convertTo(data, 'temperatureUnits', options.temperatureUnit);
|
|
235
|
+
default:
|
|
236
|
+
return data;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
|
|
241
|
+
var recordHeader = blob[startIndex];
|
|
242
|
+
var localMessageType = recordHeader & 15;
|
|
243
|
+
|
|
244
|
+
if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
|
|
245
|
+
//compressed timestamp
|
|
246
|
+
|
|
247
|
+
var timeoffset = recordHeader & CompressedTimeMask;
|
|
248
|
+
timestamp += timeoffset - lastTimeOffset & CompressedTimeMask;
|
|
249
|
+
lastTimeOffset = timeoffset;
|
|
250
|
+
|
|
251
|
+
localMessageType = (recordHeader & CompressedLocalMesgNumMask) >> 5;
|
|
252
|
+
} else if ((recordHeader & 64) === 64) {
|
|
253
|
+
// is definition message
|
|
254
|
+
// startIndex + 1 is reserved
|
|
255
|
+
|
|
256
|
+
var hasDeveloperData = (recordHeader & 32) === 32;
|
|
257
|
+
var lEnd = blob[startIndex + 2] === 0;
|
|
258
|
+
var numberOfFields = blob[startIndex + 5];
|
|
259
|
+
var numberOfDeveloperDataFields = hasDeveloperData ? blob[startIndex + 5 + numberOfFields * 3 + 1] : 0;
|
|
260
|
+
|
|
261
|
+
var mTypeDef = {
|
|
262
|
+
littleEndian: lEnd,
|
|
263
|
+
globalMessageNumber: addEndian(lEnd, [blob[startIndex + 3], blob[startIndex + 4]]),
|
|
264
|
+
numberOfFields: numberOfFields + numberOfDeveloperDataFields,
|
|
265
|
+
fieldDefs: []
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
var _message = (0, _messages.getFitMessage)(mTypeDef.globalMessageNumber);
|
|
269
|
+
|
|
270
|
+
for (var i = 0; i < numberOfFields; i++) {
|
|
271
|
+
var fDefIndex = startIndex + 6 + i * 3;
|
|
272
|
+
var baseType = blob[fDefIndex + 2];
|
|
273
|
+
|
|
274
|
+
var _message$getAttribute = _message.getAttributes(blob[fDefIndex]),
|
|
275
|
+
field = _message$getAttribute.field,
|
|
276
|
+
type = _message$getAttribute.type;
|
|
277
|
+
|
|
278
|
+
var fDef = {
|
|
279
|
+
type: type,
|
|
280
|
+
fDefNo: blob[fDefIndex],
|
|
281
|
+
size: blob[fDefIndex + 1],
|
|
282
|
+
endianAbility: (baseType & 128) === 128,
|
|
283
|
+
littleEndian: lEnd,
|
|
284
|
+
baseTypeNo: baseType & 15,
|
|
285
|
+
name: field,
|
|
286
|
+
dataType: (0, _messages.getFitMessageBaseType)(baseType & 15)
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
mTypeDef.fieldDefs.push(fDef);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// numberOfDeveloperDataFields = 0 so it wont crash here and wont loop
|
|
293
|
+
for (var _i5 = 0; _i5 < numberOfDeveloperDataFields; _i5++) {
|
|
294
|
+
// If we fail to parse then try catch
|
|
295
|
+
try {
|
|
296
|
+
var _fDefIndex = startIndex + 6 + numberOfFields * 3 + 1 + _i5 * 3;
|
|
297
|
+
|
|
298
|
+
var fieldNum = blob[_fDefIndex];
|
|
299
|
+
var size = blob[_fDefIndex + 1];
|
|
300
|
+
var devDataIndex = blob[_fDefIndex + 2];
|
|
301
|
+
|
|
302
|
+
var devDef = developerFields[devDataIndex][fieldNum];
|
|
303
|
+
|
|
304
|
+
var _baseType = devDef.fit_base_type_id;
|
|
305
|
+
|
|
306
|
+
var _fDef = {
|
|
307
|
+
type: _fit.FIT.types.fit_base_type[_baseType],
|
|
308
|
+
fDefNo: fieldNum,
|
|
309
|
+
size: size,
|
|
310
|
+
endianAbility: (_baseType & 128) === 128,
|
|
311
|
+
littleEndian: lEnd,
|
|
312
|
+
baseTypeNo: _baseType & 15,
|
|
313
|
+
name: devDef.field_name,
|
|
314
|
+
dataType: (0, _messages.getFitMessageBaseType)(_baseType & 15),
|
|
315
|
+
scale: devDef.scale || 1,
|
|
316
|
+
offset: devDef.offset || 0,
|
|
317
|
+
developerDataIndex: devDataIndex,
|
|
318
|
+
isDeveloperField: true
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
mTypeDef.fieldDefs.push(_fDef);
|
|
322
|
+
} catch (e) {
|
|
323
|
+
if (options.force) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
throw e;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
messageTypes[localMessageType] = mTypeDef;
|
|
331
|
+
|
|
332
|
+
var nextIndex = startIndex + 6 + mTypeDef.numberOfFields * 3;
|
|
333
|
+
var nextIndexWithDeveloperData = nextIndex + 1;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
messageType: 'definition',
|
|
337
|
+
nextIndex: hasDeveloperData ? nextIndexWithDeveloperData : nextIndex
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
var messageType = messageTypes[localMessageType] || messageTypes[0];
|
|
342
|
+
|
|
343
|
+
// TODO: handle compressed header ((recordHeader & 128) == 128)
|
|
344
|
+
|
|
345
|
+
// uncompressed header
|
|
346
|
+
var messageSize = 0;
|
|
347
|
+
var readDataFromIndex = startIndex + 1;
|
|
348
|
+
var fields = {};
|
|
349
|
+
var message = (0, _messages.getFitMessage)(messageType.globalMessageNumber);
|
|
350
|
+
|
|
351
|
+
for (var _i6 = 0; _i6 < messageType.fieldDefs.length; _i6++) {
|
|
352
|
+
var _fDef2 = messageType.fieldDefs[_i6];
|
|
353
|
+
var data = readData(blob, _fDef2, readDataFromIndex, options);
|
|
354
|
+
|
|
355
|
+
if (!isInvalidValue(data, _fDef2.type)) {
|
|
356
|
+
if (_fDef2.isDeveloperField) {
|
|
357
|
+
|
|
358
|
+
var field = _fDef2.name;
|
|
359
|
+
var type = _fDef2.type;
|
|
360
|
+
var scale = _fDef2.scale;
|
|
361
|
+
var offset = _fDef2.offset;
|
|
362
|
+
|
|
363
|
+
fields[_fDef2.name] = applyOptions(formatByType(data, type, scale, offset), field, options);
|
|
364
|
+
} else {
|
|
365
|
+
var _message$getAttribute2 = message.getAttributes(_fDef2.fDefNo),
|
|
366
|
+
_field = _message$getAttribute2.field,
|
|
367
|
+
_type = _message$getAttribute2.type,
|
|
368
|
+
_scale = _message$getAttribute2.scale,
|
|
369
|
+
_offset = _message$getAttribute2.offset;
|
|
370
|
+
|
|
371
|
+
if (_field !== 'unknown' && _field !== '' && _field !== undefined) {
|
|
372
|
+
fields[_field] = applyOptions(formatByType(data, _type, _scale, _offset), _field, options);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (message.name === 'record' && options.elapsedRecordField) {
|
|
377
|
+
fields.elapsed_time = (fields.timestamp - startDate) / 1000;
|
|
378
|
+
fields.timer_time = fields.elapsed_time - pausedTime;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
readDataFromIndex += _fDef2.size;
|
|
383
|
+
messageSize += _fDef2.size;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (message.name === 'field_description') {
|
|
387
|
+
developerFields[fields.developer_data_index] = developerFields[fields.developer_data_index] || [];
|
|
388
|
+
developerFields[fields.developer_data_index][fields.field_definition_number] = fields;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (message.name === 'monitoring') {
|
|
392
|
+
//we need to keep the raw timestamp value so we can calculate subsequent timestamp16 fields
|
|
393
|
+
if (fields.timestamp) {
|
|
394
|
+
monitoring_timestamp = fields.timestamp;
|
|
395
|
+
fields.timestamp = new Date(fields.timestamp * 1000 + GarminTimeOffset);
|
|
396
|
+
}
|
|
397
|
+
if (fields.timestamp16 && !fields.timestamp) {
|
|
398
|
+
monitoring_timestamp += fields.timestamp16 - (monitoring_timestamp & 0xFFFF) & 0xFFFF;
|
|
399
|
+
//fields.timestamp = monitoring_timestamp;
|
|
400
|
+
fields.timestamp = new Date(monitoring_timestamp * 1000 + GarminTimeOffset);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
var result = {
|
|
405
|
+
messageType: message.name,
|
|
406
|
+
nextIndex: startIndex + messageSize + 1,
|
|
407
|
+
message: fields
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function getArrayBuffer(buffer) {
|
|
414
|
+
if (buffer instanceof ArrayBuffer) {
|
|
415
|
+
return buffer;
|
|
416
|
+
}
|
|
417
|
+
var ab = new ArrayBuffer(buffer.length);
|
|
418
|
+
var view = new Uint8Array(ab);
|
|
419
|
+
for (var i = 0; i < buffer.length; ++i) {
|
|
420
|
+
view[i] = buffer[i];
|
|
421
|
+
}
|
|
422
|
+
return ab;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function calculateCRC(blob, start, end) {
|
|
426
|
+
var crcTable = [0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400];
|
|
427
|
+
|
|
428
|
+
var crc = 0;
|
|
429
|
+
for (var i = start; i < end; i++) {
|
|
430
|
+
var byteVal = blob[i];
|
|
431
|
+
var tmp = crcTable[crc & 0xF];
|
|
432
|
+
crc = crc >> 4 & 0x0FFF;
|
|
433
|
+
crc = crc ^ tmp ^ crcTable[byteVal & 0xF];
|
|
434
|
+
tmp = crcTable[crc & 0xF];
|
|
435
|
+
crc = crc >> 4 & 0x0FFF;
|
|
436
|
+
crc = crc ^ tmp ^ crcTable[byteVal >> 4 & 0xF];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return crc;
|
|
440
|
+
}
|