incyclist-devices 1.4.36 → 1.4.39

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.
@@ -1,11 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { EventLogger } from "gd-eventlog";
3
- import { BleInterfaceClass, BleDeviceClass, BlePeripheral, BleCharacteristic, BleDeviceProps, ConnectProps } from "./ble";
4
- interface ConnectState {
5
- isConnecting: boolean;
6
- isConnected: boolean;
7
- isDisconnecting: boolean;
8
- }
3
+ import { BleInterfaceClass, BleDeviceClass, BlePeripheral, BleDeviceProps, ConnectProps } from "./ble";
9
4
  interface BleDeviceConstructProps extends BleDeviceProps {
10
5
  log?: boolean;
11
6
  logger?: EventLogger;
@@ -17,15 +12,15 @@ export declare abstract class BleDevice extends BleDeviceClass {
17
12
  services: string[];
18
13
  ble: BleInterfaceClass;
19
14
  peripheral?: BlePeripheral;
20
- characteristics: BleCharacteristic[];
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,42 +67,96 @@ 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* () {
73
94
  this.connectState.isConnecting = true;
74
- try {
75
- yield peripheral.connectAsync();
76
- }
77
- catch (err) {
78
- this.logEvent({ message: 'cannot connect', error: err.message || err });
95
+ const connected = this.ble.findConnected(peripheral);
96
+ if (!connected) {
97
+ try {
98
+ yield peripheral.connectAsync();
99
+ }
100
+ catch (err) {
101
+ this.logEvent({ message: 'cannot connect', error: err.message || err });
102
+ }
79
103
  }
80
- this.connectState.isConnecting = false;
81
- this.connectState.isConnected = true;
82
- this.state = "connected";
83
- this.emit('connected');
84
- this.cleanupListeners();
85
- this.ble.addConnectedDevice(this);
86
- this.peripheral.once('disconnect', () => { this.onDisconnect(); });
87
104
  try {
88
- const { characteristics } = yield peripheral.discoverSomeServicesAndCharacteristicsAsync(this.services || [], []);
89
- characteristics.forEach(c => {
105
+ this.cleanupListeners();
106
+ if (!connected) {
107
+ this.logEvent({ message: 'connect: discover characteristics start' });
108
+ const res = yield peripheral.discoverSomeServicesAndCharacteristicsAsync(this.services || [], []);
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
+ });
113
+ this.characteristics = characteristics;
114
+ }
115
+ else {
116
+ this.characteristics = connected.characteristics;
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(); });
124
+ this.characteristics.forEach(c => {
90
125
  if (c.properties.find(p => p === 'notify')) {
91
126
  c.on('data', (data, _isNotification) => {
92
127
  this.onData(ble_1.uuid(c.uuid), data);
93
128
  });
94
- c.subscribe((err) => {
95
- if (err)
96
- this.logEvent({ message: 'cannot subscribe', error: err.message || err });
97
- });
129
+ if (!connected) {
130
+ this.logEvent({ message: 'subscribe', device: this.name, address: this.address, service: c._serviceUuid, characteristic: c.uuid });
131
+ c.subscribe((err) => {
132
+ if (err)
133
+ this.logEvent({ message: 'cannot subscribe', device: this.name, address: this.address, service: c._serviceUuid, characteristic: c.uuid, error: err.message || err });
134
+ });
135
+ }
98
136
  }
99
137
  });
100
138
  }
101
139
  catch (err) {
102
140
  this.logEvent({ message: 'cannot connect', error: err.message || err });
141
+ this.connectState.isConnecting = false;
142
+ this.connectState.isConnected = false;
103
143
  }
104
144
  });
105
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;
106
160
  if (this.peripheral) {
107
161
  const { id, address, advertisement } = this.peripheral;
108
162
  const name = advertisement === null || advertisement === void 0 ? void 0 : advertisement.localName;
@@ -113,8 +167,8 @@ class BleDevice extends ble_1.BleDeviceClass {
113
167
  }
114
168
  else {
115
169
  const { id, name, address } = this;
170
+ let error;
116
171
  if (this.address || this.id || this.name) {
117
- this.connectState.isConnecting = true;
118
172
  this.logEvent({ message: 'connect requested', mode: 'device', device: { id, name, address } });
119
173
  try {
120
174
  if (this.ble.isScanning()) {
@@ -129,9 +183,11 @@ class BleDevice extends ble_1.BleDeviceClass {
129
183
  }
130
184
  }
131
185
  catch (err) {
186
+ console.log('~~~ error', err);
187
+ error = err;
132
188
  }
133
189
  }
134
- 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 });
135
191
  this.connectState.isConnecting = false;
136
192
  this.connectState.isConnected = false;
137
193
  return false;
@@ -1,8 +1,10 @@
1
1
  /// <reference types="node" />
2
2
  import { EventLogger } from 'gd-eventlog';
3
- import { BleInterfaceClass, ConnectProps, ScanProps, BleDeviceClass, BleBinding } from './ble';
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,10 +51,14 @@ 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;
55
61
  addConnectedDevice(device: BleDeviceClass): void;
62
+ findConnected(device: BleDeviceClass | BlePeripheral): BleDeviceClass;
56
63
  removeConnectedDevice(device: BleDeviceClass): void;
57
64
  }
@@ -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,84 +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 = {};
286
- this.devices = [];
365
+ let opStr;
287
366
  if (scanForDevice) {
367
+ opStr = 'search device';
288
368
  const { id, address, name } = device;
289
369
  this.logEvent({ message: 'search device request', device: { id, address, name }, deviceTypes });
290
370
  }
291
- else
371
+ else {
372
+ opStr = 'scan';
292
373
  this.logEvent({ message: 'scan start', services });
293
- return new Promise((resolve, reject) => {
294
- if (this.scanState.isScanning) {
295
- this.logEvent({ message: 'scan result: already scanning' });
296
- return reject(new Error('scanning already in progress'));
374
+ }
375
+ if (this.scanState.isScanning) {
376
+ try {
377
+ yield this.waitForScanFinished(timeout);
378
+ }
379
+ catch (err) {
380
+ this.logEvent({ message: `${opStr} result: already scanning` });
381
+ return Promise.reject(err);
297
382
  }
383
+ }
384
+ return new Promise((resolve, reject) => {
298
385
  this.scanState.isScanning = true;
299
- bleBinding.startScanning(services, true, (err) => {
300
- if (err) {
301
- this.logEvent({ message: 'scan result: error', error: err.message });
302
- this.scanState.isScanning = false;
303
- return reject(err);
386
+ if (scanForDevice && device instanceof ble_1.BleDeviceClass) {
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
- bleBinding.on('discover', (peripheral) => {
306
- if (!peripheral || !peripheral.advertisement)
307
- return;
308
- if (!detectedPeripherals[peripheral.id]) {
309
- detectedPeripherals[peripheral.id] = peripheral;
310
- let DeviceClasses;
311
- if (scanForDevice && (!deviceTypes || deviceTypes.length === 0)) {
312
- const classes = BleInterface.deviceClasses.map(c => c.Class);
313
- DeviceClasses = this.getDevicesFromServices(classes, peripheral.advertisement.serviceUuids);
434
+ }
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++;
314
469
  }
315
- else {
316
- 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);
317
477
  }
318
- let cntFound = 0;
319
- DeviceClasses.forEach(DeviceClass => {
320
- if (!DeviceClass)
478
+ if (scanForDevice && cntFound > 0) {
479
+ if (fromCache) {
480
+ resolve([d]);
321
481
  return;
322
- if (scanForDevice && cntFound > 0)
323
- return;
324
- const C = DeviceClass;
325
- const d = new C({ peripheral });
326
- d.setInterface(this);
327
- if (scanForDevice) {
328
- if ((device.id && device.id !== '' && d.id === device.id) ||
329
- (device.address && device.address !== '' && d.address === device.address) ||
330
- (device.name && device.name !== '' && d.name === device.name))
331
- cntFound++;
332
482
  }
333
- else
334
- cntFound++;
335
- const existing = this.devices.find(i => i.device.id === d.id);
336
- if (cntFound > 0 && !existing) {
337
- this.logEvent({ message: 'scan: device found', device: d.name, address: d.address, services: d.services.join(',') });
338
- this.devices.push({ device: d, isConnected: false });
339
- this.emit('device', d);
340
- }
341
- if (scanForDevice && cntFound > 0) {
342
- if (this.scanState.timeout) {
343
- clearTimeout(this.scanState.timeout);
344
- this.scanState.timeout = null;
345
- bleBinding.stopScanning(() => {
346
- this.scanState.isScanning = false;
347
- resolve([d]);
348
- });
349
- }
350
- 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;
351
490
  resolve([d]);
352
- }
491
+ });
353
492
  }
354
- });
355
- }
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);
356
515
  });
357
516
  });
358
517
  this.scanState.timeout = setTimeout(() => {
359
518
  this.scanState.timeout = null;
360
- 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}` : '') });
520
+ this.getBinding().removeAllListeners('discover');
521
+ this.logEvent({ message: `${opStr}: stop scanning`, requested: scanForDevice ? { name: device.name, address: device.address } : undefined, });
361
522
  bleBinding.stopScanning(() => {
362
523
  this.scanState.isScanning = false;
524
+ if (scanForDevice) {
525
+ reject(new Error('device not found'));
526
+ return;
527
+ }
363
528
  resolve(this.devices.map(i => i.device));
364
529
  });
365
530
  }, timeout);
@@ -372,6 +537,7 @@ class BleInterface extends ble_1.BleInterfaceClass {
372
537
  }
373
538
  if (!this.getBinding())
374
539
  return Promise.reject(new Error('no binding defined'));
540
+ this.getBinding().removeAllListeners('discover');
375
541
  this.logEvent({ message: 'scan stop request' });
376
542
  return new Promise(resolve => {
377
543
  this.getBinding().stopScanning(() => {
@@ -392,6 +558,12 @@ class BleInterface extends ble_1.BleInterfaceClass {
392
558
  }
393
559
  this.devices.push({ device, isConnected: true });
394
560
  }
561
+ findConnected(device) {
562
+ const connected = this.devices.find(i => i.device.id === device.id && i.isConnected);
563
+ if (connected)
564
+ return connected.device;
565
+ return undefined;
566
+ }
395
567
  removeConnectedDevice(device) {
396
568
  const existigDevice = this.devices.find(i => i.device.id === device.id);
397
569
  if (existigDevice) {
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;
@@ -51,6 +60,7 @@ export declare abstract class BleInterfaceClass extends EventEmitter {
51
60
  abstract isScanning(): boolean;
52
61
  abstract addConnectedDevice(device: BleDeviceClass): void;
53
62
  abstract removeConnectedDevice(device: BleDeviceClass): void;
63
+ abstract findConnected(device: BleDeviceClass | BlePeripheral): BleDeviceClass;
54
64
  getBinding(): BleBinding;
55
65
  setBinding(binding: BleBinding): void;
56
66
  }
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 = [];
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
  }
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.36",
3
+ "version": "1.4.39",
4
4
  "dependencies": {
5
5
  "@serialport/parser-byte-length": "^9.0.1",
6
6
  "@serialport/parser-delimiter": "^9.0.1",