incyclist-devices 2.3.0-beta.7 → 2.3.0-beta.9

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.
@@ -37,6 +37,7 @@ export default class IncyclistDevice<P extends DeviceProperties> extends EventEm
37
37
  resume(): Promise<boolean>;
38
38
  connect(): Promise<boolean>;
39
39
  close(): Promise<boolean>;
40
+ resetData(): void;
40
41
  getControllerInfo(): ControllerConfig | undefined;
41
42
  isControllable(): boolean;
42
43
  getCapabilities(): IncyclistCapability[];
@@ -100,6 +100,9 @@ class IncyclistDevice extends events_1.default {
100
100
  }
101
101
  connect() { throw new Error('not implemented'); }
102
102
  close() { throw new Error('not implemented'); }
103
+ resetData() {
104
+ delete this.lastUpdate;
105
+ }
103
106
  getControllerInfo() {
104
107
  const a = this.constructor;
105
108
  const config = a.controllers;
@@ -87,6 +87,7 @@ class BleAdapter extends adpater_1.default {
87
87
  return (_a = this.device) === null || _a === void 0 ? void 0 : _a.isConnected();
88
88
  }
89
89
  resetData() {
90
+ super.resetData();
90
91
  this.dataMsgCount = 0;
91
92
  this.deviceData = {};
92
93
  this.data = {};
@@ -183,10 +184,7 @@ class BleAdapter extends adpater_1.default {
183
184
  this.stopped = false;
184
185
  if (wasPaused)
185
186
  this.resume();
186
- if (this.started && !wasPaused && !wasStopped) {
187
- return 'done';
188
- }
189
- if (this.started && wasPaused) {
187
+ if (this.started && !wasStopped) {
190
188
  return 'done';
191
189
  }
192
190
  const connected = yield this.connect();
@@ -261,6 +259,7 @@ class BleAdapter extends adpater_1.default {
261
259
  const preCheckResult = yield this.startPreChecks(props);
262
260
  if (preCheckResult === 'done') {
263
261
  yield (0, utils_1.resolveNextTick)();
262
+ this.logEvent({ message: `start result: ${this.started ? 'success' : 'failed'}`, preCheckResult, device: this.getName(), interface: this.getInterface(), protocol: this.getProtocolName() });
264
263
  return this.started;
265
264
  }
266
265
  if (preCheckResult === 'connection-failed') {
@@ -270,27 +269,37 @@ class BleAdapter extends adpater_1.default {
270
269
  }
271
270
  this.logEvent({ message: 'starting device', device: this.getName(), interface: this.getInterface(), props, isStarted: this.started });
272
271
  try {
272
+ this.resetData();
273
+ this.stopped = false;
273
274
  const connected = yield this.startSensor();
274
275
  if (connected) {
275
276
  this.logEvent({ message: 'peripheral connected', device: this.getName(), interface: this.getInterface(), props });
276
277
  }
277
278
  else {
278
279
  this.logEvent({ message: 'peripheral connection failed', device: this.getName(), interface: this.getInterface(), reason: 'unknown', props });
280
+ this.stopped = true;
279
281
  return false;
280
282
  }
281
283
  yield this.waitForInitialData(timeout);
282
284
  this.checkCapabilities();
283
285
  if (this.hasCapability(types_1.IncyclistCapability.Control))
284
286
  yield this.initControl(startProps);
285
- this.resetData();
286
287
  this.stopped = false;
287
288
  this.started = true;
288
289
  if (wasPaused)
289
290
  this.resume();
291
+ if (!this.isStarting()) {
292
+ this.started = false;
293
+ this.stopped = true;
294
+ return false;
295
+ }
296
+ this.logEvent({ message: 'start result: success', device: this.getName(), interface: this.getInterface(), protocol: this.getProtocolName() });
290
297
  return true;
291
298
  }
292
299
  catch (err) {
293
300
  this.logEvent({ message: 'start result: error', error: err.message, device: this.getName(), interface: this.getInterface(), protocol: this.getProtocolName() });
301
+ this.started = false;
302
+ this.stopped = true;
294
303
  return false;
295
304
  }
296
305
  });
@@ -319,6 +328,8 @@ class BleAdapter extends adpater_1.default {
319
328
  if (this.isStarting()) {
320
329
  yield this.startTask.stop();
321
330
  }
331
+ this.started = false;
332
+ this.resetData();
322
333
  let reason = 'unknown';
323
334
  let stopped = false;
324
335
  if (!this.getComms()) {
@@ -338,8 +349,9 @@ class BleAdapter extends adpater_1.default {
338
349
  else {
339
350
  this.logEvent({ message: 'stopping device failed', device: this.getName(), interface: this.getInterface(), reason });
340
351
  }
341
- this.started = false;
352
+ this.resetData();
342
353
  this.stopped = true;
354
+ this.started = false;
343
355
  return stopped;
344
356
  });
345
357
  }
@@ -32,6 +32,7 @@ export declare class BleInterface extends EventEmitter implements IBleInterface<
32
32
  protected onDiscovered: (peripheral: BlePeripheralInfo) => void;
33
33
  protected instanceId: number;
34
34
  protected connectedPeripherals: IBlePeripheral[];
35
+ protected connectAttemptCnt: number;
35
36
  static getInstance(props?: InterfaceProps): BleInterface;
36
37
  protected constructor(props: InterfaceProps);
37
38
  getLogger(): EventLogger;
@@ -63,6 +64,8 @@ export declare class BleInterface extends EventEmitter implements IBleInterface<
63
64
  pauseDiscovery(): Promise<void>;
64
65
  resumeDiscovery(): Promise<void>;
65
66
  protected onPeripheralFound(peripheral: BleRawPeripheral): void;
67
+ protected checkForWahooEnhancement(announcement: BlePeripheralAnnouncement): boolean;
68
+ protected processWahooAnnouncement(announcement: BlePeripheralAnnouncement): void;
66
69
  protected buildAnnouncement(peripheral: BleRawPeripheral): BlePeripheralAnnouncement;
67
70
  protected updateWithServices(announcement: BlePeripheralAnnouncement): Promise<BlePeripheralAnnouncement>;
68
71
  protected discoverServices(announcement: BlePeripheralAnnouncement): Promise<string[]>;
@@ -85,6 +88,8 @@ export declare class BleInterface extends EventEmitter implements IBleInterface<
85
88
  protected waitForBleConnected(): Promise<boolean>;
86
89
  protected onBleStateChange(state: BleInterfaceState): void;
87
90
  protected getAdapterFactory(): BleAdapterFactory<TBleSensor>;
91
+ protected getConnectTimeout(): number;
92
+ protected getExpectedServices(): string[];
88
93
  logEvent(event: any): void;
89
94
  logError(err: Error, fn: string, args?: any): void;
90
95
  }
@@ -49,6 +49,7 @@ class BleInterface extends events_1.default {
49
49
  this.expectedServices = ['180d', '1818', '1826', '6e40fec1'];
50
50
  this.matching = [];
51
51
  this.connectedPeripherals = [];
52
+ this.connectAttemptCnt = 0;
52
53
  this.instanceId = ++instanceCount;
53
54
  this.props = props;
54
55
  this.logEnabled = props.log || true;
@@ -90,28 +91,28 @@ class BleInterface extends events_1.default {
90
91
  return false;
91
92
  }
92
93
  if (this.isConnecting()) {
93
- this.logEvent({ message: 'connect - already connecting' });
94
+ this.logEvent({ message: 'BLE connect - already connecting' });
94
95
  return this.connectTask.getPromise();
95
96
  }
96
97
  if (this.isConnected())
97
98
  return true;
98
- this.logEvent({ message: 'Ble connect request' });
99
+ this.logEvent({ message: 'BLE connect request' });
99
100
  this.connectTask = new task_1.InteruptableTask(this.connectBle(), {
100
- timeout: BLE_DEFAULT_CONNECT_TIMEOUT,
101
- name: 'connect',
101
+ timeout: this.getConnectTimeout(),
102
+ name: 'BLE connect',
102
103
  errorOnTimeout: false,
103
104
  log: this.logEvent.bind(this),
104
105
  });
105
- const success = yield this.connectTask.run();
106
+ const success = yield this.connectTask.run().catch(() => false);
106
107
  if (success) {
107
108
  this.startPeripheralScan();
108
109
  }
109
- this.expectedServices = factories_1.BleAdapterFactory.getInstance('ble').getAllSupportedServices();
110
110
  return success;
111
111
  });
112
112
  }
113
113
  disconnect() {
114
114
  return __awaiter(this, void 0, void 0, function* () {
115
+ var _a;
115
116
  if (!this.getBinding()) {
116
117
  return false;
117
118
  }
@@ -122,14 +123,16 @@ class BleInterface extends events_1.default {
122
123
  const promises = this.connectedPeripherals.map(p => p.disconnect());
123
124
  yield Promise.allSettled(promises);
124
125
  this.connectedPeripherals = [];
125
- yield this.connectTask.stop();
126
+ if (this.isConnecting())
127
+ yield ((_a = this.connectTask) === null || _a === void 0 ? void 0 : _a.stop());
126
128
  this.getBinding().removeAllListeners();
129
+ this.connectAttemptCnt = 0;
127
130
  return true;
128
131
  });
129
132
  }
130
133
  isConnected() {
131
134
  var _a;
132
- return ((_a = this.getBinding()) === null || _a === void 0 ? void 0 : _a.state) === 'poweredOn';
135
+ return this.connectAttemptCnt > 0 && ((_a = this.getBinding()) === null || _a === void 0 ? void 0 : _a.state) === 'poweredOn';
133
136
  }
134
137
  registerConnected(peripheral) {
135
138
  this.connectedPeripherals.push(peripheral);
@@ -228,6 +231,7 @@ class BleInterface extends events_1.default {
228
231
  }
229
232
  startPeripheralScan() {
230
233
  return __awaiter(this, arguments, void 0, function* (retry = false) {
234
+ this.expectedServices = this.getExpectedServices();
231
235
  if (!retry)
232
236
  this.logEvent({ message: 'starting peripheral discovery ...' });
233
237
  if (!this.isConnected() || this.isDiscovering()) {
@@ -254,9 +258,10 @@ class BleInterface extends events_1.default {
254
258
  return;
255
259
  this.logEvent({ message: 'stopping peripheral discovery ...' });
256
260
  this.discoverTask.stop();
257
- this.getBinding().off('discover', this.onDiscovered);
261
+ const ble = this.getBinding();
262
+ ble.off('discover', this.onDiscovered);
258
263
  return new Promise(done => {
259
- this.getBinding().stopScanning(() => {
264
+ ble.stopScanning(() => {
260
265
  done();
261
266
  });
262
267
  });
@@ -313,24 +318,32 @@ class BleInterface extends events_1.default {
313
318
  }
314
319
  if (announcement.serviceUUIDs.length === 0) {
315
320
  return;
316
- if (this.isCompleting(announcement)) {
317
- return;
318
- }
319
- this.addCompleting(announcement);
320
- setTimeout(() => {
321
- if (this.find(announcement)) {
322
- this.removeCompleting(announcement);
323
- return;
324
- }
325
- this.updateWithServices(announcement)
326
- .then(() => {
327
- if (this.isSupportedPeripheral(announcement))
328
- this.addService(announcement);
329
- });
330
- }, 1000);
321
+ }
322
+ const isWahoo = this.checkForWahooEnhancement(announcement);
323
+ if (isWahoo) {
324
+ this.processWahooAnnouncement(announcement);
325
+ return;
331
326
  }
332
327
  this.addService(announcement);
333
328
  }
329
+ checkForWahooEnhancement(announcement) {
330
+ if (announcement.name.includes('KICKR')) {
331
+ const supported = announcement.serviceUUIDs.map(s => (0, utils_1.beautifyUUID)(s));
332
+ if (supported.length === 1 && supported[0] === '1818')
333
+ return true;
334
+ }
335
+ return false;
336
+ }
337
+ processWahooAnnouncement(announcement) {
338
+ if (this.isCompleting(announcement)) {
339
+ return;
340
+ }
341
+ this.updateWithServices(announcement)
342
+ .then(() => {
343
+ if (this.isSupportedPeripheral(announcement))
344
+ this.addService(announcement);
345
+ });
346
+ }
334
347
  buildAnnouncement(peripheral) {
335
348
  var _a;
336
349
  return {
@@ -379,11 +392,11 @@ class BleInterface extends events_1.default {
379
392
  const res = yield peripheral.discoverSomeServicesAndCharacteristicsAsync([], []);
380
393
  announcement.serviceUUIDs = res.services.map(s => s.uuid);
381
394
  }
382
- peripheral.removeAllListeners();
383
395
  }
384
396
  catch (err) {
385
397
  this.logEvent({ message: 'discover services failed', reason: err.message, device });
386
398
  }
399
+ peripheral === null || peripheral === void 0 ? void 0 : peripheral.removeAllListeners();
387
400
  if (paused) {
388
401
  yield this.resumeDiscovery();
389
402
  }
@@ -498,8 +511,10 @@ class BleInterface extends events_1.default {
498
511
  }
499
512
  connectBle() {
500
513
  return __awaiter(this, void 0, void 0, function* () {
514
+ this.connectAttemptCnt++;
501
515
  const state = this.getBinding().state;
502
516
  if (state === 'poweredOn') {
517
+ this.logEvent({ message: 'BLE connected' });
503
518
  return true;
504
519
  }
505
520
  const res = yield this.waitForBleConnected();
@@ -514,7 +529,7 @@ class BleInterface extends events_1.default {
514
529
  });
515
530
  this.getBinding().on('stateChange', (state) => {
516
531
  if (state === 'poweredOn') {
517
- this.logEvent({ message: 'Ble connect result: success' });
532
+ this.logEvent({ message: 'BLE connected' });
518
533
  this.getBinding().removeAllListeners('stateChange');
519
534
  this.getBinding().on('stateChange', this.onBleStateChange.bind(this));
520
535
  this.getBinding().on('error', console.log);
@@ -531,6 +546,12 @@ class BleInterface extends events_1.default {
531
546
  getAdapterFactory() {
532
547
  return factories_1.BleAdapterFactory.getInstance('ble');
533
548
  }
549
+ getConnectTimeout() {
550
+ return BLE_DEFAULT_CONNECT_TIMEOUT;
551
+ }
552
+ getExpectedServices() {
553
+ return this.getAdapterFactory().getAllSupportedServices();
554
+ }
534
555
  logEvent(event) {
535
556
  if (this.logDisabled && event.message !== 'Error')
536
557
  return;
@@ -7,8 +7,9 @@ import { IAdapter, IncyclistAdapterData, IncyclistBikeData } from '../../types';
7
7
  import { LegacyProfile } from '../../antv2/types';
8
8
  export default class BleFmAdapter extends BleAdapter<IndoorBikeData, BleFitnessMachineDevice> {
9
9
  protected static INCYCLIST_PROFILE_NAME: LegacyProfile;
10
- distanceInternal: number;
11
- connectPromise: Promise<boolean>;
10
+ protected distanceInternal: number;
11
+ protected connectPromise: Promise<boolean>;
12
+ protected requestControlRetryDelay: number;
12
13
  constructor(settings: BleDeviceSettings, props?: BleDeviceProperties);
13
14
  updateSensor(peripheral: IBlePeripheral): void;
14
15
  isSame(device: IAdapter): boolean;
@@ -20,11 +20,12 @@ const sensor_1 = __importDefault(require("./sensor"));
20
20
  const adapter_1 = __importDefault(require("../base/adapter"));
21
21
  const consts_1 = require("./consts");
22
22
  const types_1 = require("../../types");
23
- const task_1 = require("../../utils/task");
23
+ const utils_1 = require("../../utils/utils");
24
24
  class BleFmAdapter extends adapter_1.default {
25
25
  constructor(settings, props) {
26
26
  super(settings, props);
27
27
  this.distanceInternal = 0;
28
+ this.requestControlRetryDelay = 1000;
28
29
  this.logger = new gd_eventlog_1.EventLogger('BLE-FM');
29
30
  this.device = new sensor_1.default(this.getPeripheral(), { logger: this.logger });
30
31
  this.capabilities = [
@@ -117,6 +118,8 @@ class BleFmAdapter extends adapter_1.default {
117
118
  }
118
119
  initControl(_startProps) {
119
120
  return __awaiter(this, void 0, void 0, function* () {
121
+ if (!this.isStarting())
122
+ return;
120
123
  this.setConstants();
121
124
  yield this.establishControl();
122
125
  this.setConstants();
@@ -146,38 +149,30 @@ class BleFmAdapter extends adapter_1.default {
146
149
  return __awaiter(this, void 0, void 0, function* () {
147
150
  if (!this.isStarting())
148
151
  return false;
149
- let waitTask;
150
- let iv;
152
+ let hasControl = false;
153
+ let tryCnt = 0;
151
154
  const sensor = this.getComms();
152
- const wait = () => {
153
- const res = new Promise((resolve) => {
154
- iv = setInterval(() => __awaiter(this, void 0, void 0, function* () {
155
- if (!this.isStarting() || !(waitTask === null || waitTask === void 0 ? void 0 : waitTask.isRunning)) {
156
- resolve(false);
157
- clearInterval(iv);
158
- return;
155
+ return new Promise((resolve) => {
156
+ this.startTask.notifyOnStop(() => {
157
+ resolve(false);
158
+ });
159
+ const waitUntilControl = () => __awaiter(this, void 0, void 0, function* () {
160
+ while (!hasControl && this.isStarting()) {
161
+ if (tryCnt++ === 0) {
162
+ this.logEvent({ message: 'requesting control', device: this.getName(), interface: this.getInterface() });
159
163
  }
160
- const hasControl = yield sensor.requestControl();
164
+ hasControl = yield sensor.requestControl();
161
165
  if (hasControl) {
162
- clearInterval(iv);
163
- resolve(true);
166
+ this.logEvent({ message: 'control granted', device: this.getName(), interface: this.getInterface() });
167
+ resolve(this.isStarting());
164
168
  }
165
- else if (!this.isStarting() || !(waitTask === null || waitTask === void 0 ? void 0 : waitTask.isRunning)) {
166
- resolve(false);
167
- clearInterval(iv);
169
+ else {
170
+ yield (0, utils_1.sleep)(this.requestControlRetryDelay);
168
171
  }
169
- }), 1000);
172
+ }
170
173
  });
171
- return res;
172
- };
173
- waitTask = new task_1.InteruptableTask(wait(), {
174
- errorOnTimeout: false,
175
- timeout: 10000
174
+ waitUntilControl();
176
175
  });
177
- const hasControl = yield waitTask.run();
178
- clearInterval(iv);
179
- if (!hasControl && this.isStarting())
180
- throw new Error('could not establish control');
181
176
  });
182
177
  }
183
178
  sendInitialRequest() {
@@ -212,7 +207,7 @@ class BleFmAdapter extends adapter_1.default {
212
207
  try {
213
208
  const update = this.getCyclingMode().sendBikeUpdate(request);
214
209
  this.logEvent({ message: 'send bike update requested', profile: this.getProfile(), update, request });
215
- const device = this.device;
210
+ const device = this.getComms();
216
211
  if (update.slope !== undefined) {
217
212
  yield device.setSlope(update.slope);
218
213
  }
@@ -471,7 +471,7 @@ class DirectConnectPeripheral {
471
471
  }
472
472
  if (incoming.length > header.length + 6) {
473
473
  this.remainingBuffer = Buffer.from(incoming.subarray(header.length + 6));
474
- incoming = incoming.subarray(0, header.length + 6);
474
+ incoming = Buffer.from(incoming.subarray(0, header.length + 6));
475
475
  }
476
476
  return incoming;
477
477
  }
@@ -499,7 +499,6 @@ class DirectConnectPeripheral {
499
499
  this.eventEmitter.emit(uuid, notification.body.characteristicData);
500
500
  }
501
501
  else {
502
- this.logEvent({ message: 'incoming message', path: this.getPath(), raw: incoming.toString('hex'), header });
503
502
  this.eventEmitter.emit(`response-${header.seqNum}`, incoming);
504
503
  }
505
504
  }
@@ -46,5 +46,6 @@ export interface IAdapter extends EventEmitter, IBike, ISensor {
46
46
  resume(): Promise<boolean>;
47
47
  connect(): Promise<boolean>;
48
48
  close(): Promise<boolean>;
49
+ resetData(): void;
49
50
  onData(callback: OnDeviceDataCallback): any;
50
51
  }
@@ -30,15 +30,18 @@ export declare class InteruptableTask<T extends TaskState, P> {
30
30
  protected props?: TaskProps<T, P>;
31
31
  protected internalEvents: EventEmitter<[never]>;
32
32
  protected promise?: Promise<P>;
33
+ protected onStopNotifiers: Array<() => void>;
33
34
  constructor(promise: Promise<any>, props?: TaskProps<T, P>);
34
35
  getPromise(): Promise<P>;
35
36
  getState(): T;
36
37
  run(): Promise<P>;
38
+ notifyOnStop(cb: () => void): void;
37
39
  start(): void;
38
40
  stop(): Promise<boolean>;
39
41
  isRunning(): boolean;
40
42
  protected clearTimeout(): void;
41
43
  protected onTimeout(): void;
44
+ protected sendStopNotification(): void;
42
45
  protected logEvent(event: any): void;
43
46
  }
44
47
  export {};
package/lib/utils/task.js CHANGED
@@ -20,6 +20,7 @@ class InteruptableTask {
20
20
  var _a;
21
21
  this.internalState = { isRunning: false };
22
22
  this.internalEvents = new events_1.default();
23
+ this.onStopNotifiers = [];
23
24
  this.state = ((_a = props === null || props === void 0 ? void 0 : props.state) !== null && _a !== void 0 ? _a : {});
24
25
  this.props = props;
25
26
  delete this.props.state;
@@ -39,6 +40,9 @@ class InteruptableTask {
39
40
  return this.internalState.promise;
40
41
  });
41
42
  }
43
+ notifyOnStop(cb) {
44
+ this.onStopNotifiers.push(cb);
45
+ }
42
46
  start() {
43
47
  this.internalState.promise = new Promise((resolve, reject) => {
44
48
  var _a;
@@ -59,8 +63,10 @@ class InteruptableTask {
59
63
  isRunning: false,
60
64
  };
61
65
  this.internalEvents.removeAllListeners();
66
+ this.sendStopNotification();
62
67
  if (this.getState().result === 'completed' || this.getState().result === 'error')
63
68
  return;
69
+ this.getState().result = 'stopped';
64
70
  if (this.props.onDone)
65
71
  resolve(this.props.onDone(this.getState()));
66
72
  else
@@ -119,6 +125,12 @@ class InteruptableTask {
119
125
  else
120
126
  resolve(null);
121
127
  }
128
+ sendStopNotification() {
129
+ this.onStopNotifiers.forEach((cb) => {
130
+ if (typeof cb === 'function')
131
+ cb();
132
+ });
133
+ }
122
134
  logEvent(event) {
123
135
  if (this.props.log)
124
136
  this.props.log(event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "2.3.0-beta.7",
3
+ "version": "2.3.0-beta.9",
4
4
  "dependencies": {
5
5
  "@serialport/bindings-interface": "^1.2.2",
6
6
  "@serialport/parser-byte-length": "^9.0.1",