incyclist-devices 3.0.26 → 3.0.27

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.
@@ -17,6 +17,8 @@ class BleAdapter extends adpater_js_1.default {
17
17
  onDeviceDisconnectHandler = this.emit.bind(this);
18
18
  onDisconnectDoneHandler = this.onDisconnectDone.bind(this);
19
19
  startTask;
20
+ startPromise;
21
+ stopPromise;
20
22
  constructor(settings, props) {
21
23
  super(settings, props);
22
24
  this.deviceData = {};
@@ -198,8 +200,11 @@ class BleAdapter extends adpater_js_1.default {
198
200
  return 'connected';
199
201
  }
200
202
  async start(startProps) {
201
- if (this.isStarting()) {
202
- await this.stop();
203
+ while (this.stopPromise !== undefined) {
204
+ await this.stopPromise;
205
+ }
206
+ if (this.isStarting() && this.startPromise !== undefined) {
207
+ return this.startPromise;
203
208
  }
204
209
  const ble = this.getBle();
205
210
  ble.once('disconnect-done', this.onDisconnectDoneHandler);
@@ -209,11 +214,20 @@ class BleAdapter extends adpater_js_1.default {
209
214
  errorOnTimeout: false,
210
215
  log: this.logEvent.bind(this)
211
216
  });
212
- const res = await this.startTask.run();
213
- if (!res) {
214
- ble.removeListener('disconnect-done', this.onDisconnectDoneHandler);
217
+ this.startPromise = this.runStartTask(ble);
218
+ return this.startPromise;
219
+ }
220
+ async runStartTask(ble) {
221
+ try {
222
+ const res = await this.startTask.run();
223
+ if (!res) {
224
+ ble.removeListener('disconnect-done', this.onDisconnectDoneHandler);
225
+ }
226
+ return res;
227
+ }
228
+ finally {
229
+ this.startPromise = undefined;
215
230
  }
216
- return res;
217
231
  }
218
232
  isStarting() {
219
233
  return this.startTask?.isRunning();
@@ -379,9 +393,23 @@ class BleAdapter extends adpater_js_1.default {
379
393
  return success;
380
394
  }
381
395
  async stop() {
396
+ if (this.stopPromise !== undefined) {
397
+ return this.stopPromise;
398
+ }
399
+ this.stopPromise = this.runStop();
400
+ try {
401
+ return await this.stopPromise;
402
+ }
403
+ finally {
404
+ this.stopPromise = undefined;
405
+ }
406
+ }
407
+ async runStop() {
382
408
  this.logEvent({ message: 'stopping device', device: this.getName(), interface: this.getInterface() });
383
409
  if (this.isStarting()) {
384
- await this.startTask.stop();
410
+ const task = this.startTask;
411
+ await task.stop();
412
+ await task.getUnderlyingPromise()?.catch(() => { });
385
413
  }
386
414
  this.started = false;
387
415
  this.resetData();
@@ -158,8 +158,23 @@ class BlePeripheral {
158
158
  const promise = this.discoverServicesPromise;
159
159
  const res = await promise;
160
160
  (0, utils_js_1.sleep)(0).then(() => { delete this.discoverServicesPromise; });
161
+ const isComplete = this.checkAnnouncedServices(res);
162
+ if (!isComplete) {
163
+ this.logEvent({ message: 'service data incomplete - disconnecting' });
164
+ }
161
165
  return res;
162
166
  }
167
+ checkAnnouncedServices(discovered) {
168
+ const announced = this.getAnnouncedServices().map(x => (0, utils_js_2.beautifyUUID)(x));
169
+ const toBeChecked = discovered.map(x => (0, utils_js_2.beautifyUUID)(x));
170
+ const cntAnnounced = announced.length;
171
+ let cntVerified = 0;
172
+ for (const s of announced) {
173
+ if (toBeChecked.includes(s))
174
+ cntVerified++;
175
+ }
176
+ return cntAnnounced === cntVerified;
177
+ }
163
178
  async _discoverServices() {
164
179
  if (!this.getPeripheral())
165
180
  return [];
@@ -436,25 +451,61 @@ class BlePeripheral {
436
451
  return Promise.reject(new Error('characteristic not found: ' + characteristicUUID));
437
452
  }
438
453
  }
454
+ const signal = options?.signal;
439
455
  return new Promise((resolve, reject) => {
456
+ let settled = false;
457
+ let onData;
458
+ const cleanup = () => {
459
+ if (onData) {
460
+ c.off('data', onData);
461
+ onData = undefined;
462
+ }
463
+ signal?.removeEventListener('abort', onAbort);
464
+ };
465
+ const settleResolve = (result) => {
466
+ if (settled)
467
+ return;
468
+ settled = true;
469
+ cleanup();
470
+ resolve(result);
471
+ };
472
+ const settleReject = (err) => {
473
+ if (settled)
474
+ return;
475
+ settled = true;
476
+ cleanup();
477
+ reject(err);
478
+ };
479
+ const onAbort = () => {
480
+ settleReject(new Error('aborted'));
481
+ };
482
+ if (signal) {
483
+ if (signal.aborted) {
484
+ settleReject(new Error('aborted'));
485
+ return;
486
+ }
487
+ signal.addEventListener('abort', onAbort, { once: true });
488
+ }
440
489
  const write = () => {
441
- if (this.disconnecting || !this.connected)
442
- return Promise.resolve(Buffer.from([]));
443
- c.on('data', (data) => {
444
- c.removeAllListeners('data');
445
- resolve(data);
446
- });
490
+ if (this.disconnecting || !this.connected) {
491
+ settleResolve(Buffer.from([]));
492
+ return;
493
+ }
494
+ onData = (responseData) => {
495
+ settleResolve(responseData);
496
+ };
497
+ c.on('data', onData);
447
498
  this.logEvent({ message: 'write request', characteristic: uuid, data: data.toString('hex'), withoutResponse: options?.withoutResponse === true });
448
499
  c.write(data, options?.withoutResponse === true, (err) => {
449
500
  if (err)
450
- reject(err);
501
+ settleReject(err);
451
502
  });
452
503
  if (options?.withoutResponse) {
453
- resolve(Buffer.from([]));
504
+ settleResolve(Buffer.from([]));
454
505
  }
455
506
  };
456
507
  if (!options?.withoutResponse) {
457
- this.subscribe(characteristicUUID, null).then(success => {
508
+ this.subscribe(characteristicUUID, null).then(() => {
458
509
  write();
459
510
  });
460
511
  }
@@ -225,24 +225,33 @@ class BleFmAdapter extends adapter_js_1.default {
225
225
  let hasControl = false;
226
226
  let tryCnt = 0;
227
227
  const sensor = this.getSensor();
228
+ let cancelled = false;
229
+ const controlAbort = new AbortController();
230
+ const abandon = () => {
231
+ if (cancelled)
232
+ return;
233
+ cancelled = true;
234
+ controlAbort.abort();
235
+ };
228
236
  const controlPromise = new Promise((resolve) => {
229
237
  this.startTask.notifyOnStop(() => {
238
+ abandon();
230
239
  resolve(false);
231
240
  });
232
241
  const waitUntilControl = async () => {
233
242
  if (this.supportsVirtualShifting()) {
234
243
  await this.initVirtualShifting();
235
244
  }
236
- while (!hasControl && this.isStarting()) {
245
+ while (!hasControl && !cancelled && this.isStarting()) {
237
246
  if (tryCnt++ === 0) {
238
247
  this.logEvent({ message: 'requesting control', device: this.getName(), interface: this.getInterface() });
239
248
  }
240
- hasControl = await sensor.requestControl();
249
+ hasControl = await sensor.requestControl(controlAbort.signal);
241
250
  if (hasControl) {
242
251
  this.logEvent({ message: 'control granted', device: this.getName(), interface: this.getInterface() });
243
252
  resolve(this.isStarting());
244
253
  }
245
- else {
254
+ else if (!cancelled) {
246
255
  await (0, utils_js_1.sleep)(this.requestControlRetryDelay);
247
256
  }
248
257
  }
@@ -253,10 +262,12 @@ class BleFmAdapter extends adapter_js_1.default {
253
262
  timeout: this.requestControlTimeout,
254
263
  name: 'establishControl',
255
264
  errorOnTimeout: false,
256
- log: this.logEvent.bind(this)
265
+ log: this.logEvent.bind(this),
266
+ onCancel: abandon
257
267
  });
258
268
  const granted = await waitTask.run();
259
269
  if (!granted && this.isStarting()) {
270
+ abandon();
260
271
  this.logEvent({ message: 'could not establish control', device: this.getName(), interface: this.getInterface() });
261
272
  throw new Error('could not establish control');
262
273
  }
@@ -105,7 +105,7 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
105
105
  this.hasControl = false;
106
106
  this.ftmsServiceDataAttempts = 0;
107
107
  }
108
- async requestControl() {
108
+ async requestControl(signal) {
109
109
  if (this.hasControl) {
110
110
  return true;
111
111
  }
@@ -115,7 +115,7 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
115
115
  this.isCheckingControl = true;
116
116
  const data = Buffer.alloc(1);
117
117
  data.writeUInt8(0, 0);
118
- const res = await this.writeFtmsMessage(0, data, { timeout: 5000 });
118
+ const res = await this.writeFtmsMessage(0, data, { timeout: 5000, signal });
119
119
  if (res === 1) {
120
120
  this.hasControl = true;
121
121
  }
@@ -622,9 +622,15 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
622
622
  let res;
623
623
  let tsStart = Date.now();
624
624
  if (props?.timeout) {
625
- res = await new task_js_1.InteruptableTask(this.write(consts_js_1.FTMS_CP, data, props), {
625
+ const internalAbort = new AbortController();
626
+ const combined = new AbortController();
627
+ props.signal?.addEventListener('abort', () => combined.abort(), { once: true });
628
+ internalAbort.signal.addEventListener('abort', () => combined.abort(), { once: true });
629
+ const signal = combined.signal;
630
+ res = await new task_js_1.InteruptableTask(this.write(consts_js_1.FTMS_CP, data, { ...props, signal }), {
626
631
  timeout: props.timeout ?? 800,
627
- errorOnTimeout: true
632
+ errorOnTimeout: true,
633
+ onCancel: () => internalAbort.abort()
628
634
  }).run();
629
635
  }
630
636
  else {
@@ -10,6 +10,7 @@ class InteruptableTask {
10
10
  internalEvents = new node_events_1.EventEmitter();
11
11
  promise;
12
12
  onStopNotifiers = [];
13
+ cancelNotified = false;
13
14
  constructor(promise, props) {
14
15
  this.state = (props?.state ?? {});
15
16
  this.props = props;
@@ -20,6 +21,9 @@ class InteruptableTask {
20
21
  getPromise() {
21
22
  return this.internalState.promise;
22
23
  }
24
+ getUnderlyingPromise() {
25
+ return this.promise;
26
+ }
23
27
  getState() {
24
28
  return this.state;
25
29
  }
@@ -40,6 +44,7 @@ class InteruptableTask {
40
44
  const { timeout } = this.props;
41
45
  if (timeout) {
42
46
  this.internalState.tsTimeout = this.internalState.tsStart + timeout;
47
+ this.internalState.timeoutDuration = timeout;
43
48
  this.internalState.onTimeout = this.onTimeout.bind(this);
44
49
  this.internalState.timeout = setTimeout(() => { this.internalEvents.emit('timeout'); }, timeout);
45
50
  this.internalEvents.on('timeout', this.internalState.onTimeout);
@@ -53,6 +58,7 @@ class InteruptableTask {
53
58
  this.sendStopNotification();
54
59
  if (this.getState().result === 'completed' || this.getState().result === 'error')
55
60
  return;
61
+ this.notifyCancel();
56
62
  this.getState().result = 'stopped';
57
63
  if (this.props.onDone)
58
64
  resolve(this.props.onDone(this.getState()));
@@ -97,8 +103,9 @@ class InteruptableTask {
97
103
  if (!this.internalState.timeout)
98
104
  return;
99
105
  const message = this.props.name ? `${this.props.name} timeout` : 'timeout';
100
- this.logEvent({ message, active: this.isRunning() });
106
+ this.logEvent({ message, active: this.isRunning(), duration: this.internalState?.timeoutDuration });
101
107
  this.clearTimeout();
108
+ this.notifyCancel();
102
109
  this.getState().result = 'timeout';
103
110
  const resolve = this.internalState.onDone;
104
111
  const reject = this.internalState.onError;
@@ -111,6 +118,12 @@ class InteruptableTask {
111
118
  else
112
119
  resolve(null);
113
120
  }
121
+ notifyCancel() {
122
+ if (this.cancelNotified)
123
+ return;
124
+ this.cancelNotified = true;
125
+ this.props.onCancel?.();
126
+ }
114
127
  sendStopNotification() {
115
128
  this.onStopNotifiers.forEach((cb) => {
116
129
  if (typeof cb === 'function')
@@ -12,6 +12,8 @@ export default class BleAdapter extends IncyclistDevice {
12
12
  onDeviceDisconnectHandler = this.emit.bind(this);
13
13
  onDisconnectDoneHandler = this.onDisconnectDone.bind(this);
14
14
  startTask;
15
+ startPromise;
16
+ stopPromise;
15
17
  constructor(settings, props) {
16
18
  super(settings, props);
17
19
  this.deviceData = {};
@@ -193,8 +195,11 @@ export default class BleAdapter extends IncyclistDevice {
193
195
  return 'connected';
194
196
  }
195
197
  async start(startProps) {
196
- if (this.isStarting()) {
197
- await this.stop();
198
+ while (this.stopPromise !== undefined) {
199
+ await this.stopPromise;
200
+ }
201
+ if (this.isStarting() && this.startPromise !== undefined) {
202
+ return this.startPromise;
198
203
  }
199
204
  const ble = this.getBle();
200
205
  ble.once('disconnect-done', this.onDisconnectDoneHandler);
@@ -204,11 +209,20 @@ export default class BleAdapter extends IncyclistDevice {
204
209
  errorOnTimeout: false,
205
210
  log: this.logEvent.bind(this)
206
211
  });
207
- const res = await this.startTask.run();
208
- if (!res) {
209
- ble.removeListener('disconnect-done', this.onDisconnectDoneHandler);
212
+ this.startPromise = this.runStartTask(ble);
213
+ return this.startPromise;
214
+ }
215
+ async runStartTask(ble) {
216
+ try {
217
+ const res = await this.startTask.run();
218
+ if (!res) {
219
+ ble.removeListener('disconnect-done', this.onDisconnectDoneHandler);
220
+ }
221
+ return res;
222
+ }
223
+ finally {
224
+ this.startPromise = undefined;
210
225
  }
211
- return res;
212
226
  }
213
227
  isStarting() {
214
228
  return this.startTask?.isRunning();
@@ -374,9 +388,23 @@ export default class BleAdapter extends IncyclistDevice {
374
388
  return success;
375
389
  }
376
390
  async stop() {
391
+ if (this.stopPromise !== undefined) {
392
+ return this.stopPromise;
393
+ }
394
+ this.stopPromise = this.runStop();
395
+ try {
396
+ return await this.stopPromise;
397
+ }
398
+ finally {
399
+ this.stopPromise = undefined;
400
+ }
401
+ }
402
+ async runStop() {
377
403
  this.logEvent({ message: 'stopping device', device: this.getName(), interface: this.getInterface() });
378
404
  if (this.isStarting()) {
379
- await this.startTask.stop();
405
+ const task = this.startTask;
406
+ await task.stop();
407
+ await task.getUnderlyingPromise()?.catch(() => { });
380
408
  }
381
409
  this.started = false;
382
410
  this.resetData();
@@ -155,8 +155,23 @@ export class BlePeripheral {
155
155
  const promise = this.discoverServicesPromise;
156
156
  const res = await promise;
157
157
  sleep(0).then(() => { delete this.discoverServicesPromise; });
158
+ const isComplete = this.checkAnnouncedServices(res);
159
+ if (!isComplete) {
160
+ this.logEvent({ message: 'service data incomplete - disconnecting' });
161
+ }
158
162
  return res;
159
163
  }
164
+ checkAnnouncedServices(discovered) {
165
+ const announced = this.getAnnouncedServices().map(x => beautifyUUID(x));
166
+ const toBeChecked = discovered.map(x => beautifyUUID(x));
167
+ const cntAnnounced = announced.length;
168
+ let cntVerified = 0;
169
+ for (const s of announced) {
170
+ if (toBeChecked.includes(s))
171
+ cntVerified++;
172
+ }
173
+ return cntAnnounced === cntVerified;
174
+ }
160
175
  async _discoverServices() {
161
176
  if (!this.getPeripheral())
162
177
  return [];
@@ -433,25 +448,61 @@ export class BlePeripheral {
433
448
  return Promise.reject(new Error('characteristic not found: ' + characteristicUUID));
434
449
  }
435
450
  }
451
+ const signal = options?.signal;
436
452
  return new Promise((resolve, reject) => {
453
+ let settled = false;
454
+ let onData;
455
+ const cleanup = () => {
456
+ if (onData) {
457
+ c.off('data', onData);
458
+ onData = undefined;
459
+ }
460
+ signal?.removeEventListener('abort', onAbort);
461
+ };
462
+ const settleResolve = (result) => {
463
+ if (settled)
464
+ return;
465
+ settled = true;
466
+ cleanup();
467
+ resolve(result);
468
+ };
469
+ const settleReject = (err) => {
470
+ if (settled)
471
+ return;
472
+ settled = true;
473
+ cleanup();
474
+ reject(err);
475
+ };
476
+ const onAbort = () => {
477
+ settleReject(new Error('aborted'));
478
+ };
479
+ if (signal) {
480
+ if (signal.aborted) {
481
+ settleReject(new Error('aborted'));
482
+ return;
483
+ }
484
+ signal.addEventListener('abort', onAbort, { once: true });
485
+ }
437
486
  const write = () => {
438
- if (this.disconnecting || !this.connected)
439
- return Promise.resolve(Buffer.from([]));
440
- c.on('data', (data) => {
441
- c.removeAllListeners('data');
442
- resolve(data);
443
- });
487
+ if (this.disconnecting || !this.connected) {
488
+ settleResolve(Buffer.from([]));
489
+ return;
490
+ }
491
+ onData = (responseData) => {
492
+ settleResolve(responseData);
493
+ };
494
+ c.on('data', onData);
444
495
  this.logEvent({ message: 'write request', characteristic: uuid, data: data.toString('hex'), withoutResponse: options?.withoutResponse === true });
445
496
  c.write(data, options?.withoutResponse === true, (err) => {
446
497
  if (err)
447
- reject(err);
498
+ settleReject(err);
448
499
  });
449
500
  if (options?.withoutResponse) {
450
- resolve(Buffer.from([]));
501
+ settleResolve(Buffer.from([]));
451
502
  }
452
503
  };
453
504
  if (!options?.withoutResponse) {
454
- this.subscribe(characteristicUUID, null).then(success => {
505
+ this.subscribe(characteristicUUID, null).then(() => {
455
506
  write();
456
507
  });
457
508
  }
@@ -220,24 +220,33 @@ export default class BleFmAdapter extends BleAdapter {
220
220
  let hasControl = false;
221
221
  let tryCnt = 0;
222
222
  const sensor = this.getSensor();
223
+ let cancelled = false;
224
+ const controlAbort = new AbortController();
225
+ const abandon = () => {
226
+ if (cancelled)
227
+ return;
228
+ cancelled = true;
229
+ controlAbort.abort();
230
+ };
223
231
  const controlPromise = new Promise((resolve) => {
224
232
  this.startTask.notifyOnStop(() => {
233
+ abandon();
225
234
  resolve(false);
226
235
  });
227
236
  const waitUntilControl = async () => {
228
237
  if (this.supportsVirtualShifting()) {
229
238
  await this.initVirtualShifting();
230
239
  }
231
- while (!hasControl && this.isStarting()) {
240
+ while (!hasControl && !cancelled && this.isStarting()) {
232
241
  if (tryCnt++ === 0) {
233
242
  this.logEvent({ message: 'requesting control', device: this.getName(), interface: this.getInterface() });
234
243
  }
235
- hasControl = await sensor.requestControl();
244
+ hasControl = await sensor.requestControl(controlAbort.signal);
236
245
  if (hasControl) {
237
246
  this.logEvent({ message: 'control granted', device: this.getName(), interface: this.getInterface() });
238
247
  resolve(this.isStarting());
239
248
  }
240
- else {
249
+ else if (!cancelled) {
241
250
  await sleep(this.requestControlRetryDelay);
242
251
  }
243
252
  }
@@ -248,10 +257,12 @@ export default class BleFmAdapter extends BleAdapter {
248
257
  timeout: this.requestControlTimeout,
249
258
  name: 'establishControl',
250
259
  errorOnTimeout: false,
251
- log: this.logEvent.bind(this)
260
+ log: this.logEvent.bind(this),
261
+ onCancel: abandon
252
262
  });
253
263
  const granted = await waitTask.run();
254
264
  if (!granted && this.isStarting()) {
265
+ abandon();
255
266
  this.logEvent({ message: 'could not establish control', device: this.getName(), interface: this.getInterface() });
256
267
  throw new Error('could not establish control');
257
268
  }
@@ -103,7 +103,7 @@ export default class BleFitnessMachineDevice extends TBleSensor {
103
103
  this.hasControl = false;
104
104
  this.ftmsServiceDataAttempts = 0;
105
105
  }
106
- async requestControl() {
106
+ async requestControl(signal) {
107
107
  if (this.hasControl) {
108
108
  return true;
109
109
  }
@@ -113,7 +113,7 @@ export default class BleFitnessMachineDevice extends TBleSensor {
113
113
  this.isCheckingControl = true;
114
114
  const data = Buffer.alloc(1);
115
115
  data.writeUInt8(0, 0);
116
- const res = await this.writeFtmsMessage(0, data, { timeout: 5000 });
116
+ const res = await this.writeFtmsMessage(0, data, { timeout: 5000, signal });
117
117
  if (res === 1) {
118
118
  this.hasControl = true;
119
119
  }
@@ -620,9 +620,15 @@ export default class BleFitnessMachineDevice extends TBleSensor {
620
620
  let res;
621
621
  let tsStart = Date.now();
622
622
  if (props?.timeout) {
623
- res = await new InteruptableTask(this.write(FTMS_CP, data, props), {
623
+ const internalAbort = new AbortController();
624
+ const combined = new AbortController();
625
+ props.signal?.addEventListener('abort', () => combined.abort(), { once: true });
626
+ internalAbort.signal.addEventListener('abort', () => combined.abort(), { once: true });
627
+ const signal = combined.signal;
628
+ res = await new InteruptableTask(this.write(FTMS_CP, data, { ...props, signal }), {
624
629
  timeout: props.timeout ?? 800,
625
- errorOnTimeout: true
630
+ errorOnTimeout: true,
631
+ onCancel: () => internalAbort.abort()
626
632
  }).run();
627
633
  }
628
634
  else {
@@ -7,6 +7,7 @@ export class InteruptableTask {
7
7
  internalEvents = new EventEmitter();
8
8
  promise;
9
9
  onStopNotifiers = [];
10
+ cancelNotified = false;
10
11
  constructor(promise, props) {
11
12
  this.state = (props?.state ?? {});
12
13
  this.props = props;
@@ -17,6 +18,9 @@ export class InteruptableTask {
17
18
  getPromise() {
18
19
  return this.internalState.promise;
19
20
  }
21
+ getUnderlyingPromise() {
22
+ return this.promise;
23
+ }
20
24
  getState() {
21
25
  return this.state;
22
26
  }
@@ -37,6 +41,7 @@ export class InteruptableTask {
37
41
  const { timeout } = this.props;
38
42
  if (timeout) {
39
43
  this.internalState.tsTimeout = this.internalState.tsStart + timeout;
44
+ this.internalState.timeoutDuration = timeout;
40
45
  this.internalState.onTimeout = this.onTimeout.bind(this);
41
46
  this.internalState.timeout = setTimeout(() => { this.internalEvents.emit('timeout'); }, timeout);
42
47
  this.internalEvents.on('timeout', this.internalState.onTimeout);
@@ -50,6 +55,7 @@ export class InteruptableTask {
50
55
  this.sendStopNotification();
51
56
  if (this.getState().result === 'completed' || this.getState().result === 'error')
52
57
  return;
58
+ this.notifyCancel();
53
59
  this.getState().result = 'stopped';
54
60
  if (this.props.onDone)
55
61
  resolve(this.props.onDone(this.getState()));
@@ -94,8 +100,9 @@ export class InteruptableTask {
94
100
  if (!this.internalState.timeout)
95
101
  return;
96
102
  const message = this.props.name ? `${this.props.name} timeout` : 'timeout';
97
- this.logEvent({ message, active: this.isRunning() });
103
+ this.logEvent({ message, active: this.isRunning(), duration: this.internalState?.timeoutDuration });
98
104
  this.clearTimeout();
105
+ this.notifyCancel();
99
106
  this.getState().result = 'timeout';
100
107
  const resolve = this.internalState.onDone;
101
108
  const reject = this.internalState.onError;
@@ -108,6 +115,12 @@ export class InteruptableTask {
108
115
  else
109
116
  resolve(null);
110
117
  }
118
+ notifyCancel() {
119
+ if (this.cancelNotified)
120
+ return;
121
+ this.cancelNotified = true;
122
+ this.props.onCancel?.();
123
+ }
111
124
  sendStopNotification() {
112
125
  this.onStopNotifiers.forEach((cb) => {
113
126
  if (typeof cb === 'function')
@@ -15,6 +15,8 @@ export default class BleAdapter<TDeviceData extends BleDeviceData, TDevice exten
15
15
  protected onDeviceDisconnectHandler: any;
16
16
  protected onDisconnectDoneHandler: any;
17
17
  protected startTask: InteruptableTask<TaskState, boolean>;
18
+ protected startPromise?: Promise<boolean>;
19
+ protected stopPromise?: Promise<boolean>;
18
20
  constructor(settings: BleDeviceSettings, props?: DeviceProperties);
19
21
  getUniqueName(): string;
20
22
  connect(): Promise<boolean>;
@@ -43,6 +45,7 @@ export default class BleAdapter<TDeviceData extends BleDeviceData, TDevice exten
43
45
  getDefaultStartupTimeout(): number;
44
46
  startPreChecks(props: BleStartProperties): Promise<'done' | 'connected' | 'connection-failed'>;
45
47
  start(startProps?: BleStartProperties): Promise<boolean>;
48
+ protected runStartTask(ble: IBleInterface<any>): Promise<boolean>;
46
49
  protected isStarting(): boolean;
47
50
  protected hasData(): boolean;
48
51
  protected waitForInitialData(startupTimeout: any): Promise<void>;
@@ -55,6 +58,7 @@ export default class BleAdapter<TDeviceData extends BleDeviceData, TDevice exten
55
58
  protected onDisconnectDone(): Promise<void>;
56
59
  restart(pause?: number): Promise<boolean>;
57
60
  stop(): Promise<boolean>;
61
+ protected runStop(): Promise<boolean>;
58
62
  pause(): Promise<boolean>;
59
63
  resume(): Promise<boolean>;
60
64
  protected getBle(): IBleInterface<any>;
@@ -29,15 +29,16 @@ export declare class BlePeripheral implements IBlePeripheral {
29
29
  isConnected(): boolean;
30
30
  isConnecting(): boolean;
31
31
  onDisconnect(callback: () => void): void;
32
- getManufacturerData(): Buffer<ArrayBufferLike>;
32
+ getManufacturerData(): Buffer;
33
33
  getServiceData(uuid: string): Buffer | undefined;
34
34
  protected onPeripheralDisconnect(): Promise<void>;
35
35
  protected onPeripheralError(err: Error): void;
36
36
  discoverServices(): Promise<string[]>;
37
+ protected checkAnnouncedServices(discovered: string[]): boolean;
37
38
  protected _discoverServices(): Promise<string[]>;
38
39
  discoverCharacteristics(serviceUUID: string): Promise<BleCharacteristic[]>;
39
40
  protected _discoverCharacteristics(serviceUUID: string): Promise<BleCharacteristic[]>;
40
- subscribe(characteristicUUID: string, callback: (characteristicUuid: string, data: Buffer, isNotify?: any) => void): Promise<boolean>;
41
+ subscribe(characteristicUUID: string, callback: (characteristicUuid: string, data: Buffer, isNotify?: boolean) => void): Promise<boolean>;
41
42
  unsubscribe(characteristicUUID: string): Promise<boolean>;
42
43
  subscribeSelected(characteristics: string[], callback: (characteristicUuid: string, data: Buffer, isNotify?: boolean) => void): Promise<boolean>;
43
44
  discoverAllCharacteristics(): Promise<string[]>;
@@ -35,7 +35,7 @@ export default class BleFitnessMachineDevice extends TBleSensor {
35
35
  getWindSpeed(): number;
36
36
  getSupportedSports(): Array<Sport>;
37
37
  protected onDisconnect(): void;
38
- requestControl(): Promise<boolean>;
38
+ requestControl(signal?: AbortSignal): Promise<boolean>;
39
39
  setTargetPower(power: number): Promise<boolean>;
40
40
  setTargetResistanceLevel(resistanceLevel: number): Promise<boolean>;
41
41
  setSlope(slope: number): Promise<boolean>;
@@ -123,6 +123,7 @@ export type BleCommsConnectProps = {
123
123
  export interface BleWriteProps {
124
124
  withoutResponse?: boolean;
125
125
  timeout?: number;
126
+ signal?: AbortSignal;
126
127
  }
127
128
  export interface ConnectState {
128
129
  isConnecting: boolean;
@@ -13,10 +13,12 @@ export interface TaskProps<T, P> {
13
13
  errorOnTimeout?: boolean;
14
14
  log?: (event: any) => void;
15
15
  onDone?: (state: T) => P;
16
+ onCancel?: () => void;
16
17
  }
17
18
  interface InternalTaskState<P> {
18
19
  tsStart?: number;
19
20
  tsTimeout?: number;
21
+ timeoutDuration?: number;
20
22
  isRunning: boolean;
21
23
  timeout?: NodeJS.Timeout;
22
24
  promise?: Promise<P>;
@@ -31,8 +33,10 @@ export declare class InteruptableTask<T extends TaskState, P> {
31
33
  protected internalEvents: EventEmitter<any>;
32
34
  protected promise?: Promise<P>;
33
35
  protected onStopNotifiers: Array<() => void>;
36
+ protected cancelNotified: boolean;
34
37
  constructor(promise: Promise<any>, props?: TaskProps<T, P>);
35
38
  getPromise(): Promise<P>;
39
+ getUnderlyingPromise(): Promise<any> | undefined;
36
40
  getState(): T;
37
41
  run(): Promise<P>;
38
42
  notifyOnStop(cb: () => void): void;
@@ -41,6 +45,7 @@ export declare class InteruptableTask<T extends TaskState, P> {
41
45
  isRunning(): boolean;
42
46
  protected clearTimeout(): void;
43
47
  protected onTimeout(): void;
48
+ protected notifyCancel(): void;
44
49
  protected sendStopNotification(): void;
45
50
  protected logEvent(event: any): void;
46
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "3.0.26",
3
+ "version": "3.0.27",
4
4
  "scripts": {
5
5
  "lint": "eslint . --ext .ts",
6
6
  "build": "npm run build:esm && npm run build:cjs",