incyclist-devices 3.0.20 → 3.0.22
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/cjs/antv2/sc/adapter.js +41 -0
- package/lib/cjs/antv2/spd/adapter.js +39 -0
- package/lib/cjs/base/consts.js +2 -1
- package/lib/cjs/ble/base/adapter.js +5 -8
- package/lib/cjs/ble/base/sensor.js +12 -1
- package/lib/cjs/ble/characteristics/csc/measurement.js +6 -2
- package/lib/cjs/ble/csc/adapter.js +29 -2
- package/lib/cjs/ble/csc/sensor.js +9 -2
- package/lib/cjs/ble/fm/adapter.js +3 -0
- package/lib/cjs/ble/utils.js +4 -0
- package/lib/cjs/ble/wahoo/adapter.js +10 -0
- package/lib/cjs/ble/wahoo/sensor.js +35 -3
- package/lib/cjs/modes/speed.js +133 -0
- package/lib/cjs/types/sensor.js +2 -0
- package/lib/esm/antv2/sc/adapter.js +41 -0
- package/lib/esm/antv2/spd/adapter.js +39 -0
- package/lib/esm/base/consts.js +1 -0
- package/lib/esm/ble/base/adapter.js +5 -8
- package/lib/esm/ble/base/sensor.js +12 -1
- package/lib/esm/ble/characteristics/csc/measurement.js +6 -2
- package/lib/esm/ble/csc/adapter.js +29 -2
- package/lib/esm/ble/csc/sensor.js +9 -2
- package/lib/esm/ble/fm/adapter.js +3 -0
- package/lib/esm/ble/utils.js +4 -0
- package/lib/esm/ble/wahoo/adapter.js +10 -0
- package/lib/esm/ble/wahoo/sensor.js +35 -3
- package/lib/esm/modes/speed.js +127 -0
- package/lib/types/antv2/sc/adapter.d.ts +6 -1
- package/lib/types/antv2/spd/adapter.d.ts +6 -1
- package/lib/types/base/consts.d.ts +1 -0
- package/lib/types/ble/base/sensor.d.ts +2 -0
- package/lib/types/ble/characteristics/csc/measurement.d.ts +1 -0
- package/lib/types/ble/csc/adapter.d.ts +5 -1
- package/lib/types/ble/csc/sensor.d.ts +6 -2
- package/lib/types/ble/types.d.ts +1 -0
- package/lib/types/ble/wahoo/adapter.d.ts +3 -1
- package/lib/types/ble/wahoo/sensor.d.ts +5 -0
- package/lib/types/modes/speed.d.ts +43 -0
- package/lib/types/types/index.d.ts +1 -0
- package/lib/types/types/sensor.d.ts +4 -0
- package/package.json +1 -1
- package/lib/cjs/serial/base/serialport.js +0 -113
- package/lib/esm/bindings/crypto/index.js +0 -14
- package/lib/esm/modes/power-meter copy.js +0 -33
- package/lib/esm/serial/base/serialport.js +0 -73
- package/lib/types/bindings/crypto/index.d.ts +0 -8
- package/lib/types/ble/bindings/crypto/types.d.ts +0 -45
- package/lib/types/modes/power-meter copy.d.ts +0 -15
- package/lib/types/serial/base/serialport.d.ts +0 -16
- /package/lib/esm/{ble/bindings/crypto/types.js → types/sensor.js} +0 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import AntAdapter from "../base/adapter.js";
|
|
2
2
|
import { IncyclistCapability } from "../../types/index.js";
|
|
3
|
+
import SpeedCyclingMode from "../../modes/speed.js";
|
|
3
4
|
export default class AntSpdAdapter extends AntAdapter {
|
|
4
5
|
static INCYCLIST_PROFILE_NAME = 'Speed Sensor';
|
|
5
6
|
static ANT_PROFILE_NAME = 'SPD';
|
|
6
7
|
static CAPABILITIES = [IncyclistCapability.Speed];
|
|
8
|
+
static controllers = {
|
|
9
|
+
modes: [SpeedCyclingMode],
|
|
10
|
+
default: SpeedCyclingMode
|
|
11
|
+
};
|
|
7
12
|
mapToAdapterData(deviceData) {
|
|
8
13
|
if (deviceData.CalculatedSpeed !== undefined) {
|
|
9
14
|
this.data.speed = deviceData.CalculatedSpeed * 3.6;
|
|
@@ -13,7 +18,41 @@ export default class AntSpdAdapter extends AntAdapter {
|
|
|
13
18
|
this.data.deviceDistanceCounter = deviceData.CalculatedDistance;
|
|
14
19
|
}
|
|
15
20
|
}
|
|
21
|
+
mapData(deviceData) {
|
|
22
|
+
const data = {
|
|
23
|
+
isPedalling: false,
|
|
24
|
+
power: 0,
|
|
25
|
+
pedalRpm: 0,
|
|
26
|
+
speed: 0
|
|
27
|
+
};
|
|
28
|
+
data.speed = (deviceData.CalculatedSpeed !== undefined ? deviceData.CalculatedSpeed * 3.6 : data.speed);
|
|
29
|
+
data.time = (deviceData.SpeedEventTime !== undefined ? deviceData.SpeedEventTime : data.time);
|
|
30
|
+
data.isPedalling = data.speed > 0;
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
transformData(bikeData) {
|
|
34
|
+
if (bikeData === undefined)
|
|
35
|
+
return {};
|
|
36
|
+
let data = {
|
|
37
|
+
speed: bikeData.speed,
|
|
38
|
+
power: bikeData.power,
|
|
39
|
+
timestamp: Date.now()
|
|
40
|
+
};
|
|
41
|
+
if (bikeData.time)
|
|
42
|
+
data.deviceTime = bikeData.time;
|
|
43
|
+
this.data = data;
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
16
46
|
hasData() {
|
|
17
47
|
return this.deviceData.CalculatedSpeed !== undefined && this.deviceData.CalculatedSpeed !== null;
|
|
18
48
|
}
|
|
49
|
+
setCyclingMode(mode, settings, sendInitCommands) {
|
|
50
|
+
super.setCyclingMode(mode, settings, sendInitCommands);
|
|
51
|
+
try {
|
|
52
|
+
const sensor = this.sensor;
|
|
53
|
+
const wc = this.getCyclingMode().getSetting('wc');
|
|
54
|
+
sensor.setWheelCircumference(wc / 1000);
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
}
|
|
19
58
|
}
|
package/lib/esm/base/consts.js
CHANGED
|
@@ -354,17 +354,14 @@ export default class BleAdapter extends IncyclistDevice {
|
|
|
354
354
|
sensor.off('data', this.onDeviceDataHandler);
|
|
355
355
|
let connected = false;
|
|
356
356
|
if (sensor.isReconnectBusy()) {
|
|
357
|
-
connected = await sensor.reconnectSensor(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return
|
|
357
|
+
connected = await sensor.reconnectSensor(true);
|
|
358
|
+
if (connected) {
|
|
359
|
+
sensor.on('data', this.onDeviceDataHandler);
|
|
360
|
+
}
|
|
361
|
+
return connected;
|
|
362
362
|
}
|
|
363
363
|
await sensor.getPeripheral().disconnect();
|
|
364
364
|
const success = await super.restart(pause);
|
|
365
|
-
if (success) {
|
|
366
|
-
sensor.on('data', this.onDeviceDataHandler);
|
|
367
|
-
}
|
|
368
365
|
return success;
|
|
369
366
|
}
|
|
370
367
|
async stop() {
|
|
@@ -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
|
-
|
|
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 =
|
|
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
|
|
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;
|
package/lib/esm/ble/utils.js
CHANGED
|
@@ -41,6 +41,8 @@ export function getCharachteristicsInfo(c) {
|
|
|
41
41
|
return `${serviceStr}${uuid}${nameStr} ${properties}`;
|
|
42
42
|
}
|
|
43
43
|
export const parseUUID = (str) => {
|
|
44
|
+
if (!str)
|
|
45
|
+
return str;
|
|
44
46
|
const uuid = str.toUpperCase();
|
|
45
47
|
if (str.startsWith('0x')) {
|
|
46
48
|
const hex = uuid.slice(2);
|
|
@@ -95,6 +97,8 @@ export const beautifyUUID = (str, withX = false) => {
|
|
|
95
97
|
return parts.join('-');
|
|
96
98
|
};
|
|
97
99
|
export const fullUUID = (str) => {
|
|
100
|
+
if (!str)
|
|
101
|
+
return str;
|
|
98
102
|
const uuid = parseUUID(str);
|
|
99
103
|
const parts = [
|
|
100
104
|
uuid.substring(0, 8),
|
|
@@ -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.js";
|
|
2
|
+
import { CyclingModeProperyType } from "./types.js";
|
|
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
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { SpeedCadenceSensorState, Profile } from "incyclist-ant-plus";
|
|
2
2
|
import AntAdapter from "../base/adapter.js";
|
|
3
3
|
import { LegacyProfile } from "../types.js";
|
|
4
|
-
import { IncyclistCapability } from "../../types/index.js";
|
|
4
|
+
import { ControllerConfig, IncyclistAdapterData, IncyclistBikeData, IncyclistCapability } from "../../types/index.js";
|
|
5
|
+
import ICyclingMode from "../../modes/types.js";
|
|
5
6
|
export default class AntSpdAdapter extends AntAdapter<SpeedCadenceSensorState> {
|
|
6
7
|
protected static INCYCLIST_PROFILE_NAME: LegacyProfile;
|
|
7
8
|
protected static ANT_PROFILE_NAME: Profile;
|
|
8
9
|
protected static CAPABILITIES: IncyclistCapability[];
|
|
10
|
+
protected static controllers: ControllerConfig;
|
|
9
11
|
mapToAdapterData(deviceData: SpeedCadenceSensorState): void;
|
|
12
|
+
mapData(deviceData: SpeedCadenceSensorState): IncyclistBikeData;
|
|
13
|
+
transformData(bikeData: IncyclistBikeData): IncyclistAdapterData;
|
|
10
14
|
hasData(): boolean;
|
|
15
|
+
setCyclingMode(mode: string | ICyclingMode, settings?: any, sendInitCommands?: boolean): void;
|
|
11
16
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { SpeedSensorState, Profile } from "incyclist-ant-plus";
|
|
2
2
|
import AntAdapter from "../base/adapter.js";
|
|
3
3
|
import { LegacyProfile } from "../types.js";
|
|
4
|
-
import { IncyclistCapability } from "../../types/index.js";
|
|
4
|
+
import { ControllerConfig, IncyclistAdapterData, IncyclistBikeData, IncyclistCapability } from "../../types/index.js";
|
|
5
|
+
import ICyclingMode from "../../modes/types.js";
|
|
5
6
|
export default class AntSpdAdapter extends AntAdapter<SpeedSensorState> {
|
|
6
7
|
protected static INCYCLIST_PROFILE_NAME: LegacyProfile;
|
|
7
8
|
protected static ANT_PROFILE_NAME: Profile;
|
|
8
9
|
protected static CAPABILITIES: IncyclistCapability[];
|
|
10
|
+
protected static controllers: ControllerConfig;
|
|
9
11
|
mapToAdapterData(deviceData: SpeedSensorState): void;
|
|
12
|
+
mapData(deviceData: SpeedSensorState): IncyclistBikeData;
|
|
13
|
+
transformData(bikeData: IncyclistBikeData): IncyclistAdapterData;
|
|
10
14
|
hasData(): boolean;
|
|
15
|
+
setCyclingMode(mode: string | ICyclingMode, settings?: any, sendInitCommands?: boolean): void;
|
|
11
16
|
}
|
|
@@ -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
|
-
|
|
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
|
}
|
package/lib/types/ble/types.d.ts
CHANGED
|
@@ -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
|
}
|