incyclist-devices 1.4.24 → 1.4.25

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.
@@ -56,6 +56,7 @@ export default class DeviceProtocolBase {
56
56
  scan(props: ScanProps): void;
57
57
  stopScan(): void;
58
58
  isScanning(): boolean;
59
+ add(props: DeviceSettings): void;
59
60
  getDevices(): Array<Device>;
60
61
  setAnt(antClass: any): void;
61
62
  getAnt(): any;
@@ -21,6 +21,7 @@ class DeviceProtocolBase {
21
21
  scan(props) { throw new Error('not implemented'); }
22
22
  stopScan() { throw new Error('not implemented'); }
23
23
  isScanning() { throw new Error('not implemented'); }
24
+ add(props) { throw new Error('Method not implemented.'); }
24
25
  getDevices() { return this.devices; }
25
26
  setAnt(antClass) { DeviceProtocolBase.setAnt(antClass); }
26
27
  getAnt() { return DeviceProtocolBase.getAnt(); }
@@ -7,7 +7,6 @@ const cwABike = {
7
7
  triathlon: 0.29,
8
8
  mountain: 0.57
9
9
  };
10
- const k = 0.01090;
11
10
  const cRR = 0.0036;
12
11
  class IllegalArgumentException extends Error {
13
12
  constructor(message) {
@@ -28,7 +27,7 @@ class C {
28
27
  const _cRR = props.cRR || cRR;
29
28
  const _cwA = props.cwA || cwABike[props.bikeType || 'race'] || cwABike.race;
30
29
  let sl = Math.atan(slope / 100);
31
- let c1 = 0.5 * _rho * _cwA + 2 * k;
30
+ let c1 = 0.5 * _rho * _cwA;
32
31
  let c2 = (sl + _cRR) * m * g;
33
32
  let p = c2 / c1;
34
33
  let q = -1.0 * power / c1;
@@ -70,7 +69,7 @@ class C {
70
69
  let _cRR = props.cRR || cRR;
71
70
  let _cwA = props.cwA || cwABike[props.bikeType || 'race'] || cwABike.race;
72
71
  let sl = Math.sin(Math.atan(slope / 100));
73
- let P = (0.5 * _rho * _cwA + 2 * k) * Math.pow(v, 3.0) + (sl + _cRR) * m * g * v;
72
+ let P = (0.5 * _rho * _cwA) * Math.pow(v, 3.0) + (sl + _cRR) * m * g * v;
74
73
  return P;
75
74
  }
76
75
  static calculateSpeedDaum(gear, rpm, bikeType) {
@@ -13,7 +13,7 @@ export default class DaumAdapterBase extends DeviceAdapterBase implements Device
13
13
  distanceInternal: number;
14
14
  paused: boolean;
15
15
  stopped: boolean;
16
- daumRunData: IncyclistBikeData;
16
+ cyclingData: IncyclistBikeData;
17
17
  deviceData: DeviceData;
18
18
  currentRequest: any;
19
19
  requests: Array<any>;
@@ -56,7 +56,7 @@ export default class DaumAdapterBase extends DeviceAdapterBase implements Device
56
56
  update(): Promise<void>;
57
57
  sendRequests(): Promise<void>;
58
58
  bikeSync(): Promise<void>;
59
- updateData(prev: any, bikeData: any): void;
59
+ updateData(prev: any, bikeData: any): IncyclistBikeData;
60
60
  transformData(): DeviceData;
61
61
  sendRequest(request: any): Promise<any>;
62
62
  refreshRequests(): void;
@@ -22,7 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
22
22
  const Device_1 = __importStar(require("../Device"));
23
23
  const ERGCyclingMode_1 = __importDefault(require("./ERGCyclingMode"));
24
24
  const SmartTrainerCyclingMode_1 = __importDefault(require("./SmartTrainerCyclingMode"));
25
- const PowerMeterCyclingMode_1 = __importDefault(require("./PowerMeterCyclingMode"));
25
+ const DaumPowerMeterCyclingMode_1 = __importDefault(require("./DaumPowerMeterCyclingMode"));
26
26
  const utils_1 = require("../utils");
27
27
  class DaumAdapterBase extends Device_1.default {
28
28
  constructor(props, bike) {
@@ -34,7 +34,7 @@ class DaumAdapterBase extends Device_1.default {
34
34
  this.bike = bike;
35
35
  this.stopped = false;
36
36
  this.paused = false;
37
- this.daumRunData = {
37
+ this.cyclingData = {
38
38
  isPedalling: false,
39
39
  time: 0,
40
40
  power: 0,
@@ -67,7 +67,7 @@ class DaumAdapterBase extends Device_1.default {
67
67
  this.cyclingMode.setSettings(settings);
68
68
  }
69
69
  getSupportedCyclingModes() {
70
- return [ERGCyclingMode_1.default, SmartTrainerCyclingMode_1.default, PowerMeterCyclingMode_1.default];
70
+ return [ERGCyclingMode_1.default, SmartTrainerCyclingMode_1.default, DaumPowerMeterCyclingMode_1.default];
71
71
  }
72
72
  getCyclingMode() {
73
73
  if (!this.cyclingMode)
@@ -128,7 +128,7 @@ class DaumAdapterBase extends Device_1.default {
128
128
  this.distanceInternal = undefined;
129
129
  this.paused = false;
130
130
  this.stopped = false;
131
- this.daumRunData = {
131
+ this.cyclingData = {
132
132
  isPedalling: false,
133
133
  time: 0,
134
134
  power: 0,
@@ -229,12 +229,15 @@ class DaumAdapterBase extends Device_1.default {
229
229
  this.updateBusy = true;
230
230
  this.getCurrentBikeData()
231
231
  .then(bikeData => {
232
- this.updateData(this.daumRunData, bikeData);
232
+ this.updateData(this.cyclingData, bikeData);
233
233
  this.transformData();
234
234
  this.updateBusy = false;
235
235
  })
236
236
  .catch(err => {
237
237
  this.logEvent({ message: 'bike update error', error: err.message, stack: err.stack });
238
+ const { isPedalling, power, pedalRpm, speed, distanceInternal, heartrate, slope } = this.cyclingData;
239
+ this.updateData(this.cyclingData, { isPedalling, power, pedalRpm, speed, distanceInternal, heartrate, slope });
240
+ this.transformData();
238
241
  this.updateBusy = false;
239
242
  });
240
243
  });
@@ -289,27 +292,28 @@ class DaumAdapterBase extends Device_1.default {
289
292
  data.time = bikeData.time;
290
293
  if (bikeData.slope)
291
294
  data.slope = bikeData.slope;
292
- this.daumRunData = this.getCyclingMode().updateData(data);
295
+ this.cyclingData = this.getCyclingMode().updateData(data);
296
+ return this.cyclingData;
293
297
  }
294
298
  transformData() {
295
- if (this.daumRunData === undefined)
299
+ if (this.cyclingData === undefined)
296
300
  return;
297
301
  let distance = 0;
298
- if (this.distanceInternal !== undefined && this.daumRunData.distanceInternal !== undefined) {
299
- distance = utils_1.intVal(this.daumRunData.distanceInternal - this.distanceInternal);
302
+ if (this.distanceInternal !== undefined && this.cyclingData.distanceInternal !== undefined) {
303
+ distance = utils_1.intVal(this.cyclingData.distanceInternal - this.distanceInternal);
300
304
  }
301
- if (this.daumRunData.distanceInternal !== undefined)
302
- this.distanceInternal = this.daumRunData.distanceInternal;
305
+ if (this.cyclingData.distanceInternal !== undefined)
306
+ this.distanceInternal = this.cyclingData.distanceInternal;
303
307
  let data = {
304
- speed: utils_1.floatVal(this.daumRunData.speed),
305
- slope: utils_1.floatVal(this.daumRunData.slope),
306
- power: utils_1.intVal(this.daumRunData.power),
307
- cadence: utils_1.intVal(this.daumRunData.pedalRpm),
308
- heartrate: utils_1.intVal(this.daumRunData.heartrate),
308
+ speed: utils_1.floatVal(this.cyclingData.speed),
309
+ slope: utils_1.floatVal(this.cyclingData.slope),
310
+ power: utils_1.intVal(this.cyclingData.power),
311
+ cadence: utils_1.intVal(this.cyclingData.pedalRpm),
312
+ heartrate: utils_1.intVal(this.cyclingData.heartrate),
309
313
  distance,
310
314
  timestamp: Date.now(),
311
- deviceTime: this.daumRunData.time,
312
- deviceDistanceCounter: this.daumRunData.distanceInternal
315
+ deviceTime: this.cyclingData.time,
316
+ deviceDistanceCounter: this.cyclingData.distanceInternal
313
317
  };
314
318
  if (this.ignoreHrm)
315
319
  delete data.heartrate;
@@ -350,7 +354,7 @@ class DaumAdapterBase extends Device_1.default {
350
354
  });
351
355
  }
352
356
  refreshRequests() {
353
- if (!this.daumRunData.isPedalling || this.daumRunData.pedalRpm === 0)
357
+ if (!this.cyclingData.isPedalling || this.cyclingData.pedalRpm === 0)
354
358
  return;
355
359
  let bikeRequest = this.getCyclingMode().sendBikeUpdate({ refresh: true }) || {};
356
360
  const prev = this.requests[this.requests.length - 1] || {};
@@ -361,7 +365,7 @@ class DaumAdapterBase extends Device_1.default {
361
365
  }
362
366
  processClientRequest(request) {
363
367
  if (request.slope !== undefined) {
364
- this.daumRunData.slope = request.slope;
368
+ this.cyclingData.slope = request.slope;
365
369
  }
366
370
  return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
367
371
  let bikeRequest = this.getCyclingMode().sendBikeUpdate(request);
@@ -0,0 +1,8 @@
1
+ import CyclingMode, { UpdateRequest } from "../CyclingMode";
2
+ import PowerMeterCyclingMode from "../modes/power-meter";
3
+ export default class DaumPowerMeterCyclingMode extends PowerMeterCyclingMode implements CyclingMode {
4
+ prevRequest: UpdateRequest;
5
+ hasBikeUpdate: boolean;
6
+ getBikeInitRequest(): UpdateRequest;
7
+ sendBikeUpdate(request: UpdateRequest): UpdateRequest;
8
+ }
@@ -0,0 +1,21 @@
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_meter_1 = __importDefault(require("../modes/power-meter"));
7
+ class DaumPowerMeterCyclingMode extends power_meter_1.default {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.hasBikeUpdate = false;
11
+ }
12
+ getBikeInitRequest() {
13
+ return { slope: 0 };
14
+ }
15
+ sendBikeUpdate(request) {
16
+ super.sendBikeUpdate(request);
17
+ this.prevRequest = {};
18
+ return {};
19
+ }
20
+ }
21
+ exports.default = DaumPowerMeterCyclingMode;
@@ -1,17 +1,14 @@
1
- import { EventLogger } from "gd-eventlog";
2
- import CyclingMode, { CyclingModeBase, CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
1
+ import CyclingMode, { CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
3
2
  import DaumAdapter from "./DaumAdapter";
3
+ import PowerBasedCyclingModeBase from "../modes/power-base";
4
4
  export declare type ERGEvent = {
5
5
  rpmUpdated?: boolean;
6
6
  gearUpdated?: boolean;
7
7
  starting?: boolean;
8
8
  tsStart?: number;
9
9
  };
10
- export default class ERGCyclingMode extends CyclingModeBase implements CyclingMode {
11
- logger: EventLogger;
12
- data: IncyclistBikeData;
10
+ export default class ERGCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
13
11
  prevRequest: UpdateRequest;
14
- prevUpdateTS: number;
15
12
  hasBikeUpdate: boolean;
16
13
  chain: number[];
17
14
  cassette: number[];
@@ -3,9 +3,9 @@ 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 CyclingMode_1 = require("../CyclingMode");
8
7
  const calculations_1 = __importDefault(require("../calculations"));
8
+ const power_base_1 = __importDefault(require("../modes/power-base"));
9
9
  const config = {
10
10
  name: "ERG",
11
11
  description: "Calculates speed based on power and slope. Power is either set by workout or calculated based on gear and cadence",
@@ -14,15 +14,12 @@ const config = {
14
14
  { key: 'startPower', name: 'Starting Power', description: 'Initial power in Watts at start of training', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 50, min: 25, max: 800 },
15
15
  ]
16
16
  };
17
- class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
17
+ class ERGCyclingMode extends power_base_1.default {
18
18
  constructor(adapter, props) {
19
19
  super(adapter, props);
20
- this.prevUpdateTS = 0;
21
20
  this.hasBikeUpdate = false;
22
21
  this.event = {};
23
- this.logger = adapter.logger || new gd_eventlog_1.EventLogger('ERGMode');
24
- this.data = {};
25
- this.logger.logEvent({ message: 'constructor', props });
22
+ this.initLogger('ERGMode');
26
23
  }
27
24
  getName() {
28
25
  return config.name;
@@ -132,7 +129,6 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
132
129
  const prevRequest = this.prevRequest || {};
133
130
  const data = this.data || {};
134
131
  const bikeType = this.getSetting('bikeType').toLowerCase();
135
- console.log('~~~ bikeType', bikeType);
136
132
  delete this.event.gearUpdated;
137
133
  delete this.event.rpmUpdated;
138
134
  if (prevData === {} || prevData.speed === undefined || prevData.speed === 0) {
@@ -140,32 +136,25 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
140
136
  this.event.tsStart = Date.now();
141
137
  }
142
138
  try {
143
- let rpm = bikeData.pedalRpm || 0;
144
- let gear = bikeData.gear || 0;
139
+ const rpm = bikeData.pedalRpm || 0;
140
+ const gear = bikeData.gear || 0;
145
141
  let power = bikeData.power || 0;
146
- let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
147
- let speed;
148
- let m = this.adapter.getWeight();
149
- let distanceInternal = prevData.distanceInternal || 0;
150
- let ts = Date.now();
151
- let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
152
- if (rpm === 0 || bikeData.isPedalling === false) {
153
- speed = 0;
142
+ const slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
143
+ const distanceInternal = prevData.distanceInternal || 0;
144
+ if (!bikeData.pedalRpm || bikeData.isPedalling === false) {
154
145
  power = 0;
155
146
  }
156
- else {
157
- speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType });
158
- let v = speed / 3.6;
159
- distanceInternal += Math.round(v * duration);
160
- }
147
+ const m = this.getWeight();
148
+ const t = this.getTimeSinceLastUpdate();
149
+ const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t, { bikeType });
161
150
  data.speed = parseFloat(speed.toFixed(1));
162
151
  data.power = Math.round(power);
163
- data.distanceInternal = distanceInternal;
152
+ data.distanceInternal = Math.round(distanceInternal + distance);
164
153
  data.slope = slope;
165
154
  data.pedalRpm = rpm;
166
155
  data.gear = gear;
167
- if (data.time !== undefined)
168
- data.time += duration;
156
+ if (data.time !== undefined && !(this.event.starting && !bikeData.pedalRpm))
157
+ data.time += t;
169
158
  else
170
159
  data.time = 0;
171
160
  data.heartrate = bikeData.heartrate;
@@ -176,7 +165,6 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
176
165
  if (rpm && rpm !== prevData.pedalRpm) {
177
166
  this.event.rpmUpdated = true;
178
167
  }
179
- this.prevUpdateTS = ts;
180
168
  }
181
169
  catch (err) {
182
170
  this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
@@ -188,7 +176,7 @@ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
188
176
  calculateTargetPower(request, updateMode = true) {
189
177
  const bikeType = this.getSetting('bikeType').toLowerCase();
190
178
  const defaultPower = this.getSetting('startPower');
191
- let m = this.adapter.getWeight();
179
+ let m = this.getWeight();
192
180
  const prevData = this.data || {};
193
181
  let target;
194
182
  if (prevData.pedalRpm && prevData.gear && (!updateMode || prevData.pedalRpm !== 0)) {
@@ -109,7 +109,7 @@ class DaumClassicAdapter extends DaumAdapter_1.default {
109
109
  startState.startProg = true;
110
110
  }
111
111
  if (!startState.setGear) {
112
- yield this.bike.setGear(this.daumRunData.gear || (opts.gear || 10));
112
+ yield this.bike.setGear(this.cyclingData.gear || (opts.gear || 10));
113
113
  startState.setGear = true;
114
114
  }
115
115
  const startRequest = this.getCyclingMode().getBikeInitRequest();
@@ -1,7 +1,7 @@
1
1
  import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest } from "../../CyclingMode";
2
2
  import DaumAdapter from "../DaumAdapter";
3
- import PowerMeterCyclingMode from "../PowerMeterCyclingMode";
4
- export default class DaumClassicCyclingMode extends PowerMeterCyclingMode implements CyclingMode {
3
+ import DaumPowerMeterCyclingMode from "../DaumPowerMeterCyclingMode";
4
+ export default class DaumClassicCyclingMode extends DaumPowerMeterCyclingMode implements CyclingMode {
5
5
  constructor(adapter: DaumAdapter, props?: Settings);
6
6
  getName(): string;
7
7
  getDescription(): string;
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const gd_eventlog_1 = require("gd-eventlog");
7
7
  const CyclingMode_1 = require("../../CyclingMode");
8
- const PowerMeterCyclingMode_1 = __importDefault(require("../PowerMeterCyclingMode"));
8
+ const DaumPowerMeterCyclingMode_1 = __importDefault(require("../DaumPowerMeterCyclingMode"));
9
9
  const config = {
10
10
  name: "Daum Classic",
11
11
  description: "The device calculates speed and power based on slope. Incyclist will not modify any values recived from the device\nThis mode will not respect maximum power and/or workout limits",
@@ -13,7 +13,7 @@ const config = {
13
13
  { key: 'bikeType', name: 'Bike Type', description: '', type: CyclingMode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain'], default: 'Race' },
14
14
  ]
15
15
  };
16
- class DaumClassicCyclingMode extends PowerMeterCyclingMode_1.default {
16
+ class DaumClassicCyclingMode extends DaumPowerMeterCyclingMode_1.default {
17
17
  constructor(adapter, props) {
18
18
  super(adapter, props);
19
19
  this.logger = adapter ? adapter.logger : undefined;
@@ -98,7 +98,7 @@ class DaumPremiumDevice extends DaumAdapter_1.default {
98
98
  info.person = yield this.bike.setPerson(user);
99
99
  }
100
100
  if (!this.getCyclingMode().getModeProperty('eppSupport')) {
101
- const gear = yield this.bike.setGear(this.daumRunData.gear || (opts.gear || 10));
101
+ const gear = yield this.bike.setGear(this.cyclingData.gear || (opts.gear || 10));
102
102
  return gear;
103
103
  }
104
104
  return;
@@ -0,0 +1,20 @@
1
+ import { IncyclistBikeData, Settings, CyclingModeBase } from '../CyclingMode';
2
+ import { DeviceAdapter } from '../Device';
3
+ import { EventLogger } from 'gd-eventlog';
4
+ export default class PowerBasedCyclingModeBase extends CyclingModeBase {
5
+ data: IncyclistBikeData;
6
+ prevUpdateTS: number;
7
+ logger: EventLogger;
8
+ constructor(adapter: DeviceAdapter, props?: Settings);
9
+ initLogger(defaultLogName: any): void;
10
+ getWeight(): any;
11
+ getTimeSinceLastUpdate(): number;
12
+ calculateSpeedAndDistance(power: number, slope: number, m: number, t: number, props?: {}): {
13
+ speed: number;
14
+ distance: number;
15
+ };
16
+ calculatePowerAndDistance(speed: number, slope: number, m: number, t: number, props?: {}): {
17
+ power: number;
18
+ distance: number;
19
+ };
20
+ }
@@ -0,0 +1,60 @@
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 CyclingMode_1 = require("../CyclingMode");
7
+ const Device_1 = require("../Device");
8
+ const calculations_1 = __importDefault(require("../calculations"));
9
+ const gd_eventlog_1 = require("gd-eventlog");
10
+ class PowerBasedCyclingModeBase extends CyclingMode_1.CyclingModeBase {
11
+ constructor(adapter, props) {
12
+ super(adapter, props);
13
+ this.prevUpdateTS = 0;
14
+ this.data = { speed: 0, power: 0, distanceInternal: 0, pedalRpm: 0, isPedalling: false, heartrate: 0 };
15
+ }
16
+ initLogger(defaultLogName) {
17
+ const a = this.adapter;
18
+ this.logger = (a && a.getLogger) ? a.getLogger() : undefined;
19
+ if (!this.logger)
20
+ this.logger = new gd_eventlog_1.EventLogger(defaultLogName);
21
+ }
22
+ getWeight() {
23
+ const a = this.adapter;
24
+ const m = (a && a.getWeight && a.getWeight()) ? a.getWeight() : Device_1.DEFAULT_BIKE_WEIGHT + Device_1.DEFAULT_USER_WEIGHT;
25
+ return m;
26
+ }
27
+ getTimeSinceLastUpdate() {
28
+ const ts = Date.now();
29
+ const duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
30
+ this.prevUpdateTS = ts;
31
+ return duration;
32
+ }
33
+ calculateSpeedAndDistance(power, slope, m, t, props = {}) {
34
+ const prevData = this.data || {};
35
+ const vPrev = (prevData.speed || 0) / 3.6;
36
+ const EkinPrev = 1 / 2 * m * vPrev * vPrev;
37
+ let powerToMaintainSpeed = calculations_1.default.calculatePower(m, vPrev, slope, props);
38
+ const powerDelta = powerToMaintainSpeed - power;
39
+ const Ekin = EkinPrev - powerDelta * t;
40
+ const v = Math.sqrt(2 * Ekin / m);
41
+ const speed = v * 3.6;
42
+ const distance = v * t;
43
+ this.data.speed = speed;
44
+ return { speed, distance };
45
+ }
46
+ calculatePowerAndDistance(speed, slope, m, t, props = {}) {
47
+ const prevData = this.data || {};
48
+ const vPrev = (prevData.speed || 0) / 3.6;
49
+ const EkinPrev = 1 / 2 * m * vPrev * vPrev;
50
+ const Ekin = 1 / 2 * m * speed * speed;
51
+ const powerDelta = t !== 0 ? (EkinPrev - Ekin) / t : 0;
52
+ const powerToMaintainSpeed = calculations_1.default.calculatePower(m, vPrev, slope, props);
53
+ const power = powerToMaintainSpeed - powerDelta;
54
+ const v = speed / 3.6;
55
+ const distance = v * t;
56
+ this.data.power = power;
57
+ return { power, distance };
58
+ }
59
+ }
60
+ exports.default = PowerBasedCyclingModeBase;
@@ -1,18 +1,20 @@
1
- import { EventLogger } from 'gd-eventlog';
2
- import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest, CyclingModeBase } from '../CyclingMode';
1
+ import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest } from '../CyclingMode';
2
+ import PowerBasedCyclingModeBase from './power-base';
3
3
  import { DeviceAdapter } from '../Device';
4
- export default class PowerMeterCyclingMode extends CyclingModeBase implements CyclingMode {
5
- logger: EventLogger;
6
- data: IncyclistBikeData;
7
- prevRequest: UpdateRequest;
8
- prevUpdateTS: number;
9
- hasBikeUpdate: boolean;
4
+ export declare const config: {
5
+ name: string;
6
+ description: string;
7
+ properties: any[];
8
+ };
9
+ export default class PowerMeterCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
10
10
  constructor(adapter: DeviceAdapter, props?: Settings);
11
11
  getName(): string;
12
12
  getDescription(): string;
13
13
  getProperties(): CyclingModeProperty[];
14
14
  getProperty(name: string): CyclingModeProperty;
15
15
  getBikeInitRequest(): UpdateRequest;
16
- sendBikeUpdate(request: UpdateRequest): UpdateRequest;
17
- updateData(data: IncyclistBikeData): IncyclistBikeData;
16
+ sendBikeUpdate(request?: UpdateRequest): UpdateRequest;
17
+ updateData(bikeData: IncyclistBikeData, props?: {
18
+ log: boolean;
19
+ }): IncyclistBikeData;
18
20
  }
@@ -3,84 +3,68 @@ 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
- const CyclingMode_1 = require("../CyclingMode");
8
- const Device_1 = require("../Device");
9
- const calculations_1 = __importDefault(require("../calculations"));
10
- const config = {
6
+ const power_base_1 = __importDefault(require("./power-base"));
7
+ exports.config = {
11
8
  name: 'PowerMeter',
12
9
  description: 'Power and cadence are taken from device. Speed is calculated from power and current slope\nThis mode will not respect maximum power and/or workout limits',
13
10
  properties: []
14
11
  };
15
- class PowerMeterCyclingMode extends CyclingMode_1.CyclingModeBase {
12
+ class PowerMeterCyclingMode extends power_base_1.default {
16
13
  constructor(adapter, props) {
17
14
  super(adapter, props);
18
- this.prevUpdateTS = 0;
19
- this.hasBikeUpdate = false;
20
- const a = adapter;
21
- this.logger = (a && a.getLogger) ? a.getLogger() : undefined;
22
- if (!this.logger)
23
- this.logger = new gd_eventlog_1.EventLogger('PowerMeter');
15
+ this.initLogger('PowerMeterMode');
16
+ this.data = { speed: 0, slope: 0, power: 0, distanceInternal: 0, pedalRpm: 0, isPedalling: false, heartrate: 0 };
24
17
  }
25
18
  getName() {
26
- return config.name;
19
+ return exports.config.name;
27
20
  }
28
21
  getDescription() {
29
- return config.description;
22
+ return exports.config.description;
30
23
  }
31
24
  getProperties() {
32
- return config.properties;
25
+ return exports.config.properties;
33
26
  }
34
27
  getProperty(name) {
35
- return config.properties.find(p => p.name === name);
28
+ return exports.config.properties.find(p => p.name === name);
36
29
  }
37
30
  getBikeInitRequest() {
38
31
  return {};
39
32
  }
40
- sendBikeUpdate(request) {
41
- if (request.slope)
33
+ sendBikeUpdate(request = {}) {
34
+ const prevData = this.data || {};
35
+ const prevSlope = prevData.slope || 0;
36
+ if (request.slope && request.slope !== prevSlope) {
42
37
  this.data.slope = request.slope;
43
- this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest });
44
- this.prevRequest = {};
38
+ this.updateData(this.data, { log: false });
39
+ }
40
+ this.logger.logEvent({ message: "processing update request", request });
45
41
  return {};
46
42
  }
47
- updateData(data) {
43
+ updateData(bikeData, props = { log: true }) {
48
44
  try {
49
45
  const prevData = this.data || {};
50
- const prevRequest = this.prevRequest || {};
51
- const bikeData = JSON.parse(JSON.stringify(data));
52
- let power = data.power || 0;
53
- let speed = data.speed || 0;
54
- let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
55
- let distanceInternal = prevData.distanceInternal || 0;
46
+ const data = JSON.parse(JSON.stringify(bikeData));
47
+ let power = bikeData.power || 0;
48
+ const slope = prevData.slope || 0;
49
+ const distanceInternal = prevData.distanceInternal || 0;
56
50
  if (!bikeData.pedalRpm || bikeData.isPedalling === false) {
57
- speed = 0;
58
51
  power = 0;
59
52
  }
60
- let ts = Date.now();
61
- const a = this.adapter;
62
- const m = a.getWeight ? a.getWeight() : Device_1.DEFAULT_BIKE_WEIGHT + Device_1.DEFAULT_USER_WEIGHT;
63
- let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
64
- const vPrev = (prevData.speed || 0) / 3.6;
65
- const EkinPrev = 1 / 2 * m * vPrev * vPrev;
66
- let powerRequired = calculations_1.default.calculatePower(m, vPrev, prevData.slope || 0);
67
- const powerDelta = powerRequired - power;
68
- const Ekin = EkinPrev - powerDelta * duration;
69
- const v = Math.sqrt(2 * Ekin / m);
70
- speed = v * 3.6;
71
- distanceInternal += Math.round(v * duration);
72
- data.speed = parseFloat(speed.toFixed(1));
53
+ const m = this.getWeight();
54
+ let t = this.getTimeSinceLastUpdate();
55
+ const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t);
56
+ data.speed = speed;
73
57
  data.power = Math.round(power);
74
- data.distanceInternal = Math.round(distanceInternal);
58
+ data.distanceInternal = Math.round(distanceInternal + distance);
75
59
  data.slope = slope;
76
- this.logger.logEvent({ message: "updateData result", data, bikeData, prevRequest: {}, prevSpeed: prevData.speed });
77
- this.data = JSON.parse(JSON.stringify(data));
78
- this.prevUpdateTS = ts;
60
+ if (props.log)
61
+ this.logger.logEvent({ message: "updateData result", data, bikeData, prevSpeed: prevData.speed });
62
+ this.data = data;
79
63
  }
80
64
  catch (err) {
81
65
  this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
82
66
  }
83
- return data;
67
+ return this.data;
84
68
  }
85
69
  }
86
70
  exports.default = PowerMeterCyclingMode;
@@ -0,0 +1,27 @@
1
+ import { EventLogger } from "gd-eventlog";
2
+ import CyclingMode, { CyclingModeBase, CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
3
+ import { Simulator } from "../simulator/Simulator";
4
+ export declare type ERGEvent = {
5
+ rpmUpdated?: boolean;
6
+ gearUpdated?: boolean;
7
+ starting?: boolean;
8
+ tsStart?: number;
9
+ };
10
+ export default class SimulatorCyclingMode extends CyclingModeBase implements CyclingMode {
11
+ logger: EventLogger;
12
+ data: IncyclistBikeData;
13
+ prevRequest: UpdateRequest;
14
+ prevUpdateTS: number;
15
+ hasBikeUpdate: boolean;
16
+ chain: number[];
17
+ cassette: number[];
18
+ event: ERGEvent;
19
+ constructor(adapter: Simulator, props?: any);
20
+ getName(): string;
21
+ getDescription(): string;
22
+ getProperties(): CyclingModeProperty[];
23
+ getProperty(name: string): CyclingModeProperty;
24
+ getBikeInitRequest(): UpdateRequest;
25
+ sendBikeUpdate(request: UpdateRequest): UpdateRequest;
26
+ updateData(bikeData: IncyclistBikeData): IncyclistBikeData;
27
+ }
@@ -0,0 +1,118 @@
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 gd_eventlog_1 = require("gd-eventlog");
7
+ const CyclingMode_1 = require("../CyclingMode");
8
+ const calculations_1 = __importDefault(require("../calculations"));
9
+ const config = {
10
+ name: "Simulator",
11
+ description: "Simulates a ride with constant speed or power output",
12
+ properties: [
13
+ { key: 'mode', name: 'Simulation Type', description: '', type: CyclingMode_1.CyclingModeProperyType.SingleSelect, options: ['Speed', 'Power'], default: 'Power' },
14
+ { key: 'delay', name: 'Start Delay', description: 'Delay (in s) at start of training', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 2, min: 0, max: 30 },
15
+ { key: 'power', name: 'Power', description: 'Power (in W) at start of training', condition: (s) => !s.mode || s.mode === 'Power', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 150, min: 25, max: 800 },
16
+ { key: 'speed', name: 'Speed', description: 'Speed (in km/h) at start of training', condition: (s) => s.mode === 'Speed', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 30, min: 5, max: 50 },
17
+ ]
18
+ };
19
+ class SimulatorCyclingMode extends CyclingMode_1.CyclingModeBase {
20
+ constructor(adapter, props) {
21
+ super(adapter, props);
22
+ this.prevUpdateTS = 0;
23
+ this.hasBikeUpdate = false;
24
+ this.event = {};
25
+ this.logger = adapter.logger || new gd_eventlog_1.EventLogger('SIMMode');
26
+ this.data = {};
27
+ this.logger.logEvent({ message: 'constructor', props });
28
+ }
29
+ getName() {
30
+ return config.name;
31
+ }
32
+ getDescription() {
33
+ return config.description;
34
+ }
35
+ getProperties() {
36
+ return config.properties;
37
+ }
38
+ getProperty(name) {
39
+ return config.properties.find(p => p.name === name);
40
+ }
41
+ getBikeInitRequest() {
42
+ return {};
43
+ }
44
+ sendBikeUpdate(request) {
45
+ this.logger.logEvent({ message: 'bike update request', request });
46
+ const r = request || { refresh: true };
47
+ if (r.refresh) {
48
+ if (Object.keys(r).length === 1)
49
+ return this.prevRequest || {};
50
+ delete r.refresh;
51
+ }
52
+ if (request.slope !== undefined) {
53
+ if (!this.data)
54
+ this.data = {};
55
+ this.data.slope = request.slope;
56
+ }
57
+ this.prevRequest = request ? JSON.parse(JSON.stringify(request)) : {};
58
+ return r;
59
+ }
60
+ updateData(bikeData) {
61
+ const prevData = JSON.parse(JSON.stringify(this.data || {}));
62
+ const prevSpeed = prevData.speed;
63
+ const prevRequest = this.prevRequest || {};
64
+ const data = this.data || {};
65
+ const mode = this.getSetting('mode');
66
+ delete this.event.gearUpdated;
67
+ delete this.event.rpmUpdated;
68
+ try {
69
+ let rpm = 90;
70
+ let power = (mode === 'Power' || !mode) ? Number(this.getSetting('power')) : bikeData.power || 0;
71
+ let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
72
+ let speed = mode === 'Speed' ? Number(this.getSetting('speed')) : bikeData.speed || 0;
73
+ let m = 75;
74
+ let distanceInternal = prevData.distanceInternal || 0;
75
+ let ts = Date.now();
76
+ let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
77
+ if (mode === 'Power' || !mode) {
78
+ speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
79
+ }
80
+ else if (mode === 'Speed') {
81
+ power = calculations_1.default.calculatePower(m, speed / 3.6, slope, { bikeType: 'race' });
82
+ }
83
+ if (prevRequest.targetPower) {
84
+ power = prevRequest.targetPower;
85
+ speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
86
+ }
87
+ if (prevRequest.maxPower && power > prevRequest.maxPower) {
88
+ power = prevRequest.maxPower;
89
+ speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
90
+ }
91
+ else if (prevRequest.minPower && power < prevRequest.minPower) {
92
+ power = prevRequest.minPower;
93
+ speed = calculations_1.default.calculateSpeed(m, power, slope, { bikeType: 'race' });
94
+ }
95
+ let v = speed / 3.6;
96
+ distanceInternal += Math.round(v * duration);
97
+ data.speed = parseFloat(speed.toFixed(1));
98
+ data.power = Math.round(power);
99
+ data.distanceInternal = distanceInternal;
100
+ data.slope = slope;
101
+ data.pedalRpm = rpm;
102
+ if (data.time !== undefined)
103
+ data.time += duration;
104
+ else
105
+ data.time = 0;
106
+ data.heartrate = bikeData.heartrate;
107
+ data.isPedalling = true;
108
+ this.prevUpdateTS = ts;
109
+ }
110
+ catch (err) {
111
+ this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
112
+ }
113
+ this.logger.logEvent({ message: "updateData result", data, bikeData, prevRequest, prevSpeed });
114
+ this.data = data;
115
+ return data;
116
+ }
117
+ }
118
+ exports.default = SimulatorCyclingMode;
@@ -23,7 +23,7 @@ const DeviceProtocol_1 = __importStar(require("../DeviceProtocol"));
23
23
  const DeviceRegistry_1 = __importDefault(require("../DeviceRegistry"));
24
24
  const Device_1 = __importDefault(require("../Device"));
25
25
  const gd_eventlog_1 = require("gd-eventlog");
26
- const simulator_mode_1 = __importDefault(require("./simulator-mode"));
26
+ const simulator_1 = __importDefault(require("../modes/simulator"));
27
27
  const DEFAULT_SETTINGS = { name: 'Simulator', port: '', isBot: false };
28
28
  class Simulator extends Device_1.default {
29
29
  constructor(protocol, props = DEFAULT_SETTINGS) {
@@ -59,11 +59,11 @@ class Simulator extends Device_1.default {
59
59
  }
60
60
  getSupportedCyclingModes() {
61
61
  const supported = [];
62
- supported.push(simulator_mode_1.default);
62
+ supported.push(simulator_1.default);
63
63
  return supported;
64
64
  }
65
65
  getDefaultCyclingMode() {
66
- return new simulator_mode_1.default(this);
66
+ return new simulator_1.default(this);
67
67
  }
68
68
  getCyclingMode() {
69
69
  if (!this.cyclingMode)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "1.4.24",
3
+ "version": "1.4.25",
4
4
  "dependencies": {
5
5
  "@serialport/parser-byte-length": "^9.0.1",
6
6
  "@serialport/parser-delimiter": "^9.0.1",