incyclist-devices 1.4.48 → 1.4.51

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/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
- if (characteristic.toLocaleLowerCase() === '2ad2') {
222
- const res = this.parseIndoorBikeData(data);
223
- this.emit('data', res);
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 (characteristic.toLocaleLowerCase() === '2a37') {
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 success = yield this.write(FTMS_CP, data);
237
- if (success)
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.write(FTMS_CP, data);
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.mode = this.getDefaultCyclingMode();
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.mode)
292
- this.mode = this.getDefaultCyclingMode();
293
- return this.mode;
480
+ if (!this.cyclingMode)
481
+ this.cyclingMode = this.getDefaultCyclingMode();
482
+ return this.cyclingMode;
294
483
  }
295
484
  getDefaultCyclingMode() {
296
- return new power_meter_1.default(this);
485
+ return new ble_st_mode_1.default(this);
297
486
  }
298
487
  getPort() {
299
488
  return 'ble';
@@ -361,8 +550,27 @@ 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
+ }
572
+ const startRequest = this.getCyclingMode().getBikeInitRequest();
573
+ yield this.sendUpdate(startRequest);
366
574
  bleDevice.on('data', (data) => {
367
575
  this.onDeviceData(data);
368
576
  });
@@ -385,9 +593,16 @@ class FmAdapter extends Device_1.default {
385
593
  }
386
594
  sendUpdate(request) {
387
595
  return __awaiter(this, void 0, void 0, function* () {
388
- if (this.paused)
596
+ if (this.paused || !this.device)
389
597
  return;
390
- this.getCyclingMode().sendBikeUpdate(request);
598
+ const update = this.getCyclingMode().sendBikeUpdate(request);
599
+ this.logger.logEvent({ message: 'send bike update requested', profile: this.getProfile(), update, request });
600
+ if (update.slope !== undefined) {
601
+ yield this.device.setSlope(update.slope);
602
+ }
603
+ if (update.targetPower !== undefined) {
604
+ yield this.device.setTargetPower(update.targetPower);
605
+ }
391
606
  });
392
607
  }
393
608
  pause() { this.paused = true; return Promise.resolve(true); }
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.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);
@@ -0,0 +1,73 @@
1
+ /// <reference types="node" />
2
+ import BleProtocol from './incyclist-protocol';
3
+ import { BleDeviceClass } from './ble';
4
+ import DeviceAdapter from '../Device';
5
+ import BleFitnessMachineDevice, { FmAdapter } from './fm';
6
+ declare type IndoorBikeData = {
7
+ speed?: number;
8
+ averageSpeed?: number;
9
+ cadence?: number;
10
+ averageCadence?: number;
11
+ totalDistance?: number;
12
+ resistanceLevel?: number;
13
+ instantaneousPower?: number;
14
+ averagePower?: number;
15
+ expendedEnergy?: number;
16
+ heartrate?: number;
17
+ metabolicEquivalent?: number;
18
+ time?: number;
19
+ remainingTime?: number;
20
+ raw?: string;
21
+ targetPower?: number;
22
+ targetInclination?: number;
23
+ status?: string;
24
+ };
25
+ declare type CrankData = {
26
+ revolutions?: number;
27
+ time?: number;
28
+ cntUpdateMissing?: number;
29
+ };
30
+ export default class WahooAdvancedFitnessMachineDevice extends BleFitnessMachineDevice {
31
+ static services: string[];
32
+ static characteristics: string[];
33
+ prevCrankData: CrankData;
34
+ currentCrankData: CrankData;
35
+ timeOffset: number;
36
+ tsPrevWrite: any;
37
+ constructor(props?: any);
38
+ init(): Promise<boolean>;
39
+ getProfile(): string;
40
+ getServiceUUids(): string[];
41
+ isBike(): boolean;
42
+ isPower(): boolean;
43
+ isHrm(): boolean;
44
+ parseCrankData(crankData: any): {
45
+ rpm: number;
46
+ time: any;
47
+ };
48
+ parsePower(_data: Buffer): IndoorBikeData;
49
+ onData(characteristic: string, data: Buffer): void;
50
+ writeWahooFtmsMessage(requestedOpCode: number, data: Buffer): Promise<boolean>;
51
+ requestControl(): Promise<boolean>;
52
+ setPowerAdjusting(): void;
53
+ isPowerAdjusting(): boolean;
54
+ setErgMode(power: number): Promise<boolean>;
55
+ setSimMode(weight: number, crr: number, cw: number): Promise<boolean>;
56
+ setSimCRR(crr: number): Promise<boolean>;
57
+ setSimWindResistance(cw: number): Promise<boolean>;
58
+ setSimGrade(slope: number): Promise<boolean>;
59
+ setSimWindSpeed(v: number): Promise<boolean>;
60
+ setTargetPower(power: number): Promise<boolean>;
61
+ setSlope(slope: any): Promise<boolean>;
62
+ reset(): void;
63
+ }
64
+ export declare class WahooAdvancedFmAdapter extends FmAdapter {
65
+ device: WahooAdvancedFitnessMachineDevice;
66
+ constructor(device: BleDeviceClass, protocol: BleProtocol);
67
+ isSame(device: DeviceAdapter): boolean;
68
+ getProfile(): string;
69
+ start(props?: any): Promise<any>;
70
+ pause(): Promise<boolean>;
71
+ resume(): Promise<boolean>;
72
+ }
73
+ export {};