incyclist-devices 2.3.42 → 2.4.0
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/base/adpater.js +1 -1
- package/lib/ble/fm/adapter.js +21 -3
- package/lib/ble/fm/sensor.d.ts +4 -0
- package/lib/ble/fm/sensor.js +111 -14
- package/lib/ble/fm/types.d.ts +3 -0
- package/lib/modes/ant-fe-adv-st-mode.d.ts +1 -0
- package/lib/modes/base.d.ts +2 -0
- package/lib/modes/base.js +3 -0
- package/lib/modes/fm-resistance.d.ts +39 -0
- package/lib/modes/fm-resistance.js +139 -0
- package/lib/modes/types.d.ts +6 -0
- package/lib/modes/types.js +5 -0
- package/lib/types/data.d.ts +1 -0
- package/package.json +1 -1
package/lib/base/adpater.js
CHANGED
|
@@ -140,7 +140,7 @@ class IncyclistDevice extends events_1.default {
|
|
|
140
140
|
}
|
|
141
141
|
update() { throw new Error("Method not implemented."); }
|
|
142
142
|
setCyclingMode(mode, settings, sendInitCommands) {
|
|
143
|
-
if (!this.isControllable())
|
|
143
|
+
if (!this.isControllable() || !mode)
|
|
144
144
|
return;
|
|
145
145
|
const selectedMode = this.createOrGetMode(mode);
|
|
146
146
|
this.cyclingMode = selectedMode;
|
package/lib/ble/fm/adapter.js
CHANGED
|
@@ -24,6 +24,7 @@ const utils_1 = require("../../utils/utils");
|
|
|
24
24
|
const utils_2 = require("../utils");
|
|
25
25
|
const play_1 = require("../zwift/play");
|
|
26
26
|
const features_1 = require("../../features");
|
|
27
|
+
const fm_resistance_1 = __importDefault(require("../../modes/fm-resistance"));
|
|
27
28
|
const ZWIFT_PLAY_UUID = '0000000119ca465186e5fa29dcdd09d1';
|
|
28
29
|
class BleFmAdapter extends adapter_1.default {
|
|
29
30
|
constructor(settings, props) {
|
|
@@ -66,6 +67,9 @@ class BleFmAdapter extends adapter_1.default {
|
|
|
66
67
|
modes.push(antble_erg_1.default);
|
|
67
68
|
if (features.setSlope === undefined || features.setSlope)
|
|
68
69
|
modes.push(antble_smarttrainer_1.default);
|
|
70
|
+
if (features.setResistance) {
|
|
71
|
+
modes.push(fm_resistance_1.default);
|
|
72
|
+
}
|
|
69
73
|
return modes;
|
|
70
74
|
}
|
|
71
75
|
getDefaultCyclingMode() {
|
|
@@ -79,10 +83,13 @@ class BleFmAdapter extends adapter_1.default {
|
|
|
79
83
|
return new antble_smarttrainer_1.default(this);
|
|
80
84
|
if (features.setPower === undefined || features.setPower)
|
|
81
85
|
return new antble_erg_1.default(this);
|
|
86
|
+
if (features.setResistance) {
|
|
87
|
+
return new fm_resistance_1.default(this);
|
|
88
|
+
}
|
|
82
89
|
return new power_meter_1.default(this);
|
|
83
90
|
}
|
|
84
91
|
mapData(deviceData) {
|
|
85
|
-
var _a, _b, _c;
|
|
92
|
+
var _a, _b, _c, _d, _e;
|
|
86
93
|
const data = {
|
|
87
94
|
isPedalling: false,
|
|
88
95
|
power: 0,
|
|
@@ -98,6 +105,10 @@ class BleFmAdapter extends adapter_1.default {
|
|
|
98
105
|
data.time = (_c = deviceData === null || deviceData === void 0 ? void 0 : deviceData.time) !== null && _c !== void 0 ? _c : data.time;
|
|
99
106
|
data.isPedalling = data.pedalRpm > 0 || (data.pedalRpm === undefined && data.power > 0);
|
|
100
107
|
data.heartrate = deviceData.heartrate || data.heartrate;
|
|
108
|
+
const features = (_d = this.getSensor()) === null || _d === void 0 ? void 0 : _d.features;
|
|
109
|
+
if ((features === null || features === void 0 ? void 0 : features.setResistance) || ((_e = features === null || features === void 0 ? void 0 : features.fmInfo) === null || _e === void 0 ? void 0 : _e.includes('resistanceLevel'))) {
|
|
110
|
+
data.resistance = deviceData.resistanceLevel;
|
|
111
|
+
}
|
|
101
112
|
return data;
|
|
102
113
|
}
|
|
103
114
|
transformData(bikeData) {
|
|
@@ -250,7 +261,7 @@ class BleFmAdapter extends adapter_1.default {
|
|
|
250
261
|
if (features.power === false && this.hasCapability(types_1.IncyclistCapability.Power)) {
|
|
251
262
|
this.capabilities = this.capabilities.filter(cap => cap !== types_1.IncyclistCapability.Power);
|
|
252
263
|
}
|
|
253
|
-
if (features.setPower === false && features.setSlope === false) {
|
|
264
|
+
if (features.setPower === false && features.setSlope === false && features.setResistance === false) {
|
|
254
265
|
this.logEvent({ message: 'downgrade to Power Meter', name: this.getSettings().name, interface: this.getSettings().interface });
|
|
255
266
|
this.capabilities = this.capabilities.filter(cap => cap !== types_1.IncyclistCapability.Control);
|
|
256
267
|
}
|
|
@@ -283,6 +294,10 @@ class BleFmAdapter extends adapter_1.default {
|
|
|
283
294
|
yield device.setTargetPower(tp);
|
|
284
295
|
res.targetPower = tp;
|
|
285
296
|
}
|
|
297
|
+
if (update.targetResistance !== undefined) {
|
|
298
|
+
yield device.setTargetResistanceLevel(update.targetResistance);
|
|
299
|
+
res.targetResistance = update.targetResistance;
|
|
300
|
+
}
|
|
286
301
|
if (update.gearRatio !== undefined) {
|
|
287
302
|
if (!this.zwiftPlay) {
|
|
288
303
|
this.initVirtualShifting();
|
|
@@ -300,6 +315,9 @@ class BleFmAdapter extends adapter_1.default {
|
|
|
300
315
|
});
|
|
301
316
|
this.promiseSendUpdate = send();
|
|
302
317
|
const confirmed = yield this.promiseSendUpdate;
|
|
318
|
+
if (confirmed) {
|
|
319
|
+
this.getCyclingMode().confirmed(confirmed);
|
|
320
|
+
}
|
|
303
321
|
delete this.promiseSendUpdate;
|
|
304
322
|
return confirmed;
|
|
305
323
|
}
|
|
@@ -324,7 +342,7 @@ class BleFmAdapter extends adapter_1.default {
|
|
|
324
342
|
yield this.sendUpdate(request, true);
|
|
325
343
|
return true;
|
|
326
344
|
}
|
|
327
|
-
else if (mode.isSIM() && this.supportsVirtualShifting()) {
|
|
345
|
+
else if ((mode.isSIM() && this.supportsVirtualShifting()) || mode.isResistance()) {
|
|
328
346
|
yield this.sendInitialRequest();
|
|
329
347
|
return true;
|
|
330
348
|
}
|
package/lib/ble/fm/sensor.d.ts
CHANGED
|
@@ -30,15 +30,19 @@ export default class BleFitnessMachineDevice extends TBleSensor {
|
|
|
30
30
|
getWindSpeed(): number;
|
|
31
31
|
requestControl(): Promise<boolean>;
|
|
32
32
|
setTargetPower(power: number): Promise<boolean>;
|
|
33
|
+
setTargetResistanceLevel(resistanceLevel: number): Promise<boolean>;
|
|
33
34
|
setSlope(slope: any): Promise<boolean>;
|
|
34
35
|
protected parseHrm(_data: Uint8Array): IndoorBikeData;
|
|
35
36
|
protected parseIndoorBikeData(_data: Uint8Array): IndoorBikeData;
|
|
36
37
|
protected parseFitnessMachineStatus(_data: Uint8Array): IndoorBikeData;
|
|
37
38
|
getFitnessMachineFeatures(): Promise<IndoorBikeFeatures | undefined>;
|
|
39
|
+
protected buildFitnessMachineInfo(fitnessMachine: number): string[];
|
|
40
|
+
protected buildTargetSettingsInfo(targetSettings: number): string[];
|
|
38
41
|
protected writeFtmsMessage(requestedOpCode: any, data: any, props?: BleWriteProps): Promise<number>;
|
|
39
42
|
setTargetInclination(inclination: number): Promise<boolean>;
|
|
40
43
|
setIndoorBikeSimulation(windSpeed: number, gradient: number, crr: number, cw: number): Promise<boolean>;
|
|
41
44
|
startRequest(): Promise<boolean>;
|
|
42
45
|
stopRequest(): Promise<boolean>;
|
|
43
46
|
PauseRequest(): Promise<boolean>;
|
|
47
|
+
protected getName(): string;
|
|
44
48
|
}
|
package/lib/ble/fm/sensor.js
CHANGED
|
@@ -81,11 +81,13 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
81
81
|
getWindSpeed() { return this.windSpeed; }
|
|
82
82
|
requestControl() {
|
|
83
83
|
return __awaiter(this, void 0, void 0, function* () {
|
|
84
|
-
var _a, _b;
|
|
85
|
-
if (this.hasControl)
|
|
84
|
+
var _a, _b, _c;
|
|
85
|
+
if (this.hasControl) {
|
|
86
86
|
return true;
|
|
87
|
-
|
|
87
|
+
}
|
|
88
|
+
if (((_a = this.features) === null || _a === void 0 ? void 0 : _a.setPower) === false && ((_b = this.features) === null || _b === void 0 ? void 0 : _b.setSlope) === false && ((_c = this.features) === null || _c === void 0 ? void 0 : _c.setResistance) === false) {
|
|
88
89
|
return true;
|
|
90
|
+
}
|
|
89
91
|
this.logEvent({ message: 'requestControl' });
|
|
90
92
|
this.isCheckingControl = true;
|
|
91
93
|
const data = Buffer.alloc(1);
|
|
@@ -103,7 +105,7 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
103
105
|
}
|
|
104
106
|
setTargetPower(power) {
|
|
105
107
|
return __awaiter(this, void 0, void 0, function* () {
|
|
106
|
-
this.logEvent({ message: 'setTargetPower', power, skip: (this.data.targetPower !== undefined && this.data.targetPower === power) });
|
|
108
|
+
this.logEvent({ message: 'setTargetPower', device: this.getName(), power, skip: (this.data.targetPower !== undefined && this.data.targetPower === power) });
|
|
107
109
|
if (this.data.targetPower !== undefined && this.data.targetPower === power)
|
|
108
110
|
return true;
|
|
109
111
|
const hasControl = yield this.requestControl();
|
|
@@ -121,9 +123,31 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
121
123
|
return (res === 1);
|
|
122
124
|
});
|
|
123
125
|
}
|
|
126
|
+
setTargetResistanceLevel(resistanceLevel) {
|
|
127
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
this.logEvent({ message: 'setTargetResistanceLevel', device: this.getName(), resistanceLevel, skip: (this.data.resistanceLevel !== undefined && this.data.resistanceLevel === resistanceLevel) });
|
|
129
|
+
if (this.data.resistanceLevel !== undefined && this.data.resistanceLevel === resistanceLevel)
|
|
130
|
+
return true;
|
|
131
|
+
const hasControl = yield this.requestControl();
|
|
132
|
+
if (!hasControl) {
|
|
133
|
+
this.logEvent({ message: 'setTargetResistanceLevel failed', reason: 'control is disabled' });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
const data = Buffer.alloc(3);
|
|
137
|
+
data.writeUInt8(4, 0);
|
|
138
|
+
let resistance = Math.min(resistanceLevel, 100);
|
|
139
|
+
resistance = Math.max(resistance, 0);
|
|
140
|
+
data.writeInt16LE(Math.round(resistance * 10), 1);
|
|
141
|
+
const res = yield this.writeFtmsMessage(4, data);
|
|
142
|
+
if (res === 5) {
|
|
143
|
+
this.hasControl = false;
|
|
144
|
+
}
|
|
145
|
+
return (res === 1);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
124
148
|
setSlope(slope) {
|
|
125
149
|
return __awaiter(this, void 0, void 0, function* () {
|
|
126
|
-
this.logEvent({ message: 'setSlope', slope });
|
|
150
|
+
this.logEvent({ message: 'setSlope', device: this.getName(), slope });
|
|
127
151
|
const { windSpeed, crr, cw } = this;
|
|
128
152
|
return yield this.setIndoorBikeSimulation(windSpeed, slope, crr, cw);
|
|
129
153
|
});
|
|
@@ -210,7 +234,7 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
210
234
|
}
|
|
211
235
|
}
|
|
212
236
|
catch (err) {
|
|
213
|
-
this.logEvent({ message: 'error', fn: 'parseIndoorBikeData()', data: data.toString('hex'), offset, error: err.message, stack: err.stack });
|
|
237
|
+
this.logEvent({ message: 'error', fn: 'parseIndoorBikeData()', device: this.getName(), data: data.toString('hex'), offset, error: err.message, stack: err.stack });
|
|
214
238
|
}
|
|
215
239
|
}
|
|
216
240
|
return Object.assign(Object.assign({}, this.data), { raw: `2ad2:${data.toString('hex')}` });
|
|
@@ -265,7 +289,7 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
265
289
|
}
|
|
266
290
|
}
|
|
267
291
|
catch (err) {
|
|
268
|
-
this.logEvent({ message: 'error', fn: 'parseFitnessMachineStatus()', error: err.message, data: data.toString('hex'), stack: err.stack });
|
|
292
|
+
this.logEvent({ message: 'error', fn: 'parseFitnessMachineStatus()', error: err.message, device: this.getName(), data: data.toString('hex'), stack: err.stack });
|
|
269
293
|
}
|
|
270
294
|
return Object.assign(Object.assign({}, this.data), { raw: `2ada:${data.toString('hex')}` });
|
|
271
295
|
}
|
|
@@ -289,8 +313,13 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
289
313
|
const setSlope = (targetSettings & consts_2.TargetSettingFeatureFlag.IndoorBikeSimulationParametersSupported) !== 0
|
|
290
314
|
|| (targetSettings & consts_2.TargetSettingFeatureFlag.InclinationTargetSettingSupported) !== 0;
|
|
291
315
|
const setPower = (targetSettings & consts_2.TargetSettingFeatureFlag.PowerTargetSettingSupported) !== 0;
|
|
292
|
-
|
|
293
|
-
|
|
316
|
+
const setResistance = (targetSettings & consts_2.TargetSettingFeatureFlag.ResistanceTargetSettingSupported) !== 0;
|
|
317
|
+
const fmInfo = this.buildFitnessMachineInfo(fitnessMachine);
|
|
318
|
+
const tsInfo = this.buildTargetSettingsInfo(targetSettings);
|
|
319
|
+
this._features = { fitnessMachine, targetSettings, power, heartrate, cadence, setPower, setSlope, setResistance };
|
|
320
|
+
this.logEvent({ message: 'supported features', device: this.getName(), fmFeatures: fmInfo.join('|'), tsFeatures: tsInfo.join('|'), features: this._features });
|
|
321
|
+
this._features.fmInfo = fmInfo;
|
|
322
|
+
this._features.tsInfo = tsInfo;
|
|
294
323
|
return this._features;
|
|
295
324
|
}
|
|
296
325
|
else {
|
|
@@ -298,15 +327,77 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
298
327
|
}
|
|
299
328
|
}
|
|
300
329
|
catch (err) {
|
|
301
|
-
this.logEvent({ message: 'could not read FitnessMachineFeatures', error: err.message, stack: err.stack });
|
|
330
|
+
this.logEvent({ message: 'could not read FitnessMachineFeatures', error: err.message, stack: err.stack, device: this.getName() });
|
|
302
331
|
return undefined;
|
|
303
332
|
}
|
|
304
333
|
});
|
|
305
334
|
}
|
|
335
|
+
buildFitnessMachineInfo(fitnessMachine) {
|
|
336
|
+
const info = [];
|
|
337
|
+
const check = (flag, name) => {
|
|
338
|
+
if (fitnessMachine & flag)
|
|
339
|
+
info.push(name);
|
|
340
|
+
};
|
|
341
|
+
try {
|
|
342
|
+
check(consts_2.FitnessMachineFeatureFlag.AverageSpeedSupported, 'avgSpeed');
|
|
343
|
+
check(consts_2.FitnessMachineFeatureFlag.CadenceSupported, 'cadence');
|
|
344
|
+
check(consts_2.FitnessMachineFeatureFlag.TotalDistanceSupported, 'totalDistance');
|
|
345
|
+
check(consts_2.FitnessMachineFeatureFlag.InclinationSupported, 'inclination');
|
|
346
|
+
check(consts_2.FitnessMachineFeatureFlag.ElevationGainSupported, 'elevationGain');
|
|
347
|
+
check(consts_2.FitnessMachineFeatureFlag.PaceSupported, 'pace');
|
|
348
|
+
check(consts_2.FitnessMachineFeatureFlag.StepCountSupported, 'stepCount');
|
|
349
|
+
check(consts_2.FitnessMachineFeatureFlag.ResistanceLevelSupported, 'resistanceLevel');
|
|
350
|
+
check(consts_2.FitnessMachineFeatureFlag.StrideCountSupported, 'strideCount');
|
|
351
|
+
check(consts_2.FitnessMachineFeatureFlag.ExpendedEnergySupported, 'expendedEnergy');
|
|
352
|
+
check(consts_2.FitnessMachineFeatureFlag.HeartRateMeasurementSupported, 'heartrate');
|
|
353
|
+
check(consts_2.FitnessMachineFeatureFlag.MetabolicEquivalentSupported, 'metabolicEquivalent');
|
|
354
|
+
check(consts_2.FitnessMachineFeatureFlag.ElapsedTimeSupported, 'elapsedTime');
|
|
355
|
+
check(consts_2.FitnessMachineFeatureFlag.RemainingTimeSupported, 'remainingTime');
|
|
356
|
+
check(consts_2.FitnessMachineFeatureFlag.PowerMeasurementSupported, 'power');
|
|
357
|
+
check(consts_2.FitnessMachineFeatureFlag.ForceOnBeltAndPowerOutputSupported, 'force');
|
|
358
|
+
check(consts_2.FitnessMachineFeatureFlag.UserDataRetentionSupported, 'userDataRetention');
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
this.logEvent({ message: 'could not read FitnessMachineInfo', error: err.message, stack: err.stack, device: this.getName() });
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
return info;
|
|
365
|
+
}
|
|
366
|
+
buildTargetSettingsInfo(targetSettings) {
|
|
367
|
+
const info = [];
|
|
368
|
+
const check = (flag, name) => {
|
|
369
|
+
if (targetSettings & flag)
|
|
370
|
+
info.push(name);
|
|
371
|
+
};
|
|
372
|
+
try {
|
|
373
|
+
check(consts_2.TargetSettingFeatureFlag.SpeedTargetSettingSupported, 'speed');
|
|
374
|
+
check(consts_2.TargetSettingFeatureFlag.InclinationTargetSettingSupported, 'inclination');
|
|
375
|
+
check(consts_2.TargetSettingFeatureFlag.ResistanceTargetSettingSupported, 'resistance');
|
|
376
|
+
check(consts_2.TargetSettingFeatureFlag.PowerTargetSettingSupported, 'power');
|
|
377
|
+
check(consts_2.TargetSettingFeatureFlag.HeartRateTargetSettingSupported, 'heartrate');
|
|
378
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedExpendedEnergyConfigurationSupported, 'expendedEnergy');
|
|
379
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedStepNumberConfigurationSupported, 'steps');
|
|
380
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedStrideNumberConfigurationSupported, 'strides');
|
|
381
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedDistanceConfigurationSupported, 'distance');
|
|
382
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedTrainingTimeConfigurationSupported, 'trainingTime');
|
|
383
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedTimeInTwoHeartRateZonesConfigurationSupported, 'timeInTwoHRZones');
|
|
384
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedTimeInThreeHeartRateZonesConfigurationSupported, 'timeInThreeHRZones');
|
|
385
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedTimeInFiveHeartRateZonesConfigurationSupported, 'timeInFiveHRZones');
|
|
386
|
+
check(consts_2.TargetSettingFeatureFlag.IndoorBikeSimulationParametersSupported, 'SIM');
|
|
387
|
+
check(consts_2.TargetSettingFeatureFlag.WheelCircumferenceConfigurationSupported, 'wheelCircumference');
|
|
388
|
+
check(consts_2.TargetSettingFeatureFlag.SpinDownControlSupported, 'spindown');
|
|
389
|
+
check(consts_2.TargetSettingFeatureFlag.TargetedCadenceConfigurationSupported, 'cadence');
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
this.logEvent({ message: 'could not read TargetSettingsInfo', error: err.message, stack: err.stack, device: this.getName() });
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
return info;
|
|
396
|
+
}
|
|
306
397
|
writeFtmsMessage(requestedOpCode, data, props) {
|
|
307
398
|
return __awaiter(this, void 0, void 0, function* () {
|
|
308
399
|
try {
|
|
309
|
-
this.logEvent({ message: 'fmts:write', data: data.toString('hex') });
|
|
400
|
+
this.logEvent({ message: 'fmts:write', device: this.getName(), data: data.toString('hex') });
|
|
310
401
|
let res;
|
|
311
402
|
let tsStart = Date.now();
|
|
312
403
|
if (props === null || props === void 0 ? void 0 : props.timeout) {
|
|
@@ -325,11 +416,11 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
325
416
|
if (opCode !== 128 || request !== requestedOpCode)
|
|
326
417
|
throw new Error('Illegal response ');
|
|
327
418
|
const duration = Date.now() - tsStart;
|
|
328
|
-
this.logEvent({ message: 'fmts:write result', res: responseData.toString('hex'), result, duration });
|
|
419
|
+
this.logEvent({ message: 'fmts:write result', device: this.getName(), res: responseData.toString('hex'), result, duration });
|
|
329
420
|
return result;
|
|
330
421
|
}
|
|
331
422
|
catch (err) {
|
|
332
|
-
this.logEvent({ message: 'fmts:write failed', opCode: requestedOpCode, reason: err.message });
|
|
423
|
+
this.logEvent({ message: 'fmts:write failed', device: this.getName(), opCode: requestedOpCode, reason: err.message });
|
|
333
424
|
return 4;
|
|
334
425
|
}
|
|
335
426
|
});
|
|
@@ -341,6 +432,7 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
341
432
|
return true;
|
|
342
433
|
if (((_a = this.features) === null || _a === void 0 ? void 0 : _a.setSlope) === false)
|
|
343
434
|
return true;
|
|
435
|
+
this.logEvent({ message: 'setTargetInclination', device: this.getName(), inclination });
|
|
344
436
|
const hasControl = yield this.requestControl();
|
|
345
437
|
if (!hasControl) {
|
|
346
438
|
this.logEvent({ message: 'setTargetInclination failed', reason: 'control is disabled' });
|
|
@@ -358,9 +450,10 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
358
450
|
var _a;
|
|
359
451
|
if (((_a = this.features) === null || _a === void 0 ? void 0 : _a.setPower) === false)
|
|
360
452
|
return true;
|
|
453
|
+
this.logEvent({ message: 'setIndoorBikeSimulation', device: this.getName(), windSpeed, gradient, crr, cw });
|
|
361
454
|
const hasControl = yield this.requestControl();
|
|
362
455
|
if (!hasControl) {
|
|
363
|
-
this.logEvent({ message: 'setIndoorBikeSimulation failed', reason: 'control is disabled' });
|
|
456
|
+
this.logEvent({ message: 'setIndoorBikeSimulation failed', device: this.getName(), reason: 'control is disabled' });
|
|
364
457
|
return false;
|
|
365
458
|
}
|
|
366
459
|
const data = Buffer.alloc(7);
|
|
@@ -417,6 +510,10 @@ class BleFitnessMachineDevice extends sensor_1.TBleSensor {
|
|
|
417
510
|
return (res === 1);
|
|
418
511
|
});
|
|
419
512
|
}
|
|
513
|
+
getName() {
|
|
514
|
+
var _a, _b;
|
|
515
|
+
return (_b = (_a = this.peripheral) === null || _a === void 0 ? void 0 : _a.getInfo().name) !== null && _b !== void 0 ? _b : 'ble-fm-device';
|
|
516
|
+
}
|
|
420
517
|
}
|
|
421
518
|
BleFitnessMachineDevice.profile = 'Smart Trainer';
|
|
422
519
|
BleFitnessMachineDevice.protocol = 'fm';
|
package/lib/ble/fm/types.d.ts
CHANGED
package/lib/modes/base.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare abstract class CyclingModeBase extends CyclingMode implements ICy
|
|
|
8
8
|
protected static config: CyclingModeConfig;
|
|
9
9
|
protected static isERG: boolean;
|
|
10
10
|
protected prevUpdate: UpdateRequest;
|
|
11
|
+
protected prevConfirmed: UpdateRequest;
|
|
11
12
|
static supportsERGMode(): boolean;
|
|
12
13
|
constructor(adapter: IAdapter, props?: any);
|
|
13
14
|
setAdapter(adapter: IAdapter): void;
|
|
@@ -26,6 +27,7 @@ export declare abstract class CyclingModeBase extends CyclingMode implements ICy
|
|
|
26
27
|
getModeProperty(name: string): any;
|
|
27
28
|
protected updateRequired(request?: UpdateRequest): boolean;
|
|
28
29
|
buildUpdate(request?: UpdateRequest): UpdateRequest;
|
|
30
|
+
confirmed(request: UpdateRequest): void;
|
|
29
31
|
abstract getBikeInitRequest(): UpdateRequest;
|
|
30
32
|
abstract sendBikeUpdate(request: UpdateRequest): UpdateRequest;
|
|
31
33
|
abstract updateData(data: IncyclistBikeData): IncyclistBikeData;
|
package/lib/modes/base.js
CHANGED
|
@@ -91,6 +91,9 @@ class CyclingModeBase extends types_1.CyclingMode {
|
|
|
91
91
|
}
|
|
92
92
|
return this.sendBikeUpdate(request);
|
|
93
93
|
}
|
|
94
|
+
confirmed(request) {
|
|
95
|
+
this.prevConfirmed = Object.assign({}, request);
|
|
96
|
+
}
|
|
94
97
|
}
|
|
95
98
|
exports.CyclingModeBase = CyclingModeBase;
|
|
96
99
|
CyclingModeBase.config = { name: '', description: '', properties: [] };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { IncyclistDeviceAdapter } from "../base/adpater";
|
|
2
|
+
import { IncyclistBikeData } from "../types";
|
|
3
|
+
import PowerBasedCyclingModeBase from "./power-base";
|
|
4
|
+
import ICyclingMode, { CyclingModeProperyType, UpdateRequest } from "./types";
|
|
5
|
+
export default class FMResistanceMode extends PowerBasedCyclingModeBase implements ICyclingMode {
|
|
6
|
+
protected static config: {
|
|
7
|
+
isERG: boolean;
|
|
8
|
+
isSIM: boolean;
|
|
9
|
+
isResistance: boolean;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
properties: {
|
|
13
|
+
key: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
type: CyclingModeProperyType;
|
|
17
|
+
default: number;
|
|
18
|
+
min: number;
|
|
19
|
+
max: number;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
protected confirmedResistance?: number;
|
|
23
|
+
protected requestedResistance?: number;
|
|
24
|
+
constructor(adapter: IncyclistDeviceAdapter, props?: any);
|
|
25
|
+
getBikeInitRequest(): UpdateRequest;
|
|
26
|
+
sendBikeUpdate(incoming: UpdateRequest): UpdateRequest;
|
|
27
|
+
checkForResetOrEmpty(request: UpdateRequest): UpdateRequest | undefined;
|
|
28
|
+
protected checkSlope(request: UpdateRequest): void;
|
|
29
|
+
protected checkTargetResistanceSet(request: UpdateRequest, newRequest: UpdateRequest): void;
|
|
30
|
+
protected checkGearDeltaSet(request: UpdateRequest, newRequest: UpdateRequest): void;
|
|
31
|
+
updateData(bikeData: IncyclistBikeData, log?: boolean): IncyclistBikeData;
|
|
32
|
+
confirmed(request: UpdateRequest): void;
|
|
33
|
+
protected getCurrentResistanceTarget(): number;
|
|
34
|
+
protected getConfirmedResistanceTarget(): number;
|
|
35
|
+
protected getCurrentResistance(): number;
|
|
36
|
+
protected getInitialGear(): number;
|
|
37
|
+
protected getGear(resistance: number): number;
|
|
38
|
+
protected calculateTargetResistance(gear: number): number;
|
|
39
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const power_base_1 = __importDefault(require("./power-base"));
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
class FMResistanceMode extends power_base_1.default {
|
|
9
|
+
constructor(adapter, props) {
|
|
10
|
+
super(adapter, props);
|
|
11
|
+
this.initLogger('FMResistanceMode');
|
|
12
|
+
this.data.slope = 0;
|
|
13
|
+
}
|
|
14
|
+
getBikeInitRequest() {
|
|
15
|
+
return { targetResistance: this.calculateTargetResistance(this.getInitialGear()) };
|
|
16
|
+
}
|
|
17
|
+
sendBikeUpdate(incoming) {
|
|
18
|
+
if (this.logger)
|
|
19
|
+
this.logger.logEvent({ message: "processing update request", request: incoming, prev: this.prevRequest, data: this.getData() });
|
|
20
|
+
let newRequest = {};
|
|
21
|
+
const request = Object.assign({}, incoming);
|
|
22
|
+
try {
|
|
23
|
+
const req = this.checkForResetOrEmpty(request);
|
|
24
|
+
if (req) {
|
|
25
|
+
delete req.refresh;
|
|
26
|
+
return req;
|
|
27
|
+
}
|
|
28
|
+
this.checkSlope(request);
|
|
29
|
+
this.checkGearDeltaSet(request, newRequest);
|
|
30
|
+
this.checkTargetResistanceSet(request, newRequest);
|
|
31
|
+
this.checkRefresh(request, newRequest);
|
|
32
|
+
this.checkEmptyRequest(newRequest);
|
|
33
|
+
this.prevRequest = JSON.parse(JSON.stringify(newRequest));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (this.logger)
|
|
37
|
+
this.logger.logEvent({ message: "error", fn: 'sendBikeUpdate()', error: err.message, stack: err.stack });
|
|
38
|
+
}
|
|
39
|
+
if (newRequest.targetResistance !== undefined) {
|
|
40
|
+
this.requestedResistance = newRequest.targetResistance;
|
|
41
|
+
}
|
|
42
|
+
return newRequest;
|
|
43
|
+
}
|
|
44
|
+
checkForResetOrEmpty(request) {
|
|
45
|
+
if (!request || request.reset) {
|
|
46
|
+
this.prevRequest = {};
|
|
47
|
+
return { reset: true };
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(request).length === 0 && this.prevRequest) {
|
|
50
|
+
return { targetResistance: this.prevRequest.targetResistance, refresh: true };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
checkSlope(request) {
|
|
54
|
+
if (request.slope !== undefined) {
|
|
55
|
+
this.data.slope = request.slope;
|
|
56
|
+
delete request.slope;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
checkTargetResistanceSet(request, newRequest) {
|
|
60
|
+
if (request.targetResistance !== undefined) {
|
|
61
|
+
let resistance = Math.floor(request.targetResistance);
|
|
62
|
+
resistance = Math.max(resistance, 0);
|
|
63
|
+
resistance = Math.min(resistance, 100);
|
|
64
|
+
newRequest.targetResistance = resistance;
|
|
65
|
+
delete request.refresh;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
checkGearDeltaSet(request, newRequest) {
|
|
69
|
+
let resistance = this.getCurrentResistanceTarget();
|
|
70
|
+
let gear = this.getGear(resistance);
|
|
71
|
+
if (request.gearDelta !== undefined) {
|
|
72
|
+
gear += request.gearDelta;
|
|
73
|
+
gear = Math.max(gear, 1);
|
|
74
|
+
gear = Math.min(gear, 26);
|
|
75
|
+
resistance = this.calculateTargetResistance(gear);
|
|
76
|
+
newRequest.targetResistance = resistance;
|
|
77
|
+
delete request.gearDelta;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
updateData(bikeData, log) {
|
|
81
|
+
const data = super.updateData(bikeData, log);
|
|
82
|
+
if (data.resistance !== undefined) {
|
|
83
|
+
data.gear = this.getGear(data.resistance);
|
|
84
|
+
data.gearStr = `${data.gear}`;
|
|
85
|
+
if (this.getCurrentResistanceTarget() !== this.getCurrentResistance()) {
|
|
86
|
+
const gear = this.getGear(this.getConfirmedResistanceTarget());
|
|
87
|
+
if (this.getCurrentResistanceTarget() === this.getConfirmedResistanceTarget()) {
|
|
88
|
+
data.gearStr = `${gear}`;
|
|
89
|
+
}
|
|
90
|
+
else if (this.getCurrentResistanceTarget() > this.getConfirmedResistanceTarget()) {
|
|
91
|
+
data.gearStr = `${gear} \u2191`;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
data.gearStr = `${gear} \u2193`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
confirmed(request) {
|
|
101
|
+
if (request.targetResistance !== undefined) {
|
|
102
|
+
this.confirmedResistance = request.targetResistance;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
getCurrentResistanceTarget() {
|
|
106
|
+
var _a;
|
|
107
|
+
return (_a = this.requestedResistance) !== null && _a !== void 0 ? _a : this.calculateTargetResistance(this.getInitialGear());
|
|
108
|
+
}
|
|
109
|
+
getConfirmedResistanceTarget() {
|
|
110
|
+
return this.confirmedResistance;
|
|
111
|
+
}
|
|
112
|
+
getCurrentResistance() {
|
|
113
|
+
return this.getData().resistance;
|
|
114
|
+
}
|
|
115
|
+
getInitialGear() {
|
|
116
|
+
return Number(this.getSetting('startGear'));
|
|
117
|
+
}
|
|
118
|
+
getGear(resistance) {
|
|
119
|
+
let r = Math.max(resistance !== null && resistance !== void 0 ? resistance : 0, 0);
|
|
120
|
+
r = Math.min(r, 100);
|
|
121
|
+
return Math.floor(r / 4) + 1;
|
|
122
|
+
}
|
|
123
|
+
calculateTargetResistance(gear) {
|
|
124
|
+
let g = Math.max(gear, 0);
|
|
125
|
+
g = Math.min(g, 26);
|
|
126
|
+
return (g - 1) * 4;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
FMResistanceMode.config = {
|
|
130
|
+
isERG: false,
|
|
131
|
+
isSIM: false,
|
|
132
|
+
isResistance: true,
|
|
133
|
+
name: "Resistance",
|
|
134
|
+
description: "Resistance levels are set by the app based on selected gear. Calculates speed based on power and slope.",
|
|
135
|
+
properties: [
|
|
136
|
+
{ key: 'startGear', name: 'Initial Gear', description: 'Initial Gear', type: types_1.CyclingModeProperyType.Integer, default: 5, min: 1, max: 26 }
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
exports.default = FMResistanceMode;
|
package/lib/modes/types.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type UpdateRequest = {
|
|
|
5
5
|
maxPower?: number;
|
|
6
6
|
targetPower?: number;
|
|
7
7
|
targetPowerDelta?: number;
|
|
8
|
+
targetResistance?: number;
|
|
9
|
+
targetResistanceDelta?: number;
|
|
8
10
|
gearDelta?: number;
|
|
9
11
|
reset?: boolean;
|
|
10
12
|
refresh?: boolean;
|
|
@@ -42,6 +44,7 @@ export default interface ICyclingMode {
|
|
|
42
44
|
getProperty(name: string): CyclingModeProperty;
|
|
43
45
|
getBikeInitRequest(): UpdateRequest;
|
|
44
46
|
buildUpdate(request: UpdateRequest): UpdateRequest;
|
|
47
|
+
confirmed(request: UpdateRequest): void;
|
|
45
48
|
updateData(data: IncyclistBikeData): IncyclistBikeData;
|
|
46
49
|
setSettings(settings: any): any;
|
|
47
50
|
setSetting(name: string, value: any): void;
|
|
@@ -54,6 +57,7 @@ export default interface ICyclingMode {
|
|
|
54
57
|
export type CyclingModeConfig = {
|
|
55
58
|
isERG?: boolean;
|
|
56
59
|
isSIM?: boolean;
|
|
60
|
+
isResistance?: boolean;
|
|
57
61
|
name: string;
|
|
58
62
|
description: string;
|
|
59
63
|
properties: CyclingModeProperty[];
|
|
@@ -77,5 +81,7 @@ export declare class CyclingMode implements ICyclingMode {
|
|
|
77
81
|
getConfig(): CyclingModeConfig;
|
|
78
82
|
isERG(): boolean;
|
|
79
83
|
isSIM(): boolean;
|
|
84
|
+
isResistance(): boolean;
|
|
80
85
|
getData(): Partial<IncyclistBikeData>;
|
|
86
|
+
confirmed(request: UpdateRequest): void;
|
|
81
87
|
}
|
package/lib/modes/types.js
CHANGED
package/lib/types/data.d.ts
CHANGED