incyclist-devices 1.4.63 → 1.4.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,6 @@ declare type CommandQueueItem = {
11
11
  data: Buffer;
12
12
  resolve: any;
13
13
  reject: any;
14
- timeout: any;
15
14
  };
16
15
  export declare abstract class BleDevice extends BleDeviceClass {
17
16
  id: string;
@@ -253,34 +253,58 @@ class BleDevice extends ble_1.BleDeviceClass {
253
253
  }
254
254
  write(characteristicUuid, data, withoutResponse = false) {
255
255
  return __awaiter(this, void 0, void 0, function* () {
256
- if (!withoutResponse && this.subscribedCharacteristics.find(c => c === characteristicUuid) === undefined) {
256
+ try {
257
257
  const connector = this.ble.getConnector(this.peripheral);
258
- connector.on(characteristicUuid, (uuid, data) => {
259
- this.onData(uuid, data);
260
- });
261
- yield connector.subscribe(characteristicUuid);
262
- this.subscribedCharacteristics.push(characteristicUuid);
263
- }
264
- return new Promise((resolve, reject) => {
265
- const characteristic = this.characteristics.find(c => c.uuid === characteristicUuid || (0, ble_1.uuid)(c.uuid) === characteristicUuid);
266
- if (!characteristic) {
267
- reject(new Error('Characteristic not found'));
268
- return;
258
+ const isAlreadySubscribed = connector.isSubscribed(characteristicUuid);
259
+ if (!withoutResponse && !isAlreadySubscribed) {
260
+ const connector = this.ble.getConnector(this.peripheral);
261
+ connector.removeAllListeners(characteristicUuid);
262
+ connector.on(characteristicUuid, (uuid, data) => {
263
+ this.onData(uuid, data);
264
+ });
265
+ this.logEvent({ message: 'write:subscribing ', characteristic: characteristicUuid });
266
+ yield connector.subscribe(characteristicUuid);
267
+ this.subscribedCharacteristics.push(characteristicUuid);
269
268
  }
270
- if (withoutResponse) {
271
- characteristic.write(data, withoutResponse);
272
- resolve(new ArrayBuffer(0));
273
- return;
274
- }
275
- const writeId = this.writeQueue.length;
276
- this.writeQueue.push({ uuid: characteristicUuid.toLocaleLowerCase(), data, resolve, reject, timeout: Date.now() + 1000 });
277
- characteristic.write(data, withoutResponse, (err) => {
278
- if (err) {
279
- this.writeQueue.splice(writeId, 1);
280
- reject(err);
269
+ return new Promise((resolve, reject) => {
270
+ const characteristic = this.characteristics.find(c => c.uuid === characteristicUuid || (0, ble_1.uuid)(c.uuid) === characteristicUuid);
271
+ if (!characteristic) {
272
+ reject(new Error('Characteristic not found'));
273
+ return;
274
+ }
275
+ if (withoutResponse) {
276
+ this.logEvent({ message: 'writing' });
277
+ characteristic.write(data, withoutResponse);
278
+ resolve(new ArrayBuffer(0));
279
+ return;
280
+ }
281
+ else {
282
+ const writeId = this.writeQueue.length;
283
+ let messageDeleted = false;
284
+ this.writeQueue.push({ uuid: characteristicUuid.toLocaleLowerCase(), data, resolve, reject });
285
+ const to = setTimeout(() => {
286
+ console.log('~~~ write timeout');
287
+ if (this.writeQueue.length > writeId && !messageDeleted)
288
+ this.writeQueue.splice(writeId, 1);
289
+ this.logEvent({ message: 'writing response', err: 'timeout' });
290
+ reject(new Error('timeout'));
291
+ }, 5000);
292
+ this.logEvent({ message: 'writing' });
293
+ characteristic.write(data, withoutResponse, (err) => {
294
+ clearTimeout(to);
295
+ this.logEvent({ message: 'writing response', err });
296
+ if (err) {
297
+ this.writeQueue.splice(writeId, 1);
298
+ messageDeleted = true;
299
+ reject(err);
300
+ }
301
+ });
281
302
  }
282
303
  });
283
- });
304
+ }
305
+ catch (err) {
306
+ this.logEvent({ message: 'error', fn: '', error: err.message || err, stack: err.stack });
307
+ }
284
308
  });
285
309
  }
286
310
  read(characteristicUuid) {
@@ -55,9 +55,7 @@ class BleInterface extends ble_1.BleInterfaceClass {
55
55
  if (this.logger) {
56
56
  this.logger.logEvent(event);
57
57
  }
58
- if (process.env.BLE_DEBUG) {
59
- console.log('~~BLE:', event);
60
- }
58
+ console.log('~~BLE:', event);
61
59
  }
62
60
  onStateChange(state) {
63
61
  if (state !== ble_1.BleState.POWERED_ON) {
@@ -540,8 +538,8 @@ class BleInterface extends ble_1.BleInterfaceClass {
540
538
  if (fromCache)
541
539
  this.logEvent({ message: 'adding from Cache', peripheral: peripheral.address });
542
540
  else {
543
- const { id, name, address } = peripheral;
544
- this.logEvent({ message: 'BLE scan: found device', peripheral: { id, name, address } });
541
+ const { id, name, address, advertisement = {} } = peripheral;
542
+ this.logEvent({ message: 'BLE scan: found device', peripheral: { id, name, address, services: advertisement.serviceUuids } });
545
543
  }
546
544
  if (!peripheral || !peripheral.advertisement || !peripheral.advertisement.serviceUuids || peripheral.advertisement.serviceUuids.length === 0)
547
545
  return;
@@ -552,7 +550,11 @@ class BleInterface extends ble_1.BleInterfaceClass {
552
550
  return;
553
551
  peripheralsProcessed.push(peripheral.address);
554
552
  const characteristics = yield this.getCharacteristics(peripheral);
555
- const DeviceClasses = this.getDeviceClasses(peripheral, { profile });
553
+ const connector = this.getConnector(peripheral);
554
+ const connectedPeripheral = connector.getPeripheral();
555
+ const { id, name, address, advertisement = {} } = connectedPeripheral;
556
+ const DeviceClasses = this.getDeviceClasses(connectedPeripheral, { profile });
557
+ this.logEvent({ message: 'BLE scan: device connected', peripheral: { id, name, address, services: advertisement.serviceUuids, classes: DeviceClasses.map(c => c.prototype.constructor.name) } });
556
558
  let cntFound = 0;
557
559
  DeviceClasses.forEach((DeviceClass) => __awaiter(this, void 0, void 0, function* () {
558
560
  if (!DeviceClass)
@@ -22,6 +22,7 @@ export default class BlePeripheralConnector {
22
22
  reconnect(): Promise<void>;
23
23
  onDisconnect(): void;
24
24
  initialize(enforce?: boolean): Promise<void>;
25
+ isSubscribed(characteristicUuid: string): boolean;
25
26
  subscribeAll(callback: (characteristicUuid: string, data: any) => void): Promise<string[]>;
26
27
  subscribe(characteristicUuid: string): Promise<boolean>;
27
28
  onData(characteristicUuid: string, data: any): void;
@@ -31,4 +32,5 @@ export default class BlePeripheralConnector {
31
32
  getState(): string;
32
33
  getCharachteristics(): BleCharacteristic[];
33
34
  getServices(): string[];
35
+ getPeripheral(): BlePeripheral;
34
36
  }
@@ -86,6 +86,9 @@ class BlePeripheralConnector {
86
86
  this.state.isInitialized = this.characteristics !== undefined && this.services !== undefined;
87
87
  });
88
88
  }
89
+ isSubscribed(characteristicUuid) {
90
+ return this.state.subscribed.find(c => c === characteristicUuid || (0, ble_1.uuid)(c) === characteristicUuid || c === (0, ble_1.uuid)(characteristicUuid)) !== undefined;
91
+ }
89
92
  subscribeAll(callback) {
90
93
  return __awaiter(this, void 0, void 0, function* () {
91
94
  const cnt = this.characteristics.length;
@@ -109,7 +112,6 @@ class BlePeripheralConnector {
109
112
  try {
110
113
  yield this.subscribe(c.uuid);
111
114
  subscribed.push(c.uuid);
112
- this.state.subscribed.push(c.uuid);
113
115
  }
114
116
  catch (err) {
115
117
  this.logEvent({ message: 'cannot subscribe', peripheral: this.peripheral.address, characteristic: c.uuid, error: err.message || err });
@@ -127,21 +129,37 @@ class BlePeripheralConnector {
127
129
  });
128
130
  }
129
131
  subscribe(characteristicUuid) {
132
+ this.logEvent({ message: 'subscribe', characteristic: characteristicUuid, characteristics: this.characteristics.map(c => ({ characteristic: c.uuid, uuid: (0, ble_1.uuid)(c.uuid) })) });
130
133
  return new Promise((resolve, reject) => {
131
- const characteristic = this.characteristics.find(c => c.uuid === characteristicUuid || (0, ble_1.uuid)(c.uuid) === characteristicUuid);
132
- if (!characteristic) {
133
- reject(new Error('Characteristic not found'));
134
- return;
134
+ try {
135
+ const characteristic = this.characteristics.find(c => c.uuid === characteristicUuid || (0, ble_1.uuid)(c.uuid) === characteristicUuid);
136
+ this.logEvent({ message: 'subscribe', peripheral: this.peripheral.address, characteristic: characteristic.uuid, uuid: (0, ble_1.uuid)(characteristic.uuid) });
137
+ if (!characteristic) {
138
+ reject(new Error('Characteristic not found'));
139
+ return;
140
+ }
141
+ characteristic.removeAllListeners('data');
142
+ characteristic.on('data', (data, _isNotification) => {
143
+ this.onData(characteristicUuid, data);
144
+ });
145
+ const to = setTimeout(() => {
146
+ this.logEvent({ message: 'subscribe result', characteristic: characteristicUuid, error: 'timeout' });
147
+ reject(new Error('timeout'));
148
+ }, 3000);
149
+ characteristic.subscribe((err) => {
150
+ clearTimeout(to);
151
+ this.logEvent({ message: 'subscribe result', characteristic: characteristicUuid, error: err });
152
+ if (err)
153
+ reject(err);
154
+ else {
155
+ this.state.subscribed.push(characteristicUuid);
156
+ resolve(true);
157
+ }
158
+ });
159
+ }
160
+ catch (err) {
161
+ this.logEvent({ message: 'error', error: err.message || err, stack: err.stack });
135
162
  }
136
- characteristic.on('data', (data, _isNotification) => {
137
- this.onData(characteristicUuid, data);
138
- });
139
- characteristic.subscribe((err) => {
140
- if (err)
141
- reject(err);
142
- else
143
- resolve(true);
144
- });
145
163
  });
146
164
  }
147
165
  onData(characteristicUuid, data) {
@@ -167,5 +185,8 @@ class BlePeripheralConnector {
167
185
  getServices() {
168
186
  return this.services;
169
187
  }
188
+ getPeripheral() {
189
+ return this.peripheral;
190
+ }
170
191
  }
171
192
  exports.default = BlePeripheralConnector;
package/lib/ble/fm.d.ts CHANGED
@@ -45,6 +45,7 @@ export default class BleFitnessMachineDevice extends BleDevice {
45
45
  data: IndoorBikeData;
46
46
  features: IndoorBikeFeatures;
47
47
  hasControl: boolean;
48
+ isCheckingControl: boolean;
48
49
  isCPSubscribed: boolean;
49
50
  crr: number;
50
51
  cw: number;
package/lib/ble/fm.js CHANGED
@@ -86,6 +86,7 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
86
86
  super(props);
87
87
  this.features = undefined;
88
88
  this.hasControl = false;
89
+ this.isCheckingControl = false;
89
90
  this.isCPSubscribed = false;
90
91
  this.crr = 0.0033;
91
92
  this.cw = 0.6;
@@ -99,6 +100,23 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
99
100
  });
100
101
  return __awaiter(this, void 0, void 0, function* () {
101
102
  try {
103
+ const connector = this.ble.getConnector(this.peripheral);
104
+ const isAlreadySubscribed = connector.isSubscribed(FTMS_CP);
105
+ if (!isAlreadySubscribed) {
106
+ connector.removeAllListeners(FTMS_CP);
107
+ let prev = undefined;
108
+ let prevTS = undefined;
109
+ connector.on(FTMS_CP, (uuid, data) => {
110
+ const message = data.toString('hex');
111
+ if (prevTS && prev && message === prev && Date.now() - prevTS < 500) {
112
+ return;
113
+ }
114
+ prevTS = Date.now();
115
+ prev = message;
116
+ this.onData(uuid, data);
117
+ });
118
+ yield connector.subscribe(FTMS_CP);
119
+ }
102
120
  this.logEvent({ message: 'get device info' });
103
121
  yield _super.init.call(this);
104
122
  yield this.getFitnessMachineFeatures();
@@ -300,6 +318,7 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
300
318
  writeFtmsMessage(requestedOpCode, data) {
301
319
  return __awaiter(this, void 0, void 0, function* () {
302
320
  try {
321
+ this.logEvent({ message: 'fmts:write', data: data.toString('hex') });
303
322
  const res = yield this.write(FTMS_CP, data);
304
323
  const responseData = Buffer.from(res);
305
324
  const opCode = responseData.readUInt8(0);
@@ -307,19 +326,25 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
307
326
  const result = responseData.readUInt8(2);
308
327
  if (opCode !== 128 || request !== requestedOpCode)
309
328
  throw new Error('Illegal response ');
329
+ this.logEvent({ message: 'fmts:write result', res, result });
310
330
  return result;
311
331
  }
312
332
  catch (err) {
313
- this.logEvent({ message: 'writeFtmsMessage failed', opCode: requestedOpCode, reason: err.message });
333
+ this.logEvent({ message: 'fmts:write failed', opCode: requestedOpCode, reason: err.message });
314
334
  return 4;
315
335
  }
316
336
  });
317
337
  }
318
338
  requestControl() {
319
339
  return __awaiter(this, void 0, void 0, function* () {
340
+ let to = undefined;
341
+ if (this.isCheckingControl) {
342
+ to = setTimeout(() => { }, 3500);
343
+ }
320
344
  if (this.hasControl)
321
345
  return true;
322
346
  this.logEvent({ message: 'requestControl' });
347
+ this.isCheckingControl = true;
323
348
  const data = Buffer.alloc(1);
324
349
  data.writeUInt8(0, 0);
325
350
  const res = yield this.writeFtmsMessage(0, data);
@@ -329,6 +354,9 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
329
354
  else {
330
355
  this.logEvent({ message: 'requestControl failed' });
331
356
  }
357
+ this.isCheckingControl = false;
358
+ if (to)
359
+ clearTimeout(to);
332
360
  return this.hasControl;
333
361
  });
334
362
  }
@@ -340,6 +368,10 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
340
368
  if (!this.hasControl)
341
369
  return;
342
370
  const hasControl = yield this.requestControl();
371
+ if (!hasControl) {
372
+ this.logEvent({ message: 'setTargetPower failed', reason: 'control is disabled' });
373
+ return true;
374
+ }
343
375
  const data = Buffer.alloc(3);
344
376
  data.writeUInt8(5, 0);
345
377
  data.writeInt16LE(Math.round(power), 1);
@@ -350,7 +382,8 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
350
382
  setSlope(slope) {
351
383
  return __awaiter(this, void 0, void 0, function* () {
352
384
  this.logEvent({ message: 'setSlope', slope });
353
- if (!this.hasControl)
385
+ const hasControl = yield this.requestControl();
386
+ if (!hasControl)
354
387
  return;
355
388
  const { windSpeed, crr, cw } = this;
356
389
  return yield this.setIndoorBikeSimulation(windSpeed, slope, crr, cw);
@@ -582,7 +615,7 @@ class FmAdapter extends Device_1.default {
582
615
  break;
583
616
  }
584
617
  }
585
- this.device.requestControl();
618
+ yield this.device.requestControl();
586
619
  const startRequest = this.getCyclingMode().getBikeInitRequest();
587
620
  yield this.sendUpdate(startRequest);
588
621
  bleDevice.on('data', (data) => {
@@ -36,7 +36,7 @@ const ble_interface_1 = __importDefault(require("./ble-interface"));
36
36
  const Device_1 = require("../Device");
37
37
  const gd_eventlog_1 = require("gd-eventlog");
38
38
  const fm_1 = __importStar(require("./fm"));
39
- const WAHOO_ADVANCED_FTMS = 'a026e005';
39
+ const WAHOO_ADVANCED_FTMS = 'a026e00b';
40
40
  const WAHOO_ADVANCED_TRAINER_CP = 'a026e037';
41
41
  const cwABike = {
42
42
  race: 0.35,
@@ -60,6 +60,23 @@ class WahooAdvancedFitnessMachineDevice extends fm_1.default {
60
60
  });
61
61
  return __awaiter(this, void 0, void 0, function* () {
62
62
  try {
63
+ const connector = this.ble.getConnector(this.peripheral);
64
+ const isAlreadySubscribed = connector.isSubscribed(WAHOO_ADVANCED_TRAINER_CP);
65
+ if (!isAlreadySubscribed) {
66
+ connector.removeAllListeners(WAHOO_ADVANCED_TRAINER_CP);
67
+ let prev = undefined;
68
+ let prevTS = undefined;
69
+ connector.on(WAHOO_ADVANCED_TRAINER_CP, (uuid, data) => {
70
+ const message = data.toString('hex');
71
+ if (prevTS && prev && message === prev && Date.now() - prevTS < 500) {
72
+ return;
73
+ }
74
+ prevTS = Date.now();
75
+ prev = message;
76
+ this.onData(uuid, data);
77
+ });
78
+ yield connector.subscribe(WAHOO_ADVANCED_TRAINER_CP);
79
+ }
63
80
  this.logEvent({ message: 'get device info' });
64
81
  yield _super.init.call(this);
65
82
  this.logEvent({ message: 'device info', deviceInfo: this.deviceInfo, features: this.features });
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const CyclingMode_1 = require("../CyclingMode");
7
7
  const calculations_1 = __importDefault(require("../calculations"));
8
8
  const power_base_1 = __importDefault(require("../modes/power-base"));
9
+ const MIN_SPEED = 10;
9
10
  const config = {
10
11
  name: "ERG",
11
12
  description: "Calculates speed based on power and slope. Power is either set by workout or calculated based on gear and cadence",
@@ -147,9 +148,15 @@ class ERGCyclingMode extends power_base_1.default {
147
148
  const m = this.getWeight();
148
149
  const t = this.getTimeSinceLastUpdate();
149
150
  const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t, { bikeType });
150
- data.speed = speed;
151
+ if (power === 0 && speed < MIN_SPEED) {
152
+ data.speed = Math.round(prevData.speed - 1) < 0 ? 0 : Math.round(prevData.speed - 1);
153
+ data.distanceInternal = distanceInternal + data.speed / 3.6 * t;
154
+ }
155
+ else {
156
+ data.speed = speed;
157
+ data.distanceInternal = distanceInternal + distance;
158
+ }
151
159
  data.power = Math.round(power);
152
- data.distanceInternal = distanceInternal + distance;
153
160
  data.slope = slope;
154
161
  data.pedalRpm = rpm;
155
162
  data.gear = gear;
@@ -62,8 +62,8 @@ class PowerMeterCyclingMode extends power_base_1.default {
62
62
  data.distanceInternal = distanceInternal + data.speed / 3.6 * t;
63
63
  }
64
64
  else {
65
- data.speed = (power === 0 && speed < MIN_SPEED) ? 0 : speed;
66
- data.distanceInternal = (power === 0 && speed < MIN_SPEED) ? distanceInternal : distanceInternal + distance;
65
+ data.speed = speed;
66
+ data.distanceInternal = distanceInternal + distance;
67
67
  }
68
68
  if (props.log)
69
69
  this.logger.logEvent({ message: "updateData result", data, bikeData, prevSpeed: prevData.speed, stopped: speed < MIN_SPEED });
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const gd_eventlog_1 = require("gd-eventlog");
7
7
  const CyclingMode_1 = require("../CyclingMode");
8
8
  const power_base_1 = __importDefault(require("./power-base"));
9
+ const MIN_SPEED = 10;
9
10
  const config = {
10
11
  name: "Simulator",
11
12
  description: "Simulates a ride with constant speed or power output",
@@ -106,6 +107,14 @@ class SimulatorCyclingMode extends power_base_1.default {
106
107
  speed = res.speed;
107
108
  distance = res.distance;
108
109
  }
110
+ if (power === 0 && speed < MIN_SPEED) {
111
+ data.speed = Math.round(prevData.speed - 1) < 0 ? 0 : Math.round(prevData.speed - 1);
112
+ data.distanceInternal = distanceInternal + data.speed / 3.6 * t;
113
+ }
114
+ else {
115
+ data.speed = speed;
116
+ data.distanceInternal = distanceInternal + distance;
117
+ }
109
118
  data.speed = speed;
110
119
  data.power = Math.round(power);
111
120
  data.distanceInternal = distanceInternal + distance;
@@ -218,6 +218,8 @@ class Simulator extends Device_1.default {
218
218
  return;
219
219
  }
220
220
  const prevDist = this.data.distanceInternal;
221
+ const d = this.data;
222
+ const prevTime = d.deviceTime;
221
223
  this.data = this.getCyclingMode().updateData(this.data);
222
224
  let data = {
223
225
  speed: this.data.speed,
@@ -230,6 +232,9 @@ class Simulator extends Device_1.default {
230
232
  deviceTime: (Date.now() - this.startTS) / 1000,
231
233
  deviceDistanceCounter: this.data.distanceInternal
232
234
  };
235
+ if (this.isBot) {
236
+ this.logger.logEvent(Object.assign({ message: 'Coach update', prevDist, prevTime }, data));
237
+ }
233
238
  this.paused = (this.data.speed === 0);
234
239
  if (this.ignoreHrm)
235
240
  delete data.heartrate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "1.4.63",
3
+ "version": "1.4.66",
4
4
  "dependencies": {
5
5
  "@serialport/parser-byte-length": "^9.0.1",
6
6
  "@serialport/parser-delimiter": "^9.0.1",