incyclist-devices 1.4.46 → 1.4.49
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/ble/ble-device.d.ts +10 -2
- package/lib/ble/ble-device.js +24 -4
- package/lib/ble/ble-erg-mode.d.ts +1 -8
- package/lib/ble/ble-erg-mode.js +9 -28
- package/lib/ble/ble-st-mode.d.ts +1 -10
- package/lib/ble/ble-st-mode.js +22 -71
- package/lib/ble/fm.d.ts +26 -2
- package/lib/ble/fm.js +243 -20
- package/lib/ble/hrm.js +1 -0
- package/lib/ble/pwr.d.ts +1 -0
- package/lib/ble/pwr.js +8 -0
- package/lib/modes/power-meter.js +10 -3
- package/package.json +1 -1
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,6 +26,7 @@ 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;
|
|
24
32
|
setInterface(ble: BleInterfaceClass): void;
|
|
@@ -32,8 +40,8 @@ export declare abstract class BleDevice extends BleDeviceClass {
|
|
|
32
40
|
connect(props?: ConnectProps): Promise<boolean>;
|
|
33
41
|
disconnect(): Promise<boolean>;
|
|
34
42
|
abstract getProfile(): string;
|
|
35
|
-
|
|
36
|
-
write(characteristicUuid: string, data: Buffer, withoutResponse?: boolean): Promise<
|
|
43
|
+
onData(characteristic: string, data: Buffer): void;
|
|
44
|
+
write(characteristicUuid: string, data: Buffer, withoutResponse?: boolean): Promise<ArrayBuffer>;
|
|
37
45
|
read(characteristicUuid: string): Promise<Uint8Array>;
|
|
38
46
|
getDeviceInfo(): Promise<BleDeviceInfo>;
|
|
39
47
|
}
|
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;
|
|
@@ -103,6 +104,7 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
103
104
|
return Promise.resolve(true);
|
|
104
105
|
return this.getDeviceInfo().then(() => {
|
|
105
106
|
this.emit('deviceInfo', this.deviceInfo);
|
|
107
|
+
this.logEvent(Object.assign({ message: 'ftms device init done' }, this.deviceInfo));
|
|
106
108
|
this.isInitialized = true;
|
|
107
109
|
return true;
|
|
108
110
|
});
|
|
@@ -235,9 +237,20 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
235
237
|
}
|
|
236
238
|
});
|
|
237
239
|
}
|
|
240
|
+
onData(characteristic, data) {
|
|
241
|
+
if (this.writeQueue.length > 0) {
|
|
242
|
+
const writeIdx = this.writeQueue.findIndex(i => i.uuid === characteristic.toLocaleLowerCase());
|
|
243
|
+
if (writeIdx !== -1) {
|
|
244
|
+
const writeItem = this.writeQueue[writeIdx];
|
|
245
|
+
this.writeQueue.splice(writeIdx, 1);
|
|
246
|
+
if (writeItem.resolve)
|
|
247
|
+
writeItem.resolve(data);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
238
251
|
write(characteristicUuid, data, withoutResponse = false) {
|
|
239
252
|
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
-
if (this.subscribedCharacteristics.find(c => c === characteristicUuid) === undefined) {
|
|
253
|
+
if (!withoutResponse && this.subscribedCharacteristics.find(c => c === characteristicUuid) === undefined) {
|
|
241
254
|
const connector = this.ble.getConnector(this.peripheral);
|
|
242
255
|
connector.on(characteristicUuid, (uuid, data) => {
|
|
243
256
|
this.onData(uuid, data);
|
|
@@ -251,11 +264,18 @@ class BleDevice extends ble_1.BleDeviceClass {
|
|
|
251
264
|
reject(new Error('Characteristic not found'));
|
|
252
265
|
return;
|
|
253
266
|
}
|
|
267
|
+
if (withoutResponse) {
|
|
268
|
+
characteristic.write(data, withoutResponse);
|
|
269
|
+
resolve(new ArrayBuffer(0));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const writeId = this.writeQueue.length;
|
|
273
|
+
this.writeQueue.push({ uuid: characteristicUuid.toLocaleLowerCase(), data, resolve, reject, timeout: Date.now() + 1000 });
|
|
254
274
|
characteristic.write(data, withoutResponse, (err) => {
|
|
255
|
-
if (err)
|
|
275
|
+
if (err) {
|
|
276
|
+
this.writeQueue.splice(writeId, 1);
|
|
256
277
|
reject(err);
|
|
257
|
-
|
|
258
|
-
resolve(true);
|
|
278
|
+
}
|
|
259
279
|
});
|
|
260
280
|
});
|
|
261
281
|
});
|
|
@@ -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,10 @@ 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
|
-
class
|
|
16
|
+
class BleERGCyclingMode extends power_base_1.default {
|
|
17
17
|
constructor(adapter, props) {
|
|
18
18
|
super(adapter, props);
|
|
19
19
|
this.hasBikeUpdate = false;
|
|
20
|
-
this.event = {};
|
|
21
20
|
this.initLogger('ERGMode');
|
|
22
21
|
}
|
|
23
22
|
getName() {
|
|
@@ -34,7 +33,7 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
34
33
|
}
|
|
35
34
|
getBikeInitRequest() {
|
|
36
35
|
const startPower = this.getSetting('startPower');
|
|
37
|
-
return { targetPower: startPower };
|
|
36
|
+
return { slope: 0, targetPower: startPower };
|
|
38
37
|
}
|
|
39
38
|
sendBikeUpdate(request) {
|
|
40
39
|
const getData = () => {
|
|
@@ -43,25 +42,18 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
43
42
|
const { pedalRpm, slope, power, speed } = this.data;
|
|
44
43
|
return { pedalRpm, slope, power, speed };
|
|
45
44
|
};
|
|
46
|
-
this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData()
|
|
45
|
+
this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData() });
|
|
47
46
|
let newRequest = {};
|
|
48
47
|
try {
|
|
49
48
|
if (!request || request.reset || Object.keys(request).length === 0) {
|
|
50
49
|
this.prevRequest = {};
|
|
51
|
-
return request
|
|
50
|
+
return request.reset ? { reset: true } : {};
|
|
52
51
|
}
|
|
53
52
|
const prevData = this.data || {};
|
|
54
53
|
if (request.targetPower !== undefined) {
|
|
55
54
|
delete request.slope;
|
|
56
55
|
delete request.refresh;
|
|
57
56
|
}
|
|
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
57
|
if (request.refresh) {
|
|
66
58
|
delete request.refresh;
|
|
67
59
|
newRequest.targetPower = this.prevRequest.targetPower;
|
|
@@ -70,14 +62,12 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
70
62
|
if (!this.data)
|
|
71
63
|
this.data = {};
|
|
72
64
|
this.data.slope = request.slope;
|
|
65
|
+
delete request.slope;
|
|
73
66
|
}
|
|
74
67
|
if (request.maxPower !== undefined && request.minPower !== undefined && request.maxPower === request.minPower) {
|
|
75
68
|
request.targetPower = request.maxPower;
|
|
76
|
-
}
|
|
77
|
-
if (request.targetPower !== undefined) {
|
|
78
69
|
newRequest.targetPower = request.targetPower;
|
|
79
70
|
}
|
|
80
|
-
delete request.slope;
|
|
81
71
|
if (request.maxPower !== undefined) {
|
|
82
72
|
if (newRequest.targetPower !== undefined && newRequest.targetPower > request.maxPower) {
|
|
83
73
|
newRequest.targetPower = request.maxPower;
|
|
@@ -89,9 +79,8 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
89
79
|
newRequest.targetPower = request.minPower;
|
|
90
80
|
}
|
|
91
81
|
newRequest.minPower = request.minPower;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
delete newRequest.targetPower;
|
|
82
|
+
if (prevData.power && prevData.power < request.minPower)
|
|
83
|
+
newRequest.targetPower = request.minPower;
|
|
95
84
|
}
|
|
96
85
|
this.prevRequest = JSON.parse(JSON.stringify(request));
|
|
97
86
|
}
|
|
@@ -106,11 +95,6 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
106
95
|
const prevRequest = this.prevRequest || {};
|
|
107
96
|
const data = this.data || {};
|
|
108
97
|
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
98
|
try {
|
|
115
99
|
const rpm = bikeData.pedalRpm || 0;
|
|
116
100
|
let power = bikeData.power || 0;
|
|
@@ -127,15 +111,12 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
127
111
|
data.distanceInternal = Math.round(distanceInternal + distance);
|
|
128
112
|
data.slope = slope;
|
|
129
113
|
data.pedalRpm = rpm;
|
|
130
|
-
if (data.time !== undefined &&
|
|
114
|
+
if (data.time !== undefined && data.speed > 0)
|
|
131
115
|
data.time += t;
|
|
132
116
|
else
|
|
133
117
|
data.time = 0;
|
|
134
118
|
data.heartrate = bikeData.heartrate;
|
|
135
119
|
data.isPedalling = bikeData.isPedalling;
|
|
136
|
-
if (rpm && rpm !== prevData.pedalRpm) {
|
|
137
|
-
this.event.rpmUpdated = true;
|
|
138
|
-
}
|
|
139
120
|
}
|
|
140
121
|
catch (err) {
|
|
141
122
|
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
@@ -145,4 +126,4 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
145
126
|
return data;
|
|
146
127
|
}
|
|
147
128
|
}
|
|
148
|
-
exports.default =
|
|
129
|
+
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,14 @@ 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
|
-
class
|
|
15
|
+
class FtmsCyclingMode extends power_base_1.default {
|
|
17
16
|
constructor(adapter, props) {
|
|
18
17
|
super(adapter, props);
|
|
19
18
|
this.hasBikeUpdate = false;
|
|
20
|
-
this.
|
|
21
|
-
this.initLogger('ERGMode');
|
|
19
|
+
this.initLogger('FtmsMode');
|
|
22
20
|
}
|
|
23
21
|
getName() {
|
|
24
22
|
return config.name;
|
|
@@ -33,71 +31,32 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
33
31
|
return config.properties.find(p => p.name === name);
|
|
34
32
|
}
|
|
35
33
|
getBikeInitRequest() {
|
|
36
|
-
|
|
37
|
-
return { targetPower: startPower };
|
|
34
|
+
return { slope: 0 };
|
|
38
35
|
}
|
|
39
36
|
sendBikeUpdate(request) {
|
|
40
37
|
const getData = () => {
|
|
41
38
|
if (!this.data)
|
|
42
39
|
return {};
|
|
43
|
-
const { pedalRpm, slope, power, speed } = this.data;
|
|
44
|
-
return { pedalRpm, slope, power, speed };
|
|
40
|
+
const { gear, pedalRpm, slope, power, speed } = this.data;
|
|
41
|
+
return { gear, pedalRpm, slope, power, speed };
|
|
45
42
|
};
|
|
46
|
-
|
|
43
|
+
const event = {};
|
|
44
|
+
if (this.data === undefined)
|
|
45
|
+
event.noData = true;
|
|
46
|
+
if (request.slope !== undefined && (event.noData || Math.abs(request.slope - this.data.slope) >= 0.1))
|
|
47
|
+
event.slopeUpdate = true;
|
|
48
|
+
if (this.prevRequest === undefined)
|
|
49
|
+
event.initialCall = true;
|
|
50
|
+
this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData(), event });
|
|
47
51
|
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));
|
|
52
|
+
if (request.slope === undefined && request.refresh && this.prevRequest) {
|
|
53
|
+
return this.prevRequest;
|
|
97
54
|
}
|
|
98
|
-
|
|
99
|
-
|
|
55
|
+
if (request.slope !== undefined) {
|
|
56
|
+
newRequest.slope = parseFloat(request.slope.toFixed(1));
|
|
57
|
+
this.data.slope = newRequest.slope;
|
|
100
58
|
}
|
|
59
|
+
this.prevRequest = JSON.parse(JSON.stringify(newRequest));
|
|
101
60
|
return newRequest;
|
|
102
61
|
}
|
|
103
62
|
updateData(bikeData) {
|
|
@@ -106,11 +65,6 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
106
65
|
const prevRequest = this.prevRequest || {};
|
|
107
66
|
const data = this.data || {};
|
|
108
67
|
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
68
|
try {
|
|
115
69
|
const rpm = bikeData.pedalRpm || 0;
|
|
116
70
|
let power = bikeData.power || 0;
|
|
@@ -127,15 +81,12 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
127
81
|
data.distanceInternal = Math.round(distanceInternal + distance);
|
|
128
82
|
data.slope = slope;
|
|
129
83
|
data.pedalRpm = rpm;
|
|
130
|
-
if (data.time !== undefined
|
|
84
|
+
if (data.time !== undefined)
|
|
131
85
|
data.time += t;
|
|
132
86
|
else
|
|
133
87
|
data.time = 0;
|
|
134
88
|
data.heartrate = bikeData.heartrate;
|
|
135
89
|
data.isPedalling = bikeData.isPedalling;
|
|
136
|
-
if (rpm && rpm !== prevData.pedalRpm) {
|
|
137
|
-
this.event.rpmUpdated = true;
|
|
138
|
-
}
|
|
139
90
|
}
|
|
140
91
|
catch (err) {
|
|
141
92
|
this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
|
|
@@ -145,4 +96,4 @@ class ERGCyclingMode extends power_base_1.default {
|
|
|
145
96
|
return data;
|
|
146
97
|
}
|
|
147
98
|
}
|
|
148
|
-
exports.default =
|
|
99
|
+
exports.default = FtmsCyclingMode;
|
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);
|
|
@@ -159,19 +177,22 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
159
177
|
offset += 2;
|
|
160
178
|
}
|
|
161
179
|
if (flags & IndoorBikeDataFlag.TotalDistancePresent) {
|
|
162
|
-
|
|
180
|
+
const dvLow = data.readUInt8(offset);
|
|
181
|
+
offset += 1;
|
|
182
|
+
const dvHigh = data.readUInt16LE(offset);
|
|
163
183
|
offset += 2;
|
|
184
|
+
this.data.totalDistance = (dvHigh << 8) + dvLow;
|
|
164
185
|
}
|
|
165
186
|
if (flags & IndoorBikeDataFlag.ResistanceLevelPresent) {
|
|
166
|
-
this.data.resistanceLevel = data.
|
|
187
|
+
this.data.resistanceLevel = data.readInt16LE(offset);
|
|
167
188
|
offset += 2;
|
|
168
189
|
}
|
|
169
190
|
if (flags & IndoorBikeDataFlag.InstantaneousPowerPresent) {
|
|
170
|
-
this.data.instantaneousPower = data.
|
|
191
|
+
this.data.instantaneousPower = data.readInt16LE(offset);
|
|
171
192
|
offset += 2;
|
|
172
193
|
}
|
|
173
194
|
if (flags & IndoorBikeDataFlag.AveragePowerPresent) {
|
|
174
|
-
this.data.averagePower = data.
|
|
195
|
+
this.data.averagePower = data.readInt16LE(offset);
|
|
175
196
|
offset += 2;
|
|
176
197
|
}
|
|
177
198
|
if (flags & IndoorBikeDataFlag.ExpendedEnergyPresent) {
|
|
@@ -196,6 +217,43 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
196
217
|
}
|
|
197
218
|
return Object.assign(Object.assign({}, this.data), { raw: data.toString('hex') });
|
|
198
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
|
+
}
|
|
199
257
|
getFitnessMachineFeatures() {
|
|
200
258
|
return __awaiter(this, void 0, void 0, function* () {
|
|
201
259
|
if (this.features)
|
|
@@ -215,14 +273,42 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
215
273
|
});
|
|
216
274
|
}
|
|
217
275
|
onData(characteristic, data) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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;
|
|
221
291
|
}
|
|
222
|
-
if (
|
|
223
|
-
const res = this.parseHrm(data);
|
|
292
|
+
if (res)
|
|
224
293
|
this.emit('data', res);
|
|
225
|
-
|
|
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
|
+
});
|
|
226
312
|
}
|
|
227
313
|
requestControl() {
|
|
228
314
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -230,21 +316,109 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
230
316
|
return true;
|
|
231
317
|
const data = Buffer.alloc(1);
|
|
232
318
|
data.writeUInt8(0, 0);
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
319
|
+
const res = yield this.writeFtmsMessage(0, data);
|
|
320
|
+
if (res === 1) {
|
|
235
321
|
this.hasControl = true;
|
|
322
|
+
}
|
|
236
323
|
return this.hasControl;
|
|
237
324
|
});
|
|
238
325
|
}
|
|
239
326
|
setTargetPower(power) {
|
|
240
327
|
return __awaiter(this, void 0, void 0, function* () {
|
|
328
|
+
this.logEvent({ message: 'setTargetPower', power, skip: (this.data.targetPower !== undefined && this.data.targetPower === power) });
|
|
329
|
+
if (this.data.targetPower !== undefined && this.data.targetPower === power)
|
|
330
|
+
return true;
|
|
241
331
|
const hasControl = yield this.requestControl();
|
|
242
|
-
if (!hasControl)
|
|
243
|
-
|
|
332
|
+
if (!hasControl) {
|
|
333
|
+
this.logEvent({ message: 'setTargetPower failed', reason: 'control is disabled' });
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
244
336
|
const data = Buffer.alloc(3);
|
|
245
337
|
data.writeUInt8(5, 0);
|
|
246
338
|
data.writeInt16LE(Math.round(power), 1);
|
|
247
|
-
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
|
+
if (!hasControl) {
|
|
370
|
+
this.logEvent({ message: 'setTargetInclination failed', reason: 'control is disabled' });
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
const data = Buffer.alloc(7);
|
|
374
|
+
data.writeUInt8(17, 0);
|
|
375
|
+
data.writeInt16LE(Math.round(windSpeed * 1000), 1);
|
|
376
|
+
data.writeInt16LE(Math.round(gradient * 100), 3);
|
|
377
|
+
data.writeUInt8(Math.round(crr * 10000), 5);
|
|
378
|
+
data.writeUInt8(Math.round(cw * 100), 6);
|
|
379
|
+
const res = yield this.writeFtmsMessage(17, data);
|
|
380
|
+
return (res === 1);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
startRequest() {
|
|
384
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
385
|
+
const hasControl = yield this.requestControl();
|
|
386
|
+
if (!hasControl) {
|
|
387
|
+
this.logEvent({ message: 'startRequest failed', reason: 'control is disabled' });
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
const data = Buffer.alloc(1);
|
|
391
|
+
data.writeUInt8(7, 0);
|
|
392
|
+
const res = yield this.writeFtmsMessage(7, data);
|
|
393
|
+
return (res === 1);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
stopRequest() {
|
|
397
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
398
|
+
const hasControl = yield this.requestControl();
|
|
399
|
+
if (!hasControl) {
|
|
400
|
+
this.logEvent({ message: 'stopRequest failed', reason: 'control is disabled' });
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
const data = Buffer.alloc(2);
|
|
404
|
+
data.writeUInt8(8, 0);
|
|
405
|
+
data.writeUInt8(1, 1);
|
|
406
|
+
const res = yield this.writeFtmsMessage(8, data);
|
|
407
|
+
return (res === 1);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
PauseRequest() {
|
|
411
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
412
|
+
const hasControl = yield this.requestControl();
|
|
413
|
+
if (!hasControl) {
|
|
414
|
+
this.logEvent({ message: 'PauseRequest failed', reason: 'control is disabled' });
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
const data = Buffer.alloc(2);
|
|
418
|
+
data.writeUInt8(8, 0);
|
|
419
|
+
data.writeUInt8(2, 1);
|
|
420
|
+
const res = yield this.writeFtmsMessage(8, data);
|
|
421
|
+
return (res === 1);
|
|
248
422
|
});
|
|
249
423
|
}
|
|
250
424
|
reset() {
|
|
@@ -263,7 +437,7 @@ class FmAdapter extends Device_1.default {
|
|
|
263
437
|
this.distanceInternal = 0;
|
|
264
438
|
this.device = device;
|
|
265
439
|
this.ble = protocol.ble;
|
|
266
|
-
this.
|
|
440
|
+
this.cyclingMode = this.getDefaultCyclingMode();
|
|
267
441
|
this.logger = new gd_eventlog_1.EventLogger('BLE-FM');
|
|
268
442
|
}
|
|
269
443
|
isBike() { return this.device.isBike(); }
|
|
@@ -284,13 +458,33 @@ class FmAdapter extends Device_1.default {
|
|
|
284
458
|
getDisplayName() {
|
|
285
459
|
return this.getName();
|
|
286
460
|
}
|
|
461
|
+
getSupportedCyclingModes() {
|
|
462
|
+
return [ble_st_mode_1.default, ble_erg_mode_1.default, power_meter_1.default];
|
|
463
|
+
}
|
|
464
|
+
setCyclingMode(mode, settings) {
|
|
465
|
+
let selectedMode;
|
|
466
|
+
if (typeof mode === 'string') {
|
|
467
|
+
const supported = this.getSupportedCyclingModes();
|
|
468
|
+
const CyclingModeClass = supported.find(M => { const m = new M(this); return m.getName() === mode; });
|
|
469
|
+
if (CyclingModeClass) {
|
|
470
|
+
this.cyclingMode = new CyclingModeClass(this, settings);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
selectedMode = this.getDefaultCyclingMode();
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
selectedMode = mode;
|
|
477
|
+
}
|
|
478
|
+
this.cyclingMode = selectedMode;
|
|
479
|
+
this.cyclingMode.setSettings(settings);
|
|
480
|
+
}
|
|
287
481
|
getCyclingMode() {
|
|
288
|
-
if (!this.
|
|
289
|
-
this.
|
|
290
|
-
return this.
|
|
482
|
+
if (!this.cyclingMode)
|
|
483
|
+
this.cyclingMode = this.getDefaultCyclingMode();
|
|
484
|
+
return this.cyclingMode;
|
|
291
485
|
}
|
|
292
486
|
getDefaultCyclingMode() {
|
|
293
|
-
return new
|
|
487
|
+
return new ble_st_mode_1.default(this);
|
|
294
488
|
}
|
|
295
489
|
getPort() {
|
|
296
490
|
return 'ble';
|
|
@@ -360,6 +554,22 @@ class FmAdapter extends Device_1.default {
|
|
|
360
554
|
const bleDevice = yield this.ble.connectDevice(this.device);
|
|
361
555
|
if (bleDevice) {
|
|
362
556
|
this.device = bleDevice;
|
|
557
|
+
const mode = this.getCyclingMode();
|
|
558
|
+
if (mode && mode.getSetting('bikeType')) {
|
|
559
|
+
const bikeType = mode.getSetting('bikeType').toLowerCase();
|
|
560
|
+
this.device.setCrr(cRR);
|
|
561
|
+
switch (bikeType) {
|
|
562
|
+
case 'race':
|
|
563
|
+
this.device.setCw(cwABike.race);
|
|
564
|
+
break;
|
|
565
|
+
case 'triathlon':
|
|
566
|
+
this.device.setCw(cwABike.triathlon);
|
|
567
|
+
break;
|
|
568
|
+
case 'mountain':
|
|
569
|
+
this.device.setCw(cwABike.mountain);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
363
573
|
bleDevice.on('data', (data) => {
|
|
364
574
|
this.onDeviceData(data);
|
|
365
575
|
});
|
|
@@ -380,6 +590,19 @@ class FmAdapter extends Device_1.default {
|
|
|
380
590
|
return this.device.disconnect();
|
|
381
591
|
});
|
|
382
592
|
}
|
|
593
|
+
sendUpdate(request) {
|
|
594
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
595
|
+
if (this.paused || !this.device)
|
|
596
|
+
return;
|
|
597
|
+
const requested = this.getCyclingMode().sendBikeUpdate(request);
|
|
598
|
+
if (requested.slope !== undefined) {
|
|
599
|
+
yield this.device.setSlope(requested.slope);
|
|
600
|
+
}
|
|
601
|
+
if (requested.targetPower !== undefined) {
|
|
602
|
+
yield this.device.setTargetPower(requested.targetPower);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
383
606
|
pause() { this.paused = true; return Promise.resolve(true); }
|
|
384
607
|
resume() { this.paused = false; return Promise.resolve(true); }
|
|
385
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);
|
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/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',
|
|
@@ -54,12 +55,18 @@ class PowerMeterCyclingMode extends power_base_1.default {
|
|
|
54
55
|
const m = this.getWeight();
|
|
55
56
|
let 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) {
|