incyclist-devices 2.3.12 → 2.3.14

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.
Files changed (41) hide show
  1. package/lib/ble/adapter-factory.d.ts +34 -0
  2. package/lib/ble/adapter-factory.js +110 -0
  3. package/lib/ble/base/comms-utils.d.ts +7 -0
  4. package/lib/ble/base/comms-utils.js +90 -0
  5. package/lib/ble/base/comms.d.ts +75 -0
  6. package/lib/ble/base/comms.js +599 -0
  7. package/lib/ble/ble-interface.d.ts +84 -0
  8. package/lib/ble/ble-interface.js +622 -0
  9. package/lib/ble/ble-peripheral.d.ts +39 -0
  10. package/lib/ble/ble-peripheral.js +252 -0
  11. package/lib/ble/cp/comm.d.ts +30 -0
  12. package/lib/ble/cp/comm.js +126 -0
  13. package/lib/ble/elite/adapter.d.ts +21 -0
  14. package/lib/ble/elite/adapter.js +118 -0
  15. package/lib/ble/elite/comms.d.ts +31 -0
  16. package/lib/ble/elite/comms.js +127 -0
  17. package/lib/ble/elite/index.d.ts +3 -0
  18. package/lib/ble/elite/index.js +10 -0
  19. package/lib/ble/fm/comms.d.ts +49 -0
  20. package/lib/ble/fm/comms.js +506 -0
  21. package/lib/ble/fm/sensor.js +3 -3
  22. package/lib/ble/hr/comm.d.ts +19 -0
  23. package/lib/ble/hr/comm.js +65 -0
  24. package/lib/ble/peripheral-cache.d.ts +45 -0
  25. package/lib/ble/peripheral-cache.js +109 -0
  26. package/lib/ble/tacx/comms.d.ts +59 -0
  27. package/lib/ble/tacx/comms.js +634 -0
  28. package/lib/ble/wahoo/comms.d.ts +64 -0
  29. package/lib/ble/wahoo/comms.js +399 -0
  30. package/lib/direct-connect/base/comms.d.ts +3 -0
  31. package/lib/direct-connect/base/comms.js +7 -0
  32. package/lib/direct-connect/base/sensor.d.ts +3 -0
  33. package/lib/direct-connect/base/sensor.js +7 -0
  34. package/lib/direct-connect/utils.d.ts +5 -0
  35. package/lib/direct-connect/utils.js +73 -0
  36. package/lib/factories/index.d.ts +3 -0
  37. package/lib/factories/index.js +10 -0
  38. package/lib/modes/power-base.js +8 -5
  39. package/lib/utils/operation.d.ts +17 -0
  40. package/lib/utils/operation.js +20 -0
  41. package/package.json +1 -1
@@ -0,0 +1,506 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const consts_1 = require("../consts");
13
+ const comms_1 = require("../base/comms");
14
+ const utils_1 = require("../utils");
15
+ const bit = (nr) => (1 << nr);
16
+ const IndoorBikeDataFlag = {
17
+ MoreData: bit(0),
18
+ AverageSpeedPresent: bit(1),
19
+ InstantaneousCadence: bit(2),
20
+ AverageCadencePresent: bit(3),
21
+ TotalDistancePresent: bit(4),
22
+ ResistanceLevelPresent: bit(5),
23
+ InstantaneousPowerPresent: bit(6),
24
+ AveragePowerPresent: bit(7),
25
+ ExpendedEnergyPresent: bit(8),
26
+ HeartRatePresent: bit(9),
27
+ MetabolicEquivalentPresent: bit(10),
28
+ ElapsedTimePresent: bit(11),
29
+ RemainingTimePresent: bit(12)
30
+ };
31
+ const FitnessMachineFeatureFlag = {
32
+ AverageSpeedSupported: bit(0),
33
+ CadenceSupported: bit(1),
34
+ TotalDistanceSupported: bit(2),
35
+ InclinationSupported: bit(3),
36
+ ElevationGainSupported: bit(4),
37
+ PaceSupported: bit(5),
38
+ StepCountSupported: bit(6),
39
+ ResistanceLevelSupported: bit(7),
40
+ StrideCountSupported: bit(8),
41
+ ExpendedEnergySupported: bit(9),
42
+ HeartRateMeasurementSupported: bit(10),
43
+ MetabolicEquivalentSupported: bit(11),
44
+ ElapsedTimeSupported: bit(12),
45
+ RemainingTimeSupported: bit(13),
46
+ PowerMeasurementSupported: bit(14),
47
+ ForceOnBeltAndPowerOutputSupported: bit(15),
48
+ UserDataRetentionSupported: bit(16)
49
+ };
50
+ const TargetSettingFeatureFlag = {
51
+ SpeedTargetSettingSupported: bit(0),
52
+ InclinationTargetSettingSupported: bit(1),
53
+ ResistanceTargetSettingSupported: bit(2),
54
+ PowerTargetSettingSupported: bit(3),
55
+ HeartRateTargetSettingSupported: bit(4),
56
+ TargetedExpendedEnergyConfigurationSupported: bit(5),
57
+ TargetedStepNumberConfigurationSupported: bit(6),
58
+ TargetedStrideNumberConfigurationSupported: bit(7),
59
+ TargetedDistanceConfigurationSupported: bit(8),
60
+ TargetedTrainingTimeConfigurationSupported: bit(9),
61
+ TargetedTimeInTwoHeartRateZonesConfigurationSupported: bit(10),
62
+ TargetedTimeInThreeHeartRateZonesConfigurationSupported: bit(11),
63
+ TargetedTimeInFiveHeartRateZonesConfigurationSupported: bit(12),
64
+ IndoorBikeSimulationParametersSupported: bit(13),
65
+ WheelCircumferenceConfigurationSupported: bit(14),
66
+ SpinDownControlSupported: bit(15),
67
+ TargetedCadenceConfigurationSupported: bit(16)
68
+ };
69
+ class BleFitnessMachineDevice extends comms_1.BleComms {
70
+ constructor(props) {
71
+ super(props);
72
+ this.features = undefined;
73
+ this.hasControl = false;
74
+ this.isCheckingControl = false;
75
+ this.isCPSubscribed = false;
76
+ this.crr = 0.0033;
77
+ this.cw = 0.6;
78
+ this.windSpeed = 0;
79
+ this.wheelSize = 2100;
80
+ this.reset();
81
+ this.services = BleFitnessMachineDevice.services;
82
+ }
83
+ static isMatching(characteristics) {
84
+ if (!characteristics)
85
+ return false;
86
+ const announced = characteristics.map(c => (0, utils_1.uuid)(c));
87
+ const hasStatus = announced.find(c => c === consts_1.FTMS_STATUS) !== undefined;
88
+ const hasCP = announced.find(c => c === consts_1.FTMS_CP) !== undefined;
89
+ const hasIndoorBike = announced.find(c => c === consts_1.INDOOR_BIKE_DATA) !== undefined;
90
+ return hasStatus && hasCP && hasIndoorBike;
91
+ }
92
+ subscribeWriteResponse(cuuid) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ this.logEvent({ message: 'subscribe to CP response', characteristics: cuuid });
95
+ const connector = this.ble.peripheralCache.getConnector(this.peripheral);
96
+ const isAlreadySubscribed = connector.isSubscribed(cuuid);
97
+ if (!isAlreadySubscribed) {
98
+ connector.removeAllListeners(cuuid);
99
+ let prev = undefined;
100
+ let prevTS = undefined;
101
+ connector.on(cuuid, (uuid, data) => {
102
+ const message = data.toString('hex');
103
+ if (prevTS && prev && message === prev && Date.now() - prevTS < 500) {
104
+ return;
105
+ }
106
+ prevTS = Date.now();
107
+ prev = message;
108
+ this.onData(uuid, data);
109
+ });
110
+ yield connector.subscribe(cuuid);
111
+ }
112
+ });
113
+ }
114
+ subscribeAll(conn) {
115
+ const characteristics = [consts_1.INDOOR_BIKE_DATA, consts_1.FTMS_STATUS, consts_1.FTMS_CP];
116
+ if (!this.features || (this.features && this.features.cadence))
117
+ characteristics.push(consts_1.CSC_MEASUREMENT);
118
+ if (!this.features || (this.features && this.features.power))
119
+ characteristics.push(consts_1.CSP_MEASUREMENT);
120
+ if (!this.features || (this.features && this.features.heartrate))
121
+ characteristics.push(consts_1.HR_MEASUREMENT);
122
+ return this.subscribeMultiple(characteristics, conn);
123
+ }
124
+ init() {
125
+ const _super = Object.create(null, {
126
+ initDevice: { get: () => super.initDevice }
127
+ });
128
+ return __awaiter(this, void 0, void 0, function* () {
129
+ try {
130
+ yield _super.initDevice.call(this);
131
+ yield this.getFitnessMachineFeatures();
132
+ this.logEvent({ message: 'device info', deviceInfo: this.deviceInfo, features: this.features });
133
+ return true;
134
+ }
135
+ catch (err) {
136
+ this.logEvent({ message: 'error', fn: 'BleFitnessMachineDevice.init()', error: err.message || err, stack: err.stack });
137
+ return false;
138
+ }
139
+ });
140
+ }
141
+ onDisconnect() {
142
+ const _super = Object.create(null, {
143
+ onDisconnect: { get: () => super.onDisconnect }
144
+ });
145
+ return __awaiter(this, void 0, void 0, function* () {
146
+ _super.onDisconnect.call(this);
147
+ this.hasControl = false;
148
+ });
149
+ }
150
+ getProfile() {
151
+ return 'Smart Trainer';
152
+ }
153
+ getProtocol() {
154
+ return BleFitnessMachineDevice.protocol;
155
+ }
156
+ getServiceUUids() {
157
+ return BleFitnessMachineDevice.services;
158
+ }
159
+ parseHrm(_data) {
160
+ const data = Buffer.from(_data);
161
+ try {
162
+ const flags = data.readUInt8(0);
163
+ if (flags % 1 === 0) {
164
+ this.data.heartrate = data.readUInt8(1);
165
+ }
166
+ else {
167
+ this.data.heartrate = data.readUInt16LE(1);
168
+ }
169
+ }
170
+ catch (err) {
171
+ this.logEvent({ message: 'error', fn: 'parseHrm()', error: err.message | err, stack: err.stack });
172
+ }
173
+ return Object.assign(Object.assign({}, this.data), { raw: `2a37:${data.toString('hex')}` });
174
+ }
175
+ setCrr(crr) { this.crr = crr; }
176
+ getCrr() { return this.crr; }
177
+ setCw(cw) { this.cw = cw; }
178
+ getCw() { return this.cw; }
179
+ setWindSpeed(windSpeed) { this.windSpeed = windSpeed; }
180
+ getWindSpeed() { return this.windSpeed; }
181
+ parseIndoorBikeData(_data) {
182
+ const data = Buffer.from(_data);
183
+ try {
184
+ const flags = data.readUInt16LE(0);
185
+ let offset = 2;
186
+ if ((flags & IndoorBikeDataFlag.MoreData) === 0) {
187
+ this.data.speed = data.readUInt16LE(offset) / 100;
188
+ offset += 2;
189
+ }
190
+ if (flags & IndoorBikeDataFlag.AverageSpeedPresent) {
191
+ this.data.averageSpeed = data.readUInt16LE(offset) / 100;
192
+ offset += 2;
193
+ }
194
+ if (flags & IndoorBikeDataFlag.InstantaneousCadence) {
195
+ this.data.cadence = data.readUInt16LE(offset) / 2;
196
+ offset += 2;
197
+ }
198
+ if (flags & IndoorBikeDataFlag.AverageCadencePresent) {
199
+ this.data.averageCadence = data.readUInt16LE(offset) / 2;
200
+ offset += 2;
201
+ }
202
+ if (flags & IndoorBikeDataFlag.TotalDistancePresent) {
203
+ const dvLow = data.readUInt8(offset);
204
+ offset += 1;
205
+ const dvHigh = data.readUInt16LE(offset);
206
+ offset += 2;
207
+ this.data.totalDistance = (dvHigh << 8) + dvLow;
208
+ }
209
+ if (flags & IndoorBikeDataFlag.ResistanceLevelPresent) {
210
+ this.data.resistanceLevel = data.readInt16LE(offset);
211
+ offset += 2;
212
+ }
213
+ if (flags & IndoorBikeDataFlag.InstantaneousPowerPresent) {
214
+ this.data.instantaneousPower = data.readInt16LE(offset);
215
+ offset += 2;
216
+ }
217
+ if (flags & IndoorBikeDataFlag.AveragePowerPresent) {
218
+ this.data.averagePower = data.readInt16LE(offset);
219
+ offset += 2;
220
+ }
221
+ if (flags & IndoorBikeDataFlag.ExpendedEnergyPresent) {
222
+ this.data.totalEnergy = data.readUInt16LE(offset);
223
+ offset += 2;
224
+ this.data.energyPerHour = data.readUInt16LE(offset);
225
+ offset += 2;
226
+ this.data.energyPerMinute = data.readUInt8(offset);
227
+ offset += 1;
228
+ }
229
+ if (flags & IndoorBikeDataFlag.HeartRatePresent) {
230
+ this.data.heartrate = data.readUInt8(offset);
231
+ offset += 1;
232
+ }
233
+ if (flags & IndoorBikeDataFlag.MetabolicEquivalentPresent) {
234
+ this.data.metabolicEquivalent = data.readUInt8(offset) / 10;
235
+ offset += 2;
236
+ }
237
+ if (flags & IndoorBikeDataFlag.ElapsedTimePresent) {
238
+ this.data.time = data.readUInt16LE(offset);
239
+ offset += 2;
240
+ }
241
+ if (flags & IndoorBikeDataFlag.RemainingTimePresent) {
242
+ this.data.remainingTime = data.readUInt16LE(offset);
243
+ offset += 2;
244
+ }
245
+ }
246
+ catch (err) {
247
+ this.logEvent({ message: 'error', fn: 'parseIndoorBikeData()', error: err.message | err, stack: err.stack });
248
+ }
249
+ return Object.assign(Object.assign({}, this.data), { raw: `2ad2:${data.toString('hex')}` });
250
+ }
251
+ parseFitnessMachineStatus(_data) {
252
+ const data = Buffer.from(_data);
253
+ try {
254
+ const OpCode = data.readUInt8(0);
255
+ switch (OpCode) {
256
+ case 8:
257
+ this.data.targetPower = data.readInt16LE(1);
258
+ break;
259
+ case 6:
260
+ this.data.targetInclination = data.readInt16LE(1) / 10;
261
+ break;
262
+ case 4:
263
+ this.data.status = "STARTED";
264
+ break;
265
+ case 3:
266
+ case 2:
267
+ this.data.status = "STOPPED";
268
+ break;
269
+ case 20:
270
+ const spinDownStatus = data.readUInt8(1);
271
+ switch (spinDownStatus) {
272
+ case 1:
273
+ this.data.status = "SPIN DOWN REQUESTED";
274
+ break;
275
+ case 2:
276
+ this.data.status = "SPIN DOWN SUCCESS";
277
+ break;
278
+ case 3:
279
+ this.data.status = "SPIN DOWN ERROR";
280
+ break;
281
+ case 4:
282
+ this.data.status = "STOP PEDALING";
283
+ break;
284
+ default: break;
285
+ }
286
+ }
287
+ }
288
+ catch (err) {
289
+ this.logEvent({ message: 'error', fn: 'parseFitnessMachineStatus()', error: err.message | err, stack: err.stack });
290
+ }
291
+ return Object.assign(Object.assign({}, this.data), { raw: `2ada:${data.toString('hex')}` });
292
+ }
293
+ getFitnessMachineFeatures() {
294
+ return __awaiter(this, void 0, void 0, function* () {
295
+ if (this.features)
296
+ return this.features;
297
+ try {
298
+ const data = yield this.read('2acc');
299
+ const buffer = data ? Buffer.from(data) : undefined;
300
+ if (buffer) {
301
+ const fitnessMachine = buffer.readUInt32LE(0);
302
+ const targetSettings = buffer.readUInt32LE(4);
303
+ const power = (fitnessMachine & FitnessMachineFeatureFlag.PowerMeasurementSupported) !== 0;
304
+ const heartrate = (fitnessMachine & FitnessMachineFeatureFlag.HeartRateMeasurementSupported) !== 0;
305
+ const cadence = (fitnessMachine & FitnessMachineFeatureFlag.CadenceSupported) !== 0;
306
+ const setSlope = (targetSettings & TargetSettingFeatureFlag.IndoorBikeSimulationParametersSupported) !== 0
307
+ || (targetSettings & TargetSettingFeatureFlag.InclinationTargetSettingSupported) !== 0;
308
+ const setPower = (targetSettings & TargetSettingFeatureFlag.PowerTargetSettingSupported) !== 0;
309
+ this.features = { fitnessMachine, targetSettings, power, heartrate, cadence, setPower, setSlope };
310
+ this.logEvent({ message: 'supported Features: ', fatures: this.features, power, heartrate, cadence });
311
+ }
312
+ }
313
+ catch (err) {
314
+ this.logEvent({ message: 'could not read FitnessMachineFeatures', error: err.message, stack: err.stack });
315
+ return undefined;
316
+ }
317
+ });
318
+ }
319
+ onData(characteristic, data) {
320
+ const hasData = super.onData(characteristic, data);
321
+ if (!hasData)
322
+ return false;
323
+ const uuid = characteristic.toLocaleLowerCase();
324
+ let res = undefined;
325
+ switch (uuid) {
326
+ case consts_1.INDOOR_BIKE_DATA:
327
+ res = this.parseIndoorBikeData(data);
328
+ break;
329
+ case '2a37':
330
+ res = this.parseHrm(data);
331
+ break;
332
+ case consts_1.FTMS_STATUS:
333
+ res = this.parseFitnessMachineStatus(data);
334
+ break;
335
+ case '2a63':
336
+ case '2a5b':
337
+ case '347b0011-7635-408b-8918-8ff3949ce592':
338
+ break;
339
+ default:
340
+ break;
341
+ }
342
+ if (res) {
343
+ this.emit('data', res);
344
+ return false;
345
+ }
346
+ return true;
347
+ }
348
+ writeFtmsMessage(requestedOpCode, data, props) {
349
+ return __awaiter(this, void 0, void 0, function* () {
350
+ try {
351
+ this.logEvent({ message: 'fmts:write', data: data.toString('hex') });
352
+ const res = yield this.write(consts_1.FTMS_CP, data, props);
353
+ const responseData = Buffer.from(res);
354
+ const opCode = responseData.readUInt8(0);
355
+ const request = responseData.readUInt8(1);
356
+ const result = responseData.readUInt8(2);
357
+ if (opCode !== 128 || request !== requestedOpCode)
358
+ throw new Error('Illegal response ');
359
+ this.logEvent({ message: 'fmts:write result', res, result });
360
+ return result;
361
+ }
362
+ catch (err) {
363
+ this.logEvent({ message: 'fmts:write failed', opCode: requestedOpCode, reason: err.message });
364
+ return 4;
365
+ }
366
+ });
367
+ }
368
+ requestControl() {
369
+ return __awaiter(this, void 0, void 0, function* () {
370
+ let to = undefined;
371
+ if (this.isCheckingControl) {
372
+ to = setTimeout(() => { }, 3500);
373
+ }
374
+ if (this.hasControl)
375
+ return true;
376
+ this.logEvent({ message: 'requestControl' });
377
+ this.isCheckingControl = true;
378
+ const data = Buffer.alloc(1);
379
+ data.writeUInt8(0, 0);
380
+ const res = yield this.writeFtmsMessage(0, data, { timeout: 5000 });
381
+ if (res === 1) {
382
+ this.hasControl = true;
383
+ }
384
+ else {
385
+ this.logEvent({ message: 'requestControl failed' });
386
+ }
387
+ this.isCheckingControl = false;
388
+ if (to)
389
+ clearTimeout(to);
390
+ return this.hasControl;
391
+ });
392
+ }
393
+ setTargetPower(power) {
394
+ return __awaiter(this, void 0, void 0, function* () {
395
+ this.logEvent({ message: 'setTargetPower', power, skip: (this.data.targetPower !== undefined && this.data.targetPower === power) });
396
+ if (this.data.targetPower !== undefined && this.data.targetPower === power)
397
+ return true;
398
+ if (!this.hasControl)
399
+ return;
400
+ const hasControl = yield this.requestControl();
401
+ if (!hasControl) {
402
+ this.logEvent({ message: 'setTargetPower failed', reason: 'control is disabled' });
403
+ return true;
404
+ }
405
+ const data = Buffer.alloc(3);
406
+ data.writeUInt8(5, 0);
407
+ data.writeInt16LE(Math.round(power), 1);
408
+ const res = yield this.writeFtmsMessage(5, data);
409
+ return (res === 1);
410
+ });
411
+ }
412
+ setSlope(slope) {
413
+ return __awaiter(this, void 0, void 0, function* () {
414
+ this.logEvent({ message: 'setSlope', slope });
415
+ const hasControl = yield this.requestControl();
416
+ if (!hasControl)
417
+ return;
418
+ const { windSpeed, crr, cw } = this;
419
+ return yield this.setIndoorBikeSimulation(windSpeed, slope, crr, cw);
420
+ });
421
+ }
422
+ setTargetInclination(inclination) {
423
+ return __awaiter(this, void 0, void 0, function* () {
424
+ if (this.data.targetInclination !== undefined && this.data.targetInclination === inclination)
425
+ return true;
426
+ if (!this.hasControl)
427
+ return;
428
+ const hasControl = yield this.requestControl();
429
+ if (!hasControl) {
430
+ this.logEvent({ message: 'setTargetInclination failed', reason: 'control is disabled' });
431
+ return false;
432
+ }
433
+ const data = Buffer.alloc(3);
434
+ data.writeUInt8(3, 0);
435
+ data.writeInt16LE(Math.round(inclination * 10), 1);
436
+ const res = yield this.writeFtmsMessage(3, data);
437
+ return (res === 1);
438
+ });
439
+ }
440
+ setIndoorBikeSimulation(windSpeed, gradient, crr, cw) {
441
+ return __awaiter(this, void 0, void 0, function* () {
442
+ const hasControl = yield this.requestControl();
443
+ if (!hasControl) {
444
+ this.logEvent({ message: 'setIndoorBikeSimulation failed', reason: 'control is disabled' });
445
+ return false;
446
+ }
447
+ const data = Buffer.alloc(7);
448
+ data.writeUInt8(17, 0);
449
+ data.writeInt16LE(Math.round(windSpeed * 1000), 1);
450
+ data.writeInt16LE(Math.round(gradient * 100), 3);
451
+ data.writeUInt8(Math.round(crr * 10000), 5);
452
+ data.writeUInt8(Math.round(cw * 100), 6);
453
+ const res = yield this.writeFtmsMessage(17, data);
454
+ return (res === 1);
455
+ });
456
+ }
457
+ startRequest() {
458
+ return __awaiter(this, void 0, void 0, function* () {
459
+ const hasControl = yield this.requestControl();
460
+ if (!hasControl) {
461
+ this.logEvent({ message: 'startRequest failed', reason: 'control is disabled' });
462
+ return false;
463
+ }
464
+ const data = Buffer.alloc(1);
465
+ data.writeUInt8(7, 0);
466
+ const res = yield this.writeFtmsMessage(7, data);
467
+ return (res === 1);
468
+ });
469
+ }
470
+ stopRequest() {
471
+ return __awaiter(this, void 0, void 0, function* () {
472
+ const hasControl = yield this.requestControl();
473
+ if (!hasControl) {
474
+ this.logEvent({ message: 'stopRequest failed', reason: 'control is disabled' });
475
+ return false;
476
+ }
477
+ const data = Buffer.alloc(2);
478
+ data.writeUInt8(8, 0);
479
+ data.writeUInt8(1, 1);
480
+ const res = yield this.writeFtmsMessage(8, data);
481
+ return (res === 1);
482
+ });
483
+ }
484
+ PauseRequest() {
485
+ return __awaiter(this, void 0, void 0, function* () {
486
+ const hasControl = yield this.requestControl();
487
+ if (!hasControl) {
488
+ this.logEvent({ message: 'PauseRequest failed', reason: 'control is disabled' });
489
+ return false;
490
+ }
491
+ const data = Buffer.alloc(2);
492
+ data.writeUInt8(8, 0);
493
+ data.writeUInt8(2, 1);
494
+ const res = yield this.writeFtmsMessage(8, data);
495
+ return (res === 1);
496
+ });
497
+ }
498
+ reset() {
499
+ this.data = {};
500
+ }
501
+ }
502
+ BleFitnessMachineDevice.protocol = 'fm';
503
+ BleFitnessMachineDevice.services = [consts_1.FTMS];
504
+ BleFitnessMachineDevice.characteristics = ['2acc', consts_1.INDOOR_BIKE_DATA, '2ad6', '2ad8', consts_1.FTMS_CP, consts_1.FTMS_STATUS];
505
+ BleFitnessMachineDevice.detectionPriority = 100;
506
+ exports.default = BleFitnessMachineDevice;
@@ -144,9 +144,9 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
144
144
  }
145
145
  parseIndoorBikeData(_data) {
146
146
  const data = Buffer.from(_data);
147
+ let offset = 2;
147
148
  try {
148
149
  const flags = data.readUInt16LE(0);
149
- let offset = 2;
150
150
  if ((flags & consts_2.IndoorBikeDataFlag.MoreData) === 0) {
151
151
  this.data.speed = data.readUInt16LE(offset) / 100;
152
152
  offset += 2;
@@ -196,7 +196,7 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
196
196
  }
197
197
  if (flags & consts_2.IndoorBikeDataFlag.MetabolicEquivalentPresent) {
198
198
  this.data.metabolicEquivalent = data.readUInt8(offset) / 10;
199
- offset += 2;
199
+ offset += 1;
200
200
  }
201
201
  if (flags & consts_2.IndoorBikeDataFlag.ElapsedTimePresent) {
202
202
  this.data.time = data.readUInt16LE(offset);
@@ -207,7 +207,7 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
207
207
  }
208
208
  }
209
209
  catch (err) {
210
- this.logEvent({ message: 'error', fn: 'parseIndoorBikeData()', error: err.message | err, stack: err.stack });
210
+ this.logEvent({ message: 'error', fn: 'parseIndoorBikeData()', data: data.toString('hex'), offset, error: err.message | err, stack: err.stack });
211
211
  }
212
212
  return Object.assign(Object.assign({}, this.data), { raw: `2ad2:${data.toString('hex')}` });
213
213
  }
@@ -0,0 +1,19 @@
1
+ import { LegacyProfile } from '../../antv2/types';
2
+ import { BleComms } from '../base/comms';
3
+ import { BleProtocol } from '../types';
4
+ import { HrmData } from './types';
5
+ export default class BleHrmDevice extends BleComms {
6
+ static protocol: BleProtocol;
7
+ static services: string[];
8
+ static characteristics: string[];
9
+ static detectionPriority: number;
10
+ heartrate: number;
11
+ rr: number;
12
+ constructor(props?: any);
13
+ getProfile(): LegacyProfile;
14
+ getProtocol(): BleProtocol;
15
+ getServiceUUids(): string[];
16
+ static isMatching(characteristics: string[]): boolean;
17
+ parseHrm(_data: Uint8Array): HrmData;
18
+ onData(characteristic: string, data: Buffer): boolean;
19
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const comms_1 = require("../base/comms");
4
+ const consts_1 = require("../consts");
5
+ const utils_1 = require("../utils");
6
+ class BleHrmDevice extends comms_1.BleComms {
7
+ constructor(props) {
8
+ super(props);
9
+ this.heartrate = undefined;
10
+ this.rr = undefined;
11
+ }
12
+ getProfile() {
13
+ return 'Heartrate Monitor';
14
+ }
15
+ getProtocol() {
16
+ return BleHrmDevice.protocol;
17
+ }
18
+ getServiceUUids() {
19
+ return BleHrmDevice.services;
20
+ }
21
+ static isMatching(characteristics) {
22
+ if (!characteristics)
23
+ return false;
24
+ const announced = characteristics.map(c => (0, utils_1.uuid)(c));
25
+ const hasHRMeasurement = announced.find(c => c === consts_1.HR_MEASUREMENT) !== undefined;
26
+ return hasHRMeasurement;
27
+ }
28
+ parseHrm(_data) {
29
+ const data = Buffer.from(_data);
30
+ try {
31
+ const flags = data.readUInt8(0);
32
+ let offset = 2;
33
+ if (flags % 1 === 0) {
34
+ this.heartrate = data.readUInt8(1);
35
+ }
36
+ else {
37
+ this.heartrate = data.readUInt16LE(1);
38
+ offset = 3;
39
+ }
40
+ if (flags % 0xF) {
41
+ this.rr = (data.readUInt16LE(offset)) / 1024;
42
+ }
43
+ }
44
+ catch (err) {
45
+ }
46
+ const { heartrate, rr } = this;
47
+ return { heartrate, rr, raw: data.toString('hex') };
48
+ }
49
+ onData(characteristic, data) {
50
+ const hasData = super.onData(characteristic, data);
51
+ if (!hasData)
52
+ return;
53
+ if (characteristic.toLocaleLowerCase() === '2a37') {
54
+ const res = this.parseHrm(data);
55
+ this.emit('data', res);
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+ }
61
+ BleHrmDevice.protocol = 'hr';
62
+ BleHrmDevice.services = ['180d'];
63
+ BleHrmDevice.characteristics = [consts_1.HR_MEASUREMENT, '2a38', '2a39', '2a3c'];
64
+ BleHrmDevice.detectionPriority = 1;
65
+ exports.default = BleHrmDevice;
@@ -0,0 +1,45 @@
1
+ import BleAdapter from "./base/adapter";
2
+ import { BleComms } from "./base/comms";
3
+ import { BleDeviceData } from "./base/types";
4
+ import BlePeripheralConnector from "./ble-peripheral";
5
+ import { BleCharacteristic, BlePeripheral } from "./types";
6
+ export interface PeripheralState {
7
+ isLoading: boolean;
8
+ isConfigured: boolean;
9
+ isInterrupted: boolean;
10
+ }
11
+ export interface PeripheralCacheItem {
12
+ address: string;
13
+ name?: string;
14
+ id?: string;
15
+ ts: number;
16
+ peripheral: BlePeripheral;
17
+ state?: PeripheralState;
18
+ characteristics?: BleCharacteristic[];
19
+ connector?: BlePeripheralConnector;
20
+ }
21
+ export default class BlePeripheralCache {
22
+ peripherals: PeripheralCacheItem[];
23
+ findAdapter(adapter: BleAdapter<BleDeviceData, BleComms>): PeripheralCacheItem;
24
+ getConnector(peripheral: BlePeripheral): BlePeripheralConnector;
25
+ getPeripheral(query: {
26
+ id?: string;
27
+ address?: string;
28
+ name?: string;
29
+ }): BlePeripheral;
30
+ handleStopScan(): void;
31
+ find(query: {
32
+ name?: string;
33
+ id?: string;
34
+ address?: string;
35
+ peripheral?: BlePeripheral;
36
+ }): PeripheralCacheItem;
37
+ filter(services: string[]): PeripheralCacheItem[];
38
+ protected _findIndex(query: {
39
+ name?: string;
40
+ id?: string;
41
+ address?: string;
42
+ }): number;
43
+ add(item: PeripheralCacheItem): PeripheralCacheItem;
44
+ remove(query: PeripheralCacheItem | BlePeripheral): void;
45
+ }