incyclist-devices 1.1.0 → 1.3.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/lib/CyclingMode.d.ts +72 -0
  3. package/lib/CyclingMode.js +66 -0
  4. package/lib/Device.d.ts +48 -10
  5. package/lib/Device.js +9 -8
  6. package/lib/DeviceProtocol.d.ts +40 -12
  7. package/lib/DeviceProtocol.js +16 -16
  8. package/lib/DeviceRegistry.d.ts +4 -4
  9. package/lib/DeviceRegistry.js +10 -10
  10. package/lib/DeviceSupport.d.ts +4 -3
  11. package/lib/DeviceSupport.js +32 -8
  12. package/lib/ant/AntAdapter.d.ts +11 -2
  13. package/lib/ant/AntAdapter.js +30 -1
  14. package/lib/ant/AntScanner.d.ts +17 -6
  15. package/lib/ant/AntScanner.js +406 -135
  16. package/lib/ant/antfe/AntFEAdapter.d.ts +12 -8
  17. package/lib/ant/antfe/AntFEAdapter.js +404 -182
  18. package/lib/ant/anthrm/AntHrmAdapter.d.ts +4 -2
  19. package/lib/ant/anthrm/AntHrmAdapter.js +72 -25
  20. package/lib/ant/utils.js +2 -1
  21. package/lib/calculations.d.ts +12 -13
  22. package/lib/calculations.js +88 -125
  23. package/lib/daum/DaumAdapter.d.ts +29 -6
  24. package/lib/daum/DaumAdapter.js +219 -96
  25. package/lib/daum/ERGCyclingMode.d.ts +28 -0
  26. package/lib/daum/ERGCyclingMode.js +207 -0
  27. package/lib/daum/PowerMeterCyclingMode.d.ts +18 -0
  28. package/lib/daum/PowerMeterCyclingMode.js +79 -0
  29. package/lib/daum/SmartTrainerCyclingMode.d.ts +41 -0
  30. package/lib/daum/SmartTrainerCyclingMode.js +344 -0
  31. package/lib/daum/classic/DaumClassicAdapter.d.ts +3 -1
  32. package/lib/daum/classic/DaumClassicAdapter.js +46 -32
  33. package/lib/daum/classic/DaumClassicCyclingMode.d.ts +13 -0
  34. package/lib/daum/classic/DaumClassicCyclingMode.js +98 -0
  35. package/lib/daum/classic/DaumClassicProtocol.d.ts +5 -3
  36. package/lib/daum/classic/DaumClassicProtocol.js +39 -6
  37. package/lib/daum/classic/ERGCyclingMode.d.ts +23 -0
  38. package/lib/daum/classic/ERGCyclingMode.js +171 -0
  39. package/lib/daum/classic/bike.d.ts +41 -37
  40. package/lib/daum/classic/bike.js +86 -53
  41. package/lib/daum/classic/utils.d.ts +3 -3
  42. package/lib/daum/classic/utils.js +16 -10
  43. package/lib/daum/indoorbike.d.ts +2 -1
  44. package/lib/daum/indoorbike.js +23 -21
  45. package/lib/daum/premium/DaumPremiumAdapter.d.ts +2 -2
  46. package/lib/daum/premium/DaumPremiumAdapter.js +30 -20
  47. package/lib/daum/premium/DaumPremiumProtocol.d.ts +11 -2
  48. package/lib/daum/premium/DaumPremiumProtocol.js +49 -8
  49. package/lib/daum/premium/bike.d.ts +63 -52
  50. package/lib/daum/premium/bike.js +258 -207
  51. package/lib/daum/premium/tcpserial.d.ts +18 -14
  52. package/lib/daum/premium/tcpserial.js +50 -20
  53. package/lib/daum/premium/utils.d.ts +2 -2
  54. package/lib/simulator/Simulator.d.ts +13 -7
  55. package/lib/simulator/Simulator.js +62 -21
  56. package/lib/utils.d.ts +3 -1
  57. package/lib/utils.js +39 -18
  58. package/package.json +12 -11
  59. package/lib/ant/AntScanner.unit.tests.d.ts +0 -1
  60. package/lib/ant/AntScanner.unit.tests.js +0 -25
  61. package/lib/ant/antfe/AntFEProcessor.d.ts +0 -40
  62. package/lib/ant/antfe/AntFEProcessor.js +0 -238
  63. package/lib/ant/antfe/AntHrmProtocol.d.ts +0 -9
  64. package/lib/ant/antfe/AntHrmProtocol.js +0 -30
  65. package/lib/ant/antfe/bike.d.ts +0 -47
  66. package/lib/ant/antfe/bike.js +0 -602
  67. package/lib/ant/anthrm/anthrm.d.ts +0 -33
  68. package/lib/ant/anthrm/anthrm.js +0 -523
  69. package/lib/simulator/Simulator.unit.tests.d.ts +0 -1
  70. package/lib/simulator/Simulator.unit.tests.js +0 -79
@@ -8,22 +8,83 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const Device_1 = require("../Device");
13
- function floatVal(d) {
14
- if (d === undefined)
15
- return d;
16
- return parseFloat(d);
17
- }
18
- function intVal(d) {
19
- if (d === undefined)
20
- return d;
21
- return parseInt(d);
22
- }
15
+ const Device_1 = __importDefault(require("../Device"));
16
+ const ERGCyclingMode_1 = __importDefault(require("./ERGCyclingMode"));
17
+ const SmartTrainerCyclingMode_1 = __importDefault(require("./SmartTrainerCyclingMode"));
18
+ const PowerMeterCyclingMode_1 = __importDefault(require("./PowerMeterCyclingMode"));
19
+ const utils_1 = require("../utils");
20
+ const DEFAULT_BIKE_WEIGHT = 10;
21
+ const DEFAULT_USER_WEIGHT = 75;
23
22
  class DaumAdapterBase extends Device_1.default {
24
23
  constructor(props, bike) {
25
24
  super(props);
25
+ this.requests = [];
26
+ this.adapterTime = 0;
27
+ this.requestBusy = false;
28
+ this.updateBusy = false;
26
29
  this.bike = bike;
30
+ this.stopped = false;
31
+ this.paused = false;
32
+ this.data = {};
33
+ const options = props || {};
34
+ this.cyclingMode = options.cyclingMode;
35
+ this.setUserSettings(options.userSettings);
36
+ this.setBikeSettings(options.bikeSettings);
37
+ }
38
+ setCyclingMode(mode, settings) {
39
+ let selectedMode;
40
+ if (typeof mode === 'string') {
41
+ const supported = this.getSupportedCyclingModes();
42
+ const CyclingModeClass = supported.find(M => { const m = new M(this); return m.getName() === mode; });
43
+ if (CyclingModeClass) {
44
+ this.cyclingMode = new CyclingModeClass(this, settings);
45
+ return;
46
+ }
47
+ selectedMode = this.getDefaultCyclingMode();
48
+ }
49
+ else {
50
+ selectedMode = mode;
51
+ }
52
+ this.cyclingMode = selectedMode;
53
+ this.cyclingMode.setSettings(settings);
54
+ }
55
+ getSupportedCyclingModes() {
56
+ return [ERGCyclingMode_1.default, SmartTrainerCyclingMode_1.default, PowerMeterCyclingMode_1.default];
57
+ }
58
+ getCyclingMode() {
59
+ if (!this.cyclingMode)
60
+ this.setCyclingMode(this.getDefaultCyclingMode());
61
+ return this.cyclingMode;
62
+ }
63
+ getDefaultCyclingMode() {
64
+ return new ERGCyclingMode_1.default(this);
65
+ }
66
+ setUserSettings(userSettings) {
67
+ this.userSettings = userSettings || {};
68
+ if (this.bike) {
69
+ if (!this.bike.settings)
70
+ this.bike.settings = { user: {} };
71
+ if (!this.bike.settings.user)
72
+ this.bike.settings.user = {};
73
+ this.bike.settings.user.weight = this.userSettings.weight || DEFAULT_USER_WEIGHT;
74
+ }
75
+ }
76
+ setBikeSettings(bikeSettings) {
77
+ this.bikeSettings = bikeSettings || {};
78
+ if (this.bike) {
79
+ if (!this.bike.settings)
80
+ this.bike.settings = {};
81
+ this.bike.settings.weight = this.userSettings.weight || DEFAULT_BIKE_WEIGHT;
82
+ }
83
+ }
84
+ getWeight() {
85
+ const userWeight = this.userSettings.weight || DEFAULT_USER_WEIGHT;
86
+ const bikeWeight = this.bikeSettings.weight || DEFAULT_BIKE_WEIGHT;
87
+ return bikeWeight + userWeight;
27
88
  }
28
89
  getCurrentBikeData() {
29
90
  throw new Error('Method not implemented.');
@@ -46,12 +107,30 @@ class DaumAdapterBase extends Device_1.default {
46
107
  setIgnoreBike(ignore) {
47
108
  this.ignoreBike = ignore;
48
109
  }
110
+ isStopped() {
111
+ return this.stopped;
112
+ }
49
113
  initData() {
50
114
  this.distanceInternal = undefined;
51
- this.paused = undefined;
52
- this.data = {};
115
+ this.paused = false;
116
+ this.stopped = false;
117
+ this.data = {
118
+ time: 0,
119
+ slope: 0,
120
+ distance: 0,
121
+ speed: 0,
122
+ isPedalling: false,
123
+ power: 0,
124
+ distanceInternal: 0
125
+ };
53
126
  this.currentRequest = {};
54
127
  this.requests = [];
128
+ const name = this.getCyclingMode().getName();
129
+ const settings = this.getCyclingMode().getSettings();
130
+ this.setCyclingMode(name, settings);
131
+ }
132
+ start(props) {
133
+ throw new Error('Method not implemented.');
55
134
  }
56
135
  startUpdatePull() {
57
136
  if (this.iv)
@@ -59,7 +138,11 @@ class DaumAdapterBase extends Device_1.default {
59
138
  if (this.ignoreBike && this.ignoreHrm && this.ignorePower)
60
139
  return;
61
140
  this.iv = setInterval(() => {
62
- this.update();
141
+ this.bikeSync();
142
+ }, 1000);
143
+ this.iv = setInterval(() => {
144
+ this.sendData();
145
+ this.refreshRequests();
63
146
  }, 1000);
64
147
  }
65
148
  connect() {
@@ -74,43 +157,9 @@ class DaumAdapterBase extends Device_1.default {
74
157
  return;
75
158
  this.logger.logEvent(event);
76
159
  }
77
- sendBikeUpdate(request) {
78
- return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
79
- if (request.slope) {
80
- this.data.slope = request.slope;
81
- }
82
- try {
83
- if (this.bike.processor !== undefined) {
84
- this.bike.processor.setValues(request);
85
- }
86
- this.logEvent({ message: "sendBikeUpdate():sending", request });
87
- if (request.slope !== undefined) {
88
- yield this.bike.setSlope(request.slope);
89
- }
90
- if (request.targetPower !== undefined) {
91
- yield this.bike.setPower(request.targetPower);
92
- }
93
- if (request.minPower !== undefined && request.maxPower !== undefined && request.minPower === request.maxPower) {
94
- yield this.bike.setPower(request.minPower);
95
- }
96
- if (request.maxPower !== undefined) {
97
- }
98
- if (request.minPower !== undefined) {
99
- }
100
- if (request.maxHrm !== undefined) {
101
- }
102
- if (request.minHrm !== undefined) {
103
- }
104
- }
105
- catch (err) {
106
- this.logEvent({ message: 'sendBikeUpdate error', error: err.message });
107
- resolve(undefined);
108
- }
109
- resolve(request);
110
- }));
111
- }
112
160
  stop() {
113
161
  this.logEvent({ message: 'stop request' });
162
+ this.stopped = true;
114
163
  return new Promise((resolve, reject) => {
115
164
  try {
116
165
  if (this.iv) {
@@ -128,63 +177,86 @@ class DaumAdapterBase extends Device_1.default {
128
177
  });
129
178
  }
130
179
  pause() {
180
+ this.logEvent({ message: 'pause' });
131
181
  return new Promise(resolve => {
132
182
  this.paused = true;
133
183
  resolve(true);
134
184
  });
135
185
  }
136
186
  resume() {
187
+ this.logEvent({ message: 'resume' });
137
188
  return new Promise(resolve => {
138
189
  this.paused = false;
139
190
  resolve(true);
140
191
  });
141
192
  }
142
- sendUpdate(data) {
193
+ sendUpdate(request) {
143
194
  return __awaiter(this, void 0, void 0, function* () {
144
195
  if (this.paused)
145
196
  return;
146
- this.logEvent({ message: 'sendUpdate', data });
147
- this.requests.push(data);
197
+ this.logEvent({ message: 'sendUpdate', request, waiting: this.requests.length });
198
+ return yield this.processClientRequest(request);
148
199
  });
149
200
  }
201
+ sendData() {
202
+ if (this.onDataFn)
203
+ this.onDataFn(this.data);
204
+ }
150
205
  update() {
151
206
  return __awaiter(this, void 0, void 0, function* () {
152
- if (this.paused)
153
- return;
154
- if (!this.ignoreBike) {
155
- if (this.requests.length === 0) {
156
- this.sendUpdate({ refresh: true });
157
- }
158
- if (this.requests.length > 0) {
159
- const processing = [...this.requests];
160
- processing.forEach((request) => __awaiter(this, void 0, void 0, function* () {
161
- try {
162
- this.logEvent({ message: 'bike update request', request });
163
- yield this.sendBikeUpdate(request);
164
- this.requests.shift();
165
- }
166
- catch (err) {
167
- this.logEvent({ message: 'bike update error', error: err.message, stack: err.stack, request });
168
- }
169
- }));
170
- }
171
- }
207
+ this.updateBusy = true;
172
208
  this.getCurrentBikeData()
173
209
  .then(bikeData => {
174
- let data = JSON.parse(JSON.stringify(this.data));
175
- data = this.updateData(data, bikeData);
176
- this.data = this.transformData(data);
177
- if (this.onDataFn) {
178
- console.log('~~~updateBike:', this.ignoreHrm, this.data);
179
- this.onDataFn(this.data);
180
- }
210
+ this.updateData(this.data, bikeData);
211
+ this.transformData();
212
+ this.updateBusy = false;
181
213
  })
182
214
  .catch(err => {
183
215
  this.logEvent({ message: 'bike update error', error: err.message, stack: err.stack });
216
+ this.updateBusy = false;
184
217
  });
185
218
  });
186
219
  }
187
- updateData(data, bikeData) {
220
+ sendRequests() {
221
+ return __awaiter(this, void 0, void 0, function* () {
222
+ if (this.requests.length > 0) {
223
+ const processing = [...this.requests];
224
+ const cnt = processing.length;
225
+ processing.forEach((request, idx) => __awaiter(this, void 0, void 0, function* () {
226
+ if (cnt > 1 && idx < cnt - 1) {
227
+ this.logEvent({ message: 'ignoring bike update request', request });
228
+ this.requests.shift();
229
+ return;
230
+ }
231
+ }));
232
+ const request = processing[0];
233
+ try {
234
+ yield this.sendRequest(request);
235
+ this.requests.shift();
236
+ }
237
+ catch (err) {
238
+ this.logEvent({ message: 'bike update error', error: err.message, stack: err.stack, request });
239
+ }
240
+ }
241
+ });
242
+ }
243
+ bikeSync() {
244
+ return __awaiter(this, void 0, void 0, function* () {
245
+ if (this.paused) {
246
+ return;
247
+ }
248
+ if (this.updateBusy || this.requestBusy) {
249
+ return;
250
+ }
251
+ this.logEvent({ message: 'bikeSync' });
252
+ if (!this.ignoreBike) {
253
+ yield this.sendRequests();
254
+ }
255
+ yield this.update();
256
+ });
257
+ }
258
+ updateData(prev, bikeData) {
259
+ let data = {};
188
260
  data.isPedalling = bikeData.cadence > 0;
189
261
  data.power = bikeData.power;
190
262
  data.pedalRpm = bikeData.cadence;
@@ -192,28 +264,31 @@ class DaumAdapterBase extends Device_1.default {
192
264
  data.heartrate = bikeData.heartrate;
193
265
  data.distance = bikeData.distance / 100;
194
266
  data.distanceInternal = bikeData.distance;
195
- data.time = bikeData.time;
196
267
  data.gear = bikeData.gear;
197
- if (this.bike.processor !== undefined) {
198
- data = this.bike.processor.getValues(data);
268
+ if (this.tsPrevData && data.isPedalling) {
269
+ this.adapterTime = Date.now() - this.tsPrevData;
199
270
  }
200
- return data;
271
+ this.tsPrevData = Date.now();
272
+ data.time = Math.round(this.adapterTime || 0);
273
+ if (bikeData.slope)
274
+ data.slope = bikeData.slope;
275
+ this.data = this.getCyclingMode().updateData(data);
201
276
  }
202
- transformData(bikeData) {
203
- if (bikeData === undefined)
277
+ transformData() {
278
+ if (this.data === undefined)
204
279
  return;
205
280
  let distance = 0;
206
- if (this.distanceInternal !== undefined && bikeData.distanceInternal !== undefined) {
207
- distance = intVal(bikeData.distanceInternal - this.distanceInternal);
281
+ if (this.distanceInternal !== undefined && this.data.distanceInternal !== undefined) {
282
+ distance = (0, utils_1.intVal)(this.data.distanceInternal - this.distanceInternal);
208
283
  }
209
- if (bikeData.distanceInternal !== undefined)
210
- this.distanceInternal = bikeData.distanceInternal;
284
+ if (this.data.distanceInternal !== undefined)
285
+ this.distanceInternal = this.data.distanceInternal;
211
286
  let data = {
212
- speed: floatVal(bikeData.speed),
213
- slope: floatVal(bikeData.slope),
214
- power: intVal(bikeData.power),
215
- cadence: intVal(bikeData.pedalRpm),
216
- heartrate: intVal(bikeData.heartrate),
287
+ speed: (0, utils_1.floatVal)(this.data.speed),
288
+ slope: (0, utils_1.floatVal)(this.data.slope),
289
+ power: (0, utils_1.intVal)(this.data.power),
290
+ cadence: (0, utils_1.intVal)(this.data.pedalRpm),
291
+ heartrate: (0, utils_1.intVal)(this.data.heartrate),
217
292
  distance,
218
293
  timestamp: Date.now()
219
294
  };
@@ -226,7 +301,55 @@ class DaumAdapterBase extends Device_1.default {
226
301
  if (this.ignoreBike) {
227
302
  data = { heartrate: data.heartrate };
228
303
  }
229
- return data;
304
+ this.data = data;
305
+ }
306
+ sendRequest(request) {
307
+ return __awaiter(this, void 0, void 0, function* () {
308
+ this.requestBusy = true;
309
+ try {
310
+ this.logEvent({ message: 'sendRequest', request });
311
+ const bike = this.getBike();
312
+ const isReset = (!request || request.reset || Object.keys(request).length === 0);
313
+ if (isReset) {
314
+ this.requestBusy = false;
315
+ return {};
316
+ }
317
+ if (request.slope !== undefined) {
318
+ yield bike.setSlope(request.slope);
319
+ }
320
+ if (request.targetPower !== undefined) {
321
+ yield bike.setPower(request.targetPower);
322
+ }
323
+ this.requestBusy = false;
324
+ return request;
325
+ }
326
+ catch (err) {
327
+ this.requestBusy = false;
328
+ this.logEvent({ message: 'error', fn: 'sendRequest()', error: err.message || err });
329
+ return;
330
+ }
331
+ });
332
+ }
333
+ refreshRequests() {
334
+ if (!this.data.isPedalling || this.data.pedalRpm === 0)
335
+ return;
336
+ let bikeRequest = this.getCyclingMode().sendBikeUpdate({ refresh: true });
337
+ const prev = this.requests[this.requests.length - 1];
338
+ if (bikeRequest.targetPower !== undefined && bikeRequest.targetPower !== prev.targetPower) {
339
+ this.logEvent({ message: 'add request', request: bikeRequest });
340
+ this.requests.push(bikeRequest);
341
+ }
342
+ }
343
+ processClientRequest(request) {
344
+ if (request.slope !== undefined) {
345
+ this.data.slope = request.slope;
346
+ }
347
+ return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
348
+ let bikeRequest = this.getCyclingMode().sendBikeUpdate(request);
349
+ this.logEvent({ message: 'add request', request: bikeRequest });
350
+ this.requests.push(bikeRequest);
351
+ resolve(bikeRequest);
352
+ }));
230
353
  }
231
354
  }
232
355
  exports.default = DaumAdapterBase;
@@ -0,0 +1,28 @@
1
+ import { EventLogger } from "gd-eventlog";
2
+ import CyclingMode, { CyclingModeBase, CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
3
+ import DaumAdapter from "./DaumAdapter";
4
+ export declare type ERGEvent = {
5
+ rpmUpdated?: boolean;
6
+ gearUpdated?: boolean;
7
+ starting?: boolean;
8
+ tsStart?: number;
9
+ };
10
+ export default class ERGCyclingMode 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: DaumAdapter, 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): any;
27
+ calculateTargetPower(request: any, updateMode?: boolean): any;
28
+ }
@@ -0,0 +1,207 @@
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: "ERG",
11
+ description: "Calculates speed based on power and slope. Power is either set by workout or calculated based on gear and cadence",
12
+ properties: [
13
+ { key: 'bikeType', name: 'Bike Type', description: '', type: CyclingMode_1.CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' },
14
+ { key: 'startPower', name: 'Starting Power', description: 'Initial power in Watts at start of raining', type: CyclingMode_1.CyclingModeProperyType.Integer, default: 50, min: 25, max: 800 },
15
+ ]
16
+ };
17
+ class ERGCyclingMode extends CyclingMode_1.CyclingModeBase {
18
+ constructor(adapter, props) {
19
+ super(adapter, props);
20
+ this.prevUpdateTS = 0;
21
+ this.hasBikeUpdate = false;
22
+ this.event = {};
23
+ this.logger = adapter.logger || new gd_eventlog_1.EventLogger('ERGMode');
24
+ this.data = {};
25
+ this.logger.logEvent({ message: 'constructor', props });
26
+ }
27
+ getName() {
28
+ return config.name;
29
+ }
30
+ getDescription() {
31
+ return config.description;
32
+ }
33
+ getProperties() {
34
+ return config.properties;
35
+ }
36
+ getProperty(name) {
37
+ return config.properties.find(p => p.name === name);
38
+ }
39
+ getBikeInitRequest() {
40
+ const startPower = this.getSetting('startPower');
41
+ return { targetPower: startPower };
42
+ }
43
+ sendBikeUpdate(request) {
44
+ const getData = () => {
45
+ if (!this.data)
46
+ return {};
47
+ const { gear, pedalRpm, slope, power, speed } = this.data;
48
+ return { gear, pedalRpm, slope, power, speed };
49
+ };
50
+ this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData(), event: this.event });
51
+ let newRequest = {};
52
+ try {
53
+ if (!request || request.reset || Object.keys(request).length === 0) {
54
+ this.prevRequest = {};
55
+ return request || {};
56
+ }
57
+ const prevData = this.data || {};
58
+ if (request.targetPower !== undefined) {
59
+ delete request.slope;
60
+ delete request.refresh;
61
+ }
62
+ if (this.event.starting && request.targetPower === undefined) {
63
+ const startPower = this.getSetting('startPower');
64
+ if (this.event.tsStart && Date.now() - this.event.tsStart > 5000) {
65
+ delete this.event.starting;
66
+ delete this.event.tsStart;
67
+ }
68
+ const target = this.calculateTargetPower(request);
69
+ if (target <= startPower && (!request.minPower || target >= request.minPower)) {
70
+ return {};
71
+ }
72
+ else {
73
+ delete this.event.starting;
74
+ delete this.event.tsStart;
75
+ }
76
+ }
77
+ if (request.refresh) {
78
+ delete request.refresh;
79
+ if (this.prevRequest !== undefined && !this.event.gearUpdated && !this.event.rpmUpdated) {
80
+ newRequest.targetPower = this.prevRequest.targetPower;
81
+ }
82
+ else {
83
+ newRequest.targetPower = this.calculateTargetPower(request);
84
+ }
85
+ if (this.prevRequest !== undefined && Object.keys(this.prevRequest).length > 0) {
86
+ request = Object.assign({}, this.prevRequest);
87
+ }
88
+ else {
89
+ newRequest.targetPower = this.calculateTargetPower(request);
90
+ }
91
+ }
92
+ if (request.slope !== undefined) {
93
+ if (!this.data)
94
+ this.data = {};
95
+ this.data.slope = request.slope;
96
+ }
97
+ if (request.maxPower !== undefined && request.minPower !== undefined && request.maxPower === request.minPower) {
98
+ request.targetPower = request.maxPower;
99
+ }
100
+ if (request.targetPower === undefined) {
101
+ newRequest.targetPower = this.calculateTargetPower(request);
102
+ }
103
+ else {
104
+ newRequest.targetPower = request.targetPower;
105
+ }
106
+ delete request.slope;
107
+ if (request.maxPower !== undefined) {
108
+ if (newRequest.targetPower !== undefined && newRequest.targetPower > request.maxPower) {
109
+ newRequest.targetPower = request.maxPower;
110
+ }
111
+ newRequest.maxPower = request.maxPower;
112
+ }
113
+ if (request.minPower !== undefined) {
114
+ if (newRequest.targetPower !== undefined && newRequest.targetPower < request.minPower) {
115
+ newRequest.targetPower = request.minPower;
116
+ }
117
+ newRequest.minPower = request.minPower;
118
+ }
119
+ if (newRequest.targetPower !== undefined && prevData.power !== undefined && newRequest.targetPower === prevData.power) {
120
+ delete newRequest.targetPower;
121
+ }
122
+ this.prevRequest = JSON.parse(JSON.stringify(request));
123
+ }
124
+ catch (err) {
125
+ this.logger.logEvent({ message: "error", fn: 'sendBikeUpdate()', error: err.message || err, stack: err.stack });
126
+ }
127
+ return newRequest;
128
+ }
129
+ updateData(bikeData) {
130
+ const prevData = JSON.parse(JSON.stringify(this.data || {}));
131
+ const prevSpeed = prevData.speed;
132
+ const prevRequest = this.prevRequest || {};
133
+ const data = this.data || {};
134
+ const bikeType = this.getSetting('bikeType');
135
+ delete this.event.gearUpdated;
136
+ delete this.event.rpmUpdated;
137
+ if (prevData === {} || prevData.speed === undefined || prevData.speed === 0) {
138
+ this.event.starting = true;
139
+ this.event.tsStart = Date.now();
140
+ }
141
+ try {
142
+ let rpm = bikeData.pedalRpm || 0;
143
+ let gear = bikeData.gear || 0;
144
+ let power = bikeData.power || 0;
145
+ let slope = (prevData.slope !== undefined ? prevData.slope : prevRequest.slope || 0);
146
+ let speed;
147
+ let m = this.adapter.getWeight();
148
+ let distanceInternal = prevData.distanceInternal || 0;
149
+ let distance = Math.round(distanceInternal / 100);
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;
154
+ power = 0;
155
+ }
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
+ distance = Math.round(distanceInternal / 100);
161
+ }
162
+ data.speed = parseFloat(speed.toFixed(1));
163
+ data.power = Math.round(power);
164
+ data.distanceInternal = Math.round(distanceInternal);
165
+ data.distance = distance;
166
+ data.slope = slope;
167
+ data.pedalRpm = rpm;
168
+ data.gear = gear;
169
+ if (data.time)
170
+ data.time += duration;
171
+ else
172
+ data.time = 0;
173
+ data.heartrate = bikeData.heartrate;
174
+ data.isPedalling = bikeData.isPedalling;
175
+ if (gear !== prevData.gear) {
176
+ this.event.gearUpdated = true;
177
+ }
178
+ if (rpm && rpm !== prevData.pedalRpm) {
179
+ this.event.rpmUpdated = true;
180
+ }
181
+ this.prevUpdateTS = ts;
182
+ }
183
+ catch (err) {
184
+ this.logger.logEvent({ message: 'error', fn: 'updateData()', error: err.message || err });
185
+ }
186
+ this.logger.logEvent({ message: "updateData result", data, bikeData, prevRequest, prevSpeed });
187
+ this.data = data;
188
+ return data;
189
+ }
190
+ calculateTargetPower(request, updateMode = true) {
191
+ const bikeType = this.getSetting('bikeType').toLowerCase();
192
+ const defaultPower = this.getSetting('startPower');
193
+ let m = this.adapter.getWeight();
194
+ const prevData = this.data || {};
195
+ let target;
196
+ if (prevData.pedalRpm && prevData.gear && (!updateMode || prevData.pedalRpm !== 0)) {
197
+ const speed = calculations_1.default.calculateSpeedDaum(prevData.gear, prevData.pedalRpm, bikeType);
198
+ var power = calculations_1.default.calculatePower(m, speed / 3.6, 0, { bikeType });
199
+ target = Math.round(power);
200
+ }
201
+ else {
202
+ target = Math.round(request.targetPower || defaultPower);
203
+ }
204
+ return target;
205
+ }
206
+ }
207
+ exports.default = ERGCyclingMode;
@@ -0,0 +1,18 @@
1
+ import { EventLogger } from "gd-eventlog";
2
+ import CyclingMode, { CyclingModeProperty, IncyclistBikeData, Settings, UpdateRequest, CyclingModeBase } from "../CyclingMode";
3
+ import DaumAdapter from "./DaumAdapter";
4
+ export default class PowerMeterCyclingMode extends CyclingModeBase implements CyclingMode {
5
+ logger: EventLogger;
6
+ data: IncyclistBikeData;
7
+ prevRequest: UpdateRequest;
8
+ prevUpdateTS: number;
9
+ hasBikeUpdate: boolean;
10
+ constructor(adapter: DaumAdapter, props?: Settings);
11
+ getName(): string;
12
+ getDescription(): string;
13
+ getProperties(): CyclingModeProperty[];
14
+ getProperty(name: string): CyclingModeProperty;
15
+ getBikeInitRequest(): UpdateRequest;
16
+ sendBikeUpdate(request: UpdateRequest): UpdateRequest;
17
+ updateData(data: IncyclistBikeData): IncyclistBikeData;
18
+ }