incyclist-devices 3.0.10 → 3.0.12
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/ble/base/adapter.js +1 -1
- package/lib/cjs/ble/base/interface.js +1 -0
- 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 +220 -2
- 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/ble/base/adapter.js +1 -1
- package/lib/esm/ble/base/interface.js +1 -0
- package/lib/esm/ble/base/peripheral.js +13 -1
- package/lib/esm/ble/base/sensor.js +5 -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 +222 -4
- 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/ble/base/peripheral.d.ts +3 -1
- 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 +15 -7
- package/lib/types/ble/fm/types.d.ts +10 -0
- package/lib/types/ble/types.d.ts +9 -3
- package/lib/types/direct-connect/base/peripheral.d.ts +1 -1
- 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();
|
|
@@ -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();
|
|
@@ -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,9 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
21
21
|
cw = 0.6;
|
|
22
22
|
windSpeed = 0;
|
|
23
23
|
wheelSize = 2100;
|
|
24
|
+
ftmsServiceData;
|
|
25
|
+
rowerDataTS;
|
|
26
|
+
rowerMaxPower;
|
|
24
27
|
constructor(peripheral, props) {
|
|
25
28
|
super(peripheral, props);
|
|
26
29
|
this.reset();
|
|
@@ -32,6 +35,9 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
32
35
|
this.data = {};
|
|
33
36
|
}
|
|
34
37
|
getRequiredCharacteristics() {
|
|
38
|
+
this.parseServiceData();
|
|
39
|
+
if (this.ftmsServiceData?.rower)
|
|
40
|
+
return [consts_js_1.ROWER_DATA, '2a37', consts_js_1.FTMS_STATUS];
|
|
35
41
|
return [consts_js_1.INDOOR_BIKE_DATA, '2a37', consts_js_1.FTMS_STATUS];
|
|
36
42
|
}
|
|
37
43
|
onData(characteristic, characteristicData) {
|
|
@@ -46,6 +52,11 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
46
52
|
case consts_js_1.INDOOR_BIKE_DATA:
|
|
47
53
|
res = this.parseIndoorBikeData(data);
|
|
48
54
|
break;
|
|
55
|
+
case consts_js_1.ROWER_DATA:
|
|
56
|
+
res = this.parseRowerData(data);
|
|
57
|
+
break;
|
|
58
|
+
case consts_js_1.TREADMILL_DATA:
|
|
59
|
+
res = this.parseTreadmillData(data);
|
|
49
60
|
case '2a37':
|
|
50
61
|
res = this.parseHrm(data);
|
|
51
62
|
break;
|
|
@@ -76,6 +87,19 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
76
87
|
getCw() { return this.cw; }
|
|
77
88
|
setWindSpeed(windSpeed) { this.windSpeed = windSpeed; }
|
|
78
89
|
getWindSpeed() { return this.windSpeed; }
|
|
90
|
+
getSupportedSports() {
|
|
91
|
+
this.parseServiceData();
|
|
92
|
+
if (!this.ftmsServiceData)
|
|
93
|
+
return ['cycling'];
|
|
94
|
+
const sports = [];
|
|
95
|
+
if (this.ftmsServiceData?.indoorBike)
|
|
96
|
+
sports.push('cycling');
|
|
97
|
+
if (this.ftmsServiceData?.rower)
|
|
98
|
+
sports.push('rowing');
|
|
99
|
+
if (this.ftmsServiceData?.treadmill)
|
|
100
|
+
sports.push('running');
|
|
101
|
+
return sports;
|
|
102
|
+
}
|
|
79
103
|
onDisconnect() {
|
|
80
104
|
this.hasControl = false;
|
|
81
105
|
}
|
|
@@ -167,6 +191,159 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
167
191
|
}
|
|
168
192
|
return { ...this.data, raw: `2a37:${data.toString('hex')}` };
|
|
169
193
|
}
|
|
194
|
+
parseTreadmillData(_data) {
|
|
195
|
+
const data = Buffer.from(_data);
|
|
196
|
+
let offset = 2;
|
|
197
|
+
if (data.length > 2) {
|
|
198
|
+
try {
|
|
199
|
+
const flags = data.readUInt16LE(0);
|
|
200
|
+
if ((flags & 0x0001) === 0) {
|
|
201
|
+
this.data.speed = data.readUInt16LE(offset) / 100;
|
|
202
|
+
offset += 2;
|
|
203
|
+
}
|
|
204
|
+
if (flags & 0x0002) {
|
|
205
|
+
this.data.averageSpeed = data.readUInt16LE(offset) / 100;
|
|
206
|
+
offset += 2;
|
|
207
|
+
}
|
|
208
|
+
if (flags & 0x0004) {
|
|
209
|
+
const dvLow = data.readUInt8(offset);
|
|
210
|
+
offset += 1;
|
|
211
|
+
const dvHigh = data.readUInt16LE(offset);
|
|
212
|
+
offset += 2;
|
|
213
|
+
this.data.totalDistance = (dvHigh << 8) + dvLow;
|
|
214
|
+
}
|
|
215
|
+
if (flags & 0x0008) {
|
|
216
|
+
this.data.targetInclination = data.readInt16LE(offset) / 10;
|
|
217
|
+
offset += 2;
|
|
218
|
+
offset += 2;
|
|
219
|
+
}
|
|
220
|
+
if (flags & 0x0010) {
|
|
221
|
+
offset += 2;
|
|
222
|
+
offset += 2;
|
|
223
|
+
}
|
|
224
|
+
if (flags & 0x0020) {
|
|
225
|
+
const pace = data.readUInt8(offset);
|
|
226
|
+
offset += 1;
|
|
227
|
+
this.data.speed = pace > 0 ? Math.round(60 / pace * 100) / 100 : 0;
|
|
228
|
+
}
|
|
229
|
+
if (flags & 0x0040) {
|
|
230
|
+
const avgPace = data.readUInt8(offset);
|
|
231
|
+
offset += 1;
|
|
232
|
+
this.data.averageSpeed = avgPace > 0 ? Math.round(60 / avgPace * 100) / 100 : 0;
|
|
233
|
+
}
|
|
234
|
+
if (flags & 0x0080) {
|
|
235
|
+
this.data.totalEnergy = data.readUInt16LE(offset);
|
|
236
|
+
offset += 2;
|
|
237
|
+
this.data.energyPerHour = data.readUInt16LE(offset);
|
|
238
|
+
offset += 2;
|
|
239
|
+
this.data.energyPerMinute = data.readUInt8(offset);
|
|
240
|
+
offset += 1;
|
|
241
|
+
}
|
|
242
|
+
if (flags & 0x0100) {
|
|
243
|
+
this.data.heartrate = data.readUInt8(offset);
|
|
244
|
+
offset += 1;
|
|
245
|
+
}
|
|
246
|
+
if (flags & 0x0200) {
|
|
247
|
+
this.data.metabolicEquivalent = data.readUInt8(offset) / 10;
|
|
248
|
+
offset += 1;
|
|
249
|
+
}
|
|
250
|
+
if (flags & 0x0400) {
|
|
251
|
+
this.data.time = data.readUInt16LE(offset);
|
|
252
|
+
offset += 2;
|
|
253
|
+
}
|
|
254
|
+
if (flags & 0x0800) {
|
|
255
|
+
this.data.remainingTime = data.readUInt16LE(offset);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
this.logEvent({ message: 'error', fn: 'parseTreadmillData()', device: this.getName(), data: data.toString('hex'), offset, error: err.message, stack: err.stack });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return { ...this.data, raw: `2acd:${data.toString('hex')}` };
|
|
263
|
+
}
|
|
264
|
+
parseRowerData(_data) {
|
|
265
|
+
const data = Buffer.from(_data);
|
|
266
|
+
let offset = 2;
|
|
267
|
+
let maxPower;
|
|
268
|
+
if (!this.rowerDataTS || this.rowerDataTS + 1000 < Date.now()) {
|
|
269
|
+
this.rowerDataTS = Date.now();
|
|
270
|
+
maxPower = this.rowerMaxPower;
|
|
271
|
+
delete this.rowerMaxPower;
|
|
272
|
+
}
|
|
273
|
+
if (data.length > 2) {
|
|
274
|
+
try {
|
|
275
|
+
const flags = data.readUInt16LE(0);
|
|
276
|
+
if ((flags & 0x0001) === 0) {
|
|
277
|
+
this.data.cadence = data.readUInt8(offset) / 2;
|
|
278
|
+
offset += 1;
|
|
279
|
+
offset += 2;
|
|
280
|
+
}
|
|
281
|
+
if (flags & 0x0002) {
|
|
282
|
+
this.data.averageCadence = data.readUInt8(offset) / 2;
|
|
283
|
+
offset += 1;
|
|
284
|
+
}
|
|
285
|
+
if (flags & 0x0004) {
|
|
286
|
+
const dvLow = data.readUInt8(offset);
|
|
287
|
+
offset += 1;
|
|
288
|
+
const dvHigh = data.readUInt16LE(offset);
|
|
289
|
+
offset += 2;
|
|
290
|
+
this.data.totalDistance = (dvHigh << 8) + dvLow;
|
|
291
|
+
}
|
|
292
|
+
if (flags & 0x0008) {
|
|
293
|
+
const pace = data.readUInt16LE(offset);
|
|
294
|
+
offset += 2;
|
|
295
|
+
this.data.speed = pace > 0 ? Math.round(1800 / pace * 100) / 100 : 0;
|
|
296
|
+
}
|
|
297
|
+
if (flags & 0x0010) {
|
|
298
|
+
const avgPace = data.readUInt16LE(offset);
|
|
299
|
+
offset += 2;
|
|
300
|
+
this.data.averageSpeed = avgPace > 0 ? Math.round(1800 / avgPace * 100) / 100 : 0;
|
|
301
|
+
}
|
|
302
|
+
if (flags & 0x0020) {
|
|
303
|
+
this.data.instantaneousPower = data.readInt16LE(offset);
|
|
304
|
+
offset += 2;
|
|
305
|
+
this.rowerMaxPower = Math.max(this.rowerMaxPower ?? 0, this.data.instantaneousPower);
|
|
306
|
+
}
|
|
307
|
+
if (flags & 0x0040) {
|
|
308
|
+
this.data.averagePower = data.readInt16LE(offset);
|
|
309
|
+
offset += 2;
|
|
310
|
+
}
|
|
311
|
+
if (flags & 0x0080) {
|
|
312
|
+
this.data.resistanceLevel = data.readInt16LE(offset);
|
|
313
|
+
offset += 2;
|
|
314
|
+
}
|
|
315
|
+
if (flags & 0x0100) {
|
|
316
|
+
this.data.totalEnergy = data.readUInt16LE(offset);
|
|
317
|
+
offset += 2;
|
|
318
|
+
this.data.energyPerHour = data.readUInt16LE(offset);
|
|
319
|
+
offset += 2;
|
|
320
|
+
this.data.energyPerMinute = data.readUInt8(offset);
|
|
321
|
+
offset += 1;
|
|
322
|
+
}
|
|
323
|
+
if (flags & 0x0200) {
|
|
324
|
+
this.data.heartrate = data.readUInt8(offset);
|
|
325
|
+
offset += 1;
|
|
326
|
+
}
|
|
327
|
+
if (flags & 0x0400) {
|
|
328
|
+
this.data.metabolicEquivalent = data.readUInt8(offset) / 10;
|
|
329
|
+
offset += 1;
|
|
330
|
+
}
|
|
331
|
+
if (flags & 0x0800) {
|
|
332
|
+
this.data.time = data.readUInt16LE(offset);
|
|
333
|
+
offset += 2;
|
|
334
|
+
}
|
|
335
|
+
if (flags & 0x1000) {
|
|
336
|
+
this.data.remainingTime = data.readUInt16LE(offset);
|
|
337
|
+
}
|
|
338
|
+
this.logEvent({ message: 'rower data', device: this.getName(), raw: data.toString('hex'), data: structuredClone(this.data), maxPower: this.rowerMaxPower });
|
|
339
|
+
this.data.instantaneousPower = this.rowerMaxPower;
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
this.logEvent({ message: 'error', fn: 'parseRowerData()', device: this.getName(), data: data.toString('hex'), offset, error: err.message, stack: err.stack });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { ...this.data, raw: `2ad1:${data.toString('hex')}` };
|
|
346
|
+
}
|
|
170
347
|
parseIndoorBikeData(_data) {
|
|
171
348
|
const data = Buffer.from(_data);
|
|
172
349
|
let offset = 2;
|
|
@@ -292,16 +469,54 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
292
469
|
}
|
|
293
470
|
return { ...this.data, raw: `2ada:${data.toString('hex')}` };
|
|
294
471
|
}
|
|
472
|
+
parseServiceData() {
|
|
473
|
+
if (this.ftmsServiceData)
|
|
474
|
+
return this.ftmsServiceData;
|
|
475
|
+
try {
|
|
476
|
+
const peripheral = this.peripheral;
|
|
477
|
+
if (peripheral?.getServiceData === undefined)
|
|
478
|
+
return;
|
|
479
|
+
const bitSet = (value, bitNo) => (value & (0, utils_js_1.bit)(bitNo)) > 0;
|
|
480
|
+
const data = peripheral.getServiceData(consts_js_1.FTMS);
|
|
481
|
+
const dataLength = data?.length ?? 0;
|
|
482
|
+
if (dataLength >= 2) {
|
|
483
|
+
const flags = data.readUInt8(0);
|
|
484
|
+
const fmType = dataLength === 3 ? data.readUInt16LE(1) : data.readUInt8(1);
|
|
485
|
+
const available = bitSet(flags, 0);
|
|
486
|
+
const treadmill = bitSet(fmType, 0);
|
|
487
|
+
const crossTrainer = bitSet(fmType, 1);
|
|
488
|
+
const stepClimber = bitSet(fmType, 2);
|
|
489
|
+
const stairClimber = bitSet(fmType, 3);
|
|
490
|
+
const rower = bitSet(fmType, 4);
|
|
491
|
+
const indoorBike = bitSet(fmType, 5);
|
|
492
|
+
const raw = data?.toString('hex');
|
|
493
|
+
const serviceData = { available, treadmill, crossTrainer, stepClimber, stairClimber, rower, indoorBike, raw };
|
|
494
|
+
this.logEvent({ message: 'service data', device: this.getName(), ...serviceData });
|
|
495
|
+
this.ftmsServiceData = serviceData;
|
|
496
|
+
return serviceData;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
this.logEvent({ message: 'could not parse service data', reason: 'not enough data', raw: data?.toString('hex') });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
catch (err) {
|
|
503
|
+
this.logEvent({ message: 'could not parse service data', reason: err.message, stack: err.stack });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
295
506
|
async getFitnessMachineFeatures() {
|
|
296
507
|
if (this._features)
|
|
297
508
|
return this._features;
|
|
509
|
+
if (this.getName().startsWith('MRK-')) {
|
|
510
|
+
return { fitnessMachine: 0, targetSettings: 0, setPower: true, power: true, heartrate: true };
|
|
511
|
+
}
|
|
298
512
|
try {
|
|
299
513
|
const data = await this.read('2acc');
|
|
300
514
|
const buffer = data ? Buffer.from(data) : undefined;
|
|
301
515
|
const services = this.peripheral?.services || [];
|
|
302
516
|
let power = services.some(s => (0, utils_js_1.matches)(s.uuid, '1818'));
|
|
303
517
|
let heartrate = services.some(s => (0, utils_js_1.matches)(s.uuid, '180d'));
|
|
304
|
-
|
|
518
|
+
const dataLength = buffer?.length ?? 0;
|
|
519
|
+
if (dataLength >= 8) {
|
|
305
520
|
const fitnessMachine = buffer.readUInt32LE(0);
|
|
306
521
|
const targetSettings = buffer.readUInt32LE(4);
|
|
307
522
|
power = power || (fitnessMachine & consts_js_2.FitnessMachineFeatureFlag.PowerMeasurementSupported) !== 0;
|
|
@@ -320,7 +535,7 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
320
535
|
return this._features;
|
|
321
536
|
}
|
|
322
537
|
else {
|
|
323
|
-
return { fitnessMachine:
|
|
538
|
+
return { fitnessMachine: 0, targetSettings: 0, power, heartrate };
|
|
324
539
|
}
|
|
325
540
|
}
|
|
326
541
|
catch (err) {
|
|
@@ -332,6 +547,9 @@ class BleFitnessMachineDevice extends sensor_js_1.TBleSensor {
|
|
|
332
547
|
return this.getSupportedServiceUUids()?.some(s => (0, utils_js_1.matches)(s, consts_js_2.ZWIFT_PLAY_UUID));
|
|
333
548
|
}
|
|
334
549
|
buildFitnessMachineInfo(fitnessMachine) {
|
|
550
|
+
if (this.getName().startsWith('MRK-')) {
|
|
551
|
+
return ['avgSpeed', 'cadence', 'power', 'pace'];
|
|
552
|
+
}
|
|
335
553
|
const info = [];
|
|
336
554
|
const check = (flag, name) => {
|
|
337
555
|
if (fitnessMachine & flag)
|
|
@@ -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;
|
package/lib/cjs/modes/base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CyclingModeBase = void 0;
|
|
4
|
+
const gd_eventlog_1 = require("gd-eventlog");
|
|
4
5
|
const types_js_1 = require("./types.js");
|
|
5
6
|
class CyclingModeBase extends types_js_1.CyclingMode {
|
|
6
7
|
adapter;
|
|
@@ -11,6 +12,7 @@ class CyclingModeBase extends types_js_1.CyclingMode {
|
|
|
11
12
|
static isERG;
|
|
12
13
|
prevUpdate;
|
|
13
14
|
prevConfirmed;
|
|
15
|
+
logger;
|
|
14
16
|
static supportsERGMode() {
|
|
15
17
|
return this.config.isERG === true;
|
|
16
18
|
}
|
|
@@ -77,6 +79,14 @@ class CyclingModeBase extends types_js_1.CyclingMode {
|
|
|
77
79
|
return prop.default;
|
|
78
80
|
return undefined;
|
|
79
81
|
}
|
|
82
|
+
initLogger(defaultLogName) {
|
|
83
|
+
this.logger = new gd_eventlog_1.EventLogger(defaultLogName);
|
|
84
|
+
}
|
|
85
|
+
logEvent(event) {
|
|
86
|
+
if (!this.logger || this.adapter?.isLogPaused())
|
|
87
|
+
return;
|
|
88
|
+
this.logger.logEvent(event);
|
|
89
|
+
}
|
|
80
90
|
updateRequired(request = {}) {
|
|
81
91
|
const prevRequest = { ...this.prevUpdate };
|
|
82
92
|
this.prevUpdate = { ...request };
|
|
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const gd_eventlog_1 = require("gd-eventlog");
|
|
7
6
|
const types_js_1 = require("./types.js");
|
|
8
7
|
const antble_smarttrainer_js_1 = __importDefault(require("./antble-smarttrainer.js"));
|
|
9
8
|
const config = {
|
|
@@ -17,9 +16,10 @@ class DaumClassicCyclingMode extends antble_smarttrainer_js_1.default {
|
|
|
17
16
|
event;
|
|
18
17
|
constructor(adapter, props) {
|
|
19
18
|
super(adapter, props);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
if (adapter)
|
|
20
|
+
this.logger = adapter.getLogger();
|
|
21
|
+
else
|
|
22
|
+
this.initLogger('DaumClassicCyclingMode');
|
|
23
23
|
this.setConfig(config);
|
|
24
24
|
this.event = { noData: true, initialCall: true, slopeUpdate: false };
|
|
25
25
|
}
|