incyclist-devices 1.4.24 → 1.4.25
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/lib/DeviceProtocol.d.ts +1 -0
- package/lib/DeviceProtocol.js +1 -0
- package/lib/calculations.js +2 -3
- package/lib/daum/DaumAdapter.d.ts +2 -2
- package/lib/daum/DaumAdapter.js +24 -20
- package/lib/daum/DaumPowerMeterCyclingMode.d.ts +8 -0
- package/lib/daum/DaumPowerMeterCyclingMode.js +21 -0
- package/lib/daum/ERGCyclingMode.d.ts +3 -6
- package/lib/daum/ERGCyclingMode.js +15 -27
- package/lib/daum/classic/DaumClassicAdapter.js +1 -1
- package/lib/daum/premium/DaumClassicCyclingMode.d.ts +2 -2
- package/lib/daum/premium/DaumClassicCyclingMode.js +2 -2
- package/lib/daum/premium/DaumPremiumAdapter.js +1 -1
- package/lib/modes/power-base.d.ts +20 -0
- package/lib/modes/power-base.js +60 -0
- package/lib/modes/power-meter.d.ts +12 -10
- package/lib/modes/power-meter.js +30 -46
- package/lib/modes/simulator.d.ts +27 -0
- package/lib/modes/simulator.js +118 -0
- package/lib/simulator/Simulator.js +3 -3
- package/package.json +1 -1
package/lib/DeviceProtocol.d.ts
CHANGED
package/lib/DeviceProtocol.js
CHANGED
|
@@ -21,6 +21,7 @@ class DeviceProtocolBase {
|
|
|
21
21
|
scan(props) { throw new Error('not implemented'); }
|
|
22
22
|
stopScan() { throw new Error('not implemented'); }
|
|
23
23
|
isScanning() { throw new Error('not implemented'); }
|
|
24
|
+
add(props) { throw new Error('Method not implemented.'); }
|
|
24
25
|
getDevices() { return this.devices; }
|
|
25
26
|
setAnt(antClass) { DeviceProtocolBase.setAnt(antClass); }
|
|
26
27
|
getAnt() { return DeviceProtocolBase.getAnt(); }
|
package/lib/calculations.js
CHANGED
|
@@ -7,7 +7,6 @@ const cwABike = {
|
|
|
7
7
|
triathlon: 0.29,
|
|
8
8
|
mountain: 0.57
|
|
9
9
|
};
|
|
10
|
-
const k = 0.01090;
|
|
11
10
|
const cRR = 0.0036;
|
|
12
11
|
class IllegalArgumentException extends Error {
|
|
13
12
|
constructor(message) {
|
|
@@ -28,7 +27,7 @@ class C {
|
|
|
28
27
|
const _cRR = props.cRR || cRR;
|
|
29
28
|
const _cwA = props.cwA || cwABike[props.bikeType || 'race'] || cwABike.race;
|
|
30
29
|
let sl = Math.atan(slope / 100);
|
|
31
|
-
let c1 = 0.5 * _rho * _cwA
|
|
30
|
+
let c1 = 0.5 * _rho * _cwA;
|
|
32
31
|
let c2 = (sl + _cRR) * m * g;
|
|
33
32
|
let p = c2 / c1;
|
|
34
33
|
let q = -1.0 * power / c1;
|
|
@@ -70,7 +69,7 @@ class C {
|
|
|
70
69
|
let _cRR = props.cRR || cRR;
|
|
71
70
|
let _cwA = props.cwA || cwABike[props.bikeType || 'race'] || cwABike.race;
|
|
72
71
|
let sl = Math.sin(Math.atan(slope / 100));
|
|
73
|
-
let P = (0.5 * _rho * _cwA
|
|
72
|
+
let P = (0.5 * _rho * _cwA) * Math.pow(v, 3.0) + (sl + _cRR) * m * g * v;
|
|
74
73
|
return P;
|
|
75
74
|
}
|
|
76
75
|
static calculateSpeedDaum(gear, rpm, bikeType) {
|
|
@@ -13,7 +13,7 @@ export default class DaumAdapterBase extends DeviceAdapterBase implements Device
|
|
|
13
13
|
distanceInternal: number;
|
|
14
14
|
paused: boolean;
|
|
15
15
|
stopped: boolean;
|
|
16
|
-
|
|
16
|
+
cyclingData: IncyclistBikeData;
|
|
17
17
|
deviceData: DeviceData;
|
|
18
18
|
currentRequest: any;
|
|
19
19
|
requests: Array<any>;
|
|
@@ -56,7 +56,7 @@ export default class DaumAdapterBase extends DeviceAdapterBase implements Device
|
|
|
56
56
|
update(): Promise<void>;
|
|
57
57
|
sendRequests(): Promise<void>;
|
|
58
58
|
bikeSync(): Promise<void>;
|
|
59
|
-
updateData(prev: any, bikeData: any):
|
|
59
|
+
updateData(prev: any, bikeData: any): IncyclistBikeData;
|
|
60
60
|
transformData(): DeviceData;
|
|
61
61
|
sendRequest(request: any): Promise<any>;
|
|
62
62
|
refreshRequests(): void;
|
package/lib/daum/DaumAdapter.js
CHANGED
|
@@ -22,7 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
const Device_1 = __importStar(require("../Device"));
|
|
23
23
|
const ERGCyclingMode_1 = __importDefault(require("./ERGCyclingMode"));
|
|
24
24
|
const SmartTrainerCyclingMode_1 = __importDefault(require("./SmartTrainerCyclingMode"));
|
|
25
|
-
const
|
|
25
|
+
const DaumPowerMeterCyclingMode_1 = __importDefault(require("./DaumPowerMeterCyclingMode"));
|
|
26
26
|
const utils_1 = require("../utils");
|
|
27
27
|
class DaumAdapterBase extends Device_1.default {
|
|
28
28
|
constructor(props, bike) {
|
|
@@ -34,7 +34,7 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
34
34
|
this.bike = bike;
|
|
35
35
|
this.stopped = false;
|
|
36
36
|
this.paused = false;
|
|
37
|
-
this.
|
|
37
|
+
this.cyclingData = {
|
|
38
38
|
isPedalling: false,
|
|
39
39
|
time: 0,
|
|
40
40
|
power: 0,
|
|
@@ -67,7 +67,7 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
67
67
|
this.cyclingMode.setSettings(settings);
|
|
68
68
|
}
|
|
69
69
|
getSupportedCyclingModes() {
|
|
70
|
-
return [ERGCyclingMode_1.default, SmartTrainerCyclingMode_1.default,
|
|
70
|
+
return [ERGCyclingMode_1.default, SmartTrainerCyclingMode_1.default, DaumPowerMeterCyclingMode_1.default];
|
|
71
71
|
}
|
|
72
72
|
getCyclingMode() {
|
|
73
73
|
if (!this.cyclingMode)
|
|
@@ -128,7 +128,7 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
128
128
|
this.distanceInternal = undefined;
|
|
129
129
|
this.paused = false;
|
|
130
130
|
this.stopped = false;
|
|
131
|
-
this.
|
|
131
|
+
this.cyclingData = {
|
|
132
132
|
isPedalling: false,
|
|
133
133
|
time: 0,
|
|
134
134
|
power: 0,
|
|
@@ -229,12 +229,15 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
229
229
|
this.updateBusy = true;
|
|
230
230
|
this.getCurrentBikeData()
|
|
231
231
|
.then(bikeData => {
|
|
232
|
-
this.updateData(this.
|
|
232
|
+
this.updateData(this.cyclingData, bikeData);
|
|
233
233
|
this.transformData();
|
|
234
234
|
this.updateBusy = false;
|
|
235
235
|
})
|
|
236
236
|
.catch(err => {
|
|
237
237
|
this.logEvent({ message: 'bike update error', error: err.message, stack: err.stack });
|
|
238
|
+
const { isPedalling, power, pedalRpm, speed, distanceInternal, heartrate, slope } = this.cyclingData;
|
|
239
|
+
this.updateData(this.cyclingData, { isPedalling, power, pedalRpm, speed, distanceInternal, heartrate, slope });
|
|
240
|
+
this.transformData();
|
|
238
241
|
this.updateBusy = false;
|
|
239
242
|
});
|
|
240
243
|
});
|
|
@@ -289,27 +292,28 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
289
292
|
data.time = bikeData.time;
|
|
290
293
|
if (bikeData.slope)
|
|
291
294
|
data.slope = bikeData.slope;
|
|
292
|
-
this.
|
|
295
|
+
this.cyclingData = this.getCyclingMode().updateData(data);
|
|
296
|
+
return this.cyclingData;
|
|
293
297
|
}
|
|
294
298
|
transformData() {
|
|
295
|
-
if (this.
|
|
299
|
+
if (this.cyclingData === undefined)
|
|
296
300
|
return;
|
|
297
301
|
let distance = 0;
|
|
298
|
-
if (this.distanceInternal !== undefined && this.
|
|
299
|
-
distance = utils_1.intVal(this.
|
|
302
|
+
if (this.distanceInternal !== undefined && this.cyclingData.distanceInternal !== undefined) {
|
|
303
|
+
distance = utils_1.intVal(this.cyclingData.distanceInternal - this.distanceInternal);
|
|
300
304
|
}
|
|
301
|
-
if (this.
|
|
302
|
-
this.distanceInternal = this.
|
|
305
|
+
if (this.cyclingData.distanceInternal !== undefined)
|
|
306
|
+
this.distanceInternal = this.cyclingData.distanceInternal;
|
|
303
307
|
let data = {
|
|
304
|
-
speed: utils_1.floatVal(this.
|
|
305
|
-
slope: utils_1.floatVal(this.
|
|
306
|
-
power: utils_1.intVal(this.
|
|
307
|
-
cadence: utils_1.intVal(this.
|
|
308
|
-
heartrate: utils_1.intVal(this.
|
|
308
|
+
speed: utils_1.floatVal(this.cyclingData.speed),
|
|
309
|
+
slope: utils_1.floatVal(this.cyclingData.slope),
|
|
310
|
+
power: utils_1.intVal(this.cyclingData.power),
|
|
311
|
+
cadence: utils_1.intVal(this.cyclingData.pedalRpm),
|
|
312
|
+
heartrate: utils_1.intVal(this.cyclingData.heartrate),
|
|
309
313
|
distance,
|
|
310
314
|
timestamp: Date.now(),
|
|
311
|
-
deviceTime: this.
|
|
312
|
-
deviceDistanceCounter: this.
|
|
315
|
+
deviceTime: this.cyclingData.time,
|
|
316
|
+
deviceDistanceCounter: this.cyclingData.distanceInternal
|
|
313
317
|
};
|
|
314
318
|
if (this.ignoreHrm)
|
|
315
319
|
delete data.heartrate;
|
|
@@ -350,7 +354,7 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
350
354
|
});
|
|
351
355
|
}
|
|
352
356
|
refreshRequests() {
|
|
353
|
-
if (!this.
|
|
357
|
+
if (!this.cyclingData.isPedalling || this.cyclingData.pedalRpm === 0)
|
|
354
358
|
return;
|
|
355
359
|
let bikeRequest = this.getCyclingMode().sendBikeUpdate({ refresh: true }) || {};
|
|
356
360
|
const prev = this.requests[this.requests.length - 1] || {};
|
|
@@ -361,7 +365,7 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
361
365
|
}
|
|
362
366
|
processClientRequest(request) {
|
|
363
367
|
if (request.slope !== undefined) {
|
|
364
|
-
this.
|
|
368
|
+
this.cyclingData.slope = request.slope;
|
|
365
369
|
}
|
|
366
370
|
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
|
|
367
371
|
let bikeRequest = this.getCyclingMode().sendBikeUpdate(request);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import CyclingMode, { UpdateRequest } from "../CyclingMode";
|
|
2
|
+
import PowerMeterCyclingMode from "../modes/power-meter";
|
|
3
|
+
export default class DaumPowerMeterCyclingMode extends PowerMeterCyclingMode implements CyclingMode {
|
|
4
|
+
prevRequest: UpdateRequest;
|
|
5
|
+
hasBikeUpdate: boolean;
|
|
6
|
+
getBikeInitRequest(): UpdateRequest;
|
|
7
|
+
sendBikeUpdate(request: UpdateRequest): UpdateRequest;
|
|
8
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const power_meter_1 = __importDefault(require("../modes/power-meter"));
|
|
7
|
+
class DaumPowerMeterCyclingMode extends power_meter_1.default {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.hasBikeUpdate = false;
|
|
11
|
+
}
|
|
12
|
+
getBikeInitRequest() {
|
|
13
|
+
return { slope: 0 };
|
|
14
|
+
}
|
|
15
|
+
sendBikeUpdate(request) {
|
|
16
|
+
super.sendBikeUpdate(request);
|
|
17
|
+
this.prevRequest = {};
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = DaumPowerMeterCyclingMode;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import CyclingMode, { CyclingModeBase, CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
|
|
1
|
+
import CyclingMode, { CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
|
|
3
2
|
import DaumAdapter from "./DaumAdapter";
|
|
3
|
+
import PowerBasedCyclingModeBase from "../modes/power-base";
|
|
4
4
|
export declare type ERGEvent = {
|
|
5
5
|
rpmUpdated?: boolean;
|
|
6
6
|
gearUpdated?: boolean;
|
|
7
7
|
starting?: boolean;
|
|
8
8
|
tsStart?: number;
|
|
9
9
|
};
|
|
10
|
-
export default class ERGCyclingMode extends
|
|
11
|
-
logger: EventLogger;
|
|
12
|
-
data: IncyclistBikeData;
|
|
10
|
+
export default class ERGCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
|
|
13
11
|
prevRequest: UpdateRequest;
|
|
14
|
-
prevUpdateTS: number;
|
|
15
12
|
hasBikeUpdate: boolean;
|
|
16
13
|
chain: number[];
|
|
17
14
|
cassette: number[];
|
|
@@ -3,9 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const gd_eventlog_1 = require("gd-eventlog");
|
|
7
6
|
const CyclingMode_1 = require("../CyclingMode");
|
|
8
7
|
const calculations_1 = __importDefault(require("../calculations"));
|
|
8
|
+
const power_base_1 = __importDefault(require("../modes/power-base"));
|
|
9
9
|
const config = {
|
|
10
10
|
name: "ERG",
|
|
11
11
|
description: "Calculates speed based on power and slope. Power is either set by workout or calculated based on gear and cadence",
|
|
@@ -14,15 +14,12 @@ const config = {
|
|
|
14
14
|
{ key: 'startPower', name: 'Starting Power', description: 'Initial power in Watts at start of training', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 50, min: 25, max: 800 },
|
|
15
15
|
]
|
|
16
16
|
};
|
|
17
|
-
class ERGCyclingMode extends
|
|
17
|
+
class ERGCyclingMode extends power_base_1.default {
|
|
18
18
|
constructor(adapter, props) {
|
|
19
19
|
super(adapter, props);
|
|
20
|
-
this.prevUpdateTS = 0;
|
|
21
20
|
this.hasBikeUpdate = false;
|
|
22
21
|
this.event = {};
|
|
23
|
-
this.
|
|
24
|
-
this.data = {};
|
|
25
|
-
this.logger.logEvent({ message: 'constructor', props });
|
|
22
|
+
this.initLogger('ERGMode');
|
|
26
23
|
}
|
|
27
24
|
getName() {
|
|
28
25
|
return config.name;
|
|
@@ -132,7 +129,6 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
|
132
129
|
const prevRequest = this.prevRequest || {};
|
|
133
130
|
const data = this.data || {};
|
|
134
131
|
const bikeType = this.getSetting('bikeType').toLowerCase();
|
|
135
|
-
console.log('~~~ bikeType', bikeType);
|
|
136
132
|
delete this.event.gearUpdated;
|
|
137
133
|
delete this.event.rpmUpdated;
|
|
138
134
|
if (prevData === {} || prevData.speed === undefined || prevData.speed === 0) {
|
|
@@ -140,32 +136,25 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
|
140
136
|
this.event.tsStart = Date.now();
|
|
141
137
|
}
|
|
142
138
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
const rpm = bikeData.pedalRpm || 0;
|
|
140
|
+
const gear = bikeData.gear || 0;
|
|
145
141
|
let power = bikeData.power || 0;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let distanceInternal = prevData.distanceInternal || 0;
|
|
150
|
-
let ts = Date.now();
|
|
151
|
-
let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
|
|
152
|
-
if (rpm === 0 || bikeData.isPedalling === false) {
|
|
153
|
-
speed = 0;
|
|
142
|
+
const slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
|
|
143
|
+
const distanceInternal = prevData.distanceInternal || 0;
|
|
144
|
+
if (!bikeData.pedalRpm || bikeData.isPedalling === false) {
|
|
154
145
|
power = 0;
|
|
155
146
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
distanceInternal += Math.round(v * duration);
|
|
160
|
-
}
|
|
147
|
+
const m = this.getWeight();
|
|
148
|
+
const t = this.getTimeSinceLastUpdate();
|
|
149
|
+
const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t, { bikeType });
|
|
161
150
|
data.speed = parseFloat(speed.toFixed(1));
|
|
162
151
|
data.power = Math.round(power);
|
|
163
|
-
data.distanceInternal = distanceInternal;
|
|
152
|
+
data.distanceInternal = Math.round(distanceInternal + distance);
|
|
164
153
|
data.slope = slope;
|
|
165
154
|
data.pedalRpm = rpm;
|
|
166
155
|
data.gear = gear;
|
|
167
|
-
if (data.time !== undefined)
|
|
168
|
-
data.time +=
|
|
156
|
+
if (data.time !== undefined && !(this.event.starting && !bikeData.pedalRpm))
|
|
157
|
+
data.time += t;
|
|
169
158
|
else
|
|
170
159
|
data.time = 0;
|
|
171
160
|
data.heartrate = bikeData.heartrate;
|
|
@@ -176,7 +165,6 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
|
176
165
|
if (rpm && rpm !== prevData.pedalRpm) {
|
|
177
166
|
this.event.rpmUpdated = true;
|
|
178
167
|
}
|
|
179
|
-
this.prevUpdateTS = ts;
|
|
180
168
|
}
|
|
181
169
|
catch (err) {
|
|
182
170
|
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
@@ -188,7 +176,7 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
|
188
176
|
calculateTargetPower(request, updateMode = true) {
|
|
189
177
|
const bikeType = this.getSetting('bikeType').toLowerCase();
|
|
190
178
|
const defaultPower = this.getSetting('startPower');
|
|
191
|
-
let m = this.
|
|
179
|
+
let m = this.getWeight();
|
|
192
180
|
const prevData = this.data || {};
|
|
193
181
|
let target;
|
|
194
182
|
if (prevData.pedalRpm && prevData.gear && (!updateMode || prevData.pedalRpm !== 0)) {
|
|
@@ -109,7 +109,7 @@ class DaumClassicAdapter extends DaumAdapter_1.default {
|
|
|
109
109
|
startState.startProg = true;
|
|
110
110
|
}
|
|
111
111
|
if (!startState.setGear) {
|
|
112
|
-
yield this.bike.setGear(this.
|
|
112
|
+
yield this.bike.setGear(this.cyclingData.gear || (opts.gear || 10));
|
|
113
113
|
startState.setGear = true;
|
|
114
114
|
}
|
|
115
115
|
const startRequest = this.getCyclingMode().getBikeInitRequest();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest } from "../../CyclingMode";
|
|
2
2
|
import DaumAdapter from "../DaumAdapter";
|
|
3
|
-
import
|
|
4
|
-
export default class DaumClassicCyclingMode extends
|
|
3
|
+
import DaumPowerMeterCyclingMode from "../DaumPowerMeterCyclingMode";
|
|
4
|
+
export default class DaumClassicCyclingMode extends DaumPowerMeterCyclingMode implements CyclingMode {
|
|
5
5
|
constructor(adapter: DaumAdapter, props?: Settings);
|
|
6
6
|
getName(): string;
|
|
7
7
|
getDescription(): string;
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const gd_eventlog_1 = require("gd-eventlog");
|
|
7
7
|
const CyclingMode_1 = require("../../CyclingMode");
|
|
8
|
-
const
|
|
8
|
+
const DaumPowerMeterCyclingMode_1 = __importDefault(require("../DaumPowerMeterCyclingMode"));
|
|
9
9
|
const config = {
|
|
10
10
|
name: "Daum Classic",
|
|
11
11
|
description: "The device calculates speed and power based on slope. Incyclist will not modify any values recived from the device\nThis mode will not respect maximum power and/or workout limits",
|
|
@@ -13,7 +13,7 @@ const config = {
|
|
|
13
13
|
{ key: 'bikeType', name: 'Bike Type', description: '', type: CyclingMode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain'], default: 'Race' },
|
|
14
14
|
]
|
|
15
15
|
};
|
|
16
|
-
class DaumClassicCyclingMode extends
|
|
16
|
+
class DaumClassicCyclingMode extends DaumPowerMeterCyclingMode_1.default {
|
|
17
17
|
constructor(adapter, props) {
|
|
18
18
|
super(adapter, props);
|
|
19
19
|
this.logger = adapter ? adapter.logger : undefined;
|
|
@@ -98,7 +98,7 @@ class DaumPremiumDevice extends DaumAdapter_1.default {
|
|
|
98
98
|
info.person = yield this.bike.setPerson(user);
|
|
99
99
|
}
|
|
100
100
|
if (!this.getCyclingMode().getModeProperty('eppSupport')) {
|
|
101
|
-
const gear = yield this.bike.setGear(this.
|
|
101
|
+
const gear = yield this.bike.setGear(this.cyclingData.gear || (opts.gear || 10));
|
|
102
102
|
return gear;
|
|
103
103
|
}
|
|
104
104
|
return;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IncyclistBikeData, Settings, CyclingModeBase } from '../CyclingMode';
|
|
2
|
+
import { DeviceAdapter } from '../Device';
|
|
3
|
+
import { EventLogger } from 'gd-eventlog';
|
|
4
|
+
export default class PowerBasedCyclingModeBase extends CyclingModeBase {
|
|
5
|
+
data: IncyclistBikeData;
|
|
6
|
+
prevUpdateTS: number;
|
|
7
|
+
logger: EventLogger;
|
|
8
|
+
constructor(adapter: DeviceAdapter, props?: Settings);
|
|
9
|
+
initLogger(defaultLogName: any): void;
|
|
10
|
+
getWeight(): any;
|
|
11
|
+
getTimeSinceLastUpdate(): number;
|
|
12
|
+
calculateSpeedAndDistance(power: number, slope: number, m: number, t: number, props?: {}): {
|
|
13
|
+
speed: number;
|
|
14
|
+
distance: number;
|
|
15
|
+
};
|
|
16
|
+
calculatePowerAndDistance(speed: number, slope: number, m: number, t: number, props?: {}): {
|
|
17
|
+
power: number;
|
|
18
|
+
distance: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const CyclingMode_1 = require("../CyclingMode");
|
|
7
|
+
const Device_1 = require("../Device");
|
|
8
|
+
const calculations_1 = __importDefault(require("../calculations"));
|
|
9
|
+
const gd_eventlog_1 = require("gd-eventlog");
|
|
10
|
+
class PowerBasedCyclingModeBase extends CyclingMode_1.CyclingModeBase {
|
|
11
|
+
constructor(adapter, props) {
|
|
12
|
+
super(adapter, props);
|
|
13
|
+
this.prevUpdateTS = 0;
|
|
14
|
+
this.data = { speed: 0, power: 0, distanceInternal: 0, pedalRpm: 0, isPedalling: false, heartrate: 0 };
|
|
15
|
+
}
|
|
16
|
+
initLogger(defaultLogName) {
|
|
17
|
+
const a = this.adapter;
|
|
18
|
+
this.logger = (a && a.getLogger) ? a.getLogger() : undefined;
|
|
19
|
+
if (!this.logger)
|
|
20
|
+
this.logger = new gd_eventlog_1.EventLogger(defaultLogName);
|
|
21
|
+
}
|
|
22
|
+
getWeight() {
|
|
23
|
+
const a = this.adapter;
|
|
24
|
+
const m = (a && a.getWeight && a.getWeight()) ? a.getWeight() : Device_1.DEFAULT_BIKE_WEIGHT + Device_1.DEFAULT_USER_WEIGHT;
|
|
25
|
+
return m;
|
|
26
|
+
}
|
|
27
|
+
getTimeSinceLastUpdate() {
|
|
28
|
+
const ts = Date.now();
|
|
29
|
+
const duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
|
|
30
|
+
this.prevUpdateTS = ts;
|
|
31
|
+
return duration;
|
|
32
|
+
}
|
|
33
|
+
calculateSpeedAndDistance(power, slope, m, t, props = {}) {
|
|
34
|
+
const prevData = this.data || {};
|
|
35
|
+
const vPrev = (prevData.speed || 0) / 3.6;
|
|
36
|
+
const EkinPrev = 1 / 2 * m * vPrev * vPrev;
|
|
37
|
+
let powerToMaintainSpeed = calculations_1.default.calculatePower(m, vPrev, slope, props);
|
|
38
|
+
const powerDelta = powerToMaintainSpeed - power;
|
|
39
|
+
const Ekin = EkinPrev - powerDelta * t;
|
|
40
|
+
const v = Math.sqrt(2 * Ekin / m);
|
|
41
|
+
const speed = v * 3.6;
|
|
42
|
+
const distance = v * t;
|
|
43
|
+
this.data.speed = speed;
|
|
44
|
+
return { speed, distance };
|
|
45
|
+
}
|
|
46
|
+
calculatePowerAndDistance(speed, slope, m, t, props = {}) {
|
|
47
|
+
const prevData = this.data || {};
|
|
48
|
+
const vPrev = (prevData.speed || 0) / 3.6;
|
|
49
|
+
const EkinPrev = 1 / 2 * m * vPrev * vPrev;
|
|
50
|
+
const Ekin = 1 / 2 * m * speed * speed;
|
|
51
|
+
const powerDelta = t !== 0 ? (EkinPrev - Ekin) / t : 0;
|
|
52
|
+
const powerToMaintainSpeed = calculations_1.default.calculatePower(m, vPrev, slope, props);
|
|
53
|
+
const power = powerToMaintainSpeed - powerDelta;
|
|
54
|
+
const v = speed / 3.6;
|
|
55
|
+
const distance = v * t;
|
|
56
|
+
this.data.power = power;
|
|
57
|
+
return { power, distance };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.default = PowerBasedCyclingModeBase;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest } from '../CyclingMode';
|
|
2
|
+
import PowerBasedCyclingModeBase from './power-base';
|
|
3
3
|
import { DeviceAdapter } from '../Device';
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
export declare const config: {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
properties: any[];
|
|
8
|
+
};
|
|
9
|
+
export default class PowerMeterCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
|
|
10
10
|
constructor(adapter: DeviceAdapter, props?: Settings);
|
|
11
11
|
getName(): string;
|
|
12
12
|
getDescription(): string;
|
|
13
13
|
getProperties(): CyclingModeProperty[];
|
|
14
14
|
getProperty(name: string): CyclingModeProperty;
|
|
15
15
|
getBikeInitRequest(): UpdateRequest;
|
|
16
|
-
sendBikeUpdate(request
|
|
17
|
-
updateData(
|
|
16
|
+
sendBikeUpdate(request?: UpdateRequest): UpdateRequest;
|
|
17
|
+
updateData(bikeData: IncyclistBikeData, props?: {
|
|
18
|
+
log: boolean;
|
|
19
|
+
}): IncyclistBikeData;
|
|
18
20
|
}
|
package/lib/modes/power-meter.js
CHANGED
|
@@ -3,84 +3,68 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const Device_1 = require("../Device");
|
|
9
|
-
const calculations_1 = __importDefault(require("../calculations"));
|
|
10
|
-
const config = {
|
|
6
|
+
const power_base_1 = __importDefault(require("./power-base"));
|
|
7
|
+
exports.config = {
|
|
11
8
|
name: 'PowerMeter',
|
|
12
9
|
description: 'Power and cadence are taken from device. Speed is calculated from power and current slope\nThis mode will not respect maximum power and/or workout limits',
|
|
13
10
|
properties: []
|
|
14
11
|
};
|
|
15
|
-
class PowerMeterCyclingMode extends
|
|
12
|
+
class PowerMeterCyclingMode extends power_base_1.default {
|
|
16
13
|
constructor(adapter, props) {
|
|
17
14
|
super(adapter, props);
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
const a = adapter;
|
|
21
|
-
this.logger = (a && a.getLogger) ? a.getLogger() : undefined;
|
|
22
|
-
if (!this.logger)
|
|
23
|
-
this.logger = new gd_eventlog_1.EventLogger('PowerMeter');
|
|
15
|
+
this.initLogger('PowerMeterMode');
|
|
16
|
+
this.data = { speed: 0, slope: 0, power: 0, distanceInternal: 0, pedalRpm: 0, isPedalling: false, heartrate: 0 };
|
|
24
17
|
}
|
|
25
18
|
getName() {
|
|
26
|
-
return config.name;
|
|
19
|
+
return exports.config.name;
|
|
27
20
|
}
|
|
28
21
|
getDescription() {
|
|
29
|
-
return config.description;
|
|
22
|
+
return exports.config.description;
|
|
30
23
|
}
|
|
31
24
|
getProperties() {
|
|
32
|
-
return config.properties;
|
|
25
|
+
return exports.config.properties;
|
|
33
26
|
}
|
|
34
27
|
getProperty(name) {
|
|
35
|
-
return config.properties.find(p => p.name === name);
|
|
28
|
+
return exports.config.properties.find(p => p.name === name);
|
|
36
29
|
}
|
|
37
30
|
getBikeInitRequest() {
|
|
38
31
|
return {};
|
|
39
32
|
}
|
|
40
|
-
sendBikeUpdate(request) {
|
|
41
|
-
|
|
33
|
+
sendBikeUpdate(request = {}) {
|
|
34
|
+
const prevData = this.data || {};
|
|
35
|
+
const prevSlope = prevData.slope || 0;
|
|
36
|
+
if (request.slope && request.slope !== prevSlope) {
|
|
42
37
|
this.data.slope = request.slope;
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
this.updateData(this.data, { log: false });
|
|
39
|
+
}
|
|
40
|
+
this.logger.logEvent({ message: "processing update request", request });
|
|
45
41
|
return {};
|
|
46
42
|
}
|
|
47
|
-
updateData(
|
|
43
|
+
updateData(bikeData, props = { log: true }) {
|
|
48
44
|
try {
|
|
49
45
|
const prevData = this.data || {};
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
|
|
55
|
-
let distanceInternal = prevData.distanceInternal || 0;
|
|
46
|
+
const data = JSON.parse(JSON.stringify(bikeData));
|
|
47
|
+
let power = bikeData.power || 0;
|
|
48
|
+
const slope = prevData.slope || 0;
|
|
49
|
+
const distanceInternal = prevData.distanceInternal || 0;
|
|
56
50
|
if (!bikeData.pedalRpm || bikeData.isPedalling === false) {
|
|
57
|
-
speed = 0;
|
|
58
51
|
power = 0;
|
|
59
52
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const vPrev = (prevData.speed || 0) / 3.6;
|
|
65
|
-
const EkinPrev = 1 / 2 * m * vPrev * vPrev;
|
|
66
|
-
let powerRequired = calculations_1.default.calculatePower(m, vPrev, prevData.slope || 0);
|
|
67
|
-
const powerDelta = powerRequired - power;
|
|
68
|
-
const Ekin = EkinPrev - powerDelta * duration;
|
|
69
|
-
const v = Math.sqrt(2 * Ekin / m);
|
|
70
|
-
speed = v * 3.6;
|
|
71
|
-
distanceInternal += Math.round(v * duration);
|
|
72
|
-
data.speed = parseFloat(speed.toFixed(1));
|
|
53
|
+
const m = this.getWeight();
|
|
54
|
+
let t = this.getTimeSinceLastUpdate();
|
|
55
|
+
const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t);
|
|
56
|
+
data.speed = speed;
|
|
73
57
|
data.power = Math.round(power);
|
|
74
|
-
data.distanceInternal = Math.round(distanceInternal);
|
|
58
|
+
data.distanceInternal = Math.round(distanceInternal + distance);
|
|
75
59
|
data.slope = slope;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.
|
|
60
|
+
if (props.log)
|
|
61
|
+
this.logger.logEvent({ message: "updateData result", data, bikeData, prevSpeed: prevData.speed });
|
|
62
|
+
this.data = data;
|
|
79
63
|
}
|
|
80
64
|
catch (err) {
|
|
81
65
|
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
82
66
|
}
|
|
83
|
-
return data;
|
|
67
|
+
return this.data;
|
|
84
68
|
}
|
|
85
69
|
}
|
|
86
70
|
exports.default = PowerMeterCyclingMode;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { EventLogger } from "gd-eventlog";
|
|
2
|
+
import CyclingMode, { CyclingModeBase, CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
|
|
3
|
+
import { Simulator } from "../simulator/Simulator";
|
|
4
|
+
export declare type ERGEvent = {
|
|
5
|
+
rpmUpdated?: boolean;
|
|
6
|
+
gearUpdated?: boolean;
|
|
7
|
+
starting?: boolean;
|
|
8
|
+
tsStart?: number;
|
|
9
|
+
};
|
|
10
|
+
export default class SimulatorCyclingMode extends CyclingModeBase implements CyclingMode {
|
|
11
|
+
logger: EventLogger;
|
|
12
|
+
data: IncyclistBikeData;
|
|
13
|
+
prevRequest: UpdateRequest;
|
|
14
|
+
prevUpdateTS: number;
|
|
15
|
+
hasBikeUpdate: boolean;
|
|
16
|
+
chain: number[];
|
|
17
|
+
cassette: number[];
|
|
18
|
+
event: ERGEvent;
|
|
19
|
+
constructor(adapter: Simulator, props?: any);
|
|
20
|
+
getName(): string;
|
|
21
|
+
getDescription(): string;
|
|
22
|
+
getProperties(): CyclingModeProperty[];
|
|
23
|
+
getProperty(name: string): CyclingModeProperty;
|
|
24
|
+
getBikeInitRequest(): UpdateRequest;
|
|
25
|
+
sendBikeUpdate(request: UpdateRequest): UpdateRequest;
|
|
26
|
+
updateData(bikeData: IncyclistBikeData): IncyclistBikeData;
|
|
27
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const gd_eventlog_1 = require("gd-eventlog");
|
|
7
|
+
const CyclingMode_1 = require("../CyclingMode");
|
|
8
|
+
const calculations_1 = __importDefault(require("../calculations"));
|
|
9
|
+
const config = {
|
|
10
|
+
name: "Simulator",
|
|
11
|
+
description: "Simulates a ride with constant speed or power output",
|
|
12
|
+
properties: [
|
|
13
|
+
{ key: 'mode', name: 'Simulation Type', description: '', type: CyclingMode_1.CyclingModeProperyType.SingleSelect, options: ['Speed', 'Power'], default: 'Power' },
|
|
14
|
+
{ key: 'delay', name: 'Start Delay', description: 'Delay (in s) at start of training', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 2, min: 0, max: 30 },
|
|
15
|
+
{ key: 'power', name: 'Power', description: 'Power (in W) at start of training', condition: (s) => !s.mode || s.mode === 'Power', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 150, min: 25, max: 800 },
|
|
16
|
+
{ key: 'speed', name: 'Speed', description: 'Speed (in km/h) at start of training', condition: (s) => s.mode === 'Speed', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 30, min: 5, max: 50 },
|
|
17
|
+
]
|
|
18
|
+
};
|
|
19
|
+
class SimulatorCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
20
|
+
constructor(adapter, props) {
|
|
21
|
+
super(adapter, props);
|
|
22
|
+
this.prevUpdateTS = 0;
|
|
23
|
+
this.hasBikeUpdate = false;
|
|
24
|
+
this.event = {};
|
|
25
|
+
this.logger = adapter.logger || new gd_eventlog_1.EventLogger('SIMMode');
|
|
26
|
+
this.data = {};
|
|
27
|
+
this.logger.logEvent({ message: 'constructor', props });
|
|
28
|
+
}
|
|
29
|
+
getName() {
|
|
30
|
+
return config.name;
|
|
31
|
+
}
|
|
32
|
+
getDescription() {
|
|
33
|
+
return config.description;
|
|
34
|
+
}
|
|
35
|
+
getProperties() {
|
|
36
|
+
return config.properties;
|
|
37
|
+
}
|
|
38
|
+
getProperty(name) {
|
|
39
|
+
return config.properties.find(p => p.name === name);
|
|
40
|
+
}
|
|
41
|
+
getBikeInitRequest() {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
sendBikeUpdate(request) {
|
|
45
|
+
this.logger.logEvent({ message: 'bike update request', request });
|
|
46
|
+
const r = request || { refresh: true };
|
|
47
|
+
if (r.refresh) {
|
|
48
|
+
if (Object.keys(r).length === 1)
|
|
49
|
+
return this.prevRequest || {};
|
|
50
|
+
delete r.refresh;
|
|
51
|
+
}
|
|
52
|
+
if (request.slope !== undefined) {
|
|
53
|
+
if (!this.data)
|
|
54
|
+
this.data = {};
|
|
55
|
+
this.data.slope = request.slope;
|
|
56
|
+
}
|
|
57
|
+
this.prevRequest = request ? JSON.parse(JSON.stringify(request)) : {};
|
|
58
|
+
return r;
|
|
59
|
+
}
|
|
60
|
+
updateData(bikeData) {
|
|
61
|
+
const prevData = JSON.parse(JSON.stringify(this.data || {}));
|
|
62
|
+
const prevSpeed = prevData.speed;
|
|
63
|
+
const prevRequest = this.prevRequest || {};
|
|
64
|
+
const data = this.data || {};
|
|
65
|
+
const mode = this.getSetting('mode');
|
|
66
|
+
delete this.event.gearUpdated;
|
|
67
|
+
delete this.event.rpmUpdated;
|
|
68
|
+
try {
|
|
69
|
+
let rpm = 90;
|
|
70
|
+
let power = (mode === 'Power' || !mode) ? Number(this.getSetting('power')) : bikeData.power || 0;
|
|
71
|
+
let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
|
|
72
|
+
let speed = mode === 'Speed' ? Number(this.getSetting('speed')) : bikeData.speed || 0;
|
|
73
|
+
let m = 75;
|
|
74
|
+
let distanceInternal = prevData.distanceInternal || 0;
|
|
75
|
+
let ts = Date.now();
|
|
76
|
+
let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
|
|
77
|
+
if (mode === 'Power' || !mode) {
|
|
78
|
+
speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
|
|
79
|
+
}
|
|
80
|
+
else if (mode === 'Speed') {
|
|
81
|
+
power = calculations_1.default.calculatePower(m, speed / 3.6, slope, { bikeType: 'race' });
|
|
82
|
+
}
|
|
83
|
+
if (prevRequest.targetPower) {
|
|
84
|
+
power = prevRequest.targetPower;
|
|
85
|
+
speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
|
|
86
|
+
}
|
|
87
|
+
if (prevRequest.maxPower && power > prevRequest.maxPower) {
|
|
88
|
+
power = prevRequest.maxPower;
|
|
89
|
+
speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
|
|
90
|
+
}
|
|
91
|
+
else if (prevRequest.minPower && power < prevRequest.minPower) {
|
|
92
|
+
power = prevRequest.minPower;
|
|
93
|
+
speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
|
|
94
|
+
}
|
|
95
|
+
let v = speed / 3.6;
|
|
96
|
+
distanceInternal += Math.round(v * duration);
|
|
97
|
+
data.speed = parseFloat(speed.toFixed(1));
|
|
98
|
+
data.power = Math.round(power);
|
|
99
|
+
data.distanceInternal = distanceInternal;
|
|
100
|
+
data.slope = slope;
|
|
101
|
+
data.pedalRpm = rpm;
|
|
102
|
+
if (data.time !== undefined)
|
|
103
|
+
data.time += duration;
|
|
104
|
+
else
|
|
105
|
+
data.time = 0;
|
|
106
|
+
data.heartrate = bikeData.heartrate;
|
|
107
|
+
data.isPedalling = true;
|
|
108
|
+
this.prevUpdateTS = ts;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
112
|
+
}
|
|
113
|
+
this.logger.logEvent({ message: "updateData result", data, bikeData, prevRequest, prevSpeed });
|
|
114
|
+
this.data = data;
|
|
115
|
+
return data;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.default = SimulatorCyclingMode;
|
|
@@ -23,7 +23,7 @@ const DeviceProtocol_1 = __importStar(require("../DeviceProtocol"));
|
|
|
23
23
|
const DeviceRegistry_1 = __importDefault(require("../DeviceRegistry"));
|
|
24
24
|
const Device_1 = __importDefault(require("../Device"));
|
|
25
25
|
const gd_eventlog_1 = require("gd-eventlog");
|
|
26
|
-
const
|
|
26
|
+
const simulator_1 = __importDefault(require("../modes/simulator"));
|
|
27
27
|
const DEFAULT_SETTINGS = { name: 'Simulator', port: '', isBot: false };
|
|
28
28
|
class Simulator extends Device_1.default {
|
|
29
29
|
constructor(protocol, props = DEFAULT_SETTINGS) {
|
|
@@ -59,11 +59,11 @@ class Simulator extends Device_1.default {
|
|
|
59
59
|
}
|
|
60
60
|
getSupportedCyclingModes() {
|
|
61
61
|
const supported = [];
|
|
62
|
-
supported.push(
|
|
62
|
+
supported.push(simulator_1.default);
|
|
63
63
|
return supported;
|
|
64
64
|
}
|
|
65
65
|
getDefaultCyclingMode() {
|
|
66
|
-
return new
|
|
66
|
+
return new simulator_1.default(this);
|
|
67
67
|
}
|
|
68
68
|
getCyclingMode() {
|
|
69
69
|
if (!this.cyclingMode)
|