incyclist-devices 1.4.45 → 1.4.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ble/fm.js CHANGED
@@ -18,6 +18,7 @@ const ble_interface_1 = __importDefault(require("./ble-interface"));
18
18
  const Device_1 = __importDefault(require("../Device"));
19
19
  const gd_eventlog_1 = require("gd-eventlog");
20
20
  const power_meter_1 = __importDefault(require("../modes/power-meter"));
21
+ const FTMS_CP = '2ad9';
21
22
  const bit = (nr) => (1 << nr);
22
23
  const IndoorBikeDataFlag = {
23
24
  MoreData: bit(0),
@@ -76,6 +77,8 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
76
77
  constructor(props) {
77
78
  super(props);
78
79
  this.features = undefined;
80
+ this.hasControl = false;
81
+ this.isCPSubscribed = false;
79
82
  this.data = {};
80
83
  }
81
84
  init() {
@@ -84,14 +87,20 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
84
87
  });
85
88
  return __awaiter(this, void 0, void 0, function* () {
86
89
  try {
90
+ this.logEvent({ message: 'get device info' });
87
91
  yield _super.init.call(this);
88
92
  yield this.getFitnessMachineFeatures();
93
+ this.logEvent({ message: 'device info', deviceInfo: this.deviceInfo, features: this.features });
89
94
  }
90
95
  catch (err) {
91
96
  return Promise.resolve(false);
92
97
  }
93
98
  });
94
99
  }
100
+ onDisconnect() {
101
+ super.onDisconnect();
102
+ this.hasControl = false;
103
+ }
95
104
  getProfile() {
96
105
  return 'Smart Trainer';
97
106
  }
@@ -112,7 +121,7 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
112
121
  return true;
113
122
  }
114
123
  isHrm() {
115
- return this.hasService('180d');
124
+ return this.hasService('180d') || (this.features && (this.features.fitnessMachine & FitnessMachineFeatureFlag.HeartRateMeasurementSupported) !== 0);
116
125
  }
117
126
  parseHrm(_data) {
118
127
  const data = Buffer.from(_data);
@@ -150,19 +159,22 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
150
159
  offset += 2;
151
160
  }
152
161
  if (flags & IndoorBikeDataFlag.TotalDistancePresent) {
153
- this.data.totalDistance = data.readUInt16LE(offset);
162
+ const dvLow = data.readUInt8(offset);
163
+ offset += 1;
164
+ const dvHigh = data.readUInt16LE(offset);
154
165
  offset += 2;
166
+ this.data.totalDistance = dvHigh << 8 + dvLow;
155
167
  }
156
168
  if (flags & IndoorBikeDataFlag.ResistanceLevelPresent) {
157
- this.data.resistanceLevel = data.readUInt16LE(offset);
169
+ this.data.resistanceLevel = data.readInt16LE(offset);
158
170
  offset += 2;
159
171
  }
160
172
  if (flags & IndoorBikeDataFlag.InstantaneousPowerPresent) {
161
- this.data.instantaneousPower = data.readUInt16LE(offset);
173
+ this.data.instantaneousPower = data.readInt16LE(offset);
162
174
  offset += 2;
163
175
  }
164
176
  if (flags & IndoorBikeDataFlag.AveragePowerPresent) {
165
- this.data.averagePower = data.readUInt16LE(offset);
177
+ this.data.averagePower = data.readInt16LE(offset);
166
178
  offset += 2;
167
179
  }
168
180
  if (flags & IndoorBikeDataFlag.ExpendedEnergyPresent) {
@@ -193,8 +205,12 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
193
205
  return this.features;
194
206
  try {
195
207
  const data = yield this.read('2acc');
196
- this.features.fitnessMachine = data.readUInt32LE(0);
197
- this.features.targetSettings = data.readUInt32LE(4);
208
+ const buffer = data ? Buffer.from(data) : undefined;
209
+ if (buffer) {
210
+ const fitnessMachine = buffer.readUInt32LE(0);
211
+ const targetSettings = buffer.readUInt32LE(4);
212
+ this.features = { fitnessMachine, targetSettings };
213
+ }
198
214
  }
199
215
  catch (err) {
200
216
  this.logEvent({ message: 'could not read FitnessMachineFeatures', error: err.message, stack: err.stack });
@@ -211,9 +227,28 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
211
227
  this.emit('data', res);
212
228
  }
213
229
  }
214
- write(characteristic, data) {
215
- console.log('write', characteristic, data);
216
- return Promise.resolve(true);
230
+ requestControl() {
231
+ return __awaiter(this, void 0, void 0, function* () {
232
+ if (this.hasControl)
233
+ return true;
234
+ const data = Buffer.alloc(1);
235
+ data.writeUInt8(0, 0);
236
+ const success = yield this.write(FTMS_CP, data);
237
+ if (success)
238
+ this.hasControl = true;
239
+ return this.hasControl;
240
+ });
241
+ }
242
+ setTargetPower(power) {
243
+ return __awaiter(this, void 0, void 0, function* () {
244
+ const hasControl = yield this.requestControl();
245
+ if (!hasControl)
246
+ throw new Error('setTargetPower not possible - control is disabled');
247
+ const data = Buffer.alloc(3);
248
+ data.writeUInt8(5, 0);
249
+ data.writeInt16LE(Math.round(power), 1);
250
+ const res = yield this.write(FTMS_CP, data);
251
+ });
217
252
  }
218
253
  reset() {
219
254
  this.data = {};
@@ -237,6 +272,12 @@ class FmAdapter extends Device_1.default {
237
272
  isBike() { return this.device.isBike(); }
238
273
  isHrm() { return this.device.isHrm(); }
239
274
  isPower() { return this.device.isPower(); }
275
+ isSame(device) {
276
+ if (!(device instanceof FmAdapter))
277
+ return false;
278
+ const adapter = device;
279
+ return (adapter.getName() === this.getName() && adapter.getProfile() === this.getProfile());
280
+ }
240
281
  getProfile() {
241
282
  return 'Smart Trainer';
242
283
  }
@@ -316,6 +357,8 @@ class FmAdapter extends Device_1.default {
316
357
  start(props) {
317
358
  return __awaiter(this, void 0, void 0, function* () {
318
359
  this.logger.logEvent({ message: 'start requested', profile: this.getProfile(), props });
360
+ if (this.ble.isScanning())
361
+ yield this.ble.stopScan();
319
362
  try {
320
363
  const bleDevice = yield this.ble.connectDevice(this.device);
321
364
  if (bleDevice) {
@@ -340,6 +383,13 @@ class FmAdapter extends Device_1.default {
340
383
  return this.device.disconnect();
341
384
  });
342
385
  }
386
+ sendUpdate(request) {
387
+ return __awaiter(this, void 0, void 0, function* () {
388
+ if (this.paused)
389
+ return;
390
+ this.getCyclingMode().sendBikeUpdate(request);
391
+ });
392
+ }
343
393
  pause() { this.paused = true; return Promise.resolve(true); }
344
394
  resume() { this.paused = false; return Promise.resolve(true); }
345
395
  }
package/lib/ble/hrm.d.ts CHANGED
@@ -21,7 +21,6 @@ export default class BleHrmDevice extends BleDevice {
21
21
  getServiceUUids(): string[];
22
22
  parseHrm(_data: Uint8Array): HrmData;
23
23
  onData(characteristic: string, data: Buffer): void;
24
- write(characteristic: any, data: any): Promise<boolean>;
25
24
  }
26
25
  export declare class HrmAdapter extends DeviceAdapter {
27
26
  device: BleHrmDevice;
@@ -34,6 +33,7 @@ export declare class HrmAdapter extends DeviceAdapter {
34
33
  isBike(): boolean;
35
34
  isHrm(): boolean;
36
35
  isPower(): boolean;
36
+ isSame(device: DeviceAdapter): boolean;
37
37
  getProfile(): string;
38
38
  getName(): string;
39
39
  getDisplayName(): string;
package/lib/ble/hrm.js CHANGED
@@ -56,10 +56,6 @@ class BleHrmDevice extends ble_device_1.BleDevice {
56
56
  this.emit('data', res);
57
57
  }
58
58
  }
59
- write(characteristic, data) {
60
- console.log('write', characteristic, data);
61
- return Promise.resolve(true);
62
- }
63
59
  }
64
60
  exports.default = BleHrmDevice;
65
61
  BleHrmDevice.services = ['180d'];
@@ -77,6 +73,12 @@ class HrmAdapter extends Device_1.default {
77
73
  isBike() { return false; }
78
74
  isHrm() { return true; }
79
75
  isPower() { return false; }
76
+ isSame(device) {
77
+ if (!(device instanceof HrmAdapter))
78
+ return false;
79
+ const adapter = device;
80
+ return (adapter.getName() === this.getName() && adapter.getProfile() === this.getProfile());
81
+ }
80
82
  getProfile() {
81
83
  return 'Heartrate Monitor';
82
84
  }
@@ -78,7 +78,15 @@ class BleProtocol extends DeviceProtocol_1.default {
78
78
  case 'fm':
79
79
  case 'smart trainer':
80
80
  case 'fitness machine':
81
- return new fm_1.FmAdapter(fromDevice ? bleDevice : new fm_1.default(props()), this);
81
+ let device;
82
+ if (fromDevice)
83
+ device = bleDevice;
84
+ else {
85
+ device = this.ble.findDeviceInCache(Object.assign(Object.assign({}, props()), { profile: 'Smart Trainer' }));
86
+ if (!device)
87
+ device = new fm_1.default(props());
88
+ }
89
+ return new fm_1.FmAdapter(device, this);
82
90
  case 'cp':
83
91
  case 'power meter':
84
92
  return new pwr_1.PwrAdapter(fromDevice ? bleDevice : new pwr_1.default(props()), this);
package/lib/ble/pwr.d.ts CHANGED
@@ -42,7 +42,6 @@ export default class BleCyclingPowerDevice extends BleDevice {
42
42
  };
43
43
  parsePower(_data: Uint8Array): PowerData;
44
44
  onData(characteristic: string, data: Buffer): void;
45
- write(characteristic: any, data: any): Promise<boolean>;
46
45
  reset(): void;
47
46
  }
48
47
  export declare class PwrAdapter extends DeviceAdapter {
@@ -59,6 +58,7 @@ export declare class PwrAdapter extends DeviceAdapter {
59
58
  isBike(): boolean;
60
59
  isHrm(): boolean;
61
60
  isPower(): boolean;
61
+ isSame(device: DeviceAdapter): boolean;
62
62
  getProfile(): string;
63
63
  getName(): string;
64
64
  getDisplayName(): string;
@@ -72,6 +72,7 @@ export declare class PwrAdapter extends DeviceAdapter {
72
72
  transformData(bikeData: IncyclistBikeData): DeviceData;
73
73
  start(props?: any): Promise<any>;
74
74
  stop(): Promise<boolean>;
75
+ sendUpdate(request: any): Promise<void>;
75
76
  pause(): Promise<boolean>;
76
77
  resume(): Promise<boolean>;
77
78
  }
package/lib/ble/pwr.js CHANGED
@@ -114,10 +114,6 @@ class BleCyclingPowerDevice extends ble_device_1.BleDevice {
114
114
  this.emit('data', res);
115
115
  }
116
116
  }
117
- write(characteristic, data) {
118
- console.log('write', characteristic, data);
119
- return Promise.resolve(true);
120
- }
121
117
  reset() {
122
118
  this.instantaneousPower = undefined;
123
119
  this.balance = undefined;
@@ -147,6 +143,12 @@ class PwrAdapter extends Device_1.default {
147
143
  isBike() { return true; }
148
144
  isHrm() { return false; }
149
145
  isPower() { return true; }
146
+ isSame(device) {
147
+ if (!(device instanceof PwrAdapter))
148
+ return false;
149
+ const adapter = device;
150
+ return (adapter.getName() === this.getName() && adapter.getProfile() === this.getProfile());
151
+ }
150
152
  getProfile() {
151
153
  return 'Power Meter';
152
154
  }
@@ -252,6 +254,13 @@ class PwrAdapter extends Device_1.default {
252
254
  return this.device.disconnect();
253
255
  });
254
256
  }
257
+ sendUpdate(request) {
258
+ return __awaiter(this, void 0, void 0, function* () {
259
+ if (this.paused)
260
+ return;
261
+ this.getCyclingMode().sendBikeUpdate(request);
262
+ });
263
+ }
255
264
  pause() { this.paused = true; return Promise.resolve(true); }
256
265
  resume() { this.paused = false; return Promise.resolve(true); }
257
266
  }
@@ -39,6 +39,7 @@ export default class DaumAdapterBase extends IncyclistDevice implements DeviceAd
39
39
  isBike(): boolean;
40
40
  isPower(): boolean;
41
41
  isHrm(): boolean;
42
+ isSame(device: DeviceAdapter): boolean;
42
43
  setIgnoreHrm(ignore: any): void;
43
44
  setIgnoreBike(ignore: any): void;
44
45
  isStopped(): boolean;
@@ -131,6 +131,12 @@ class DaumAdapterBase extends Device_1.default {
131
131
  isHrm() {
132
132
  return true;
133
133
  }
134
+ isSame(device) {
135
+ if (!(device instanceof DaumAdapterBase))
136
+ return false;
137
+ const adapter = device;
138
+ return (adapter.getName() === this.getName() && adapter.getPort() === this.getPort());
139
+ }
134
140
  setIgnoreHrm(ignore) {
135
141
  this.ignoreHrm = ignore;
136
142
  }
@@ -47,6 +47,7 @@ export default class KettlerRacerAdapter extends DeviceAdapterBase implements De
47
47
  isBike(): boolean;
48
48
  isPower(): boolean;
49
49
  isHrm(): boolean;
50
+ isSame(device: DeviceAdapter): boolean;
50
51
  setID(id: any): void;
51
52
  getID(): string;
52
53
  getName(): string;
@@ -56,6 +56,12 @@ class KettlerRacerAdapter extends Device_1.default {
56
56
  isBike() { return true; }
57
57
  isPower() { return true; }
58
58
  isHrm() { return true; }
59
+ isSame(device) {
60
+ if (!(device instanceof KettlerRacerAdapter))
61
+ return false;
62
+ const adapter = device;
63
+ return (adapter.getName() === this.getName() && adapter.getPort() === this.getPort());
64
+ }
59
65
  setID(id) {
60
66
  this.id = id;
61
67
  }
@@ -37,6 +37,7 @@ class PowerBasedCyclingModeBase extends CyclingMode_1.CyclingModeBase {
37
37
  let powerToMaintainSpeed = calculations_1.default.calculatePower(m, vPrev, slope, props);
38
38
  const powerDelta = powerToMaintainSpeed - power;
39
39
  const Ekin = EkinPrev - powerDelta * t;
40
+ console.log('~~~~ calculateSpeedAndDistance', Ekin, powerToMaintainSpeed, power, m, vPrev, slope);
40
41
  if (Ekin > 0) {
41
42
  const v = Math.sqrt(2 * Ekin / m);
42
43
  const speed = v * 3.6;
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.config = void 0;
7
7
  const power_base_1 = __importDefault(require("./power-base"));
8
+ const MIN_SPEED = 10;
8
9
  exports.config = {
9
10
  name: 'PowerMeter',
10
11
  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',
@@ -54,12 +55,18 @@ class PowerMeterCyclingMode extends power_base_1.default {
54
55
  const m = this.getWeight();
55
56
  let t = this.getTimeSinceLastUpdate();
56
57
  const { speed, distance } = this.calculateSpeedAndDistance(power, slope, m, t);
57
- data.speed = speed;
58
58
  data.power = Math.round(power);
59
- data.distanceInternal = Math.round(distanceInternal + distance);
60
59
  data.slope = slope;
60
+ if (power === 0 && speed < MIN_SPEED) {
61
+ data.speed = Math.round(prevData.speed - 1) < 0 ? 0 : Math.round(prevData.speed - 1);
62
+ data.distanceInternal = Math.round(distanceInternal + data.speed / 3.6 * t);
63
+ }
64
+ else {
65
+ data.speed = (power === 0 && speed < MIN_SPEED) ? 0 : speed;
66
+ data.distanceInternal = (power === 0 && speed < MIN_SPEED) ? Math.round(distanceInternal) : Math.round(distanceInternal + distance);
67
+ }
61
68
  if (props.log)
62
- this.logger.logEvent({ message: "updateData result", data, bikeData, prevSpeed: prevData.speed });
69
+ this.logger.logEvent({ message: "updateData result", data, bikeData, prevSpeed: prevData.speed, stopped: speed < MIN_SPEED });
63
70
  this.data = data;
64
71
  }
65
72
  catch (err) {
@@ -28,6 +28,7 @@ export declare class Simulator extends DeviceAdapter {
28
28
  isBike(): boolean;
29
29
  isHrm(): boolean;
30
30
  isPower(): boolean;
31
+ isSame(device: DeviceAdapter): boolean;
31
32
  getID(): string;
32
33
  getName(): string;
33
34
  getPort(): string;
@@ -68,6 +68,11 @@ class Simulator extends Device_1.default {
68
68
  isBike() { return true; }
69
69
  isHrm() { return false; }
70
70
  isPower() { return true; }
71
+ isSame(device) {
72
+ if (!(device instanceof Simulator))
73
+ return false;
74
+ return true;
75
+ }
71
76
  getID() { return Simulator.NAME; }
72
77
  getName() { return Simulator.NAME; }
73
78
  getPort() { return 'local'; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-devices",
3
- "version": "1.4.45",
3
+ "version": "1.4.48",
4
4
  "dependencies": {
5
5
  "@serialport/parser-byte-length": "^9.0.1",
6
6
  "@serialport/parser-delimiter": "^9.0.1",