incyclist-devices 3.0.15 → 3.0.17
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.
- package/lib/cjs/ble/base/adapter.js +2 -1
- package/lib/cjs/ble/base/interface.js +10 -3
- package/lib/cjs/ble/fm/adapter.js +0 -1
- package/lib/cjs/ble/zwift/play/adapter.js +33 -4
- package/lib/cjs/ble/zwift/play/sensor.js +115 -15
- package/lib/cjs/modes/antble-smarttrainer.js +42 -29
- package/lib/cjs/modes/types.js +2 -0
- package/lib/cjs/proto/zwift_hub.js +6 -5
- package/lib/esm/ble/base/adapter.js +2 -1
- package/lib/esm/ble/base/interface.js +11 -4
- package/lib/esm/ble/fm/adapter.js +0 -1
- package/lib/esm/ble/zwift/play/adapter.js +33 -4
- package/lib/esm/ble/zwift/play/sensor.js +116 -16
- package/lib/esm/modes/antble-smarttrainer.js +42 -29
- package/lib/esm/modes/types.js +2 -0
- package/lib/esm/proto/zwift_hub.js +6 -5
- package/lib/types/ble/zwift/play/adapter.d.ts +5 -0
- package/lib/types/ble/zwift/play/sensor.d.ts +5 -2
- package/lib/types/modes/antble-smarttrainer.d.ts +9 -9
- package/lib/types/modes/base.d.ts +1 -1
- package/lib/types/modes/types.d.ts +2 -1
- package/lib/types/proto/zwift_hub.d.ts +1 -1
- package/package.json +2 -1
|
@@ -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
|
|
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?.
|
|
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.
|
|
284
|
+
if (device.address === settings.address) {
|
|
285
285
|
const peripheral = this.createPeripheralFromSettings(device);
|
|
286
286
|
if (peripheral) {
|
|
287
287
|
this.off('device', onDevice);
|
|
@@ -298,6 +298,11 @@ class BleInterface extends node_events_1.EventEmitter {
|
|
|
298
298
|
const { peripheral } = service;
|
|
299
299
|
if (peripheral.address === undefined || peripheral.address === '')
|
|
300
300
|
peripheral.address = peripheral.id || peripheral.name;
|
|
301
|
+
if (service.name === 'Zwift Ride' && service.serviceUUIDs.some(uuid => (0, utils_js_1.matches)(uuid, 'FC82'))) {
|
|
302
|
+
const protocol = 'zwift-play';
|
|
303
|
+
const { id, name, address } = (0, utils_js_1.getPeripheralInfo)(peripheral);
|
|
304
|
+
return { interface: BleInterface.INTERFACE_NAME, protocol, id, name, address };
|
|
305
|
+
}
|
|
301
306
|
const protocol = this.getAdapterFactory().getProtocol(service.serviceUUIDs);
|
|
302
307
|
const { id, name, address } = (0, utils_js_1.getPeripheralInfo)(peripheral);
|
|
303
308
|
return { interface: BleInterface.INTERFACE_NAME, protocol, id, name, address };
|
|
@@ -573,6 +578,8 @@ class BleInterface extends node_events_1.EventEmitter {
|
|
|
573
578
|
return false;
|
|
574
579
|
const found = service.serviceUUIDs.map(utils_js_1.parseUUID);
|
|
575
580
|
const expected = this.expectedServices.map(utils_js_1.parseUUID);
|
|
581
|
+
if (service.name.startsWith('Zwift'))
|
|
582
|
+
return true;
|
|
576
583
|
const supported = found.filter(uuid => expected.includes(uuid)) ?? [];
|
|
577
584
|
if (!supported.length) {
|
|
578
585
|
this.logEvent({ message: 'peripheral not supported', name: service.name, uuids: service.serviceUUIDs });
|
|
@@ -581,7 +588,7 @@ class BleInterface extends node_events_1.EventEmitter {
|
|
|
581
588
|
return supported.length > 0;
|
|
582
589
|
}
|
|
583
590
|
find(service) {
|
|
584
|
-
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);
|
|
585
592
|
}
|
|
586
593
|
getAll() {
|
|
587
594
|
return this.services.filter(a => a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
|
|
@@ -250,7 +250,6 @@ class BleFmAdapter extends adapter_js_1.default {
|
|
|
250
250
|
if (before !== after) {
|
|
251
251
|
this.logEvent({ message: 'device capabilities updated', name: this.getSettings().name, interface: this.getSettings().interface, capabilities: this.capabilities });
|
|
252
252
|
this.emit('device-info', this.getSettings(), { capabilities: this.capabilities });
|
|
253
|
-
this.updateCyclingModeConfig();
|
|
254
253
|
}
|
|
255
254
|
}
|
|
256
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
|
-
|
|
29
|
-
|
|
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
|
}
|
|
@@ -19,6 +19,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
19
19
|
prevClickMessage;
|
|
20
20
|
upState;
|
|
21
21
|
downState;
|
|
22
|
+
rideKeyPadStates;
|
|
22
23
|
deviceType;
|
|
23
24
|
publicKey;
|
|
24
25
|
privateKey;
|
|
@@ -66,6 +67,42 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
66
67
|
getRequiredCharacteristics() {
|
|
67
68
|
return ['00000002-19ca-4651-86e5-fa29dcdd09d1', '00000004-19ca-4651-86e5-fa29dcdd09d1'];
|
|
68
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
|
+
}
|
|
69
106
|
onData(characteristic, data, isNotify) {
|
|
70
107
|
const uuid = (0, utils_js_1.beautifyUUID)(characteristic).toLowerCase();
|
|
71
108
|
if (uuid === '00000002-19ca-4651-86e5-fa29dcdd09d1') {
|
|
@@ -153,7 +190,6 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
153
190
|
}
|
|
154
191
|
onMeasurement(d) {
|
|
155
192
|
const data = Buffer.from(d);
|
|
156
|
-
this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
|
|
157
193
|
if (data?.length < 1) {
|
|
158
194
|
console.log('Invalid click measurement data', data.toString('hex'));
|
|
159
195
|
return false;
|
|
@@ -172,15 +208,19 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
172
208
|
else if (type === 0x03) {
|
|
173
209
|
this.onRidingData(message);
|
|
174
210
|
}
|
|
211
|
+
else if (type === 0x23) {
|
|
212
|
+
this.onRideKeyPadStatus(message);
|
|
213
|
+
}
|
|
175
214
|
else if (type === 0x2A) {
|
|
176
215
|
this.onTrainerResponse(message);
|
|
177
216
|
}
|
|
178
217
|
else if (type === 0x3c) {
|
|
179
218
|
this.onDeviceInformation(message);
|
|
180
219
|
}
|
|
220
|
+
else if (type === 0x15) {
|
|
221
|
+
}
|
|
181
222
|
else {
|
|
182
|
-
|
|
183
|
-
this.emit('data', { raw: data.toString('hex') });
|
|
223
|
+
this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
|
|
184
224
|
}
|
|
185
225
|
return true;
|
|
186
226
|
}
|
|
@@ -285,6 +325,55 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
285
325
|
this.logEvent({ message: 'Error', fn: 'onRidingData', error: err.message, stack: err.stack });
|
|
286
326
|
}
|
|
287
327
|
}
|
|
328
|
+
onRideKeyPadStatus(m) {
|
|
329
|
+
try {
|
|
330
|
+
const data = zwift_hub_js_1.RideKeyPadStatus.fromBinary(m);
|
|
331
|
+
const buttonNames = new Map([
|
|
332
|
+
[0x01, 'left'],
|
|
333
|
+
[0x02, 'up'],
|
|
334
|
+
[0x04, 'right'],
|
|
335
|
+
[0x08, 'down'],
|
|
336
|
+
[0x10, 'a'],
|
|
337
|
+
[0x20, 'b'],
|
|
338
|
+
[0x40, 'y'],
|
|
339
|
+
[0x80, 'z'],
|
|
340
|
+
[0x0100, 'l-shift-up'],
|
|
341
|
+
[0x0200, 'l-shift-down'],
|
|
342
|
+
[0x1000, 'r-shift-up'],
|
|
343
|
+
[0x2000, 'r-shift-down'],
|
|
344
|
+
[0x0400, 'l-power-up'],
|
|
345
|
+
[0x4000, 'r-power-up'],
|
|
346
|
+
[0x0800, 'l-power'],
|
|
347
|
+
[0x8000, 'r-power'],
|
|
348
|
+
]);
|
|
349
|
+
const buttonMap = data.buttonMap ?? 0;
|
|
350
|
+
const currentPresses = new Set();
|
|
351
|
+
const address = this.peripheral?.getInfo()?.address;
|
|
352
|
+
const name = this.peripheral?.getInfo()?.name;
|
|
353
|
+
buttonNames.forEach((name, bit) => {
|
|
354
|
+
const isPressed = (buttonMap & bit) === 0;
|
|
355
|
+
if (isPressed) {
|
|
356
|
+
currentPresses.add(bit);
|
|
357
|
+
const prevState = this.rideKeyPadStates.get(bit);
|
|
358
|
+
if (!prevState || !prevState.pressed) {
|
|
359
|
+
this.rideKeyPadStates.set(bit, { pressed: true, timestamp: Date.now() });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
this.rideKeyPadStates.forEach((state, bit) => {
|
|
364
|
+
if (!currentPresses.has(bit) && state.pressed) {
|
|
365
|
+
const keyName = buttonNames.get(bit);
|
|
366
|
+
const duration = Date.now() - state.timestamp;
|
|
367
|
+
this.logEvent({ message: 'key pressed', key: keyName, name, address, duration, deviceType: this.deviceType });
|
|
368
|
+
this.emit('key-pressed', { key: keyName, duration, deviceType: this.deviceType });
|
|
369
|
+
this.rideKeyPadStates.set(bit, { pressed: false, timestamp: Date.now() });
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
this.logEvent({ message: 'Error', fn: 'onRideKeyPadStatus', error: err.message, stack: err.stack });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
288
377
|
onDeviceInformation(m) {
|
|
289
378
|
try {
|
|
290
379
|
const envelope = zwift_hub_js_1.DeviceDataEnvelope.fromBinary(m);
|
|
@@ -312,6 +401,8 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
312
401
|
}
|
|
313
402
|
onClickButtonMessage(d) {
|
|
314
403
|
try {
|
|
404
|
+
const address = this.peripheral?.getInfo()?.address;
|
|
405
|
+
const name = this.peripheral?.getInfo()?.name;
|
|
315
406
|
const message = Buffer.from(d);
|
|
316
407
|
const messageStr = message.toString('hex');
|
|
317
408
|
if (messageStr === this.prevClickMessage) {
|
|
@@ -322,6 +413,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
322
413
|
const prev = { ...this.upState };
|
|
323
414
|
this.upState = { pressed: false, timestamp: Date.now() };
|
|
324
415
|
if (prev.pressed) {
|
|
416
|
+
this.logEvent({ message: 'key pressed', key: 'up', name, address, duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
325
417
|
this.emit('key-pressed', { key: 'up', duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
326
418
|
}
|
|
327
419
|
}
|
|
@@ -332,6 +424,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
332
424
|
const prev = { ...this.downState };
|
|
333
425
|
this.downState = { pressed: false, timestamp: Date.now() };
|
|
334
426
|
if (prev.pressed) {
|
|
427
|
+
this.logEvent({ message: 'key pressed', key: 'down', name, address, duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
335
428
|
this.emit('key-pressed', { key: 'down', duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
336
429
|
}
|
|
337
430
|
}
|
|
@@ -430,7 +523,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
430
523
|
this.deviceType = 'hub';
|
|
431
524
|
this.encrypted = false;
|
|
432
525
|
}
|
|
433
|
-
else if (this.peripheral
|
|
526
|
+
else if (this.peripheral?.getManufacturerData) {
|
|
434
527
|
manufacturerData = this.getManufacturerData();
|
|
435
528
|
if (manufacturerData?.startsWith('4a09')) {
|
|
436
529
|
const typeVal = Number('0x' + manufacturerData.substring(2, 4));
|
|
@@ -444,6 +537,12 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
444
537
|
else if (typeVal === 3) {
|
|
445
538
|
this.deviceType = 'left';
|
|
446
539
|
}
|
|
540
|
+
else if (typeVal === 7) {
|
|
541
|
+
this.deviceType = 'ride-right';
|
|
542
|
+
}
|
|
543
|
+
else if (typeVal === 8) {
|
|
544
|
+
this.deviceType = 'ride-left';
|
|
545
|
+
}
|
|
447
546
|
}
|
|
448
547
|
}
|
|
449
548
|
if (!this.deviceType && !this.encryptedSupported()) {
|
|
@@ -451,7 +550,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
451
550
|
this.encrypted = false;
|
|
452
551
|
}
|
|
453
552
|
this.deviceType = this.deviceType ?? 'click';
|
|
454
|
-
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() });
|
|
455
554
|
let message;
|
|
456
555
|
if (this.isFM) {
|
|
457
556
|
message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x02, 0x01])]);
|
|
@@ -468,7 +567,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
468
567
|
this.logEvent({ message: `send rideOn` });
|
|
469
568
|
await this.write((0, utils_js_1.fullUUID)('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
|
|
470
569
|
this.isHubServicePaired = true;
|
|
471
|
-
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() });
|
|
472
571
|
return true;
|
|
473
572
|
}
|
|
474
573
|
catch (err) {
|
|
@@ -479,6 +578,15 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
479
578
|
}
|
|
480
579
|
reset() {
|
|
481
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
|
+
}
|
|
482
590
|
getCrypto() {
|
|
483
591
|
let crypto = index_js_1.BindingsFactory.getInstance()?.getBinding()?.crypto;
|
|
484
592
|
if (!crypto)
|
|
@@ -506,15 +614,7 @@ class BleZwiftPlaySensor extends sensor_js_1.TBleSensor {
|
|
|
506
614
|
this.isHubServiceActive = false;
|
|
507
615
|
delete this.initHubServicePromise;
|
|
508
616
|
delete this.prevHubSettings;
|
|
509
|
-
|
|
510
|
-
getManufacturerData() {
|
|
511
|
-
const data = this.peripheral.getManufacturerData();
|
|
512
|
-
if (typeof data === 'string')
|
|
513
|
-
return data;
|
|
514
|
-
if (Buffer.isBuffer(data)) {
|
|
515
|
-
return data.toString('hex');
|
|
516
|
-
}
|
|
517
|
-
return undefined;
|
|
617
|
+
this.rideKeyPadStates = new Map();
|
|
518
618
|
}
|
|
519
619
|
}
|
|
520
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
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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 = [
|
|
118
|
+
'Disabled',
|
|
119
|
+
{ key: 'Incyclist', display: 'App only (beta)' },
|
|
120
|
+
{ key: 'Mixed', display: 'App + Bike' },
|
|
121
|
+
{ key: 'SmartTrainer', display: 'SmartTreiner (beta)' }
|
|
122
|
+
];
|
|
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 = [
|
|
115
128
|
'Disabled',
|
|
116
|
-
{ key: '
|
|
129
|
+
{ key: 'Incyclist', display: 'App only (beta)' },
|
|
130
|
+
{ key: 'Mixed', display: 'App + Bike' }
|
|
117
131
|
];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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);
|
|
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' };
|
|
@@ -234,10 +240,13 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
|
|
|
234
240
|
const m = this.adapter?.getWeight() ?? 85;
|
|
235
241
|
const vCurrent = this.data.speed * 1000 / 3600;
|
|
236
242
|
const eKinCurrent = m * vCurrent * vCurrent / 2;
|
|
237
|
-
|
|
238
|
-
|
|
243
|
+
const cadence = this.data?.pedalRpm ?? 0;
|
|
244
|
+
const gear = this.gear ?? 1;
|
|
245
|
+
const simSlope = this.simSlope ?? 0;
|
|
246
|
+
if (cadence > 0) {
|
|
247
|
+
const virtualSpeed = (0, calculations_js_1.calculateVirtualSpeed)(cadence, this.gearRatios[gear - 1]) * 3.6;
|
|
239
248
|
const v = virtualSpeed / 3.6;
|
|
240
|
-
const newPower = calculations_js_1.default.calculatePower(m, virtualSpeed / 3.6,
|
|
249
|
+
const newPower = calculations_js_1.default.calculatePower(m, virtualSpeed / 3.6, simSlope);
|
|
241
250
|
const prevPower = this.data.power;
|
|
242
251
|
const prev = (this.prevSimPower ?? prevPower ?? 0);
|
|
243
252
|
const delta = newPower - prev;
|
|
@@ -253,7 +262,7 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
|
|
|
253
262
|
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
263
|
}
|
|
255
264
|
else if (changed === 'slope' || changed === 'cadence') {
|
|
256
|
-
const adjustTime = this.simSlope < 0 ? 5 : 3;
|
|
265
|
+
const adjustTime = (this.simSlope ?? 0) < 0 ? 5 : 3;
|
|
257
266
|
this.simPower = prev + delta / adjustTime;
|
|
258
267
|
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
268
|
}
|
|
@@ -344,9 +353,13 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
|
|
|
344
353
|
else {
|
|
345
354
|
if (this.gear === undefined) {
|
|
346
355
|
const initialGear = (0, utils_js_1.intVal)(this.getSetting('startGear'));
|
|
347
|
-
this.gear = initialGear + request.gearDelta;
|
|
356
|
+
this.gear = initialGear + (request.gearDelta ?? 0);
|
|
348
357
|
}
|
|
349
|
-
|
|
358
|
+
if (this.gear < 1)
|
|
359
|
+
this.gear = 1;
|
|
360
|
+
if (this.gear > this.gearRatios.length - 1)
|
|
361
|
+
this.gear = this.gearRatios.length - 1;
|
|
362
|
+
newRequest.gearRatio = this.gearRatios[this.gear - 1];
|
|
350
363
|
this.logEvent({ message: 'gear initialized', gear: this.gear, gearRatio: newRequest.gearRatio });
|
|
351
364
|
}
|
|
352
365
|
break;
|
|
@@ -356,10 +369,10 @@ class SmartTrainerCyclingMode extends power_base_js_1.default {
|
|
|
356
369
|
}
|
|
357
370
|
}
|
|
358
371
|
verifySimPower() {
|
|
359
|
-
if (this.simPower < 0) {
|
|
372
|
+
if ((this.simPower ?? 0) < 0) {
|
|
360
373
|
this.simPower = 0;
|
|
361
374
|
}
|
|
362
|
-
if (this.data.pedalRpm > 0 && this.simPower < MIN_POWER) {
|
|
375
|
+
if ((this.data.pedalRpm ?? 0) > 0 && (this.simPower ?? 0) < MIN_POWER) {
|
|
363
376
|
this.simPower = MIN_POWER;
|
|
364
377
|
}
|
|
365
378
|
if (!this.data.isPedalling) {
|
package/lib/cjs/modes/types.js
CHANGED
|
@@ -672,11 +672,12 @@ class RideKeyPadStatus$Type extends runtime_4.MessageType {
|
|
|
672
672
|
constructor() {
|
|
673
673
|
super("org.cagnulen.qdomyoszwift.RideKeyPadStatus", [
|
|
674
674
|
{ no: 1, name: "ButtonMap", kind: "scalar", jsonName: "ButtonMap", opt: true, T: 13 },
|
|
675
|
-
{ no:
|
|
675
|
+
{ no: 3, name: "AnalogButtons", kind: "message", jsonName: "AnalogButtons", repeat: 2, T: () => exports.RideAnalogKeyPress }
|
|
676
676
|
]);
|
|
677
677
|
}
|
|
678
678
|
create(value) {
|
|
679
679
|
const message = globalThis.Object.create((this.messagePrototype));
|
|
680
|
+
message.analogButtons = [];
|
|
680
681
|
if (value !== undefined)
|
|
681
682
|
(0, runtime_3.reflectionMergePartial)(this, message, value);
|
|
682
683
|
return message;
|
|
@@ -689,8 +690,8 @@ class RideKeyPadStatus$Type extends runtime_4.MessageType {
|
|
|
689
690
|
case 1:
|
|
690
691
|
message.buttonMap = reader.uint32();
|
|
691
692
|
break;
|
|
692
|
-
case
|
|
693
|
-
message.analogButtons
|
|
693
|
+
case 3:
|
|
694
|
+
message.analogButtons.push(exports.RideAnalogKeyPress.internalBinaryRead(reader, reader.uint32(), options));
|
|
694
695
|
break;
|
|
695
696
|
default:
|
|
696
697
|
let u = options.readUnknownField;
|
|
@@ -706,8 +707,8 @@ class RideKeyPadStatus$Type extends runtime_4.MessageType {
|
|
|
706
707
|
internalBinaryWrite(message, writer, options) {
|
|
707
708
|
if (message.buttonMap !== undefined)
|
|
708
709
|
writer.tag(1, runtime_1.WireType.Varint).uint32(message.buttonMap);
|
|
709
|
-
|
|
710
|
-
exports.
|
|
710
|
+
for (let i = 0; i < message.analogButtons.length; i++)
|
|
711
|
+
exports.RideAnalogKeyPress.internalBinaryWrite(message.analogButtons[i], writer.tag(3, runtime_1.WireType.LengthDelimited).fork(), options).join();
|
|
711
712
|
let u = options.writeUnknownFields;
|
|
712
713
|
if (u !== false)
|
|
713
714
|
(u == true ? runtime_2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
@@ -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
|
|
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
|
}
|
|
@@ -2,7 +2,7 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import { EventLogger } from "gd-eventlog";
|
|
3
3
|
import { InteruptableTask } from "../../utils/task.js";
|
|
4
4
|
import { BlePeripheral } from "./peripheral.js";
|
|
5
|
-
import { beautifyUUID, getPeripheralInfo, parseUUID } from "../utils.js";
|
|
5
|
+
import { beautifyUUID, getPeripheralInfo, matches, parseUUID } from "../utils.js";
|
|
6
6
|
import { InterfaceFactory } from "./types.js";
|
|
7
7
|
import { BleAdapterFactory } from "../factories/index.js";
|
|
8
8
|
const BLE_EXPIRATION_TIMEOUT = 10 * 1000 * 60;
|
|
@@ -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?.
|
|
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.
|
|
281
|
+
if (device.address === settings.address) {
|
|
282
282
|
const peripheral = this.createPeripheralFromSettings(device);
|
|
283
283
|
if (peripheral) {
|
|
284
284
|
this.off('device', onDevice);
|
|
@@ -295,6 +295,11 @@ export class BleInterface extends EventEmitter {
|
|
|
295
295
|
const { peripheral } = service;
|
|
296
296
|
if (peripheral.address === undefined || peripheral.address === '')
|
|
297
297
|
peripheral.address = peripheral.id || peripheral.name;
|
|
298
|
+
if (service.name === 'Zwift Ride' && service.serviceUUIDs.some(uuid => matches(uuid, 'FC82'))) {
|
|
299
|
+
const protocol = 'zwift-play';
|
|
300
|
+
const { id, name, address } = getPeripheralInfo(peripheral);
|
|
301
|
+
return { interface: BleInterface.INTERFACE_NAME, protocol, id, name, address };
|
|
302
|
+
}
|
|
298
303
|
const protocol = this.getAdapterFactory().getProtocol(service.serviceUUIDs);
|
|
299
304
|
const { id, name, address } = getPeripheralInfo(peripheral);
|
|
300
305
|
return { interface: BleInterface.INTERFACE_NAME, protocol, id, name, address };
|
|
@@ -570,6 +575,8 @@ export class BleInterface extends EventEmitter {
|
|
|
570
575
|
return false;
|
|
571
576
|
const found = service.serviceUUIDs.map(parseUUID);
|
|
572
577
|
const expected = this.expectedServices.map(parseUUID);
|
|
578
|
+
if (service.name.startsWith('Zwift'))
|
|
579
|
+
return true;
|
|
573
580
|
const supported = found.filter(uuid => expected.includes(uuid)) ?? [];
|
|
574
581
|
if (!supported.length) {
|
|
575
582
|
this.logEvent({ message: 'peripheral not supported', name: service.name, uuids: service.serviceUUIDs });
|
|
@@ -578,7 +585,7 @@ export class BleInterface extends EventEmitter {
|
|
|
578
585
|
return supported.length > 0;
|
|
579
586
|
}
|
|
580
587
|
find(service) {
|
|
581
|
-
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);
|
|
582
589
|
}
|
|
583
590
|
getAll() {
|
|
584
591
|
return this.services.filter(a => a.ts > Date.now() - BLE_EXPIRATION_TIMEOUT);
|
|
@@ -245,7 +245,6 @@ export default class BleFmAdapter extends BleAdapter {
|
|
|
245
245
|
if (before !== after) {
|
|
246
246
|
this.logEvent({ message: 'device capabilities updated', name: this.getSettings().name, interface: this.getSettings().interface, capabilities: this.capabilities });
|
|
247
247
|
this.emit('device-info', this.getSettings(), { capabilities: this.capabilities });
|
|
248
|
-
this.updateCyclingModeConfig();
|
|
249
248
|
}
|
|
250
249
|
}
|
|
251
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
|
-
|
|
23
|
-
|
|
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
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClickKeyPadStatus, DeviceDataEnvelope, DeviceInformationContent, DeviceSettings, HubCommand, HubRequest, HubRidingData, Idle, PlayButtonStatus, TrainerResponse } from "../../../proto/zwift_hub.js";
|
|
1
|
+
import { ClickKeyPadStatus, DeviceDataEnvelope, DeviceInformationContent, DeviceSettings, HubCommand, HubRequest, HubRidingData, Idle, PlayButtonStatus, RideKeyPadStatus, TrainerResponse } from "../../../proto/zwift_hub.js";
|
|
2
2
|
import { TBleSensor } from "../../base/sensor.js";
|
|
3
3
|
import { beautifyUUID, fullUUID } from "../../utils.js";
|
|
4
4
|
import { EventEmitter } from "node:events";
|
|
@@ -16,6 +16,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
16
16
|
prevClickMessage;
|
|
17
17
|
upState;
|
|
18
18
|
downState;
|
|
19
|
+
rideKeyPadStates;
|
|
19
20
|
deviceType;
|
|
20
21
|
publicKey;
|
|
21
22
|
privateKey;
|
|
@@ -63,6 +64,42 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
63
64
|
getRequiredCharacteristics() {
|
|
64
65
|
return ['00000002-19ca-4651-86e5-fa29dcdd09d1', '00000004-19ca-4651-86e5-fa29dcdd09d1'];
|
|
65
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
|
+
}
|
|
66
103
|
onData(characteristic, data, isNotify) {
|
|
67
104
|
const uuid = beautifyUUID(characteristic).toLowerCase();
|
|
68
105
|
if (uuid === '00000002-19ca-4651-86e5-fa29dcdd09d1') {
|
|
@@ -150,7 +187,6 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
150
187
|
}
|
|
151
188
|
onMeasurement(d) {
|
|
152
189
|
const data = Buffer.from(d);
|
|
153
|
-
this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
|
|
154
190
|
if (data?.length < 1) {
|
|
155
191
|
console.log('Invalid click measurement data', data.toString('hex'));
|
|
156
192
|
return false;
|
|
@@ -169,15 +205,19 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
169
205
|
else if (type === 0x03) {
|
|
170
206
|
this.onRidingData(message);
|
|
171
207
|
}
|
|
208
|
+
else if (type === 0x23) {
|
|
209
|
+
this.onRideKeyPadStatus(message);
|
|
210
|
+
}
|
|
172
211
|
else if (type === 0x2A) {
|
|
173
212
|
this.onTrainerResponse(message);
|
|
174
213
|
}
|
|
175
214
|
else if (type === 0x3c) {
|
|
176
215
|
this.onDeviceInformation(message);
|
|
177
216
|
}
|
|
217
|
+
else if (type === 0x15) {
|
|
218
|
+
}
|
|
178
219
|
else {
|
|
179
|
-
|
|
180
|
-
this.emit('data', { raw: data.toString('hex') });
|
|
220
|
+
this.logEvent({ message: 'got hub notification', raw: data.toString('hex') });
|
|
181
221
|
}
|
|
182
222
|
return true;
|
|
183
223
|
}
|
|
@@ -282,6 +322,55 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
282
322
|
this.logEvent({ message: 'Error', fn: 'onRidingData', error: err.message, stack: err.stack });
|
|
283
323
|
}
|
|
284
324
|
}
|
|
325
|
+
onRideKeyPadStatus(m) {
|
|
326
|
+
try {
|
|
327
|
+
const data = RideKeyPadStatus.fromBinary(m);
|
|
328
|
+
const buttonNames = new Map([
|
|
329
|
+
[0x01, 'left'],
|
|
330
|
+
[0x02, 'up'],
|
|
331
|
+
[0x04, 'right'],
|
|
332
|
+
[0x08, 'down'],
|
|
333
|
+
[0x10, 'a'],
|
|
334
|
+
[0x20, 'b'],
|
|
335
|
+
[0x40, 'y'],
|
|
336
|
+
[0x80, 'z'],
|
|
337
|
+
[0x0100, 'l-shift-up'],
|
|
338
|
+
[0x0200, 'l-shift-down'],
|
|
339
|
+
[0x1000, 'r-shift-up'],
|
|
340
|
+
[0x2000, 'r-shift-down'],
|
|
341
|
+
[0x0400, 'l-power-up'],
|
|
342
|
+
[0x4000, 'r-power-up'],
|
|
343
|
+
[0x0800, 'l-power'],
|
|
344
|
+
[0x8000, 'r-power'],
|
|
345
|
+
]);
|
|
346
|
+
const buttonMap = data.buttonMap ?? 0;
|
|
347
|
+
const currentPresses = new Set();
|
|
348
|
+
const address = this.peripheral?.getInfo()?.address;
|
|
349
|
+
const name = this.peripheral?.getInfo()?.name;
|
|
350
|
+
buttonNames.forEach((name, bit) => {
|
|
351
|
+
const isPressed = (buttonMap & bit) === 0;
|
|
352
|
+
if (isPressed) {
|
|
353
|
+
currentPresses.add(bit);
|
|
354
|
+
const prevState = this.rideKeyPadStates.get(bit);
|
|
355
|
+
if (!prevState || !prevState.pressed) {
|
|
356
|
+
this.rideKeyPadStates.set(bit, { pressed: true, timestamp: Date.now() });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
this.rideKeyPadStates.forEach((state, bit) => {
|
|
361
|
+
if (!currentPresses.has(bit) && state.pressed) {
|
|
362
|
+
const keyName = buttonNames.get(bit);
|
|
363
|
+
const duration = Date.now() - state.timestamp;
|
|
364
|
+
this.logEvent({ message: 'key pressed', key: keyName, name, address, duration, deviceType: this.deviceType });
|
|
365
|
+
this.emit('key-pressed', { key: keyName, duration, deviceType: this.deviceType });
|
|
366
|
+
this.rideKeyPadStates.set(bit, { pressed: false, timestamp: Date.now() });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
this.logEvent({ message: 'Error', fn: 'onRideKeyPadStatus', error: err.message, stack: err.stack });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
285
374
|
onDeviceInformation(m) {
|
|
286
375
|
try {
|
|
287
376
|
const envelope = DeviceDataEnvelope.fromBinary(m);
|
|
@@ -309,6 +398,8 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
309
398
|
}
|
|
310
399
|
onClickButtonMessage(d) {
|
|
311
400
|
try {
|
|
401
|
+
const address = this.peripheral?.getInfo()?.address;
|
|
402
|
+
const name = this.peripheral?.getInfo()?.name;
|
|
312
403
|
const message = Buffer.from(d);
|
|
313
404
|
const messageStr = message.toString('hex');
|
|
314
405
|
if (messageStr === this.prevClickMessage) {
|
|
@@ -319,6 +410,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
319
410
|
const prev = { ...this.upState };
|
|
320
411
|
this.upState = { pressed: false, timestamp: Date.now() };
|
|
321
412
|
if (prev.pressed) {
|
|
413
|
+
this.logEvent({ message: 'key pressed', key: 'up', name, address, duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
322
414
|
this.emit('key-pressed', { key: 'up', duration: this.upState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
323
415
|
}
|
|
324
416
|
}
|
|
@@ -329,6 +421,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
329
421
|
const prev = { ...this.downState };
|
|
330
422
|
this.downState = { pressed: false, timestamp: Date.now() };
|
|
331
423
|
if (prev.pressed) {
|
|
424
|
+
this.logEvent({ message: 'key pressed', key: 'down', name, address, duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
332
425
|
this.emit('key-pressed', { key: 'down', duration: this.downState.timestamp - prev.timestamp, deviceType: this.deviceType });
|
|
333
426
|
}
|
|
334
427
|
}
|
|
@@ -427,7 +520,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
427
520
|
this.deviceType = 'hub';
|
|
428
521
|
this.encrypted = false;
|
|
429
522
|
}
|
|
430
|
-
else if (this.peripheral
|
|
523
|
+
else if (this.peripheral?.getManufacturerData) {
|
|
431
524
|
manufacturerData = this.getManufacturerData();
|
|
432
525
|
if (manufacturerData?.startsWith('4a09')) {
|
|
433
526
|
const typeVal = Number('0x' + manufacturerData.substring(2, 4));
|
|
@@ -441,6 +534,12 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
441
534
|
else if (typeVal === 3) {
|
|
442
535
|
this.deviceType = 'left';
|
|
443
536
|
}
|
|
537
|
+
else if (typeVal === 7) {
|
|
538
|
+
this.deviceType = 'ride-right';
|
|
539
|
+
}
|
|
540
|
+
else if (typeVal === 8) {
|
|
541
|
+
this.deviceType = 'ride-left';
|
|
542
|
+
}
|
|
444
543
|
}
|
|
445
544
|
}
|
|
446
545
|
if (!this.deviceType && !this.encryptedSupported()) {
|
|
@@ -448,7 +547,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
448
547
|
this.encrypted = false;
|
|
449
548
|
}
|
|
450
549
|
this.deviceType = this.deviceType ?? 'click';
|
|
451
|
-
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() });
|
|
452
551
|
let message;
|
|
453
552
|
if (this.isFM) {
|
|
454
553
|
message = Buffer.concat([Buffer.from('RideOn'), Buffer.from([0x02, 0x01])]);
|
|
@@ -465,7 +564,7 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
465
564
|
this.logEvent({ message: `send rideOn` });
|
|
466
565
|
await this.write(fullUUID('00000003-19ca-4651-86e5-fa29dcdd09d1'), message, { withoutResponse: true });
|
|
467
566
|
this.isHubServicePaired = true;
|
|
468
|
-
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() });
|
|
469
568
|
return true;
|
|
470
569
|
}
|
|
471
570
|
catch (err) {
|
|
@@ -476,6 +575,15 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
476
575
|
}
|
|
477
576
|
reset() {
|
|
478
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
|
+
}
|
|
479
587
|
getCrypto() {
|
|
480
588
|
let crypto = BindingsFactory.getInstance()?.getBinding()?.crypto;
|
|
481
589
|
if (!crypto)
|
|
@@ -503,14 +611,6 @@ export class BleZwiftPlaySensor extends TBleSensor {
|
|
|
503
611
|
this.isHubServiceActive = false;
|
|
504
612
|
delete this.initHubServicePromise;
|
|
505
613
|
delete this.prevHubSettings;
|
|
506
|
-
|
|
507
|
-
getManufacturerData() {
|
|
508
|
-
const data = this.peripheral.getManufacturerData();
|
|
509
|
-
if (typeof data === 'string')
|
|
510
|
-
return data;
|
|
511
|
-
if (Buffer.isBuffer(data)) {
|
|
512
|
-
return data.toString('hex');
|
|
513
|
-
}
|
|
514
|
-
return undefined;
|
|
614
|
+
this.rideKeyPadStates = new Map();
|
|
515
615
|
}
|
|
516
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
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 = [
|
|
80
|
+
'Disabled',
|
|
81
|
+
{ key: 'Incyclist', display: 'App only (beta)' },
|
|
82
|
+
{ key: 'Mixed', display: 'App + Bike' },
|
|
83
|
+
{ key: 'SmartTrainer', display: 'SmartTreiner (beta)' }
|
|
84
|
+
];
|
|
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 = [
|
|
77
90
|
'Disabled',
|
|
78
|
-
{ key: '
|
|
91
|
+
{ key: 'Incyclist', display: 'App only (beta)' },
|
|
92
|
+
{ key: 'Mixed', display: 'App + Bike' }
|
|
79
93
|
];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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);
|
|
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' };
|
|
@@ -196,10 +202,13 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase {
|
|
|
196
202
|
const m = this.adapter?.getWeight() ?? 85;
|
|
197
203
|
const vCurrent = this.data.speed * 1000 / 3600;
|
|
198
204
|
const eKinCurrent = m * vCurrent * vCurrent / 2;
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
const cadence = this.data?.pedalRpm ?? 0;
|
|
206
|
+
const gear = this.gear ?? 1;
|
|
207
|
+
const simSlope = this.simSlope ?? 0;
|
|
208
|
+
if (cadence > 0) {
|
|
209
|
+
const virtualSpeed = calculateVirtualSpeed(cadence, this.gearRatios[gear - 1]) * 3.6;
|
|
201
210
|
const v = virtualSpeed / 3.6;
|
|
202
|
-
const newPower = calc.calculatePower(m, virtualSpeed / 3.6,
|
|
211
|
+
const newPower = calc.calculatePower(m, virtualSpeed / 3.6, simSlope);
|
|
203
212
|
const prevPower = this.data.power;
|
|
204
213
|
const prev = (this.prevSimPower ?? prevPower ?? 0);
|
|
205
214
|
const delta = newPower - prev;
|
|
@@ -215,7 +224,7 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase {
|
|
|
215
224
|
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 });
|
|
216
225
|
}
|
|
217
226
|
else if (changed === 'slope' || changed === 'cadence') {
|
|
218
|
-
const adjustTime = this.simSlope < 0 ? 5 : 3;
|
|
227
|
+
const adjustTime = (this.simSlope ?? 0) < 0 ? 5 : 3;
|
|
219
228
|
this.simPower = prev + delta / adjustTime;
|
|
220
229
|
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 });
|
|
221
230
|
}
|
|
@@ -306,9 +315,13 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase {
|
|
|
306
315
|
else {
|
|
307
316
|
if (this.gear === undefined) {
|
|
308
317
|
const initialGear = intVal(this.getSetting('startGear'));
|
|
309
|
-
this.gear = initialGear + request.gearDelta;
|
|
318
|
+
this.gear = initialGear + (request.gearDelta ?? 0);
|
|
310
319
|
}
|
|
311
|
-
|
|
320
|
+
if (this.gear < 1)
|
|
321
|
+
this.gear = 1;
|
|
322
|
+
if (this.gear > this.gearRatios.length - 1)
|
|
323
|
+
this.gear = this.gearRatios.length - 1;
|
|
324
|
+
newRequest.gearRatio = this.gearRatios[this.gear - 1];
|
|
312
325
|
this.logEvent({ message: 'gear initialized', gear: this.gear, gearRatio: newRequest.gearRatio });
|
|
313
326
|
}
|
|
314
327
|
break;
|
|
@@ -318,10 +331,10 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase {
|
|
|
318
331
|
}
|
|
319
332
|
}
|
|
320
333
|
verifySimPower() {
|
|
321
|
-
if (this.simPower < 0) {
|
|
334
|
+
if ((this.simPower ?? 0) < 0) {
|
|
322
335
|
this.simPower = 0;
|
|
323
336
|
}
|
|
324
|
-
if (this.data.pedalRpm > 0 && this.simPower < MIN_POWER) {
|
|
337
|
+
if ((this.data.pedalRpm ?? 0) > 0 && (this.simPower ?? 0) < MIN_POWER) {
|
|
325
338
|
this.simPower = MIN_POWER;
|
|
326
339
|
}
|
|
327
340
|
if (!this.data.isPedalling) {
|
package/lib/esm/modes/types.js
CHANGED
|
@@ -669,11 +669,12 @@ class RideKeyPadStatus$Type extends MessageType {
|
|
|
669
669
|
constructor() {
|
|
670
670
|
super("org.cagnulen.qdomyoszwift.RideKeyPadStatus", [
|
|
671
671
|
{ no: 1, name: "ButtonMap", kind: "scalar", jsonName: "ButtonMap", opt: true, T: 13 },
|
|
672
|
-
{ no:
|
|
672
|
+
{ no: 3, name: "AnalogButtons", kind: "message", jsonName: "AnalogButtons", repeat: 2, T: () => RideAnalogKeyPress }
|
|
673
673
|
]);
|
|
674
674
|
}
|
|
675
675
|
create(value) {
|
|
676
676
|
const message = globalThis.Object.create((this.messagePrototype));
|
|
677
|
+
message.analogButtons = [];
|
|
677
678
|
if (value !== undefined)
|
|
678
679
|
reflectionMergePartial(this, message, value);
|
|
679
680
|
return message;
|
|
@@ -686,8 +687,8 @@ class RideKeyPadStatus$Type extends MessageType {
|
|
|
686
687
|
case 1:
|
|
687
688
|
message.buttonMap = reader.uint32();
|
|
688
689
|
break;
|
|
689
|
-
case
|
|
690
|
-
message.analogButtons
|
|
690
|
+
case 3:
|
|
691
|
+
message.analogButtons.push(RideAnalogKeyPress.internalBinaryRead(reader, reader.uint32(), options));
|
|
691
692
|
break;
|
|
692
693
|
default:
|
|
693
694
|
let u = options.readUnknownField;
|
|
@@ -703,8 +704,8 @@ class RideKeyPadStatus$Type extends MessageType {
|
|
|
703
704
|
internalBinaryWrite(message, writer, options) {
|
|
704
705
|
if (message.buttonMap !== undefined)
|
|
705
706
|
writer.tag(1, WireType.Varint).uint32(message.buttonMap);
|
|
706
|
-
|
|
707
|
-
|
|
707
|
+
for (let i = 0; i < message.analogButtons.length; i++)
|
|
708
|
+
RideAnalogKeyPress.internalBinaryWrite(message.analogButtons[i], writer.tag(3, WireType.LengthDelimited).fork(), options).join();
|
|
708
709
|
let u = options.writeUnknownFields;
|
|
709
710
|
if (u !== false)
|
|
710
711
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
@@ -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;
|
|
@@ -27,6 +27,7 @@ export declare class BleZwiftPlaySensor extends TBleSensor {
|
|
|
27
27
|
protected prevClickMessage: string;
|
|
28
28
|
protected upState: ButtonState;
|
|
29
29
|
protected downState: ButtonState;
|
|
30
|
+
protected rideKeyPadStates: Map<number, ButtonState>;
|
|
30
31
|
protected deviceType: DeviceType;
|
|
31
32
|
protected publicKey: Buffer;
|
|
32
33
|
protected privateKey: Buffer;
|
|
@@ -44,6 +45,7 @@ export declare class BleZwiftPlaySensor extends TBleSensor {
|
|
|
44
45
|
reconnectSensor(): Promise<boolean>;
|
|
45
46
|
stopSensor(): Promise<boolean>;
|
|
46
47
|
protected getRequiredCharacteristics(): Array<string>;
|
|
48
|
+
getDeviceType(): DeviceType;
|
|
47
49
|
onData(characteristic: string, data: Buffer, isNotify?: boolean): boolean;
|
|
48
50
|
requestDataUpdate(dataId: number): Promise<void>;
|
|
49
51
|
setSimulationData(data?: SimulationParam): Promise<void>;
|
|
@@ -56,6 +58,7 @@ export declare class BleZwiftPlaySensor extends TBleSensor {
|
|
|
56
58
|
sendHubCommand(command: HubCommand): Promise<Buffer<ArrayBufferLike>>;
|
|
57
59
|
protected onTrainerResponse(m: Buffer): void;
|
|
58
60
|
protected onRidingData(m: Buffer): void;
|
|
61
|
+
protected onRideKeyPadStatus(m: Buffer): void;
|
|
59
62
|
protected onDeviceInformation(m: Buffer): void;
|
|
60
63
|
onClickButtonMessage(d: Buffer): void;
|
|
61
64
|
onPingMessage(message: Buffer): void;
|
|
@@ -63,10 +66,10 @@ export declare class BleZwiftPlaySensor extends TBleSensor {
|
|
|
63
66
|
read(characteristic: string, ignoreErrors?: boolean): Promise<Buffer | null>;
|
|
64
67
|
pair(): Promise<boolean>;
|
|
65
68
|
reset(): void;
|
|
69
|
+
getManufacturerData(): string;
|
|
66
70
|
protected getCrypto(): ICryptoBinding;
|
|
67
71
|
protected encryptedSupported(): boolean;
|
|
68
72
|
protected createKeyPair(): any;
|
|
69
73
|
protected setInitialState(): void;
|
|
70
|
-
protected getManufacturerData(): string;
|
|
71
74
|
}
|
|
72
75
|
export {};
|
|
@@ -28,14 +28,14 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase i
|
|
|
28
28
|
})[];
|
|
29
29
|
};
|
|
30
30
|
protected gearDelta: number;
|
|
31
|
-
protected gear
|
|
32
|
-
protected tsStart
|
|
33
|
-
protected simPower
|
|
34
|
-
protected simSlope
|
|
35
|
-
protected maintainPower
|
|
36
|
-
protected prevData
|
|
37
|
-
protected prevEkin
|
|
38
|
-
protected prevSimPower
|
|
31
|
+
protected gear?: number;
|
|
32
|
+
protected tsStart?: number;
|
|
33
|
+
protected simPower?: number;
|
|
34
|
+
protected simSlope?: number;
|
|
35
|
+
protected maintainPower?: number;
|
|
36
|
+
protected prevData?: any;
|
|
37
|
+
protected prevEkin?: number;
|
|
38
|
+
protected prevSimPower?: number;
|
|
39
39
|
protected readonly gearRatios: number[];
|
|
40
40
|
constructor(adapter: IAdapter, props?: any);
|
|
41
41
|
getBikeInitRequest(): UpdateRequest;
|
|
@@ -58,6 +58,6 @@ export default class SmartTrainerCyclingMode extends PowerBasedCyclingModeBase i
|
|
|
58
58
|
getData(): Partial<IncyclistBikeData>;
|
|
59
59
|
protected updateRequired(request?: UpdateRequest): boolean;
|
|
60
60
|
sendBikeUpdate(incoming: UpdateRequest): UpdateRequest;
|
|
61
|
-
protected getGearString(): string;
|
|
61
|
+
protected getGearString(): string | undefined;
|
|
62
62
|
protected getFeatureToogle(): import("../features/features.js").FeatureToggle;
|
|
63
63
|
}
|
|
@@ -5,7 +5,7 @@ export declare abstract class CyclingModeBase extends CyclingMode implements ICy
|
|
|
5
5
|
adapter: IAdapter;
|
|
6
6
|
settings: Settings;
|
|
7
7
|
properties: Settings;
|
|
8
|
-
localConfig
|
|
8
|
+
localConfig?: CyclingModeConfig;
|
|
9
9
|
protected static config: CyclingModeConfig;
|
|
10
10
|
protected static isERG: boolean;
|
|
11
11
|
protected prevUpdate: UpdateRequest;
|
|
@@ -47,7 +47,7 @@ export default interface ICyclingMode {
|
|
|
47
47
|
buildUpdate(request: UpdateRequest): UpdateRequest;
|
|
48
48
|
confirmed(request: UpdateRequest): void;
|
|
49
49
|
updateData(data: IncyclistBikeData): IncyclistBikeData;
|
|
50
|
-
setSettings(settings: any):
|
|
50
|
+
setSettings(settings: any): void;
|
|
51
51
|
setSetting(name: string, value: any): void;
|
|
52
52
|
getSetting(name: string): any;
|
|
53
53
|
getSettings(): Settings;
|
|
@@ -88,4 +88,5 @@ export declare class CyclingMode implements ICyclingMode {
|
|
|
88
88
|
resetConfig(): void;
|
|
89
89
|
getData(): Partial<IncyclistBikeData>;
|
|
90
90
|
confirmed(request: UpdateRequest): void;
|
|
91
|
+
onAdapterCapabilitiesChanged(): void;
|
|
91
92
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "incyclist-devices",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.17",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"lint": "eslint . --ext .ts",
|
|
6
6
|
"build": "npm run build:esm && npm run build:cjs",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@serialport/binding-mock": "^10.2.2",
|
|
32
32
|
"@serialport/bindings-cpp": "^13.0.1",
|
|
33
33
|
"@stoprocent/noble": "^2.4.0",
|
|
34
|
+
"@types/jest": "^30.0.0",
|
|
34
35
|
"@types/node": "^25.6.0",
|
|
35
36
|
"@typescript-eslint/eslint-plugin": "^8.59.0",
|
|
36
37
|
"@typescript-eslint/parser": "^8.59.0",
|