fit_js 0.94.5 → 0.94.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +37 -0
- data/Rakefile +4 -0
- data/app/.DS_Store +0 -0
- data/app/assets/.DS_Store +0 -0
- data/app/assets/javascripts/accumulator.js +48 -0
- data/app/assets/javascripts/bit-stream.js +85 -0
- data/app/assets/javascripts/crc-calculator.js +56 -0
- data/app/assets/javascripts/decoder.js +737 -0
- data/app/assets/javascripts/fit.js +95 -0
- data/app/assets/javascripts/fitjs.js +11 -0
- data/app/assets/javascripts/index.js +18 -0
- data/app/assets/javascripts/profile.js +21386 -0
- data/app/assets/javascripts/stream.js +250 -0
- data/app/assets/javascripts/utils-hr-mesg.js +175 -0
- data/app/assets/javascripts/utils-internal.js +35 -0
- data/app/assets/javascripts/utils.js +31 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/fit_js.gemspec +27 -0
- data/lib/fitjs/rails/version.rb +5 -0
- data/sig/fitjs.rbs +4 -0
- metadata +46 -11
@@ -0,0 +1,250 @@
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
2
|
+
// Copyright 2022 Garmin International, Inc.
|
3
|
+
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
4
|
+
// may not use this file except in compliance with the Flexible and Interoperable Data
|
5
|
+
// Transfer (FIT) Protocol License.
|
6
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
7
|
+
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
8
|
+
// Profile Version = 21.94Release
|
9
|
+
// Tag = production/akw/21.94.00-0-g0f668193
|
10
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
11
|
+
|
12
|
+
|
13
|
+
import FIT from "./fit.js";
|
14
|
+
import UtilsInternal from "./utils-internal.js";
|
15
|
+
|
16
|
+
class Stream {
|
17
|
+
static LITTLE_ENDIAN = true;
|
18
|
+
static BIG_ENDIAN = false;
|
19
|
+
|
20
|
+
#position = 0;
|
21
|
+
#arrayBuffer = null;
|
22
|
+
#textDecoder = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true });
|
23
|
+
#crcCalculator = null;
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Convenience method for creating a Stream from a byte array
|
27
|
+
* @param {Array<number>} data An array of bytes
|
28
|
+
* @returns {Stream} A new Stream object
|
29
|
+
* @static
|
30
|
+
*/
|
31
|
+
static fromByteArray(data) {
|
32
|
+
const buf = new Uint8Array(data);
|
33
|
+
return this.fromArrayBuffer(buf.buffer);
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Convenience method for creating a Stream from a Node Buffer
|
38
|
+
* @param {Buffer} buffer - Node Buffer of bytes
|
39
|
+
* @returns {Stream} A new Stream object
|
40
|
+
* @static
|
41
|
+
*/
|
42
|
+
static fromBuffer(buffer) {
|
43
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
44
|
+
return this.fromArrayBuffer(arrayBuffer);
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Convenience method for creating a Stream from an ArrayBuffer
|
49
|
+
* @param {ArrayBuffer} arrayBuffer - An ArrayBuffer of bytes
|
50
|
+
* @returns {Stream} A new Stream object
|
51
|
+
* @static
|
52
|
+
*/
|
53
|
+
static fromArrayBuffer(arrayBuffer) {
|
54
|
+
const stream = new Stream(arrayBuffer);
|
55
|
+
return stream;
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Creates a Stream containing a FIT file
|
60
|
+
* @constructor
|
61
|
+
* @param {ArrayBuffer} stream - ArrayBuffer containing a FIT file
|
62
|
+
*/
|
63
|
+
constructor(arrayBuffer) {
|
64
|
+
this.#position = 0;
|
65
|
+
this.#arrayBuffer = arrayBuffer;
|
66
|
+
}
|
67
|
+
|
68
|
+
get length() {
|
69
|
+
return this.#arrayBuffer.byteLength;
|
70
|
+
}
|
71
|
+
|
72
|
+
get bytesRead() {
|
73
|
+
return this.#position;
|
74
|
+
}
|
75
|
+
|
76
|
+
get position() {
|
77
|
+
return this.#position;
|
78
|
+
}
|
79
|
+
|
80
|
+
get crcCalculator() {
|
81
|
+
return this.#crcCalculator;
|
82
|
+
}
|
83
|
+
|
84
|
+
set crcCalculator(crcCalculator) {
|
85
|
+
this.#crcCalculator = crcCalculator;
|
86
|
+
}
|
87
|
+
|
88
|
+
reset() {
|
89
|
+
this.seek(0);
|
90
|
+
}
|
91
|
+
|
92
|
+
seek(position) {
|
93
|
+
this.#position = position;
|
94
|
+
}
|
95
|
+
|
96
|
+
slice(begin, end) {
|
97
|
+
return this.#arrayBuffer.slice(begin, end);
|
98
|
+
}
|
99
|
+
|
100
|
+
peekByte() {
|
101
|
+
const arrayBuffer = this.#arrayBuffer.slice(this.#position, this.#position + 1);
|
102
|
+
const dataView = new DataView(arrayBuffer);
|
103
|
+
return dataView.getUint8(0);
|
104
|
+
}
|
105
|
+
|
106
|
+
readByte() {
|
107
|
+
return this.readUInt8();
|
108
|
+
}
|
109
|
+
|
110
|
+
readBytes(size) {
|
111
|
+
if (this.#position + size > this.#arrayBuffer.byteLength) {
|
112
|
+
throw Error(`FIT Runtime Error end of stream at byte ${this.#position}`);
|
113
|
+
}
|
114
|
+
|
115
|
+
const bytes = this.#arrayBuffer.slice(this.#position, this.#position + size);
|
116
|
+
this.#position += size;
|
117
|
+
|
118
|
+
this.#crcCalculator?.addBytes(new Uint8Array(bytes), 0, size);
|
119
|
+
|
120
|
+
return bytes;
|
121
|
+
}
|
122
|
+
|
123
|
+
readUInt8() {
|
124
|
+
return this.readValue(FIT.BaseType.UINT8, 1);
|
125
|
+
}
|
126
|
+
|
127
|
+
readInt8() {
|
128
|
+
return this.readValue(FIT.BaseType.SINT8, 1);
|
129
|
+
}
|
130
|
+
|
131
|
+
readUInt16(opts) {
|
132
|
+
return this.readValue(FIT.BaseType.UINT16, 2, { convertInvalidToNull: false, ...opts });
|
133
|
+
}
|
134
|
+
|
135
|
+
readInt16(opts) {
|
136
|
+
return this.readValue(FIT.BaseType.SINT16, 2, { convertInvalidToNull: false, ...opts });
|
137
|
+
}
|
138
|
+
|
139
|
+
readUInt32(opts) {
|
140
|
+
return this.readValue(FIT.BaseType.UINT32, 4, { convertInvalidToNull: false, ...opts });
|
141
|
+
}
|
142
|
+
|
143
|
+
readInt32(opts) {
|
144
|
+
return this.readValue(FIT.BaseType.SINT32, 4, { convertInvalidToNull: false, ...opts });
|
145
|
+
}
|
146
|
+
|
147
|
+
readUInt64(opts) {
|
148
|
+
return this.readValue(FIT.BaseType.UINT64, 8, { convertInvalidToNull: false, ...opts });
|
149
|
+
}
|
150
|
+
|
151
|
+
readInt64(opts) {
|
152
|
+
return this.readValue(FIT.BaseType.SINT64, 8, { convertInvalidToNull: false, ...opts });
|
153
|
+
}
|
154
|
+
|
155
|
+
readFloat32(opts) {
|
156
|
+
return this.readValue(FIT.BaseType.FLOAT32, 4, { convertInvalidToNull: false, ...opts });
|
157
|
+
}
|
158
|
+
|
159
|
+
readFloat64(opts) {
|
160
|
+
return this.readValue(FIT.BaseType.FLOAT64, 8, { convertInvalidToNull: false, ...opts });
|
161
|
+
}
|
162
|
+
|
163
|
+
readString(strlen) {
|
164
|
+
return this.readValue(FIT.BaseType.STRING, strlen);
|
165
|
+
}
|
166
|
+
|
167
|
+
readValue(baseType, size, { endianness = Stream.LITTLE_ENDIAN, convertInvalidToNull = true } = {}) {
|
168
|
+
const baseTypeSize = FIT.BaseTypeDefinitions[baseType].size;
|
169
|
+
const baseTypeInvalid = FIT.BaseTypeDefinitions[baseType].invalid;
|
170
|
+
|
171
|
+
const arrayBuffer = this.readBytes(size);
|
172
|
+
const count = size / baseTypeSize;
|
173
|
+
|
174
|
+
if (baseType === FIT.BaseType.STRING) {
|
175
|
+
const string = this.#textDecoder.decode(arrayBuffer).replace(/\uFFFD/g, "");
|
176
|
+
const strings = string.split('\0');
|
177
|
+
|
178
|
+
while (strings[strings.length - 1] === "") {
|
179
|
+
strings.pop();
|
180
|
+
}
|
181
|
+
|
182
|
+
if (strings.length === 0) {
|
183
|
+
return null;
|
184
|
+
}
|
185
|
+
|
186
|
+
return strings.length === 1 ? strings[0] : strings;
|
187
|
+
}
|
188
|
+
|
189
|
+
const dataView = new DataView(arrayBuffer);
|
190
|
+
let values = [];
|
191
|
+
|
192
|
+
for (let i = 0; i < count; i++) {
|
193
|
+
|
194
|
+
switch (baseType) {
|
195
|
+
case FIT.BaseType.BYTE:
|
196
|
+
case FIT.BaseType.ENUM:
|
197
|
+
case FIT.BaseType.UINT8:
|
198
|
+
case FIT.BaseType.UINT8Z:
|
199
|
+
values.push(dataView.getUint8(i * baseTypeSize));
|
200
|
+
break;
|
201
|
+
|
202
|
+
case FIT.BaseType.SINT8:
|
203
|
+
values.push(dataView.getInt8(i * baseTypeSize));
|
204
|
+
break;
|
205
|
+
|
206
|
+
case FIT.BaseType.UINT16:
|
207
|
+
case FIT.BaseType.UINT16Z:
|
208
|
+
values.push(dataView.getUint16(i * baseTypeSize, endianness));
|
209
|
+
break;
|
210
|
+
|
211
|
+
case FIT.BaseType.SINT16:
|
212
|
+
values.push(dataView.getInt16(i * baseTypeSize, endianness));
|
213
|
+
break;
|
214
|
+
|
215
|
+
case FIT.BaseType.UINT32:
|
216
|
+
case FIT.BaseType.UINT32Z:
|
217
|
+
values.push(dataView.getUint32(i * baseTypeSize, endianness));
|
218
|
+
break;
|
219
|
+
|
220
|
+
case FIT.BaseType.SINT32:
|
221
|
+
values.push(dataView.getInt32(i * baseTypeSize, endianness));
|
222
|
+
break;
|
223
|
+
|
224
|
+
case FIT.BaseType.UINT64:
|
225
|
+
case FIT.BaseType.UINT64Z:
|
226
|
+
values.push(dataView.getBigUint64(i * baseTypeSize, endianness));
|
227
|
+
break;
|
228
|
+
case FIT.BaseType.SINT64:
|
229
|
+
values.push(dataView.getBigInt64(i * baseTypeSize, endianness));
|
230
|
+
break;
|
231
|
+
|
232
|
+
case FIT.BaseType.FLOAT32:
|
233
|
+
values.push(dataView.getFloat32(i * baseTypeSize, endianness));
|
234
|
+
break;
|
235
|
+
|
236
|
+
case FIT.BaseType.FLOAT64:
|
237
|
+
values.push(dataView.getFloat64(i * baseTypeSize, endianness));
|
238
|
+
break;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
if (convertInvalidToNull) {
|
243
|
+
values = values.map(value => value === baseTypeInvalid ? null : value);
|
244
|
+
}
|
245
|
+
|
246
|
+
return UtilsInternal.sanitizeValues(values);
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
export default Stream;
|
@@ -0,0 +1,175 @@
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
2
|
+
// Copyright 2022 Garmin International, Inc.
|
3
|
+
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
4
|
+
// may not use this file except in compliance with the Flexible and Interoperable Data
|
5
|
+
// Transfer (FIT) Protocol License.
|
6
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
7
|
+
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
8
|
+
// Profile Version = 21.94Release
|
9
|
+
// Tag = production/akw/21.94.00-0-g0f668193
|
10
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
11
|
+
|
12
|
+
|
13
|
+
import Utils from "./utils.js";
|
14
|
+
|
15
|
+
const mergeHeartRates = (hrMesgs, recordMesgs) => {
|
16
|
+
|
17
|
+
if (hrMesgs == null || recordMesgs == null ||
|
18
|
+
hrMesgs.length == 0 || recordMesgs.length == 0) {
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
|
22
|
+
const heartrates = expandHeartRates(hrMesgs);
|
23
|
+
|
24
|
+
let heartrateIndex = 0;
|
25
|
+
let recordRangeStartTime = null;
|
26
|
+
|
27
|
+
for (let i = 0; i < recordMesgs.length; ++i) {
|
28
|
+
const recordMesg = recordMesgs[i];
|
29
|
+
|
30
|
+
let hrSum = 0;
|
31
|
+
let hrSumCount = 0;
|
32
|
+
|
33
|
+
const recordRangeEndTime = secondsSinceFitEpoch(recordMesg.timestamp);
|
34
|
+
|
35
|
+
if (recordRangeStartTime == null) {
|
36
|
+
recordRangeStartTime = recordRangeEndTime;
|
37
|
+
}
|
38
|
+
|
39
|
+
if (recordRangeStartTime === recordRangeEndTime) {
|
40
|
+
recordRangeStartTime--;
|
41
|
+
heartrateIndex = (heartrateIndex >= 1) ? heartrateIndex - 1 : 0;
|
42
|
+
}
|
43
|
+
|
44
|
+
let findingInRangeHrMesgs = true;
|
45
|
+
while (findingInRangeHrMesgs && (heartrateIndex < heartrates.length)) {
|
46
|
+
|
47
|
+
const heartrate = heartrates[heartrateIndex];
|
48
|
+
|
49
|
+
// Check if the heartrate timestamp is gt record start time
|
50
|
+
// and if the heartrate timestamp is lte to record end time
|
51
|
+
if (heartrate.timestamp > recordRangeStartTime
|
52
|
+
&& heartrate.timestamp <= recordRangeEndTime) {
|
53
|
+
hrSum += heartrate.heartRate;
|
54
|
+
hrSumCount++;
|
55
|
+
}
|
56
|
+
// Check if the heartrate timestamp exceeds the record time
|
57
|
+
else if (heartrate.timestamp > recordRangeEndTime) {
|
58
|
+
findingInRangeHrMesgs = false;
|
59
|
+
|
60
|
+
if (hrSumCount > 0) {
|
61
|
+
// Update record's heart rate value
|
62
|
+
const avgHR = Math.round(hrSum / hrSumCount);
|
63
|
+
recordMesg.heartRate = avgHR;
|
64
|
+
|
65
|
+
}
|
66
|
+
// Reset HR average accumulators
|
67
|
+
hrSum = 0;
|
68
|
+
hrSumCount = 0;
|
69
|
+
|
70
|
+
recordRangeStartTime = recordRangeEndTime;
|
71
|
+
|
72
|
+
// Breaks out of findingInRangeHrMesgs while loop w/o incrementing heartrateIndex
|
73
|
+
break;
|
74
|
+
}
|
75
|
+
|
76
|
+
heartrateIndex++;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
const expandHeartRates = (hrMesgs) => {
|
82
|
+
const GAP_INCREMENT_MILLISECONDS = 250;
|
83
|
+
const GAP_INCREMENT_SECONDS = GAP_INCREMENT_MILLISECONDS / 1000.0;
|
84
|
+
const GAP_MAX_MILLISECONDS = 5000;
|
85
|
+
const GAP_MAX_STEPS = GAP_MAX_MILLISECONDS / GAP_INCREMENT_MILLISECONDS;
|
86
|
+
|
87
|
+
if (hrMesgs == null || hrMesgs.length == 0) {
|
88
|
+
return [];
|
89
|
+
}
|
90
|
+
|
91
|
+
let anchorEventTimestamp = 0.0;
|
92
|
+
let anchorTimestamp = null;
|
93
|
+
|
94
|
+
const heartrates = [];
|
95
|
+
hrMesgs.forEach(hrMesg => {
|
96
|
+
if (hrMesg == null) {
|
97
|
+
throwError("HR mesg must not be null");
|
98
|
+
}
|
99
|
+
|
100
|
+
const eventTimestamps = Array.isArray(hrMesg.eventTimestamp) ? hrMesg.eventTimestamp : [hrMesg.eventTimestamp];
|
101
|
+
const filteredBpms = Array.isArray(hrMesg.filteredBpm) ? hrMesg.filteredBpm : [hrMesg.filteredBpm];
|
102
|
+
|
103
|
+
// Update HR timestamp anchor, if present
|
104
|
+
if (hrMesg.timestamp != null) {
|
105
|
+
anchorTimestamp = secondsSinceFitEpoch(hrMesg.timestamp);
|
106
|
+
|
107
|
+
if (hrMesg.fractionalTimestamp != null) {
|
108
|
+
anchorTimestamp += hrMesg.fractionalTimestamp;
|
109
|
+
}
|
110
|
+
|
111
|
+
if (eventTimestamps.length == 1) {
|
112
|
+
anchorEventTimestamp = eventTimestamps[0];
|
113
|
+
} else {
|
114
|
+
throwError("anchor HR mesg must have 1 event_timestamp");
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
if (anchorTimestamp == null || anchorEventTimestamp == null) {
|
119
|
+
// We cannot process any HR messages if we have not received a timestamp anchor
|
120
|
+
throwError("no anchor timestamp received in a HR mesg before delta HR mesgs");
|
121
|
+
} else if (eventTimestamps.length != filteredBpms.length) {
|
122
|
+
throwError("HR mesg with mismatching event timestamp and filtered bpm");
|
123
|
+
}
|
124
|
+
|
125
|
+
for (let i = 0; i < eventTimestamps.length; i++) {
|
126
|
+
let eventTimestamp = eventTimestamps[i];
|
127
|
+
|
128
|
+
// Check to see if the event timestamp rolled over
|
129
|
+
if (eventTimestamp < anchorEventTimestamp) {
|
130
|
+
if ((anchorEventTimestamp - eventTimestamp) > (0x400000)) {
|
131
|
+
eventTimestamp += (0x400000);
|
132
|
+
} else {
|
133
|
+
throwError("anchor event_timestamp is greater than subsequent event_timestamp. This does not allow for correct delta calculation.");
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
const currentHr = { timestamp: anchorTimestamp, heartRate: filteredBpms[i] };
|
138
|
+
currentHr.timestamp += (eventTimestamp - anchorEventTimestamp);
|
139
|
+
|
140
|
+
// Carry the previous HR value forward across the gap to the current
|
141
|
+
// HR value for up to 5 Seconds (5000ms) in 250ms increments
|
142
|
+
if (heartrates.length > 0) {
|
143
|
+
const previousHR = heartrates[heartrates.length - 1];
|
144
|
+
let gapInMilliseconds = Math.abs(currentHr.timestamp - previousHR.timestamp) * 1000;
|
145
|
+
let step = 1;
|
146
|
+
while (gapInMilliseconds > GAP_INCREMENT_MILLISECONDS && step <= GAP_MAX_STEPS) {
|
147
|
+
const gapHR = { timestamp: previousHR.timestamp, heartRate: previousHR.heartRate };
|
148
|
+
gapHR.timestamp += (GAP_INCREMENT_SECONDS * step);
|
149
|
+
heartrates.push(gapHR);
|
150
|
+
|
151
|
+
gapInMilliseconds -= GAP_INCREMENT_MILLISECONDS;
|
152
|
+
step++;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
heartrates.push(currentHr);
|
157
|
+
}
|
158
|
+
});
|
159
|
+
|
160
|
+
return heartrates;
|
161
|
+
}
|
162
|
+
|
163
|
+
const secondsSinceFitEpoch = (timestamp) => {
|
164
|
+
if (timestamp instanceof Date) {
|
165
|
+
return (timestamp.getTime() - Utils.FIT_EPOCH_MS) / 1000;
|
166
|
+
}
|
167
|
+
|
168
|
+
return timestamp;
|
169
|
+
}
|
170
|
+
|
171
|
+
const throwError = (error = "") => {
|
172
|
+
throw Error(`FIT Runtime Error ${error}`.trimEnd());
|
173
|
+
}
|
174
|
+
|
175
|
+
export default { mergeHeartRates, expandHeartRates };
|
@@ -0,0 +1,35 @@
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
2
|
+
// Copyright 2022 Garmin International, Inc.
|
3
|
+
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
4
|
+
// may not use this file except in compliance with the Flexible and Interoperable Data
|
5
|
+
// Transfer (FIT) Protocol License.
|
6
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
7
|
+
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
8
|
+
// Profile Version = 21.94Release
|
9
|
+
// Tag = production/akw/21.94.00-0-g0f668193
|
10
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
11
|
+
|
12
|
+
|
13
|
+
const sanitizeValues = (values) => {
|
14
|
+
if (onlyNullValues(values)) {
|
15
|
+
return null;
|
16
|
+
}
|
17
|
+
|
18
|
+
return values.length === 1 ? values[0] : values;
|
19
|
+
}
|
20
|
+
|
21
|
+
const onlyNullValues = (values) => values.reduce((state, value) => value != null ? false : state, true);
|
22
|
+
|
23
|
+
const onlyInvalidValues = (rawFieldValue, invalidValue) => {
|
24
|
+
if (Array.isArray(rawFieldValue)) {
|
25
|
+
return rawFieldValue.reduce((state, value) => value != invalidValue ? false : state, true);
|
26
|
+
}
|
27
|
+
|
28
|
+
return rawFieldValue === invalidValue;
|
29
|
+
}
|
30
|
+
|
31
|
+
export default {
|
32
|
+
sanitizeValues,
|
33
|
+
onlyNullValues,
|
34
|
+
onlyInvalidValues
|
35
|
+
};
|
@@ -0,0 +1,31 @@
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
2
|
+
// Copyright 2022 Garmin International, Inc.
|
3
|
+
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
4
|
+
// may not use this file except in compliance with the Flexible and Interoperable Data
|
5
|
+
// Transfer (FIT) Protocol License.
|
6
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
7
|
+
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
8
|
+
// Profile Version = 21.94Release
|
9
|
+
// Tag = production/akw/21.94.00-0-g0f668193
|
10
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
11
|
+
|
12
|
+
|
13
|
+
/**
|
14
|
+
* The millisecond offset between UNIX and FIT Epochs (631065600000).
|
15
|
+
* @const {number}
|
16
|
+
*/
|
17
|
+
const FIT_EPOCH_MS = 631065600000;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Convert a FIT DateTime to a JavaScript Date
|
21
|
+
* @param {number} datetime - Seconds since FIT EPOCH
|
22
|
+
* @returns {Date} A JavaScript Date object
|
23
|
+
*/
|
24
|
+
const convertDateTimeToDate = (datetime) => {
|
25
|
+
return new Date((datetime ?? 0) * 1000 + FIT_EPOCH_MS);
|
26
|
+
};
|
27
|
+
|
28
|
+
export default {
|
29
|
+
FIT_EPOCH_MS,
|
30
|
+
convertDateTimeToDate,
|
31
|
+
};
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "fitjs"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/fit_js.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "fitjs/rails/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "fit_js"
|
7
|
+
s.version = Fitjs::Rails::VERSION
|
8
|
+
s.summary = "Javascript Wrapper for FIT Files"
|
9
|
+
s.description = "The javascript library for the Garmin FIT file format"
|
10
|
+
s.authors = ["Justin Dunn"]
|
11
|
+
s.email = "axeouse@gmail.com"
|
12
|
+
s.files = ["lib", "app/assets/javascript"]
|
13
|
+
s.homepage =
|
14
|
+
"https://rubygems.org/gems/fit_js"
|
15
|
+
s.license = "MIT"
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
s.bindir = "exe"
|
22
|
+
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
|
25
|
+
s.add_development_dependency "bundler", "~> 1.16"
|
26
|
+
s.add_development_dependency "rake", "~> 10.0"
|
27
|
+
end
|
data/sig/fitjs.rbs
ADDED
metadata
CHANGED
@@ -1,42 +1,77 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fit_js
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.94.
|
4
|
+
version: 0.94.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Dunn
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2023-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 0.94.5
|
23
|
-
type: :runtime
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
24
|
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
30
|
-
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
31
39
|
- !ruby/object:Gem::Version
|
32
|
-
version: 0
|
40
|
+
version: '10.0'
|
33
41
|
description: The javascript library for the Garmin FIT file format
|
34
42
|
email: axeouse@gmail.com
|
35
43
|
executables: []
|
36
44
|
extensions: []
|
37
45
|
extra_rdoc_files: []
|
38
46
|
files:
|
47
|
+
- ".DS_Store"
|
48
|
+
- CHANGELOG.md
|
49
|
+
- CODE_OF_CONDUCT.md
|
50
|
+
- Gemfile
|
51
|
+
- Gemfile.lock
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- app/.DS_Store
|
56
|
+
- app/assets/.DS_Store
|
57
|
+
- app/assets/javascripts/accumulator.js
|
58
|
+
- app/assets/javascripts/bit-stream.js
|
59
|
+
- app/assets/javascripts/crc-calculator.js
|
60
|
+
- app/assets/javascripts/decoder.js
|
61
|
+
- app/assets/javascripts/fit.js
|
62
|
+
- app/assets/javascripts/fitjs.js
|
63
|
+
- app/assets/javascripts/index.js
|
64
|
+
- app/assets/javascripts/profile.js
|
65
|
+
- app/assets/javascripts/stream.js
|
66
|
+
- app/assets/javascripts/utils-hr-mesg.js
|
67
|
+
- app/assets/javascripts/utils-internal.js
|
68
|
+
- app/assets/javascripts/utils.js
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- fit_js.gemspec
|
39
72
|
- lib/fitjs.rb
|
73
|
+
- lib/fitjs/rails/version.rb
|
74
|
+
- sig/fitjs.rbs
|
40
75
|
homepage: https://rubygems.org/gems/fit_js
|
41
76
|
licenses:
|
42
77
|
- MIT
|