incyclist-devices 1.4.51 → 1.4.54

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.
@@ -1,5 +1,41 @@
1
+ /// <reference types="node" />
1
2
  import AntAdapter from '../AntAdapter';
2
3
  import { Queue } from '../../utils';
4
+ import CyclingMode, { IncyclistBikeData } from '../../CyclingMode';
5
+ export declare type AntFEDeviceData = {
6
+ DeviceID: number;
7
+ Temperature?: number;
8
+ ZeroOffset?: number;
9
+ SpinDownTime?: number;
10
+ EquipmentType?: 'Treadmill' | 'Elliptical' | 'StationaryBike' | 'Rower' | 'Climber' | 'NordicSkier' | 'Trainer' | 'General';
11
+ ElapsedTime?: number;
12
+ Distance?: number;
13
+ RealSpeed?: number;
14
+ VirtualSpeed?: number;
15
+ HeartRate?: number;
16
+ HeartRateSource?: 'HandContact' | 'EM' | 'ANT+';
17
+ State?: 'OFF' | 'READY' | 'IN_USE' | 'FINISHED';
18
+ CycleLength?: number;
19
+ Incline?: number;
20
+ Resistance?: number;
21
+ METs?: number;
22
+ CaloricBurnRate?: number;
23
+ Calories?: number;
24
+ _EventCount0x19?: number;
25
+ Cadence?: number;
26
+ AccumulatedPower?: number;
27
+ InstantaneousPower?: number;
28
+ AveragePower?: number;
29
+ TrainerStatus?: number;
30
+ TargetStatus?: 'OnTarget' | 'LowSpeed' | 'HighSpeed';
31
+ HwVersion?: number;
32
+ ManId?: number;
33
+ ModelNum?: number;
34
+ SwVersion?: number;
35
+ SerialNumber?: number;
36
+ PairedDevices?: any[];
37
+ RawData: Buffer;
38
+ };
3
39
  export default class AntFEAdapter extends AntAdapter {
4
40
  started: boolean;
5
41
  starting: boolean;
@@ -8,6 +44,7 @@ export default class AntFEAdapter extends AntAdapter {
8
44
  queue?: Queue<any>;
9
45
  workerId?: any;
10
46
  currentCmd?: any;
47
+ cyclingMode: CyclingMode;
11
48
  constructor(DeviceID: any, port: any, stick: any, protocol: any);
12
49
  isBike(): boolean;
13
50
  isHrm(): boolean;
@@ -15,11 +52,15 @@ export default class AntFEAdapter extends AntAdapter {
15
52
  getProfile(): string;
16
53
  getName(): string;
17
54
  getDisplayName(): string;
55
+ getSupportedCyclingModes(): Array<any>;
56
+ setCyclingMode(mode: string | CyclingMode, settings?: any): void;
57
+ getCyclingMode(): CyclingMode;
58
+ getDefaultCyclingMode(): CyclingMode;
18
59
  onAttached(): void;
19
60
  getLogData(data: any, excludeList: any): any;
20
- onDeviceData(deviceData: any): void;
61
+ onDeviceData(deviceData: AntFEDeviceData): void;
62
+ mapData(deviceData: AntFEDeviceData): IncyclistBikeData;
21
63
  onDeviceEvent(data: any): void;
22
- updateData(data: any, deviceData: any): any;
23
64
  transformData(bikeData: any): any;
24
65
  start(props?: any): Promise<any>;
25
66
  stop(): Promise<boolean>;
@@ -16,6 +16,8 @@ const gd_eventlog_1 = require("gd-eventlog");
16
16
  const AntAdapter_1 = __importDefault(require("../AntAdapter"));
17
17
  const utils_1 = require("../utils");
18
18
  const utils_2 = require("../../utils");
19
+ const ant_fe_st_mode_1 = __importDefault(require("./ant-fe-st-mode"));
20
+ const ant_fe_erg_mode_1 = __importDefault(require("./ant-fe-erg-mode"));
19
21
  const floatVal = (d) => d ? parseFloat(d) : d;
20
22
  const intVal = (d) => d ? parseInt(d) : d;
21
23
  const hex = (v) => Math.abs(v).toString(16).toUpperCase();
@@ -54,6 +56,34 @@ class AntFEAdapter extends AntAdapter_1.default {
54
56
  const hrmStr = ComputedHeartRate ? ` (${ComputedHeartRate})` : '';
55
57
  return `${(0, utils_1.getBrand)(ManId)} FE ${DeviceID}${hrmStr}`;
56
58
  }
59
+ getSupportedCyclingModes() {
60
+ return [ant_fe_st_mode_1.default, ant_fe_erg_mode_1.default];
61
+ }
62
+ setCyclingMode(mode, settings) {
63
+ let selectedMode;
64
+ if (typeof mode === 'string') {
65
+ const supported = this.getSupportedCyclingModes();
66
+ const CyclingModeClass = supported.find(M => { const m = new M(this); return m.getName() === mode; });
67
+ if (CyclingModeClass) {
68
+ this.cyclingMode = new CyclingModeClass(this, settings);
69
+ return;
70
+ }
71
+ selectedMode = this.getDefaultCyclingMode();
72
+ }
73
+ else {
74
+ selectedMode = mode;
75
+ }
76
+ this.cyclingMode = selectedMode;
77
+ this.cyclingMode.setSettings(settings);
78
+ }
79
+ getCyclingMode() {
80
+ if (!this.cyclingMode)
81
+ this.cyclingMode = this.getDefaultCyclingMode();
82
+ return this.cyclingMode;
83
+ }
84
+ getDefaultCyclingMode() {
85
+ return new ant_fe_st_mode_1.default(this);
86
+ }
57
87
  onAttached() {
58
88
  this.logger.logEvent({ message: 'Device connected' });
59
89
  this.connected = true;
@@ -74,8 +104,9 @@ class AntFEAdapter extends AntAdapter_1.default {
74
104
  if (!this.lastUpdate || (Date.now() - this.lastUpdate) > this.updateFrequency) {
75
105
  const logData = this.getLogData(deviceData, ['PairedDevices', 'RawData']);
76
106
  this.logger.logEvent({ message: 'onDeviceData', data: logData });
77
- this.data = this.updateData(this.data, deviceData);
78
- const data = this.transformData(this.data);
107
+ let incyclistData = this.mapData(deviceData);
108
+ incyclistData = this.getCyclingMode().updateData(incyclistData);
109
+ const data = this.transformData(incyclistData);
79
110
  this.onDataFn(data);
80
111
  this.lastUpdate = Date.now();
81
112
  }
@@ -84,6 +115,25 @@ class AntFEAdapter extends AntAdapter_1.default {
84
115
  catch (err) {
85
116
  }
86
117
  }
118
+ mapData(deviceData) {
119
+ const data = {
120
+ isPedalling: false,
121
+ power: 0,
122
+ pedalRpm: undefined,
123
+ speed: 0,
124
+ heartrate: 0,
125
+ distanceInternal: 0,
126
+ slope: undefined,
127
+ time: undefined
128
+ };
129
+ data.speed = (deviceData.VirtualSpeed !== undefined ? deviceData.VirtualSpeed : (deviceData.RealSpeed || 0)) * 3.6;
130
+ data.slope = (deviceData.Incline !== undefined ? deviceData.Incline : data.slope);
131
+ data.power = (deviceData.InstantaneousPower !== undefined ? deviceData.InstantaneousPower : data.power);
132
+ data.time = (deviceData.ElapsedTime !== undefined ? deviceData.ElapsedTime : data.time);
133
+ data.pedalRpm = (deviceData.Cadence !== undefined ? deviceData.Cadence : data.pedalRpm);
134
+ data.isPedalling = data.pedalRpm > 0 || (data.pedalRpm === undefined && data.power > 0);
135
+ return data;
136
+ }
87
137
  onDeviceEvent(data) {
88
138
  try {
89
139
  const cmdInfo = this.currentCmd;
@@ -127,43 +177,21 @@ class AntFEAdapter extends AntAdapter_1.default {
127
177
  this.logger.logEvent({ message: 'Error', fn: 'parseEvent', event: { message: hex(data.message), code: hex(data.code) }, error: err.message || err });
128
178
  }
129
179
  }
130
- updateData(data, deviceData) {
131
- if (data.distanceOffs === undefined)
132
- data.distanceOffs = 0;
133
- data.speed = (deviceData.VirtualSpeed !== undefined ? deviceData.VirtualSpeed : deviceData.RealSpeed) * 3.6;
134
- data.slope = (deviceData.Incline !== undefined ? deviceData.Incline : data.slope);
135
- data.power = (deviceData.InstantaneousPower !== undefined ? deviceData.InstantaneousPower : data.power);
136
- data.pedalRpm = (deviceData.Cadence !== undefined ? deviceData.Cadence : data.pedalRpm);
137
- data.heartrate = (deviceData.HeartRate !== undefined ? deviceData.HeartRate : data.heartrate);
138
- if (deviceData.Distance !== undefined && deviceData.Distance !== 0) {
139
- data.distanceInternal = deviceData.Distance - data.distanceOffs;
140
- data.distance = data.distanceInternal / 1000;
141
- }
142
- else {
143
- if (this.lastUpdate && deviceData.Cadence !== undefined && deviceData.Cadence > 0 && data.speed) {
144
- const t = (Date.now() - this.lastUpdate) / 1000;
145
- const prevDistance = data.distanceInternal || 0;
146
- data.distanceInternal = Math.round(data.speed / 3.6 * t) + prevDistance;
147
- data.distance = data.distanceInternal / 1000;
148
- }
149
- }
150
- return data;
151
- }
152
180
  transformData(bikeData) {
153
181
  if (bikeData === undefined)
154
182
  return;
155
183
  let distance = 0;
156
184
  if (this.distanceInternal !== undefined && bikeData.distanceInternal !== undefined) {
157
- distance = intVal(bikeData.distanceInternal - this.distanceInternal);
185
+ distance = Math.round(bikeData.distanceInternal - this.distanceInternal);
158
186
  }
159
187
  if (bikeData.distanceInternal !== undefined)
160
188
  this.distanceInternal = bikeData.distanceInternal;
161
189
  let data = {
162
- speed: floatVal(bikeData.speed),
163
- slope: floatVal(bikeData.slope),
164
- power: intVal(bikeData.power),
165
- cadence: intVal(bikeData.pedalRpm),
166
- heartrate: intVal(bikeData.heartrate),
190
+ speed: bikeData.speed,
191
+ slope: bikeData.slope,
192
+ power: bikeData.power !== undefined ? Math.round(bikeData.power) : undefined,
193
+ cadence: bikeData.pedalRpm !== undefined ? Math.round(bikeData.pedalRpm) : undefined,
194
+ heartrate: bikeData.heartrate !== undefined ? Math.round(bikeData.heartrate) : undefined,
167
195
  distance,
168
196
  timestamp: Date.now()
169
197
  };
@@ -295,14 +323,17 @@ class AntFEAdapter extends AntAdapter_1.default {
295
323
  }
296
324
  sendUpdate(request) {
297
325
  return __awaiter(this, void 0, void 0, function* () {
298
- this.logger.logEvent({ message: "sendBikeUpdate():", request });
326
+ if (this.paused)
327
+ return;
328
+ const update = this.getCyclingMode().sendBikeUpdate(request);
329
+ this.logger.logEvent({ message: 'send bike update requested', update, request });
299
330
  try {
300
331
  const isReset = (!request || request.reset || Object.keys(request).length === 0);
301
- if (request.slope !== undefined) {
302
- yield (0, utils_2.runWithRetries)(() => __awaiter(this, void 0, void 0, function* () { return yield this.sendTrackResistance(request.slope); }), 2, 100);
332
+ if (update.slope !== undefined) {
333
+ yield (0, utils_2.runWithRetries)(() => __awaiter(this, void 0, void 0, function* () { return yield this.sendTrackResistance(update.slope); }), 2, 100);
303
334
  }
304
- if (request.targetPower !== undefined) {
305
- yield (0, utils_2.runWithRetries)(() => __awaiter(this, void 0, void 0, function* () { return yield this.sendTargetPower(request.targetPower); }), 2, 100);
335
+ if (update.targetPower !== undefined) {
336
+ yield (0, utils_2.runWithRetries)(() => __awaiter(this, void 0, void 0, function* () { return yield this.sendTargetPower(update.targetPower); }), 2, 100);
306
337
  }
307
338
  else if (request.maxPower !== undefined) {
308
339
  if (this.data.power && this.data.power > request.maxPower)
@@ -0,0 +1,5 @@
1
+ import BleERGCyclingMode from "../../ble/ble-erg-mode";
2
+ import { DeviceAdapter } from "../../Device";
3
+ export default class AntFeERGCyclingMode extends BleERGCyclingMode {
4
+ constructor(adapter: DeviceAdapter, props?: any);
5
+ }
@@ -0,0 +1,13 @@
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 ble_erg_mode_1 = __importDefault(require("../../ble/ble-erg-mode"));
7
+ class AntFeERGCyclingMode extends ble_erg_mode_1.default {
8
+ constructor(adapter, props) {
9
+ super(adapter, props);
10
+ this.initLogger('AntERGMode');
11
+ }
12
+ }
13
+ exports.default = AntFeERGCyclingMode;
@@ -0,0 +1,7 @@
1
+ import FtmsCyclingMode from "../../ble/ble-st-mode";
2
+ import { UpdateRequest } from "../../CyclingMode";
3
+ import { DeviceAdapter } from "../../Device";
4
+ export default class AntStCyclingMode extends FtmsCyclingMode {
5
+ constructor(adapter: DeviceAdapter, props?: any);
6
+ sendBikeUpdate(request: UpdateRequest): UpdateRequest;
7
+ }
@@ -0,0 +1,52 @@
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 ble_st_mode_1 = __importDefault(require("../../ble/ble-st-mode"));
7
+ class AntStCyclingMode extends ble_st_mode_1.default {
8
+ constructor(adapter, props) {
9
+ super(adapter, props);
10
+ this.initLogger('AntSTMode');
11
+ }
12
+ sendBikeUpdate(request) {
13
+ const getData = () => {
14
+ if (!this.data)
15
+ return {};
16
+ const { gear, pedalRpm, slope, power, speed } = this.data;
17
+ return { gear, pedalRpm, slope, power, speed };
18
+ };
19
+ const event = {};
20
+ if (this.data === undefined)
21
+ event.noData = true;
22
+ if (request.slope !== undefined && (event.noData || Math.abs(request.slope - this.data.slope) >= 0.1))
23
+ event.slopeUpdate = true;
24
+ if (this.prevRequest === undefined)
25
+ event.initialCall = true;
26
+ this.logger.logEvent({ message: "processing update request", request, prev: this.prevRequest, data: getData(), event });
27
+ let newRequest = {};
28
+ if (request.slope === undefined && request.targetPower === undefined && request.refresh && this.prevRequest) {
29
+ return this.prevRequest;
30
+ }
31
+ if (request.slope !== undefined) {
32
+ newRequest.slope = parseFloat(request.slope.toFixed(1));
33
+ this.data.slope = newRequest.slope;
34
+ }
35
+ if (request.targetPower !== undefined) {
36
+ newRequest.targetPower = request.targetPower;
37
+ }
38
+ if (request.minPower && request.maxPower && request.minPower === request.maxPower) {
39
+ newRequest.targetPower = request.minPower;
40
+ }
41
+ const prevData = this.data;
42
+ if (newRequest.targetPower === undefined && prevData && prevData.power) {
43
+ if (request.minPower !== undefined && prevData.power < request.minPower)
44
+ newRequest.targetPower = request.minPower;
45
+ if (request.maxPower !== undefined && prevData.power > request.maxPower)
46
+ newRequest.targetPower = request.maxPower;
47
+ }
48
+ this.prevRequest = JSON.parse(JSON.stringify(newRequest));
49
+ return newRequest;
50
+ }
51
+ }
52
+ exports.default = AntStCyclingMode;
@@ -1,12 +1,12 @@
1
1
  import CyclingMode, { CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
2
+ import { DeviceAdapter } from "../Device";
2
3
  import PowerBasedCyclingModeBase from "../modes/power-base";
3
- import { FmAdapter } from "./fm";
4
4
  export default class BleERGCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
5
5
  prevRequest: UpdateRequest;
6
6
  hasBikeUpdate: boolean;
7
7
  chain: number[];
8
8
  cassette: number[];
9
- constructor(adapter: FmAdapter, props?: any);
9
+ constructor(adapter: DeviceAdapter, props?: any);
10
10
  getName(): string;
11
11
  getDescription(): string;
12
12
  getProperties(): CyclingModeProperty[];
@@ -346,6 +346,7 @@ class BleInterface extends ble_1.BleInterfaceClass {
346
346
  return DeviceClasses;
347
347
  }
348
348
  createDevice(DeviceClass, peripheral, characteristics) {
349
+ this.logEvent({ message: 'trying to create device', peripheral: peripheral.address, characteristics });
349
350
  const C = DeviceClass;
350
351
  const device = new C({ peripheral });
351
352
  const existingDevice = this.devices.find(i => i.device.id === device.id && i.device.getProfile() === device.getProfile());
@@ -1,10 +1,10 @@
1
1
  import CyclingMode, { CyclingModeProperty, IncyclistBikeData, UpdateRequest } from "../CyclingMode";
2
+ import { DeviceAdapter } from "../Device";
2
3
  import PowerBasedCyclingModeBase from "../modes/power-base";
3
- import { FmAdapter } from "./fm";
4
4
  export default class FtmsCyclingMode extends PowerBasedCyclingModeBase implements CyclingMode {
5
5
  prevRequest: UpdateRequest;
6
6
  hasBikeUpdate: boolean;
7
- constructor(adapter: FmAdapter, props?: any);
7
+ constructor(adapter: DeviceAdapter, props?: any);
8
8
  getName(): string;
9
9
  getDescription(): string;
10
10
  getProperties(): CyclingModeProperty[];
package/lib/ble/fm.js CHANGED
@@ -332,6 +332,8 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
332
332
  this.logEvent({ message: 'setTargetPower', power, skip: (this.data.targetPower !== undefined && this.data.targetPower === power) });
333
333
  if (this.data.targetPower !== undefined && this.data.targetPower === power)
334
334
  return true;
335
+ if (!this.hasControl)
336
+ return;
335
337
  const hasControl = yield this.requestControl();
336
338
  const data = Buffer.alloc(3);
337
339
  data.writeUInt8(5, 0);
@@ -343,6 +345,8 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
343
345
  setSlope(slope) {
344
346
  return __awaiter(this, void 0, void 0, function* () {
345
347
  this.logEvent({ message: 'setSlope', slope });
348
+ if (!this.hasControl)
349
+ return;
346
350
  const { windSpeed, crr, cw } = this;
347
351
  return yield this.setIndoorBikeSimulation(windSpeed, slope, crr, cw);
348
352
  });
@@ -351,6 +355,8 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
351
355
  return __awaiter(this, void 0, void 0, function* () {
352
356
  if (this.data.targetInclination !== undefined && this.data.targetInclination === inclination)
353
357
  return true;
358
+ if (!this.hasControl)
359
+ return;
354
360
  const hasControl = yield this.requestControl();
355
361
  if (!hasControl) {
356
362
  this.logEvent({ message: 'setTargetInclination failed', reason: 'control is disabled' });
@@ -365,6 +371,8 @@ class BleFitnessMachineDevice extends ble_device_1.BleDevice {
365
371
  }
366
372
  setIndoorBikeSimulation(windSpeed, gradient, crr, cw) {
367
373
  return __awaiter(this, void 0, void 0, function* () {
374
+ if (!this.hasControl)
375
+ return;
368
376
  const hasControl = yield this.requestControl();
369
377
  const data = Buffer.alloc(7);
370
378
  data.writeUInt8(17, 0);
@@ -569,6 +577,7 @@ class FmAdapter extends Device_1.default {
569
577
  break;
570
578
  }
571
579
  }
580
+ this.device.requestControl();
572
581
  const startRequest = this.getCyclingMode().getBikeInitRequest();
573
582
  yield this.sendUpdate(startRequest);
574
583
  bleDevice.on('data', (data) => {
@@ -77,6 +77,7 @@ class BleProtocol extends DeviceProtocol_1.default {
77
77
  return new hrm_1.HrmAdapter(fromDevice ? bleDevice : new hrm_1.default(props()), this);
78
78
  case 'fm':
79
79
  case 'smart trainer':
80
+ case 'wahoo smart trainer':
80
81
  case 'fitness machine':
81
82
  let device;
82
83
  if (fromDevice)
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
- {
2
- "name": "incyclist-devices",
3
- "version": "1.4.51",
4
- "dependencies": {
5
- "@serialport/parser-byte-length": "^9.0.1",
6
- "@serialport/parser-delimiter": "^9.0.1",
7
- "@types/serialport": "^8.0.1",
8
- "gd-ant-plus": "^0.0.33",
9
- "win32filetime": "^1.0.2"
10
- },
11
- "peerDependencies": {
12
- "gd-eventlog": "^0.1.22"
13
- },
14
- "devDependencies": {
15
- "@types/jest": "^27.0.3",
16
- "@types/node": "^10.17.58",
17
- "eslint": "^7.32.0",
18
- "eslint-config-react-app": "^6.0.0",
19
- "eslint-loader": "^4.0.2",
20
- "jest": "^27.4.3",
21
- "ts-jest": "^27.1.0",
22
- "typescript": "^4.5.2"
23
- },
24
- "scripts": {
25
- "lint": "eslint . --ext .ts",
26
- "build": "tsc",
27
- "test": "jest --coverage",
28
- "dev": "nodemon --watch src -e ts,js --exec npm run build"
29
- },
30
- "files": [
31
- "lib/"
32
- ],
33
- "main": "lib/DeviceSupport.js",
34
- "directories": {
35
- "example": "./sample",
36
- "lib": "./src"
37
- },
38
- "eslintConfig": {
39
- "extends": "react-app",
40
- "rules": {
41
- "jsx-a11y/anchor-is-valid": [
42
- "off"
43
- ]
44
- }
45
- }
46
- }
1
+ {
2
+ "name": "incyclist-devices",
3
+ "version": "1.4.54",
4
+ "dependencies": {
5
+ "@serialport/parser-byte-length": "^9.0.1",
6
+ "@serialport/parser-delimiter": "^9.0.1",
7
+ "@types/serialport": "^8.0.1",
8
+ "gd-ant-plus": "^0.0.33",
9
+ "win32filetime": "^1.0.2"
10
+ },
11
+ "peerDependencies": {
12
+ "gd-eventlog": "^0.1.22"
13
+ },
14
+ "devDependencies": {
15
+ "@types/jest": "^27.0.3",
16
+ "@types/node": "^10.17.58",
17
+ "eslint": "^7.32.0",
18
+ "eslint-config-react-app": "^6.0.0",
19
+ "eslint-loader": "^4.0.2",
20
+ "jest": "^27.4.3",
21
+ "ts-jest": "^27.1.0",
22
+ "typescript": "^4.5.2"
23
+ },
24
+ "scripts": {
25
+ "lint": "eslint . --ext .ts",
26
+ "build": "tsc",
27
+ "test": "jest --coverage",
28
+ "dev": "nodemon --watch src -e ts,js --exec npm run build"
29
+ },
30
+ "files": [
31
+ "lib/"
32
+ ],
33
+ "main": "lib/DeviceSupport.js",
34
+ "directories": {
35
+ "example": "./sample",
36
+ "lib": "./src"
37
+ },
38
+ "eslintConfig": {
39
+ "extends": "react-app",
40
+ "rules": {
41
+ "jsx-a11y/anchor-is-valid": [
42
+ "off"
43
+ ]
44
+ }
45
+ }
46
+ }