incyclist-devices 1.4.37 → 1.4.40

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.
@@ -11,6 +11,7 @@ import { CyclingModeProperyType } from './CyclingMode';
11
11
  import BleInterface from './ble/ble-interface';
12
12
  import BleHrmDevice from './ble/hrm';
13
13
  import BleCyclingPowerDevice from './ble/pwr';
14
+ import BleFitnessMachineDevice from './ble/fm';
14
15
  declare const Protocols: {
15
16
  SimulatorProtocol: typeof SimulatorProtocol;
16
17
  DaumClassicProtocol: typeof DaumClassicProtocol;
@@ -18,4 +19,4 @@ declare const Protocols: {
18
19
  KettlerRacerProtocol: typeof KettlerRacerProtocol;
19
20
  BleProtocol: typeof BleProtocol;
20
21
  };
21
- export { DeviceProtocolBase, DeviceProtocol, DeviceRegistry, INTERFACE, DeviceAdapter as Device, Protocols, AntScanner, BleProtocol, CyclingModeProperyType, BleInterface, BleHrmDevice, BleCyclingPowerDevice };
22
+ export { DeviceProtocolBase, DeviceProtocol, DeviceRegistry, INTERFACE, DeviceAdapter as Device, Protocols, AntScanner, BleProtocol, CyclingModeProperyType, BleInterface, BleHrmDevice, BleCyclingPowerDevice, BleFitnessMachineDevice };
@@ -33,6 +33,8 @@ const hrm_1 = __importDefault(require("./ble/hrm"));
33
33
  exports.BleHrmDevice = hrm_1.default;
34
34
  const pwr_1 = __importDefault(require("./ble/pwr"));
35
35
  exports.BleCyclingPowerDevice = pwr_1.default;
36
+ const fm_1 = __importDefault(require("./ble/fm"));
37
+ exports.BleFitnessMachineDevice = fm_1.default;
36
38
  const Protocols = {
37
39
  SimulatorProtocol: Simulator_1.default,
38
40
  DaumClassicProtocol: DaumClassicProtocol_1.default,
@@ -1,11 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { EventLogger } from "gd-eventlog";
3
3
  import { BleInterfaceClass, BleDeviceClass, BlePeripheral, BleDeviceProps, ConnectProps } from "./ble";
4
- interface ConnectState {
5
- isConnecting: boolean;
6
- isConnected: boolean;
7
- isDisconnecting: boolean;
8
- }
9
4
  interface BleDeviceConstructProps extends BleDeviceProps {
10
5
  log?: boolean;
11
6
  logger?: EventLogger;
@@ -19,13 +14,13 @@ export declare abstract class BleDevice extends BleDeviceClass {
19
14
  peripheral?: BlePeripheral;
20
15
  characteristics: any[];
21
16
  state?: string;
22
- connectState: ConnectState;
23
17
  logger?: EventLogger;
24
18
  constructor(props?: BleDeviceConstructProps);
25
19
  logEvent(event: any): void;
26
20
  setInterface(ble: BleInterfaceClass): void;
27
21
  private cleanupListeners;
28
22
  private onDisconnect;
23
+ waitForConnectFinished(timeout: any): Promise<unknown>;
29
24
  connect(props?: ConnectProps): Promise<boolean>;
30
25
  disconnect(): Promise<boolean>;
31
26
  abstract getProfile(): string;
@@ -11,11 +11,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const gd_eventlog_1 = require("gd-eventlog");
13
13
  const ble_1 = require("./ble");
14
+ const CONNECT_WAIT_TIMEOUT = 10000;
14
15
  class BleDevice extends ble_1.BleDeviceClass {
15
16
  constructor(props) {
16
17
  super();
17
18
  this.characteristics = [];
18
- this.connectState = { isConnecting: false, isConnected: false, isDisconnecting: false };
19
19
  this.id = props.id;
20
20
  this.address = props.address;
21
21
  this.name = props.name;
@@ -43,7 +43,7 @@ class BleDevice extends ble_1.BleDeviceClass {
43
43
  this.logger.logEvent(event);
44
44
  }
45
45
  if (process.env.BLE_DEBUG) {
46
- console.log(event);
46
+ console.log('~~~BLE:', event);
47
47
  }
48
48
  }
49
49
  setInterface(ble) {
@@ -55,9 +55,9 @@ class BleDevice extends ble_1.BleDeviceClass {
55
55
  }
56
56
  else {
57
57
  this.characteristics.forEach(c => {
58
+ c.unsubscribe();
58
59
  c.removeAllListeners('data');
59
60
  });
60
- this.characteristics = [];
61
61
  }
62
62
  }
63
63
  onDisconnect() {
@@ -67,6 +67,27 @@ class BleDevice extends ble_1.BleDeviceClass {
67
67
  }
68
68
  this.emit('disconnected');
69
69
  }
70
+ waitForConnectFinished(timeout) {
71
+ const waitStart = Date.now();
72
+ const waitTimeout = waitStart + timeout;
73
+ return new Promise((resolve, reject) => {
74
+ const waitIv = setInterval(() => {
75
+ try {
76
+ if (this.connectState.isConnecting && Date.now() > waitTimeout) {
77
+ clearInterval(waitIv);
78
+ return reject(new Error('connection already in progress'));
79
+ }
80
+ if (!this.connectState.isConnecting) {
81
+ clearInterval(waitIv);
82
+ return resolve(true);
83
+ }
84
+ }
85
+ catch (err) {
86
+ console.log('~~~ error', err);
87
+ }
88
+ }, 100);
89
+ });
90
+ }
70
91
  connect(props) {
71
92
  return __awaiter(this, void 0, void 0, function* () {
72
93
  const connectPeripheral = (peripheral) => __awaiter(this, void 0, void 0, function* () {
@@ -80,30 +101,36 @@ class BleDevice extends ble_1.BleDeviceClass {
80
101
  this.logEvent({ message: 'cannot connect', error: err.message || err });
81
102
  }
82
103
  }
83
- this.connectState.isConnecting = false;
84
- this.connectState.isConnected = true;
85
- this.state = "connected";
86
- this.emit('connected');
87
- this.cleanupListeners();
88
- this.ble.addConnectedDevice(this);
89
- this.peripheral.once('disconnect', () => { this.onDisconnect(); });
90
104
  try {
105
+ this.cleanupListeners();
91
106
  if (!connected) {
92
- const { characteristics } = yield peripheral.discoverSomeServicesAndCharacteristicsAsync(this.services || [], []);
107
+ this.logEvent({ message: 'connect: discover characteristics start' });
108
+ const res = yield peripheral.discoverSomeServicesAndCharacteristicsAsync([], []);
109
+ const { characteristics } = res;
110
+ this.logEvent({ message: 'connect: discover characteristics result',
111
+ result: characteristics.map(c => ({ uuid: ble_1.uuid(c.uuid), properties: c.properties.join(','), service: ble_1.uuid(c._serviceUuid) }))
112
+ });
93
113
  this.characteristics = characteristics;
94
114
  }
95
115
  else {
96
116
  this.characteristics = connected.characteristics;
97
117
  }
118
+ this.connectState.isConnecting = false;
119
+ this.connectState.isConnected = true;
120
+ this.state = "connected";
121
+ this.emit('connected');
122
+ this.ble.addConnectedDevice(this);
123
+ this.peripheral.once('disconnect', () => { this.onDisconnect(); });
98
124
  this.characteristics.forEach(c => {
99
125
  if (c.properties.find(p => p === 'notify')) {
100
126
  c.on('data', (data, _isNotification) => {
101
127
  this.onData(ble_1.uuid(c.uuid), data);
102
128
  });
103
129
  if (!connected) {
130
+ this.logEvent({ message: 'subscribe', device: this.name, address: this.address, service: c._serviceUuid, characteristic: c.uuid });
104
131
  c.subscribe((err) => {
105
132
  if (err)
106
- this.logEvent({ message: 'cannot subscribe', error: err.message || err });
133
+ this.logEvent({ message: 'cannot subscribe', device: this.name, address: this.address, service: c._serviceUuid, characteristic: c.uuid, error: err.message || err });
107
134
  });
108
135
  }
109
136
  }
@@ -111,9 +138,25 @@ class BleDevice extends ble_1.BleDeviceClass {
111
138
  }
112
139
  catch (err) {
113
140
  this.logEvent({ message: 'cannot connect', error: err.message || err });
141
+ this.connectState.isConnecting = false;
142
+ this.connectState.isConnected = false;
114
143
  }
115
144
  });
116
145
  try {
146
+ if (this.connectState.isConnecting) {
147
+ yield this.waitForConnectFinished(CONNECT_WAIT_TIMEOUT);
148
+ }
149
+ if (this.connectState.isConnected) {
150
+ this.characteristics.forEach(c => {
151
+ if (c.properties.find(p => p === 'notify')) {
152
+ c.on('data', (data, _isNotification) => {
153
+ this.onData(ble_1.uuid(c.uuid), data);
154
+ });
155
+ }
156
+ });
157
+ return true;
158
+ }
159
+ this.connectState.isConnecting = true;
117
160
  if (this.peripheral) {
118
161
  const { id, address, advertisement } = this.peripheral;
119
162
  const name = advertisement === null || advertisement === void 0 ? void 0 : advertisement.localName;
@@ -124,8 +167,8 @@ class BleDevice extends ble_1.BleDeviceClass {
124
167
  }
125
168
  else {
126
169
  const { id, name, address } = this;
170
+ let error;
127
171
  if (this.address || this.id || this.name) {
128
- this.connectState.isConnecting = true;
129
172
  this.logEvent({ message: 'connect requested', mode: 'device', device: { id, name, address } });
130
173
  try {
131
174
  if (this.ble.isScanning()) {
@@ -140,9 +183,11 @@ class BleDevice extends ble_1.BleDeviceClass {
140
183
  }
141
184
  }
142
185
  catch (err) {
186
+ console.log('~~~ error', err);
187
+ error = err;
143
188
  }
144
189
  }
145
- this.logEvent({ message: 'connect result: failure', mode: 'device', device: { id, name, address } });
190
+ this.logEvent({ message: 'connect result: failure', mode: 'device', device: { id, name, address }, error: error.message, stack: error.stack });
146
191
  this.connectState.isConnecting = false;
147
192
  this.connectState.isConnected = false;
148
193
  return false;
@@ -3,6 +3,8 @@ import { EventLogger } from 'gd-eventlog';
3
3
  import { BleInterfaceClass, ConnectProps, ScanProps, BleDeviceClass, BlePeripheral, BleBinding } from './ble';
4
4
  export interface ScanState {
5
5
  isScanning: boolean;
6
+ isConnecting: boolean;
7
+ isBackgroundScan: boolean;
6
8
  timeout?: NodeJS.Timeout;
7
9
  }
8
10
  export interface ConnectState {
@@ -26,6 +28,7 @@ export default class BleInterface extends BleInterfaceClass {
26
28
  connectState: ConnectState;
27
29
  devices: BleDeviceInfo[];
28
30
  logger: EventLogger;
31
+ deviceCache: any[];
29
32
  static deviceClasses: BleDeviceClassInfo[];
30
33
  static _instance: BleInterface;
31
34
  static getInstance(props?: {
@@ -48,7 +51,10 @@ export default class BleInterface extends BleInterfaceClass {
48
51
  getDevicesFromServices(deviceTypes: (typeof BleDeviceClass)[], services: string | string[]): (typeof BleDeviceClass)[];
49
52
  getServicesFromDeviceTypes(deviceTypes: (typeof BleDeviceClass)[]): string[];
50
53
  getServicesFromDevice(device: BleDeviceClass): string[];
54
+ waitForConnectFinished(timeout: any): Promise<unknown>;
51
55
  connectDevice(requested: BleDeviceClass, timeout?: number): Promise<BleDeviceClass>;
56
+ waitForScanFinished(timeout: any): Promise<unknown>;
57
+ addPeripheralToCache(peripheral: BlePeripheral): void;
52
58
  scan(props: ScanProps): Promise<BleDeviceClass[]>;
53
59
  stopScan(): Promise<boolean>;
54
60
  isScanning(): boolean;
@@ -12,12 +12,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const gd_eventlog_1 = require("gd-eventlog");
13
13
  const utils_1 = require("../utils");
14
14
  const ble_1 = require("./ble");
15
+ const CONNECT_TIMEOUT = 5000;
15
16
  class BleInterface extends ble_1.BleInterfaceClass {
16
17
  constructor(props = {}) {
17
18
  super(props);
18
- this.scanState = { isScanning: false, timeout: undefined };
19
+ this.scanState = { isScanning: false, isConnecting: false, timeout: undefined, isBackgroundScan: false };
19
20
  this.connectState = { isConnecting: false, isConnected: false, isInitSuccess: false };
20
21
  this.devices = [];
22
+ this.deviceCache = [];
21
23
  if (props.logger)
22
24
  this.logger = props.logger;
23
25
  else if (props.log) {
@@ -51,7 +53,7 @@ class BleInterface extends ble_1.BleInterfaceClass {
51
53
  this.logger.logEvent(event);
52
54
  }
53
55
  if (process.env.BLE_DEBUG) {
54
- console.log(event);
56
+ console.log('~~BLE:', event);
55
57
  }
56
58
  }
57
59
  onStateChange(state) {
@@ -67,11 +69,21 @@ class BleInterface extends ble_1.BleInterfaceClass {
67
69
  }
68
70
  connect(props = {}) {
69
71
  const timeout = props.timeout || 2000;
72
+ const runBackgroundScan = () => {
73
+ this.scanState.isBackgroundScan = true;
74
+ this.scan({ timeout: 5000, isBackgroundScan: true })
75
+ .then(() => {
76
+ this.scanState.isBackgroundScan = false;
77
+ })
78
+ .catch(() => {
79
+ this.scanState.isBackgroundScan = false;
80
+ });
81
+ };
70
82
  return new Promise((resolve, reject) => {
71
83
  if (this.connectState.isConnected) {
72
84
  return resolve(true);
73
85
  }
74
- this.logEvent({ message: 'connect request' });
86
+ this.logEvent({ message: 'connect request', });
75
87
  if (!this.getBinding())
76
88
  return Promise.reject(new Error('no binding defined'));
77
89
  this.connectState.timeout = setTimeout(() => {
@@ -114,7 +126,9 @@ class BleInterface extends ble_1.BleInterfaceClass {
114
126
  this.connectState.isConnected = true;
115
127
  this.connectState.isConnecting = false;
116
128
  this.logEvent({ message: 'connect result: success' });
117
- return resolve(true);
129
+ resolve(true);
130
+ runBackgroundScan();
131
+ return;
118
132
  }
119
133
  else {
120
134
  this.getBinding().once('error', (err) => {
@@ -133,6 +147,7 @@ class BleInterface extends ble_1.BleInterfaceClass {
133
147
  this.connectState.isConnected = true;
134
148
  this.connectState.isConnecting = false;
135
149
  this.logEvent({ message: 'connect result: success' });
150
+ runBackgroundScan();
136
151
  return resolve(true);
137
152
  }
138
153
  else {
@@ -229,16 +244,37 @@ class BleInterface extends ble_1.BleInterfaceClass {
229
244
  });
230
245
  return services;
231
246
  }
232
- connectDevice(requested, timeout = 2000) {
247
+ waitForConnectFinished(timeout) {
248
+ const waitStart = Date.now();
249
+ const waitTimeout = waitStart + timeout;
250
+ return new Promise((resolve, reject) => {
251
+ const waitIv = setInterval(() => {
252
+ if (this.scanState.isConnecting && Date.now() > waitTimeout) {
253
+ clearInterval(waitIv);
254
+ return reject(new Error('Connecting already in progress'));
255
+ }
256
+ if (!this.scanState.isConnecting) {
257
+ clearInterval(waitIv);
258
+ return resolve(true);
259
+ }
260
+ }, 100);
261
+ });
262
+ }
263
+ connectDevice(requested, timeout = CONNECT_TIMEOUT) {
233
264
  return __awaiter(this, void 0, void 0, function* () {
234
- const { id, name, address } = requested;
235
- this.logEvent({ message: 'connectDevice', id, name, address });
265
+ const { id, name, address, getProfile } = requested;
266
+ const profile = getProfile && typeof (getProfile) === 'function' ? getProfile() : undefined;
267
+ this.logEvent({ message: 'connectDevice', id, name, address, profile, isbusy: this.scanState.isConnecting });
268
+ if (this.scanState.isConnecting) {
269
+ yield this.waitForConnectFinished(10000);
270
+ }
271
+ this.scanState.isConnecting = true;
236
272
  let devices = [];
237
273
  let retry = false;
238
274
  let retryCount = 0;
239
275
  do {
240
276
  if (retryCount > 0) {
241
- this.logEvent({ message: 'retry connect device', retryCount });
277
+ this.logEvent({ message: 'retry connect device', id, name, address, profile, retryCount });
242
278
  }
243
279
  try {
244
280
  devices = yield this.scan({ timeout, device: requested });
@@ -249,28 +285,66 @@ class BleInterface extends ble_1.BleInterfaceClass {
249
285
  }
250
286
  catch (err) {
251
287
  if (err.message === 'scanning already in progress') {
252
- this.logEvent({ message: 'scan busy' });
253
288
  yield utils_1.sleep(1000);
254
289
  retryCount++;
255
290
  retry = retryCount < 5;
256
291
  continue;
257
292
  }
293
+ this.scanState.isConnecting = false;
258
294
  throw err;
259
295
  }
260
296
  } while (devices.length === 0 && retry);
261
- if (devices.length === 0)
297
+ if (devices.length === 0) {
298
+ this.logEvent({ message: 'connectDevice failure', id, name, address, profile, error: 'device not found' });
299
+ this.scanState.isConnecting = false;
262
300
  throw new Error('device not found');
301
+ }
263
302
  if (devices[0]) {
303
+ this.logEvent({ message: 'connectDevice connecting', id, name, address, profile });
264
304
  const connected = yield devices[0].connect();
305
+ this.scanState.isConnecting = false;
265
306
  if (connected) {
307
+ this.logEvent({ message: 'connectDevice success', id, name, address, profile });
266
308
  return devices[0];
267
309
  }
268
310
  else {
311
+ this.logEvent({ message: 'connectDevice failure', id, name, address, profile });
269
312
  throw new Error('connect failed');
270
313
  }
271
314
  }
272
315
  });
273
316
  }
317
+ waitForScanFinished(timeout) {
318
+ const waitStart = Date.now();
319
+ const waitTimeout = waitStart + timeout;
320
+ return new Promise((resolve, reject) => {
321
+ const waitIv = setInterval(() => {
322
+ if (this.scanState.isScanning && Date.now() > waitTimeout) {
323
+ clearInterval(waitIv);
324
+ return reject(new Error('scanning already in progress'));
325
+ }
326
+ if (!this.scanState.isScanning) {
327
+ clearInterval(waitIv);
328
+ return resolve(true);
329
+ }
330
+ }, 100);
331
+ });
332
+ }
333
+ addPeripheralToCache(peripheral) {
334
+ try {
335
+ this.logEvent({ message: 'adding device to cache', device: { address: peripheral.address, name: peripheral.advertisement ? peripheral.advertisement.localName : '' } });
336
+ const existing = this.deviceCache.find(p => p.address === peripheral.address);
337
+ if (!existing)
338
+ this.deviceCache.push(peripheral);
339
+ else {
340
+ if (peripheral.advertisement && peripheral.advertisement.localName !== '' && existing.advertisement && existing.advertisement.localName === '')
341
+ existing.advertisement.localName = peripheral.advertisement.localName;
342
+ }
343
+ }
344
+ catch (err) {
345
+ console.log('~~~ error', err);
346
+ }
347
+ }
274
348
  scan(props) {
275
349
  return __awaiter(this, void 0, void 0, function* () {
276
350
  const { timeout = 5000, deviceTypes = [], device } = props;
@@ -282,99 +356,175 @@ class BleInterface extends ble_1.BleInterfaceClass {
282
356
  if (!this.isConnected()) {
283
357
  yield this.connect();
284
358
  }
359
+ this.logEvent({ message: 'scan()', props, scanState: this.scanState, cache: this.deviceCache.map(p => ({ name: p.advertisement ? p.advertisement.localName : '', address: p.address })) });
360
+ if (!props.isBackgroundScan && this.scanState.isBackgroundScan) {
361
+ yield this.stopScan();
362
+ this.scanState.isBackgroundScan = false;
363
+ }
285
364
  const detectedPeripherals = {};
365
+ let opStr;
286
366
  if (scanForDevice) {
367
+ opStr = 'search device';
287
368
  const { id, address, name } = device;
288
369
  this.logEvent({ message: 'search device request', device: { id, address, name }, deviceTypes });
289
370
  }
290
- else
371
+ else {
372
+ opStr = 'scan';
291
373
  this.logEvent({ message: 'scan start', services });
292
- return new Promise((resolve, reject) => {
293
- if (this.scanState.isScanning) {
294
- this.logEvent({ message: 'scan result: already scanning' });
295
- return reject(new Error('scanning already in progress'));
374
+ }
375
+ if (this.scanState.isScanning) {
376
+ try {
377
+ yield this.waitForScanFinished(timeout);
296
378
  }
379
+ catch (err) {
380
+ this.logEvent({ message: `${opStr} result: already scanning` });
381
+ return Promise.reject(err);
382
+ }
383
+ }
384
+ return new Promise((resolve, reject) => {
297
385
  this.scanState.isScanning = true;
298
386
  if (scanForDevice && device instanceof ble_1.BleDeviceClass) {
299
- const existing = this.devices.find(i => i.device.address === device.address && i.isConnected);
300
- if (existing) {
301
- device.peripheral = existing.device.peripheral;
302
- this.logEvent({ message: 'scan: device already connected', device: device.name, address: device.address });
303
- return resolve([device]);
387
+ if (this.devices && this.devices.length > 0) {
388
+ const connectedDevices = this.devices.map(i => ({ name: i.device.name, address: i.device.address, isConnected: i.isConnected, connectState: i.device.getConnectState() }));
389
+ const { name, address } = device;
390
+ this.logEvent({ message: `${opStr}: check if already registered`, device: { name, address }, connectedDevices });
391
+ const existing = this.devices.find(i => (i.device.address === device.address || i.device.name === device.name));
392
+ if (existing) {
393
+ const d = device;
394
+ const linkedDevice = existing.device;
395
+ d.peripheral = existing.device.peripheral;
396
+ if (d.setInterface && typeof (d.setInterface) === 'function')
397
+ d.setInterface(this);
398
+ setTimeout(() => {
399
+ let connectState = linkedDevice.getConnectState();
400
+ this.logEvent({ message: `${opStr}: device already registered`, device: device.name, address: device.address, connectState });
401
+ if (connectState.isConnecting) {
402
+ const waitStart = Date.now();
403
+ const waitTimeout = waitStart + timeout;
404
+ const waitIv = setInterval(() => {
405
+ try {
406
+ connectState = linkedDevice.getConnectState();
407
+ if (connectState.isConnecting && Date.now() > waitTimeout) {
408
+ clearInterval(waitIv);
409
+ this.scanState.isScanning = false;
410
+ return resolve([]);
411
+ }
412
+ if (!connectState.isConnecting) {
413
+ clearInterval(waitIv);
414
+ this.scanState.isScanning = false;
415
+ return resolve([device]);
416
+ }
417
+ }
418
+ catch (err) {
419
+ console.log('~~~ error', err);
420
+ }
421
+ }, 100);
422
+ }
423
+ else if (connectState.isConnected) {
424
+ this.scanState.isScanning = false;
425
+ resolve([device]);
426
+ }
427
+ else {
428
+ this.scanState.isScanning = false;
429
+ resolve([]);
430
+ }
431
+ }, 100);
432
+ }
304
433
  }
305
434
  }
306
- bleBinding.startScanning([], true, (err) => {
307
- if (err) {
308
- this.logEvent({ message: 'scan result: error', error: err.message });
309
- this.scanState.isScanning = false;
310
- return reject(err);
311
- }
312
- bleBinding.on('discover', (peripheral) => {
313
- if (!peripheral || !peripheral.advertisement)
314
- return;
315
- if (!detectedPeripherals[peripheral.id]) {
316
- if (process.env.BLE_DEBUG)
317
- console.log('discovered', peripheral);
318
- detectedPeripherals[peripheral.id] = peripheral;
319
- let DeviceClasses;
320
- if (scanForDevice && (!deviceTypes || deviceTypes.length === 0)) {
321
- const classes = BleInterface.deviceClasses.map(c => c.Class);
322
- DeviceClasses = this.getDevicesFromServices(classes, peripheral.advertisement.serviceUuids);
435
+ const onPeripheralFound = (peripheral, fromCache = false) => {
436
+ if (fromCache)
437
+ this.logEvent({ message: 'adding from Cache', peripheral: peripheral.address });
438
+ if (!peripheral || !peripheral.advertisement)
439
+ return;
440
+ if (!detectedPeripherals[peripheral.id]) {
441
+ if (process.env.BLE_DEBUG)
442
+ console.log('discovered', peripheral);
443
+ detectedPeripherals[peripheral.id] = peripheral;
444
+ this.addPeripheralToCache(peripheral);
445
+ let DeviceClasses;
446
+ if (scanForDevice && (!deviceTypes || deviceTypes.length === 0)) {
447
+ const classes = BleInterface.deviceClasses.map(c => c.Class);
448
+ DeviceClasses = this.getDevicesFromServices(classes, peripheral.advertisement.serviceUuids);
449
+ }
450
+ else {
451
+ DeviceClasses = this.getDevicesFromServices(deviceTypes, peripheral.advertisement.serviceUuids);
452
+ }
453
+ DeviceClasses.forEach(DeviceClass => {
454
+ let cntFound = 0;
455
+ if (!DeviceClass)
456
+ return;
457
+ if (scanForDevice && cntFound > 0)
458
+ return;
459
+ const C = DeviceClass;
460
+ const d = new C({ peripheral });
461
+ if (device && device.getProfile && device.getProfile() !== d.getProfile())
462
+ return;
463
+ d.setInterface(this);
464
+ if (scanForDevice) {
465
+ if ((device.id && device.id !== '' && d.id === device.id) ||
466
+ (device.address && device.address !== '' && d.address === device.address) ||
467
+ (device.name && device.name !== '' && d.name === device.name))
468
+ cntFound++;
323
469
  }
324
- else {
325
- DeviceClasses = this.getDevicesFromServices(deviceTypes, peripheral.advertisement.serviceUuids);
470
+ else
471
+ cntFound++;
472
+ const existing = this.devices.find(i => i.device.id === d.id && i.device.getProfile() === d.getProfile());
473
+ if (!scanForDevice && cntFound > 0 && !existing) {
474
+ this.logEvent({ message: `${opStr}: device found`, device: d.name, address: d.address, services: d.services.join(',') });
475
+ this.devices.push({ device: d, isConnected: false });
476
+ this.emit('device', d);
326
477
  }
327
- let cntFound = 0;
328
- DeviceClasses.forEach(DeviceClass => {
329
- if (!DeviceClass)
478
+ if (scanForDevice && cntFound > 0) {
479
+ if (fromCache) {
480
+ resolve([d]);
330
481
  return;
331
- if (scanForDevice && cntFound > 0)
332
- return;
333
- const C = DeviceClass;
334
- const d = new C({ peripheral });
335
- if (device.getProfile && device.getProfile() !== d.getProfile())
336
- return;
337
- d.setInterface(this);
338
- if (scanForDevice) {
339
- if ((device.id && device.id !== '' && d.id === device.id) ||
340
- (device.address && device.address !== '' && d.address === device.address) ||
341
- (device.name && device.name !== '' && d.name === device.name))
342
- cntFound++;
343
482
  }
344
- else
345
- cntFound++;
346
- const existing = this.devices.find(i => i.device.id === d.id && i.device.getProfile() === d.getProfile());
347
- if (cntFound > 0 && !existing) {
348
- this.logEvent({ message: 'scan: device found', device: d.name, address: d.address, services: d.services.join(',') });
349
- this.devices.push({ device: d, isConnected: false });
350
- this.emit('device', d);
351
- }
352
- if (scanForDevice && cntFound > 0) {
353
- if (this.scanState.timeout) {
354
- clearTimeout(this.scanState.timeout);
355
- this.scanState.timeout = null;
356
- bleBinding.stopScanning(() => {
357
- this.getBinding().removeAllListeners('discover');
358
- this.scanState.isScanning = false;
359
- resolve([d]);
360
- });
361
- }
362
- else {
483
+ if (this.scanState.timeout) {
484
+ clearTimeout(this.scanState.timeout);
485
+ this.scanState.timeout = null;
486
+ this.logEvent({ message: `${opStr}: stop scanning`, requested: scanForDevice ? { name: device.name, address: device.address } : undefined, });
487
+ bleBinding.stopScanning(() => {
488
+ this.getBinding().removeAllListeners('discover');
489
+ this.scanState.isScanning = false;
363
490
  resolve([d]);
364
- }
491
+ });
365
492
  }
366
- });
367
- }
368
- else {
369
- }
493
+ else {
494
+ resolve([d]);
495
+ }
496
+ }
497
+ });
498
+ }
499
+ else {
500
+ }
501
+ };
502
+ this.logEvent({ message: `${opStr}: start scanning`, requested: scanForDevice ? { name: device.name, address: device.address } : undefined, timeout });
503
+ this.deviceCache.forEach(peripheral => {
504
+ onPeripheralFound(peripheral, true);
505
+ });
506
+ bleBinding.startScanning([], true, (err) => {
507
+ if (err) {
508
+ this.logEvent({ message: `${opStr} result: error`, requested: scanForDevice ? { name: device.name, address: device.address } : undefined, error: err.message });
509
+ this.scanState.isScanning = false;
510
+ return reject(err);
511
+ }
512
+ bleBinding.on('discover', (p) => {
513
+ console.log('~~~ discovered:', p.address, p.advertisement ? p.advertisement.localName : '');
514
+ onPeripheralFound(p);
370
515
  });
371
516
  });
372
517
  this.scanState.timeout = setTimeout(() => {
373
518
  this.scanState.timeout = null;
374
- this.logEvent({ message: 'scan result: devices found', devices: this.devices.map(i => i.device.name + (!i.device.name || i.device.name === '') ? `addr=${i.device.address}` : '') });
519
+ this.logEvent({ message: `${opStr} result: devices found`, requested: scanForDevice ? { name: device.name, address: device.address } : undefined, devices: this.devices.map(i => i.device.name + (!i.device.name || i.device.name === '') ? `addr=${i.device.address}` : '') });
375
520
  this.getBinding().removeAllListeners('discover');
521
+ this.logEvent({ message: `${opStr}: stop scanning`, requested: scanForDevice ? { name: device.name, address: device.address } : undefined, });
376
522
  bleBinding.stopScanning(() => {
377
523
  this.scanState.isScanning = false;
524
+ if (scanForDevice) {
525
+ reject(new Error('device not found'));
526
+ return;
527
+ }
378
528
  resolve(this.devices.map(i => i.device));
379
529
  });
380
530
  }, timeout);
package/lib/ble/ble.d.ts CHANGED
@@ -8,12 +8,20 @@ export interface BleDeviceIdentifier {
8
8
  address?: string;
9
9
  name?: string;
10
10
  }
11
+ export interface ConnectState {
12
+ isConnecting: boolean;
13
+ isConnected: boolean;
14
+ isDisconnecting: boolean;
15
+ }
11
16
  export declare abstract class BleDeviceClass extends EventEmitter {
12
17
  static services: string[];
13
18
  id?: string;
14
19
  address?: string;
15
20
  name?: string;
16
21
  peripheral?: BlePeripheral;
22
+ connectState: ConnectState;
23
+ getConnectState(): ConnectState;
24
+ isConnected(): boolean;
17
25
  abstract getProfile(): string;
18
26
  abstract getServiceUUids(): string[];
19
27
  abstract connect(props?: ConnectProps): Promise<boolean>;
@@ -29,6 +37,7 @@ export declare type ScanProps = {
29
37
  timeout?: number;
30
38
  deviceTypes?: (typeof BleDeviceClass)[];
31
39
  device?: BleDeviceClass;
40
+ isBackgroundScan?: boolean;
32
41
  };
33
42
  export declare class BleBindingWrapper {
34
43
  protected binding: BleBinding;
package/lib/ble/ble.js CHANGED
@@ -5,6 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const events_1 = __importDefault(require("events"));
7
7
  class BleDeviceClass extends events_1.default {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.connectState = { isConnecting: false, isConnected: false, isDisconnecting: false };
11
+ }
12
+ getConnectState() {
13
+ return this.connectState;
14
+ }
15
+ isConnected() {
16
+ return this.connectState.isConnected;
17
+ }
8
18
  }
9
19
  exports.BleDeviceClass = BleDeviceClass;
10
20
  BleDeviceClass.services = [];
@@ -0,0 +1,78 @@
1
+ /// <reference types="node" />
2
+ import { BleDevice } from './ble-device';
3
+ import BleInterface from './ble-interface';
4
+ import BleProtocol from './incyclist-protocol';
5
+ import { BleDeviceClass } from './ble';
6
+ import DeviceAdapter, { DeviceData } from '../Device';
7
+ import { DeviceProtocol } from '../DeviceProtocol';
8
+ import { EventLogger } from 'gd-eventlog';
9
+ import CyclingMode from '../CyclingMode';
10
+ import { IncyclistBikeData } from '../CyclingMode';
11
+ declare type PowerData = {
12
+ instantaneousPower?: number;
13
+ balance?: number;
14
+ accTorque?: number;
15
+ time: number;
16
+ rpm: number;
17
+ raw?: string;
18
+ };
19
+ declare type IndoorBikeData = {
20
+ speed?: number;
21
+ averageSpeed?: number;
22
+ cadence?: number;
23
+ averageCadence?: number;
24
+ totalDistance?: number;
25
+ resistanceLevel?: number;
26
+ instantaneousPower?: number;
27
+ averagePower?: number;
28
+ expendedEnergy?: number;
29
+ heartrate?: number;
30
+ metabolicEquivalent?: number;
31
+ time?: number;
32
+ remainingTime?: number;
33
+ raw?: string;
34
+ };
35
+ export default class BleFitnessMachineDevice extends BleDevice {
36
+ static services: string[];
37
+ static characteristics: string[];
38
+ data: IndoorBikeData;
39
+ constructor(props?: any);
40
+ getProfile(): string;
41
+ getServiceUUids(): string[];
42
+ parseIndoorBikeData(_data: Uint8Array): IndoorBikeData;
43
+ onData(characteristic: string, data: Buffer): void;
44
+ write(characteristic: any, data: any): Promise<boolean>;
45
+ read(characteristic: any): Promise<Buffer>;
46
+ reset(): void;
47
+ }
48
+ export declare class FmAdapter extends DeviceAdapter {
49
+ device: BleFitnessMachineDevice;
50
+ ignore: boolean;
51
+ ble: BleInterface;
52
+ protocol: DeviceProtocol;
53
+ paused: boolean;
54
+ logger: EventLogger;
55
+ mode: CyclingMode;
56
+ distanceInternal: number;
57
+ prevDataTS: number;
58
+ constructor(device: BleDeviceClass, protocol: BleProtocol);
59
+ isBike(): boolean;
60
+ isHrm(): boolean;
61
+ isPower(): boolean;
62
+ getProfile(): string;
63
+ getName(): string;
64
+ getDisplayName(): string;
65
+ getCyclingMode(): CyclingMode;
66
+ getDefaultCyclingMode(): CyclingMode;
67
+ getPort(): string;
68
+ setIgnoreBike(ignore: any): void;
69
+ setIgnorePower(ignore: any): void;
70
+ onDeviceData(deviceData: PowerData): void;
71
+ mapData(deviceData: IndoorBikeData): IncyclistBikeData;
72
+ transformData(bikeData: IncyclistBikeData): DeviceData;
73
+ start(props?: any): Promise<any>;
74
+ stop(): Promise<boolean>;
75
+ pause(): Promise<boolean>;
76
+ resume(): Promise<boolean>;
77
+ }
78
+ export {};
package/lib/ble/fm.js ADDED
@@ -0,0 +1,248 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const ble_device_1 = require("./ble-device");
16
+ const ble_interface_1 = __importDefault(require("./ble-interface"));
17
+ const Device_1 = __importDefault(require("../Device"));
18
+ const gd_eventlog_1 = require("gd-eventlog");
19
+ const power_meter_1 = __importDefault(require("../modes/power-meter"));
20
+ const bit = (nr) => (1 << nr);
21
+ const IndoorBikeDataFlag = {
22
+ MoreData: bit(0),
23
+ AverageSpeedPresent: bit(1),
24
+ InstantaneousCadence: bit(2),
25
+ AverageCadencePresent: bit(3),
26
+ TotalDistancePresent: bit(4),
27
+ ResistanceLevelPresent: bit(5),
28
+ InstantaneousPowerPresent: bit(6),
29
+ AveragePowerPresent: bit(7),
30
+ ExpendedEnergyPresent: bit(8),
31
+ HeartRatePresent: bit(9),
32
+ MetabolicEquivalentPresent: bit(10),
33
+ ElapsedTimePresent: bit(11),
34
+ RemainingTimePresent: bit(12)
35
+ };
36
+ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
37
+ constructor(props) {
38
+ super(props);
39
+ this.data = {};
40
+ }
41
+ getProfile() {
42
+ return 'Smart Trainer';
43
+ }
44
+ getServiceUUids() {
45
+ return BleFitnessMachineDevice.services;
46
+ }
47
+ parseIndoorBikeData(_data) {
48
+ const data = Buffer.from(_data);
49
+ const flags = data.readUInt16LE(0);
50
+ let offset = 2;
51
+ if ((flags & IndoorBikeDataFlag.MoreData) === 0) {
52
+ this.data.speed = data.readUInt16LE(offset) / 100;
53
+ offset += 2;
54
+ }
55
+ if (flags & IndoorBikeDataFlag.AverageSpeedPresent) {
56
+ this.data.averageSpeed = data.readUInt16LE(offset);
57
+ offset += 2;
58
+ }
59
+ if (flags & IndoorBikeDataFlag.InstantaneousCadence) {
60
+ this.data.cadence = data.readUInt16LE(offset) / 2;
61
+ offset += 2;
62
+ }
63
+ if (flags & IndoorBikeDataFlag.AverageCadencePresent) {
64
+ this.data.averageCadence = data.readUInt16LE(offset);
65
+ offset += 2;
66
+ }
67
+ if (flags & IndoorBikeDataFlag.TotalDistancePresent) {
68
+ this.data.totalDistance = data.readUInt16LE(offset);
69
+ offset += 2;
70
+ }
71
+ if (flags & IndoorBikeDataFlag.ResistanceLevelPresent) {
72
+ this.data.resistanceLevel = data.readUInt16LE(offset);
73
+ offset += 2;
74
+ }
75
+ if (flags & IndoorBikeDataFlag.InstantaneousPowerPresent) {
76
+ this.data.instantaneousPower = data.readUInt16LE(offset);
77
+ offset += 2;
78
+ }
79
+ if (flags & IndoorBikeDataFlag.AveragePowerPresent) {
80
+ this.data.averagePower = data.readUInt16LE(offset);
81
+ offset += 2;
82
+ }
83
+ if (flags & IndoorBikeDataFlag.ExpendedEnergyPresent) {
84
+ this.data.expendedEnergy = data.readUInt16LE(offset);
85
+ offset += 2;
86
+ }
87
+ if (flags & IndoorBikeDataFlag.HeartRatePresent) {
88
+ this.data.heartrate = data.readUInt16LE(offset);
89
+ offset += 2;
90
+ }
91
+ if (flags & IndoorBikeDataFlag.MetabolicEquivalentPresent) {
92
+ this.data.metabolicEquivalent = data.readUInt16LE(offset);
93
+ offset += 2;
94
+ }
95
+ if (flags & IndoorBikeDataFlag.ElapsedTimePresent) {
96
+ this.data.time = data.readUInt16LE(offset);
97
+ offset += 2;
98
+ }
99
+ if (flags & IndoorBikeDataFlag.RemainingTimePresent) {
100
+ this.data.remainingTime = data.readUInt16LE(offset);
101
+ offset += 2;
102
+ }
103
+ return Object.assign(Object.assign({}, this.data), { raw: data.toString('hex') });
104
+ }
105
+ onData(characteristic, data) {
106
+ console.log(characteristic.toLocaleLowerCase(), data);
107
+ if (characteristic.toLocaleLowerCase() === '2ad2') {
108
+ const res = this.parseIndoorBikeData(data);
109
+ this.emit('data', res);
110
+ }
111
+ }
112
+ write(characteristic, data) {
113
+ console.log('write', characteristic, data);
114
+ return Promise.resolve(true);
115
+ }
116
+ read(characteristic) {
117
+ console.log('read', characteristic);
118
+ return Promise.resolve(Buffer.from([]));
119
+ }
120
+ reset() {
121
+ this.data = {};
122
+ }
123
+ }
124
+ exports.default = BleFitnessMachineDevice;
125
+ BleFitnessMachineDevice.services = ['1826'];
126
+ BleFitnessMachineDevice.characteristics = ['2acc', '2ad2', '2ad6', '2ad8', '2ad9', '2ada'];
127
+ ble_interface_1.default.register('BleFitnessMachineDevice', 'fm', BleFitnessMachineDevice, BleFitnessMachineDevice.services);
128
+ class FmAdapter extends Device_1.default {
129
+ constructor(device, protocol) {
130
+ super(protocol);
131
+ this.ignore = false;
132
+ this.paused = false;
133
+ this.distanceInternal = 0;
134
+ this.device = device;
135
+ this.ble = protocol.ble;
136
+ this.mode = this.getDefaultCyclingMode();
137
+ this.logger = new gd_eventlog_1.EventLogger('BLE-FM');
138
+ }
139
+ isBike() { return true; }
140
+ isHrm() { return false; }
141
+ isPower() { return true; }
142
+ getProfile() {
143
+ return 'Power Meter';
144
+ }
145
+ getName() {
146
+ return `${this.device.name}`;
147
+ }
148
+ getDisplayName() {
149
+ return this.getName();
150
+ }
151
+ getCyclingMode() {
152
+ if (!this.mode)
153
+ this.mode = this.getDefaultCyclingMode();
154
+ return this.mode;
155
+ }
156
+ getDefaultCyclingMode() {
157
+ return new power_meter_1.default(this);
158
+ }
159
+ getPort() {
160
+ return 'ble';
161
+ }
162
+ setIgnoreBike(ignore) {
163
+ this.ignore = ignore;
164
+ }
165
+ setIgnorePower(ignore) {
166
+ this.ignore = ignore;
167
+ }
168
+ onDeviceData(deviceData) {
169
+ if (this.prevDataTS && Date.now() - this.prevDataTS < 1000)
170
+ return;
171
+ this.prevDataTS = Date.now();
172
+ this.logger.logEvent({ message: 'onDeviceData', data: deviceData });
173
+ let incyclistData = this.mapData(deviceData);
174
+ incyclistData = this.getCyclingMode().updateData(incyclistData);
175
+ const data = this.transformData(incyclistData);
176
+ if (this.onDataFn && !this.ignore && !this.paused)
177
+ this.onDataFn(data);
178
+ }
179
+ mapData(deviceData) {
180
+ const data = {
181
+ isPedalling: false,
182
+ power: 0,
183
+ pedalRpm: undefined,
184
+ speed: 0,
185
+ heartrate: 0,
186
+ distanceInternal: 0,
187
+ slope: undefined,
188
+ time: undefined
189
+ };
190
+ data.power = (deviceData.instantaneousPower !== undefined ? deviceData.instantaneousPower : data.power);
191
+ data.pedalRpm = (deviceData.cadence !== undefined ? deviceData.cadence : data.pedalRpm);
192
+ data.time = (deviceData.time !== undefined ? deviceData.time : data.time);
193
+ data.isPedalling = data.pedalRpm > 0 || (data.pedalRpm === undefined && data.power > 0);
194
+ return data;
195
+ }
196
+ transformData(bikeData) {
197
+ if (this.ignore) {
198
+ return {};
199
+ }
200
+ if (bikeData === undefined)
201
+ return;
202
+ let distance = 0;
203
+ if (this.distanceInternal !== undefined && bikeData.distanceInternal !== undefined) {
204
+ distance = Math.round(bikeData.distanceInternal - this.distanceInternal);
205
+ }
206
+ if (bikeData.distanceInternal !== undefined)
207
+ this.distanceInternal = bikeData.distanceInternal;
208
+ let data = {
209
+ speed: bikeData.speed,
210
+ slope: bikeData.slope,
211
+ power: bikeData.power !== undefined ? Math.round(bikeData.power) : undefined,
212
+ cadence: bikeData.pedalRpm !== undefined ? Math.round(bikeData.pedalRpm) : undefined,
213
+ distance,
214
+ timestamp: Date.now()
215
+ };
216
+ return data;
217
+ }
218
+ start(props) {
219
+ return __awaiter(this, void 0, void 0, function* () {
220
+ this.logger.logEvent({ message: 'start requested', profile: this.getProfile(), props });
221
+ try {
222
+ const bleDevice = yield this.ble.connectDevice(this.device);
223
+ if (bleDevice) {
224
+ this.device = bleDevice;
225
+ bleDevice.on('data', (data) => {
226
+ this.onDeviceData(data);
227
+ });
228
+ return true;
229
+ }
230
+ }
231
+ catch (err) {
232
+ this.logger.logEvent({ message: 'start result: error', error: err.message, profile: this.getProfile() });
233
+ throw new Error(`could not start device, reason:${err.message}`);
234
+ }
235
+ });
236
+ }
237
+ stop() {
238
+ return __awaiter(this, void 0, void 0, function* () {
239
+ this.logger.logEvent({ message: 'stop requested', profile: this.getProfile() });
240
+ this.distanceInternal = 0;
241
+ this.device.reset();
242
+ return this.device.disconnect();
243
+ });
244
+ }
245
+ pause() { this.paused = true; return Promise.resolve(true); }
246
+ resume() { this.paused = false; return Promise.resolve(true); }
247
+ }
248
+ exports.FmAdapter = FmAdapter;
package/lib/ble/hrm.js CHANGED
@@ -99,7 +99,7 @@ class HrmAdapter extends Device_1.default {
99
99
  }
100
100
  start(props) {
101
101
  return __awaiter(this, void 0, void 0, function* () {
102
- this.logger.logEvent({ message: 'start requested', props });
102
+ this.logger.logEvent({ message: 'start requested', profile: this.getProfile(), props });
103
103
  try {
104
104
  const bleDevice = yield this.ble.connectDevice(this.device);
105
105
  if (bleDevice) {
@@ -112,13 +112,14 @@ class HrmAdapter extends Device_1.default {
112
112
  }
113
113
  }
114
114
  catch (err) {
115
- this.logger.logEvent({ message: 'start result: error', error: err.message });
115
+ this.logger.logEvent({ message: 'start result: error', error: err.message, profile: this.getProfile() });
116
116
  throw new Error(`could not start device, reason:${err.message}`);
117
117
  }
118
118
  });
119
119
  }
120
120
  stop() {
121
121
  return __awaiter(this, void 0, void 0, function* () {
122
+ this.logger.logEvent({ message: 'stop requested', profile: this.getProfile() });
122
123
  return this.device.disconnect();
123
124
  });
124
125
  }
@@ -24,9 +24,10 @@ const DeviceProtocol_1 = __importStar(require("../DeviceProtocol"));
24
24
  const DeviceRegistry_1 = __importDefault(require("../DeviceRegistry"));
25
25
  const ble_1 = require("./ble");
26
26
  const ble_interface_1 = __importDefault(require("./ble-interface"));
27
+ const fm_1 = __importStar(require("./fm"));
27
28
  const hrm_1 = __importStar(require("./hrm"));
28
29
  const pwr_1 = __importStar(require("./pwr"));
29
- const supportedDeviceTypes = [hrm_1.default, pwr_1.default];
30
+ const supportedDeviceTypes = [hrm_1.default, pwr_1.default, fm_1.default];
30
31
  class BleProtocol extends DeviceProtocol_1.default {
31
32
  constructor(binding) {
32
33
  super();
@@ -58,6 +59,10 @@ class BleProtocol extends DeviceProtocol_1.default {
58
59
  case 'hr':
59
60
  case 'heartrate monitor':
60
61
  return new hrm_1.HrmAdapter(fromDevice ? bleDevice : new hrm_1.default(props()), this);
62
+ case 'fm':
63
+ case 'smart trainer':
64
+ case 'fitness machine':
65
+ return new fm_1.FmAdapter(fromDevice ? bleDevice : new fm_1.default(props()), this);
61
66
  case 'cp':
62
67
  case 'power meter':
63
68
  return new pwr_1.PwrAdapter(fromDevice ? bleDevice : new pwr_1.default(props()), this);
package/lib/ble/pwr.d.ts CHANGED
@@ -54,6 +54,7 @@ export declare class PwrAdapter extends DeviceAdapter {
54
54
  logger: EventLogger;
55
55
  mode: CyclingMode;
56
56
  distanceInternal: number;
57
+ prevDataTS: number;
57
58
  constructor(device: BleDeviceClass, protocol: BleProtocol);
58
59
  isBike(): boolean;
59
60
  isHrm(): boolean;
package/lib/ble/pwr.js CHANGED
@@ -30,7 +30,7 @@ class BleCyclingPowerDevice extends ble_device_1.BleDevice {
30
30
  this.prevCrankData = undefined;
31
31
  }
32
32
  getProfile() {
33
- return 'cp';
33
+ return 'Power Meter';
34
34
  }
35
35
  getServiceUUids() {
36
36
  return BleCyclingPowerDevice.services;
@@ -166,6 +166,9 @@ class PwrAdapter extends Device_1.default {
166
166
  this.ignore = ignore;
167
167
  }
168
168
  onDeviceData(deviceData) {
169
+ if (this.prevDataTS && Date.now() - this.prevDataTS < 1000)
170
+ return;
171
+ this.prevDataTS = Date.now();
169
172
  this.logger.logEvent({ message: 'onDeviceData', data: deviceData });
170
173
  let incyclistData = this.mapData(deviceData);
171
174
  incyclistData = this.getCyclingMode().updateData(incyclistData);
@@ -214,7 +217,7 @@ class PwrAdapter extends Device_1.default {
214
217
  }
215
218
  start(props) {
216
219
  return __awaiter(this, void 0, void 0, function* () {
217
- this.logger.logEvent({ message: 'start requested', props });
220
+ this.logger.logEvent({ message: 'start requested', profile: this.getProfile(), props });
218
221
  try {
219
222
  const bleDevice = yield this.ble.connectDevice(this.device);
220
223
  if (bleDevice) {
@@ -226,13 +229,14 @@ class PwrAdapter extends Device_1.default {
226
229
  }
227
230
  }
228
231
  catch (err) {
229
- this.logger.logEvent({ message: 'start result: error', error: err.message });
232
+ this.logger.logEvent({ message: 'start result: error', error: err.message, profile: this.getProfile() });
230
233
  throw new Error(`could not start device, reason:${err.message}`);
231
234
  }
232
235
  });
233
236
  }
234
237
  stop() {
235
238
  return __awaiter(this, void 0, void 0, function* () {
239
+ this.logger.logEvent({ message: 'stop requested', profile: this.getProfile() });
236
240
  this.distanceInternal = 0;
237
241
  this.device.reset();
238
242
  return this.device.disconnect();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "1.4.37",
3
+ "version": "1.4.40",
4
4
  "dependencies": {
5
5
  "@serialport/parser-byte-length": "^9.0.1",
6
6
  "@serialport/parser-delimiter": "^9.0.1",