incyclist-devices 1.4.21 → 1.4.24
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/ant/AntScanner.js +3 -1
- package/lib/ant/antfe/AntFEAdapter.d.ts +1 -0
- package/lib/ant/antfe/AntFEAdapter.js +9 -1
- package/lib/ant/antpwr/AntPWRAdapter.d.ts +24 -0
- package/lib/ant/antpwr/AntPWRAdapter.js +252 -0
- package/lib/ant/antpwr/pwr-adapter.d.ts +48 -0
- package/lib/ant/antpwr/pwr-adapter.js +259 -0
- package/lib/kettler/ergo-racer/adapter.js +1 -1
- package/lib/kettler/ergo-racer/modes/power-meter.d.ts +2 -2
- package/lib/kettler/ergo-racer/modes/power-meter.js +12 -4
- package/lib/modes/power-meter.d.ts +18 -0
- package/lib/modes/power-meter.js +86 -0
- 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/package.json +1 -1
package/lib/ant/AntScanner.js
CHANGED
|
@@ -22,6 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
const gd_eventlog_1 = require("gd-eventlog");
|
|
23
23
|
const DeviceProtocol_1 = __importStar(require("../DeviceProtocol"));
|
|
24
24
|
const AntHrmAdapter_1 = __importDefault(require("./anthrm/AntHrmAdapter"));
|
|
25
|
+
const pwr_adapter_1 = __importDefault(require("./antpwr/pwr-adapter"));
|
|
25
26
|
const AntFEAdapter_1 = __importDefault(require("./antfe/AntFEAdapter"));
|
|
26
27
|
const LOGGER_NAME = 'ANT+Scanner';
|
|
27
28
|
const DEFAULT_SCAN_TIMEOUT = 30000;
|
|
@@ -89,7 +90,8 @@ class AntProtocol extends DeviceProtocol_1.default {
|
|
|
89
90
|
this.scanning = false;
|
|
90
91
|
this.profiles = [
|
|
91
92
|
{ name: 'Heartrate Monitor', Adapter: AntHrmAdapter_1.default },
|
|
92
|
-
{ name: 'Smart Trainer', Adapter: AntFEAdapter_1.default }
|
|
93
|
+
{ name: 'Smart Trainer', Adapter: AntFEAdapter_1.default },
|
|
94
|
+
{ name: 'Power Meter', Adapter: pwr_adapter_1.default }
|
|
93
95
|
];
|
|
94
96
|
}
|
|
95
97
|
add(settings) {
|
|
@@ -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);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import AntAdapter from '../AntAdapter';
|
|
2
|
+
export default class AntFEAdapter extends AntAdapter {
|
|
3
|
+
started: boolean;
|
|
4
|
+
starting: boolean;
|
|
5
|
+
connected: boolean;
|
|
6
|
+
distanceInternal?: number;
|
|
7
|
+
workerId?: any;
|
|
8
|
+
currentCmd?: any;
|
|
9
|
+
constructor(DeviceID: any, port: any, stick: any, protocol: any);
|
|
10
|
+
isBike(): boolean;
|
|
11
|
+
isHrm(): boolean;
|
|
12
|
+
isPower(): boolean;
|
|
13
|
+
getProfile(): string;
|
|
14
|
+
getName(): string;
|
|
15
|
+
getDisplayName(): string;
|
|
16
|
+
onAttached(): void;
|
|
17
|
+
getLogData(data: any, excludeList: any): any;
|
|
18
|
+
onDeviceData(deviceData: any): void;
|
|
19
|
+
onDeviceEvent(data: any): void;
|
|
20
|
+
updateData(data: any, deviceData: any): any;
|
|
21
|
+
transformData(bikeData: any): any;
|
|
22
|
+
start(props?: any): Promise<any>;
|
|
23
|
+
stop(): Promise<boolean>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
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
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const gd_eventlog_1 = require("gd-eventlog");
|
|
16
|
+
const AntAdapter_1 = __importDefault(require("../AntAdapter"));
|
|
17
|
+
const utils_1 = require("../utils");
|
|
18
|
+
const floatVal = (d) => d ? parseFloat(d) : d;
|
|
19
|
+
const intVal = (d) => d ? parseInt(d) : d;
|
|
20
|
+
const hex = (v) => Math.abs(v).toString(16).toUpperCase();
|
|
21
|
+
const DEFAULT_START_TIMEOUT = 5000;
|
|
22
|
+
class AntFEAdapter extends AntAdapter_1.default {
|
|
23
|
+
constructor(DeviceID, port, stick, protocol) {
|
|
24
|
+
super(protocol);
|
|
25
|
+
this.logger = new gd_eventlog_1.EventLogger('Ant+PWR');
|
|
26
|
+
this.logger.logEvent({ message: 'Ant+PWR Adapter created', DeviceID, port });
|
|
27
|
+
this.deviceID = DeviceID;
|
|
28
|
+
this.port = port;
|
|
29
|
+
this.stick = stick;
|
|
30
|
+
this.deviceData = {
|
|
31
|
+
DeviceID
|
|
32
|
+
};
|
|
33
|
+
this.data = {};
|
|
34
|
+
this.started = false;
|
|
35
|
+
this.starting = false;
|
|
36
|
+
this.connected = false;
|
|
37
|
+
}
|
|
38
|
+
isBike() { return true; }
|
|
39
|
+
isHrm() { return false; }
|
|
40
|
+
isPower() { return true; }
|
|
41
|
+
getProfile() {
|
|
42
|
+
return 'Power Meter';
|
|
43
|
+
}
|
|
44
|
+
getName() {
|
|
45
|
+
return `Ant+PWR ${this.deviceID}`;
|
|
46
|
+
}
|
|
47
|
+
getDisplayName() {
|
|
48
|
+
const { DeviceID, ManId } = this.deviceData;
|
|
49
|
+
return `${utils_1.getBrand(ManId)} PWR ${DeviceID}`;
|
|
50
|
+
}
|
|
51
|
+
onAttached() {
|
|
52
|
+
this.logger.logEvent({ message: 'Device connected' });
|
|
53
|
+
this.connected = true;
|
|
54
|
+
}
|
|
55
|
+
getLogData(data, excludeList) {
|
|
56
|
+
const logData = JSON.parse(JSON.stringify(data));
|
|
57
|
+
excludeList.forEach((key) => {
|
|
58
|
+
delete logData[key];
|
|
59
|
+
});
|
|
60
|
+
return logData;
|
|
61
|
+
}
|
|
62
|
+
onDeviceData(deviceData) {
|
|
63
|
+
if (!this.started || this.isStopped())
|
|
64
|
+
return;
|
|
65
|
+
this.deviceData = deviceData;
|
|
66
|
+
try {
|
|
67
|
+
if (this.onDataFn && !(this.ignoreBike && this.ignorePower) && !this.paused) {
|
|
68
|
+
if (!this.lastUpdate || (Date.now() - this.lastUpdate) > this.updateFrequency) {
|
|
69
|
+
const logData = this.getLogData(deviceData, ['PairedDevices', 'RawData']);
|
|
70
|
+
this.logger.logEvent({ message: 'onDeviceData', data: logData });
|
|
71
|
+
this.data = this.updateData(this.data, deviceData);
|
|
72
|
+
const data = this.transformData(this.data);
|
|
73
|
+
this.onDataFn(data);
|
|
74
|
+
this.lastUpdate = Date.now();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
onDeviceEvent(data) {
|
|
82
|
+
try {
|
|
83
|
+
const cmdInfo = this.currentCmd;
|
|
84
|
+
if (!cmdInfo)
|
|
85
|
+
return;
|
|
86
|
+
const msg = cmdInfo.msg.readUInt8(2);
|
|
87
|
+
const Constants = this.getProtocol().getAnt().Constants;
|
|
88
|
+
const { expectedResponse } = cmdInfo;
|
|
89
|
+
if (data.message === msg) {
|
|
90
|
+
if (expectedResponse === undefined && data.code === Constants.EVENT_TRANSFER_TX_COMPLETED) {
|
|
91
|
+
this.currentCmd.response = { success: true, message: hex(data.message), code: hex(data.code) };
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (expectedResponse === undefined && data.code !== Constants.EVENT_TRANSFER_TX_COMPLETED) {
|
|
95
|
+
this.currentCmd.response = { success: false, message: hex(data.message), code: hex(data.code) };
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (data.message === 1) {
|
|
100
|
+
if (expectedResponse !== undefined && data.code === expectedResponse) {
|
|
101
|
+
this.currentCmd.response = { success: true, message: hex(data.message), code: hex(data.code) };
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (expectedResponse === undefined && (data.code === Constants.EVENT_TRANSFER_TX_COMPLETED || data.code === 3)) {
|
|
105
|
+
this.currentCmd.response = { success: true, message: hex(data.message), code: hex(data.code) };
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (data.code === Constants.EVENT_TRANSFER_TX_FAILED || data.code === Constants.EVENT_CHANNEL_COLLISION) {
|
|
109
|
+
this.currentCmd.response = { success: false, message: hex(data.message), code: hex(data.code) };
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (this.currentCmd !== undefined && data.message === Constants.MESSAGE_CHANNEL_ACKNOWLEDGED_DATA && data.code === 31) {
|
|
114
|
+
this.logger.log("could not send (TRANSFER_IN_PROGRESS)");
|
|
115
|
+
this.currentCmd.response = { success: false, message: hex(data.message), code: hex(data.code) };
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.logger.logEvent({ message: "Incoming Event ", event: { message: hex(data.message), code: hex(data.code) } });
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
this.logger.logEvent({ message: 'Error', fn: 'parseEvent', event: { message: hex(data.message), code: hex(data.code) }, error: err.message || err });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
updateData(data, deviceData) {
|
|
125
|
+
return;
|
|
126
|
+
if (data.distanceOffs === undefined)
|
|
127
|
+
data.distanceOffs = 0;
|
|
128
|
+
data.speed = (deviceData.VirtualSpeed !== undefined ? deviceData.VirtualSpeed : deviceData.RealSpeed) * 3.6;
|
|
129
|
+
data.slope = (deviceData.Incline !== undefined ? deviceData.Incline : data.slope);
|
|
130
|
+
data.power = (deviceData.InstantaneousPower !== undefined ? deviceData.InstantaneousPower : data.power);
|
|
131
|
+
data.pedalRpm = (deviceData.Cadence !== undefined ? deviceData.Cadence : data.pedalRpm);
|
|
132
|
+
data.heartrate = (deviceData.HeartRate !== undefined ? deviceData.HeartRate : data.heartrate);
|
|
133
|
+
if (deviceData.Distance !== undefined && deviceData.Distance !== 0) {
|
|
134
|
+
data.distanceInternal = deviceData.Distance - data.distanceOffs;
|
|
135
|
+
data.distance = data.distanceInternal / 1000;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
if (this.lastUpdate && deviceData.Cadence !== undefined && deviceData.Cadence > 0 && data.speed) {
|
|
139
|
+
const t = (Date.now() - this.lastUpdate) / 1000;
|
|
140
|
+
const prevDistance = data.distanceInternal || 0;
|
|
141
|
+
data.distanceInternal = Math.round(data.speed / 3.6 * t) + prevDistance;
|
|
142
|
+
data.distance = data.distanceInternal / 1000;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
transformData(bikeData) {
|
|
148
|
+
return;
|
|
149
|
+
if (bikeData === undefined)
|
|
150
|
+
return;
|
|
151
|
+
let distance = 0;
|
|
152
|
+
if (this.distanceInternal !== undefined && bikeData.distanceInternal !== undefined) {
|
|
153
|
+
distance = intVal(bikeData.distanceInternal - this.distanceInternal);
|
|
154
|
+
}
|
|
155
|
+
if (bikeData.distanceInternal !== undefined)
|
|
156
|
+
this.distanceInternal = bikeData.distanceInternal;
|
|
157
|
+
let data = {
|
|
158
|
+
speed: floatVal(bikeData.speed),
|
|
159
|
+
slope: floatVal(bikeData.slope),
|
|
160
|
+
power: intVal(bikeData.power),
|
|
161
|
+
cadence: intVal(bikeData.pedalRpm),
|
|
162
|
+
heartrate: intVal(bikeData.heartrate),
|
|
163
|
+
distance,
|
|
164
|
+
timestamp: Date.now()
|
|
165
|
+
};
|
|
166
|
+
if (this.ignorePower) {
|
|
167
|
+
delete data.power;
|
|
168
|
+
delete data.cadence;
|
|
169
|
+
}
|
|
170
|
+
if (this.ignoreBike) {
|
|
171
|
+
data = { heartrate: data.heartrate };
|
|
172
|
+
}
|
|
173
|
+
if (this.ignoreHrm)
|
|
174
|
+
delete data.heartrate;
|
|
175
|
+
return data;
|
|
176
|
+
}
|
|
177
|
+
start(props) {
|
|
178
|
+
const _super = Object.create(null, {
|
|
179
|
+
start: { get: () => super.start }
|
|
180
|
+
});
|
|
181
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
182
|
+
yield _super.start.call(this, props);
|
|
183
|
+
this.logger.logEvent({ message: 'start()', props });
|
|
184
|
+
const opts = props || {};
|
|
185
|
+
const { args = {} } = opts;
|
|
186
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
if (this.ignoreBike && this.ignorePower) {
|
|
188
|
+
this.logger.logEvent({ message: 'start() not done: bike disabled' });
|
|
189
|
+
return resolve(false);
|
|
190
|
+
}
|
|
191
|
+
if (this.starting) {
|
|
192
|
+
this.logger.logEvent({ message: 'start() not done: bike starting' });
|
|
193
|
+
return resolve(false);
|
|
194
|
+
}
|
|
195
|
+
if (this.started) {
|
|
196
|
+
this.logger.logEvent({ message: 'start() done: bike was already started' });
|
|
197
|
+
return resolve(true);
|
|
198
|
+
}
|
|
199
|
+
this.starting = true;
|
|
200
|
+
const Ant = this.getProtocol().getAnt();
|
|
201
|
+
const protocol = this.getProtocol();
|
|
202
|
+
let start = Date.now();
|
|
203
|
+
let timeout = start + (args.timeout || DEFAULT_START_TIMEOUT);
|
|
204
|
+
const iv = setInterval(() => {
|
|
205
|
+
if (Date.now() > timeout) {
|
|
206
|
+
clearInterval(iv);
|
|
207
|
+
this.starting = false;
|
|
208
|
+
reject(new Error('timeout'));
|
|
209
|
+
}
|
|
210
|
+
if (this.isStopped()) {
|
|
211
|
+
clearInterval(iv);
|
|
212
|
+
this.starting = false;
|
|
213
|
+
reject(new Error('stopped'));
|
|
214
|
+
}
|
|
215
|
+
}, 100);
|
|
216
|
+
protocol.attachSensors(this, Ant.BicyclePowerSensor, 'powerData')
|
|
217
|
+
.then(() => {
|
|
218
|
+
this.starting = false;
|
|
219
|
+
this.started = true;
|
|
220
|
+
clearInterval(iv);
|
|
221
|
+
resolve(true);
|
|
222
|
+
})
|
|
223
|
+
.catch(err => reject(err));
|
|
224
|
+
}));
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
stop() {
|
|
228
|
+
const _super = Object.create(null, {
|
|
229
|
+
stop: { get: () => super.stop }
|
|
230
|
+
});
|
|
231
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
yield _super.stop.call(this);
|
|
233
|
+
this.logger.logEvent({ message: 'stop()' });
|
|
234
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
this.starting = false;
|
|
236
|
+
return resolve(true);
|
|
237
|
+
this.started = false;
|
|
238
|
+
if (this.ignoreHrm)
|
|
239
|
+
return resolve(false);
|
|
240
|
+
try {
|
|
241
|
+
const protocol = this.getProtocol();
|
|
242
|
+
yield protocol.detachSensor(this);
|
|
243
|
+
resolve(true);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
reject(err);
|
|
247
|
+
}
|
|
248
|
+
}));
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
exports.default = AntFEAdapter;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import AntAdapter from '../AntAdapter';
|
|
2
|
+
import { IncyclistBikeData } from '../../CyclingMode';
|
|
3
|
+
import CyclingMode from '../../CyclingMode';
|
|
4
|
+
import { DeviceData } from '../../Device';
|
|
5
|
+
export declare type AntPwrData = {
|
|
6
|
+
DeviceID: number;
|
|
7
|
+
PedalPower?: number;
|
|
8
|
+
RightPedalPower?: number;
|
|
9
|
+
LeftPedalPower?: number;
|
|
10
|
+
Cadence?: number;
|
|
11
|
+
AccumulatedPower?: number;
|
|
12
|
+
Power?: number;
|
|
13
|
+
offset?: number;
|
|
14
|
+
EventCount?: number;
|
|
15
|
+
TimeStamp?: number;
|
|
16
|
+
Slope?: number;
|
|
17
|
+
TorqueTicksStamp?: number;
|
|
18
|
+
CalculatedCadence?: number;
|
|
19
|
+
CalculatedTorque?: number;
|
|
20
|
+
CalculatedPower?: number;
|
|
21
|
+
};
|
|
22
|
+
export default class AntFEAdapter extends AntAdapter {
|
|
23
|
+
started: boolean;
|
|
24
|
+
starting: boolean;
|
|
25
|
+
connected: boolean;
|
|
26
|
+
distanceInternal?: number;
|
|
27
|
+
workerId?: any;
|
|
28
|
+
currentCmd?: any;
|
|
29
|
+
mode: CyclingMode;
|
|
30
|
+
constructor(DeviceID: any, port: any, stick: any, protocol: any);
|
|
31
|
+
isBike(): boolean;
|
|
32
|
+
isHrm(): boolean;
|
|
33
|
+
isPower(): boolean;
|
|
34
|
+
getProfile(): string;
|
|
35
|
+
getName(): string;
|
|
36
|
+
getDisplayName(): string;
|
|
37
|
+
getCyclingMode(): CyclingMode;
|
|
38
|
+
getDefaultCyclingMode(): CyclingMode;
|
|
39
|
+
onAttached(): void;
|
|
40
|
+
getLogData(data: any, excludeList: any): any;
|
|
41
|
+
onDeviceData(deviceData: AntPwrData): void;
|
|
42
|
+
onDeviceEvent(data: any): void;
|
|
43
|
+
sendUpdate(request: any): void;
|
|
44
|
+
mapData(deviceData: AntPwrData): IncyclistBikeData;
|
|
45
|
+
transformData(bikeData: IncyclistBikeData): DeviceData;
|
|
46
|
+
start(props?: any): Promise<any>;
|
|
47
|
+
stop(): Promise<boolean>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
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
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const gd_eventlog_1 = require("gd-eventlog");
|
|
16
|
+
const AntAdapter_1 = __importDefault(require("../AntAdapter"));
|
|
17
|
+
const utils_1 = require("../utils");
|
|
18
|
+
const power_meter_1 = __importDefault(require("../../modes/power-meter"));
|
|
19
|
+
const floatVal = (d) => d ? parseFloat(d) : d;
|
|
20
|
+
const intVal = (d) => d ? parseInt(d) : d;
|
|
21
|
+
const hex = (v) => Math.abs(v).toString(16).toUpperCase();
|
|
22
|
+
const DEFAULT_START_TIMEOUT = 5000;
|
|
23
|
+
class AntFEAdapter extends AntAdapter_1.default {
|
|
24
|
+
constructor(DeviceID, port, stick, protocol) {
|
|
25
|
+
super(protocol);
|
|
26
|
+
this.logger = new gd_eventlog_1.EventLogger('Ant+PWR');
|
|
27
|
+
this.logger.logEvent({ message: 'Ant+PWR Adapter created', DeviceID, port });
|
|
28
|
+
this.deviceID = DeviceID;
|
|
29
|
+
this.port = port;
|
|
30
|
+
this.stick = stick;
|
|
31
|
+
this.deviceData = {
|
|
32
|
+
DeviceID
|
|
33
|
+
};
|
|
34
|
+
this.data = {};
|
|
35
|
+
this.started = false;
|
|
36
|
+
this.starting = false;
|
|
37
|
+
this.connected = false;
|
|
38
|
+
this.mode = this.getDefaultCyclingMode();
|
|
39
|
+
}
|
|
40
|
+
isBike() { return true; }
|
|
41
|
+
isHrm() { return false; }
|
|
42
|
+
isPower() { return true; }
|
|
43
|
+
getProfile() {
|
|
44
|
+
return 'Power Meter';
|
|
45
|
+
}
|
|
46
|
+
getName() {
|
|
47
|
+
return `Ant+PWR ${this.deviceID}`;
|
|
48
|
+
}
|
|
49
|
+
getDisplayName() {
|
|
50
|
+
const { DeviceID, ManId } = this.deviceData;
|
|
51
|
+
return `${utils_1.getBrand(ManId)} PWR ${DeviceID}`;
|
|
52
|
+
}
|
|
53
|
+
getCyclingMode() {
|
|
54
|
+
if (!this.mode)
|
|
55
|
+
this.mode = this.getDefaultCyclingMode();
|
|
56
|
+
return this.mode;
|
|
57
|
+
}
|
|
58
|
+
getDefaultCyclingMode() {
|
|
59
|
+
return new power_meter_1.default(this);
|
|
60
|
+
}
|
|
61
|
+
onAttached() {
|
|
62
|
+
this.logger.logEvent({ message: 'Device connected' });
|
|
63
|
+
this.connected = true;
|
|
64
|
+
}
|
|
65
|
+
getLogData(data, excludeList) {
|
|
66
|
+
const logData = JSON.parse(JSON.stringify(data));
|
|
67
|
+
excludeList.forEach((key) => {
|
|
68
|
+
delete logData[key];
|
|
69
|
+
});
|
|
70
|
+
return logData;
|
|
71
|
+
}
|
|
72
|
+
onDeviceData(deviceData) {
|
|
73
|
+
if (!this.started || this.isStopped())
|
|
74
|
+
return;
|
|
75
|
+
this.deviceData = deviceData;
|
|
76
|
+
try {
|
|
77
|
+
if (this.onDataFn && !(this.ignoreBike && this.ignorePower) && !this.paused) {
|
|
78
|
+
if (!this.lastUpdate || (Date.now() - this.lastUpdate) > this.updateFrequency) {
|
|
79
|
+
const logData = this.getLogData(deviceData, ['PairedDevices', 'RawData']);
|
|
80
|
+
this.logger.logEvent({ message: 'onDeviceData', data: logData });
|
|
81
|
+
let incyclistData = this.mapData(deviceData);
|
|
82
|
+
incyclistData = this.getCyclingMode().updateData(incyclistData);
|
|
83
|
+
const data = this.transformData(incyclistData);
|
|
84
|
+
this.onDataFn(data);
|
|
85
|
+
this.lastUpdate = Date.now();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
onDeviceEvent(data) {
|
|
93
|
+
try {
|
|
94
|
+
const cmdInfo = this.currentCmd;
|
|
95
|
+
if (!cmdInfo)
|
|
96
|
+
return;
|
|
97
|
+
const msg = cmdInfo.msg.readUInt8(2);
|
|
98
|
+
const Constants = this.getProtocol().getAnt().Constants;
|
|
99
|
+
const { expectedResponse } = cmdInfo;
|
|
100
|
+
if (data.message === msg) {
|
|
101
|
+
if (expectedResponse === undefined && data.code === Constants.EVENT_TRANSFER_TX_COMPLETED) {
|
|
102
|
+
this.currentCmd.response = { success: true, message: hex(data.message), code: hex(data.code) };
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (expectedResponse === undefined && data.code !== Constants.EVENT_TRANSFER_TX_COMPLETED) {
|
|
106
|
+
this.currentCmd.response = { success: false, message: hex(data.message), code: hex(data.code) };
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (data.message === 1) {
|
|
111
|
+
if (expectedResponse !== undefined && data.code === expectedResponse) {
|
|
112
|
+
this.currentCmd.response = { success: true, message: hex(data.message), code: hex(data.code) };
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (expectedResponse === undefined && (data.code === Constants.EVENT_TRANSFER_TX_COMPLETED || data.code === 3)) {
|
|
116
|
+
this.currentCmd.response = { success: true, message: hex(data.message), code: hex(data.code) };
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (data.code === Constants.EVENT_TRANSFER_TX_FAILED || data.code === Constants.EVENT_CHANNEL_COLLISION) {
|
|
120
|
+
this.currentCmd.response = { success: false, message: hex(data.message), code: hex(data.code) };
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (this.currentCmd !== undefined && data.message === Constants.MESSAGE_CHANNEL_ACKNOWLEDGED_DATA && data.code === 31) {
|
|
125
|
+
this.logger.log("could not send (TRANSFER_IN_PROGRESS)");
|
|
126
|
+
this.currentCmd.response = { success: false, message: hex(data.message), code: hex(data.code) };
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.logger.logEvent({ message: "Incoming Event ", event: { message: hex(data.message), code: hex(data.code) } });
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
this.logger.logEvent({ message: 'Error', fn: 'parseEvent', event: { message: hex(data.message), code: hex(data.code) }, error: err.message || err });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
sendUpdate(request) {
|
|
136
|
+
if (this.paused)
|
|
137
|
+
return;
|
|
138
|
+
this.getCyclingMode().sendBikeUpdate(request);
|
|
139
|
+
}
|
|
140
|
+
mapData(deviceData) {
|
|
141
|
+
const data = {
|
|
142
|
+
isPedalling: false,
|
|
143
|
+
power: 0,
|
|
144
|
+
pedalRpm: 0,
|
|
145
|
+
speed: 0,
|
|
146
|
+
heartrate: 0,
|
|
147
|
+
distanceInternal: 0,
|
|
148
|
+
slope: undefined,
|
|
149
|
+
time: undefined
|
|
150
|
+
};
|
|
151
|
+
data.slope = (deviceData.Slope !== undefined ? deviceData.Slope : data.slope);
|
|
152
|
+
data.power = (deviceData.Power !== undefined ? deviceData.Power : data.power);
|
|
153
|
+
data.pedalRpm = (deviceData.Cadence !== undefined ? deviceData.Cadence : data.pedalRpm);
|
|
154
|
+
data.time = (deviceData.TimeStamp !== undefined ? deviceData.TimeStamp : data.time);
|
|
155
|
+
data.isPedalling = data.pedalRpm > 0;
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
transformData(bikeData) {
|
|
159
|
+
if (bikeData === undefined)
|
|
160
|
+
return;
|
|
161
|
+
let distance = 0;
|
|
162
|
+
if (this.distanceInternal !== undefined && bikeData.distanceInternal !== undefined) {
|
|
163
|
+
distance = intVal(bikeData.distanceInternal - this.distanceInternal);
|
|
164
|
+
}
|
|
165
|
+
if (bikeData.distanceInternal !== undefined)
|
|
166
|
+
this.distanceInternal = bikeData.distanceInternal;
|
|
167
|
+
let data = {
|
|
168
|
+
speed: floatVal(bikeData.speed),
|
|
169
|
+
slope: floatVal(bikeData.slope),
|
|
170
|
+
power: intVal(bikeData.power),
|
|
171
|
+
cadence: intVal(bikeData.pedalRpm),
|
|
172
|
+
distance,
|
|
173
|
+
timestamp: Date.now()
|
|
174
|
+
};
|
|
175
|
+
if (this.ignorePower) {
|
|
176
|
+
delete data.power;
|
|
177
|
+
delete data.cadence;
|
|
178
|
+
}
|
|
179
|
+
if (this.ignoreBike) {
|
|
180
|
+
data = {};
|
|
181
|
+
}
|
|
182
|
+
return data;
|
|
183
|
+
}
|
|
184
|
+
start(props) {
|
|
185
|
+
const _super = Object.create(null, {
|
|
186
|
+
start: { get: () => super.start }
|
|
187
|
+
});
|
|
188
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
189
|
+
yield _super.start.call(this, props);
|
|
190
|
+
this.logger.logEvent({ message: 'start()', props });
|
|
191
|
+
const opts = props || {};
|
|
192
|
+
const { args = {} } = opts;
|
|
193
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
194
|
+
if (this.ignoreBike && this.ignorePower) {
|
|
195
|
+
this.logger.logEvent({ message: 'start() not done: bike disabled' });
|
|
196
|
+
return resolve(false);
|
|
197
|
+
}
|
|
198
|
+
if (this.starting) {
|
|
199
|
+
this.logger.logEvent({ message: 'start() not done: bike starting' });
|
|
200
|
+
return resolve(false);
|
|
201
|
+
}
|
|
202
|
+
if (this.started) {
|
|
203
|
+
this.logger.logEvent({ message: 'start() done: bike was already started' });
|
|
204
|
+
return resolve(true);
|
|
205
|
+
}
|
|
206
|
+
this.starting = true;
|
|
207
|
+
const Ant = this.getProtocol().getAnt();
|
|
208
|
+
const protocol = this.getProtocol();
|
|
209
|
+
let start = Date.now();
|
|
210
|
+
let timeout = start + (args.timeout || DEFAULT_START_TIMEOUT);
|
|
211
|
+
const iv = setInterval(() => {
|
|
212
|
+
if (Date.now() > timeout) {
|
|
213
|
+
clearInterval(iv);
|
|
214
|
+
this.starting = false;
|
|
215
|
+
reject(new Error('timeout'));
|
|
216
|
+
}
|
|
217
|
+
if (this.isStopped()) {
|
|
218
|
+
clearInterval(iv);
|
|
219
|
+
this.starting = false;
|
|
220
|
+
reject(new Error('stopped'));
|
|
221
|
+
}
|
|
222
|
+
}, 100);
|
|
223
|
+
protocol.attachSensors(this, Ant.BicyclePowerSensor, 'powerData')
|
|
224
|
+
.then(() => {
|
|
225
|
+
this.starting = false;
|
|
226
|
+
this.started = true;
|
|
227
|
+
clearInterval(iv);
|
|
228
|
+
resolve(true);
|
|
229
|
+
})
|
|
230
|
+
.catch(err => reject(err));
|
|
231
|
+
}));
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
stop() {
|
|
235
|
+
const _super = Object.create(null, {
|
|
236
|
+
stop: { get: () => super.stop }
|
|
237
|
+
});
|
|
238
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
239
|
+
yield _super.stop.call(this);
|
|
240
|
+
this.logger.logEvent({ message: 'stop()' });
|
|
241
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
242
|
+
this.starting = false;
|
|
243
|
+
return resolve(true);
|
|
244
|
+
this.started = false;
|
|
245
|
+
if (this.ignoreHrm)
|
|
246
|
+
return resolve(false);
|
|
247
|
+
try {
|
|
248
|
+
const protocol = this.getProtocol();
|
|
249
|
+
yield protocol.detachSensor(this);
|
|
250
|
+
resolve(true);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
reject(err);
|
|
254
|
+
}
|
|
255
|
+
}));
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
exports.default = AntFEAdapter;
|
|
@@ -23,7 +23,7 @@ const Device_1 = __importStar(require("../../Device"));
|
|
|
23
23
|
const gd_eventlog_1 = require("gd-eventlog");
|
|
24
24
|
const comms_1 = __importDefault(require("../comms"));
|
|
25
25
|
const utils_1 = require("../../utils");
|
|
26
|
-
const power_meter_1 = __importDefault(require("
|
|
26
|
+
const power_meter_1 = __importDefault(require("../../modes/power-meter"));
|
|
27
27
|
class KettlerRacerAdapter extends Device_1.default {
|
|
28
28
|
constructor(protocol, settings) {
|
|
29
29
|
super(protocol);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { EventLogger } from 'gd-eventlog';
|
|
2
2
|
import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest, CyclingModeBase } from '../../../CyclingMode';
|
|
3
|
-
import
|
|
3
|
+
import { DeviceAdapter } from '../../../Device';
|
|
4
4
|
export default class PowerMeterCyclingMode extends CyclingModeBase implements CyclingMode {
|
|
5
5
|
logger: EventLogger;
|
|
6
6
|
data: IncyclistBikeData;
|
|
7
7
|
prevRequest: UpdateRequest;
|
|
8
8
|
prevUpdateTS: number;
|
|
9
9
|
hasBikeUpdate: boolean;
|
|
10
|
-
constructor(adapter:
|
|
10
|
+
constructor(adapter: DeviceAdapter, props?: Settings);
|
|
11
11
|
getName(): string;
|
|
12
12
|
getDescription(): string;
|
|
13
13
|
getProperties(): CyclingModeProperty[];
|
|
@@ -5,6 +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 Device_1 = require("../../../Device");
|
|
8
9
|
const calculations_1 = __importDefault(require("../../../calculations"));
|
|
9
10
|
const config = {
|
|
10
11
|
name: 'PowerMeter',
|
|
@@ -16,7 +17,8 @@ class PowerMeterCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
|
16
17
|
super(adapter, props);
|
|
17
18
|
this.prevUpdateTS = 0;
|
|
18
19
|
this.hasBikeUpdate = false;
|
|
19
|
-
|
|
20
|
+
const a = adapter;
|
|
21
|
+
this.logger = (a && a.getLogger) ? a.getLogger() : undefined;
|
|
20
22
|
if (!this.logger)
|
|
21
23
|
this.logger = new gd_eventlog_1.EventLogger('PowerMeter');
|
|
22
24
|
}
|
|
@@ -56,10 +58,16 @@ class PowerMeterCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
|
56
58
|
power = 0;
|
|
57
59
|
}
|
|
58
60
|
let ts = Date.now();
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
let v = speed / 3.6;
|
|
61
|
+
const a = this.adapter;
|
|
62
|
+
const m = a.getWeight ? a.getWeight() : Device_1.DEFAULT_BIKE_WEIGHT + Device_1.DEFAULT_USER_WEIGHT;
|
|
62
63
|
let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
|
|
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;
|
|
63
71
|
distanceInternal += Math.round(v * duration);
|
|
64
72
|
data.speed = parseFloat(speed.toFixed(1));
|
|
65
73
|
data.power = Math.round(power);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EventLogger } from 'gd-eventlog';
|
|
2
|
+
import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest, CyclingModeBase } from '../CyclingMode';
|
|
3
|
+
import { DeviceAdapter } from '../Device';
|
|
4
|
+
export default class PowerMeterCyclingMode extends CyclingModeBase implements CyclingMode {
|
|
5
|
+
logger: EventLogger;
|
|
6
|
+
data: IncyclistBikeData;
|
|
7
|
+
prevRequest: UpdateRequest;
|
|
8
|
+
prevUpdateTS: number;
|
|
9
|
+
hasBikeUpdate: boolean;
|
|
10
|
+
constructor(adapter: DeviceAdapter, props?: Settings);
|
|
11
|
+
getName(): string;
|
|
12
|
+
getDescription(): string;
|
|
13
|
+
getProperties(): CyclingModeProperty[];
|
|
14
|
+
getProperty(name: string): CyclingModeProperty;
|
|
15
|
+
getBikeInitRequest(): UpdateRequest;
|
|
16
|
+
sendBikeUpdate(request: UpdateRequest): UpdateRequest;
|
|
17
|
+
updateData(data: IncyclistBikeData): IncyclistBikeData;
|
|
18
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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 Device_1 = require("../Device");
|
|
9
|
+
const calculations_1 = __importDefault(require("../calculations"));
|
|
10
|
+
const config = {
|
|
11
|
+
name: 'PowerMeter',
|
|
12
|
+
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
|
+
properties: []
|
|
14
|
+
};
|
|
15
|
+
class PowerMeterCyclingMode extends CyclingMode_1.CyclingModeBase {
|
|
16
|
+
constructor(adapter, props) {
|
|
17
|
+
super(adapter, props);
|
|
18
|
+
this.prevUpdateTS = 0;
|
|
19
|
+
this.hasBikeUpdate = false;
|
|
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');
|
|
24
|
+
}
|
|
25
|
+
getName() {
|
|
26
|
+
return config.name;
|
|
27
|
+
}
|
|
28
|
+
getDescription() {
|
|
29
|
+
return config.description;
|
|
30
|
+
}
|
|
31
|
+
getProperties() {
|
|
32
|
+
return config.properties;
|
|
33
|
+
}
|
|
34
|
+
getProperty(name) {
|
|
35
|
+
return config.properties.find(p => p.name === name);
|
|
36
|
+
}
|
|
37
|
+
getBikeInitRequest() {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
sendBikeUpdate(request) {
|
|
41
|
+
if (request.slope)
|
|
42
|
+
this.data.slope = request.slope;
|
|
43
|
+
this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest });
|
|
44
|
+
this.prevRequest = {};
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
updateData(data) {
|
|
48
|
+
try {
|
|
49
|
+
const prevData = this.data || {};
|
|
50
|
+
const prevRequest = this.prevRequest || {};
|
|
51
|
+
const bikeData = JSON.parse(JSON.stringify(data));
|
|
52
|
+
let power = data.power || 0;
|
|
53
|
+
let speed = data.speed || 0;
|
|
54
|
+
let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
|
|
55
|
+
let distanceInternal = prevData.distanceInternal || 0;
|
|
56
|
+
if (!bikeData.pedalRpm || bikeData.isPedalling === false) {
|
|
57
|
+
speed = 0;
|
|
58
|
+
power = 0;
|
|
59
|
+
}
|
|
60
|
+
let ts = Date.now();
|
|
61
|
+
const a = this.adapter;
|
|
62
|
+
const m = a.getWeight ? a.getWeight() : Device_1.DEFAULT_BIKE_WEIGHT + Device_1.DEFAULT_USER_WEIGHT;
|
|
63
|
+
let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
|
|
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));
|
|
73
|
+
data.power = Math.round(power);
|
|
74
|
+
data.distanceInternal = Math.round(distanceInternal);
|
|
75
|
+
data.slope = slope;
|
|
76
|
+
this.logger.logEvent({ message: "updateData result", data, bikeData, prevRequest: {}, prevSpeed: prevData.speed });
|
|
77
|
+
this.data = JSON.parse(JSON.stringify(data));
|
|
78
|
+
this.prevUpdateTS = ts;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.default = PowerMeterCyclingMode;
|
|
@@ -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;
|