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.
- package/lib/cjs/antv2/base/interface.js +11 -1
- package/lib/cjs/antv2/fe/adapter.js +3 -0
- package/lib/cjs/base/adpater.js +11 -1
- package/lib/cjs/bindings/crypto/types.js +2 -0
- package/lib/cjs/bindings/index.js +18 -0
- package/lib/cjs/bindings/types.js +2 -0
- package/lib/cjs/ble/base/adapter.js +1 -1
- package/lib/cjs/ble/base/interface.js +2 -1
- package/lib/cjs/ble/base/peripheral.js +12 -0
- package/lib/cjs/ble/base/sensor.js +5 -0
- package/lib/cjs/ble/consts.js +3 -1
- package/lib/cjs/ble/fm/adapter.js +12 -0
- package/lib/cjs/ble/fm/sensor.js +211 -2
- package/lib/cjs/ble/zwift/play/sensor.js +11 -3
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/modes/ant-fe-adv-st-mode.js +2 -2
- package/lib/cjs/modes/antble-smarttrainer.js +12 -12
- package/lib/cjs/modes/base.js +10 -0
- package/lib/cjs/modes/daum-classic-standard.js +4 -4
- package/lib/cjs/modes/daum-erg.js +3 -3
- package/lib/cjs/modes/daum-premium-standard.js +7 -7
- package/lib/cjs/modes/daum-smarttrainer.js +8 -9
- package/lib/cjs/modes/fm-resistance.js +2 -2
- package/lib/cjs/modes/kettler-erg.js +4 -4
- package/lib/cjs/modes/power-base.js +4 -9
- package/lib/cjs/modes/power-meter.js +1 -1
- package/lib/cjs/modes/rower.js +49 -0
- package/lib/cjs/serial/base/serial-interface.js +10 -7
- package/lib/cjs/serial/daum/DaumAdapter.js +3 -0
- package/lib/cjs/serial/kettler/ergo-racer/adapter.js +3 -0
- package/lib/cjs/types/sport.js +2 -0
- package/lib/esm/antv2/base/interface.js +11 -1
- package/lib/esm/antv2/fe/adapter.js +3 -0
- package/lib/esm/base/adpater.js +11 -1
- package/lib/esm/bindings/crypto/index.js +14 -0
- package/lib/esm/bindings/crypto/types.js +1 -0
- package/lib/esm/bindings/index.js +14 -0
- package/lib/esm/bindings/types.js +1 -0
- package/lib/esm/ble/base/adapter.js +1 -1
- package/lib/esm/ble/base/interface.js +2 -1
- package/lib/esm/ble/base/peripheral.js +13 -1
- package/lib/esm/ble/base/sensor.js +5 -0
- package/lib/esm/ble/bindings/crypto/types.js +1 -0
- package/lib/esm/ble/consts.js +2 -0
- package/lib/esm/ble/fm/adapter.js +12 -0
- package/lib/esm/ble/fm/sensor.js +213 -4
- package/lib/esm/ble/zwift/play/sensor.js +11 -3
- package/lib/esm/index.js +1 -0
- package/lib/esm/modes/ant-fe-adv-st-mode.js +2 -2
- package/lib/esm/modes/antble-smarttrainer.js +12 -12
- package/lib/esm/modes/base.js +10 -0
- package/lib/esm/modes/daum-classic-standard.js +4 -4
- package/lib/esm/modes/daum-erg.js +3 -3
- package/lib/esm/modes/daum-premium-standard.js +7 -7
- package/lib/esm/modes/daum-smarttrainer.js +8 -9
- package/lib/esm/modes/fm-resistance.js +2 -2
- package/lib/esm/modes/kettler-erg.js +4 -4
- package/lib/esm/modes/power-base.js +4 -9
- package/lib/esm/modes/power-meter copy.js +33 -0
- package/lib/esm/modes/power-meter.js +1 -1
- package/lib/esm/modes/rower.js +43 -0
- package/lib/esm/serial/base/serial-interface.js +10 -7
- package/lib/esm/serial/daum/DaumAdapter.js +3 -0
- package/lib/esm/serial/kettler/ergo-racer/adapter.js +3 -0
- package/lib/esm/types/sport.js +1 -0
- package/lib/types/antv2/base/interface.d.ts +11 -8
- package/lib/types/antv2/fe/adapter.d.ts +4 -2
- package/lib/types/base/adpater.d.ts +6 -2
- package/lib/types/bindings/crypto/index.d.ts +8 -0
- package/lib/types/bindings/crypto/types.d.ts +45 -0
- package/lib/types/bindings/index.d.ts +8 -0
- package/lib/types/bindings/types.d.ts +4 -0
- package/lib/types/ble/base/peripheral.d.ts +3 -1
- package/lib/types/ble/bindings/crypto/types.d.ts +45 -0
- package/lib/types/ble/consts.d.ts +2 -0
- package/lib/types/ble/fm/adapter.d.ts +4 -2
- package/lib/types/ble/fm/sensor.d.ts +13 -7
- package/lib/types/ble/fm/types.d.ts +10 -0
- package/lib/types/ble/types.d.ts +9 -3
- package/lib/types/ble/zwift/play/sensor.d.ts +2 -0
- package/lib/types/direct-connect/base/peripheral.d.ts +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/modes/base.d.ts +4 -0
- package/lib/types/modes/daum-smarttrainer.d.ts +0 -2
- package/lib/types/modes/power-base.d.ts +0 -3
- package/lib/types/modes/power-meter copy.d.ts +15 -0
- package/lib/types/modes/rower.d.ts +19 -0
- package/lib/types/serial/base/serial-interface.d.ts +3 -0
- package/lib/types/serial/daum/DaumAdapter.d.ts +4 -2
- package/lib/types/serial/kettler/ergo-racer/adapter.d.ts +4 -2
- package/lib/types/types/adapter.d.ts +12 -7
- package/lib/types/types/interface.d.ts +2 -0
- package/lib/types/types/sport.d.ts +1 -0
- 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})` : '';
|
package/lib/cjs/base/adpater.js
CHANGED
|
@@ -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,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;
|
|
@@ -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) {
|
package/lib/cjs/ble/consts.js
CHANGED
|
@@ -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);
|
package/lib/cjs/ble/fm/sensor.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
489
|
+
const crypto = this.getCrypto();
|
|
490
|
+
return crypto?.generateKeyPairSync !== undefined && typeof (crypto?.generateKeyPairSync) === 'function';
|
|
484
491
|
}
|
|
485
492
|
createKeyPair() {
|
|
486
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|