incyclist-devices 3.0.19 → 3.0.21

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 (39) hide show
  1. package/lib/cjs/base/consts.js +2 -1
  2. package/lib/cjs/ble/base/adapter.js +5 -8
  3. package/lib/cjs/ble/base/interface.js +20 -2
  4. package/lib/cjs/ble/base/peripheral.js +22 -5
  5. package/lib/cjs/ble/base/sensor.js +12 -1
  6. package/lib/cjs/ble/characteristics/csc/measurement.js +6 -2
  7. package/lib/cjs/ble/csc/adapter.js +29 -2
  8. package/lib/cjs/ble/csc/sensor.js +9 -2
  9. package/lib/cjs/ble/fm/adapter.js +3 -0
  10. package/lib/cjs/ble/wahoo/adapter.js +10 -0
  11. package/lib/cjs/ble/wahoo/sensor.js +35 -3
  12. package/lib/cjs/modes/speed.js +133 -0
  13. package/lib/cjs/types/sensor.js +2 -0
  14. package/lib/esm/base/consts.js +1 -0
  15. package/lib/esm/ble/base/adapter.js +5 -8
  16. package/lib/esm/ble/base/interface.js +20 -2
  17. package/lib/esm/ble/base/peripheral.js +22 -5
  18. package/lib/esm/ble/base/sensor.js +12 -1
  19. package/lib/esm/ble/characteristics/csc/measurement.js +6 -2
  20. package/lib/esm/ble/csc/adapter.js +29 -2
  21. package/lib/esm/ble/csc/sensor.js +9 -2
  22. package/lib/esm/ble/fm/adapter.js +3 -0
  23. package/lib/esm/ble/wahoo/adapter.js +10 -0
  24. package/lib/esm/ble/wahoo/sensor.js +35 -3
  25. package/lib/esm/modes/speed.js +127 -0
  26. package/lib/esm/types/sensor.js +1 -0
  27. package/lib/types/base/consts.d.ts +1 -0
  28. package/lib/types/ble/base/interface.d.ts +2 -1
  29. package/lib/types/ble/base/sensor.d.ts +2 -0
  30. package/lib/types/ble/characteristics/csc/measurement.d.ts +1 -0
  31. package/lib/types/ble/csc/adapter.d.ts +5 -1
  32. package/lib/types/ble/csc/sensor.d.ts +6 -2
  33. package/lib/types/ble/types.d.ts +1 -0
  34. package/lib/types/ble/wahoo/adapter.d.ts +3 -1
  35. package/lib/types/ble/wahoo/sensor.d.ts +5 -0
  36. package/lib/types/modes/speed.d.ts +43 -0
  37. package/lib/types/types/index.d.ts +1 -0
  38. package/lib/types/types/sensor.d.ts +4 -0
  39. package/package.json +1 -1
@@ -160,8 +160,9 @@ export class BlePeripheral {
160
160
  async _discoverServices() {
161
161
  if (!this.getPeripheral())
162
162
  return [];
163
+ const { name, address } = this.getInfo();
163
164
  if (this.getPeripheral().discoverServicesAsync) {
164
- this.logEvent({ message: 'discover services', address: this.getPeripheral().address });
165
+ this.logEvent({ message: 'discover services', name, address });
165
166
  const peripheral = this.getPeripheral();
166
167
  let services = [];
167
168
  if (peripheral?.discoverServicesAsync) {
@@ -169,14 +170,14 @@ export class BlePeripheral {
169
170
  .catch(() => []);
170
171
  }
171
172
  this.discoveredServiceUUIds = services.map(s => beautifyUUID(s.uuid));
172
- this.logEvent({ message: 'discover services result', address: this.getPeripheral().address, services: this.discoveredServiceUUIds });
173
+ this.logEvent({ message: 'discover services result', name, address, services: this.discoveredServiceUUIds });
173
174
  return services.map(s => s.uuid);
174
175
  }
175
176
  else {
176
- this.logEvent({ message: 'discover services and characteristics', address: this.getPeripheral().address });
177
+ this.logEvent({ message: 'discover services and characteristics', name, address });
177
178
  const res = await this.getPeripheral().discoverSomeServicesAndCharacteristicsAsync([], []);
178
179
  this.discoveredServiceUUIds = res.services.map(s => beautifyUUID(s.uuid));
179
- this.logEvent({ message: 'discover services result', address: this.getPeripheral().address, services: this.discoveredServiceUUIds });
180
+ this.logEvent({ message: 'discover services result', name, address, services: this.discoveredServiceUUIds });
180
181
  return res.services.map(s => s.uuid);
181
182
  }
182
183
  }
@@ -190,10 +191,12 @@ export class BlePeripheral {
190
191
  async _discoverCharacteristics(serviceUUID) {
191
192
  if (!this.getPeripheral())
192
193
  return [];
193
- this.logEvent({ message: 'discover services and characteristics', service: serviceUUID, address: this.getPeripheral().address });
194
+ const { name, address } = this.getInfo();
195
+ this.logEvent({ message: 'discover services and characteristics', name, address, service: serviceUUID });
194
196
  const res = await this.getPeripheral().discoverSomeServicesAndCharacteristicsAsync([serviceUUID], [])
195
197
  .catch(() => ({ services: [], characteristics: [] }));
196
198
  res.characteristics.forEach(c => this.characteristics[beautifyUUID(c.uuid)] = c);
199
+ this.logEvent({ message: 'discover services and characteristics result', name, address, service: serviceUUID });
197
200
  return res.characteristics.map(c => {
198
201
  const { uuid, properties, name, _serviceUuid } = c;
199
202
  return { uuid, properties, name, _serviceUuid };
@@ -315,6 +318,15 @@ export class BlePeripheral {
315
318
  if (!success)
316
319
  retry.push(c);
317
320
  }
321
+ else {
322
+ const uuid = beautifyUUID(element);
323
+ if (c?.properties) {
324
+ this.logEvent({ message: 'cannot subscribe', uuid, reason: 'invalid type', properties: c.properties.join('|') });
325
+ }
326
+ else {
327
+ this.logEvent({ message: 'cannot subscribe', uuid, reason: 'not found' });
328
+ }
329
+ }
318
330
  }
319
331
  for (const element of retry) {
320
332
  const c = element;
@@ -329,12 +341,17 @@ export class BlePeripheral {
329
341
  }
330
342
  async discoverAllCharacteristics() {
331
343
  try {
344
+ const { name, address } = this.getInfo();
345
+ this.logEvent({ message: 'discover all characteristics', name, address });
332
346
  const res = await this.getPeripheral().discoverSomeServicesAndCharacteristicsAsync([], []);
333
347
  const found = [];
348
+ const uuids = [];
334
349
  res.characteristics.forEach(c => {
335
350
  this.characteristics[beautifyUUID(c.uuid)] = c;
336
351
  found.push(c.uuid);
352
+ uuids.push(beautifyUUID(c.uuid));
337
353
  });
354
+ this.logEvent({ message: 'discover all characteristics result', name, address, uuids: uuids.join('|') });
338
355
  return found;
339
356
  }
340
357
  catch (err) {
@@ -9,6 +9,7 @@ export class TBleSensor extends EventEmitter {
9
9
  stopRequested = false;
10
10
  subscribeSuccess = false;
11
11
  reconnectPromise;
12
+ connectPromise;
12
13
  onDataHandler;
13
14
  logEvent(event, ...args) {
14
15
  try {
@@ -73,7 +74,8 @@ export class TBleSensor extends EventEmitter {
73
74
  this.logEvent({ message: 'no peripheral' });
74
75
  return false;
75
76
  }
76
- const connected = await this.peripheral.connect();
77
+ this.connectPromise = this.connectPromise ?? this.peripheral.connect();
78
+ const connected = await this.connectPromise;
77
79
  if (!connected)
78
80
  return false;
79
81
  if (!reconnect)
@@ -125,6 +127,9 @@ export class TBleSensor extends EventEmitter {
125
127
  let connected = false;
126
128
  let subscribed = false;
127
129
  let success = false;
130
+ if (connectionLost) {
131
+ this.emit('connctionLost');
132
+ }
128
133
  await sleep(500);
129
134
  do {
130
135
  try {
@@ -144,6 +149,9 @@ export class TBleSensor extends EventEmitter {
144
149
  if (!this.stopRequested && !success)
145
150
  this.logEvent({ message: 'reconnect sensor retry', name, address });
146
151
  } while (!success || this.stopRequested);
152
+ if (connectionLost && success) {
153
+ this.emit('connctionRecovered');
154
+ }
147
155
  this.logEvent({ message: 'reconnect sensor completed', success, stopRequested: this.stopRequested });
148
156
  return success;
149
157
  }
@@ -153,6 +161,9 @@ export class TBleSensor extends EventEmitter {
153
161
  isConnected() {
154
162
  return this.peripheral?.isConnected();
155
163
  }
164
+ isReady() {
165
+ return this.isConnected() && this.connectPromise === undefined && this.reconnectPromise === undefined;
166
+ }
156
167
  isSubscribed() {
157
168
  return this.subscribeSuccess;
158
169
  }
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_WHEEL_CIRCUMFERENCE } from "../../../base/consts.js";
1
2
  export class CscMeasurement {
2
3
  data;
3
4
  prevCrankData = undefined;
@@ -5,13 +6,16 @@ export class CscMeasurement {
5
6
  prevWheelData = undefined;
6
7
  currentWheelData = undefined;
7
8
  timeOffset = 0;
8
- cw = 2.1;
9
+ cw = DEFAULT_WHEEL_CIRCUMFERENCE;
9
10
  constructor(data = {}) {
10
11
  this.data = data;
11
12
  }
12
13
  setWheelCircumference(wheelCircumference) {
13
14
  this.cw = wheelCircumference;
14
15
  }
16
+ getWheelCircumference() {
17
+ return this.cw;
18
+ }
15
19
  parse(buffer, features) {
16
20
  const data = Buffer.from(buffer);
17
21
  let offset = 0;
@@ -86,7 +90,7 @@ export class CscMeasurement {
86
90
  }
87
91
  const seconds = time / 1024;
88
92
  const rps = revs / seconds;
89
- speed = rps * this.cw;
93
+ speed = rps * this.cw * 3.6;
90
94
  }
91
95
  else if (p.cntUpdateMissing < 0 || p.cntUpdateMissing > 2) {
92
96
  speed = 0;
@@ -2,9 +2,14 @@ import { EventLogger } from 'gd-eventlog';
2
2
  import BleAdapter from '../base/adapter.js';
3
3
  import { IncyclistCapability } from '../../types/index.js';
4
4
  import { BleCyclingSpeedCadenceDevice } from './sensor.js';
5
+ import SpeedCyclingMode from '../../modes/speed.js';
5
6
  export class BleCSCAdapter extends BleAdapter {
6
7
  static INCYCLIST_PROFILE_NAME = 'Speed + Cadence Sensor';
7
8
  static CAPABILITIES = [IncyclistCapability.Speed, IncyclistCapability.Cadence];
9
+ static controllers = {
10
+ modes: [SpeedCyclingMode],
11
+ default: SpeedCyclingMode
12
+ };
8
13
  constructor(settings, props) {
9
14
  super(settings, props);
10
15
  this.logger = new EventLogger('Ble-CSC');
@@ -42,7 +47,29 @@ export class BleCSCAdapter extends BleAdapter {
42
47
  return this.getName();
43
48
  }
44
49
  mapData(deviceData) {
45
- const { cadence, speed } = deviceData;
46
- return { cadence, speed: speed * 3.6 };
50
+ const { cadence, speed = 0 } = deviceData;
51
+ return { cadence, speed };
52
+ }
53
+ transformData(bikeData) {
54
+ if (bikeData === undefined)
55
+ return;
56
+ let data = {
57
+ speed: bikeData.speed,
58
+ power: bikeData.power,
59
+ cadence: bikeData.pedalRpm,
60
+ timestamp: Date.now()
61
+ };
62
+ if (bikeData.time)
63
+ data.deviceTime = bikeData.time;
64
+ this.data = data;
65
+ return data;
66
+ }
67
+ setCyclingMode(mode, settings, sendInitCommands) {
68
+ super.setCyclingMode(mode, settings, sendInitCommands);
69
+ try {
70
+ const wc = this.getCyclingMode().getSetting('wc');
71
+ this.getSensor().setWheelCircumference(wc / 1000);
72
+ }
73
+ catch { }
47
74
  }
48
75
  }
@@ -12,12 +12,12 @@ export class BleCyclingSpeedCadenceDevice extends TBleSensor {
12
12
  data;
13
13
  parsers = {};
14
14
  featureParser;
15
+ measurement = new CscMeasurement();
15
16
  constructor(peripheral, props) {
16
17
  super(peripheral, props);
17
18
  this.data = {};
18
- const measurement = new CscMeasurement();
19
19
  this.featureParser = new CscFeatures();
20
- this.parsers[beautifyUUID(CSC_MEASUREMENT)] = measurement;
20
+ this.parsers[beautifyUUID(CSC_MEASUREMENT)] = this.measurement;
21
21
  }
22
22
  getRequiredCharacteristics() {
23
23
  return [CSC_MEASUREMENT];
@@ -31,6 +31,7 @@ export class BleCyclingSpeedCadenceDevice extends TBleSensor {
31
31
  }
32
32
  catch (err) {
33
33
  this.logEvent({ message: 'read failed', characteristic: CSC_FEATURE, reason: err.message });
34
+ return {};
34
35
  }
35
36
  }
36
37
  onData(characteristic, characteristicData) {
@@ -51,4 +52,10 @@ export class BleCyclingSpeedCadenceDevice extends TBleSensor {
51
52
  reset() {
52
53
  this.data = {};
53
54
  }
55
+ async setWheelCircumference(wheelCircumference) {
56
+ this.measurement.setWheelCircumference(wheelCircumference);
57
+ }
58
+ getWheelCircumference() {
59
+ return this.measurement.getWheelCircumference();
60
+ }
54
61
  }
@@ -322,6 +322,9 @@ export default class BleFmAdapter extends BleAdapter {
322
322
  delete this.promiseSendUpdate;
323
323
  return confirmed;
324
324
  }
325
+ if (!device.isReady()) {
326
+ this.logEvent({ message: 'send bike update failed', reason: 'not connected' });
327
+ }
325
328
  }
326
329
  catch (error) {
327
330
  const err = error;
@@ -2,6 +2,7 @@ import { EventLogger } from 'gd-eventlog';
2
2
  import { BleFmAdapter } from '../fm/index.js';
3
3
  import BleWahooDevice from './sensor.js';
4
4
  import { IncyclistCapability } from '../../types/index.js';
5
+ import calc from '../../utils/calculations.js';
5
6
  export default class BleWahooAdapter extends BleFmAdapter {
6
7
  static INCYCLIST_PROFILE_NAME = 'Smart Trainer';
7
8
  constructor(settings, props) {
@@ -24,6 +25,15 @@ export default class BleWahooAdapter extends BleFmAdapter {
24
25
  updateSensor(peripheral) {
25
26
  this.device = new BleWahooDevice(peripheral, { logger: this.logger });
26
27
  }
28
+ mapData(deviceData) {
29
+ const data = super.mapData(deviceData);
30
+ if (!deviceData.instantaneousPower && !deviceData.cadence && (deviceData.speed ?? 0) > 0) {
31
+ const m = this.getWeight();
32
+ data.isPedalling = true;
33
+ data.power = calc.calculatePower(m, data.speed / 3.6, 0);
34
+ }
35
+ return data;
36
+ }
27
37
  async checkCapabilities() {
28
38
  this.logEvent({ message: 'device capabilities updated', name: this.getSettings().name, interface: this.getSettings().interface, capabilities: this.capabilities });
29
39
  }
@@ -18,6 +18,9 @@ export default class BleWahooDevice extends BleFitnessMachineDevice {
18
18
  isSimMode;
19
19
  isRequestControlBusy = false;
20
20
  weight = DEFAULT_BIKE_WEIGHT + DEFAULT_USER_WEIGHT;
21
+ prevWheelRevolutions = undefined;
22
+ prevWheelEventTime = undefined;
23
+ wheelCircumference = 2.096;
21
24
  simModeSettings;
22
25
  constructor(peripheral, props) {
23
26
  super(peripheral, props);
@@ -158,17 +161,40 @@ export default class BleWahooDevice extends BleFitnessMachineDevice {
158
161
  }
159
162
  parsePower(_data) {
160
163
  const data = Buffer.from(_data);
164
+ let balance;
165
+ let accTorque;
166
+ let speed;
167
+ let wheelRevolutions, wheelEventTime;
161
168
  try {
162
169
  let offset = 4;
163
170
  const flags = data.readUInt16LE(0);
164
171
  this.data.instantaneousPower = data.readUInt16LE(2);
165
172
  if (flags & 0x1)
166
- data.readUInt8(offset++);
173
+ balance = data.readUInt8(offset++);
167
174
  if (flags & 0x4) {
175
+ accTorque = data.readUInt16LE(offset);
168
176
  offset += 2;
169
177
  }
170
178
  if (flags & 0x10) {
179
+ wheelRevolutions = data.readUInt32LE(offset);
180
+ wheelEventTime = data.readUInt16LE(offset + 4);
171
181
  offset += 6;
182
+ if (this.prevWheelRevolutions !== undefined && this.prevWheelEventTime !== undefined) {
183
+ const wheelRevDelta = wheelRevolutions - this.prevWheelRevolutions;
184
+ const wheelTimeDelta = (wheelEventTime - this.prevWheelEventTime + 65536) % 65536;
185
+ const timeSeconds = (wheelTimeDelta / 1024);
186
+ if (timeSeconds > 0 && wheelRevDelta >= 0) {
187
+ const distance = wheelRevDelta * this.wheelCircumference;
188
+ speed = distance / timeSeconds * 3.6;
189
+ this.data.speed = speed;
190
+ }
191
+ else if (timeSeconds > 0 && wheelRevDelta === 0) {
192
+ speed = 0;
193
+ this.data.speed = speed;
194
+ }
195
+ }
196
+ this.prevWheelRevolutions = wheelRevolutions;
197
+ this.prevWheelEventTime = wheelEventTime;
172
198
  }
173
199
  if (flags & 0x20) {
174
200
  const crankData = {
@@ -183,8 +209,8 @@ export default class BleWahooDevice extends BleFitnessMachineDevice {
183
209
  catch (err) {
184
210
  this.logEvent({ message: 'error', fn: 'parsePower', error: err.message, stack: err.stack });
185
211
  }
186
- const { instantaneousPower, cadence, time } = this.data;
187
- return { instantaneousPower, cadence, time, raw: data.toString('hex') };
212
+ const { instantaneousPower, cadence, speed: dataSpeed, time } = this.data;
213
+ return { instantaneousPower, speed: dataSpeed, cadence, time, raw: '2a63:' + data.toString('hex') };
188
214
  }
189
215
  supportsHeartRate() {
190
216
  try {
@@ -336,4 +362,10 @@ export default class BleWahooDevice extends BleFitnessMachineDevice {
336
362
  return false;
337
363
  }
338
364
  }
365
+ async setWheelCircumference(wheelCircumference) {
366
+ this.wheelCircumference = wheelCircumference;
367
+ }
368
+ getWheelCircumference() {
369
+ return this.wheelCircumference;
370
+ }
339
371
  }
@@ -0,0 +1,127 @@
1
+ import { CyclingModeBase } from "./base";
2
+ import { CyclingModeProperyType } from "./types";
3
+ import calc from '../utils/calculations.js';
4
+ import { DEFAULT_BIKE_WEIGHT, DEFAULT_USER_WEIGHT, DEFAULT_WHEEL_CIRCUMFERENCE } from "../base/consts.js";
5
+ export default class SpeedCyclingMode extends CyclingModeBase {
6
+ data;
7
+ prevUpdateTS = 0;
8
+ prevRequest;
9
+ static config = {
10
+ name: "SpeedSensor",
11
+ description: "Calculates power based on speed and slope.",
12
+ properties: [
13
+ { key: 'bikeType', name: 'Bike Type', description: '', type: CyclingModeProperyType.SingleSelect, options: ['Race', 'Mountain', 'Triathlon'], default: 'Race' },
14
+ { key: 'wc', name: 'Wheel Circumference', description: 'Circumference in mm', type: CyclingModeProperyType.Integer, default: Math.ceil(DEFAULT_WHEEL_CIRCUMFERENCE * 1000) },
15
+ ]
16
+ };
17
+ constructor(adapter, props) {
18
+ super(adapter, props);
19
+ this.data = { speed: 0, power: 0, distanceInternal: 0, pedalRpm: 0, isPedalling: false, heartrate: 0 };
20
+ }
21
+ getData() {
22
+ return this.data;
23
+ }
24
+ getSlope() {
25
+ const { slope } = this.data;
26
+ return slope || 0;
27
+ }
28
+ getWeight() {
29
+ const a = this.adapter;
30
+ const defaultWeight = DEFAULT_BIKE_WEIGHT + DEFAULT_USER_WEIGHT;
31
+ const m = (a) ? a.getWeight() || defaultWeight : defaultWeight;
32
+ return m;
33
+ }
34
+ getTimeSinceLastUpdate() {
35
+ const ts = Date.now();
36
+ const duration = this.prevUpdateTS === 0 ? 0 : ((ts - this.prevUpdateTS) / 1000);
37
+ return duration;
38
+ }
39
+ getBikeInitRequest() {
40
+ return {};
41
+ }
42
+ updateData(data, log = true) {
43
+ const prevData = this.data;
44
+ const prevRequest = this.prevRequest ?? {};
45
+ const slope = (prevRequest.slope !== undefined ? prevRequest.slope : prevData.slope ?? 0);
46
+ const { pedalRpm = 0 } = data ?? {};
47
+ const bikeType = this.getSetting('bikeType')?.toLowerCase();
48
+ if (data.isPedalling === false || data.speed === 0) {
49
+ this.data = {
50
+ ...data,
51
+ speed: 0,
52
+ isPedalling: false,
53
+ power: 0,
54
+ slope
55
+ };
56
+ this.prevUpdateTS = Date.now();
57
+ return this.data;
58
+ }
59
+ const m = this.getWeight();
60
+ const t = this.getTimeSinceLastUpdate();
61
+ const v = (data.speed ?? 0) / 3.6;
62
+ const power = calc.calculatePower(m, v, 0, { bikeType });
63
+ const { speed: calcSpeed, distance } = this.calculateSpeedAndDistance(power, slope, m, t, { bikeType });
64
+ const prevTime = this.data.time ?? 0;
65
+ const prevDistance = this.data.distanceInternal ?? 0;
66
+ const updated = {
67
+ ...data,
68
+ time: prevTime + t,
69
+ isPedalling: data.isPedalling ?? (v > 0 || pedalRpm > 0),
70
+ slope,
71
+ power,
72
+ speed: calcSpeed,
73
+ distanceInternal: prevDistance + distance
74
+ };
75
+ if (log && this.logger)
76
+ this.logEvent({ message: "updateData result", mode: this.getName(), data: updated, bikeData: data });
77
+ this.data = updated;
78
+ this.prevUpdateTS = Date.now();
79
+ return updated;
80
+ }
81
+ sendBikeUpdate(request) {
82
+ if (request.slope !== undefined) {
83
+ this.data.slope = request.slope;
84
+ }
85
+ return {};
86
+ }
87
+ calculatePowerAndDistance(speed, slope, m, t, props = {}) {
88
+ const prevData = this.getData();
89
+ const vPrev = (prevData.speed || 0) / 3.6;
90
+ const EkinPrev = 1 / 2 * m * vPrev * vPrev;
91
+ const vTarget = (speed || 0) / 3.6;
92
+ const Ekin = 1 / 2 * m * vTarget * vTarget;
93
+ const powerDelta = t !== 0 ? (EkinPrev - Ekin) / t : 0;
94
+ const powerToMaintainSpeed = calc.calculatePower(m, vPrev, slope, props);
95
+ const power = powerToMaintainSpeed - powerDelta;
96
+ const v = speed / 3.6;
97
+ const distance = v * t;
98
+ this.data.power = power;
99
+ return { power, distance };
100
+ }
101
+ calculateSpeedAndDistance(power, slope, m, t, props = {}) {
102
+ const prevData = this.getData();
103
+ const vPrev = (prevData.speed || 0) / 3.6;
104
+ const EkinPrev = 1 / 2 * m * vPrev * vPrev;
105
+ let powerToMaintainSpeed = calc.calculatePower(m, vPrev, slope, props);
106
+ if (t >= 30) {
107
+ const speed = calc.calculateSpeed(m, power, slope, props);
108
+ return { speed, distance: 0 };
109
+ }
110
+ const powerDelta = powerToMaintainSpeed - power;
111
+ const Ekin = EkinPrev - powerDelta * t;
112
+ if (Ekin > 0) {
113
+ const v = Math.sqrt(2 * Ekin / m);
114
+ const speed = v * 3.6;
115
+ const distance = v * t;
116
+ this.data.speed = speed;
117
+ return { speed, distance };
118
+ }
119
+ else {
120
+ const v = vPrev * 0.5;
121
+ const speed = v * 3.6;
122
+ const distance = v * t;
123
+ this.data.speed = speed;
124
+ return { speed, distance };
125
+ }
126
+ }
127
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { DeviceProperties } from "../types/index.js";
2
2
  export declare const DEFAULT_BIKE_WEIGHT = 10;
3
3
  export declare const DEFAULT_USER_WEIGHT = 75;
4
+ export declare const DEFAULT_WHEEL_CIRCUMFERENCE = 2.118;
4
5
  export declare const DEFAULT_PROPS: DeviceProperties;
@@ -79,7 +79,8 @@ export declare class BleInterface extends EventEmitter implements IBleInterface<
79
79
  protected discoverPeripherals(): Promise<void>;
80
80
  protected onPeripheralFound(peripheral: BleRawPeripheral): void;
81
81
  protected checkForWahooEnhancement(announcement: BlePeripheralAnnouncement): boolean;
82
- protected processWahooAnnouncement(announcement: BlePeripheralAnnouncement): void;
82
+ protected processEnrichedAnnouncement(announcement: BlePeripheralAnnouncement): void;
83
+ protected checkForTacxEnhancement(announcement: BlePeripheralAnnouncement): boolean;
83
84
  protected buildAnnouncement(peripheral: BleRawPeripheral): BlePeripheralAnnouncement;
84
85
  protected updateWithServices(announcement: BlePeripheralAnnouncement): Promise<BlePeripheralAnnouncement>;
85
86
  protected discoverServices(announcement: BlePeripheralAnnouncement): Promise<string[]>;
@@ -9,6 +9,7 @@ export declare class TBleSensor extends EventEmitter implements IBleSensor {
9
9
  protected stopRequested: boolean;
10
10
  protected subscribeSuccess: boolean;
11
11
  protected reconnectPromise: Promise<boolean> | undefined;
12
+ protected connectPromise: Promise<boolean> | undefined;
12
13
  protected onDataHandler: any;
13
14
  logEvent(event: any, ...args: any): void;
14
15
  constructor(peripheral: IBlePeripheral, props?: {
@@ -32,6 +33,7 @@ export declare class TBleSensor extends EventEmitter implements IBleSensor {
32
33
  doReconnectSensor(connectionLost?: boolean): Promise<boolean>;
33
34
  reset(): void;
34
35
  isConnected(): boolean;
36
+ isReady(): boolean;
35
37
  isSubscribed(): boolean;
36
38
  protected onDisconnect(): void;
37
39
  read(characteristicUUID: string): Promise<Buffer>;
@@ -14,6 +14,7 @@ export declare class CscMeasurement implements CharacteristicParser<CyclingCaden
14
14
  protected cw: number;
15
15
  constructor(data?: CyclingCadenceAndSpeed);
16
16
  setWheelCircumference(wheelCircumference: any): void;
17
+ getWheelCircumference(): number;
17
18
  parse(buffer: Buffer, features?: Feature): CyclingCadenceAndSpeed;
18
19
  protected parseCrankData(crankData: any): {
19
20
  rpm?: undefined;
@@ -1,12 +1,14 @@
1
1
  import BleAdapter from '../base/adapter.js';
2
2
  import { BleDeviceSettings, IBlePeripheral } from '../types.js';
3
- import { DeviceProperties, IncyclistCapability, IAdapter, IncyclistAdapterData } from '../../types/index.js';
3
+ import { DeviceProperties, IncyclistCapability, IAdapter, IncyclistAdapterData, ControllerConfig, IncyclistBikeData } from '../../types/index.js';
4
4
  import { LegacyProfile } from '../../antv2/types.js';
5
5
  import { BleCyclingSpeedCadenceDevice } from './sensor.js';
6
6
  import { CSCData } from './types.js';
7
+ import ICyclingMode from '../../modes/types.js';
7
8
  export declare class BleCSCAdapter extends BleAdapter<CSCData, BleCyclingSpeedCadenceDevice> {
8
9
  protected static INCYCLIST_PROFILE_NAME: LegacyProfile;
9
10
  protected static CAPABILITIES: IncyclistCapability[];
11
+ protected static controllers: ControllerConfig;
10
12
  constructor(settings: BleDeviceSettings, props?: DeviceProperties);
11
13
  protected checkCapabilities(): Promise<void>;
12
14
  isSame(device: IAdapter): boolean;
@@ -14,4 +16,6 @@ export declare class BleCSCAdapter extends BleAdapter<CSCData, BleCyclingSpeedCa
14
16
  getProfile(): LegacyProfile;
15
17
  getDisplayName(): string;
16
18
  mapData(deviceData: CSCData): IncyclistAdapterData;
19
+ transformData(bikeData: IncyclistBikeData): IncyclistAdapterData;
20
+ setCyclingMode(mode: string | ICyclingMode, settings?: any, sendInitCommands?: boolean): void;
17
21
  }
@@ -2,9 +2,10 @@ import { LegacyProfile } from '../../antv2/types.js';
2
2
  import { BleProtocol } from '../types.js';
3
3
  import { TBleSensor } from '../base/sensor.js';
4
4
  import { CharacteristicParser } from '../characteristics/types.js';
5
- import { CyclingCadenceAndSpeed } from '../characteristics/csc/measurement.js';
5
+ import { CscMeasurement, CyclingCadenceAndSpeed } from '../characteristics/csc/measurement.js';
6
6
  import { BleCSCFeatures, CscFeatures } from '../characteristics/csc/features.js';
7
- export declare class BleCyclingSpeedCadenceDevice extends TBleSensor {
7
+ import { ISpeedSensor } from '../../types/sensor.js';
8
+ export declare class BleCyclingSpeedCadenceDevice extends TBleSensor implements ISpeedSensor {
8
9
  static readonly profile: LegacyProfile;
9
10
  static readonly protocol: BleProtocol;
10
11
  static readonly services: string[];
@@ -13,9 +14,12 @@ export declare class BleCyclingSpeedCadenceDevice extends TBleSensor {
13
14
  protected data: CyclingCadenceAndSpeed;
14
15
  protected parsers: Record<string, CharacteristicParser<any>>;
15
16
  protected featureParser: CscFeatures;
17
+ protected measurement: CscMeasurement;
16
18
  constructor(peripheral: any, props?: any);
17
19
  protected getRequiredCharacteristics(): Array<string>;
18
20
  getFeatures(): Promise<BleCSCFeatures>;
19
21
  onData(characteristic: string, characteristicData: Buffer): boolean;
20
22
  reset(): void;
23
+ setWheelCircumference(wheelCircumference: number): Promise<void>;
24
+ getWheelCircumference(): number;
21
25
  }
@@ -175,6 +175,7 @@ export interface IBleSensor extends EventEmitter {
175
175
  stopSensor(): Promise<boolean>;
176
176
  reset(): void;
177
177
  isConnected(): boolean;
178
+ isReady(): boolean;
178
179
  read(characteristicUUID: string): Promise<Buffer>;
179
180
  write(characteristicUUID: string, data: Buffer, options?: BleWriteProps): Promise<Buffer>;
180
181
  }
@@ -1,12 +1,14 @@
1
1
  import { BleFmAdapter } from '../fm/index.js';
2
2
  import { BleDeviceProperties, BleDeviceSettings, IBlePeripheral } from '../types.js';
3
- import { IAdapter } from '../../types/index.js';
3
+ import { IAdapter, IncyclistBikeData } from '../../types/index.js';
4
4
  import { LegacyProfile } from '../../antv2/types.js';
5
+ import { IndoorBikeData } from './types.js';
5
6
  export default class BleWahooAdapter extends BleFmAdapter {
6
7
  protected static INCYCLIST_PROFILE_NAME: LegacyProfile;
7
8
  constructor(settings: BleDeviceSettings, props?: BleDeviceProperties);
8
9
  isSame(device: IAdapter): boolean;
9
10
  getProfile(): LegacyProfile;
10
11
  updateSensor(peripheral: IBlePeripheral): void;
12
+ mapData(deviceData: IndoorBikeData): IncyclistBikeData;
11
13
  protected checkCapabilities(): Promise<void>;
12
14
  }
@@ -18,6 +18,9 @@ export default class BleWahooDevice extends BleFitnessMachineDevice {
18
18
  isSimMode: boolean;
19
19
  isRequestControlBusy: boolean;
20
20
  weight: number;
21
+ prevWheelRevolutions: number | undefined;
22
+ prevWheelEventTime: number | undefined;
23
+ wheelCircumference: number;
21
24
  simModeSettings: {
22
25
  weight: number;
23
26
  crr: number;
@@ -46,4 +49,6 @@ export default class BleWahooDevice extends BleFitnessMachineDevice {
46
49
  setSimWindResistance(cw: number): Promise<boolean>;
47
50
  setSimGrade(slope: number): Promise<boolean>;
48
51
  setSimWindSpeed(v: number): Promise<boolean>;
52
+ setWheelCircumference(wheelCircumference: number): Promise<void>;
53
+ getWheelCircumference(): number;
49
54
  }
@@ -0,0 +1,43 @@
1
+ import { IAdapter, IncyclistBikeData } from "../types";
2
+ import { CyclingModeBase } from "./base";
3
+ import ICyclingMode, { CyclingModeProperyType, Settings, UpdateRequest } from "./types";
4
+ export default class SpeedCyclingMode extends CyclingModeBase implements ICyclingMode {
5
+ data: IncyclistBikeData;
6
+ prevUpdateTS: number;
7
+ prevRequest: UpdateRequest | undefined;
8
+ protected static config: {
9
+ name: string;
10
+ description: string;
11
+ properties: ({
12
+ key: string;
13
+ name: string;
14
+ description: string;
15
+ type: CyclingModeProperyType;
16
+ options: string[];
17
+ default: string;
18
+ } | {
19
+ key: string;
20
+ name: string;
21
+ description: string;
22
+ type: CyclingModeProperyType;
23
+ default: number;
24
+ options?: undefined;
25
+ })[];
26
+ };
27
+ constructor(adapter: IAdapter, props?: Settings);
28
+ getData(): Partial<IncyclistBikeData>;
29
+ getSlope(): number;
30
+ getWeight(): number;
31
+ getTimeSinceLastUpdate(): number;
32
+ getBikeInitRequest(): UpdateRequest;
33
+ updateData(data: IncyclistBikeData, log?: boolean): IncyclistBikeData;
34
+ sendBikeUpdate(request: UpdateRequest): UpdateRequest;
35
+ protected calculatePowerAndDistance(speed: number, slope: number, m: number, t: number, props?: {}): {
36
+ power: number;
37
+ distance: number;
38
+ };
39
+ protected calculateSpeedAndDistance(power: number, slope: number, m: number, t: number, props?: {}): {
40
+ speed: number;
41
+ distance: number;
42
+ };
43
+ }
@@ -5,3 +5,4 @@ export * from './device.js';
5
5
  export type * from './interface.js';
6
6
  export * from './user.js';
7
7
  export type * from './sport.js';
8
+ export type * from './sensor.js';