incyclist-devices 3.0.9 → 3.0.11

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.
Files changed (94) hide show
  1. package/lib/cjs/antv2/base/interface.js +11 -1
  2. package/lib/cjs/antv2/fe/adapter.js +3 -0
  3. package/lib/cjs/base/adpater.js +11 -1
  4. package/lib/cjs/bindings/crypto/types.js +2 -0
  5. package/lib/cjs/bindings/index.js +18 -0
  6. package/lib/cjs/bindings/types.js +2 -0
  7. package/lib/cjs/ble/base/adapter.js +1 -1
  8. package/lib/cjs/ble/base/interface.js +2 -1
  9. package/lib/cjs/ble/base/peripheral.js +12 -0
  10. package/lib/cjs/ble/base/sensor.js +5 -0
  11. package/lib/cjs/ble/consts.js +3 -1
  12. package/lib/cjs/ble/fm/adapter.js +12 -0
  13. package/lib/cjs/ble/fm/sensor.js +211 -2
  14. package/lib/cjs/ble/zwift/play/sensor.js +11 -3
  15. package/lib/cjs/index.js +1 -0
  16. package/lib/cjs/modes/ant-fe-adv-st-mode.js +2 -2
  17. package/lib/cjs/modes/antble-smarttrainer.js +12 -12
  18. package/lib/cjs/modes/base.js +10 -0
  19. package/lib/cjs/modes/daum-classic-standard.js +4 -4
  20. package/lib/cjs/modes/daum-erg.js +3 -3
  21. package/lib/cjs/modes/daum-premium-standard.js +7 -7
  22. package/lib/cjs/modes/daum-smarttrainer.js +8 -9
  23. package/lib/cjs/modes/fm-resistance.js +2 -2
  24. package/lib/cjs/modes/kettler-erg.js +4 -4
  25. package/lib/cjs/modes/power-base.js +4 -9
  26. package/lib/cjs/modes/power-meter.js +1 -1
  27. package/lib/cjs/modes/rower.js +49 -0
  28. package/lib/cjs/serial/base/serial-interface.js +10 -7
  29. package/lib/cjs/serial/daum/DaumAdapter.js +3 -0
  30. package/lib/cjs/serial/kettler/ergo-racer/adapter.js +3 -0
  31. package/lib/cjs/types/sport.js +2 -0
  32. package/lib/esm/antv2/base/interface.js +11 -1
  33. package/lib/esm/antv2/fe/adapter.js +3 -0
  34. package/lib/esm/base/adpater.js +11 -1
  35. package/lib/esm/bindings/crypto/index.js +14 -0
  36. package/lib/esm/bindings/crypto/types.js +1 -0
  37. package/lib/esm/bindings/index.js +14 -0
  38. package/lib/esm/bindings/types.js +1 -0
  39. package/lib/esm/ble/base/adapter.js +1 -1
  40. package/lib/esm/ble/base/interface.js +2 -1
  41. package/lib/esm/ble/base/peripheral.js +13 -1
  42. package/lib/esm/ble/base/sensor.js +5 -0
  43. package/lib/esm/ble/bindings/crypto/types.js +1 -0
  44. package/lib/esm/ble/consts.js +2 -0
  45. package/lib/esm/ble/fm/adapter.js +12 -0
  46. package/lib/esm/ble/fm/sensor.js +213 -4
  47. package/lib/esm/ble/zwift/play/sensor.js +11 -3
  48. package/lib/esm/index.js +1 -0
  49. package/lib/esm/modes/ant-fe-adv-st-mode.js +2 -2
  50. package/lib/esm/modes/antble-smarttrainer.js +12 -12
  51. package/lib/esm/modes/base.js +10 -0
  52. package/lib/esm/modes/daum-classic-standard.js +4 -4
  53. package/lib/esm/modes/daum-erg.js +3 -3
  54. package/lib/esm/modes/daum-premium-standard.js +7 -7
  55. package/lib/esm/modes/daum-smarttrainer.js +8 -9
  56. package/lib/esm/modes/fm-resistance.js +2 -2
  57. package/lib/esm/modes/kettler-erg.js +4 -4
  58. package/lib/esm/modes/power-base.js +4 -9
  59. package/lib/esm/modes/power-meter copy.js +33 -0
  60. package/lib/esm/modes/power-meter.js +1 -1
  61. package/lib/esm/modes/rower.js +43 -0
  62. package/lib/esm/serial/base/serial-interface.js +10 -7
  63. package/lib/esm/serial/daum/DaumAdapter.js +3 -0
  64. package/lib/esm/serial/kettler/ergo-racer/adapter.js +3 -0
  65. package/lib/esm/types/sport.js +1 -0
  66. package/lib/types/antv2/base/interface.d.ts +11 -8
  67. package/lib/types/antv2/fe/adapter.d.ts +4 -2
  68. package/lib/types/base/adpater.d.ts +6 -2
  69. package/lib/types/bindings/crypto/index.d.ts +8 -0
  70. package/lib/types/bindings/crypto/types.d.ts +45 -0
  71. package/lib/types/bindings/index.d.ts +8 -0
  72. package/lib/types/bindings/types.d.ts +4 -0
  73. package/lib/types/ble/base/peripheral.d.ts +3 -1
  74. package/lib/types/ble/bindings/crypto/types.d.ts +45 -0
  75. package/lib/types/ble/consts.d.ts +2 -0
  76. package/lib/types/ble/fm/adapter.d.ts +4 -2
  77. package/lib/types/ble/fm/sensor.d.ts +13 -7
  78. package/lib/types/ble/fm/types.d.ts +10 -0
  79. package/lib/types/ble/types.d.ts +9 -3
  80. package/lib/types/ble/zwift/play/sensor.d.ts +2 -0
  81. package/lib/types/direct-connect/base/peripheral.d.ts +1 -1
  82. package/lib/types/index.d.ts +1 -0
  83. package/lib/types/modes/base.d.ts +4 -0
  84. package/lib/types/modes/daum-smarttrainer.d.ts +0 -2
  85. package/lib/types/modes/power-base.d.ts +0 -3
  86. package/lib/types/modes/power-meter copy.d.ts +15 -0
  87. package/lib/types/modes/rower.d.ts +19 -0
  88. package/lib/types/serial/base/serial-interface.d.ts +3 -0
  89. package/lib/types/serial/daum/DaumAdapter.d.ts +4 -2
  90. package/lib/types/serial/kettler/ergo-racer/adapter.d.ts +4 -2
  91. package/lib/types/types/adapter.d.ts +12 -7
  92. package/lib/types/types/interface.d.ts +2 -0
  93. package/lib/types/types/sport.d.ts +1 -0
  94. package/package.json +1 -1
@@ -28,6 +28,7 @@ class AntInterface extends node_events_1.EventEmitter {
28
28
  activeScan;
29
29
  props;
30
30
  logEnabled;
31
+ logPaused = false;
31
32
  channelsInUse;
32
33
  constructor(props) {
33
34
  super();
@@ -35,6 +36,7 @@ class AntInterface extends node_events_1.EventEmitter {
35
36
  this.device = undefined;
36
37
  this.connected = false;
37
38
  this.connectPromise = null;
39
+ this.scanPromise = null;
38
40
  this.channelsInUse = [];
39
41
  this.logEnabled = props.log || true;
40
42
  const { binding } = props;
@@ -52,6 +54,12 @@ class AntInterface extends node_events_1.EventEmitter {
52
54
  setBinding(binding) {
53
55
  this.Binding = binding;
54
56
  }
57
+ pauseLogging() {
58
+ this.logPaused = true;
59
+ }
60
+ resumeLogging() {
61
+ this.logPaused = false;
62
+ }
55
63
  getLogger() {
56
64
  return this.logger;
57
65
  }
@@ -65,7 +73,7 @@ class AntInterface extends node_events_1.EventEmitter {
65
73
  this.logEnabled = false;
66
74
  }
67
75
  logEvent(event) {
68
- if (!this.logEnabled || !this.logger)
76
+ if (!this.logEnabled || !this.logger || this.logPaused)
69
77
  return;
70
78
  this.logger.logEvent(event);
71
79
  const w = global.window;
@@ -77,6 +85,8 @@ class AntInterface extends node_events_1.EventEmitter {
77
85
  return this.connected && this.device !== undefined;
78
86
  }
79
87
  async connect() {
88
+ if (!this.Binding)
89
+ return false;
80
90
  if (this.isConnected())
81
91
  return true;
82
92
  if (this.connectPromise) {
@@ -31,6 +31,9 @@ class AntFEAdapter extends adapter_js_1.default {
31
31
  index_js_1.IncyclistCapability.Control
32
32
  ];
33
33
  }
34
+ getSupportedSports() {
35
+ return ['cycling'];
36
+ }
34
37
  getDisplayName() {
35
38
  const { InstantaneousPower } = this.deviceData;
36
39
  const pwrStr = InstantaneousPower ? ` (${InstantaneousPower})` : '';
@@ -22,6 +22,7 @@ class IncyclistDevice extends node_events_1.EventEmitter {
22
22
  user;
23
23
  data;
24
24
  debugLogEnabled;
25
+ logPaused;
25
26
  constructor(settings, props) {
26
27
  super();
27
28
  this.onDataFn = undefined;
@@ -48,8 +49,17 @@ class IncyclistDevice extends node_events_1.EventEmitter {
48
49
  }
49
50
  return false;
50
51
  }
52
+ pauseLogging() {
53
+ this.logPaused = true;
54
+ }
55
+ resumeLogging() {
56
+ this.logPaused = false;
57
+ }
58
+ isLogPaused() {
59
+ return this.logPaused;
60
+ }
51
61
  logEvent(event) {
52
- if (!this.logger || this.paused)
62
+ if (!this.logger || this.paused || this.logPaused)
53
63
  return;
54
64
  if (this.isDebugEnabled() && !this.debugLogEnabled)
55
65
  this.initDebugLog();
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BindingsFactory = void 0;
4
+ class BindingsFactory {
5
+ static instance;
6
+ binding;
7
+ static getInstance() {
8
+ this.instance = this.instance ?? new BindingsFactory();
9
+ return this.instance;
10
+ }
11
+ getBinding() {
12
+ return this.binding;
13
+ }
14
+ setBinding(binding) {
15
+ this.binding = binding;
16
+ }
17
+ }
18
+ exports.BindingsFactory = BindingsFactory;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -304,7 +304,7 @@ class BleAdapter extends adpater_js_1.default {
304
304
  return true;
305
305
  }
306
306
  catch (err) {
307
- this.logEvent({ message: 'start result: error', error: err.message, device: this.getName(), interface: this.getInterface(), protocol: this.getProtocolName() });
307
+ this.logEvent({ message: 'start result: error', error: err.message, stack: err.stack, device: this.getName(), interface: this.getInterface(), protocol: this.getProtocolName() });
308
308
  this.started = false;
309
309
  this.stopped = true;
310
310
  const ble = this.getBle();
@@ -406,7 +406,7 @@ class BleInterface extends node_events_1.EventEmitter {
406
406
  this.addService(announcement);
407
407
  }
408
408
  checkForWahooEnhancement(announcement) {
409
- if (announcement.name.includes('KICKR')) {
409
+ if (announcement.name.includes('KICKR') || announcement.name.includes('Zwift')) {
410
410
  const supported = announcement.serviceUUIDs.map(s => (0, utils_js_1.beautifyUUID)(s));
411
411
  if (supported.length === 1 && (supported[0] === '1818' || supported[0] === 'FC82')) {
412
412
  return true;
@@ -430,6 +430,7 @@ class BleInterface extends node_events_1.EventEmitter {
430
430
  name: peripheral.advertisement.localName,
431
431
  manufacturerData: peripheral.advertisement?.manufacturerData ? Buffer.from(peripheral.advertisement.manufacturerData) : undefined,
432
432
  serviceUUIDs: peripheral.advertisement.serviceUuids ?? [],
433
+ serviceData: peripheral.advertisement.serviceData,
433
434
  peripheral,
434
435
  transport: this.getName()
435
436
  };
@@ -25,6 +25,9 @@ class BlePeripheral {
25
25
  getPeripheral() {
26
26
  return this.announcement.peripheral;
27
27
  }
28
+ getInterface() {
29
+ return this.ble;
30
+ }
28
31
  getAnnouncedServices() {
29
32
  return this.announcement.serviceUUIDs.map(s => (0, utils_js_1.beautifyUUID)(s));
30
33
  }
@@ -116,6 +119,15 @@ class BlePeripheral {
116
119
  getManufacturerData() {
117
120
  return this.announcement?.manufacturerData;
118
121
  }
122
+ getServiceData(uuid) {
123
+ const serviceData = this.announcement?.serviceData;
124
+ if (!serviceData)
125
+ return;
126
+ const data = serviceData.find(sd => (0, utils_js_1.matches)(sd.uuid, uuid))?.data;
127
+ if (data)
128
+ return Buffer.from(data);
129
+ return data;
130
+ }
119
131
  async onPeripheralDisconnect() {
120
132
  if (this.disconnectedSignalled || this.disconnecting)
121
133
  return;
@@ -14,6 +14,11 @@ class TBleSensor extends node_events_1.EventEmitter {
14
14
  reconnectPromise;
15
15
  onDataHandler;
16
16
  logEvent(event, ...args) {
17
+ try {
18
+ if (this.getPeripheral()?.getInterface()?.isLoggingPaused())
19
+ return;
20
+ }
21
+ catch { }
17
22
  this.logger.logEvent(event, ...args);
18
23
  }
19
24
  constructor(peripheral, props) {
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ELITE_TRAINER_SVC = exports.CSC_FEATURE = exports.CSC_MEASUREMENT = exports.CSC = exports.HR_MEASUREMENT = exports.CSP_FEATURE = exports.CSP_MEASUREMENT = exports.CSP = exports.POWER_RANGE = exports.RES_LEVEL_RANGE = exports.FTMS_FEATURE = exports.INDOOR_BIKE_DATA = exports.FTMS_STATUS = exports.FTMS_CP = exports.FTMS = void 0;
3
+ exports.ELITE_TRAINER_SVC = exports.CSC_FEATURE = exports.CSC_MEASUREMENT = exports.CSC = exports.HR_MEASUREMENT = exports.CSP_FEATURE = exports.CSP_MEASUREMENT = exports.CSP = exports.POWER_RANGE = exports.RES_LEVEL_RANGE = exports.FTMS_FEATURE = exports.TREADMILL_DATA = exports.INDOOR_BIKE_DATA = exports.ROWER_DATA = exports.FTMS_STATUS = exports.FTMS_CP = exports.FTMS = void 0;
4
4
  exports.FTMS = '1826';
5
5
  exports.FTMS_CP = '2ad9';
6
6
  exports.FTMS_STATUS = '2ada';
7
+ exports.ROWER_DATA = '2ad1';
7
8
  exports.INDOOR_BIKE_DATA = '2ad2';
9
+ exports.TREADMILL_DATA = '2acd';
8
10
  exports.FTMS_FEATURE = '2acc';
9
11
  exports.RES_LEVEL_RANGE = '2ad6';
10
12
  exports.POWER_RANGE = '2ad8';
@@ -15,6 +15,7 @@ const utils_js_1 = require("../../utils/utils.js");
15
15
  const index_js_2 = require("../zwift/play/index.js");
16
16
  const index_js_3 = require("../../features/index.js");
17
17
  const fm_resistance_js_1 = __importDefault(require("../../modes/fm-resistance.js"));
18
+ const rower_js_1 = __importDefault(require("../../modes/rower.js"));
18
19
  class BleFmAdapter extends adapter_js_1.default {
19
20
  static INCYCLIST_PROFILE_NAME = 'Smart Trainer';
20
21
  distanceInternal = 0;
@@ -32,6 +33,9 @@ class BleFmAdapter extends adapter_js_1.default {
32
33
  index_js_1.IncyclistCapability.Control
33
34
  ];
34
35
  }
36
+ getSupportedSports() {
37
+ return this.device?.getSupportedSports() ?? ['cycling'];
38
+ }
35
39
  updateSensor(peripheral) {
36
40
  this.device = new sensor_js_1.default(peripheral, { logger: this.logger });
37
41
  }
@@ -52,6 +56,10 @@ class BleFmAdapter extends adapter_js_1.default {
52
56
  const modes = [power_meter_js_1.default];
53
57
  if (this.props?.capabilities && this.props.capabilities.indexOf(index_js_1.IncyclistCapability.Control) === -1)
54
58
  return modes;
59
+ const sports = this.getSupportedSports();
60
+ if (sports.length === 1 && sports[0] === 'rowing') {
61
+ return [rower_js_1.default];
62
+ }
55
63
  const features = this.getSensor()?.features;
56
64
  if (!features)
57
65
  return [power_meter_js_1.default, antble_smarttrainer_js_1.default, antble_erg_js_1.default];
@@ -67,6 +75,10 @@ class BleFmAdapter extends adapter_js_1.default {
67
75
  getDefaultCyclingMode() {
68
76
  if (this.props?.capabilities && this.props.capabilities.indexOf(index_js_1.IncyclistCapability.Control) === -1)
69
77
  return this.createMode(power_meter_js_1.default);
78
+ const sports = this.getSupportedSports();
79
+ if (sports.length === 1 && sports[0] === 'rowing') {
80
+ return this.createMode(rower_js_1.default);
81
+ }
70
82
  const features = this.getSensor()?.features;
71
83
  if (!features) {
72
84
  return this.createMode(antble_smarttrainer_js_1.default);
@@ -21,6 +21,7 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
21
21
  cw = 0.6;
22
22
  windSpeed = 0;
23
23
  wheelSize = 2100;
24
+ ftmsServiceData;
24
25
  constructor(peripheral, props) {
25
26
  super(peripheral, props);
26
27
  this.reset();
@@ -32,6 +33,9 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
32
33
  this.data = {};
33
34
  }
34
35
  getRequiredCharacteristics() {
36
+ this.parseServiceData();
37
+ if (this.ftmsServiceData?.rower)
38
+ return [consts_js_1.ROWER_DATA, '2a37', consts_js_1.FTMS_STATUS];
35
39
  return [consts_js_1.INDOOR_BIKE_DATA, '2a37', consts_js_1.FTMS_STATUS];
36
40
  }
37
41
  onData(characteristic, characteristicData) {
@@ -46,6 +50,11 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
46
50
  case consts_js_1.INDOOR_BIKE_DATA:
47
51
  res = this.parseIndoorBikeData(data);
48
52
  break;
53
+ case consts_js_1.ROWER_DATA:
54
+ res = this.parseRowerData(data);
55
+ break;
56
+ case consts_js_1.TREADMILL_DATA:
57
+ res = this.parseTreadmillData(data);
49
58
  case '2a37':
50
59
  res = this.parseHrm(data);
51
60
  break;
@@ -76,6 +85,19 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
76
85
  getCw() { return this.cw; }
77
86
  setWindSpeed(windSpeed) { this.windSpeed = windSpeed; }
78
87
  getWindSpeed() { return this.windSpeed; }
88
+ getSupportedSports() {
89
+ this.parseServiceData();
90
+ if (!this.ftmsServiceData)
91
+ return ['cycling'];
92
+ const sports = [];
93
+ if (this.ftmsServiceData?.indoorBike)
94
+ sports.push('cycling');
95
+ if (this.ftmsServiceData?.rower)
96
+ sports.push('rowing');
97
+ if (this.ftmsServiceData?.treadmill)
98
+ sports.push('running');
99
+ return sports;
100
+ }
79
101
  onDisconnect() {
80
102
  this.hasControl = false;
81
103
  }
@@ -167,6 +189,152 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
167
189
  }
168
190
  return { ...this.data, raw: `2a37:${data.toString('hex')}` };
169
191
  }
192
+ parseTreadmillData(_data) {
193
+ const data = Buffer.from(_data);
194
+ let offset = 2;
195
+ if (data.length > 2) {
196
+ try {
197
+ const flags = data.readUInt16LE(0);
198
+ if ((flags & 0x0001) === 0) {
199
+ this.data.speed = data.readUInt16LE(offset) / 100;
200
+ offset += 2;
201
+ }
202
+ if (flags & 0x0002) {
203
+ this.data.averageSpeed = data.readUInt16LE(offset) / 100;
204
+ offset += 2;
205
+ }
206
+ if (flags & 0x0004) {
207
+ const dvLow = data.readUInt8(offset);
208
+ offset += 1;
209
+ const dvHigh = data.readUInt16LE(offset);
210
+ offset += 2;
211
+ this.data.totalDistance = (dvHigh << 8) + dvLow;
212
+ }
213
+ if (flags & 0x0008) {
214
+ this.data.targetInclination = data.readInt16LE(offset) / 10;
215
+ offset += 2;
216
+ offset += 2;
217
+ }
218
+ if (flags & 0x0010) {
219
+ offset += 2;
220
+ offset += 2;
221
+ }
222
+ if (flags & 0x0020) {
223
+ const pace = data.readUInt8(offset);
224
+ offset += 1;
225
+ this.data.speed = pace > 0 ? Math.round(60 / pace * 100) / 100 : 0;
226
+ }
227
+ if (flags & 0x0040) {
228
+ const avgPace = data.readUInt8(offset);
229
+ offset += 1;
230
+ this.data.averageSpeed = avgPace > 0 ? Math.round(60 / avgPace * 100) / 100 : 0;
231
+ }
232
+ if (flags & 0x0080) {
233
+ this.data.totalEnergy = data.readUInt16LE(offset);
234
+ offset += 2;
235
+ this.data.energyPerHour = data.readUInt16LE(offset);
236
+ offset += 2;
237
+ this.data.energyPerMinute = data.readUInt8(offset);
238
+ offset += 1;
239
+ }
240
+ if (flags & 0x0100) {
241
+ this.data.heartrate = data.readUInt8(offset);
242
+ offset += 1;
243
+ }
244
+ if (flags & 0x0200) {
245
+ this.data.metabolicEquivalent = data.readUInt8(offset) / 10;
246
+ offset += 1;
247
+ }
248
+ if (flags & 0x0400) {
249
+ this.data.time = data.readUInt16LE(offset);
250
+ offset += 2;
251
+ }
252
+ if (flags & 0x0800) {
253
+ this.data.remainingTime = data.readUInt16LE(offset);
254
+ }
255
+ }
256
+ catch (err) {
257
+ this.logEvent({ message: 'error', fn: 'parseTreadmillData()', device: this.getName(), data: data.toString('hex'), offset, error: err.message, stack: err.stack });
258
+ }
259
+ }
260
+ return { ...this.data, raw: `2acd:${data.toString('hex')}` };
261
+ }
262
+ parseRowerData(_data) {
263
+ const data = Buffer.from(_data);
264
+ let offset = 2;
265
+ if (data.length > 2) {
266
+ try {
267
+ const flags = data.readUInt16LE(0);
268
+ if ((flags & 0x0001) === 0) {
269
+ this.data.cadence = data.readUInt8(offset) / 2;
270
+ offset += 1;
271
+ offset += 2;
272
+ }
273
+ if (flags & 0x0002) {
274
+ this.data.averageCadence = data.readUInt8(offset) / 2;
275
+ offset += 1;
276
+ }
277
+ if (flags & 0x0004) {
278
+ const dvLow = data.readUInt8(offset);
279
+ offset += 1;
280
+ const dvHigh = data.readUInt16LE(offset);
281
+ offset += 2;
282
+ this.data.totalDistance = (dvHigh << 8) + dvLow;
283
+ }
284
+ if (flags & 0x0008) {
285
+ const pace = data.readUInt16LE(offset);
286
+ offset += 2;
287
+ this.data.speed = pace > 0 ? Math.round(1800 / pace * 100) / 100 : 0;
288
+ }
289
+ if (flags & 0x0010) {
290
+ const avgPace = data.readUInt16LE(offset);
291
+ offset += 2;
292
+ this.data.averageSpeed = avgPace > 0 ? Math.round(1800 / avgPace * 100) / 100 : 0;
293
+ }
294
+ if (flags & 0x0020) {
295
+ this.data.instantaneousPower = data.readInt16LE(offset);
296
+ offset += 2;
297
+ }
298
+ if (flags & 0x0040) {
299
+ this.data.averagePower = data.readInt16LE(offset);
300
+ offset += 2;
301
+ this.data.instantaneousPower = data.readInt16LE(offset);
302
+ offset += 2;
303
+ }
304
+ if (flags & 0x0080) {
305
+ this.data.resistanceLevel = data.readInt16LE(offset);
306
+ offset += 2;
307
+ }
308
+ if (flags & 0x0100) {
309
+ this.data.totalEnergy = data.readUInt16LE(offset);
310
+ offset += 2;
311
+ this.data.energyPerHour = data.readUInt16LE(offset);
312
+ offset += 2;
313
+ this.data.energyPerMinute = data.readUInt8(offset);
314
+ offset += 1;
315
+ }
316
+ if (flags & 0x0200) {
317
+ this.data.heartrate = data.readUInt8(offset);
318
+ offset += 1;
319
+ }
320
+ if (flags & 0x0400) {
321
+ this.data.metabolicEquivalent = data.readUInt8(offset) / 10;
322
+ offset += 1;
323
+ }
324
+ if (flags & 0x0800) {
325
+ this.data.time = data.readUInt16LE(offset);
326
+ offset += 2;
327
+ }
328
+ if (flags & 0x1000) {
329
+ this.data.remainingTime = data.readUInt16LE(offset);
330
+ }
331
+ }
332
+ catch (err) {
333
+ this.logEvent({ message: 'error', fn: 'parseRowerData()', device: this.getName(), data: data.toString('hex'), offset, error: err.message, stack: err.stack });
334
+ }
335
+ }
336
+ return { ...this.data, raw: `2ad1:${data.toString('hex')}` };
337
+ }
170
338
  parseIndoorBikeData(_data) {
171
339
  const data = Buffer.from(_data);
172
340
  let offset = 2;
@@ -292,16 +460,54 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
292
460
  }
293
461
  return { ...this.data, raw: `2ada:${data.toString('hex')}` };
294
462
  }
463
+ parseServiceData() {
464
+ if (this.ftmsServiceData)
465
+ return this.ftmsServiceData;
466
+ try {
467
+ const peripheral = this.peripheral;
468
+ if (!peripheral.getServiceData)
469
+ return;
470
+ const bitSet = (value, bitNo) => (value & (0, utils_js_1.bit)(bitNo)) > 0;
471
+ const data = peripheral.getServiceData(consts_js_1.FTMS);
472
+ const dataLength = data?.length ?? 0;
473
+ if (dataLength >= 2) {
474
+ const flags = data.readUInt8(0);
475
+ const fmType = dataLength === 3 ? data.readUInt16LE(1) : data.readUInt8(1);
476
+ const available = bitSet(flags, 0);
477
+ const treadmill = bitSet(fmType, 0);
478
+ const crossTrainer = bitSet(fmType, 1);
479
+ const stepClimber = bitSet(fmType, 2);
480
+ const stairClimber = bitSet(fmType, 3);
481
+ const rower = bitSet(fmType, 4);
482
+ const indoorBike = bitSet(fmType, 5);
483
+ const raw = data?.toString('hex');
484
+ const serviceData = { available, treadmill, crossTrainer, stepClimber, stairClimber, rower, indoorBike, raw };
485
+ this.logEvent({ message: 'service data', device: this.getName(), ...serviceData });
486
+ this.ftmsServiceData = serviceData;
487
+ return serviceData;
488
+ }
489
+ else {
490
+ this.logEvent({ message: 'could not parse service data', reason: 'not enough data', raw: data?.toString('hex') });
491
+ }
492
+ }
493
+ catch (err) {
494
+ this.logEvent({ message: 'could not parse service data', reason: err.message, stack: err.stack });
495
+ }
496
+ }
295
497
  async getFitnessMachineFeatures() {
296
498
  if (this._features)
297
499
  return this._features;
500
+ if (this.getName().startsWith('MRK-')) {
501
+ return { fitnessMachine: 0, targetSettings: 0, setPower: true, power: true, heartrate: true };
502
+ }
298
503
  try {
299
504
  const data = await this.read('2acc');
300
505
  const buffer = data ? Buffer.from(data) : undefined;
301
506
  const services = this.peripheral?.services || [];
302
507
  let power = services.some(s => (0, utils_js_1.matches)(s.uuid, '1818'));
303
508
  let heartrate = services.some(s => (0, utils_js_1.matches)(s.uuid, '180d'));
304
- if (buffer?.length >= 8) {
509
+ const dataLength = buffer?.length ?? 0;
510
+ if (dataLength >= 8) {
305
511
  const fitnessMachine = buffer.readUInt32LE(0);
306
512
  const targetSettings = buffer.readUInt32LE(4);
307
513
  power = power || (fitnessMachine & consts_js_2.FitnessMachineFeatureFlag.PowerMeasurementSupported) !== 0;
@@ -320,7 +526,7 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
320
526
  return this._features;
321
527
  }
322
528
  else {
323
- return { fitnessMachine: undefined, targetSettings: undefined, power, heartrate };
529
+ return { fitnessMachine: 0, targetSettings: 0, power, heartrate };
324
530
  }
325
531
  }
326
532
  catch (err) {
@@ -332,6 +538,9 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
332
538
  return this.getSupportedServiceUUids()?.some(s => (0, utils_js_1.matches)(s, consts_js_2.ZWIFT_PLAY_UUID));
333
539
  }
334
540
  buildFitnessMachineInfo(fitnessMachine) {
541
+ if (this.getName().startsWith('MRK-')) {
542
+ return ['avgSpeed', 'cadence', 'power', 'pace'];
543
+ }
335
544
  const info = [];
336
545
  const check = (flag, name) => {
337
546
  if (fitnessMachine & flag)
@@ -4,8 +4,8 @@ exports.BleZwiftPlaySensor = void 0;
4
4
  const zwift_hub_js_1 = require("../../../proto/zwift_hub.js");
5
5
  const sensor_js_1 = require("../../base/sensor.js");
6
6
  const utils_js_1 = require("../../utils.js");
7
- const node_crypto_1 = require("node:crypto");
8
7
  const node_events_1 = require("node:events");
8
+ const index_js_1 = require("../../../bindings/index.js");
9
9
  class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
10
10
  static profile = 'Controller';
11
11
  static protocol = 'zwift-play';
@@ -479,11 +479,19 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
479
479
  }
480
480
  reset() {
481
481
  }
482
+ getCrypto() {
483
+ let crypto = index_js_1.BindingsFactory.getInstance()?.getBinding()?.crypto;
484
+ if (!crypto)
485
+ crypto = require('node:crypto');
486
+ return crypto;
487
+ }
482
488
  encryptedSupported() {
483
- return node_crypto_1.generateKeyPairSync !== undefined && typeof (node_crypto_1.generateKeyPairSync) === 'function';
489
+ const crypto = this.getCrypto();
490
+ return crypto?.generateKeyPairSync !== undefined && typeof (crypto?.generateKeyPairSync) === 'function';
484
491
  }
485
492
  createKeyPair() {
486
- return (0, node_crypto_1.generateKeyPairSync)('ec', {
493
+ const crypto = this.getCrypto();
494
+ return crypto.generateKeyPairSync('ec', {
487
495
  namedCurve: 'prime256v1',
488
496
  publicKeyEncoding: { type: 'spki', format: 'der' },
489
497
  privateKeyEncoding: { type: 'pkcs8', format: 'der' }
package/lib/cjs/index.js CHANGED
@@ -49,3 +49,4 @@ __exportStar(require("./antv2/index.js"), exports);
49
49
  __exportStar(require("./direct-connect/index.js"), exports);
50
50
  __exportStar(require("./features/index.js"), exports);
51
51
  __exportStar(require("./proto/zwift_hub.js"), exports);
52
+ __exportStar(require("./bindings/index.js"), exports);
@@ -44,7 +44,7 @@ class AntAdvSimCyclingMode extends antble_smarttrainer_js_1.default {
44
44
  }
45
45
  }
46
46
  sendBikeUpdate(incoming) {
47
- this.logger.logEvent({ message: "processing update request", request: incoming, prev: this.prevRequest, data: this.getData() });
47
+ this.logEvent({ message: "processing update request", request: incoming, prev: this.prevRequest, data: this.getData() });
48
48
  let newRequest = {};
49
49
  const request = Object.assign({}, incoming);
50
50
  try {
@@ -75,7 +75,7 @@ class AntAdvSimCyclingMode extends antble_smarttrainer_js_1.default {
75
75
  this.prevRequest = JSON.parse(JSON.stringify(newRequest));
76
76
  }
77
77
  catch (err) {
78
- this.logger.logEvent({ message: "error", fn: 'sendBikeUpdate()', error: err.message, stack: err.stack });
78
+ this.logEvent({ message: "error", fn: 'sendBikeUpdate()', error: err.message, stack: err.stack });
79
79
  }
80
80
  return newRequest;
81
81
  }
@@ -224,7 +224,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
224
224
  return;
225
225
  }
226
226
  if (this.data?.pedalRpm !== this.prevData?.pedalRpm) {
227
- this.logger.logEvent({ message: 'cadence changed', cadence: this.data?.pedalRpm, prevCadence: this.prevData?.pedalRpm });
227
+ this.logEvent({ message: 'cadence changed', cadence: this.data?.pedalRpm, prevCadence: this.prevData?.pedalRpm });
228
228
  request.slope = request.slope ?? this.data.slope;
229
229
  }
230
230
  }
@@ -245,20 +245,20 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
245
245
  this.simPower = prev + delta / 2;
246
246
  this.simPower = Math.max(newPower, prevPower);
247
247
  this.prevEkin = m * v * v / 2;
248
- this.logger.logEvent({ message: 'set simulated power (starting)', target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
248
+ this.logEvent({ message: 'set simulated power (starting)', target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
249
249
  }
250
250
  else if (changed === 'gear') {
251
251
  this.simPower = prev + delta;
252
252
  this.prevEkin = m * v * v / 2;
253
- this.logger.logEvent({ message: 'set simulated power (gear change)', target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
253
+ this.logEvent({ message: 'set simulated power (gear change)', target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
254
254
  }
255
255
  else if (changed === 'slope' || changed === 'cadence') {
256
256
  const adjustTime = this.simSlope < 0 ? 5 : 3;
257
257
  this.simPower = prev + delta / adjustTime;
258
- this.logger.logEvent({ message: `set simulated power (${changed} change)`, target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
258
+ this.logEvent({ message: `set simulated power (${changed} change)`, target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
259
259
  }
260
260
  else {
261
- this.logger.logEvent({ message: `set simulated power (mothing changed)`, target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
261
+ this.logEvent({ message: `set simulated power (mothing changed)`, target: this.simPower, gear: this.gear, simSlope: this.simSlope, routeSlope: this.data.slope, prevTarget: this.prevSimPower, actualPower: prevPower, newPower });
262
262
  }
263
263
  }
264
264
  else {
@@ -295,7 +295,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
295
295
  this.gear = this.gearRatios.length;
296
296
  }
297
297
  delete request.gearDelta;
298
- this.logger.logEvent({ message: 'gear changed', gear: this.gear, oldGear });
298
+ this.logEvent({ message: 'gear changed', gear: this.gear, oldGear });
299
299
  this.data.gearStr = this.getGearString();
300
300
  this.calculateSimulatedPower('gear');
301
301
  if (this.simPower !== undefined) {
@@ -319,7 +319,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
319
319
  }
320
320
  }
321
321
  this.gear = closestIndex + 1;
322
- this.logger.logEvent({ message: 'gear changed', gear: this.gear, gearRatio: newRequest.gearRatio, oldGear });
322
+ this.logEvent({ message: 'gear changed', gear: this.gear, gearRatio: newRequest.gearRatio, oldGear });
323
323
  }
324
324
  }
325
325
  else if (request.gearDelta !== undefined) {
@@ -339,7 +339,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
339
339
  }
340
340
  delete request.gearDelta;
341
341
  newRequest.gearRatio = this.gearRatios[this.gear];
342
- this.logger.logEvent({ message: 'gear changed', gear: this.gear, gearRatio: newRequest.gearRatio, oldGear });
342
+ this.logEvent({ message: 'gear changed', gear: this.gear, gearRatio: newRequest.gearRatio, oldGear });
343
343
  }
344
344
  else {
345
345
  if (this.gear === undefined) {
@@ -347,7 +347,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
347
347
  this.gear = initialGear + request.gearDelta;
348
348
  }
349
349
  newRequest.gearRatio = this.gearRatios[this.gear];
350
- this.logger.logEvent({ message: 'gear initialized', gear: this.gear, gearRatio: newRequest.gearRatio });
350
+ this.logEvent({ message: 'gear initialized', gear: this.gear, gearRatio: newRequest.gearRatio });
351
351
  }
352
352
  break;
353
353
  case 'Disabled':
@@ -397,7 +397,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
397
397
  }
398
398
  }
399
399
  catch (err) {
400
- this.logger.logEvent({ message: 'error', fn: 'getVirtualShiftMode', error: err.message, stack: err.stack });
400
+ this.logEvent({ message: 'error', fn: 'getVirtualShiftMode', error: err.message, stack: err.stack });
401
401
  }
402
402
  return 'Disabled';
403
403
  }
@@ -432,7 +432,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
432
432
  return super.updateRequired(request);
433
433
  }
434
434
  sendBikeUpdate(incoming) {
435
- this.logger.logEvent({ message: "processing update request", request: incoming, prev: this.prevRequest, data: this.getData() });
435
+ this.logEvent({ message: "processing update request", request: incoming, prev: this.prevRequest, data: this.getData() });
436
436
  let newRequest = {};
437
437
  const request = { ...incoming };
438
438
  delete this.simPower;
@@ -449,7 +449,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
449
449
  this.prevRequest.slope = this.data.slope;
450
450
  }
451
451
  catch (err) {
452
- this.logger.logEvent({ message: "error", fn: 'sendBikeUpdate()', error: err.message, stack: err.stack });
452
+ this.logEvent({ message: "error", fn: 'sendBikeUpdate()', error: err.message, stack: err.stack });
453
453
  }
454
454
  if (newRequest.targetPower && this.simPower) {
455
455
  this.prevSimPower = newRequest.targetPower;