incyclist-devices 2.3.43 → 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.
@@ -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
  }
@@ -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
  }
@@ -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
- 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)
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
- this._features = { fitnessMachine, targetSettings, power, heartrate, cadence, setPower, setSlope };
293
- this.logEvent({ message: 'supported Features: ', fatures: this._features, power, heartrate, cadence });
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';
@@ -27,4 +27,7 @@ export type IndoorBikeFeatures = {
27
27
  cadence?: boolean;
28
28
  setSlope?: boolean;
29
29
  setPower?: boolean;
30
+ setResistance?: boolean;
31
+ fmInfo?: string[];
32
+ tsInfo?: string[];
30
33
  };
@@ -8,6 +8,7 @@ export default class AntAdvSimCyclingMode extends SmartTrainerCyclingMode {
8
8
  name: string;
9
9
  isERG?: boolean;
10
10
  isSIM?: boolean;
11
+ isResistance?: boolean;
11
12
  description: string;
12
13
  properties: import("./types").CyclingModeProperty[];
13
14
  };
@@ -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;
@@ -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
  }
@@ -61,8 +61,13 @@ class CyclingMode {
61
61
  isSIM() {
62
62
  return this.getConfig().isSIM;
63
63
  }
64
+ isResistance() {
65
+ return this.getConfig().isResistance;
66
+ }
64
67
  getData() {
65
68
  return {};
66
69
  }
70
+ confirmed(request) {
71
+ }
67
72
  }
68
73
  exports.CyclingMode = CyclingMode;
@@ -22,4 +22,5 @@ export type IncyclistBikeData = {
22
22
  gear?: number;
23
23
  gearStr?: string;
24
24
  slope?: number;
25
+ resistance?: number;
25
26
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "2.3.43",
3
+ "version": "2.4.0",
4
4
  "dependencies": {
5
5
  "@protobuf-ts/runtime": "^2.11.1",
6
6
  "@serialport/bindings-interface": "^1.2.2",