incyclist-devices 1.4.38 → 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.
- package/lib/ble/ble-device.d.ts +1 -6
- package/lib/ble/ble-device.js +59 -14
- package/lib/ble/ble-interface.d.ts +6 -0
- package/lib/ble/ble-interface.js +229 -79
- package/lib/ble/ble.d.ts +9 -0
- package/lib/ble/ble.js +10 -0
- package/lib/ble/hrm.js +3 -2
- package/lib/ble/pwr.d.ts +1 -0
- package/lib/ble/pwr.js +7 -3
- package/package.json +1 -1
package/lib/ble/ble-device.d.ts
CHANGED
|
@@ -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;
|
package/lib/ble/ble-device.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
});
|
|
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;
|
package/lib/ble/ble-interface.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
this.logEvent({ message:
|
|
303
|
-
|
|
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
|
-
|
|
307
|
-
if (
|
|
308
|
-
this.logEvent({ message: '
|
|
309
|
-
|
|
310
|
-
return
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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 && 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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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:
|
|
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 = [];
|
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
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 '
|
|
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();
|