incyclist-devices 1.4.20 → 1.4.23
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/Device.d.ts +1 -0
- package/lib/ant/antfe/AntFEAdapter.d.ts +1 -0
- package/lib/ant/antfe/AntFEAdapter.js +9 -1
- package/lib/kettler/ergo-racer/adapter.d.ts +4 -3
- package/lib/kettler/ergo-racer/adapter.js +81 -67
- package/lib/simulator/Simulator.d.ts +20 -4
- package/lib/simulator/Simulator.js +106 -54
- package/lib/simulator/simulator-mode.d.ts +28 -0
- package/lib/simulator/simulator-mode.js +120 -0
- package/lib/utils.js +6 -3
- package/package.json +1 -1
package/lib/Device.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare type DeviceData = {
|
|
|
12
12
|
timestamp?: number;
|
|
13
13
|
deviceTime?: number;
|
|
14
14
|
deviceDistanceCounter?: number;
|
|
15
|
+
internalDistanceCounter?: number;
|
|
15
16
|
};
|
|
16
17
|
export declare type OnDeviceDataCallback = (data: DeviceData) => void;
|
|
17
18
|
export declare type OnDeviceStartCallback = (completed: number, total: number) => void;
|
|
@@ -16,6 +16,7 @@ export default class AntFEAdapter extends AntAdapter {
|
|
|
16
16
|
getName(): string;
|
|
17
17
|
getDisplayName(): string;
|
|
18
18
|
onAttached(): void;
|
|
19
|
+
getLogData(data: any, excludeList: any): any;
|
|
19
20
|
onDeviceData(deviceData: any): void;
|
|
20
21
|
onDeviceEvent(data: any): void;
|
|
21
22
|
updateData(data: any, deviceData: any): any;
|
|
@@ -58,6 +58,13 @@ class AntFEAdapter extends AntAdapter_1.default {
|
|
|
58
58
|
this.logger.logEvent({ message: 'Device connected' });
|
|
59
59
|
this.connected = true;
|
|
60
60
|
}
|
|
61
|
+
getLogData(data, excludeList) {
|
|
62
|
+
const logData = JSON.parse(JSON.stringify(data));
|
|
63
|
+
excludeList.forEach((key) => {
|
|
64
|
+
delete logData[key];
|
|
65
|
+
});
|
|
66
|
+
return logData;
|
|
67
|
+
}
|
|
61
68
|
onDeviceData(deviceData) {
|
|
62
69
|
if (!this.started || this.isStopped())
|
|
63
70
|
return;
|
|
@@ -65,7 +72,8 @@ class AntFEAdapter extends AntAdapter_1.default {
|
|
|
65
72
|
try {
|
|
66
73
|
if (this.onDataFn && !(this.ignoreHrm && this.ignoreBike && this.ignorePower) && !this.paused) {
|
|
67
74
|
if (!this.lastUpdate || (Date.now() - this.lastUpdate) > this.updateFrequency) {
|
|
68
|
-
this.
|
|
75
|
+
const logData = this.getLogData(deviceData, ['PairedDevices', 'RawData']);
|
|
76
|
+
this.logger.logEvent({ message: 'onDeviceData', data: logData });
|
|
69
77
|
this.data = this.updateData(this.data, deviceData);
|
|
70
78
|
const data = this.transformData(this.data);
|
|
71
79
|
this.onDataFn(data);
|
|
@@ -37,11 +37,12 @@ export default class KettlerRacerAdapter extends DeviceAdapterBase implements De
|
|
|
37
37
|
private iv;
|
|
38
38
|
private requests;
|
|
39
39
|
private data;
|
|
40
|
-
private
|
|
40
|
+
private internalData;
|
|
41
41
|
private kettlerData;
|
|
42
42
|
private updateBusy;
|
|
43
43
|
private requestBusy;
|
|
44
44
|
private comms;
|
|
45
|
+
private prevDistance;
|
|
45
46
|
constructor(protocol: DeviceProtocol, settings: DeviceSettings);
|
|
46
47
|
isBike(): boolean;
|
|
47
48
|
isPower(): boolean;
|
|
@@ -76,7 +77,7 @@ export default class KettlerRacerAdapter extends DeviceAdapterBase implements De
|
|
|
76
77
|
parseExtendedStatus(data: string): KettlerExtendedBikeData;
|
|
77
78
|
parseStatus(data: string): KettlerBikeData;
|
|
78
79
|
check(): Promise<boolean>;
|
|
79
|
-
start(props?: any): Promise<
|
|
80
|
+
start(props?: any): Promise<IncyclistBikeData>;
|
|
80
81
|
startUpdatePull(): void;
|
|
81
82
|
stop(): Promise<boolean>;
|
|
82
83
|
pause(): Promise<boolean>;
|
|
@@ -91,7 +92,7 @@ export default class KettlerRacerAdapter extends DeviceAdapterBase implements De
|
|
|
91
92
|
sendData(): void;
|
|
92
93
|
refreshRequests(): void;
|
|
93
94
|
processClientRequest(request: any): Promise<unknown>;
|
|
94
|
-
waitForOpened(): Promise<boolean>;
|
|
95
|
+
waitForOpened(retries?: boolean): Promise<boolean>;
|
|
95
96
|
waitForClosed(): Promise<boolean>;
|
|
96
97
|
getSupportedCyclingModes(): any[];
|
|
97
98
|
setCyclingMode(mode: CyclingMode | string, settings?: any): void;
|
|
@@ -220,7 +220,7 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
220
220
|
}
|
|
221
221
|
const distance = parseInt(states[3]);
|
|
222
222
|
if (!isNaN(distance)) {
|
|
223
|
-
result.distance = distance;
|
|
223
|
+
result.distance = distance * 100;
|
|
224
224
|
}
|
|
225
225
|
const requestedPower = parseInt(states[4]);
|
|
226
226
|
if (!isNaN(requestedPower)) {
|
|
@@ -228,7 +228,7 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
228
228
|
}
|
|
229
229
|
const energy = parseInt(states[5]);
|
|
230
230
|
if (!isNaN(energy)) {
|
|
231
|
-
result.
|
|
231
|
+
result.energy = energy;
|
|
232
232
|
}
|
|
233
233
|
const timeStr = states[6];
|
|
234
234
|
const time = timeStr.split(':');
|
|
@@ -284,46 +284,49 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
286
|
start(props) {
|
|
287
|
-
this
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
info.checkDone = yield this.check();
|
|
293
|
-
}
|
|
287
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
288
|
+
this.logger.logEvent({ message: 'start()' });
|
|
289
|
+
var info = {};
|
|
290
|
+
yield this.waitForOpened(true);
|
|
291
|
+
return utils_1.runWithRetries(() => __awaiter(this, void 0, void 0, function* () {
|
|
294
292
|
try {
|
|
295
|
-
if (!info.
|
|
296
|
-
info.
|
|
293
|
+
if (!info.checkDone) {
|
|
294
|
+
info.checkDone = yield this.check();
|
|
297
295
|
}
|
|
296
|
+
try {
|
|
297
|
+
if (!info.started) {
|
|
298
|
+
info.started = yield this.startTraining();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
this.logger.logEvent({ message: 'Error', error: e.message });
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
yield this.setPower(100);
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
this.logger.logEvent({ message: 'Error', error: e.message });
|
|
309
|
+
}
|
|
310
|
+
if (!info.data) {
|
|
311
|
+
yield this.update();
|
|
312
|
+
info.data = this.data;
|
|
313
|
+
}
|
|
314
|
+
return info.data;
|
|
298
315
|
}
|
|
299
|
-
catch (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
if (!info.data) {
|
|
309
|
-
yield this.update();
|
|
310
|
-
info.data = this.data;
|
|
311
|
-
}
|
|
312
|
-
return info.data;
|
|
313
|
-
}
|
|
314
|
-
catch (err) {
|
|
315
|
-
try {
|
|
316
|
-
yield this.reset();
|
|
317
|
-
}
|
|
318
|
-
catch (e) {
|
|
319
|
-
this.logger.logEvent({ message: 'Error', error: e.message });
|
|
316
|
+
catch (err) {
|
|
317
|
+
try {
|
|
318
|
+
yield this.reset();
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
this.logger.logEvent({ message: 'Error', error: e.message });
|
|
322
|
+
}
|
|
323
|
+
throw (new Error(`could not start device, reason:${err.message}`));
|
|
320
324
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return data;
|
|
325
|
+
}), 5, 1000)
|
|
326
|
+
.then((data) => {
|
|
327
|
+
this.startUpdatePull();
|
|
328
|
+
return data;
|
|
329
|
+
});
|
|
327
330
|
});
|
|
328
331
|
}
|
|
329
332
|
startUpdatePull() {
|
|
@@ -394,6 +397,10 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
394
397
|
}
|
|
395
398
|
transformData(internalData, bikeData) {
|
|
396
399
|
let data = {};
|
|
400
|
+
const prevDistance = this.prevDistance || 0;
|
|
401
|
+
let distance = internalData.distanceInternal - prevDistance;
|
|
402
|
+
if (distance < 0)
|
|
403
|
+
distance = internalData.distanceInternal < 100 ? internalData.distanceInternal : 0;
|
|
397
404
|
data.heartrate = internalData.heartrate;
|
|
398
405
|
data.timestamp = Date.now();
|
|
399
406
|
data.deviceTime = bikeData.time;
|
|
@@ -401,8 +408,10 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
401
408
|
data.speed = internalData.speed;
|
|
402
409
|
data.power = internalData.power;
|
|
403
410
|
data.cadence = internalData.pedalRpm;
|
|
404
|
-
data.distance =
|
|
411
|
+
data.distance = distance;
|
|
405
412
|
data.deviceDistanceCounter = bikeData.distance;
|
|
413
|
+
data.internalDistanceCounter = internalData.distanceInternal;
|
|
414
|
+
this.prevDistance = internalData.distanceInternal;
|
|
406
415
|
}
|
|
407
416
|
if (this.ignoreHrm)
|
|
408
417
|
delete this.data.heartrate;
|
|
@@ -415,13 +424,14 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
415
424
|
update() {
|
|
416
425
|
return __awaiter(this, void 0, void 0, function* () {
|
|
417
426
|
this.updateBusy = true;
|
|
418
|
-
this.getStatus()
|
|
427
|
+
return this.getStatus()
|
|
419
428
|
.then((bikeData) => {
|
|
420
429
|
if (bikeData) {
|
|
421
430
|
try {
|
|
422
431
|
this.kettlerData = bikeData;
|
|
423
432
|
let data = this.mapData(bikeData);
|
|
424
433
|
data = this.getCyclingMode().updateData(data);
|
|
434
|
+
this.internalData = data;
|
|
425
435
|
this.data = this.transformData(data, bikeData);
|
|
426
436
|
}
|
|
427
437
|
catch (err) {
|
|
@@ -533,34 +543,38 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
533
543
|
resolve(bikeRequest);
|
|
534
544
|
}));
|
|
535
545
|
}
|
|
536
|
-
waitForOpened() {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
const cleanup = () => {
|
|
545
|
-
this.comms.removeAllListeners();
|
|
546
|
-
};
|
|
547
|
-
const onOpen = () => {
|
|
548
|
-
resolve(true);
|
|
549
|
-
cleanup();
|
|
550
|
-
};
|
|
551
|
-
const onError = (err) => { reject(err); cleanup(); };
|
|
552
|
-
const onClose = () => { cleanup(); };
|
|
553
|
-
this.comms.on('opened', onOpen);
|
|
554
|
-
this.comms.on('closed', onClose);
|
|
555
|
-
this.comms.on('error', onError);
|
|
556
|
-
this.logger.logEvent({ message: 'opening', port: this.getPort() });
|
|
557
|
-
this.comms.open();
|
|
558
|
-
}
|
|
559
|
-
catch (err) {
|
|
560
|
-
this.logger.logEvent({ message: 'error', fn: 'waitForOpened()', error: err.message || err });
|
|
561
|
-
reject(err);
|
|
546
|
+
waitForOpened(retries = false) {
|
|
547
|
+
const run = (resolve, reject) => {
|
|
548
|
+
try {
|
|
549
|
+
if (this.comms.isConnected()) {
|
|
550
|
+
resolve(true);
|
|
551
|
+
return;
|
|
562
552
|
}
|
|
563
|
-
|
|
553
|
+
const cleanup = () => {
|
|
554
|
+
this.comms.removeAllListeners();
|
|
555
|
+
};
|
|
556
|
+
const onOpen = () => {
|
|
557
|
+
resolve(true);
|
|
558
|
+
cleanup();
|
|
559
|
+
};
|
|
560
|
+
const onError = (err) => { reject(err); cleanup(); };
|
|
561
|
+
const onClose = () => { cleanup(); };
|
|
562
|
+
this.comms.on('opened', onOpen);
|
|
563
|
+
this.comms.on('closed', onClose);
|
|
564
|
+
this.comms.on('error', onError);
|
|
565
|
+
this.logger.logEvent({ message: 'opening', port: this.getPort() });
|
|
566
|
+
this.comms.open();
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
this.logger.logEvent({ message: 'error', fn: 'waitForOpened()', error: err.message || err });
|
|
570
|
+
reject(err);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
if (!retries) {
|
|
574
|
+
return new Promise((resolve, reject) => run(resolve, reject));
|
|
575
|
+
}
|
|
576
|
+
return utils_1.runWithRetries(() => {
|
|
577
|
+
return new Promise((resolve, reject) => run(resolve, reject));
|
|
564
578
|
}, 3, 1000);
|
|
565
579
|
}
|
|
566
580
|
waitForClosed() {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import DeviceProtocolBase, { DeviceSettings } from '../DeviceProtocol';
|
|
1
|
+
import DeviceProtocolBase, { DeviceSettings, DeviceProtocol } from '../DeviceProtocol';
|
|
2
2
|
import DeviceAdapter from '../Device';
|
|
3
3
|
import { EventLogger } from 'gd-eventlog';
|
|
4
|
+
import CyclingMode, { IncyclistBikeData } from '../CyclingMode';
|
|
5
|
+
interface SimulatorSettings extends DeviceSettings {
|
|
6
|
+
isBot?: boolean;
|
|
7
|
+
settings?: any;
|
|
8
|
+
}
|
|
4
9
|
export declare class Simulator extends DeviceAdapter {
|
|
5
10
|
static NAME: string;
|
|
6
11
|
logger: EventLogger;
|
|
@@ -14,13 +19,23 @@ export declare class Simulator extends DeviceAdapter {
|
|
|
14
19
|
slope: number;
|
|
15
20
|
limit: any;
|
|
16
21
|
startProps?: any;
|
|
17
|
-
|
|
22
|
+
cyclingMode: CyclingMode;
|
|
23
|
+
startTS: number;
|
|
24
|
+
data: IncyclistBikeData;
|
|
25
|
+
isBot: boolean;
|
|
26
|
+
ignoreHrm: boolean;
|
|
27
|
+
constructor(protocol?: DeviceProtocol, props?: SimulatorSettings);
|
|
18
28
|
isBike(): boolean;
|
|
19
29
|
isHrm(): boolean;
|
|
20
30
|
isPower(): boolean;
|
|
21
31
|
getID(): string;
|
|
22
32
|
getName(): string;
|
|
23
33
|
getPort(): string;
|
|
34
|
+
setIgnoreHrm(ignore: any): void;
|
|
35
|
+
getSupportedCyclingModes(): Array<any>;
|
|
36
|
+
getDefaultCyclingMode(): CyclingMode;
|
|
37
|
+
getCyclingMode(): CyclingMode;
|
|
38
|
+
setCyclingMode(mode: CyclingMode | string, settings?: any): void;
|
|
24
39
|
start(props?: any): Promise<unknown>;
|
|
25
40
|
stop(): Promise<boolean>;
|
|
26
41
|
pause(): Promise<boolean>;
|
|
@@ -30,12 +45,12 @@ export declare class Simulator extends DeviceAdapter {
|
|
|
30
45
|
slower(): void;
|
|
31
46
|
update(): void;
|
|
32
47
|
calculateDistance(speedKps: any, timeS: any): number;
|
|
33
|
-
sendUpdate(request: any):
|
|
48
|
+
sendUpdate(request: any): import("../CyclingMode").UpdateRequest;
|
|
34
49
|
}
|
|
35
50
|
export default class SimulatorProtocol extends DeviceProtocolBase {
|
|
36
51
|
static NAME: string;
|
|
37
52
|
constructor();
|
|
38
|
-
add(settings:
|
|
53
|
+
add(settings: SimulatorSettings): any;
|
|
39
54
|
getName(): string;
|
|
40
55
|
getInterfaces(): string[];
|
|
41
56
|
isBike(): boolean;
|
|
@@ -43,3 +58,4 @@ export default class SimulatorProtocol extends DeviceProtocolBase {
|
|
|
43
58
|
isPower(): boolean;
|
|
44
59
|
getDevices(): import("../DeviceProtocol").Device[];
|
|
45
60
|
}
|
|
61
|
+
export {};
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
3
12
|
if (mod && mod.__esModule) return mod;
|
|
4
13
|
var result = {};
|
|
@@ -14,9 +23,10 @@ const DeviceProtocol_1 = __importStar(require("../DeviceProtocol"));
|
|
|
14
23
|
const DeviceRegistry_1 = __importDefault(require("../DeviceRegistry"));
|
|
15
24
|
const Device_1 = __importDefault(require("../Device"));
|
|
16
25
|
const gd_eventlog_1 = require("gd-eventlog");
|
|
17
|
-
const
|
|
26
|
+
const simulator_mode_1 = __importDefault(require("./simulator-mode"));
|
|
27
|
+
const DEFAULT_SETTINGS = { name: 'Simulator', port: '', isBot: false };
|
|
18
28
|
class Simulator extends Device_1.default {
|
|
19
|
-
constructor(protocol) {
|
|
29
|
+
constructor(protocol, props = DEFAULT_SETTINGS) {
|
|
20
30
|
const proto = protocol || DeviceRegistry_1.default.findByName('Simulator');
|
|
21
31
|
super(proto);
|
|
22
32
|
this.logger = new gd_eventlog_1.EventLogger(Simulator.NAME);
|
|
@@ -27,8 +37,16 @@ class Simulator extends Device_1.default {
|
|
|
27
37
|
this.time = undefined;
|
|
28
38
|
this.iv = undefined;
|
|
29
39
|
this.started = false;
|
|
40
|
+
this.paused = false;
|
|
30
41
|
this.slope = 0;
|
|
31
42
|
this.limit = {};
|
|
43
|
+
this.startTS = undefined;
|
|
44
|
+
this.data = { isPedalling: false, power: 0, pedalRpm: 0, speed: 0, heartrate: 0, distanceInternal: 0 };
|
|
45
|
+
this.isBot = props.isBot || false;
|
|
46
|
+
this.ignoreHrm = false;
|
|
47
|
+
const name = this.getCyclingMode().getName();
|
|
48
|
+
const modeSettings = this.isBot ? props.settings || {} : this.getCyclingMode().getSettings();
|
|
49
|
+
this.setCyclingMode(name, modeSettings);
|
|
32
50
|
}
|
|
33
51
|
isBike() { return true; }
|
|
34
52
|
isHrm() { return false; }
|
|
@@ -36,28 +54,66 @@ class Simulator extends Device_1.default {
|
|
|
36
54
|
getID() { return Simulator.NAME; }
|
|
37
55
|
getName() { return Simulator.NAME; }
|
|
38
56
|
getPort() { return 'local'; }
|
|
39
|
-
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
setIgnoreHrm(ignore) {
|
|
58
|
+
this.ignoreHrm = ignore;
|
|
59
|
+
}
|
|
60
|
+
getSupportedCyclingModes() {
|
|
61
|
+
const supported = [];
|
|
62
|
+
supported.push(simulator_mode_1.default);
|
|
63
|
+
return supported;
|
|
64
|
+
}
|
|
65
|
+
getDefaultCyclingMode() {
|
|
66
|
+
return new simulator_mode_1.default(this);
|
|
67
|
+
}
|
|
68
|
+
getCyclingMode() {
|
|
69
|
+
if (!this.cyclingMode)
|
|
70
|
+
this.setCyclingMode(this.getDefaultCyclingMode());
|
|
71
|
+
return this.cyclingMode;
|
|
72
|
+
}
|
|
73
|
+
setCyclingMode(mode, settings) {
|
|
74
|
+
let selectedMode;
|
|
75
|
+
if (typeof mode === 'string') {
|
|
76
|
+
const supported = this.getSupportedCyclingModes();
|
|
77
|
+
const CyclingModeClass = supported.find(M => { const m = new M(this); return m.getName() === mode; });
|
|
78
|
+
if (CyclingModeClass) {
|
|
79
|
+
this.cyclingMode = new CyclingModeClass(this, settings);
|
|
80
|
+
return;
|
|
45
81
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
82
|
+
selectedMode = this.getDefaultCyclingMode();
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
selectedMode = mode;
|
|
86
|
+
}
|
|
87
|
+
this.cyclingMode = selectedMode;
|
|
88
|
+
this.cyclingMode.setSettings(settings);
|
|
89
|
+
}
|
|
90
|
+
start(props) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
this.startProps = props;
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
if (!this.isBot)
|
|
95
|
+
this.logger.logEvent({ message: 'start', iv: this.iv });
|
|
96
|
+
if (this.started) {
|
|
97
|
+
return resolve({ started: true, error: undefined });
|
|
98
|
+
}
|
|
99
|
+
this.started = true;
|
|
100
|
+
this.time = Date.now();
|
|
101
|
+
this.startTS = this.time;
|
|
102
|
+
if (this.iv !== undefined) {
|
|
103
|
+
clearInterval(this.iv);
|
|
104
|
+
this.iv = undefined;
|
|
105
|
+
}
|
|
106
|
+
this.iv = setInterval(() => this.update(), 1000);
|
|
107
|
+
if (!this.isBot)
|
|
108
|
+
this.logger.logEvent({ message: 'started' });
|
|
109
|
+
resolve({ started: true, error: undefined });
|
|
110
|
+
});
|
|
56
111
|
});
|
|
57
112
|
}
|
|
58
113
|
stop() {
|
|
59
114
|
return new Promise((resolve, reject) => {
|
|
60
|
-
|
|
115
|
+
if (!this.isBot)
|
|
116
|
+
this.logger.logEvent({ message: 'stop', iv: this.iv });
|
|
61
117
|
this.started = false;
|
|
62
118
|
clearInterval(this.iv);
|
|
63
119
|
this.iv = undefined;
|
|
@@ -69,7 +125,8 @@ class Simulator extends Device_1.default {
|
|
|
69
125
|
return new Promise((resolve, reject) => {
|
|
70
126
|
if (!this.started)
|
|
71
127
|
return reject(new Error('illegal state - pause() has been called before start()'));
|
|
72
|
-
|
|
128
|
+
if (!this.isBot)
|
|
129
|
+
this.logger.logEvent({ message: 'pause', iv: this.iv });
|
|
73
130
|
this.paused = true;
|
|
74
131
|
resolve(true);
|
|
75
132
|
});
|
|
@@ -78,7 +135,8 @@ class Simulator extends Device_1.default {
|
|
|
78
135
|
return new Promise((resolve, reject) => {
|
|
79
136
|
if (!this.started)
|
|
80
137
|
reject(new Error('illegal state - resume() has been called before start()'));
|
|
81
|
-
|
|
138
|
+
if (!this.isBot)
|
|
139
|
+
this.logger.logEvent({ message: 'resume', iv: this.iv });
|
|
82
140
|
this.paused = false;
|
|
83
141
|
resolve(true);
|
|
84
142
|
});
|
|
@@ -114,29 +172,26 @@ class Simulator extends Device_1.default {
|
|
|
114
172
|
}
|
|
115
173
|
}
|
|
116
174
|
update() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.speed
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
let distance = this.calculateDistance(this.speed, timespan / 1000);
|
|
139
|
-
let data = { speed: this.speed, cadence: Math.round(this.cadence), power: Math.round(this.power), timespan, distance };
|
|
175
|
+
const startDelay = this.getCyclingMode().getSetting('delay');
|
|
176
|
+
const timeSinceStart = Date.now() - this.startTS;
|
|
177
|
+
if (startDelay && timeSinceStart < startDelay * 1000)
|
|
178
|
+
return;
|
|
179
|
+
const prevDist = this.data.distanceInternal;
|
|
180
|
+
this.data = this.getCyclingMode().updateData(this.data);
|
|
181
|
+
let data = {
|
|
182
|
+
speed: this.data.speed,
|
|
183
|
+
slope: this.data.slope,
|
|
184
|
+
power: this.data.power,
|
|
185
|
+
cadence: this.data.pedalRpm,
|
|
186
|
+
distance: this.data.distanceInternal - prevDist,
|
|
187
|
+
heartrate: Math.round(this.data.power - 10 + Math.random() * 20),
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
deviceTime: Math.round((Date.now() - this.startTS) / 1000),
|
|
190
|
+
deviceDistanceCounter: this.data.distanceInternal
|
|
191
|
+
};
|
|
192
|
+
this.paused = (this.data.speed === 0);
|
|
193
|
+
if (this.ignoreHrm)
|
|
194
|
+
delete data.heartrate;
|
|
140
195
|
if (this.onDataFn) {
|
|
141
196
|
this.onDataFn(data);
|
|
142
197
|
}
|
|
@@ -145,15 +200,9 @@ class Simulator extends Device_1.default {
|
|
|
145
200
|
return timeS * speedKps / 3.6;
|
|
146
201
|
}
|
|
147
202
|
sendUpdate(request) {
|
|
148
|
-
this.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (Object.keys(r).length === 1)
|
|
152
|
-
return this.limit;
|
|
153
|
-
delete r.refresh;
|
|
154
|
-
}
|
|
155
|
-
this.limit = r;
|
|
156
|
-
return this.limit;
|
|
203
|
+
if (this.paused)
|
|
204
|
+
return;
|
|
205
|
+
return this.getCyclingMode().sendBikeUpdate(request);
|
|
157
206
|
}
|
|
158
207
|
}
|
|
159
208
|
exports.Simulator = Simulator;
|
|
@@ -161,9 +210,12 @@ Simulator.NAME = 'Simulator';
|
|
|
161
210
|
class SimulatorProtocol extends DeviceProtocol_1.default {
|
|
162
211
|
constructor() {
|
|
163
212
|
super();
|
|
164
|
-
this.devices
|
|
213
|
+
this.devices = [];
|
|
165
214
|
}
|
|
166
215
|
add(settings) {
|
|
216
|
+
let device = new Simulator(this, settings);
|
|
217
|
+
this.devices.push(device);
|
|
218
|
+
return device;
|
|
167
219
|
}
|
|
168
220
|
getName() {
|
|
169
221
|
return SimulatorProtocol.NAME;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EventLogger } from "gd-eventlog";
|
|
2
|
+
import CyclingMode, { CyclingModeBase, CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
|
|
3
|
+
import { Simulator } from "./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
|
+
calculateTargetPower(request: any, updateMode?: boolean): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
calculateTargetPower(request, updateMode = true) {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.default = SimulatorCyclingMode;
|
package/lib/utils.js
CHANGED
|
@@ -17,16 +17,18 @@ function runWithRetries(fn, maxRetries, timeBetween) {
|
|
|
17
17
|
let retries = 0;
|
|
18
18
|
let tLastFailure = undefined;
|
|
19
19
|
let busy = false;
|
|
20
|
-
|
|
20
|
+
let iv = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
21
21
|
const tNow = Date.now();
|
|
22
|
-
if (busy)
|
|
22
|
+
if (busy) {
|
|
23
23
|
return;
|
|
24
|
+
}
|
|
24
25
|
if (tLastFailure === undefined || tNow - tLastFailure > timeBetween) {
|
|
25
26
|
try {
|
|
26
27
|
busy = true;
|
|
27
28
|
const data = yield fn();
|
|
28
|
-
busy = false;
|
|
29
29
|
clearInterval(iv);
|
|
30
|
+
iv = undefined;
|
|
31
|
+
busy = false;
|
|
30
32
|
return resolve(data);
|
|
31
33
|
}
|
|
32
34
|
catch (err) {
|
|
@@ -34,6 +36,7 @@ function runWithRetries(fn, maxRetries, timeBetween) {
|
|
|
34
36
|
retries++;
|
|
35
37
|
if (retries >= maxRetries) {
|
|
36
38
|
clearInterval(iv);
|
|
39
|
+
iv = undefined;
|
|
37
40
|
busy = false;
|
|
38
41
|
return reject(err);
|
|
39
42
|
}
|