incyclist-devices 1.4.47 → 1.4.50
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/DeviceSupport.js +1 -5
- package/lib/ant/AntScanner.js +1 -5
- package/lib/ble/ble-device.d.ts +11 -2
- package/lib/ble/ble-device.js +28 -5
- package/lib/ble/ble-erg-mode.d.ts +1 -8
- package/lib/ble/ble-erg-mode.js +20 -33
- package/lib/ble/ble-st-mode.d.ts +1 -10
- package/lib/ble/ble-st-mode.js +33 -76
- package/lib/ble/ble.d.ts +0 -1
- package/lib/ble/fm.d.ts +26 -2
- package/lib/ble/fm.js +237 -17
- package/lib/ble/hrm.js +1 -0
- package/lib/ble/incyclist-protocol.js +1 -5
- package/lib/ble/pwr.d.ts +1 -0
- package/lib/ble/pwr.js +8 -0
- package/lib/daum/DaumAdapter.js +1 -5
- package/lib/daum/classic/DaumClassicProtocol.js +1 -5
- package/lib/daum/premium/DaumPremiumProtocol.js +1 -5
- package/lib/kettler/comms.d.ts +0 -1
- package/lib/kettler/ergo-racer/adapter.js +1 -5
- package/lib/kettler/ergo-racer/protocol.d.ts +1 -1
- package/lib/kettler/ergo-racer/protocol.js +1 -5
- package/lib/modes/power-meter.js +11 -4
- package/lib/simulator/Simulator.d.ts +1 -1
- package/lib/simulator/Simulator.js +1 -5
- package/package.json +1 -1
package/lib/DeviceSupport.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
package/lib/ant/AntScanner.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
package/lib/ble/ble-device.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ interface BleDeviceConstructProps extends BleDeviceProps {
|
|
|
6
6
|
log?: boolean;
|
|
7
7
|
logger?: EventLogger;
|
|
8
8
|
}
|
|
9
|
+
declare type CommandQueueItem = {
|
|
10
|
+
uuid: string;
|
|
11
|
+
data: Buffer;
|
|
12
|
+
resolve: any;
|
|
13
|
+
reject: any;
|
|
14
|
+
timeout: any;
|
|
15
|
+
};
|
|
9
16
|
export declare abstract class BleDevice extends BleDeviceClass {
|
|
10
17
|
id: string;
|
|
11
18
|
address: string;
|
|
@@ -19,8 +26,10 @@ export declare abstract class BleDevice extends BleDeviceClass {
|
|
|
19
26
|
deviceInfo: BleDeviceInfo;
|
|
20
27
|
isInitialized: boolean;
|
|
21
28
|
subscribedCharacteristics: string[];
|
|
29
|
+
writeQueue: CommandQueueItem[];
|
|
22
30
|
constructor(props?: BleDeviceConstructProps);
|
|
23
31
|
logEvent(event: any): void;
|
|
32
|
+
setLogger(logger: EventLogger): void;
|
|
24
33
|
setInterface(ble: BleInterfaceClass): void;
|
|
25
34
|
cleanupListeners(): void;
|
|
26
35
|
onDisconnect(): void;
|
|
@@ -32,8 +41,8 @@ export declare abstract class BleDevice extends BleDeviceClass {
|
|
|
32
41
|
connect(props?: ConnectProps): Promise<boolean>;
|
|
33
42
|
disconnect(): Promise<boolean>;
|
|
34
43
|
abstract getProfile(): string;
|
|
35
|
-
|
|
36
|
-
write(characteristicUuid: string, data: Buffer, withoutResponse?: boolean): Promise<
|
|
44
|
+
onData(characteristic: string, data: Buffer): void;
|
|
45
|
+
write(characteristicUuid: string, data: Buffer, withoutResponse?: boolean): Promise<ArrayBuffer>;
|
|
37
46
|
read(characteristicUuid: string): Promise<Uint8Array>;
|
|
38
47
|
getDeviceInfo(): Promise<BleDeviceInfo>;
|
|
39
48
|
}
|
package/lib/ble/ble-device.js
CHANGED
|
@@ -26,6 +26,7 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
26
26
|
this.characteristics = [];
|
|
27
27
|
this.subscribedCharacteristics = [];
|
|
28
28
|
this.isInitialized = false;
|
|
29
|
+
this.writeQueue = [];
|
|
29
30
|
if (props.peripheral) {
|
|
30
31
|
const { id, address, advertisement, state } = props.peripheral;
|
|
31
32
|
this.peripheral = props.peripheral;
|
|
@@ -38,7 +39,7 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
38
39
|
if (props.logger) {
|
|
39
40
|
this.logger = props.logger;
|
|
40
41
|
}
|
|
41
|
-
else if (props.log) {
|
|
42
|
+
else if (props.log !== false) {
|
|
42
43
|
this.logger = new gd_eventlog_1.EventLogger('BleDevice');
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -50,6 +51,9 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
50
51
|
console.log('~~~BLE:', event);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
setLogger(logger) {
|
|
55
|
+
this.logger = logger;
|
|
56
|
+
}
|
|
53
57
|
setInterface(ble) {
|
|
54
58
|
this.ble = ble;
|
|
55
59
|
}
|
|
@@ -103,6 +107,7 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
103
107
|
return Promise.resolve(true);
|
|
104
108
|
return this.getDeviceInfo().then(() => {
|
|
105
109
|
this.emit('deviceInfo', this.deviceInfo);
|
|
110
|
+
this.logEvent(Object.assign({ message: 'ftms device init done' }, this.deviceInfo));
|
|
106
111
|
this.isInitialized = true;
|
|
107
112
|
return true;
|
|
108
113
|
});
|
|
@@ -235,9 +240,20 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
235
240
|
}
|
|
236
241
|
});
|
|
237
242
|
}
|
|
243
|
+
onData(characteristic, data) {
|
|
244
|
+
if (this.writeQueue.length > 0) {
|
|
245
|
+
const writeIdx = this.writeQueue.findIndex(i => i.uuid === characteristic.toLocaleLowerCase());
|
|
246
|
+
if (writeIdx !== -1) {
|
|
247
|
+
const writeItem = this.writeQueue[writeIdx];
|
|
248
|
+
this.writeQueue.splice(writeIdx, 1);
|
|
249
|
+
if (writeItem.resolve)
|
|
250
|
+
writeItem.resolve(data);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
238
254
|
write(characteristicUuid, data, withoutResponse = false) {
|
|
239
255
|
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
-
if (this.subscribedCharacteristics.find(c => c === characteristicUuid) === undefined) {
|
|
256
|
+
if (!withoutResponse && this.subscribedCharacteristics.find(c => c === characteristicUuid) === undefined) {
|
|
241
257
|
const connector = this.ble.getConnector(this.peripheral);
|
|
242
258
|
connector.on(characteristicUuid, (uuid, data) => {
|
|
243
259
|
this.onData(uuid, data);
|
|
@@ -251,11 +267,18 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
251
267
|
reject(new Error('Characteristic not found'));
|
|
252
268
|
return;
|
|
253
269
|
}
|
|
270
|
+
if (withoutResponse) {
|
|
271
|
+
characteristic.write(data, withoutResponse);
|
|
272
|
+
resolve(new ArrayBuffer(0));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const writeId = this.writeQueue.length;
|
|
276
|
+
this.writeQueue.push({ uuid: characteristicUuid.toLocaleLowerCase(), data, resolve, reject, timeout: Date.now() + 1000 });
|
|
254
277
|
characteristic.write(data, withoutResponse, (err) => {
|
|
255
|
-
if (err)
|
|
278
|
+
if (err) {
|
|
279
|
+
this.writeQueue.splice(writeId, 1);
|
|
256
280
|
reject(err);
|
|
257
|
-
|
|
258
|
-
resolve(true);
|
|
281
|
+
}
|
|
259
282
|
});
|
|
260
283
|
});
|
|
261
284
|
});
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import CyclingMode, { CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
|
|
2
2
|
import PowerBasedCyclingModeBase from "../modes/power-base";
|
|
3
3
|
import { FmAdapter } from "./fm";
|
|
4
|
-
export
|
|
5
|
-
rpmUpdated?: boolean;
|
|
6
|
-
gearUpdated?: boolean;
|
|
7
|
-
starting?: boolean;
|
|
8
|
-
tsStart?: number;
|
|
9
|
-
};
|
|
10
|
-
export default class ERGCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
|
|
4
|
+
export default class BleERGCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
|
|
11
5
|
prevRequest: UpdateRequest;
|
|
12
6
|
hasBikeUpdate: boolean;
|
|
13
7
|
chain: number[];
|
|
14
8
|
cassette: number[];
|
|
15
|
-
event: ERGEvent;
|
|
16
9
|
constructor(adapter: FmAdapter, props?: any);
|
|
17
10
|
getName(): string;
|
|
18
11
|
getDescription(): string;
|
package/lib/ble/ble-erg-mode.js
CHANGED
|
@@ -13,11 +13,11 @@ const config = {
|
|
|
13
13
|
{ 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 },
|
|
14
14
|
]
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
const MIN_SPEED = 10;
|
|
17
|
+
class BleERGCyclingMode extends power_base_1.default {
|
|
17
18
|
constructor(adapter, props) {
|
|
18
19
|
super(adapter, props);
|
|
19
20
|
this.hasBikeUpdate = false;
|
|
20
|
-
this.event = {};
|
|
21
21
|
this.initLogger('ERGMode');
|
|
22
22
|
}
|
|
23
23
|
getName() {
|
|
@@ -34,7 +34,7 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
34
34
|
}
|
|
35
35
|
getBikeInitRequest() {
|
|
36
36
|
const startPower = this.getSetting('startPower');
|
|
37
|
-
return { targetPower: startPower };
|
|
37
|
+
return { slope: 0, targetPower: startPower };
|
|
38
38
|
}
|
|
39
39
|
sendBikeUpdate(request) {
|
|
40
40
|
const getData = () => {
|
|
@@ -43,25 +43,18 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
43
43
|
const { pedalRpm, slope, power, speed } = this.data;
|
|
44
44
|
return { pedalRpm, slope, power, speed };
|
|
45
45
|
};
|
|
46
|
-
this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData()
|
|
46
|
+
this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData() });
|
|
47
47
|
let newRequest = {};
|
|
48
48
|
try {
|
|
49
49
|
if (!request || request.reset || Object.keys(request).length === 0) {
|
|
50
50
|
this.prevRequest = {};
|
|
51
|
-
return request
|
|
51
|
+
return request.reset ? { reset: true } : {};
|
|
52
52
|
}
|
|
53
53
|
const prevData = this.data || {};
|
|
54
54
|
if (request.targetPower !== undefined) {
|
|
55
55
|
delete request.slope;
|
|
56
56
|
delete request.refresh;
|
|
57
57
|
}
|
|
58
|
-
if (this.event.starting && request.targetPower === undefined) {
|
|
59
|
-
newRequest.targetPower = this.getSetting('startPower');
|
|
60
|
-
if (this.event.tsStart && Date.now() - this.event.tsStart > 5000) {
|
|
61
|
-
delete this.event.starting;
|
|
62
|
-
delete this.event.tsStart;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
58
|
if (request.refresh) {
|
|
66
59
|
delete request.refresh;
|
|
67
60
|
newRequest.targetPower = this.prevRequest.targetPower;
|
|
@@ -70,14 +63,12 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
70
63
|
if (!this.data)
|
|
71
64
|
this.data = {};
|
|
72
65
|
this.data.slope = request.slope;
|
|
66
|
+
delete request.slope;
|
|
73
67
|
}
|
|
74
68
|
if (request.maxPower !== undefined && request.minPower !== undefined && request.maxPower === request.minPower) {
|
|
75
69
|
request.targetPower = request.maxPower;
|
|
76
|
-
}
|
|
77
|
-
if (request.targetPower !== undefined) {
|
|
78
70
|
newRequest.targetPower = request.targetPower;
|
|
79
71
|
}
|
|
80
|
-
delete request.slope;
|
|
81
72
|
if (request.maxPower !== undefined) {
|
|
82
73
|
if (newRequest.targetPower !== undefined && newRequest.targetPower > request.maxPower) {
|
|
83
74
|
newRequest.targetPower = request.maxPower;
|
|
@@ -89,9 +80,8 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
89
80
|
newRequest.targetPower = request.minPower;
|
|
90
81
|
}
|
|
91
82
|
newRequest.minPower = request.minPower;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
delete newRequest.targetPower;
|
|
83
|
+
if (prevData.power && prevData.power < request.minPower)
|
|
84
|
+
newRequest.targetPower = request.minPower;
|
|
95
85
|
}
|
|
96
86
|
this.prevRequest = JSON.parse(JSON.stringify(request));
|
|
97
87
|
}
|
|
@@ -106,36 +96,33 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
106
96
|
const prevRequest = this.prevRequest || {};
|
|
107
97
|
const data = this.data || {};
|
|
108
98
|
const bikeType = this.getSetting('bikeType').toLowerCase();
|
|
109
|
-
delete this.event.rpmUpdated;
|
|
110
|
-
if (prevData === {} || prevData.speed === undefined || prevData.speed === 0) {
|
|
111
|
-
this.event.starting = true;
|
|
112
|
-
this.event.tsStart = Date.now();
|
|
113
|
-
}
|
|
114
99
|
try {
|
|
115
|
-
const rpm = bikeData.pedalRpm || 0;
|
|
116
100
|
let power = bikeData.power || 0;
|
|
117
101
|
const slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
|
|
118
102
|
const distanceInternal = prevData.distanceInternal || 0;
|
|
119
|
-
if (
|
|
103
|
+
if (bikeData.pedalRpm === 0 || bikeData.isPedalling === false) {
|
|
120
104
|
power = 0;
|
|
121
105
|
}
|
|
122
106
|
const m = this.getWeight();
|
|
123
107
|
const t = this.getTimeSinceLastUpdate();
|
|
124
108
|
const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t, { bikeType });
|
|
125
|
-
|
|
109
|
+
if (power === 0 && speed < MIN_SPEED) {
|
|
110
|
+
data.speed = Math.round(prevData.speed - 1) < 0 ? 0 : Math.round(prevData.speed - 1);
|
|
111
|
+
data.distanceInternal = Math.round(distanceInternal + data.speed / 3.6 * t);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
data.speed = (power === 0 && speed < MIN_SPEED) ? 0 : speed;
|
|
115
|
+
data.distanceInternal = (power === 0 && speed < MIN_SPEED) ? Math.round(distanceInternal) : Math.round(distanceInternal + distance);
|
|
116
|
+
}
|
|
126
117
|
data.power = Math.round(power);
|
|
127
|
-
data.distanceInternal = Math.round(distanceInternal + distance);
|
|
128
118
|
data.slope = slope;
|
|
129
|
-
data.pedalRpm =
|
|
130
|
-
if (data.time !== undefined &&
|
|
119
|
+
data.pedalRpm = bikeData.pedalRpm || 0;
|
|
120
|
+
if (data.time !== undefined && data.speed > 0)
|
|
131
121
|
data.time += t;
|
|
132
122
|
else
|
|
133
123
|
data.time = 0;
|
|
134
124
|
data.heartrate = bikeData.heartrate;
|
|
135
125
|
data.isPedalling = bikeData.isPedalling;
|
|
136
|
-
if (rpm && rpm !== prevData.pedalRpm) {
|
|
137
|
-
this.event.rpmUpdated = true;
|
|
138
|
-
}
|
|
139
126
|
}
|
|
140
127
|
catch (err) {
|
|
141
128
|
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
@@ -145,4 +132,4 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
145
132
|
return data;
|
|
146
133
|
}
|
|
147
134
|
}
|
|
148
|
-
exports.default =
|
|
135
|
+
exports.default = BleERGCyclingMode;
|
package/lib/ble/ble-st-mode.d.ts
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import CyclingMode, { CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
|
|
2
2
|
import PowerBasedCyclingModeBase from "../modes/power-base";
|
|
3
3
|
import { FmAdapter } from "./fm";
|
|
4
|
-
export
|
|
5
|
-
rpmUpdated?: boolean;
|
|
6
|
-
gearUpdated?: boolean;
|
|
7
|
-
starting?: boolean;
|
|
8
|
-
tsStart?: number;
|
|
9
|
-
};
|
|
10
|
-
export default class ERGCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
|
|
4
|
+
export default class FtmsCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
|
|
11
5
|
prevRequest: UpdateRequest;
|
|
12
6
|
hasBikeUpdate: boolean;
|
|
13
|
-
chain: number[];
|
|
14
|
-
cassette: number[];
|
|
15
|
-
event: ERGEvent;
|
|
16
7
|
constructor(adapter: FmAdapter, props?: any);
|
|
17
8
|
getName(): string;
|
|
18
9
|
getDescription(): string;
|
package/lib/ble/ble-st-mode.js
CHANGED
|
@@ -9,16 +9,15 @@ const config = {
|
|
|
9
9
|
name: "Smart Trainer",
|
|
10
10
|
description: "Calculates speed based on power and slope. Slope is set to the device",
|
|
11
11
|
properties: [
|
|
12
|
-
{ key: 'bikeType', name: 'Bike Type', description: '', type: CyclingMode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' }
|
|
13
|
-
{ 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 },
|
|
12
|
+
{ key: 'bikeType', name: 'Bike Type', description: '', type: CyclingMode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' }
|
|
14
13
|
]
|
|
15
14
|
};
|
|
16
|
-
|
|
15
|
+
const MIN_SPEED = 10;
|
|
16
|
+
class FtmsCyclingMode extends power_base_1.default {
|
|
17
17
|
constructor(adapter, props) {
|
|
18
18
|
super(adapter, props);
|
|
19
19
|
this.hasBikeUpdate = false;
|
|
20
|
-
this.
|
|
21
|
-
this.initLogger('ERGMode');
|
|
20
|
+
this.initLogger('FtmsMode');
|
|
22
21
|
}
|
|
23
22
|
getName() {
|
|
24
23
|
return config.name;
|
|
@@ -33,71 +32,32 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
33
32
|
return config.properties.find(p => p.name === name);
|
|
34
33
|
}
|
|
35
34
|
getBikeInitRequest() {
|
|
36
|
-
|
|
37
|
-
return { targetPower: startPower };
|
|
35
|
+
return { slope: 0 };
|
|
38
36
|
}
|
|
39
37
|
sendBikeUpdate(request) {
|
|
40
38
|
const getData = () => {
|
|
41
39
|
if (!this.data)
|
|
42
40
|
return {};
|
|
43
|
-
const { pedalRpm, slope, power, speed } = this.data;
|
|
44
|
-
return { pedalRpm, slope, power, speed };
|
|
41
|
+
const { gear, pedalRpm, slope, power, speed } = this.data;
|
|
42
|
+
return { gear, pedalRpm, slope, power, speed };
|
|
45
43
|
};
|
|
46
|
-
|
|
44
|
+
const event = {};
|
|
45
|
+
if (this.data === undefined)
|
|
46
|
+
event.noData = true;
|
|
47
|
+
if (request.slope !== undefined && (event.noData || Math.abs(request.slope - this.data.slope) >= 0.1))
|
|
48
|
+
event.slopeUpdate = true;
|
|
49
|
+
if (this.prevRequest === undefined)
|
|
50
|
+
event.initialCall = true;
|
|
51
|
+
this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData(), event });
|
|
47
52
|
let newRequest = {};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.prevRequest = {};
|
|
51
|
-
return request || {};
|
|
52
|
-
}
|
|
53
|
-
const prevData = this.data || {};
|
|
54
|
-
if (request.targetPower !== undefined) {
|
|
55
|
-
delete request.slope;
|
|
56
|
-
delete request.refresh;
|
|
57
|
-
}
|
|
58
|
-
if (this.event.starting && request.targetPower === undefined) {
|
|
59
|
-
newRequest.targetPower = this.getSetting('startPower');
|
|
60
|
-
if (this.event.tsStart && Date.now() - this.event.tsStart > 5000) {
|
|
61
|
-
delete this.event.starting;
|
|
62
|
-
delete this.event.tsStart;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (request.refresh) {
|
|
66
|
-
delete request.refresh;
|
|
67
|
-
newRequest.targetPower = this.prevRequest.targetPower;
|
|
68
|
-
}
|
|
69
|
-
if (request.slope !== undefined) {
|
|
70
|
-
if (!this.data)
|
|
71
|
-
this.data = {};
|
|
72
|
-
this.data.slope = request.slope;
|
|
73
|
-
}
|
|
74
|
-
if (request.maxPower !== undefined && request.minPower !== undefined && request.maxPower === request.minPower) {
|
|
75
|
-
request.targetPower = request.maxPower;
|
|
76
|
-
}
|
|
77
|
-
if (request.targetPower !== undefined) {
|
|
78
|
-
newRequest.targetPower = request.targetPower;
|
|
79
|
-
}
|
|
80
|
-
delete request.slope;
|
|
81
|
-
if (request.maxPower !== undefined) {
|
|
82
|
-
if (newRequest.targetPower !== undefined && newRequest.targetPower > request.maxPower) {
|
|
83
|
-
newRequest.targetPower = request.maxPower;
|
|
84
|
-
}
|
|
85
|
-
newRequest.maxPower = request.maxPower;
|
|
86
|
-
}
|
|
87
|
-
if (request.minPower !== undefined) {
|
|
88
|
-
if (newRequest.targetPower !== undefined && newRequest.targetPower < request.minPower) {
|
|
89
|
-
newRequest.targetPower = request.minPower;
|
|
90
|
-
}
|
|
91
|
-
newRequest.minPower = request.minPower;
|
|
92
|
-
}
|
|
93
|
-
if (newRequest.targetPower !== undefined && prevData.power !== undefined && newRequest.targetPower === prevData.power) {
|
|
94
|
-
delete newRequest.targetPower;
|
|
95
|
-
}
|
|
96
|
-
this.prevRequest = JSON.parse(JSON.stringify(request));
|
|
53
|
+
if (request.slope === undefined && request.refresh && this.prevRequest) {
|
|
54
|
+
return this.prevRequest;
|
|
97
55
|
}
|
|
98
|
-
|
|
99
|
-
|
|
56
|
+
if (request.slope !== undefined) {
|
|
57
|
+
newRequest.slope = parseFloat(request.slope.toFixed(1));
|
|
58
|
+
this.data.slope = newRequest.slope;
|
|
100
59
|
}
|
|
60
|
+
this.prevRequest = JSON.parse(JSON.stringify(newRequest));
|
|
101
61
|
return newRequest;
|
|
102
62
|
}
|
|
103
63
|
updateData(bikeData) {
|
|
@@ -106,36 +66,33 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
106
66
|
const prevRequest = this.prevRequest || {};
|
|
107
67
|
const data = this.data || {};
|
|
108
68
|
const bikeType = this.getSetting('bikeType').toLowerCase();
|
|
109
|
-
delete this.event.rpmUpdated;
|
|
110
|
-
if (prevData === {} || prevData.speed === undefined || prevData.speed === 0) {
|
|
111
|
-
this.event.starting = true;
|
|
112
|
-
this.event.tsStart = Date.now();
|
|
113
|
-
}
|
|
114
69
|
try {
|
|
115
|
-
const rpm = bikeData.pedalRpm || 0;
|
|
116
70
|
let power = bikeData.power || 0;
|
|
117
71
|
const slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
|
|
118
72
|
const distanceInternal = prevData.distanceInternal || 0;
|
|
119
|
-
if (
|
|
73
|
+
if (bikeData.pedalRpm === 0 || bikeData.isPedalling === false) {
|
|
120
74
|
power = 0;
|
|
121
75
|
}
|
|
122
76
|
const m = this.getWeight();
|
|
123
77
|
const t = this.getTimeSinceLastUpdate();
|
|
124
78
|
const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t, { bikeType });
|
|
125
|
-
|
|
79
|
+
if (power === 0 && speed < MIN_SPEED) {
|
|
80
|
+
data.speed = Math.round(prevData.speed - 1) < 0 ? 0 : Math.round(prevData.speed - 1);
|
|
81
|
+
data.distanceInternal = Math.round(distanceInternal + data.speed / 3.6 * t);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
data.speed = (power === 0 && speed < MIN_SPEED) ? 0 : speed;
|
|
85
|
+
data.distanceInternal = (power === 0 && speed < MIN_SPEED) ? Math.round(distanceInternal) : Math.round(distanceInternal + distance);
|
|
86
|
+
}
|
|
126
87
|
data.power = Math.round(power);
|
|
127
|
-
data.distanceInternal = Math.round(distanceInternal + distance);
|
|
128
88
|
data.slope = slope;
|
|
129
|
-
data.pedalRpm =
|
|
130
|
-
if (data.time !== undefined
|
|
89
|
+
data.pedalRpm = bikeData.pedalRpm || 0;
|
|
90
|
+
if (data.time !== undefined)
|
|
131
91
|
data.time += t;
|
|
132
92
|
else
|
|
133
93
|
data.time = 0;
|
|
134
94
|
data.heartrate = bikeData.heartrate;
|
|
135
95
|
data.isPedalling = bikeData.isPedalling;
|
|
136
|
-
if (rpm && rpm !== prevData.pedalRpm) {
|
|
137
|
-
this.event.rpmUpdated = true;
|
|
138
|
-
}
|
|
139
96
|
}
|
|
140
97
|
catch (err) {
|
|
141
98
|
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
@@ -145,4 +102,4 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
145
102
|
return data;
|
|
146
103
|
}
|
|
147
104
|
}
|
|
148
|
-
exports.default =
|
|
105
|
+
exports.default = FtmsCyclingMode;
|
package/lib/ble/ble.d.ts
CHANGED
package/lib/ble/fm.d.ts
CHANGED
|
@@ -31,6 +31,9 @@ declare type IndoorBikeData = {
|
|
|
31
31
|
time?: number;
|
|
32
32
|
remainingTime?: number;
|
|
33
33
|
raw?: string;
|
|
34
|
+
targetPower?: number;
|
|
35
|
+
targetInclination?: number;
|
|
36
|
+
status?: string;
|
|
34
37
|
};
|
|
35
38
|
declare type IndoorBikeFeatures = {
|
|
36
39
|
fitnessMachine: number;
|
|
@@ -43,6 +46,10 @@ export default class BleFitnessMachineDevice extends BleDevice {
|
|
|
43
46
|
features: IndoorBikeFeatures;
|
|
44
47
|
hasControl: boolean;
|
|
45
48
|
isCPSubscribed: boolean;
|
|
49
|
+
crr: number;
|
|
50
|
+
cw: number;
|
|
51
|
+
windSpeed: number;
|
|
52
|
+
wheelSize: number;
|
|
46
53
|
constructor(props?: any);
|
|
47
54
|
init(): Promise<boolean>;
|
|
48
55
|
onDisconnect(): void;
|
|
@@ -52,11 +59,25 @@ export default class BleFitnessMachineDevice extends BleDevice {
|
|
|
52
59
|
isPower(): boolean;
|
|
53
60
|
isHrm(): boolean;
|
|
54
61
|
parseHrm(_data: Uint8Array): IndoorBikeData;
|
|
62
|
+
setCrr(crr: number): void;
|
|
63
|
+
getCrr(): number;
|
|
64
|
+
setCw(cw: number): void;
|
|
65
|
+
getCw(): number;
|
|
66
|
+
setWindSpeed(windSpeed: number): void;
|
|
67
|
+
getWindSpeed(): number;
|
|
55
68
|
parseIndoorBikeData(_data: Uint8Array): IndoorBikeData;
|
|
69
|
+
parseFitnessMachineStatus(_data: Uint8Array): IndoorBikeData;
|
|
56
70
|
getFitnessMachineFeatures(): Promise<IndoorBikeFeatures>;
|
|
57
71
|
onData(characteristic: string, data: Buffer): void;
|
|
72
|
+
writeFtmsMessage(requestedOpCode: any, data: any): Promise<number>;
|
|
58
73
|
requestControl(): Promise<boolean>;
|
|
59
|
-
setTargetPower(power: number): Promise<
|
|
74
|
+
setTargetPower(power: number): Promise<boolean>;
|
|
75
|
+
setSlope(slope: any): Promise<boolean>;
|
|
76
|
+
setTargetInclination(inclination: number): Promise<boolean>;
|
|
77
|
+
setIndoorBikeSimulation(windSpeed: number, gradient: number, crr: number, cw: number): Promise<boolean>;
|
|
78
|
+
startRequest(): Promise<boolean>;
|
|
79
|
+
stopRequest(): Promise<boolean>;
|
|
80
|
+
PauseRequest(): Promise<boolean>;
|
|
60
81
|
reset(): void;
|
|
61
82
|
}
|
|
62
83
|
export declare class FmAdapter extends DeviceAdapter {
|
|
@@ -66,7 +87,7 @@ export declare class FmAdapter extends DeviceAdapter {
|
|
|
66
87
|
protocol: DeviceProtocol;
|
|
67
88
|
paused: boolean;
|
|
68
89
|
logger: EventLogger;
|
|
69
|
-
|
|
90
|
+
cyclingMode: CyclingMode;
|
|
70
91
|
distanceInternal: number;
|
|
71
92
|
prevDataTS: number;
|
|
72
93
|
constructor(device: BleDeviceClass, protocol: BleProtocol);
|
|
@@ -77,6 +98,8 @@ export declare class FmAdapter extends DeviceAdapter {
|
|
|
77
98
|
getProfile(): string;
|
|
78
99
|
getName(): string;
|
|
79
100
|
getDisplayName(): string;
|
|
101
|
+
getSupportedCyclingModes(): Array<any>;
|
|
102
|
+
setCyclingMode(mode: string | CyclingMode, settings?: any): void;
|
|
80
103
|
getCyclingMode(): CyclingMode;
|
|
81
104
|
getDefaultCyclingMode(): CyclingMode;
|
|
82
105
|
getPort(): string;
|
|
@@ -87,6 +110,7 @@ export declare class FmAdapter extends DeviceAdapter {
|
|
|
87
110
|
transformData(bikeData: IncyclistBikeData): DeviceData;
|
|
88
111
|
start(props?: any): Promise<any>;
|
|
89
112
|
stop(): Promise<boolean>;
|
|
113
|
+
sendUpdate(request: any): Promise<void>;
|
|
90
114
|
pause(): Promise<boolean>;
|
|
91
115
|
resume(): Promise<boolean>;
|
|
92
116
|
}
|
package/lib/ble/fm.js
CHANGED
|
@@ -18,7 +18,15 @@ const ble_interface_1 = __importDefault(require("./ble-interface"));
|
|
|
18
18
|
const Device_1 = __importDefault(require("../Device"));
|
|
19
19
|
const gd_eventlog_1 = require("gd-eventlog");
|
|
20
20
|
const power_meter_1 = __importDefault(require("../modes/power-meter"));
|
|
21
|
+
const ble_st_mode_1 = __importDefault(require("./ble-st-mode"));
|
|
22
|
+
const ble_erg_mode_1 = __importDefault(require("./ble-erg-mode"));
|
|
21
23
|
const FTMS_CP = '2ad9';
|
|
24
|
+
const cwABike = {
|
|
25
|
+
race: 0.35,
|
|
26
|
+
triathlon: 0.29,
|
|
27
|
+
mountain: 0.57
|
|
28
|
+
};
|
|
29
|
+
const cRR = 0.0036;
|
|
22
30
|
const bit = (nr) => (1 << nr);
|
|
23
31
|
const IndoorBikeDataFlag = {
|
|
24
32
|
MoreData: bit(0),
|
|
@@ -79,6 +87,10 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
79
87
|
this.features = undefined;
|
|
80
88
|
this.hasControl = false;
|
|
81
89
|
this.isCPSubscribed = false;
|
|
90
|
+
this.crr = 0.0033;
|
|
91
|
+
this.cw = 0.6;
|
|
92
|
+
this.windSpeed = 0;
|
|
93
|
+
this.wheelSize = 2100;
|
|
82
94
|
this.data = {};
|
|
83
95
|
}
|
|
84
96
|
init() {
|
|
@@ -138,6 +150,12 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
138
150
|
}
|
|
139
151
|
return Object.assign(Object.assign({}, this.data), { raw: data.toString('hex') });
|
|
140
152
|
}
|
|
153
|
+
setCrr(crr) { this.crr = crr; }
|
|
154
|
+
getCrr() { return this.crr; }
|
|
155
|
+
setCw(cw) { this.cw = cw; }
|
|
156
|
+
getCw() { return this.cw; }
|
|
157
|
+
setWindSpeed(windSpeed) { this.windSpeed = windSpeed; }
|
|
158
|
+
getWindSpeed() { return this.windSpeed; }
|
|
141
159
|
parseIndoorBikeData(_data) {
|
|
142
160
|
const data = Buffer.from(_data);
|
|
143
161
|
const flags = data.readUInt16LE(0);
|
|
@@ -163,7 +181,7 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
163
181
|
offset += 1;
|
|
164
182
|
const dvHigh = data.readUInt16LE(offset);
|
|
165
183
|
offset += 2;
|
|
166
|
-
this.data.totalDistance = dvHigh << 8 + dvLow;
|
|
184
|
+
this.data.totalDistance = (dvHigh << 8) + dvLow;
|
|
167
185
|
}
|
|
168
186
|
if (flags & IndoorBikeDataFlag.ResistanceLevelPresent) {
|
|
169
187
|
this.data.resistanceLevel = data.readInt16LE(offset);
|
|
@@ -199,6 +217,43 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
199
217
|
}
|
|
200
218
|
return Object.assign(Object.assign({}, this.data), { raw: data.toString('hex') });
|
|
201
219
|
}
|
|
220
|
+
parseFitnessMachineStatus(_data) {
|
|
221
|
+
const data = Buffer.from(_data);
|
|
222
|
+
const OpCode = data.readUInt8(0);
|
|
223
|
+
switch (OpCode) {
|
|
224
|
+
case 8:
|
|
225
|
+
this.data.targetPower = data.readInt16LE(1);
|
|
226
|
+
break;
|
|
227
|
+
case 6:
|
|
228
|
+
this.data.targetInclination = data.readInt16LE(1) / 10;
|
|
229
|
+
break;
|
|
230
|
+
case 4:
|
|
231
|
+
this.data.status = "STARTED";
|
|
232
|
+
break;
|
|
233
|
+
case 3:
|
|
234
|
+
case 2:
|
|
235
|
+
this.data.status = "STOPPED";
|
|
236
|
+
break;
|
|
237
|
+
case 20:
|
|
238
|
+
const spinDownStatus = data.readUInt8(1);
|
|
239
|
+
switch (spinDownStatus) {
|
|
240
|
+
case 1:
|
|
241
|
+
this.data.status = "SPIN DOWN REQUESTED";
|
|
242
|
+
break;
|
|
243
|
+
case 2:
|
|
244
|
+
this.data.status = "SPIN DOWN SUCCESS";
|
|
245
|
+
break;
|
|
246
|
+
case 3:
|
|
247
|
+
this.data.status = "SPIN DOWN ERROR";
|
|
248
|
+
break;
|
|
249
|
+
case 4:
|
|
250
|
+
this.data.status = "STOP PEDALING";
|
|
251
|
+
break;
|
|
252
|
+
default: break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return Object.assign(Object.assign({}, this.data), { raw: data.toString('hex') });
|
|
256
|
+
}
|
|
202
257
|
getFitnessMachineFeatures() {
|
|
203
258
|
return __awaiter(this, void 0, void 0, function* () {
|
|
204
259
|
if (this.features)
|
|
@@ -218,36 +273,148 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
218
273
|
});
|
|
219
274
|
}
|
|
220
275
|
onData(characteristic, data) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
276
|
+
super.onData(characteristic, data);
|
|
277
|
+
const uuid = characteristic.toLocaleLowerCase();
|
|
278
|
+
let res = undefined;
|
|
279
|
+
switch (uuid) {
|
|
280
|
+
case '2ad2':
|
|
281
|
+
res = this.parseIndoorBikeData(data);
|
|
282
|
+
break;
|
|
283
|
+
case '2a37':
|
|
284
|
+
res = this.parseHrm(data);
|
|
285
|
+
break;
|
|
286
|
+
case '2ada':
|
|
287
|
+
res = this.parseFitnessMachineStatus(data);
|
|
288
|
+
break;
|
|
289
|
+
default:
|
|
290
|
+
break;
|
|
224
291
|
}
|
|
225
|
-
if (
|
|
226
|
-
const res = this.parseHrm(data);
|
|
292
|
+
if (res)
|
|
227
293
|
this.emit('data', res);
|
|
228
|
-
|
|
294
|
+
}
|
|
295
|
+
writeFtmsMessage(requestedOpCode, data) {
|
|
296
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
297
|
+
try {
|
|
298
|
+
const res = yield this.write(FTMS_CP, data);
|
|
299
|
+
const responseData = Buffer.from(res);
|
|
300
|
+
const opCode = responseData.readUInt8(0);
|
|
301
|
+
const request = responseData.readUInt8(1);
|
|
302
|
+
const result = responseData.readUInt8(2);
|
|
303
|
+
if (opCode !== 128 || request !== requestedOpCode)
|
|
304
|
+
throw new Error('Illegal response ');
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
this.logEvent({ message: 'writeFtmsMessage failed', opCode: requestedOpCode, reason: err.message });
|
|
309
|
+
return 4;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
229
312
|
}
|
|
230
313
|
requestControl() {
|
|
231
314
|
return __awaiter(this, void 0, void 0, function* () {
|
|
232
315
|
if (this.hasControl)
|
|
233
316
|
return true;
|
|
317
|
+
this.logEvent({ message: 'requestControl' });
|
|
234
318
|
const data = Buffer.alloc(1);
|
|
235
319
|
data.writeUInt8(0, 0);
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
320
|
+
const res = yield this.writeFtmsMessage(0, data);
|
|
321
|
+
if (res === 1) {
|
|
238
322
|
this.hasControl = true;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
this.logEvent({ message: 'requestControl failed' });
|
|
326
|
+
}
|
|
239
327
|
return this.hasControl;
|
|
240
328
|
});
|
|
241
329
|
}
|
|
242
330
|
setTargetPower(power) {
|
|
243
331
|
return __awaiter(this, void 0, void 0, function* () {
|
|
332
|
+
this.logEvent({ message: 'setTargetPower', power, skip: (this.data.targetPower !== undefined && this.data.targetPower === power) });
|
|
333
|
+
if (this.data.targetPower !== undefined && this.data.targetPower === power)
|
|
334
|
+
return true;
|
|
244
335
|
const hasControl = yield this.requestControl();
|
|
245
|
-
if (!hasControl)
|
|
246
|
-
throw new Error('setTargetPower not possible - control is disabled');
|
|
247
336
|
const data = Buffer.alloc(3);
|
|
248
337
|
data.writeUInt8(5, 0);
|
|
249
338
|
data.writeInt16LE(Math.round(power), 1);
|
|
250
|
-
const res = yield this.
|
|
339
|
+
const res = yield this.writeFtmsMessage(5, data);
|
|
340
|
+
return (res === 1);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
setSlope(slope) {
|
|
344
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
345
|
+
this.logEvent({ message: 'setSlope', slope });
|
|
346
|
+
const { windSpeed, crr, cw } = this;
|
|
347
|
+
return yield this.setIndoorBikeSimulation(windSpeed, slope, crr, cw);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
setTargetInclination(inclination) {
|
|
351
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
352
|
+
if (this.data.targetInclination !== undefined && this.data.targetInclination === inclination)
|
|
353
|
+
return true;
|
|
354
|
+
const hasControl = yield this.requestControl();
|
|
355
|
+
if (!hasControl) {
|
|
356
|
+
this.logEvent({ message: 'setTargetInclination failed', reason: 'control is disabled' });
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
const data = Buffer.alloc(3);
|
|
360
|
+
data.writeUInt8(3, 0);
|
|
361
|
+
data.writeInt16LE(Math.round(inclination * 10), 1);
|
|
362
|
+
const res = yield this.writeFtmsMessage(3, data);
|
|
363
|
+
return (res === 1);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
setIndoorBikeSimulation(windSpeed, gradient, crr, cw) {
|
|
367
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
368
|
+
const hasControl = yield this.requestControl();
|
|
369
|
+
const data = Buffer.alloc(7);
|
|
370
|
+
data.writeUInt8(17, 0);
|
|
371
|
+
data.writeInt16LE(Math.round(windSpeed * 1000), 1);
|
|
372
|
+
data.writeInt16LE(Math.round(gradient * 100), 3);
|
|
373
|
+
data.writeUInt8(Math.round(crr * 10000), 5);
|
|
374
|
+
data.writeUInt8(Math.round(cw * 100), 6);
|
|
375
|
+
const res = yield this.writeFtmsMessage(17, data);
|
|
376
|
+
return (res === 1);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
startRequest() {
|
|
380
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
381
|
+
const hasControl = yield this.requestControl();
|
|
382
|
+
if (!hasControl) {
|
|
383
|
+
this.logEvent({ message: 'startRequest failed', reason: 'control is disabled' });
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
const data = Buffer.alloc(1);
|
|
387
|
+
data.writeUInt8(7, 0);
|
|
388
|
+
const res = yield this.writeFtmsMessage(7, data);
|
|
389
|
+
return (res === 1);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
stopRequest() {
|
|
393
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
394
|
+
const hasControl = yield this.requestControl();
|
|
395
|
+
if (!hasControl) {
|
|
396
|
+
this.logEvent({ message: 'stopRequest failed', reason: 'control is disabled' });
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
const data = Buffer.alloc(2);
|
|
400
|
+
data.writeUInt8(8, 0);
|
|
401
|
+
data.writeUInt8(1, 1);
|
|
402
|
+
const res = yield this.writeFtmsMessage(8, data);
|
|
403
|
+
return (res === 1);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
PauseRequest() {
|
|
407
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
408
|
+
const hasControl = yield this.requestControl();
|
|
409
|
+
if (!hasControl) {
|
|
410
|
+
this.logEvent({ message: 'PauseRequest failed', reason: 'control is disabled' });
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
const data = Buffer.alloc(2);
|
|
414
|
+
data.writeUInt8(8, 0);
|
|
415
|
+
data.writeUInt8(2, 1);
|
|
416
|
+
const res = yield this.writeFtmsMessage(8, data);
|
|
417
|
+
return (res === 1);
|
|
251
418
|
});
|
|
252
419
|
}
|
|
253
420
|
reset() {
|
|
@@ -266,8 +433,10 @@ class FmAdapter extends Device_1.default {
|
|
|
266
433
|
this.distanceInternal = 0;
|
|
267
434
|
this.device = device;
|
|
268
435
|
this.ble = protocol.ble;
|
|
269
|
-
this.
|
|
436
|
+
this.cyclingMode = this.getDefaultCyclingMode();
|
|
270
437
|
this.logger = new gd_eventlog_1.EventLogger('BLE-FM');
|
|
438
|
+
if (this.device)
|
|
439
|
+
this.device.setLogger(this.logger);
|
|
271
440
|
}
|
|
272
441
|
isBike() { return this.device.isBike(); }
|
|
273
442
|
isHrm() { return this.device.isHrm(); }
|
|
@@ -287,13 +456,33 @@ class FmAdapter extends Device_1.default {
|
|
|
287
456
|
getDisplayName() {
|
|
288
457
|
return this.getName();
|
|
289
458
|
}
|
|
459
|
+
getSupportedCyclingModes() {
|
|
460
|
+
return [ble_st_mode_1.default, ble_erg_mode_1.default, power_meter_1.default];
|
|
461
|
+
}
|
|
462
|
+
setCyclingMode(mode, settings) {
|
|
463
|
+
let selectedMode;
|
|
464
|
+
if (typeof mode === 'string') {
|
|
465
|
+
const supported = this.getSupportedCyclingModes();
|
|
466
|
+
const CyclingModeClass = supported.find(M => { const m = new M(this); return m.getName() === mode; });
|
|
467
|
+
if (CyclingModeClass) {
|
|
468
|
+
this.cyclingMode = new CyclingModeClass(this, settings);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
selectedMode = this.getDefaultCyclingMode();
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
selectedMode = mode;
|
|
475
|
+
}
|
|
476
|
+
this.cyclingMode = selectedMode;
|
|
477
|
+
this.cyclingMode.setSettings(settings);
|
|
478
|
+
}
|
|
290
479
|
getCyclingMode() {
|
|
291
|
-
if (!this.
|
|
292
|
-
this.
|
|
293
|
-
return this.
|
|
480
|
+
if (!this.cyclingMode)
|
|
481
|
+
this.cyclingMode = this.getDefaultCyclingMode();
|
|
482
|
+
return this.cyclingMode;
|
|
294
483
|
}
|
|
295
484
|
getDefaultCyclingMode() {
|
|
296
|
-
return new
|
|
485
|
+
return new ble_st_mode_1.default(this);
|
|
297
486
|
}
|
|
298
487
|
getPort() {
|
|
299
488
|
return 'ble';
|
|
@@ -361,8 +550,25 @@ class FmAdapter extends Device_1.default {
|
|
|
361
550
|
yield this.ble.stopScan();
|
|
362
551
|
try {
|
|
363
552
|
const bleDevice = yield this.ble.connectDevice(this.device);
|
|
553
|
+
bleDevice.setLogger(this.logger);
|
|
364
554
|
if (bleDevice) {
|
|
365
555
|
this.device = bleDevice;
|
|
556
|
+
const mode = this.getCyclingMode();
|
|
557
|
+
if (mode && mode.getSetting('bikeType')) {
|
|
558
|
+
const bikeType = mode.getSetting('bikeType').toLowerCase();
|
|
559
|
+
this.device.setCrr(cRR);
|
|
560
|
+
switch (bikeType) {
|
|
561
|
+
case 'race':
|
|
562
|
+
this.device.setCw(cwABike.race);
|
|
563
|
+
break;
|
|
564
|
+
case 'triathlon':
|
|
565
|
+
this.device.setCw(cwABike.triathlon);
|
|
566
|
+
break;
|
|
567
|
+
case 'mountain':
|
|
568
|
+
this.device.setCw(cwABike.mountain);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
366
572
|
bleDevice.on('data', (data) => {
|
|
367
573
|
this.onDeviceData(data);
|
|
368
574
|
});
|
|
@@ -383,6 +589,20 @@ class FmAdapter extends Device_1.default {
|
|
|
383
589
|
return this.device.disconnect();
|
|
384
590
|
});
|
|
385
591
|
}
|
|
592
|
+
sendUpdate(request) {
|
|
593
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
594
|
+
if (this.paused || !this.device)
|
|
595
|
+
return;
|
|
596
|
+
const update = this.getCyclingMode().sendBikeUpdate(request);
|
|
597
|
+
this.logger.logEvent({ message: 'send bike update requested', profile: this.getProfile(), update, request });
|
|
598
|
+
if (update.slope !== undefined) {
|
|
599
|
+
yield this.device.setSlope(update.slope);
|
|
600
|
+
}
|
|
601
|
+
if (update.targetPower !== undefined) {
|
|
602
|
+
yield this.device.setTargetPower(update.targetPower);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
386
606
|
pause() { this.paused = true; return Promise.resolve(true); }
|
|
387
607
|
resume() { this.paused = false; return Promise.resolve(true); }
|
|
388
608
|
}
|
package/lib/ble/hrm.js
CHANGED
|
@@ -51,6 +51,7 @@ class BleHrmDevice extends ble_device_1.BleDevice {
|
|
|
51
51
|
return { heartrate, rr, raw: data.toString('hex') };
|
|
52
52
|
}
|
|
53
53
|
onData(characteristic, data) {
|
|
54
|
+
super.onData(characteristic, data);
|
|
54
55
|
if (characteristic.toLocaleLowerCase() === '2a37') {
|
|
55
56
|
const res = this.parseHrm(data);
|
|
56
57
|
this.emit('data', res);
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
package/lib/ble/pwr.d.ts
CHANGED
|
@@ -72,6 +72,7 @@ export declare class PwrAdapter extends DeviceAdapter {
|
|
|
72
72
|
transformData(bikeData: IncyclistBikeData): DeviceData;
|
|
73
73
|
start(props?: any): Promise<any>;
|
|
74
74
|
stop(): Promise<boolean>;
|
|
75
|
+
sendUpdate(request: any): Promise<void>;
|
|
75
76
|
pause(): Promise<boolean>;
|
|
76
77
|
resume(): Promise<boolean>;
|
|
77
78
|
}
|
package/lib/ble/pwr.js
CHANGED
|
@@ -109,6 +109,7 @@ class BleCyclingPowerDevice extends ble_device_1.BleDevice {
|
|
|
109
109
|
return { instantaneousPower, balance, accTorque, rpm, time, raw: data.toString('hex') };
|
|
110
110
|
}
|
|
111
111
|
onData(characteristic, data) {
|
|
112
|
+
super.onData(characteristic, data);
|
|
112
113
|
if (characteristic.toLocaleLowerCase() === '2a63') {
|
|
113
114
|
const res = this.parsePower(data);
|
|
114
115
|
this.emit('data', res);
|
|
@@ -254,6 +255,13 @@ class PwrAdapter extends Device_1.default {
|
|
|
254
255
|
return this.device.disconnect();
|
|
255
256
|
});
|
|
256
257
|
}
|
|
258
|
+
sendUpdate(request) {
|
|
259
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
260
|
+
if (this.paused)
|
|
261
|
+
return;
|
|
262
|
+
this.getCyclingMode().sendBikeUpdate(request);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
257
265
|
pause() { this.paused = true; return Promise.resolve(true); }
|
|
258
266
|
resume() { this.paused = false; return Promise.resolve(true); }
|
|
259
267
|
}
|
package/lib/daum/DaumAdapter.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
package/lib/kettler/comms.d.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
|
@@ -28,7 +28,7 @@ export default class KettlerRacerProtocol extends DeviceProtocolBase implements
|
|
|
28
28
|
isBike(): boolean;
|
|
29
29
|
isHrm(): boolean;
|
|
30
30
|
isPower(): boolean;
|
|
31
|
-
add(settings: DeviceSettings):
|
|
31
|
+
add(settings: DeviceSettings): any;
|
|
32
32
|
scan(props: KettlerRacerScanProps): void;
|
|
33
33
|
checkDevice(port: string): boolean;
|
|
34
34
|
doScan(port: string): Promise<void>;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|
package/lib/modes/power-meter.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.config = void 0;
|
|
7
7
|
const power_base_1 = __importDefault(require("./power-base"));
|
|
8
|
+
const MIN_SPEED = 10;
|
|
8
9
|
exports.config = {
|
|
9
10
|
name: 'PowerMeter',
|
|
10
11
|
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',
|
|
@@ -52,14 +53,20 @@ class PowerMeterCyclingMode extends power_base_1.default {
|
|
|
52
53
|
power = 0;
|
|
53
54
|
}
|
|
54
55
|
const m = this.getWeight();
|
|
55
|
-
|
|
56
|
+
const t = this.getTimeSinceLastUpdate();
|
|
56
57
|
const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t);
|
|
57
|
-
data.speed = speed;
|
|
58
58
|
data.power = Math.round(power);
|
|
59
|
-
data.distanceInternal = Math.round(distanceInternal + distance);
|
|
60
59
|
data.slope = slope;
|
|
60
|
+
if (power === 0 && speed < MIN_SPEED) {
|
|
61
|
+
data.speed = Math.round(prevData.speed - 1) < 0 ? 0 : Math.round(prevData.speed - 1);
|
|
62
|
+
data.distanceInternal = Math.round(distanceInternal + data.speed / 3.6 * t);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
data.speed = (power === 0 && speed < MIN_SPEED) ? 0 : speed;
|
|
66
|
+
data.distanceInternal = (power === 0 && speed < MIN_SPEED) ? Math.round(distanceInternal) : Math.round(distanceInternal + distance);
|
|
67
|
+
}
|
|
61
68
|
if (props.log)
|
|
62
|
-
this.logger.logEvent({ message: "updateData result", data, bikeData, prevSpeed: prevData.speed });
|
|
69
|
+
this.logger.logEvent({ message: "updateData result", data, bikeData, prevSpeed: prevData.speed, stopped: speed < MIN_SPEED });
|
|
63
70
|
this.data = data;
|
|
64
71
|
}
|
|
65
72
|
catch (err) {
|
|
@@ -51,7 +51,7 @@ export declare class Simulator extends DeviceAdapter {
|
|
|
51
51
|
export default class SimulatorProtocol extends DeviceProtocolBase {
|
|
52
52
|
static NAME: string;
|
|
53
53
|
constructor();
|
|
54
|
-
add(settings: SimulatorSettings):
|
|
54
|
+
add(settings: SimulatorSettings): any;
|
|
55
55
|
getName(): string;
|
|
56
56
|
getInterfaces(): string[];
|
|
57
57
|
isBike(): boolean;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
9
5
|
}) : (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
11
7
|
o[k2] = m[k];
|