incyclist-devices 3.0.16 → 3.0.18

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.
@@ -29,7 +29,8 @@ class BleAdapter extends adpater_js_1.default {
29
29
  if (settings.name?.match(/\d/g) || settings.address === undefined)
30
30
  return this.getName();
31
31
  else {
32
- const addressHash = settings.id?.slice(-4)?.toUpperCase() ?? (settings.address?.substring(0, 2) ?? '') + (settings.address?.slice(-2) ?? '');
32
+ const id = (settings.id ?? settings.address ?? '').replace(/[:\-]/g, '');
33
+ const addressHash = id.length > 4 ? id.slice(-4).toUpperCase() : id.toUpperCase();
33
34
  return `${this.getName()} ${addressHash}`;
34
35
  }
35
36
  }
@@ -267,7 +267,7 @@ class BleInterface extends node_events_1.EventEmitter {
267
267
  return new peripheral_js_1.BlePeripheral(announcement);
268
268
  }
269
269
  createPeripheralFromSettings(settings) {
270
- const info = this.getAll().find(a => a.service?.name === settings.name || a.service?.peripheral?.address === settings.address);
270
+ const info = this.getAll().find(a => a.service?.peripheral?.address === settings.address);
271
271
  if (!info?.service)
272
272
  return null;
273
273
  return this.createPeripheral(info.service);
@@ -281,7 +281,7 @@ class BleInterface extends node_events_1.EventEmitter {
281
281
  if (!wasDiscovering)
282
282
  this.startPeripheralScan();
283
283
  const onDevice = (device) => {
284
- if (device.name === settings.name || device.address === settings.address) {
284
+ if (device.address === settings.address) {
285
285
  const peripheral = this.createPeripheralFromSettings(device);
286
286
  if (peripheral) {
287
287
  this.off('device', onDevice);
@@ -588,7 +588,7 @@ class BleInterface extends node_events_1.EventEmitter {
588
588
  return supported.length > 0;
589
589
  }
590
590
  find(service) {
591
- return this.services.find(a => a.service.name === service.name && a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
591
+ return this.services.find(a => a.service.name === service.name && a.service?.peripheral?.address && a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
592
592
  }
593
593
  getAll() {
594
594
  return this.services.filter(a => a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
@@ -50,6 +50,7 @@ class BlePeripheral {
50
50
  if (this.connectPromise !== undefined) {
51
51
  return this.connectPromise.then(() => this.connected);
52
52
  }
53
+ let address;
53
54
  this.connectPromise = new Promise((done) => {
54
55
  const peripheral = this.getPeripheral();
55
56
  this.connected = false;
@@ -60,7 +61,8 @@ class BlePeripheral {
60
61
  if (!this.ble.isConnected()) {
61
62
  return done();
62
63
  }
63
- this.logEvent({ message: 'connect peripheral', address: peripheral.address });
64
+ address = peripheral.address;
65
+ this.logEvent({ message: 'connect peripheral', address });
64
66
  peripheral.connectAsync().then(() => {
65
67
  this.ble.registerConnected(this, peripheralId);
66
68
  peripheral.once('disconnect', () => { this.onPeripheralDisconnect(); });
@@ -75,11 +77,12 @@ class BlePeripheral {
75
77
  });
76
78
  await this.connectPromise;
77
79
  delete this.connectPromise;
80
+ this.logEvent({ message: 'connect peripheral result', address, connected: this.connected });
78
81
  return this.connected;
79
82
  }
80
83
  async disconnect(connectionLost = false) {
81
84
  this.disconnecting = true;
82
- if (this.isConnected()) {
85
+ if (this.isConnected() || connectionLost) {
83
86
  await this.unsubscribeAll(connectionLost);
84
87
  }
85
88
  Object.keys(this.characteristics).forEach(uuid => {
@@ -101,13 +104,14 @@ class BlePeripheral {
101
104
  .catch(() => { });
102
105
  }
103
106
  peripheral.removeAllListeners();
107
+ this.ble.unregisterConnected(peripheral.id);
104
108
  }
105
109
  else {
106
110
  delete this.onDisconnectHandler;
107
111
  }
108
- this.ble.unregisterConnected(peripheral.id);
109
112
  this.connected = false;
110
113
  this.disconnecting = false;
114
+ this.logEvent({ message: 'peripheral disconnect completed', address: peripheral.address });
111
115
  return !this.connected;
112
116
  }
113
117
  isConnected() {
@@ -200,8 +204,12 @@ class BlePeripheral {
200
204
  }
201
205
  async subscribe(characteristicUUID, callback) {
202
206
  try {
203
- if (this.disconnecting || !this.connected)
207
+ if (this.disconnecting || !this.connected) {
208
+ this.logEvent({ message: 'peripheral subscribe failed', uuid: characteristicUUID, reason: 'not connected',
209
+ disconnecting: this.disconnecting, connected: this.connected
210
+ });
204
211
  return false;
212
+ }
205
213
  const uuid = (0, utils_js_2.beautifyUUID)(characteristicUUID);
206
214
  const onData = (data, isNotify) => {
207
215
  try {
@@ -211,6 +219,7 @@ class BlePeripheral {
211
219
  };
212
220
  const subscription = this.subscribed.find(s => s.uuid === uuid);
213
221
  if (subscription) {
222
+ this.logEvent({ message: 'peripheral subscribe skipped', uuid: characteristicUUID, reason: 'already subscribed' });
214
223
  const c = this.getRawCharacteristic(characteristicUUID);
215
224
  if (c) {
216
225
  c.off('data', onData);
@@ -220,11 +229,13 @@ class BlePeripheral {
220
229
  }
221
230
  let c = await this.queryRawCharacteristic(characteristicUUID).catch(() => null);
222
231
  if (!c) {
232
+ this.logEvent({ message: 'peripheral subscribe failed', uuid: characteristicUUID, reason: 'not found' });
223
233
  return false;
224
234
  }
225
235
  return new Promise((resolve, reject) => {
226
236
  const info = this.subscribed.find(s => s.uuid === characteristicUUID);
227
237
  if (info) {
238
+ this.logEvent({ message: 'peripheral subscribe skipped', uuid: characteristicUUID, reason: 'already subscribed' });
228
239
  return Promise.resolve(true);
229
240
  }
230
241
  this.logEvent({ message: 'subscribe request', address: this.getPeripheral().address, characteristic: uuid, success: true });
@@ -287,6 +298,8 @@ class BlePeripheral {
287
298
  }
288
299
  }
289
300
  async subscribeSelected(characteristics, callback) {
301
+ const uuids = characteristics != null ? characteristics.map(c => (0, utils_js_2.beautifyUUID)(c)).join('|') : 'none';
302
+ this.logEvent({ message: 'peripheral subscribe selected', uuids });
290
303
  if (!this.discoveredServiceUUIds) {
291
304
  try {
292
305
  await this.discoverServices();
@@ -349,6 +362,7 @@ class BlePeripheral {
349
362
  }
350
363
  }
351
364
  async subscribeAll(callback) {
365
+ this.logEvent({ message: 'peripheral subscribe all' });
352
366
  if (!this.discoveredServiceUUIds) {
353
367
  try {
354
368
  await this.discoverServices();
@@ -360,6 +374,7 @@ class BlePeripheral {
360
374
  return success;
361
375
  }
362
376
  async unsubscribeAll(connectionLost = false) {
377
+ this.logEvent({ message: 'peripheral unsubscribe all', connectionLost });
363
378
  if (connectionLost) {
364
379
  this.subscribed = [];
365
380
  return;
@@ -235,11 +235,9 @@ class BleFmAdapter extends adapter_js_1.default {
235
235
  async checkCapabilities() {
236
236
  const before = this.capabilities.join(',');
237
237
  const sensor = this.getSensor();
238
- let updateRequired = false;
239
238
  if (!sensor.features) {
240
239
  try {
241
240
  await sensor.getFitnessMachineFeatures();
242
- updateRequired = true;
243
241
  }
244
242
  catch (err) {
245
243
  this.logEvent({ message: 'error getting fitness machine features', device: this.getName(), interface: this.getInterface(), error: err });
@@ -252,10 +250,6 @@ class BleFmAdapter extends adapter_js_1.default {
252
250
  if (before !== after) {
253
251
  this.logEvent({ message: 'device capabilities updated', name: this.getSettings().name, interface: this.getSettings().interface, capabilities: this.capabilities });
254
252
  this.emit('device-info', this.getSettings(), { capabilities: this.capabilities });
255
- updateRequired = true;
256
- }
257
- if (updateRequired) {
258
- this.updateCyclingModeConfig();
259
253
  }
260
254
  }
261
255
  updateCapabilitiesFromFeatures(features) {
@@ -11,6 +11,7 @@ const index_js_1 = require("../../../types/index.js");
11
11
  class ZwiftPlayAdapter extends adapter_js_1.default {
12
12
  static INCYCLIST_PROFILE_NAME = 'Controller';
13
13
  static CAPABILITIES = [index_js_1.IncyclistCapability.AppControl];
14
+ keyPressedHandler;
14
15
  constructor(settings, props) {
15
16
  super(settings, props);
16
17
  this.logger = new gd_eventlog_1.EventLogger('ZwiftPlay');
@@ -25,9 +26,13 @@ class ZwiftPlayAdapter extends adapter_js_1.default {
25
26
  let connected = await super.startSensor();
26
27
  if (connected) {
27
28
  const sensor = this.getSensor();
28
- sensor.on('key-pressed', (event) => {
29
- this.emit('key-pressed', this.getSettings(), event);
30
- });
29
+ if (this.keyPressedHandler) {
30
+ sensor.off('key-pressed', this.keyPressedHandler);
31
+ }
32
+ else {
33
+ this.keyPressedHandler = this.onKeyPressed.bind(this);
34
+ }
35
+ sensor.on('key-pressed', this.keyPressedHandler);
31
36
  }
32
37
  return connected;
33
38
  }
@@ -36,10 +41,18 @@ class ZwiftPlayAdapter extends adapter_js_1.default {
36
41
  return false;
37
42
  }
38
43
  }
44
+ onKeyPressed(event) {
45
+ this.emit('key-pressed', this.getSettings(), event);
46
+ }
47
+ isEqual(settings) {
48
+ const equal = super.isEqual(settings) &&
49
+ settings.address == this.settings.address;
50
+ return equal;
51
+ }
39
52
  isSame(adapter) {
40
53
  if (!(adapter instanceof ZwiftPlayAdapter))
41
54
  return false;
42
- return this.isEqual(adapter.settings);
55
+ return this.isEqual(adapter.settings) && this.getUniqueName() === adapter.getUniqueName();
43
56
  }
44
57
  updateSensor(peripheral) {
45
58
  this.device = new sensor_js_1.BleZwiftPlaySensor(peripheral, { logger: this.logger });
@@ -47,6 +60,22 @@ class ZwiftPlayAdapter extends adapter_js_1.default {
47
60
  getProfile() {
48
61
  return ZwiftPlayAdapter.INCYCLIST_PROFILE_NAME;
49
62
  }
63
+ getUniqueName() {
64
+ return this.getName();
65
+ }
66
+ getName() {
67
+ const settings = this.settings;
68
+ let name = settings.name;
69
+ if (settings.name === 'Zwift-Ride') {
70
+ if (this.device.getDeviceType() === 'ride-left')
71
+ name = name + '-L';
72
+ if (this.device.getDeviceType() === 'ride-right')
73
+ name = name + '-R';
74
+ }
75
+ const id = (settings.id ?? settings.address ?? '').replace(/[:\-]/g, '');
76
+ const addressHash = id.length > 4 ? id.slice(-4).toUpperCase() : id.toUpperCase();
77
+ return `${name} ${addressHash}`;
78
+ }
50
79
  getDisplayName() {
51
80
  return this.getName();
52
81
  }
@@ -67,6 +67,42 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
67
67
  getRequiredCharacteristics() {
68
68
  return ['00000002-19ca-4651-86e5-fa29dcdd09d1', '00000004-19ca-4651-86e5-fa29dcdd09d1'];
69
69
  }
70
+ getDeviceType() {
71
+ if (this.deviceType)
72
+ return this.deviceType;
73
+ if (this.isFM) {
74
+ this.deviceType = 'hub';
75
+ }
76
+ else if (this.peripheral?.getManufacturerData) {
77
+ const manufacturerData = this.getManufacturerData();
78
+ if (manufacturerData?.startsWith('4a09')) {
79
+ const typeVal = Number('0x' + manufacturerData.substring(2, 4));
80
+ if (typeVal === 9) {
81
+ this.deviceType = 'click';
82
+ this.encrypted = false;
83
+ }
84
+ else if (typeVal === 2) {
85
+ this.deviceType = 'right';
86
+ }
87
+ else if (typeVal === 3) {
88
+ this.deviceType = 'left';
89
+ }
90
+ else if (typeVal === 7) {
91
+ this.deviceType = 'ride-right';
92
+ }
93
+ else if (typeVal === 8) {
94
+ this.deviceType = 'ride-left';
95
+ }
96
+ }
97
+ }
98
+ console.log('# [ZwiftPlay] device type ', this.deviceType, this.peripheral?.getInfo().name, this.peripheral?.getInfo().address);
99
+ if (!this.deviceType && !this.encryptedSupported()) {
100
+ this.deviceType = 'click';
101
+ this.encrypted = false;
102
+ }
103
+ this.deviceType = this.deviceType ?? 'click';
104
+ return this.deviceType;
105
+ }
70
106
  onData(characteristic, data, isNotify) {
71
107
  const uuid = (0, utils_js_1.beautifyUUID)(characteristic).toLowerCase();
72
108
  if (uuid === '00000002-19ca-4651-86e5-fa29dcdd09d1') {
@@ -154,7 +190,6 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
154
190
  }
155
191
  onMeasurement(d) {
156
192
  const data = Buffer.from(d);
157
- this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
158
193
  if (data?.length < 1) {
159
194
  console.log('Invalid click measurement data', data.toString('hex'));
160
195
  return false;
@@ -182,9 +217,10 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
182
217
  else if (type === 0x3c) {
183
218
  this.onDeviceInformation(message);
184
219
  }
220
+ else if (type === 0x15) {
221
+ }
185
222
  else {
186
- console.log('Unknown click measurement type', type, message.toString('hex'));
187
- this.emit('data', { raw: data.toString('hex') });
223
+ this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
188
224
  }
189
225
  return true;
190
226
  }
@@ -293,17 +329,27 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
293
329
  try {
294
330
  const data = zwift_hub_js_1.RideKeyPadStatus.fromBinary(m);
295
331
  const buttonNames = new Map([
332
+ [0x01, 'left'],
333
+ [0x02, 'up'],
334
+ [0x04, 'right'],
335
+ [0x08, 'down'],
296
336
  [0x10, 'a'],
297
337
  [0x20, 'b'],
298
338
  [0x40, 'y'],
299
339
  [0x80, 'z'],
340
+ [0x0100, 'l-shift-up'],
341
+ [0x0200, 'l-shift-down'],
300
342
  [0x1000, 'r-shift-up'],
301
343
  [0x2000, 'r-shift-down'],
302
- [0x4000, 'ride-on'],
303
- [0x8000, 'r-power']
344
+ [0x0400, 'l-power-up'],
345
+ [0x4000, 'r-power-up'],
346
+ [0x0800, 'l-power'],
347
+ [0x8000, 'r-power'],
304
348
  ]);
305
349
  const buttonMap = data.buttonMap ?? 0;
306
350
  const currentPresses = new Set();
351
+ const address = this.peripheral?.getInfo()?.address;
352
+ const name = this.peripheral?.getInfo()?.name;
307
353
  buttonNames.forEach((name, bit) => {
308
354
  const isPressed = (buttonMap & bit) === 0;
309
355
  if (isPressed) {
@@ -318,19 +364,11 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
318
364
  if (!currentPresses.has(bit) && state.pressed) {
319
365
  const keyName = buttonNames.get(bit);
320
366
  const duration = Date.now() - state.timestamp;
367
+ this.logEvent({ message: 'key pressed', key: keyName, name, address, duration, deviceType: this.deviceType });
321
368
  this.emit('key-pressed', { key: keyName, duration, deviceType: this.deviceType });
322
369
  this.rideKeyPadStates.set(bit, { pressed: false, timestamp: Date.now() });
323
370
  }
324
371
  });
325
- const pressedButtons = Array.from(currentPresses).map(bit => ({
326
- bit: `0x${bit.toString(16).padStart(8, '0')}`,
327
- name: buttonNames.get(bit)
328
- }));
329
- this.logEvent({
330
- message: 'ride keypad status received',
331
- buttonMap: `0x${buttonMap.toString(16)}`,
332
- buttons: pressedButtons
333
- });
334
372
  }
335
373
  catch (err) {
336
374
  this.logEvent({ message: 'Error', fn: 'onRideKeyPadStatus', error: err.message, stack: err.stack });
@@ -363,6 +401,8 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
363
401
  }
364
402
  onClickButtonMessage(d) {
365
403
  try {
404
+ const address = this.peripheral?.getInfo()?.address;
405
+ const name = this.peripheral?.getInfo()?.name;
366
406
  const message = Buffer.from(d);
367
407
  const messageStr = message.toString('hex');
368
408
  if (messageStr === this.prevClickMessage) {
@@ -373,6 +413,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
373
413
  const prev = { ...this.upState };
374
414
  this.upState = { pressed: false, timestamp: Date.now() };
375
415
  if (prev.pressed) {
416
+ this.logEvent({ message: 'key pressed', key: 'up', name, address, duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
376
417
  this.emit('key-pressed', { key: 'up', duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
377
418
  }
378
419
  }
@@ -383,6 +424,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
383
424
  const prev = { ...this.downState };
384
425
  this.downState = { pressed: false, timestamp: Date.now() };
385
426
  if (prev.pressed) {
427
+ this.logEvent({ message: 'key pressed', key: 'down', name, address, duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
386
428
  this.emit('key-pressed', { key: 'down', duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
387
429
  }
388
430
  }
@@ -481,7 +523,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
481
523
  this.deviceType = 'hub';
482
524
  this.encrypted = false;
483
525
  }
484
- else if (this.peripheral.getManufacturerData) {
526
+ else if (this.peripheral?.getManufacturerData) {
485
527
  manufacturerData = this.getManufacturerData();
486
528
  if (manufacturerData?.startsWith('4a09')) {
487
529
  const typeVal = Number('0x' + manufacturerData.substring(2, 4));
@@ -495,6 +537,12 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
495
537
  else if (typeVal === 3) {
496
538
  this.deviceType = 'left';
497
539
  }
540
+ else if (typeVal === 7) {
541
+ this.deviceType = 'ride-right';
542
+ }
543
+ else if (typeVal === 8) {
544
+ this.deviceType = 'ride-left';
545
+ }
498
546
  }
499
547
  }
500
548
  if (!this.deviceType && !this.encryptedSupported()) {
@@ -502,7 +550,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
502
550
  this.encrypted = false;
503
551
  }
504
552
  this.deviceType = this.deviceType ?? 'click';
505
- this.logEvent({ message: 'Play protocol pairing info', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData });
553
+ this.logEvent({ message: 'Play protocol pairing info', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData: this.getManufacturerData() });
506
554
  let message;
507
555
  if (this.isFM) {
508
556
  message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x02, 0x01])]);
@@ -519,7 +567,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
519
567
  this.logEvent({ message: `send rideOn` });
520
568
  await this.write((0, utils_js_1.fullUUID)('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
521
569
  this.isHubServicePaired = true;
522
- this.logEvent({ message: 'pairing done', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData });
570
+ this.logEvent({ message: 'pairing done', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData: this.getManufacturerData() });
523
571
  return true;
524
572
  }
525
573
  catch (err) {
@@ -530,6 +578,15 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
530
578
  }
531
579
  reset() {
532
580
  }
581
+ getManufacturerData() {
582
+ const data = this.peripheral?.getManufacturerData?.();
583
+ if (typeof data === 'string')
584
+ return data;
585
+ if (Buffer.isBuffer(data)) {
586
+ return data.toString('hex');
587
+ }
588
+ return undefined;
589
+ }
533
590
  getCrypto() {
534
591
  let crypto = index_js_1.BindingsFactory.getInstance()?.getBinding()?.crypto;
535
592
  if (!crypto)
@@ -559,14 +616,5 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
559
616
  delete this.prevHubSettings;
560
617
  this.rideKeyPadStates = new Map();
561
618
  }
562
- getManufacturerData() {
563
- const data = this.peripheral.getManufacturerData();
564
- if (typeof data === 'string')
565
- return data;
566
- if (Buffer.isBuffer(data)) {
567
- return data.toString('hex');
568
- }
569
- return undefined;
570
- }
571
619
  }
572
620
  exports.BleZwiftPlaySensor = BleZwiftPlaySensor;
@@ -99,34 +99,40 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
99
99
  if (startGearIdx !== -1) {
100
100
  config.properties.splice(startGearIdx, 1);
101
101
  }
102
+ try {
103
+ const device = this.adapter?.getName();
104
+ this.logger.logEvent({ message: 'reset config', device });
105
+ }
106
+ catch { }
102
107
  }
103
108
  getConfig() {
104
109
  const config = super.getConfig();
105
- const virtShiftEnabled = this.getFeatureToogle().has('VirtualShifting');
106
110
  let virtshift = config.properties.find(p => p.key === 'virtshift');
107
111
  let startGear = config.properties.find(p => p.key === 'startGear');
108
- if (!virtshift && !this.adapter?.supportsVirtualShifting()) {
109
- const options = virtShiftEnabled ? [
110
- 'Disabled',
111
- { key: 'Incyclist', display: 'App only (beta)' },
112
- { key: 'Mixed', display: 'App + Bike' }
113
- ] :
114
- [
112
+ if (!virtshift) {
113
+ const device = this.adapter?.getName();
114
+ const supportsZwift = this.adapter?.supportsVirtualShifting();
115
+ this.logger.logEvent({ message: 'prepare gear settings config', device, supportsZwift });
116
+ if (supportsZwift) {
117
+ const options = [
115
118
  'Disabled',
116
- { key: 'Mixed', display: 'Enabled' }
119
+ { key: 'Incyclist', display: 'App only (beta)' },
120
+ { key: 'Mixed', display: 'App + Bike' },
121
+ { key: 'SmartTrainer', display: 'SmartTreiner (beta)' }
117
122
  ];
118
- virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: types_js_1.CyclingModeProperyType.SingleSelect, options, default: 'Disabled' };
119
- config.properties.push(virtshift);
120
- }
121
- if (!virtshift && virtShiftEnabled && this.adapter?.supportsVirtualShifting()) {
122
- const options = [
123
- 'Disabled',
124
- { key: 'Incyclist', display: 'App only (beta)' },
125
- { key: 'Mixed', display: 'App + Bike' },
126
- { key: 'SmartTrainer', display: 'SmartTreiner (beta)' }
127
- ];
128
- virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: types_js_1.CyclingModeProperyType.SingleSelect, options, default: 'Mixed' };
129
- config.properties.push(virtshift);
123
+ virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: types_js_1.CyclingModeProperyType.SingleSelect, options, default: 'Mixed' };
124
+ config.properties.push(virtshift);
125
+ }
126
+ else {
127
+ const options = [
128
+ 'Disabled',
129
+ { key: 'Incyclist', display: 'App only (beta)' },
130
+ { key: 'Mixed', display: 'App + Bike' }
131
+ ];
132
+ virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: types_js_1.CyclingModeProperyType.SingleSelect, options, default: 'Disabled' };
133
+ config.properties.push(virtshift);
134
+ }
135
+ this.logger.logEvent({ message: 'gear settings config', config: config.properties });
130
136
  }
131
137
  if (virtshift && !startGear) {
132
138
  startGear = { key: 'startGear', name: 'Initial Gear', description: 'Initial Gear', type: types_js_1.CyclingModeProperyType.Integer, default: 12, min: 1, max: 24, condition: (s) => s?.virtshift === 'Incyclist' || s?.virtshift === 'SmartTrainer' };
@@ -24,7 +24,8 @@ export default class BleAdapter extends IncyclistDevice {
24
24
  if (settings.name?.match(/\d/g) || settings.address === undefined)
25
25
  return this.getName();
26
26
  else {
27
- const addressHash = settings.id?.slice(-4)?.toUpperCase() ?? (settings.address?.substring(0, 2) ?? '') + (settings.address?.slice(-2) ?? '');
27
+ const id = (settings.id ?? settings.address ?? '').replace(/[:\-]/g, '');
28
+ const addressHash = id.length > 4 ? id.slice(-4).toUpperCase() : id.toUpperCase();
28
29
  return `${this.getName()} ${addressHash}`;
29
30
  }
30
31
  }
@@ -264,7 +264,7 @@ export class BleInterface extends EventEmitter {
264
264
  return new BlePeripheral(announcement);
265
265
  }
266
266
  createPeripheralFromSettings(settings) {
267
- const info = this.getAll().find(a => a.service?.name === settings.name || a.service?.peripheral?.address === settings.address);
267
+ const info = this.getAll().find(a => a.service?.peripheral?.address === settings.address);
268
268
  if (!info?.service)
269
269
  return null;
270
270
  return this.createPeripheral(info.service);
@@ -278,7 +278,7 @@ export class BleInterface extends EventEmitter {
278
278
  if (!wasDiscovering)
279
279
  this.startPeripheralScan();
280
280
  const onDevice = (device) => {
281
- if (device.name === settings.name || device.address === settings.address) {
281
+ if (device.address === settings.address) {
282
282
  const peripheral = this.createPeripheralFromSettings(device);
283
283
  if (peripheral) {
284
284
  this.off('device', onDevice);
@@ -585,7 +585,7 @@ export class BleInterface extends EventEmitter {
585
585
  return supported.length > 0;
586
586
  }
587
587
  find(service) {
588
- return this.services.find(a => a.service.name === service.name && a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
588
+ return this.services.find(a => a.service.name === service.name && a.service?.peripheral?.address && a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
589
589
  }
590
590
  getAll() {
591
591
  return this.services.filter(a => a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
@@ -47,6 +47,7 @@ export class BlePeripheral {
47
47
  if (this.connectPromise !== undefined) {
48
48
  return this.connectPromise.then(() => this.connected);
49
49
  }
50
+ let address;
50
51
  this.connectPromise = new Promise((done) => {
51
52
  const peripheral = this.getPeripheral();
52
53
  this.connected = false;
@@ -57,7 +58,8 @@ export class BlePeripheral {
57
58
  if (!this.ble.isConnected()) {
58
59
  return done();
59
60
  }
60
- this.logEvent({ message: 'connect peripheral', address: peripheral.address });
61
+ address = peripheral.address;
62
+ this.logEvent({ message: 'connect peripheral', address });
61
63
  peripheral.connectAsync().then(() => {
62
64
  this.ble.registerConnected(this, peripheralId);
63
65
  peripheral.once('disconnect', () => { this.onPeripheralDisconnect(); });
@@ -72,11 +74,12 @@ export class BlePeripheral {
72
74
  });
73
75
  await this.connectPromise;
74
76
  delete this.connectPromise;
77
+ this.logEvent({ message: 'connect peripheral result', address, connected: this.connected });
75
78
  return this.connected;
76
79
  }
77
80
  async disconnect(connectionLost = false) {
78
81
  this.disconnecting = true;
79
- if (this.isConnected()) {
82
+ if (this.isConnected() || connectionLost) {
80
83
  await this.unsubscribeAll(connectionLost);
81
84
  }
82
85
  Object.keys(this.characteristics).forEach(uuid => {
@@ -98,13 +101,14 @@ export class BlePeripheral {
98
101
  .catch(() => { });
99
102
  }
100
103
  peripheral.removeAllListeners();
104
+ this.ble.unregisterConnected(peripheral.id);
101
105
  }
102
106
  else {
103
107
  delete this.onDisconnectHandler;
104
108
  }
105
- this.ble.unregisterConnected(peripheral.id);
106
109
  this.connected = false;
107
110
  this.disconnecting = false;
111
+ this.logEvent({ message: 'peripheral disconnect completed', address: peripheral.address });
108
112
  return !this.connected;
109
113
  }
110
114
  isConnected() {
@@ -197,8 +201,12 @@ export class BlePeripheral {
197
201
  }
198
202
  async subscribe(characteristicUUID, callback) {
199
203
  try {
200
- if (this.disconnecting || !this.connected)
204
+ if (this.disconnecting || !this.connected) {
205
+ this.logEvent({ message: 'peripheral subscribe failed', uuid: characteristicUUID, reason: 'not connected',
206
+ disconnecting: this.disconnecting, connected: this.connected
207
+ });
201
208
  return false;
209
+ }
202
210
  const uuid = beautifyUUID(characteristicUUID);
203
211
  const onData = (data, isNotify) => {
204
212
  try {
@@ -208,6 +216,7 @@ export class BlePeripheral {
208
216
  };
209
217
  const subscription = this.subscribed.find(s => s.uuid === uuid);
210
218
  if (subscription) {
219
+ this.logEvent({ message: 'peripheral subscribe skipped', uuid: characteristicUUID, reason: 'already subscribed' });
211
220
  const c = this.getRawCharacteristic(characteristicUUID);
212
221
  if (c) {
213
222
  c.off('data', onData);
@@ -217,11 +226,13 @@ export class BlePeripheral {
217
226
  }
218
227
  let c = await this.queryRawCharacteristic(characteristicUUID).catch(() => null);
219
228
  if (!c) {
229
+ this.logEvent({ message: 'peripheral subscribe failed', uuid: characteristicUUID, reason: 'not found' });
220
230
  return false;
221
231
  }
222
232
  return new Promise((resolve, reject) => {
223
233
  const info = this.subscribed.find(s => s.uuid === characteristicUUID);
224
234
  if (info) {
235
+ this.logEvent({ message: 'peripheral subscribe skipped', uuid: characteristicUUID, reason: 'already subscribed' });
225
236
  return Promise.resolve(true);
226
237
  }
227
238
  this.logEvent({ message: 'subscribe request', address: this.getPeripheral().address, characteristic: uuid, success: true });
@@ -284,6 +295,8 @@ export class BlePeripheral {
284
295
  }
285
296
  }
286
297
  async subscribeSelected(characteristics, callback) {
298
+ const uuids = characteristics != null ? characteristics.map(c => beautifyUUID(c)).join('|') : 'none';
299
+ this.logEvent({ message: 'peripheral subscribe selected', uuids });
287
300
  if (!this.discoveredServiceUUIds) {
288
301
  try {
289
302
  await this.discoverServices();
@@ -346,6 +359,7 @@ export class BlePeripheral {
346
359
  }
347
360
  }
348
361
  async subscribeAll(callback) {
362
+ this.logEvent({ message: 'peripheral subscribe all' });
349
363
  if (!this.discoveredServiceUUIds) {
350
364
  try {
351
365
  await this.discoverServices();
@@ -357,6 +371,7 @@ export class BlePeripheral {
357
371
  return success;
358
372
  }
359
373
  async unsubscribeAll(connectionLost = false) {
374
+ this.logEvent({ message: 'peripheral unsubscribe all', connectionLost });
360
375
  if (connectionLost) {
361
376
  this.subscribed = [];
362
377
  return;
@@ -230,11 +230,9 @@ export default class BleFmAdapter extends BleAdapter {
230
230
  async checkCapabilities() {
231
231
  const before = this.capabilities.join(',');
232
232
  const sensor = this.getSensor();
233
- let updateRequired = false;
234
233
  if (!sensor.features) {
235
234
  try {
236
235
  await sensor.getFitnessMachineFeatures();
237
- updateRequired = true;
238
236
  }
239
237
  catch (err) {
240
238
  this.logEvent({ message: 'error getting fitness machine features', device: this.getName(), interface: this.getInterface(), error: err });
@@ -247,10 +245,6 @@ export default class BleFmAdapter extends BleAdapter {
247
245
  if (before !== after) {
248
246
  this.logEvent({ message: 'device capabilities updated', name: this.getSettings().name, interface: this.getSettings().interface, capabilities: this.capabilities });
249
247
  this.emit('device-info', this.getSettings(), { capabilities: this.capabilities });
250
- updateRequired = true;
251
- }
252
- if (updateRequired) {
253
- this.updateCyclingModeConfig();
254
248
  }
255
249
  }
256
250
  updateCapabilitiesFromFeatures(features) {
@@ -5,6 +5,7 @@ import { IncyclistCapability } from '../../../types/index.js';
5
5
  export class ZwiftPlayAdapter extends BleAdapter {
6
6
  static INCYCLIST_PROFILE_NAME = 'Controller';
7
7
  static CAPABILITIES = [IncyclistCapability.AppControl];
8
+ keyPressedHandler;
8
9
  constructor(settings, props) {
9
10
  super(settings, props);
10
11
  this.logger = new EventLogger('ZwiftPlay');
@@ -19,9 +20,13 @@ export class ZwiftPlayAdapter extends BleAdapter {
19
20
  let connected = await super.startSensor();
20
21
  if (connected) {
21
22
  const sensor = this.getSensor();
22
- sensor.on('key-pressed', (event) => {
23
- this.emit('key-pressed', this.getSettings(), event);
24
- });
23
+ if (this.keyPressedHandler) {
24
+ sensor.off('key-pressed', this.keyPressedHandler);
25
+ }
26
+ else {
27
+ this.keyPressedHandler = this.onKeyPressed.bind(this);
28
+ }
29
+ sensor.on('key-pressed', this.keyPressedHandler);
25
30
  }
26
31
  return connected;
27
32
  }
@@ -30,10 +35,18 @@ export class ZwiftPlayAdapter extends BleAdapter {
30
35
  return false;
31
36
  }
32
37
  }
38
+ onKeyPressed(event) {
39
+ this.emit('key-pressed', this.getSettings(), event);
40
+ }
41
+ isEqual(settings) {
42
+ const equal = super.isEqual(settings) &&
43
+ settings.address == this.settings.address;
44
+ return equal;
45
+ }
33
46
  isSame(adapter) {
34
47
  if (!(adapter instanceof ZwiftPlayAdapter))
35
48
  return false;
36
- return this.isEqual(adapter.settings);
49
+ return this.isEqual(adapter.settings) && this.getUniqueName() === adapter.getUniqueName();
37
50
  }
38
51
  updateSensor(peripheral) {
39
52
  this.device = new BleZwiftPlaySensor(peripheral, { logger: this.logger });
@@ -41,6 +54,22 @@ export class ZwiftPlayAdapter extends BleAdapter {
41
54
  getProfile() {
42
55
  return ZwiftPlayAdapter.INCYCLIST_PROFILE_NAME;
43
56
  }
57
+ getUniqueName() {
58
+ return this.getName();
59
+ }
60
+ getName() {
61
+ const settings = this.settings;
62
+ let name = settings.name;
63
+ if (settings.name === 'Zwift-Ride') {
64
+ if (this.device.getDeviceType() === 'ride-left')
65
+ name = name + '-L';
66
+ if (this.device.getDeviceType() === 'ride-right')
67
+ name = name + '-R';
68
+ }
69
+ const id = (settings.id ?? settings.address ?? '').replace(/[:\-]/g, '');
70
+ const addressHash = id.length > 4 ? id.slice(-4).toUpperCase() : id.toUpperCase();
71
+ return `${name} ${addressHash}`;
72
+ }
44
73
  getDisplayName() {
45
74
  return this.getName();
46
75
  }
@@ -64,6 +64,42 @@ export class BleZwiftPlaySensor extends TBleSensor {
64
64
  getRequiredCharacteristics() {
65
65
  return ['00000002-19ca-4651-86e5-fa29dcdd09d1', '00000004-19ca-4651-86e5-fa29dcdd09d1'];
66
66
  }
67
+ getDeviceType() {
68
+ if (this.deviceType)
69
+ return this.deviceType;
70
+ if (this.isFM) {
71
+ this.deviceType = 'hub';
72
+ }
73
+ else if (this.peripheral?.getManufacturerData) {
74
+ const manufacturerData = this.getManufacturerData();
75
+ if (manufacturerData?.startsWith('4a09')) {
76
+ const typeVal = Number('0x' + manufacturerData.substring(2, 4));
77
+ if (typeVal === 9) {
78
+ this.deviceType = 'click';
79
+ this.encrypted = false;
80
+ }
81
+ else if (typeVal === 2) {
82
+ this.deviceType = 'right';
83
+ }
84
+ else if (typeVal === 3) {
85
+ this.deviceType = 'left';
86
+ }
87
+ else if (typeVal === 7) {
88
+ this.deviceType = 'ride-right';
89
+ }
90
+ else if (typeVal === 8) {
91
+ this.deviceType = 'ride-left';
92
+ }
93
+ }
94
+ }
95
+ console.log('# [ZwiftPlay] device type ', this.deviceType, this.peripheral?.getInfo().name, this.peripheral?.getInfo().address);
96
+ if (!this.deviceType && !this.encryptedSupported()) {
97
+ this.deviceType = 'click';
98
+ this.encrypted = false;
99
+ }
100
+ this.deviceType = this.deviceType ?? 'click';
101
+ return this.deviceType;
102
+ }
67
103
  onData(characteristic, data, isNotify) {
68
104
  const uuid = beautifyUUID(characteristic).toLowerCase();
69
105
  if (uuid === '00000002-19ca-4651-86e5-fa29dcdd09d1') {
@@ -151,7 +187,6 @@ export class BleZwiftPlaySensor extends TBleSensor {
151
187
  }
152
188
  onMeasurement(d) {
153
189
  const data = Buffer.from(d);
154
- this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
155
190
  if (data?.length < 1) {
156
191
  console.log('Invalid click measurement data', data.toString('hex'));
157
192
  return false;
@@ -179,9 +214,10 @@ export class BleZwiftPlaySensor extends TBleSensor {
179
214
  else if (type === 0x3c) {
180
215
  this.onDeviceInformation(message);
181
216
  }
217
+ else if (type === 0x15) {
218
+ }
182
219
  else {
183
- console.log('Unknown click measurement type', type, message.toString('hex'));
184
- this.emit('data', { raw: data.toString('hex') });
220
+ this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
185
221
  }
186
222
  return true;
187
223
  }
@@ -290,17 +326,27 @@ export class BleZwiftPlaySensor extends TBleSensor {
290
326
  try {
291
327
  const data = RideKeyPadStatus.fromBinary(m);
292
328
  const buttonNames = new Map([
329
+ [0x01, 'left'],
330
+ [0x02, 'up'],
331
+ [0x04, 'right'],
332
+ [0x08, 'down'],
293
333
  [0x10, 'a'],
294
334
  [0x20, 'b'],
295
335
  [0x40, 'y'],
296
336
  [0x80, 'z'],
337
+ [0x0100, 'l-shift-up'],
338
+ [0x0200, 'l-shift-down'],
297
339
  [0x1000, 'r-shift-up'],
298
340
  [0x2000, 'r-shift-down'],
299
- [0x4000, 'ride-on'],
300
- [0x8000, 'r-power']
341
+ [0x0400, 'l-power-up'],
342
+ [0x4000, 'r-power-up'],
343
+ [0x0800, 'l-power'],
344
+ [0x8000, 'r-power'],
301
345
  ]);
302
346
  const buttonMap = data.buttonMap ?? 0;
303
347
  const currentPresses = new Set();
348
+ const address = this.peripheral?.getInfo()?.address;
349
+ const name = this.peripheral?.getInfo()?.name;
304
350
  buttonNames.forEach((name, bit) => {
305
351
  const isPressed = (buttonMap & bit) === 0;
306
352
  if (isPressed) {
@@ -315,19 +361,11 @@ export class BleZwiftPlaySensor extends TBleSensor {
315
361
  if (!currentPresses.has(bit) && state.pressed) {
316
362
  const keyName = buttonNames.get(bit);
317
363
  const duration = Date.now() - state.timestamp;
364
+ this.logEvent({ message: 'key pressed', key: keyName, name, address, duration, deviceType: this.deviceType });
318
365
  this.emit('key-pressed', { key: keyName, duration, deviceType: this.deviceType });
319
366
  this.rideKeyPadStates.set(bit, { pressed: false, timestamp: Date.now() });
320
367
  }
321
368
  });
322
- const pressedButtons = Array.from(currentPresses).map(bit => ({
323
- bit: `0x${bit.toString(16).padStart(8, '0')}`,
324
- name: buttonNames.get(bit)
325
- }));
326
- this.logEvent({
327
- message: 'ride keypad status received',
328
- buttonMap: `0x${buttonMap.toString(16)}`,
329
- buttons: pressedButtons
330
- });
331
369
  }
332
370
  catch (err) {
333
371
  this.logEvent({ message: 'Error', fn: 'onRideKeyPadStatus', error: err.message, stack: err.stack });
@@ -360,6 +398,8 @@ export class BleZwiftPlaySensor extends TBleSensor {
360
398
  }
361
399
  onClickButtonMessage(d) {
362
400
  try {
401
+ const address = this.peripheral?.getInfo()?.address;
402
+ const name = this.peripheral?.getInfo()?.name;
363
403
  const message = Buffer.from(d);
364
404
  const messageStr = message.toString('hex');
365
405
  if (messageStr === this.prevClickMessage) {
@@ -370,6 +410,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
370
410
  const prev = { ...this.upState };
371
411
  this.upState = { pressed: false, timestamp: Date.now() };
372
412
  if (prev.pressed) {
413
+ this.logEvent({ message: 'key pressed', key: 'up', name, address, duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
373
414
  this.emit('key-pressed', { key: 'up', duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
374
415
  }
375
416
  }
@@ -380,6 +421,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
380
421
  const prev = { ...this.downState };
381
422
  this.downState = { pressed: false, timestamp: Date.now() };
382
423
  if (prev.pressed) {
424
+ this.logEvent({ message: 'key pressed', key: 'down', name, address, duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
383
425
  this.emit('key-pressed', { key: 'down', duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
384
426
  }
385
427
  }
@@ -478,7 +520,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
478
520
  this.deviceType = 'hub';
479
521
  this.encrypted = false;
480
522
  }
481
- else if (this.peripheral.getManufacturerData) {
523
+ else if (this.peripheral?.getManufacturerData) {
482
524
  manufacturerData = this.getManufacturerData();
483
525
  if (manufacturerData?.startsWith('4a09')) {
484
526
  const typeVal = Number('0x' + manufacturerData.substring(2, 4));
@@ -492,6 +534,12 @@ export class BleZwiftPlaySensor extends TBleSensor {
492
534
  else if (typeVal === 3) {
493
535
  this.deviceType = 'left';
494
536
  }
537
+ else if (typeVal === 7) {
538
+ this.deviceType = 'ride-right';
539
+ }
540
+ else if (typeVal === 8) {
541
+ this.deviceType = 'ride-left';
542
+ }
495
543
  }
496
544
  }
497
545
  if (!this.deviceType && !this.encryptedSupported()) {
@@ -499,7 +547,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
499
547
  this.encrypted = false;
500
548
  }
501
549
  this.deviceType = this.deviceType ?? 'click';
502
- this.logEvent({ message: 'Play protocol pairing info', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData });
550
+ this.logEvent({ message: 'Play protocol pairing info', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData: this.getManufacturerData() });
503
551
  let message;
504
552
  if (this.isFM) {
505
553
  message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x02, 0x01])]);
@@ -516,7 +564,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
516
564
  this.logEvent({ message: `send rideOn` });
517
565
  await this.write(fullUUID('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
518
566
  this.isHubServicePaired = true;
519
- this.logEvent({ message: 'pairing done', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData });
567
+ this.logEvent({ message: 'pairing done', deviceType: this.deviceType, encrypted: this.encrypted, manufacturerData: this.getManufacturerData() });
520
568
  return true;
521
569
  }
522
570
  catch (err) {
@@ -527,6 +575,15 @@ export class BleZwiftPlaySensor extends TBleSensor {
527
575
  }
528
576
  reset() {
529
577
  }
578
+ getManufacturerData() {
579
+ const data = this.peripheral?.getManufacturerData?.();
580
+ if (typeof data === 'string')
581
+ return data;
582
+ if (Buffer.isBuffer(data)) {
583
+ return data.toString('hex');
584
+ }
585
+ return undefined;
586
+ }
530
587
  getCrypto() {
531
588
  let crypto = BindingsFactory.getInstance()?.getBinding()?.crypto;
532
589
  if (!crypto)
@@ -556,13 +613,4 @@ export class BleZwiftPlaySensor extends TBleSensor {
556
613
  delete this.prevHubSettings;
557
614
  this.rideKeyPadStates = new Map();
558
615
  }
559
- getManufacturerData() {
560
- const data = this.peripheral.getManufacturerData();
561
- if (typeof data === 'string')
562
- return data;
563
- if (Buffer.isBuffer(data)) {
564
- return data.toString('hex');
565
- }
566
- return undefined;
567
- }
568
616
  }
@@ -61,34 +61,40 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase {
61
61
  if (startGearIdx !== -1) {
62
62
  config.properties.splice(startGearIdx, 1);
63
63
  }
64
+ try {
65
+ const device = this.adapter?.getName();
66
+ this.logger.logEvent({ message: 'reset config', device });
67
+ }
68
+ catch { }
64
69
  }
65
70
  getConfig() {
66
71
  const config = super.getConfig();
67
- const virtShiftEnabled = this.getFeatureToogle().has('VirtualShifting');
68
72
  let virtshift = config.properties.find(p => p.key === 'virtshift');
69
73
  let startGear = config.properties.find(p => p.key === 'startGear');
70
- if (!virtshift && !this.adapter?.supportsVirtualShifting()) {
71
- const options = virtShiftEnabled ? [
72
- 'Disabled',
73
- { key: 'Incyclist', display: 'App only (beta)' },
74
- { key: 'Mixed', display: 'App + Bike' }
75
- ] :
76
- [
74
+ if (!virtshift) {
75
+ const device = this.adapter?.getName();
76
+ const supportsZwift = this.adapter?.supportsVirtualShifting();
77
+ this.logger.logEvent({ message: 'prepare gear settings config', device, supportsZwift });
78
+ if (supportsZwift) {
79
+ const options = [
77
80
  'Disabled',
78
- { key: 'Mixed', display: 'Enabled' }
81
+ { key: 'Incyclist', display: 'App only (beta)' },
82
+ { key: 'Mixed', display: 'App + Bike' },
83
+ { key: 'SmartTrainer', display: 'SmartTreiner (beta)' }
79
84
  ];
80
- virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: CyclingModeProperyType.SingleSelect, options, default: 'Disabled' };
81
- config.properties.push(virtshift);
82
- }
83
- if (!virtshift && virtShiftEnabled && this.adapter?.supportsVirtualShifting()) {
84
- const options = [
85
- 'Disabled',
86
- { key: 'Incyclist', display: 'App only (beta)' },
87
- { key: 'Mixed', display: 'App + Bike' },
88
- { key: 'SmartTrainer', display: 'SmartTreiner (beta)' }
89
- ];
90
- virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: CyclingModeProperyType.SingleSelect, options, default: 'Mixed' };
91
- config.properties.push(virtshift);
85
+ virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: CyclingModeProperyType.SingleSelect, options, default: 'Mixed' };
86
+ config.properties.push(virtshift);
87
+ }
88
+ else {
89
+ const options = [
90
+ 'Disabled',
91
+ { key: 'Incyclist', display: 'App only (beta)' },
92
+ { key: 'Mixed', display: 'App + Bike' }
93
+ ];
94
+ virtshift = { key: 'virtshift', name: 'Virtual Shifting', description: 'Enable virtual shifting', type: CyclingModeProperyType.SingleSelect, options, default: 'Disabled' };
95
+ config.properties.push(virtshift);
96
+ }
97
+ this.logger.logEvent({ message: 'gear settings config', config: config.properties });
92
98
  }
93
99
  if (virtshift && !startGear) {
94
100
  startGear = { key: 'startGear', name: 'Initial Gear', description: 'Initial Gear', type: CyclingModeProperyType.Integer, default: 12, min: 1, max: 24, condition: (s) => s?.virtshift === 'Incyclist' || s?.virtshift === 'SmartTrainer' };
@@ -7,12 +7,17 @@ import { BleDeviceSettings, IBlePeripheral } from '../../types.js';
7
7
  export declare class ZwiftPlayAdapter extends BleAdapter<BleDeviceData, BleZwiftPlaySensor> {
8
8
  protected static INCYCLIST_PROFILE_NAME: LegacyProfile;
9
9
  protected static CAPABILITIES: IncyclistCapability[];
10
+ protected keyPressedHandler?: (event: any) => void;
10
11
  constructor(settings: BleDeviceSettings, props?: DeviceProperties);
11
12
  protected checkCapabilities(): Promise<void>;
12
13
  startSensor(): Promise<boolean>;
14
+ protected onKeyPressed(event: any): void;
15
+ isEqual(settings: BleDeviceSettings): boolean;
13
16
  isSame(adapter: IAdapter): boolean;
14
17
  updateSensor(peripheral: IBlePeripheral): void;
15
18
  getProfile(): LegacyProfile;
19
+ getUniqueName(): string;
20
+ getName(): string;
16
21
  getDisplayName(): string;
17
22
  mapData(deviceData: BleDeviceData): IncyclistAdapterData;
18
23
  }
@@ -13,7 +13,7 @@ type BleZwiftPlaySensorProps = {
13
13
  logger?: EventLogger;
14
14
  isTrainer?: boolean;
15
15
  };
16
- type DeviceType = 'left' | 'right' | 'click' | 'hub';
16
+ type DeviceType = 'left' | 'right' | 'click' | 'hub' | 'ride-left' | 'ride-right';
17
17
  export declare class BleZwiftPlaySensor extends TBleSensor {
18
18
  static readonly profile: LegacyProfile;
19
19
  static readonly protocol: BleProtocol;
@@ -45,6 +45,7 @@ export declare class BleZwiftPlaySensor extends TBleSensor {
45
45
  reconnectSensor(): Promise<boolean>;
46
46
  stopSensor(): Promise<boolean>;
47
47
  protected getRequiredCharacteristics(): Array<string>;
48
+ getDeviceType(): DeviceType;
48
49
  onData(characteristic: string, data: Buffer, isNotify?: boolean): boolean;
49
50
  requestDataUpdate(dataId: number): Promise<void>;
50
51
  setSimulationData(data?: SimulationParam): Promise<void>;
@@ -65,10 +66,10 @@ export declare class BleZwiftPlaySensor extends TBleSensor {
65
66
  read(characteristic: string, ignoreErrors?: boolean): Promise<Buffer | null>;
66
67
  pair(): Promise<boolean>;
67
68
  reset(): void;
69
+ getManufacturerData(): string;
68
70
  protected getCrypto(): ICryptoBinding;
69
71
  protected encryptedSupported(): boolean;
70
72
  protected createKeyPair(): any;
71
73
  protected setInitialState(): void;
72
- protected getManufacturerData(): string;
73
74
  }
74
75
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "3.0.16",
3
+ "version": "3.0.18",
4
4
  "scripts": {
5
5
  "lint": "eslint . --ext .ts",
6
6
  "build": "npm run build:esm && npm run build:cjs",