incyclist-devices 1.4.45 → 1.4.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Device.d.ts +2 -0
- package/lib/Device.js +1 -0
- package/lib/ant/AntAdapter.d.ts +1 -0
- package/lib/ant/AntAdapter.js +6 -0
- package/lib/ble/ble-device.d.ts +9 -7
- package/lib/ble/ble-device.js +83 -121
- package/lib/ble/ble-erg-mode.d.ts +24 -0
- package/lib/ble/ble-erg-mode.js +148 -0
- package/lib/ble/ble-interface.d.ts +13 -0
- package/lib/ble/ble-interface.js +49 -25
- package/lib/ble/ble-peripheral.d.ts +34 -0
- package/lib/ble/ble-peripheral.js +170 -0
- package/lib/ble/ble-st-mode.d.ts +24 -0
- package/lib/ble/ble-st-mode.js +148 -0
- package/lib/ble/ble.d.ts +9 -0
- package/lib/ble/fm.d.ts +7 -1
- package/lib/ble/fm.js +60 -10
- package/lib/ble/hrm.d.ts +1 -1
- package/lib/ble/hrm.js +6 -4
- package/lib/ble/incyclist-protocol.js +9 -1
- package/lib/ble/pwr.d.ts +2 -1
- package/lib/ble/pwr.js +13 -4
- package/lib/daum/DaumAdapter.d.ts +1 -0
- package/lib/daum/DaumAdapter.js +6 -0
- package/lib/kettler/ergo-racer/adapter.d.ts +1 -0
- package/lib/kettler/ergo-racer/adapter.js +6 -0
- package/lib/modes/power-base.js +1 -0
- package/lib/modes/power-meter.js +10 -3
- package/lib/simulator/Simulator.d.ts +1 -0
- package/lib/simulator/Simulator.js +5 -0
- package/package.json +1 -1
package/lib/ble/fm.js
CHANGED
|
@@ -18,6 +18,7 @@ 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 FTMS_CP = '2ad9';
|
|
21
22
|
const bit = (nr) => (1 << nr);
|
|
22
23
|
const IndoorBikeDataFlag = {
|
|
23
24
|
MoreData: bit(0),
|
|
@@ -76,6 +77,8 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
76
77
|
constructor(props) {
|
|
77
78
|
super(props);
|
|
78
79
|
this.features = undefined;
|
|
80
|
+
this.hasControl = false;
|
|
81
|
+
this.isCPSubscribed = false;
|
|
79
82
|
this.data = {};
|
|
80
83
|
}
|
|
81
84
|
init() {
|
|
@@ -84,14 +87,20 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
84
87
|
});
|
|
85
88
|
return __awaiter(this, void 0, void 0, function* () {
|
|
86
89
|
try {
|
|
90
|
+
this.logEvent({ message: 'get device info' });
|
|
87
91
|
yield _super.init.call(this);
|
|
88
92
|
yield this.getFitnessMachineFeatures();
|
|
93
|
+
this.logEvent({ message: 'device info', deviceInfo: this.deviceInfo, features: this.features });
|
|
89
94
|
}
|
|
90
95
|
catch (err) {
|
|
91
96
|
return Promise.resolve(false);
|
|
92
97
|
}
|
|
93
98
|
});
|
|
94
99
|
}
|
|
100
|
+
onDisconnect() {
|
|
101
|
+
super.onDisconnect();
|
|
102
|
+
this.hasControl = false;
|
|
103
|
+
}
|
|
95
104
|
getProfile() {
|
|
96
105
|
return 'Smart Trainer';
|
|
97
106
|
}
|
|
@@ -112,7 +121,7 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
112
121
|
return true;
|
|
113
122
|
}
|
|
114
123
|
isHrm() {
|
|
115
|
-
return this.hasService('180d');
|
|
124
|
+
return this.hasService('180d') || (this.features && (this.features.fitnessMachine & FitnessMachineFeatureFlag.HeartRateMeasurementSupported) !== 0);
|
|
116
125
|
}
|
|
117
126
|
parseHrm(_data) {
|
|
118
127
|
const data = Buffer.from(_data);
|
|
@@ -150,19 +159,22 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
150
159
|
offset += 2;
|
|
151
160
|
}
|
|
152
161
|
if (flags & IndoorBikeDataFlag.TotalDistancePresent) {
|
|
153
|
-
|
|
162
|
+
const dvLow = data.readUInt8(offset);
|
|
163
|
+
offset += 1;
|
|
164
|
+
const dvHigh = data.readUInt16LE(offset);
|
|
154
165
|
offset += 2;
|
|
166
|
+
this.data.totalDistance = dvHigh << 8 + dvLow;
|
|
155
167
|
}
|
|
156
168
|
if (flags & IndoorBikeDataFlag.ResistanceLevelPresent) {
|
|
157
|
-
this.data.resistanceLevel = data.
|
|
169
|
+
this.data.resistanceLevel = data.readInt16LE(offset);
|
|
158
170
|
offset += 2;
|
|
159
171
|
}
|
|
160
172
|
if (flags & IndoorBikeDataFlag.InstantaneousPowerPresent) {
|
|
161
|
-
this.data.instantaneousPower = data.
|
|
173
|
+
this.data.instantaneousPower = data.readInt16LE(offset);
|
|
162
174
|
offset += 2;
|
|
163
175
|
}
|
|
164
176
|
if (flags & IndoorBikeDataFlag.AveragePowerPresent) {
|
|
165
|
-
this.data.averagePower = data.
|
|
177
|
+
this.data.averagePower = data.readInt16LE(offset);
|
|
166
178
|
offset += 2;
|
|
167
179
|
}
|
|
168
180
|
if (flags & IndoorBikeDataFlag.ExpendedEnergyPresent) {
|
|
@@ -193,8 +205,12 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
193
205
|
return this.features;
|
|
194
206
|
try {
|
|
195
207
|
const data = yield this.read('2acc');
|
|
196
|
-
|
|
197
|
-
|
|
208
|
+
const buffer = data ? Buffer.from(data) : undefined;
|
|
209
|
+
if (buffer) {
|
|
210
|
+
const fitnessMachine = buffer.readUInt32LE(0);
|
|
211
|
+
const targetSettings = buffer.readUInt32LE(4);
|
|
212
|
+
this.features = { fitnessMachine, targetSettings };
|
|
213
|
+
}
|
|
198
214
|
}
|
|
199
215
|
catch (err) {
|
|
200
216
|
this.logEvent({ message: 'could not read FitnessMachineFeatures', error: err.message, stack: err.stack });
|
|
@@ -211,9 +227,28 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
|
|
|
211
227
|
this.emit('data', res);
|
|
212
228
|
}
|
|
213
229
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
230
|
+
requestControl() {
|
|
231
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
if (this.hasControl)
|
|
233
|
+
return true;
|
|
234
|
+
const data = Buffer.alloc(1);
|
|
235
|
+
data.writeUInt8(0, 0);
|
|
236
|
+
const success = yield this.write(FTMS_CP, data);
|
|
237
|
+
if (success)
|
|
238
|
+
this.hasControl = true;
|
|
239
|
+
return this.hasControl;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
setTargetPower(power) {
|
|
243
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
const hasControl = yield this.requestControl();
|
|
245
|
+
if (!hasControl)
|
|
246
|
+
throw new Error('setTargetPower not possible - control is disabled');
|
|
247
|
+
const data = Buffer.alloc(3);
|
|
248
|
+
data.writeUInt8(5, 0);
|
|
249
|
+
data.writeInt16LE(Math.round(power), 1);
|
|
250
|
+
const res = yield this.write(FTMS_CP, data);
|
|
251
|
+
});
|
|
217
252
|
}
|
|
218
253
|
reset() {
|
|
219
254
|
this.data = {};
|
|
@@ -237,6 +272,12 @@ class FmAdapter extends Device_1.default {
|
|
|
237
272
|
isBike() { return this.device.isBike(); }
|
|
238
273
|
isHrm() { return this.device.isHrm(); }
|
|
239
274
|
isPower() { return this.device.isPower(); }
|
|
275
|
+
isSame(device) {
|
|
276
|
+
if (!(device instanceof FmAdapter))
|
|
277
|
+
return false;
|
|
278
|
+
const adapter = device;
|
|
279
|
+
return (adapter.getName() === this.getName() && adapter.getProfile() === this.getProfile());
|
|
280
|
+
}
|
|
240
281
|
getProfile() {
|
|
241
282
|
return 'Smart Trainer';
|
|
242
283
|
}
|
|
@@ -316,6 +357,8 @@ class FmAdapter extends Device_1.default {
|
|
|
316
357
|
start(props) {
|
|
317
358
|
return __awaiter(this, void 0, void 0, function* () {
|
|
318
359
|
this.logger.logEvent({ message: 'start requested', profile: this.getProfile(), props });
|
|
360
|
+
if (this.ble.isScanning())
|
|
361
|
+
yield this.ble.stopScan();
|
|
319
362
|
try {
|
|
320
363
|
const bleDevice = yield this.ble.connectDevice(this.device);
|
|
321
364
|
if (bleDevice) {
|
|
@@ -340,6 +383,13 @@ class FmAdapter extends Device_1.default {
|
|
|
340
383
|
return this.device.disconnect();
|
|
341
384
|
});
|
|
342
385
|
}
|
|
386
|
+
sendUpdate(request) {
|
|
387
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
388
|
+
if (this.paused)
|
|
389
|
+
return;
|
|
390
|
+
this.getCyclingMode().sendBikeUpdate(request);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
343
393
|
pause() { this.paused = true; return Promise.resolve(true); }
|
|
344
394
|
resume() { this.paused = false; return Promise.resolve(true); }
|
|
345
395
|
}
|
package/lib/ble/hrm.d.ts
CHANGED
|
@@ -21,7 +21,6 @@ export default class BleHrmDevice extends BleDevice {
|
|
|
21
21
|
getServiceUUids(): string[];
|
|
22
22
|
parseHrm(_data: Uint8Array): HrmData;
|
|
23
23
|
onData(characteristic: string, data: Buffer): void;
|
|
24
|
-
write(characteristic: any, data: any): Promise<boolean>;
|
|
25
24
|
}
|
|
26
25
|
export declare class HrmAdapter extends DeviceAdapter {
|
|
27
26
|
device: BleHrmDevice;
|
|
@@ -34,6 +33,7 @@ export declare class HrmAdapter extends DeviceAdapter {
|
|
|
34
33
|
isBike(): boolean;
|
|
35
34
|
isHrm(): boolean;
|
|
36
35
|
isPower(): boolean;
|
|
36
|
+
isSame(device: DeviceAdapter): boolean;
|
|
37
37
|
getProfile(): string;
|
|
38
38
|
getName(): string;
|
|
39
39
|
getDisplayName(): string;
|
package/lib/ble/hrm.js
CHANGED
|
@@ -56,10 +56,6 @@ class BleHrmDevice extends ble_device_1.BleDevice {
|
|
|
56
56
|
this.emit('data', res);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
write(characteristic, data) {
|
|
60
|
-
console.log('write', characteristic, data);
|
|
61
|
-
return Promise.resolve(true);
|
|
62
|
-
}
|
|
63
59
|
}
|
|
64
60
|
exports.default = BleHrmDevice;
|
|
65
61
|
BleHrmDevice.services = ['180d'];
|
|
@@ -77,6 +73,12 @@ class HrmAdapter extends Device_1.default {
|
|
|
77
73
|
isBike() { return false; }
|
|
78
74
|
isHrm() { return true; }
|
|
79
75
|
isPower() { return false; }
|
|
76
|
+
isSame(device) {
|
|
77
|
+
if (!(device instanceof HrmAdapter))
|
|
78
|
+
return false;
|
|
79
|
+
const adapter = device;
|
|
80
|
+
return (adapter.getName() === this.getName() && adapter.getProfile() === this.getProfile());
|
|
81
|
+
}
|
|
80
82
|
getProfile() {
|
|
81
83
|
return 'Heartrate Monitor';
|
|
82
84
|
}
|
|
@@ -78,7 +78,15 @@ class BleProtocol extends DeviceProtocol_1.default {
|
|
|
78
78
|
case 'fm':
|
|
79
79
|
case 'smart trainer':
|
|
80
80
|
case 'fitness machine':
|
|
81
|
-
|
|
81
|
+
let device;
|
|
82
|
+
if (fromDevice)
|
|
83
|
+
device = bleDevice;
|
|
84
|
+
else {
|
|
85
|
+
device = this.ble.findDeviceInCache(Object.assign(Object.assign({}, props()), { profile: 'Smart Trainer' }));
|
|
86
|
+
if (!device)
|
|
87
|
+
device = new fm_1.default(props());
|
|
88
|
+
}
|
|
89
|
+
return new fm_1.FmAdapter(device, this);
|
|
82
90
|
case 'cp':
|
|
83
91
|
case 'power meter':
|
|
84
92
|
return new pwr_1.PwrAdapter(fromDevice ? bleDevice : new pwr_1.default(props()), this);
|
package/lib/ble/pwr.d.ts
CHANGED
|
@@ -42,7 +42,6 @@ export default class BleCyclingPowerDevice extends BleDevice {
|
|
|
42
42
|
};
|
|
43
43
|
parsePower(_data: Uint8Array): PowerData;
|
|
44
44
|
onData(characteristic: string, data: Buffer): void;
|
|
45
|
-
write(characteristic: any, data: any): Promise<boolean>;
|
|
46
45
|
reset(): void;
|
|
47
46
|
}
|
|
48
47
|
export declare class PwrAdapter extends DeviceAdapter {
|
|
@@ -59,6 +58,7 @@ export declare class PwrAdapter extends DeviceAdapter {
|
|
|
59
58
|
isBike(): boolean;
|
|
60
59
|
isHrm(): boolean;
|
|
61
60
|
isPower(): boolean;
|
|
61
|
+
isSame(device: DeviceAdapter): boolean;
|
|
62
62
|
getProfile(): string;
|
|
63
63
|
getName(): string;
|
|
64
64
|
getDisplayName(): string;
|
|
@@ -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
|
@@ -114,10 +114,6 @@ class BleCyclingPowerDevice extends ble_device_1.BleDevice {
|
|
|
114
114
|
this.emit('data', res);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
write(characteristic, data) {
|
|
118
|
-
console.log('write', characteristic, data);
|
|
119
|
-
return Promise.resolve(true);
|
|
120
|
-
}
|
|
121
117
|
reset() {
|
|
122
118
|
this.instantaneousPower = undefined;
|
|
123
119
|
this.balance = undefined;
|
|
@@ -147,6 +143,12 @@ class PwrAdapter extends Device_1.default {
|
|
|
147
143
|
isBike() { return true; }
|
|
148
144
|
isHrm() { return false; }
|
|
149
145
|
isPower() { return true; }
|
|
146
|
+
isSame(device) {
|
|
147
|
+
if (!(device instanceof PwrAdapter))
|
|
148
|
+
return false;
|
|
149
|
+
const adapter = device;
|
|
150
|
+
return (adapter.getName() === this.getName() && adapter.getProfile() === this.getProfile());
|
|
151
|
+
}
|
|
150
152
|
getProfile() {
|
|
151
153
|
return 'Power Meter';
|
|
152
154
|
}
|
|
@@ -252,6 +254,13 @@ class PwrAdapter extends Device_1.default {
|
|
|
252
254
|
return this.device.disconnect();
|
|
253
255
|
});
|
|
254
256
|
}
|
|
257
|
+
sendUpdate(request) {
|
|
258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
+
if (this.paused)
|
|
260
|
+
return;
|
|
261
|
+
this.getCyclingMode().sendBikeUpdate(request);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
255
264
|
pause() { this.paused = true; return Promise.resolve(true); }
|
|
256
265
|
resume() { this.paused = false; return Promise.resolve(true); }
|
|
257
266
|
}
|
|
@@ -39,6 +39,7 @@ export default class DaumAdapterBase extends IncyclistDevice implements DeviceAd
|
|
|
39
39
|
isBike(): boolean;
|
|
40
40
|
isPower(): boolean;
|
|
41
41
|
isHrm(): boolean;
|
|
42
|
+
isSame(device: DeviceAdapter): boolean;
|
|
42
43
|
setIgnoreHrm(ignore: any): void;
|
|
43
44
|
setIgnoreBike(ignore: any): void;
|
|
44
45
|
isStopped(): boolean;
|
package/lib/daum/DaumAdapter.js
CHANGED
|
@@ -131,6 +131,12 @@ class DaumAdapterBase extends Device_1.default {
|
|
|
131
131
|
isHrm() {
|
|
132
132
|
return true;
|
|
133
133
|
}
|
|
134
|
+
isSame(device) {
|
|
135
|
+
if (!(device instanceof DaumAdapterBase))
|
|
136
|
+
return false;
|
|
137
|
+
const adapter = device;
|
|
138
|
+
return (adapter.getName() === this.getName() && adapter.getPort() === this.getPort());
|
|
139
|
+
}
|
|
134
140
|
setIgnoreHrm(ignore) {
|
|
135
141
|
this.ignoreHrm = ignore;
|
|
136
142
|
}
|
|
@@ -56,6 +56,12 @@ class KettlerRacerAdapter extends Device_1.default {
|
|
|
56
56
|
isBike() { return true; }
|
|
57
57
|
isPower() { return true; }
|
|
58
58
|
isHrm() { return true; }
|
|
59
|
+
isSame(device) {
|
|
60
|
+
if (!(device instanceof KettlerRacerAdapter))
|
|
61
|
+
return false;
|
|
62
|
+
const adapter = device;
|
|
63
|
+
return (adapter.getName() === this.getName() && adapter.getPort() === this.getPort());
|
|
64
|
+
}
|
|
59
65
|
setID(id) {
|
|
60
66
|
this.id = id;
|
|
61
67
|
}
|
package/lib/modes/power-base.js
CHANGED
|
@@ -37,6 +37,7 @@ class PowerBasedCyclingModeBase extends CyclingMode_1.CyclingModeBase {
|
|
|
37
37
|
let powerToMaintainSpeed = calculations_1.default.calculatePower(m, vPrev, slope, props);
|
|
38
38
|
const powerDelta = powerToMaintainSpeed - power;
|
|
39
39
|
const Ekin = EkinPrev - powerDelta * t;
|
|
40
|
+
console.log('~~~~ calculateSpeedAndDistance', Ekin, powerToMaintainSpeed, power, m, vPrev, slope);
|
|
40
41
|
if (Ekin > 0) {
|
|
41
42
|
const v = Math.sqrt(2 * Ekin / m);
|
|
42
43
|
const speed = v * 3.6;
|
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) {
|
|
@@ -68,6 +68,11 @@ class Simulator extends Device_1.default {
|
|
|
68
68
|
isBike() { return true; }
|
|
69
69
|
isHrm() { return false; }
|
|
70
70
|
isPower() { return true; }
|
|
71
|
+
isSame(device) {
|
|
72
|
+
if (!(device instanceof Simulator))
|
|
73
|
+
return false;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
71
76
|
getID() { return Simulator.NAME; }
|
|
72
77
|
getName() { return Simulator.NAME; }
|
|
73
78
|
getPort() { return 'local'; }
|