incyclist-devices 2.0.38 → 2.1.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.
Files changed (103) hide show
  1. package/lib/antv2/adapter-factory.d.ts +4 -2
  2. package/lib/antv2/adapter.d.ts +3 -20
  3. package/lib/antv2/adapter.js +2 -92
  4. package/lib/antv2/fe/adapter.d.ts +9 -5
  5. package/lib/antv2/fe/adapter.js +25 -14
  6. package/lib/antv2/hr/adapter.d.ts +2 -1
  7. package/lib/antv2/pwr/adapter.d.ts +9 -5
  8. package/lib/antv2/pwr/adapter.js +19 -8
  9. package/lib/antv2/types.d.ts +2 -1
  10. package/lib/base/adpater.d.ts +31 -13
  11. package/lib/base/adpater.js +84 -28
  12. package/lib/ble/adapter-factory.d.ts +10 -9
  13. package/lib/ble/base/adapter.d.ts +9 -20
  14. package/lib/ble/base/adapter.js +13 -69
  15. package/lib/ble/ble-interface.d.ts +5 -4
  16. package/lib/ble/cp/adapter.d.ts +5 -8
  17. package/lib/ble/cp/adapter.js +26 -10
  18. package/lib/ble/elite/adapter.d.ts +6 -5
  19. package/lib/ble/elite/adapter.js +26 -2
  20. package/lib/ble/fm/adapter.d.ts +10 -9
  21. package/lib/ble/fm/adapter.js +38 -11
  22. package/lib/ble/hr/adapter.d.ts +5 -3
  23. package/lib/ble/peripheral-cache.d.ts +3 -2
  24. package/lib/ble/tacx/adapter.d.ts +2 -2
  25. package/lib/ble/wahoo/adapter.d.ts +2 -2
  26. package/lib/index.d.ts +5 -5
  27. package/lib/index.js +4 -2
  28. package/lib/interfaces.d.ts +1 -1
  29. package/lib/modes/ant-fe-adv-st-mode.d.ts +12 -0
  30. package/lib/modes/ant-fe-adv-st-mode.js +83 -0
  31. package/lib/modes/antble-erg.d.ts +31 -0
  32. package/lib/modes/antble-erg.js +28 -0
  33. package/lib/modes/antble-smarttrainer.d.ts +23 -0
  34. package/lib/modes/antble-smarttrainer.js +65 -0
  35. package/lib/modes/base.d.ts +29 -0
  36. package/lib/modes/{cycling-mode.js → base.js} +23 -26
  37. package/lib/modes/daum-classic-standard.d.ts +16 -0
  38. package/lib/modes/daum-classic-standard.js +54 -0
  39. package/lib/modes/daum-erg.d.ts +49 -0
  40. package/lib/{serial/daum/ERGCyclingMode.js → modes/daum-erg.js} +45 -73
  41. package/lib/modes/daum-power.d.ts +5 -0
  42. package/lib/{serial/daum/DaumPowerMeterCyclingMode.js → modes/daum-power.js} +1 -10
  43. package/lib/modes/daum-premium-standard.d.ts +23 -0
  44. package/lib/{serial/daum/premium/modes/daum-classic.js → modes/daum-premium-standard.js} +26 -38
  45. package/lib/modes/daum-smarttrainer.d.ts +82 -0
  46. package/lib/{serial/daum/SmartTrainerCyclingMode.js → modes/daum-smarttrainer.js} +23 -26
  47. package/lib/modes/kettler-erg.d.ts +44 -0
  48. package/lib/{serial/kettler/ergo-racer/modes/erg.js → modes/kettler-erg.js} +12 -23
  49. package/lib/modes/power-base.d.ts +28 -3
  50. package/lib/modes/power-base.js +182 -5
  51. package/lib/modes/power-meter.d.ts +10 -16
  52. package/lib/modes/power-meter.js +15 -54
  53. package/lib/modes/simulator.d.ts +37 -10
  54. package/lib/modes/simulator.js +14 -46
  55. package/lib/modes/{cycling-mode.d.ts → types.d.ts} +22 -19
  56. package/lib/modes/types.js +56 -0
  57. package/lib/serial/adapter-factory.d.ts +7 -5
  58. package/lib/serial/adapter.d.ts +2 -2
  59. package/lib/serial/adapter.js +5 -2
  60. package/lib/serial/daum/DaumAdapter.d.ts +27 -24
  61. package/lib/serial/daum/DaumAdapter.js +200 -128
  62. package/lib/serial/daum/classic/adapter.d.ts +20 -21
  63. package/lib/serial/daum/classic/adapter.js +83 -157
  64. package/lib/serial/daum/classic/comms.d.ts +19 -13
  65. package/lib/serial/daum/classic/comms.js +39 -12
  66. package/lib/serial/daum/classic/mock.d.ts +4 -0
  67. package/lib/serial/daum/classic/mock.js +14 -4
  68. package/lib/serial/daum/premium/adapter.d.ts +16 -11
  69. package/lib/serial/daum/premium/adapter.js +98 -95
  70. package/lib/serial/daum/premium/comms.d.ts +4 -1
  71. package/lib/serial/daum/premium/comms.js +25 -17
  72. package/lib/serial/daum/premium/mock.d.ts +4 -0
  73. package/lib/serial/daum/premium/mock.js +15 -5
  74. package/lib/serial/daum/types.d.ts +11 -0
  75. package/lib/serial/daum/types.js +5 -0
  76. package/lib/serial/kettler/ergo-racer/adapter.d.ts +10 -8
  77. package/lib/serial/kettler/ergo-racer/adapter.js +17 -7
  78. package/lib/serial/serial-interface.js +1 -2
  79. package/lib/simulator/Simulator.d.ts +14 -17
  80. package/lib/simulator/Simulator.js +18 -47
  81. package/lib/types/adapter.d.ts +29 -8
  82. package/lib/types/adapter.js +68 -0
  83. package/lib/utils/utils.d.ts +1 -1
  84. package/package.json +3 -3
  85. package/lib/antv2/modes/ant-fe-adv-st-mode.d.ts +0 -9
  86. package/lib/antv2/modes/ant-fe-adv-st-mode.js +0 -51
  87. package/lib/antv2/modes/ant-fe-erg-mode.d.ts +0 -6
  88. package/lib/antv2/modes/ant-fe-erg-mode.js +0 -14
  89. package/lib/antv2/modes/ant-fe-st-mode.d.ts +0 -5
  90. package/lib/antv2/modes/ant-fe-st-mode.js +0 -13
  91. package/lib/modes/ble-erg-mode.d.ts +0 -18
  92. package/lib/modes/ble-erg-mode.js +0 -148
  93. package/lib/modes/ble-st-mode.d.ts +0 -15
  94. package/lib/modes/ble-st-mode.js +0 -96
  95. package/lib/serial/daum/DaumPowerMeterCyclingMode.d.ts +0 -8
  96. package/lib/serial/daum/ERGCyclingMode.d.ts +0 -26
  97. package/lib/serial/daum/SmartTrainerCyclingMode.d.ts +0 -42
  98. package/lib/serial/daum/classic/modes/daum-classic.d.ts +0 -13
  99. package/lib/serial/daum/classic/modes/daum-classic.js +0 -97
  100. package/lib/serial/daum/premium/modes/daum-classic.d.ts +0 -14
  101. package/lib/serial/kettler/ergo-racer/modes/erg.d.ts +0 -25
  102. /package/lib/serial/daum/{constants.d.ts → consts.d.ts} +0 -0
  103. /package/lib/serial/daum/{constants.js → consts.js} +0 -0
@@ -0,0 +1,23 @@
1
+ import ICyclingMode, { CyclingModeProperyType, IncyclistBikeData, Settings, UpdateRequest } from "./types";
2
+ import { IncyclistDeviceAdapter } from "../types/adapter";
3
+ import PowerMeterCyclingMode from "./power-meter";
4
+ export default class DaumClassicCyclingMode extends PowerMeterCyclingMode implements ICyclingMode {
5
+ prevInternalDistance: number;
6
+ distanceOffset: number;
7
+ protected static config: {
8
+ name: string;
9
+ description: string;
10
+ properties: {
11
+ key: string;
12
+ name: string;
13
+ description: string;
14
+ type: CyclingModeProperyType;
15
+ options: string[];
16
+ default: string;
17
+ }[];
18
+ };
19
+ constructor(adapter: IncyclistDeviceAdapter, props?: Settings);
20
+ getBikeInitRequest(): UpdateRequest;
21
+ updateData(data: IncyclistBikeData): IncyclistBikeData;
22
+ sendBikeUpdate(request: UpdateRequest): UpdateRequest;
23
+ }
@@ -4,16 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const gd_eventlog_1 = require("gd-eventlog");
7
- const cycling_mode_1 = require("../../../../modes/cycling-mode");
8
- const DaumPowerMeterCyclingMode_1 = __importDefault(require("../../DaumPowerMeterCyclingMode"));
9
- const config = {
10
- name: "Daum Classic",
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",
12
- properties: [
13
- { key: 'bikeType', name: 'Bike Type', description: '', type: cycling_mode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain'], default: 'Race' },
14
- ]
15
- };
16
- class DaumClassicCyclingMode extends DaumPowerMeterCyclingMode_1.default {
7
+ const types_1 = require("./types");
8
+ const power_meter_1 = __importDefault(require("./power-meter"));
9
+ class DaumClassicCyclingMode extends power_meter_1.default {
17
10
  constructor(adapter, props) {
18
11
  super(adapter, props);
19
12
  this.logger = adapter ? adapter.getLogger() : undefined;
@@ -22,65 +15,60 @@ class DaumClassicCyclingMode extends DaumPowerMeterCyclingMode_1.default {
22
15
  this.setModeProperty('eppSupport', true);
23
16
  this.setModeProperty('setPersonSupport', true);
24
17
  }
25
- getName() {
26
- return config.name;
27
- }
28
- getDescription() {
29
- return config.description;
30
- }
31
- getProperties() {
32
- return config.properties;
33
- }
34
- getProperty(name) {
35
- return config.properties.find(p => p.name === name);
36
- }
37
18
  getBikeInitRequest() {
38
19
  return {};
39
20
  }
40
- getSettings() {
41
- const settings = super.getSettings();
42
- settings['setPerson'] = true;
43
- return settings;
44
- }
45
- getSetting(name) {
46
- if (name === 'setPerson')
47
- return true;
48
- return super.getSetting(name);
49
- }
50
21
  updateData(data) {
51
22
  try {
52
- const prevData = this.data || {};
23
+ const prevData = Object.assign({}, this.getData());
53
24
  const prevRequest = this.prevRequest || {};
54
25
  const bikeData = JSON.parse(JSON.stringify(data));
55
26
  let power = data.power || 0;
56
27
  let speed = data.speed || 0;
57
28
  let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
58
29
  let distanceBike = data.distanceInternal || 0;
59
- let distancePrev = prevData.distanceInternal || 0;
60
- let distanceInternal = distanceBike;
30
+ let distancePrev = this.prevInternalDistance || 0;
31
+ let distanceInternal = distanceBike + (this.distanceOffset || 0);
32
+ let time = prevData.time || 0;
61
33
  let ts = Date.now();
34
+ let t = this.getTimeSinceLastUpdate();
62
35
  if (!bikeData.pedalRpm || bikeData.isPedalling === false) {
63
36
  speed = 0;
64
37
  power = 0;
65
38
  }
66
39
  if (distanceBike < distancePrev) {
67
- this.logger.logEvent({ message: '~~~ distance overflow', distanceBike, distancePrev });
40
+ this.logger.logEvent({ message: 'distance overflow', distanceBike, distancePrev });
68
41
  let v = speed / 3.6;
69
- let duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
70
- distanceInternal = distancePrev + v * duration;
42
+ const calculateDistance = distancePrev + (this.distanceOffset || 0) + v * t;
43
+ this.distanceOffset = calculateDistance - distanceBike;
44
+ distanceInternal = calculateDistance;
71
45
  }
72
46
  data.speed = parseFloat(speed.toFixed(1));
73
47
  data.power = Math.round(power);
74
48
  data.distanceInternal = distanceInternal;
75
49
  data.slope = slope;
50
+ data.time = speed > 0 ? time + t : time;
76
51
  this.logger.logEvent({ message: "updateData result", data, bikeData, prevRequest: {}, prevSpeed: prevData.speed });
77
52
  this.data = JSON.parse(JSON.stringify(data));
78
53
  this.prevUpdateTS = ts;
54
+ this.prevInternalDistance = distanceBike;
79
55
  }
80
56
  catch (err) {
81
57
  this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
82
58
  }
83
59
  return data;
84
60
  }
61
+ sendBikeUpdate(request) {
62
+ super.sendBikeUpdate(request);
63
+ this.prevRequest = {};
64
+ return {};
65
+ }
85
66
  }
67
+ DaumClassicCyclingMode.config = {
68
+ name: "Daum Classic",
69
+ 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",
70
+ properties: [
71
+ { key: 'bikeType', name: 'Bike Type', description: '', type: types_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain'], default: 'Race' },
72
+ ]
73
+ };
86
74
  exports.default = DaumClassicCyclingMode;
@@ -0,0 +1,82 @@
1
+ import { EventLogger } from "gd-eventlog";
2
+ import ICyclingMode, { CyclingModeProperyType, IncyclistBikeData, Settings, UpdateRequest } from "./types";
3
+ import { IncyclistDeviceAdapter } from "..";
4
+ import { CyclingModeBase } from "./base";
5
+ interface STUpdateRequest extends UpdateRequest {
6
+ calculatedPower?: number;
7
+ delta?: number;
8
+ enforced?: boolean;
9
+ belowMin?: boolean;
10
+ aboveMax?: boolean;
11
+ }
12
+ export declare enum direction {
13
+ up = "up",
14
+ down = "down"
15
+ }
16
+ interface STEvent {
17
+ gearUpdate?: direction;
18
+ rpmUpdate?: boolean;
19
+ targetNotReached?: number;
20
+ }
21
+ export default class SmartTrainerCyclingMode extends CyclingModeBase implements ICyclingMode {
22
+ logger: EventLogger;
23
+ data?: IncyclistBikeData;
24
+ prevRequest?: STUpdateRequest;
25
+ prevUpdateTS: number;
26
+ chain: number[];
27
+ cassette: number[];
28
+ event: STEvent;
29
+ protected static config: {
30
+ name: string;
31
+ description: string;
32
+ properties: ({
33
+ key: string;
34
+ name: string;
35
+ description: string;
36
+ type: CyclingModeProperyType;
37
+ options: string[];
38
+ default: string;
39
+ validation?: undefined;
40
+ condition?: undefined;
41
+ } | {
42
+ key: string;
43
+ name: string;
44
+ description: string;
45
+ type: CyclingModeProperyType;
46
+ default: number;
47
+ options?: undefined;
48
+ validation?: undefined;
49
+ condition?: undefined;
50
+ } | {
51
+ key: string;
52
+ name: string;
53
+ description: string;
54
+ type: CyclingModeProperyType;
55
+ default: boolean;
56
+ options?: undefined;
57
+ validation?: undefined;
58
+ condition?: undefined;
59
+ } | {
60
+ key: string;
61
+ name: string;
62
+ description: string;
63
+ type: CyclingModeProperyType;
64
+ validation: string;
65
+ default: string;
66
+ condition: (s: any) => any;
67
+ options?: undefined;
68
+ })[];
69
+ };
70
+ constructor(adapter: IncyclistDeviceAdapter, props?: Settings);
71
+ getData(): IncyclistBikeData;
72
+ getSlope(): number;
73
+ getWeight(): number;
74
+ getBikeInitRequest(): STUpdateRequest;
75
+ useGearSimulation(): boolean;
76
+ getMinMaxGears(source: string): [number, number];
77
+ sendBikeUpdate(request: STUpdateRequest): STUpdateRequest;
78
+ updateData(bikeData: IncyclistBikeData): any;
79
+ calculateSpeed(gear: any, rpm: any, slope: any, bikeSpeed: any, props?: any): any;
80
+ calculateTargetPower(request: STUpdateRequest, speed?: number): STUpdateRequest;
81
+ }
82
+ export {};
@@ -5,28 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.direction = void 0;
7
7
  const gd_eventlog_1 = require("gd-eventlog");
8
- const cycling_mode_1 = require("../../modes/cycling-mode");
9
- const calculations_1 = __importDefault(require("../../utils/calculations"));
10
- const utils_1 = require("./classic/utils");
8
+ const types_1 = require("./types");
9
+ const calculations_1 = __importDefault(require("../utils/calculations"));
10
+ const utils_1 = require("../serial/daum/classic/utils");
11
+ const base_1 = require("./base");
11
12
  const SEC_DELAY = 3;
12
- const config = {
13
- name: "SmartTrainer",
14
- description: "Calculates power based on speed and slope.",
15
- properties: [
16
- { key: 'bikeType', name: 'Bike Type', description: '', type: cycling_mode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' },
17
- { key: 'startPower', name: 'Starting Power', description: 'Initial power in Watts at start of training', type: cycling_mode_1.CyclingModeProperyType.Integer, default: 50 },
18
- { key: 'minPower', name: 'Minimum Power', description: 'Minimum power in declines', type: cycling_mode_1.CyclingModeProperyType.Integer, default: 50 },
19
- { key: 'simulation', name: 'Simulate ', description: 'Simulate ', type: cycling_mode_1.CyclingModeProperyType.Boolean, default: false },
20
- { key: 'chainRings', name: 'Chain Rings', description: 'Simulated chain rings (format: <min>-<max>)', type: cycling_mode_1.CyclingModeProperyType.String, validation: '', default: '36-52', condition: (s) => s.simulation },
21
- { key: 'cassetteRings', name: 'Cassette', description: 'Simulated cassette (format: <min>-<max>)', type: cycling_mode_1.CyclingModeProperyType.String, validation: '', default: '11-30', condition: (s) => s.simulation },
22
- ]
23
- };
24
13
  var direction;
25
14
  (function (direction) {
26
15
  direction["up"] = "up";
27
16
  direction["down"] = "down";
28
17
  })(direction || (exports.direction = direction = {}));
29
- class SmartTrainerCyclingMode extends cycling_mode_1.CyclingModeBase {
18
+ class SmartTrainerCyclingMode extends base_1.CyclingModeBase {
30
19
  constructor(adapter, props) {
31
20
  super(adapter, props);
32
21
  this.prevUpdateTS = 0;
@@ -34,18 +23,14 @@ class SmartTrainerCyclingMode extends cycling_mode_1.CyclingModeBase {
34
23
  this.logger = adapter ? adapter.getLogger() : undefined;
35
24
  if (!this.logger)
36
25
  this.logger = new gd_eventlog_1.EventLogger('SmartTrainer');
26
+ this.data = { speed: 0, power: 0, distanceInternal: 0, pedalRpm: 0, isPedalling: false, heartrate: 0 };
37
27
  }
38
- getName() {
39
- return config.name;
40
- }
41
- getDescription() {
42
- return config.description;
28
+ getData() {
29
+ return this.data;
43
30
  }
44
- getProperties() {
45
- return config.properties;
46
- }
47
- getProperty(name) {
48
- return config.properties.find(p => p.name === name);
31
+ getSlope() {
32
+ const { slope } = this.data;
33
+ return slope || 0;
49
34
  }
50
35
  getWeight() {
51
36
  const a = this.adapter;
@@ -347,4 +332,16 @@ class SmartTrainerCyclingMode extends cycling_mode_1.CyclingModeBase {
347
332
  return request;
348
333
  }
349
334
  }
335
+ SmartTrainerCyclingMode.config = {
336
+ name: "SmartTrainer",
337
+ description: "Calculates power based on speed and slope.",
338
+ properties: [
339
+ { key: 'bikeType', name: 'Bike Type', description: '', type: types_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' },
340
+ { key: 'startPower', name: 'Starting Power', description: 'Initial power in Watts at start of training', type: types_1.CyclingModeProperyType.Integer, default: 50 },
341
+ { key: 'minPower', name: 'Minimum Power', description: 'Minimum power in declines', type: types_1.CyclingModeProperyType.Integer, default: 50 },
342
+ { key: 'simulation', name: 'Simulate ', description: 'Simulate ', type: types_1.CyclingModeProperyType.Boolean, default: false },
343
+ { key: 'chainRings', name: 'Chain Rings', description: 'Simulated chain rings (format: <min>-<max>)', type: types_1.CyclingModeProperyType.String, validation: '', default: '36-52', condition: (s) => s.simulation },
344
+ { key: 'cassetteRings', name: 'Cassette', description: 'Simulated cassette (format: <min>-<max>)', type: types_1.CyclingModeProperyType.String, validation: '', default: '11-30', condition: (s) => s.simulation },
345
+ ]
346
+ };
350
347
  exports.default = SmartTrainerCyclingMode;
@@ -0,0 +1,44 @@
1
+ import ICyclingMode, { CyclingModeProperyType, IncyclistBikeData, UpdateRequest } from './types';
2
+ import PowerBasedCyclingModeBase from './power-base';
3
+ import { IncyclistDeviceAdapter } from '../types/adapter';
4
+ export type ERGEvent = {
5
+ rpmUpdated?: boolean;
6
+ gearUpdated?: boolean;
7
+ starting?: boolean;
8
+ tsStart?: number;
9
+ };
10
+ export default class ERGCyclingMode extends PowerBasedCyclingModeBase implements ICyclingMode {
11
+ protected static config: {
12
+ isERG: boolean;
13
+ name: string;
14
+ description: string;
15
+ properties: ({
16
+ key: string;
17
+ name: string;
18
+ description: string;
19
+ type: CyclingModeProperyType;
20
+ options: string[];
21
+ default: string;
22
+ min?: undefined;
23
+ max?: undefined;
24
+ } | {
25
+ key: string;
26
+ name: string;
27
+ description: string;
28
+ type: CyclingModeProperyType;
29
+ default: number;
30
+ min: number;
31
+ max: number;
32
+ options?: undefined;
33
+ })[];
34
+ };
35
+ prevRequest: UpdateRequest;
36
+ hasBikeUpdate: boolean;
37
+ chain: number[];
38
+ cassette: number[];
39
+ event: ERGEvent;
40
+ constructor(adapter: IncyclistDeviceAdapter, props?: any);
41
+ getBikeInitRequest(): UpdateRequest;
42
+ sendBikeUpdate(request: UpdateRequest): UpdateRequest;
43
+ updateData(bikeData: IncyclistBikeData): any;
44
+ }
@@ -3,17 +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 cycling_mode_1 = require("../../../../modes/cycling-mode");
7
- const power_base_1 = __importDefault(require("../../../../modes/power-base"));
6
+ const types_1 = require("./types");
7
+ const power_base_1 = __importDefault(require("./power-base"));
8
8
  const MIN_SPEED = 10;
9
- const config = {
10
- name: "ERG",
11
- description: "Calculates speed based on power and slope. Power is either set by a workout",
12
- properties: [
13
- { key: 'bikeType', name: 'Bike Type', description: '', type: cycling_mode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' },
14
- { key: 'startPower', name: 'Starting Power', description: 'Initial power in Watts at start of training', type: cycling_mode_1.CyclingModeProperyType.Integer, default: 50, min: 25, max: 800 },
15
- ]
16
- };
17
9
  class ERGCyclingMode extends power_base_1.default {
18
10
  constructor(adapter, props) {
19
11
  super(adapter, props);
@@ -21,18 +13,6 @@ class ERGCyclingMode extends power_base_1.default {
21
13
  this.event = {};
22
14
  this.initLogger('ERGMode');
23
15
  }
24
- getName() {
25
- return config.name;
26
- }
27
- getDescription() {
28
- return config.description;
29
- }
30
- getProperties() {
31
- return config.properties;
32
- }
33
- getProperty(name) {
34
- return config.properties.find(p => p.name === name);
35
- }
36
16
  getBikeInitRequest() {
37
17
  const startPower = this.getSetting('startPower');
38
18
  return { targetPower: startPower };
@@ -131,6 +111,7 @@ class ERGCyclingMode extends power_base_1.default {
131
111
  data.time = 0;
132
112
  data.heartrate = bikeData.heartrate;
133
113
  data.isPedalling = bikeData.isPedalling;
114
+ this.prevUpdateTS = Date.now();
134
115
  }
135
116
  catch (err) {
136
117
  this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
@@ -140,5 +121,13 @@ class ERGCyclingMode extends power_base_1.default {
140
121
  return data;
141
122
  }
142
123
  }
143
- ERGCyclingMode.isERG = true;
124
+ ERGCyclingMode.config = {
125
+ isERG: true,
126
+ name: "ERG",
127
+ description: "Calculates speed based on power and slope. Power is either set by a workout",
128
+ properties: [
129
+ { key: 'bikeType', name: 'Bike Type', description: '', type: types_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' },
130
+ { key: 'startPower', name: 'Starting Power', description: 'Initial power in Watts at start of training', type: types_1.CyclingModeProperyType.Integer, default: 50, min: 25, max: 800 },
131
+ ]
132
+ };
144
133
  exports.default = ERGCyclingMode;
@@ -1,20 +1,45 @@
1
- import { IncyclistBikeData, Settings, CyclingModeBase } from './cycling-mode';
1
+ import { CyclingModeConfig, IncyclistBikeData, Settings, UpdateRequest } from './types';
2
2
  import { EventLogger } from 'gd-eventlog';
3
- import { ControllableDeviceAdapter } from '..';
3
+ import { IncyclistDeviceAdapter } from '..';
4
+ import { CyclingModeBase } from './base';
4
5
  export default class PowerBasedCyclingModeBase extends CyclingModeBase {
5
6
  data: IncyclistBikeData;
6
7
  prevUpdateTS: number;
7
8
  logger: EventLogger;
8
- constructor(adapter: ControllableDeviceAdapter, props?: Settings);
9
+ prevRequest: UpdateRequest | undefined;
10
+ protected static config: CyclingModeConfig;
11
+ constructor(adapter: IncyclistDeviceAdapter, props?: Settings);
12
+ getData(): IncyclistBikeData;
13
+ getSlope(): number;
9
14
  initLogger(defaultLogName: any): void;
10
15
  getWeight(): number;
11
16
  getTimeSinceLastUpdate(): number;
17
+ checkForResetOrEmpty(request: UpdateRequest): UpdateRequest | undefined;
18
+ protected checkForTempPowerAdjustments(request: UpdateRequest): void;
19
+ protected checkRefresh(request: UpdateRequest, newRequest: UpdateRequest): void;
20
+ protected checkTargetPowerSet(request: UpdateRequest, newRequest: UpdateRequest): void;
21
+ protected checkSlope(request: UpdateRequest): void;
22
+ protected checkEmptyRequest(newRequest: UpdateRequest): void;
23
+ protected checkMinPower(request: UpdateRequest, newRequest: UpdateRequest): void;
24
+ protected checkMaxPower(request: UpdateRequest, newRequest: UpdateRequest): void;
12
25
  calculateSpeedAndDistance(power: number, slope: number, m: number, t: number, props?: {}): {
13
26
  speed: number;
14
27
  distance: number;
15
28
  };
29
+ protected updateSpeedAndDistance(power: number, slope: any, bikeType: any, data: IncyclistBikeData, prevData: any): number;
30
+ protected getCalcBasics(bikeData: IncyclistBikeData): {
31
+ data: IncyclistBikeData;
32
+ prevData: any;
33
+ prevRequest: UpdateRequest;
34
+ bikeType: any;
35
+ slope: any;
36
+ };
37
+ copyBikeData(data: IncyclistBikeData, bikeData: IncyclistBikeData): IncyclistBikeData;
16
38
  calculatePowerAndDistance(speed: number, slope: number, m: number, t: number, props?: {}): {
17
39
  power: number;
18
40
  distance: number;
19
41
  };
42
+ getBikeInitRequest(): UpdateRequest;
43
+ sendBikeUpdate(incoming: UpdateRequest): UpdateRequest;
44
+ updateData(bikeData: IncyclistBikeData, log?: boolean): IncyclistBikeData;
20
45
  }
@@ -3,16 +3,24 @@ 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 cycling_mode_1 = require("./cycling-mode");
7
6
  const adpater_1 = require("../base/adpater");
8
7
  const calculations_1 = __importDefault(require("../utils/calculations"));
9
8
  const gd_eventlog_1 = require("gd-eventlog");
10
- class PowerBasedCyclingModeBase extends cycling_mode_1.CyclingModeBase {
9
+ const base_1 = require("./base");
10
+ const MIN_SPEED = 10;
11
+ class PowerBasedCyclingModeBase extends base_1.CyclingModeBase {
11
12
  constructor(adapter, props) {
12
13
  super(adapter, props);
13
14
  this.prevUpdateTS = 0;
14
15
  this.data = { speed: 0, power: 0, distanceInternal: 0, pedalRpm: 0, isPedalling: false, heartrate: 0 };
15
16
  }
17
+ getData() {
18
+ return this.data;
19
+ }
20
+ getSlope() {
21
+ const { slope } = this.data;
22
+ return slope || 0;
23
+ }
16
24
  initLogger(defaultLogName) {
17
25
  const a = this.adapter;
18
26
  this.logger = a.getLogger();
@@ -28,11 +36,86 @@ class PowerBasedCyclingModeBase extends cycling_mode_1.CyclingModeBase {
28
36
  getTimeSinceLastUpdate() {
29
37
  const ts = Date.now();
30
38
  const duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
31
- this.prevUpdateTS = ts;
32
39
  return duration;
33
40
  }
41
+ checkForResetOrEmpty(request) {
42
+ if (!request || request.reset) {
43
+ this.prevRequest = {};
44
+ return { reset: true };
45
+ }
46
+ if (Object.keys(request).length === 0 && this.prevRequest) {
47
+ return { targetPower: this.prevRequest.targetPower, refresh: true };
48
+ }
49
+ }
50
+ checkForTempPowerAdjustments(request) {
51
+ if (request.targetPowerDelta && this.prevRequest && this.prevRequest.targetPower) {
52
+ request.targetPower = this.prevRequest.targetPower + request.targetPowerDelta;
53
+ if (request.targetPower < 10)
54
+ request.targetPower = this.prevRequest.targetPower;
55
+ delete request.targetPowerDelta;
56
+ }
57
+ }
58
+ checkRefresh(request, newRequest) {
59
+ if (request.refresh && request.targetPower === undefined) {
60
+ delete request.refresh;
61
+ if (this.prevRequest)
62
+ newRequest.targetPower = this.prevRequest.targetPower;
63
+ }
64
+ }
65
+ checkTargetPowerSet(request, newRequest) {
66
+ let target = undefined;
67
+ if (request.targetPower !== undefined)
68
+ target = request.targetPower;
69
+ if (request.targetPower === undefined && request.maxPower !== undefined && request.minPower !== undefined && request.maxPower === request.minPower) {
70
+ target = request.minPower;
71
+ }
72
+ if (target !== undefined) {
73
+ delete request.refresh;
74
+ newRequest.targetPower = Number(target);
75
+ }
76
+ }
77
+ checkSlope(request) {
78
+ if (request.slope !== undefined) {
79
+ this.data.slope = request.slope;
80
+ delete request.slope;
81
+ }
82
+ }
83
+ checkEmptyRequest(newRequest) {
84
+ if (Object.keys(newRequest).length === 0) {
85
+ if (this.prevRequest) {
86
+ newRequest.targetPower = this.prevRequest.targetPower;
87
+ newRequest.refresh = true;
88
+ }
89
+ }
90
+ }
91
+ checkMinPower(request, newRequest) {
92
+ if (request.minPower !== undefined) {
93
+ const target = newRequest.targetPower !== undefined ? newRequest.targetPower : this.prevRequest.targetPower;
94
+ if (target && target < request.minPower) {
95
+ newRequest.targetPower = request.minPower;
96
+ }
97
+ else {
98
+ if (this.prevRequest)
99
+ newRequest.targetPower = this.prevRequest.targetPower;
100
+ }
101
+ newRequest.minPower = request.minPower;
102
+ }
103
+ }
104
+ checkMaxPower(request, newRequest) {
105
+ if (request.maxPower !== undefined) {
106
+ const target = newRequest.targetPower !== undefined ? newRequest.targetPower : this.prevRequest.targetPower;
107
+ if (target && target > request.maxPower) {
108
+ newRequest.targetPower = request.maxPower;
109
+ }
110
+ else {
111
+ if (this.prevRequest)
112
+ newRequest.targetPower = this.prevRequest.targetPower;
113
+ }
114
+ newRequest.maxPower = request.maxPower;
115
+ }
116
+ }
34
117
  calculateSpeedAndDistance(power, slope, m, t, props = {}) {
35
- const prevData = this.data || {};
118
+ const prevData = this.getData();
36
119
  const vPrev = (prevData.speed || 0) / 3.6;
37
120
  const EkinPrev = 1 / 2 * m * vPrev * vPrev;
38
121
  let powerToMaintainSpeed = calculations_1.default.calculatePower(m, vPrev, slope, props);
@@ -57,8 +140,57 @@ class PowerBasedCyclingModeBase extends cycling_mode_1.CyclingModeBase {
57
140
  return { speed, distance };
58
141
  }
59
142
  }
143
+ updateSpeedAndDistance(power, slope, bikeType, data, prevData) {
144
+ const distanceInternal = prevData.distanceInternal || 0;
145
+ const m = this.getWeight();
146
+ const t = this.getTimeSinceLastUpdate();
147
+ const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t, { bikeType });
148
+ if (power === 0 && speed < MIN_SPEED) {
149
+ data.speed = prevData.speed < 1 ? 0 : prevData.speed - 1;
150
+ data.distanceInternal = distanceInternal || 0 + data.speed / 3.6 * t;
151
+ }
152
+ else {
153
+ data.speed = speed;
154
+ data.distanceInternal = distanceInternal + distance;
155
+ }
156
+ return t;
157
+ }
158
+ getCalcBasics(bikeData) {
159
+ var _a;
160
+ const prevData = JSON.parse(JSON.stringify(this.getData()));
161
+ const data = Object.assign({}, this.getData());
162
+ const prevRequest = this.prevRequest || {};
163
+ const bikeType = (_a = this.getSetting('bikeType')) === null || _a === void 0 ? void 0 : _a.toLowerCase();
164
+ const slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
165
+ this.copyBikeData(data, bikeData);
166
+ return { data, prevData, prevRequest, bikeType, slope };
167
+ }
168
+ copyBikeData(data, bikeData) {
169
+ var _a;
170
+ const keys = Object.keys(bikeData);
171
+ keys.forEach(key => {
172
+ if (bikeData[key] === null)
173
+ delete data[key];
174
+ if (key === 'slope')
175
+ return;
176
+ else if (bikeData[key] !== undefined)
177
+ data[key] = bikeData[key];
178
+ });
179
+ if (data.distanceInternal === undefined)
180
+ data.distanceInternal = 0;
181
+ if (data.time === undefined)
182
+ data.time = 0;
183
+ if (data.slope === undefined)
184
+ data.slope = 0;
185
+ if (bikeData.isPedalling === undefined)
186
+ data.isPedalling = data.pedalRpm > 0;
187
+ if (((_a = this.prevRequest) === null || _a === void 0 ? void 0 : _a.slope) !== undefined) {
188
+ data.slope = this.prevRequest.slope;
189
+ }
190
+ return data;
191
+ }
60
192
  calculatePowerAndDistance(speed, slope, m, t, props = {}) {
61
- const prevData = this.data || {};
193
+ const prevData = this.getData();
62
194
  const vPrev = (prevData.speed || 0) / 3.6;
63
195
  const EkinPrev = 1 / 2 * m * vPrev * vPrev;
64
196
  const vTarget = (speed || 0) / 3.6;
@@ -71,5 +203,50 @@ class PowerBasedCyclingModeBase extends cycling_mode_1.CyclingModeBase {
71
203
  this.data.power = power;
72
204
  return { power, distance };
73
205
  }
206
+ getBikeInitRequest() {
207
+ return {};
208
+ }
209
+ sendBikeUpdate(incoming) {
210
+ if (this.logger)
211
+ this.logger.logEvent({ message: "processing update request", request: incoming, prev: this.prevRequest, data: this.getData() });
212
+ let newRequest = {};
213
+ const request = Object.assign({}, incoming);
214
+ try {
215
+ const req = this.checkForResetOrEmpty(request);
216
+ if (req)
217
+ return req;
218
+ this.checkSlope(request);
219
+ this.checkForTempPowerAdjustments(request);
220
+ this.checkTargetPowerSet(request, newRequest);
221
+ this.checkRefresh(request, newRequest);
222
+ this.checkMaxPower(request, newRequest);
223
+ this.checkMinPower(request, newRequest);
224
+ this.checkEmptyRequest(newRequest);
225
+ this.prevRequest = JSON.parse(JSON.stringify(newRequest));
226
+ }
227
+ catch (err) {
228
+ if (this.logger)
229
+ this.logger.logEvent({ message: "error", fn: 'sendBikeUpdate()', error: err.message, stack: err.stack });
230
+ }
231
+ return newRequest;
232
+ }
233
+ updateData(bikeData, log = true) {
234
+ try {
235
+ const { data, prevData, slope, bikeType } = this.getCalcBasics(bikeData);
236
+ const t = this.updateSpeedAndDistance(data.power, slope, bikeType, data, prevData);
237
+ data.time = (data.speed > 0) ? data.time + t : data.time;
238
+ if (log && this.logger)
239
+ this.logger.logEvent({ message: "updateData result", mode: this.getName(), data, bikeData });
240
+ this.data = data;
241
+ this.prevUpdateTS = Date.now();
242
+ return data;
243
+ }
244
+ catch (err) {
245
+ if (this.logger)
246
+ this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
247
+ return this.getData();
248
+ }
249
+ }
74
250
  }
251
+ PowerBasedCyclingModeBase.config = { name: '', description: '', properties: [] };
75
252
  exports.default = PowerBasedCyclingModeBase;